-
Notifications
You must be signed in to change notification settings - Fork 489
refactor(test-benchmark): split stateful setup transactions across blocks #3282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: forks/amsterdam
Are you sure you want to change the base?
Changes from all commits
25cb876
50b9420
0468269
4afd3ee
3af71ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1119,7 +1119,7 @@ def get_alloc( | |
| ) | ||
|
|
||
| all_calls: List[RPCCall] = [] | ||
| # (address, per-account call_info list, call count) | ||
| # (address, per-account call_info list) | ||
| address_info: List[tuple[Address, List[tuple[str, Any]]]] = [] | ||
|
|
||
| for address, account in alloc.root.items(): | ||
|
|
@@ -1129,7 +1129,12 @@ def get_alloc( | |
| all_calls.extend(calls) | ||
| address_info.append((address, call_info)) | ||
|
|
||
| responses = self.post_batch_request(calls=all_calls) | ||
| responses: List[JSONRPCResponse] = [] | ||
| batch_size = self.max_transactions_per_batch | ||
| for i in range(0, len(all_calls), batch_size): | ||
| responses.extend( | ||
| self.post_batch_request(calls=all_calls[i : i + batch_size]) | ||
| ) | ||
|
Comment on lines
+1132
to
+1137
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of sending RPC requests all at once, this PR batches them to reduce server load. |
||
|
|
||
| result_alloc: Dict[Address, Account | None] = {} | ||
| offset = 0 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| """Test suite for ``_split_setup_blocks_by_gas`` in stateful filling.""" | ||
|
|
||
| import pytest | ||
|
|
||
| from execution_testing.test_types import TestPhase, Transaction | ||
|
|
||
| from ..blockchain import ( | ||
| _SETUP_BLOCK_GAS_HEADROOM, | ||
| Block, | ||
| _split_setup_blocks_by_gas, | ||
| ) | ||
|
|
||
| BLOCK_GAS_LIMIT = 1_000_000 | ||
| """Stand-in for the snapshot chain's block gas limit.""" | ||
|
|
||
| BUDGET = int(BLOCK_GAS_LIMIT * _SETUP_BLOCK_GAS_HEADROOM) | ||
|
|
||
|
|
||
| def _tx( | ||
| gas_limit: int, phase: TestPhase | None = TestPhase.SETUP | ||
| ) -> Transaction: | ||
| """Build a phase-tagged Transaction of the given gas limit.""" | ||
| tx = Transaction(gas_limit=gas_limit) | ||
| tx.test_phase = phase | ||
| return tx | ||
|
|
||
|
|
||
| def _split(block: Block) -> list[Block]: | ||
| """Split *block* against the stand-in block gas limit.""" | ||
| return _split_setup_blocks_by_gas([block], block_gas_limit=BLOCK_GAS_LIMIT) | ||
|
|
||
|
|
||
| def test_passthrough_within_budget() -> None: | ||
| """A setup block that already fits is returned unchanged.""" | ||
| block = Block(txs=[_tx(100_000) for _ in range(5)]) | ||
| out = _split(block) | ||
| assert len(out) == 1 | ||
| assert out[0] is block | ||
|
|
||
|
|
||
| def test_passthrough_empty() -> None: | ||
| """Empty-txs block is returned unchanged (identity).""" | ||
| block = Block(txs=[]) | ||
| out = _split(block) | ||
| assert len(out) == 1 | ||
| assert out[0] is block | ||
|
|
||
|
|
||
| def test_passthrough_all_untagged() -> None: | ||
| """Block with no phase-tagged txs is returned unchanged.""" | ||
| block = Block(txs=[_tx(BLOCK_GAS_LIMIT, phase=None) for _ in range(4)]) | ||
| out = _split(block) | ||
| assert len(out) == 1 | ||
| assert out[0] is block | ||
|
|
||
|
|
||
| def test_execution_block_never_split() -> None: | ||
| """Execution block survives intact far past the block gas limit.""" | ||
| block = Block( | ||
| txs=[ | ||
| _tx(BLOCK_GAS_LIMIT, phase=TestPhase.EXECUTION) for _ in range(10) | ||
| ] | ||
| ) | ||
| out = _split(block) | ||
| assert len(out) == 1 | ||
| assert out[0] is block | ||
|
|
||
|
|
||
| def test_every_sub_block_fits_the_budget() -> None: | ||
| """Over-budget setup work spans blocks that each fit.""" | ||
| out = _split(Block(txs=[_tx(200_000) for _ in range(25)])) | ||
| assert len(out) > 1 | ||
| for block in out: | ||
| assert sum(tx.gas_limit for tx in block.txs) <= BUDGET | ||
|
|
||
|
|
||
| def test_split_preserves_every_tx_in_order() -> None: | ||
| """Splitting neither drops, duplicates nor reorders transactions.""" | ||
| txs = [_tx(200_000) for _ in range(25)] | ||
| out = _split(Block(txs=txs)) | ||
| assert [tx for block in out for tx in block.txs] == txs | ||
|
|
||
|
|
||
| def test_tx_at_the_limit_gets_its_own_block() -> None: | ||
| """A tx past the budget but within the limit is isolated, not dropped.""" | ||
| small, huge = _tx(21_000), _tx(BLOCK_GAS_LIMIT) | ||
| out = _split(Block(txs=[small, huge, small])) | ||
| assert [len(block.txs) for block in out] == [1, 1, 1] | ||
| assert out[1].txs[0] is huge | ||
|
|
||
|
|
||
| def test_tx_past_the_limit_raises() -> None: | ||
| """A tx no block can hold fails loudly instead of being emitted.""" | ||
| block = Block(txs=[_tx(21_000), _tx(BLOCK_GAS_LIMIT * 3)]) | ||
| with pytest.raises(ValueError, match="no split can make it fit"): | ||
| _split(block) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "gas_limits, expected_sizes", | ||
| [ | ||
| ([500_000, 400_000], [2]), | ||
| ([500_000, 600_000], [1, 1]), | ||
| ([900_000, 90_000], [2]), | ||
| ([900_000, 100_000], [1, 1]), | ||
| ([250_000] * 9, [3, 3, 3]), | ||
| ([300_000] * 7, [3, 3, 1]), | ||
| ([BLOCK_GAS_LIMIT // 2] * 2, [1, 1]), | ||
| ], | ||
| ) | ||
| def test_sub_block_sizes( | ||
| gas_limits: list[int], expected_sizes: list[int] | ||
| ) -> None: | ||
| """Sub-block sizes follow a greedy pack of the gas budget.""" | ||
| block = Block(txs=[_tx(gas) for gas in gas_limits]) | ||
| out = _split(block) | ||
| assert [len(block.txs) for block in out] == expected_sizes |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,7 +155,7 @@ def test_ether_transfers_onchain_receivers( | |
| raise ValueError(f"Unknown case: {case_id}") | ||
|
|
||
| sends_value = transfer_amount > 0 | ||
| iteration_cost = ( | ||
| regular_cost = ( | ||
| fork.transaction_intrinsic_cost_calculator()( | ||
| sends_value=sends_value, | ||
| recipient_type=recipient_type, | ||
|
|
@@ -164,13 +164,14 @@ def test_ether_transfers_onchain_receivers( | |
| sends_value=sends_value, | ||
| recipient_type=recipient_type, | ||
| ) | ||
| + fork.transaction_top_frame_state_gas( | ||
| sends_value=sends_value, | ||
| recipient_type=recipient_type, | ||
| ) | ||
| + receiver_execution_gas | ||
| ) | ||
| iteration_count = gas_benchmark_value // iteration_cost | ||
| state_cost = fork.transaction_top_frame_state_gas( | ||
| sends_value=sends_value, | ||
| recipient_type=recipient_type, | ||
| ) | ||
| iteration_cost = regular_cost + state_cost | ||
| iteration_count = gas_benchmark_value // max(regular_cost, state_cost) | ||
|
Comment on lines
+169
to
+174
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the actual worst case for ether transfer, the iteration cost is The original approach used ~90% of the block gas limit, but now it reaches 99%. |
||
|
|
||
| txs = [] | ||
| for _ in range(iteration_count): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
max_fee_per_gasand other gas configuration values adjust based on network status. However, theFEE_MULTIPLIERis only 1.5x. For benchmarks with many blocks, the cumulative gas limit can exceed the value calculated at the start of the test, leading to lower base fee issues.