diff --git a/eth2/beacon/chains/base.py b/eth2/beacon/chains/base.py index c480adc976..db179f5ae8 100644 --- a/eth2/beacon/chains/base.py +++ b/eth2/beacon/chains/base.py @@ -40,6 +40,9 @@ BlockClassError, StateMachineNotFound, ) +from eth2.beacon.fork_choice import ( + ForkChoiceScoring, +) from eth2.beacon.types.attestations import ( Attestation, ) @@ -226,18 +229,21 @@ def from_genesis(cls, chaindb = cls.get_chaindb_class()(db=base_db, genesis_config=genesis_config) chaindb.persist_state(genesis_state) - return cls._from_genesis_block(base_db, genesis_block, genesis_config) + state_machine = sm_class(chaindb, genesis_block, genesis_state) + fork_choice_scoring = state_machine.get_fork_choice_scoring() + return cls._from_genesis_block(base_db, genesis_block, fork_choice_scoring, genesis_config) @classmethod def _from_genesis_block(cls, base_db: BaseAtomicDB, genesis_block: BaseBeaconBlock, + fork_choice_scoring: ForkChoiceScoring, genesis_config: Eth2GenesisConfig) -> 'BaseBeaconChain': """ Initialize the ``BeaconChain`` from the genesis block. """ chaindb = cls.get_chaindb_class()(db=base_db, genesis_config=genesis_config) - chaindb.persist_block(genesis_block, genesis_block.__class__) + chaindb.persist_block(genesis_block, genesis_block.__class__, fork_choice_scoring) return cls(base_db, genesis_config) # @@ -408,17 +414,15 @@ def import_block( # TODO: Now it just persists all state. Should design how to clean up the old state. self.chaindb.persist_state(state) - self.chaindb.persist_block_without_scoring(imported_block, imported_block.__class__) - fork_choice_scoring = state_machine.get_fork_choice_scoring() - score = fork_choice_scoring(imported_block) - - self.chaindb.set_score(imported_block, score) - ( new_canonical_blocks, old_canonical_blocks, - ) = self.chaindb.update_canonical_head_if_needed(imported_block, imported_block.__class__) + ) = self.chaindb.persist_block( + imported_block, + imported_block.__class__, + fork_choice_scoring, + ) self.logger.debug( 'IMPORTED_BLOCK: slot %s | signed root %s', diff --git a/eth2/beacon/db/chain.py b/eth2/beacon/db/chain.py index a449353f2d..45d766d5cc 100644 --- a/eth2/beacon/db/chain.py +++ b/eth2/beacon/db/chain.py @@ -39,6 +39,9 @@ from eth.validation import ( validate_word, ) +from eth2.beacon.fork_choice import ( + ForkChoiceScoring, +) from eth2.beacon.helpers import ( slot_to_epoch, ) @@ -59,6 +62,7 @@ AttestationRootNotFound, FinalizedHeadNotFound, JustifiedHeadNotFound, + MissingForkChoiceScorings, ) from eth2.beacon.db.schema import SchemaV1 @@ -88,7 +92,8 @@ def __init__(self, db: BaseAtomicDB, genesis_config: Eth2GenesisConfig) -> None: def persist_block( self, block: BaseBeaconBlock, - block_class: Type[BaseBeaconBlock] + block_class: Type[BaseBeaconBlock], + fork_choice_scoring: ForkChoiceScoring, ) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]: pass @@ -145,7 +150,8 @@ def block_exists(self, block_root: Hash32) -> bool: def persist_block_chain( self, blocks: Iterable[BaseBeaconBlock], - block_class: Type[BaseBeaconBlock] + block_class: Type[BaseBeaconBlock], + fork_choice_scoring: Iterable[ForkChoiceScoring], ) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]: pass @@ -213,7 +219,8 @@ def _get_highest_justified_epoch(self, db: BaseDB) -> Epoch: def persist_block( self, block: BaseBeaconBlock, - block_class: Type[BaseBeaconBlock] + block_class: Type[BaseBeaconBlock], + fork_choice_scoring: ForkChoiceScoring, ) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]: """ Persist the given block. @@ -222,20 +229,23 @@ def persist_block( if block.is_genesis: self._handle_exceptional_justification_and_finality(db, block) - return self._persist_block(db, block, block_class) + return self._persist_block(db, block, block_class, fork_choice_scoring) @classmethod def _persist_block( cls, db: 'BaseDB', block: BaseBeaconBlock, - block_class: Type[BaseBeaconBlock] + block_class: Type[BaseBeaconBlock], + fork_choice_scoring: ForkChoiceScoring, ) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]: block_chain = (block, ) + scorings = (fork_choice_scoring, ) new_canonical_blocks, old_canonical_blocks = cls._persist_block_chain( db, block_chain, block_class, + scorings, ) return new_canonical_blocks, old_canonical_blocks @@ -428,23 +438,22 @@ def _block_exists(db: BaseDB, block_root: Hash32) -> bool: def persist_block_chain( self, blocks: Iterable[BaseBeaconBlock], - block_class: Type[BaseBeaconBlock] + block_class: Type[BaseBeaconBlock], + fork_choice_scorings: Iterable[ForkChoiceScoring], ) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]: """ Return two iterable of blocks, the first containing the new canonical blocks, the second containing the old canonical headers """ with self.db.atomic_batch() as db: - return self._persist_block_chain(db, blocks, block_class) + return self._persist_block_chain(db, blocks, block_class, fork_choice_scorings) @staticmethod def _set_block_score_to_db( db: BaseDB, - block: BaseBeaconBlock + block: BaseBeaconBlock, + score: int, ) -> int: - # TODO: It's a stub before we implement fork choice rule - score = block.slot - db.set( SchemaV1.make_block_root_to_score_lookup_key(block.signing_root), ssz.encode(score, sedes=ssz.sedes.uint64), @@ -462,12 +471,15 @@ def _persist_block_chain( cls, db: BaseDB, blocks: Iterable[BaseBeaconBlock], - block_class: Type[BaseBeaconBlock] + block_class: Type[BaseBeaconBlock], + fork_choice_scorings: Iterable[ForkChoiceScoring], ) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]: blocks_iterator = iter(blocks) + scorings_iterator = iter(fork_choice_scorings) try: first_block = first(blocks_iterator) + first_scoring = first(scorings_iterator) except StopIteration: return tuple(), tuple() @@ -488,7 +500,7 @@ def _persist_block_chain( ) ) - score = first_block.slot + score = first_scoring(first_block) curr_block_head = first_block db.set( @@ -496,7 +508,7 @@ def _persist_block_chain( ssz.encode(curr_block_head), ) cls._add_block_root_to_slot_lookup(db, curr_block_head) - cls._set_block_score_to_db(db, curr_block_head) + cls._set_block_score_to_db(db, curr_block_head, score) cls._add_attestations_root_to_block_lookup(db, curr_block_head) orig_blocks_seq = concat([(first_block,), blocks_iterator]) @@ -517,9 +529,17 @@ def _persist_block_chain( ssz.encode(curr_block_head), ) cls._add_block_root_to_slot_lookup(db, curr_block_head) - score = cls._set_block_score_to_db(db, curr_block_head) cls._add_attestations_root_to_block_lookup(db, curr_block_head) + # NOTE: len(scorings_iterator) should equal len(blocks_iterator) + try: + next_scoring = next(scorings_iterator) + except StopIteration: + raise MissingForkChoiceScorings + + score = next_scoring(curr_block_head) + cls._set_block_score_to_db(db, curr_block_head, score) + if no_canonical_head: return cls._set_as_canonical_chain_head(db, curr_block_head.signing_root, block_class) diff --git a/eth2/beacon/db/exceptions.py b/eth2/beacon/db/exceptions.py index 97404b064b..b36371ca89 100644 --- a/eth2/beacon/db/exceptions.py +++ b/eth2/beacon/db/exceptions.py @@ -24,3 +24,11 @@ class AttestationRootNotFound(BeaconDBException): Exception raised if no attestation root is set in this database. """ pass + + +class MissingForkChoiceScorings(BeaconDBException): + """ + Exception raised if a client tries to score a block without providing + the ability to generate a score via a ``scoring``. + """ + pass diff --git a/tests/eth2/core/beacon/chains/test_beacon_chain.py b/tests/eth2/core/beacon/chains/test_beacon_chain.py index 0a9b9cb98d..2df57f3069 100644 --- a/tests/eth2/core/beacon/chains/test_beacon_chain.py +++ b/tests/eth2/core/beacon/chains/test_beacon_chain.py @@ -42,7 +42,7 @@ def valid_chain(beacon_chain_with_block_validation): (100, 20, 10, 10), ] ) -def test_canonical_chain(valid_chain, genesis_slot): +def test_canonical_chain(valid_chain, genesis_slot, fork_choice_scoring): genesis_block = valid_chain.get_canonical_block_by_slot(genesis_slot) # Our chain fixture is created with only the genesis header, so initially that's the head of @@ -53,7 +53,7 @@ def test_canonical_chain(valid_chain, genesis_slot): slot=genesis_block.slot + 1, previous_block_root=genesis_block.signing_root, ) - valid_chain.chaindb.persist_block(block, block.__class__) + valid_chain.chaindb.persist_block(block, block.__class__, fork_choice_scoring) assert valid_chain.get_canonical_head() == block diff --git a/tests/eth2/core/beacon/conftest.py b/tests/eth2/core/beacon/conftest.py index 1e26b6e6d2..6d409ebf1f 100644 --- a/tests/eth2/core/beacon/conftest.py +++ b/tests/eth2/core/beacon/conftest.py @@ -16,6 +16,9 @@ from eth2.beacon.constants import ( FAR_FUTURE_EPOCH, ) +from eth2.beacon.fork_choice import ( + higher_slot_scoring, +) from eth2.beacon.helpers import ( slot_to_epoch, ) @@ -803,6 +806,11 @@ def fixture_sm_class(config): ) +@pytest.fixture +def fork_choice_scoring(): + return higher_slot_scoring + + @pytest.fixture def genesis_config(config): return Eth2GenesisConfig(config) diff --git a/tests/eth2/core/beacon/db/test_beacon_chaindb.py b/tests/eth2/core/beacon/db/test_beacon_chaindb.py index 5682e99647..d9c4ee5223 100644 --- a/tests/eth2/core/beacon/db/test_beacon_chaindb.py +++ b/tests/eth2/core/beacon/db/test_beacon_chaindb.py @@ -36,9 +36,9 @@ @pytest.fixture -def chaindb_at_genesis(chaindb, genesis_state, genesis_block): +def chaindb_at_genesis(chaindb, genesis_state, genesis_block, fork_choice_scoring): chaindb.persist_state(genesis_state) - chaindb.persist_block(genesis_block, BeaconBlock) + chaindb.persist_block(genesis_block, BeaconBlock, fork_choice_scoring) return chaindb @@ -74,45 +74,45 @@ def maximum_score_value(): return 2**64 - 1 -def test_chaindb_add_block_number_to_root_lookup(chaindb, block): +def test_chaindb_add_block_number_to_root_lookup(chaindb, block, fork_choice_scoring): block_slot_to_root_key = SchemaV1.make_block_slot_to_root_lookup_key(block.slot) assert not chaindb.exists(block_slot_to_root_key) - chaindb.persist_block(block, block.__class__) + chaindb.persist_block(block, block.__class__, fork_choice_scoring) assert chaindb.exists(block_slot_to_root_key) -def test_chaindb_persist_block_and_slot_to_root(chaindb, block): +def test_chaindb_persist_block_and_slot_to_root(chaindb, block, fork_choice_scoring): with pytest.raises(BlockNotFound): chaindb.get_block_by_root(block.signing_root, block.__class__) slot_to_root_key = SchemaV1.make_block_root_to_score_lookup_key(block.signing_root) assert not chaindb.exists(slot_to_root_key) - chaindb.persist_block(block, block.__class__) + chaindb.persist_block(block, block.__class__, fork_choice_scoring) assert chaindb.get_block_by_root(block.signing_root, block.__class__) == block assert chaindb.exists(slot_to_root_key) @given(seed=st.binary(min_size=32, max_size=32)) -def test_chaindb_persist_block_and_unknown_parent(chaindb, block, seed): +def test_chaindb_persist_block_and_unknown_parent(chaindb, block, fork_choice_scoring, seed): n_block = block.copy(previous_block_root=hash_eth2(seed)) with pytest.raises(ParentNotFound): - chaindb.persist_block(n_block, n_block.__class__) + chaindb.persist_block(n_block, n_block.__class__, fork_choice_scoring) -def test_chaindb_persist_block_and_block_to_root(chaindb, block): +def test_chaindb_persist_block_and_block_to_root(chaindb, block, fork_choice_scoring): block_to_root_key = SchemaV1.make_block_root_to_score_lookup_key(block.signing_root) assert not chaindb.exists(block_to_root_key) - chaindb.persist_block(block, block.__class__) + chaindb.persist_block(block, block.__class__, fork_choice_scoring) assert chaindb.exists(block_to_root_key) -def test_chaindb_get_score(chaindb, sample_beacon_block_params): +def test_chaindb_get_score(chaindb, sample_beacon_block_params, fork_choice_scoring): genesis = BeaconBlock(**sample_beacon_block_params).copy( previous_block_root=GENESIS_PARENT_HASH, slot=0, ) - chaindb.persist_block(genesis, genesis.__class__) + chaindb.persist_block(genesis, genesis.__class__, fork_choice_scoring) genesis_score_key = SchemaV1.make_block_root_to_score_lookup_key(genesis.signing_root) genesis_score = ssz.decode(chaindb.db.get(genesis_score_key), sedes=ssz.sedes.uint64) @@ -123,7 +123,7 @@ def test_chaindb_get_score(chaindb, sample_beacon_block_params): previous_block_root=genesis.signing_root, slot=1, ) - chaindb.persist_block(block1, block1.__class__) + chaindb.persist_block(block1, block1.__class__, fork_choice_scoring) block1_score_key = SchemaV1.make_block_root_to_score_lookup_key(block1.signing_root) block1_score = ssz.decode(chaindb.db.get(block1_score_key), sedes=ssz.sedes.uint64) @@ -140,20 +140,20 @@ def test_chaindb_set_score(chaindb, block, maximum_score_value): assert block_score == score -def test_chaindb_get_block_by_root(chaindb, block): - chaindb.persist_block(block, block.__class__) +def test_chaindb_get_block_by_root(chaindb, block, fork_choice_scoring): + chaindb.persist_block(block, block.__class__, fork_choice_scoring) result_block = chaindb.get_block_by_root(block.signing_root, block.__class__) validate_ssz_equal(result_block, block) -def test_chaindb_get_canonical_block_root(chaindb, block): - chaindb.persist_block(block, block.__class__) +def test_chaindb_get_canonical_block_root(chaindb, block, fork_choice_scoring): + chaindb.persist_block(block, block.__class__, fork_choice_scoring) block_root = chaindb.get_canonical_block_root(block.slot) assert block_root == block.signing_root -def test_chaindb_get_genesis_block_root(chaindb, genesis_block): - chaindb.persist_block(genesis_block, genesis_block.__class__) +def test_chaindb_get_genesis_block_root(chaindb, genesis_block, fork_choice_scoring): + chaindb.persist_block(genesis_block, genesis_block.__class__, fork_choice_scoring) block_root = chaindb.get_genesis_block_root() assert block_root == genesis_block.signing_root @@ -176,7 +176,8 @@ def test_chaindb_get_justified_head_at_genesis(chaindb_at_genesis, genesis_block def test_chaindb_get_finalized_head(chaindb_at_genesis, genesis_block, genesis_state, - sample_beacon_block_params): + sample_beacon_block_params, + fork_choice_scoring): chaindb = chaindb_at_genesis block = BeaconBlock(**sample_beacon_block_params).copy( previous_block_root=genesis_block.signing_root, @@ -189,7 +190,7 @@ def test_chaindb_get_finalized_head(chaindb_at_genesis, finalized_root=block.signing_root, ) chaindb.persist_state(state_with_finalized_block) - chaindb.persist_block(block, BeaconBlock) + chaindb.persist_block(block, BeaconBlock, fork_choice_scoring) assert chaindb.get_finalized_head(BeaconBlock).signing_root == block.signing_root assert chaindb.get_justified_head(genesis_block.__class__) == genesis_block @@ -199,6 +200,7 @@ def test_chaindb_get_justified_head(chaindb_at_genesis, genesis_block, genesis_state, sample_beacon_block_params, + fork_choice_scoring, config): chaindb = chaindb_at_genesis block = BeaconBlock(**sample_beacon_block_params).copy( @@ -214,7 +216,7 @@ def test_chaindb_get_justified_head(chaindb_at_genesis, current_justified_epoch=config.GENESIS_EPOCH, ) chaindb.persist_state(state_with_bad_epoch) - chaindb.persist_block(block, BeaconBlock) + chaindb.persist_block(block, BeaconBlock, fork_choice_scoring) assert chaindb.get_finalized_head(genesis_block.__class__) == genesis_block assert chaindb.get_justified_head(genesis_block.__class__) == genesis_block @@ -240,8 +242,8 @@ def test_chaindb_get_justified_head_at_init_time(chaindb): chaindb.get_justified_head(BeaconBlock) -def test_chaindb_get_canonical_head(chaindb, block): - chaindb.persist_block(block, block.__class__) +def test_chaindb_get_canonical_head(chaindb, block, fork_choice_scoring): + chaindb.persist_block(block, block.__class__, fork_choice_scoring) canonical_head_root = chaindb.get_canonical_head_root() assert canonical_head_root == block.signing_root @@ -253,7 +255,7 @@ def test_chaindb_get_canonical_head(chaindb, block): slot=block.slot + 1, previous_block_root=block.signing_root, ) - chaindb.persist_block(block_2, block_2.__class__) + chaindb.persist_block(block_2, block_2.__class__, fork_choice_scoring) result_block = chaindb.get_canonical_head(block.__class__) assert result_block == block_2 @@ -261,13 +263,13 @@ def test_chaindb_get_canonical_head(chaindb, block): slot=block_2.slot + 1, previous_block_root=block_2.signing_root, ) - chaindb.persist_block(block_3, block_3.__class__) + chaindb.persist_block(block_3, block_3.__class__, fork_choice_scoring) result_block = chaindb.get_canonical_head(block.__class__) assert result_block == block_3 -def test_get_slot_by_root(chaindb, block): - chaindb.persist_block(block, block.__class__) +def test_get_slot_by_root(chaindb, block, fork_choice_scoring): + chaindb.persist_block(block, block.__class__, fork_choice_scoring) block_slot = block.slot result_slot = chaindb.get_slot_by_root(block.signing_root) assert result_slot == block_slot diff --git a/tests/eth2/core/beacon/state_machines/test_state_transition.py b/tests/eth2/core/beacon/state_machines/test_state_transition.py index c8d5e488e4..20929ee60b 100644 --- a/tests/eth2/core/beacon/state_machines/test_state_transition.py +++ b/tests/eth2/core/beacon/state_machines/test_state_transition.py @@ -51,8 +51,9 @@ def test_per_slot_transition(chaindb, fixture_sm_class, config, state_slot, + fork_choice_scoring, keymap): - chaindb.persist_block(genesis_block, SerenityBeaconBlock) + chaindb.persist_block(genesis_block, SerenityBeaconBlock, fork_choice_scoring) chaindb.persist_state(genesis_state) state = genesis_state @@ -72,7 +73,7 @@ def test_per_slot_transition(chaindb, ) # Store in chaindb - chaindb.persist_block(block, SerenityBeaconBlock) + chaindb.persist_block(block, SerenityBeaconBlock, fork_choice_scoring) # Get state machine instance sm = fixture_sm_class(