async def _keep_ticking(self) -> None: """ Ticker should tick twice in one slot: one for a new slot, one for the second half of an already ticked slot, e.g., if `seconds_per_slot` is `6`, for slot `49` it should tick once for the first 3 seconds and once for the last 3 seconds. """ # `has_sent_second_half_slot_tick` is used to prevent another tick # for the second half of a ticked slot. has_sent_second_half_slot_tick = False while self.is_operational: elapsed_time = Second(int(time.time()) - self.genesis_time) if elapsed_time >= self.seconds_per_slot: slot = Slot(elapsed_time // self.seconds_per_slot + self.genesis_slot) is_second_tick = ((elapsed_time % self.seconds_per_slot) >= (self.seconds_per_slot / 2)) # Case 1: new slot if slot > self.latest_slot: self.logger.debug( bold_green("Tick this_slot=%s elapsed=%s"), slot, elapsed_time, ) self.latest_slot = slot await self.event_bus.broadcast( SlotTickEvent( slot=slot, elapsed_time=elapsed_time, is_second_tick=is_second_tick, ), BroadcastConfig(internal=True), ) has_sent_second_half_slot_tick = is_second_tick # Case 2: second half of an already ticked slot and it hasn't tick yet elif is_second_tick and not has_sent_second_half_slot_tick: self.logger.debug( bold_green("Tick this_slot=%s (second-tick)"), slot) await self.event_bus.broadcast( SlotTickEvent( slot=slot, elapsed_time=elapsed_time, is_second_tick=is_second_tick, ), BroadcastConfig(internal=True), ) has_sent_second_half_slot_tick = True await asyncio.sleep(self.seconds_per_slot // DEFAULT_CHECK_FREQUENCY)
async def _run(self) -> None: self.logger.info( bold_green("validating with indices %s"), sorted(tuple(self.validator_privkeys.keys())) ) self.run_daemon_task(self.handle_slot_tick()) await self.cancellation()
async def propose_block(self, proposer_index: ValidatorIndex, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine, head_block: BaseBeaconBlock) -> BaseBeaconBlock: ready_attestations = self.get_ready_attestations(slot) block = self._make_proposing_block( proposer_index=proposer_index, slot=slot, state=state, state_machine=state_machine, parent_block=head_block, attestations=ready_attestations, ) self.logger.debug( bold_green("validator %s is proposing a block %s with attestations %s"), proposer_index, block, block.body.attestations, ) self.chain.import_block(block) self.logger.debug("broadcasting block %s", block) await self.p2p_node.broadcast_beacon_block(block) return block
def propose_block(self, proposer_index: ValidatorIndex, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine, head_block: BaseBeaconBlock) -> BaseBeaconBlock: ready_attestations = self.get_ready_attestations() block = self._make_proposing_block( proposer_index=proposer_index, slot=slot, state=state, state_machine=state_machine, parent_block=head_block, attestations=ready_attestations, ) self.logger.info( bold_green("Validator=%s proposing block=%s with attestations=%s"), proposer_index, block, block.body.attestations, ) for peer in self.peer_pool.connected_nodes.values(): peer = cast(BCCPeer, peer) self.logger.debug(bold_red("Sending block=%s to peer=%s"), block, peer) peer.sub_proto.send_new_block(block) self.chain.import_block(block) return block
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 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 _run(self) -> None: await self.event_bus.wait_until_serving() self.logger.info( bold_green("Validator service up Handle indices=%s"), tuple(self.validator_privkeys.keys()) ) self.run_daemon_task(self.handle_slot_tick()) await self.cancellation()
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.message.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, ) # To see if a validator is assigned to propose during the slot, the beacon state must # be in the epoch in question. At the epoch boundaries, the validator must run an # epoch transition into the epoch to successfully check the proposal assignment of the # first slot. temp_state = state_machine.state_transition.apply_state_transition( state, future_slot=slot, ) proposer_index = get_beacon_proposer_index( temp_state, CommitteeConfig(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_at_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 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), ) 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 propose_block(self, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine, head_block: BaseBeaconBlock) -> BaseBeaconBlock: block = self._make_proposing_block(slot, state, state_machine, head_block) self.logger.debug(bold_green(f"proposing block, block={block}")) for peer in self.peer_pool.connected_nodes.values(): peer = cast(BCCPeer, peer) self.logger.debug(bold_red(f"sending block to peer={peer}")) peer.sub_proto.send_new_block(block) self.chain.import_block(block) return block
def skip_block(self, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine) -> Hash32: post_state = state_machine.state_transition.apply_state_transition_without_block( state, # TODO: Change back to `slot` instead of `slot + 1`. # Currently `apply_state_transition_without_block` only returns the post state # of `slot - 1`, so we increment it by one to get the post state of `slot`. cast(Slot, slot + 1), ) self.logger.debug( bold_green(f"skipping block, post state={post_state.root}")) # FIXME: We might not need to persist state for skip slots since `create_block_on_state` # will run the state transition which also includes the state transition for skipped slots. self.chain.chaindb.persist_state(post_state) return post_state.root
def skip_block(self, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine) -> BeaconState: post_state = state_machine.state_transition.apply_state_transition( state, future_slot=slot, ) self.logger.debug( bold_green("Skip block at slot=%s post_state=%s"), slot, post_state, ) # FIXME: We might not need to persist state for skip slots since `create_block_on_state` # will run the state transition which also includes the state transition for skipped slots. self.chain.chaindb.persist_state(post_state) return post_state
def skip_block(self, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine) -> BeaconState: """ Forward state to the target ``slot`` and persist the state. """ post_state = state_machine.state_transition.apply_state_transition( state, future_slot=slot) self.logger.debug(bold_green("Skip block at slot=%s post_state=%s"), slot, repr(post_state)) # FIXME: We might not need to persist state for skip slots since `create_block_on_state` # will run the state transition which also includes the state transition for skipped slots. self.chain.chaindb.persist_state(post_state) self.chain.chaindb.update_head_state(post_state.slot, post_state.hash_tree_root) return post_state
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
def run_generate_testnet_dir(cls, args: Namespace, trinity_config: TrinityConfig) -> None: logger = cls.get_logger() logger.info("Generating testnet") network_dir = args.network_dir if len(os.listdir(network_dir)) > 0: logger.error( "This directory is not empty, won't create network files here." ) sys.exit(1) clients = cls.generate_trinity_root_dirs(network_dir) keymap = cls.generate_keys(args.num, network_dir, clients) cls.generate_genesis_state(args.genesis_delay, network_dir, keymap, clients) logger.info(bold_green("Network generation completed"))
async def propose_block( self, proposer_index: ValidatorIndex, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine, head_block: BaseBeaconBlock, ) -> BaseBeaconBlock: """ Propose a block and broadcast it. """ eth1_vote = await self._get_eth1_vote(slot, state, state_machine) deposits = await self._get_deposit_data(state, state_machine, eth1_vote) # TODO(hwwhww): Check if need to aggregate and if they are overlapping. aggregated_attestations = self.get_ready_attestations(slot, True) unaggregated_attestations = self.get_ready_attestations(slot, False) ready_attestations = aggregated_attestations + unaggregated_attestations block = create_block_on_state( state=state, config=state_machine.config, state_machine=state_machine, signed_block_class= SerenitySignedBeaconBlock, # TODO: Should get block class from slot parent_block=head_block, slot=slot, validator_index=proposer_index, privkey=self.validator_privkeys[proposer_index], attestations=ready_attestations, eth1_data=eth1_vote, deposits=deposits, check_proposer_index=False, ) self.logger.debug( bold_green( "validator %s is proposing a block %s with attestations %s"), proposer_index, block, block.body.attestations, ) self.chain.import_block(block) self.logger.debug("broadcasting block %s", block) await self.p2p_node.broadcast_beacon_block(block) metrics.validator_proposed_blocks.inc() return block
async def propose_block( self, proposer_index: ValidatorIndex, slot: Slot, state: BeaconState, state_machine: BaseBeaconStateMachine, head_block: BaseBeaconBlock, ) -> BaseBeaconBlock: """ Propose a block and broadcast it. """ eth1_vote = await self._get_eth1_vote(slot, state, state_machine) # deposits = await self._get_deposit_data(state, state_machine, eth1_vote) # TODO(hwwhww): Check if need to aggregate and if they are overlapping. aggregated_attestations = self.get_ready_attestations(slot, True) unaggregated_attestations = self.get_ready_attestations(slot, False) ready_attestations = aggregated_attestations + unaggregated_attestations private_key = self.validator_privkeys[proposer_index] randao_reveal = generate_randao_reveal( private_key, slot, state, state_machine.config ) block = create_block_proposal( slot, head_block.message.hash_tree_root, randao_reveal, eth1_vote, ready_attestations, state, state_machine, ) block = sign_block(state, block, private_key, self.slots_per_epoch) self.logger.debug( bold_green("validator %s is proposing a block %s with attestations %s"), proposer_index, block, block.body.attestations, ) self.chain.import_block(block) self.logger.debug("broadcasting block %s", block) await self.p2p_node.broadcast_beacon_block(block) metrics.validator_proposed_blocks.inc() return block
async def _keep_ticking(self) -> None: while self.is_operational: elapsed_time = Second(int(time.time()) - self.genesis_time) if elapsed_time >= self.seconds_per_slot: slot = Slot(elapsed_time // self.seconds_per_slot + self.genesis_slot) if slot > self.latest_slot: self.logger.debug( bold_green( f"New slot: {slot}\tElapsed time: {elapsed_time}")) self.latest_slot = slot self.event_bus.broadcast( NewSlotEvent( slot=slot, elapsed_time=elapsed_time, ), BroadcastConfig(internal=True), ) await asyncio.sleep(self.seconds_per_slot // DEFAULT_CHECK_FREQUENCY)
async def attest(self, slot: Slot) -> Tuple[Attestation, ...]: """ Attest the block at the given ``slot`` and broadcast them. """ result_attestations: Tuple[Attestation, ...] = () head = self.chain.get_canonical_head() state_machine = self.chain.get_state_machine() state = self.chain.get_head_state() epoch = compute_epoch_at_slot(slot, self.slots_per_epoch) attesting_committee_assignments_at_slot = self._get_attesting_assignments_at_slot( slot) for committee_assignment in attesting_committee_assignments_at_slot: committee_index = committee_assignment.committee_index committee = committee_assignment.committee attesting_validators_indices = tuple( filter( lambda attester: self.latest_attested_epoch[attester] < epoch, self._get_local_attesters_at_assignment( committee_assignment), )) # Get the validator_index -> privkey map of the attesting validators attesting_validator_privkeys = { index: self.validator_privkeys[index] for index in attesting_validators_indices } attestations = create_signed_attestations_at_slot( state, state_machine.config, state_machine, slot, head.message.hash_tree_root, attesting_validator_privkeys, committee, committee_index, tuple( CommitteeValidatorIndex(committee.index(index)) for index in attesting_validators_indices), ) self.logger.debug( bold_green( "validators %s attesting to block %s with attestation %s"), attesting_validators_indices, head, attestations, ) # await self.p2p_node.broadcast_attestation(attestation) subnet_id = SubnetId(committee_index % ATTESTATION_SUBNET_COUNT) # Import attestation to pool and then broadcast it for attestation in attestations: self.import_attestation(attestation, False) await self.p2p_node.broadcast_attestation_to_subnet( attestation, subnet_id) # Log the last epoch that the validator attested for index in attesting_validators_indices: self.latest_attested_epoch[index] = epoch metrics.validator_sent_attestation.inc() result_attestations = result_attestations + attestations # TODO: Aggregate attestations return result_attestations
async def _run(self) -> None: await self.event_bus.wait_until_serving() self.logger.debug(bold_green("validator running!!!")) self.run_daemon_task(self.handle_slot_tick()) await self.cancellation()
async def attest(self, slot: Slot) -> Tuple[Attestation, ...]: attestations: Tuple[Attestation, ...] = () head = self.chain.get_canonical_head() state_machine = self.chain.get_state_machine() state = self.chain.get_head_state() epoch = compute_epoch_of_slot(slot, self.slots_per_epoch) validator_assignments = { validator_index: self._get_this_epoch_assignment( validator_index, epoch, ) for validator_index in self.validator_privkeys } attesting_validators = self._get_attesting_validator_and_shard( validator_assignments, slot, epoch, ) if len(attesting_validators) == 0: return () # Sort the attesting validators by shard sorted_attesting_validators = sorted( attesting_validators, key=itemgetter(1), ) # Group the attesting validators by shard attesting_validators_groups = groupby( sorted_attesting_validators, key=itemgetter(1), ) for shard, group in attesting_validators_groups: # Get the validator_index -> privkey map of the attesting validators attesting_validator_privkeys = { attesting_data[0]: self.validator_privkeys[attesting_data[0]] for attesting_data in group } attesting_validators_indices = tuple( attesting_validator_privkeys.keys()) # Get one of the attesting validator's assignment in order to get the committee info assignment = self._get_this_epoch_assignment( attesting_validators_indices[0], epoch, ) attestation = create_signed_attestation_at_slot( state, state_machine.config, state_machine, slot, head.signing_root, attesting_validator_privkeys, assignment.committee, shard, ) self.logger.debug( bold_green("Validators=%s attest to block=%s attestation=%s"), attesting_validators_indices, head, attestation, ) for validator_index in attesting_validators_indices: self.latest_attested_epoch[validator_index] = epoch attestations = attestations + (attestation, ) self.logger.debug("Brodcasting attestations %s", attestations) await self.p2p_node.broadcast_attestations(attestations) return attestations
async def _run(self) -> None: self.logger.info(bold_green("ValidatorHandler is running")) self.run_daemon_task(self.handle_get_block_requests()) await self.cancellation()
async def _run(self) -> None: self.logger.info(bold_green("ChainMaintainer is running")) self.run_daemon_task(self.handle_slot_tick()) await self.cancellation()