commit
9d284b6eb4
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
fork_data: ForkData(
|
|
||||||
|
# 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(
|
||||||
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -7,14 +7,16 @@
|
||||||
# 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
|
||||||
return some(i)
|
v.latest_status_change_slot + DELETION_PERIOD.uint64 <= current_slot:
|
||||||
|
return some(i)
|
||||||
|
|
||||||
func get_new_validators*(current_validators: seq[ValidatorRecord],
|
func get_new_validators*(current_validators: seq[ValidatorRecord],
|
||||||
fork_data: ForkData,
|
fork_data: ForkData,
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue