From 149dd1c6bacec761568d5c0efb175ad7b1edce7c Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 19 Nov 2024 19:31:36 -0500 Subject: [PATCH] More strict bounds on ommer types --- src/ethereum/arrow_glacier/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/berlin/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/byzantium/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/cancun/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/constantinople/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/dao_fork/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/gray_glacier/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/homestead/blocks.py | 40 ++++++++++++++++++++-- src/ethereum/istanbul/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/london/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/muir_glacier/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/paris/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/shanghai/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/spurious_dragon/blocks.py | 42 ++++++++++++++++++++++-- src/ethereum/tangerine_whistle/blocks.py | 42 ++++++++++++++++++++++-- tests/berlin/test_rlp.py | 2 +- tests/byzantium/test_rlp.py | 2 +- tests/cancun/test_rlp.py | 2 +- tests/constantinople/test_rlp.py | 2 +- tests/istanbul/test_rlp.py | 2 +- tests/london/test_rlp.py | 2 +- tests/paris/test_rlp.py | 2 +- tests/shanghai/test_rlp.py | 2 +- tests/spurious_dragon/test_rlp.py | 2 +- tests/tangerine_whistle/test_rlp.py | 2 +- 25 files changed, 608 insertions(+), 40 deletions(-) diff --git a/src/ethereum/arrow_glacier/blocks.py b/src/ethereum/arrow_glacier/blocks.py index fa3450dc9b..2027365186 100644 --- a/src/ethereum/arrow_glacier/blocks.py +++ b/src/ethereum/arrow_glacier/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, 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 @@ -55,6 +57,42 @@ class Header: """ +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: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/berlin/blocks.py b/src/ethereum/berlin/blocks.py index 41ab460c9b..836b0b7948 100644 --- a/src/ethereum/berlin/blocks.py +++ b/src/ethereum/berlin/blocks.py @@ -9,13 +9,15 @@ 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 @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/byzantium/blocks.py b/src/ethereum/byzantium/blocks.py index 5e56c27381..76690bd846 100644 --- a/src/ethereum/byzantium/blocks.py +++ b/src/ethereum/byzantium/blocks.py @@ -9,13 +9,15 @@ 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.spurious_dragon import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/cancun/blocks.py b/src/ethereum/cancun/blocks.py index bea3763c6e..7a4bb3ba24 100644 --- a/src/ethereum/cancun/blocks.py +++ b/src/ethereum/cancun/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, 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 U64, U256, Uint from typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.shanghai import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -72,6 +74,42 @@ class Header: """ +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: @@ -81,7 +119,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] withdrawals: Tuple[Withdrawal, ...] diff --git a/src/ethereum/constantinople/blocks.py b/src/ethereum/constantinople/blocks.py index f4ba5b20d7..8d0d38762b 100644 --- a/src/ethereum/constantinople/blocks.py +++ b/src/ethereum/constantinople/blocks.py @@ -9,14 +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.byzantium import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/dao_fork/blocks.py b/src/ethereum/dao_fork/blocks.py index 13999d68f3..91cd4abb79 100644 --- a/src/ethereum/dao_fork/blocks.py +++ b/src/ethereum/dao_fork/blocks.py @@ -9,13 +9,15 @@ 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.homestead import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/gray_glacier/blocks.py b/src/ethereum/gray_glacier/blocks.py index a8a0825a4f..120b3e1a20 100644 --- a/src/ethereum/gray_glacier/blocks.py +++ b/src/ethereum/gray_glacier/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Optional, 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.arrow_glacier import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -55,6 +57,42 @@ class Header: """ +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: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/homestead/blocks.py b/src/ethereum/homestead/blocks.py index 1207b14641..327207bb07 100644 --- a/src/ethereum/homestead/blocks.py +++ b/src/ethereum/homestead/blocks.py @@ -9,7 +9,7 @@ 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 @@ -56,6 +56,42 @@ class Header: """ +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: @@ -65,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[Header, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/istanbul/blocks.py b/src/ethereum/istanbul/blocks.py index 5240ce6b7f..046c8b4162 100644 --- a/src/ethereum/istanbul/blocks.py +++ b/src/ethereum/istanbul/blocks.py @@ -9,14 +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.constantinople import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/london/blocks.py b/src/ethereum/london/blocks.py index 73f3223d6c..2c82dc906b 100644 --- a/src/ethereum/london/blocks.py +++ b/src/ethereum/london/blocks.py @@ -9,14 +9,16 @@ chain. """ from dataclasses import dataclass -from typing import Optional, 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.berlin import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -55,6 +57,42 @@ class Header: """ +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: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/muir_glacier/blocks.py b/src/ethereum/muir_glacier/blocks.py index c056dbd4c8..55669f5745 100644 --- a/src/ethereum/muir_glacier/blocks.py +++ b/src/ethereum/muir_glacier/blocks.py @@ -9,13 +9,15 @@ 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.istanbul import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/paris/blocks.py b/src/ethereum/paris/blocks.py index d6c308f53c..503bb1b689 100644 --- a/src/ethereum/paris/blocks.py +++ b/src/ethereum/paris/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, 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.gray_glacier import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -55,6 +57,42 @@ class Header: """ +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: @@ -64,7 +102,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/shanghai/blocks.py b/src/ethereum/shanghai/blocks.py index cc8db81330..dfb9a24bfb 100644 --- a/src/ethereum/shanghai/blocks.py +++ b/src/ethereum/shanghai/blocks.py @@ -9,13 +9,15 @@ chain. """ from dataclasses import dataclass -from typing import Optional, 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 U64, U256, Uint from typing_extensions import TypeAlias +from ethereum.exceptions import InvalidBlock from ethereum.paris import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -69,6 +71,42 @@ class Header: """ +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: @@ -78,7 +116,7 @@ class Block: header: Header transactions: Tuple[Union[Bytes, LegacyTransaction], ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] withdrawals: Tuple[Withdrawal, ...] diff --git a/src/ethereum/spurious_dragon/blocks.py b/src/ethereum/spurious_dragon/blocks.py index baf42e6d13..a1a59b3dc4 100644 --- a/src/ethereum/spurious_dragon/blocks.py +++ b/src/ethereum/spurious_dragon/blocks.py @@ -9,13 +9,15 @@ 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.tangerine_whistle import blocks as previous_blocks from ..crypto.hash import Hash32 @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/src/ethereum/tangerine_whistle/blocks.py b/src/ethereum/tangerine_whistle/blocks.py index e703ea2687..943de99e63 100644 --- a/src/ethereum/tangerine_whistle/blocks.py +++ b/src/ethereum/tangerine_whistle/blocks.py @@ -9,14 +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.dao_fork import blocks as previous_blocks +from ethereum.exceptions import InvalidBlock from ..crypto.hash import Hash32 from .fork_types import Address, Bloom, Root @@ -54,6 +56,42 @@ class Header: """ +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: @@ -63,7 +101,7 @@ class Block: header: Header transactions: Tuple[Transaction, ...] - ommers: Tuple[AnyHeader, ...] + ommers: Tuple[Annotated[AnyHeader, rlp.With(decode_header)], ...] AnyBlock: TypeAlias = Union[previous_blocks.AnyBlock, Block] diff --git a/tests/berlin/test_rlp.py b/tests/berlin/test_rlp.py index e853f4e577..0ca8842432 100644 --- a/tests/berlin/test_rlp.py +++ b/tests/berlin/test_rlp.py @@ -74,7 +74,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(12244000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/byzantium/test_rlp.py b/tests/byzantium/test_rlp.py index fa0985ac9c..ef9945f3e5 100644 --- a/tests/byzantium/test_rlp.py +++ b/tests/byzantium/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(4370000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/cancun/test_rlp.py b/tests/cancun/test_rlp.py index 6478fe25d9..80ba30408e 100644 --- a/tests/cancun/test_rlp.py +++ b/tests/cancun/test_rlp.py @@ -93,7 +93,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(19426587), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/constantinople/test_rlp.py b/tests/constantinople/test_rlp.py index bdb13cc502..f1b942417a 100644 --- a/tests/constantinople/test_rlp.py +++ b/tests/constantinople/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(7280000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/istanbul/test_rlp.py b/tests/istanbul/test_rlp.py index 3eb39041ac..50df6027f9 100644 --- a/tests/istanbul/test_rlp.py +++ b/tests/istanbul/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(9069000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/london/test_rlp.py b/tests/london/test_rlp.py index a18856e6e7..25fd7cc6ed 100644 --- a/tests/london/test_rlp.py +++ b/tests/london/test_rlp.py @@ -91,7 +91,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(12965000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/paris/test_rlp.py b/tests/paris/test_rlp.py index 33497fde81..6de37432a1 100644 --- a/tests/paris/test_rlp.py +++ b/tests/paris/test_rlp.py @@ -91,7 +91,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(15537394), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/shanghai/test_rlp.py b/tests/shanghai/test_rlp.py index 7a7d689ef6..25e00b4e2e 100644 --- a/tests/shanghai/test_rlp.py +++ b/tests/shanghai/test_rlp.py @@ -93,7 +93,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(17034870), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/spurious_dragon/test_rlp.py b/tests/spurious_dragon/test_rlp.py index 05506ef6f1..87f00fc9a2 100644 --- a/tests/spurious_dragon/test_rlp.py +++ b/tests/spurious_dragon/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(2675000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5), diff --git a/tests/tangerine_whistle/test_rlp.py b/tests/tangerine_whistle/test_rlp.py index 3a4a30074a..368dac3127 100644 --- a/tests/tangerine_whistle/test_rlp.py +++ b/tests/tangerine_whistle/test_rlp.py @@ -66,7 +66,7 @@ receipt_root=hash5, bloom=bloom, difficulty=Uint(1), - number=Uint(2), + number=Uint(2463000), gas_limit=Uint(3), gas_used=Uint(4), timestamp=U256(5),