简介
储备余额机制是一组轻量级约束 —— 在共识时限制哪些交易可以被包含, 以及在执行时限制哪些交易不会回滚 —— 这些约束使 Monad 能够同时支持 异步执行和 EIP-7702。 储备余额机制旨在在保持异步执行下的安全性的同时不干扰正常使用模式。 大多数用户和开发者无需担心储备余额约束,但对于遇到极端情况的用户,我们在此提供详细信息。摘要
异步执行意味着节点在执行区块中的交易之前就该区块提案达成共识。执行需要在接下来的k
(延迟因子)个区块内完成。(目前 k=3。注意:在讨论区块状态
和异步执行的上下文中,通常使用字母 D
来表示同一参数。)
因为共识在 k 个区块延迟的全局状态视图上运行,所以有必要略微调整共识和执行规则,
以允许共识安全地构建和验证仅包含可以支付其 gas 成本的交易的区块。
Monad 引入了储备余额机制,以允许共识和执行跨多区块滞后进行协作,
确保所有 EOA 必须在其账户中拥有足够的 MON 来为区块链中包含的任何交易支付 gas。
在本文档中,在途交易指的是在少于
k 个区块前被包含在区块中的交易。- 从特定 EOA 的角度来看,在交易过程中从该 EOA 花费的 MON 分为两部分:gas 花费和价值花费。
- 在 EOA 是发送者的情况下:
- gas 花费是
gas_price * gas_limit; - 价值花费是该交易上的
value参数。
- gas 花费是
- 在 EOA 不是发送者的情况下(即它之前通过 EIP-7702 委托,并且其他 EOA 提交了调用该 EOA 的交易):
- gas 花费为 0;
- 价值花费是在执行该 EOA 代码过程中发送出去的任何 MON。
- 在 EOA 是发送者的情况下:
- 设
user_reserve_balance = 10 MON - 执行时:在执行期间,当账户的结束余额(退款前)因价值花费降至
user_reserve_balance以下时, 交易会回滚,除非满足下面描述的某些情况。 - 共识时:对于每个账户,共识对所有在途交易的gas 花费有一个预算;
该预算为
user_reserve_balance(或延迟执行状态中账户的余额,取较低者)。 如果第一笔在途交易获得了上述例外,则该预算进一步减少该笔交易的价值花费金额。 在对区块n执行区块有效性检查时,共识会检查预算是否被超出。
另请参阅 Category Labs 的
Monad 初始规范
提案中的正式定义。
参数
为什么需要储备余额?
Monad 具有异步执行:允许共识继续构建和验证区块,而无需等待执行赶上。 具体来说,提议和验证共识区块n 只需要知道应用区块 n-k 后获得的状态。
虽然异步执行有性能优势,但它引入了一个新的挑战:如果共识没有最新状态,
它应该如何知道区块的有效性?
让我们用一个例子说明这个挑战(我们的例子中使用 k = 3):
共识正在验证区块 4,其中包含来自 Alice 的一笔交易 t,相关字段如下:
t' 中花光了余额。这会造成拒绝服务(DoS)向量,
因为 Alice 可以让共识免费包含许多交易。
解决方案的第一次尝试
一个想法是让共识客户端静态检查区块 2 及以后的交易,检查 Alice 是否在她的交易中花费了任何价值。 这将使共识能够拒绝区块 4 为无效,如果t 之前(例如 t')在区块 2、3 或 4 中的任何交易
来自 Alice 并花费了一些价值或 gas。
虽然这乍看是一个不错的解决方案,但它有两个缺点:
-
假设作为区块 2 或 3 中智能合约执行的一部分,Alice 收到了大量货币。如果我们有最新状态,
尽管存在
t',她本可以有足够的余额支付交易t。因此,仅根据静态检查拒绝交易过于严格。 -
它不仅严格,而且在 EIP-7702 下也不安全。使用 EIP-7702,Alice 可以将她的账户委托给智能合约,
该合约可以以静态无法检查的方式从 Alice 的账户转出货币。具体在我们的例子中,如果 Alice 的账户
被委托,她不需要从她的账户发送像
t'这样的交易就可以从她的账户花费货币。花费可能是由 其他任何人提交的交易触发的。因此我们的静态检查不会成功,即使我们在区块 2、3 和 4 中没有 看到 Alice 的任何其他交易,接受区块 4 为有效也可能是不安全的。
储备余额作为解决方案
简单版本
直观地说,储备余额的核心思想如下:如果共识和执行事先达成一致,即对于每个 EOA, 执行将阻止其结束余额降至共识已知的某个预定阈值(user_reserve_balance)以下,
直到发送者的gas 花费额度,那么共识可以安全地包含一系列gas 花费运行总和保持在
user_reserve_balance 以下的交易,而无需知道最新状态,也不会受到上述 DoS 向量的攻击。
该概念可以推广如下:
- 执行时:执行后(gas 退款前),对结束余额应用储备余额检查。对于非发送者账户,
结束余额不得低于
min(交易开始时的余额, user_reserve_balance)。对于发送者,结束余额 最多可以低于该交易的gas 花费(除非下面的清空例外适用)。只要结束余额充足, 执行期间允许过多的中间借记。 - 共识时:对于每个账户,共识对所有在途交易的gas 花费有一个预算;
该预算为
user_reserve_balance(或延迟执行状态中账户的余额,取较低者)。 在对区块n执行区块有效性检查时,共识会检查预算是否被超出。
user_reserve_balance 目前为每个 EOA 设置为 10 MON。
上述规则足以确保共识中包含的所有交易都能被支付,从而解决了我们的问题。然而,它有一个缺点,
即 EOA 无法花费所有的 MON,并且余额低于 user_reserve_balance 的 EOA 将无法发送任何成功的交易。
例如,以下行为可能是期望的,但当前受到上述规则(user_reserve_balance 设置为 10 MON)的阻止:
- Alice 的余额为 5 MON,想向 Bob 发送 4.99 MON(外加 0.01 MON 的 gas)
- Alice 的余额为 20 MON,想将 18 MON 换成 memecoin(外加 0.01 MON 的 gas)
解决缺点
首先我们来定义”清空交易”:一笔交易是**“清空交易”**当且仅当
- 发送者未被委托。
- 发送者在过去
k个区块内未发送任何其他交易。 - 在过去
k个区块内(包括在此交易中),没有人为发送者发送过委托或取消委托请求。
- 执行策略:对于每个未委托的账户发送者:
- _如果_一笔交易是清空交易
- _那么_无论如何允许该交易继续(一笔”清空交易”)。
- 共识策略:对于每个未委托的账户发送者:
- _如果_一笔交易是清空交易
- _那么_静态检查该交易的总 MON 需求(即
gas_bid * gas_limit + value), 并考虑到执行仍会允许该交易通过。这意味着对于接下来k个区块中的任何后续交易, 共识使用的储备余额将减少value。
k 个区块低于储备余额一次。由于 k 个区块是 1.2 秒,
此策略应允许大多数小账户仍然正常与区块链交互。
请注意,如果账户在最近(k 个区块内)有取消委托请求,即使当时它已经未委托,该账户也不符合例外条件。
参见完整规范以了解详情。
只要它们是发送者在 k 个区块内发送的第一笔交易,附加策略允许上一节末尾提到的两个示例。
讨论
EIP-7702 委托账户
如果 EOA 未被 EIP-7702 委托,那么即使 EOA 的余额非常低,储备余额也很少具有侵入性, 因为大多数交易是该 EOA 在k=3 个区块内发送的第一笔交易。
但如果 EOA 被 EIP-7702 委托了呢?影响是什么?
- 唯一会回滚的交易是 EIP-7702 委托的 EOA 的余额低于 10 MON 的交易。
- “低于” 意味着 “减少且降到 10 MON 以下”。
- EOA 余额结束时高于或等于 10 MON 的交易没有问题。
- EOA 余额未改变或增加的交易没有问题。
- 只有减少余额且导致最终余额低于 10 MON 的交易才会被回滚。
- 已委托的 EOA 不能使用上面描述的清空例外。
被包含但回滚的交易
由于储备余额规则,您可能会看到执行时_回滚_但被_包含_在链中的交易,例如尝试转出比账户余额中 更多的 MON 的交易。 这些交易仍然是_有效_交易,支付 gas,但这些交易的_结果_只是从发送者那里扣除 gas。 它们之所以被包含,是因为在共识时,提议者无法确定该账户不会从其他人那里收到更多的 MON, 并且发送者有预算支付 gas。 以太坊包含许多执行回滚的交易,因此这不是协议差异。但实际上,以太坊区块构建者可能会筛除余额不足 以处理转出的交易,因此这种行为可能与您习惯看到的不同。检测储备余额下降
合约可以通过调用位于0x1001 的储备余额预编译
在执行时检查其当前执行是否已进入储备余额。它公开了一个方法 dippedIntoReserve()
(选择器 0x3a61584e,gas 成本 100),该方法返回一个 bool。它在 MIP-4
中规定。
dippedIntoReserve() 必须通过 CALL 调用。通过 STATICCALL(或通过 DELEGATECALL 或 CALLCODE)调用它会回滚。
虽然它读取状态并返回值,但它有意不是 view 函数,以便 Solidity 调用点编译为 CALL 而不是 STATICCALL。
完整规范
参见储备余额规范 以了解储备余额规则的正式集合。 算法 1 和 2 分别为共识和执行实现此检查。 算法 3 实现了检测进入储备余额的机制(算法 2 使用算法 3 来回滚进入的交易)。 算法 4 规定了清空交易的标准:- 发送者账户在过去
k个区块内必须未被委托。这通过静态验证账户在过去k个区块内的 已知状态中未被委托,并且过去k个区块内没有委托或取消委托请求(这可以静态检查)来验证。 - 在过去 k 个区块内不得有来自同一发送者的另一笔交易。
如果账户未被委托且没有在途交易
如果账户未被委托,并且没有以前的在途交易,那么共识会检查此交易的 gas 费用是否小于 延迟状态中的余额。如果账户未被委托且有一笔清空的在途交易
如果账户未被委托,并且有一笔以前的在途交易,那么共识必须考虑在途交易的总 MON 支出 (包括value):
只有当所有在途交易的 gas 费用总和(不包括第一笔)小于储备时,新交易才能被包含:
所有其他情况
储备等于系统级储备余额(10 MON)或账户在区块 n - k 的余额中的最小值:
只有当所有在途交易的 gas 费用总和小于储备时,新交易才能被包含:
调整储备余额
目前每个账户的储备余额都相同(10 MON)。
在未来的版本中,协议可能会允许用户通过有状态的预编译自定义其储备余额。
Coq 证明
储备余额规范的安全性已在 Coq 中正式证明。 完整的证明文档可在此处获取。 共识检查在 Coq 中被形式化为consensusAcceptableTxs。谓词 consensusAcceptableTxs s ltx
定义了共识模块在状态 s 之上接受交易列表 ltx 的标准。
证明表明,consensusAcceptableTxs s ltx 意味着当执行模块在 s 之上逐个执行 ltx 中的所有交易时,
它们不会因余额不足以支付 gas 费用而失败。证明是对列表 ltx 进行归纳:可以将其视为对 ltx 的
长度进行自然归纳。归纳步骤中的证明涉及展开共识和执行检查的定义并考虑所有情况。在每种情况下,
共识检查中的有效储备余额估计都被证明相对于执行中发生的情况是保守的。
附加示例
为了测试您的理解,以下是一些示例及其预期结果。每个示例都是独立的。 在以下示例中,我们使用start_block = 2,这意味着初始余额和储备是在区块 1 之后。
我们还为每个示例指定储备余额参数,尽管它是一个系统级常量参数。
对于每笔交易,预期结果由代码表示:
- 2:成功执行
- 1:被包含但执行期间回滚(由于储备余额下降)
- 0:被共识排除
示例 1:基本交易包含
初始状态:示例 2:低储备余额但高余额
初始状态:示例 3:多区块、低储备但高余额
初始状态:示例 4:综合
初始状态:示例 5:边缘情况 — 零价值交易
初始状态:示例 6:储备余额边界
初始状态:示例 7:账户在中间被委托
初始状态:Block 3 中的交易(通过检查过去 k 个区块内的委托状态),
共识将包含 Block 5 中的交易,该交易稍后将耗尽用于费用的 MON。
另外,考虑 Block 2 为空的情况。那么 Block 3 中的交易就是清空交易,它继续执行,
但 Block 5 中的交易因没有足够的储备来支付费用而被排除。
示例 8:账户在第二笔在途交易之前收到 MON
初始状态:Block 3 中的第 2 笔交易(来自 Alice;通过检查过去 k 个区块内是否存在清空交易),
共识将包含 Block 5 中的交易,该交易稍后将耗尽用于费用的 MON。
另外,考虑 Block 2 为空的情况。那么 Block 3 中的(来自 Alice 的)交易就是清空交易,
它继续执行,但 Block 5 中的交易被排除,因为它可能没有足够的储备来支付费用 —— 共识
看不到 Alice 在 Block 3 中被记入。
