Skip to content

Commit 97a8a70

Browse files
committed
feat(consume): Add consume sync tests
Add a new type of test that is basically `consume_engine` but gets another client to sync after payloads are executed. - These tests can be filled with: `uv run fill ... -m "blockchain_engine_sync_test"` - 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 - some cleanup
1 parent 2f97840 commit 97a8a70

File tree

12 files changed

+900
-14
lines changed

12 files changed

+900
-14
lines changed

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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,3 +568,29 @@ class BlockchainEngineXFixture(BlockchainEngineFixtureCommon):
568568

569569
sync_payload: FixtureEngineNewPayload | None = None
570570
"""Optional sync payload for blockchain synchronization."""
571+
572+
573+
class BlockchainEngineSyncFixture(BlockchainEngineFixtureCommon):
574+
"""
575+
Engine Sync specific test fixture information.
576+
577+
This fixture format is specifically designed for sync testing where:
578+
- The client under test receives all payloads
579+
- A sync client attempts to sync from the client under test
580+
- Both client types are parametrized from hive client config
581+
"""
582+
583+
format_name: ClassVar[str] = "blockchain_engine_sync_test"
584+
description: ClassVar[str] = (
585+
"Tests that generate a blockchain test fixture for Engine API testing with client sync."
586+
)
587+
pre: Alloc
588+
genesis: FixtureHeader = Field(..., alias="genesisBlockHeader")
589+
post_state: Alloc | None = Field(None)
590+
payloads: List[FixtureEngineNewPayload] = Field(..., alias="engineNewPayloads")
591+
sync_payload: FixtureEngineNewPayload | None = None
592+
593+
@classmethod
594+
def output_base_dir_name(cls) -> str:
595+
"""Return the proper output directory name for sync fixtures."""
596+
return "blockchain_engine_sync_tests"

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
]

src/ethereum_test_rpc/rpc.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ def get_block_by_number(self, block_number: BlockNumberType = "latest", full_txs
152152
block = hex(block_number) if isinstance(block_number, int) else block_number
153153
return self.post_request("getBlockByNumber", block, full_txs)
154154

155+
def get_block_by_hash(self, block_hash: Hash, full_txs: bool = True):
156+
"""`eth_getBlockByHash`: Returns information about a block by hash."""
157+
return self.post_request("getBlockByHash", f"{block_hash}", full_txs)
158+
155159
def get_balance(self, address: Address, block_number: BlockNumberType = "latest") -> int:
156160
"""`eth_getBalance`: Returns the balance of the account of given address."""
157161
block = hex(block_number) if isinstance(block_number, int) else block_number
@@ -378,3 +382,20 @@ def get_blobs(
378382
),
379383
context=self.response_validation_context,
380384
)
385+
386+
387+
class NetRPC(BaseRPC):
388+
"""Represents a net RPC class for network-related RPC calls."""
389+
390+
def peer_count(self) -> int:
391+
"""`net_peerCount`: Get the number of peers connected to the client."""
392+
response = self.post_request("peerCount")
393+
return int(response, 16) # hex -> int
394+
395+
396+
class AdminRPC(BaseRPC):
397+
"""Represents an admin RPC class for administrative RPC calls."""
398+
399+
def add_peer(self, enode: str) -> bool:
400+
"""`admin_addPeer`: Add a peer by enode URL."""
401+
return self.post_request("addPeer", enode)

src/ethereum_test_specs/blockchain.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from ethereum_test_fixtures import (
3434
BaseFixture,
3535
BlockchainEngineFixture,
36+
BlockchainEngineSyncFixture,
3637
BlockchainEngineXFixture,
3738
BlockchainFixture,
3839
FixtureFormat,
@@ -415,6 +416,7 @@ class BlockchainTest(BaseTest):
415416
BlockchainFixture,
416417
BlockchainEngineFixture,
417418
BlockchainEngineXFixture,
419+
BlockchainEngineSyncFixture,
418420
]
419421
supported_execute_formats: ClassVar[Sequence[LabeledExecuteFormat]] = [
420422
LabeledExecuteFormat(
@@ -427,6 +429,7 @@ class BlockchainTest(BaseTest):
427429
supported_markers: ClassVar[Dict[str, str]] = {
428430
"blockchain_test_engine_only": "Only generate a blockchain test engine fixture",
429431
"blockchain_test_only": "Only generate a blockchain test fixture",
432+
"blockchain_engine_sync_test": "Generate a blockchain engine sync test fixture",
430433
}
431434

432435
@classmethod
@@ -437,11 +440,18 @@ def discard_fixture_format_by_marks(
437440
markers: List[pytest.Mark],
438441
) -> bool:
439442
"""Discard a fixture format from filling if the appropriate marker is used."""
440-
if "blockchain_test_only" in [m.name for m in markers]:
443+
marker_names = [m.name for m in markers]
444+
445+
if "blockchain_test_only" in marker_names:
441446
return fixture_format != BlockchainFixture
442-
if "blockchain_test_engine_only" in [m.name for m in markers]:
447+
if "blockchain_test_engine_only" in marker_names:
443448
return fixture_format != BlockchainEngineFixture
444-
return False
449+
if "blockchain_engine_sync_test" in marker_names:
450+
# Only generate sync fixture when marker is present
451+
return fixture_format != BlockchainEngineSyncFixture
452+
else:
453+
# When sync marker is NOT present, exclude sync fixture
454+
return fixture_format == BlockchainEngineSyncFixture
445455

446456
def get_genesis_environment(self, fork: Fork) -> Environment:
447457
"""Get the genesis environment for pre-allocation groups."""
@@ -710,7 +720,7 @@ def make_hive_fixture(
710720
t8n: TransitionTool,
711721
fork: Fork,
712722
fixture_format: FixtureFormat = BlockchainEngineFixture,
713-
) -> BlockchainEngineFixture | BlockchainEngineXFixture:
723+
) -> BlockchainEngineFixture | BlockchainEngineXFixture | BlockchainEngineSyncFixture:
714724
"""Create a hive fixture from the blocktest definition."""
715725
fixture_payloads: List[FixtureEngineNewPayload] = []
716726

@@ -755,15 +765,12 @@ def make_hive_fixture(
755765
self.verify_post_state(t8n, t8n_state=alloc)
756766

757767
sync_payload: Optional[FixtureEngineNewPayload] = None
758-
if self.verify_sync:
768+
# Only include sync_payload for BlockchainEngineSyncFixture
769+
if fixture_format == BlockchainEngineSyncFixture:
759770
# Test is marked for syncing verification.
760-
assert genesis.header.block_hash != head_hash, (
761-
"Invalid payload tests negative test via sync is not supported yet."
762-
)
763-
764771
# Most clients require the header to start the sync process, so we create an empty
765-
# block on top of the last block of the test to send it as new payload and trigger the
766-
# sync process.
772+
# block on top of the last block of the test to send it as new payload and trigger
773+
# the sync process.
767774
sync_built_block = self.generate_block_data(
768775
t8n=t8n,
769776
fork=fork,
@@ -802,6 +809,17 @@ def make_hive_fixture(
802809
}
803810
)
804811
return BlockchainEngineXFixture(**fixture_data)
812+
elif fixture_format == BlockchainEngineSyncFixture:
813+
# For sync fixture format
814+
fixture_data.update(
815+
{
816+
"payloads": fixture_payloads,
817+
"sync_payload": sync_payload,
818+
"pre": pre,
819+
"post_state": alloc if not self.exclude_full_post_state_in_output else None,
820+
}
821+
)
822+
return BlockchainEngineSyncFixture(**fixture_data)
805823
else:
806824
# Standard engine fixture
807825
fixture_data.update(
@@ -822,7 +840,11 @@ def generate(
822840
) -> BaseFixture:
823841
"""Generate the BlockchainTest fixture."""
824842
t8n.reset_traces()
825-
if fixture_format in [BlockchainEngineFixture, BlockchainEngineXFixture]:
843+
if fixture_format in [
844+
BlockchainEngineFixture,
845+
BlockchainEngineXFixture,
846+
BlockchainEngineSyncFixture,
847+
]:
826848
return self.make_hive_fixture(t8n, fork, fixture_format)
827849
elif fixture_format == BlockchainFixture:
828850
return self.make_fixture(t8n, fork)

src/pytest_plugins/consume/simulators/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ def check_live_port(test_suite_name: str) -> Literal[8545, 8551]:
2929
return 8545
3030
elif test_suite_name == "eest/consume-engine":
3131
return 8551
32+
elif test_suite_name == "eest/consume-sync":
33+
return 8551
3234
raise ValueError(
3335
f"Unexpected test suite name '{test_suite_name}' while setting HIVE_CHECK_LIVE_PORT."
3436
)

0 commit comments

Comments
 (0)