# SSZ transition implementation guide ## M1: `StableContainer` **Spec: [EIP-7495: SSZ StableContainer](https://eips.ethereum.org/EIPS/eip-7495)** **Tests: [ethereum/EIPs](https://github.com/ethereum/EIPs/blob/master/assets/eip-7495/tests.py)** For EL, `StableContainer` is required but can skip `Variant` and `OneOf` initially. `StableContainer[N]` merkleizes same as regular `Container`, but the Merkle tree is padded up to `nextpow2(N)` members. When serializing `StableContainer[N]`, a `Bitvector[N]` is prefixed with 1/0 denoting presence/absence of fields, in the order that they are specified. Absent fields are subsequently skipped when encoding the container. When parsing, first read the `Bitvector[N]` and then use it to read the present fields in order. Ensure that extra bits referring to unknown fields are set to 0, and that no data is left after reading the buffer. Implementation notes: https://hackmd.io/Swl71tWeS7WP_f4D8abKHA **Goals:** - Pass tests for `StableContainer` **Reference implementations:** - [protolambda/remerkleable](https://github.com/protolambda/remerkleable/pull/22) - [status-im/nim-ssz-serialization](https://github.com/status-im/nim-ssz-serialization/pull/84/files) ## M2: `TransactionSignature` **Spec: [EIP-6493: SSZ Transaction Signature Scheme](https://eips.ethereum.org/EIPS/eip-6493#transaction-validation)** 1. Split `Transaction` into two pieces, `TransactionPayload` (unsigned transaction) and `TransactionSignature` (v/r/s). This split is core to the SSZ representation. Reconstruct the RLP representation during serialization. 2. If you have both a pre-[EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) `gasPrice` and post-[EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) `max_fee_per_gas` fields, get rid of the `gasPrice` one and store it in `max_fee_per_gas` instead. Pass the `effective_gas_price` out-of-bands or compute it on demand (needs base fee from the corresponding block). 3. Switch transaction signatures from `v/r/s` to `y_parity/r/s`. Reconstruct `v` from `chain_id` + `y_parity` on demand. 4. Implement the `ecdsa_pack_signature` and `ecdsa_unpack_signature` functions from EIP-6493, and store the packed version instead of `y_parity/r/s`. Reconstruct `y_parity/r/s` on demand. 5. Extend `TransactionSignature` with `from_address`. Fill it in lazily when the sender is computed using `ecrecover`. Read from that `from_address` field instead of computing `ecrecover` in multiple locations. **Goals:** - Have signatures in the format that's close to the SSZ representation. This is also the native format within crypto libraries. **Reference implementation:** - [ethereum/eips](https://eips.ethereum.org/EIPS/eip-6493#transaction-validation) ## M3: `TransactionPayload` **Spec: [EIP-6493: SSZ Transaction Signature Scheme](https://eips.ethereum.org/EIPS/eip-6493#ssz-signedtransaction-container)** **Tests: [ethereum/EIPs](https://github.com/ethereum/EIPs/blob/master/assets/eip-6493/convert_tests.py)** 1. Implement the conversion routines from RLP --> SSZ ([Reference implementation](https://github.com/ethereum/EIPs/blob/master/assets/eip-6493/convert.py)). 2. Implement the conversion routines from SSZ --> RLP ([Reference implementation](https://github.com/ethereum/EIPs/blob/master/assets/eip-6493/tx_hashes.py)) 3. Implement the `compute_sig_hash` and `compute_tx_hash` functions ([Reference implementation](https://github.com/ethereum/EIPs/blob/master/assets/eip-6493/tx_hashes.py#L105)) 4. Ensure that all hash computation goes through `compute_sig_hash` (unsigned `TransactionPayload`) and through `compute_tx_hash` (signed `Transaction`) 5. Ensure that the devp2p protocol handlers support custom serialization / deserialization via the SSZ --> RLP and RLP --> SSZ conversion functions. **Goals:** - Still be interoperable with unchanged clients; no semantic changes - Have all functionality to convert transactions between RLP <-> SSZ formats ## M4: EL block building **Spec: [EIP-6404: SSZ Transactions Root](https://eips.ethereum.org/EIPS/eip-6404)** 1. When constructing EL blocks, replace the `transactions_root` computation with one based on SSZ. 2. When sending the payload to the CL, continue sending as RLP for now. **Goals:** - Replace the MPT while still being compatible with a stock CL implementation (no engine API changes) ## Stretch goal: Receipts **Spec: [EIP-6493: SSZ Transaction Signature Scheme](https://eips.ethereum.org/EIPS/eip-6493#ssz-receipt-container)** **Spec: [EIP-6466: SSZ SSZ Receipts Root](https://eips.ethereum.org/EIPS/eip-6466)** The overall process is similar to transactions, the following notable changes exist: 1. The receipt now contains `gas_used` instead of `cumulative_gas_used`. The SSZ <-> RLP converters have to be on lists of receipts instead of individual receipts. 2. The receipts now contains the `contract_address`, so the transactions list must be available before receipts can be converted. ## Stretch goal: Withdrawals **Spec: [EIP-6465: SSZ SSZ Withdrawals Root](https://eips.ethereum.org/EIPS/eip-6465)** Similar to transactions / receipts, except no changes to data structures should be needed. ## Stretch goal: JSON-RPC 1. When sending transactions via JSON-RPC, add a `root` field that contains the `hash_tree_root(transaction)` next to the existing `hash` field. 2. Add an `eth_getTransactionByRoot` endpoint that allows lookup by root instead of hash. Also allow lookup of receipt by tx root. 3. Lateron, an additional field with an inclusion proof will be added to this endpoint that proofs that `root` is included in `transactions_root`. ## Stretch goal: Engine API 1. Change the transaction encoding for engine to be no longer opaque but instead encode the backing SSZ `Transaction` as JSON using its canonical encoding. 2. This requires a CL that also supports EIP-6493. Before this stretch goal, a stock CL can be used. ## Stretch goal: SSZ `BasicTransaction` / `BlobTransaction` * New EIP-2718 transaction type * When validating them, have to check that the received `from_address` matches the `ecrecover` value. The `from_address` is exchanged on the network. * Encodes in SSZ on the network, no RLP <-> SSZ conversion necessary anymore * Has the same `hash` and `root` value.