当前 Eigenlayer 面临的问题
在 EigenLayer 正式上线后,协议本身为用户提供了 2 种 Restaking 选择,用户可以根据自己的需求来选取进行 LST Restaking 还是 Native ETH Retaking。对于 EigenLayer 而言,如何统计用户质押的 LST 或 ETH 数量是协议需要解决的一个核心问题。
对于普通的 LST 而言,质押数量的统计是简单的,由于 LST 代币本身符合 ERC20 标准,无论是否为 rebasing 代币,其都可以简单的通过下面的等式来算出每一个用户的在协议中的质押数量:
userRestakingAmount = userShare * (totalLSTTokenAmount / totalShare)
对于 LST 代币而言,totalLSTTokenAmount
这个变量是很容易获取的,只需要直接调用对应代币的 balanceOf
函数便可以获取到具体的数值。
但是相同的场景到了 Native ETH 上,情况会变得稍微有点复杂。由于通过 EigenLayer 进行 ETH Restaking 后,ETH 实际上是质押在 Beacon chain,也就是信标链上,而 EigenLayer 合约只是代为记录用户的份额。对于 EigenLayer 合约而言,其实是无法清楚用户究竟在 Beacon chain 上质押了多少 ETH 的,那么用在 LST 上的公式就无法直接套用在 ETH 上。从合约层面来看,Beacon chain 上的质押信息是被动获取的,需要依赖外部用户(podOwner 本身,或其他任何人)来进行验证后,合约才能更新正确的质押数据。
在目前的设计下,EigenLayer 的 EigenPod 合约提供了 2 个方法来为用户验证节点余额的更新和为用户提供提现功能。分别是 verifyBalanceUpdates
和 verifyAndProcessWithdrawals
。目前这两个方法的底层原理其实是根据协议公布的 Beacon chain 状态根(state root),在用户提供其质押节点的 validator proof 和 withdraw proof 后,利用 Merkle 树来进行验证的。这种方法的特点是,一个 proof 只能验证一个状态,即一次只能更新一次节点余额。基于这个特性,目前引入了 2 个问题:
-
由于 一次 Proof 只能验证一次节点在 Beacon chain 的状态,合约层无法和信表链进行通信,导致合约获取的信息必然是存在延迟的。举例:
-
1): 节点 A 通过 EigenLayer 进行 ETH Restaking,在激活 Restaking 后,节点在 EigenPod 合约记录的份额为 32 ETH。
-
2): 此时,假设节点 A 发起退出,则节点在信标链的 32 ETH 会返回到 EigenPod 中,同时节点会变成
exit
状态。 -
3): 但此时,如果没有人通过
verifyBalanceUpdates
来更新合约的话。EigenPod 合约是不清楚节点已经退出了。此时从合约上看,节点仍在 Beacon chain 上质押。 -
4): 此时继续往节点充值 1 ETH,由于节点为退出状态,这 1 ETH最终还是会回到 EigenPod 合约中。但是在之前,这 1 ETH 的数量会记录在 Beacon chain 的state root 中。此时如果根据这个 state root 通过
verifyBalanceUpdates
进行验证,合约会认为节点在 Beacon chain 上还剩下 1 ETH,并且是在质押状态。但实际上,节点 A 已经完全退出,并没有保留任何 ETH。此时,合约数据上已经存在错误。最后,用户可以通过verifyAndProcessWithdrawals
把 33 ETH 全部提走。 -
在当前 Beacon chain 的设计中,如果节点的提现地址设置为 Type1 地址,那么超出节点质押部分(32 ETH)的节点奖励,是会自动发送到节点指定的提现地址中。由于这个过程是自动的,而节点的余额实际上每一个 epoch 都会增加。那么相当于每一个 epoch 后,EigenPod 合约都会收到来自 Beacon chain 的节点奖励。而要验证这些奖励,需要通过
verifyAndProcessWithdrawals
函数来进行。通过上文我们知道,一条 proof 只能验证一次的状态,也就是一次节点奖励,而如果你有100条奖励,那么实际上你是需要验证100次的。而由于奖励是自动发送的,且每个 epoch 都有,那么为了拿出来这个奖励,要验证的数量。。会变得非常巨大。
为什么要迫切地解决这个问题?
上面说到的 2 个问题,总结起来无非就是 2 点。分别是:
-
合约数据记录错误
-
验证节点奖励成本消耗巨大
如果这两个问题不解决,会导致后面的 slashing 机制无法正确实现。因为在 slashing 的过程中,用户可以通过上文介绍的方法进行作恶,让合约错误地记录节点在 Beacon chain 的余额。由于罚没是基于合约记录的数值进行测算的,错误的余额将导致 slashing 无法进行。
同时,验证节点奖励成本消耗巨大也会影响用户进行提现,由于每一笔奖励都要单独验证,有一些奖励在 gas price 很高的情况下甚至还没手续费高,导致用户的收益直线下降。
引入 checkpoint 机制
为了有效解决上面提到的问题,Eigenlayer 官方介绍了一种新的机制,从以前的直接通过 Merkle Tree 验证节点余额的形式改成了通过 Checkpoint 的方式来验证节点在信标链的余额。CheckPoint 的数据结构如下:
struct Checkpoint {
bytes32 beaconBlockRoot; // beacon block root of the block before the checkpoint is started
uint256 podBalanceGwei; // pod balance in gwei when checkpoint starts, minus balance already backed by shares
int256 balanceDeltasGwei; // total change in beacon balances, updated as checkpoint progresses
uint256 proofsRemaining; // # of validators to prove before checkpoint is done
}
从 Checkpoint 的数据结构设计上,我们不难发现在新的结构中引入了 podBalanceGwei
这个新的变量。正是由于这个新的变量,可以有效避免上文说到的节点退出了但合约不知道的情况。其原理为由于所有 EigenLayer 的 ETH Restaking 节点的 Withdraw Credential 实际上都是指定为其 EigenPod 的合约地址。在每次创建 Checkpoint 的时候,假使节点在创建 Checkpoint 之前退出,那么合约就可以通过 EigenPod 合约中的 ETH 余额来把节点退出的余额记录起来,同时这部分的资金会重新算入节点的 share 中(而不是在旧版本中直接把这部分奖励发送给用户)。而 Beacon chain 的部分则通过 balanceDeltasGwei
来核算节点在 Beacon chain 上的的真实余额,用户需要通过对创建的 CheckPoint 进行验证,在 checkpoint 验证结束后,EigenPod 上对应管理的节点的余额会被更新,如果节点的余额变为0,则把该节点标记为退出状态。同
还是以上面的作恶情况为例:
-
节点 A 先通过 Eigenlayer 激活 Restaking,合约份额更新为 32 ETH
-
在激活后 节点 A 退出。ETH 最终回到 EigenPod 上,此时 EigenPod 合约还不清楚用户已经退出了。
-
用户需要创建 checkpoint,此时 32 ETH 被记录到 checkpoint 的 podBalanceGwei 上
-
在checkpoint 完成验证后,32 ETH 会记录到用户的 share 中,此时合约认为节点还有 32 ETH 的份额,由于节点余额为0,节点被标记为退出状态。
-
用户继续充值 1 ETH,此时合约不会记录该 1 ETH 的余额。同时也无法通过创建新的 checkpoint 把节点 A 的余额更新为 1ETH,因为节点A已经是退出状态,但由于该 1 ETH最终会 返回到 EigenPod 合约中,并记录在
podBalanceGwei
变量中。此时如果 Eigenpod 合约没有其他管理的节点,则直接完成了 Checkpoint 的验证,1 ETH 的余额直接累加到节点的 share 中,此时节点的 share 数量还是 33 ETH,并没有被误算。
通过上面的例子,不难发现 Checkpoint 机制的核心其实是分离信标链余额和 EigenPod 余额。将 Merkle Tree 直接用于验证节点在 Beacon chain 的余额,而由于无论是节点奖励还是节点退出的资金返还,最终都是会回到 EigenPod 合约中,所以 EigenPod 合约可以直接把 EigenPod 中的这部分资金视为节点的份额。最终,用户的 share 构成就可以根据下面这个等式来得到。
userRestakingBalance = BeaconBalance + EigenpodBalance
除了解决节点数据不一致的问题外,EigenLayer 还通过 CheckPoint 机制引入了批量验证的功能,在旧的版本中,每一笔交易只能验证一个 proof ,在新的版本中,一个交易可以批量验证多条proof,有效减少了验证 proof 需要发送的交易数量。但是这个机制能不能有效减少费用其实并不是十分确定,因为这受限于 gas price 和总的验证数据的大小。并且官方也没有给出具体的测算数据。
附录:
-
EigenLayer checkpoint 机制解读:https://hackmd.io/U36dE9lnQha3tbf7D0GtKw?view=
原文始发于微信公众号(蛋蛋的区块链笔记):EigenLayer checkpoint 机制技术解读