Skip to content

Commit 835e0a7

Browse files
authored
chore(consume): mark sync tests flaky (#2252)
* chore(consume): Mark all consume sync tests as flaky * refactor: simplify consume sync logic * chore: CHANGELOG entry
1 parent 6116b96 commit 835e0a7

File tree

5 files changed

+50
-89
lines changed

5 files changed

+50
-89
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ Test fixtures for use by clients are available for each release on the [Github r
2222
#### `consume`
2323

2424
- ✨ Add retry logic to RPC requests to fix flaky connection issues in Hive ([#2205](https://github.com/ethereum/execution-spec-tests/pull/2205)).
25+
- 🛠️ Mark `consume sync` tests as `flaky` with 3 retires due to client sync inconsistencies ([#2252](https://github.com/ethereum/execution-spec-tests/pull/2252)).
2526

2627
### 📋 Misc
2728

2829
- ✨ Add tighter validation for EIP-7928 model coming from t8n when filling ([#2138](https://github.com/ethereum/execution-spec-tests/pull/2138)).
2930
- ✨ Add flexible API for absence checks for EIP-7928 (BAL) tests ([#2124](https://github.com/ethereum/execution-spec-tests/pull/2124)).
30-
- 🐞 Use ``engine_newPayloadV5`` for `>=Amsterdam` forks in `consume engine` ([#2170](https://github.com/ethereum/execution-spec-tests/pull/2170)).
31+
- 🐞 Use `engine_newPayloadV5` for `>=Amsterdam` forks in `consume engine` ([#2170](https://github.com/ethereum/execution-spec-tests/pull/2170)).
3132
- 🔀 Refactor EIP-7928 (BAL) absence checks into a friendlier class-based DevEx ([#2175](https://github.com/ethereum/execution-spec-tests/pull/2175)).
3233

3334
### 🧪 Test Cases

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dependencies = [
4646
"questionary>=2.1.0,<3",
4747
"ethereum-rlp>=0.1.3,<0.2",
4848
"pytest-regex>=0.2.0,<0.3",
49+
"pytest-rerunfailures>=16.0,<17",
4950
"eth-abi>=5.2.0",
5051
"joblib>=1.4.2",
5152
"ckzg>=2.1.3,<3",

src/pytest_plugins/consume/simulators/simulator_logic/test_via_sync.py

Lines changed: 27 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import pytest
1818

19-
from ethereum_test_base_types import Hash
2019
from ethereum_test_exceptions import UndefinedException
2120
from ethereum_test_fixtures import BlockchainEngineSyncFixture
2221
from ethereum_test_rpc import AdminRPC, EngineRPC, EthRPC, NetRPC
@@ -42,65 +41,6 @@ def __init__(self, *args: object) -> None:
4241
logger.fail(str(self))
4342

4443

45-
def wait_for_sync(
46-
sync_eth_rpc: EthRPC,
47-
expected_block_hash: str | Hash,
48-
timeout: int = 10,
49-
poll_interval: float = 1.0,
50-
) -> bool:
51-
"""Wait for the sync client to reach the expected block hash."""
52-
start_time = time.time()
53-
last_block_number = 0
54-
no_progress_count = 0
55-
56-
while time.time() - start_time < timeout:
57-
try:
58-
# First check if we have the expected block
59-
block = sync_eth_rpc.get_block_by_hash(Hash(expected_block_hash))
60-
if block is not None:
61-
logger.info(f"Sync complete! Client has block {expected_block_hash}")
62-
return True
63-
64-
# Check current sync progress
65-
current_block = sync_eth_rpc.get_block_by_number("latest")
66-
if current_block:
67-
current_number = int(current_block.get("number", "0x0"), 16)
68-
current_hash = current_block.get("hash", "unknown")
69-
if current_number > last_block_number:
70-
logger.info(f"Sync progress: block {current_number} (hash: {current_hash})")
71-
last_block_number = current_number
72-
no_progress_count = 0
73-
else:
74-
no_progress_count += 1
75-
if no_progress_count == 1:
76-
logger.info(
77-
f"Sync client is at block {current_number} (hash: {current_hash})"
78-
)
79-
elif no_progress_count % 10 == 0:
80-
logger.debug(
81-
f"No sync progress for {no_progress_count} polls, "
82-
f"still at block {current_number}"
83-
)
84-
85-
except Exception as e:
86-
logger.debug(f"Error checking sync status: {e}")
87-
88-
time.sleep(poll_interval)
89-
90-
# Log final state
91-
try:
92-
final_block = sync_eth_rpc.get_block_by_number("latest")
93-
if final_block:
94-
logger.warning(
95-
f"Sync timeout! Final block: {final_block.get('number', 'unknown')} "
96-
f"(hash: {final_block.get('hash', 'unknown')})"
97-
)
98-
except Exception:
99-
pass
100-
101-
return False
102-
103-
10444
def test_blockchain_via_sync(
10545
timing_data: TimingData,
10646
eth_rpc: EthRPC,
@@ -445,7 +385,7 @@ def test_blockchain_via_sync(
445385
last_forkchoice_time = time.time()
446386
forkchoice_interval = 2.0 # Send forkchoice updates every 2 seconds
447387

448-
while time.time() - sync_start_time < 60: # 60 second timeout
388+
while time.time() - sync_start_time < 15: # 15 second timeout
449389
# Send periodic forkchoice updates to keep sync alive
450390
if time.time() - last_forkchoice_time >= forkchoice_interval:
451391
try:
@@ -473,35 +413,35 @@ def test_blockchain_via_sync(
473413
f"within timeout"
474414
)
475415

476-
# Final verification
416+
logger.info("Sync verification successful!")
417+
418+
# Verify the final state but give a few tries
419+
assert eth_rpc is not None, "eth_rpc is required"
477420
assert sync_eth_rpc is not None, "sync_eth_rpc is required"
478-
assert sync_engine_rpc is not None, "sync_engine_rpc is required"
479-
if wait_for_sync(sync_eth_rpc, last_valid_block_hash, timeout=5):
480-
logger.info("Sync verification successful!")
481-
482-
# Verify the final state
483-
sync_block = sync_eth_rpc.get_block_by_hash(last_valid_block_hash)
484-
client_block = eth_rpc.get_block_by_hash(last_valid_block_hash)
485-
486-
if sync_block["stateRoot"] != client_block["stateRoot"]:
487-
raise LoggedError(
488-
f"State root mismatch after sync. "
489-
f"Sync client: {sync_block['stateRoot']}, "
490-
f"Client under test: {client_block['stateRoot']}"
491-
)
492421

493-
# Verify post state if available
494-
if fixture.post_state_hash:
495-
if sync_block["stateRoot"] != str(fixture.post_state_hash):
422+
for attempt in range(3):
423+
try:
424+
sync_block = sync_eth_rpc.get_block_by_hash(last_valid_block_hash)
425+
client_block = eth_rpc.get_block_by_hash(last_valid_block_hash)
426+
427+
if sync_block["stateRoot"] != client_block["stateRoot"]:
496428
raise LoggedError(
497-
f"Final state root mismatch. "
498-
f"Expected: {fixture.post_state_hash}, "
499-
f"Got: {sync_block['stateRoot']}"
429+
f"State root mismatch after sync. "
430+
f"Sync client: {sync_block['stateRoot']}, "
431+
f"Client under test: {client_block['stateRoot']}"
500432
)
501-
else:
502-
raise LoggedError(
503-
f"Sync client failed to synchronize to block {last_valid_block_hash} "
504-
f"within timeout"
505-
)
433+
434+
if fixture.post_state_hash:
435+
if sync_block["stateRoot"] != str(fixture.post_state_hash):
436+
raise LoggedError(
437+
f"Final state root mismatch. "
438+
f"Expected: {fixture.post_state_hash}, "
439+
f"Got: {sync_block['stateRoot']}"
440+
)
441+
except Exception as e:
442+
if attempt < 2:
443+
time.sleep(1)
444+
continue
445+
raise e
506446

507447
logger.info("Sync test completed successfully!")

src/pytest_plugins/consume/simulators/sync/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ def pytest_collection_modifyitems(session, config, items):
4747
del session, config
4848

4949
for item in items:
50+
# Auto-mark all verify_sync tests as flaky with 3 reruns
51+
if item.get_closest_marker("blockchain_test_sync"):
52+
item.add_marker(pytest.mark.flaky(reruns=3))
53+
5054
# Check if this test has both client_type and sync_client_type
5155
if (
5256
hasattr(item, "callspec")

uv.lock

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)