Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 154 additions & 56 deletions fillers/eips/eip4844/excess_data_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
from dataclasses import dataclass
from typing import List, Mapping, Optional

from ethereum_test_forks import Fork, ShardingFork
from ethereum_test_forks import (
Fork,
Shanghai,
ShanghaiToShardingAtTime15k,
ShardingFork,
is_fork,
)
from ethereum_test_tools import (
Account,
Block,
Expand All @@ -15,6 +21,7 @@
TestAddress,
Transaction,
test_from,
test_only,
to_address,
to_hash_bytes,
)
Expand Down Expand Up @@ -426,12 +433,76 @@ def test_invalid_excess_data_gas_in_header(_: Fork):
yield tc.generate()


def ignore_test_fork_transition_excess_data_gas_in_header(_: Fork):
@test_only(fork=ShanghaiToShardingAtTime15k)
def test_fork_transition_excess_data_gas_in_header(_: Fork):
"""
Test excess_data_gas calculation in the header when the fork is activated.
"""
# TODO!
pass
env = Environment()
pre = {
TestAddress: Account(balance=10**40),
}
destination_account = to_address(0x100)

# Generate some blocks to reach Sharding fork
FORK_TIMESTAMP = 15_000
blocks: List[Block] = []
for t in range(999, FORK_TIMESTAMP, 1_000):
blocks.append(Block(timestamp=t))

# Test N blocks until excess data gas after fork reaches data gas cost > 1
BLOBS_TO_DATA_GAS_COST_INCREASE = 12
assert get_data_gasprice_from_blobs(
BLOBS_TO_DATA_GAS_COST_INCREASE - 1
) != get_data_gasprice_from_blobs(BLOBS_TO_DATA_GAS_COST_INCREASE)

parent_excess_data_gas = 0
destination_account_value = 0
for i in range(
BLOBS_TO_DATA_GAS_COST_INCREASE
// (MAX_BLOBS_PER_BLOCK - TARGET_BLOBS_PER_BLOCK)
+ 1
):
blocks.append(
Block(
txs=[
Transaction(
ty=5,
nonce=i,
to=destination_account,
value=1,
gas_limit=3000000,
max_fee_per_gas=1000000,
max_priority_fee_per_gas=10,
max_fee_per_data_gas=get_data_gasprice(
excess_data_gas=parent_excess_data_gas
),
access_list=[],
blob_versioned_hashes=[
to_hash_bytes(x)
for x in range(MAX_BLOBS_PER_BLOCK)
],
)
],
)
)
destination_account_value += 1
parent_excess_data_gas = calc_excess_data_gas(
parent_excess_data_gas,
MAX_BLOBS_PER_BLOCK,
)

post: Mapping[str, Account] = {
destination_account: Account(balance=destination_account_value),
}

yield BlockchainTest(
pre=pre,
post=post,
blocks=blocks,
genesis_environment=env,
tag="correct_initial_data_gas_calc",
)


@dataclass(kw_only=True)
Expand All @@ -446,10 +517,10 @@ class InvalidBlobTransactionTestCase:
"""

tag: str
parent_excess_blobs: int
blobs_per_tx: int
tx_error: str
tx_count: int = 1
parent_excess_blobs: Optional[int] = None
tx_max_data_gas_cost: Optional[int] = None
account_balance_modifier: int = 0
block_base_fee: int = 7
Expand All @@ -458,14 +529,18 @@ def generate(self) -> BlockchainTest:
"""
Generate the test case.
"""
parent_excess_data_gas = self.parent_excess_blobs * DATA_GAS_PER_BLOB
env = Environment(excess_data_gas=parent_excess_data_gas)

env = Environment()
data_gasprice = 1
destination_account = to_address(0x100)

data_gasprice = get_data_gasprice(
excess_data_gas=parent_excess_data_gas
)
if self.parent_excess_blobs is not None:
parent_excess_data_gas = (
self.parent_excess_blobs * DATA_GAS_PER_BLOB
)
env = Environment(excess_data_gas=parent_excess_data_gas)
data_gasprice = get_data_gasprice(
excess_data_gas=parent_excess_data_gas
)

total_account_minimum_balance = 0

Expand Down Expand Up @@ -522,8 +597,8 @@ def generate(self) -> BlockchainTest:
)


@test_from(fork=ShardingFork)
def test_invalid_blob_txs(_: Fork):
@test_from(fork=Shanghai)
def test_invalid_blob_txs(fork: Fork):
"""
Reject blocks with invalid blob txs due to:
- The user cannot afford the data gas specified (but max_fee_per_gas
Expand All @@ -534,49 +609,72 @@ def test_invalid_blob_txs(_: Fork):
- blob count > MAX_BLOBS_PER_BLOCK in type 5 transaction
- block blob count > MAX_BLOBS_PER_BLOCK
"""
test_cases: List[InvalidBlobTransactionTestCase] = [
InvalidBlobTransactionTestCase(
tag="insufficient_max_fee_per_data_gas",
parent_excess_blobs=15, # data gas cost = 2
tx_max_data_gas_cost=1, # less than minimum
tx_error="insufficient max fee per data gas",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="insufficient_balance_sufficient_fee",
parent_excess_blobs=15, # data gas cost = 1
tx_max_data_gas_cost=100, # > data gas cost
account_balance_modifier=-1,
tx_error="insufficient max fee per data gas",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="zero_max_fee_per_data_gas",
parent_excess_blobs=0, # data gas cost = 1
tx_max_data_gas_cost=0, # invalid value
tx_error="insufficient max fee per data gas",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="blob_overflow",
parent_excess_blobs=10, # data gas cost = 1
tx_error="too_many_blobs",
blobs_per_tx=5,
),
InvalidBlobTransactionTestCase(
tag="multi_tx_blob_overflow",
parent_excess_blobs=10, # data gas cost = 1
tx_error="too_many_blobs",
tx_count=5,
blobs_per_tx=1,
),
# InvalidBlobTransactionTestCase(
# tag="blob_underflow",
# parent_excess_blobs=10, # data gas cost = 1
# tx_error="too_few_blobs",
# blobs_per_tx=0,
# ),
]
test_cases: List[InvalidBlobTransactionTestCase] = []
if is_fork(fork, ShardingFork):
test_cases = [
InvalidBlobTransactionTestCase(
tag="insufficient_max_fee_per_data_gas",
parent_excess_blobs=15, # data gas cost = 2
tx_max_data_gas_cost=1, # less than minimum
tx_error="insufficient max fee per data gas",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="insufficient_balance_sufficient_fee",
parent_excess_blobs=15, # data gas cost = 1
tx_max_data_gas_cost=100, # > data gas cost
account_balance_modifier=-1,
tx_error="insufficient max fee per data gas",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="zero_max_fee_per_data_gas",
parent_excess_blobs=0, # data gas cost = 1
tx_max_data_gas_cost=0, # invalid value
tx_error="insufficient max fee per data gas",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="blob_overflow",
parent_excess_blobs=10, # data gas cost = 1
tx_error="too_many_blobs",
blobs_per_tx=5,
),
InvalidBlobTransactionTestCase(
tag="multi_tx_blob_overflow",
parent_excess_blobs=10, # data gas cost = 1
tx_error="too_many_blobs",
tx_count=5,
blobs_per_tx=1,
),
# TODO: Enable, at the time of writing this test case, the EIP
# does not specify a minimum blob amount for the type 5
# transaction.
# InvalidBlobTransactionTestCase(
# tag="blob_underflow",
# parent_excess_blobs=10, # data gas cost = 1
# tx_error="too_few_blobs",
# blobs_per_tx=0,
# ),
]
else:
# Pre-Sharding, blocks with type 5 txs must be rejected
test_cases = [
InvalidBlobTransactionTestCase(
tag="type_5_tx_pre_fork",
parent_excess_blobs=None,
tx_max_data_gas_cost=1,
tx_error="tx_type_5_not_allowed_yet",
blobs_per_tx=1,
),
InvalidBlobTransactionTestCase(
tag="empty_type_5_tx_pre_fork",
parent_excess_blobs=None,
tx_max_data_gas_cost=1,
tx_error="tx_type_5_not_allowed_yet",
blobs_per_tx=0,
),
]

for tc in test_cases:
yield tc.generate()
11 changes: 8 additions & 3 deletions src/ethereum_test_forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
MuirGlacier,
Shanghai,
)
from .forks.transition import BerlinToLondonAt5, MergeToShanghaiAtTime15k
from .forks.transition import (
BerlinToLondonAt5,
MergeToShanghaiAtTime15k,
ShanghaiToShardingAtTime15k,
)
from .forks.upcoming import ShardingFork
from .helpers import (
fork_only,
Expand All @@ -33,6 +37,7 @@
"Fork",
"ArrowGlacier",
"Berlin",
"BerlinToLondonAt5",
"Byzantium",
"Constantinople",
"ConstantinopleFix",
Expand All @@ -42,10 +47,10 @@
"Istanbul",
"London",
"Merge",
"MergeToShanghaiAtTime15k",
"MuirGlacier",
"Shanghai",
"BerlinToLondonAt5",
"MergeToShanghaiAtTime15k",
"ShanghaiToShardingAtTime15k",
"ShardingFork",
"fork_only",
"forks_from",
Expand Down
7 changes: 7 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ def get_reward(cls, block_number: int, timestamp: int) -> int:
"""
pass

@classmethod
def name(cls) -> str:
"""
Returns the name of the fork.
"""
return cls.__name__


# Fork Type
Fork = Type[BaseFork]
16 changes: 15 additions & 1 deletion src/ethereum_test_forks/forks/transition.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
from ..transition_base_fork import transition_fork
from .forks import Berlin, London, Merge, Shanghai
from .upcoming import TestOnlyUpcomingFork
from .upcoming import ShardingFork, TestOnlyUpcomingFork


# Transition Forks
Expand Down Expand Up @@ -35,6 +35,20 @@ def header_withdrawals_required(cls, _: int, timestamp: int) -> bool:
return timestamp >= 15_000


@transition_fork(to_fork=ShardingFork)
class ShanghaiToShardingAtTime15k(Shanghai):
"""
Shanghai to Sharding transition at Timestamp 15k
"""

@classmethod
def header_excess_data_gas_required(cls, _: int, timestamp: int) -> bool:
"""
Excess data gas is required if transitioning to Sharding.
"""
return timestamp >= 15_000


# Test-only transition forks


Expand Down
5 changes: 4 additions & 1 deletion src/ethereum_test_forks/tests/test_forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ def test_forks():
]
assert forks_from(Merge) == [Merge, Shanghai]

assert London.__name__ == "London"
# Test fork names
assert London.name() == "London"
assert TestOnlyUpcomingFork.name() == "TestOnlyUpcomingFork"
assert MergeToShanghaiAtTime15k.name() == "MergeToShanghaiAtTime15k"

# Test some fork properties
assert Berlin.header_base_fee_required(0, 0) is False
Expand Down
9 changes: 8 additions & 1 deletion src/ethereum_test_forks/transition_base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ def transition_fork(to_fork: Fork):
"""

def decorator(cls) -> Type[TransitionBaseClass]:
transition_name = cls.__name__

class NewTransitionClass(cls, TransitionBaseClass): # type: ignore
pass
@classmethod
def name(cls) -> str:
"""
Returns the name of the transition fork.
"""
return transition_name

NewTransitionClass.transitions_to = lambda: to_fork # type: ignore

Expand Down
5 changes: 4 additions & 1 deletion src/ethereum_test_tools/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,10 @@ def set_fork_requirements(self, fork: Fork) -> "Environment":
if fork.header_zero_difficulty_required(self.number, self.timestamp):
res.difficulty = 0

if fork.header_excess_data_gas_required(self.number, self.timestamp):
if (
fork.header_excess_data_gas_required(self.number, self.timestamp)
and res.excess_data_gas is None
):
res.excess_data_gas = 0

return res
Expand Down
Loading