def get_crosslink_deltas( state: BeaconState, config: Eth2Config) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: rewards = tuple(0 for _ in range(len(state.validators))) penalties = tuple(0 for _ in range(len(state.validators))) epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) active_validators_indices = get_active_validator_indices( state.validators, epoch) epoch_committee_count = get_committee_count( len(active_validators_indices), config.SHARD_COUNT, config.SLOTS_PER_EPOCH, config.TARGET_COMMITTEE_SIZE, ) epoch_start_shard = get_start_shard( state, epoch, CommitteeConfig(config), ) for shard_offset in range(epoch_committee_count): shard = Shard((epoch_start_shard + shard_offset) % config.SHARD_COUNT) crosslink_committee = set( get_crosslink_committee( state, epoch, shard, CommitteeConfig(config), )) _, attesting_indices = get_winning_crosslink_and_attesting_indices( state=state, epoch=epoch, shard=shard, config=config, ) total_attesting_balance = get_total_balance( state, attesting_indices, ) total_committee_balance = get_total_balance( state, crosslink_committee, ) for index in crosslink_committee: base_reward = get_base_reward(state, index, config) if index in attesting_indices: rewards = update_tuple_item_with_fn( rewards, index, lambda balance, delta: balance + delta, base_reward * total_attesting_balance // total_committee_balance) else: penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, base_reward, ) return tuple(Gwei(reward) for reward in rewards), tuple( Gwei(penalty) for penalty in penalties)
def test_process_rewards_and_penalties_for_crosslinks( genesis_state, config, slots_per_epoch, target_committee_size, shard_count, current_slot, num_attesting_validators, max_effective_balance, min_attestation_inclusion_delay, sample_attestation_data_params, sample_pending_attestation_record_params): state = genesis_state.copy(slot=current_slot, ) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) prev_epoch_start_slot = compute_start_slot_of_epoch( previous_epoch, slots_per_epoch) prev_epoch_crosslink_committees = [ get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), )[0] for slot in range(prev_epoch_start_slot, prev_epoch_start_slot + slots_per_epoch) ] # Record which validators attest during each slot for reward collation. each_slot_attestion_validators_list = [] epoch_start_shard = get_start_shard( state, previous_epoch, CommitteeConfig(config), ) shard_delta = get_shard_delta( state, previous_epoch, CommitteeConfig(config), ) a = epoch_start_shard b = epoch_start_shard + shard_delta if a > b: valid_shards_for_epoch = range(b, a) else: valid_shards_for_epoch = range(a, b) indices_to_check = set() previous_epoch_attestations = [] for committee, shard in prev_epoch_crosslink_committees: if shard not in valid_shards_for_epoch: continue for index in committee: indices_to_check.add(index) # Randomly sample `num_attesting_validators` validators # from the committee to attest in this slot. crosslink_attesting_validators = random.sample( committee, num_attesting_validators, ) each_slot_attestion_validators_list.append( crosslink_attesting_validators) participants_bitfield = get_empty_bitfield(len(committee)) for index in crosslink_attesting_validators: participants_bitfield = set_voted(participants_bitfield, committee.index(index)) previous_epoch_attestations.append( PendingAttestation( **sample_pending_attestation_record_params).copy( aggregation_bits=participants_bitfield, data=AttestationData( **sample_attestation_data_params).copy( target=Checkpoint(epoch=previous_epoch, ), crosslink=Crosslink( shard=shard, parent_root=Crosslink().hash_tree_root, ), ), )) state = state.copy( previous_epoch_attestations=tuple(previous_epoch_attestations), ) rewards_received, penalties_received = get_crosslink_deltas( state, config, ) expected_rewards_received = { index: 0 for index in range(len(state.validators)) } validator_balance = max_effective_balance for i in range(slots_per_epoch): crosslink_committee, shard = prev_epoch_crosslink_committees[i] if shard not in valid_shards_for_epoch: continue attesting_validators = each_slot_attestion_validators_list[i] total_attesting_balance = len(attesting_validators) * validator_balance total_committee_balance = len(crosslink_committee) * validator_balance for index in crosslink_committee: if index in attesting_validators: reward = get_base_reward( state=state, index=index, config=config, ) * total_attesting_balance // total_committee_balance expected_rewards_received[index] += reward else: penalty = get_base_reward( state=state, index=index, config=config, ) expected_rewards_received[index] -= penalty # Check the rewards/penalties match for index in range(len(state.validators)): if index not in indices_to_check: continue assert (rewards_received[index] - penalties_received[index] == expected_rewards_received[index])
def get_attestation_deltas( state: BeaconState, config: Eth2Config) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: rewards = tuple(0 for _ in range(len(state.validators))) penalties = tuple(0 for _ in range(len(state.validators))) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH) total_balance = get_total_active_balance(state, config) eligible_validator_indices = tuple( ValidatorIndex(index) for index, v in enumerate(state.validators) if v.is_active(previous_epoch) or ( v.slashed and previous_epoch + 1 < v.withdrawable_epoch)) matching_source_attestations = get_matching_source_attestations( state, previous_epoch, config) matching_target_attestations = get_matching_target_attestations( state, previous_epoch, config) matching_head_attestations = get_matching_head_attestations( state, previous_epoch, config) increment = config.EFFECTIVE_BALANCE_INCREMENT total_balance_in_increment = total_balance // increment for attestations in ( matching_source_attestations, matching_target_attestations, matching_head_attestations, ): unslashed_attesting_indices = get_unslashed_attesting_indices( state, attestations, config) attesting_balance = get_total_balance(state, unslashed_attesting_indices, config) attesting_balance_in_increment = attesting_balance // increment for index in eligible_validator_indices: if index in unslashed_attesting_indices: if is_in_inactivity_leak(state, config): reward = get_base_reward(state, index, config) else: reward = Gwei((get_base_reward(state, index, config) * attesting_balance_in_increment) // total_balance_in_increment) rewards = update_tuple_item_with_fn( rewards, index, lambda balance, delta: balance + delta, reward) else: penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, get_base_reward(state, index, config), ) for index in get_unslashed_attesting_indices(state, matching_source_attestations, config): attestation = min( (a for a in matching_source_attestations if index in get_attesting_indices(state, a.data, a.aggregation_bits, config)), key=lambda a: a.inclusion_delay, ) proposer_reward = get_proposer_reward(state, index, config) rewards = update_tuple_item_with_fn( rewards, attestation.proposer_index, lambda balance, delta: balance + delta, proposer_reward, ) base_reward = get_base_reward(state, index, config) max_attester_reward = base_reward - proposer_reward rewards = update_tuple_item_with_fn( rewards, index, lambda balance, delta: balance + delta, (max_attester_reward // attestation.inclusion_delay), ) if is_in_inactivity_leak(state, config): matching_target_attesting_indices = get_unslashed_attesting_indices( state, matching_target_attestations, config) for index in eligible_validator_indices: base_reward = get_base_reward(state, index, config) penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, BASE_REWARDS_PER_EPOCH * base_reward - get_proposer_reward(state, index, config), ) if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, effective_balance * get_finality_delay(state, config.SLOTS_PER_EPOCH) // config.INACTIVITY_PENALTY_QUOTIENT, ) return ( tuple(Gwei(reward) for reward in rewards), tuple(Gwei(penalty) for penalty in penalties), )
def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex, config: Eth2Config) -> Gwei: return Gwei( get_base_reward(state, attesting_index, config) // config.PROPOSER_REWARD_QUOTIENT)
def get_attestation_deltas(state: BeaconState, config: Eth2Config) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: committee_config = CommitteeConfig(config) rewards = tuple( 0 for _ in range(len(state.validators)) ) penalties = tuple( 0 for _ in range(len(state.validators)) ) previous_epoch = state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH) total_balance = get_total_active_balance(state, config) eligible_validator_indices = tuple( ValidatorIndex(index) for index, v in enumerate(state.validators) if v.is_active(previous_epoch) or ( v.slashed and previous_epoch + 1 < v.withdrawable_epoch ) ) matching_source_attestations = get_matching_source_attestations( state, previous_epoch, config, ) matching_target_attestations = get_matching_target_attestations( state, previous_epoch, config, ) matching_head_attestations = get_matching_head_attestations( state, previous_epoch, config, ) for attestations in ( matching_source_attestations, matching_target_attestations, matching_head_attestations ): unslashed_attesting_indices = get_unslashed_attesting_indices( state, attestations, committee_config, ) attesting_balance = get_total_balance(state, unslashed_attesting_indices) for index in eligible_validator_indices: if index in unslashed_attesting_indices: rewards = update_tuple_item_with_fn( rewards, index, lambda balance, delta: balance + delta, get_base_reward( state, index, config, ) * attesting_balance // total_balance, ) else: penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, get_base_reward( state, index, config, ), ) for index in get_unslashed_attesting_indices( state, matching_source_attestations, committee_config, ): attestation = min( ( a for a in matching_source_attestations if index in get_attesting_indices( state, a.data, a.aggregation_bitfield, committee_config, ) ), key=lambda a: a.inclusion_delay, ) base_reward = get_base_reward(state, index, config) proposer_reward = base_reward // config.PROPOSER_REWARD_QUOTIENT rewards = update_tuple_item_with_fn( rewards, attestation.proposer_index, lambda balance, delta: balance + delta, proposer_reward, ) max_attester_reward = base_reward - proposer_reward rewards = update_tuple_item_with_fn( rewards, index, lambda balance, delta: balance + delta, ( max_attester_reward * config.MIN_ATTESTATION_INCLUSION_DELAY // attestation.inclusion_delay ) ) finality_delay = previous_epoch - state.finalized_epoch if finality_delay > config.MIN_EPOCHS_TO_INACTIVITY_PENALTY: matching_target_attesting_indices = get_unslashed_attesting_indices( state, matching_target_attestations, committee_config, ) for index in eligible_validator_indices: penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, BASE_REWARDS_PER_EPOCH * get_base_reward( state, index, config, ), ) if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance penalties = update_tuple_item_with_fn( penalties, index, lambda balance, delta: balance + delta, effective_balance * finality_delay // config.INACTIVITY_PENALTY_QUOTIENT, ) return tuple( Gwei(reward) for reward in rewards ), tuple( Gwei(penalty) for penalty in penalties )
def test_get_base_reward(genesis_state, config): assert get_base_reward(genesis_state, 0, config) == 905097
def process_rewards_and_penalties(state: BeaconState, config: BeaconConfig) -> BeaconState: # Compute previous epoch active validator indices and the total balance they account for # for later use. previous_epoch_active_validator_indices = set( get_active_validator_indices( state.validator_registry, state.previous_epoch(config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH))) previous_total_balance: Gwei = get_total_balance( state.validator_balances, tuple(previous_epoch_active_validator_indices), config.MAX_DEPOSIT_AMOUNT, ) # Compute previous epoch attester indices and the total balance they account for # for later use. previous_epoch_attestations = get_previous_epoch_attestations( state, config.SLOTS_PER_EPOCH, config.GENESIS_EPOCH, ) previous_epoch_attester_indices = get_attester_indices_from_attesttion( state=state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) # Compute inclusion slot/distance of previous attestations for later use. inclusion_infos = get_inclusion_infos( state=state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) # Compute effective balance of each previous epoch active validator for later use effective_balances = { index: get_effective_balance( state.validator_balances, index, config.MAX_DEPOSIT_AMOUNT, ) for index in previous_epoch_active_validator_indices } # Compute base reward of each previous epoch active validator for later use _base_reward_quotient = (integer_squareroot(previous_total_balance) // config.BASE_REWARD_QUOTIENT) base_rewards = { index: get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, ) for index in previous_epoch_active_validator_indices } # Initialize the reward (validator) received map rewards_received = { index: SignedGwei(0) for index in previous_epoch_active_validator_indices } # 1. Process rewards and penalties for justification and finalization rewards_received = pipe( rewards_received, _process_rewards_and_penalties_for_finality( state, config, previous_epoch_active_validator_indices, previous_total_balance, previous_epoch_attestations, previous_epoch_attester_indices, inclusion_infos, effective_balances, base_rewards, ), _process_rewards_and_penalties_for_attestation_inclusion( state, config, previous_epoch_attester_indices, inclusion_infos, base_rewards, ), _process_rewards_and_penalties_for_crosslinks( state, config, previous_epoch_attestations, effective_balances, base_rewards, )) # Apply the overall rewards/penalties for index in previous_epoch_active_validator_indices: state = state.update_validator_balance( index, # Prevent validator balance under flow max(state.validator_balances[index] + rewards_received[index], 0), ) return state
def test_process_rewards_and_penalties_for_crosslinks( random, n_validators_state, config, slots_per_epoch, target_committee_size, shard_count, current_slot, num_attesting_validators, max_deposit_amount, min_attestation_inclusion_delay, sample_attestation_data_params, sample_pending_attestation_record_params): previous_epoch = current_slot // slots_per_epoch - 1 state = n_validators_state.copy( slot=current_slot, ) # Compute previous epoch committees prev_epoch_start_slot = get_epoch_start_slot(previous_epoch, slots_per_epoch) prev_epoch_crosslink_committees = [ get_crosslink_committees_at_slot( state, slot, CommitteeConfig(config), )[0] for slot in range(prev_epoch_start_slot, prev_epoch_start_slot + slots_per_epoch) ] # Record which validators attest during each slot for reward collation. each_slot_attestion_validators_list = [] previous_epoch_attestations = [] for i in range(slots_per_epoch): committee, shard = prev_epoch_crosslink_committees[i] # Randomly sample `num_attesting_validators` validators # from the committee to attest in this slot. crosslink_data_root_attesting_validators = random.sample( committee, num_attesting_validators, ) each_slot_attestion_validators_list.append(crosslink_data_root_attesting_validators) participants_bitfield = get_empty_bitfield(target_committee_size) for index in crosslink_data_root_attesting_validators: participants_bitfield = set_voted(participants_bitfield, committee.index(index)) data_slot = i + previous_epoch * slots_per_epoch previous_epoch_attestations.append( PendingAttestationRecord(**sample_pending_attestation_record_params).copy( data=AttestationData(**sample_attestation_data_params).copy( slot=data_slot, shard=shard, ), aggregation_bitfield=participants_bitfield, slot_included=(data_slot + min_attestation_inclusion_delay), ) ) active_validators = set( [ i for i in range(len(state.validator_registry)) ] ) effective_balances = { index: get_effective_balance( state.validator_balances, index, config.MAX_DEPOSIT_AMOUNT, ) for index in active_validators } validator_balance = max_deposit_amount total_active_balance = len(active_validators) * validator_balance _base_reward_quotient = ( integer_squareroot(total_active_balance) // config.BASE_REWARD_QUOTIENT ) base_rewards = { index: get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=max_deposit_amount, ) for index in active_validators } rewards_received = { index: 0 for index in range(len(state.validator_registry)) } rewards_received = _process_rewards_and_penalties_for_crosslinks( state, config, tuple(previous_epoch_attestations), effective_balances, base_rewards, rewards_received, ) expected_rewards_received = { index: 0 for index in range(len(state.validator_registry)) } for i in range(slots_per_epoch): crosslink_committee, shard = prev_epoch_crosslink_committees[i] attesting_validators = each_slot_attestion_validators_list[i] total_attesting_balance = len(attesting_validators) * validator_balance total_committee_balance = len(crosslink_committee) * validator_balance _base_reward_quotient = ( integer_squareroot(total_active_balance) // config.BASE_REWARD_QUOTIENT ) for index in attesting_validators: reward = get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=max_deposit_amount, ) * total_attesting_balance // total_committee_balance expected_rewards_received[index] += reward for index in set(crosslink_committee).difference(attesting_validators): penalty = get_base_reward( state=state, index=index, base_reward_quotient=_base_reward_quotient, max_deposit_amount=max_deposit_amount, ) expected_rewards_received[index] -= penalty # Check the rewards/penalties match for index, reward_received in rewards_received.items(): assert rewards_received[index] == expected_rewards_received[index]
def process_rewards_and_penalties(state: BeaconState, config: Eth2Config) -> BeaconState: # Compute previous epoch active validator indices and the total balance they account for # for later use. previous_epoch_active_validator_indices = set( get_active_validator_indices( state.validator_registry, state.previous_epoch(config.SLOTS_PER_EPOCH))) previous_total_balance: Gwei = get_total_balance( state.validator_balances, tuple(previous_epoch_active_validator_indices), config.MAX_DEPOSIT_AMOUNT, ) # Compute previous epoch attester indices and the total balance they account for # for later use. previous_epoch_attestations = state.previous_epoch_attestations previous_epoch_attester_indices = get_attester_indices_from_attestations( state=state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) # Compute inclusion slot/distance of previous attestations for later use. inclusion_infos = get_inclusion_infos( state=state, attestations=previous_epoch_attestations, committee_config=CommitteeConfig(config), ) # Compute effective balance of each previous epoch active validator for later use effective_balances = { ValidatorIndex(index): get_effective_balance( state.validator_balances, ValidatorIndex(index), config.MAX_DEPOSIT_AMOUNT, ) for index in range(len(state.validator_registry)) } # Compute base reward of each previous epoch active validator for later use base_rewards = { ValidatorIndex(index): get_base_reward( state=state, index=ValidatorIndex(index), base_reward_quotient=config.BASE_REWARD_QUOTIENT, previous_total_balance=previous_total_balance, max_deposit_amount=config.MAX_DEPOSIT_AMOUNT, ) for index in range(len(state.validator_registry)) } # 1. Process rewards and penalties for justification and finalization finality_rewards, finality_penalties = _process_rewards_and_penalties_for_finality( state, config, previous_epoch_active_validator_indices, previous_total_balance, previous_epoch_attestations, previous_epoch_attester_indices, inclusion_infos, effective_balances, base_rewards, ) # 2. Process rewards and penalties for crosslinks crosslinks_rewards, crosslinks_penalties = _process_rewards_and_penalties_for_crosslinks( state, config, effective_balances, base_rewards, ) # Apply the overall rewards/penalties for index in range(len(state.validator_registry)): state = state.update_validator_balance( ValidatorIndex(index), # Prevent validator balance under flow max( (state.validator_balances[index] + finality_rewards[index] + crosslinks_rewards[index] - finality_penalties[index] - crosslinks_penalties[index]), 0, ), ) return state