spec updates
* spec-following renames * more documentation * simplify casper slashing * fix block creation flow / logic
This commit is contained in:
parent
400ac83aa0
commit
0b0c66ebd9
|
@ -144,9 +144,10 @@ func update_validator_status*(state: var BeaconState,
|
|||
if new_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY]:
|
||||
exit_validator(state, index, new_status)
|
||||
|
||||
func on_startup*(initial_validator_deposits: openArray[Deposit],
|
||||
genesis_time: uint64,
|
||||
processed_pow_receipt_root: Eth2Digest): BeaconState =
|
||||
func get_initial_beacon_state*(
|
||||
initial_validator_deposits: openArray[Deposit],
|
||||
genesis_time: uint64,
|
||||
processed_pow_receipt_root: Eth2Digest): BeaconState =
|
||||
## BeaconState constructor
|
||||
##
|
||||
## Before the beacon chain starts, validators will register in the Eth1 chain
|
||||
|
|
|
@ -66,7 +66,7 @@ const
|
|||
BLS_WITHDRAWAL_PREFIX_BYTE* = 0'u8
|
||||
|
||||
MAX_CASPER_VOTES* = 2^10
|
||||
LATEST_BLOCK_ROOTS_COUNT* = 2'u64^13
|
||||
LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13
|
||||
|
||||
MIN_BALANCE* = 2'u64^4 ##\
|
||||
## Minimum balance in ETH before a validator is removed from the validator
|
||||
|
@ -142,8 +142,8 @@ type
|
|||
proposal_signature_2*: ValidatorSig
|
||||
|
||||
CasperSlashing* = object
|
||||
votes_1*: SlashableVoteData
|
||||
votes_2*: SlashableVoteData
|
||||
slashable_vote_data_1*: SlashableVoteData
|
||||
slashable_vote_data_2*: SlashableVoteData
|
||||
|
||||
SlashableVoteData* = object
|
||||
aggregate_signature_poc_0_indices*: seq[Uint24] ##\
|
||||
|
@ -157,9 +157,14 @@ type
|
|||
|
||||
Attestation* = object
|
||||
data*: AttestationData
|
||||
participation_bitfield*: seq[byte] # Attester participation bitfield
|
||||
custody_bitfield*: seq[byte] # Proof of custody bitfield
|
||||
aggregate_signature*: ValidatorSig # BLS aggregate signature
|
||||
participation_bitfield*: seq[byte] ##\
|
||||
## The attesters that are represented in the aggregate signature - each
|
||||
## bit represents an index in `ShardCommittee.committee`
|
||||
|
||||
custody_bitfield*: seq[byte] ##\
|
||||
## Proof of custody - Phase 1
|
||||
aggregate_signature*: ValidatorSig ##\
|
||||
## Aggregate signature of the validators in `custody_bitfield`
|
||||
|
||||
AttestationData* = object
|
||||
slot*: uint64
|
||||
|
@ -220,9 +225,11 @@ type
|
|||
## is formed.
|
||||
|
||||
slot*: uint64
|
||||
parent_root*: Eth2Digest
|
||||
parent_root*: Eth2Digest ##\
|
||||
##\ Root hash of the previous block
|
||||
|
||||
state_root*: Eth2Digest
|
||||
state_root*: Eth2Digest ##\
|
||||
##\ The state root, _after_ this block has been processed
|
||||
|
||||
randao_reveal*: Eth2Digest ##\
|
||||
## Proposer RANDAO reveal
|
||||
|
@ -261,12 +268,15 @@ type
|
|||
## For light clients to easily track delta
|
||||
|
||||
# Randomness and committees
|
||||
randao_mix*: Eth2Digest # RANDAO state
|
||||
next_seed*: Eth2Digest # Randao seed used for next shuffling
|
||||
randao_mix*: Eth2Digest
|
||||
next_seed*: Eth2Digest ##\
|
||||
## Randao seed used for next shuffling
|
||||
|
||||
shard_committees_at_slots*: array[2 * EPOCH_LENGTH, seq[ShardCommittee]] ## \
|
||||
## Committee members and their assigned shard, per slot, covers 2 cycles
|
||||
## worth of assignments
|
||||
persistent_committees*: seq[seq[Uint24]] # Persistent shard committees
|
||||
|
||||
persistent_committees*: seq[seq[Uint24]]
|
||||
persistent_committee_reassignments*: seq[ShardReassignmentRecord]
|
||||
|
||||
# Finality
|
||||
|
@ -277,10 +287,12 @@ type
|
|||
|
||||
latest_crosslinks*: array[SHARD_COUNT, CrosslinkRecord]
|
||||
latest_state_recalculation_slot*: uint64
|
||||
latest_block_roots*: array[LATEST_BLOCK_ROOTS_COUNT.int, Eth2Digest] ##\
|
||||
latest_block_roots*: array[LATEST_BLOCK_ROOTS_LENGTH.int, Eth2Digest] ##\
|
||||
## Needed to process attestations, older to newer
|
||||
|
||||
latest_penalized_exit_balances*: seq[uint64] ##\
|
||||
## Balances penalized in the current withdrawal period
|
||||
|
||||
latest_attestations*: seq[PendingAttestationRecord]
|
||||
batched_block_roots*: seq[Eth2Digest]
|
||||
|
||||
|
@ -299,7 +311,8 @@ type
|
|||
## * processRandaoReveal
|
||||
|
||||
randao_layers*: uint64 ##\
|
||||
## Slot the proposer has skipped (ie. layers of RANDAO expected)
|
||||
## Number of proposals the proposer missed, and thus the number of times to
|
||||
## apply hash function to randao reveal
|
||||
|
||||
balance*: uint64 # Balance in Gwei
|
||||
status*: ValidatorStatusCodes
|
||||
|
@ -310,18 +323,28 @@ type
|
|||
## Exit counter when validator exited (or 0)
|
||||
|
||||
CrosslinkRecord* = object
|
||||
slot*: uint64 # Slot number
|
||||
shard_block_root*: Eth2Digest # Shard chain block hash
|
||||
slot*: uint64
|
||||
shard_block_root*: Eth2Digest ##\
|
||||
## Shard chain block root
|
||||
|
||||
ShardCommittee* = object
|
||||
shard*: uint64 # Shard number
|
||||
committee*: seq[Uint24] # Validator indices
|
||||
total_validator_count*: uint64 # # Total validator count (for proofs of custody)
|
||||
shard*: uint64
|
||||
committee*: seq[Uint24] ##\
|
||||
## Committe participants that get to attest to blocks on this shard -
|
||||
## indices into BeaconState.validator_registry
|
||||
|
||||
total_validator_count*: uint64 ##\
|
||||
## Total validator count (for proofs of custody)
|
||||
|
||||
ShardReassignmentRecord* = object
|
||||
validator_index*: Uint24 # Which validator to reassign
|
||||
shard*: uint64 # To which shard
|
||||
slot*: uint64 # When
|
||||
validator_index*: Uint24 ##\
|
||||
## Which validator to reassign
|
||||
|
||||
shard*: uint64 ##\
|
||||
## To which shard
|
||||
|
||||
slot*: uint64 ##\
|
||||
## When
|
||||
|
||||
CandidatePoWReceiptRootRecord* = object
|
||||
candidate_pow_receipt_root*: Eth2Digest # Candidate PoW receipt root
|
||||
|
@ -338,6 +361,12 @@ type
|
|||
post_fork_version*: uint64 # Post fork version
|
||||
fork_slot*: uint64 # Fork slot number
|
||||
|
||||
ValidatorRegistryDeltaBlock* = object
|
||||
latest_registry_delta_root*: Eth2Digest
|
||||
validator_index*: Uint24
|
||||
pubkey*: ValidatorPubKey
|
||||
flag*: ValidatorSetDeltaFlags
|
||||
|
||||
ValidatorStatusCodes* {.pure.} = enum
|
||||
PENDING_ACTIVATION = 0
|
||||
ACTIVE = 1
|
||||
|
|
|
@ -111,11 +111,14 @@ func get_beacon_proposer_index*(state: BeaconState, slot: uint64): Uint24 =
|
|||
## 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)
|
||||
|
||||
# TODO this index is invalid outside of the block state transition function
|
||||
# because presently, `state.slot += 1` happens before this function
|
||||
# is called - see also testutil.getNextBeaconProposerIndex
|
||||
let idx = get_shard_committees_index(state, slot)
|
||||
state.shard_committees_at_slots[idx][0].committee.mod_get(slot)
|
||||
|
||||
func int_sqrt*(n: SomeInteger): SomeInteger =
|
||||
func integer_squareroot*(n: SomeInteger): SomeInteger =
|
||||
## The largest integer ``x`` such that ``x**2`` is less than ``n``.
|
||||
var
|
||||
x = n
|
||||
y = (x + 1) div 2
|
||||
|
@ -141,4 +144,24 @@ func merkle_root*(values: openArray[Eth2Digest]): Eth2Digest =
|
|||
# o[i] = hash(o[i*2] + o[i*2+1])
|
||||
# return o[1]
|
||||
# TODO
|
||||
discard
|
||||
discard
|
||||
|
||||
proc is_double_vote*(attestation_data_1: AttestationData,
|
||||
attestation_data_2: AttestationData): bool =
|
||||
## Assumes ``attestation_data_1`` is distinct from ``attestation_data_2``.
|
||||
## Returns True if the provided ``AttestationData`` are slashable
|
||||
## due to a 'double vote'.
|
||||
attestation_data_1.slot == attestation_data_2.slot
|
||||
|
||||
proc is_surround_vote*(attestation_data_1: AttestationData,
|
||||
attestation_data_2: AttestationData): bool =
|
||||
## Assumes ``attestation_data_1`` is distinct from ``attestation_data_2``.
|
||||
## Returns True if the provided ``AttestationData`` are slashable
|
||||
## due to a 'surround vote'.
|
||||
## Note: parameter order matters as this function only checks
|
||||
## that ``attestation_data_1`` surrounds ``attestation_data_2``.
|
||||
(
|
||||
(attestation_data_1.justified_slot < attestation_data_2.justified_slot) and
|
||||
(attestation_data_1.justified_slot + 1 == attestation_data_2.slot) and
|
||||
(attestation_data_2.slot < attestation_data_1.slot)
|
||||
)
|
||||
|
|
|
@ -71,11 +71,12 @@ func get_new_validator_registry_delta_chain_tip*(
|
|||
flag: ValidatorSetDeltaFlags): Eth2Digest =
|
||||
## Compute the next hash in the validator registry delta hash chain.
|
||||
|
||||
withEth2Hash:
|
||||
h.update hash_tree_root(current_validator_registry_delta_chain_tip)
|
||||
h.update hash_tree_root(flag.uint8)
|
||||
h.update hash_tree_root(index)
|
||||
h.update hash_tree_root(pubkey)
|
||||
Eth2Digest(data: hash_tree_root(ValidatorRegistryDeltaBlock(
|
||||
latest_registry_delta_root: current_validator_registry_delta_chain_tip,
|
||||
validator_index: index,
|
||||
pubkey: pubkey,
|
||||
flag: flag
|
||||
)))
|
||||
|
||||
func get_effective_balance*(validator: ValidatorRecord): uint64 =
|
||||
min(validator.balance, MAX_DEPOSIT * GWEI_PER_ETH)
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
# signed bigint semantics - under- and overflows ensue
|
||||
# * Sane error handling is missing in most cases (yay, we'll get the chance to
|
||||
# debate exceptions again!)
|
||||
#
|
||||
# When updating the code, add TODO sections to mark where there are clear
|
||||
# improvements to be made - other than that, keep things similar to spec for
|
||||
# now.
|
||||
|
@ -44,6 +43,10 @@ func flatten[T](v: openArray[seq[T]]): seq[T] =
|
|||
for x in v: result.add x
|
||||
|
||||
func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
||||
## When creating the block, the proposer will sign a version of the block that
|
||||
## doesn't contain the data (chicken and egg), then add the signature to that
|
||||
## block.
|
||||
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#proposer-signature
|
||||
|
||||
var blck_without_sig = blck
|
||||
|
@ -56,15 +59,14 @@ func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
|||
block_root: Eth2Digest(data: hash_tree_root(blck_without_sig))
|
||||
)
|
||||
proposal_hash = hash_tree_root(signed_data)
|
||||
|
||||
let proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
|
||||
bls_verify(
|
||||
state.validator_registry[proposer_index].pubkey,
|
||||
proposal_hash, blck.signature,
|
||||
get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))
|
||||
|
||||
func processRandao(state: var BeaconState, blck: BeaconBlock): bool =
|
||||
func processRandao(state: var BeaconState, blck: BeaconBlock, force: bool): bool =
|
||||
## When a validator signs up, they will commit an hash to the block,
|
||||
## the randao_commitment - this hash is the result of a secret value
|
||||
## hashed n times.
|
||||
|
@ -82,10 +84,11 @@ func processRandao(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
proposer_index = get_beacon_proposer_index(state, state.slot)
|
||||
proposer = addr state.validator_registry[proposer_index]
|
||||
|
||||
# Check that proposer commit and reveal match
|
||||
if repeat_hash(blck.randao_reveal, proposer.randao_layers) !=
|
||||
proposer.randao_commitment:
|
||||
return false
|
||||
if not force:
|
||||
# Check that proposer commit and reveal match
|
||||
if repeat_hash(blck.randao_reveal, proposer.randao_layers) !=
|
||||
proposer.randao_commitment:
|
||||
return false
|
||||
|
||||
# Update state and proposer now that we're alright
|
||||
for i, b in state.randao_mix.data:
|
||||
|
@ -189,29 +192,32 @@ proc processCasperSlashings(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
return false
|
||||
|
||||
for casper_slashing in blck.body.casper_slashings:
|
||||
if not verify_slashable_vote_data(state, casper_slashing.votes_1):
|
||||
warn("CaspSlash: invalid votes 1")
|
||||
return false
|
||||
if not verify_slashable_vote_data(state, casper_slashing.votes_2):
|
||||
warn("CaspSlash: invalid votes 2")
|
||||
return false
|
||||
if not (casper_slashing.votes_1.data != casper_slashing.votes_2.data):
|
||||
let
|
||||
slashable_vote_data_1 = casper_slashing.slashable_vote_data_1
|
||||
slashable_vote_data_2 = casper_slashing.slashable_vote_data_2
|
||||
intersection = filterIt(
|
||||
indices(slashable_vote_data_1), it in indices(slashable_vote_data_2))
|
||||
|
||||
if not (slashable_vote_data_1.data != slashable_vote_data_2.data):
|
||||
warn("CaspSlash: invalid data")
|
||||
return false
|
||||
|
||||
let intersection = filterIt(
|
||||
indices(casper_slashing.votes_1), it in indices(casper_slashing.votes_2))
|
||||
|
||||
if len(intersection) < 1:
|
||||
if not (len(intersection) >= 1):
|
||||
warn("CaspSlash: no intersection")
|
||||
return false
|
||||
|
||||
if not (
|
||||
((casper_slashing.votes_1.data.justified_slot + 1 <
|
||||
casper_slashing.votes_2.data.justified_slot + 1) ==
|
||||
(casper_slashing.votes_2.data.slot < casper_slashing.votes_1.data.slot)) or
|
||||
(casper_slashing.votes_1.data.slot == casper_slashing.votes_2.data.slot)):
|
||||
warn("CaspSlash: some weird long condition failed")
|
||||
is_double_vote(slashable_vote_data_1.data, slashable_vote_data_2.data) or
|
||||
is_surround_vote(slashable_vote_data_1.data, slashable_vote_data_2.data)):
|
||||
warn("CaspSlash: surround or double vote check failed")
|
||||
return false
|
||||
|
||||
if not verify_slashable_vote_data(state, slashable_vote_data_1):
|
||||
warn("CaspSlash: invalid votes 1")
|
||||
return false
|
||||
|
||||
if not verify_slashable_vote_data(state, slashable_vote_data_2):
|
||||
warn("CaspSlash: invalid votes 2")
|
||||
return false
|
||||
|
||||
for i in intersection:
|
||||
|
@ -295,22 +301,56 @@ proc process_ejections(state: var BeaconState) =
|
|||
if is_active_validator(validator) and validator.balance < EJECTION_BALANCE:
|
||||
update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY)
|
||||
|
||||
proc processBlock(state: var BeaconState, latest_block, blck: BeaconBlock): bool =
|
||||
func processSlot(state: var BeaconState, latest_block: BeaconBlock) =
|
||||
## Time on the beacon chain moves in slots. Every time we make it to a new
|
||||
## slot, a proposer cleates a block to represent the state of the beacon
|
||||
## chain at that time. In case the proposer is missing, it may happen that
|
||||
## the no block is produced during the slot.
|
||||
##
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#per-slot-processing
|
||||
|
||||
state.slot += 1
|
||||
state.validator_registry[
|
||||
get_beacon_proposer_index(state, state.slot)].randao_layers += 1
|
||||
|
||||
let
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(latest_block))
|
||||
for i in 0 ..< state.latest_block_roots.len - 1:
|
||||
state.latest_block_roots[i] = state.latest_block_roots[i + 1]
|
||||
state.latest_block_roots[state.latest_block_roots.len - 1] =
|
||||
previous_block_root
|
||||
|
||||
if state.slot mod LATEST_BLOCK_ROOTS_LENGTH == 0:
|
||||
state.batched_block_roots.add(merkle_root(state.latest_block_roots))
|
||||
|
||||
|
||||
proc processBlock(
|
||||
state: var BeaconState, blck: BeaconBlock, force: bool): bool =
|
||||
## When there's a new block, we need to verify that the block is sane and
|
||||
## update the state accordingly
|
||||
|
||||
# TODO when there's a failure, we should reset the state!
|
||||
# TODO probably better to do all verification first, then apply state changes
|
||||
|
||||
if blck.slot != state.slot:
|
||||
if not (blck.slot == state.slot):
|
||||
warn("Unexpected block slot number")
|
||||
return false
|
||||
|
||||
if not verifyProposerSignature(state, blck):
|
||||
warn("Proposer signature not valid")
|
||||
# TODO does this need checking? not in the spec!
|
||||
if not (blck.parent_root == state.latest_block_roots[^1]):
|
||||
warn("Unexpected parent root")
|
||||
return false
|
||||
|
||||
if not processRandao(state, blck):
|
||||
if not force:
|
||||
# TODO Technically, we could make processBlock take a generic type instead
|
||||
# of BeaconBlock - we would then have an intermediate `ProposedBlock`
|
||||
# type that omits some fields - this way, the compiler would guarantee
|
||||
# that we don't try to access fields that don't have a value yet
|
||||
if not verifyProposerSignature(state, blck):
|
||||
warn("Proposer signature not valid")
|
||||
return false
|
||||
|
||||
if not processRandao(state, blck, force):
|
||||
warn("Randao reveal failed")
|
||||
return false
|
||||
|
||||
|
@ -335,34 +375,10 @@ proc processBlock(state: var BeaconState, latest_block, blck: BeaconBlock): bool
|
|||
|
||||
return true
|
||||
|
||||
func processSlot(state: var BeaconState, latest_block: BeaconBlock) =
|
||||
## Time on the beacon chain moves in slots. Every time we make it to a new
|
||||
## slot, a proposer cleates a block to represent the state of the beacon
|
||||
## chain at that time. In case the proposer is missing, it may happen that
|
||||
## the no block is produced during the slot.
|
||||
##
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#per-slot-processing
|
||||
|
||||
let
|
||||
latest_hash = Eth2Digest(data: hash_tree_root(latest_block))
|
||||
|
||||
state.slot += 1
|
||||
state.validator_registry[
|
||||
get_beacon_proposer_index(state, state.slot)].randao_layers += 1
|
||||
|
||||
let
|
||||
previous_block_root = Eth2Digest(data: hash_tree_root(latest_block))
|
||||
for i in 0 ..< state.latest_block_roots.len - 1:
|
||||
state.latest_block_roots[i] = state.latest_block_roots[i + 1]
|
||||
state.latest_block_roots[state.latest_block_roots.len - 1] =
|
||||
previous_block_root
|
||||
|
||||
if state.slot mod LATEST_BLOCK_ROOTS_COUNT == 0:
|
||||
state.batched_block_roots.add(merkle_root(state.latest_block_roots))
|
||||
|
||||
func get_epoch_boundary_attesters(
|
||||
func get_attesters(
|
||||
state: BeaconState,
|
||||
attestations: openArray[PendingAttestationRecord]): seq[Uint24] =
|
||||
# Union of attesters that participated in some attestations
|
||||
# TODO spec - add as helper?
|
||||
deduplicate(flatten(mapIt(attestations,
|
||||
get_attestation_participants(state, it.data, it.participation_bitfield))))
|
||||
|
@ -381,7 +397,7 @@ func boundary_attestations(
|
|||
|
||||
func sum_effective_balances(
|
||||
state: BeaconState, validator_indices: openArray[Uint24]): uint64 =
|
||||
# TODO spec - add as helper?
|
||||
# TODO spec - add as helper?
|
||||
sum(mapIt(
|
||||
validator_indices, get_effective_balance(state.validator_registry[it]))
|
||||
)
|
||||
|
@ -408,7 +424,8 @@ func processEpoch(state: var BeaconState) =
|
|||
total_balance_in_eth = total_balance div GWEI_PER_ETH
|
||||
|
||||
# The per-slot maximum interest rate is `2/reward_quotient`.)
|
||||
base_reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth)
|
||||
base_reward_quotient =
|
||||
BASE_REWARD_QUOTIENT * integer_squareroot(total_balance_in_eth)
|
||||
|
||||
func base_reward(v: ValidatorRecord): uint64 =
|
||||
get_effective_balance(v) div base_reward_quotient.uint64 div 4
|
||||
|
@ -433,7 +450,7 @@ func processEpoch(state: var BeaconState) =
|
|||
this_epoch_attestations)
|
||||
|
||||
this_epoch_boundary_attesters =
|
||||
get_epoch_boundary_attesters(state, this_epoch_attestations)
|
||||
get_attesters(state, this_epoch_attestations)
|
||||
|
||||
this_epoch_boundary_attesting_balance =
|
||||
sum_effective_balances(state, this_epoch_boundary_attesters)
|
||||
|
@ -444,10 +461,8 @@ func processEpoch(state: var BeaconState) =
|
|||
state.slot <= it.data.slot + 2 * EPOCH_LENGTH and
|
||||
it.data.slot + EPOCH_LENGTH < state.slot)
|
||||
|
||||
previous_epoch_attesters = flatten(mapIt(
|
||||
previous_epoch_attestations,
|
||||
get_attestation_participants(state, it.data, it.participation_bitfield)
|
||||
))
|
||||
previous_epoch_attesters =
|
||||
get_attesters(state, previous_epoch_attestations)
|
||||
|
||||
let # Previous epoch justified
|
||||
previous_epoch_justified_attestations = filterIt(
|
||||
|
@ -455,10 +470,8 @@ func processEpoch(state: var BeaconState) =
|
|||
it.data.justified_slot == state.previous_justified_slot
|
||||
)
|
||||
|
||||
previous_epoch_justified_attesters = flatten(mapIt(
|
||||
previous_epoch_justified_attestations,
|
||||
get_attestation_participants(state, it.data, it.participation_bitfield)
|
||||
))
|
||||
previous_epoch_justified_attesters =
|
||||
get_attesters(state, previous_epoch_justified_attestations)
|
||||
|
||||
previous_epoch_justified_attesting_balance =
|
||||
sum_effective_balances(state, previous_epoch_justified_attesters)
|
||||
|
@ -472,10 +485,8 @@ func processEpoch(state: var BeaconState) =
|
|||
state, get_block_root(state, negative_uint_hack),
|
||||
previous_epoch_attestations)
|
||||
|
||||
previous_epoch_boundary_attesters = flatten(mapIt(
|
||||
previous_epoch_boundary_attestations,
|
||||
get_attestation_participants(state, it.data, it.participation_bitfield)
|
||||
))
|
||||
previous_epoch_boundary_attesters =
|
||||
get_attesters(state, previous_epoch_boundary_attestations)
|
||||
|
||||
previous_epoch_boundary_attesting_balance =
|
||||
sum_effective_balances(state, previous_epoch_boundary_attesters)
|
||||
|
@ -486,10 +497,8 @@ func processEpoch(state: var BeaconState) =
|
|||
previous_epoch_attestations,
|
||||
it.data.beacon_block_root == get_block_root(state, it.data.slot))
|
||||
|
||||
previous_epoch_head_attesters = flatten(mapIt(
|
||||
previous_epoch_head_attestations,
|
||||
get_attestation_participants(state, it.data, it.participation_bitfield)
|
||||
))
|
||||
previous_epoch_head_attesters =
|
||||
get_attesters(state, previous_epoch_head_attestations)
|
||||
|
||||
previous_epoch_head_attesting_balance =
|
||||
sum_effective_balances(state, previous_epoch_head_attesters)
|
||||
|
@ -500,12 +509,11 @@ func processEpoch(state: var BeaconState) =
|
|||
let statePtr = state.addr
|
||||
func attesting_validators(
|
||||
shard_committee: ShardCommittee, shard_block_root: Eth2Digest): seq[Uint24] =
|
||||
flatten(
|
||||
mapIt(
|
||||
filterIt(concat(this_epoch_attestations, previous_epoch_attestations),
|
||||
it.data.shard == shard_committee.shard and
|
||||
it.data.shard_block_root == shard_block_root),
|
||||
get_attestation_participants(statePtr[], it.data, it.participation_bitfield)))
|
||||
let shard_block_attestations =
|
||||
filterIt(concat(this_epoch_attestations, previous_epoch_attestations),
|
||||
it.data.shard == shard_committee.shard and
|
||||
it.data.shard_block_root == shard_block_root)
|
||||
get_attesters(statePtr[], shard_block_attestations)
|
||||
|
||||
func winning_hash(obj: ShardCommittee): Eth2Digest =
|
||||
# * Let `winning_hash(obj)` be the winning `shard_block_root` value.
|
||||
|
@ -544,13 +552,13 @@ func processEpoch(state: var BeaconState) =
|
|||
for a in statePtr[].latest_attestations:
|
||||
if v in get_attestation_participants(statePtr[], a.data, a.participation_bitfield):
|
||||
return a.slot_included
|
||||
assert false # shouldn't happen..
|
||||
doAssert false # shouldn't happen..
|
||||
|
||||
func inclusion_distance(v: Uint24): uint64 =
|
||||
for a in statePtr[].latest_attestations:
|
||||
if v in get_attestation_participants(statePtr[], a.data, a.participation_bitfield):
|
||||
return a.slot_included - a.data.slot
|
||||
assert false # shouldn't happen..
|
||||
doAssert false # shouldn't happen..
|
||||
|
||||
block: # Receipt roots
|
||||
if state.slot mod POW_RECEIPT_ROOT_VOTING_PERIOD == 0:
|
||||
|
@ -567,23 +575,25 @@ func processEpoch(state: var BeaconState) =
|
|||
# TODO why are all bits kept?
|
||||
state.justification_bitfield = state.justification_bitfield shl 1
|
||||
|
||||
if 3'u64 * previous_epoch_boundary_attesting_balance >=
|
||||
2'u64 * total_balance:
|
||||
state.justification_bitfield = state.justification_bitfield or 2
|
||||
state.justified_slot = state.slot - 2 * EPOCH_LENGTH
|
||||
# TODO Spec - underflow
|
||||
if state.slot >= 2'u64 * EPOCH_LENGTH:
|
||||
if 3'u64 * previous_epoch_boundary_attesting_balance >=
|
||||
2'u64 * total_balance:
|
||||
state.justification_bitfield = state.justification_bitfield or 2
|
||||
state.justified_slot = state.slot - 2 * EPOCH_LENGTH
|
||||
|
||||
if 3'u64 * this_epoch_boundary_attesting_balance >=
|
||||
2'u64 * total_balance:
|
||||
state.justification_bitfield = state.justification_bitfield or 1
|
||||
state.justified_slot = state.slot - 1 * EPOCH_LENGTH
|
||||
if 3'u64 * this_epoch_boundary_attesting_balance >=
|
||||
2'u64 * total_balance:
|
||||
state.justification_bitfield = state.justification_bitfield or 1
|
||||
state.justified_slot = state.slot - 1 * EPOCH_LENGTH
|
||||
|
||||
block: # Finalization
|
||||
if
|
||||
(state.previous_justified_slot == state.slot - 2 * EPOCH_LENGTH and
|
||||
(state.previous_justified_slot + 2 * EPOCH_LENGTH == state.slot and
|
||||
state.justification_bitfield mod 4 == 3) or
|
||||
(state.previous_justified_slot == state.slot - 3 * EPOCH_LENGTH and
|
||||
(state.previous_justified_slot + 3 * EPOCH_LENGTH == state.slot and
|
||||
state.justification_bitfield mod 8 == 7) or
|
||||
(state.previous_justified_slot == state.slot - 4 * EPOCH_LENGTH and
|
||||
(state.previous_justified_slot + 4 * EPOCH_LENGTH == state.slot and
|
||||
state.justification_bitfield mod 16 in [15'u64, 14]):
|
||||
state.finalized_slot = state.justified_slot
|
||||
|
||||
|
@ -600,42 +610,33 @@ func processEpoch(state: var BeaconState) =
|
|||
let
|
||||
slots_since_finality = state.slot - state.finalized_slot
|
||||
|
||||
proc update_balance(attesters: openArray[Uint24], attesting_balance: uint64) =
|
||||
# TODO Spec - add helper?
|
||||
for v in attesters:
|
||||
statePtr.validator_registry[v].balance += adjust_for_inclusion_distance(
|
||||
base_reward(statePtr.validator_registry[v]) *
|
||||
attesting_balance div total_balance, inclusion_distance(v))
|
||||
|
||||
for v in active_validator_indices:
|
||||
if v notin attesters:
|
||||
statePtr.validator_registry[v].balance -=
|
||||
base_reward(statePtr.validator_registry[v])
|
||||
|
||||
if slots_since_finality <= 4'u64 * EPOCH_LENGTH:
|
||||
# Expected FFG source
|
||||
for v in previous_epoch_justified_attesters:
|
||||
state.validator_registry[v].balance += adjust_for_inclusion_distance(
|
||||
base_reward(state.validator_registry[v]) *
|
||||
previous_epoch_justified_attesting_balance div total_balance,
|
||||
inclusion_distance(v))
|
||||
|
||||
for v in active_validator_indices:
|
||||
if v notin previous_epoch_justified_attesters:
|
||||
state.validator_registry[v].balance -=
|
||||
base_reward(state.validator_registry[v])
|
||||
update_balance(
|
||||
previous_epoch_justified_attesters,
|
||||
previous_epoch_justified_attesting_balance)
|
||||
|
||||
# Expected FFG target:
|
||||
for v in previous_epoch_boundary_attesters:
|
||||
state.validator_registry[v].balance += adjust_for_inclusion_distance(
|
||||
base_reward(state.validator_registry[v]) *
|
||||
previous_epoch_boundary_attesting_balance div total_balance,
|
||||
inclusion_distance(v))
|
||||
|
||||
for v in active_validator_indices:
|
||||
if v notin previous_epoch_boundary_attesters:
|
||||
state.validator_registry[v].balance -=
|
||||
base_reward(state.validator_registry[v])
|
||||
update_balance(
|
||||
previous_epoch_boundary_attesters,
|
||||
previous_epoch_boundary_attesting_balance)
|
||||
|
||||
# Expected beacon chain head:
|
||||
for v in previous_epoch_head_attesters:
|
||||
state.validator_registry[v].balance += adjust_for_inclusion_distance(
|
||||
base_reward(state.validator_registry[v]) *
|
||||
previous_epoch_head_attesting_balance div total_balance,
|
||||
inclusion_distance(v))
|
||||
|
||||
for v in active_validator_indices:
|
||||
if v notin previous_epoch_head_attesters:
|
||||
state.validator_registry[v].balance -=
|
||||
base_reward(state.validator_registry[v])
|
||||
update_balance(
|
||||
previous_epoch_head_attesters,
|
||||
previous_epoch_head_attesting_balance)
|
||||
|
||||
else:
|
||||
for v in active_validator_indices:
|
||||
|
@ -655,8 +656,7 @@ func processEpoch(state: var BeaconState) =
|
|||
|
||||
block: # Attestation inclusion
|
||||
for v in previous_epoch_attesters:
|
||||
let proposer_index =
|
||||
get_beacon_proposer_index(state, inclusion_slot(v))
|
||||
let proposer_index = get_beacon_proposer_index(state, inclusion_slot(v))
|
||||
state.validator_registry[proposer_index].balance +=
|
||||
base_reward(state.validator_registry[v]) div INCLUDER_REWARD_QUOTIENT
|
||||
|
||||
|
@ -745,7 +745,7 @@ func processEpoch(state: var BeaconState) =
|
|||
)
|
||||
|
||||
proc updateState*(state: BeaconState, latest_block: BeaconBlock,
|
||||
new_block: Option[BeaconBlock]):
|
||||
new_block: Option[BeaconBlock], force: bool):
|
||||
tuple[state: BeaconState, block_ok: bool] =
|
||||
## Time in the beacon chain moves by slots. Every time (haha.) that happens,
|
||||
## we will update the beacon state. Normally, the state updates will be driven
|
||||
|
@ -753,6 +753,10 @@ proc updateState*(state: BeaconState, latest_block: BeaconBlock,
|
|||
## missing - the state updates happen regardless.
|
||||
## Each call to this function will advance the state by one slot - new_block,
|
||||
## if present, must match that slot.
|
||||
##
|
||||
## The `force` flag is used to skip a few checks, and apply `new_block`
|
||||
## regardless. We do this because some fields in the block depend on the state
|
||||
## being updated with the information in the new block.
|
||||
#
|
||||
# TODO this function can be written with a loop inside to handle all empty
|
||||
# slots up to the slot of the new_block - but then again, why not eagerly
|
||||
|
@ -779,20 +783,34 @@ proc updateState*(state: BeaconState, latest_block: BeaconBlock,
|
|||
# TODO what should happen if block processing fails?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/293
|
||||
var block_state = new_state
|
||||
if processBlock(block_state, latest_block, new_block.get()):
|
||||
if processBlock(block_state, new_block.get(), force):
|
||||
# processBlock will mutate the state! only apply if it worked..
|
||||
# TODO yeah, this too is inefficient
|
||||
new_state = block_state
|
||||
true
|
||||
processEpoch(block_state)
|
||||
|
||||
# This is a bit awkward - at the end of processing we verify that the
|
||||
# state we arrive at is what the block producer thought it would be -
|
||||
# meaning that potentially, it could fail verification
|
||||
let expected_state_root = Eth2Digest(data: hash_tree_root(block_state))
|
||||
if force or
|
||||
(new_block.get().state_root == expected_state_root):
|
||||
new_state = block_state
|
||||
true
|
||||
else:
|
||||
warn("State: root verification failed",
|
||||
state_root = new_block.get().state_root, expected_state_root)
|
||||
processEpoch(new_state)
|
||||
false
|
||||
else:
|
||||
false
|
||||
else:
|
||||
# Skip all per-block processing. Move directly to epoch processing
|
||||
# prison. Do not do any block updates when passing go.
|
||||
true
|
||||
|
||||
# Heavy updates that happen for every epoch - these never fail (or so we hope)
|
||||
processEpoch(new_state)
|
||||
# Heavy updates that happen for every epoch - these never fail (or so we hope)
|
||||
processEpoch(new_state)
|
||||
true
|
||||
|
||||
# State update never fails, but block validation might...
|
||||
(new_state, block_ok)
|
||||
|
|
|
@ -4,7 +4,8 @@ import
|
|||
../tests/testutil
|
||||
|
||||
proc stateSize(deposits: int) =
|
||||
let state = on_startup(makeInitialDeposits(deposits), 0, Eth2Digest())
|
||||
let state = get_initial_beacon_state(
|
||||
makeInitialDeposits(deposits), 0, Eth2Digest())
|
||||
|
||||
echo "Validators: ", deposits, ", total: ", state.serialize().len
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import
|
|||
../beacon_chain/spec/[beaconstate, datatypes, digest]
|
||||
|
||||
suite "Beacon state":
|
||||
test "Smoke test on_startup":
|
||||
let state = on_startup(makeInitialDeposits(EPOCH_LENGTH), 0, Eth2Digest())
|
||||
test "Smoke test get_initial_beacon_state":
|
||||
let state = get_initial_beacon_state(
|
||||
makeInitialDeposits(EPOCH_LENGTH), 0, Eth2Digest())
|
||||
check: state.validator_registry.len == EPOCH_LENGTH
|
||||
|
|
|
@ -15,21 +15,28 @@ suite "Block processing":
|
|||
## For now just test that we can compile and execute block processing with
|
||||
## mock data.
|
||||
|
||||
let
|
||||
# Genesis state with minimal number of deposits
|
||||
# TODO bls verification is a bit of a bottleneck here
|
||||
genesisState = get_initial_beacon_state(
|
||||
makeInitialDeposits(), 0, Eth2Digest())
|
||||
genesisBlock = makeGenesisBlock(genesisState)
|
||||
|
||||
test "Passes from genesis state, no block":
|
||||
let
|
||||
state = on_startup(makeInitialDeposits(), 0, Eth2Digest())
|
||||
latest_block = makeGenesisBlock(state)
|
||||
new_state = updateState(state, latest_block, none(BeaconBlock))
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
new_state = updateState(state, latest_block, none(BeaconBlock), false)
|
||||
check:
|
||||
new_state.state.slot == latest_block.slot + 1
|
||||
new_state.block_ok
|
||||
|
||||
test "Passes from genesis state, empty block":
|
||||
let
|
||||
state = on_startup(makeInitialDeposits(), 0, Eth2Digest())
|
||||
latest_block = makeGenesisBlock(state)
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
new_block = makeBlock(state, latest_block)
|
||||
new_state = updateState(state, latest_block, some(new_block))
|
||||
new_state = updateState(state, latest_block, some(new_block), false)
|
||||
|
||||
check:
|
||||
new_state.state.slot == latest_block.slot + 1
|
||||
|
@ -37,11 +44,11 @@ suite "Block processing":
|
|||
|
||||
test "Passes through epoch update, no block":
|
||||
var
|
||||
state = on_startup(makeInitialDeposits(), 0, Eth2Digest())
|
||||
latest_block = makeGenesisBlock(state)
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
|
||||
for i in 1..EPOCH_LENGTH.int:
|
||||
let new_state = updateState(state, latest_block, none(BeaconBlock))
|
||||
let new_state = updateState(state, latest_block, none(BeaconBlock), false)
|
||||
check:
|
||||
new_state.block_ok
|
||||
state = new_state.state
|
||||
|
@ -51,13 +58,13 @@ suite "Block processing":
|
|||
|
||||
test "Passes through epoch update, empty block":
|
||||
var
|
||||
state = on_startup(makeInitialDeposits(), 0, Eth2Digest())
|
||||
latest_block = makeGenesisBlock(state)
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
|
||||
for i in 1..EPOCH_LENGTH.int:
|
||||
var new_block = makeBlock(state, latest_block)
|
||||
|
||||
let new_state = updateState(state, latest_block, some(new_block))
|
||||
let new_state = updateState(state, latest_block, some(new_block), false)
|
||||
|
||||
check:
|
||||
new_state.block_ok
|
||||
|
@ -69,11 +76,12 @@ suite "Block processing":
|
|||
|
||||
test "Increments proposer randao_layers, no block":
|
||||
let
|
||||
state = on_startup(makeInitialDeposits(), 0, Eth2Digest())
|
||||
latest_block = makeGenesisBlock(state)
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot + 1)
|
||||
previous_randao_layers = state.validator_registry[proposer_index].randao_layers
|
||||
new_state = updateState(state, latest_block, none(BeaconBlock))
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_randao_layers =
|
||||
state.validator_registry[proposer_index].randao_layers
|
||||
new_state = updateState(state, latest_block, none(BeaconBlock), false)
|
||||
updated_proposer = new_state.state.validator_registry[proposer_index]
|
||||
|
||||
check:
|
||||
|
@ -81,12 +89,13 @@ suite "Block processing":
|
|||
|
||||
test "Proposer randao layers unchanged, empty block":
|
||||
let
|
||||
state = on_startup(makeInitialDeposits(), 0, Eth2Digest())
|
||||
latest_block = makeGenesisBlock(state)
|
||||
proposer_index = get_beacon_proposer_index(state, state.slot + 1)
|
||||
previous_randao_layers = state.validator_registry[proposer_index].randao_layers
|
||||
state = genesisState
|
||||
latest_block = genesisBlock
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_randao_layers =
|
||||
state.validator_registry[proposer_index].randao_layers
|
||||
new_block = makeBlock(state, latest_block)
|
||||
new_state = updateState(state, latest_block, some(new_block))
|
||||
new_state = updateState(state, latest_block, some(new_block), false)
|
||||
updated_proposer = new_state.state.validator_registry[proposer_index]
|
||||
|
||||
check:
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
milagro_crypto,
|
||||
../beacon_chain/[extras, ssz],
|
||||
options, milagro_crypto,
|
||||
../beacon_chain/[extras, ssz, state_transition],
|
||||
../beacon_chain/spec/[crypto, datatypes, digest, helpers]
|
||||
|
||||
const
|
||||
|
@ -71,25 +71,55 @@ func makeGenesisBlock*(state: BeaconState): BeaconBlock =
|
|||
state_root: Eth2Digest(data: hash_tree_root(state))
|
||||
)
|
||||
|
||||
func makeBlock*(
|
||||
state: BeaconState, latest_block: BeaconBlock): BeaconBlock =
|
||||
func getNextBeaconProposerIndex*(state: BeaconState): Uint24 =
|
||||
# TODO: this works but looks wrong - we update the slot in the state without
|
||||
# updating corresponding data - this works because the state update
|
||||
# code does the same - updates slot, then uses that slot when calling
|
||||
# beacon_proposer_index, then finally updates the shuffling at the end!
|
||||
var next_state = state
|
||||
next_state.slot += 1
|
||||
let
|
||||
proposer = state.validator_registry[
|
||||
get_beacon_proposer_index(next_state, next_state.slot)]
|
||||
get_beacon_proposer_index(next_state, next_state.slot)
|
||||
|
||||
var new_block = BeaconBlock(
|
||||
slot: next_state.slot,
|
||||
state_root: Eth2Digest(data: hash_tree_root(state)),
|
||||
randao_reveal: hackReveal(proposer)
|
||||
proc makeBlock*(state: BeaconState, previous_block: BeaconBlock): BeaconBlock =
|
||||
# Create a block for `state.slot + 1` - like a block proposer would do!
|
||||
# It's a bit awkward - in order to produce a block for N+1, we need to
|
||||
# calculate what the state will look like after that block has been applied,
|
||||
# because the block includes the state root.
|
||||
|
||||
let
|
||||
# Index from the new state, but registry from the old state.. hmm...
|
||||
proposer = state.validator_registry[getNextBeaconProposerIndex(state)]
|
||||
|
||||
var
|
||||
# In order to reuse the state transition function, we first create a dummy
|
||||
# block that has some fields set, and use that to generate the state as it
|
||||
# would look with the new block applied.
|
||||
new_block = BeaconBlock(
|
||||
slot: state.slot + 1,
|
||||
|
||||
# TODO is this checked anywhere?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/336
|
||||
parent_root: Eth2Digest(data: hash_tree_root(previous_block)),
|
||||
state_root: Eth2Digest(), # we need the new state first
|
||||
randao_reveal: hackReveal(proposer),
|
||||
candidate_pow_receipt_root: Eth2Digest(), # TODO
|
||||
signature: ValidatorSig(), # we need the rest of the block first!
|
||||
body: BeaconBlockBody() # TODO throw in stuff here...
|
||||
)
|
||||
|
||||
let
|
||||
next_state = updateState(state, previous_block, some(new_block), true)
|
||||
assert next_state.block_ok
|
||||
|
||||
# Ok, we have the new state as it would look with the block applied - now we
|
||||
# can set the state root in order to be able to create a valid signature
|
||||
new_block.state_root = Eth2Digest(data: hash_tree_root(next_state.state))
|
||||
|
||||
let
|
||||
proposerPrivkey = hackPrivKey(proposer)
|
||||
|
||||
# Once we've collected all the state data, we sign the block data along with
|
||||
# some book-keeping values
|
||||
signed_data = ProposalSignedData(
|
||||
slot: new_block.slot,
|
||||
shard: BEACON_CHAIN_SHARD_NUMBER,
|
||||
|
@ -97,14 +127,18 @@ func makeBlock*(
|
|||
)
|
||||
proposal_hash = hash_tree_root(signed_data)
|
||||
|
||||
proposerPrivkey = hackPrivKey(proposer)
|
||||
|
||||
assert proposerPrivkey.fromSigKey() == proposer.pubkey
|
||||
assert proposerPrivkey.fromSigKey() == proposer.pubkey,
|
||||
"signature key should be derived from private key! - wrong privkey?"
|
||||
|
||||
# We have a signature - put it in the block and we should be done!
|
||||
new_block.signature =
|
||||
# TODO domain missing!
|
||||
signMessage(proposerPrivkey, proposal_hash)
|
||||
|
||||
assert verifyMessage(
|
||||
new_block.signature, proposal_hash, proposerPrivkey.fromSigKey())
|
||||
assert bls_verify(
|
||||
proposer.pubkey,
|
||||
proposal_hash, new_block.signature,
|
||||
get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL)),
|
||||
"we just signed this message - it should pass verification!"
|
||||
|
||||
new_block
|
||||
|
|
Loading…
Reference in New Issue