11"""Pytest test to verify a client's configuration using `eth_config` RPC endpoint."""
22
3+ import json
34import time
5+ from hashlib import sha256
6+ from typing import Dict , List
47
58import pytest
69
710from ethereum_test_rpc import EthConfigResponse , EthRPC
811
12+ from .eth_config import get_eth_config , get_rpc_url_combinations_el_cl
913from .types import NetworkConfig
1014
1115
@@ -16,9 +20,10 @@ def eth_config_response(eth_rpc: EthRPC) -> EthConfigResponse | None:
1620
1721
1822@pytest .fixture (scope = "session" )
19- def network (request : pytest . FixtureRequest ) -> NetworkConfig :
23+ def network (request ) -> NetworkConfig :
2024 """Get the network that will be used to verify all tests."""
21- return request .config .network # type: ignore
25+ config = request .config
26+ return config .getoption ("network" )
2227
2328
2429@pytest .fixture (scope = "session" )
@@ -36,8 +41,13 @@ def expected_eth_config(network: NetworkConfig, current_time: int) -> EthConfigR
3641def test_eth_config_current (
3742 eth_config_response : EthConfigResponse | None ,
3843 expected_eth_config : EthConfigResponse ,
44+ request ,
3945) -> None :
4046 """Validate `current` field of the `eth_config` RPC endpoint."""
47+ config = request .config
48+ if config .getoption ("network_config_file" ) is None :
49+ pytest .skip ("Skipping test because no 'network_config_file' was specified" )
50+
4151 assert eth_config_response is not None , "Client did not return a valid `eth_config` response."
4252 assert eth_config_response .current is not None , (
4353 "Client did not return a valid `current` fork config."
@@ -53,8 +63,13 @@ def test_eth_config_current(
5363def test_eth_config_current_fork_id (
5464 eth_config_response : EthConfigResponse | None ,
5565 expected_eth_config : EthConfigResponse ,
66+ request ,
5667) -> None :
5768 """Validate `forkId` field within the `current` configuration object."""
69+ config = request .config
70+ if config .getoption ("network_config_file" ) is None :
71+ pytest .skip ("Skipping test because no 'network_config_file' was specified" )
72+
5873 assert eth_config_response is not None , "Client did not return a valid `eth_config` response."
5974 assert eth_config_response .current is not None , (
6075 "Client did not return a valid `current` fork config."
@@ -72,8 +87,15 @@ def test_eth_config_current_fork_id(
7287def test_eth_config_next (
7388 eth_config_response : EthConfigResponse | None ,
7489 expected_eth_config : EthConfigResponse ,
90+ request ,
7591) -> None :
7692 """Validate `next` field of the `eth_config` RPC endpoint."""
93+ config = request .config
94+ if config .getoption ("network_config_file" ) is None :
95+ pytest .skip ("Skipping test because no 'network_config_file' was specified" )
96+ else :
97+ print ("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" )
98+
7799 assert eth_config_response is not None , "Client did not return a valid `eth_config` response."
78100 expected_next = expected_eth_config .next
79101 if expected_next is None :
@@ -94,8 +116,13 @@ def test_eth_config_next(
94116def test_eth_config_next_fork_id (
95117 eth_config_response : EthConfigResponse | None ,
96118 expected_eth_config : EthConfigResponse ,
119+ request ,
97120) -> None :
98121 """Validate `forkId` field within the `next` configuration object."""
122+ config = request .config
123+ if config .getoption ("network_config_file" ) is None :
124+ pytest .skip ("Skipping test because no 'network_config_file' was specified" )
125+
99126 assert eth_config_response is not None , "Client did not return a valid `eth_config` response."
100127 expected_next = expected_eth_config .next
101128 if expected_next is None :
@@ -124,8 +151,12 @@ def test_eth_config_next_fork_id(
124151def test_eth_config_last (
125152 eth_config_response : EthConfigResponse | None ,
126153 expected_eth_config : EthConfigResponse ,
154+ config : pytest .Config ,
127155) -> None :
128156 """Validate `last` field of the `eth_config` RPC endpoint."""
157+ if config .getoption ("network_config_file" ) is None :
158+ pytest .skip ("Skipping test because no 'network_config_file' was specified" )
159+
129160 expected_last = expected_eth_config .last
130161 assert eth_config_response is not None , "Client did not return a valid `eth_config` response."
131162 if expected_last is None :
@@ -146,8 +177,12 @@ def test_eth_config_last(
146177def test_eth_config_last_fork_id (
147178 eth_config_response : EthConfigResponse | None ,
148179 expected_eth_config : EthConfigResponse ,
180+ config : pytest .Config ,
149181) -> None :
150182 """Validate `forkId` field within the `last` configuration object."""
183+ if config .getoption ("network_config_file" ) is None :
184+ pytest .skip ("Skipping test because no 'network_config_file' was specified" )
185+
151186 assert eth_config_response is not None , "Client did not return a valid `eth_config` response."
152187 expected_last = expected_eth_config .last
153188 if expected_last is None :
@@ -171,3 +206,66 @@ def test_eth_config_last_fork_id(
171206 f"{ received_fork_id } != "
172207 f"{ expected_last_fork_id } "
173208 )
209+
210+
211+ def test_eth_config_majority (
212+ request ,
213+ ) -> None :
214+ """Queries devnet exec clients for their eth_config and fails if not all have the same response.""" # noqa: E501
215+ # decide whether to run this test
216+ config = request .config
217+ run_this_test_bool = config .getoption (name = "majority_eth_config_test_enabled" )
218+ if not run_this_test_bool :
219+ pytest .skip ("Skipping eth_config majority test" )
220+
221+ # retrieve required values for running this test
222+ rpc_endpoint = config .getoption ("rpc_endpoint" )
223+ el_clients : List [str ] = config .getoption ("majority_clients" ) # besu, erigon, ..
224+
225+ url_dict : None | Dict [str , List [str ]] = get_rpc_url_combinations_el_cl (
226+ el_clients = el_clients , rpc_endpoint = rpc_endpoint
227+ )
228+ assert url_dict is not None
229+ responses = dict () # noqa: C408
230+ for exec_client in url_dict .keys ():
231+ # try only as many consensus+exec client combinations until you receive a response
232+ # if all combinations fail we panic
233+ for url in url_dict [exec_client ]:
234+ success , response = get_eth_config (url )
235+ if not success :
236+ # safely split url to not leak rpc_endpoint in logs
237+ print (
238+ f"When trying to get eth_config from { url .split ('@' , 1 )[- 1 ] if '@' in url else '' } the following problem occurred: { response } " # noqa: E501
239+ )
240+ continue
241+
242+ responses [exec_client ] = response
243+ print (f"Response of { exec_client } : { response } \n \n " )
244+ break # no need to gather more responses for this client
245+
246+ assert len (responses .keys ()) == len (el_clients ), "Failed to get an eth_config response "
247+ f" from each specified execution client. Full list of execution clients is { el_clients } "
248+ f"but we were only able to gather eth_config responses from: { responses .keys ()} \n Will try "
249+ "again with a different consensus-execution client combination for this execution client"
250+
251+ # determine hashes of client responses
252+ client_to_hash_dict = dict () # noqa: C408
253+ for client in responses .keys ():
254+ response_bytes = json .dumps (responses [client ], sort_keys = True ).encode ("utf-8" )
255+ response_hash = sha256 (response_bytes ).digest ().hex ()
256+ print (f"Response hash of client { client } : { response_hash } " )
257+ client_to_hash_dict [client ] = response_hash
258+
259+ # if not all responses have the same hash there is a critical consensus issue
260+ expected_hash = ""
261+ for h in client_to_hash_dict .keys ():
262+ if expected_hash == "" :
263+ expected_hash = client_to_hash_dict [h ]
264+ continue
265+
266+ assert client_to_hash_dict [h ] == expected_hash , (
267+ "Critical consensus issue: Not all eth_config responses are the same!"
268+ )
269+ assert expected_hash != ""
270+
271+ print ("All clients returned the same eth_config response. Test has been passed!" )
0 commit comments