def get_validator_duties(self, public_keys: Collection[BLSPubkey], epoch: Epoch) -> Iterable[ValidatorDuty]: if epoch < GENESIS_EPOCH: return () current_tick = self.clock.compute_current_tick() state = advance_state_to_slot(self.chain, current_tick.slot) for public_key in public_keys: validator_index = state.get_validator_index_for_public_key( public_key) try: committee_assignment = get_committee_assignment( state, self.eth2_config, epoch, validator_index) except NoCommitteeAssignment: continue if is_proposer(state, validator_index, self.eth2_config): # TODO (ralexstokes) clean this up! if state.slot != 0: block_proposal_slot = state.slot else: block_proposal_slot = Slot((1 << 64) - 1) else: # NOTE: temporary sentinel value for "no slot" # The API has since been updated w/ much better ergonomics block_proposal_slot = Slot((1 << 64) - 1) yield ValidatorDuty( public_key, committee_assignment.slot, committee_assignment.committee_index, block_proposal_slot, )
def get_committee_assignment( state: BeaconState, config: BeaconConfig, epoch: Epoch, validator_index: ValidatorIndex, registry_change: bool = False) -> CommitteeAssignment: """ Return the ``CommitteeAssignment`` in the ``epoch`` for ``validator_index`` and ``registry_change``. ``CommitteeAssignment.committee`` is the tuple array of validators in the committee ``CommitteeAssignment.shard`` is the shard to which the committee is assigned ``CommitteeAssignment.slot`` is the slot at which the committee is assigned ``CommitteeAssignment.is_proposer`` is a bool signalling if the validator is expected to propose a beacon block at the assigned slot. """ current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) next_epoch = Epoch(current_epoch + 1) validate_epoch_within_previous_and_next(epoch, previous_epoch, next_epoch) epoch_start_slot = get_epoch_start_slot(epoch, config.SLOTS_PER_EPOCH) committee_config = CommitteeConfig(config) for slot in range(epoch_start_slot, epoch_start_slot + config.SLOTS_PER_EPOCH): crosslink_committees = get_crosslink_committees_at_slot( state, slot, committee_config, registry_change=registry_change, ) selected_committees = [ committee for committee in crosslink_committees if validator_index in committee[0] ] if len(selected_committees) > 0: validators = selected_committees[0][0] shard = selected_committees[0][1] is_proposer = validator_index == get_beacon_proposer_index( state, Slot(slot), committee_config, registry_change=registry_change, ) return CommitteeAssignment(validators, shard, Slot(slot), is_proposer) raise NoCommitteeAssignment
def _read_state_randao_mixes(self, state_root: Root, EPOCHS_PER_HISTORICAL_VECTOR: int, SLOTS_PER_EPOCH: int) -> Iterable[Root]: """ Reconstructs the ``randao_mixes`` at a given state root. """ state_slot = self._read_state_slot(state_root) state_epoch = compute_epoch_at_slot(state_slot, SLOTS_PER_EPOCH) finalized_slot = self.get_finalized_head(BeaconBlock).slot non_finalized_state_roots = dict( enumerate( self.get_state_parents(state_root, state_slot - finalized_slot), finalized_slot, )) # create a list of epochs that corresponds to each mix in ``state.randao_mixes`` epochs = [ Epoch(n) for n in range(state_epoch - EPOCHS_PER_HISTORICAL_VECTOR + 1, state_epoch + 1) ] offset = EPOCHS_PER_HISTORICAL_VECTOR - epochs[ 0] % EPOCHS_PER_HISTORICAL_VECTOR epochs = epochs[offset:] + epochs[:offset] genesis_root = self._read_state_root_at_slot(Slot(0)) genesis_randao_mix = Root( Hash32(self.db[SchemaV1.state_root_to_randao_mix(genesis_root)])) for epoch in epochs: if epoch < 0: yield genesis_randao_mix elif epoch == state_epoch: # yield the randao mix at the particular slot key = SchemaV1.state_root_to_randao_mix(state_root) yield Root(Hash32(self.db[key])) else: # yield the randao mix at the last slot in the epoch slot = Slot((epoch + 1) * SLOTS_PER_EPOCH - 1) if slot in non_finalized_state_roots: root = non_finalized_state_roots[slot] else: root = self._read_state_root_at_slot(slot) key = SchemaV1.state_root_to_randao_mix(root) yield Root(Hash32(self.db[key]))
def compute_slots_since_epoch_start(slot: Slot, slots_per_epoch: int) -> Slot: return Slot( slot - compute_start_slot_at_epoch( compute_epoch_at_slot(slot, slots_per_epoch), slots_per_epoch ) )
def _find_present_ancestor_state( self, block_root: Root ) -> Tuple[BeaconState, Tuple[SignedBeaconBlock, ...]]: """ Find the first state we have persisted that is an ancestor of ``target_block``. """ try: block = self._chain_db.get_block_by_root(block_root, BeaconBlock) blocks: Tuple[SignedBeaconBlock, ...] = () # NOTE: re: bounds here; worst case, we return the genesis state. for slot in range(block.slot, GENESIS_SLOT - 1, -1): try: state_machine = self.get_state_machine(Slot(slot)) state = self._chain_db.get_state_by_root( block.state_root, state_machine.state_class, state_machine.config, ) return (state, blocks) except StateNotFound: signature = self._chain_db.get_block_signature_by_root( block.hash_tree_root) blocks += (SignedBeaconBlock.create(message=block, signature=signature), ) block = self._chain_db.get_block_by_root( block.parent_root, BeaconBlock) except BlockNotFound: raise Exception( "invariant violated: querying a block that has not been persisted" ) # NOTE: `mypy` complains without this although execution should never get here... return (None, ())
async def _keep_ticking(self) -> None: """ Ticker should tick three times in one slot: SLOT_START: at the beginning of the slot SLOT_ONE_THIRD: at 1/3 of the slot SLOT_TWO_THIRD: at 2/3 of the slot """ # Use `sent_tick_types_at_slot` set to record # the tick types that haven been sent at current slot. sent_tick_types_at_slot: Set[TickType] = set() while self.is_operational: elapsed_time = Second(int(time.time()) - self.genesis_time) # Skip genesis slot if elapsed_time < self.seconds_per_slot: continue elapsed_slots = elapsed_time // self.seconds_per_slot slot = Slot(elapsed_slots + self.genesis_slot) tick_type = self._get_tick_type(elapsed_time) # New slot if slot > self.latest_slot: self.latest_slot = slot await self._broadcast_slot_tick_event(slot, elapsed_time, tick_type) # Clear set sent_tick_types_at_slot = set() sent_tick_types_at_slot.add(TickType.SLOT_START) elif not tick_type.is_start and tick_type not in sent_tick_types_at_slot: await self._broadcast_slot_tick_event(slot, elapsed_time, tick_type) sent_tick_types_at_slot.add(tick_type) await asyncio.sleep(self.seconds_per_slot // self.check_frequency)
def get_committee_assignment( state: BeaconState, config: Eth2Config, epoch: Epoch, validator_index: ValidatorIndex, ) -> CommitteeAssignment: """ Return the ``CommitteeAssignment`` in the ``epoch`` for ``validator_index``. ``CommitteeAssignment.committee`` is the tuple array of validators in the committee ``CommitteeAssignment.committee_index`` is the index to which the committee is assigned ``CommitteeAssignment.slot`` is the slot at which the committee is assigned """ next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) if epoch > next_epoch: raise ValidationError( f"Epoch for committee assignment ({epoch}) must not be after next epoch {next_epoch}." ) for committee, committee_index, slot in iterate_committees_at_epoch( state, epoch, CommitteeConfig(config) ): if validator_index in committee: return CommitteeAssignment( committee, CommitteeIndex(committee_index), Slot(slot) ) raise NoCommitteeAssignment
def create_mock_attester_slashing_is_surround_vote( state: BeaconState, config: BeaconConfig, keymap: Dict[BLSPubkey, int], attestation_epoch: Epoch) -> AttesterSlashing: # target_epoch_2 < target_epoch_1 attestation_slot_2 = get_epoch_start_slot(attestation_epoch, config.SLOTS_PER_EPOCH) attestation_slot_1 = Slot(attestation_slot_2 + config.SLOTS_PER_EPOCH) slashable_attestation_1 = create_mock_slashable_attestation( state.copy( slot=attestation_slot_1, justified_epoch=config.GENESIS_EPOCH, ), config, keymap, attestation_slot_1, ) slashable_attestation_2 = create_mock_slashable_attestation( state.copy( slot=attestation_slot_1, justified_epoch=config.GENESIS_EPOCH + 1, # source_epoch_1 < source_epoch_2 ), config, keymap, attestation_slot_2, ) return AttesterSlashing( slashable_attestation_1=slashable_attestation_1, slashable_attestation_2=slashable_attestation_2, )
def __init__( self, chain: BaseBeaconChain, p2p_node: Node, validator_privkeys: Dict[ValidatorIndex, int], event_bus: EndpointAPI, get_ready_attestations_fn: GetReadyAttestationsFn, get_aggregatable_attestations_fn: GetAggregatableAttestationsFn, import_attestation_fn: ImportAttestationFn, token: CancelToken = None) -> None: super().__init__( chain, p2p_node, event_bus, get_ready_attestations_fn, get_aggregatable_attestations_fn, import_attestation_fn, token, ) self.validator_privkeys = validator_privkeys config = self.chain.get_state_machine().config self.slots_per_epoch = config.SLOTS_PER_EPOCH # TODO: `latest_proposed_epoch` and `latest_attested_epoch` should be written # into/read from validator's own db. self.latest_proposed_epoch = {} self.latest_attested_epoch = {} self.local_validator_epoch_assignment = {} for validator_index in validator_privkeys: self.latest_proposed_epoch[validator_index] = Epoch(-1) self.latest_attested_epoch[validator_index] = Epoch(-1) self.local_validator_epoch_assignment[validator_index] = ( Epoch(-1), CommitteeAssignment((), CommitteeIndex(-1), Slot(-1)), )
def create_block_header_with_signature( state: BeaconState, body_root: Root, privkey: int, slots_per_epoch: int, proposer_index: ValidatorIndex, parent_root: Root = SAMPLE_HASH_1, state_root: Root = SAMPLE_HASH_2, ) -> SignedBeaconBlockHeader: block_header = BeaconBlockHeader.create( slot=Slot(state.slot), proposer_index=proposer_index, parent_root=parent_root, state_root=state_root, body_root=body_root, ) block_header_signature = sign_transaction( object=block_header, privkey=privkey, state=state, slot=block_header.slot, signature_domain=SignatureDomain.DOMAIN_BEACON_PROPOSER, slots_per_epoch=slots_per_epoch, ) return SignedBeaconBlockHeader.create(message=block_header, signature=block_header_signature)
def create_mock_attester_slashing_is_double_vote( state: BeaconState, config: Eth2Config, keymap: Dict[BLSPubkey, int], attestation_epoch: Epoch) -> AttesterSlashing: attestation_slot_1 = get_epoch_start_slot(attestation_epoch, config.SLOTS_PER_EPOCH) attestation_slot_2 = Slot(attestation_slot_1 + 1) slashable_attestation_1 = create_mock_slashable_attestation( state, config, keymap, attestation_slot_1, ) slashable_attestation_2 = create_mock_slashable_attestation( state, config, keymap, attestation_slot_2, ) return AttesterSlashing( slashable_attestation_1=slashable_attestation_1, slashable_attestation_2=slashable_attestation_2, )
def __init__(self, chain: BeaconChain, p2p_node: Node, validator_privkeys: Dict[ValidatorIndex, int], event_bus: EndpointAPI, get_ready_attestations_fn: GetReadyAttestationsFn, token: CancelToken = None) -> None: super().__init__(token) self.chain = chain self.p2p_node = p2p_node self.validator_privkeys = validator_privkeys self.event_bus = event_bus config = self.chain.get_state_machine().config self.slots_per_epoch = config.SLOTS_PER_EPOCH # TODO: `latest_proposed_epoch` and `latest_attested_epoch` should be written # into/read from validator's own db. self.latest_proposed_epoch = {} self.latest_attested_epoch = {} self.this_epoch_assignment = {} for validator_index in validator_privkeys: self.latest_proposed_epoch[validator_index] = Epoch(-1) self.latest_attested_epoch[validator_index] = Epoch(-1) self.this_epoch_assignment[validator_index] = ( Epoch(-1), CommitteeAssignment((), Shard(-1), Slot(-1), False), ) self.get_ready_attestations: GetReadyAttestationsFn = get_ready_attestations_fn
def main_validator() -> None: # TODO: # Merge into trinity platform # 1. CLI parsing # 2. Loading config from file and/or cmd line # 3. Logging logger = _setup_logging() arguments = parse_cli_args() root_data_dir = (Path(os.environ["HOME"]) / ".local" / "share" / "trinity" / "eth2" / "validator_client") slots_per_epoch = Slot(4) seconds_per_slot = 2 genesis_time = int(time.time()) + slots_per_epoch * seconds_per_slot + 3 num_validators = 16 key_pairs = tuple( _mk_random_key_pair(index) for index in range(num_validators)) config = Config( key_store_constructor=_mk_key_store_from_key_pairs(key_pairs), root_data_dir=root_data_dir, slots_per_epoch=slots_per_epoch, seconds_per_slot=seconds_per_slot, genesis_time=genesis_time, demo_mode=arguments.demo_mode, ) trio.run(arguments.func, logger, config, arguments)
def create_mock_attester_slashing_is_surround_vote( state: BeaconState, config: Eth2Config, keymap: Dict[BLSPubkey, int], attestation_epoch: Epoch, ) -> AttesterSlashing: # target_epoch_2 < target_epoch_1 attestation_slot_2 = compute_start_slot_at_epoch(attestation_epoch, config.SLOTS_PER_EPOCH) attestation_slot_1 = Slot(attestation_slot_2 + config.SLOTS_PER_EPOCH) slashable_attestation_1 = create_mock_slashable_attestation( state.mset("slot", attestation_slot_1, "current_justified_epoch", config.GENESIS_EPOCH), config, keymap, attestation_slot_1, ) slashable_attestation_2 = create_mock_slashable_attestation( state.mset( "slot", attestation_slot_1, "current_justified_epoch", config.GENESIS_EPOCH + 1, # source_epoch_1 < source_epoch_2 ), config, keymap, attestation_slot_2, ) return AttesterSlashing.create(attestation_1=slashable_attestation_1, attestation_2=slashable_attestation_2)
def get_batches(self, max_size: int) -> Iterable["SyncRequest"]: last_slot = self.start_slot + self.count batch_offsets = range(self.start_slot, self.start_slot + self.count, max_size) for offset in batch_offsets: remainder = last_slot - offset batch_size = max_size if remainder > max_size else remainder yield self.__class__(self.peer_id, Slot(offset), batch_size)
class StateTestCase(BaseTestCase): bls_setting: bool description: str pre: BeaconState post: BeaconState slots: Slot = Slot(0) blocks: Tuple[BeaconBlock, ...] = field(default_factory=tuple) is_valid: bool = True
async def _get_block_proposal(self, request: web.Request) -> web.Response: slot = Slot(int(request.query["slot"])) randao_reveal = BLSSignature( decode_hex(request.query["randao_reveal"]).ljust(96, b"\x00")) block = BeaconBlock.create( slot=slot, body=BeaconBlockBody.create(randao_reveal=randao_reveal)) return web.json_response(to_formatted_dict(block))
async def block(self, request: web.Request) -> Dict[str, Any]: if 'slot' in request.query: slot = Slot(int(request.query['slot'])) block = self.chain.get_canonical_block_by_slot(slot) elif 'root' in request.query: root = cast(Root, decode_hex(request.query['root'])) block = self.chain.get_block_by_root(root) return to_formatted_dict(block, sedes=BeaconBlock)
def _compute_slot_and_alignment(self, t: float) -> Tuple[Slot, bool]: """ Compute the slot corresponding to a time ``t``. Indicate if ``t`` corresponds to the first tick in the relevant slot with a boolean return value. """ time_since_genesis = t - self._genesis_time slot, sub_slot = divmod(time_since_genesis, self._seconds_per_slot) return (Slot(int(slot)), int(sub_slot * self._tick_multiplier) == 0)
def _get_slot_by_root(db: DatabaseAPI, block_root: Hash32) -> Slot: validate_word(block_root, title="block root") try: encoded_slot = db[SchemaV1.make_block_root_to_slot_lookup_key( block_root)] except KeyError: raise BlockNotFound("No block with root {0} found".format( encode_hex(block_root))) return Slot(ssz.decode(encoded_slot, sedes=ssz.sedes.uint64))
async def _get_attestation(context: Context, request: Request) -> Response: if not isinstance(request, dict): return {} public_key = BLSPubkey(decode_hex(request["validator_pubkey"])) slot = Slot(int(request["slot"])) committee_index = CommitteeIndex(int(request["committee_index"])) attestation = context.get_attestation(public_key, slot, committee_index) return to_formatted_dict(attestation)
async def _get_attestation(self, request: web.Request) -> web.Response: # _public_key = BLSPubkey(decode_hex(request.query["validator_pubkey"])) slot = Slot(int(request.query["slot"])) committee_index = CommitteeIndex(int(request.query["committee_index"])) attestation = Attestation.create( aggregation_bits=Bitfield([True, False, False]), data=AttestationData.create(index=committee_index, slot=slot), ) return web.json_response(to_formatted_dict(attestation))
def beacon_attestation_validator(msg_forwarder: ID, msg: rpc_pb2.Message) -> bool: try: attestation = ssz.decode(msg.data, sedes=Attestation) except (TypeError, ssz.DeserializationError) as error: # Not correctly encoded logger.debug( bold_red("Failed to validate attestation=%s, error=%s"), attestation, str(error), ) return False state_machine = chain.get_state_machine() config = state_machine.config state = chain.get_head_state() # Check that beacon blocks attested to by the attestation are validated try: chain.get_block_by_root(attestation.data.beacon_block_root) except BlockNotFound: logger.debug( bold_red( "Failed to validate attestation=%s, attested block=%s is not validated yet" ), attestation, encode_hex(attestation.data.beacon_block_root), ) return False # Fast forward to state in future slot in order to pass # attestation.data.slot validity check attestation_data_slot = get_attestation_data_slot( state, attestation.data, config, ) future_state = state_machine.state_transition.apply_state_transition( state, future_slot=Slot(attestation_data_slot + config.MIN_ATTESTATION_INCLUSION_DELAY), ) try: validate_attestation( future_state, attestation, config, ) except ValidationError as error: logger.debug( bold_red("Failed to validate attestation=%s, error=%s"), attestation, str(error), ) return False return True
def test_chain2_full(base_db, genesis_state, config): chain_db = BeaconChainDB.from_genesis(base_db, genesis_state, SignedBeaconBlock, config) state = genesis_state states = [genesis_state] blocks = {} num_slots = 500 skip_slots = [5, 64, 100, 300, 301, 302, 401] finalized_slots = [8, 24, 32, 72, 152, 160, 328, 336, 344, 352, 400] # create a new state at each slot using ``_mini_stf()`` and persist it for _ in range(1, num_slots): if state.slot not in skip_slots: new_block = BeaconBlock.create(slot=state.slot, state_root=state.hash_tree_root) blocks[state.slot] = new_block chain_db.persist_block(SignedBeaconBlock.create(message=new_block)) else: new_block = None state = _mini_stf(state, new_block, config) chain_db.persist_state(state, config) states.append(state) # test that each state created above equals the state stored at its root for state in states: # finalize a slot two epochs after processing it # this is here to test the reconstruction of ``state.randao_mixes`` maybe_finalized_slot = state.slot - config.SLOTS_PER_EPOCH * 2 if maybe_finalized_slot in finalized_slots: chain_db.mark_finalized_head(blocks[maybe_finalized_slot]) retrieved_state = chain_db._read_state(state.hash_tree_root, BeaconState, config) assert retrieved_state == state for slot in range(0, num_slots): if slot in blocks and slot <= finalized_slots[-1]: assert chain_db.get_block_by_slot(Slot(slot), BeaconBlock) == blocks[slot] else: assert chain_db.get_block_by_slot(Slot(slot), BeaconBlock) is None
async def state(self, request: web.Request) -> Dict[str, Any]: if 'slot' in request.query: slot = Slot(int(request.query['slot'])) state = self.chain.get_state_by_slot(slot) elif 'root' in request.query: root = cast(Root, decode_hex(request.query['root'])) state = self.chain.get_state_by_root(root) else: raise APIServerError(f"Wrong querystring: {request.query}") return to_formatted_dict(state)
def _determine_sync_requests(self, peer_id: PeerID, status: Status) -> Iterable[SyncRequest]: """ If the peer has a higher finalized epoch or head slot, sync blocks from them. """ head_state = self._chain.get_canonical_head_state() finalized_slot = compute_start_slot_at_epoch( head_state.finalized_checkpoint.epoch, self._eth2_config.SLOTS_PER_EPOCH) while finalized_slot < status.head_slot: count = min(status.head_slot - finalized_slot, MAX_REQUEST_BLOCKS) yield SyncRequest(peer_id, Slot(finalized_slot + 1), count) finalized_slot += count
async def _get_block_proposal(context: Context, request: Request) -> Response: if not isinstance(request, dict): return {} slot = Slot(int(request["slot"])) randao_reveal = BLSSignature( decode_hex(request["randao_reveal"]).ljust(96, b"\x00")) try: block = context.get_block_proposal(slot, randao_reveal) return to_formatted_dict(block) except Exception as e: # TODO error handling... return {"error": str(e)}
def advance_state_to_slot(chain: BaseBeaconChain, target_slot: Slot, state: BeaconState = None) -> BeaconState: if state is None: state = chain.get_canonical_head_state() current_slot = state.slot for slot in range(current_slot, target_slot + 1): slot = Slot(slot) state_machine = chain.get_state_machine(slot) state, _ = state_machine.apply_state_transition(state, future_slot=slot) return state
def _determine_sync_request(self, peer_id: PeerID, status: Status) -> Optional[SyncRequest]: """ If the peer has a higher finalized epoch or head slot, sync blocks from them. """ head_state = self._chain.get_canonical_head_state() finalized_slot = compute_start_slot_at_epoch( head_state.finalized_checkpoint.epoch, self._eth2_config.SLOTS_PER_EPOCH) if finalized_slot < status.head_slot: span = status.head_slot - finalized_slot return SyncRequest(peer_id, Slot(finalized_slot + 1), span) else: return None
def get_committee_assignment( state: BeaconState, config: Eth2Config, epoch: Epoch, validator_index: ValidatorIndex, ) -> CommitteeAssignment: """ Return the ``CommitteeAssignment`` in the ``epoch`` for ``validator_index``. ``CommitteeAssignment.committee`` is the tuple array of validators in the committee ``CommitteeAssignment.shard`` is the shard to which the committee is assigned ``CommitteeAssignment.slot`` is the slot at which the committee is assigned ``CommitteeAssignment.is_proposer`` is a bool signalling if the validator is expected to propose a beacon block at the assigned slot. """ next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) if epoch > next_epoch: raise ValidationError( f"Epoch for committee assignment ({epoch}) must not be after next epoch {next_epoch}." ) active_validators = get_active_validator_indices(state.validators, epoch) committees_per_slot = ( get_committee_count( len(active_validators), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) // config.SLOTS_PER_EPOCH ) epoch_start_slot = compute_start_slot_of_epoch(epoch, config.SLOTS_PER_EPOCH) epoch_start_shard = get_start_shard(state, epoch, CommitteeConfig(config)) for slot in range(epoch_start_slot, epoch_start_slot + config.SLOTS_PER_EPOCH): offset = committees_per_slot * (slot % config.SLOTS_PER_EPOCH) slot_start_shard = (epoch_start_shard + offset) % config.SHARD_COUNT for i in range(committees_per_slot): shard = Shard((slot_start_shard + i) % config.SHARD_COUNT) committee = get_crosslink_committee( state, epoch, shard, CommitteeConfig(config) ) if validator_index in committee: is_proposer = validator_index == get_beacon_proposer_index( state.copy(slot=slot), CommitteeConfig(config) ) return CommitteeAssignment( committee, Shard(shard), Slot(slot), is_proposer ) raise NoCommitteeAssignment