EIP-4895: Beacon chain withdrawals as system-level operations

the index starts at 0 and increments by 1 for each withdrawal.

the withdrawals are automatically made at the consensus layer where the logic to do this is enforced.

withdrawal indices will never duplicate – so you couldn’t get into the [0,1,2] then [0,1] situation you describe.

you have no (direct) control over the index so we will never see uint64.maxNumber - 1

This should be indicated in the specification. Right now it only says:

  • a monotonically increasing index as a uint64 value that uniquely identifies each withdrawal

It should be updated to include that this number starts at 0 for the very first withdraw, and each withdraw after that has an index that is 1 more than the previous withdraw.

It may also be valuable to note that this index is global, not per-block.

Yes, this was exactly my point, since indeed monotonically increasing does not mean that it +1s for each withdrawal, it can increase with any number (integer). And you are right, it should also specify that it should start at 0.

Just to verify: this monotonically increasing the withdrawal indices which start at 0 should not be checked by the execution layer, because it is enforced by the consensus layer?

EDIT: other question: to compute the withdrawals root, I assume we put (index, withdrawalRLP) in the trie, where withdrawalRLP is the RLPd version of the withdrawal in question?

I have started implementing this in EthereumJS: Implement EIP4895: Beacon Chain withdrawals by jochem-brouwer · Pull Request #2353 · ethereumjs/ethereumjs-monorepo · GitHub

because it is enforced by the consensus layer?

correct, the EL only needs to apply the balance update – the content has already been verified by the CL.

I assume we put (index, withdrawalRLP) in the trie, where withdrawalRLP is the RLPd version of the withdrawal in question?


1 Like

I have added some extra context around the index value here: Update EIP-4895: `validator_index` and more info around `index` by ralexstokes · Pull Request #5835 · ethereum/EIPs · GitHub

The proposed withdrawal_root does not match the one that is currently included in the consensus specs ExecutionPayloadHeader.

In the CL:

withdrawals_root = hash_tree_root([SSZ.encode(Withdrawal), ...])

In the proposed EIP:

withdrawals_root = hexary_trie_root([RLP.encode(Withdrawal), ...])

Given that withdrawals_root and the Withdrawal structure are new additions, I’m wondering if it would be possible to use a consistent tree style across the two specs.

This would enable the EL block header to be derived from the CL ExecutionPayloadHeader, enabling light clients that are subscribed to the CL light clients gossip to follow the chain without requiring any additional network requests. Network requests would only be needed when encountering a block of interest via logs_bloom, or when requesting a state or transaction inclusion proof.

I have explored three other approaches to solve this inconsistency across EL and CL root formats for light clients.

  1. Including hexary transactions_root and withdrawals_root into the ExecutionPayload
    • This would require extensions to the BeaconBlock and BeaconState, hence is more invasive than it should be, just to make life easier for light clients.
    • May interfere with future changes to the trie format (statelessness / verkle tries).
  2. Have the CL implementation re-compute hexary transactions_root and withdrawals_root from the beacon block data when providing light client data.
    • This would require CL implementations to add software for hexary trie computation. Given that SSZ tends to be seen as more modern than hexary tries, it would be a step into the wrong direction.
    • May interfere with split block storage designs, because the CL would no longer have the raw transactions and withdrawals that are needed to compute the hexary trie root hashes. The CL may need to fetch that info via eth_getBlockByRoot(..., includeTransactions: false), and that call may compete with resources needed for ongoing validator duties or fork choice processing.
  3. Only provide the other ExecutionPayloadHeader fields to the light client, omitting the hexary transactions_root and withdrawals_root.
    • This would require light clients to issue a second network request to obtain those hexary trie roots, which are needed to validate inclusion of transactions. It would be great if light clients could follow the chain without additional network requests to individual peers, just by following gossip on light client topics (or the event stream from REST).
    • May introduce complexity by having not only full blocks and block headers, but also a partial block headers concept.

Hence, the request here, whether withdrawals_root and Withdrawals could be modernized with a change to SSZ. SSZ libraries should already be available for all programming languages used by major EL implementations. Going forward, new roots should become consistent across both the EL and CL.

Note that transactions_root is the other blocker currently preventing to convert ExecutionPayloadHeader to an EL block header. The situation is different, though, because the individual transactions are also RLP encoded in the CL. So, only the hexary transactions_root would need to be converted to a SSZ root, but not the individual transactions. If that is not feasible given the long-term existence of this field, the ExecutionPayload could be extended with the hexary transactions_root as a one-time exception.