Merge pull request #27 from status-im/epoch-recalc

spec updates
This commit is contained in:
tersec 2018-12-04 12:36:35 +00:00 committed by GitHub
commit 9d284b6eb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 461 additions and 136 deletions

View File

@ -1,7 +1,7 @@
import import
os, net, os, net,
asyncdispatch2, chronicles, confutils, eth_p2p, eth_keys, asyncdispatch2, chronicles, confutils, eth_p2p, eth_keys,
spec/[beaconstate, datatypes], conf, time, fork_choice, spec/[beaconstate, datatypes, helpers], conf, time, fork_choice,
beacon_chain_db, validator_pool, mainchain_monitor, beacon_chain_db, validator_pool, mainchain_monitor,
sync_protocol, gossipsub_protocol, trusted_state_snapshots sync_protocol, gossipsub_protocol, trusted_state_snapshots
@ -52,9 +52,9 @@ proc sync*(node: BeaconNode): Future[bool] {.async.} =
node.beaconState = persistedState[] node.beaconState = persistedState[]
var targetSlot = toSlot timeSinceGenesis(node.beaconState) var targetSlot = toSlot timeSinceGenesis(node.beaconState)
while node.beaconState.last_finalized_slot.int < targetSlot: while node.beaconState.finalized_slot.int < targetSlot:
var (peer, changeLog) = await node.network.getValidatorChangeLog( var (peer, changeLog) = await node.network.getValidatorChangeLog(
node.beaconState.validator_set_delta_hash_chain) node.beaconState.validator_registry_delta_chain_tip)
if peer == nil: if peer == nil:
error "Failed to sync with any peer" error "Failed to sync with any peer"
@ -79,7 +79,7 @@ proc addLocalValidators*(node: BeaconNode) =
discard discard
proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator = proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator =
let validatorKey = node.beaconState.validators[idx].pubkey let validatorKey = node.beaconState.validator_registry[idx].pubkey
return node.attachedValidators.getValidator(validatorKey) return node.attachedValidators.getValidator(validatorKey)
proc makeAttestation(node: BeaconNode, proc makeAttestation(node: BeaconNode,
@ -129,7 +129,7 @@ proc proposeBlock(node: BeaconNode,
proc scheduleCycleActions(node: BeaconNode) = proc scheduleCycleActions(node: BeaconNode) =
## This schedules the required block proposals and ## This schedules the required block proposals and
## attestations from our attached validators. ## attestations from our attached validators.
let cycleStart = node.beaconState.last_state_recalculation_slot.int let cycleStart = node.beaconState.latest_state_recalculation_slot.int
for i in 0 ..< EPOCH_LENGTH: for i in 0 ..< EPOCH_LENGTH:
# Schedule block proposals # Schedule block proposals
@ -148,7 +148,7 @@ proc scheduleCycleActions(node: BeaconNode) =
# Schedule attestations # Schedule attestations
let let
committeesIdx = get_shards_and_committees_index(node.beaconState, slot.uint64) committeesIdx = get_shard_and_committees_index(node.beaconState, slot.uint64)
for shard in node.beaconState.shard_and_committee_for_slots[committees_idx]: for shard in node.beaconState.shard_and_committee_for_slots[committees_idx]:
for validatorIdx in shard.committee: for validatorIdx in shard.committee:

View File

@ -5,7 +5,7 @@ import
type type
Attestation* = object Attestation* = object
validator*: int validator*: int
data*: AttestationSignedData data*: AttestationData
signature*: ValidatorSig signature*: ValidatorSig
AttestationPool* = object AttestationPool* = object

View File

@ -10,11 +10,8 @@ import
../extras, ../extras,
./datatypes, ./digest, ./helpers, ./validator ./datatypes, ./digest, ./helpers, ./validator
func mod_get[T](arr: openarray[T], pos: Natural): T =
arr[pos mod arr.len]
func on_startup*(initial_validator_entries: openArray[InitialValidator], func on_startup*(initial_validator_entries: openArray[InitialValidator],
genesis_time: int, genesis_time: uint64,
processed_pow_receipt_root: Eth2Digest): BeaconState = processed_pow_receipt_root: Eth2Digest): BeaconState =
## BeaconState constructor ## BeaconState constructor
## ##
@ -40,7 +37,7 @@ func on_startup*(initial_validator_entries: openArray[InitialValidator],
ForkData( ForkData(
pre_fork_version: INITIAL_FORK_VERSION, pre_fork_version: INITIAL_FORK_VERSION,
post_fork_version: INITIAL_FORK_VERSION, post_fork_version: INITIAL_FORK_VERSION,
fork_slot_number: 0xffffffffffffffff'u64 fork_slot: INITIAL_SLOT_NUMBER
), ),
v.pubkey, v.pubkey,
v.deposit_size, v.deposit_size,
@ -48,71 +45,65 @@ func on_startup*(initial_validator_entries: openArray[InitialValidator],
v.withdrawal_credentials, v.withdrawal_credentials,
v.randao_commitment, v.randao_commitment,
ACTIVE, ACTIVE,
0 INITIAL_SLOT_NUMBER
).validators ).validators
# Setup state # Setup state
let let
x = get_new_shuffling(Eth2Digest(), validators, 0) initial_shuffling = get_new_shuffling(Eth2Digest(), validators, 0)
# x + x in spec, but more ugly # initial_shuffling + initial_shuffling in spec, but more ugly
var tmp: array[2 * EPOCH_LENGTH, seq[ShardAndCommittee]] var shard_and_committee_for_slots: array[2 * EPOCH_LENGTH, seq[ShardAndCommittee]]
for i, n in x: for i, n in initial_shuffling:
tmp[i] = n shard_and_committee_for_slots[i] = n
tmp[EPOCH_LENGTH + i] = n shard_and_committee_for_slots[EPOCH_LENGTH + i] = n
# The spec says to use validators, but it's actually indices.. # The spec says to use validators, but it's actually indices..
let validator_indices = get_active_validator_indices(validators) let validator_indices = get_active_validator_indices(validators)
let persistent_committees = split(shuffle(
validator_indices, ZERO_HASH), SHARD_COUNT)
BeaconState( BeaconState(
validators: validators, validator_registry: validators,
shard_and_committee_for_slots: tmp, validator_registry_latest_change_slot: INITIAL_SLOT_NUMBER,
persistent_committees: split( validator_registry_exit_count: 0,
shuffle(validator_indices, Eth2Digest()), SHARD_COUNT), validator_registry_delta_chain_tip: ZERO_HASH,
# Randomness and committees
randao_mix: ZERO_HASH,
next_seed: ZERO_HASH,
shard_and_committee_for_slots: shard_and_committee_for_slots,
persistent_committees: persistent_committees,
# Finality
previous_justified_slot: INITIAL_SLOT_NUMBER,
justified_slot: INITIAL_SLOT_NUMBER,
finalized_slot: INITIAL_SLOT_NUMBER,
# Recent state
latest_state_recalculation_slot: INITIAL_SLOT_NUMBER,
latest_block_hashes: repeat(ZERO_HASH, EPOCH_LENGTH * 2),
# PoW receipt root
processed_pow_receipt_root: processed_pow_receipt_root,
# Misc
genesis_time: genesis_time,
fork_data: ForkData( fork_data: ForkData(
pre_fork_version: INITIAL_FORK_VERSION, pre_fork_version: INITIAL_FORK_VERSION,
post_fork_version: INITIAL_FORK_VERSION post_fork_version: INITIAL_FORK_VERSION,
fork_slot: INITIAL_SLOT_NUMBER,
),
) )
)
func get_shards_and_committees_index*(state: BeaconState, slot: uint64): uint64 =
# TODO spec unsigned-unsafe here
let earliest_slot_in_array =
if state.last_state_recalculation_slot > EPOCH_LENGTH.uint64:
state.last_state_recalculation_slot - EPOCH_LENGTH
else:
0
doAssert earliest_slot_in_array <= slot and
slot < earliest_slot_in_array + EPOCH_LENGTH * 2
slot - earliest_slot_in_array
proc get_shards_and_committees_for_slot*(
state: BeaconState, slot: uint64): seq[ShardAndCommittee] =
let index = state.get_shards_and_committees_index(slot)
state.shard_and_committee_for_slots[index]
func get_beacon_proposer_index*(state: BeaconState, slot: uint64): uint64 =
## From Casper RPJ mini-spec:
## When slot i begins, validator Vidx is expected
## to create ("propose") a block, which contains a pointer to some parent block
## that they perceive as the "head of the chain",
## and includes all of the **attestations** that they know about
## that have not yet been included into that chain.
##
## idx in Vidx == p(i mod N), pi being a random permutation of validators indices (i.e. a committee)
let idx = get_shards_and_committees_index(state, slot)
state.shard_and_committee_for_slots[idx][0].committee.mod_get(slot)
func get_block_hash*(state: BeaconState, func get_block_hash*(state: BeaconState,
current_block: BeaconBlock, current_block: BeaconBlock,
slot: int): Eth2Digest = slot: uint64): Eth2Digest =
let earliest_slot_in_array = let earliest_slot_in_array =
current_block.slot.int - state.recent_block_hashes.len current_block.slot.int - state.latest_block_hashes.len
assert earliest_slot_in_array <= slot assert earliest_slot_in_array <= slot.int
assert slot < current_block.slot.int assert slot < current_block.slot
state.recent_block_hashes[slot - earliest_slot_in_array] state.latest_block_hashes[slot.int - earliest_slot_in_array]
func append_to_recent_block_hashes*(old_block_hashes: seq[Eth2Digest], func append_to_recent_block_hashes*(old_block_hashes: seq[Eth2Digest],
parent_slot, current_slot: uint64, parent_slot, current_slot: uint64,
@ -122,8 +113,8 @@ func append_to_recent_block_hashes*(old_block_hashes: seq[Eth2Digest],
result.add repeat(parent_hash, d) result.add repeat(parent_hash, d)
proc get_attestation_participants*(state: BeaconState, proc get_attestation_participants*(state: BeaconState,
attestation_data: AttestationSignedData, attestation_data: AttestationData,
attester_bitfield: seq[byte]): seq[int] = participation_bitfield: seq[byte]): seq[int] =
## Attestation participants in the attestation data are called out in a ## Attestation participants in the attestation data are called out in a
## bit field that corresponds to the committee of the shard at the time - this ## bit field that corresponds to the committee of the shard at the time - this
## function converts it to list of indices in to BeaconState.validators ## function converts it to list of indices in to BeaconState.validators
@ -132,7 +123,7 @@ proc get_attestation_participants*(state: BeaconState,
# TODO bitfield type needed, once bit order settles down # TODO bitfield type needed, once bit order settles down
# TODO iterator candidate # TODO iterator candidate
let let
sncs_for_slot = get_shards_and_committees_for_slot( sncs_for_slot = get_shard_and_committees_for_slot(
state, attestation_data.slot) state, attestation_data.slot)
for snc in sncs_for_slot: for snc in sncs_for_slot:
@ -140,10 +131,10 @@ proc get_attestation_participants*(state: BeaconState,
continue continue
# TODO investigate functional library / approach to help avoid loop bugs # TODO investigate functional library / approach to help avoid loop bugs
assert len(attester_bitfield) == ceil_div8(len(snc.committee)) assert len(participation_bitfield) == ceil_div8(len(snc.committee))
for i, vindex in snc.committee: for i, vindex in snc.committee:
let let
bit = (attester_bitfield[i div 8] shr (7 - (i mod 8))) mod 2 bit = (participation_bitfield[i div 8] shr (7 - (i mod 8))) mod 2
if bit == 1: if bit == 1:
result.add(vindex) result.add(vindex)
return # found the shard, we're done return # found the shard, we're done

View File

@ -37,9 +37,8 @@ const
INITIAL_FORK_VERSION* = 0 # INITIAL_FORK_VERSION* = 0 #
INITIAL_SLOT_NUMBER* = 0 # INITIAL_SLOT_NUMBER* = 0 #
GWEI_PER_ETH* = 10^9 # Gwei/ETH GWEI_PER_ETH* = 10^9 # Gwei/ETH
ZERO_HASH* = Eth2Digest()
BEACON_CHAIN_SHARD_NUMBER* = not 0'u64 BEACON_CHAIN_SHARD_NUMBER* = not 0'u64
WITHDRAWALS_PER_CYCLE* = 2^2 # validators (5.2m ETH in ~6 months)
MIN_WITHDRAWAL_PERIOD* = 2^13 # slots (~14 hours)
# Time constants # Time constants
SLOT_DURATION* = 6 # seconds SLOT_DURATION* = 6 # seconds
@ -82,18 +81,18 @@ type
proposer_signature*: ValidatorSig # Proposer signature proposer_signature*: ValidatorSig # Proposer signature
AttestationRecord* = object AttestationRecord* = object
data*: AttestationSignedData # data*: AttestationData
attester_bitfield*: seq[byte] # Attester participation bitfield participation_bitfield*: seq[byte] # Attester participation bitfield
poc_bitfield*: seq[byte] # Proof of custody bitfield custody_bitfield*: seq[byte] # Proof of custody bitfield
aggregate_sig*: ValidatorSig # BLS aggregate signature aggregate_sig*: ValidatorSig # BLS aggregate signature
AttestationSignedData* = object AttestationData* = object
slot*: uint64 # Slot number slot*: uint64 # Slot number
shard*: uint64 # Shard number shard*: uint64 # Shard number
block_hash*: Eth2Digest # Hash of the block we're signing beacon_block_hash*: Eth2Digest # Hash of the block we're signing
cycle_boundary_hash*: Eth2Digest # Hash of the ancestor at the cycle boundary epoch_boundary_hash*: Eth2Digest # Hash of the ancestor at the cycle boundary
shard_block_hash*: Eth2Digest # Shard block hash being attested to shard_block_hash*: Eth2Digest # Shard block hash being attested to
last_crosslink_hash*: Eth2Digest # Last crosslink hash latest_crosslink_hash*: Eth2Digest # Last crosslink hash
justified_slot*: uint64 # Slot of last justified beacon block justified_slot*: uint64 # Slot of last justified beacon block
justified_block_hash*: Eth2Digest # Hash of last justified beacon block justified_block_hash*: Eth2Digest # Hash of last justified beacon block
@ -107,31 +106,42 @@ type
data*: seq[byte] # Data data*: seq[byte] # Data
BeaconState* = object BeaconState* = object
validator_set_change_slot*: uint64 # Slot of last validator set change # Validator registry
validators*: seq[ValidatorRecord] # List of validators validator_registry*: seq[ValidatorRecord]
crosslinks*: array[SHARD_COUNT, CrosslinkRecord] # Most recent crosslink for each shard validator_registry_latest_change_slot*: uint64
last_state_recalculation_slot*: uint64 # Last cycle-boundary state recalculation validator_registry_exit_count*: uint64
last_finalized_slot*: uint64 # Last finalized slot validator_registry_delta_chain_tip*: Eth2Digest ##\
justification_source*: uint64 # Justification source ## For light clients to easily track delta
prev_cycle_justification_source*: uint64 #
justified_slot_bitfield*: uint64 # Recent justified slot bitmask # Randomness and committees
randao_mix*: Eth2Digest # RANDAO state
next_seed*: Eth2Digest # Randao seed used for next shuffling
shard_and_committee_for_slots*: array[2 * EPOCH_LENGTH, seq[ShardAndCommittee]] ## \ shard_and_committee_for_slots*: array[2 * EPOCH_LENGTH, seq[ShardAndCommittee]] ## \
## Committee members and their assigned shard, per slot, covers 2 cycles ## Committee members and their assigned shard, per slot, covers 2 cycles
## worth of assignments ## worth of assignments
persistent_committees*: seq[seq[Uint24]] # Persistent shard committees persistent_committees*: seq[seq[Uint24]] # Persistent shard committees
persistent_committee_reassignments*: seq[ShardReassignmentRecord] persistent_committee_reassignments*: seq[ShardReassignmentRecord]
next_shuffling_seed*: Eth2Digest # Randao seed used for next shuffling
deposits_penalized_in_period*: uint32 # Total deposits penalized in the given withdrawal period # Finality
validator_set_delta_hash_chain*: Eth2Digest # Hash chain of validator set changes (for light clients to easily track deltas) previous_justified_slot*: uint64
current_exit_seq*: uint64 # Current sequence number for withdrawals justified_slot*: uint64
genesis_time*: uint64 # Genesis time justified_slot_bitfield*: uint64
candidate_pow_receipt_root*: Eth2Digest # PoW receipt root finalized_slot*: uint64
candidate_pow_receipt_roots*: seq[CandidatePoWReceiptRootRecord] #
fork_data*: ForkData # Parameters relevant to hard forks / versioning. latest_crosslinks*: array[SHARD_COUNT, CrosslinkRecord]
# Should be updated only by hard forks. latest_state_recalculation_slot*: uint64
pending_attestations*: seq[ProcessedAttestation] # Attestations not yet processed latest_block_hashes*: seq[Eth2Digest] ##\
recent_block_hashes*: seq[Eth2Digest] # recent beacon block hashes needed to process attestations, older to newer ## Needed to process attestations, older to newer
randao_mix*: Eth2Digest # RANDAO state latest_penalized_exit_balances*: seq[uint64] ##\
## Balances penalized in the current withdrawal period
latest_attestations*: seq[PendingAttestationRecord]
processed_pow_receipt_root*: Eth2Digest
candidate_pow_receipt_roots*: seq[CandidatePoWReceiptRootRecord]
genesis_time*: uint64
fork_data*: ForkData ##\
## For versioning hard forks
ValidatorRecord* = object ValidatorRecord* = object
pubkey*: ValidatorPubKey # Public key pubkey*: ValidatorPubKey # Public key
@ -140,12 +150,12 @@ type
randao_skips*: uint64 # Slot the proposer has skipped (ie. layers of RANDAO expected) randao_skips*: uint64 # Slot the proposer has skipped (ie. layers of RANDAO expected)
balance*: uint64 # Balance in Gwei balance*: uint64 # Balance in Gwei
status*: ValidatorStatusCodes # Status code status*: ValidatorStatusCodes # Status code
last_status_change_slot*: uint64 # Slot when validator last changed status (or 0) latest_status_change_slot*: uint64 # Slot when validator last changed status (or 0)
exit_seq*: uint64 # Sequence number when validator exited (or 0) exit_count*: uint64 # Exit counter when validator exited (or 0)
CrosslinkRecord* = object CrosslinkRecord* = object
slot*: uint64 # Slot number slot*: uint64 # Slot number
hash*: Eth2Digest # Shard chain block hash shard_block_hash*: Eth2Digest # Shard chain block hash
ShardAndCommittee* = object ShardAndCommittee* = object
shard*: uint64 # Shard number shard*: uint64 # Shard number
@ -163,12 +173,12 @@ type
ForkData* = object ForkData* = object
pre_fork_version*: uint64 # Previous fork version pre_fork_version*: uint64 # Previous fork version
post_fork_version*: uint64 # Post fork version post_fork_version*: uint64 # Post fork version
fork_slot_number*: uint64 # Fork slot number fork_slot*: uint64 # Fork slot number
ProcessedAttestation* = object PendingAttestationRecord* = object
data*: AttestationSignedData # Signed data data*: AttestationData # Signed data
attester_bitfield*: seq[byte] # Attester participation bitfield (2 bits per attester) participation_bitfield*: seq[byte] # Attester participation bitfield
poc_bitfield*: seq[byte] # Proof of custody bitfield custody_bitfield*: seq[byte] # Proof of custody bitfield
slot_included*: uint64 # Slot in which it was included slot_included*: uint64 # Slot in which it was included
ValidatorStatusCodes* {.pure.} = enum ValidatorStatusCodes* {.pure.} = enum

View File

@ -9,6 +9,9 @@
import ./datatypes, ./digest, sequtils, math import ./datatypes, ./digest, sequtils, math
func mod_get[T](arr: openarray[T], pos: Natural): T =
arr[pos mod arr.len]
func shuffle*[T](values: seq[T], seed: Eth2Digest): seq[T] = func shuffle*[T](values: seq[T], seed: Eth2Digest): seq[T] =
## Returns the shuffled ``values`` with seed as entropy. ## Returns the shuffled ``values`` with seed as entropy.
## TODO: this calls out for tests, but I odn't particularly trust spec ## TODO: this calls out for tests, but I odn't particularly trust spec
@ -83,3 +86,50 @@ func repeat_hash*(v: Eth2Digest, n: SomeInteger): Eth2Digest =
v v
else: else:
repeat_hash(eth2hash(v.data), n - 1) repeat_hash(eth2hash(v.data), n - 1)
func get_shard_and_committees_index*(state: BeaconState, slot: uint64): uint64 =
# TODO spec unsigned-unsafe here
let earliest_slot_in_array =
if state.latest_state_recalculation_slot > EPOCH_LENGTH.uint64:
state.latest_state_recalculation_slot - EPOCH_LENGTH
else:
0
doAssert earliest_slot_in_array <= slot and
slot < earliest_slot_in_array + EPOCH_LENGTH * 2
slot - earliest_slot_in_array
proc get_shard_and_committees_for_slot*(
state: BeaconState, slot: uint64): seq[ShardAndCommittee] =
let index = state.get_shard_and_committees_index(slot)
state.shard_and_committee_for_slots[index]
func get_beacon_proposer_index*(state: BeaconState, slot: uint64): uint64 =
## From Casper RPJ mini-spec:
## When slot i begins, validator Vidx is expected
## to create ("propose") a block, which contains a pointer to some parent block
## that they perceive as the "head of the chain",
## and includes all of the **attestations** that they know about
## that have not yet been included into that chain.
##
## idx in Vidx == p(i mod N), pi being a random permutation of validators indices (i.e. a committee)
let idx = get_shard_and_committees_index(state, slot)
state.shard_and_committee_for_slots[idx][0].committee.mod_get(slot)
func int_sqrt*(n: SomeInteger): SomeInteger =
var
x = n
y = (x + 1) div 2
while y < x:
x = y
y = (x + n div x) div 2
x
func get_fork_version*(fork_data: ForkData, slot: uint64): uint64 =
if slot < fork_data.fork_slot: fork_data.pre_fork_version
else: fork_data.post_fork_version
func get_domain*(fork_data: ForkData, slot: uint64, domain_type: uint64): uint64 =
# TODO Slot overflow? Or is slot 32 bits for all intents and purposes?
(get_fork_version(fork_data, slot) shl 32) + domain_type

View File

@ -7,13 +7,15 @@
# Helpers and functions pertaining to managing the validator set # Helpers and functions pertaining to managing the validator set
import import
options, options, nimcrypto,
eth_common, eth_common,
../ssz,
./crypto, ./datatypes, ./digest, ./helpers ./crypto, ./datatypes, ./digest, ./helpers
func min_empty_validator_index(validators: seq[ValidatorRecord], current_slot: uint64): Option[int] = func min_empty_validator_index(validators: seq[ValidatorRecord], current_slot: uint64): Option[int] =
for i, v in validators: for i, v in validators:
if v.balance == 0 and v.last_status_change_slot + DELETION_PERIOD.uint64 <= current_slot: if v.balance == 0 and
v.latest_status_change_slot + DELETION_PERIOD.uint64 <= current_slot:
return some(i) return some(i)
func get_new_validators*(current_validators: seq[ValidatorRecord], func get_new_validators*(current_validators: seq[ValidatorRecord],
@ -59,8 +61,8 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
randao_skips: 0, randao_skips: 0,
balance: deposit, balance: deposit,
status: status, status: status,
last_status_change_slot: current_slot, latest_status_change_slot: current_slot,
exit_seq: 0 exit_count: 0
) )
let index = min_empty_validator_index(new_validators, current_slot) let index = min_empty_validator_index(new_validators, current_slot)
@ -74,7 +76,7 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
func get_active_validator_indices*(validators: openArray[ValidatorRecord]): seq[Uint24] = func get_active_validator_indices*(validators: openArray[ValidatorRecord]): seq[Uint24] =
## Select the active validators ## Select the active validators
for idx, val in validators: for idx, val in validators:
if val.status == ACTIVE or val.status == PENDING_EXIT: if val.status in {ACTIVE, PENDING_EXIT}:
result.add idx.Uint24 result.add idx.Uint24
func get_new_shuffling*(seed: Eth2Digest, func get_new_shuffling*(seed: Eth2Digest,
@ -109,3 +111,65 @@ func get_new_shuffling*(seed: Eth2Digest,
committees[shard_position].committee = indices committees[shard_position].committee = indices
result[slot] = committees result[slot] = committees
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:
h.update hashSSZ(current_validator_registry_delta_chain_tip)
h.update hashSSZ(flag.uint8)
h.update hashSSZ(index)
# TODO h.update hashSSZ(pubkey)
func get_effective_balance*(validator: ValidatorRecord): uint64 =
min(validator.balance, MAX_DEPOSIT.uint64)
func exit_validator*(index: Uint24,
state: var BeaconState,
penalize: bool,
current_slot: uint64) =
## Remove the validator with the given `index` from `state`.
## Note that this function mutates `state`.
state.validator_registry_exit_count.inc()
var
validator = state.validator_registry[index]
validator.latest_status_change_slot = current_slot
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
if penalize:
validator.status = EXITED_WITH_PENALTY
state.latest_penalized_exit_balances[
(current_slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD.uint64).int].inc(
get_effective_balance(validator).int)
var
whistleblower =
state.validator_registry[get_beacon_proposer_index(state, current_slot).int]
whistleblower_reward =
validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT.uint64
whistleblower.balance.inc(whistleblower_reward.int)
validator.balance.dec(whistleblower_reward.int)
else:
validator.status = PENDING_EXIT
state.validator_registry_delta_chain_tip =
get_new_validator_registry_delta_chain_tip(
state.validator_registry_delta_chain_tip,
index,
validator.pubkey,
EXIT,
)

View File

@ -194,8 +194,8 @@ func hashSSZ*(x: ValidatorRecord): array[32, byte] =
h.update hashSSZ(x.randao_skips) h.update hashSSZ(x.randao_skips)
h.update hashSSZ(x.balance) h.update hashSSZ(x.balance)
# h.update hashSSZ(x.status) # TODO it's an enum, deal with it # h.update hashSSZ(x.status) # TODO it's an enum, deal with it
h.update hashSSZ(x.last_status_change_slot) h.update hashSSZ(x.latest_status_change_slot)
h.update hashSSZ(x.exit_seq) h.update hashSSZ(x.exit_count)
func hashSSZ*(x: ShardAndCommittee): array[32, byte] = func hashSSZ*(x: ShardAndCommittee): array[32, byte] =
withHash: withHash:

View File

@ -12,9 +12,9 @@
# missing pieces - needs testing throughout # missing pieces - needs testing throughout
import import
options, math, options, sequtils,
./extras, ./extras,
./spec/[beaconstate, crypto, datatypes, digest, helpers], ./spec/[beaconstate, crypto, datatypes, digest, helpers, validator],
./ssz, ./ssz,
milagro_crypto # nimble install https://github.com/status-im/nim-milagro-crypto@#master milagro_crypto # nimble install https://github.com/status-im/nim-milagro-crypto@#master
@ -24,30 +24,45 @@ import
func checkAttestations(state: BeaconState, func checkAttestations(state: BeaconState,
blck: BeaconBlock, blck: BeaconBlock,
parent_slot: uint64): Option[seq[ProcessedAttestation]] = parent_slot: uint64): Option[seq[PendingAttestationRecord]] =
# TODO perf improvement potential.. # TODO perf improvement potential..
if blck.attestations.len > MAX_ATTESTATIONS_PER_BLOCK: if blck.attestations.len > MAX_ATTESTATIONS_PER_BLOCK:
return return
var res: seq[ProcessedAttestation] var res: seq[PendingAttestationRecord]
for attestation in blck.attestations: for attestation in blck.attestations:
if attestation.data.slot <= blck.slot - MIN_ATTESTATION_INCLUSION_DELAY: if attestation.data.slot <= blck.slot - MIN_ATTESTATION_INCLUSION_DELAY:
return return
if attestation.data.slot >= max(parent_slot - EPOCH_LENGTH + 1, 0): # TODO unsigned undeflow in spec
if attestation.data.slot >= max(parent_slot.int - EPOCH_LENGTH + 1, 0).uint64:
return
let expected_justified_slot =
if attestation.data.slot >= state.latest_state_recalculation_slot:
state.justified_slot
else:
state.previous_justified_slot
if attestation.data.justified_slot != expected_justified_slot:
return
let expected_justified_block_hash =
get_block_hash(state, blck, attestation.data.justified_slot)
if attestation.data.justified_block_hash != expected_justified_block_hash:
return
if state.latest_crosslinks[attestation.data.shard].shard_block_hash notin [
attestation.data.latest_crosslink_hash, attestation.data.shard_block_hash]:
return return
#doAssert attestation.data.justified_slot == justification_source if attestation.data.slot >= state.last_state_recalculation_slot else prev_cycle_justification_source
# doAssert attestation.data.justified_block_hash == get_block_hash(state, block, attestation.data.justified_slot).
# doAssert either attestation.data.last_crosslink_hash or attestation.data.shard_block_hash equals state.crosslinks[shard].shard_block_hash.
let attestation_participants = get_attestation_participants( let attestation_participants = get_attestation_participants(
state, attestation.data, attestation.attester_bitfield) state, attestation.data, attestation.participation_bitfield)
var var
agg_pubkey: ValidatorPubKey agg_pubkey: ValidatorPubKey
empty = true empty = true
for attester_idx in attestation_participants: for attester_idx in attestation_participants:
let validator = state.validators[attester_idx] let validator = state.validator_registry[attester_idx]
if empty: if empty:
agg_pubkey = validator.pubkey agg_pubkey = validator.pubkey
empty = false empty = false
@ -62,10 +77,10 @@ func checkAttestations(state: BeaconState,
debugEcho "Aggregate sig verify message: ", debugEcho "Aggregate sig verify message: ",
attestation.aggregate_sig.verifyMessage(msg, agg_pubkey) attestation.aggregate_sig.verifyMessage(msg, agg_pubkey)
res.add ProcessedAttestation( res.add PendingAttestationRecord(
data: attestation.data, data: attestation.data,
attester_bitfield: attestation.attester_bitfield, participation_bitfield: attestation.participation_bitfield,
poc_bitfield: attestation.poc_bitfield, custody_bitfield: attestation.custody_bitfield,
slot_included: blck.slot slot_included: blck.slot
) )
@ -84,7 +99,7 @@ func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
verifyMessage( verifyMessage(
blck.proposer_signature, proposal_hash, blck.proposer_signature, proposal_hash,
state.validators[get_beacon_proposer_index(state, blck.slot).int].pubkey) state.validator_registry[get_beacon_proposer_index(state, blck.slot).int].pubkey)
func processRandaoReveal(state: var BeaconState, func processRandaoReveal(state: var BeaconState,
blck: BeaconBlock, blck: BeaconBlock,
@ -92,11 +107,11 @@ func processRandaoReveal(state: var BeaconState,
# Update randao skips # Update randao skips
for slot in parentslot + 1 ..< blck.slot: for slot in parentslot + 1 ..< blck.slot:
let proposer_index = get_beacon_proposer_index(state, slot) let proposer_index = get_beacon_proposer_index(state, slot)
state.validators[proposer_index.int].randao_skips.inc() state.validator_registry[proposer_index.int].randao_skips.inc()
var var
proposer_index = get_beacon_proposer_index(state, blck.slot) proposer_index = get_beacon_proposer_index(state, blck.slot)
proposer = state.validators[proposer_index.int] proposer = state.validator_registry[proposer_index.int]
# Check that proposer commit and reveal match # Check that proposer commit and reveal match
if repeat_hash(blck.randao_reveal, proposer.randao_skips + 1) != if repeat_hash(blck.randao_reveal, proposer.randao_skips + 1) !=
@ -112,6 +127,22 @@ func processRandaoReveal(state: var BeaconState,
true true
func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock): bool =
for x in state.candidate_pow_receipt_roots.mitems():
if blck.candidate_pow_receipt_root == x.candidate_pow_receipt_root:
x.votes.inc
return true
state.candidate_pow_receipt_roots.add CandidatePoWReceiptRootRecord(
candidate_pow_receipt_root: blck.candidate_pow_receipt_root,
votes: 1
)
return true
func processSpecials(state: var BeaconState, blck: BeaconBlock): bool =
# TODO incoming spec changes here..
true
func process_block*(state: BeaconState, blck: BeaconBlock): Option[BeaconState] = func process_block*(state: BeaconState, blck: BeaconBlock): Option[BeaconState] =
## When a new block is received, all participants must verify that the block ## When a new block is received, all participants must verify that the block
## makes sense and update their state accordingly. This function will return ## makes sense and update their state accordingly. This function will return
@ -127,15 +158,15 @@ func process_block*(state: BeaconState, blck: BeaconBlock): Option[BeaconState]
# TODO actually get parent block, which means fixing up BeaconState refs above; # TODO actually get parent block, which means fixing up BeaconState refs above;
# there's no distinction between active/crystallized state anymore, etc. # there's no distinction between active/crystallized state anymore, etc.
state.recent_block_hashes = state.latest_block_hashes =
append_to_recent_block_hashes(state.recent_block_hashes, parent_slot, slot, append_to_recent_block_hashes(state.latest_block_hashes, parent_slot, slot,
parent_hash) parent_hash)
let processed_attestations = checkAttestations(state, blck, parent_slot) let processed_attestations = checkAttestations(state, blck, parent_slot)
if processed_attestations.isNone: if processed_attestations.isNone:
return return
state.pending_attestations.add processed_attestations.get() state.latest_attestations.add processed_attestations.get()
if not verifyProposerSignature(state, blck): if not verifyProposerSignature(state, blck):
return return
@ -143,4 +174,183 @@ func process_block*(state: BeaconState, blck: BeaconBlock): Option[BeaconState]
if not processRandaoReveal(state, blck, parent_slot): if not processRandaoReveal(state, blck, parent_slot):
return return
if not processPoWReceiptRoot(state, blck):
return
if not processSpecials(state, blck):
return
some(state) # Looks ok - move on with the updated state some(state) # Looks ok - move on with the updated state
func flatten[T](v: openArray[seq[T]]): seq[T] =
for x in v: result.add x
func get_epoch_boundary_attesters(
state: BeaconState,
attestations: openArray[PendingAttestationRecord]): seq[int] =
deduplicate(flatten(mapIt(attestations,
get_attestation_participants(state, it.data, it.participation_bitfield))))
func adjust_for_inclusion_distance[T](magnitude: T, dist: T): T =
magnitude div 2 + (magnitude div 2) * MIN_ATTESTATION_INCLUSION_DELAY div dist
func processEpoch*(state: BeaconState, blck: BeaconBlock): Option[BeaconState] =
## Epoch processing happens every time we've passed EPOCH_LENGTH blocks.
## Because some slots may be skipped, it may happen that we go through the
## loop more than once - each time the latest_state_recalculation_slot will be
## increased by EPOCH_LENGTH.
# TODO: simplistic way to be able to rollback state
var state = state
# Precomputation
while blck.slot >= EPOCH_LENGTH.uint64 + state.latest_state_recalculation_slot:
let s = state.latest_state_recalculation_slot
let
active_validators =
mapIt(get_active_validator_indices(state.validator_registry),
state.validator_registry[it])
total_balance = sum(mapIt(active_validators, get_effective_balance(it)))
total_balance_in_eth = total_balance.int div GWEI_PER_ETH
# The per-slot maximum interest rate is `2/reward_quotient`.)
reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth)
proc base_reward(v: ValidatorRecord): uint64 =
get_effective_balance(v) div reward_quotient.uint64
# TODO doing this with iterators failed:
# https://github.com/nim-lang/Nim/issues/9827
let
this_epoch_attestations = filterIt(state.latest_attestations,
s <= it.data.slot and it.data.slot < s + EPOCH_LENGTH)
this_epoch_boundary_attestations = filterIt(this_epoch_attestations,
it.data.epoch_boundary_hash == get_block_hash(state, blck, s) and
it.data.justified_slot == state.justified_slot)
this_epoch_boundary_attesters =
get_epoch_boundary_attesters(state, this_epoch_attestations)
this_epoch_boundary_attesting_balance = sum(
mapIt(this_epoch_boundary_attesters,
get_effective_balance(state.validator_registry[it]))
)
let
previous_epoch_attestations = filterIt(state.latest_attestations,
s <= it.data.slot + EPOCH_LENGTH and it.data.slot < s)
previous_epoch_boundary_attestations = filterIt(previous_epoch_attestations,
it.data.epoch_boundary_hash == get_block_hash(state, blck, s) and
it.data.justified_slot == state.justified_slot)
previous_epoch_boundary_attesters =
get_epoch_boundary_attesters(state, previous_epoch_boundary_attestations)
previous_epoch_boundary_attesting_balance = sum(
mapIt(previous_epoch_boundary_attesters,
get_effective_balance(state.validator_registry[it]))
)
# TODO gets pretty hairy here
func attesting_validators(
obj: ShardAndCommittee, shard_block_hash: Eth2Digest): seq[int] =
flatten(
mapIt(
filterIt(concat(this_epoch_attestations, previous_epoch_attestations),
it.data.shard == obj.shard and
it.data.shard_block_hash == shard_block_hash),
get_attestation_participants(state, it.data, it.participation_bitfield)))
# TODO which shard_block_hash:es?
# * Let `attesting_validators(obj)` be equal to `attesting_validators(obj, shard_block_hash)` for the value of `shard_block_hash` such that `sum([get_effective_balance(v) for v in attesting_validators(obj, shard_block_hash)])` is maximized (ties broken by favoring lower `shard_block_hash` values).
# * Let `total_attesting_balance(obj)` be the sum of the balances-at-stake of `attesting_validators(obj)`.
# * Let `winning_hash(obj)` be the winning `shard_block_hash` value.
# * Let `total_balance(obj) = sum([get_effective_balance(v) for v in obj.committee])`.
# Let `inclusion_slot(v)` equal `a.slot_included` for the attestation `a` where `v` is in `get_attestation_participants(state, a.data, a.participation_bitfield)`, and `inclusion_distance(v) = a.slot_included - a.data.slot` for the same attestation. We define a function `adjust_for_inclusion_distance(magnitude, distance)` which adjusts the reward of an attestation based on how long it took to get included (the longer, the lower the reward). Returns a value between 0 and `magnitude`.
# Adjust justified slots and crosslink status
var new_justified_slot: Option[uint64]
# overflow intentional!
state.justified_slot_bitfield = state.justified_slot_bitfield * 2
if 3'u64 * previous_epoch_boundary_attesting_balance >= 2'u64 * total_balance:
# TODO spec says "flip the second lowest bit to 1" and does "AND", wrong?
state.justified_slot_bitfield = state.justified_slot_bitfield or 2
new_justified_slot = some(s - EPOCH_LENGTH)
if 3'u64 * this_epoch_boundary_attesting_balance >= 2'u64 * total_balance:
# TODO spec says "flip the second lowest bit to 1" and does "AND", wrong?
state.justified_slot_bitfield = state.justified_slot_bitfield or 1
new_justified_slot = some(s)
if state.justified_slot == s - EPOCH_LENGTH and
state.justified_slot_bitfield mod 4 == 3:
state.finalized_slot = state.justified_slot
if state.justified_slot == s - EPOCH_LENGTH - EPOCH_LENGTH and
state.justified_slot_bitfield mod 8 == 7:
state.finalized_slot = state.justified_slot
if state.justified_slot == s - EPOCH_LENGTH - 2 * EPOCH_LENGTH and
state.justified_slot_bitfield mod 16 in [15'u64, 14]:
state.finalized_slot = state.justified_slot
state.previous_justified_slot = state.justified_slot
if new_justified_slot.isSome():
state.justified_slot = new_justified_slot.get()
# for obj in state.shard_and_committee_for_slots:
# 3 * total_attesting_balance(obj) >= 2 * total_balance(obj):
# state.crosslinks[shard] = CrosslinkRecord(
# slot: latest_state_recalculation_slot + EPOCH_LENGTH,
# hash: winning_hash(obj))
# Balance recalculations related to FFG rewards
let
# The portion lost by offline [validators](#dfn-validator) after `D`
# epochs is about `D*D/2/inactivity_penalty_quotient`.
inactivity_penalty_quotient = SQRT_E_DROP_TIME^2
time_since_finality = blck.slot - state.finalized_slot
if time_since_finality <= 4'u64 * EPOCH_LENGTH:
# for v in previous_epoch_boundary_attesters:
# state.validators[v].balance.inc(adjust_for_inclusion_distance(
# base_reward(state.validators[v]) *
# prev_cycle_boundary_attesting_balance div total_balance,
# inclusion_distance(v)))
for v in get_active_validator_indices(state.validator_registry):
if v notin previous_epoch_boundary_attesters:
state.validator_registry[v].balance.dec(
base_reward(state.validator_registry[v]).int)
else:
# Any validator in `prev_cycle_boundary_attesters` sees their balance
# unchanged.
# Others might get penalized:
for vindex, v in state.validator_registry.mpairs():
if (v.status == ACTIVE and vindex notin previous_epoch_boundary_attesters) or
v.status == EXITED_WITH_PENALTY:
v.balance.dec(
(base_reward(v) + get_effective_balance(v) * time_since_finality div
inactivity_penalty_quotient.uint64).int)
# For each `v` in `prev_cycle_boundary_attesters`, we determine the proposer `proposer_index = get_beacon_proposer_index(state, inclusion_slot(v))` and set `state.validators[proposer_index].balance += base_reward(v) // INCLUDER_REWARD_SHARE_QUOTIENT`.
# Balance recalculations related to crosslink rewards
# Ethereum 1.0 chain related rules
# Validator registry change
# If a validator registry change does NOT happen
# Proposer reshuffling
# Finally...
some(state)

View File

@ -85,9 +85,9 @@ proc applyValidatorChangeLog*(log: ChangeLog,
# 4. Apply all changes to the validator set # 4. Apply all changes to the validator set
# #
outBeaconState.last_finalized_slot = outBeaconState.finalized_slot =
log.signedBlock.slot div EPOCH_LENGTH log.signedBlock.slot div EPOCH_LENGTH
outBeaconState.validator_set_delta_hash_chain = outBeaconState.validator_registry_delta_chain_tip =
log.beaconState.validator_set_delta_hash_chain log.beaconState.validator_registry_delta_chain_tip

View File

@ -35,7 +35,7 @@ proc randomTimeInSlot*(s: BeaconState,
proc slotDistanceFromNow*(s: BeaconState): int64 = proc slotDistanceFromNow*(s: BeaconState): int64 =
## Returns how many slots have passed since a particular BeaconState was finalized ## Returns how many slots have passed since a particular BeaconState was finalized
int64(s.timeSinceGenesis() div (SLOT_DURATION * 1000)) - int64(s.last_finalized_slot) int64(s.timeSinceGenesis() div (SLOT_DURATION * 1000)) - int64(s.finalized_slot)
proc syncrhronizeClock*() {.async.} = proc syncrhronizeClock*() {.async.} =
## This should determine the offset of the local clock against a global ## This should determine the offset of the local clock against a global

View File

@ -53,7 +53,7 @@ proc signBlockProposal*(v: AttachedValidator,
discard discard
proc signAttestation*(v: AttachedValidator, proc signAttestation*(v: AttachedValidator,
attestation: AttestationSignedData): Future[ValidatorSig] {.async.} = attestation: AttestationData): Future[ValidatorSig] {.async.} =
# TODO: implement this # TODO: implement this
if v.kind == inProcess: if v.kind == inProcess:
await sleepAsync(1) await sleepAsync(1)

View File

@ -16,4 +16,4 @@ suite "Beacon state":
test "Smoke on_startup": test "Smoke on_startup":
let state = on_startup(makeInitialValidators(EPOCH_LENGTH), 0, Eth2Digest()) let state = on_startup(makeInitialValidators(EPOCH_LENGTH), 0, Eth2Digest())
check: state.validators.len == EPOCH_LENGTH check: state.validator_registry.len == EPOCH_LENGTH