Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(tests): EIP-6110, EIP-7002, EIP-7685: General purpose execution layer requests, Deposits on Chain, EL Withdrawals #530

Merged
merged 7 commits into from
May 24, 2024
Merged
3 changes: 3 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Test fixtures for use by clients are available for each release on the [Github r
- ✨ Add eof example valid invalid tests from ori, fetch EOF Container implementation ([#535](https://github.com/ethereum/execution-spec-tests/pull/535)).
- ✨ Add tests for [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) ([#499](https://github.com/ethereum/execution-spec-tests/pull/499)).
- ✨ [EIP-663](https://eips.ethereum.org/EIPS/eip-663): Add `test_dupn.py` and `test_swapn.py` ([#502](https://github.com/ethereum/execution-spec-tests/pull/502)).
- ✨ Add tests for [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).
- ✨ Add tests for [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).
- ✨ Add tests for [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).

### 🛠️ Framework

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ ethereum_test_tools =
py.typed
ethereum_test_forks =
py.typed
forks/*.bin
evm_transition_tool =
py.typed
pytest_plugins =
Expand Down
8 changes: 8 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ def header_beacon_root_required(cls, block_number: int, timestamp: int) -> bool:
"""
pass

@classmethod
@abstractmethod
def header_requests_required(cls, block_number: int, timestamp: int) -> bool:
"""
Returns true if the header must contain beacon chain requests
"""
pass

@classmethod
@abstractmethod
def blob_gas_per_blob(cls, block_number: int, timestamp: int) -> int:
Expand Down
Binary file not shown.
60 changes: 60 additions & 0 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
All Ethereum fork class definitions.
"""

from hashlib import sha256
from os.path import realpath
from pathlib import Path
from typing import List, Mapping, Optional

from semver import Version

from ..base_fork import BaseFork

CURRENT_FILE = Path(realpath(__file__))
CURRENT_FOLDER = CURRENT_FILE.parent


# All forks must be listed here !!! in the order they were introduced !!!
class Frontier(BaseFork, solc_name="homestead"):
Expand Down Expand Up @@ -89,6 +95,13 @@ def blob_gas_per_blob(cls, block_number: int, timestamp: int) -> int:
"""
return 0

@classmethod
def header_requests_required(cls, block_number: int, timestamp: int) -> bool:
"""
At genesis, header must not contain beacon chain requests.
"""
return False

@classmethod
def engine_new_payload_version(
cls, block_number: int = 0, timestamp: int = 0
Expand Down Expand Up @@ -492,6 +505,53 @@ def precompiles(cls, block_number: int = 0, timestamp: int = 0) -> List[int]:
"""
return list(range(0xB, 0x13 + 1)) + super(Prague, cls).precompiles(block_number, timestamp)

@classmethod
def pre_allocation_blockchain(cls) -> Mapping:
"""
Prague requires pre-allocation of the beacon chain deposit contract for EIP-6110, and
the exits contract for EIP-7002.
"""
new_allocation = {}

# Add the beacon chain deposit contract
DEPOSIT_CONTRACT_TREE_DEPTH = 32
storage = {}
next_hash = sha256(b"\x00" * 64).digest()
for i in range(DEPOSIT_CONTRACT_TREE_DEPTH + 2, DEPOSIT_CONTRACT_TREE_DEPTH * 2 + 1):
storage[i] = next_hash
next_hash = sha256(next_hash + next_hash).digest()

with open(CURRENT_FOLDER / "deposit_contract.bin", mode="rb") as f:
new_allocation.update(
{
0x00000000219AB540356CBB839CBE05303D7705FA: {
"nonce": 1,
"code": f.read(),
"storage": storage,
}
}
)

# Add the withdrawal request contract
with open(CURRENT_FOLDER / "withdrawal_request.bin", mode="rb") as f:
new_allocation.update(
{
0x00A3CA265EBCB825B45F985A16CEFB49958CE017: {
"nonce": 1,
"code": f.read(),
},
}
)
return new_allocation | super(Prague, cls).pre_allocation_blockchain()

@classmethod
def header_requests_required(cls, block_number: int, timestamp: int) -> bool:
"""
Prague requires that the execution layer block contains the beacon
chain requests.
"""
return True

@classmethod
def engine_new_payload_version(
cls, block_number: int = 0, timestamp: int = 0
Expand Down
Binary file added src/ethereum_test_forks/forks/withdrawal_request.bin
Binary file not shown.
4 changes: 4 additions & 0 deletions src/ethereum_test_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Account,
Address,
Alloc,
DepositRequest,
EngineAPIError,
Environment,
Hash,
Expand All @@ -31,6 +32,7 @@
TestPrivateKey2,
Transaction,
Withdrawal,
WithdrawalRequest,
add_kzg_version,
ceiling_division,
compute_create2_address,
Expand Down Expand Up @@ -77,6 +79,7 @@
"Code",
"CodeGasMeasure",
"Conditional",
"DepositRequest",
"EngineAPIError",
"Environment",
"EOFException",
Expand Down Expand Up @@ -109,6 +112,7 @@
"Transaction",
"TransactionException",
"Withdrawal",
"WithdrawalRequest",
"Yul",
"YulCompiler",
"add_kzg_version",
Expand Down
6 changes: 6 additions & 0 deletions src/ethereum_test_tools/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@
AccessList,
Account,
Alloc,
DepositRequest,
Environment,
Removable,
Requests,
Storage,
Transaction,
Withdrawal,
WithdrawalRequest,
)

__all__ = (
Expand All @@ -55,6 +58,7 @@
"Alloc",
"Bloom",
"Bytes",
"DepositRequest",
"EngineAPIError",
"EmptyOmmersRoot",
"EmptyTrieRoot",
Expand All @@ -64,6 +68,7 @@
"HexNumber",
"Number",
"Removable",
"Requests",
"Storage",
"TestAddress",
"TestAddress2",
Expand All @@ -72,6 +77,7 @@
"TestPrivateKey2",
"Transaction",
"Withdrawal",
"WithdrawalRequest",
"ZeroPaddedHexNumber",
"add_kzg_version",
"ceiling_division",
Expand Down
16 changes: 16 additions & 0 deletions src/ethereum_test_tools/common/base_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,19 @@ class HeaderNonce(FixedSizeBytes[8]): # type: ignore
"""

pass


class BLSPublicKey(FixedSizeBytes[48]): # type: ignore
"""
Class that helps represent BLS public keys in tests.
"""

pass


class BLSSignature(FixedSizeBytes[96]): # type: ignore
"""
Class that helps represent BLS signatures in tests.
"""

pass
138 changes: 138 additions & 0 deletions src/ethereum_test_tools/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
from .base_types import (
Address,
Bloom,
BLSPublicKey,
BLSSignature,
Bytes,
Hash,
HashInt,
Expand Down Expand Up @@ -1240,6 +1242,139 @@ def list_blob_versioned_hashes(input_txs: List["Transaction"]) -> List[Hash]:
]


class RequestBase:
"""
Base class for requests.
"""

@classmethod
def type_byte(cls) -> bytes:
"""
Returns the request type.
"""
raise NotImplementedError("request_type must be implemented in child classes")

def to_serializable_list(self) -> List[Any]:
"""
Returns the request's attributes as a list of serializable elements.
"""
raise NotImplementedError("to_serializable_list must be implemented in child classes")


class DepositRequestGeneric(RequestBase, CamelModel, Generic[NumberBoundTypeVar]):
"""
Generic deposit type used as a parent for DepositRequest and FixtureDepositRequest.
"""

pubkey: BLSPublicKey
withdrawal_credentials: Hash
amount: NumberBoundTypeVar
signature: BLSSignature
index: NumberBoundTypeVar

@classmethod
def type_byte(cls) -> bytes:
"""
Returns the deposit request type.
"""
return b"\0"

def to_serializable_list(self) -> List[Any]:
"""
Returns the deposit's attributes as a list of serializable elements.
"""
return [
self.pubkey,
self.withdrawal_credentials,
Uint(self.amount),
self.signature,
Uint(self.index),
]


class DepositRequest(DepositRequestGeneric[HexNumber]):
"""
Deposit Request type
"""

pass


class WithdrawalRequestGeneric(RequestBase, CamelModel, Generic[NumberBoundTypeVar]):
"""
Generic withdrawal request type used as a parent for WithdrawalRequest and
FixtureWithdrawalRequest.
"""

source_address: Address = Address(0)
validator_public_key: BLSPublicKey
amount: NumberBoundTypeVar

@classmethod
def type_byte(cls) -> bytes:
"""
Returns the withdrawal request type.
"""
return b"\1"

def to_serializable_list(self) -> List[Any]:
"""
Returns the deposit's attributes as a list of serializable elements.
"""
return [
self.source_address,
self.validator_public_key,
Uint(self.amount),
]


class WithdrawalRequest(WithdrawalRequestGeneric[HexNumber]):
"""
Withdrawal Request type
"""

pass


class Requests(RootModel[List[DepositRequest | WithdrawalRequest]]):
"""
Requests for the transition tool.
"""

root: List[DepositRequest | WithdrawalRequest] = Field(default_factory=list)

def to_serializable_list(self) -> List[Any]:
"""
Returns the requests as a list of serializable elements.
"""
return [r.type_byte() + eth_rlp.encode(r.to_serializable_list()) for r in self.root]

@cached_property
def trie_root(self) -> Hash:
"""
Returns the root hash of the requests.
"""
t = HexaryTrie(db={})
for i, r in enumerate(self.root):
t.set(
eth_rlp.encode(Uint(i)),
r.type_byte() + eth_rlp.encode(r.to_serializable_list()),
)
return Hash(t.root_hash)

def deposit_requests(self) -> List[DepositRequest]:
"""
Returns the list of deposit requests.
"""
return [d for d in self.root if isinstance(d, DepositRequest)]

def withdrawal_requests(self) -> List[WithdrawalRequest]:
"""
Returns the list of withdrawal requests.
"""
return [w for w in self.root if isinstance(w, WithdrawalRequest)]


# TODO: Move to other file
# Transition tool models

Expand Down Expand Up @@ -1310,6 +1445,9 @@ class Result(CamelModel):
withdrawals_root: Hash | None = None
excess_blob_gas: HexNumber | None = Field(None, alias="currentExcessBlobGas")
blob_gas_used: HexNumber | None = None
requests_root: Hash | None = None
deposit_requests: List[DepositRequest] | None = None
withdrawal_requests: List[WithdrawalRequest] | None = None


class TransitionToolOutput(CamelModel):
Expand Down
4 changes: 4 additions & 0 deletions src/ethereum_test_tools/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ class BlockException(ExceptionBase):
"""
Block's rlp encoding is valid but ethereum structures in it are invalid
"""
INVALID_REQUESTS = auto()
"""
Block's requests are invalid
"""


@unique
Expand Down