Skip to content

Commit 5e4fe91

Browse files
committed
fix(execute): add gas validation for benchmark tests in execute mode
Previously, execute mode was not validating that transactions consumed the expected amount of gas when expected_benchmark_gas_used was set. This could cause benchmark tests to incorrectly pass even when consuming significantly less gas than expected (e.g., due to missing factory contracts). This feature is needed by benchmark tests like the ones in #2186 in order to make sure that the benchmarks are indeed consuming all gas available or causing a failure otherwise when the flag is set. Changes: - Add expected_benchmark_gas_used and skip_gas_used_validation fields to TransactionPost - Implement gas validation logic in TransactionPost.execute() using transaction receipts - Pass gas validation parameters from StateTest and BlockchainTest to TransactionPost - Add eth_getTransactionReceipt RPC method to fetch gas used from receipts This ensures benchmark tests fail appropriately when gas consumption doesn't match expectations, preventing false positives in performance testing.
1 parent 6262592 commit 5e4fe91

File tree

6 files changed

+60
-0
lines changed

6 files changed

+60
-0
lines changed

src/ethereum_test_execution/transaction_post.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ class TransactionPost(BaseExecute):
2020

2121
blocks: List[List[Transaction]]
2222
post: Alloc
23+
# Gas validation fields for benchmark tests
24+
gas_benchmark_value: int | None = None # Default expected gas from command-line
25+
expected_benchmark_gas_used: int | None = None # Expected total gas to be consumed
26+
skip_gas_used_validation: bool = False # Skip gas validation even if expected is set
2327

2428
format_name: ClassVar[str] = "transaction_post_test"
2529
description: ClassVar[str] = (
@@ -33,6 +37,10 @@ def execute(
3337
assert not any(tx.ty == 3 for block in self.blocks for tx in block), (
3438
"Transaction type 3 is not supported in execute mode."
3539
)
40+
41+
# Track transaction hashes for gas validation (benchmarking)
42+
all_tx_hashes = []
43+
3644
for block in self.blocks:
3745
signed_txs = []
3846
for tx_index, tx in enumerate(block):
@@ -51,11 +59,38 @@ def execute(
5159
for transaction in signed_txs:
5260
if transaction.error is None:
5361
eth_rpc.send_wait_transaction(transaction)
62+
all_tx_hashes.append(transaction.hash)
5463
else:
5564
with pytest.raises(SendTransactionExceptionError):
5665
eth_rpc.send_transaction(transaction)
5766
else:
5867
eth_rpc.send_wait_transactions(signed_txs)
68+
all_tx_hashes.extend([tx.hash for tx in signed_txs])
69+
70+
# Perform gas validation if required for benchmarking
71+
# Ensures benchmark tests consume exactly the expected gas
72+
if not self.skip_gas_used_validation:
73+
# Use expected_benchmark_gas_used if set, else gas_benchmark_value
74+
expected_gas = self.expected_benchmark_gas_used
75+
if expected_gas is None:
76+
expected_gas = self.gas_benchmark_value
77+
78+
# Only validate if we have an expected value
79+
if expected_gas is not None:
80+
total_gas_used = 0
81+
# Fetch transaction receipts to get actual gas used
82+
for tx_hash in all_tx_hashes:
83+
receipt = eth_rpc.get_transaction_receipt(tx_hash)
84+
assert receipt is not None, f"Failed to get receipt for transaction {tx_hash}"
85+
gas_used = int(receipt["gasUsed"], 16)
86+
total_gas_used += gas_used
87+
88+
# Verify that the total gas consumed matches expectations
89+
assert total_gas_used == expected_gas, (
90+
f"Total gas used ({total_gas_used}) does not match "
91+
f"expected gas ({expected_gas}), "
92+
f"difference: {total_gas_used - expected_gas}"
93+
)
5994

6095
for address, account in self.post.root.items():
6196
balance = eth_rpc.get_balance(address)

src/ethereum_test_rpc/rpc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,18 @@ def get_transaction_by_hash(self, transaction_hash: Hash) -> TransactionByHashRe
292292
pprint(e.errors())
293293
raise e
294294

295+
def get_transaction_receipt(self, transaction_hash: Hash) -> dict | None:
296+
"""
297+
`eth_getTransactionReceipt`: Returns transaction receipt.
298+
299+
Used to get the actual gas used by a transaction for gas validation
300+
in benchmark tests.
301+
"""
302+
response = self.post_request(
303+
method="getTransactionReceipt", params=[f"{transaction_hash}"]
304+
)
305+
return response
306+
295307
def get_storage_at(
296308
self, address: Address, position: Hash, block_number: BlockNumberType = "latest"
297309
) -> Hash:

src/ethereum_test_specs/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class BaseTest(BaseModel):
7676
_gas_optimization: int | None = PrivateAttr(None)
7777
_gas_optimization_max_gas_limit: int | None = PrivateAttr(None)
7878

79+
gas_benchmark_value: int | None = None
7980
expected_benchmark_gas_used: int | None = None
8081
skip_gas_used_validation: bool = False
8182

@@ -124,6 +125,7 @@ def from_test(
124125
new_instance = cls(
125126
tag=base_test.tag,
126127
t8n_dump_dir=base_test.t8n_dump_dir,
128+
gas_benchmark_value=base_test.gas_benchmark_value,
127129
expected_benchmark_gas_used=base_test.expected_benchmark_gas_used,
128130
skip_gas_used_validation=base_test.skip_gas_used_validation,
129131
**kwargs,

src/ethereum_test_specs/blockchain.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,9 +899,13 @@ def execute(
899899
blocks: List[List[Transaction]] = []
900900
for block in self.blocks:
901901
blocks += [block.txs]
902+
# Pass gas validation params for benchmark tests
902903
return TransactionPost(
903904
blocks=blocks,
904905
post=self.post,
906+
gas_benchmark_value=self.gas_benchmark_value,
907+
expected_benchmark_gas_used=self.expected_benchmark_gas_used,
908+
skip_gas_used_validation=self.skip_gas_used_validation,
905909
)
906910
raise Exception(f"Unsupported execute format: {execute_format}")
907911

src/ethereum_test_specs/state.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,13 @@ def execute(
444444
) -> BaseExecute:
445445
"""Generate the list of test fixtures."""
446446
if execute_format == TransactionPost:
447+
# Pass gas validation params for benchmark tests
447448
return TransactionPost(
448449
blocks=[[self.tx]],
449450
post=self.post,
451+
gas_benchmark_value=self.gas_benchmark_value,
452+
expected_benchmark_gas_used=self.expected_benchmark_gas_used,
453+
skip_gas_used_validation=self.skip_gas_used_validation,
450454
)
451455
raise Exception(f"Unsupported execute format: {execute_format}")
452456

src/pytest_plugins/execute/execute.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ def __init__(self, *args, **kwargs):
342342
kwargs["pre"] = pre
343343
elif kwargs["pre"] != pre:
344344
raise ValueError("The pre-alloc object was modified by the test.")
345+
# Pass gas_benchmark_value if not already set
346+
if "gas_benchmark_value" not in kwargs:
347+
kwargs["gas_benchmark_value"] = request.getfixturevalue("gas_benchmark_value")
345348
kwargs |= {
346349
p: request.getfixturevalue(p)
347350
for p in cls_fixture_parameters

0 commit comments

Comments
 (0)