2018-11-23 22:44:43 +00:00
|
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
|
|
|
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
|
|
|
# Helpers and functions pertaining to managing the validator set
|
|
|
|
|
2018-11-23 19:42:47 +00:00
|
|
|
import
|
2018-12-04 18:45:30 +00:00
|
|
|
options, nimcrypto, sequtils, math,
|
2018-11-27 23:10:09 +00:00
|
|
|
eth_common,
|
2018-12-03 21:41:24 +00:00
|
|
|
../ssz,
|
2018-11-28 19:49:03 +00:00
|
|
|
./crypto, ./datatypes, ./digest, ./helpers
|
2018-11-23 19:42:47 +00:00
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
func is_active_validator*(validator: ValidatorRecord): bool =
|
|
|
|
validator.status in {ACTIVE, ACTIVE_PENDING_EXIT}
|
|
|
|
|
2018-12-03 17:46:22 +00:00
|
|
|
func min_empty_validator_index(validators: seq[ValidatorRecord], current_slot: uint64): Option[int] =
|
2018-11-23 19:42:47 +00:00
|
|
|
for i, v in validators:
|
2018-12-03 21:41:24 +00:00
|
|
|
if v.balance == 0 and
|
2018-12-06 02:07:04 +00:00
|
|
|
v.latest_status_change_slot +
|
|
|
|
ZERO_BALANCE_VALIDATOR_TTL.uint64 <= current_slot:
|
2018-12-03 21:41:24 +00:00
|
|
|
return some(i)
|
2018-11-23 19:42:47 +00:00
|
|
|
|
2018-11-29 05:23:40 +00:00
|
|
|
func get_new_validators*(current_validators: seq[ValidatorRecord],
|
|
|
|
fork_data: ForkData,
|
|
|
|
pubkey: ValidatorPubKey,
|
2018-12-03 17:46:22 +00:00
|
|
|
deposit: uint64,
|
2018-12-11 17:55:45 +00:00
|
|
|
proof_of_possession: ValidatorSig,
|
2018-11-29 05:23:40 +00:00
|
|
|
withdrawal_credentials: Eth2Digest,
|
|
|
|
randao_commitment: Eth2Digest,
|
|
|
|
status: ValidatorStatusCodes,
|
|
|
|
current_slot: uint64
|
|
|
|
): tuple[validators: seq[ValidatorRecord], index: int] =
|
2018-11-29 22:11:05 +00:00
|
|
|
# TODO Spec candidate: inefficient API
|
2018-11-29 18:18:12 +00:00
|
|
|
#
|
2018-11-23 19:42:47 +00:00
|
|
|
# Check that validator really did register
|
2018-12-11 17:55:45 +00:00
|
|
|
# TODO fix tests and enable (nightmare)
|
2018-12-11 21:53:18 +00:00
|
|
|
# let msg = hash_tree_root((pubkey, withdrawal_credentials, randao_commitment))
|
2018-12-11 17:55:45 +00:00
|
|
|
# assert BLSVerify(
|
|
|
|
# pubkey, msg, proof_of_possession,
|
|
|
|
# get_domain(fork_data, current_slot, DOMAIN_DEPOSIT))
|
|
|
|
|
|
|
|
var validators_copy = current_validators
|
|
|
|
|
|
|
|
for index, validator in validators_copy.mpairs():
|
|
|
|
if validator.pubkey == pubkey:
|
|
|
|
assert validator.withdrawal_credentials == withdrawal_credentials
|
|
|
|
|
|
|
|
validator.balance += deposit
|
|
|
|
return (validators_copy, index)
|
2018-11-29 05:23:40 +00:00
|
|
|
|
|
|
|
# new validator
|
2018-11-23 19:42:47 +00:00
|
|
|
let
|
|
|
|
rec = ValidatorRecord(
|
|
|
|
pubkey: pubkey,
|
2018-11-28 04:23:27 +00:00
|
|
|
withdrawal_credentials: withdrawal_credentials,
|
2018-11-23 19:42:47 +00:00
|
|
|
randao_commitment: randao_commitment,
|
2018-11-28 04:23:27 +00:00
|
|
|
randao_skips: 0,
|
2018-12-03 17:46:22 +00:00
|
|
|
balance: deposit,
|
2018-11-23 19:42:47 +00:00
|
|
|
status: status,
|
2018-12-03 21:41:24 +00:00
|
|
|
latest_status_change_slot: current_slot,
|
|
|
|
exit_count: 0
|
2018-11-23 19:42:47 +00:00
|
|
|
)
|
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
let index = min_empty_validator_index(validators_copy, current_slot)
|
2018-11-23 19:42:47 +00:00
|
|
|
if index.isNone:
|
2018-12-11 17:55:45 +00:00
|
|
|
validators_copy.add(rec)
|
|
|
|
(validators_copy, len(validators_copy) - 1)
|
2018-11-23 19:42:47 +00:00
|
|
|
else:
|
2018-12-11 17:55:45 +00:00
|
|
|
validators_copy[index.get()] = rec
|
|
|
|
(validators_copy, index.get())
|
2018-11-23 22:44:43 +00:00
|
|
|
|
2018-11-29 05:23:40 +00:00
|
|
|
func get_active_validator_indices*(validators: openArray[ValidatorRecord]): seq[Uint24] =
|
2018-11-23 22:44:43 +00:00
|
|
|
## Select the active validators
|
|
|
|
for idx, val in validators:
|
2018-12-11 17:55:45 +00:00
|
|
|
if is_active_validator(val):
|
2018-11-23 22:44:43 +00:00
|
|
|
result.add idx.Uint24
|
|
|
|
|
2018-11-27 23:10:09 +00:00
|
|
|
func get_new_shuffling*(seed: Eth2Digest,
|
2018-11-23 22:44:43 +00:00
|
|
|
validators: openArray[ValidatorRecord],
|
2018-12-04 18:45:30 +00:00
|
|
|
crosslinking_start_shard: uint64
|
2018-12-06 02:07:04 +00:00
|
|
|
): array[EPOCH_LENGTH, seq[ShardCommittee]] =
|
2018-11-23 22:44:43 +00:00
|
|
|
## Split up validators into groups at the start of every epoch,
|
|
|
|
## determining at what height they can make attestations and what shard they are making crosslinks for
|
|
|
|
## Implementation should do the following: http://vitalik.ca/files/ShuffleAndAssign.png
|
|
|
|
|
|
|
|
let
|
2018-12-06 02:07:04 +00:00
|
|
|
active_validator_indices = get_active_validator_indices(validators)
|
2018-11-23 22:44:43 +00:00
|
|
|
committees_per_slot = clamp(
|
2018-12-06 02:07:04 +00:00
|
|
|
len(active_validator_indices) div EPOCH_LENGTH div TARGET_COMMITTEE_SIZE,
|
2018-12-04 18:45:30 +00:00
|
|
|
1, SHARD_COUNT div EPOCH_LENGTH).uint64
|
2018-11-23 22:44:43 +00:00
|
|
|
# Shuffle with seed
|
2018-12-06 02:07:04 +00:00
|
|
|
shuffled_active_validator_indices = shuffle(active_validator_indices, seed)
|
2018-11-23 22:44:43 +00:00
|
|
|
# Split the shuffled list into cycle_length pieces
|
2018-12-03 17:46:22 +00:00
|
|
|
validators_per_slot = split(shuffled_active_validator_indices, EPOCH_LENGTH)
|
2018-11-23 22:44:43 +00:00
|
|
|
|
2018-12-03 17:46:22 +00:00
|
|
|
assert validators_per_slot.len() == EPOCH_LENGTH # what split should do..
|
2018-11-23 22:44:43 +00:00
|
|
|
|
|
|
|
for slot, slot_indices in validators_per_slot:
|
|
|
|
let
|
|
|
|
shard_indices = split(slot_indices, committees_per_slot)
|
2018-12-04 18:45:30 +00:00
|
|
|
shard_id_start =
|
|
|
|
crosslinking_start_shard + slot.uint64 * committees_per_slot
|
2018-11-23 22:44:43 +00:00
|
|
|
|
2018-12-06 02:07:04 +00:00
|
|
|
var committees = newSeq[ShardCommittee](shard_indices.len)
|
2018-11-23 22:44:43 +00:00
|
|
|
for shard_position, indices in shard_indices:
|
2018-11-29 05:23:40 +00:00
|
|
|
committees[shard_position].shard =
|
2018-12-11 17:55:45 +00:00
|
|
|
shard_id_start + shard_position.uint64 mod SHARD_COUNT.uint64
|
2018-11-23 22:44:43 +00:00
|
|
|
committees[shard_position].committee = indices
|
2018-12-06 02:07:04 +00:00
|
|
|
committees[shard_position].total_validator_count =
|
|
|
|
len(active_validator_indices).uint64
|
2018-11-23 22:44:43 +00:00
|
|
|
|
|
|
|
result[slot] = committees
|
2018-12-03 21:41:24 +00:00
|
|
|
|
|
|
|
func get_new_validator_registry_delta_chain_tip(
|
|
|
|
current_validator_registry_delta_chain_tip: Eth2Digest,
|
|
|
|
index: Uint24,
|
|
|
|
pubkey: ValidatorPubKey,
|
|
|
|
flag: ValidatorSetDeltaFlags): Eth2Digest =
|
|
|
|
## Compute the next hash in the validator registry delta hash chain.
|
|
|
|
|
|
|
|
withEth2Hash:
|
2018-12-11 21:53:18 +00:00
|
|
|
h.update hash_tree_root(current_validator_registry_delta_chain_tip)
|
|
|
|
h.update hash_tree_root(flag.uint8)
|
|
|
|
h.update hash_tree_root(index)
|
|
|
|
h.update hash_tree_root(pubkey)
|
2018-12-03 21:41:24 +00:00
|
|
|
|
|
|
|
func get_effective_balance*(validator: ValidatorRecord): uint64 =
|
2018-12-11 17:55:45 +00:00
|
|
|
min(validator.balance, MAX_DEPOSIT)
|
2018-12-03 21:41:24 +00:00
|
|
|
|
|
|
|
func exit_validator*(index: Uint24,
|
|
|
|
state: var BeaconState,
|
2018-12-11 17:55:45 +00:00
|
|
|
new_status: ValidatorStatusCodes) =
|
2018-12-03 21:41:24 +00:00
|
|
|
## Remove the validator with the given `index` from `state`.
|
|
|
|
## Note that this function mutates `state`.
|
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
state.validator_registry_exit_count += 1
|
2018-12-03 21:41:24 +00:00
|
|
|
|
|
|
|
var
|
2018-12-11 17:55:45 +00:00
|
|
|
validator = addr state.validator_registry[index]
|
2018-12-03 21:41:24 +00:00
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
validator.status = new_status
|
|
|
|
validator.latest_status_change_slot = state.slot
|
2018-12-03 21:41:24 +00:00
|
|
|
validator.exit_count = state.validator_registry_exit_count
|
|
|
|
|
|
|
|
# Remove validator from persistent committees
|
|
|
|
for committee in state.persistent_committees.mitems():
|
|
|
|
for i, validator_index in committee:
|
|
|
|
if validator_index == index:
|
|
|
|
committee.delete(i)
|
|
|
|
break
|
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
if new_status == EXITED_WITH_PENALTY:
|
2018-12-03 21:41:24 +00:00
|
|
|
state.latest_penalized_exit_balances[
|
2018-12-11 17:55:45 +00:00
|
|
|
(state.slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int] +=
|
|
|
|
get_effective_balance(validator[])
|
2018-12-03 21:41:24 +00:00
|
|
|
|
|
|
|
var
|
2018-12-11 17:55:45 +00:00
|
|
|
whistleblower = addr state.validator_registry[
|
|
|
|
get_beacon_proposer_index(state, state.slot)]
|
2018-12-03 21:41:24 +00:00
|
|
|
whistleblower_reward =
|
2018-12-11 17:55:45 +00:00
|
|
|
validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT
|
|
|
|
|
|
|
|
whistleblower.balance += whistleblower_reward
|
|
|
|
validator.balance -= whistleblower_reward
|
2018-12-03 21:41:24 +00:00
|
|
|
|
|
|
|
state.validator_registry_delta_chain_tip =
|
|
|
|
get_new_validator_registry_delta_chain_tip(
|
2018-12-11 17:55:45 +00:00
|
|
|
state.validator_registry_delta_chain_tip, index, validator.pubkey,
|
|
|
|
ValidatorSetDeltaFlags.EXIT)
|
|
|
|
|
|
|
|
func get_updated_validator_registry*(
|
|
|
|
validator_registry: seq[ValidatorRecord],
|
|
|
|
latest_penalized_exit_balances: seq[uint64],
|
|
|
|
validator_registry_delta_chain_tip: Eth2Digest,
|
|
|
|
current_slot: uint64):
|
2018-12-04 18:45:30 +00:00
|
|
|
tuple[
|
|
|
|
validators: seq[ValidatorRecord],
|
|
|
|
latest_penalized_exit_balances: seq[uint64],
|
|
|
|
validator_registry_delta_chain_tip: Eth2Digest] =
|
|
|
|
## Return changed validator registry and `latest_penalized_exit_balances`,
|
|
|
|
## `validator_registry_delta_chain_tip`.
|
|
|
|
|
|
|
|
# TODO inefficient
|
2018-12-11 17:55:45 +00:00
|
|
|
var
|
|
|
|
validator_registry = validator_registry
|
|
|
|
latest_penalized_exit_balances = latest_penalized_exit_balances
|
2018-12-04 18:45:30 +00:00
|
|
|
|
|
|
|
# The active validators
|
2018-12-11 17:55:45 +00:00
|
|
|
let active_validator_indices =
|
|
|
|
get_active_validator_indices(validator_registry)
|
2018-12-06 02:07:04 +00:00
|
|
|
# The total effective balance of active validators
|
|
|
|
let total_balance = sum(mapIt(
|
2018-12-11 17:55:45 +00:00
|
|
|
active_validator_indices, get_effective_balance(validator_registry[it])))
|
2018-12-06 02:07:04 +00:00
|
|
|
|
|
|
|
# The maximum balance churn in Gwei (for deposits and exits separately)
|
|
|
|
let max_balance_churn = max(
|
2018-12-11 17:55:45 +00:00
|
|
|
MAX_DEPOSIT * GWEI_PER_ETH,
|
2018-12-06 02:07:04 +00:00
|
|
|
total_balance div (2 * MAX_BALANCE_CHURN_QUOTIENT)
|
2018-12-04 18:45:30 +00:00
|
|
|
)
|
|
|
|
|
2018-12-06 02:07:04 +00:00
|
|
|
# Activate validators within the allowable balance churn
|
|
|
|
var balance_churn = 0'u64
|
2018-12-04 18:45:30 +00:00
|
|
|
var validator_registry_delta_chain_tip = validator_registry_delta_chain_tip
|
2018-12-11 17:55:45 +00:00
|
|
|
for i in 0..<len(validator_registry):
|
|
|
|
if validator_registry[i].status == PENDING_ACTIVATION and
|
|
|
|
validator_registry[i].balance >= MAX_DEPOSIT:
|
2018-12-06 02:07:04 +00:00
|
|
|
# Check the balance churn would be within the allowance
|
2018-12-11 17:55:45 +00:00
|
|
|
balance_churn += get_effective_balance(validator_registry[i])
|
2018-12-06 02:07:04 +00:00
|
|
|
if balance_churn > max_balance_churn:
|
|
|
|
break
|
|
|
|
|
|
|
|
# Activate validator
|
2018-12-11 17:55:45 +00:00
|
|
|
validator_registry[i].status = ACTIVE
|
|
|
|
validator_registry[i].latest_status_change_slot = current_slot
|
2018-12-06 02:07:04 +00:00
|
|
|
validator_registry_delta_chain_tip =
|
2018-12-04 18:45:30 +00:00
|
|
|
get_new_validator_registry_delta_chain_tip(
|
|
|
|
validator_registry_delta_chain_tip,
|
|
|
|
i.Uint24,
|
2018-12-11 17:55:45 +00:00
|
|
|
validator_registry[i].pubkey,
|
2018-12-04 18:45:30 +00:00
|
|
|
ACTIVATION,
|
2018-12-06 02:07:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# Exit validators within the allowable balance churn
|
|
|
|
balance_churn = 0
|
2018-12-11 17:55:45 +00:00
|
|
|
for i in 0..<len(validator_registry):
|
|
|
|
if validator_registry[i].status == ACTIVE_PENDING_EXIT:
|
2018-12-06 02:07:04 +00:00
|
|
|
# Check the balance churn would be within the allowance
|
2018-12-11 17:55:45 +00:00
|
|
|
balance_churn += get_effective_balance(validator_registry[i])
|
2018-12-06 02:07:04 +00:00
|
|
|
if balance_churn > max_balance_churn:
|
|
|
|
break
|
|
|
|
|
|
|
|
# Exit validator
|
2018-12-11 17:55:45 +00:00
|
|
|
validator_registry[i].status = EXITED_WITHOUT_PENALTY
|
|
|
|
validator_registry[i].latest_status_change_slot = current_slot
|
2018-12-04 18:45:30 +00:00
|
|
|
validator_registry_delta_chain_tip =
|
|
|
|
get_new_validator_registry_delta_chain_tip(
|
|
|
|
validator_registry_delta_chain_tip,
|
|
|
|
i.Uint24,
|
2018-12-11 17:55:45 +00:00
|
|
|
validator_registry[i].pubkey,
|
|
|
|
ValidatorSetDeltaFlags.EXIT,
|
2018-12-06 02:07:04 +00:00
|
|
|
)
|
2018-12-04 18:45:30 +00:00
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal
|
|
|
|
# periods
|
|
|
|
let period_index =
|
|
|
|
(current_slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int
|
2018-12-04 18:45:30 +00:00
|
|
|
let total_penalties = (
|
|
|
|
(latest_penalized_exit_balances[period_index]) +
|
2018-12-11 17:55:45 +00:00
|
|
|
(if period_index >= 1:
|
|
|
|
latest_penalized_exit_balances[period_index - 1] else: 0) +
|
|
|
|
(if period_index >= 2:
|
|
|
|
latest_penalized_exit_balances[period_index - 2] else: 0)
|
2018-12-04 18:45:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# Calculate penalties for slashed validators
|
|
|
|
func to_penalize(v: ValidatorRecord): bool =
|
2018-12-06 02:07:04 +00:00
|
|
|
v.status == EXITED_WITH_PENALTY
|
2018-12-11 17:55:45 +00:00
|
|
|
for v in validator_registry.mitems():
|
|
|
|
if not to_penalize(v): continue
|
|
|
|
v.balance -=
|
2018-12-06 02:07:04 +00:00
|
|
|
(get_effective_balance(v) * min(total_penalties * 3, total_balance) div
|
2018-12-11 17:55:45 +00:00
|
|
|
total_balance)
|
2018-12-04 18:45:30 +00:00
|
|
|
|
2018-12-11 17:55:45 +00:00
|
|
|
(validator_registry, latest_penalized_exit_balances,
|
|
|
|
validator_registry_delta_chain_tip)
|