Skip to content

test_worst_keccak()

Documentation for tests/benchmark/test_worst_compute.py::test_worst_keccak@88e9fb8f.

Generate fixtures for these test cases for Osaka with:

fill -v tests/benchmark/test_worst_compute.py::test_worst_keccak -m benchmark

Test running a block with as many KECCAK256 permutations as possible.

Source code in tests/benchmark/test_worst_compute.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
def test_worst_keccak(
    benchmark_test: BenchmarkTestFiller,
    fork: Fork,
    gas_benchmark_value: int,
) -> None:
    """Test running a block with as many KECCAK256 permutations as possible."""
    # Intrinsic gas cost is paid once.
    intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
    available_gas = gas_benchmark_value - intrinsic_gas_calculator()

    gsc = fork.gas_costs()
    mem_exp_gas_calculator = fork.memory_expansion_gas_calculator()

    # Discover the optimal input size to maximize keccak-permutations,
    # not to maximize keccak calls.
    # The complication of the discovery arises from
    # the non-linear gas cost of memory expansion.
    max_keccak_perm_per_block = 0
    optimal_input_length = 0
    for i in range(1, 1_000_000, 32):
        iteration_gas_cost = (
            2 * gsc.G_VERY_LOW  # PUSHN + PUSH1
            + gsc.G_KECCAK_256  # KECCAK256 static cost
            + math.ceil(i / 32) * gsc.G_KECCAK_256_WORD  # KECCAK256 dynamic
            # cost
            + gsc.G_BASE  # POP
        )
        # From the available gas, we subtract the mem expansion costs
        # considering we know the current input size length i.
        available_gas_after_expansion = max(0, available_gas - mem_exp_gas_calculator(new_bytes=i))
        # Calculate how many calls we can do.
        num_keccak_calls = available_gas_after_expansion // iteration_gas_cost
        # KECCAK does 1 permutation every 136 bytes.
        num_keccak_permutations = num_keccak_calls * math.ceil(i / KECCAK_RATE)

        # If we found an input size that is better (reg permutations/gas), then
        # save it.
        if num_keccak_permutations > max_keccak_perm_per_block:
            max_keccak_perm_per_block = num_keccak_permutations
            optimal_input_length = i

    # max_iters_loop contains how many keccak calls can be done per loop. The
    # loop is as big as possible bounded by the maximum code size.
    #
    # The loop structure is: JUMPDEST + [attack iteration] + PUSH0 + JUMP
    #
    # Now calculate available gas for [attack iteration]:
    #   Numerator = max_code_size-3. (JUMPDEST, PUSH0 and JUMP)
    #   Denominator = (PUSHN + PUSH1 + KECCAK256 + POP) + PUSH1_DATA +
    #   PUSHN_DATA
    # TODO: the testing framework uses PUSH1(0) instead of PUSH0 which is
    # suboptimal for the
    # attack, whenever this is fixed adjust accordingly.
    benchmark_test(
        code_generator=JumpLoopGenerator(
            setup=Op.PUSH20[optimal_input_length],
            attack_block=Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1)),
        ),
    )

Parametrized Test Cases

This test case is only parametrized by fork.

Test ID (Abbreviated)
...fork_Prague-blockchain_test
...fork_Osaka-blockchain_test