AlertSourceDiscuss
Skip to content
On this page

EIP-6466: SSZ Receipts Root

Migration of receipts MPT commitment to SSZ

⚠️ DraftCore

Draft Notice

This EIP is in the process of being drafted. The content of this EIP is not final and can change at any time; this EIP is not yet suitable for use in production. Thank you!

AuthorsEtan Kissling (@etan-status), Vitalik Buterin (@vbuterin)
Created2023-02-15

Abstract

This EIP defines a migration process of existing Merkle-Patricia Trie (MPT) commitments for receipts to Simple Serialize (SSZ).

Motivation

While the consensus ExecutionPayloadHeader and the execution block header map to each other conceptually, they are encoded differently. This EIP aims to align the encoding of the receipts_root, taking advantage of the more modern SSZ format. This brings several advantages:

  1. Reducing complexity: Merkle-Patricia Tries (MPT) are hard to work with. Replacing them with SSZ leaves only the state trie in the legacy MPT format.

  2. Better for smart contracts: The SSZ format is optimized for production and verification of merkle proofs. It allows proving specific fields of containers and allows chunked processing.

  3. Better for light clients: Light clients with access to the consensus ExecutionPayloadHeader no longer need to implement Merkle-Patricia Trie or RLP libraries to work with receipts.

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.

Consensus ExecutionPayload changes

A new Receipt SSZ container is introduced to represent receipts.

NameSSZ equivalent
TopicBytes32
TransactionTypeuint8
NameValueNotes
BYTES_PER_LOGS_BLOOMuint64(2**8) (= 256)Fixed constant
MAX_TOPICS_PER_LOGuint64(2**2) (= 4)LOG0 through LOG4 opcodes allow 0-4 topics per log
MAX_LOG_DATA_SIZEuint64(2**24) (= 16,777,216)Recommended devp2p soft limit for entire receipt: 2 MiB
MAX_LOGS_PER_RECEIPTuint64(2**20) (= 1,048,576)Same scaling factor as MAX_TRANSACTIONS_PER_PAYLOAD
python
class ReceiptLog(Container):
    address: Address
    topics: List[Topic, MAX_TOPICS_PER_LOG]
    data: ByteVector[MAX_LOG_DATA_SIZE]

class Receipt(Container):
    status: uint256  # EIP-658
    cumulative_transaction_gas_used: uint64
    logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
    logs: List[ReceiptLog, MAX_LOGS_PER_RECEIPT]

class TypedReceipt(Container):
    tx_type: TransactionType
    payload: Receipt

The consensus ExecutionPayload's receipts_root now refers to an SSZ Root instead of an MPT Hash32.

python
class ExecutionPayload(Container):
    ...
    receipts_root: Root
    ...

To compute the new receipts_root, the list of individual TypedReceipt containers is represented as a SSZ List.

python
payload.receipts_root = hash_tree_root(List[TypedReceipt, MAX_TRANSACTIONS_PER_PAYLOAD](
    receipt_0,
    receipt_1,
    receipt_2,
    ...
))

Consensus ExecutionPayloadHeader changes

The consensus ExecutionPayloadHeader is updated for the new ExecutionPayload.receipts_root definition.

python
class ExecutionPayloadHeader(Container):
    ...
    receipts_root: Root
    ...
python
payload_header.receipts_root = payload.receipts_root

Execution block header changes

The execution block header's receipts-root is updated to match the consensus ExecutionPayloadHeader.receipts_root.

Helpers

These helpers use TransactionType constants as defined in EIP-6404.

python
def encode_receipt(receipt: TypedReceipt) -> bytes:
    schema = (
        (big_endian_int, receipt.status),
        (big_endian_int, receipt.cumulative_transaction_gas_used),
        (Binary[256, 256], receipt.logs_bloom),
        (List([Binary[20, 20], List([Binary[32, 32]]), Binary[0, uint64(2**24)]]), [
            (log.address, log.topics, log.data) for log in receipt.logs
        ]),
    )
    sedes = List([schema for schema, _ in schema])
    values = [value for _, value in schema]
    encoded = rlp.encode(values, sedes)

    if receipt.tx_type == TRANSACTION_TYPE_EIP4844:
        return [0x05] + encoded
    if receipt.tx_type == TRANSACTION_TYPE_EIP1559:
        return [0x02] + encoded
    if receipt.tx_type == TRANSACTION_TYPE_EIP2930:
        return [0x01] + encoded
    if receipt.tx_type == TRANSACTION_TYPE_LEGACY:
        return encoded
    assert False
python
def decode_receipt(encoded_receipt: bytes) -> Receipt:
    eip2718_type = encoded_receipt[0]

    class RLPReceipt(rlp.Serializable):
        fields = (
            ('status', big_endian_int),
            ('cumulative_transaction_gas_used', big_endian_int),
            ('logs_bloom', Binary[256, 256]),
            ('logs', List([Binary[20, 20], List([Binary[32, 32]]), Binary[0, uint64(2**24)]])),
        )
    if eip2718_type == 0x05:
        tx_type = TRANSACTION_TYPE_EIP4844
        pre = RLPReceipt.deserialize(encoded_receipt[1:])
    elif eip2718_type == 0x02:
        tx_type = TRANSACTION_TYPE_EIP1559
        pre = RLPReceipt.deserialize(encoded_receipt[1:])
    elif eip2718_type == 0x01:
        tx_type = TRANSACTION_TYPE_EIP2930
        pre = RLPReceipt.deserialize(encoded_receipt[1:])
    elif 0xc0 <= eip2718_type <= 0xfe:
        tx_type = TRANSACTION_TYPE_LEGACY
        pre = RLPReceipt.deserialize(encoded_receipt)
    else:
        assert False

    return Receipt(
        status=pre.status,
        cumulative_transaction_gas_used=pre.cumulative_transaction_gas_used,
        logs_bloom=pre.logs_bloom,
        logs=[ReceiptLog(
            address=log[0],
            topics=log[1],
            data=log[2],
        ) for log in pre.logs],
    )

Rationale

This aligns the receipt representation with the EIP-6404 transaction representation.

What about ReceiptLog data?

ReceiptLog data is formatted according to the Ethereum contract ABI. Merkleizing log data according to its original structure would be more useful than merkleizing it as a ByteVector. However, the data structure is determined by the log event signature, of which only the hash is known. As the hash preimages are erased from emitted EVM logs, it is not reliably possible to recover the original log event signature. Therefore, log data is provided as a ByteVector for now, with the option for a future EIP to extend it.

Backwards Compatibility

Applications that solely rely on the TypedReceipt RLP encoding but do not rely on the receipts_root commitment in the block header can still be used through a re-encoding proxy.

Applications that rely on the replaced MPT receipts_root in the block header can no longer find that information.

Receipts are tied to transactions, so are typically tied to either the transaction hash, or to a block hash + sequential transaction index. At time of writing, there is no JSON-RPC endpoint to obtain a receipts proof. It is not expected that major applications rely on the Merkle-Patricia Trie commitment for receipts.

Test Cases

TBD

Reference Implementation

TBD

Security Considerations

None

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Etan Kissling, Vitalik Buterin, "EIP-6466: SSZ Receipts Root[DRAFT]," Ethereum Improvement Proposals, no. 6466, 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6466.