Skip to content

Commit

Permalink
Update EIP-4844: de-sszify spec
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
lightclient committed May 16, 2023
1 parent 25ceb14 commit 8703d20
Showing 1 changed file with 43 additions and 121 deletions.
164 changes: 43 additions & 121 deletions EIPS/eip-4844.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,93 +101,28 @@ def fake_exponential(factor: int, numerator: int, denominator: int) -> int:

### New transaction type

We introduce a new [EIP-2718](./eip-2718.md) transaction type,
with the format being the single byte `BLOB_TX_TYPE` followed by an SSZ encoding of the
`SignedBlobTransaction` container comprising the transaction contents:
We introduce a new [EIP-2718](./eip-2718.md) transaction, "blob transaction", where the `TransactionType` is `BLOB_TX_TYPE` and the `TransactionPayload` is the following RLP value:

```python
class SignedBlobTransaction(Container):
message: BlobTransaction
signature: ECDSASignature

class BlobTransaction(Container):
chain_id: uint256
nonce: uint64
max_priority_fee_per_gas: uint256
max_fee_per_gas: uint256
gas: uint64
to: Union[None, Address] # Address = Bytes20
value: uint256
data: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_fee_per_data_gas: uint256
versioned_hashes: List[VersionedHash, MAX_VERSIONED_HASHES_LIST_SIZE]

class AccessTuple(Container):
address: Address # Bytes20
storage_keys: List[Hash, MAX_ACCESS_LIST_STORAGE_KEYS]

class ECDSASignature(Container):
y_parity: boolean
r: uint256
s: uint256
```
rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_data_gas, blob_versioned_hashes, y_parity, r, s])`.
```

The `max_priority_fee_per_gas` and `max_fee_per_gas` fields follow [EIP-1559](./eip-1559.md) semantics,
and `access_list` as in [`EIP-2930`](./eip-2930.md).

[`EIP-2718`](./eip-2718.md) is extended with a "wrapper data", the typed transaction can be encoded in two forms, dependent on the context:

- Network (default): `TransactionType || TransactionNetworkPayload`, or `LegacyTransaction`
- Minimal (as in execution payload): `TransactionType || TransactionPayload`, or `LegacyTransaction`

Execution-payloads / blocks use the minimal encoding of transactions.
In the transaction-pool and local transaction-journal the network encoding is used.

For previous types of transactions the network encoding is no different, i.e. `TransactionNetworkPayload == TransactionPayload`.

The `TransactionNetworkPayload` wraps a `TransactionPayload` with additional data:
this wrapping data SHOULD be verified directly before or after signature verification.

When a blob transaction is passed through the network (see the [Networking](#networking) section below),
the `TransactionNetworkPayload` version of the transaction also includes `blobs`, `commitments` and `proofs`.
The execution layer verifies the wrapper validity against the inner `TransactionPayload` after signature verification as:

- `versioned_hashes` must not be empty
- All hashes in `versioned_hashes` must start with the byte `BLOB_COMMITMENT_VERSION_KZG`
- There may be at most `MAX_DATA_GAS_PER_BLOCK // DATA_GAS_PER_BLOB` total blob commitments in a valid block.
- There is an equal amount of versioned hashes, commitments, blobs and proofs.
- The commitments hash to the versioned hashes, i.e. `kzg_to_versioned_hash(commitment[i]) == versioned_hash[i]`
- The commitments match the blob contents. (Note: this can be optimized with additional data, using a proof for a
random evaluation at two points derived from the commitment and blob data)

The `max_priority_fee_per_gas` and `max_fee_per_gas` fields follow [EIP-1559](./eip-1559.md) semantics
and `access_list` follows [EIP-2930](./eip-2930.md).

The signature is verified and `tx.origin` is calculated as follows:
The `max_fee_per_data_gas` is `uint256` and the `blob_versioned_hashes` field represents a list hash outputs from `kzg_to_versioned_hash`.

```python
def unsigned_tx_hash(tx: SignedBlobTransaction) -> Bytes32:
# The pre-image is prefixed with the transaction-type to avoid hash collisions with other tx hashers and types
return keccak256(BLOB_TX_TYPE + ssz.serialize(tx.message))

def get_origin(tx: SignedBlobTransaction) -> Address:
sig = tx.signature
# v = int(y_parity) + 27, same as EIP-1559
return ecrecover(unsigned_tx_hash(tx), int(sig.y_parity)+27, sig.r, sig.s)
```
The [EIP-2718](./eip-2718.md) `ReceiptPayload` for this transaction is `rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])`.

The hash of a signed blob transaction should be computed as:
#### Signature

```python
def signed_tx_hash(tx: SignedBlobTransaction) -> Bytes32:
return keccak256(BLOB_TX_TYPE + ssz.serialize(tx))
```
The signature values `y_parity`, `r`, and `s` are calculated by constructing a secp256k1 signature over the following digest:

Blob transactions with empty `versioned_hashes` are also considered invalid during execution block verification, and must not be included in a valid block.
`keccak256(BLOB_TX_TYPE || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_data_gas, blob_versioned_hashes]))`.

### Header extension

The current header encoding is extended with a new 256-bit unsigned integer field `excess_data_gas`. This is the running
total of excess data gas consumed on chain since this EIP was activated. If the total amount of data gas is below the
The current header encoding is extended with a new 256-bit unsigned integer field `excess_data_gas`. This is the running total of excess data gas consumed on chain since this EIP was activated. If the total amount of data gas is below the
target, `excess_data_gas` is capped at zero.

The resulting RLP encoding of the header is therefore:
Expand Down Expand Up @@ -247,8 +182,8 @@ The `ethereum/consensus-specs` repository defines the following beacon-node chan
### Opcode to get versioned hashes

We add an opcode `DATAHASH` (with byte value `HASH_OPCODE_BYTE`) which reads `index` from the top of the stack
as big-endian `uint256`, and replaces it on the stack with `tx.message.versioned_hashes[index]`
if `index < len(tx.message.versioned_hashes)`, and otherwise with a zeroed `bytes32` value.
as big-endian `uint256`, and replaces it on the stack with `tx.blob_versioned_hashes[index]`
if `index < len(tx.blob_versioned_hashes)`, and otherwise with a zeroed `bytes32` value.
The opcode has a gas cost of `HASH_OPCODE_GAS`.

### Point evaluation precompile
Expand Down Expand Up @@ -294,7 +229,7 @@ def calc_data_fee(tx: SignedBlobTransaction, parent: Header) -> int:
return get_total_data_gas(tx) * get_data_gasprice(parent)

def get_total_data_gas(tx: SignedBlobTransaction) -> int:
return DATA_GAS_PER_BLOB * len(tx.message.versioned_hashes)
return DATA_GAS_PER_BLOB * len(tx.blob_versioned_hashes)

def get_data_gasprice(header: Header) -> int:
return fake_exponential(
Expand All @@ -315,12 +250,12 @@ def validate_block(block: Block) -> None:
...

# the signer must be able to afford the transaction
assert signer(tx).balance >= tx.message.gas * tx.message.max_fee_per_gas + get_total_data_gas(tx) * tx.message.max_fee_per_data_gas
assert signer(tx).balance >= tx.gas * tx.max_fee_per_gas + get_total_data_gas(tx) * tx.max_fee_per_data_gas

# ensure that the user was willing to at least pay the current data gasprice
assert tx.message.max_fee_per_data_gas >= get_data_gasprice(parent(block).header)
assert tx.max_fee_per_data_gas >= get_data_gasprice(parent(block).header)

num_blobs += len(tx.message.versioned_hashes)
num_blobs += len(tx.blob_versioned_hashes)

# check that the excess data gas is correct
expected_edg = calc_excess_data_gas(parent(block).header, num_blobs)
Expand All @@ -331,42 +266,33 @@ The actual `data_fee` as calculated via `calc_data_fee` is deducted from the sen

### Networking

Nodes must not automatically broadcast blob transactions to their peers.
Instead, those transactions are only announced using `NewPooledTransactionHashes` messages, and can then be manually requested via `GetPooledTransactions`.

Transactions are presented as `TransactionType || TransactionNetworkPayload` on the execution layer network,
the payload is a SSZ encoded container:
Blob transactions have two network representations. During transaction gossip responses (`PooledTransactions`), the EIP-2718 `TransactionPayload` of the blob transaction is wrapped to become:

```python
class BlobTransactionNetworkWrapper(Container):
tx: SignedBlobTransaction
# KZGCommitment = Bytes48
commitments: List[KZGCommitment, MAX_TX_WRAP_COMMITMENTS]
# BLSFieldElement = uint256
blobs: List[Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB], LIMIT_BLOBS_PER_TX]
# KZGProof = Bytes48
proofs: List[KZGProof, MAX_TX_WRAP_COMMITMENTS]
```
rlp([blob_tx_payload, blob_kzgs, blobs, blob_kzg_proofs])
```

We do network-level validation of `BlobTransactionNetworkWrapper` objects as follows:
Each of these elements are defined as follows:

```python
def validate_blob_transaction_wrapper(wrapper: BlobTransactionNetworkWrapper):
versioned_hashes = wrapper.tx.message.versioned_hashes
commitments = wrapper.commitments
blobs = wrapper.blobs
proofs = wrapper.proofs
# note: assert blobs are not malformatted
assert len(versioned_hashes) == len(commitments) == len(blobs) == len(proofs)
assert len(versioned_hashes) > 0

# Verify that commitments match the blobs by checking the KZG proofs
assert verify_blob_kzg_proof_batch(blobs, commitments, proofs)

# Now that all commitments have been verified, check that versioned_hashes matches the commitments
for versioned_hash, commitment in zip(versioned_hashes, commitments):
assert versioned_hash == kzg_to_versioned_hash(commitment)
```
- `blob_tx_payload` - standard EIP-2718 blob transaction `TransactionPayload`
- `blob_kzgs` - list of `KZGCommitment`
- `blobs` - list of `blob` where `blob` is a list of `BLSFieldElement`
- `kzg_aggregated_proof` - `KZGProof`

The node MUST validate `blob_tx_payload` and verify the wrapped data against it. To do so, ensure that:

- `blob_tx_payload.blob_versioned_hashes` must not be empty
- All hashes in `blob_tx_payload.blob_versioned_hashes` must start with the byte `BLOB_COMMITMENT_VERSION_KZG`
- There must be at most `MAX_DATA_GAS_PER_BLOCK // DATA_GAS_PER_BLOB` total blob commitments in the transaction.
- There are an equal number of versioned hashes, kzg commitments, and blobs.
- The KZG commitments hash to the versioned hashes, i.e. `kzg_to_versioned_hash(kzg[i]) == versioned_hash[i]`
- The KZG commitments match the blob contents. (Note: this can be optimized using `blob_kzg_proofs`, with a proof for a
random evaluation at a point derived from the commitment and blob data for each blob)

For body retrieval responses (`BlockBodies`), the standard EIP-2718 blob transaction `TransactionPayload` is used.

Nodes MUST NOT automatically broadcast blob transactions to their peers.
Instead, those transactions are only announced using `NewPooledTransactionHashes` messages, and can then be manually requested via `GetPooledTransactions`.

## Rationale

Expand Down Expand Up @@ -405,11 +331,7 @@ The work that remains to be done to get to full sharding includes:
- PBS (proposer/builder separation), to avoid requiring individual validators to process 32 MB of data in one slot
- Proof of custody or similar in-protocol requirement for each validator to verify a particular part of the sharded data in each block

This EIP also sets the stage for longer-term protocol cleanups:

- It adds an SSZ transaction type, and paves the precedent that all new transaction types should be SSZ
- It defines `TransactionNetworkPayload` to separate network and block encodings of a transaction type
- Its (cleaner) gas price update rule could be applied to the primary basefee
This EIP also sets the stage for longer-term protocol cleanups. For example, its (cleaner) gas price update rule could be applied to the primary basefee calculation.

### How rollups would function

Expand Down Expand Up @@ -460,8 +382,8 @@ The values for `TARGET_DATA_GAS_PER_BLOCK` and `MAX_DATA_GAS_PER_BLOCK` are chos

### Blob non-accessibility

This EIP introduces a transaction type that has a distinct mempool version (`BlobTransactionNetworkWrapper`) and execution-payload version (`SignedBlobTransaction`),
with only one-way convertibility between the two. The blobs are in the `BlobTransactionNetworkWrapper` and not in the `SignedBlobTransaction`;
This EIP introduces a transaction type that has a distinct mempool version and execution-payload version,
with only one-way convertibility between the two. The blobs are in the network representation and not in the consensus representation;
instead, they go into the `BeaconBlockBody`. This means that there is now a part of a transaction that will not be accessible from the web3 API.

### Mempool issues
Expand Down

0 comments on commit 8703d20

Please sign in to comment.