fhevm Gateway 协议暂停机制(Pausing Mechanism)实战指南
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
导读
本指南系统讲解 fhevm Gateway 协议的暂停(Pause)与恢复(Unpause)机制:当协议出现紧急状况(如安全事件、密钥泄露、升级过渡期)时,如何通过预授权的 pauser 账户快速冻结新的输入证明(input proof)与解密(decryption)请求,同时保证已提交请求的响应链路与链上 view 查询不受影响。阅读本文后,你将掌握PauserSet/Pausable合约的权限模型、被暂停的精确函数清单、GatewayConfig聚合暂停的实现原理,以及如何用仓库内置的 Hardhat 任务与PAUSER_PRIVATE_KEY/DEPLOYER_PRIVATE_KEY环境变量完成一轮完整的暂停—恢复操作。
1. 暂停机制概览:为什么需要"紧急刹车"
fhevm Gateway 协议对外承担两类关键职责:验证用户的输入证明(Input Verification)与处理链上的解密请求(Decryption)。这两条链路一旦被恶意请求冲击,可能影响整个 Gateway 服务的可用性甚至资金安全。因此,协议设计了一套只允许人工触发的紧急暂停开关(The Gateway can only be paused and unpaused manually):
- 暂停是"单向门":只有预注册的 pauser 账户可以暂停,且暂停后所有新的
request类交易都会立即 revert; - 暂停不阻断存量请求:已经发送并上链的请求,其响应仍然会被接受,进行中的共识(on-going consensus)可以继续达成;
- view 函数不受影响:只读查询依旧可调用,便于暂停期间排查问题;
- 恢复权限更高:只有
GatewayConfig合约的 owner(或GatewayConfig合约自身)能够解除暂停,避免 pauser 误操作导致协议无法恢复。
该机制由两个合约配合实现(详见 PauserSet 合约文档):
| 合约 | 类型 | 职责 |
|---|---|---|
PauserSet | 不可升级(immutable)合约 | 管理"谁可以暂停"的账户白名单 |
Pausable | 抽象合约(abstract) | 提供pause()/unpause()核心逻辑,被各 Gateway 合约继承 |
2. 可暂停的合约与暂停后的行为语义
2.1 哪些合约可被暂停
pauser 账户可以暂停以下两个 Gateway 合约:
Decryption(解密请求入口)InputVerification(输入证明验证入口)
其他 Gateway 合约(如CiphertextCommits、GatewayConfig、KMSGeneration)在技术上也继承了Pausable,但当前版本没有对外暴露任何whenNotPaused修饰的请求函数,因此pauseAllGatewayContracts不会(也无需)暂停它们——这一设计取舍在 pauseContracts.ts 的注释中有明确说明。
2.2 暂停后的行为语义
一个合约被暂停意味着:任何触发其"请求(request)"类函数的交易都会 revert。同时保证:
- 对已发送请求的响应交易仍被接受(网关后台仍可提交解密结果/证明响应);
- 进行中的共识流程可以正常收敛;
- 所有 view 函数(读取类)继续可调用。
该语义直接来自 OpenZeppelin 的PausableUpgradeable状态机:paused == true时,带whenNotPaused修饰符的函数全部回滚。
3. 被暂停的确切函数清单(源码级验证)
3.1 Decryption 合约
当Decryption被暂停时,以下函数会 revert(在 Decryption.sol 中均带whenNotPaused修饰符):
| 函数 | 源码位置 | 功能 |
|---|---|---|
publicDecryptionRequest | Decryption.sol#L332 | 公钥公开解密请求 |
userDecryptionRequest | Decryption.sol#L466 | 用户私钥解密请求 |
delegatedUserDecryptionRequest | Decryption.sol#L560 | 委托式用户解密请求(如 委托解密指南 场景) |
3.2 InputVerification 合约
当InputVerification被暂停时,以下函数会 revert(见 InputVerification.sol#L190):
| 函数 | 源码位置 | 功能 |
|---|---|---|
verifyProofRequest | InputVerification.sol#L190 | 提交输入证明的验证请求 |
也就是说,暂停后任何新的加密输入(ciphertext input)都无法进入协议,同时任何新的解密请求都无法发起——这正是"冻结新请求"这一设计目标的精确落点。
4. 权限模型源码剖析:谁可以暂停、谁可以恢复
4.1Pausable抽象合约:双通道鉴权
Pausable.sol 基于 OpenZeppelinPausableUpgradeable实现,并通过硬编码的GATEWAY_CONFIG地址(来自 GatewayAddresses.sol)进行跨合约鉴权:
function pause() external virtual { if (!GATEWAY_CONFIG.isPauser(msg.sender) && msg.sender != gatewayConfigAddress) { revert NotPauserOrGatewayConfig(msg.sender); } _pause(); } function unpause() external virtual { // GatewayConfig owner(deployer 或多签)或 GatewayConfig 合约本身 if (msg.sender != Ownable2StepUpgradeable(gatewayConfigAddress).owner() && msg.sender != gatewayConfigAddress) { revert NotOwnerOrGatewayConfig(msg.sender); } _unpause(); }关键点:
- 暂停通道(pause):
msg.sender必须是GatewayConfig.isPauser(addr) == true的已注册 pauser,或者是GatewayConfig合约地址本身(用于聚合暂停); - 恢复通道(unpause):
msg.sender必须是GatewayConfig的 owner(由Ownable2StepUpgradeable管理,见 GatewayConfig),或者是GatewayConfig合约地址本身; - 源码中通过将
GatewayConfig强转为Ownable2StepUpgradeable读取 owner,避免循环依赖,注释中对此有专门说明。
由此可得出结论(有源码依据):pauser 永远无法自行解除暂停,恢复权始终握在 owner 手中,形成"暂停快、恢复受控"的安全设计。
4.2PauserSet不可升级合约:pauser 名单管理
PauserSet.sol 是一个部署后不可升级的合约,其地址存储在GatewayConfig合约中,提供四个管理函数(接口定义见 IPauserSet.sol):
| 函数 | 说明 | 权限与校验(源码实现) |
|---|---|---|
addPauser(address) | 添加 pauser | 仅GatewayConfigowner 可调用;拒绝零地址、拒绝重复添加(AccountAlreadyPauser),发出AddPauser事件 |
removePauser(address) | 移除 pauser | 仅 owner 可调用;目标必须是现有 pauser(AccountNotPauser),发出RemovePauser事件 |
swapPauser(old, new) | 原子替换 pauser | 仅 owner 可调用;校验旧地址是 pauser、新地址不是,发出SwapPauser事件 |
isPauser(address) | 查询是否 pauser | view 函数,GatewayConfig内部pause()鉴权时调用 |
pauser 账户被设计为由 fhevm Gateway 协议 operators 控制的热钱包(见 gateway_config.md 中的 operators 说明),便于在紧急时刻快速响应。owner 添加/移除 pauser 的操作可通过仓库 tasks 目录下的 addPausers.ts、removePauser.ts、swapPauser.ts 执行,相关行为由测试 PauserSet.ts 覆盖验证。
5.GatewayConfig的聚合暂停:一次暂停全部
为了让 pauser 不需要逐个合约调用,GatewayConfig合约提供了聚合入口(源码见 GatewayConfig.sol#L535-L580,接口定义见 IGatewayConfig.sol):
function pauseAllGatewayContracts() external virtual onlyPauser { bool isDecryptionPaused = DECRYPTION.paused(); bool isInputVerificationPaused = INPUT_VERIFICATION.paused(); if (isDecryptionPaused && isInputVerificationPaused) { revert AllGatewayContractsAlreadyPaused(); } if (!isDecryptionPaused) DECRYPTION.pause(); if (!isInputVerificationPaused) INPUT_VERIFICATION.pause(); emit PauseAllGatewayContracts(); } function unpauseAllGatewayContracts() external virtual onlyOwner { bool isDecryptionPaused = DECRYPTION.paused(); bool isInputVerificationPaused = INPUT_VERIFICATION.paused(); if (!isDecryptionPaused && !isInputVerificationPaused) { revert AllGatewayContractsAlreadyUnpaused(); } if (isDecryptionPaused) DECRYPTION.unpause(); if (isInputVerificationPaused) INPUT_VERIFICATION.unpause(); emit UnpauseAllGatewayContracts(); }实现要点:
pauseAllGatewayContracts带onlyPauser修饰符——pauser 即可一键暂停Decryption与InputVerification;unpauseAllGatewayContracts带onlyOwner修饰符——只有 owner 才能一键恢复;- 两个聚合函数都是幂等且带全量状态检查的:全部已暂停时再暂停会 revert(
AllGatewayContractsAlreadyPaused),全部未暂停时再恢复会 revert(AllGatewayContractsAlreadyUnpaused),避免了重复交易与 Gas 浪费; - 由于
CiphertextCommits、GatewayConfig自身暂无whenNotPaused的请求函数,KMSGeneration尚未启用,聚合暂停只作用于上述两个合约(见 pauseContracts.ts#L104-L110 注释)。
6. Hardhat 任务:一键暂停与恢复
仓库在 tasks/pauseContracts.ts 中定义了完整的 Hardhat 任务。注意:源码中注册的任务名带task:前缀(文档中的简写名去掉前缀即为实际命令名)。
6.1 暂停任务(需要PAUSER_PRIVATE_KEY)
| 文档中的任务名 | 实际命令(npx hardhat ...) | 作用 |
|---|---|---|
pauseAllGatewayContracts | task:pauseAllGatewayContracts | 暂停全部可暂停的 Gateway 合约 |
pauseInputVerification | task:pauseInputVerification | 仅暂停InputVerification |
pauseDecryption | task:pauseDecryption | 仅暂停Decryption |
这些任务读取环境变量PAUSER_PRIVATE_KEY,用对应 pauser 钱包调用合约(见 pauseContracts.ts 中pauseSingleContract对contract.pause()的调用)。
# 仅暂停 Decryption 合约 PAUSER_PRIVATE_KEY="0x..." npx hardhat task:pauseDecryption --network <network>6.2 恢复任务(需要DEPLOYER_PRIVATE_KEY)
| 文档中的任务名 | 实际命令(npx hardhat ...) | 作用 |
|---|---|---|
unpauseAllGatewayContracts | task:unpauseAllGatewayContracts | 恢复全部已暂停的 Gateway 合约 |
unpauseInputVerification | task:unpauseInputVerification | 仅恢复InputVerification |
unpauseDecryption | task:unpauseDecryption | 仅恢复Decryption |
恢复任务读取DEPLOYER_PRIVATE_KEY,因为只有GatewayConfigowner 才能调用unpause()(见 pauseContracts.ts#L43-L52 中unpauseSingleContract)。
所有任务还支持--use-internal-proxy-address可选参数:为true时使用/addresses目录中记录的内部代理地址,否则读取对应的地址环境变量(见 getGatewayContract)。
6.3 重要限制:多签接管后恢复任务的失效
在GatewayConfig的所有权由部署者(deployer)转移给多签(multi-sig)之后,基于DEPLOYER_PRIVATE_KEY的恢复任务将无法再解除暂停。此时必须通过多签钱包调用GatewayConfig的unpauseAllGatewayContracts(或对具体合约unpause)来完成恢复。部署与所有权转移的完整流程可参考 本地部署指南 与 ownership.ts。
7. 环境变量配置详解
暂停/恢复任务依赖两个环境变量(完整说明见 env_variables.md,示例值来自.env.example,用于本地测试):
| 环境变量 | 描述 | Solidity 类型 | 默认值 |
|---|---|---|---|
PAUSER_PRIVATE_KEY | 任一已注册 pauser 的私钥 | bytes32 | - |
DEPLOYER_PRIVATE_KEY | 部署者账户私钥 | bytes32 | - |
# pauser 私钥:用于执行暂停任务(含 pauseAllGatewayContracts) PAUSER_PRIVATE_KEY="0x3588ffb4f4d9bea785a012b895543fe68f2d580a9d449decc91a25878064079a" # (bytes32) # 部署者私钥:用于部署合约,以及在所有权未转移时执行恢复任务 DEPLOYER_PRIVATE_KEY="0x7136d8dc72f873124f4eded25f3525a20f6cee4296564c76b44f1d582c57640f" # (bytes32)说明:
- 每个 operator 都应将
PAUSER_PRIVATE_KEY设为其热钱包(pauser)对应的私钥,以便在紧急时快速暂停; - 文档中给出的示例账户是通过
make get-accounts生成的、已注资的 Hardhat 测试账户; - 部署相关环境变量的完整集合见 部署环境变量文档。
8. 端到端操作流程示例
结合上述机制,一次完整的"紧急暂停 → 排查 → 恢复"流程如下:
- 确认 pauser 已注册:owner 通过
addPauser(或 addPausers.ts 任务)确保紧急响应账户在PauserSet白名单内; - 设置环境变量:将 pauser 私钥填入
PAUSER_PRIVATE_KEY; - 触发暂停:
PAUSER_PRIVATE_KEY="0x..." npx hardhat task:pauseAllGatewayContracts --network <network>此时
Decryption.publicDecryptionRequest/userDecryptionRequest/delegatedUserDecryptionRequest与InputVerification.verifyProofRequest全部开始 revert,新输入与新解密请求被冻结; - 存量请求继续消化:后台对已提交请求的响应交易仍可正常上链,共识正常收敛;
- 排查并恢复:确认风险解除后,由 owner 侧执行恢复:
DEPLOYER_PRIVATE_KEY="0x..." npx hardhat task:unpauseAllGatewayContracts --network <network>(若所有权已转移至多签,则改为通过多签发起
GatewayConfig.unpauseAllGatewayContracts交易); - 验证状态:可调用各合约的
paused()视图函数确认状态已恢复正常。
9. 常见问题与边界情况
- 暂停后还能提交请求吗?不能,带
whenNotPaused的请求函数全部 revert;但响应类交易与 view 调用不受影响。 - 重复暂停/恢复会怎样?聚合函数会因
AllGatewayContractsAlreadyPaused/AllGatewayContractsAlreadyUnpausedrevert,天然防重复。 - pauser 能恢复吗?不能。
Pausable.unpause()仅允许GatewayConfigowner 或GatewayConfig合约本身调用。 GatewayConfig本身会被暂停吗?当前版本它虽继承Pausable,但未暴露任何whenNotPaused请求函数,聚合暂停不涉及它。- 多签接管后如何恢复?放弃 Hardhat 任务,改由多签钱包直接调用
GatewayConfig的unpauseAllGatewayContracts函数。
10. 延伸阅读
- PauserSet 合约文档:pauser 名单管理接口与事件
- Pausable 抽象合约源码:暂停/恢复鉴权实现
- GatewayConfig 聚合暂停源码:
pauseAllGatewayContracts/unpauseAllGatewayContracts - PauserSet 合约源码 与 IPauserSet 接口
- 暂停任务实现 与 环境变量文档
- GatewayConfig 合约文档:operators 与所有权模型
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考