-
-
Published
Linked with GitHub
# peep-an-eip: validator withdrawals
## draft / outline
goal:
- resource to explain how validator withdrawals will work and what users/validators need to do to execute them
- give some narrative about how we got here
## todo
- logging
- eth1 analysis
- https://notes.ethereum.org/@djrtwo/0x01-payable
# outline
- what do we mean “validator withdrawals”?
- warning: generally assuming post-merge
- ethereum is a proof-of-stake network
- consensus layer
- execution layer
- consensus layer: separate layer of the protocol from the EVM execution environment
- actors called “validators” form consensus by validating the chain and attesting to its correctness
- necessary for consensus security that validators lock up some ETH
- the “stake”
- some analogy to a “principal” of a loan
- currently 32 ETH
- if you do a good job validating, then rewards accrue
- some analogy to “interest”
- how to move ETH back to execution environment?
- either bc
- you are done validating?
- or just want to move your rewards?
- designing a solution to this problem: validator withdrawals
- how to implement?
- first, when is a validator allowed to withdraw?
- second, how is that signaled from the consensus layer to execution layer?
- (as it originates on CL, need to signal to EL somehow where effects take place)
- third, where does the actual ETH come from?
- as new ETH is created at the consensus layer
- when withdraw?
- need some consensus conditions met
- ensure validators remain accountable
- but assuming a withdraw would still keep incentive for a validator to be honest
- can withdraw the extra funds
- being generic here, bc you can have “full” or “partial” withdrawals
- “all of the stake”
- or just “the rewards”
- examples:
- validator has successfully “exited” the active validator set
- both an exit queue *and* some delay to allow for catching “trailing slashing”
- they can withdraw all of their balance
- validator is active but has some balance over the “consensus stake weight” of 32 ETH
- assuming it is *safe* to withdraw a validator’s ETH, just make a note of it in the beacon state
- “put a receipt into some list of receipts”
- the task then becomes:
- how to make EL aware of the receipts in this list?
- important aside for validators:
- will need to change withdrawal credentials from `0x00` to `0x01`
- indicates how withdrawals can be authenticated
- move from BLS signature (e.g. cold storage key protecting stake) to any execution address
- open pr: https://github.com/ethereum/consensus-specs/pull/2855
- how to signal?
- lots of possible designs
- big decision point:
- “pull” vs. “push” semantics
- another decision point:
- how to credit the ETH in the EVM state?
- analogy: pile of ETH
- do you want to “pull” it from the consensus layer into the execution layer
- or, do you want to have it “pushed” to you from the consensus layer to the execution layer
- intuition here: someone else “does the work”
- what does pull look like?
- as the execution layer owner (could be a smart contract) of the validator’s funds, I initiate a regular transaction that tells the system: “I would like to consume the i’th withdrawal in the list”
- the “system” needs to know what the list is
- and also to track which withdrawals in this list have been consumed
- otherwise you could double-spend withdrawals and print eth :(
- how does the “system” learn?
- well, luckily, the beacon state has a cryptographic commitment
- Merkle tree structure with SSZ
- so can make proofs about receipts in the beacon state, IF this state root is exposed to the EL
- this was the intent of https://eips.ethereum.org/EIPS/eip-4788
- as we will see, ended up going the “push” route, so no immediate need for this EIP
- although lots of other cool applications becomes possible if the EVM gets the state root, so I’d like to see sooner or later
- how does the “system” keep track?
- a few options on structure but ultimately, *somewhere* in the EVM keep some state, e.g. a bitfield to track which receipts in the withdrawal list have already been consumed
- “stateful precompile”
- no prior precedent
- how to credit ETH?
- assume a stateful precompile:
- either pull ETH “out of thin air”
- but again, no real precedent other than awarding the block subsidy to coinbase
- a “quick and dirty” idea:
- the precompile could have `MAX_WEI` eth and just pull down from this “endowment”
- just looks like a normal balance transfer from a smart contract, this happens all the time
- what would it look like end-to-end?
- user: here is the receipt at index `i` that says I am owed X eth from a withdrawal and a proof that it is a valid withdrawal against the latest beacon state root
- system (e.g. precompile): let me verify the proof, and make sure this index has not already been redeemed. If it all looks good, then I will send you the ETH (perhaps out of my huge endowment)
- ok, so this would work but:
- “stateful precompile”
- entirely new EVM concept
- would complicate testing, etc.
- esp if this thing has `MAX_WEI` and or arbitrary “credit” powers
- scheduling is on the user
- they pay gas and take up block space from other users
- so for these reasons, current favored solution is the “push” style semantics
- what does push look like?
- rather than a simple list in the beacon state, withdrawals are now put into a *queue*
- and the consensus layer becomes responsible for *dequeuing* withdrawals at a certain rate and placing them into the execution payload
- so now, the execution layer (which already does a lot)
- does not have to track which withdrawals have been consumed
- can use block space for other transactions
- doesn’t need to manage synchronizing the beacon state root into the EVM somehow
- doesn’t need some novel “stateful precompile” concept
- from the EL perspective, much simpler — just apply the balance increases from the withdrawals
- very much like we already do w/ coinbase transactions
- to answer the prior key questions for this stage of the process:
- how does the system learn?
- it just processes the payload (like it already does)
- how does the system keep track?
- it doesn’t, this logic is now handled by the consensus layer
- better division of responsibilites
- how to credit ETH?
- again, a few options you can imagine but simplest is to just treat like a coinbase “transaction”
- what does it look like end-to-end?
- user: does nothing
- system: when I encounter a withdrawal in the execution payload, I just increase the balance according to the details in the receipt
- get us to where we are now:
- push-style withdrawals, managed by the CL, processed by the EL
- one dangling question: how are withdrawals structured in the execution payload?
- the CL will have some structure by definition of the SSZ schema (whatever goes into the beacon state)
- the interface to the EL (engine API) can have whatever schema as long as it gets the relevant data across
- but, we want the EL to be able to independently process execution payloads w/o having to ask the consensus layer for data
- e.g. if the EL is syncing
- so need some way to structure the withdrawals
- how to structure withdrawals at EL?
- again, “quick and dirty” and a much cleaner solution
- “quick and dirty”
- abuse EIP-2718-style transaction types
- just make a new type that has special semantics
- this was the approach in https://eips.ethereum.org/EIPS/eip-4863
- “cleaner” solution
- new type of object: “operation”
- looks like a transaction but is firewalled from EVM concerns
- which we want
- main problem w/ new txn type is that you now need special logic for processing a common concept, the transaction, but this new type of transaction doesn’t touch the actual virtual machine, unlike all other transaction types to date
- also this new transaction type can “credit” ETH, which is also substantially different from existing transaction types
- e.g. coinbase is not “written” down as a transaction anywhere
- so, instead, prefer the much cleaner idea of “operation”
- new type of thing that lives alongside transactions but it fundamentally different
- bonus: symmetry to how many rollups structure themselves, so can re-use code/concepts at several layers of the stack
- one final feature to look at, now that we have the high-level pipeline in place
- partial withdrawals
- can "skim off" excess ETH over the "consensus stake weight" of 32 ETH
- will happen automatically when it is secure to do so and a given validator has excess balance
- rotate over the entire val set to check every N epochs
- once some ETH has been identified as withdrawable, it is put into a withdrawal receipt just like a full withdrawal
- rest of the process is the same from there
- open PR: https://github.com/ethereum/consensus-specs/pull/2862
- still some fine-tuning to do around exact parameters, etc.
- tl;dr
- CL changes: https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md
- EL changes: EIP-4895: Beacon chain push withdrawals as operations
- https://eips.ethereum.org/EIPS/eip-4895
- as a user / validator, you don’t need to do anything once you have switched to 0x01 credentials
- withdrawn eth will simply appear where you specify, when the requisite conditions are met
- e.g. no special “withdraw” operation, on top of your exit
- when?
- CFI for Shanghai (fork after the Merge)
- https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md