diff --git a/src/ethereum/osaka/blocks.py b/src/ethereum/osaka/blocks.py index 80b29087e9..d445fd59c5 100644 --- a/src/ethereum/osaka/blocks.py +++ b/src/ethereum/osaka/blocks.py @@ -82,6 +82,7 @@ class Block: transactions: Tuple[Union[Bytes, LegacyTransaction], ...] ommers: Tuple[Header, ...] withdrawals: Tuple[Withdrawal, ...] + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...] @slotted_freezable diff --git a/src/ethereum/osaka/fork.py b/src/ethereum/osaka/fork.py index 70f5578dd8..4a1f3d6de1 100644 --- a/src/ethereum/osaka/fork.py +++ b/src/ethereum/osaka/fork.py @@ -216,6 +216,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: block_env=block_env, transactions=block.transactions, withdrawals=block.withdrawals, + inclusion_list=block.inclusion_list, ) block_state_root = state_root(block_env.state) transactions_root = root(block_output.transactions_trie) @@ -663,6 +664,7 @@ def apply_body( block_env: vm.BlockEnvironment, transactions: Tuple[Union[LegacyTransaction, Bytes], ...], withdrawals: Tuple[Withdrawal, ...], + inclusion_list: Tuple[Union[LegacyTransaction, Bytes], ...], ) -> vm.BlockOutput: """ Executes a block. @@ -682,6 +684,8 @@ def apply_body( Transactions included in the block. withdrawals : Withdrawals to be processed in the current block. + inclusion_list : + Transactions that must be included in the block if possible. Returns ------- @@ -705,6 +709,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( + block_env, + block_output, + transactions, + inclusion_list, + ) + process_withdrawals(block_env, block_output, withdrawals) process_general_purpose_requests( @@ -952,6 +963,33 @@ def increase_recipient_balance(recipient: Account) -> None: destroy_account(block_env.state, wd.address) +def validate_inclusion_list( + block_env: vm.BlockEnvironment, + block_output: vm.BlockOutput, + transactions: Tuple[Union[Bytes, LegacyTransaction], ...], + inclusion_list: Tuple[Union[Bytes, LegacyTransaction], ...], +) -> None: + """ + Validate the block satisfies the inclusion list. + """ + + index = Uint(len(transactions)) + for tx in inclusion_list: + # If the transaction is already present in the block, then skip. + if tx in transactions: + continue + + try: + tx = decode_transaction(tx) + process_transaction(block_env, block_output, tx, index) + except Exception as e: + continue + + # If the transaction was not in the block and was decoded and + # executed successfully, then mark the block invalid. + raise InvalidBlock("unsatisfied inclusion list") + + def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index e7899b3a85..360d46b680 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -147,6 +147,11 @@ def process_transaction(self) -> Any: """process_transaction function of the fork""" return self._module("fork").process_transaction + @property + def validate_inclusion_list(self) -> Any: + """validate_inclusion_list function of the fork""" + return self._module("fork").validate_inclusion_list + @property def MAX_BLOB_GAS_PER_BLOCK(self) -> Any: """MAX_BLOB_GAS_PER_BLOCK parameter of the fork""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 5f979297ca..cb92833000 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -235,6 +235,14 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: self.env.ommers, ) + if self.fork.is_after_fork("ethereum.osaka"): + self.fork.validate_inclusion_list( + block_env, + block_output, + self.txs.transactions, + self.env.inclusion_list, + ) + if self.fork.is_after_fork("ethereum.shanghai"): self.fork.process_withdrawals( block_env, block_output, self.env.withdrawals diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py index d1cff3975a..e131cdd0c7 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/env.py @@ -54,11 +54,15 @@ class Env: parent_blob_gas_used: Optional[U64] excess_blob_gas: Optional[U64] requests: Any + inclusion_list: Any def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): + inclusion_list = None + if t8n.options.input_env == "stdin": assert stdin is not None data = stdin["env"] + inclusion_list = stdin["inclusionList"] else: with open(t8n.options.input_env, "r") as f: data = json.load(f) @@ -75,6 +79,8 @@ def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): self.read_ommers(data, t8n) self.read_withdrawals(data, t8n) + self.read_inclusion_list(inclusion_list, t8n) + self.parent_beacon_block_root = None if t8n.fork.is_after_fork("ethereum.cancun"): if not t8n.options.state_test: @@ -191,6 +197,16 @@ def read_withdrawals(self, data: Any, t8n: "T8N") -> None: t8n.json_to_withdrawals(wd) for wd in data["withdrawals"] ) + def read_inclusion_list(self, inclusion_list: Any, t8n: "T8N") -> None: + """ + Read the inclusion list from the data. + """ + self.inclusion_list = None + if t8n.fork.is_after_fork("ethereum.osaka"): + self.inclusion_list = tuple( + hex_to_bytes(tx) for tx in inclusion_list + ) + def read_block_difficulty(self, data: Any, t8n: "T8N") -> None: """ Read the block difficulty from the data. diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index 97ac1f6989..d99b7782b9 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -339,18 +339,10 @@ def json_encode_receipts(self) -> Any: receipt_dict["gasUsed"] = hex(receipt.cumulative_gas_used) receipt_dict["bloom"] = "0x" + receipt.bloom.hex() - receipt_dict["logs"] = [ - { - "address": "0x" + log.address.hex(), - "topics": ["0x" + topic.hex() for topic in log.topics], - "data": "0x" + log.data.hex(), - } - for log in receipt.logs - ] receipts_json.append(receipt_dict) - return receipt_dict + return receipts_json def to_json(self) -> Any: """Encode the result to JSON"""