Try   HackMD

EIP-7495 Notes for CL

There are two groups of EIPs that affect the CL.

  • "CL owned" data structures
    • EIP-7688: Forward compatible consensus data structures
  • "EL owned" data structures

For supporting for CL datastructures (EIP-7688), the implementation burden is fairly low. There's no need to fully implement "full" StableContainer and "full" Variant support as specified, rather we only need to implement a subset of Variant. This simplified Variant has nearly identical functionality as Container, the only difference being a slightly modified hashTreeRoot.

For supporting new EL datastructures, implementation burden is higher. "Full" support for StableContainer is required.

Simplified StableContainer Explainer for EIP-7688

Goal: explain the StableContainer EIP step-by-step with an emphasis on EIP-7688

EIP-7495 defines newStableContainer and Variant SSZ primitives.

The primary mechanism for providing "stability" (stability of merkleization) is the use of an "active fields" bitvector. Fields that are in use are toggled on, and fields that are not in use are toggled off. A StableContainer is also defined with a maximum number of fields. As opposed to EIP-6475 - OptionalType, which wraps each type individually, StableContainer uses the "active fields" bitvector to manage whether its fields are Some or None.

Merkleization is similar to Container taking into account the active fields. Fields are merkleized at the depth determined by the maximum number of fields. Inactive fields are merkleized as 0x0, and active fields are merkleized normally. Then, the active fields bitvector is "mixed in" to the root, similarly to how the length is mixed-in to an SSZ List.

Small example:

class Example(StableContainer[4]):
    a: Optional<uint64>
    b: Optional<uint64>

merkleized as

         *
       /   \
      *    0b11 + 0x0
    /
   *
  / \
 a   b

Serialization is similar to Container taking into account the active fields. The active field bitvector is prepended. Then, the fields are serialized like aContainer, with the exception that inactive fields are skipped over entirely.

For the purposes of EIP-7688, what we've been calling a StableContainer is actually a StableContainer Variant. "Lets turn the BeaconState into a StableContainer Variant"

StableContainer can be thought of an abstract data type that exists across forks. It is a template for merkleization. It is not meant to be used directly by the state transition, networking, etc. Rather, we will be operating on specific Variants of StableContainers.

A Variant of a StableContainer is a specific subset of fields from the StableContainer. It forgoes stable serialization (across variants) in favor of serialization rules that make it compatible with normal Container serialization. Merkleization still includes the active fields bitvector.

Variants are useful for enhanced type safety and simpler (and slightly cheaper) serialization.

For example, for EIP-7688, we define a StableContainer for the beacon state, lets call it AbstractBeaconState, which defines the beacon state shape across all forks, electra onwards. Then we define fork-specific BeaconStateElectra which only contains the fields relevant to that fork. This will be what we operate on in the spec and likely in our code.
At each future fork that updates the beacon state fields, either adds or deprecates a field, AbstractBeaconState is updated with new fields, and a new Variant will be defined that only includes the relevant fields.

Within the beacon node, AbstractBeaconState would solely be used as a merkleization template. All beacon state variants would use the layout described by AbstractBeaconState for merkleization. For example, BeaconStateElectra is merkleized as an AbstractBeaconState.


This explanation only treats a subset of features of StableContainer and Variant tailored for EIP-7688. Specifically it ignores handling of optional fields in both StableContainer and Variant.

Notes from Implementing 7688 in Lodestar

StableContainer

  • Started with copy/pasting Container implementation
  • Optional fields are handled at the container level. Our SSZ library already has SSZ Optional EIP-6475 implemented. So, we can either make a new way to declare optionals in StableContainers or we can leverage existing Optional types. We decided to leverage existing Optional types as this makes the UX of creating StableContainers more straightforward. This looks like StableConainer "pulling out" the type from Optional<type> for any optional field.
  • Update variable-field offset calculation. We were previously "precomputing" variable-field offsets. This can't be done anymore, computing offsets requires the active-fields bitvector.
  • Update precomputed min and max serialized length calculation

Variant

  • Starting with a simplified version of Variant as described above (ignoring potential field reordering during merkleization and also ignoring optional fields)
    • this subset of functionality will suffice for EIP-7688
  • Started with copy/pasting Container implementation
  • simplified version has a static active fields bitvector, passed into the type constructor
  • in constructor, store the map of fields to the 'chunk index' of the field as determined by the active fields bitvector
  • update maxChunksCount to activeFields.length
  • update hashTreeRoot to mix-in active fields
  • update getRoots to intersperse zero hashes for inactive fields