-
Notifications
You must be signed in to change notification settings - Fork 659
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
Refactor Clique Consensus to handle initialization internally #1874
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ | |
JournalDBCheckpoint, | ||
AccountState, | ||
HeaderParams, | ||
VMConfiguration, | ||
) | ||
|
||
|
||
|
@@ -1638,9 +1639,8 @@ def validate_receipt(self, receipt: ReceiptAPI) -> None: | |
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 | ||
|
@@ -1661,9 +1661,8 @@ def validate_transaction_against_header(self, | |
""" | ||
... | ||
|
||
@classmethod | ||
@abstractmethod | ||
def validate_seal(cls, header: BlockHeaderAPI) -> None: | ||
def validate_seal(self, header: BlockHeaderAPI) -> None: | ||
... | ||
|
||
@classmethod | ||
|
@@ -1688,6 +1687,34 @@ def state_in_temp_block(self) -> ContextManager[StateAPI]: | |
... | ||
|
||
|
||
class VirtualMachineModifierAPI(ABC): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been wondering if this should be named something like |
||
""" | ||
Amend a set of VMs for a chain. This allows modifying a chain for different consensus schemes. | ||
""" | ||
|
||
@abstractmethod | ||
def __init__(self, base_db: AtomicDatabaseAPI) -> None: | ||
... | ||
|
||
@classmethod | ||
@abstractmethod | ||
def amend_vm_configuration_for_chain_class(cls, vm_config: VMConfiguration) -> None: | ||
""" | ||
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. | ||
""" | ||
... | ||
|
||
@abstractmethod | ||
def amend_vm_for_chain_instance(self, vm: VirtualMachineAPI) -> None: | ||
""" | ||
Make amendments to ``vm`` that are only valid for a specific chain instance. This | ||
includes any modifications that depend on stateful data. | ||
""" | ||
... | ||
|
||
|
||
class HeaderChainAPI(ABC): | ||
header: BlockHeaderAPI | ||
chain_id: int | ||
|
@@ -1748,6 +1775,8 @@ class ChainAPI(ConfigurableAPI): | |
vm_configuration: Tuple[Tuple[BlockNumber, Type[VirtualMachineAPI]], ...] | ||
chain_id: int | ||
chaindb: ChainDatabaseAPI | ||
consensus_engine_class: Type[VirtualMachineModifierAPI] | ||
consensus_engine: VirtualMachineModifierAPI | ||
|
||
# | ||
# Helpers | ||
|
@@ -1923,10 +1952,9 @@ def validate_gaslimit(self, header: BlockHeaderAPI) -> None: | |
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: | ||
|
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
import logging | ||
from typing import Sequence, Iterable | ||
from typing import ( | ||
Iterable, | ||
Sequence, | ||
) | ||
|
||
from eth.abc import ( | ||
AtomicDatabaseAPI, | ||
BlockHeaderAPI, | ||
VirtualMachineAPI, | ||
VirtualMachineModifierAPI, | ||
) | ||
from eth.db.chain import ChainDB | ||
|
||
|
@@ -14,14 +18,12 @@ | |
) | ||
from eth_utils import ( | ||
encode_hex, | ||
to_tuple, | ||
ValidationError, | ||
) | ||
|
||
from eth.typing import ( | ||
HeaderParams, | ||
VMConfiguration, | ||
VMFork, | ||
) | ||
from eth.vm.chain_context import ChainContext | ||
from eth.vm.execution_context import ( | ||
|
@@ -65,7 +67,7 @@ def _construct_turn_error_message(expected_difficulty: int, | |
) | ||
|
||
|
||
class CliqueConsensus: | ||
class CliqueConsensus(VirtualMachineModifierAPI): | ||
""" | ||
This class is the entry point to operate a chain under the rules of Clique consensus which | ||
is defined in EIP-225: https://eips.ethereum.org/EIPS/eip-225 | ||
|
@@ -85,22 +87,23 @@ def __init__(self, base_db: AtomicDatabaseAPI, epoch_length: int = EPOCH_LENGTH) | |
self._epoch_length, | ||
) | ||
|
||
@to_tuple | ||
def amend_vm_configuration(self, config: VMConfiguration) -> Iterable[VMFork]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I liked this old approach of not mutating the original VM class. If this was just to make it easier to write the test, I think we can find another way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree but I don't have a good answer. With the old approach you always end up having different classes then those defined in |
||
@classmethod | ||
def amend_vm_configuration_for_chain_class(cls, config: VMConfiguration) -> None: | ||
""" | ||
Amend the given ``VMConfiguration`` to operate under the rules of Clique consensus. | ||
""" | ||
for pair in config: | ||
block_number, vm = pair | ||
vm_class = vm.configure( | ||
extra_data_max_bytes=65535, | ||
validate_seal=staticmethod(self.validate_seal), | ||
create_execution_context=staticmethod(self.create_execution_context), | ||
configure_header=configure_header, | ||
_assign_block_rewards=lambda _, __: None, | ||
) | ||
|
||
yield block_number, vm_class | ||
setattr(vm, 'extra_data_max_bytes', 65535) | ||
setattr(vm, 'create_execution_context', staticmethod(cls.create_execution_context)) | ||
setattr(vm, 'configure_header', configure_header) | ||
setattr(vm, '_assign_block_rewards', lambda *_: None) | ||
|
||
def amend_vm_for_chain_instance(self, vm: VirtualMachineAPI) -> None: | ||
# `validate_seal` is stateful. We would not want two different instances of GoerliChain | ||
# to operate on the same instance of CliqueConsensus. Therefore, this modification needs | ||
# to be done for a specic chain instance, not chain class. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Getting backed into a corner here, where this seems like the best solution, makes me think we're doing something else wrong at an architectural level. One avenue to explore is the location of the consensus class. I'm thinking this might all get a lot easier if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking the same especially because
But it also becomes really blurry where consensus rules go. I mean, every new VM is a consensus change and that's what VMs are for. Other consensus engines are a weird and barely formalized way of somehow hacking and bending the system (e.g. how |
||
setattr(vm, 'validate_seal', self.validate_seal) | ||
|
||
@staticmethod | ||
def create_execution_context(header: BlockHeaderAPI, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from eth.abc import ( | ||
AtomicDatabaseAPI, | ||
VirtualMachineAPI, | ||
VirtualMachineModifierAPI, | ||
) | ||
from eth.typing import ( | ||
VMConfiguration, | ||
) | ||
|
||
|
||
class NoProofConsensus(VirtualMachineModifierAPI): | ||
""" | ||
Modify a set of VMs to accept blocks without any validation. | ||
""" | ||
|
||
def __init__(self, base_db: AtomicDatabaseAPI) -> None: | ||
pass | ||
|
||
@classmethod | ||
def amend_vm_configuration_for_chain_class(cls, config: VMConfiguration) -> None: | ||
""" | ||
Amend the given ``VMConfiguration`` to operate under the default POW rules. | ||
""" | ||
for pair in config: | ||
block_number, vm = pair | ||
setattr(vm, 'validate_seal', lambda *_: None) | ||
|
||
def amend_vm_for_chain_instance(self, vm: VirtualMachineAPI) -> None: | ||
setattr(vm, 'validate_seal', lambda *_: None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still kind of like the idea of
validate_header
dropping the seal check, so it can stay aclassmethod
. But... I'm okay saving that for a different PR, since it probably causes a decent amount of churn, where places need to remove thecheck_seal
and add avalidate_seal
.