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

Transition types #1038

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ install_requires =
typing_extensions>=4.2
py_ecc @ git+https://github.com/petertdavies/py_ecc.git@127184f4c57b1812da959586d0fe8f43bb1a2389
ethereum-types>=0.2.1,<0.3
ethereum-rlp>=0.1.1,<0.2
ethereum-rlp>=0.1.2,<0.2

[options.package_data]
ethereum =
Expand Down Expand Up @@ -163,7 +163,7 @@ test =
lint =
types-setuptools>=68.1.0.1,<69
isort==5.13.2
mypy==1.14.1
mypy==1.15.0
black==23.12.0
flake8==6.1.0
flake8-bugbear==23.12.2
Expand Down
69 changes: 67 additions & 2 deletions src/ethereum/arrow_glacier/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
chain.
"""
from dataclasses import dataclass
from typing import Tuple, Union
from typing import Annotated, Optional, Tuple, Union

from ethereum_rlp import rlp
from ethereum_types.bytes import Bytes, Bytes8, Bytes32
from ethereum_types.frozen import slotted_freezable
from ethereum_types.numeric import U256, Uint
from typing_extensions import TypeAlias

from ethereum.exceptions import InvalidBlock
from ethereum.london import blocks as previous_blocks

from ..crypto.hash import Hash32
from .fork_types import Address, Bloom, Root
Expand Down Expand Up @@ -45,6 +50,49 @@ class Header:
base_fee_per_gas: Uint


AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header]
"""
Represents all headers that may have appeared in the blockchain before or in
the current fork.
"""


def decode_header(raw_header: rlp.Simple) -> AnyHeader:
"""
Convert `raw_header` from raw sequences and bytes to a structured block
header.

Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs
to this fork, decodes it accordingly. If not, this function forwards to the
preceding fork where the process is repeated.
"""
from . import FORK_CRITERIA

# First, ensure that `raw_header` is not `bytes` (and is therefore a
# sequence.)
if isinstance(raw_header, bytes):
raise InvalidBlock("header is bytes, expected sequence")

# Next, extract the block number and timestamp (which are always at index 8
# and 11 respectively.)
raw_number = raw_header[8]
if not isinstance(raw_number, bytes):
raise InvalidBlock("header number is sequence, expected bytes")
number = Uint.from_be_bytes(raw_number)

raw_timestamp = raw_header[11]
if not isinstance(raw_timestamp, bytes):
raise InvalidBlock("header timestamp is sequence, expected bytes")
timestamp = U256.from_be_bytes(raw_timestamp)

# Finally, check if this header belongs to this fork.
if FORK_CRITERIA.check(number, timestamp):
return rlp.deserialize_to(Header, raw_header)

# If it doesn't, forward to the preceding fork.
return previous_blocks.decode_header(raw_header)


@slotted_freezable
@dataclass
class Block:
Expand All @@ -54,7 +102,14 @@ class Block:

header: Header
transactions: Tuple[Union[Bytes, LegacyTransaction], ...]
ommers: Tuple[Header, ...]
ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...]


AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block]
"""
Represents all blocks that may have appeared in the blockchain before or in the
current fork.
"""


@slotted_freezable
Expand All @@ -80,3 +135,13 @@ class Receipt:
cumulative_gas_used: Uint
bloom: Bloom
logs: Tuple[Log, ...]


def header_base_fee_per_gas(header: AnyHeader) -> Optional[Uint]:
"""
Returns the `base_fee_per_gas` of the given header, or `None` for headers
without that field.
"""
if isinstance(header, Header):
return header.base_fee_per_gas
return previous_blocks.header_base_fee_per_gas(header)
44 changes: 32 additions & 12 deletions src/ethereum/arrow_glacier/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,18 @@
InvalidBlock,
InvalidSenderError,
)
from ethereum.london import fork as previous_fork

from . import vm
from .blocks import Block, Header, Log, Receipt
from .blocks import (
AnyBlock,
AnyHeader,
Block,
Header,
Log,
Receipt,
header_base_fee_per_gas,
)
from .bloom import logs_bloom
from .fork_types import Address, Bloom, Root
from .state import (
Expand Down Expand Up @@ -62,6 +71,7 @@
GAS_LIMIT_ADJUSTMENT_FACTOR = Uint(1024)
GAS_LIMIT_MINIMUM = Uint(5000)
MINIMUM_DIFFICULTY = Uint(131072)
INITIAL_BASE_FEE = Uint(1000000000)
MAX_OMMER_DEPTH = Uint(6)
BOMB_DELAY_BLOCKS = 10700000
EMPTY_OMMER_HASH = keccak256(rlp.encode([]))
Expand All @@ -73,7 +83,7 @@ class BlockChain:
History and current state of the block chain.
"""

blocks: List[Block]
blocks: List[AnyBlock]
state: State
chain_id: U64

Expand Down Expand Up @@ -260,7 +270,7 @@ def calculate_base_fee_per_gas(
return Uint(expected_base_fee_per_gas)


def validate_header(header: Header, parent_header: Header) -> None:
def validate_header(header: AnyHeader, parent_header: AnyHeader) -> None:
"""
Verifies a block header.

Expand All @@ -278,15 +288,25 @@ def validate_header(header: Header, parent_header: Header) -> None:
parent_header :
Parent Header of the header to check for correctness
"""
if not isinstance(header, Header):
assert not isinstance(parent_header, Header)
return previous_fork.validate_header(header, parent_header)

if header.gas_used > header.gas_limit:
raise InvalidBlock

expected_base_fee_per_gas = calculate_base_fee_per_gas(
header.gas_limit,
parent_header.gas_limit,
parent_header.gas_used,
parent_header.base_fee_per_gas,
)
expected_base_fee_per_gas = INITIAL_BASE_FEE
parent_base_fee_per_gas = header_base_fee_per_gas(parent_header)
if parent_base_fee_per_gas is not None:
# For every block except the first, calculate the base fee per gas
# based on the parent block.
expected_base_fee_per_gas = calculate_base_fee_per_gas(
header.gas_limit,
parent_header.gas_limit,
parent_header.gas_used,
parent_base_fee_per_gas,
)

if expected_base_fee_per_gas != header.base_fee_per_gas:
raise InvalidBlock

Expand Down Expand Up @@ -521,7 +541,7 @@ def apply_body(
block_time: U256,
block_difficulty: Uint,
transactions: Tuple[Union[LegacyTransaction, Bytes], ...],
ommers: Tuple[Header, ...],
ommers: Tuple[AnyHeader, ...],
chain_id: U64,
) -> ApplyBodyOutput:
"""
Expand Down Expand Up @@ -631,7 +651,7 @@ def apply_body(


def validate_ommers(
ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain
ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain
) -> None:
"""
Validates the ommers mentioned in the block.
Expand Down Expand Up @@ -712,7 +732,7 @@ def pay_rewards(
state: State,
block_number: Uint,
coinbase: Address,
ommers: Tuple[Header, ...],
ommers: Tuple[AnyHeader, ...],
) -> None:
"""
Pay rewards to the block miner as well as the ommers miners.
Expand Down
59 changes: 57 additions & 2 deletions src/ethereum/berlin/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
chain.
"""
from dataclasses import dataclass
from typing import Tuple, Union
from typing import Annotated, Tuple, Union

from ethereum_rlp import rlp
from ethereum_types.bytes import Bytes, Bytes8, Bytes32
from ethereum_types.frozen import slotted_freezable
from ethereum_types.numeric import U256, Uint
from typing_extensions import TypeAlias

from ethereum.exceptions import InvalidBlock
from ethereum.muir_glacier import blocks as previous_blocks

from ..crypto.hash import Hash32
from .fork_types import Address, Bloom, Root
Expand Down Expand Up @@ -44,6 +49,49 @@ class Header:
nonce: Bytes8


AnyHeader: TypeAlias = Union[previous_blocks.AnyHeader, Header]
"""
Represents all headers that may have appeared in the blockchain before or in
the current fork.
"""


def decode_header(raw_header: rlp.Simple) -> AnyHeader:
"""
Convert `raw_header` from raw sequences and bytes to a structured block
header.

Checks `raw_header` against this fork's `FORK_CRITERIA`, and if it belongs
to this fork, decodes it accordingly. If not, this function forwards to the
preceding fork where the process is repeated.
"""
from . import FORK_CRITERIA

# First, ensure that `raw_header` is not `bytes` (and is therefore a
# sequence.)
if isinstance(raw_header, bytes):
raise InvalidBlock("header is bytes, expected sequence")

# Next, extract the block number and timestamp (which are always at index 8
# and 11 respectively.)
raw_number = raw_header[8]
if not isinstance(raw_number, bytes):
raise InvalidBlock("header number is sequence, expected bytes")
number = Uint.from_be_bytes(raw_number)

raw_timestamp = raw_header[11]
if not isinstance(raw_timestamp, bytes):
raise InvalidBlock("header timestamp is sequence, expected bytes")
timestamp = U256.from_be_bytes(raw_timestamp)

# Finally, check if this header belongs to this fork.
if FORK_CRITERIA.check(number, timestamp):
return rlp.deserialize_to(Header, raw_header)

# If it doesn't, forward to the preceding fork.
return previous_blocks.decode_header(raw_header)


@slotted_freezable
@dataclass
class Block:
Expand All @@ -53,7 +101,14 @@ class Block:

header: Header
transactions: Tuple[Union[Bytes, LegacyTransaction], ...]
ommers: Tuple[Header, ...]
ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...]


AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block]
"""
Represents all blocks that may have appeared in the blockchain before or in the
current fork.
"""


@slotted_freezable
Expand Down
17 changes: 11 additions & 6 deletions src/ethereum/berlin/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
InvalidBlock,
InvalidSenderError,
)
from ethereum.muir_glacier import fork as previous_fork

from . import vm
from .blocks import Block, Header, Log, Receipt
from .blocks import AnyBlock, AnyHeader, Block, Header, Log, Receipt
from .bloom import logs_bloom
from .fork_types import Address, Bloom, Root
from .state import (
Expand Down Expand Up @@ -70,7 +71,7 @@ class BlockChain:
History and current state of the block chain.
"""

blocks: List[Block]
blocks: List[AnyBlock]
state: State
chain_id: U64

Expand Down Expand Up @@ -194,7 +195,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
chain.blocks = chain.blocks[-255:]


def validate_header(header: Header, parent_header: Header) -> None:
def validate_header(header: AnyHeader, parent_header: AnyHeader) -> None:
"""
Verifies a block header.

Expand All @@ -212,6 +213,10 @@ def validate_header(header: Header, parent_header: Header) -> None:
parent_header :
Parent Header of the header to check for correctness
"""
if not isinstance(header, Header):
assert not isinstance(parent_header, Header)
return previous_fork.validate_header(header, parent_header)

parent_has_ommers = parent_header.ommers_hash != EMPTY_OMMER_HASH
if header.timestamp <= parent_header.timestamp:
raise InvalidBlock
Expand Down Expand Up @@ -420,7 +425,7 @@ def apply_body(
block_time: U256,
block_difficulty: Uint,
transactions: Tuple[Union[LegacyTransaction, Bytes], ...],
ommers: Tuple[Header, ...],
ommers: Tuple[AnyHeader, ...],
chain_id: U64,
) -> ApplyBodyOutput:
"""
Expand Down Expand Up @@ -525,7 +530,7 @@ def apply_body(


def validate_ommers(
ommers: Tuple[Header, ...], block_header: Header, chain: BlockChain
ommers: Tuple[AnyHeader, ...], block_header: Header, chain: BlockChain
) -> None:
"""
Validates the ommers mentioned in the block.
Expand Down Expand Up @@ -606,7 +611,7 @@ def pay_rewards(
state: State,
block_number: Uint,
coinbase: Address,
ommers: Tuple[Header, ...],
ommers: Tuple[AnyHeader, ...],
) -> None:
"""
Pay rewards to the block miner as well as the ommers miners.
Expand Down
Loading
Loading