Skip to content

Commit d1186c9

Browse files
committed
feat(consume): Add consume sync tests
Add a new type of test that is essentially `consume_engine` but gets another client to sync after payloads are executed. - These tests can be filled with: `uv run fill ... -m "blockchain_test_sync"` - Filled tests can be run against hive with `uv run consume sync` Squashes: - chore: fix formatting and lint errors - chore: lower some of the timeouts and sleeps for sync tests - chore(docs): Document consume sync and add entry to CHANGELOG.md - Add tests for the ``verify_sync`` marker to make sure it's behaving as expected. - chore: Don't convert state tests to sync tests - refactor: Make test ids better - Changes from comments on PR #2007 - Use `pytest.mark.verify_sync` instead of the previous `verify_sync` flag on the blockchain test. - Remove the syncing periodic checks as when the forkchoice_updated response is `VALID`, we can break. - Update all files according to changes above.
1 parent 273d7a8 commit d1186c9

File tree

20 files changed

+1299
-42
lines changed

20 files changed

+1299
-42
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Users can select any of the artifacts depending on their testing needs for their
8282
- ✨ Added support for the `--benchmark-gas-values` flag in the `fill` command, allowing a single genesis file to be used across different gas limit settings when generating fixtures. ([#1895](https://github.com/ethereum/execution-spec-tests/pull/1895)).
8383
- ✨ Static tests can now specify a maximum fork where they should be filled for ([#1977](https://github.com/ethereum/execution-spec-tests/pull/1977)).
8484
- ✨ Static tests can now be filled in every format using `--generate-all-formats` ([#2006](https://github.com/ethereum/execution-spec-tests/pull/2006)).
85+
- ✨ Add support for `BlockchainEngineSyncFixture` format for tests marked with `pytest.mark.verify_sync` to enable client synchronization testing via `consume sync` command ([#2007](https://github.com/ethereum/execution-spec-tests/pull/2007)).
8586

8687
#### `consume`
8788

@@ -90,6 +91,7 @@ Users can select any of the artifacts depending on their testing needs for their
9091
- 🔀 `consume` now automatically avoids GitHub API calls when using direct release URLs (better for CI environments), while release specifiers like `stable@latest` continue to use the API for version resolution ([#1788](https://github.com/ethereum/execution-spec-tests/pull/1788)).
9192
- 🔀 Refactor consume simulator architecture to use explicit pytest plugin structure with forward-looking architecture ([#1801](https://github.com/ethereum/execution-spec-tests/pull/1801)).
9293
- 🔀 Add exponential retry logic to initial fcu within consume engine ([#1815](https://github.com/ethereum/execution-spec-tests/pull/1815)).
94+
- ✨ Add `consume sync` command to test client synchronization capabilities by having one client sync from another via Engine API and P2P networking ([#2007](https://github.com/ethereum/execution-spec-tests/pull/2007)).
9395

9496
#### `execute`
9597

docs/running_tests/running.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Both `consume` and `execute` provide sub-commands which correspond to different
1414
| [`consume direct`](#direct) | Client consume tests via a `statetest` interface | EVM | None | Module test |
1515
| [`consume direct`](#direct) | Client consume tests via a `blocktest` interface | EVM, block processing | None | Module test,</br>Integration test |
1616
| [`consume engine`](#engine) | Client imports blocks via Engine API `EngineNewPayload` in Hive | EVM, block processing, Engine API | Staging, Hive | System test |
17+
| [`consume sync`](#sync) | Client syncs from another client using Engine API in Hive | EVM, block processing, Engine API, P2P sync | Staging, Hive | System test |
1718
| [`consume rlp`](#rlp) | Client imports RLP-encoded blocks upon start-up in Hive | EVM, block processing, RLP import (sync\*) | Staging, Hive | System test |
1819
| [`execute hive`](./execute/hive.md) | Tests executed against a client via JSON RPC `eth_sendRawTransaction` in Hive | EVM, JSON RPC, mempool | Staging, Hive | System test |
1920
| [`execute remote`](./execute/remote.md) | Tests executed against a client via JSON RPC `eth_sendRawTransaction` on a live network | EVM, JSON RPC, mempool, EL-EL/EL-CL interaction (indirectly) | Production | System Test |
@@ -81,6 +82,25 @@ The `consume rlp` command:
8182

8283
This method simulates how clients import blocks during historical sync, testing the complete block validation and state transition pipeline, see below for more details and a comparison to consumption via the Engine API.
8384

85+
## Sync
86+
87+
| Nomenclature | |
88+
| -------------- |------------------------|
89+
| Command | `consume sync` |
90+
| Simulator | None |
91+
| Fixture format | `blockchain_test_sync` |
92+
93+
The consume sync method tests execution client synchronization capabilities by having one client sync from another via the Engine API and P2P networking. This method validates that clients can correctly synchronize state and blocks from peers, testing both the Engine API, sync triggering, and P2P block propagation mechanisms.
94+
95+
The `consume sync` command:
96+
97+
1. **Initializes the client under test** with genesis state and executes all test payloads.
98+
2. **Spins up a sync client** with the same genesis state.
99+
3. **Establishes P2P connection** between the two clients, utilizing ``admin_addPeer`` with enode url.
100+
4. **Triggers synchronization** by sending the target block to the sync client via `engine_newPayload` followed by `engine_forkchoiceUpdated` requests.
101+
5. **Monitors sync progress** and validates that the sync client reaches the same state.
102+
6. **Verifies final state** matches between both clients.
103+
84104
## Engine vs RLP Simulator
85105

86106
The RLP Simulator (`eest/consume-rlp`) and the Engine Simulator (`eest/consume-engine`) should be seen as complimentary to one another. Although they execute the same underlying EVM test cases, the block validation logic is executed via different client code paths (using different [fixture formats](./test_formats/index.md)). Therefore, ideally, **both simulators should be executed for full coverage**.
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# Blockchain Engine Sync Tests <!-- markdownlint-disable MD051 (MD051=link-fragments "Link fragments should be valid") -->
2+
3+
The Blockchain Engine Sync Test fixture format tests are included in the fixtures subdirectory `blockchain_tests_sync`, and use Engine API directives to test client synchronization capabilities after fixtures are executed with valid payloads.
4+
5+
These are produced by the `BlockchainTest` test spec when `pytest.mark.verify_sync` is used as a test marker.
6+
7+
## Description
8+
9+
The Blockchain Engine Sync Test fixture format is used to test execution client synchronization between peers. It validates that clients can correctly sync state and blocks from another client using the Engine API and P2P networking.
10+
11+
The test works by:
12+
13+
1. Setting up a client under test, defining a pre-execution state, a series of `engine_newPayloadVX` directives, and a post-execution state, as in Blockchain Engine Test fixture formats.
14+
2. Starting a sync client with the same genesis and pre-execution state.
15+
3. Having the sync client synchronize from the client under test.
16+
4. Verifying that both clients reach the same final state.
17+
18+
A single JSON fixture file is composed of a JSON object where each key-value pair is a different [`SyncFixture`](#syncfixture) test object, with the key string representing the test name.
19+
20+
The JSON file path plus the test name are used as the unique test identifier, as well as a `{client under test}::sync_{sync client}` identifier.
21+
22+
## Consumption
23+
24+
For each [`HiveFixture`](#hivefixture) test object in the JSON fixture file, perform the following steps:
25+
26+
### Client Under Test Setup
27+
28+
1. Start the client under test using:
29+
- [`network`](#-network-fork) to configure the execution fork schedule according to the [`Fork`](./common_types.md#fork) type definition.
30+
- [`pre`](#-pre-alloc) as the starting state allocation of the execution environment for the test and calculate the genesis state root.
31+
- [`genesisBlockHeader`](#-genesisblockheader-fixtureheader) as the genesis block header.
32+
33+
2. Verify the head of the chain is the genesis block, and the state root matches the one calculated on step 1, otherwise fail the test.
34+
35+
3. Process all [`FixtureEngineNewPayload`](#fixtureenginenewpayload) objects in [`engineNewPayloads`](#-enginenewpayloads-listfixtureenginenewpayload) to build the complete chain on the client under test.
36+
37+
### Sync Client Setup and Synchronization
38+
39+
1. Start a sync client using the same genesis configuration:
40+
- Use the same [`network`](#-network-fork), [`pre`](#-pre-alloc), and [`genesisBlockHeader`](#-genesisblockheader-fixtureheader).
41+
42+
2. Establish P2P connection between the clients:
43+
- Get the enode URL from the client under test
44+
- Use `admin_addPeer` to connect the sync client to the client under test
45+
46+
3. Trigger synchronization on the sync client:
47+
- Send the [`syncPayload`](#-syncpayload-fixtureenginenewpayload) using `engine_newPayloadVX`
48+
- Send `engine_forkchoiceUpdatedVX` pointing to the last block hash
49+
50+
4. Monitor and verify synchronization:
51+
- Wait for the sync client to reach the [`lastblockhash`](#-lastblockhash-hash)
52+
- Verify the final state root matches between both clients
53+
- If [`post`](#-post-alloc) is provided, verify the final state matches
54+
55+
## Structures
56+
57+
### `HiveFixture`
58+
59+
#### - `network`: [`Fork`](./common_types.md#fork)
60+
61+
##### TO BE DEPRECATED
62+
63+
Fork configuration for the test.
64+
65+
This field is going to be replaced by the value contained in `config.network`.
66+
67+
#### - `genesisBlockHeader`: [`FixtureHeader`](./blockchain_test.md#fixtureheader)
68+
69+
Genesis block header.
70+
71+
#### - `engineNewPayloads`: [`List`](./common_types.md#list)`[`[`FixtureEngineNewPayload`](#fixtureenginenewpayload)`]`
72+
73+
List of `engine_newPayloadVX` directives to be processed by the client under test to build the complete chain.
74+
75+
#### - `syncPayload`: [`FixtureEngineNewPayload`](#fixtureenginenewpayload)
76+
77+
The final payload to be sent to the sync client to trigger synchronization. This is typically an empty block built on top of the last test block.
78+
79+
#### - `engineFcuVersion`: [`Number`](./common_types.md#number)
80+
81+
Version of the `engine_forkchoiceUpdatedVX` directive to use to set the head of the chain.
82+
83+
#### - `pre`: [`Alloc`](./common_types.md#alloc-mappingaddressaccount)
84+
85+
Starting account allocation for the test. State root calculated from this allocation must match the one in the genesis block.
86+
87+
#### - `lastblockhash`: [`Hash`](./common_types.md#hash)
88+
89+
Hash of the last valid block that the sync client should reach after successful synchronization.
90+
91+
#### - `post`: [`Alloc`](./common_types.md#alloc-mappingaddressaccount)
92+
93+
Account allocation for verification after synchronization is complete.
94+
95+
#### - `postStateHash`: [`Optional`](./common_types.md#optional)`[`[`Hash`](./common_types.md#hash)`]`
96+
97+
Optional state root hash for verification after synchronization is complete. Used when full post-state is not included.
98+
99+
#### - `config`: [`FixtureConfig`](#fixtureconfig)
100+
101+
Chain configuration object to be applied to both clients running the blockchain sync test.
102+
103+
### `FixtureConfig`
104+
105+
#### - `network`: [`Fork`](./common_types.md#fork)
106+
107+
Fork configuration for the test. It is guaranteed that this field contains the same value as the root field `network`.
108+
109+
#### - `chainId`: [`Number`](./common_types.md#number)
110+
111+
Chain ID configuration for the test network.
112+
113+
#### - `blobSchedule`: [`BlobSchedule`](./common_types.md#blobschedule-mappingforkforkblobschedule)
114+
115+
Optional; present from Cancun on. Maps forks to their blob schedule configurations as defined by [EIP-7840](https://eips.ethereum.org/EIPS/eip-7840).
116+
117+
### `FixtureEngineNewPayload`
118+
119+
#### - `executionPayload`: [`FixtureExecutionPayload`](#fixtureexecutionpayload)
120+
121+
Execution payload.
122+
123+
#### - `blob_versioned_hashes`: [`Optional`](./common_types.md#optional)`[`[`List`](./common_types.md#list)`[`[`Hash`](./common_types.md#hash)`]]` `(fork: Cancun)`
124+
125+
List of hashes of the versioned blobs that are part of the execution payload.
126+
127+
#### - `parentBeaconBlockRoot`: [`Optional`](./common_types.md#optional)`[`[`Hash`](./common_types.md#hash)`]` `(fork: Cancun)`
128+
129+
Hash of the parent beacon block root.
130+
131+
#### - `validationError`: [`Optional`](./common_types.md#optional)`[`[`TransactionException`](../../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException)` | `[`BlockException`](../../library/ethereum_test_exceptions.md#ethereum_test_exceptions.BlockException)`]`
132+
133+
For sync tests, this field should not be present as sync tests only work with valid chains. Invalid blocks cannot be synced.
134+
135+
#### - `version`: [`Number`](./common_types.md#number)
136+
137+
Version of the `engine_newPayloadVX` directive to use to deliver the payload.
138+
139+
### `FixtureExecutionPayload`
140+
141+
#### - `parentHash`: [`Hash`](./common_types.md#hash)
142+
143+
Hash of the parent block.
144+
145+
#### - `feeRecipient`: [`Address`](./common_types.md#address)
146+
147+
Address of the account that will receive the rewards for building the block.
148+
149+
#### - `stateRoot`: [`Hash`](./common_types.md#hash)
150+
151+
Root hash of the state trie.
152+
153+
#### - `receiptsRoot`: [`Hash`](./common_types.md#hash)
154+
155+
Root hash of the receipts trie.
156+
157+
#### - `logsBloom`: [`Bloom`](./common_types.md#bloom)
158+
159+
Bloom filter composed of the logs of all the transactions in the block.
160+
161+
#### - `blockNumber`: [`HexNumber`](./common_types.md#hexnumber)
162+
163+
Number of the block.
164+
165+
#### - `gasLimit`: [`HexNumber`](./common_types.md#hexnumber)
166+
167+
Total gas limit of the block.
168+
169+
#### - `gasUsed`: [`HexNumber`](./common_types.md#hexnumber)
170+
171+
Total gas used by all the transactions in the block.
172+
173+
#### - `timestamp`: [`HexNumber`](./common_types.md#hexnumber)
174+
175+
Timestamp of the block.
176+
177+
#### - `extraData`: [`Bytes`](./common_types.md#bytes)
178+
179+
Extra data of the block.
180+
181+
#### - `prevRandao`: [`Hash`](./common_types.md#hash)
182+
183+
PrevRandao of the block.
184+
185+
#### - `blockHash`: [`Hash`](./common_types.md#hash)
186+
187+
Hash of the block.
188+
189+
#### - `transactions`: [`List`](./common_types.md#list)`[`[`Bytes`](./common_types.md#bytes)`]`
190+
191+
List of transactions in the block, in serialized format.
192+
193+
#### - `withdrawals`: [`List`](./common_types.md#list)`[`[`FixtureWithdrawal`](#fixturewithdrawal)`]`
194+
195+
List of withdrawals in the block.
196+
197+
#### - `baseFeePerGas`: [`HexNumber`](./common_types.md#hexnumber) `(fork: London)`
198+
199+
Base fee per gas of the block.
200+
201+
#### - `blobGasUsed`: [`HexNumber`](./common_types.md#hexnumber) `(fork: Cancun)`
202+
203+
Total blob gas used by all the transactions in the block.
204+
205+
#### - `excessBlobGas`: [`HexNumber`](./common_types.md#hexnumber) `(fork: Cancun)`
206+
207+
Excess blob gas of the block used to calculate the blob fee per gas for this block.
208+
209+
### `FixtureWithdrawal`
210+
211+
#### - `index`: [`HexNumber`](./common_types.md#hexnumber)
212+
213+
Index of the withdrawal
214+
215+
#### - `validatorIndex`: [`HexNumber`](./common_types.md#hexnumber)
216+
217+
Withdrawing validator index
218+
219+
#### - `address`: [`Address`](./common_types.md#address)
220+
221+
Address to withdraw to
222+
223+
#### - `amount`: [`HexNumber`](./common_types.md#hexnumber)
224+
225+
Amount of the withdrawal
226+
227+
## Differences from Blockchain Engine Tests
228+
229+
While the Blockchain Sync Test format is similar to the Blockchain Engine Test format, there are key differences:
230+
231+
1. **`syncPayload` field**: Contains the final block used to trigger synchronization on the sync client.
232+
2. **Multi-client testing**: Tests involve two clients (client under test and sync client) rather than a single client.
233+
3. **P2P networking**: Tests require P2P connection establishment between clients.
234+
4. **No invalid blocks**: Sync tests only work with valid chains as invalid blocks cannot be synced.
235+
5. **`postStateHash` field**: Optional field for state verification when full post-state is not included.
236+
237+
## Fork Support
238+
239+
Blockchain Sync Tests are only supported for post-merge forks (Paris and later) as they rely on the Engine API for synchronization triggering.

src/cli/pytest_commands/consume.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def get_command_logic_test_paths(command_name: str, is_hive: bool) -> List[Path]
4848
command_logic_test_paths = [
4949
base_path / "simulators" / "simulator_logic" / f"test_via_{command_name}.py"
5050
]
51+
elif command_name == "sync":
52+
command_logic_test_paths = [
53+
base_path / "simulators" / "simulator_logic" / "test_via_sync.py"
54+
]
5155
elif command_name == "direct":
5256
command_logic_test_paths = [base_path / "direct" / "test_via_direct.py"]
5357
else:
@@ -107,6 +111,12 @@ def engine() -> None:
107111
pass
108112

109113

114+
@consume_command(is_hive=True)
115+
def sync() -> None:
116+
"""Client consumes via the Engine API with sync testing."""
117+
pass
118+
119+
110120
@consume_command(is_hive=True)
111121
def hive() -> None:
112122
"""Client consumes via all available hive methods (rlp, engine)."""

src/cli/pytest_commands/processors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def process_args(self, args: List[str]) -> List[str]:
101101

102102
if self.command_name == "engine":
103103
modified_args.extend(["-p", "pytest_plugins.consume.simulators.engine.conftest"])
104+
elif self.command_name == "sync":
105+
modified_args.extend(["-p", "pytest_plugins.consume.simulators.sync.conftest"])
104106
elif self.command_name == "rlp":
105107
modified_args.extend(["-p", "pytest_plugins.consume.simulators.rlp.conftest"])
106108
else:

src/ethereum_test_fixtures/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .blockchain import (
55
BlockchainEngineFixture,
66
BlockchainEngineFixtureCommon,
7+
BlockchainEngineSyncFixture,
78
BlockchainEngineXFixture,
89
BlockchainFixture,
910
BlockchainFixtureCommon,
@@ -19,6 +20,7 @@
1920
"BaseFixture",
2021
"BlockchainEngineFixture",
2122
"BlockchainEngineFixtureCommon",
23+
"BlockchainEngineSyncFixture",
2224
"BlockchainEngineXFixture",
2325
"BlockchainFixture",
2426
"BlockchainFixtureCommon",

src/ethereum_test_fixtures/blockchain.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ class BlockchainEngineFixture(BlockchainEngineFixtureCommon):
543543
genesis: FixtureHeader = Field(..., alias="genesisBlockHeader")
544544
post_state: Alloc | None = Field(None)
545545
payloads: List[FixtureEngineNewPayload] = Field(..., alias="engineNewPayloads")
546-
sync_payload: FixtureEngineNewPayload | None = None
547546

548547

549548
@post_state_validator(alternate_field="post_state_diff")
@@ -571,5 +570,19 @@ class BlockchainEngineXFixture(BlockchainEngineFixtureCommon):
571570
payloads: List[FixtureEngineNewPayload] = Field(..., alias="engineNewPayloads")
572571
"""Engine API payloads for blockchain execution."""
573572

573+
574+
class BlockchainEngineSyncFixture(BlockchainEngineFixture):
575+
"""
576+
Engine Sync specific test fixture information.
577+
578+
This fixture format is specifically designed for sync testing where:
579+
- The client under test receives all payloads
580+
- A sync client attempts to sync from the client under test
581+
- Both client types are parametrized from hive client config
582+
"""
583+
584+
format_name: ClassVar[str] = "blockchain_test_sync"
585+
description: ClassVar[str] = (
586+
"Tests that generate a blockchain test fixture for Engine API testing with client sync."
587+
)
574588
sync_payload: FixtureEngineNewPayload | None = None
575-
"""Optional sync payload for blockchain synchronization."""

src/ethereum_test_rpc/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
"""JSON-RPC methods and helper functions for EEST consume based hive simulators."""
22

3-
from .rpc import BlockNumberType, DebugRPC, EngineRPC, EthRPC, SendTransactionExceptionError
3+
from .rpc import (
4+
AdminRPC,
5+
BlockNumberType,
6+
DebugRPC,
7+
EngineRPC,
8+
EthRPC,
9+
NetRPC,
10+
SendTransactionExceptionError,
11+
)
412
from .types import (
513
BlobAndProofV1,
614
BlobAndProofV2,
@@ -10,6 +18,7 @@
1018
)
1119

1220
__all__ = [
21+
"AdminRPC",
1322
"BlobAndProofV1",
1423
"BlobAndProofV2",
1524
"BlockNumberType",
@@ -19,5 +28,6 @@
1928
"EthRPC",
2029
"ForkConfig",
2130
"ForkConfigBlobSchedule",
31+
"NetRPC",
2232
"SendTransactionExceptionError",
2333
]

0 commit comments

Comments
 (0)