def test_is_active(sample_validator_record_params, activation_epoch, exit_epoch, epoch, expected): validator_record_params = { **sample_validator_record_params, 'activation_epoch': activation_epoch, 'exit_epoch': exit_epoch, } validator = Validator(**validator_record_params) assert validator.is_active(epoch) == expected
def _process_activation_eligibility_or_ejections( state: BeaconState, validator: Validator, config: Eth2Config) -> Validator: current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) if (validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH and validator.effective_balance == config.MAX_EFFECTIVE_BALANCE): validator = validator.copy(activation_eligibility_epoch=current_epoch) if (validator.is_active(current_epoch) and validator.effective_balance <= config.EJECTION_BALANCE): validator = initiate_exit_for_validator(validator, state, config) return validator
def test_get_active_validator_indices(sample_validator_record_params): current_epoch = 1 # 3 validators are ACTIVE validators = tuple( Validator.create(**sample_validator_record_params).mset( "activation_epoch", 0, "exit_epoch", FAR_FUTURE_EPOCH ) for i in range(3) ) active_validator_indices = get_active_validator_indices(validators, current_epoch) assert len(active_validator_indices) == 3 # activation_epoch > current_epoch two_active_vals = update_tuple_item( validators, 0, validators[0].set("activation_epoch", current_epoch + 1) ) active_validator_indices = get_active_validator_indices( two_active_vals, current_epoch ) assert len(active_validator_indices) == 2 # current_epoch == exit_epoch one_active_val = update_tuple_item( two_active_vals, 1, validators[1].set("exit_epoch", current_epoch) ) active_validator_indices = get_active_validator_indices( one_active_val, current_epoch ) assert len(active_validator_indices) == 1
def test_get_active_validator_indices(sample_validator_record_params): current_epoch = 1 # 3 validators are ACTIVE validators = [ Validator.create(**sample_validator_record_params).mset( "activation_epoch", 0, "exit_epoch", FAR_FUTURE_EPOCH) for i in range(3) ] active_validator_indices = get_active_validator_indices( validators, current_epoch) assert len(active_validator_indices) == 3 validators[0] = validators[0].set( "activation_epoch", current_epoch + 1 # activation_epoch > current_epoch ) active_validator_indices = get_active_validator_indices( validators, current_epoch) assert len(active_validator_indices) == 2 validators[1] = validators[1].set( "exit_epoch", current_epoch # current_epoch == exit_epoch ) active_validator_indices = get_active_validator_indices( validators, current_epoch) assert len(active_validator_indices) == 1
def test_get_active_validator_indices(sample_validator_record_params): current_epoch = 1 # 3 validators are ACTIVE validators = [ Validator( **sample_validator_record_params, ).copy( activation_epoch=0, exit_epoch=FAR_FUTURE_EPOCH, ) for i in range(3) ] active_validator_indices = get_active_validator_indices(validators, current_epoch) assert len(active_validator_indices) == 3 validators[0] = validators[0].copy( activation_epoch=current_epoch + 1, # activation_epoch > current_epoch ) active_validator_indices = get_active_validator_indices(validators, current_epoch) assert len(active_validator_indices) == 2 validators[1] = validators[1].copy( exit_epoch=current_epoch, # current_epoch == exit_epoch ) active_validator_indices = get_active_validator_indices(validators, current_epoch) assert len(active_validator_indices) == 1
def genesis_validators(eth2_config, sample_bls_key_pairs): return tuple( Validator.create( pubkey=public_key, effective_balance=eth2_config.MAX_EFFECTIVE_BALANCE, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, ) for public_key in sample_bls_key_pairs)
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 activate_validator(validator: Validator, activation_epoch: Epoch) -> Validator: return validator.mset( "activation_eligibility_epoch", activation_epoch, "activation_epoch", activation_epoch, )
def _update_validator_activation_epoch(state: BeaconState, config: Eth2Config, validator: Validator) -> Validator: return validator.set( "activation_epoch", compute_activation_exit_epoch( state.current_epoch(config.SLOTS_PER_EPOCH), config.MAX_SEED_LOOKAHEAD), )
def process_deposit(state: BeaconState, deposit: Deposit, slots_per_epoch: int, deposit_contract_tree_depth: int) -> BeaconState: """ Process a deposit from Ethereum 1.0. """ validate_deposit(state, deposit, deposit_contract_tree_depth) # Increment the next deposit index we are expecting. Note that this # needs to be done here because while the deposit contract will never # create an invalid Merkle branch, it may admit an invalid deposit # object, and we need to be able to skip over it state = state.copy(deposit_index=state.deposit_index + 1, ) validator_pubkeys = tuple(v.pubkey for v in state.validator_registry) deposit_input = deposit.deposit_data.deposit_input pubkey = deposit_input.pubkey amount = deposit.deposit_data.amount withdrawal_credentials = deposit_input.withdrawal_credentials if pubkey not in validator_pubkeys: # Verify the proof of possession proof_is_valid = bls.verify( pubkey=pubkey, message_hash=deposit_input.signing_root, signature=deposit_input.signature, domain=get_domain( state.fork, state.current_epoch(slots_per_epoch), SignatureDomain.DOMAIN_DEPOSIT, ), ) if not proof_is_valid: return state validator = Validator.create_pending_validator( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, ) # Note: In phase 2 registry indices that has been withdrawn for a long time # will be recycled. state = add_pending_validator( state, validator, amount, ) else: # Top-up - increase balance by deposit index = ValidatorIndex(validator_pubkeys.index(pubkey)) validator = state.validator_registry[index] # Update validator's balance and state state = state.update_validator_balance( validator_index=index, balance=state.validator_balances[index] + amount, ) return state
def test_is_active(sample_validator_record_params, activation_epoch, exit_epoch, epoch, expected): validator_record_params = { **sample_validator_record_params, "activation_epoch": activation_epoch, "exit_epoch": exit_epoch, } validator = Validator.create(**validator_record_params) assert validator.is_active(epoch) == expected
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)), )
def test_process_registry_updates(validator_count, genesis_state, config, slots_per_epoch): eligible_index = len(genesis_state.validators) activation_index = len(genesis_state.validators) + 1 exiting_index = len(genesis_state.validators) - 1 eligible_validator = Validator.create_pending_validator( pubkey=b"\x10" * 48, withdrawal_credentials=b"\x11" * 32, amount=Gwei(32 * GWEI_PER_ETH), config=config, ) activating_validator = eligible_validator.set( "activation_eligibility_epoch", genesis_state.finalized_checkpoint.epoch) state = genesis_state.mset( "validators", genesis_state.validators[:exiting_index] + (genesis_state.validators[exiting_index].set( "effective_balance", config.EJECTION_BALANCE - 1), ) + (eligible_validator, activating_validator), "balances", genesis_state.balances + (config.MAX_EFFECTIVE_BALANCE, config.MAX_EFFECTIVE_BALANCE), ) # handles activations post_state = process_registry_updates(state, config) # Check if the eligible_validator is eligible pre_eligible_validator = state.validators[eligible_index] post_eligible_validator = post_state.validators[eligible_index] assert pre_eligible_validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH assert pre_eligible_validator.activation_epoch == FAR_FUTURE_EPOCH assert (post_eligible_validator.activation_eligibility_epoch == state.current_epoch(slots_per_epoch) + 1) # Check if the activating_validator is activated pre_activation_validator = state.validators[activation_index] post_activation_validator = post_state.validators[activation_index] assert pre_activation_validator.activation_epoch == FAR_FUTURE_EPOCH assert post_activation_validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH activation_epoch = compute_activation_exit_epoch( state.current_epoch(config.SLOTS_PER_EPOCH), config.MAX_SEED_LOOKAHEAD) assert post_activation_validator.is_active(activation_epoch) # Check if the exiting_validator is exited pre_exiting_validator = state.validators[exiting_index] post_exiting_validator = post_state.validators[exiting_index] assert pre_exiting_validator.exit_epoch == FAR_FUTURE_EPOCH assert pre_exiting_validator.withdrawable_epoch == FAR_FUTURE_EPOCH assert state.validators[ exiting_index].effective_balance <= config.EJECTION_BALANCE assert post_exiting_validator.exit_epoch != FAR_FUTURE_EPOCH assert post_exiting_validator.withdrawable_epoch != FAR_FUTURE_EPOCH assert post_exiting_validator.withdrawable_epoch > post_exiting_validator.exit_epoch
def validate_proposer_slashing_is_slashable(state: BeaconState, proposer: Validator, slots_per_epoch: int) -> None: current_epoch = state.current_epoch(slots_per_epoch) is_slashable = proposer.is_slashable(current_epoch) if not is_slashable: raise ValidationError( f"Proposer {encode_hex(proposer.pubkey)} is not slashable in epoch {current_epoch}." )
def _update_validator_activation_epoch(state: BeaconState, config: Eth2Config, validator: Validator) -> Validator: if validator.activation_epoch == FAR_FUTURE_EPOCH: return validator.copy(activation_epoch=compute_activation_exit_epoch( state.current_epoch(config.SLOTS_PER_EPOCH), config.ACTIVATION_EXIT_DELAY, )) else: return validator
def test_add_pending_validator(sample_beacon_state_params, sample_validator_record_params): validator_registry_len = 2 state = BeaconState(**sample_beacon_state_params).copy( validator_registry=[ Validator(**sample_validator_record_params) for _ in range(validator_registry_len) ], validator_balances=(100, ) * validator_registry_len, ) validator = Validator(**sample_validator_record_params) amount = 5566 state = add_pending_validator( state, validator, amount, ) assert state.validator_registry[-1] == validator
def _update_validator_activation_epoch( state: BeaconState, config: Eth2Config, validator: Validator ) -> Validator: if validator.activation_epoch == FAR_FUTURE_EPOCH: return validator.copy( activation_epoch=compute_activation_exit_epoch( state.current_epoch(config.SLOTS_PER_EPOCH), config.MAX_SEED_LOOKAHEAD ) ) else: return validator
def process_deposit(state: BeaconState, deposit: Deposit, config: Eth2Config) -> BeaconState: """ Process a deposit from Ethereum 1.0. """ validate_deposit_proof(state, deposit, DEPOSIT_CONTRACT_TREE_DEPTH) # Increment the next deposit index we are expecting. Note that this # needs to be done here because while the deposit contract will never # create an invalid Merkle branch, it may admit an invalid deposit # object, and we need to be able to skip over it state = state.copy( eth1_deposit_index=state.eth1_deposit_index + 1, ) pubkey = deposit.data.pubkey amount = deposit.data.amount validator_pubkeys = tuple(v.pubkey for v in state.validators) if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) for new validators. # Note: The deposit contract does not check signatures. # Note: Deposits are valid across forks, thus the deposit domain # is retrieved directly from `compute_domain`. is_valid_proof_of_possession = bls.verify( message_hash=deposit.data.signing_root, pubkey=pubkey, signature=deposit.data.signature, domain=compute_domain( SignatureDomain.DOMAIN_DEPOSIT, ), ) if not is_valid_proof_of_possession: return state withdrawal_credentials = deposit.data.withdrawal_credentials validator = Validator.create_pending_validator( pubkey, withdrawal_credentials, amount, config, ) return state.copy( validators=state.validators + (validator,), balances=state.balances + (amount, ), ) else: index = ValidatorIndex(validator_pubkeys.index(pubkey)) return increase_balance( state, index, amount, )
def create_mock_validator( pubkey: BLSPubkey, config: Eth2Config, withdrawal_credentials: Hash32 = ZERO_HASH32, is_active: bool = True, ) -> Validator: validator = Validator.create_pending_validator( pubkey, withdrawal_credentials, config.MAX_EFFECTIVE_BALANCE, config) if is_active: return activate_validator(validator, config.GENESIS_EPOCH) else: return validator
def mock_validator(pubkey: BLSPubkey, config: Eth2Config, withdrawal_credentials: Hash32 = ZERO_HASH32, is_active: bool = True) -> Validator: return Validator( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, activation_epoch=config.GENESIS_EPOCH if is_active else FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, initiated_exit=False, slashed=False, )
def test_create_pending_validator(): pubkey = 123 withdrawal_credentials = b'\x11' * 32 validator = Validator.create_pending_validator( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, ) assert validator.pubkey == pubkey assert validator.withdrawal_credentials == withdrawal_credentials assert validator.activation_epoch == FAR_FUTURE_EPOCH assert validator.exit_epoch == FAR_FUTURE_EPOCH assert validator.initiated_exit is False assert validator.slashed is False
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_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=exit_queue_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY, )
def test_process_registry_updates(validator_count, genesis_state, config, slots_per_epoch): activation_index = len(genesis_state.validators) exiting_index = len(genesis_state.validators) - 1 activating_validator = Validator.create_pending_validator( pubkey=b'\x10' * 48, withdrawal_credentials=b'\x11' * 32, amount=Gwei(32 * GWEI_PER_ETH), config=config, ) state = genesis_state.copy( validators=genesis_state.validators[:exiting_index] + (genesis_state.validators[exiting_index].copy( effective_balance=config.EJECTION_BALANCE - 1, ), ) + (activating_validator, ), balances=genesis_state.balances + (config.MAX_EFFECTIVE_BALANCE, ), ) # handles activations post_state = process_registry_updates(state, config) # Check if the activating_validator is activated pre_activation_validator = state.validators[activation_index] post_activation_validator = post_state.validators[activation_index] assert pre_activation_validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH assert pre_activation_validator.activation_epoch == FAR_FUTURE_EPOCH assert post_activation_validator.activation_eligibility_epoch != FAR_FUTURE_EPOCH activation_epoch = compute_activation_exit_epoch( state.current_epoch(config.SLOTS_PER_EPOCH), config.ACTIVATION_EXIT_DELAY, ) assert post_activation_validator.is_active(activation_epoch) # Check if the activating_validator is exited pre_exiting_validator = state.validators[exiting_index] post_exiting_validator = post_state.validators[exiting_index] assert pre_exiting_validator.exit_epoch == FAR_FUTURE_EPOCH assert pre_exiting_validator.withdrawable_epoch == FAR_FUTURE_EPOCH assert state.validators[ exiting_index].effective_balance <= config.EJECTION_BALANCE assert post_exiting_validator.exit_epoch != FAR_FUTURE_EPOCH assert post_exiting_validator.withdrawable_epoch != FAR_FUTURE_EPOCH assert post_exiting_validator.withdrawable_epoch > post_exiting_validator.exit_epoch
def test_create_pending_validator(config): pubkey = b"\x12" * 48 withdrawal_credentials = b"\x11" * 32 effective_balance = 22 * GWEI_PER_ETH amount = Gwei(effective_balance + config.EFFECTIVE_BALANCE_INCREMENT // 2) validator = Validator.create_pending_validator( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, config=config, ) assert validator.pubkey == pubkey assert validator.withdrawal_credentials == withdrawal_credentials assert validator.activation_epoch == FAR_FUTURE_EPOCH assert validator.exit_epoch == FAR_FUTURE_EPOCH assert validator.slashed is False assert validator.effective_balance == effective_balance
def test_defaults(sample_validator_record_params): validator = Validator.create(**sample_validator_record_params) assert validator.pubkey == sample_validator_record_params["pubkey"] assert (validator.withdrawal_credentials == sample_validator_record_params["withdrawal_credentials"] ) # noqa: E501
def test_defaults(sample_validator_record_params): validator = Validator(**sample_validator_record_params) assert validator.pubkey == sample_validator_record_params['pubkey'] assert validator.withdrawal_credentials == sample_validator_record_params[ 'withdrawal_credentials'] # noqa: E501
def _validate_validator_is_active(validator: Validator, target_epoch: Epoch) -> None: is_active = validator.is_active(target_epoch) if not is_active: raise ValidationError( f"Validator trying to exit in {target_epoch} is not active.")
def _mini_stf(state: BeaconState, block: Optional[BeaconBlock], config: Eth2Config) -> BeaconState: """ A simplified state transition for testing state storage. - updates ``state_roots`` with the previous slot's state root - updates ``block_roots`` with the previous slot's block root - updates ``randao_mixes`` with an arbitrary mix at the current epoch - creates a new ``latest_block_header`` and adds it to the state - fills in the rest of the attributes with arbitrary values """ current_slot = state.slot + 1 current_epoch = current_slot // config.SLOTS_PER_EPOCH if block: latest_block_header = block.header else: latest_block_header = state.latest_block_header # state changes that depend on the previous state for retrieval randao_mix = Root(Hash32(current_slot.to_bytes(32, byteorder="little"))) state = (state.transform( ("state_roots", state.slot % config.SLOTS_PER_HISTORICAL_ROOT), state.hash_tree_root, ).transform( ("block_roots", state.slot % config.SLOTS_PER_HISTORICAL_ROOT), state.latest_block_header.hash_tree_root, ).transform( ("randao_mixes", current_epoch % config.EPOCHS_PER_HISTORICAL_VECTOR), randao_mix, ).mset("slot", current_slot, "latest_block_header", latest_block_header)) # state changes that do not depend on the previous state for retrieval new_validators = [ Validator.create(pubkey=BLSPubkey(n.to_bytes(48, byteorder="little"))) for n in range(current_slot, current_slot + 20) ] new_eth1_data_votes = [ Eth1Data.create( deposit_root=Root(Hash32(n.to_bytes(32, byteorder="little")))) for n in range(current_slot, current_slot + 7) ] new_previous_epoch_attestations = [ PendingAttestation.create(proposer_index=ValidatorIndex(n)) for n in range(current_slot, current_slot + 5) ] new_current_epoch_attestations = [ PendingAttestation.create(proposer_index=ValidatorIndex(n)) for n in range(current_slot + 5, current_slot + 10) ] state = state.mset( "validators", new_validators, "balances", (32, ) * len(new_validators), "eth1_data_votes", new_eth1_data_votes, "eth1_data", new_eth1_data_votes[0], "previous_epoch_attestations", new_previous_epoch_attestations, "current_epoch_attestations", new_current_epoch_attestations, "previous_justified_checkpoint", Checkpoint.create(epoch=Epoch(current_slot + 42)), "current_justified_checkpoint", Checkpoint.create(epoch=Epoch(current_slot + 43)), "finalized_checkpoint", Checkpoint.create(epoch=Epoch(current_slot + 44)), ) return state
def _set_validator_slashed(v: Validator, withdrawable_epoch: Epoch) -> Validator: return v.copy( slashed=True, withdrawable_epoch=withdrawable_epoch, )
def activate_validator(validator: Validator, activation_epoch: Epoch) -> Validator: return validator.copy( activation_eligibility_epoch=activation_epoch, activation_epoch=activation_epoch, )