diff --git a/packages/testing/src/execution_testing/forks/forks/eips/bogota/eip_8198.py b/packages/testing/src/execution_testing/forks/forks/eips/bogota/eip_8198.py index 8dc9adde68a..81d502e8a45 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/bogota/eip_8198.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/bogota/eip_8198.py @@ -2,9 +2,8 @@ EIP-8198: Quick Slots. Reduce the consensus-layer slot duration. The execution layer lowers the -maximum per-block base fee change and updates the blob schedule so that fee -dynamics and blob throughput per unit of wall-clock time stay close to their -previous values. +maximum per-block base fee change so that fee dynamics per unit of wall-clock +time stay close to their previous values. https://eips.ethereum.org/EIPS/eip-8198 """ @@ -12,15 +11,7 @@ from ....base_fork import BaseFork -class EIP8198( - BaseFork, - # Provisional values, pending a joint decision with the consensus layer. - update_blob_constants={ - "TARGET_BLOBS_PER_BLOCK": 12, - "MAX_BLOBS_PER_BLOCK": 18, - "BLOB_BASE_FEE_UPDATE_FRACTION": 12018519, - }, -): +class EIP8198(BaseFork): """EIP-8198 class.""" @classmethod diff --git a/src/ethereum/forks/bogota/vm/gas.py b/src/ethereum/forks/bogota/vm/gas.py index 79cd87f4a85..b535dfc2d9e 100644 --- a/src/ethereum/forks/bogota/vm/gas.py +++ b/src/ethereum/forks/bogota/vm/gas.py @@ -128,19 +128,13 @@ class GasCosts: ) # Blobs - # - # The target, maximum, and update fraction are provisional pending a - # joint decision with the consensus layer. They keep both the fastest - # rise and the fastest fall of the blob base fee, per unit of - # wall-clock time, and the ratio of maximum to target unchanged under - # more frequent blocks. PER_BLOB: Final[U64] = U64(2**17) - BLOB_SCHEDULE_TARGET: Final[U64] = U64(12) + BLOB_SCHEDULE_TARGET: Final[U64] = U64(14) BLOB_TARGET_GAS_PER_BLOCK: Final[U64] = PER_BLOB * BLOB_SCHEDULE_TARGET BLOB_BASE_COST: Final[Uint] = Uint(2**13) - BLOB_SCHEDULE_MAX: Final[U64] = U64(18) + BLOB_SCHEDULE_MAX: Final[U64] = U64(21) BLOB_MIN_GASPRICE: Final[Uint] = Uint(1) - BLOB_BASE_FEE_UPDATE_FRACTION: Final[Uint] = Uint(12018519) + BLOB_BASE_FEE_UPDATE_FRACTION: Final[Uint] = Uint(11684671) # Block Access Lists BLOCK_ACCESS_LIST_ITEM: Final[ExecutionGas] = ExecutionGas(Uint(2000)) diff --git a/tests/bogota/eip8198_quick_slots/eip_checklist_external_coverage.txt b/tests/bogota/eip8198_quick_slots/eip_checklist_external_coverage.txt index 4cb308e945f..0f81d7cb9e2 100644 --- a/tests/bogota/eip8198_quick_slots/eip_checklist_external_coverage.txt +++ b/tests/bogota/eip8198_quick_slots/eip_checklist_external_coverage.txt @@ -1,5 +1,4 @@ -general/code_coverage/eels = EIP-8198 changes the base fee update in forks/bogota/fork.py (calculate_base_fee_per_gas, BASE_FEE_MAX_CHANGE_NUMERATOR, BASE_FEE_MAX_CHANGE_DENOMINATOR) and the blob schedule constants in forks/bogota/vm/gas.py; both directions of every changed branch are driven by this suite, and mutating any of the five changed constants back to its Amsterdam value makes tests in this suite fail +general/code_coverage/eels = EIP-8198 changes the base fee update in forks/bogota/fork.py (calculate_base_fee_per_gas, BASE_FEE_MAX_CHANGE_NUMERATOR, BASE_FEE_MAX_CHANGE_DENOMINATOR); both directions of every changed branch are driven by this suite, and mutating either changed constant back to its Amsterdam value makes tests in this suite fail general/code_coverage/test_coverage = Fill the suite with `uv run fill --cov=ethereum.forks.bogota --cov-branch --cov-report=term-missing --until Bogota tests/bogota/eip8198_quick_slots` general/code_coverage/missed_lines = In calculate_base_fee_per_gas, only the `raise InvalidBlock` for an out-of-bounds gas limit is missed at fill time; it runs only in block validation and is covered by the one_beyond cases of test_gas_limit_adjustment_unchanged when their fixtures are validated with `just validate-blocks /blockchain_tests` -blob_count_changes/test/eip4844_blobs_changes = tests/cancun/eip4844_blobs and tests/osaka/eip7918_blob_reserve_price read the blob schedule from the fork, so at Bogota they run against the new target, maximum, and update fraction; at the Amsterdam to Bogota transition, test_fork_transition_excess_blob_gas_post_blob_genesis and test_reserve_price_at_transition cover the excess carry-over and the reserve price under the new schedule; re-verify with `uv run fill --collect-only tests/cancun/eip4844_blobs tests/osaka/eip7918_blob_reserve_price --from Bogota --until Bogota` general/code_coverage/second_client = Optional diff --git a/tests/bogota/eip8198_quick_slots/eip_checklist_not_applicable.txt b/tests/bogota/eip8198_quick_slots/eip_checklist_not_applicable.txt index c3dad139c68..9e897a76db2 100644 --- a/tests/bogota/eip8198_quick_slots/eip_checklist_not_applicable.txt +++ b/tests/bogota/eip8198_quick_slots/eip_checklist_not_applicable.txt @@ -7,8 +7,9 @@ block_header_field = EIP does not add any new block header fields block_body_field = EIP does not add any new block body fields gas_refunds_changes = EIP does not change any refund rules execution_layer_request = EIP does not introduce an execution layer request -new_transaction_validity_constraint = EIP adds no transaction validity constraint; the blob fee check is unchanged apart from the blob base fee it compares against, which test_insufficient_max_fee_per_blob_gas covers +new_transaction_validity_constraint = EIP adds no transaction validity constraint modified_transaction_validity_constraint = EIP changes no transaction validity constraint gas_cost_changes/test/gas_updates_measurement = EIP changes no opcode or intrinsic gas cost; the base fee update it changes is measured by test_base_fee_update gas_cost_changes/test/out_of_gas = EIP changes no gas cost a transaction can run out of -block_level_constraint/test/content = The blob limit counts blobs only, so no transaction type, log, receipt, or withdrawal content affects it +block_level_constraint = EIP adds or changes no block-level constraint; test_blob_count_limit_unchanged_at_fork pins the parent fork's blob limit at the fork block +blob_count_changes = EIP does not change the blob schedule; test_blob_schedule_unchanged_across_fork pins the parent fork's schedule across the fork diff --git a/tests/bogota/eip8198_quick_slots/spec.py b/tests/bogota/eip8198_quick_slots/spec.py index 8c9ccbedea8..9dee291bcfb 100644 --- a/tests/bogota/eip8198_quick_slots/spec.py +++ b/tests/bogota/eip8198_quick_slots/spec.py @@ -23,8 +23,8 @@ class Spec(EIP4844Spec): """ Parameters from the EIP-8198 specifications. Extends the EIP-4844 spec. - The new base fee change and blob schedule are read from the fork, which - carries them from the EIP-8198 fork class. + The new base fee change is read from the fork, which carries it from the + EIP-8198 fork class. """ GAS_LIMIT_ADJUSTMENT_FACTOR = 1024 diff --git a/tests/bogota/eip8198_quick_slots/test_blob_schedule.py b/tests/bogota/eip8198_quick_slots/test_blob_schedule.py deleted file mode 100644 index 7e2afaa78c3..00000000000 --- a/tests/bogota/eip8198_quick_slots/test_blob_schedule.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -Blob schedule tests for -[EIP-8198: Quick Slots](https://eips.ethereum.org/EIPS/eip-8198). - -From the fork onward, blocks carry at most the fork's maximum blob count, -the excess blob gas moves around its target blob count, and the blob base -fee uses its update fraction. -""" - -from typing import List - -import pytest -from execution_testing import ( - Account, - Alloc, - Block, - BlockchainTestFiller, - EIPChecklist, - Environment, - Fork, - Header, - ParameterSet, - TransactionException, -) - -from .helpers import BLOB_COUNT_ERRORS, blob_transactions -from .spec import ref_spec_8198 - -REFERENCE_SPEC_GIT_PATH = ref_spec_8198.git_path -REFERENCE_SPEC_VERSION = ref_spec_8198.version - -pytestmark = pytest.mark.valid_from("EIP8198") - -BASE_FEE_PER_GAS = 7 -""" -Base fee of every block in these tests. It is low enough that the blob base -fee reserve price stays inactive, and blocks well below their gas target -keep it unchanged. -""" - -GENESIS_EXCESS_BLOBS = 300 -""" -Excess blobs at genesis, high enough that the blob base fee is well above -its minimum and depends on the update fraction. -""" - - -def next_excess_blob_gas( - fork: Fork, parent_excess_blob_gas: int, parent_blob_count: int -) -> int: - """Return the next block's excess blob gas under the fork's schedule.""" - calc_excess_blob_gas = fork.excess_blob_gas_calculator() - return calc_excess_blob_gas( - parent_excess_blob_gas=parent_excess_blob_gas, - parent_blob_count=parent_blob_count, - parent_base_fee_per_gas=BASE_FEE_PER_GAS, - ) - - -@pytest.mark.parametrize_by_fork( - "blob_count", - lambda fork: [ - pytest.param(fork.target_blobs_per_block(), id="target"), - pytest.param(fork.max_blobs_per_block() - 1, id="max_minus_one"), - pytest.param(fork.max_blobs_per_block(), id="max"), - pytest.param( - fork.max_blobs_per_block() + 1, - id="max_plus_one", - marks=pytest.mark.exception_test, - ), - ], -) -@EIPChecklist.BlockLevelConstraint.Test.Boundary.Under() -@EIPChecklist.BlockLevelConstraint.Test.Boundary.Exact() -@EIPChecklist.BlockLevelConstraint.Test.Boundary.Over() -def test_blob_count_limit( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - fork: Fork, - blob_count: int, -) -> None: - """ - Accept a block with up to the fork's maximum blob count and reject one - with a blob more. - """ - sender = pre.fund_eoa() - destination = pre.fund_eoa(amount=0) - get_blob_gas_price = fork.blob_gas_price_calculator() - over_limit = blob_count > fork.max_blobs_per_block() - txs = blob_transactions( - sender=sender, - destination=destination, - fork=fork, - blob_count=blob_count, - max_fee_per_blob_gas=get_blob_gas_price(excess_blob_gas=0), - error=BLOB_COUNT_ERRORS if over_limit else None, - ) - blockchain_test( - pre=pre, - post={} if over_limit else {destination: Account(balance=len(txs))}, - blocks=[ - Block( - txs=txs, - timestamp=10, - exception=BLOB_COUNT_ERRORS if over_limit else None, - header_verify=( - None - if over_limit - else Header( - blob_gas_used=blob_count * fork.blob_gas_per_blob() - ) - ), - ) - ], - genesis_environment=Environment(base_fee_per_gas=BASE_FEE_PER_GAS), - ) - - -def blob_count_sequences(fork: Fork) -> List[ParameterSet]: - """Return the blob counts of successive blocks.""" - target = fork.target_blobs_per_block() - maximum = fork.max_blobs_per_block() - return [ - pytest.param([maximum] * 4, id="max_blobs"), - pytest.param([target] * 4, id="target_blobs"), - pytest.param([0] * 4, id="no_blobs"), - pytest.param([maximum, 0, target, 1], id="mixed"), - ] - - -@pytest.mark.parametrize_by_fork("blob_counts", blob_count_sequences) -def test_blob_base_fee_evolution( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - fork: Fork, - blob_counts: List[int], -) -> None: - """ - Check the excess blob gas of each block and accept blob transactions - offering exactly the blob base fee. - """ - sender = pre.fund_eoa() - destination = pre.fund_eoa(amount=0) - get_blob_gas_price = fork.blob_gas_price_calculator() - blob_gas_per_blob = fork.blob_gas_per_blob() - genesis_excess_blob_gas = GENESIS_EXCESS_BLOBS * blob_gas_per_blob - excess_blob_gas = next_excess_blob_gas(fork, genesis_excess_blob_gas, 0) - blocks = [] - tx_count = 0 - blob_index = 0 - # A final one-blob block checks the excess left by the last block. - for i, blob_count in enumerate(blob_counts + [1]): - txs = blob_transactions( - sender=sender, - destination=destination, - fork=fork, - blob_count=blob_count, - max_fee_per_blob_gas=get_blob_gas_price( - excess_blob_gas=excess_blob_gas - ), - first_blob_index=blob_index, - ) - blocks.append( - Block( - txs=txs, - timestamp=10 * (i + 1), - header_verify=Header( - excess_blob_gas=excess_blob_gas, - blob_gas_used=blob_count * blob_gas_per_blob, - base_fee_per_gas=BASE_FEE_PER_GAS, - ), - ) - ) - tx_count += len(txs) - blob_index += blob_count - excess_blob_gas = next_excess_blob_gas( - fork, excess_blob_gas, blob_count - ) - blockchain_test( - pre=pre, - post={destination: Account(balance=tx_count)}, - blocks=blocks, - genesis_environment=Environment( - base_fee_per_gas=BASE_FEE_PER_GAS, - excess_blob_gas=genesis_excess_blob_gas, - blob_gas_used=0, - ), - ) - - -@pytest.mark.exception_test -def test_insufficient_max_fee_per_blob_gas( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """ - Reject a blob transaction offering one wei less than the blob base fee - given by the fork's update fraction. - """ - sender = pre.fund_eoa() - destination = pre.fund_eoa(amount=0) - genesis_excess_blob_gas = GENESIS_EXCESS_BLOBS * fork.blob_gas_per_blob() - excess_blob_gas = next_excess_blob_gas(fork, genesis_excess_blob_gas, 0) - blob_gas_price = fork.blob_gas_price_calculator()( - excess_blob_gas=excess_blob_gas - ) - error = TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS - blockchain_test( - pre=pre, - post={}, - blocks=[ - Block( - txs=blob_transactions( - sender=sender, - destination=destination, - fork=fork, - blob_count=1, - max_fee_per_blob_gas=blob_gas_price - 1, - error=error, - ), - timestamp=10, - exception=error, - ) - ], - genesis_environment=Environment( - base_fee_per_gas=BASE_FEE_PER_GAS, - excess_blob_gas=genesis_excess_blob_gas, - blob_gas_used=0, - ), - ) diff --git a/tests/bogota/eip8198_quick_slots/test_fork_transition.py b/tests/bogota/eip8198_quick_slots/test_fork_transition.py index 83d681ef57a..70e350f6477 100644 --- a/tests/bogota/eip8198_quick_slots/test_fork_transition.py +++ b/tests/bogota/eip8198_quick_slots/test_fork_transition.py @@ -18,6 +18,7 @@ BlockException, EIPChecklist, Environment, + Fork, Header, TransactionException, TransitionFork, @@ -59,7 +60,7 @@ GENESIS_EXCESS_BLOBS = 300 """ Excess blobs at genesis, high enough that the blob base fee depends on the -update fraction, and lower under the new fraction than under the old. +update fraction. """ @@ -285,20 +286,26 @@ def test_gas_limit_adjustment_unchanged( ) -def next_excess_blob_gas( - fork: TransitionFork, - timestamp: int, - parent_excess_blob_gas: int, - parent_blob_count: int, -) -> int: +def unchanged_blob_schedule(fork: TransitionFork) -> Fork: """ - Return the excess blob gas of the block at `timestamp`, under the blob - schedule in effect at that block. + Return the parent fork after asserting that the new fork keeps its blob + target, maximum, and update fraction. """ - calc_excess_blob_gas = fork.fork_at( - timestamp=timestamp - ).excess_blob_gas_calculator() - return calc_excess_blob_gas( + parent, child = fork.transitions_from(), fork.transitions_to() + assert child.target_blobs_per_block() == parent.target_blobs_per_block() + assert child.max_blobs_per_block() == parent.max_blobs_per_block() + assert ( + child.blob_base_fee_update_fraction() + == parent.blob_base_fee_update_fraction() + ) + return parent + + +def next_excess_blob_gas( + schedule: Fork, parent_excess_blob_gas: int, parent_blob_count: int +) -> int: + """Return the next block's excess blob gas under `schedule`.""" + return schedule.excess_blob_gas_calculator()( parent_excess_blob_gas=parent_excess_blob_gas, parent_blob_count=parent_blob_count, parent_base_fee_per_gas=BLOB_BASE_FEE_PER_GAS, @@ -330,9 +337,7 @@ def next_excess_blob_gas( ), ], ) -@EIPChecklist.BlobCountChanges.Test.Eip4844BlobsChanges() -@EIPChecklist.BlockLevelConstraint.Test.ForkTransition.AcceptedAfterFork() -def test_blob_schedule_across_fork( +def test_blob_schedule_unchanged_across_fork( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: TransitionFork, @@ -340,18 +345,20 @@ def test_blob_schedule_across_fork( fork_block_blobs: int, ) -> None: """ - Check that the fork block computes its excess blob gas with the new target - and prices blobs with the new update fraction. + Check that the excess blob gas and blob base fee follow the parent fork's + blob schedule on both sides of the fork. """ + schedule = unchanged_blob_schedule(fork) sender = pre.fund_eoa() destination = pre.fund_eoa(amount=0) + blob_gas_per_blob = schedule.blob_gas_per_blob() + get_blob_gas_price = schedule.blob_gas_price_calculator() timestamps = BLOCK_TIMESTAMPS[FORK_BLOCK_INDEX - 1 :] + # A final one-blob block checks the excess left by the fork block. blob_counts = [pre_fork_blobs, fork_block_blobs, 1] - blob_gas_per_blob = fork.transitions_to().blob_gas_per_blob() genesis_excess_blob_gas = GENESIS_EXCESS_BLOBS * blob_gas_per_blob - excess_blob_gas = next_excess_blob_gas( - fork, timestamps[0], genesis_excess_blob_gas, 0 + schedule, genesis_excess_blob_gas, 0 ) blocks = [] tx_count = 0 @@ -359,12 +366,10 @@ def test_blob_schedule_across_fork( for i, (timestamp, blob_count) in enumerate( zip(timestamps, blob_counts, strict=True) ): - block_fork = fork.fork_at(block_number=i + 1, timestamp=timestamp) - get_blob_gas_price = block_fork.blob_gas_price_calculator() txs = blob_transactions( sender=sender, destination=destination, - fork=block_fork, + fork=fork.fork_at(block_number=i + 1, timestamp=timestamp), blob_count=blob_count, max_fee_per_blob_gas=get_blob_gas_price( excess_blob_gas=excess_blob_gas @@ -383,13 +388,12 @@ def test_blob_schedule_across_fork( ) tx_count += len(txs) blob_index += blob_count - if i + 1 < len(timestamps): - excess_blob_gas = next_excess_blob_gas( - fork, timestamps[i + 1], excess_blob_gas, blob_count - ) + excess_blob_gas = next_excess_blob_gas( + schedule, excess_blob_gas, blob_count + ) blockchain_test( pre=pre, - post={destination: Account(balance=tx_count)} if tx_count else {}, + post={destination: Account(balance=tx_count)}, blocks=blocks, genesis_environment=Environment( base_fee_per_gas=BLOB_BASE_FEE_PER_GAS, @@ -399,78 +403,88 @@ def test_blob_schedule_across_fork( ) -@pytest.mark.parametrize_by_fork( - "fork_block_blobs", - lambda fork: [ - pytest.param( - fork.transitions_to().max_blobs_per_block() + 1, - id="max_plus_one", - ), +@pytest.mark.parametrize( + "blob_offset", + [ + pytest.param(0, id="parent_fork_max"), pytest.param( - fork.transitions_from().max_blobs_per_block(), - id="parent_fork_max", + 1, + id="parent_fork_max_plus_one", + marks=pytest.mark.exception_test, ), ], ) -@pytest.mark.exception_test -@EIPChecklist.BlockLevelConstraint.Test.ForkTransition.AcceptedBeforeFork() -@EIPChecklist.BlockLevelConstraint.Test.ForkTransition.RejectedAfterFork() -def test_blob_count_limit_at_fork( +def test_blob_count_limit_unchanged_at_fork( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: TransitionFork, - fork_block_blobs: int, + blob_offset: int, ) -> None: """ Accept the parent fork's maximum blob count in the last block before the - fork, and reject a fork block carrying more than the new maximum blob - count. + fork and in the fork block, and reject one more blob in the fork block. """ + schedule = unchanged_blob_schedule(fork) sender = pre.fund_eoa() destination = pre.fund_eoa(amount=0) + max_blobs = schedule.max_blobs_per_block() + blob_gas_per_blob = schedule.blob_gas_per_blob() + get_blob_gas_price = schedule.blob_gas_price_calculator() + fork_block_blobs = max_blobs + blob_offset + if blob_offset == 0: + error = None + elif blob_offset == 1: + error = BLOB_COUNT_ERRORS + else: + raise ValueError(f"Unexpected blob offset {blob_offset}") pre_fork_timestamp = BLOCK_TIMESTAMPS[FORK_BLOCK_INDEX - 1] - parent_fork = fork.transitions_from() - pre_fork_blobs = parent_fork.max_blobs_per_block() - fork_block_excess_blob_gas = next_excess_blob_gas( - fork, FORK_TIMESTAMP, 0, pre_fork_blobs + pre_fork_txs = blob_transactions( + sender=sender, + destination=destination, + fork=fork.fork_at(block_number=1, timestamp=pre_fork_timestamp), + blob_count=max_blobs, + max_fee_per_blob_gas=get_blob_gas_price(excess_blob_gas=0), ) - fork_block_blob_gas_price = fork.fork_at( - timestamp=FORK_TIMESTAMP - ).blob_gas_price_calculator()(excess_blob_gas=fork_block_excess_blob_gas) - blocks = [ - Block( - txs=blob_transactions( - sender=sender, - destination=destination, - fork=fork.fork_at( - block_number=1, timestamp=pre_fork_timestamp - ), - blob_count=pre_fork_blobs, - max_fee_per_blob_gas=parent_fork.min_base_fee_per_blob_gas(), - ), - timestamp=pre_fork_timestamp, - header_verify=Header( - blob_gas_used=pre_fork_blobs * parent_fork.blob_gas_per_blob() - ), + fork_block_txs = blob_transactions( + sender=sender, + destination=destination, + fork=fork.fork_at(block_number=2, timestamp=FORK_TIMESTAMP), + blob_count=fork_block_blobs, + max_fee_per_blob_gas=get_blob_gas_price( + excess_blob_gas=next_excess_blob_gas(schedule, 0, max_blobs) ), - Block( - txs=blob_transactions( - sender=sender, - destination=destination, - fork=fork.fork_at(block_number=2, timestamp=FORK_TIMESTAMP), - blob_count=fork_block_blobs, - max_fee_per_blob_gas=fork_block_blob_gas_price, - first_blob_index=pre_fork_blobs, - error=BLOB_COUNT_ERRORS, + first_blob_index=max_blobs, + error=error, + ) + if error is None: + fork_block = Block( + txs=fork_block_txs, + timestamp=FORK_TIMESTAMP, + header_verify=Header( + blob_gas_used=fork_block_blobs * blob_gas_per_blob ), + ) + tx_count = len(pre_fork_txs) + len(fork_block_txs) + else: + fork_block = Block( + txs=fork_block_txs, timestamp=FORK_TIMESTAMP, - exception=BLOB_COUNT_ERRORS, - ), - ] + exception=error, + ) + tx_count = len(pre_fork_txs) blockchain_test( pre=pre, - post={}, - blocks=blocks, + post={destination: Account(balance=tx_count)}, + blocks=[ + Block( + txs=pre_fork_txs, + timestamp=pre_fork_timestamp, + header_verify=Header( + blob_gas_used=max_blobs * blob_gas_per_blob + ), + ), + fork_block, + ], genesis_environment=Environment( base_fee_per_gas=BLOB_BASE_FEE_PER_GAS ), @@ -478,75 +492,69 @@ def test_blob_count_limit_at_fork( @pytest.mark.parametrize( - "rejected_block_index", + "price_offset", [ - pytest.param( - FORK_BLOCK_INDEX - 1, id="last_pre_fork_block_new_fraction" - ), - pytest.param(FORK_BLOCK_INDEX, id="fork_block_one_wei_short"), + pytest.param(0, id="exact_price"), + pytest.param(-1, id="one_wei_short", marks=pytest.mark.exception_test), ], ) -@pytest.mark.exception_test -def test_insufficient_max_fee_per_blob_gas_at_fork( +def test_blob_base_fee_unchanged_at_fork( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: TransitionFork, - rejected_block_index: int, + price_offset: int, ) -> None: """ - Reject a blob transaction priced at the new update fraction one block - before the fork, or one wei under it at the fork block. + Accept a fork block blob transaction offering exactly the blob base fee + under the parent fork's update fraction, and reject one wei less. """ + schedule = unchanged_blob_schedule(fork) sender = pre.fund_eoa() destination = pre.fund_eoa(amount=0) - timestamps = BLOCK_TIMESTAMPS[ - FORK_BLOCK_INDEX - 1 : rejected_block_index + 1 - ] + pre_fork_timestamp = BLOCK_TIMESTAMPS[FORK_BLOCK_INDEX - 1] genesis_excess_blob_gas = ( - GENESIS_EXCESS_BLOBS * fork.transitions_to().blob_gas_per_blob() + GENESIS_EXCESS_BLOBS * schedule.blob_gas_per_blob() ) - excess_blob_gas = genesis_excess_blob_gas - for timestamp in timestamps: - excess_blob_gas = next_excess_blob_gas( - fork, timestamp, excess_blob_gas, 0 - ) - rejected_fork = fork.fork_at( - block_number=len(timestamps), timestamp=timestamps[-1] + # Two blocks without blobs, the last before the fork and the fork block. + excess_blob_gas = next_excess_blob_gas( + schedule, + next_excess_blob_gas(schedule, genesis_excess_blob_gas, 0), + 0, ) - required_price = rejected_fork.blob_gas_price_calculator()( + blob_gas_price = schedule.blob_gas_price_calculator()( excess_blob_gas=excess_blob_gas ) - if rejected_block_index < FORK_BLOCK_INDEX: - offered_price = fork.transitions_to().blob_gas_price_calculator()( - excess_blob_gas=excess_blob_gas - ) - # The new fraction prices the same excess lower, so a client applying - # it a block early would accept this offer. - assert offered_price < required_price + if price_offset == 0: + error = None + header_verify = Header(excess_blob_gas=excess_blob_gas) + post = {destination: Account(balance=1)} + elif price_offset == -1: + error = TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS + header_verify = None + post = {} else: - offered_price = required_price - 1 - - error = TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS - blocks = [Block(timestamp=timestamp) for timestamp in timestamps[:-1]] - blocks.append( - Block( - txs=blob_transactions( - sender=sender, - destination=destination, - fork=rejected_fork, - blob_count=1, - max_fee_per_blob_gas=offered_price, - error=error, - ), - timestamp=timestamps[-1], - exception=error, - ) - ) - + raise ValueError(f"Unexpected price offset {price_offset}") blockchain_test( pre=pre, - post={}, - blocks=blocks, + post=post, + blocks=[ + Block(timestamp=pre_fork_timestamp), + Block( + txs=blob_transactions( + sender=sender, + destination=destination, + fork=fork.fork_at( + block_number=2, timestamp=FORK_TIMESTAMP + ), + blob_count=1, + max_fee_per_blob_gas=blob_gas_price + price_offset, + error=error, + ), + timestamp=FORK_TIMESTAMP, + exception=error, + header_verify=header_verify, + ), + ], genesis_environment=Environment( base_fee_per_gas=BLOB_BASE_FEE_PER_GAS, excess_blob_gas=genesis_excess_blob_gas,