EIP-7688: Forward compatible consensus data structures

Transition consensus SSZ data structures to StableContainer


Metadata
Status: ReviewStandards Track: CoreCreated: 2024-04-15
Authors
Etan Kissling (@etan-status), Cayman (@wemeetagain)

Abstract


This EIP defines the changes needed to adopt StableContainer from EIP-7495 in consensus data structures.

Motivation


Ethereum's consensus data structures make heavy use of Simple Serialize (SSZ) Container, which defines how they are serialized and merkleized. The merkleization scheme allows application implementations to verify that individual fields (and partial fields) have not been tampered with. This is useful, for example, in smart contracts of decentralized staking pools that wish to verify that participating validators have not been slashed.

While SSZ Container defines how data structures are merkleized, the merkleization is prone to change across the different forks. When that happens, e.g., because new features are added or old features get removed, existing verifier implementations need to be updated to be able to continue processing proofs.

StableContainer, of EIP-7495, is a forward compatible alternative that guarantees a forward compatible merkleization scheme. By transitioning consensus data structures to use StableContainer, smart contracts that contain verifier logic no longer have to be maintained in lockstep with Ethereum's fork schedule as long as the underlying features that they verify don't change. For example, as long as the concept of slashing is represented using the boolean slashed field, existing verifiers will not break when unrelated features get added or removed. This is also true for off-chain verifiers, e.g., in hardware wallets or in operating systems for mobile devices that are on a different software update cadence than Ethereum.

Specification


The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Conversion procedure

For each converted data structure, a new fork agnostic StableContainer type B is introduced that serves as the primary definition of each data structure.

  • Each StableContainer is assigned a capacity to represent its potential design space that SHALL NOT change across future forks; if it is later determined that it is insufficient, a new field can be added to contain additional fields in a sub-container.
  • The StableContainer starts as a copy of the latest fork's Container equivalent, except that all field types T are wrapped into Optional[T].
  • To guarantee forward and backward compatibility, new fields from future forks MUST only be appended to the StableContainer definition.
  • The type of existing fields MUST NOT change, including the capacity of List/Bitlist. If a change is necessary, the old field SHALL NOT be used anymore and a new field with a new name SHALL be considered. It is important to anticipate potential future extensions when deciding on the capacities of the StableContainer itself and of the various lists.
  • For List/Bitlist, the opportunity SHOULD be used to re-evaluate their design space capacity. If the design space is increased, application logic SHALL check the fork specific length limit; the SSZ library solely defines the merkleization limit, not the serialization limit.
  • The conversion process is repeated for each field type. All field types referred to by the StableContainer MUST be StableContainer themselves, or are considered immutable.

Subsequently, for each StableContainer base type B, a fork specific Profile[B] type is introduced that matches the latest fork's Container equivalent. The old Container is no longer necessary. The SSZ serialization of Profile is compatible with Container, but the merkleization and hash_tree_root are computed differently. Furthermore, Profile MAY use fields of Optional type if necessary.

Subsequent forks specify a new Profile.

  • If new fields of type T are added, they are appended to the StableContainer as Optional[T] to register them with the stable merkleization scheme. In the new fork's Profile, the new field MAY be T (required), or Optional[T] (optional).
  • If old fields are deprecated, they are kept in the StableContainer to retain the stable merkleization scheme. In the new fork's Profile, the field is omitted from the definition. The SSZ library guarantees that hash_tree_root and all generalized indices remain the same.
  • Other fields MAY be changed between T (required) and Optional[T] (optional) in the new fork's Profile. No changes to the StableContainer are required for such changes.

Immutable types

These types are used as part of the StableContainer definitions, and, as they are not StableContainer themselves, are considered to have immutable Merkleization. If a future fork requires changing these types in an incompatible way, a new type SHALL be defined and assigned a new field name.

TypeDescription
SlotSlot number on the beacon chain
EpochEpoch number on the beacon chain, a group of slots
CommitteeIndexIndex of a committee within a slot
ValidatorIndexUnique index of a beacon chain validator
GweiAmount in Gwei (1 ETH = 10^9 Gwei = 10^18 Wei)
RootByte vector containing an SSZ Merkle root
Hash32Byte vector containing an opaque 32-byte hash
VersionConsensus fork version number
BLSPubkeyCryptographic type representing a BLS12-381 public key
BLSSignatureCryptographic type representing a BLS12-381 signature
KZGCommitmentG1 curve point for the KZG polynomial commitment scheme
ForkConsensus fork information
CheckpointTuple referring to the most recent beacon block up through an epoch's start slot
ValidatorInformation about a beacon chain validator
AttestationDataVote that attests to the availability and validity of a particular consensus block
Eth1DataTarget tracker for importing deposits from transaction logs
DepositDataLog data emitted as part of a transaction's receipt when depositing to the beacon chain
BeaconBlockHeaderConsensus block header
ProposerSlashingTuple of two equivocating consensus block headers
DepositTuple of deposit data and its inclusion proof
VoluntaryExitConsensus originated request to exit a validator from the beacon chain
SignedVoluntaryExitTuple of voluntary exit request and its signature
SyncAggregateCryptographic type representing an aggregate sync committee signature
ExecutionAddressByte vector containing an account address on the execution layer
TransactionByte list containing an RLP encoded transaction
WithdrawalIndexUnique index of a withdrawal from any validator's balance to the execution layer
WithdrawalWithdrawal from a beacon chain validator's balance to the execution layer
DepositRequestTuple of flattened deposit data and its sequential index
WithdrawalRequestExecution originated request to withdraw from a validator to the execution layer
ConsolidationRequestExecution originated request to consolidate two beacon chain validators
BLSToExecutionChangeRequest to register the withdrawal account address of a beacon chain validator
SignedBLSToExecutionChangeTuple of withdrawal account address registration request and its signature
ParticipationFlagsParticipation tracker of a beacon chain validator within an epoch
HistoricalSummaryTuple combining a historical block root and historical state root
PendingBalanceDepositPending operation for depositing to a beacon chain validator
PendingPartialWithdrawalPending operation for withdrawing from a beacon chain validator
PendingConsolidationPending operation for consolidating two beacon chain validators

StableContainer capacities

NameValueDescription
MAX_ATTESTATION_FIELDSuint64(2**3) (= 8)Maximum number of fields to which StableAttestation can ever grow in the future
MAX_INDEXED_ATTESTATION_FIELDSuint64(2**3) (= 8)Maximum number of fields to which StableIndexedAttestation can ever grow in the future
MAX_EXECUTION_PAYLOAD_FIELDSuint64(2**6) (= 64)Maximum number of fields to which StableExecutionPayload can ever grow in the future
MAX_EXECUTION_REQUESTS_FIELDSuint64(2**4) (= 16)Maximum number of fields to which StableExecutionRequests can ever grow in the future
MAX_BEACON_BLOCK_BODY_FIELDSuint64(2**6) (= 64)Maximum number of fields to which StableBeaconBlockBody can ever grow in the future
MAX_BEACON_STATE_FIELDSuint64(2**7) (= 128)Maximum number of fields to which StableBeaconState can ever grow in the future

Maximum proof depth:

  • StableBeaconState > validators (1 + 7) > <item> (1 + 40) > pubkey (3) > <chunk> (1) = 53 bits
  • StableBeaconBlockBody > execution_payload (1 + 6) > transactions (1 + 6) > <item> (1 + 20) > <chunk> (1 + 25) = 61 bits

Fork-agnostic StableContainer definitions

These type definitions are fork independent and shared across all forks. They are not exchanged over libp2p.


Fork-specific Profile definitions

The consensus type definitions specific to the fork that introduces this EIP are updated to inherit the Merkleization of the StableContainer definitions. Fields are kept as is.


Rationale


Best timing?

Applying this EIP breaks hash_tree_root and Merkle tree verifiers a single time, while promising forward compatibility from the fork going forward. It is best to apply it before merkleization would be broken by different changes. Merkleization is broken by a Container reaching a new power of 2 in its number of fields.

Can this be applied retroactively?

While Profile serializes in the same way as the legacy Container, the merkleization and hash_tree_root of affected data structures changes. Therefore, verifiers that wish to process Merkle proofs of legacy variants still need to support the corresponding legacy schemes.

Immutability

Once a field in a StableContainer has been published, its name can no longer be used to represent a different type in the future. This includes list types with a higher capacity than originally intended. This is in line with historical management of certain cases:

  • Phase0: BeaconState contained previous_epoch_attestations / current_epoch_attestations
  • Altair: BeaconState replaced these fields with previous_epoch_participation / current_epoch_participation

Furthermore, new fields have to be appended at the end of StableContainer. This is in line with historical management of other cases:

  • Capella appended historical_summaries to BeaconState instead of squeezing the new field next to historical_roots

With StableContainer, stable Merkleization requires these rules to become strict.

Backwards Compatibility


Existing Merkle proof verifiers need to be updated to support the new Merkle tree shape. This includes verifiers in smart contracts on different blockchains and verifiers in hardware wallets, if applicable.

Note that backwards compatibility is also broken when one of the converted Container data structures would reach a new power of 2 in its number of fields.

Security Considerations


None

Copyright


Copyright and related rights waived via CC0.