async def test_validator_propose_block_fails(caplog, event_loop, event_bus): alice, bob = await get_linked_validators(event_loop=event_loop, event_bus=event_bus) state_machine = alice.chain.get_state_machine() state = state_machine.state slot = state.slot + 1 proposer_index = _get_proposer_index( state=state, slot=slot, config=state_machine.config, ) # select the wrong validator as proposer v: Validator = None if proposer_index == alice.validator_index: v = bob else: v = alice head = v.chain.get_canonical_head() # test: if a non-proposer validator proposes a block, the block validation should fail. with pytest.raises(ValidationError): v.propose_block( slot=slot, state=state, state_machine=state_machine, head_block=head, )
async def propose_or_skip_block(self, slot: Slot, is_second_tick: bool) -> None: head = self.chain.get_canonical_head() state_machine = self.chain.get_state_machine() state = state_machine.state self.logger.debug( bold_green( f"head: slot={head.slot}, state root={head.state_root.hex()}")) proposer_index = _get_proposer_index( state, slot, state_machine.config, ) # Since it's expected to tick twice in one slot, `latest_proposed_epoch` is used to prevent # proposing twice in the same slot. has_proposed = slot_to_epoch( slot, self.slots_per_epoch) <= self.latest_proposed_epoch if not has_proposed and proposer_index in self.validator_privkeys: self.propose_block( proposer_index=proposer_index, slot=slot, state=state, state_machine=state_machine, head_block=head, ) self.latest_proposed_epoch = slot_to_epoch(slot, self.slots_per_epoch) # skip the block if it's second half of the slot and we are not proposing elif is_second_tick and proposer_index not in self.validator_privkeys: self.skip_block( slot=slot, state=state, state_machine=state_machine, )
async def new_slot(self, slot: Slot) -> None: head = self.chain.get_canonical_head() state_machine = self.chain.get_state_machine() state = state_machine.state self.logger.debug( bold_green( f"head: slot={head.slot}, state root={head.state_root}")) proposer_index = _get_proposer_index( state, slot, state_machine.config, ) if self.validator_index == proposer_index: self.propose_block( slot=slot, state=state, state_machine=state_machine, head_block=head, ) else: self.skip_block( slot=slot, state=state, state_machine=state_machine, )
async def test_validator_include_ready_attestations(event_loop, event_bus, monkeypatch): # Alice controls all validators alice_indices = list(range(8)) alice = await get_validator(event_loop=event_loop, event_bus=event_bus, indices=alice_indices) state_machine = alice.chain.get_state_machine() state = state_machine.state attesting_slot = state.slot + 1 attestations = await alice.attest(attesting_slot) # Mock `get_ready_attestations_fn` so it returns the attestation alice # attested to. def get_ready_attestations_fn(slog): return attestations monkeypatch.setattr(alice, 'get_ready_attestations', get_ready_attestations_fn) proposing_slot = attesting_slot + XIAO_LONG_BAO_CONFIG.MIN_ATTESTATION_INCLUSION_DELAY proposer_index = _get_proposer_index( state, proposing_slot, state_machine.config, ) head = alice.chain.get_canonical_head() block = alice.propose_block( proposer_index=proposer_index, slot=proposing_slot, state=state, state_machine=state_machine, head_block=head, ) # Check that attestation is included in the proposed block. assert attestations[0] in block.body.attestations
async def handle_first_tick(self, slot: Slot) -> None: head = self.chain.get_canonical_head() state_machine = self.chain.get_state_machine() state = state_machine.state self.logger.debug( # Align with debug log below bold_green("Head epoch=%s slot=%s state_root=%s"), state.current_epoch(self.slots_per_epoch), head.slot, encode_hex(head.state_root), ) self.logger.debug( bold_green("Justified epoch=%s root=%s (current)"), state.current_justified_epoch, encode_hex(state.current_justified_root), ) self.logger.debug( bold_green("Justified epoch=%s root=%s (previous)"), state.previous_justified_epoch, encode_hex(state.previous_justified_root), ) self.logger.debug( bold_green("Finalized epoch=%s root=%s"), state.finalized_epoch, encode_hex(state.finalized_root), ) self.logger.debug( bold_green("current_epoch_attestations %s"), state.current_epoch_attestations, ) self.logger.debug( bold_green("previous_epoch_attestations %s"), state.previous_epoch_attestations, ) proposer_index = _get_proposer_index( state, slot, state_machine.config, ) # `latest_proposed_epoch` is used to prevent validator from erraneously proposing twice # in the same epoch due to service crashing. epoch = slot_to_epoch(slot, self.slots_per_epoch) if proposer_index in self.validator_privkeys: has_proposed = epoch <= self.latest_proposed_epoch[proposer_index] if not has_proposed: self.propose_block( proposer_index=proposer_index, slot=slot, state=state, state_machine=state_machine, head_block=head, ) self.latest_proposed_epoch[proposer_index] = epoch
def _get_slot_with_validator_selected(is_desired_proposer_index, start_slot, state, state_machine): slot = start_slot num_trials = 1000 while True: if (slot - start_slot) > num_trials: raise Exception("Failed to find a slot where we have validators selected as a proposer") proposer_index = _get_proposer_index( state, slot, state_machine.config, ) if is_desired_proposer_index(proposer_index): return slot, proposer_index slot += 1
async def handle_first_tick(self, slot: Slot) -> None: head = self.chain.get_canonical_head() state_machine = self.chain.get_state_machine() state = self.chain.get_head_state() self.logger.debug( bold_green( "status at slot %s in epoch %s: state_root %s, finalized_checkpoint %s" ), state.slot, state.current_epoch(self.slots_per_epoch), humanize_hash(head.state_root), state.finalized_checkpoint, ) self.logger.debug( ("status at slot %s in epoch %s:" " previous_justified_checkpoint %s, current_justified_checkpoint %s" ), state.slot, state.current_epoch(self.slots_per_epoch), state.previous_justified_checkpoint, state.current_justified_checkpoint, ) self.logger.debug( ("status at slot %s in epoch %s:" " previous_epoch_attestations %s, current_epoch_attestations %s"), state.slot, state.current_epoch(self.slots_per_epoch), state.previous_epoch_attestations, state.current_epoch_attestations, ) proposer_index = _get_proposer_index( state.copy(slot=slot, ), state_machine.config, ) # `latest_proposed_epoch` is used to prevent validator from erraneously proposing twice # in the same epoch due to service crashing. epoch = compute_epoch_of_slot(slot, self.slots_per_epoch) if proposer_index in self.validator_privkeys: has_proposed = epoch <= self.latest_proposed_epoch[proposer_index] if not has_proposed: await self.propose_block( proposer_index=proposer_index, slot=slot, state=state, state_machine=state_machine, head_block=head, ) self.latest_proposed_epoch[proposer_index] = epoch
async def test_validator_new_slot(caplog, event_loop, event_bus, monkeypatch): caplog.set_level(logging.DEBUG) alice = await get_validator(event_loop=event_loop, event_bus=event_bus, index=0) state_machine = alice.chain.get_state_machine() state = state_machine.state new_slot = state.slot + 1 # test: `new_slot` should call `propose_block` if the validator get selected, # else calls `skip_block`. index = _get_proposer_index( state, new_slot, state_machine.config, ) is_proposing = True def propose_block(slot, state, state_machine, head_block): nonlocal is_proposing is_proposing = True def skip_block(slot, state, state_machine): nonlocal is_proposing is_proposing = False monkeypatch.setattr(alice, 'propose_block', propose_block) monkeypatch.setattr(alice, 'skip_block', skip_block) await alice.new_slot(new_slot) # test: either `propose_block` or `skip_block` should be called. assert is_proposing is not None if alice.validator_index == index: assert is_proposing else: assert not is_proposing