# [EIP-7495](https://eips.ethereum.org/EIPS/eip-7495) Notes for CL There are two groups of EIPs that affect the CL. - "CL owned" data structures - [EIP-7688](https://eips.ethereum.org/EIPS/eip-7688): Forward compatible consensus data structures - "EL owned" data structures - [EIP-6493](https://eips.ethereum.org/EIPS/eip-6493): SSZ Transaction Signature Scheme - [EIP-6404](https://eips.ethereum.org/EIPS/eip-6404): SSZ Transactions Root - [EIP-6466](https://eips.ethereum.org/EIPS/eip-6466): SSZ Receipts Root - [EIP-6465](https://eips.ethereum.org/EIPS/eip-6465): SSZ Withdrawals Root 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 new`StableContainer` 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](https://eips.ethereum.org/EIPS/eip-6475), 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: ```python 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 a`Container`, 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 `Variant`s of `StableContainer`s. 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. `Variant`s 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`. <!-- `BeaconStateElectra` and other beacon state variants would be used --> --- 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](https://eips.ethereum.org/EIPS/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