Skip to content

Commit

Permalink
feat(fw|tests): add changes for verkle transition tests. (ethereum#507)
Browse files Browse the repository at this point in the history
* feat(fw|tests): add changes for verkle transition tests.

---------

Co-authored-by: Mario Vega <marioevz@gmail.com>
  • Loading branch information
spencer-tb and marioevz committed Jul 30, 2024
1 parent a6a400b commit fb9c617
Show file tree
Hide file tree
Showing 23 changed files with 623 additions and 91 deletions.
12 changes: 6 additions & 6 deletions src/ethereum_test_fixtures/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ class FixtureHeader(CamelModel):
extra_data: Bytes
prev_randao: Hash = Field(Hash(0), alias="mixHash")
nonce: HeaderNonce = Field(HeaderNonce(0), validate_default=True)
base_fee_per_gas: Annotated[
ZeroPaddedHexNumber, HeaderForkRequirement("base_fee")
] | None = Field(None)
base_fee_per_gas: Annotated[ZeroPaddedHexNumber, HeaderForkRequirement("base_fee")] | None = (
Field(None)
)
withdrawals_root: Annotated[Hash, HeaderForkRequirement("withdrawals")] | None = Field(None)
blob_gas_used: (
Annotated[ZeroPaddedHexNumber, HeaderForkRequirement("blob_gas_used")] | None
Expand Down Expand Up @@ -234,9 +234,9 @@ def from_fixture_header(
withdrawals=withdrawals,
deposit_requests=requests.deposit_requests() if requests is not None else None,
withdrawal_requests=requests.withdrawal_requests() if requests is not None else None,
consolidation_requests=requests.consolidation_requests()
if requests is not None
else None,
consolidation_requests=(
requests.consolidation_requests() if requests is not None else None
),
)


Expand Down
4 changes: 4 additions & 0 deletions src/ethereum_test_forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Cancun,
Constantinople,
ConstantinopleFix,
EIP6800Transition,
Frontier,
GrayGlacier,
Homestead,
Expand All @@ -19,6 +20,7 @@
Paris,
Prague,
Shanghai,
ShanghaiEIP6800,
)
from .forks.transition import (
BerlinToLondonAt5,
Expand Down Expand Up @@ -60,6 +62,8 @@
"MuirGlacier",
"Shanghai",
"ShanghaiToCancunAtTime15k",
"ShanghaiEIP6800",
"EIP6800Transition",
"Cancun",
"Prague",
"get_transition_forks",
Expand Down
16 changes: 16 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,22 @@ def pre_allocation_blockchain(cls) -> Mapping:
"""
pass

@classmethod
@abstractmethod
def environment_verkle_conversion_starts(cls) -> bool:
"""
Returns true if the fork starts the verkle conversion process.
"""
pass

@classmethod
@abstractmethod
def environment_verkle_conversion_completed(cls) -> bool:
"""
Returns true if verkle conversion must have been completed by this fork.
"""
pass

# Engine API information abstract methods
@classmethod
@abstractmethod
Expand Down
62 changes: 62 additions & 0 deletions src/ethereum_test_forks/forks/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Constant values used by the forks.
"""

from typing import Dict, Generator, Iterator, Mapping, Tuple

from Crypto.Hash import SHA256

# TODO: Use for large verkle conversion init MPT
MAX_ACCOUNTS = 1000
MAX_NONCE = 2**64 - 1
MAX_BALANCE = 2**256 - 1
MAX_STORAGE_SLOTS_PER_ACCOUNT = 1000
MAX_ACCOUNT_CODE_SIZE = 2**14 + 2**13 # EIP-170


def seed_generator(seed: int) -> Generator[int, None, None]:
"""
Generate a seed using the SHA256 hash function.
"""
seed = int.from_bytes(
bytes=SHA256.new(data=seed.to_bytes(length=256, byteorder="big")).digest(), byteorder="big"
)
while True:
yield seed
seed = int.from_bytes(
bytes=SHA256.new(data=seed.to_bytes(length=256, byteorder="big")).digest(),
byteorder="big",
)


def storage_generator(
seed: Iterator[int], max_slots: int
) -> Generator[Tuple[int, int], None, None]:
"""
Generate storage slots for an account.
"""
MAX_KEY_VALUE = 2**256 - 1
for _ in range(max_slots):
yield next(seed) % MAX_KEY_VALUE, next(seed) % MAX_KEY_VALUE


def account_generator(
seed: Iterator[int], max_accounts: int
) -> Generator[Tuple[int, Dict[str, str | int | Dict[int, int]]], None, None]:
"""
Generate accounts.
"""
for _ in range(max_accounts):
storage_g = storage_generator(seed, next(seed) % MAX_STORAGE_SLOTS_PER_ACCOUNT)
yield next(seed) % 2**160, {
"nonce": next(seed) % MAX_NONCE,
"balance": next(seed) % MAX_BALANCE,
"storage": {k: v for k, v in storage_g},
"code": "0x" + "00" * 32,
}


VERKLE_PRE_ALLOCATION: Mapping = {
addr: account
for addr, account in account_generator(seed=seed_generator(0), max_accounts=MAX_ACCOUNTS)
}
75 changes: 74 additions & 1 deletion src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from semver import Version

from ..base_fork import BaseFork
from ..transition_base_fork import transition_fork

CURRENT_FILE = Path(realpath(__file__))
CURRENT_FOLDER = CURRENT_FILE.parent
Expand Down Expand Up @@ -188,6 +189,20 @@ def pre_allocation_blockchain(cls) -> Mapping:
"""
return {}

@classmethod
def environment_verkle_conversion_starts(cls) -> bool:
"""
Returns true if the fork starts the verkle conversion process.
"""
return False

@classmethod
def environment_verkle_conversion_completed(cls) -> bool:
"""
Returns true if verkle conversion must have been completed by this fork.
"""
return False


class Homestead(Frontier):
"""
Expand Down Expand Up @@ -615,7 +630,65 @@ def engine_forkchoice_updated_version(
return 3


class CancunEIP7692( # noqa: SC200
class ShanghaiEIP6800(
Shanghai,
transition_tool_name="Prague",
blockchain_test_network_name="Prague",
solc_name="shanghai",
):
"""
Shanghai + EIP-6800 (Verkle) fork
"""

@classmethod
def is_deployed(cls) -> bool:
"""
Flags that the fork has not been deployed to mainnet; it is under active
development.
"""
return False

@classmethod
def environment_verkle_conversion_completed(cls) -> bool:
"""
Verkle conversion has already completed in this fork.
"""
return True

@classmethod
def pre_allocation_blockchain(cls) -> Mapping:
"""
Verkle requires pre-allocation of the history storage contract for EIP-2935 on blockchain
type tests.
"""
new_allocation = {
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE: {
"nonce": 1,
"code": (
"0x60203611603157600143035f35116029575f35612000014311602957612000"
"5f3506545f5260205ff35b5f5f5260205ff35b5f5ffd00"
),
}
}
# TODO: Utilize when testing for large init MPT
# return VERKLE_PRE_ALLOCATION | super(Shanghai, cls).pre_allocation()
return new_allocation | super(Shanghai, cls).pre_allocation_blockchain()


# TODO: move back to transition.py after filling and executing ShanghaiEIP6800 tests successfully
@transition_fork(to_fork=ShanghaiEIP6800, at_timestamp=32)
class EIP6800Transition(
Shanghai,
blockchain_test_network_name="ShanghaiToPragueAtTime32",
):
"""
Shanghai to Verkle transition at Timestamp 32.
"""

pass


class CancunEIP7692(
Cancun,
transition_tool_name="Prague", # Evmone enables (only) EOF at Prague
blockchain_test_network_name="Prague", # Evmone enables (only) EOF at Prague
Expand Down
2 changes: 1 addition & 1 deletion src/ethereum_test_forks/forks/transition.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""
List of all transition fork definitions.
"""

from ..transition_base_fork import transition_fork
from .forks import Berlin, Cancun, London, Paris, Prague, Shanghai


# Transition Forks
@transition_fork(to_fork=London, at_block=5)
class BerlinToLondonAt5(Berlin):
"""
Expand Down
8 changes: 7 additions & 1 deletion src/ethereum_test_forks/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Helper methods to resolve forks during test filling
"""

from typing import List, Optional

from semver import Version
Expand Down Expand Up @@ -32,6 +33,9 @@ def get_forks() -> List[Fork]:
continue
if issubclass(fork, BaseFork) and fork is not BaseFork:
all_forks.append(fork)

all_forks += get_transition_forks(always_execute=True)

return all_forks


Expand Down Expand Up @@ -87,7 +91,7 @@ def get_closest_fork_with_solc_support(fork: Fork, solc_version: Version) -> Opt
)


def get_transition_forks() -> List[Fork]:
def get_transition_forks(always_execute: bool = False) -> List[Fork]:
"""
Returns all the transition forks
"""
Expand All @@ -98,6 +102,8 @@ def get_transition_forks() -> List[Fork]:
if not isinstance(fork, type):
continue
if issubclass(fork, TransitionBaseClass) and issubclass(fork, BaseFork):
if always_execute and not fork.always_execute():
continue
transition_forks.append(fork)

return transition_forks
Expand Down
13 changes: 12 additions & 1 deletion src/ethereum_test_forks/transition_base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ def transitions_from(cls) -> Fork:
"""
raise Exception("Not implemented")

@classmethod
def always_execute(cls) -> bool:
"""
Whether the transition fork should be treated as a normal fork and all tests should
be filled with it.
"""
raise Exception("Not implemented")


def base_fork_abstract_methods() -> List[str]:
"""
Expand All @@ -38,7 +46,9 @@ def base_fork_abstract_methods() -> List[str]:
return list(getattr(BaseFork, "__abstractmethods__"))


def transition_fork(to_fork: Fork, at_block: int = 0, at_timestamp: int = 0):
def transition_fork(
to_fork: Fork, at_block: int = 0, at_timestamp: int = 0, always_execute: bool = False
):
"""
Decorator to mark a class as a transition fork.
"""
Expand Down Expand Up @@ -102,6 +112,7 @@ def transition_method(

NewTransitionClass.transitions_to = lambda: to_fork # type: ignore
NewTransitionClass.transitions_from = lambda: from_fork # type: ignore
NewTransitionClass.always_execute = lambda: always_execute # type: ignore
NewTransitionClass.fork_at = lambda block_number=0, timestamp=0: ( # type: ignore
to_fork if block_number >= at_block and timestamp >= at_timestamp else from_fork
)
Expand Down
26 changes: 25 additions & 1 deletion src/ethereum_test_specs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from ethereum_test_base_types import to_hex
from ethereum_test_fixtures import BaseFixture, FixtureFormats
from ethereum_test_forks import Fork
from ethereum_test_types import Environment, Transaction, Withdrawal
from ethereum_test_types import Alloc, Environment, Transaction, VerkleTree, Withdrawal
from evm_transition_tool import Result, TransitionTool


Expand Down Expand Up @@ -61,6 +61,30 @@ def verify_result(result: Result, env: Environment):
assert result.withdrawals_root == to_hex(Withdrawal.list_root(env.withdrawals))


def verify_post_vkt(t8n: TransitionTool, expected_post: Alloc, got_vkt: VerkleTree):
"""
Verify that the final verkle tree from t8n matches the expected post alloc defined within
the test. Raises exception on unexpected values.
"""
if not t8n.verkle_subcommand:
raise Exception("Only geth's evm tool is supported to verify verkle trees.")

# Convert the expected post alloc to a verkle tree for comparison.
expected_vkt = t8n.from_mpt_to_vkt(mpt_alloc=expected_post)

# TODO: utilize missing keys?
# Check for keys that are missing the actual VKT
_ = [key for key in expected_vkt.root if key not in got_vkt.root]

# Compare the values for each key in the expected VKT
for key, expected_value in expected_vkt.root.items():
actual_value = got_vkt.root.get(key)
if expected_value != actual_value:
raise Exception(
f"VKT mismatch at key {key}: expected {expected_value}, got {actual_value}"
)


class BaseTest(BaseModel):
"""
Represents a base Ethereum test which must return a single test fixture.
Expand Down
Loading

0 comments on commit fb9c617

Please sign in to comment.