From 1dd0e4c67d92ebdeb4a7b53fb5dc65f7e4452399 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Wed, 16 Aug 2023 11:46:24 +0100 Subject: [PATCH] improvement: Add Engine API forkchoice updated field to fixtures for hive. --- src/ethereum_test_forks/base_fork.py | 25 +++- src/ethereum_test_forks/forks/forks.py | 42 ++++++- src/ethereum_test_tools/common/__init__.py | 2 + src/ethereum_test_tools/common/types.py | 114 ++++++++++++++++++ .../spec/blockchain_test.py | 10 ++ src/ethereum_test_tools/spec/state_test.py | 9 ++ 6 files changed, 197 insertions(+), 5 deletions(-) diff --git a/src/ethereum_test_forks/base_fork.py b/src/ethereum_test_forks/base_fork.py index 1bc51683fc5..8d04c4fbba1 100644 --- a/src/ethereum_test_forks/base_fork.py +++ b/src/ethereum_test_forks/base_fork.py @@ -161,7 +161,7 @@ def engine_new_payload_version( @abstractmethod def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = 0) -> bool: """ - Returns true if the engine api version requires new payload calls to include blob hashes. + Returns true if the engine new payload version requires new calls to include blob hashes. """ pass @@ -169,11 +169,32 @@ def engine_new_payload_blob_hashes(cls, block_number: int = 0, timestamp: int = @abstractmethod def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = 0) -> bool: """ - Returns true if the engine api version requires new payload calls to include a parent + Returns true if the engine new payload version requires new calls to include a parent beacon block root. """ pass + @classmethod + @abstractmethod + def engine_forkchoice_updated_version( + cls, block_number: int = 0, timestamp: int = 0 + ) -> Optional[int]: + """ + Returns `None` if this fork's canonical chain cannot be set using the forkchoice method. + """ + pass + + @classmethod + @abstractmethod + def engine_forkchoice_updated_beacon_root( + cls, block_number: int = 0, timestamp: int = 0 + ) -> bool: + """ + Returns true if the engine forkchoice updated version requires new calls to include a + parent beacon block root. + """ + pass + # Meta information about the fork @classmethod def name(cls) -> str: diff --git a/src/ethereum_test_forks/forks/forks.py b/src/ethereum_test_forks/forks/forks.py index c8e7fe86ebb..814b172cb01 100644 --- a/src/ethereum_test_forks/forks/forks.py +++ b/src/ethereum_test_forks/forks/forks.py @@ -284,7 +284,16 @@ def engine_new_payload_version( cls, block_number: int = 0, timestamp: int = 0 ) -> Optional[int]: """ - Starting at the merge, payloads can be sent through the engine API + Starting at the merge, payloads can be sent through the engine API. + """ + return 1 + + @classmethod + def engine_forkchoice_updated_version( + cls, block_number: int = 0, timestamp: int = 0 + ) -> Optional[int]: + """ + Starting at the merge, forkchoice update determines the canonical chain """ return 1 @@ -306,7 +315,16 @@ def engine_new_payload_version( cls, block_number: int = 0, timestamp: int = 0 ) -> Optional[int]: """ - Starting at Shanghai, new payload calls must use version 2 + Starting at Shanghai, new payload calls must use version 2. + """ + return 2 + + @classmethod + def engine_forkchoice_updated_version( + cls, block_number: int = 0, timestamp: int = 0 + ) -> Optional[int]: + """ + Starting at Shanghai, forkchoice updated calls must use version 2. """ return 2 @@ -379,7 +397,7 @@ def engine_new_payload_version( cls, block_number: int = 0, timestamp: int = 0 ) -> Optional[int]: """ - Starting at Cancun, new payload calls must use version 3 + Starting at Cancun, new payload calls must use version 3. """ return 3 @@ -396,3 +414,21 @@ def engine_new_payload_beacon_root(cls, block_number: int = 0, timestamp: int = Starting at Cancun, payloads must have a parent beacon block root. """ return True + + @classmethod + def engine_forkchoice_updated_version( + cls, block_number: int = 0, timestamp: int = 0 + ) -> Optional[int]: + """ + Starting at Cancun, forkchoice updated calls must use version 3. + """ + return 3 + + @classmethod + def engine_forkchoice_updated_beacon_root( + cls, block_number: int = 0, timestamp: int = 0 + ) -> bool: + """ + Starting at Cancun, forkchoice update calls must have a parent beacon block root. + """ + return True diff --git a/src/ethereum_test_tools/common/__init__.py b/src/ethereum_test_tools/common/__init__.py index 3d3903b8369..73f9a440528 100644 --- a/src/ethereum_test_tools/common/__init__.py +++ b/src/ethereum_test_tools/common/__init__.py @@ -37,6 +37,7 @@ Environment, Fixture, FixtureBlock, + FixtureEngineForkchoiceUpdated, FixtureEngineNewPayload, FixtureHeader, Hash, @@ -73,6 +74,7 @@ "Environment", "Fixture", "FixtureBlock", + "FixtureEngineForkchoiceUpdated", "FixtureEngineNewPayload", "FixtureHeader", "Hash", diff --git a/src/ethereum_test_tools/common/types.py b/src/ethereum_test_tools/common/types.py index 59d33f73bf3..3ecf9ec9b9e 100644 --- a/src/ethereum_test_tools/common/types.py +++ b/src/ethereum_test_tools/common/types.py @@ -2400,6 +2400,113 @@ def copy_with_rlp(self, rlp: Bytes | BytesConvertible | None) -> "Block": return new_block +@dataclass(kw_only=True) +class FixtureEnginePayloadAttributes: + """ + Representation of the engine api payload attributes from a block within a test fixture. + """ + + mix_digest: Hash = field( + json_encoder=JSONEncoder.Field( + name="prevRandao", + ), + ) + + coinbase: Address = field( + json_encoder=JSONEncoder.Field( + name="feeRecipient", + ) + ) + + timestamp: int = field( + json_encoder=JSONEncoder.Field( + cast_type=HexNumber, + ) + ) + + withdrawals: Optional[List[Withdrawal]] = field( + default=None, + json_encoder=JSONEncoder.Field( + to_json=True, + ), + ) + + beacon_root: Optional[Hash] = field( + default=None, + json_encoder=JSONEncoder.Field( + name="parentBeaconBlockRoot", + ), + ) + + @classmethod + def from_fixture_header( + cls, + fork, + header: FixtureHeader, + withdrawals: Optional[List[Withdrawal]] = None, + ) -> "FixtureEnginePayloadAttributes": + """ + Returns `FixtureEnginePayloadAttributes` from a `FixtureHeader` and a list of withdrawals. + """ + return cls( + mix_digest=header.mix_digest, + coinbase=header.coinbase, + timestamp=header.timestamp, + withdrawals=withdrawals, + beacon_root=header.beacon_root + if fork.engine_forkchoice_updated_beacon_root(header.number, header.timestamp) + else None, + ) + + +@dataclass(kw_only=True) +class FixtureEngineForkchoiceUpdated: + """ + Representation of the `engine_forkchoiceUpdatedVX` information to be + sent using the block information. + """ + + payload_attributes: FixtureEnginePayloadAttributes = field( + json_encoder=JSONEncoder.Field( + name="payloadAttributes", + to_json=True, + ) + ) + + version: int = field( + json_encoder=JSONEncoder.Field(), + ) + + @classmethod + def from_fixture_header( + cls, + fork: Fork, + header: FixtureHeader, + withdrawals: Optional[List[Withdrawal]], + ) -> Optional["FixtureEngineForkchoiceUpdated"]: + """ + Creates `FixtureEngineForkchoiceUpdated` from a `FixtureHeader`. + """ + forkchoice_updated_version = fork.engine_forkchoice_updated_version( + header.number, + header.timestamp, + ) + + if forkchoice_updated_version is None: + return None + + forkchoice_updated = cls( + payload_attributes=FixtureEnginePayloadAttributes.from_fixture_header( + fork=fork, + header=header, + withdrawals=withdrawals, + ), + version=forkchoice_updated_version, + ) + + return forkchoice_updated + + @dataclass(kw_only=True) class FixtureExecutionPayload(FixtureHeader): """ @@ -2608,6 +2715,13 @@ class FixtureBlock: to_json=True, ), ) + forkchoice_updated: Optional[FixtureEngineForkchoiceUpdated] = field( + default=None, + json_encoder=JSONEncoder.Field( + name="forkchoiceUpdated", + to_json=True, + ), + ) new_payload: Optional[FixtureEngineNewPayload] = field( default=None, json_encoder=JSONEncoder.Field( diff --git a/src/ethereum_test_tools/spec/blockchain_test.py b/src/ethereum_test_tools/spec/blockchain_test.py index a8a63a700fb..5a1200bde97 100644 --- a/src/ethereum_test_tools/spec/blockchain_test.py +++ b/src/ethereum_test_tools/spec/blockchain_test.py @@ -18,6 +18,7 @@ EmptyTrieRoot, Environment, FixtureBlock, + FixtureEngineForkchoiceUpdated, FixtureEngineNewPayload, FixtureHeader, Hash, @@ -207,8 +208,15 @@ def make_block( withdrawals=env.withdrawals, ) + forkchoice_updated: FixtureEngineForkchoiceUpdated | None = None new_payload: FixtureEngineNewPayload | None = None if not self.base_test_config.disable_hive: + forkchoice_updated = FixtureEngineForkchoiceUpdated.from_fixture_header( + fork=fork, + header=header, + withdrawals=env.withdrawals, + ) + new_payload = FixtureEngineNewPayload.from_fixture_header( fork=fork, header=header, @@ -222,6 +230,7 @@ def make_block( return ( FixtureBlock( rlp=rlp, + forkchoice_updated=forkchoice_updated, new_payload=new_payload, block_header=header, block_number=Number(header.number), @@ -237,6 +246,7 @@ def make_block( return ( FixtureBlock( rlp=rlp, + forkchoice_updated=forkchoice_updated, new_payload=new_payload, expected_exception=block.exception, block_number=Number(header.number), diff --git a/src/ethereum_test_tools/spec/state_test.py b/src/ethereum_test_tools/spec/state_test.py index 64476e6b101..69f055cee6d 100644 --- a/src/ethereum_test_tools/spec/state_test.py +++ b/src/ethereum_test_tools/spec/state_test.py @@ -16,6 +16,7 @@ EmptyTrieRoot, Environment, FixtureBlock, + FixtureEngineForkchoiceUpdated, FixtureEngineNewPayload, FixtureHeader, Hash, @@ -178,8 +179,15 @@ def make_blocks( withdrawals=env.withdrawals, ) + forkchoice_updated: FixtureEngineForkchoiceUpdated | None = None new_payload: FixtureEngineNewPayload | None = None if not self.base_test_config.disable_hive: + forkchoice_updated = FixtureEngineForkchoiceUpdated.from_fixture_header( + fork=fork, + header=header, + withdrawals=env.withdrawals, + ) + new_payload = FixtureEngineNewPayload.from_fixture_header( fork=fork, header=header, @@ -192,6 +200,7 @@ def make_blocks( [ FixtureBlock( rlp=block, + forkchoice_updated=forkchoice_updated, new_payload=new_payload, block_header=header, txs=txs,