Skip to content

Commit

Permalink
Add Consensus classes for POW and any consensus
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Nov 18, 2019
1 parent b81ce44 commit ea4c981
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 78 deletions.
11 changes: 9 additions & 2 deletions eth/chains/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
SignedTransactionAPI,
UnsignedTransactionAPI,
)
from eth.consensus.pow import (
PowConsensus,
)
from eth.constants import (
EMPTY_UNCLE_HASH,
MAX_UNCLE_DEPTH,
Expand Down Expand Up @@ -115,7 +118,7 @@ class BaseChain(Configurable, ChainAPI):
vm_configuration: Tuple[Tuple[BlockNumber, Type[VirtualMachineAPI]], ...] = None
chain_id: int = None
consensus_engine: VirtualMachineModifierAPI = None
consensus_engine_class: Type[VirtualMachineModifierAPI] = None
consensus_engine_class: Type[VirtualMachineModifierAPI] = PowConsensus

@classmethod
def initialize_consensus_engine(cls, base_db: AtomicDatabaseAPI) -> None:
Expand Down Expand Up @@ -206,7 +209,11 @@ def __init__(self, base_db: AtomicDatabaseAPI) -> None:
else:
validate_vm_configuration(self.vm_configuration)

if self.consensus_engine_class is not None:
if not self.consensus_engine_class:
raise ValueError(
"The Chain class cannot be instantiated with out a `consensus_engine_class`"
)
else:
self.initialize_consensus_engine(base_db)

self.chaindb = self.get_chaindb_class()(base_db)
Expand Down
39 changes: 39 additions & 0 deletions eth/consensus/noproof.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

from typing import (
Iterable,
)

from eth_utils import (
to_tuple,
)

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


class NoProofConsensus(VirtualMachineModifierAPI):
"""
Modify a set of VMs to accept blocks without any validation.
"""

def __init__(self, base_db: AtomicDatabaseAPI) -> None:
pass

@to_tuple
def amend_vm_configuration(self, config: VMConfiguration) -> Iterable[VMFork]:
"""
Amend the given ``VMConfiguration`` to operate under the default POW rules.
"""
for pair in config:
block_number, vm = pair
vm_class = vm.configure(
validate_seal=lambda _: None
)

yield block_number, vm_class
44 changes: 43 additions & 1 deletion eth/consensus/pow.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from collections import OrderedDict
from typing import (
Iterable,
Tuple
)

from eth_typing import (
Hash32
Hash32,
)

from eth_utils import (
big_endian_to_int,
ValidationError,
encode_hex,
to_tuple,
)

from eth_hash.auto import keccak
Expand All @@ -22,6 +24,15 @@
)


from eth.abc import (
AtomicDatabaseAPI,
BlockHeaderAPI,
VirtualMachineModifierAPI,
)
from eth.typing import (
VMConfiguration,
VMFork,
)
from eth.validation import (
validate_length,
validate_lte,
Expand Down Expand Up @@ -92,3 +103,34 @@ def mine_pow_nonce(block_number: int, mining_hash: Hash32, difficulty: int) -> T
return nonce.to_bytes(8, 'big'), mining_output[b'mix digest']

raise Exception("Too many attempts at POW mining, giving up")


class PowConsensus(VirtualMachineModifierAPI):
"""
Modify a set of VMs to validate blocks via Proof of Work (POW)
"""

def __init__(self, base_db: AtomicDatabaseAPI) -> None:
pass

@to_tuple
def amend_vm_configuration(self, config: VMConfiguration) -> Iterable[VMFork]:
"""
Amend the given ``VMConfiguration`` to operate under the default POW rules.
"""
for pair in config:
block_number, vm = pair
vm_class = vm.configure(
validate_seal=staticmethod(self.validate_seal),
)

yield block_number, vm_class

@classmethod
def validate_seal(cls, header: BlockHeaderAPI) -> None:
"""
Validate the seal on the given header.
"""
check_pow(
header.block_number, header.mining_hash,
header.mix_hash, header.nonce, header.difficulty)
47 changes: 3 additions & 44 deletions eth/tools/builder/chain/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
from eth.abc import (
AtomicDatabaseAPI,
BlockAPI,
BlockHeaderAPI,
ChainAPI,
MiningChainAPI,
VirtualMachineAPI,
)
from eth.consensus.noproof import NoProofConsensus
from eth.db.atomic import AtomicDB
from eth.db.backends.memory import (
MemoryDB,
Expand Down Expand Up @@ -287,33 +287,6 @@ def enable_pow_mining(chain_class: Type[ChainAPI]) -> Type[ChainAPI]:
return chain_class.configure(vm_configuration=vm_configuration)


class NoChainSealValidationMixin:
@classmethod
def validate_seal(cls, block: BlockAPI) -> None:
pass


class NoVMSealValidationMixin:
@classmethod
def validate_seal(cls, header: BlockHeaderAPI) -> None:
pass


@to_tuple
def _mix_in_disable_seal_validation(vm_configuration: VMConfiguration) -> Iterable[VMFork]:
for fork_block, vm_class in vm_configuration:
if issubclass(vm_class, NoVMSealValidationMixin):
# Seal validation already disabled, hence nothing to change
vm_class_without_seal_validation = vm_class
else:
vm_class_without_seal_validation = type(
vm_class.__name__,
(NoVMSealValidationMixin, vm_class),
{},
)
yield fork_block, vm_class_without_seal_validation


@curry
def disable_pow_check(chain_class: Type[ChainAPI]) -> Type[ChainAPI]:
"""
Expand All @@ -325,22 +298,8 @@ def disable_pow_check(chain_class: Type[ChainAPI]) -> Type[ChainAPI]:
blocks mined this way will not be importable on any chain that does not
have proof of work disabled.
"""
if not chain_class.vm_configuration:
raise ValidationError("Chain class has no vm_configuration")

if issubclass(chain_class, NoChainSealValidationMixin):
# Seal validation already disabled, hence nothing to change
chain_class_without_seal_validation = chain_class
else:
chain_class_without_seal_validation = type(
chain_class.__name__,
(chain_class, NoChainSealValidationMixin),
{},
)
return chain_class_without_seal_validation.configure( # type: ignore
vm_configuration=_mix_in_disable_seal_validation(
chain_class_without_seal_validation.vm_configuration # type: ignore
),
return chain_class.configure(
consensus_engine_class=NoProofConsensus
)


Expand Down
6 changes: 4 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Chain,
MiningChain,
)
from eth.consensus.noproof import NoProofConsensus
from eth.db.atomic import AtomicDB
from eth.rlp.headers import BlockHeader
from eth.vm.forks import (
Expand Down Expand Up @@ -148,6 +149,7 @@ def _chain_with_block_validation(VM, base_db, genesis_state, chain_cls=Chain):
vm_configuration=(
(constants.GENESIS_BLOCK_NUMBER, VM),
),
consensus_engine_class=NoProofConsensus,
chain_id=1337,
)
chain = klass.from_genesis(base_db, genesis_params, genesis_state)
Expand Down Expand Up @@ -207,13 +209,13 @@ def _chain_without_block_validation(request, VM, base_db, genesis_state):
'import_block': import_block_without_validation,
'validate_block': lambda self, block: None,
}
VMForTesting = VM.configure(validate_seal=lambda block: None)
chain_class = request.param
klass = chain_class.configure(
__name__='TestChainWithoutBlockValidation',
vm_configuration=(
(constants.GENESIS_BLOCK_NUMBER, VMForTesting),
(constants.GENESIS_BLOCK_NUMBER, VM),
),
consensus_engine_class=NoProofConsensus,
chain_id=1337,
**overrides,
)
Expand Down
23 changes: 0 additions & 23 deletions tests/core/builder-tools/test_chain_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

from eth_utils import ValidationError

from eth.chains import (
MainnetChain,
MainnetTesterChain,
RopstenChain,
)
from eth.chains.base import (
Chain,
MiningChain,
Expand All @@ -24,9 +19,6 @@
mine_block,
mine_blocks,
)
from eth.tools.builder.chain.builders import (
NoChainSealValidationMixin,
)


MINING_CHAIN_PARAMS = (
Expand Down Expand Up @@ -237,18 +229,3 @@ def test_chain_builder_chain_split(mining_chain):

head_b = chain_b.get_canonical_head()
assert head_b.block_number == 3


@pytest.mark.parametrize(
"chain",
(
MainnetChain,
MainnetTesterChain,
RopstenChain,
)
)
def test_disabling_pow_for_already_pow_disabled_chain(chain):
pow_disabled_chain = disable_pow_check(chain)
assert issubclass(pow_disabled_chain, NoChainSealValidationMixin)
again_pow_disabled_chain = disable_pow_check(pow_disabled_chain)
assert issubclass(again_pow_disabled_chain, NoChainSealValidationMixin)
12 changes: 6 additions & 6 deletions tests/core/chain-object/test_chain_retrieval_of_vm_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,24 @@ def chaindb(base_db):
def test_header_chain_get_vm_class_for_block_number(base_db, genesis_header):
chain = ChainForTesting.from_genesis_header(base_db, genesis_header)

assert chain.get_vm_class_for_block_number(0) is VM_A
assert type(chain.get_vm_class_for_block_number(0)) is type(VM_A)

for num in range(1, 10):
assert chain.get_vm_class_for_block_number(num) is VM_A
assert type(chain.get_vm_class_for_block_number(num)) is type(VM_A)

assert chain.get_vm_class_for_block_number(10) is VM_B
assert type(chain.get_vm_class_for_block_number(10)) is type(VM_B)

for num in range(11, 100, 5):
assert chain.get_vm_class_for_block_number(num) is VM_B
assert type(chain.get_vm_class_for_block_number(num)) is type(VM_B)


def test_header_chain_get_vm_class_using_block_header(base_db, genesis_header):
chain = ChainForTesting.from_genesis_header(base_db, genesis_header)
assert chain.get_vm_class(genesis_header) is VM_A
assert type(chain.get_vm_class(genesis_header)) is type(VM_A)

header_at_height_10 = genesis_header.copy(block_number=10)

assert chain.get_vm_class(header_at_height_10) is VM_B
assert type(chain.get_vm_class(header_at_height_10)) is type(VM_B)


def test_header_chain_invalid_if_no_vm_configuration(base_db, genesis_header):
Expand Down

0 comments on commit ea4c981

Please sign in to comment.