-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update EIP-4844: de-sszify spec #6985
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5b71983
4844: de-sszify spec
lightclient b5a30dc
4844: fix the fucking linter
lightclient 5ffb5c6
4844: reorder fields in rlp add missing fee field for data
lightclient f66370f
better names
lightclient 7782d6c
4844: one kzg proof per blob
lightclient 39a4857
4844: fix info about optimization
lightclient 6d1e840
4844: fix typo
lightclient File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
|
@@ -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 | ||
|
@@ -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( | ||
|
@@ -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) | ||
|
@@ -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 | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I misread, but should this read "not in the execution implementation"? 🤔 |
||
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 | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"represents a list hash outputs" something is wrong here. 🤔