Skip to content

Commit 2c4d277

Browse files
raxhvlmarioevz
andauthored
✨ feat(gentest): Support for transaction types (#1166)
* ✨ feat(gentest): Type 2 transaction * ✨ feat: Source of truth for Tx model * 🥢 nit: Casting to tx * gentest: src/cli/gentest/tests/test_cli.py refactor * fix(rpc): Adapt client's response to our model * fix(rpc): More fixes * fix(rpc): Use pydantic's AliasChoice --------- Co-authored-by: raxhvl <[email protected]> Co-authored-by: Mario Vega <[email protected]>
1 parent ed958b8 commit 2c4d277

File tree

7 files changed

+136
-144
lines changed

7 files changed

+136
-144
lines changed

src/cli/gentest/request_manager.py

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,16 @@
1212

1313
from typing import Dict
1414

15-
from pydantic import BaseModel
16-
1715
from config import EnvConfig
18-
from ethereum_test_base_types import Hash, HexNumber
16+
from ethereum_test_base_types import Hash
1917
from ethereum_test_rpc import BlockNumberType, DebugRPC, EthRPC
20-
from ethereum_test_types import Transaction
18+
from ethereum_test_rpc.types import TransactionByHashResponse
19+
from ethereum_test_types import Environment
2120

2221

2322
class RPCRequest:
2423
"""Interface for the RPC interaction with remote node."""
2524

26-
class RemoteTransaction(Transaction):
27-
"""Model that represents a transaction."""
28-
29-
block_number: HexNumber
30-
tx_hash: Hash
31-
32-
class RemoteBlock(BaseModel):
33-
"""Model that represents a block."""
34-
35-
coinbase: str
36-
difficulty: str
37-
gas_limit: str
38-
number: str
39-
timestamp: str
40-
4125
node_url: str
4226
headers: dict[str, str]
4327

@@ -49,46 +33,27 @@ def __init__(self):
4933
self.rpc = EthRPC(node_config.node_url, extra_headers=headers)
5034
self.debug_rpc = DebugRPC(node_config.node_url, extra_headers=headers)
5135

52-
def eth_get_transaction_by_hash(self, transaction_hash: Hash) -> RemoteTransaction:
36+
def eth_get_transaction_by_hash(self, transaction_hash: Hash) -> TransactionByHashResponse:
5337
"""Get transaction data."""
5438
res = self.rpc.get_transaction_by_hash(transaction_hash)
5539
block_number = res.block_number
5640
assert block_number is not None, "Transaction does not seem to be included in any block"
5741

58-
assert res.ty == 0, (
59-
f"Transaction has type {res.ty}: Currently only type 0 transactions are supported."
60-
)
61-
62-
return RPCRequest.RemoteTransaction(
63-
block_number=block_number,
64-
tx_hash=res.transaction_hash,
65-
ty=res.ty,
66-
gas_limit=res.gas_limit,
67-
gas_price=res.gas_price,
68-
data=res.data,
69-
nonce=res.nonce,
70-
sender=res.from_address,
71-
to=res.to_address,
72-
value=res.value,
73-
v=res.v,
74-
r=res.r,
75-
s=res.s,
76-
protected=True if res.v > 30 else False,
77-
)
42+
return res
7843

79-
def eth_get_block_by_number(self, block_number: BlockNumberType) -> RemoteBlock:
44+
def eth_get_block_by_number(self, block_number: BlockNumberType) -> Environment:
8045
"""Get block by number."""
8146
res = self.rpc.get_block_by_number(block_number)
8247

83-
return RPCRequest.RemoteBlock(
84-
coinbase=res["miner"],
48+
return Environment(
49+
fee_recipient=res["miner"],
8550
number=res["number"],
8651
difficulty=res["difficulty"],
8752
gas_limit=res["gasLimit"],
8853
timestamp=res["timestamp"],
8954
)
9055

91-
def debug_trace_call(self, transaction: RemoteTransaction) -> Dict[str, dict]:
56+
def debug_trace_call(self, transaction: TransactionByHashResponse) -> Dict[str, dict]:
9257
"""Get pre-state required for transaction."""
9358
assert transaction.sender is not None
9459
assert transaction.to is not None

src/cli/gentest/test_context_providers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class StateTestProvider(Provider):
3838
"""Provides context required to generate a `state_test` using pytest."""
3939

4040
transaction_hash: Hash
41-
block: Optional[RPCRequest.RemoteBlock] = None
42-
transaction: Optional[RPCRequest.RemoteTransaction] = None
41+
block: Optional[Environment] = None
42+
transaction: Optional[Transaction] = None
4343
state: Optional[Dict[str, Dict]] = None
4444

4545
def _make_rpc_calls(self):
@@ -60,7 +60,7 @@ def _make_rpc_calls(self):
6060

6161
def _get_environment(self) -> Environment:
6262
assert self.block is not None
63-
return Environment(**self.block.model_dump())
63+
return self.block
6464

6565
def _get_pre_state(self) -> Dict[str, Account]:
6666
assert self.state is not None
@@ -79,7 +79,7 @@ def _get_pre_state(self) -> Dict[str, Account]:
7979

8080
def _get_transaction(self) -> Transaction:
8181
assert self.transaction is not None
82-
return Transaction(**self.transaction.model_dump())
82+
return self.transaction
8383

8484
def get_context(self) -> Dict[str, Any]:
8585
"""

src/cli/gentest/test_providers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
from pydantic import BaseModel
1818

1919
from ethereum_test_base_types import Account, Address, ZeroPaddedHexNumber
20-
21-
from .request_manager import RPCRequest
20+
from ethereum_test_rpc.types import TransactionByHashResponse
21+
from ethereum_test_types import Environment
2222

2323

2424
class BlockchainTestProvider(BaseModel):
2525
"""Provides context required to generate a `blockchain_test` using pytest."""
2626

27-
block: RPCRequest.RemoteBlock
28-
transaction: RPCRequest.RemoteTransaction
27+
block: Environment
28+
transaction: TransactionByHashResponse
2929
state: Dict[Address, Account]
3030

3131
def _get_environment_kwargs(self) -> str:
@@ -111,5 +111,5 @@ def get_context(self) -> Dict[str, Any]:
111111
"environment_kwargs": self._get_environment_kwargs(),
112112
"pre_state_items": self._get_pre_state_items(),
113113
"transaction_items": self._get_transaction_items(),
114-
"tx_hash": self.transaction.tx_hash,
114+
"tx_hash": self.transaction.hash,
115115
}

src/cli/gentest/tests/test_cli.py

Lines changed: 88 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for the gentest CLI command."""
22

3+
import pytest
34
from click.testing import CliRunner
45

56
from cli.gentest.cli import generate
@@ -8,91 +9,104 @@
89
from ethereum_test_base_types import Account
910
from ethereum_test_tools import Environment, Storage, Transaction
1011

12+
transactions_by_type = {
13+
0: {
14+
"environment": Environment(
15+
fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
16+
gas_limit=9916577,
17+
number=9974504,
18+
timestamp=1588257377,
19+
difficulty=2315196811272822,
20+
parent_ommers_hash="0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
21+
extra_data=b"\x00",
22+
),
23+
"pre_state": {
24+
"0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c": Account(
25+
nonce=6038603, balance=23760714652307793035, code=b"", storage=Storage(root={})
26+
),
27+
"0x8a4a4d396a06cba2a7a4a73245991de40cdec289": Account(
28+
nonce=2, balance=816540000000000000, code=b"", storage=Storage(root={})
29+
),
30+
"0xc6d96786477f82491bfead8f00b8294688f77abc": Account(
31+
nonce=25, balance=29020266497911578313, code=b"", storage=Storage(root={})
32+
),
33+
},
34+
"transaction": Transaction(
35+
ty=0,
36+
chain_id=1,
37+
nonce=2,
38+
gas_price=10000000000,
39+
gas_limit=21000,
40+
to="0xc6d96786477f82491bfead8f00b8294688f77abc",
41+
value=668250000000000000,
42+
data=b"",
43+
v=38,
44+
r=57233334052658009540326312124836763247359579695589124499839562829147086216092,
45+
s=49687643984819828983661675232336138386174947240467726918882054280625462464348,
46+
sender="0x8a4a4d396a06cba2a7a4a73245991de40cdec289",
47+
),
48+
},
49+
2: {
50+
"environment": Environment(
51+
fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
52+
gas_limit=30172625,
53+
number=21758000,
54+
timestamp=1738489319,
55+
parent_ommers_hash="0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
56+
extra_data=b"\x00",
57+
),
58+
"pre_state": {
59+
"0x24d6c74d811cfde65995ed26fd08af445f8aab06": Account(
60+
nonce=1011, balance=139840767390685635650, code=b"", storage=Storage(root={})
61+
),
62+
"0xd5fbda4c79f38920159fe5f22df9655fde292d47": Account(
63+
nonce=553563, balance=162510989019530720334, code=b"", storage=Storage(root={})
64+
),
65+
"0xe2e29f9a85cfecb9cdaa83a81c7aa2792f24d93f": Account(
66+
nonce=104, balance=553317651330968100, code=b"", storage=Storage(root={})
67+
),
68+
},
69+
"transaction": Transaction(
70+
ty=2,
71+
chain_id=1,
72+
nonce=553563,
73+
max_priority_fee_per_gas=1900000,
74+
max_fee_per_gas=3992652948,
75+
gas_limit=63000,
76+
to="0xe2e29f9a85cfecb9cdaa83a81c7aa2792f24d93f",
77+
value=221305417266040400,
78+
v=1,
79+
r=23565967349511399087318407428036702220029523660288023156323795583373026415631,
80+
s=9175853102116430015855393834807954374677057556696757715994220939907579927771,
81+
sender="0xd5fbda4c79f38920159fe5f22df9655fde292d47",
82+
),
83+
},
84+
}
1185

12-
def test_generate_success(tmp_path, monkeypatch):
13-
"""Test the generate command with a successful scenario."""
14-
## Arrange ##
1586

87+
@pytest.fixture
88+
def transaction_hash(tx_type: int) -> str: # noqa: D103
89+
return str(transactions_by_type[tx_type]["transaction"].hash) # type: ignore
90+
91+
92+
@pytest.mark.parametrize("tx_type", list(transactions_by_type.keys()))
93+
def test_tx_type(tmp_path, monkeypatch, tx_type, transaction_hash):
94+
"""Generates a test case for any transaction type."""
95+
## Arrange ##
1696
# This test is run in a CI environment, where connection to a node could be
1797
# unreliable. Therefore, we mock the RPC request to avoid any network issues.
1898
# This is done by patching the `get_context` method of the `StateTestProvider`.
1999
runner = CliRunner()
20-
transaction_hash = "0xa41f343be7a150b740e5c939fa4d89f3a2850dbe21715df96b612fc20d1906be"
21-
output_file = str(tmp_path / "gentest.py")
100+
output_file = str(tmp_path / f"gentest_type_{tx_type}.py")
101+
102+
tx = transactions_by_type[tx_type]
22103

23104
def get_mock_context(self: StateTestProvider) -> dict:
24-
return {
25-
"environment": Environment(
26-
fee_recipient="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
27-
gas_limit=9916577,
28-
number=9974504,
29-
timestamp=1588257377,
30-
prev_randao=None,
31-
difficulty=2315196811272822,
32-
base_fee_per_gas=None,
33-
excess_blob_gas=None,
34-
target_blobs_per_block=None,
35-
parent_difficulty=None,
36-
parent_timestamp=None,
37-
parent_base_fee_per_gas=None,
38-
parent_gas_used=None,
39-
parent_gas_limit=None,
40-
blob_gas_used=None,
41-
parent_ommers_hash="0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
42-
parent_blob_gas_used=None,
43-
parent_excess_blob_gas=None,
44-
parent_beacon_block_root=None,
45-
block_hashes={},
46-
ommers=[],
47-
withdrawals=None,
48-
extra_data=b"\x00",
49-
parent_hash=None,
50-
),
51-
"pre_state": {
52-
"0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c": Account(
53-
nonce=6038603, balance=23760714652307793035, code=b"", storage=Storage(root={})
54-
),
55-
"0x8a4a4d396a06cba2a7a4a73245991de40cdec289": Account(
56-
nonce=2, balance=816540000000000000, code=b"", storage=Storage(root={})
57-
),
58-
"0xc6d96786477f82491bfead8f00b8294688f77abc": Account(
59-
nonce=25, balance=29020266497911578313, code=b"", storage=Storage(root={})
60-
),
61-
},
62-
"transaction": Transaction(
63-
ty=0,
64-
chain_id=1,
65-
nonce=2,
66-
gas_price=10000000000,
67-
max_priority_fee_per_gas=None,
68-
max_fee_per_gas=None,
69-
gas_limit=21000,
70-
to="0xc6d96786477f82491bfead8f00b8294688f77abc",
71-
value=668250000000000000,
72-
data=b"",
73-
access_list=None,
74-
max_fee_per_blob_gas=None,
75-
blob_versioned_hashes=None,
76-
v=38,
77-
r=57233334052658009540326312124836763247359579695589124499839562829147086216092,
78-
s=49687643984819828983661675232336138386174947240467726918882054280625462464348,
79-
sender="0x8a4a4d396a06cba2a7a4a73245991de40cdec289",
80-
authorization_list=None,
81-
secret_key=None,
82-
error=None,
83-
protected=True,
84-
rlp_override=None,
85-
wrapped_blob_transaction=False,
86-
blobs=None,
87-
blob_kzg_commitments=None,
88-
blob_kzg_proofs=None,
89-
),
90-
"tx_hash": transaction_hash,
91-
}
105+
return tx
92106

93107
monkeypatch.setattr(StateTestProvider, "get_context", get_mock_context)
94108

95-
## Genenrate ##
109+
## Generate ##
96110
gentest_result = runner.invoke(generate, [transaction_hash, output_file])
97111
assert gentest_result.exit_code == 0
98112

src/config/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __init__(self):
6868
if not ENV_PATH.exists():
6969
raise FileNotFoundError(
7070
f"The configuration file '{ENV_PATH}' does not exist. "
71-
"Run `uv run env_int` to create it."
71+
"Run `uv run eest make env` to create it."
7272
)
7373

7474
with ENV_PATH.open("r") as file:

0 commit comments

Comments
 (0)