Skip to content

test_sload_bloated_prefetch_miss()

Documentation for tests/benchmark/stateful/bloatnet/test_sload.py::test_sload_bloated_prefetch_miss@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/benchmark/stateful/bloatnet/test_sload.py::test_sload_bloated_prefetch_miss --gas-benchmark-values 1

Benchmark SLOAD with calldata-driven offsets to defeat prefetching.

A small first transaction writes an initial offset into the authority's slot 0 via calldata. Subsequent max-gas transactions each read the previous offset from slot 0, immediately overwrite slot 0 with a new offset from their own calldata, then SLOAD sequentially from the previous offset. Because each transaction's SLOAD range depends on state written by its predecessor, a prefetcher that predicts SLOAD targets from pre-block state without simulating intra-block writes will pre-warm incorrect storage slots. The minimal first tx is load-bearing: it lives inside the benchmark block so every subsequent max-gas tx reads a slot 0 value that differs from the prefetcher's pre-block snapshot, achieving a 100% miss rate.

When distinct_senders is True every transaction uses a fresh sender. This additionally defeats per-sender prewarm serialization (e.g. Nethermind) that groups txs by sender and runs them sequentially to propagate state changes — forcing every tx's prewarm scope to restart from pre-block state.

Source code in tests/benchmark/stateful/bloatnet/test_sload.py
@pytest.mark.stub_parametrize("token_name", "bloated_eoa_")
@pytest.mark.parametrize("distinct_senders", [False, True])
@pytest.mark.parametrize("existing_slots", [False, True])
def test_sload_bloated_prefetch_miss(
    benchmark_test: BenchmarkTestFiller,
    pre: Alloc,
    fork: Fork,
    gas_benchmark_value: int,
    tx_gas_limit: int,
    token_name: str,
    existing_slots: bool,
    distinct_senders: bool,
) -> None:
    """
    Benchmark SLOAD with calldata-driven offsets to defeat prefetching.

    A small first transaction writes an initial offset into the
    authority's slot 0 via calldata. Subsequent max-gas transactions
    each read the previous offset from slot 0, immediately overwrite
    slot 0 with a new offset from their own calldata, then SLOAD
    sequentially from the previous offset. Because each transaction's
    SLOAD range depends on state written by its predecessor, a
    prefetcher that predicts SLOAD targets from pre-block state
    without simulating intra-block writes will pre-warm incorrect
    storage slots. The minimal first tx is load-bearing: it lives
    inside the benchmark block so every subsequent max-gas tx reads
    a slot 0 value that differs from the prefetcher's pre-block
    snapshot, achieving a 100% miss rate.

    When ``distinct_senders`` is True every transaction uses a fresh
    sender. This additionally defeats per-sender prewarm
    serialization (e.g. Nethermind) that groups txs by sender and
    runs them sequentially to propagate state changes — forcing
    every tx's prewarm scope to restart from pre-block state.
    """
    plant_code = Op.SLOAD(Op.PUSH0, key_warm=False) + Op.SSTORE(
        Op.PUSH0,
        Op.CALLDATALOAD(Op.PUSH0),
        key_warm=True,
        original_value=0,
        current_value=0,
        new_value=1,
    )
    # Read the old offset from slot 0, write the new offset from
    # calldata to slot 0, then SLOAD sequentially from the old offset.
    runtime_code = plant_code + While(
        body=(Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(1) + Op.ADD),
        condition=Op.GT(Op.GAS, LOOP_GAS_THRESHOLD),
    )

    authority = pre.stub_eoa(token_name)
    runtime_address = pre.deploy_contract(code=runtime_code)

    # Setup: delegate authority to the runtime contract. Slot 0 is
    # left at 0 (the delegation tx's calldata) so the benchmark
    # block's pre-state has slot 0 = 0; the first benchmark tx
    # then plants base_offset in slot 0 inside the benchmark block,
    # forcing the prefetcher's pre-block snapshot to disagree with
    # the actual slot 0 value seen by every max-gas tx that follows.
    delegation_tx = delegate_with_calldata(
        pre,
        fork,
        authority,
        runtime_address,
        Hash(0),
    )

    blocks: list[Block] = [Block(txs=[delegation_tx])]

    # Offset spacing: upper bound on SLOADs per tx ensures each
    # transaction reads a completely disjoint slot range.
    max_sloads_per_tx = _max_sloads_per_tx(tx_gas_limit, fork)

    # The base offset must be at least max_sloads_per_tx away from
    # the pre-block slot 0 value (0) so the prefetcher's predicted
    # SLOAD range is completely disjoint from the actual range.
    base_offset = max_sloads_per_tx if existing_slots else START_SLOT
    intrinsic_gas = fork.transaction_intrinsic_cost_calculator()(
        calldata=Hash(base_offset),
        recipient_type=RecipientType.DELEGATION_7702,
        return_cost_deducted_prior_execution=True,
    ) + fork.transaction_top_frame_gas_calculator()(
        recipient_type=RecipientType.DELEGATION_7702
    )

    plant_tx_gas = (
        intrinsic_gas + plant_code.gas_cost(fork) + LOOP_GAS_THRESHOLD
    )

    # senders_iter yields one sender per tx (fresh per call in
    # distinct mode, a single shared sender otherwise). The senders
    # list collects one entry per tx so the BAL builder below can
    # group nonce changes by sender uniformly.
    senders_iter = _sender_generator(pre, distinct_senders)
    senders: list[EOA] = []

    gas_available = gas_benchmark_value
    txs: list[Transaction] = []

    # This tx's job is to change slot 0 inside the benchmark block so
    # every subsequent max-gas tx reads an offset the prefetcher's
    # pre-block snapshot does not see, achieving a 100% miss rate.
    first_tx_gas = min(gas_available, plant_tx_gas)
    sender = next(senders_iter)
    senders.append(sender)
    txs.append(
        Transaction(
            gas_limit=first_tx_gas,
            to=authority,
            data=Hash(base_offset),
            sender=sender,
        )
    )
    gas_available -= first_tx_gas

    # Subsequent transactions: max gas, each shifts the offset
    # so the next transaction SLOADs from a different range.
    tx_index = 1
    while gas_available >= plant_tx_gas:
        tx_gas = min(gas_available, tx_gas_limit)
        new_offset = base_offset + tx_index * max_sloads_per_tx
        sender = next(senders_iter)
        senders.append(sender)
        txs.append(
            Transaction(
                gas_limit=tx_gas,
                to=authority,
                data=Hash(new_offset),
                sender=sender,
            )
        )
        gas_available -= tx_gas
        tx_index += 1

    expectations: dict[Address, BalAccountExpectation] = {
        authority: BalAccountExpectation(
            storage_reads=[base_offset],
            storage_changes=[
                BalStorageSlot(
                    slot=0,
                    validate_any_change=True,
                ),
            ],
        ),
    }
    sender_nonces: dict[Address, list[BalNonceChange]] = {}
    for i, s in enumerate(senders):
        changes = sender_nonces.setdefault(s, [])
        changes.append(
            BalNonceChange(
                block_access_index=i + 1,
                post_nonce=len(changes) + 1,
            )
        )
    for addr, nonces in sender_nonces.items():
        expectations[addr] = BalAccountExpectation(nonce_changes=nonces)
    blocks.append(
        Block(
            txs=txs,
            expected_block_access_list=BlockAccessListExpectation(
                account_expectations=expectations,
            ),
        )
    )

    benchmark_test(
        pre=pre,
        blocks=blocks,
        skip_gas_used_validation=True,
        verifications=[ReceiptStatusExpected()],
    )

Parametrized Test Cases

This test generates 4 parametrized test cases across 4 forks.