Skip to content

Commit

Permalink
Merge pull request #283 from marioevz/update-t8n-to-lightclient-devnet-8
Browse files Browse the repository at this point in the history
tests,tools: Update t8n to lightclient/devnet-8 version
  • Loading branch information
marioevz authored Sep 1, 2023
2 parents 5bf8633 + ab50946 commit 1c4dfd1
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 44 deletions.
2 changes: 2 additions & 0 deletions src/ethereum_test_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
Header,
HistoryStorageAddress,
JSONEncoder,
Removable,
Storage,
TestAddress,
TestAddress2,
Expand Down Expand Up @@ -73,6 +74,7 @@
"Opcodes",
"ReferenceSpec",
"ReferenceSpecTypes",
"Removable",
"StateTest",
"StateTestFiller",
"Storage",
Expand Down
2 changes: 2 additions & 0 deletions src/ethereum_test_tools/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
HeaderNonce,
JSONEncoder,
Number,
Removable,
Storage,
Transaction,
Withdrawal,
Expand Down Expand Up @@ -80,6 +81,7 @@
"HistoryStorageAddress",
"JSONEncoder",
"Number",
"Removable",
"Storage",
"TestAddress",
"TestAddress2",
Expand Down
28 changes: 28 additions & 0 deletions src/ethereum_test_tools/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,10 @@ class Header:
"""
Sentinel object used to specify that a header field should be removed.
"""
EMPTY_FIELD: ClassVar[Removable] = Removable()
"""
Sentinel object used to specify that a header field must be empty during verification.
"""


@dataclass(kw_only=True)
Expand Down Expand Up @@ -2222,6 +2226,26 @@ def join(self, modifier: Header) -> "FixtureHeader":
setattr(new_fixture_header, header_field, value)
return new_fixture_header

def verify(self, baseline: Header):
"""
Verifies that the header fields from the baseline are as expected.
"""
for header_field in fields(self):
field_name = header_field.name
baseline_value = getattr(baseline, field_name)
if baseline_value is not None:
assert baseline_value is not Header.REMOVE_FIELD, "invalid baseline header"
value = getattr(self, field_name)
if baseline_value is Header.EMPTY_FIELD:
assert value is None, f"invalid header field {header_field}"
continue
metadata = header_field.metadata
field_metadata = metadata.get("source")
# type check is performed on collect()
if field_metadata.parse_type is not None: # type: ignore
baseline_value = field_metadata.parse_type(baseline_value) # type: ignore
assert value == baseline_value, f"invalid header field {header_field}"

def build(
self,
*,
Expand Down Expand Up @@ -2288,6 +2312,10 @@ class Block(Header):
Only meant to be used to simulate blocks with bad formats, and therefore
requires the block to produce an exception.
"""
header_verify: Optional[Header] = None
"""
If set, the block header will be verified against the specified values.
"""
rlp_modifier: Optional[Header] = None
"""
An RLP modifying header which values would be used to override the ones
Expand Down
4 changes: 4 additions & 0 deletions src/ethereum_test_tools/spec/blockchain_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ def make_block(
environment=env,
)

if block.header_verify is not None:
# Verify the header after transition tool processing.
header.verify(block.header_verify)

if block.rlp_modifier is not None:
# Modify any parameter specified in the `rlp_modifier` after
# transition tool processing.
Expand Down
19 changes: 10 additions & 9 deletions tests/cancun/eip4844_blobs/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from hashlib import sha256
from typing import Optional

from ethereum_test_tools import Transaction


@dataclass(frozen=True)
class ReferenceSpec:
Expand Down Expand Up @@ -102,15 +104,14 @@ def calc_excess_blob_gas(cls, parent: BlockHeaderBlobGasFields) -> int:
else:
return parent.excess_blob_gas + parent.blob_gas_used - cls.TARGET_BLOB_GAS_PER_BLOCK

# Note: Currently unused.
# @classmethod
# def get_total_blob_gas(cls, tx: Transaction) -> int:
# """
# Calculate the total blob gas for a transaction.
# """
# if tx.blob_versioned_hashes is None:
# return 0
# return cls.GAS_PER_BLOB * len(tx.blob_versioned_hashes)
@classmethod
def get_total_blob_gas(cls, tx: Transaction) -> int:
"""
Calculate the total blob gas for a transaction.
"""
if tx.blob_versioned_hashes is None:
return 0
return cls.GAS_PER_BLOB * len(tx.blob_versioned_hashes)

@classmethod
def get_blob_gasprice(cls, *, excess_blob_gas: int) -> int:
Expand Down
98 changes: 79 additions & 19 deletions tests/cancun/eip4844_blobs/test_blob_txs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import pytest

from ethereum_test_forks import Fork
from ethereum_test_tools import (
Account,
Block,
Expand All @@ -31,6 +32,7 @@
)
from ethereum_test_tools import Opcodes as Op
from ethereum_test_tools import (
Removable,
Storage,
TestAddress,
TestAddress2,
Expand Down Expand Up @@ -328,33 +330,91 @@ def engine_api_error_code() -> Optional[EngineAPIError]:
return None


@pytest.fixture
def block_error(tx_error: Optional[str]) -> Optional[str]:
"""
Default expected error produced by the block transactions (no error).
Can be overloaded on test cases where the transactions are expected
to fail.
"""
return tx_error


@pytest.fixture
def block_number() -> int:
"""
Default number of the first block.
"""
return 1


@pytest.fixture
def block_timestamp() -> int:
"""
Default timestamp of the first block.
"""
return 1


@pytest.fixture
def expected_blob_gas_used(
fork: Fork,
txs: List[Transaction],
block_number: int,
block_timestamp: int,
) -> Optional[int | Removable]:
"""
Calculates the blob gas used by the test block.
"""
if not fork.header_blob_gas_used_required(
block_number=block_number, timestamp=block_timestamp
):
return Header.EMPTY_FIELD
return sum([Spec.get_total_blob_gas(tx) for tx in txs])


@pytest.fixture
def expected_excess_blob_gas(
fork: Fork,
parent_excess_blob_gas: Optional[int],
parent_blobs: Optional[int],
block_number: int,
block_timestamp: int,
) -> Optional[int | Removable]:
"""
Calculates the blob gas used by the test block.
"""
if not fork.header_excess_blob_gas_required(
block_number=block_number, timestamp=block_timestamp
):
return Header.EMPTY_FIELD
return SpecHelpers.calc_excess_blob_gas_from_blob_count(
parent_excess_blob_gas=parent_excess_blob_gas if parent_excess_blob_gas else 0,
parent_blob_count=parent_blobs if parent_blobs else 0,
)


@pytest.fixture
def blocks(
expected_blob_gas_used: Optional[int | Removable],
expected_excess_blob_gas: Optional[int | Removable],
txs: List[Transaction],
tx_error: Optional[str],
block_error: Optional[str],
engine_api_error_code: Optional[EngineAPIError],
) -> List[Block]:
"""
Prepare the list of blocks for all test cases.
"""
header_blob_gas_used = 0
if len(txs) > 0:
header_blob_gas_used = (
sum(
[
len(tx.blob_versioned_hashes)
for tx in txs
if tx.blob_versioned_hashes is not None
]
)
* Spec.GAS_PER_BLOB
)
return [
Block(
txs=txs,
exception=tx_error,
exception=block_error,
engine_api_error_code=engine_api_error_code,
rlp_modifier=Header(blob_gas_used=header_blob_gas_used),
header_verify=Header(
blob_gas_used=expected_blob_gas_used,
excess_blob_gas=expected_excess_blob_gas,
),
)
]

Expand Down Expand Up @@ -520,7 +580,7 @@ def test_invalid_normal_gas(
"blobs_per_tx",
invalid_blob_combinations(),
)
@pytest.mark.parametrize("tx_error", ["invalid_blob_count"])
@pytest.mark.parametrize("block_error", ["invalid_blob_count"])
@pytest.mark.valid_from("Cancun")
def test_invalid_block_blob_count(
blockchain_test: BlockchainTestFiller,
Expand Down Expand Up @@ -606,10 +666,10 @@ def test_insufficient_balance_blob_tx_combinations(


@pytest.mark.parametrize(
"blobs_per_tx,tx_error",
"blobs_per_tx,tx_error,block_error",
[
([0], "zero_blob_tx"),
([SpecHelpers.max_blobs_per_block() + 1], "too_many_blobs_tx"),
([0], "zero_blob_tx", "zero_blob_tx"),
([SpecHelpers.max_blobs_per_block() + 1], None, "too_many_blobs"),
],
ids=["too_few_blobs", "too_many_blobs"],
)
Expand Down
50 changes: 34 additions & 16 deletions tests/cancun/eip4844_blobs/test_excess_blob_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
""" # noqa: E501
import itertools
from typing import Iterator, List, Mapping, Optional, Tuple
from typing import Dict, Iterator, List, Mapping, Optional, Tuple

import pytest

Expand Down Expand Up @@ -223,40 +223,57 @@ def header_blob_gas_used() -> Optional[int]: # noqa: D103
return None


@pytest.fixture
def correct_blob_gas_used( # noqa: D103
tx: Transaction,
) -> int:
return Spec.get_total_blob_gas(tx)


@pytest.fixture
def blocks( # noqa: D103
tx: Transaction,
header_excess_blob_gas: Optional[int],
header_blob_gas_used: Optional[int],
correct_excess_blob_gas: int,
correct_blob_gas_used: int,
non_zero_blob_gas_used_genesis_block: Block,
):
blocks = (
[]
if non_zero_blob_gas_used_genesis_block is None
else [non_zero_blob_gas_used_genesis_block]
)
if header_excess_blob_gas is not None:

def add_block(header_modifier: Optional[Dict] = None, exception_message: Optional[str] = None):
"""
Utility function to add a block to the blocks list.
"""
blocks.append(
Block(
txs=[tx],
rlp_modifier=Header(
excess_blob_gas=header_excess_blob_gas,
rlp_modifier=Header(**header_modifier) if header_modifier else None,
header_verify=Header(
excess_blob_gas=correct_excess_blob_gas,
blob_gas_used=correct_blob_gas_used,
),
exception="invalid excess blob gas",
),
exception=exception_message,
)
)

if header_excess_blob_gas is not None:
add_block(
header_modifier={"excess_blob_gas": header_excess_blob_gas},
exception_message="invalid excess blob gas",
)
elif header_blob_gas_used is not None:
blocks.append(
Block(
txs=[tx],
rlp_modifier=Header(
blob_gas_used=header_blob_gas_used,
),
exception="invalid blob gas used",
),
add_block(
header_modifier={"blob_gas_used": header_blob_gas_used},
exception_message="invalid blob gas used",
)
else:
blocks.append(Block(txs=[tx]))
add_block()

return blocks


Expand Down Expand Up @@ -515,7 +532,8 @@ def test_invalid_static_excess_blob_gas(
Test is parametrized to `MAX_BLOBS_PER_BLOCK` and `TARGET_BLOBS_PER_BLOCK`.
"""
blocks[-1].rlp_modifier = Header(excess_blob_gas=parent_excess_blob_gas)
blocks[-1].exception = "invalid excessBlobGas"
blocks[-1].header_verify = None
blocks[-1].exception = "invalid excess blob gas"
blockchain_test(
pre=pre,
post={},
Expand Down

0 comments on commit 1c4dfd1

Please sign in to comment.