or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
EigenPod Redesign - Checkpoint Proofs
References
eigenlayer-contracts/#515
per_block_processing
per_epoch_processing
apply_blocks
The Problem with Pods
The primary technical problem with EigenPod beacon state proofs is using them to determine where ETH is with any degree of accuracy.
The reason we require state proofs in the first place is to ensure that if we award shares, they are guaranteed to correspond 1:1 to ETH that will eventually flow through the pod.
For this reason, the ultimate goal of the EigenPod proof system is to establish the current whereabouts of the assets backing the shares being awarded. Ideally, the proof system should not be ambiguous. When we process a proof, we should definitively know two things:
As long as we can determine these things without ambiguity, we can safely award shares that we know to be backed.
The M2 Proof System
The M2 proofs struggle with ambiguity and have some accounting quirks that will make it harder to implement new features (especially slashing). To understand why, we need to understand what information our M2 system consumes - that is, what we are actually proving.
Currently, we have three types of proofs:
Between these three proof types, we're mainly consuming two pieces of beacon chain info: effective balances and withdrawals. We're using this info to determine both:
M2 uses this information to award or remove shares accordingly.
Effective Balance
Withdrawal credential and balance update proofs consume a validator's
effective_balance
, located in the beacon chain'sValidator
container.The problem with effective balances is that they are only updated every epoch, rather than every block. This means that an effective balance proof may be up to 32 blocks stale - omitting validator balance changes like:
By relying solely on an effective balance proof, we do not know exactly how much ETH the validator currently has on the beacon chain. We also do not know if the validator has already withdrawn.
Withdrawals
Partial/full withdrawal proofs consume an
amount
found in the beacon chain'sWithdrawal
container. They also compare the epoch theWithdrawal
was created in with thewithdrawable_epoch
found in theValidator
container. This acts as a heuristic to determine whether theWithdrawal
is treated as a "partial" or a "full" withdrawal.The upside of consuming beacon chain
Withdrawals
is that by the time an EigenPod sees aWithdrawal
proof, the exact ETH amount proven is guaranteed to be in the pod.However, there are several downsides to relying on beacon chain
Withdrawals
:Withdrawals
are created every block, and as of the Dencun upgrade, there are a maximum of 16 withdrawals processed per block.Validator's
withdrawable_epoch
does not give a reliable indicator of full withdrawal - it only indicates that "at some point afterwithdrawable_epoch
, the validator will be withdrawn."Withdrawal
in a block after the validator'swithdrawable_epoch
DOES mean that the validator has fully withdrawn. However, there is no way to tell whether that specificWithdrawal
is the entire amount withdrawn!Withdrawal Edge Cases - Scenario
A single withdrawal proof in isolation tells you how much ETH moved to the pod, but tells you nothing about how much ETH is still on the beacon chain. This means that we can't tell if the withdrawal we're seeing is ALL the ETH we'll see from this validator, or if there is more on the way.
This makes it hard to adjust shares based on withdrawal proofs, and leads to ambiguity and edge cases in M2 share accounting. Consider the following scenario:
0) I have a validator with 32 ETH on the beacon chain, and I've proven my validator's withdrawal credentials are pointed at my pod.
1) Then, I exit one of my validators from the beacon chain. I do not prove this to my pod.
- 32 ETH in my pod
2) Next, I deposit 1 ETH to my exited validator. This will be automatically withdrawn to my pod, but not immediately. For now, it's just recorded in my validator's
effective_balance
.- 32 ETH in my pod
3) Finally, I perform a balance update proof, showing my pod that my validator only has 1 ETH. Because the last proven balance my pod saw was 32 ETH, this results in a share decrease of 31 ETH.
- 32 ETH in my pod
Ultimately, this allows a validator to misrepresent the amount of shares they have in their pod, which is especially dangerous in the context of slashing. An attacker could commit malicious behavior, then manipulate their beacon shares as described to reduce the number of shares that can be slashed.
This carries little risk for the attacker, as the m2 proof system will still accept the missing withdrawal proof - so the attacker can reinstate their shares when the danger of slashing has passed.
M2 Proofs - Conclusion
Using beacon chain proofs, we cannot distinguish between full and partial withdrawals, and we cannot rely on withdrawal proofs to determine an order of events. In M2, we apply a heuristic to get a "best guess" on this, but this guess has some accounting quirks that lead to significant attack vectors for a future EigenLayer slashing release.
We will need to address this before releasing slashing.
The Checkpoint Proof System
The checkpoint proof system is based around the concept of a pod's "active validator set." That is - a set of validators on the beacon chain that are known by the pod to have withdrawal credentials pointed at the pod.
When you prove a validator's withdrawal credentials, that validator enters the pod's active validator set. When you show that a validator has exited, that validator leaves the pod's active validator set. The M2 system uses this state model already:
verifyWithdrawalCredentials
setsvalidatorInfo.status
toACTIVE
, and increasesactiveValidatorCount
by 1verifyAndProcessWithdrawals
setsvalidatorInfo.status
toWITHDRAWN
and decreasesactiveValidatorCount
by 1A checkpoint proof combines a pod's active validator set with a snapshot of both beacon chain state and EigenPod state to unambiguously determine both how much ETH is backing a pod AND where the ETH is.
What is a Checkpoint Proof?
A checkpoint proof has two steps:
startCheckpoint
. This takes a snapshot of beacon chain and EigenPod state:verifyCheckpointProofs
, the pod owner (or anyone else) submits one balance proof for EACH validator in theACTIVE
state. This decreases the checkpoint'sproofsRemaining
and updates the checkpoint'sbalanceDeltasGwei
with the difference since the last seen balance.verifyCheckpointProofs
automatically finalizes a checkpoint once enough proofs have been submitted. To finalize a checkpoint, the pod computes a total share delta and sends it to theEigenPodManager
:Shares for Partial Withdrawals (and More)
A key detail of the checkpoint proof system is that any ETH in the pod that we are not able to attribute to existing shares MUST be awarded shares. This is because native ETH in the pod is fundamentally unattributable - we don't know where that ETH came from, because there are several sources of ETH that bypass contract execution:
Out of each of these sources, there is one in particular we need to consider: for validators with verified withdrawal credentials, any ETH in the pod from beacon chain full withdrawals has already been awarded shares! We need to be sure not to blindly grant shares for any ETH in the pod, as this may mean we're double-counting shares from exited validators.
In order to avoid double-counting, finalizing a checkpoint proof results in a share delta that follows this equation, where \(v(i)\) is an individual validator in a pod with \(n\) validators with verified withdrawal credentials:
\[ \Delta \text{podShares} = \Delta \text{podBalance} + \sum_{i=0}^{n} \Delta \text{beaconBalance}[ v(i) ] \]
When a validator exits, its \(\Delta beaconBalance\) will be a negative number: its current balance (0) minus its previously-proven \(beaconBalance\). This number is added to \(podBalance\), ensuring those shares are not double-counted.
This also means that shares will be awarded for partial withdrawals (as well as any other ETH for which we can't attribute a source).
What Gets Snapshotted?
The snapshot taken by
startCheckpoint
allows us to disambiguate "where" ETH is, while the sum of balance proofs across the pod's active validator set allow us to disambiguate "how much." This creates a guarantee we can use to award shares:The key to this guarantee is the snapshot process used by
startCheckpoint
, which does two things:block.timestamp
as input. This returns the parent beacon block root - i.e. the beacon block root corresponding toblock.number - 1
.activeValidatorCount
This checkpoint is used as the starting point for all subsequent proofs.
Why Does this Snapshot Work?
A weird quirk of execution layer/beacon chain block processing is that in the execution layer, beacon chain withdrawals are processed AFTER all block transactions (this actually led to our sole "High" bug in Cantina).
This quirk means that if we were to snapshot the current beacon block root alongside the current pod ETH balance, it may be possible for:
This is why, when
startCheckpoint
is called, it queries the EIP-4788 oracle with the currentblock.timestamp
, which actually returns the previous block's root! This means that whenstartCheckpoint
is called:These properties mean that the \(beaconBalances\) proven in a checkpoint are 100% distinct from the \(podBalance\) snapshotted at the checkpoint's start, and we can award shares without worrying about double-counting.
Checkpointing Incentives and Beacon Chain Slashings
With upcoming releases of EigenLayer-native payments and slashing, EigenLayer and AVSs will depend on the EigenPod share values reported by the core protocol in order to:
As such, an important consideration for the pod accounting system is: how do we ensure pod shares remain up-to-date? After all, the pod accounting system's accuracy depends in large part on being supplied with regular balance update proofs, as this is the only "view" it has of beacon chain state.
Note that a validator's beacon chain balance can drop due to a few events:
M2's answer to this question is
EigenPod.verifyBalanceUpdate
, which can be called by anyone (not just the pod owner) to prove that a validator's beacon chain balance has risen or dropped since the last seen proof. However, if a validator's balance drops, there is never an incentive for the pod owner to prove this, as it would decrease their shares.Checkpointing is slightly different: because shares are awarded for partial withdrawals, a pod owner is incenvitized to prove balance updates when:
In most cases, the checkpointing system provides adequate incentive to keep pod shares up to date.
Stale Balance Proofs
Typically,
startCheckpoint
can only be called by the pod owner. This is because checkpoints MUST be finished before starting a new one - and as a potentially gas-intensive process, we want to enable the pod owner to decide when checkpointing is worth it.However, if a large proportion of validators in a pod are slashed on the beacon chain, it's unlikely the pod owner will want to perform a checkpoint. In this case, we rely on a "staleness proof" to allow a third party to start a checkpoint on behalf of the pod owner.
Anyone can submit a staleness proof to
EigenPod.verifyStaleBalance
:verifyStaleBalance
allows anyone to submit a proof that an ACTIVE validator in the pod was slashed on the beacon chain.If successful, this allows the caller to start a checkpoint. Note that if there is an existing checkpoint, the call will fail - the existing checkpoint needs to be finished before this call succeeds!
Changes from M2
Follow along with ongoing contract work here:
eigenlayer-contracts/#515
. A brief summary of changes follows.Note: there is currently no concrete ETA for this release! These notes are an early look into the contract changes that will accompany the release – we're hoping to get feedback on this as early as possible to know if these changes pose major problems for anyone!
Changed: Proofs Interface
Removed:
EigenPod.verifyBalanceUpdates()
EigenPod.verifyAndProcessWithdrawals()
EigenPod.provenWithdrawal()
EigenPod.sumOfPartialWithdrawalsClaimedGwei()
Added:
Context:
Much of the context and explanation for how these methods work can be found by reading through the upper section of this document.
To summarize, this release removes two major
EigenPod
methods:verifyBalanceUpdates
verifyAndProcessWithdrawals
In their place, pod owners will use:
startCheckpoint
verifyCheckpointProofs
These two new methods should provide all of the same functionality as the removed methods, with these additional benefits:
DelayedWithdrawalRouter
. Instead, finalized checkpoints award any partial withdrawals with shares that can be withdrawn via theDelegationManager
withdrawal queue, the same way all other EigenLayer shares are withdrawn.Example User Flow - Claiming Partial Withdrawals
EigenPod.startCheckpoint()
. This will update:EigenPod.currentCheckpointTimestamp
EigenPod.currentCheckpoint
EigenPod.currentCheckpoint()
and fetch thebeaconBlockRoot
that will be used to generate proofs for the new checkpoint.ACTIVE
state in your pod.ACTIVE
state by queryingEigenPod.validatorStatus(pubkeyHash).
ACTIVE
validators have been verified viaverifyWithdrawalCredentials
, but have not been provenWITHDRAWN
.Validator.effective_balance
, but rather uses the current balance found inBeaconState.balances
EigenPod.verifyCheckpointProofs
. You can submit these proofs individually, or all at once.verifyCheckpointProofs
multiple times, thestateRootProof
parameter can be left empty on subsequent calls; this value is cached the first time it is proven for a given checkpoint.currentCheckpoint.proofsRemaining
decreases by 1currentCheckpoint.balanceDeltasGwei
updates to include the balance delta calculated for that proofcurrentCheckpoint.proofsRemaining
hits 0, the checkpoint is automatically finalized and your partial withdrawals are awarded shares.Some important notes:
WITHDRAWN
and decrementEigenPod.activeValidatorCount
. This means that for future checkpoint proofs, you won't need to provide a proof for this validator.Added: Staleness Proofs
Added:
For the most part, pod owners won't need to think about this method - it's here to allow a third party to start checkpoints if a validator has been slashed on the beacon chain.
See Checkpointing Incentives and Beacon Chain Slashings above for further explanation.
Changed: Verifying Withdrawal Credentials
Removed:
EigenPodManager.beaconChainOracle()
EigenPodManager.updateBeaconChainOracle(...)
EigenPodManager.getBlockRootAtTimestamp(...)
Changed:
EigenPod.verifyWithdrawalCredentials(...)
oracleTimestamp
withinVERIFY_BALANCE_UPDATE_WINDOW_SECONDS (4.5 hours)
ofblock.timestamp
oracleTimestamp
parameter now corresponds to the next valid block after the beacon block root being used for proofs.currentCheckpointTimestamp
Context:
verifyWithdrawalCredentials
is seeing some minor changes to ensure it's compatible with the new checkpoint system. The interface will remain the same, but we've adjusted some of the conditions under which a withdrawal credential proof can be processed.In M2,
verifyWithdrawalCredentials
proofs:oracleTimestamp
added to a third-party-controlledbeaconChainOracle
oracleTimestamp
that was no older than 4.5 hours oldThese are both holdovers from our pre-Deneb system, which relied on
EigenPods
being supplied beacon chain block roots by a trusted party. However, we're in a post-Deneb world now, and EIP-4788 has us covered!We're removing the two requirements listed above. Instead,
verifyWithdrawalCredentials
proofs:block.timestamp
from the last 24 hours, as long as the timestamp is newer than thecurrentCheckpointTimestamp
block.timestamp
should correspond to the timestamp of the block that comes after the beacon block you're proving against! This is due to how EIP-4788 works.Validator
container being proven shows that the validator has anexit_epoch != FAR_FUTURE_EPOCH
Deprecated: Pre-M2 Pod Activation and
DelayedWithdrawalRouter
Removed:
EigenPod.activateRestaking
EigenPod.withdrawBeforeRestaking
EigenPod.withdrawNonBeaconChainETHBalanceWei
EigenPod.hasRestaked()
EigenPod.nonBeaconChainETHBalanceWei
EigenPod.mostRecentWithdrawalTimestamp
Changed:
EigenPod
no longer calls theDelayedWithdrawalRouter
DelayedWithdrawalRouter
will not be affected and will be claimable at their expected datesDelegationManager
withdrawal queue.Context - Pod Activation:
activateRestaking
was originally designed to allow pre-M2 pod owners to "opt in" to the M2 upgrade by choosing to callactivateRestaking
and enable proofs on their EigenPod. Likewise,withdrawBeforeRestaking
was design to allow pod owners that did not "opt in" to continue to withdraw consensus rewards without needing to participate in the M2 proof system.With checkpoint proofs, this separation between M1 and M2 pods is no longer important, as the checkpoint process directly supports both featuresets:
startCheckpoint
.verifyWithdrawalCredentials
,startCheckpoint
will auto-complete a checkpoint that awards the pod owner with shares equal to the pod's native ETH balance. These can be restaked or withdrawn via theDelegationManager
.Context - Removing
DelayedWithdrawalRouter
:Because both featuresets now involve a pod owner claiming shares and restaking/withdrawing those shares via the
DelegationManager
, we saw an opportunity to further simplify the EigenPod system by removing theDelayedWithdrawalRouter
entirely.This is highly desirable, as the
DelegationManager
queue is how the rest of EigenLayer's withdrawals are processed, and keeping theDelayedWithdrawalRouter
around means we have 2 separate paths for funds to leave the system.Since we're removing the
DelayedWithdrawalRouter
, its use viawithdrawNonBeaconChainETHBalanceWei
is also being removed.Deprecated: Misc
EigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR
Additional Notes
Compatibility with Pectra
The Pectra hard fork brings several major changes, the most important of which are laid out below.
1) MaxEB increase to 2048 ETH means validators can have much higher balances, and may have custom partial withdrawal sweep ceilings. The M2 proof system would struggle to support this because it tries to distinguish between partial/full withdrawals. However, checkpoint proofs don't care which is which!
2) Validator consolidation allows two validators (a
source
andtarget
) with the same withdrawal credentials to be consolidated. This sets thesource
validator's balance to 0, and thetarget
validator's balance totarget + source
.It's still TBD whether consolidation is initiated on the consensus layer, or the execution layer (details below). In the latter case, the
EigenPod
itself would expose methods to allow a pod owner to initiate consolidation.Generally, checkpoint proofs handle consolidation without issue. Because the
source
will have a balance of 0, a checkpoint proof will move it to theWITHDRAWN
state, and no further proofs will be required of it. Likewise, thetarget
validator's balance delta will simply include thesource
validator's previous balance, so the overall share delta shouldn't change.However, recounting that checkpoint proofs are only required for validators in the pod's
ACTIVE
validator set (verified withdrawal credentials + notWITHDRAWN
), there are a few important edge cases to consider:source: INACTIVE, target: ACTIVE
: if thesource
does not have verified withdrawal credentials, the change intarget's
balance will be picked up by the next checkpoint.source
consolidates intotarget
then callsstartCheckpoint
in the same epoch. Ifsource
then verifies withdrawal credentials using a timestamp afterstartCheckpoint
, but still in the same epoch, theeffective_balance
used in the withdrawal credential proof will not reflect consolidation.Validator
container state in any way. If, for example, it sets that validator'sexit_epoch
, then verifying withdrawal credentials in this case won't be possible and this is not a problem (verifyWithdrawalCredentials
reverts if the validator has anexit_epoch
)Validator
container, we'll need to changeverifyWithdrawalCredentials
to usecurrent_balance
, like the checkpoint system uses.source: ACTIVE, target: INACTIVE
: if thetarget
does not have verified withdrawal credentials, the change intarget's
balance will NOT be picked up by the next checkpoint.source
balance drop to 0, and no corresponding balance increase in the pod's ETH balance. It would seem like the ETH vanishes - and, likewise, the checkpoint system will deduct shares accordingly. This doesn't put the user's assets at risk, but for the future EigenLayer-slashing feature, we don't want stakers to be able to "temporarily remove" their slashable assets!verifyWithdrawalCredentials
to be callable by anyone (rather than just the pod owner), and to run watchers to monitor pods for this type of activity. It's not ideal, but there isn't really another option.The main question right now is whether consolidation is initiated at the consensus layer, or in the execution layer. In the former case, we'll need to do a few things:
verifyWithdrawalCredentials
to be callable by anyone, rather than just the pod ownersource -> target
consolidation wheretarget
isINACTIVE
.Validator
containers change due to consolidation, we may need to changeverifyWithdrawalCredentials
to usecurrent_balance
proofs in addition to theValidator
container proofs they currently use.If consolidation is initiated in the execution layer, we don't need to make any immediate changes! We can (at a later date) choose to add consolidation support to the
EigenPod
interface, and include the rules mentioned above to avoid their associated edge cases.Not Yet Mentioned
(probably incomplete) collection of things this doc hasn't explicitly mentioned - want to get yall a first look for the call today; we can talk about this stuff if you want!
effective_balance
proofs, as the latter results in ambiguity.validators
andcurrent_balances
trees, the new balance proofs will only require the latter. We don't need to examine theValidator
container after withdrawal credentials have been proven.verifyWithdrawalCredentials
and balance proof snapshots.WITHDRAWN
state if their snapshot balance proof shows a "current balance" of 0.podBalance
part of the checkpoint). We award it shares like anything else.