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
77 changes: 76 additions & 1 deletion src/ethereum/osaka/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,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 @@ -210,6 +214,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 validated.
"""
if len(rlp.encode(block)) > MAX_RLP_BLOCK_SIZE:
raise InvalidBlock("Block rlp size exceeds MAX_RLP_BLOCK_SIZE")
Expand All @@ -236,6 +242,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
block_env=block_env,
transactions=block.transactions,
withdrawals=block.withdrawals,
inclusion_list_transactions=inclusion_list_transactions,
)
block_state_root = state_root(block_env.state)
transactions_root = root(block_output.transactions_trie)
Expand Down Expand Up @@ -727,6 +734,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 @@ -746,6 +754,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 validated.

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

validate_inclusion_list_transactions(
block_env=block_env,
block_output=block_output,
transactions=transactions,
inclusion_list_transactions=inclusion_list_transactions,
)

process_withdrawals(block_env, block_output, withdrawals)

process_general_purpose_requests(
Expand Down Expand Up @@ -1053,3 +1070,61 @@ def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool:
return False

return True


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

It checks if each inclusion list transaction is present in the block.
For those not included, it checks if there was sufficient gas remaining
to include the transaction and whether the transaction is valid against
the nonce and balance of the sender. If any inclusion list transaction
could have been included but was not, the block is marked as not
satisfying inclusion list constraints. Any inclusion list transaction
that is a blob transaction is ignored.

Compliance with inclusion list constraints does not affect any other
block outputs.

[EIP-7805]: https://eips.ethereum.org/EIPS/eip-7805

Parameters
----------
block_env :
Environment for the Ethereum Virtual Machine.
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 validated.
"""
for inclusion_list_transaction in inclusion_list_transactions:
# Skip if an inclusion list transaction is present in the block.
if inclusion_list_transaction in transactions:
continue

tx = decode_transaction(inclusion_list_transaction)

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

try:
# Run the tests of intrinsic validity.
validate_transaction(tx)
check_transaction(block_env, block_output, tx)
except EthereumException:
# This inclusion list transaction could not be included.
continue
else:
# This inclusion list transaction could have been included.
# Mark the block as not satisfying inclusion list constraints.
block_output.is_inclusion_list_satisfied = False
break
3 changes: 3 additions & 0 deletions src/ethereum/osaka/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class BlockOutput:
Total blob gas used in the block.
requests : `Bytes`
Hash of all the requests in the block.
is_inclusion_list_satisfied : `bool`
Whether the block satisfies the inclusion list constraints.
"""

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


@dataclass
Expand Down
5 changes: 5 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 @@ -152,6 +152,11 @@ def process_transaction(self) -> Any:
"""process_transaction function of the fork"""
return self._module("fork").process_transaction

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

@property
def MAX_BLOB_GAS_PER_BLOCK(self) -> Any:
"""MAX_BLOB_GAS_PER_BLOCK parameter of the fork"""
Expand Down
8 changes: 8 additions & 0 deletions src/ethereum_spec_tools/evm_tools/t8n/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None:
U256(self.options.state_reward), block_env
)

if self.fork.is_after_fork("ethereum.osaka"):
self.fork.validate_inclusion_list_transactions(
block_env,
block_output,
self.txs.transactions,
self.env.inclusion_list_transactions,
)

if self.fork.is_after_fork("ethereum.shanghai"):
self.fork.process_withdrawals(
block_env, block_output, self.env.withdrawals
Expand Down
19 changes: 19 additions & 0 deletions src/ethereum_spec_tools/evm_tools/t8n/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ethereum.crypto.hash import Hash32, keccak256
from ethereum.utils.byte import left_pad_zero_bytes
from ethereum.utils.hexadecimal import hex_to_bytes
from ethereum_spec_tools.evm_tools.t8n.t8n_types import Txs

from ..utils import parse_hex_or_int

Expand Down Expand Up @@ -54,6 +55,7 @@ class Env:
parent_blob_gas_used: Optional[U64]
excess_blob_gas: Optional[U64]
requests: Any
inclusion_list_transactions: Any

def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None):
if t8n.options.input_env == "stdin":
Expand All @@ -74,6 +76,7 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None):
self.read_block_hashes(data, t8n)
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.is_after_fork("ethereum.cancun"):
Expand Down Expand Up @@ -328,3 +331,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 lists.
"""
self.inclusion_list_transactions = None

if not t8n.fork.is_after_fork("ethereum.osaka"):
return

inclusion_list_transactions = []
if "inclusionLists" in data:
inclusion_list_transactions = Txs(
t8n, data["inclusionLists"]
).all_txs
self.inclusion_list_transactions = 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 @@ -268,6 +268,7 @@ class Result:
requests_hash: Optional[Hash32] = None
requests: Optional[List[Bytes]] = None
block_exception: Optional[str] = None
is_inclusion_list_satisfied: Optional[bool] = None

def get_receipts_from_output(
self,
Expand Down Expand Up @@ -323,6 +324,11 @@ def update(self, t8n: "T8N", block_env: Any, block_output: Any) -> None:
self.requests = block_output.requests
self.requests_hash = t8n.fork.compute_requests_hash(self.requests)

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 @@ -390,4 +396,7 @@ def to_json(self) -> Any:
if self.block_exception is not None:
data["blockException"] = self.block_exception

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

return data