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 __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 generate_seed(state: 'BeaconState', epoch: Epoch, slots_per_epoch: int, min_seed_lookahead: int, activation_exit_delay: int, latest_active_index_roots_length: int, latest_randao_mixes_length: int) -> Hash32: """ Generate a seed for the given ``epoch``. """ randao_mix = get_randao_mix( state=state, epoch=Epoch(epoch - min_seed_lookahead), slots_per_epoch=slots_per_epoch, latest_randao_mixes_length=latest_randao_mixes_length, ) active_index_root = get_active_index_root( state=state, epoch=epoch, slots_per_epoch=slots_per_epoch, activation_exit_delay=activation_exit_delay, latest_active_index_roots_length=latest_active_index_roots_length, ) epoch_as_bytes = epoch.to_bytes(32, byteorder="little") return hash_eth2(randao_mix + active_index_root + epoch_as_bytes)
def get_beacon_proposer_index(state: 'BeaconState', slot: Slot, committee_config: CommitteeConfig, registry_change: bool = False) -> ValidatorIndex: """ Return the beacon proposer index for the ``slot``. """ epoch = slot_to_epoch(slot, committee_config.SLOTS_PER_EPOCH) current_epoch = state.current_epoch(committee_config.SLOTS_PER_EPOCH) previous_epoch = state.previous_epoch( committee_config.SLOTS_PER_EPOCH, committee_config.GENESIS_EPOCH, ) next_epoch = Epoch(current_epoch + 1) validate_epoch_within_previous_and_next(epoch, previous_epoch, next_epoch) crosslink_committees_at_slot = get_crosslink_committees_at_slot( state=state, slot=slot, committee_config=committee_config, registry_change=registry_change, ) try: first_crosslink_committee = crosslink_committees_at_slot[0] except IndexError: raise ValidationError("crosslink_committees should not be empty.") first_committee, _ = first_crosslink_committee if len(first_committee) <= 0: raise ValidationError("The first committee should not be empty") return first_committee[slot % len(first_committee)]
async def _fetch_latest_duties( tick: Tick, beacon_node: BeaconNodeAPI, validator_public_keys: Collection[BLSPubkey], duty_store: DutyStore, ) -> None: current_epoch = tick.epoch next_epoch = Epoch(current_epoch + 1) current_duties = await beacon_node.fetch_duties(tick, validator_public_keys, current_epoch) upcoming_duties = await beacon_node.fetch_duties(tick, validator_public_keys, next_epoch) latest_duties = cast(Tuple[Duty, ...], current_duties) + cast( Tuple[Duty, ...], upcoming_duties) if not latest_duties: return logger.debug("%s: found duties %s", tick, latest_duties) # TODO manage duties correctly, accounting for re-orgs, etc. # NOTE: the naive strategy is likely "last write wins" await duty_store.add_duties(*latest_duties)
def previous_epoch(self, slots_per_epoch: int, genesis_epoch: Epoch) -> Epoch: current_epoch = self.current_epoch(slots_per_epoch) if current_epoch == genesis_epoch: return genesis_epoch else: return Epoch(current_epoch - 1)
def compute_activation_exit_epoch(epoch: Epoch, activation_exit_delay: int) -> Epoch: """ An entry or exit triggered in the ``epoch`` given by the input takes effect at the epoch given by the output. """ return Epoch(epoch + 1 + activation_exit_delay)
def compute_activation_exit_epoch(epoch: Epoch, max_seed_lookahead: int) -> Epoch: """ An entry or exit triggered in the ``epoch`` given by the input takes effect at the epoch given by the output. """ return Epoch(epoch + 1 + max_seed_lookahead)
def _set_validator_slashed(v: Validator, current_epoch: Epoch, epochs_per_slashings_vector: int) -> Validator: return v.copy( slashed=True, withdrawable_epoch=max( v.withdrawable_epoch, Epoch(current_epoch + epochs_per_slashings_vector)), )
def _set_validator_slashed(v: Validator, current_epoch: Epoch, epochs_per_slashings_vector: int) -> Validator: return v.mset( "slashed", True, "withdrawable_epoch", max(v.withdrawable_epoch, Epoch(current_epoch + epochs_per_slashings_vector)), )
async def _get_validator_duties(context: Context, request: Request) -> Response: if not isinstance(request, dict): return () if "validator_pubkeys" not in request: return () public_keys = tuple(map(decode_hex, request["validator_pubkeys"].split(","))) epoch = Epoch(int(request["epoch"])) duties = context.get_validator_duties(public_keys, epoch) return tuple(map(_marshal_duty, duties))
def process_attestation(self, validator_index: ValidatorIndex, block_root: Root, target_epoch: Epoch) -> None: if validator_index >= len(self.votes): self.votes.extend([ VoteTracker(default_root, default_root, Epoch(0)) for _ in range(validator_index - len(self.votes) + 1) ]) vote = self.votes[validator_index] if target_epoch > vote.next_epoch: vote.next_root = block_root vote.next_epoch = target_epoch
def _randao_provider_of_epoch_signature( public_key: BLSPubkey, epoch: Epoch ) -> BLSSignature: private_key = private_key_provider(public_key) # TODO: fix how we get the signing root message = Hash32(epoch.to_bytes(32, byteorder="big")) domain = Domain( b"\x00" * 4 + signature_domain_to_domain_type(SignatureDomain.DOMAIN_RANDAO) ) sig = bls.sign(message, private_key, domain) return sig
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 _compute_exit_queue_epoch(state: BeaconState, churn_limit: int, config: Eth2Config) -> Epoch: slots_per_epoch = config.SLOTS_PER_EPOCH exit_epochs = tuple(v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH) exit_queue_epoch = max(exit_epochs + (compute_activation_exit_epoch( state.current_epoch(slots_per_epoch), config.MAX_SEED_LOOKAHEAD), )) exit_queue_churn = len( tuple(v for v in state.validators if v.exit_epoch == exit_queue_epoch)) if exit_queue_churn >= churn_limit: exit_queue_epoch += 1 return Epoch(exit_queue_epoch)
def _compute_next_active_index_roots(state: BeaconState, config: Eth2Config) -> Tuple[Hash32, ...]: next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) index_root_position = (next_epoch + config.ACTIVATION_EXIT_DELAY ) % config.EPOCHS_PER_HISTORICAL_VECTOR validator_indices_for_new_active_index_root = get_active_validator_indices( state.validators, Epoch(next_epoch + config.ACTIVATION_EXIT_DELAY)) new_active_index_root = ssz.get_hash_tree_root( validator_indices_for_new_active_index_root, ssz.sedes.List(ssz.uint64, config.VALIDATOR_REGISTRY_LIMIT), ) return update_tuple_item(state.active_index_roots, index_root_position, new_active_index_root)
def generate_seed(state: 'BeaconState', epoch: Epoch, committee_config: CommitteeConfig) -> Hash32: """ Generate a seed for the given ``epoch``. """ randao_mix = get_randao_mix( state=state, epoch=Epoch(epoch - committee_config.MIN_SEED_LOOKAHEAD), slots_per_epoch=committee_config.SLOTS_PER_EPOCH, latest_randao_mixes_length=committee_config.LATEST_RANDAO_MIXES_LENGTH, ) active_index_root = get_active_index_root( state=state, epoch=epoch, slots_per_epoch=committee_config.SLOTS_PER_EPOCH, activation_exit_delay=committee_config.ACTIVATION_EXIT_DELAY, latest_active_index_roots_length=committee_config. LATEST_ACTIVE_INDEX_ROOTS_LENGTH, ) epoch_as_bytes = epoch.to_bytes(32, byteorder="little") return hash_eth2(randao_mix + active_index_root + epoch_as_bytes)
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__(token) self.genesis_time = chain.get_head_state().genesis_time 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.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)), ) self.get_ready_attestations: GetReadyAttestationsFn = get_ready_attestations_fn self.get_aggregatable_attestations: GetAggregatableAttestationsFn = get_aggregatable_attestations_fn # noqa: E501 self.import_attestation: ImportAttestationFn = import_attestation_fn # `state.eth1_data` can be updated in the middle of voting period and thus # the starting `eth1_data.block_hash` must be stored separately. self.starting_eth1_block_hash = chain.get_head_state( ).eth1_data.block_hash
def get_start_shard(state: BeaconState, epoch: Epoch, config: CommitteeConfig) -> Shard: current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) next_epoch = state.next_epoch(config.SLOTS_PER_EPOCH) if epoch > next_epoch: raise ValidationError("Asking for start shard for an epoch after next") check_epoch = int(next_epoch) shard = (state.start_shard + get_shard_delta(state, current_epoch, config)) % config.SHARD_COUNT while check_epoch > epoch: check_epoch -= 1 shard = (shard + config.SHARD_COUNT - get_shard_delta( state, Epoch(check_epoch), config)) % config.SHARD_COUNT return shard
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 _get_seed( state: "BeaconState", epoch: Epoch, domain_type: DomainType, randao_provider: RandaoProvider, epoch_provider: Callable[[Epoch], Hash32], committee_config: CommitteeConfig, ) -> Hash32: randao_mix = randao_provider( state, Epoch(epoch + committee_config.EPOCHS_PER_HISTORICAL_VECTOR - committee_config.MIN_SEED_LOOKAHEAD - 1), committee_config.EPOCHS_PER_HISTORICAL_VECTOR, ) epoch_as_bytes = epoch_provider(epoch) return hash_eth2(domain_type + epoch_as_bytes + randao_mix)
def initiate_exit_for_validator(validator: Validator, state: BeaconState, config: Eth2Config) -> Validator: """ Performs the mutations to ``validator`` used to initiate an exit. More convenient given our immutability patterns compared to ``initiate_validator_exit``. """ if validator.exit_epoch != FAR_FUTURE_EPOCH: return validator churn_limit = get_validator_churn_limit(state, config) exit_queue_epoch = _compute_exit_queue_epoch(state, churn_limit, config) return validator.copy( exit_epoch=exit_queue_epoch, withdrawable_epoch=Epoch(exit_queue_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY), )
def validate_randao_reveal(randao_reveal: BLSSignature, proposer_pubkey: BLSPubkey, epoch: Epoch, fork: Fork) -> None: message_hash = Hash32(epoch.to_bytes(32, byteorder="little")) domain = get_domain(fork, epoch, SignatureDomain.DOMAIN_RANDAO) is_randao_reveal_valid = bls.verify( pubkey=proposer_pubkey, message_hash=message_hash, signature=randao_reveal, domain=domain, ) if not is_randao_reveal_valid: raise ValidationError( f"RANDAO reveal is invalid. " f"reveal={randao_reveal}, proposer_pubkey={proposer_pubkey}, " f"message_hash={message_hash}, domain={domain}")
def _decoder( # NOTE: mypy incorrectly thinks `Field` is a generic type data: Dict[str, EncodedConfigTypes], fields: Collection[Field], # type: ignore ) -> Iterable[Tuple[str, ConfigTypes]]: # NOTE: this code is unwieldly but it satisfies `mypy` for field in fields: if field.type is Gwei: yield field.name, Gwei(cast(int, data[field.name])) elif field.type is Slot: yield field.name, Slot(cast(int, data[field.name])) elif field.type is Epoch: yield field.name, Epoch(cast(int, data[field.name])) elif field.type is Second: yield field.name, Second(cast(int, data[field.name])) elif field.type is bytes: yield field.name, decode_hex(cast(str, data[field.name])) else: yield field.name, int(data[field.name])
def _get_seed(state: 'BeaconState', epoch: Epoch, randao_provider: RandaoProvider, active_index_root_provider: ActiveIndexRootProvider, epoch_provider: Callable[[Epoch], Hash32], committee_config: CommitteeConfig) -> Hash32: randao_mix = randao_provider( state, Epoch(epoch + committee_config.EPOCHS_PER_HISTORICAL_VECTOR - committee_config.MIN_SEED_LOOKAHEAD - 1), committee_config.EPOCHS_PER_HISTORICAL_VECTOR, ) active_index_root = active_index_root_provider( state, epoch, committee_config.EPOCHS_PER_HISTORICAL_VECTOR, ) epoch_as_bytes = epoch_provider(epoch) return hash_eth2(randao_mix + active_index_root + epoch_as_bytes)
def _update_latest_active_index_roots( state: BeaconState, committee_config: CommitteeConfig) -> BeaconState: """ Return the BeaconState with updated `latest_active_index_roots`. """ next_epoch = state.next_epoch(committee_config.SLOTS_PER_EPOCH) # TODO: chanege to hash_tree_root active_validator_indices = get_active_validator_indices( state.validator_registry, Epoch(next_epoch + committee_config.ACTIVATION_EXIT_DELAY), ) index_root = hash_eth2(b''.join( [index.to_bytes(32, 'little') for index in active_validator_indices])) latest_active_index_roots = update_tuple_item( state.latest_active_index_roots, ((next_epoch + committee_config.ACTIVATION_EXIT_DELAY) % committee_config.LATEST_ACTIVE_INDEX_ROOTS_LENGTH), index_root, ) return state.copy(latest_active_index_roots=latest_active_index_roots, )
def _update_latest_active_index_roots( state: BeaconState, committee_config: CommitteeConfig) -> BeaconState: """ Return the BeaconState with updated `latest_active_index_roots`. """ next_epoch = state.next_epoch(committee_config.SLOTS_PER_EPOCH) active_validator_indices = get_active_validator_indices( state.validator_registry, Epoch(next_epoch + committee_config.ACTIVATION_EXIT_DELAY), ) index_root = ssz.hash_tree_root( active_validator_indices, ssz.sedes.List(ssz.uint64), ) latest_active_index_roots = update_tuple_item( state.latest_active_index_roots, ((next_epoch + committee_config.ACTIVATION_EXIT_DELAY) % committee_config.LATEST_ACTIVE_INDEX_ROOTS_LENGTH), index_root, ) return state.copy(latest_active_index_roots=latest_active_index_roots, )
def next_epoch(self, slots_per_epoch: int) -> Epoch: return Epoch(self.current_epoch(slots_per_epoch) + 1)
def _compute_epoch(self, slot: Slot) -> Epoch: return Epoch(slot // self._slots_per_epoch)
def compute_epoch_at_slot(slot: Slot, slots_per_epoch: int) -> Epoch: return Epoch(slot // slots_per_epoch)