Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
Closed
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
60 changes: 53 additions & 7 deletions eth/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
JournalDBCheckpoint,
AccountState,
HeaderParams,
VMConfiguration,
)


Expand Down Expand Up @@ -1756,6 +1757,31 @@ def get_transaction_context(cls,
...


class ConsensusAPI(ABC):
@abstractmethod
def __init__(self, db: AtomicDatabaseAPI) -> None:
...

@abstractmethod
def validate_seal(self, header: BlockHeaderAPI) -> None:
"""
Validate the seal on the given header, even if its parent is missing.
"""
...

@abstractmethod
def validate_extension(self, header: BlockHeaderAPI) -> None:
"""
Validate the seal on the given header, after its parent is imported.
"""
...

@classmethod
@abstractmethod
def get_fee_recipient(cls, header: BlockHeaderAPI) -> Address:
...


class VirtualMachineAPI(ConfigurableAPI):
"""
The :class:`~eth.abc.VirtualMachineAPI` class represents the Chain rules for a
Expand All @@ -1772,6 +1798,7 @@ class VirtualMachineAPI(ConfigurableAPI):
fork: str # noqa: E701 # flake8 bug that's fixed in 3.6.0+
chaindb: ChainDatabaseAPI
extra_data_max_bytes: ClassVar[int]
consensus_class: Type[ConsensusAPI]

@abstractmethod
def __init__(self, header: BlockHeaderAPI, chaindb: ChainDatabaseAPI) -> None:
Expand Down Expand Up @@ -2098,9 +2125,8 @@ def validate_block(self, block: BlockAPI) -> None:
"""
...

@classmethod
@abstractmethod
def validate_header(cls,
def validate_header(self,
header: BlockHeaderAPI,
parent_header: BlockHeaderAPI,
check_seal: bool = True
Expand All @@ -2124,11 +2150,17 @@ def validate_transaction_against_header(self,
"""
...

@classmethod
@abstractmethod
def validate_seal(cls, header: BlockHeaderAPI) -> None:
def validate_seal(self, header: BlockHeaderAPI) -> None:
"""
Validate the seal on the given header, even if its parent is missing.
"""
...

@abstractmethod
def validate_extension_seal(self, header: BlockHeaderAPI) -> None:
"""
Validate the seal on the given header.
Validate the seal on the given header, after its parent is imported.
"""
...

Expand Down Expand Up @@ -2160,6 +2192,21 @@ def state_in_temp_block(self) -> ContextManager[StateAPI]:
...


class VirtualMachineModifierAPI(ABC):
"""
Amend a set of VMs for a chain. This allows modifying a chain for different consensus schemes.
"""

@abstractmethod
def amend_vm_configuration(self, vm_config: VMConfiguration) -> VMConfiguration:
"""
Make amendments to the ``vm_config`` that are independent of any instance state. These
changes are applied across all instances of the chain where this
``VirtualMachineModifierAPI`` is applied on.
"""
...


class HeaderChainAPI(ABC):
header: BlockHeaderAPI
chain_id: int
Expand Down Expand Up @@ -2558,10 +2605,9 @@ def validate_uncles(self, block: BlockAPI) -> None:
"""
...

@classmethod
@abstractmethod
def validate_chain(
cls,
self,
root: BlockHeaderAPI,
descendants: Tuple[BlockHeaderAPI, ...],
seal_check_random_sample_rate: int = 1) -> None:
Expand Down
32 changes: 24 additions & 8 deletions eth/chains/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,8 @@ def get_vm_class(cls, header: BlockHeaderAPI) -> Type[VirtualMachineAPI]:
#
# Validation API
#
@classmethod
def validate_chain(
cls,
self,
root: BlockHeaderAPI,
descendants: Tuple[BlockHeaderAPI, ...],
seal_check_random_sample_rate: int = 1) -> None:
Expand All @@ -158,14 +157,30 @@ def validate_chain(
f" but expected {encode_hex(parent.hash)}"
)
should_check_seal = index in indices_to_check_seal
vm_class = cls.get_vm_class_for_block_number(child.block_number)
vm = self.get_vm(child)
try:
vm_class.validate_header(child, parent, check_seal=should_check_seal)
vm.validate_header(child, parent, check_seal=should_check_seal)
except ValidationError as exc:
raise ValidationError(
f"{child} is not a valid child of {parent}: {exc}"
) from exc

def validate_extension(
self,
new_header: BlockHeaderAPI,
check_seal: bool = True) -> None:
"""
Run any validations that cannot be executed until the parent header is persisted
to the database.

This does *not* guarantee to re-run any checks found in :meth:`validate_chain`.
"""
if check_seal:
vm = self.get_vm(new_header)
vm.validate_extension_seal(new_header)

# non-seal checks can be added here


class Chain(BaseChain):
logger = logging.getLogger("eth.chain.chain.Chain")
Expand Down Expand Up @@ -465,15 +480,16 @@ def validate_receipt(self, receipt: ReceiptAPI, at_header: BlockHeaderAPI) -> No
def validate_block(self, block: BlockAPI) -> None:
if block.is_genesis:
raise ValidationError("Cannot validate genesis block this way")
VM_class = self.get_vm_class_for_block_number(BlockNumber(block.number))
vm = self.get_vm(block.header)
parent_header = self.get_block_header_by_hash(block.header.parent_hash)
VM_class.validate_header(block.header, parent_header, check_seal=True)
vm.validate_header(block.header, parent_header, check_seal=True)
vm.validate_extension_seal(block.header)
self.validate_uncles(block)
self.validate_gaslimit(block.header)

def validate_seal(self, header: BlockHeaderAPI) -> None:
VM_class = self.get_vm_class_for_block_number(BlockNumber(header.block_number))
VM_class.validate_seal(header)
vm = self.get_vm(header)
vm.validate_seal(header)

def validate_gaslimit(self, header: BlockHeaderAPI) -> None:
parent_header = self.get_block_header_by_hash(header.parent_hash)
Expand Down
9 changes: 4 additions & 5 deletions eth/chains/mainnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,25 @@
class MainnetDAOValidatorVM(HomesteadVM):
"""Only on mainnet, TheDAO fork is accompanied by special extra data. Validate those headers"""

@classmethod
def validate_header(cls,
def validate_header(self,
header: BlockHeaderAPI,
previous_header: BlockHeaderAPI,
check_seal: bool=True) -> None:

super().validate_header(header, previous_header, check_seal)

# The special extra_data is set on the ten headers starting at the fork
dao_fork_at = cls.get_dao_fork_block_number()
dao_fork_at = self.get_dao_fork_block_number()
extra_data_block_nums = range(dao_fork_at, dao_fork_at + 10)

if header.block_number in extra_data_block_nums:
if cls.support_dao_fork and header.extra_data != DAO_FORK_MAINNET_EXTRA_DATA:
if self.support_dao_fork and header.extra_data != DAO_FORK_MAINNET_EXTRA_DATA:
raise ValidationError(
f"Block {header!r} must have extra data "
f"{encode_hex(DAO_FORK_MAINNET_EXTRA_DATA)} not "
f"{encode_hex(header.extra_data)} when supporting DAO fork"
)
elif not cls.support_dao_fork and header.extra_data == DAO_FORK_MAINNET_EXTRA_DATA:
elif not self.support_dao_fork and header.extra_data == DAO_FORK_MAINNET_EXTRA_DATA:
raise ValidationError(
f"Block {header!r} must not have extra data "
f"{encode_hex(DAO_FORK_MAINNET_EXTRA_DATA)} when declining the DAO fork"
Expand Down
4 changes: 2 additions & 2 deletions eth/chains/tester/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ class MainnetTesterChain(BaseMainnetTesterChain):
It exposes one additional API `configure_forks` to allow for in-flight
configuration of fork rules.
"""
@classmethod
def validate_seal(cls, block: BlockAPI) -> None:

def validate_seal(self, block: BlockAPI) -> None:
"""
We don't validate the proof of work seal on the tester chain.
"""
Expand Down
3 changes: 3 additions & 0 deletions eth/consensus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .clique.clique import CliqueConsensus # noqa: F401
from .noproof import NoProofConsensus # noqa: F401
from .pow import PowConsensus # noqa: F401
37 changes: 37 additions & 0 deletions eth/consensus/applier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import (
Iterable,
Type,
)

from eth_utils import (
to_tuple,
)

from eth.abc import (
ConsensusAPI,
VirtualMachineModifierAPI,
VMConfiguration,
)
from eth.typing import (
VMFork,
)


class ConsensusApplier(VirtualMachineModifierAPI):
"""
This class is used to apply simple types of consensus engines to a series of virtual machines.

Note that this *must not* be used for Clique, which has its own modifier
"""

def __init__(self, consensus_class: Type[ConsensusAPI]) -> None:
self._consensus_class = consensus_class

@to_tuple
def amend_vm_configuration(self, config: VMConfiguration) -> Iterable[VMFork]:
"""
Amend the given ``VMConfiguration`` to operate under the rules of the pre-defined consensus
"""
for pair in config:
block_number, vm = pair
yield block_number, vm.configure(consensus_class=self._consensus_class)
1 change: 1 addition & 0 deletions eth/consensus/clique/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .clique import ( # noqa: F401
CliqueApplier,
CliqueConsensus,
)
from .constants import ( # noqa: F401
Expand Down
Loading