Skip to content

Commit 72bd211

Browse files
committed
enable support for eth beacon single method tests
- rename EthConsensus to EthBeacon for related classes - enable support for eth beacon methods to be tested in single method test mode - moved method classes to methods module - fixed incorrect EthValidatorStatus, removed PENDING_ONGOING as it doesn't exist, added PENDING_QUEUED - enable retry for fetching blocks from eth beacon endpoint -
1 parent 2bb22c4 commit 72bd211

File tree

17 files changed

+679
-471
lines changed

17 files changed

+679
-471
lines changed

chainbench/main.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@
1111
from click import Context, Parameter
1212
from locust import runners
1313

14-
from chainbench.user import EvmMethods
14+
from chainbench.user.methods import (
15+
all_method_classes,
16+
all_methods,
17+
get_subclass_functions,
18+
)
1519
from chainbench.util.cli import (
1620
ContextData,
1721
ensure_results_dir,
1822
get_base_path,
1923
get_master_command,
2024
get_profile_path,
2125
get_profiles,
22-
get_subclass_methods,
2326
get_worker_command,
24-
task_to_method,
2527
)
2628
from chainbench.util.monitor import monitors
2729
from chainbench.util.notify import NoopNotifier, Notifier
@@ -52,8 +54,7 @@ def cli(ctx: Context):
5254

5355
def validate_method(ctx: Context, param: Parameter, value: str) -> str:
5456
if value is not None:
55-
method_list = [task_to_method(task) for task in get_subclass_methods(EvmMethods)]
56-
if value not in method_list:
57+
if value not in all_methods.keys():
5758
raise click.BadParameter(
5859
f"Method {value} is not supported. " f"Use 'chainbench list methods' to list all available methods."
5960
)
@@ -229,7 +230,7 @@ def start(
229230
profile_dir = get_base_path(__file__) / "profile"
230231

231232
if method:
232-
profile_path = get_base_path(__file__) / "tools" / "test_method.py"
233+
profile_path = Path(all_methods[method])
233234
elif profile:
234235
profile_path = get_profile_path(profile_dir, profile)
235236
else:
@@ -472,12 +473,14 @@ def profiles(profile_dir: Path) -> None:
472473

473474

474475
@_list.command(
475-
help="Lists all available evm methods.",
476+
help="Lists all available methods.",
476477
)
477478
def methods() -> None:
478-
task_list = get_subclass_methods(EvmMethods)
479-
for task in task_list:
480-
click.echo(task_to_method(task))
479+
for method_class in all_method_classes:
480+
click.echo(f"\nMethods for {method_class.__name__}:")
481+
task_list = get_subclass_functions(method_class)
482+
for task in task_list:
483+
click.echo(f"- {method_class.task_to_method(task.name)}")
481484

482485

483486
if __name__ == "__main__":
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
"""
2-
Ethereum Consensus profile.
2+
Ethereum Beacon profile.
33
"""
44
from locust import constant_pacing
55

6-
from chainbench.user.ethereum import EthConsensusMethods
6+
from chainbench.user.methods.ethereum import EthBeaconMethods
77

88
# mypy: ignore_errors
99

1010

11-
class EthereumConsensusProfile(EthConsensusMethods):
11+
class EthereumBeaconProfile(EthBeaconMethods):
1212
wait_time = constant_pacing(1)
1313
tasks = {
14-
EthConsensusMethods.eth_v1_beacon_states_validators_random_ids_task: 698,
15-
EthConsensusMethods.eth_v1_beacon_states_validators_head_task: 23,
16-
EthConsensusMethods.eth_v1_beacon_states_head_committees_random_epoch_task: 10,
17-
EthConsensusMethods.eth_v1_beacon_states_validators_random_validator_status_task: 10,
18-
EthConsensusMethods.eth_v1_beacon_states_random_state_id_finality_checkpoints_task: 10,
19-
EthConsensusMethods.eth_v2_beacon_blocks_random_block_id_task: 8,
20-
EthConsensusMethods.eth_v2_beacon_blocks_head_task: 6,
21-
EthConsensusMethods.eth_v1_beacon_headers_head_task: 4,
22-
EthConsensusMethods.eth_v1_config_spec_task: 3,
23-
EthConsensusMethods.eth_v1_node_health_task: 2,
24-
EthConsensusMethods.eth_v2_beacon_blocks_finalized_task: 1,
25-
EthConsensusMethods.eth_v1_beacon_headers_random_block_id_task: 1,
26-
EthConsensusMethods.eth_v1_node_version_task: 1,
14+
EthBeaconMethods.eth_v1_beacon_states_validators_random_ids_task: 698,
15+
EthBeaconMethods.eth_v1_beacon_states_validators_head_task: 23,
16+
EthBeaconMethods.eth_v1_beacon_states_head_committees_random_epoch_task: 10,
17+
EthBeaconMethods.eth_v1_beacon_states_validators_random_status_task: 10,
18+
EthBeaconMethods.eth_v1_beacon_states_random_state_id_finality_checkpoints_task: 10,
19+
EthBeaconMethods.eth_v2_beacon_blocks_random_block_id_task: 8,
20+
EthBeaconMethods.eth_v2_beacon_blocks_head_task: 6,
21+
EthBeaconMethods.eth_v1_beacon_headers_head_task: 4,
22+
EthBeaconMethods.eth_v1_config_spec_task: 3,
23+
EthBeaconMethods.eth_v1_node_health_task: 2,
24+
EthBeaconMethods.eth_v2_beacon_blocks_finalized_task: 1,
25+
EthBeaconMethods.eth_v1_beacon_headers_random_block_id_task: 1,
26+
EthBeaconMethods.eth_v1_node_version_task: 1,
2727
}

chainbench/profile/evm/all.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from locust import constant_pacing
66

7-
from chainbench.user import EvmMethods
8-
from chainbench.util.cli import get_subclass_methods
7+
from chainbench.user.methods import EvmMethods
8+
from chainbench.user.methods.common import get_subclass_functions
99

1010

1111
class EvmAllProfile(EvmMethods):
1212
wait_time = constant_pacing(1)
13-
tasks = [EvmMethods.get_method(method) for method in get_subclass_methods(EvmMethods)]
13+
tasks = [task.method for task in get_subclass_functions(EvmMethods)]

chainbench/profile/starknet/wallet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def simulate_transaction_task(self):
7070
name="simulate_transaction",
7171
method="starknet_simulateTransaction",
7272
params=self._simulate_transaction_params_factory(get_rng()),
73-
url_postfix="/rpc/v0.3",
73+
path="/rpc/v0.3",
7474
),
7575

7676
@task(1)

chainbench/test_data/blockchain.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from gevent.lock import Semaphore as GeventSemaphore
88
from tenacity import retry, stop_after_attempt
99

10-
from chainbench.util.http import HttpClient, HttpErrorLevel
10+
from chainbench.util.http import HttpClient
1111
from chainbench.util.rng import RNG, get_rng
1212

1313
logger = logging.getLogger(__name__)
@@ -124,8 +124,8 @@ def __init__(self) -> None:
124124
self._lock.acquire()
125125
self._logger.debug("Locked")
126126

127-
def init_http_client(self, host_url: str):
128-
self._client = HttpClient(host_url, error_level=HttpErrorLevel.ServerError)
127+
def init_http_client(self, host_url: str) -> None:
128+
self._client = HttpClient(host_url)
129129
self._logger.debug("Host: %s", host_url)
130130

131131
@property

chainbench/test_data/ethereum.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
from argparse import Namespace
55
from dataclasses import dataclass
66

7+
from tenacity import retry, stop_after_attempt
8+
79
from chainbench.test_data.blockchain import (
810
Block,
911
BlockNotFoundError,
1012
BlockNumber,
1113
BlockRange,
1214
TestData,
1315
)
16+
from chainbench.util.http import HttpClient, HttpErrorLevel, HttpStatusError
1417
from chainbench.util.rng import RNG
1518

1619
logger = logging.getLogger(__name__)
@@ -20,15 +23,15 @@
2023

2124
class EthValidatorStatus:
2225
ACTIVE = "active"
23-
ACTIVE_ONGOING = "active_ongoing"
2426
ACTIVE_EXITING = "active_exiting"
27+
ACTIVE_ONGOING = "active_ongoing"
2528
ACTIVE_SLASHED = "active_slashed"
26-
PENDING = "pending"
27-
PENDING_ONGOING = "pending_ongoing"
28-
PENDING_INITIALIZED = "pending_initialized"
2929
EXITED = "exited"
30-
EXITED_UNSLASHED = "exited_unslashed"
3130
EXITED_SLASHED = "exited_slashed"
31+
EXITED_UNSLASHED = "exited_unslashed"
32+
PENDING = "pending"
33+
PENDING_INITIALIZED = "pending_initialized"
34+
PENDING_QUEUED = "pending_queued"
3235
WITHDRAWAL = "withdrawal"
3336
WITHDRAWAL_DONE = "withdrawal_done"
3437
WITHDRAWAL_POSSIBLE = "withdrawal_possible"
@@ -55,7 +58,7 @@ def to_dict(self):
5558

5659

5760
@dataclass(frozen=True)
58-
class EthConsensusBlock(Block):
61+
class EthBeaconBlock(Block):
5962
epoch: int
6063
committees: list[EthCommittee]
6164

@@ -77,7 +80,7 @@ def from_response(cls, slot: BlockNumber, data: dict[str, t.Any]):
7780
for validator in committee["validators"]:
7881
validators.append(EthValidator(validator))
7982
committees.append(EthCommittee(committee["index"], validators))
80-
return EthConsensusBlock(slot, epoch, committees)
83+
return EthBeaconBlock(slot, epoch, committees)
8184

8285
def get_random_committee(self, rng: RNG) -> EthCommittee:
8386
return rng.random.choice(self.committees)
@@ -94,29 +97,42 @@ def get_random_validator_indexes(self, count: int, rng: RNG) -> list[str]:
9497
return [validator.index for validator in committee.validators[start_index : start_index + count]]
9598

9699

97-
class EthConsensusTestData(TestData[EthConsensusBlock]):
100+
class EthBeaconTestData(TestData[EthBeaconBlock]):
101+
def init_http_client(self, host_url: str):
102+
self._client = HttpClient(host_url, error_level=HttpErrorLevel.ServerError)
103+
self._logger.debug("Host: %s", host_url)
104+
98105
def fetch_block_header(self, block_id: int | str) -> dict[str, t.Any]:
99106
block_response = self.client.get(f"/eth/v1/beacon/headers/{block_id}")
100107
if block_response.status_code == 404:
101108
raise BlockNotFoundError
109+
elif block_response.status_code != 200:
110+
raise HttpStatusError(
111+
block_response.status_code, f"Failed to fetch block header: {block_response.status_message}"
112+
)
102113
return block_response.json["data"]["header"]["message"]
103114

104-
def fetch_block(self, block_id: int | str) -> EthConsensusBlock:
115+
def fetch_block(self, block_id: int | str) -> EthBeaconBlock:
105116
if isinstance(block_id, str):
106117
if (block_id := block_id.lower()) not in (
107118
"head",
108119
"genesis",
109120
"finalized",
110121
):
111122
raise ValueError("Invalid block identifier")
112-
slot = int(self.fetch_block_header(block_id)["slot"])
123+
try:
124+
slot = int(self.fetch_block_header(block_id)["slot"])
113125

114-
committees_response = self.client.get(f"/eth/v1/beacon/states/{block_id}/committees", params={"slot": slot})
115-
return EthConsensusBlock.from_response(slot, committees_response.json)
126+
committees_response = self.client.get(f"/eth/v1/beacon/states/{block_id}/committees", params={"slot": slot})
127+
return EthBeaconBlock.from_response(slot, committees_response.json)
128+
except BlockNotFoundError:
129+
return self.fetch_latest_block()
116130

117-
def fetch_latest_block(self) -> EthConsensusBlock:
131+
@retry(reraise=True, stop=stop_after_attempt(5))
132+
def fetch_latest_block(self) -> EthBeaconBlock:
118133
return self.fetch_block("head")
119134

135+
@retry(reraise=True, stop=stop_after_attempt(5))
120136
def fetch_latest_block_number(self) -> BlockNumber:
121137
return int(self.fetch_block_header("head")["slot"])
122138

@@ -129,7 +145,7 @@ def _get_start_and_end_blocks(self, parsed_options: Namespace) -> BlockRange:
129145
logger.info("Using blocks from %s to %s as test data", start_block_number, end_block_number)
130146
return BlockRange(start_block_number, end_block_number)
131147

132-
def get_block_from_data(self, data: dict[str, t.Any] | str) -> EthConsensusBlock:
148+
def get_block_from_data(self, data: dict[str, t.Any] | str) -> EthBeaconBlock:
133149
def get_committee(committee: dict[str, t.Any]) -> EthCommittee:
134150
return EthCommittee(
135151
index=committee["index"],
@@ -144,7 +160,7 @@ def get_committee(committee: dict[str, t.Any]) -> EthCommittee:
144160
slot = data_dict["block_number"]
145161
epoch = slot // 32
146162
committees = [get_committee(committee) for committee in data_dict["committees"]]
147-
return EthConsensusBlock(slot, epoch, committees)
163+
return EthBeaconBlock(slot, epoch, committees)
148164

149165
def get_random_epoch(self, rng: RNG) -> Epoch:
150166
return self.get_random_block(rng).epoch

chainbench/tools/test_method.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

chainbench/user/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from chainbench.util.event import setup_event_listeners
22

3-
from .evm import EvmMethods, EvmUser
3+
from .evm import EvmUser
44
from .http import HttpUser, JsonRpcUser
55
from .solana import SolanaUser
66
from .starknet import StarkNetUser
@@ -16,5 +16,4 @@
1616
"JsonRpcUser",
1717
"SolanaUser",
1818
"StarkNetUser",
19-
"EvmMethods",
2019
]

0 commit comments

Comments
 (0)