Skip to content

Commit 5a51af9

Browse files
authored
feat(execute): Add Engine RPC option to execute remote (#2070)
* feat(rpc): Allow EngineRPC to have a custom jwt secret * fix(rpc): Allow specifying a namespace for RPC subclasses * refactor(rpc): Small changes * feat(execute): Allow to specify an Engine RPC endpoint to drive the chain refactor(execute): rename class fix(plugins/execute): rebase issues * docs: Update `docs/running_tests/execute/remote.md` * docs: changelog
1 parent 54d2050 commit 5a51af9

File tree

7 files changed

+608
-506
lines changed

7 files changed

+608
-506
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ Users can select any of the artifacts depending on their testing needs for their
105105
- ✨ Added new `Blob` class which can use the ckzg library to generate valid blobs at runtime ([#1614](https://github.com/ethereum/execution-spec-tests/pull/1614)).
106106
- ✨ Added `blob_transaction_test` execute test spec, which allows tests that send blob transactions to a running client and verifying its `engine_getBlobsVX` endpoint behavior ([#1644](https://github.com/ethereum/execution-spec-tests/pull/1644)).
107107
- ✨ Added `execute eth-config` command to test the `eth_config` RPC endpoint of a client, and includes configurations by default for Mainnet, Sepolia, Holesky, and Hoodi ([#1863](https://github.com/ethereum/execution-spec-tests/pull/1863)).
108+
- ✨ Command `execute remote` now allows specification of an Engine API endpoint to drive the chain via `--engine-endpoint` and either `--engine-jwt-secret` or `--engine-jwt-secret-file`. This mode is useful when there's no consensus client connected to the execution client so `execute` will automatically request blocks via the Engine API when it sends transactions ([#2070](https://github.com/ethereum/execution-spec-tests/pull/2070)).
108109
- ✨ Added `--address-stubs` flag to the `execute` command which allows to specify a JSON-formatted string, JSON file or YAML file which contains label-to-address of specific pre-deployed contracts already existing in the network where the tests are executed ([#2073](https://github.com/ethereum/execution-spec-tests/pull/2073)).
109110

110111
### 📋 Misc

docs/running_tests/execute/remote.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ One last requirement is that the `--rpc-chain-id` flag is set to the chain id of
2424
uv run execute remote --fork=Prague --rpc-endpoint=https://rpc.endpoint.io --rpc-seed-key 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f --rpc-chain-id 12345
2525
```
2626

27+
## Engine RPC Endpoint (Optional)
28+
29+
By default, the `execute remote` command assumes that the execution client is connected to a beacon node and the chain progresses automatically. However, you can optionally specify an Engine RPC endpoint to drive the chain manually when new transactions are submitted.
30+
31+
To use this feature, you need to provide both the `--engine-endpoint` and JWT authentication:
32+
33+
```bash
34+
uv run execute remote --fork=Prague --rpc-endpoint=https://rpc.endpoint.io --rpc-seed-key 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f --rpc-chain-id 12345 --engine-endpoint=https://engine.endpoint.io --engine-jwt-secret "your-jwt-secret-here"
35+
```
36+
37+
Alternatively, you can provide the JWT secret from a file:
38+
39+
```bash
40+
uv run execute remote --fork=Prague --rpc-endpoint=https://rpc.endpoint.io --rpc-seed-key 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f --rpc-chain-id 12345 --engine-endpoint=https://engine.endpoint.io --engine-jwt-secret-file /path/to/jwt-secret.txt
41+
```
42+
43+
The JWT secret file must contain only the JWT secret as a hex string.
44+
45+
When an engine endpoint is provided, the test execution will use the Engine API to create new blocks and include transactions, giving you full control over the chain progression.
46+
2747
The `execute remote` command will connect to the client via the RPC endpoint and will start executing every test in the `./tests` folder in the same way as the `execute hive` command, but instead of using the Engine API to generate blocks, it will send the transactions to the client via the RPC endpoint.
2848

2949
It is recommended to only run a subset of the tests when executing on a live network. To do so, a path to a specific test can be provided to the command:

src/ethereum_test_rpc/rpc.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,22 @@ class BaseRPC:
5757
def __init__(
5858
self,
5959
url: str,
60-
extra_headers: Dict | None = None,
60+
*,
6161
response_validation_context: Any | None = None,
6262
):
6363
"""Initialize BaseRPC class with the given url."""
64-
if extra_headers is None:
65-
extra_headers = {}
6664
self.url = url
6765
self.request_id_counter = count(1)
68-
self.extra_headers = extra_headers
6966
self.response_validation_context = response_validation_context
7067

71-
def __init_subclass__(cls) -> None:
68+
def __init_subclass__(cls, namespace: str | None = None) -> None:
7269
"""Set namespace of the RPC class to the lowercase of the class name."""
73-
namespace = cls.__name__
74-
if namespace.endswith("RPC"):
75-
namespace = namespace.removesuffix("RPC")
76-
cls.namespace = namespace.lower()
70+
if namespace is None:
71+
namespace = cls.__name__
72+
if namespace.endswith("RPC"):
73+
namespace = namespace.removesuffix("RPC")
74+
namespace = namespace.lower()
75+
cls.namespace = namespace
7776

7877
def post_request(
7978
self,
@@ -99,7 +98,7 @@ def post_request(
9998
base_header = {
10099
"Content-Type": "application/json",
101100
}
102-
headers = base_header | self.extra_headers | extra_headers
101+
headers = base_header | extra_headers
103102

104103
response = requests.post(self.url, json=payload, headers=headers)
105104
response.raise_for_status()
@@ -125,18 +124,12 @@ class EthRPC(BaseRPC):
125124

126125
def __init__(
127126
self,
128-
url: str,
129-
extra_headers: Dict | None = None,
130-
*,
127+
*args,
131128
transaction_wait_timeout: int = 60,
132-
response_validation_context: Any | None = None,
129+
**kwargs,
133130
):
134131
"""Initialize EthRPC class with the given url and transaction wait timeout."""
135-
if extra_headers is None:
136-
extra_headers = {}
137-
super().__init__(
138-
url, extra_headers, response_validation_context=response_validation_context
139-
)
132+
super().__init__(*args, **kwargs)
140133
self.transaction_wait_timeout = transaction_wait_timeout
141134

142135
def config(self):
@@ -331,6 +324,18 @@ class EngineRPC(BaseRPC):
331324
simulators.
332325
"""
333326

327+
jwt_secret: bytes
328+
329+
def __init__(
330+
self,
331+
*args,
332+
jwt_secret: bytes = b"secretsecretsecretsecretsecretse", # Default secret used in hive
333+
**kwargs,
334+
):
335+
"""Initialize Engine RPC class with the given JWT secret."""
336+
super().__init__(*args, **kwargs)
337+
self.jwt_secret = jwt_secret
338+
334339
def post_request(
335340
self,
336341
method: str,
@@ -343,7 +348,7 @@ def post_request(
343348
extra_headers = {}
344349
jwt_token = encode(
345350
{"iat": int(time.time())},
346-
b"secretsecretsecretsecretsecretse", # the secret used within clients in hive
351+
self.jwt_secret,
347352
algorithm="HS256",
348353
)
349354
extra_headers = {

src/pytest_plugins/execute/execute.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test execution plugin for pytest, to run Ethereum tests using in live networks."""
22

3+
import os
34
from dataclasses import dataclass, field
45
from pathlib import Path
56
from typing import Any, Dict, Generator, List, Type
@@ -73,6 +74,22 @@ def pytest_addoption(parser):
7374
f"(Default: {EnvironmentDefaults.gas_limit // 4})"
7475
),
7576
)
77+
execute_group.addoption(
78+
"--transactions-per-block",
79+
action="store",
80+
dest="transactions_per_block",
81+
type=int,
82+
default=None,
83+
help=("Number of transactions to send before producing the next block."),
84+
)
85+
execute_group.addoption(
86+
"--get-payload-wait-time",
87+
action="store",
88+
dest="get_payload_wait_time",
89+
type=float,
90+
default=0.3,
91+
help=("Time to wait after sending a forkchoice_updated before getting the payload."),
92+
)
7693

7794
report_group = parser.getgroup("tests", "Arguments defining html report behavior")
7895
report_group.addoption(
@@ -178,6 +195,18 @@ def pytest_html_report_title(report):
178195
report.title = "Execute Test Report"
179196

180197

198+
@pytest.fixture(scope="session")
199+
def transactions_per_block(request) -> int: # noqa: D103
200+
if transactions_per_block := request.config.getoption("transactions_per_block"):
201+
return transactions_per_block
202+
203+
# Get the number of workers for the test
204+
worker_count_env = os.environ.get("PYTEST_XDIST_WORKER_COUNT")
205+
if not worker_count_env:
206+
return 1
207+
return max(int(worker_count_env), 1)
208+
209+
181210
@pytest.fixture(scope="session")
182211
def default_gas_price(request) -> int:
183212
"""Return default gas price used for transactions."""

0 commit comments

Comments
 (0)