Skip to content

Commit 0ef435a

Browse files
authored
fix(execute): add gas validation for benchmark tests in execute mode (#2219)
* 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. * refactor(execute): simplify gas validation implementation Addresses review comment to make execute mode gas validation cleaner: - Set expected_benchmark_gas_used to gas_benchmark_value as default in execute parametrizer - Remove gas_benchmark_value parameter from TransactionPost, StateTest, BlockchainTest, and BaseTest - Simplify gas validation logic in TransactionPost This ensures consistent gas validation behavior between fill and execute modes with a cleaner implementation that sets defaults at the parametrizer level.
1 parent 675f1a7 commit 0ef435a

File tree

5 files changed

+50
-0
lines changed

5 files changed

+50
-0
lines changed

src/ethereum_test_execution/transaction_post.py

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

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

2427
format_name: ClassVar[str] = "transaction_post_test"
2528
description: ClassVar[str] = (
@@ -33,6 +36,10 @@ def execute(
3336
assert not any(tx.ty == 3 for block in self.blocks for tx in block), (
3437
"Transaction type 3 is not supported in execute mode."
3538
)
39+
40+
# Track transaction hashes for gas validation (benchmarking)
41+
all_tx_hashes = []
42+
3643
for block in self.blocks:
3744
signed_txs = []
3845
for tx_index, tx in enumerate(block):
@@ -51,11 +58,31 @@ def execute(
5158
for transaction in signed_txs:
5259
if transaction.error is None:
5360
eth_rpc.send_wait_transaction(transaction)
61+
all_tx_hashes.append(transaction.hash)
5462
else:
5563
with pytest.raises(SendTransactionExceptionError):
5664
eth_rpc.send_transaction(transaction)
5765
else:
5866
eth_rpc.send_wait_transactions(signed_txs)
67+
all_tx_hashes.extend([tx.hash for tx in signed_txs])
68+
69+
# Perform gas validation if required for benchmarking
70+
# Ensures benchmark tests consume exactly the expected gas
71+
if not self.skip_gas_used_validation and self.expected_benchmark_gas_used is not None:
72+
total_gas_used = 0
73+
# Fetch transaction receipts to get actual gas used
74+
for tx_hash in all_tx_hashes:
75+
receipt = eth_rpc.get_transaction_receipt(tx_hash)
76+
assert receipt is not None, f"Failed to get receipt for transaction {tx_hash}"
77+
gas_used = int(receipt["gasUsed"], 16)
78+
total_gas_used += gas_used
79+
80+
# Verify that the total gas consumed matches expectations
81+
assert total_gas_used == self.expected_benchmark_gas_used, (
82+
f"Total gas used ({total_gas_used}) does not match "
83+
f"expected benchmark gas ({self.expected_benchmark_gas_used}), "
84+
f"difference: {total_gas_used - self.expected_benchmark_gas_used}"
85+
)
5986

6087
for address, account in self.post.root.items():
6188
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/blockchain.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,9 +898,12 @@ def execute(
898898
blocks: List[List[Transaction]] = []
899899
for block in self.blocks:
900900
blocks += [block.txs]
901+
# Pass gas validation params for benchmark tests
901902
return TransactionPost(
902903
blocks=blocks,
903904
post=self.post,
905+
expected_benchmark_gas_used=self.expected_benchmark_gas_used,
906+
skip_gas_used_validation=self.skip_gas_used_validation,
904907
)
905908
raise Exception(f"Unsupported execute format: {execute_format}")
906909

src/ethereum_test_specs/state.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,9 +444,12 @@ 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+
expected_benchmark_gas_used=self.expected_benchmark_gas_used,
452+
skip_gas_used_validation=self.skip_gas_used_validation,
450453
)
451454
raise Exception(f"Unsupported execute format: {execute_format}")
452455

src/pytest_plugins/execute/execute.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,11 @@ 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+
# Set default for expected_benchmark_gas_used
346+
if "expected_benchmark_gas_used" not in kwargs:
347+
kwargs["expected_benchmark_gas_used"] = request.getfixturevalue(
348+
"gas_benchmark_value"
349+
)
345350
kwargs |= {
346351
p: request.getfixturevalue(p)
347352
for p in cls_fixture_parameters

0 commit comments

Comments
 (0)