fhEVM Coprocessor 链上升级实操:借助 Aragon DAO 完成proposeCoprocessorUpgrade提案的全流程指南
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
本篇指南完整讲解 fhEVM 仓库中 coprocessor(协处理器)需要链上升级提案时的标准操作流程:如何通过 GitHub Actions 工作流或 Hardhat 任务,为 Aragon DAO 生成调用ProtocolConfig.proposeCoprocessorUpgrade(...)的 calldata,并在 DAO 投票通过后打开覆盖所有 host 链与 gateway 的升级窗口。读完本文,你将掌握从环境参数确认、跨链区块窗口计算、calldata 获取到 DAO 提案提交与本地演练的完整实战能力,并能理解底层源码的窗口投影与 buffer 校验逻辑。
原始操作手册位于 host-contracts/COPROCESSOR_UPGRADE_RUNBOOK.md,本文以其为骨架,结合 host-contracts/tasks 下的实现源码与测试进行纵深展开。
一、升级提案的背景与产出
fhEVM 的 coprocessor 是链下负责同态加密计算(TFHE 运算、解密等)的分布式服务。当 coprocessor 需要发布新版本(例如v0.14.0)时,出于安全与可审计性要求,升级必须经过链上治理:由 Aragon DAO 对ProtocolConfig.proposeCoprocessorUpgrade(...)进行投票,投票通过后升级窗口才会在所有 host 链与 gateway 上打开,GCS(Gateway Coprocessor Service)等相关组件才能在该窗口内完成蓝绿切换与回放(dry-run)。
本流程产出物是一个十六进制字符串(calldata),它对应 Aragon DAO 提案中调用的ProtocolConfig.proposeCoprocessorUpgrade(...)动作。DAO 投票通过并执行提案后,链上会发出CoprocessorUpgradeProposed事件,升级窗口随即开启。
从合约源码看,host-contracts/contracts/ProtocolConfig.sol 中的proposeCoprocessorUpgrade方法签名如下:
function proposeCoprocessorUpgrade( uint256 proposalId, string calldata softwareVersion, ChainUpgradeWindow[] calldata chainUpgradeWindows, uint64 gwStartBlock ) external virtual onlyACLOwner其中每个链的升级窗口使用 host-contracts/contracts/shared/Structs.sol 中定义的ChainUpgradeWindow结构:
struct ChainUpgradeWindow { uint64 chainId; // 该窗口适用的 host 链 ID uint64 startBlock; // GCS 在 dry-run 中回放的首个区块(含) uint64 endBlock; // GCS 在 dry-run 中回放的最后一个区块(含) }该结构体注释明确说明其用途是"coprocessor blue-green upgrade"期间每个 host 链的回放窗口,这解释了为什么提案需要为每一条链精确计算起止区块号。
二、前置条件
在运行任何工具之前,请确认以下条件已满足:
- 新版本已构建完成:新的 coprocessor 版本已经构建好,并已知其发布标签(release tag),例如
v0.14.0。 - dry-run 评估窗口的起始墙钟时间已确定:评估窗口的 wall-clock 开始时间已经最终确认,并以 ISO 8601 UTC 格式记录。
- 起始时间足够靠后:开始时间必须给 DAO 投票留出足够的前置时间,这个前置时间即
--buffer参数,主网通常取2h。如果起始区块距离当前链头太近,任务会因 buffer 校验失败而拒绝输出(见下文"buffer 门禁")。
三、Step 1 — 运行 GitHub Actions 工作流
仓库提供了名为host-contracts-prepare-coprocessor-upgrade的手动触发工作流(.github/workflows/host-contracts-prepare-coprocessor-upgrade.yml),它针对所选环境的真实 RPC 执行task:prepareCoprocessorUpgrade任务,并把计算报告与 ABI 编码后的 calldata 输出到工作流日志。
在仓库页面进入Actions → host-contracts-prepare-coprocessor-upgrade → Run workflow,填写以下输入:
| 输入项 | 取值 | 说明 |
|---|---|---|
| Environment | devnet、testnet或mainnet | 目标环境,下拉选择 |
| Start time | ISO 8601 UTC,例如2026-07-01T12:00:00Z | 评估窗口的墙钟开始时间 |
| Duration | 窗口长度,例如30m | 支持30s、30m、2h、1d或裸整数秒 |
| Buffer | DAO 前置时间,例如2h | 从"现在"到startBlock之间必须保留的秒数 |
| Proposal id | 任意正整数(运营方自选) | 合约会拒绝0 |
| Software version | coprocessor 发布标签,例如v0.14.0 | 随提案上链的版本字符串 |
点击Run workflow并等待任务完成。
工作流的内部实现值得注意两点(来源:.github/workflows/host-contracts-prepare-coprocessor-upgrade.yml):
- 所有 RPC 密钥通过
env:注入,而不是在run:中插值,以防止workflow_dispatch输入造成 shell 注入; - 工作流把所有环境的 RPC secrets 无条件注入(
RPC_URL_ETHEREUM、RPC_URL_POLYGON、RPC_URL_SEPOLIA、RPC_URL_AMOY、RPC_URL_GATEWAY_DEVNET、RPC_URL_GATEWAY_TESTNET、RPC_URL_GATEWAY_MAINNET),脚本只读取所选环境需要的那些,因此某个环境缺失 secret 会以env var RPC_URL_X is not set报错(见下文"失败模式")。
四、Step 2 — 复制 calldata
工作流完成后,打开"Prepare upgrade proposal"步骤的日志,滚动到末尾。在## Calldata标题下的最后一块内容即是以0xccbf8199…开头的十六进制字符串。
0xccbf8199是proposeCoprocessorUpgrade函数的函数选择器(前 4 字节),其 ABI 定义内联在 host-contracts/tasks/utils/coprocessorUpgradeProposal.ts 中:
function proposeCoprocessorUpgrade(uint256 proposalId, string softwareVersion, tuple(uint64 chainId, uint64 startBlock, uint64 endBlock)[] chainUpgradeWindows, uint64 gwStartBlock)请复制整个十六进制字符串(不要只复制选择器),它将在 Step 3 中作为 DAO 提案的 calldata 使用。
五、Step 3 — 提交 DAO 提案
打开目标环境对应的 Aragon DAO:
- Mainnet— Ethereum 上的 Aragon DAO:
0xB6D69D5F334d8B97B194617B53c6aB62f8681Ef3 - Testnet— Sepolia 上的 Aragon DAO:
0x08e8a84c3c8c7cba165B1adcf67Ae4639eF84f52
在 DAO 中创建新提案,填写:
- Target contract:host 链上的
ProtocolConfig合约(mainnet 为 Ethereum 上的部署,testnet 为 Sepolia 上的部署); - Calldata:Step 2 中复制的十六进制字符串。
DAO 投票通过且提案执行后,链上proposeCoprocessorUpgrade事件触发,升级窗口在所有 host 链与 gateway 上打开。注意该方法是onlyACLOwner限定的(host-contracts/contracts/ProtocolConfig.sol),即只有 ACL 所有者(即 DAO 地址)能够成功调用,这正是必须经由 DAO 提案的原因。
六、升级窗口的生成原理:从墙钟时间到区块号
工作流和 Hardhat 任务的核心逻辑都在 host-contracts/tasks/utils/blockWindow.ts 中,理解它能帮助你正确设置--start-time与--duration。
6.1 区块时间采样
sampleBlockTime读取链头区块号与时间戳,再向前回溯采样BLOCK_TIME_SAMPLE_SIZE = 1000个区块(host-contracts/tasks/utils/blockWindow.ts),用"采样起止时间差 ÷ 区块数"估算平均出块时间。如果链太年轻(不足 1000 个区块)或采样失败,则回退到该环境配置的fallbackBlockTimeSeconds(并在报告中标记usedFallback/WARN: block-time sampling failed)。
6.2 区块号投影
projectBlockNumber以链头为基准,把目标墙钟时刻投影为区块号:
deltaSeconds = targetTimestamp - tipTimestamp projectedBlock = tipBlock + round(deltaSeconds / blockTimeSeconds)由于按"最近的区块"取整,投影产生的 skew 被限定在半块出块时间以内——报告中会以startSkewSeconds明确展示这一误差(host-contracts/tasks/utils/blockWindow.ts)。
6.3 buffer 门禁(DAO 投票缓冲)
bufferSatisfied校验startBlock与链头之间按有效出块时间换算出的墙钟间隔是否 ≥--buffer(host-contracts/tasks/utils/blockWindow.ts)。若任一 host 链或 gateway 不满足,任务会硬性失败(DAO 路径打印 calldata 供检查但明确提示"不得提交";直连路径直接拒绝广播)。
报告中还会输出跨链对齐信息:所有 host 链与 gateway 的startBlock估计时间戳的最大跨度(start skew range)以及各自相对最早起点的延迟,便于运营方判断窗口是否足够对齐。
6.4 出块时间漂移告警
如果采样得到的真实出块时间与配置的 fallback 相差超过 20%(BLOCK_TIME_DRIFT_WARN_FRACTION = 0.2),报告会给出WARN: observed block time drifted >20% from fallback提示,警示当前网络可能处于拥堵状态,窗口估算应谨慎对待(host-contracts/tasks/utils/blockWindow.ts)。
七、环境与链集合参考
每个环境的链 ID、出块时间与 RPC 环境变量名统一登记在 host-contracts/tasks/utils/environments.ts 的ENVIRONMENTS注册表中:
| 环境 | 链(chainId) | fallback 出块时间 | RPC 环境变量 | 公共默认 RPC |
|---|---|---|---|---|
devnet | sepolia11155111 | 12s | RPC_URL_SEPOLIA | https://ethereum-sepolia-rpc.publicnode.com |
devnet | amoy80002 | 1.5s | RPC_URL_AMOY | https://rpc-amoy.polygon.technology |
devnet | gateway-devnet | 2s | RPC_URL_GATEWAY_DEVNET | 无(内部端点,必须设置) |
testnet | sepolia11155111 | 12s | RPC_URL_SEPOLIA | 同上公共端点 |
testnet | amoy80002 | 1.5s | RPC_URL_AMOY | 同上公共端点 |
testnet | gateway-testnet | 2s | RPC_URL_GATEWAY_TESTNET | https://rpc.testnet.zama.org |
mainnet | ethereum1 | 12s | RPC_URL_ETHEREUM | 无(生产 RPC 私有,必须设置) |
mainnet | polygon137 | 2s | RPC_URL_POLYGON | 无(生产 RPC 私有,必须设置) |
mainnet | gateway-mainnet | 2s | RPC_URL_GATEWAY_MAINNET | 无(生产 RPC 私有,必须设置) |
从源码注释可以确认设计原则:defaultRpcUrl仅保留给公开、众所周知的端点(如测试网公共 RPC),主网端点和任何私有/内部 URL 必须省略defaultRpcUrl,强制运营方通过 repo secrets 配置,防止密钥泄露或误连公共限流端点。
新增链的方式:向目标环境的chains数组追加条目;新增环境则添加顶层键并给出chains+gateway。注册表在模块加载时校验同一环境内chainId唯一,重复会直接抛错。
八、合约层的输入校验
即使 calldata 已生成并提交,ProtocolConfig.proposeCoprocessorUpgrade在执行时还会做一套完整的链上校验(host-contracts/contracts/ProtocolConfig.sol):
proposalId == 0→ 拒绝(InvalidProposalId);softwareVersion为空 → 拒绝(EmptySoftwareVersion);chainUpgradeWindows为空数组 → 拒绝(EmptyChainUpgradeWindows);gwStartBlock == 0→ 拒绝(ZeroGwStartBlock);- 任一
chainId == 0→ 拒绝(ZeroChainId); - 任一
startBlock > endBlock→ 拒绝(InvalidBlockWindow); - 出现重复
chainId→ 拒绝(DuplicateChainId)。
全部校验通过后才发出CoprocessorUpgradeProposed(proposalId, softwareVersion, chainUpgradeWindows, gwStartBlock)事件。这也解释了为什么运营方选择的--proposal-id必须是正整数、且--duration不能过短——校验在链下任务与链上合约两个层面同时生效。
九、失败模式与排障
运行工作流或任务时可能遇到以下日志错误,对应的处理方法如下:
| 日志中的错误 | 处理方法 |
|---|---|
DAO buffer violated for: chain X — short by 22m | 将--start-time至少再向后推迟所缺的时长后重跑。 |
env var RPC_URL_X is not set | 在 repo settings 中补齐缺失的 secret;工作流文件头部按环境列出了所需 secrets。 |
--environment must be one of: devnet, testnet, mainnet | 从下拉框中选择一个合法环境。 |
duration too short for chain block time | 至少使用1m作为--duration(该错误由 host-contracts/tasks/utils/blockWindow.ts 在投影出的endBlock <= startBlock时抛出)。 |
此外还有两类非致命告警需要留意:block-time sampling failed; used configured fallback(采样失败回退到配置值)与block-time drifted >20% from fallback(网络拥堵,出块时间与配置偏差过大)。
十、本地演练(DAO 路径)
在本地做 dry-run 或开发调试时,可以直接运行task:prepareCoprocessorUpgradeHardhat 任务(定义在 host-contracts/tasks/prepareCoprocessorUpgrade.ts),它只计算并打印 calldata,绝不广播交易:
cd host-contracts npx hardhat task:prepareCoprocessorUpgrade \ --environment testnet \ --start-time "$(date -u -v+2H '+%Y-%m-%dT%H:%M:%SZ')" \ --duration 30m \ --buffer 1h \ --proposal-id 1 \ --software-version v0.14.0输出与 calldata 和工作流运行完全一致。如果任何链的startBlock距离链头小于--buffer,任务会以非零退出码失败,并打印 calldata 供检查(但明确提示不得提交)。可用以下命令查看完整参数说明:
npx hardhat help task:prepareCoprocessorUpgrade任务底层调用链为:parseCoprocessorUpgradeInputs(校验并解析输入、解析 RPC URL)→buildCoprocessorUpgradeProposal(调用computeBlockWindow逐链/逐 gateway 计算窗口)→encodeProposeCoprocessorUpgrade(纯 ABI 编码)→printCoprocessorUpgradeProposal(打印报告与 calldata)→bufferViolations(buffer 门禁判定),全部实现在 host-contracts/tasks/utils/coprocessorUpgradeProposal.ts。
时间与时长参数的解析规则(host-contracts/tasks/utils/blockWindow.ts):
- 时间戳:ISO 8601 UTC,如
2026-07-01T12:00:00Z; - 时长:
30s/30m/2h/1d或裸整数(按秒解释); --proposal-id支持十进制或0x十六进制,且必须 > 0。
十一、直连(no-DAO)路径 — devnet / test-suite
在 devnet 或 test-suite 环境中,deployer 密钥拥有 host 链上的ProtocolConfig(即onlyACLOwner的 owner 是 deployer),因此可以跳过 DAO,直接把 calldata 广播上链。task:proposeCoprocessorUpgrade执行相同的构建步骤,然后用DEPLOYER_PRIVATE_KEY发送字节完全一致的 calldata——它是 KMS 上下文切换任务task:defineNewKmsContextAndEpoch广播路径的姊妹实现。
它发送到--network指定网络上的 hostProtocolConfig;合约地址可通过环境变量PROTOCOL_CONFIG_CONTRACT_ADDRESS指定,或加--use-internal-proxy-address从addresses/目录读取:
cd host-contracts DEPLOYER_PRIVATE_KEY=0x... npx hardhat --network sepolia task:proposeCoprocessorUpgrade \ --environment devnet \ --start-time "$(date -u -v+2H '+%Y-%m-%dT%H:%M:%SZ')" \ --duration 30m --buffer 1h --proposal-id 1 --software-version v0.14.0 \ --use-internal-proxy-address广播前同样会执行 buffer 门禁:若任一链不满足 buffer,任务直接拒绝广播(Refusing to broadcast)。广播实现位于executeCoprocessorUpgradeProposal(host-contracts/tasks/utils/coprocessorUpgradeProposal.ts),它用 deployer 钱包向目标地址发送{ to: target, data: calldata }并等待交易确认后返回交易哈希。
十二、测试与验证
仓库为这套逻辑提供了针对非 RPC 面的单元测试(host-contracts/test/tasks/prepareCoprocessorUpgrade.ts),覆盖:
- 编码往返:
encodeProposeCoprocessorUpgrade生成的 calldata 能被PROPOSE_UPGRADE_ABI解码还原出完全一致的proposalId、softwareVersion与各链窗口(含 sepolia、amoy 两条链与 gateway); - 输入校验:
parseCoprocessorUpgradeInputs对非法--proposal-id(非整数、≤ 0)与空--software-version的拒绝行为; - buffer 门禁:
bufferViolations正确列出不满足 buffer 的链与 gateway。
窗口计算本身依赖真实 RPC(由工作流覆盖),因此测试刻意避开 RPC,只验证与网络无关的纯逻辑面。合约层的CoprocessorUpgradeProposed事件与各类 revert 分支的完整行为,可在 host-contracts/test/protocolConfig/protocolConfig.t.sol 中找到对应用例,可作为理解链上校验的补充阅读材料。
结语
coprocessor 升级是 fhEVM 多链体系中的关键治理动作:它把"新版本发布"与"多链+gateway 同步切换"通过 Aragon DAO 提案串成一条可审计、可回放的闭环。掌握本 runbook 的四个要点——正确设置时间参数(start-time/duration/buffer)、理解区块窗口的投影与 buffer 门禁、区分 DAO 路径与 no-DAO 路径、善用本地演练与排障表——即可安全、可重复地完成每一次 coprocessor 链上升级。
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考