逃生舱不是功能按钮,而是资产安全边界

ZK-Rollup 的用户体验通常依赖排序器(Sequencer):用户把交易提交给排序器,排序器打包批次,证明者生成有效性证明,L1 合约更新状态根。正常情况下,这条路径比 L1 便宜得多。但一旦排序器拒绝处理提现、长时间离线、选择性审查某个地址,用户就会遇到 Rollup 最关键的安全问题:资产在 L2 状态里是自己的,但自己无法让系统把它释放回 L1。

逃生舱(Escape Hatch)和强制提现(Forced Withdrawal)的目标,就是让用户在排序器不合作时仍能单方面退出。它不是“更快提现”的产品功能,而是对审查风险和运营故障的最后约束。对 [AllSwap 跨链交换](/exchange-swap) 这类无托管路由入口,逃生舱决定了异常路径是否可退款、是否可归因,以及用户资产是否会因为目标 Rollup 停机或排序器拒绝打包而卡在中间状态。

本文讨论 ZK-Rollup 场景,不把 optimistic challenge game、中心化托管交易所内部账本或普通跨链桥多签混在一起。核心问题只有一个:当 L2 不能或不愿执行用户提现请求时,L1 合约需要哪些状态、证明和时钟规则,才能让用户在不信任排序器的情况下取回资产?

系统模型:L1 只保存状态根,用户保存证明材料

一个典型 ZK-Rollup 至少包含五类对象:

- L2 状态树 `S_t`:记录账户余额、nonce、订单、仓位或 UTXO; - L1 Rollup 合约 `R`:保存最新已验证状态根 `root_t`; - 排序器 `Seq`:收集交易并生成批次; - 证明者 `P`:为批次生成 ZK proof; - 用户 `U`:持有账户密钥、本地状态证明和提现意图。

正常提现路径是:用户向 L2 提交 withdrawal,排序器纳入批次,证明者证明状态转移,L1 合约接受新 root,用户在 L1 finalize withdrawal。强制提现路径则要绕开排序器:

1. 用户在 L1 提交强制提现请求; 2. Rollup 协议把该请求强制注入 L2 队列或冻结窗口; 3. 若排序器在期限内处理,走正常批次; 4. 若排序器不处理,用户用最新可用状态根和 inclusion proof 触发逃生; 5. L1 合约验证资产属于用户且未被消费,释放或标记可提取。

最小安全命题可以写成:

`Withdraw(u, a, root_t, proof, nullifier) is valid`

当且仅当 `proof` 证明用户 `u` 在 `root_t` 下拥有金额 `a`,`root_t` 是 L1 已接受的状态根,且 `nullifier` 未被使用。这个命题看似简单,但每个变量都很容易出错:root 可能过时,proof 可能对应旧余额,nullifier 可能没有覆盖全部重放路径,资产可能已经在后续 L2 状态中被消费。

强制入队和逃生舱是两层机制

很多讨论把 forced transaction、forced withdrawal、escape hatch 混成一个概念。工程上它们应拆开。

强制入队是活性机制。用户在 L1 提交一个请求,要求排序器必须在某个窗口内把它纳入 L2 批次。Arbitrum delayed inbox 和 OP Stack forced transactions 都属于这类设计:L1 提供一个绕过排序器普通 mempool 的入口,防止 sequencer 永久审查用户交易。对 ZK-Rollup,强制入队可用于要求 L2 处理提现或退出操作。

逃生舱是失败终局机制。当排序器不处理强制入队,或系统进入冻结/撤退模式,用户不再等待 L2 继续出块,而是在 L1 用状态证明直接退出。StarkEx 文档中的 forced withdrawal 和 freeze request 体现了这种思路:用户先请求服务方处理,若未在窗口内处理,可触发冻结,冻结后用户通过链上机制撤回资产。

两者的差别是:强制入队仍假设 Rollup 会继续运行;逃生舱假设 Rollup 已经不能提供正常服务。AllSwap 路由在判断目标链风险时,也应区分这两层:一条链有 force inclusion,不代表它有完整 escape mode;有逃生舱,也不代表日常提现不会慢。

还有第三层经常被忽略:证明材料获取。强制入队和逃生舱都写在合约里,并不代表用户一定拿得到账户 leaf、Merkle path、历史批次数据或 UTXO opening。一个严肃的 Rollup 退出设计,需要让用户、钱包、索引器或第三方数据服务在正常时期持续保存可退出 witness。否则真正停机时,用户才发现自己只有余额截图,没有可提交给 L1 的证明。

L1 合约需要验证什么

逃生舱的核心不是“用户上传一条 Merkle proof 就能提现”。L1 合约至少要验证四件事。

第一,状态根有效。用户证明必须基于 L1 已接受的 `root_t`。若允许用户引用未验证 root,逃生舱就退化成信任用户或排序器。

第二,资产所有权。证明路径必须绑定账户、资产 ID、金额、nonce 或 UTXO commitment。账户模型里通常证明 `(owner, token, balance, nonce)`;UTXO/record 模型里通常证明 note 或 commitment 的 inclusion。

第三,未消费性。若用户能用旧 root 证明自己曾经拥有资产,但资产已在后续状态中转出,就会出现重放提现。解决方式通常是 nullifier、exit nonce、forced withdrawal request id 或“只能基于冻结前最后状态”的全局撤退规则。

第四,退出金额上界。合约不能只看 proof 里的金额,还要检查该资产在 L1 escrow 中可释放、该用户没有重复退出、以及手续费或部分提现规则没有制造负余额。

伪代码可以压缩为:

`require(acceptedRoot[root_t])`

`require(verifyInclusion(root_t, accountLeaf, proof))`

`require(accountLeaf.owner == msg.sender)`

`require(!spent[nullifier])`

`spent[nullifier] = true`

`release(token, msg.sender, amount)`

顺序也重要。`spent[nullifier]` 应在外部转账前写入,避免重入或重复提交。proof 的 public inputs 应绑定链 ID、Rollup ID、资产合约、用户地址和退出模式,否则跨实例重放会成为现实风险。

更完整的逃生合约通常需要四个状态:`normal`、`forceRequested`、`frozen`、`withdrawOnly`。`normal` 下接受普通批次;`forceRequested` 下开始计时,等待排序器处理指定请求;`frozen` 下停止接受新批次,防止状态继续变化;`withdrawOnly` 下只允许用户提交退出证明和领取资产。状态切换必须单调,不能让管理员在用户开始逃生后随意恢复到普通模式,否则用户可能基于一个冻结 root 准备 proof,却在提交前被新的状态根覆盖。

管理员权限也是风险点。逃生舱如果依赖单一管理员手动打开,就不能对抗管理员本身作恶。更合理的设计是把“未处理强制请求超过窗口”作为可公开触发的条件;任何人都能提交证据让合约进入 frozen 或 withdrawOnly。管理员可以有暂停恶意攻击的权力,但不应拥有阻止合法退出的最终权力。

旧证明重放是最难看的失败路径

逃生舱最危险的 bug 不是证明验证失败,而是证明验证“正确但语义过期”。假设用户在 `root_100` 下有 100 USDC,之后在 L2 花掉 80 USDC,到 `root_110` 只剩 20 USDC。如果系统允许用户在冻结后拿 `root_100` 的 inclusion proof 直接退出 100 USDC,而没有 nullifier 或最后有效 root 规则,就会破坏 L1 escrow 的守恒。

因此逃生舱必须回答:用户可以基于哪个 root 退出?常见设计有两种。

一种是冻结模式:系统在某个 frozen root 停止推进,所有退出都基于这个 root。好处是逻辑清晰;坏处是冻结前没有及时同步本地 proof 的用户可能需要依赖数据可用性恢复状态路径。

另一种是强制请求模式:用户先在 L1 登记退出请求,后续证明必须绑定该 request id 和登记时间。若排序器没有处理,用户才能走 L1 逃生。好处是减少旧 root 滥用;坏处是用户必须先发起强制请求,不能在完全离线后才发现自己没有登记。

无论哪种方式,都需要数据可用性。用户必须能拿到状态树路径、账户叶子或 UTXO witness。若 Rollup 把数据放在链下 DAC 或中心化服务里,逃生舱在 DAC 合谋 withholding 时可能失效。这就是 Validium 与 Rollup 在逃生能力上的本质差别之一。

旧证明重放还会出现在跨资产场景。假设同一用户在 L2 同时持有 USDC、USDT 和某个 LP token,退出 proof 若只绑定 owner 和余额,而没有绑定 token address、chain id、rollup id 和资产 registry,就可能被错误解释为另一种资产的退出。L1 逃生合约必须把 proof 的 public inputs 设计成完整语义,而不是只验证一条路径存在。

强制提现的时钟设计

强制提现必须有明确时钟,否则不是安全机制,而是客服流程。时钟至少包含三个窗口。

第一,排序器响应窗口。用户在 L1 提交 forced request 后,排序器有多少时间把它纳入 L2 批次?窗口太短会误伤正常拥堵,窗口太长会让审查持续太久。

第二,证明提交窗口。即使排序器纳入交易,ZK proof 也可能延迟。系统要区分“已执行但 proof 未上链”和“未执行”。这影响用户何时可以升级到逃生模式。

第三,撤退窗口。进入冻结或 escape mode 后,用户多久内可以提交 proof?如果没有截止,合约长期保留大量状态;如果截止过短,离线用户可能错过退出。

这些窗口应进入路由风险模型。对 AllSwap 来说,目标链是否支持 forced withdrawal,不应只是布尔值。更有用的参数是 `forceRequestDelay`、`proofLag`、`escapeModeAvailable`、`dataAvailabilityMode` 和 `maxExitWindow`。大额跨链交换应该避开强制退出窗口过长、数据可用性弱或逃生模式不清晰的路径。

时钟还要考虑 L1 拥堵。真正需要逃生舱的时候,往往不是市场平静期,而是排序器事故、价格剧烈波动或系统性清算压力。此时 L1 gas 可能上升,所有用户同时提交 forced withdrawal 或 exit proof。若逃生舱只在理论 gas 成本下可用,实际危机中就会变成“有门但挤不出去”。批量退出、证明聚合、分阶段 claim 和 gas 补贴机制,都是逃生舱可用性的一部分。

批量退出可以降低 L1 压力,但会引入新的协调风险。若多个用户的 exit proof 被聚合成一个批次,聚合者必须不能篡改收款地址,也不能扣留某个用户的 claim。一个稳健模式是:聚合 proof 只证明一组 withdrawal claim 有效,实际领取仍由每个用户按自己的 claim id 执行。这样聚合者帮助节省 gas,但不能成为新的托管方。

排序器审查、停机和部分失败

逃生舱要覆盖至少三种失败。

第一,选择性审查。排序器继续为其他用户出块,但拒绝某个地址或某类提现。强制入队可以把请求放到 L1 可见队列,迫使排序器处理或留下可证明审查证据。

第二,全局停机。排序器或证明者长期离线,Rollup 不再更新 root。这时强制入队没有意义,系统必须进入冻结或 escape mode。用户是否能退出,取决于最后可用状态根和数据可用性。

第三,部分执行失败。用户的跨链交换在源链已扣款,目标 Rollup 因排序器问题没有到账。此时 AllSwap 需要明确失败发生在源链锁定、目标链执行、proof 提交还是 L1 finalize。逃生舱能解决的是“目标 Rollup 内部资产如何退出”,不能自动补偿源链和目标链之间的所有经济差额。

第四,证明者延迟。ZK-Rollup 的排序器可能处理了提现,但证明者没有及时提交有效性证明。用户在 UI 中看到“已执行”,但 L1 合约还没有接受对应 root。这种状态不应被展示为最终到账。路由系统应区分 `executedOnL2`、`provedOnL1` 和 `claimableOnL1`,否则用户会误判资金是否真正可退出。

第五,逃生队列拥堵。冻结模式下,所有用户都可能同时退出。若 L1 合约逐笔验证昂贵 proof,队列会被大户、机器人或套利者优先占据。协议需要考虑公平排序、每账户限速、批量 proof 或分资产窗口,避免逃生舱在危机中被少数参与者耗尽区块空间。

对 AllSwap 路由的影响

AllSwap 路由可以把 Rollup 出口安全分成四级:

- `normal-withdrawal`:只依赖排序器正常处理; - `force-inclusion`:用户可在 L1 强制请求排序器纳入交易; - `escape-mode`:排序器失败后可基于状态证明单边退出; - `full-recovery`:退出 proof、数据可用性、退款地址和补偿状态都有可验证路径。

路由评分可以写成:

`routeScore = priceScore + speedScore - exitRiskPenalty - daPenalty - refundUncertainty`

其中 `exitRiskPenalty` 来自强制提现能力,`daPenalty` 来自数据可用性模式,`refundUncertainty` 来自失败后能否证明和释放资金。最低手续费路径如果没有逃生舱,不一定适合大额交易;稍慢但有 force inclusion 和 escape mode 的路径,在风险调整后可能更优。

对用户界面,表达可以保持克制:不用展示所有证明细节,但应在高风险路径上提示“目标 Rollup 退出依赖排序器正常服务”或“支持 L1 强制退出但预计窗口较长”。在 [/fees](/fees)、[/swap/usdt-erc20](/swap/usdt-erc20)、[/assets/usdc](/assets/usdc) 这类路径中,费用不只是 gas,也包括失败恢复的时间和证明成本。

后台状态也要足够细。每笔跨链交易至少应能落到这些状态之一:`submitted`、`sequenced`、`proved`、`claimable`、`forceRequested`、`escapeAvailable`、`refunded`、`failedFinal`。如果所有异常都显示成“处理中”,用户无法判断自己应该等待、补交 proof、发起强制请求,还是联系路由器处理源链退款。对无托管产品来说,状态透明本身就是安全功能。

对于 Solver 路径,逃生能力还影响保证金设计。若 Solver 在目标 Rollup 预垫资产,但 Rollup 后续进入冻结模式,Solver 的再平衡资金可能被锁。路由器需要知道这条路径的逃生窗口和数据可用性,否则 Solver 会把风险溢价转嫁给用户,或在高波动时直接撤出流动性。

这也是为什么“是否支持逃生舱”不能只存在于内部风险文档。路由器至少应把它折算成价格、限额或提示:无 escape 的链降低单笔上限;数据可用性弱的链提高风险溢价;force request 窗口过长的链不适合即时到账承诺。这样产品不用向每个用户解释 Merkle proof,却仍然把底层安全差异反映在路由结果里,避免把风险藏到后台和客服流程中,这一点非常关键且必要安全。

仍未解决的问题

第一,账户模型和 UTXO 模型的逃生证明差异很大。账户模型需要处理 nonce、余额更新和旧 proof 重放;UTXO 模型需要处理 nullifier、note 隐私和批量退出。统一 UI 很难表达这些差异。

第二,数据可用性仍是逃生舱的硬前提。若用户拿不到 witness,L1 合约即使支持 escape,也无法凭空恢复状态路径。Validium、Volition、Rollup 的逃生语义不能混用。

第三,冻结模式会伤害正常用户。系统一旦 frozen,所有用户都进入低效撤退路径。何时冻结、谁能冻结、恶意冻结如何惩罚,是治理和经济学问题。

第四,跨链交换的退款可能跨越多个系统。目标 Rollup 能逃生,不代表源链资产能自动解锁,也不代表 Solver 已补偿。路由层需要把 Rollup escape proof 和跨链 swap refund proof 接起来。

第五,强制提现的 UX 仍然很差。用户可能需要保存证明、支付 L1 gas、等待窗口、理解 nullifier。真正可用的逃生舱需要钱包、索引器和路由器共同把复杂性隐藏在可验证流程后面。

第六,跨域逃生还缺少标准接口。今天每个 Rollup、Validium、Volition 或应用链的强制退出方式都不同。对聚合器来说,最需要的不是统一 UI 文案,而是机器可读的 `exitCapabilities`:是否可强制入队、是否可冻结、证明类型、DA 模式、最长等待窗口、是否支持批量退出、是否支持第三方代提交 proof。没有这类接口,路由器只能把所有 L2 风险粗暴归为同一类。

References

[1] StarkEx Forced Transactions and Freeze Request, StarkWare Docs, 2026, https://docs.starkware.co/starkex/perpetual/performing-forced-transactions.html

[2] StarkEx Data Availability, StarkWare Docs, 2026, https://docs.starkware.co/starkex/con_data_availability.html

[3] ZKsync Era Priority Queue, ZKsync Docs, 2026, https://docs.zksync.io/zksync-protocol/contracts/l1-contracts

[4] ZKsync Era Protocol Overview, ZKsync Docs, 2026, https://docs.zksync.io/zksync-protocol

[5] Arbitrum Delayed Inbox and Censorship Resistance, Arbitrum Docs, 2026, https://docs.arbitrum.io/how-arbitrum-works/sequencer

[6] OP Stack Forced Transactions and Sequencer Censorship Resistance, Optimism Docs, 2026, https://docs.optimism.io/stack/transactions/forced-transaction

[7] L2BEAT Stages and Rollup Risk Framework, L2BEAT, 2026, https://l2beat.com/scaling/summary

[8] Validium, Ethereum.org, StarkWare, 2020, https://ethereum.org/en/developers/docs/scaling/validium/

[9] Rollups, Validiums, and Disconnected Exits, Vitalik Buterin, 2021, https://vitalik.eth.limo/general/2021/01/05/rollup.html

[10] Plasma and Mass Exit Background, Ethereum.org, Kelvin Fichter, 2019, https://ethereum.org/en/developers/docs/scaling/plasma/

常见问题

ZK-Rollup 逃生舱和普通提现有什么区别?

普通提现依赖排序器把请求纳入批次并等待证明上链;逃生舱用于排序器审查、停机或系统冻结时,让用户基于 L1 已接受状态根和状态证明单边退出。

强制提现一定能保证用户拿回资产吗?

不一定。强制提现还依赖数据可用性、可提交的状态证明、未被使用的 nullifier、L1 gas 可承受,以及逃生合约没有管理员阻止合法退出。

为什么旧证明重放是逃生舱的关键风险?

用户可能用旧状态根证明自己曾经拥有资产,但资产后来已在 L2 花掉。逃生合约必须用 nullifier、冻结 root 或 request id 防止旧 proof 重复提现。

AllSwap 路由为什么要关注 Rollup 逃生能力?

目标 Rollup 若缺少强制入队或逃生舱,跨链交换失败时可能只能等待排序器恢复。路由评分应把退出窗口、DA 模式和退款证明纳入风险成本。

Validium 的逃生能力和 Rollup 一样吗?

不一样。Validium 通常把数据放在链下委员会或服务中,若数据不可用,用户可能拿不到退出所需 witness。Rollup 把数据发布到链上,逃生证明更容易恢复。

参考资料