Skip to content

Commit 9d894e2

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 ethereum#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 9d894e2

File tree

4 files changed

+45
-0
lines changed

4 files changed

+45
-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 self.expected_benchmark_gas_used is not None and not self.skip_gas_used_validation:
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 exactly
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_used ({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
@@ -899,9 +899,12 @@ 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+
expected_benchmark_gas_used=self.expected_benchmark_gas_used,
907+
skip_gas_used_validation=self.skip_gas_used_validation,
905908
)
906909
raise Exception(f"Unsupported execute format: {execute_format}")
907910

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

0 commit comments

Comments
 (0)