Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ class Result(CamelModel):
requests: List[Bytes] | None = None
block_access_list: Bytes | None = None
block_access_list_hash: Hash | None = None
is_inclusion_list_satisfied: bool | None = None
block_exception: Annotated[
BlockExceptionWithMessage | UndefinedException | None,
ExceptionMapperValidator,
Expand Down
87 changes: 85 additions & 2 deletions src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ def get_last_256_block_hashes(chain: BlockChain) -> List[Hash32]:
return recent_block_hashes


def state_transition(chain: BlockChain, block: Block) -> None:
def state_transition(
chain: BlockChain,
block: Block,
inclusion_list_transactions: Tuple[LegacyTransaction | Bytes, ...] = (),
) -> None:
"""
Attempts to apply a block to an existing block chain.

Expand All @@ -246,6 +250,8 @@ def state_transition(chain: BlockChain, block: Block) -> None:
History and current state.
block :
Block to apply to `chain`.
inclusion_list_transactions :
Inclusion list transactions against which the block will be checked.

"""
chain_context = ChainContext(
Expand All @@ -254,7 +260,9 @@ def state_transition(chain: BlockChain, block: Block) -> None:
parent_header=chain.blocks[-1].header,
)

block_diff = execute_block(block, chain.state, chain_context)
block_diff = execute_block(
block, chain.state, chain_context, inclusion_list_transactions
)

apply_changes_to_state(chain.state, block_diff)
chain.blocks.append(block)
Expand All @@ -268,6 +276,7 @@ def execute_block(
block: Block,
pre_state: PreState,
chain_context: ChainContext,
inclusion_list_transactions: Tuple[LegacyTransaction | Bytes, ...] = (),
) -> BlockDiff:
"""
Execute a block and validate the resulting roots against the header.
Expand All @@ -282,6 +291,8 @@ def execute_block(
Pre-execution state provider.
chain_context :
Chain context that the block may need during execution.
inclusion_list_transactions :
Inclusion list transactions against which the block will be checked.

Returns
-------
Expand Down Expand Up @@ -319,6 +330,7 @@ def execute_block(
block_env=block_env,
transactions=block.transactions,
withdrawals=block.withdrawals,
inclusion_list_transactions=inclusion_list_transactions,
)
block_diff = extract_block_diff(block_state)
block_state_root, _ = pre_state.compute_state_root_and_trie_changes(
Expand Down Expand Up @@ -809,6 +821,7 @@ def apply_body(
block_env: vm.BlockEnvironment,
transactions: Tuple[LegacyTransaction | Bytes, ...],
withdrawals: Tuple[Withdrawal, ...],
inclusion_list_transactions: Tuple[LegacyTransaction | Bytes, ...],
) -> vm.BlockOutput:
"""
Executes a block.
Expand All @@ -828,6 +841,8 @@ def apply_body(
Transactions included in the block.
withdrawals :
Withdrawals to be processed in the current block.
inclusion_list_transactions :
Inclusion list transactions against which the block will be checked.

Returns
-------
Expand All @@ -852,6 +867,16 @@ def apply_body(
for i, tx in enumerate(map(decode_transaction, transactions)):
process_transaction(block_env, block_output, tx, Uint(i))

# Check if the block satisfies the inclusion list constraints (EIP-7805)
block_output.is_inclusion_list_satisfied = (
check_inclusion_list_transactions(
block_env=block_env,
block_output=block_output,
transactions=transactions,
inclusion_list_transactions=inclusion_list_transactions,
)
)

# EIP-7928: Post-execution operations use index N+1
block_env.block_access_list_builder.block_access_index = BlockAccessIndex(
Uint(len(transactions)) + Uint(1)
Expand Down Expand Up @@ -1155,3 +1180,61 @@ def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool:
return False

return True


def check_inclusion_list_transactions(
block_env: vm.BlockEnvironment,
block_output: vm.BlockOutput,
transactions: Tuple[LegacyTransaction | Bytes, ...],
inclusion_list_transactions: Tuple[LegacyTransaction | Bytes, ...],
) -> bool:
"""
Check whether the block satisfies the inclusion list constraints.

For each inclusion list transaction not present in the block,
check whether it could have been validly appended to the end of the block.
Blob transactions are excluded from this check. If any such transaction
could have been appended, the block fails the inclusion list check.

The inclusion list compliance does not affect any other block outputs.

Parameters
----------
block_env :
The block scoped environment.
block_output :
The block output for the current block.
transactions :
Transactions included in the block.
inclusion_list_transactions :
Inclusion list transactions against which the block will be checked.

Returns
-------
is_inclusion_list_satisfied : `bool`
True if the block passes the inclusion list check, False otherwise.

"""
tx_hashes = {get_transaction_hash(raw_tx) for raw_tx in transactions}
tx_state = TransactionState(parent=block_env.state)

for raw_tx in inclusion_list_transactions:
if get_transaction_hash(raw_tx) in tx_hashes:
continue

tx = decode_transaction(raw_tx)

# Ignore blob transactions.
if isinstance(tx, BlobTransaction):
continue

try:
validate_transaction(tx)
check_transaction(block_env, block_output, tx, tx_state)
except EthereumException:
continue
else:
# This inclusion list transaction could have been included.
return False

return True
3 changes: 3 additions & 0 deletions src/ethereum/forks/amsterdam/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class BlockOutput:
Hash of all the requests in the block.
block_access_list: `BlockAccessList`
The block access list for the block.
is_inclusion_list_satisfied : `bool`
Whether the block satisfies the inclusion list constraints.
"""

block_gas_used: Uint = Uint(0)
Expand All @@ -95,6 +97,7 @@ class BlockOutput:
blob_gas_used: U64 = U64(0)
requests: List[Bytes] = field(default_factory=list)
block_access_list: BlockAccessList = field(default_factory=list)
is_inclusion_list_satisfied: bool = True


@dataclass
Expand Down
22 changes: 22 additions & 0 deletions src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,18 @@ def process_transaction(self) -> Any:
"""process_transaction function of the fork."""
return self._module("fork").process_transaction

@property
def check_inclusion_list_transactions(self) -> Any:
"""check_inclusion_list_transactions function of the fork."""
return self._module("fork").check_inclusion_list_transactions

@property
def has_is_inclusion_list_satisfied(self) -> bool:
"""
Check if the block output has an `is_inclusion_list_satisfied` field.
"""
return hasattr(self.BlockOutput, "is_inclusion_list_satisfied")

@property
def Block(self) -> Any:
"""Block class of the fork."""
Expand Down Expand Up @@ -297,6 +309,16 @@ def has_decode_transaction(self) -> bool:
"""Check if this fork has a `decode_transaction`."""
return hasattr(self._module("transactions"), "decode_transaction")

@property
def encode_transaction(self) -> Any:
"""encode_transaction function of the fork."""
return self._module("transactions").encode_transaction

@property
def has_encode_transaction(self) -> bool:
"""Check if this fork has an `encode_transaction`."""
return hasattr(self._module("transactions"), "encode_transaction")

@property
def has_block_state(self) -> bool:
"""Check if the fork uses BlockState instead of State."""
Expand Down
13 changes: 13 additions & 0 deletions src/ethereum_spec_tools/evm_tools/t8n/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,19 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None:
U256(self.options.state_reward), block_env
)

if self.fork.has_is_inclusion_list_satisfied:
block_output.is_inclusion_list_satisfied = (
self.fork.check_inclusion_list_transactions(
block_env,
block_output,
tuple(
self.fork.encode_transaction(tx)
for tx in self.txs.transactions
),
self.env.inclusion_list_transactions,
)
)

if self.fork.has_withdrawal:
self.fork.process_withdrawals(
block_env, block_output, self.env.withdrawals
Expand Down
20 changes: 19 additions & 1 deletion src/ethereum_spec_tools/evm_tools/t8n/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from ethereum_rlp import rlp
from ethereum_types.bytes import Bytes8, Bytes20, Bytes32, Bytes256
Expand Down Expand Up @@ -54,6 +54,7 @@ class Env:
parent_blob_gas_used: Optional[U64]
excess_blob_gas: Optional[U64]
requests: Any
inclusion_list_transactions: Optional[Tuple[Any, ...]]

def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None):
if t8n.options.input_env == "stdin":
Expand All @@ -74,6 +75,7 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None):
self.read_block_hashes(data)
self.read_ommers(data, t8n)
self.read_withdrawals(data, t8n)
self.read_inclusion_list_transactions(data, t8n)

self.parent_beacon_block_root = None
if t8n.fork.has_beacon_roots_address:
Expand Down Expand Up @@ -316,3 +318,19 @@ def read_ommers(self, data: Any, t8n: "T8N") -> None:
)
)
self.ommers = ommers

def read_inclusion_list_transactions(self, data: Any, t8n: "T8N") -> None:
"""
Read the inclusion list transactions.
"""
self.inclusion_list_transactions = None

if not t8n.fork.has_is_inclusion_list_satisfied:
return

inclusion_list_transactions = []
if "inclusionListTransactions" in data:
for tx in data["inclusionListTransactions"]:
inclusion_list_transactions.append(hex_to_bytes(tx))

self.inclusion_list_transactions = tuple(inclusion_list_transactions)
9 changes: 9 additions & 0 deletions src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ class Result:
block_exception: Optional[str] = None
block_access_list: Optional[Any] = None
block_access_list_hash: Optional[Hash32] = None
is_inclusion_list_satisfied: Optional[bool] = None

def get_receipts_from_output(
self,
Expand Down Expand Up @@ -354,6 +355,11 @@ def update(self, t8n: "T8N", block_env: Any, block_output: Any) -> None:
block_output.block_access_list
)

if hasattr(block_output, "is_inclusion_list_satisfied"):
self.is_inclusion_list_satisfied = (
block_output.is_inclusion_list_satisfied
)

def json_encode_receipts(self) -> Any:
"""
Encode receipts to JSON.
Expand Down Expand Up @@ -446,4 +452,7 @@ def to_json(self) -> Any:
self.block_access_list_hash
)

if self.is_inclusion_list_satisfied is not None:
data["isInclusionListSatisfied"] = self.is_inclusion_list_satisfied

return data
Loading