spec updates (#34)

* spec updates

* make several constants uint64 to help minimize casting
* document data type woes - will have to revisit these
* change comment style on fields and constants to make room for better
comments
* add BLSVerify and BLSAddPbkeys facades to insulate spec code from
milagro
* fix proof of possession type
* drop explicitly ordered container fields from ssz - there's an issue
open to sort this out before committing it to the spec
This commit is contained in:
Jacek Sieka 2018-12-11 11:55:45 -06:00 committed by GitHub
parent d18ca32ff5
commit 7ea51d5b0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 564 additions and 392 deletions

View File

@ -99,7 +99,7 @@ proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator =
proc makeAttestation(node: BeaconNode, proc makeAttestation(node: BeaconNode,
validator: AttachedValidator) {.async.} = validator: AttachedValidator) {.async.} =
var attestation: Attestation var attestation: AttestationCandidate
attestation.validator = validator.idx attestation.validator = validator.idx
# TODO: Populate attestation.data # TODO: Populate attestation.data
@ -126,21 +126,22 @@ proc proposeBlock(node: BeaconNode,
node.mainchainMonitor.getBeaconBlockRef() node.mainchainMonitor.getBeaconBlockRef()
for a in node.attestations.each(firstSlot = node.headBlock.slot.int + 1, for a in node.attestations.each(firstSlot = node.headBlock.slot.int + 1,
lastSlot = slot - MIN_ATTESTATION_INCLUSION_DELAY): lastSlot = slot - MIN_ATTESTATION_INCLUSION_DELAY.int):
# TODO: this is not quite right, # TODO: this is not quite right,
# the attestations from individual validators have to be merged. # the attestations from individual validators have to be merged.
# proposal.attestations.add a # proposal.attestations.add a
discard discard
for r in node.mainchainMonitor.getValidatorActions( # TODO update after spec change removed specials
node.headBlock.candidate_pow_receipt_root, # for r in node.mainchainMonitor.getValidatorActions(
proposal.candidate_pow_receipt_root): # node.headBlock.candidate_pow_receipt_root,
proposal.specials.add r # proposal.candidate_pow_receipt_root):
# proposal.specials.add r
var signedData: ProposalSignedData var signedData: ProposalSignedData
# TODO: populate the signed data # TODO: populate the signed data
proposal.proposer_signature = await validator.signBlockProposal(signedData) proposal.signature = await validator.signBlockProposal(signedData)
await node.network.broadcast(topicBeaconBlocks, proposal) await node.network.broadcast(topicBeaconBlocks, proposal)
proc scheduleCycleActions(node: BeaconNode) = proc scheduleCycleActions(node: BeaconNode) =
@ -153,7 +154,7 @@ proc scheduleCycleActions(node: BeaconNode) =
let let
slot = cycleStart + i slot = cycleStart + i
proposerIdx = get_beacon_proposer_index(node.beaconState, slot.uint64) proposerIdx = get_beacon_proposer_index(node.beaconState, slot.uint64)
attachedValidator = node.getAttachedValidator(proposerIdx.int) attachedValidator = node.getAttachedValidator(proposerIdx)
if attachedValidator != nil: if attachedValidator != nil:
# TODO: # TODO:

View File

@ -19,6 +19,6 @@ type
## Eth1 validator registration contract output ## Eth1 validator registration contract output
pubkey*: ValidatorPubKey pubkey*: ValidatorPubKey
deposit_size*: uint64 deposit_size*: uint64
proof_of_possession*: seq[byte] proof_of_possession*: ValidatorSig
withdrawal_credentials*: Eth2Digest withdrawal_credentials*: Eth2Digest
randao_commitment*: Eth2Digest randao_commitment*: Eth2Digest

View File

@ -3,17 +3,17 @@ import
spec/[datatypes, crypto] spec/[datatypes, crypto]
type type
Attestation* = object AttestationCandidate* = object
validator*: int validator*: int
data*: AttestationData data*: AttestationData
signature*: ValidatorSig signature*: ValidatorSig
AttestationPool* = object AttestationPool* = object
attestations: Deque[seq[Attestation]] attestations: Deque[seq[AttestationCandidate]]
startingSlot: int startingSlot: int
proc init*(T: type AttestationPool, startingSlot: int): T = proc init*(T: type AttestationPool, startingSlot: int): T =
result.attestationsPerSlot = initDeque[seq[Attestation]]() result.attestationsPerSlot = initDeque[seq[AttestationCandidate]]()
result.startingSlot = startingSlot result.startingSlot = startingSlot
proc setLen*[T](d: var Deque[T], len: int) = proc setLen*[T](d: var Deque[T], len: int) =
@ -27,7 +27,7 @@ proc setLen*[T](d: var Deque[T], len: int) =
d.shrink(fromLast = delta) d.shrink(fromLast = delta)
proc add*(pool: var AttestationPool, proc add*(pool: var AttestationPool,
attestation: Attestation, attestation: AttestationCandidate,
beaconState: BeaconState) = beaconState: BeaconState) =
# The caller of this function is responsible for ensuring that # The caller of this function is responsible for ensuring that
# the attestations will be given in a strictly slot increasing order: # the attestations will be given in a strictly slot increasing order:
@ -40,7 +40,7 @@ proc add*(pool: var AttestationPool,
pool.attestations[slotIdxInPool].add attestation pool.attestations[slotIdxInPool].add attestation
iterator each*(pool: AttestationPool, iterator each*(pool: AttestationPool,
firstSlot, lastSlot: int): Attestation = firstSlot, lastSlot: int): AttestationCandidate =
## Both indices are treated inclusively ## Both indices are treated inclusively
## TODO: this should return a lent value ## TODO: this should return a lent value
doAssert firstSlot <= lastSlot doAssert firstSlot <= lastSlot

View File

@ -21,9 +21,10 @@ proc getBeaconBlockRef*(m: MainchainMonitor): Eth2Digest =
# This should be a simple accessor for the reference kept above # This should be a simple accessor for the reference kept above
discard discard
iterator getValidatorActions*(m: MainchainMonitor, # TODO update after spec change removed Specials
fromBlock, toBlock: Eth2Digest): SpecialRecord = # iterator getValidatorActions*(m: MainchainMonitor,
# It's probably better if this doesn't return a SpecialRecord, but # fromBlock, toBlock: Eth2Digest): SpecialRecord =
# rather a more readable description of the change that can be packed # # It's probably better if this doesn't return a SpecialRecord, but
# in a SpecialRecord by the client of the API. # # rather a more readable description of the change that can be packed
discard # # in a SpecialRecord by the client of the API.
# discard

View File

@ -7,8 +7,8 @@
import import
math, sequtils, math, sequtils,
../extras, ../extras, ../ssz,
./datatypes, ./digest, ./helpers, ./validator ./crypto, ./datatypes, ./digest, ./helpers, ./validator
func on_startup*(initial_validator_entries: openArray[InitialValidator], func on_startup*(initial_validator_entries: openArray[InitialValidator],
genesis_time: uint64, genesis_time: uint64,
@ -96,14 +96,12 @@ func on_startup*(initial_validator_entries: openArray[InitialValidator],
) )
func get_block_hash*(state: BeaconState, func get_block_hash*(state: BeaconState,
current_block: BeaconBlock,
slot: uint64): Eth2Digest = slot: uint64): Eth2Digest =
let earliest_slot_in_array = let earliest_slot_in_array =
current_block.slot.int - state.latest_block_hashes.len state.slot - len(state.latest_block_hashes).uint64
assert earliest_slot_in_array <= slot.int assert earliest_slot_in_array <= slot
assert slot < current_block.slot assert slot < state.slot
state.latest_block_hashes[(slot - earliest_slot_in_array).int]
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,
@ -139,15 +137,76 @@ func get_attestation_participants*(state: BeaconState,
result.add(vindex) result.add(vindex)
return # found the shard, we're done return # found the shard, we're done
func change_validators*(state: var BeaconState, func process_ejections*(state: var BeaconState) =
current_slot: uint64) = ## Iterate through the validator registry
## Change validator registry. ## and eject active validators with balance below ``EJECTION_BALANCE``.
let res = get_changed_validators( for i, v in state.validator_registry.mpairs():
if is_active_validator(v) and v.balance < EJECTION_BALANCE:
exit_validator(i.Uint24, state, EXITED_WITHOUT_PENALTY)
func update_validator_registry*(state: var BeaconState) =
# Update validator registry.
# Note that this function mutates ``state``.
(state.validator_registry,
state.latest_penalized_exit_balances,
state.validator_registry_delta_chain_tip) =
get_updated_validator_registry(
state.validator_registry, state.validator_registry,
state.latest_penalized_exit_balances, state.latest_penalized_exit_balances,
state.validator_registry_delta_chain_tip, state.validator_registry_delta_chain_tip,
current_slot state.slot
) )
state.validator_registry = res.validators
state.latest_penalized_exit_balances = res.latest_penalized_exit_balances func checkAttestation*(state: BeaconState, attestation: Attestation): bool =
## Check that an attestation follows the rules of being included in the state
## at the current slot.
##
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestations-1
if attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot:
return
if attestation.data.slot + EPOCH_LENGTH >= state.slot:
return
let expected_justified_slot =
if attestation.data.slot >= state.slot - (state.slot mod EPOCH_LENGTH):
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, 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
let
participants = get_attestation_participants(
state, attestation.data, attestation.participation_bitfield)
group_public_key = BLSAddPubkeys(mapIt(
participants, state.validator_registry[it].pubkey))
# Verify that aggregate_signature verifies using the group pubkey.
let msg = hashSSZ(attestation.data)
if not BLSVerify(
group_public_key, @msg & @[0'u8], attestation.aggregate_signature,
get_domain(state.fork_data, attestation.data.slot, DOMAIN_ATTESTATION)
):
return
# To be removed in Phase1:
if attestation.data.shard_block_hash != ZERO_HASH:
return
true

View File

@ -20,5 +20,22 @@ type
template hash*(k: ValidatorPubKey|ValidatorPrivKey): Hash = template hash*(k: ValidatorPubKey|ValidatorPrivKey): Hash =
hash(k.getRaw) hash(k.getRaw)
proc pubKey*(pk: ValidatorPrivKey): ValidatorPubKey = fromSigKey(pk) func pubKey*(pk: ValidatorPrivKey): ValidatorPubKey = fromSigKey(pk)
func BLSAddPubkeys*(keys: openArray[ValidatorPubKey]): ValidatorPubKey =
# name from spec!
var empty = false
for key in keys:
if empty:
result = key
empty = false
else:
result.combine(key)
func BLSVerify*(
pubkey: ValidatorPubKey, msg: openArray[byte], sig: ValidatorSig,
domain: uint64): bool =
# name from spec!
# TODO domain!
sig.verifyMessage(msg, pubkey)

View File

@ -27,86 +27,224 @@ import
intsets, eth_common, math, intsets, eth_common, math,
./crypto, ./digest ./crypto, ./digest
const # TODO Data types:
SHARD_COUNT* = 1024 # a constant referring to the number of shards # Presently, we're reusing the data types from the serialization (uint64) in the
TARGET_COMMITTEE_SIZE* = 2^8 # validators # objects we pass around to the beacon chain logic, thus keeping the two
MAX_ATTESTATIONS_PER_BLOCK* = 2^7 # attestations # similar. This is convenient for keeping up with the specification, but
MIN_BALANCE* = 2^4 # ETH # will eventually need a more robust approach such that we don't run into
MAX_BALANCE_CHURN_QUOTIENT* = 2^5 # ETH # over- and underflows.
GWEI_PER_ETH* = 10^9 # Gwei/ETH # Some of the open questions are being tracked here:
BEACON_CHAIN_SHARD_NUMBER* = not 0'u64 # https://github.com/ethereum/eth2.0-specs/issues/224
#
# The present approach causes some problems due to how Nim treats unsigned
# integers - here's no high(uint64), arithmetic support is incomplete, there's
# no over/underflow checking available
#
# Eventually, we could also differentiate between user/tainted data and
# internal state that's gone through sanity checks already.
DEPOSIT_CONTRACT_TREE_DEPTH* = 2^5 # const
MIN_DEPOSIT* = 2^0 # SHARD_COUNT* = 1024 ##\
MAX_DEPOSIT* = 2^5 # ## Number of shards supported by the network - validators will jump around
## between these shards and provide attestations to their state.
TARGET_COMMITTEE_SIZE* = 2^8 ##\
## Number of validators in the committee attesting to one shard
EJECTION_BALANCE* = 2'u64^4 ##\
## Once the balance of a validator drops below this, it will be ejected from
## the validator pool
MAX_ATTESTATIONS_PER_BLOCK* = 2^7 # attestations
MIN_BALANCE* = 2'u64^4 ##\
## Minimum balance in ETH before a validator is removed from the validator
## pool
MAX_BALANCE_CHURN_QUOTIENT* = 2^5 ##\
## At most `1/MAX_BALANCE_CHURN_QUOTIENT` of the validators can change during
## each validator registry change.
GWEI_PER_ETH* = 10'u64^9 # Gwei/ETH
BEACON_CHAIN_SHARD_NUMBER* = not 0'u64 # 2^64 - 1 in spec
DEPOSIT_CONTRACT_TREE_DEPTH* = 2^5
MIN_DEPOSIT* = 2'u64^0 ##\
## Minimum amounth of ETH that can be deposited in one call - deposits can
## be used either to top up an existing validator or commit to a new one
MAX_DEPOSIT* = 2'u64^5 ##\
## Maximum amounth of ETH that can be deposited in one call
# Initial values # Initial values
INITIAL_FORK_VERSION* = 0 # INITIAL_FORK_VERSION* = 0'u64
INITIAL_SLOT_NUMBER* = 0 # INITIAL_SLOT_NUMBER* = 0'u64
ZERO_HASH* = Eth2Digest() ZERO_HASH* = Eth2Digest()
# Time constants # Time constants
SLOT_DURATION* = 6 # seconds SLOT_DURATION* = 6 ## \
MIN_ATTESTATION_INCLUSION_DELAY* = 4 # slots (~25 minutes) ## TODO consistent time unit across projects, similar to C++ chrono?
EPOCH_LENGTH* = 64 # slots (~6.4 minutes)
MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL* = 2^8 # slots (~25.6 minutes) MIN_ATTESTATION_INCLUSION_DELAY* = 4'u64 ##\
POW_RECEIPT_ROOT_VOTING_PERIOD* = 2^10 # slots (~1.7 hours) ## (24 seconds)
SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD* = 2^17 # slots (~9 days) ## Number of slots that attestations stay in the attestation
COLLECTIVE_PENALTY_CALCULATION_PERIOD* = 2^20 # slots (~2.4 months) ## pool before being added to a block
ZERO_BALANCE_VALIDATOR_TTL* = 2^22 # slots (~290 days)
EPOCH_LENGTH* = 64 ##\
## (~6.4 minutes)
## slots that make up an epoch, at the end of which more heavy
## processing is done
MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL* = 2'u64^8 ##
## slots (~25.6 minutes)
POW_RECEIPT_ROOT_VOTING_PERIOD* = 2'u64^10 ##\
## slots (~1.7 hours)
SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD* = 2'u64^17 ##\
## slots (~9 days)
COLLECTIVE_PENALTY_CALCULATION_PERIOD* = 2'u64^20 ##\
## slots (~73 days)
ZERO_BALANCE_VALIDATOR_TTL* = 2'u64^22 ##\
## slots (~291 days)
# Quotients # Quotients
BASE_REWARD_QUOTIENT* = 2^11 # per-cycle interest rate assuming all validators are BASE_REWARD_QUOTIENT* = 2'u64^11 ##\
# participating, assuming total deposits of 1 ETH. It ## per-cycle interest rate assuming all validators are participating, assuming
# corresponds to ~2.57% annual interest assuming 10 ## total deposits of 1 ETH. It corresponds to ~2.57% annual interest assuming
# million participating ETH. ## 10 million participating ETH.
WHISTLEBLOWER_REWARD_QUOTIENT* = 2^9 # ? WHISTLEBLOWER_REWARD_QUOTIENT* = 2'u64^9
INCLUDER_REWARD_QUOTIENT* = 2^3 # INCLUDER_REWARD_QUOTIENT* = 2'u64^3
INACTIVITY_PENALTY_QUOTIENT* = 2^34 # INACTIVITY_PENALTY_QUOTIENT* = 2'u64^34
type type
Uint24* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around Uint24* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around
BeaconBlock* = object # https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#data-structures
slot*: uint64 # Slot number ProposerSlashing* = object
randao_reveal*: Eth2Digest # Proposer RANDAO reveal proposer_index*: Uint24
candidate_pow_receipt_root*: Eth2Digest # Recent PoW receipt root proposal_data_1*: ProposalSignedData
ancestor_hashes*: seq[Eth2Digest] # Skip list of previous beacon block hashes proposal_signature_1*: ValidatorSig
# i'th item is most recent ancestor whose proposal_data_2*: ProposalSignedData
# slot is a multiple of 2**i for proposal_signature_2*: ValidatorSig
# i == 0, ..., 31
state_root*: Eth2Digest # State root
attestations*: seq[AttestationRecord] # Attestations
specials*: seq[SpecialRecord] # Specials (e.g. logouts, penalties)
proposer_signature*: ValidatorSig # Proposer signature
AttestationRecord* = object CasperSlashing* = object
votes_1*: SlashableVoteData
votes_2*: SlashableVoteData
SlashableVoteData* = object
aggregate_signature_poc_0_indices*: seq[Uint24] ##\
## Proof-of-custody indices (0 bits)
aggregate_signature_poc_1_indices*: seq[Uint24] ##\
## Proof-of-custody indices (1 bits)
data*: AttestationData
aggregate_signature*: ValidatorSig
Attestation* = object
data*: AttestationData data*: AttestationData
participation_bitfield*: seq[byte] # Attester participation bitfield participation_bitfield*: seq[byte] # Attester participation bitfield
custody_bitfield*: seq[byte] # Proof of custody bitfield custody_bitfield*: seq[byte] # Proof of custody bitfield
aggregate_signature*: ValidatorSig # BLS aggregate signature aggregate_signature*: ValidatorSig # BLS aggregate signature
AttestationData* = object AttestationData* = object
slot*: uint64 # Slot number slot*: uint64
shard*: uint64 # Shard number shard*: uint64
beacon_block_hash*: Eth2Digest # Hash of the block we're signing beacon_block_hash*: Eth2Digest ##\
epoch_boundary_hash*: Eth2Digest # Hash of the ancestor at the cycle boundary ## Hash of the block we're signing
shard_block_hash*: Eth2Digest # Shard block hash being attested to
latest_crosslink_hash*: Eth2Digest # Last crosslink hash epoch_boundary_hash*: Eth2Digest ##\
justified_slot*: uint64 # Slot of last justified beacon block ## Hash of the ancestor at the cycle boundary
justified_block_hash*: Eth2Digest # Hash of last justified beacon block
shard_block_hash*: Eth2Digest ##\
## Shard block hash being attested to
latest_crosslink_hash*: Eth2Digest ##\
## Last crosslink hash
justified_slot*: uint64 ##\
## Slot of last justified beacon block
justified_block_hash*: Eth2Digest ##\
## Hash of last justified beacon block
Deposit* = object
merkle_branch*: seq[Eth2Digest] ##\
## Receipt Merkle branch
merkle_tree_index*: uint64
## Deposit data
deposit_data*: DepositData
DepositData* = object
deposit_parameters*: DepositParameters
value*: uint64 ## Value in Gwei
timestamp*: uint64 # Timestamp from deposit contract
DepositParameters* = object
pubkey*: ValidatorPubKey
proof_of_possession*: ValidatorSig ##\
## BLS proof of possession (a BLS signature)
withdrawal_credentials*: Eth2Digest
randao_commitment*: Eth2Digest # Initial RANDAO commitment
Exit* = object
# Minimum slot for processing exit
slot*: uint64
# Index of the exiting validator
validator_index*: uint64
# Validator signature
signature*: ValidatorSig
BeaconBlock* = object
## For each slot, a proposer is chosen from the validator pool to propose
## a new block. Once the block as been proposed, it is transmitted to
## validators that will have a chance to vote on it through attestations.
## Each block collects attestations, or votes, on past blocks, thus a chain
## is formed.
slot*: uint64
ancestor_hashes*: seq[Eth2Digest] ##\
## Skip list of previous beacon block hashes i'th item is most recent
## ancestor whose slot is a multiple of 2**i for i == 0, ..., 31
state_root*: Eth2Digest
randao_reveal*: Eth2Digest ##\
## Proposer RANDAO reveal
candidate_pow_receipt_root*: Eth2Digest
signature*: ValidatorSig ##\
## Proposer signature
body*: BeaconBlockBody
BeaconBlockBody* = object
attestations*: seq[Attestation]
proposer_slashings*: seq[ProposerSlashing]
casper_slashings*: seq[CasperSlashing]
deposits*: seq[Deposit]
exits*: seq[Exit]
ProposalSignedData* = object ProposalSignedData* = object
slot*: uint64 # Slot number slot*: uint64
shard*: uint64 # Shard number (or `BEACON_CHAIN_SHARD_NUMBER` for beacon chain) shard*: uint64 ##\
block_hash*: Eth2Digest # Block hash ## Shard number (or `BEACON_CHAIN_SHARD_NUMBER` for beacon chain)
block_hash*: Eth2Digest
SpecialRecord* = object
kind*: SpecialRecordType # Kind
data*: seq[byte] # Data
BeaconState* = object BeaconState* = object
slot*: uint64
genesis_time*: uint64
fork_data*: ForkData ##\
## For versioning hard forks
# Validator registry # Validator registry
validator_registry*: seq[ValidatorRecord] validator_registry*: seq[ValidatorRecord]
validator_registry_latest_change_slot*: uint64 validator_registry_latest_change_slot*: uint64
@ -126,7 +264,7 @@ type
# Finality # Finality
previous_justified_slot*: uint64 previous_justified_slot*: uint64
justified_slot*: uint64 justified_slot*: uint64
justified_slot_bitfield*: uint64 justification_bitfield*: uint64
finalized_slot*: uint64 finalized_slot*: uint64
latest_crosslinks*: array[SHARD_COUNT, CrosslinkRecord] latest_crosslinks*: array[SHARD_COUNT, CrosslinkRecord]
@ -140,10 +278,6 @@ type
processed_pow_receipt_root*: Eth2Digest processed_pow_receipt_root*: Eth2Digest
candidate_pow_receipt_roots*: seq[CandidatePoWReceiptRootRecord] 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
withdrawal_credentials*: Eth2Digest # Withdrawal credentials withdrawal_credentials*: Eth2Digest # Withdrawal credentials
@ -200,6 +334,12 @@ type
Activation = 0 Activation = 0
Exit = 1 Exit = 1
SignatureDomain* {.pure.} = enum
DOMAIN_DEPOSIT = 0
DOMAIN_ATTESTATION = 1
DOMAIN_PROPOSAL = 2
DOMAIN_EXIT = 3
# Note: # Note:
# We use IntSet from Nim Standard library which are efficient sparse bitsets. # We use IntSet from Nim Standard library which are efficient sparse bitsets.
# See: https://nim-lang.org/docs/intsets.html # See: https://nim-lang.org/docs/intsets.html
@ -236,4 +376,3 @@ when true:
proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} = proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} =
discard discard

View File

@ -130,8 +130,17 @@ func get_fork_version*(fork_data: ForkData, slot: uint64): uint64 =
if slot < fork_data.fork_slot: fork_data.pre_fork_version if slot < fork_data.fork_slot: fork_data.pre_fork_version
else: fork_data.post_fork_version else: fork_data.post_fork_version
func get_domain*(fork_data: ForkData, slot: uint64, domain_type: uint64): uint64 = func get_domain*(
fork_data: ForkData, slot: uint64, domain_type: SignatureDomain): uint64 =
# TODO Slot overflow? Or is slot 32 bits for all intents and purposes? # TODO Slot overflow? Or is slot 32 bits for all intents and purposes?
(get_fork_version(fork_data, slot) shl 32) + domain_type (get_fork_version(fork_data, slot) shl 32) + domain_type.uint32
func is_power_of_2*(v: uint64): bool = (v and (v-1)) == 0 func is_power_of_2*(v: uint64): bool = (v and (v-1)) == 0
func get_updated_ancestor_hashes*(latest_block: BeaconBlock,
latest_hash: Eth2Digest): seq[Eth2Digest] =
var new_ancestor_hashes = latest_block.ancestor_hashes
for i in 0..<32:
if latest_block.slot mod 2'u64^i == 0:
new_ancestor_hashes[i] = latest_hash
new_ancestor_hashes

View File

@ -12,6 +12,9 @@ import
../ssz, ../ssz,
./crypto, ./datatypes, ./digest, ./helpers ./crypto, ./datatypes, ./digest, ./helpers
func is_active_validator*(validator: ValidatorRecord): bool =
validator.status in {ACTIVE, ACTIVE_PENDING_EXIT}
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 if v.balance == 0 and
@ -23,7 +26,7 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
fork_data: ForkData, fork_data: ForkData,
pubkey: ValidatorPubKey, pubkey: ValidatorPubKey,
deposit: uint64, deposit: uint64,
proof_of_possession: seq[byte], proof_of_possession: ValidatorSig,
withdrawal_credentials: Eth2Digest, withdrawal_credentials: Eth2Digest,
randao_commitment: Eth2Digest, randao_commitment: Eth2Digest,
status: ValidatorStatusCodes, status: ValidatorStatusCodes,
@ -32,26 +35,20 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
# TODO Spec candidate: inefficient API # TODO Spec candidate: inefficient API
# #
# Check that validator really did register # Check that validator really did register
# let signed_message = signed_message = bytes32(pubkey) + withdrawal_credentials + randao_commitment # TODO fix tests and enable (nightmare)
# assert BLSVerify(pub=pubkey, # let msg = hashSSZ((pubkey, withdrawal_credentials, randao_commitment))
# msg=hash(signed_message), # assert BLSVerify(
# sig=proof_of_possession, # pubkey, msg, proof_of_possession,
# domain=get_domain( # get_domain(fork_data, current_slot, DOMAIN_DEPOSIT))
# fork_data,
# current_slot,
# DOMAIN_DEPOSIT
# ))
var new_validators = current_validators var validators_copy = current_validators
for index, val in new_validators.mpairs(): for index, validator in validators_copy.mpairs():
if val.pubkey == pubkey: if validator.pubkey == pubkey:
# assert deposit_size >= MIN_TOPUP_SIZE assert validator.withdrawal_credentials == withdrawal_credentials
# assert val.status != WITHDRAWN
# assert val.withdrawal_credentials == withdrawal_credentials
val.balance.inc(deposit.int) validator.balance += deposit
return (new_validators, index) return (validators_copy, index)
# new validator # new validator
let let
@ -66,18 +63,18 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
exit_count: 0 exit_count: 0
) )
let index = min_empty_validator_index(new_validators, current_slot) let index = min_empty_validator_index(validators_copy, current_slot)
if index.isNone: if index.isNone:
new_validators.add(rec) validators_copy.add(rec)
(new_validators, len(new_validators) - 1) (validators_copy, len(validators_copy) - 1)
else: else:
new_validators[index.get()] = rec validators_copy[index.get()] = rec
(new_validators, index.get()) (validators_copy, index.get())
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 in {ACTIVE, ACTIVE_PENDING_EXIT}: if is_active_validator(val):
result.add idx.Uint24 result.add idx.Uint24
func get_new_shuffling*(seed: Eth2Digest, func get_new_shuffling*(seed: Eth2Digest,
@ -109,7 +106,7 @@ func get_new_shuffling*(seed: Eth2Digest,
var committees = newSeq[ShardCommittee](shard_indices.len) var committees = newSeq[ShardCommittee](shard_indices.len)
for shard_position, indices in shard_indices: for shard_position, indices in shard_indices:
committees[shard_position].shard = committees[shard_position].shard =
uint64(shard_id_start + shard_position.uint64) mod SHARD_COUNT.uint64 shard_id_start + shard_position.uint64 mod SHARD_COUNT.uint64
committees[shard_position].committee = indices committees[shard_position].committee = indices
committees[shard_position].total_validator_count = committees[shard_position].total_validator_count =
len(active_validator_indices).uint64 len(active_validator_indices).uint64
@ -127,24 +124,24 @@ func get_new_validator_registry_delta_chain_tip(
h.update hashSSZ(current_validator_registry_delta_chain_tip) h.update hashSSZ(current_validator_registry_delta_chain_tip)
h.update hashSSZ(flag.uint8) h.update hashSSZ(flag.uint8)
h.update hashSSZ(index) h.update hashSSZ(index)
# TODO h.update hashSSZ(pubkey) h.update hashSSZ(pubkey)
func get_effective_balance*(validator: ValidatorRecord): uint64 = func get_effective_balance*(validator: ValidatorRecord): uint64 =
min(validator.balance, MAX_DEPOSIT.uint64) min(validator.balance, MAX_DEPOSIT)
func exit_validator*(index: Uint24, func exit_validator*(index: Uint24,
state: var BeaconState, state: var BeaconState,
penalize: bool, new_status: ValidatorStatusCodes) =
current_slot: uint64) =
## Remove the validator with the given `index` from `state`. ## Remove the validator with the given `index` from `state`.
## Note that this function mutates `state`. ## Note that this function mutates `state`.
state.validator_registry_exit_count.inc() state.validator_registry_exit_count += 1
var var
validator = state.validator_registry[index] validator = addr state.validator_registry[index]
validator.latest_status_change_slot = current_slot validator.status = new_status
validator.latest_status_change_slot = state.slot
validator.exit_count = state.validator_registry_exit_count validator.exit_count = state.validator_registry_exit_count
# Remove validator from persistent committees # Remove validator from persistent committees
@ -154,31 +151,27 @@ func exit_validator*(index: Uint24,
committee.delete(i) committee.delete(i)
break break
if penalize: if new_status == EXITED_WITH_PENALTY:
validator.status = EXITED_WITH_PENALTY
state.latest_penalized_exit_balances[ state.latest_penalized_exit_balances[
(current_slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD.uint64).int].inc( (state.slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int] +=
get_effective_balance(validator).int) get_effective_balance(validator[])
var var
whistleblower = whistleblower = addr state.validator_registry[
state.validator_registry[get_beacon_proposer_index(state, current_slot).int] get_beacon_proposer_index(state, state.slot)]
whistleblower_reward = whistleblower_reward =
validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT.uint64 validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT
whistleblower.balance.inc(whistleblower_reward.int)
validator.balance.dec(whistleblower_reward.int) whistleblower.balance += whistleblower_reward
else: validator.balance -= whistleblower_reward
validator.status = ACTIVE_PENDING_EXIT
state.validator_registry_delta_chain_tip = state.validator_registry_delta_chain_tip =
get_new_validator_registry_delta_chain_tip( get_new_validator_registry_delta_chain_tip(
state.validator_registry_delta_chain_tip, state.validator_registry_delta_chain_tip, index, validator.pubkey,
index, ValidatorSetDeltaFlags.EXIT)
validator.pubkey,
EXIT,
)
func get_changed_validators*(validators: seq[ValidatorRecord], func get_updated_validator_registry*(
validator_registry: seq[ValidatorRecord],
latest_penalized_exit_balances: seq[uint64], latest_penalized_exit_balances: seq[uint64],
validator_registry_delta_chain_tip: Eth2Digest, validator_registry_delta_chain_tip: Eth2Digest,
current_slot: uint64): current_slot: uint64):
@ -190,77 +183,85 @@ func get_changed_validators*(validators: seq[ValidatorRecord],
## `validator_registry_delta_chain_tip`. ## `validator_registry_delta_chain_tip`.
# TODO inefficient # TODO inefficient
var validators = validators var
validator_registry = validator_registry
latest_penalized_exit_balances = latest_penalized_exit_balances
# The active validators # The active validators
let active_validator_indices = get_active_validator_indices(validators) let active_validator_indices =
get_active_validator_indices(validator_registry)
# The total effective balance of active validators # The total effective balance of active validators
let total_balance = sum(mapIt( let total_balance = sum(mapIt(
active_validator_indices, get_effective_balance(validators[it]))) active_validator_indices, get_effective_balance(validator_registry[it])))
# The maximum balance churn in Gwei (for deposits and exits separately) # The maximum balance churn in Gwei (for deposits and exits separately)
let max_balance_churn = max( let max_balance_churn = max(
(MAX_DEPOSIT * GWEI_PER_ETH).uint64, MAX_DEPOSIT * GWEI_PER_ETH,
total_balance div (2 * MAX_BALANCE_CHURN_QUOTIENT) total_balance div (2 * MAX_BALANCE_CHURN_QUOTIENT)
) )
# Activate validators within the allowable balance churn # Activate validators within the allowable balance churn
var balance_churn = 0'u64 var balance_churn = 0'u64
var validator_registry_delta_chain_tip = validator_registry_delta_chain_tip var validator_registry_delta_chain_tip = validator_registry_delta_chain_tip
for i in 0..<len(validators): for i in 0..<len(validator_registry):
if validators[i].status == PENDING_ACTIVATION and if validator_registry[i].status == PENDING_ACTIVATION and
validators[i].balance >= MAX_DEPOSIT.uint64: validator_registry[i].balance >= MAX_DEPOSIT:
# Check the balance churn would be within the allowance # Check the balance churn would be within the allowance
balance_churn.inc(get_effective_balance(validators[i]).int) balance_churn += get_effective_balance(validator_registry[i])
if balance_churn > max_balance_churn: if balance_churn > max_balance_churn:
break break
# Activate validator # Activate validator
validators[i].status = ACTIVE validator_registry[i].status = ACTIVE
validators[i].latest_status_change_slot = current_slot validator_registry[i].latest_status_change_slot = current_slot
validator_registry_delta_chain_tip = validator_registry_delta_chain_tip =
get_new_validator_registry_delta_chain_tip( get_new_validator_registry_delta_chain_tip(
validator_registry_delta_chain_tip, validator_registry_delta_chain_tip,
i.Uint24, i.Uint24,
validators[i].pubkey, validator_registry[i].pubkey,
ACTIVATION, ACTIVATION,
) )
# Exit validators within the allowable balance churn # Exit validators within the allowable balance churn
balance_churn = 0 balance_churn = 0
for i in 0..<len(validators): for i in 0..<len(validator_registry):
if validators[i].status == ACTIVE_PENDING_EXIT: if validator_registry[i].status == ACTIVE_PENDING_EXIT:
# Check the balance churn would be within the allowance # Check the balance churn would be within the allowance
balance_churn.inc(get_effective_balance(validators[i]).int) balance_churn += get_effective_balance(validator_registry[i])
if balance_churn > max_balance_churn: if balance_churn > max_balance_churn:
break break
# Exit validator # Exit validator
validators[i].status = EXITED_WITHOUT_PENALTY validator_registry[i].status = EXITED_WITHOUT_PENALTY
validators[i].latest_status_change_slot = current_slot validator_registry[i].latest_status_change_slot = current_slot
validator_registry_delta_chain_tip = validator_registry_delta_chain_tip =
get_new_validator_registry_delta_chain_tip( get_new_validator_registry_delta_chain_tip(
validator_registry_delta_chain_tip, validator_registry_delta_chain_tip,
i.Uint24, i.Uint24,
validators[i].pubkey, validator_registry[i].pubkey,
EXIT, ValidatorSetDeltaFlags.EXIT,
) )
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods # Calculate the total ETH that has been penalized in the last ~2-3 withdrawal
let period_index = current_slot.int div COLLECTIVE_PENALTY_CALCULATION_PERIOD # periods
let period_index =
(current_slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int
let total_penalties = ( let total_penalties = (
(latest_penalized_exit_balances[period_index]) + (latest_penalized_exit_balances[period_index]) +
(if period_index >= 1: latest_penalized_exit_balances[period_index - 1] else: 0) + (if period_index >= 1:
(if period_index >= 2: latest_penalized_exit_balances[period_index - 2] else: 0) latest_penalized_exit_balances[period_index - 1] else: 0) +
(if period_index >= 2:
latest_penalized_exit_balances[period_index - 2] else: 0)
) )
# Calculate penalties for slashed validators # Calculate penalties for slashed validators
func to_penalize(v: ValidatorRecord): bool = func to_penalize(v: ValidatorRecord): bool =
v.status == EXITED_WITH_PENALTY v.status == EXITED_WITH_PENALTY
var validators_to_penalize = filter(validators, to_penalize) for v in validator_registry.mitems():
for v in validators_to_penalize.mitems(): if not to_penalize(v): continue
v.balance.dec( v.balance -=
(get_effective_balance(v) * min(total_penalties * 3, total_balance) div (get_effective_balance(v) * min(total_penalties * 3, total_balance) div
total_balance).int) total_balance)
(validators, latest_penalized_exit_balances, validator_registry_delta_chain_tip) (validator_registry, latest_penalized_exit_balances,
validator_registry_delta_chain_tip)

View File

@ -209,6 +209,7 @@ func hashSSZ*[T: not enum](x: T): array[32, byte] =
else: else:
## Containers have their fields recursively hashed, concatenated and hashed ## Containers have their fields recursively hashed, concatenated and hashed
# TODO could probaby compile-time-macro-sort fields... # TODO could probaby compile-time-macro-sort fields...
# TODO or.. https://github.com/ethereum/eth2.0-specs/issues/275
var fields: seq[tuple[name: string, value: seq[byte]]] var fields: seq[tuple[name: string, value: seq[byte]]]
for name, field in x.fieldPairs: for name, field in x.fieldPairs:
fields.add (name, @(hashSSZ(field))) fields.add (name, @(hashSSZ(field)))
@ -227,36 +228,17 @@ func hashSSZ*(x: enum): array[32, byte] =
withHash: withHash:
h.update [uint8 x] h.update [uint8 x]
func hashSSZ*(x: ValidatorSig): array[32, byte] = func hashSSZ*(x: ValidatorPubKey): array[32, byte] =
## TODO - Warning ⚠️: not part of the spec ## TODO - Warning ⚠️: not part of the spec
## as of https://github.com/ethereum/beacon_chain/pull/133/files ## as of https://github.com/ethereum/beacon_chain/pull/133/files
## This is a "stub" needed for BeaconBlock hashing ## This is a "stub" needed for BeaconBlock hashing
x.getRaw().hash() x.getRaw().hash()
func hashSSZ*(x: AttestationRecord): array[32, byte] = func hashSSZ*(x: ValidatorSig): array[32, byte] =
## TODO - Warning ⚠️: not part of the spec ## TODO - Warning ⚠️: not part of the spec
## as of https://github.com/ethereum/beacon_chain/pull/133/files ## as of https://github.com/ethereum/beacon_chain/pull/133/files
## This is a "stub" needed for BeaconBlock hashing ## This is a "stub" needed for BeaconBlock hashing
withHash: x.getRaw().hash()
# h.update hashSSZ(x.data) # TODO this is now a sub-object of its own
# h.update hashSSZ(attester_bitfield) # TODO - the bitfield as a specific serialisation format
# h.update hashSSZ(x.poc_bitfield) # TODO - same serialization format
h.update hashSSZ(x.aggregate_signature)
func hashSSZ*(x: BeaconBlock): array[32, byte] =
## TODO - Warning ⚠️: not part of the spec
## as of https://github.com/ethereum/beacon_chain/pull/133/files
## This is a "stub" needed for fork_choice_rule
## and networking
withHash:
h.update hashSSZ(x.slot)
h.update hashSSZ(x.randao_reveal)
h.update hashSSZ(x.candidate_pow_receipt_root)
h.update hashSSZ(x.ancestor_hashes)
h.update hashSSZ(x.state_root)
h.update hashSSZ(x.attestations)
h.update hashSSZ(x.specials)
h.update hashSSZ(x.proposer_signature)
# ################### Tree hash ################################### # ################### Tree hash ###################################

View File

@ -40,104 +40,55 @@ import
milagro_crypto milagro_crypto
func processAttestations(state: var BeaconState, func processAttestations(state: var BeaconState,
blck: BeaconBlock, blck: BeaconBlock): bool =
parent_slot: uint64): bool =
# Each block includes a number of attestations that the proposer chose. Each # Each block includes a number of attestations that the proposer chose. Each
# attestation represents an update to a specific shard and is signed by a # attestation represents an update to a specific shard and is signed by a
# committee of validators. # committee of validators.
# Here we make sanity checks for each attestation and it to the state - most # Here we make sanity checks for each attestation and it to the state - most
# updates will happen at the epoch boundary where state updates happen in # updates will happen at the epoch boundary where state updates happen in
# bulk. # bulk.
if blck.attestations.len > MAX_ATTESTATIONS_PER_BLOCK: if blck.body.attestations.len > MAX_ATTESTATIONS_PER_BLOCK:
return return
var res: seq[PendingAttestationRecord] if not allIt(blck.body.attestations, checkAttestation(state, it)):
for attestation in blck.attestations:
if attestation.data.slot <= blck.slot - MIN_ATTESTATION_INCLUSION_DELAY:
return return
# TODO spec - unsigned underflow
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
let attestation_participants = get_attestation_participants(
state, attestation.data, attestation.participation_bitfield)
var
agg_pubkey: ValidatorPubKey
empty = true
for attester_idx in attestation_participants:
let validator = state.validator_registry[attester_idx]
if empty:
agg_pubkey = validator.pubkey
empty = false
else:
agg_pubkey.combine(validator.pubkey)
# Verify that aggregate_signature verifies using the group pubkey.
let msg = hashSSZ(attestation.data)
# For now only check compilation
# doAssert attestation.aggregate_signature.verifyMessage(msg, agg_pubkey)
debugEcho "Aggregate sig verify message: ",
attestation.aggregate_signature.verifyMessage(msg, agg_pubkey)
# All checks passed - update state # All checks passed - update state
# TODO no rollback in case of errors state.latest_attestations.add mapIt(blck.body.attestations,
state.latest_attestations.add PendingAttestationRecord( PendingAttestationRecord(
data: attestation.data, data: it.data,
participation_bitfield: attestation.participation_bitfield, participation_bitfield: it.participation_bitfield,
custody_bitfield: attestation.custody_bitfield, custody_bitfield: it.custody_bitfield,
slot_included: blck.slot slot_included: state.slot
)
) )
true true
func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool = func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#proposer-signature
var blck_without_sig = blck var blck_without_sig = blck
blck_without_sig.proposer_signature = ValidatorSig() blck_without_sig.signature = ValidatorSig()
let let
proposal_hash = hashSSZ(ProposalSignedData( proposal_hash = hashSSZ(ProposalSignedData(
slot: blck.slot, slot: state.slot,
shard: BEACON_CHAIN_SHARD, shard: BEACON_CHAIN_SHARD,
block_hash: Eth2Digest(data: hashSSZ(blck_without_sig)) block_hash: Eth2Digest(data: hashSSZ(blck_without_sig))
)) ))
verifyMessage( let validator_idx = get_beacon_proposer_index(state, state.slot)
blck.proposer_signature, proposal_hash, BLSVerify(
state.validator_registry[get_beacon_proposer_index(state, blck.slot).int].pubkey) state.validator_registry[validator_idx].pubkey,
proposal_hash, blck.signature,
get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))
func processRandaoReveal(state: var BeaconState, func processRandaoReveal(state: var BeaconState,
blck: BeaconBlock, blck: BeaconBlock): bool =
parent_slot: uint64): bool =
# Update randao skips
for slot in parentslot + 1 ..< blck.slot:
let proposer_index = get_beacon_proposer_index(state, slot)
state.validator_registry[proposer_index.int].randao_skips.inc()
let let
proposer_index = get_beacon_proposer_index(state, blck.slot) proposer_index = get_beacon_proposer_index(state, state.slot)
proposer = state.validator_registry[proposer_index.int].addr proposer = addr state.validator_registry[proposer_index]
# 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) !=
@ -156,7 +107,7 @@ func processRandaoReveal(state: var BeaconState,
func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock): bool = func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock): bool =
for x in state.candidate_pow_receipt_roots.mitems(): for x in state.candidate_pow_receipt_roots.mitems():
if blck.candidate_pow_receipt_root == x.candidate_pow_receipt_root: if blck.candidate_pow_receipt_root == x.candidate_pow_receipt_root:
x.votes.inc x.votes += 1
return true return true
state.candidate_pow_receipt_roots.add CandidatePoWReceiptRootRecord( state.candidate_pow_receipt_roots.add CandidatePoWReceiptRootRecord(
@ -165,41 +116,49 @@ func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock): bool =
) )
return true return true
func processSpecials(state: var BeaconState, blck: BeaconBlock): bool =
# TODO incoming spec changes here..
true
func processBlock(state: var BeaconState, blck: BeaconBlock): bool = func processBlock(state: var BeaconState, blck: BeaconBlock): bool =
## When a new block is received, all participants must verify that the block if not processAttestations(state, blck):
## makes sense and update their state accordingly. This function will return false
## the new state, unless something breaks along the way elif not verifyProposerSignature(state, blck):
false
elif not processRandaoReveal(state, blck):
false
elif not processPoWReceiptRoot(state, blck):
false
else:
true
func processSlot(state: var BeaconState, latest_block: BeaconBlock): bool =
## 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
# TODO state not rolled back in case of failure # TODO state not rolled back in case of failure
let let
parent_hash = blck.ancestor_hashes[0] latest_hash = Eth2Digest(data: hashSSZ(latest_block))
slot = blck.slot
parent_slot = slot - 1 # TODO Not!! can skip slots...
# TODO actually get parent block, which means fixing up BeaconState refs above;
state.latest_block_hashes = state.slot += 1
append_to_recent_block_hashes(state.latest_block_hashes, parent_slot, slot, state.latest_block_hashes.add latest_hash
parent_hash)
if not processAttestations(state, blck, parent_slot):
return
if not verifyProposerSignature(state, blck):
return
if not processRandaoReveal(state, blck, parent_slot):
return
if not processPoWReceiptRoot(state, blck):
return
if not processSpecials(state, blck):
return
if state.latest_block_hashes.len < 2 or
state.latest_block_hashes[^2] != state.latest_block_hashes[^1]:
# TODO a bit late for the following checks?
# https://github.com/ethereum/eth2.0-specs/issues/284
if latest_block.slot != state.slot:
false
elif latest_block.ancestor_hashes !=
get_updated_ancestor_hashes(latest_block, latest_hash):
false
else:
processBlock(state, latest_block)
else:
state.validator_registry[get_beacon_proposer_index(state, state.slot)].randao_skips += 1
# Skip all other per-slot processing. Move directly to epoch processing
# prison. Do not do any slot updates when passing go.
true true
func flatten[T](v: openArray[seq[T]]): seq[T] = func flatten[T](v: openArray[seq[T]]): seq[T] =
@ -255,13 +214,13 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
active_validator_indices = active_validator_indices =
get_active_validator_indices(state.validator_registry) get_active_validator_indices(state.validator_registry)
total_balance = sum_effective_balances(state, active_validator_indices) total_balance = sum_effective_balances(state, active_validator_indices)
total_balance_in_eth = total_balance.int div GWEI_PER_ETH total_balance_in_eth = total_balance div GWEI_PER_ETH
# The per-slot maximum interest rate is `2/reward_quotient`.) # The per-slot maximum interest rate is `2/reward_quotient`.)
reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth) reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth)
# TODO not in spec, convenient # TODO not in spec, convenient
epoch_boundary_hash = get_block_hash(state, blck, s) epoch_boundary_hash = get_block_hash(state, s)
proc base_reward(v: ValidatorRecord): uint64 = proc base_reward(v: ValidatorRecord): uint64 =
get_effective_balance(v) div reward_quotient.uint64 get_effective_balance(v) div reward_quotient.uint64
@ -350,61 +309,65 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
return a.slot_included - a.data.slot return a.slot_included - a.data.slot
assert false # shouldn't happen.. assert false # shouldn't happen..
block: # Adjust justified slots and crosslink status block: # Receipt roots
var new_justified_slot: Option[uint64] if state.slot mod POW_RECEIPT_ROOT_VOTING_PERIOD == 0:
# TODO where's that bitfield type when you need it? for x in state.candidate_pow_receipt_roots:
# TODO what happens with the bits that drop off..? if x.votes * 2 >= POW_RECEIPT_ROOT_VOTING_PERIOD:
state.justified_slot_bitfield = state.justified_slot_bitfield shl 1 state.processed_pow_receipt_root = x.candidate_pow_receipt_root
break
if 3'u64 * previous_epoch_boundary_attesting_balance >= 2'u64 * total_balance: state.candidate_pow_receipt_roots = @[]
# 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
block: # Justification
state.previous_justified_slot = state.justified_slot state.previous_justified_slot = state.justified_slot
if new_justified_slot.isSome(): # TODO where's that bitfield type when you need it?
state.justified_slot = new_justified_slot.get() # 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
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.justification_bitfield mod 4 == 3) or
(state.previous_justified_slot == state.slot - 3 * EPOCH_LENGTH and
state.justification_bitfield mod 8 == 7) or
(state.previous_justified_slot == state.slot - 4 * EPOCH_LENGTH and
state.justification_bitfield mod 16 in [15'u64, 14]):
state.finalized_slot = state.justified_slot
block: # Crosslinks
for sac in state.shard_committees_at_slots: for sac in state.shard_committees_at_slots:
# TODO or just state.shard_committees_at_slots[s]?
for obj in sac: for obj in sac:
if 3'u64 * total_attesting_balance(obj) >= 2'u64 * total_balance_sac(obj): if 3'u64 * total_attesting_balance(obj) >=
2'u64 * total_balance_sac(obj):
state.latest_crosslinks[obj.shard] = CrosslinkRecord( state.latest_crosslinks[obj.shard] = CrosslinkRecord(
slot: state.latest_state_recalculation_slot + EPOCH_LENGTH, slot: state.latest_state_recalculation_slot + EPOCH_LENGTH,
shard_block_hash: winning_hash(obj)) shard_block_hash: winning_hash(obj))
block: # Balance recalculations related to FFG rewards block: # Justification and finalization rewards and penalties
let let
time_since_finality = blck.slot - state.finalized_slot slots_since_finality = blck.slot - state.finalized_slot
if time_since_finality <= 4'u64 * EPOCH_LENGTH: if slots_since_finality <= 4'u64 * EPOCH_LENGTH:
for v in previous_epoch_boundary_attesters: for v in previous_epoch_boundary_attesters:
state.validator_registry[v].balance.inc(adjust_for_inclusion_distance( state.validator_registry[v].balance += adjust_for_inclusion_distance(
base_reward(state.validator_registry[v]) * base_reward(state.validator_registry[v]) *
previous_epoch_boundary_attesting_balance div total_balance, previous_epoch_boundary_attesting_balance div total_balance,
inclusion_distance(v)).int) inclusion_distance(v))
for v in active_validator_indices: for v in active_validator_indices:
if v notin previous_epoch_boundary_attesters: if v notin previous_epoch_boundary_attesters:
state.validator_registry[v].balance.dec( state.validator_registry[v].balance -=
base_reward(state.validator_registry[v]).int) base_reward(state.validator_registry[v])
else: else:
# Any validator in `prev_cycle_boundary_attesters` sees their balance # Any validator in `prev_cycle_boundary_attesters` sees their balance
# unchanged. # unchanged.
@ -413,63 +376,63 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
if (v.status == ACTIVE and if (v.status == ACTIVE and
vindex.Uint24 notin previous_epoch_boundary_attesters) or vindex.Uint24 notin previous_epoch_boundary_attesters) or
v.status == EXITED_WITH_PENALTY: v.status == EXITED_WITH_PENALTY:
v.balance.dec( v.balance -= base_reward(v) +
(base_reward(v) + get_effective_balance(v) * time_since_finality div get_effective_balance(v) * slots_since_finality div
INACTIVITY_PENALTY_QUOTIENT.uint64).int) INACTIVITY_PENALTY_QUOTIENT
for v in previous_epoch_boundary_attesters: for v in previous_epoch_boundary_attesters:
let proposer_index = get_beacon_proposer_index(state, inclusion_slot(v)) let proposer_index =
state.validator_registry[proposer_index].balance.inc( get_beacon_proposer_index(state, inclusion_slot(v))
(base_reward(state.validator_registry[v]) div INCLUDER_REWARD_QUOTIENT.uint64).int) state.validator_registry[proposer_index].balance +=
base_reward(state.validator_registry[v]) div
INCLUDER_REWARD_QUOTIENT
block: # Balance recalculations related to crosslink rewards block: # Crosslink rewards and penalties
for sac in state.shard_committees_at_slots[0 ..< EPOCH_LENGTH]: for sac in state.shard_committees_at_slots[0 ..< EPOCH_LENGTH]:
for obj in sac: for obj in sac:
for vindex in obj.committee: for vindex in obj.committee:
let v = state.validator_registry[vindex].addr let v = state.validator_registry[vindex].addr
if vindex in attesting_validators(obj): if vindex in attesting_validators(obj):
v.balance.inc(adjust_for_inclusion_distance( v.balance += adjust_for_inclusion_distance(
base_reward(v[]) * total_attesting_balance(obj) div total_balance_sac(obj), base_reward(v[]) * total_attesting_balance(obj) div total_balance_sac(obj),
inclusion_distance(vindex)).int) inclusion_distance(vindex))
else: else:
v.balance.dec(base_reward(v[]).int) v.balance -= base_reward(v[])
block: # Ethereum 1.0 chain related rules block: # Validator registry
if state.latest_state_recalculation_slot mod
POW_RECEIPT_ROOT_VOTING_PERIOD.uint64 == 0:
for x in state.candidate_pow_receipt_roots:
if x.votes * 2 >= POW_RECEIPT_ROOT_VOTING_PERIOD.uint64:
state.processed_pow_receipt_root = x.candidate_pow_receipt_root
break
state.candidate_pow_receipt_roots = @[]
block: # Validator registry change
if state.finalized_slot > state.validator_registry_latest_change_slot and if state.finalized_slot > state.validator_registry_latest_change_slot and
allIt(state.shard_committees_at_slots, allIt(state.shard_committees_at_slots,
allIt(it, allIt(it,
state.latest_crosslinks[it.shard].slot > state.latest_crosslinks[it.shard].slot >
state.validator_registry_latest_change_slot)): state.validator_registry_latest_change_slot)):
state.change_validators(s) update_validator_registry(state)
state.validator_registry_latest_change_slot = s + EPOCH_LENGTH state.validator_registry_latest_change_slot = state.slot
for i in 0..<EPOCH_LENGTH: for i in 0..<EPOCH_LENGTH:
state.shard_committees_at_slots[i] = state.shard_committees_at_slots[i] =
state.shard_committees_at_slots[EPOCH_LENGTH + i] state.shard_committees_at_slots[EPOCH_LENGTH + i]
# https://github.com/ethereum/eth2.0-specs/issues/223
let next_start_shard = (state.shard_committees_at_slots[^1][^1].shard + 1) mod SHARD_COUNT let next_start_shard =
(state.shard_committees_at_slots[^1][^1].shard + 1) mod SHARD_COUNT
for i, v in get_new_shuffling( for i, v in get_new_shuffling(
state.next_seed, state.validator_registry, next_start_shard): state.next_seed, state.validator_registry, next_start_shard):
state.shard_committees_at_slots[i + EPOCH_LENGTH] = v state.shard_committees_at_slots[i + EPOCH_LENGTH] = v
state.next_seed = state.randao_mix state.next_seed = state.randao_mix
else: else:
# If a validator registry change does NOT happen # If a validator registry change does NOT happen
for i in 0..<EPOCH_LENGTH: for i in 0..<EPOCH_LENGTH:
state.shard_committees_at_slots[i] = state.shard_committees_at_slots[i] =
state.shard_committees_at_slots[EPOCH_LENGTH + i] state.shard_committees_at_slots[EPOCH_LENGTH + i]
let time_since_finality = blck.slot - state.validator_registry_latest_change_slot
let slots_since_finality =
state.slot - state.validator_registry_latest_change_slot
let start_shard = state.shard_committees_at_slots[0][0].shard let start_shard = state.shard_committees_at_slots[0][0].shard
if time_since_finality * EPOCH_LENGTH <= MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL.uint64 or if slots_since_finality * EPOCH_LENGTH <=
is_power_of_2(time_since_finality): MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL or
is_power_of_2(slots_since_finality):
for i, v in get_new_shuffling( for i, v in get_new_shuffling(
state.next_seed, state.validator_registry, start_shard): state.next_seed, state.validator_registry, start_shard):
state.shard_committees_at_slots[i + EPOCH_LENGTH] = v state.shard_committees_at_slots[i + EPOCH_LENGTH] = v
@ -478,7 +441,9 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
block: # Proposer reshuffling block: # Proposer reshuffling
let active_validator_indices = get_active_validator_indices(state.validator_registry) let active_validator_indices = get_active_validator_indices(state.validator_registry)
let num_validators_to_reshuffle = len(active_validator_indices) div SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD let num_validators_to_reshuffle =
len(active_validator_indices) div
SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD.int
for i in 0..<num_validators_to_reshuffle: for i in 0..<num_validators_to_reshuffle:
# Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling # Multiplying i to 2 to ensure we have different input to all the required hashes in the shuffling
# and none of the hashes used for entropy in this loop will be the same # and none of the hashes used for entropy in this loop will be the same
@ -487,7 +452,7 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
let shard_reassignment_record = ShardReassignmentRecord( let shard_reassignment_record = ShardReassignmentRecord(
validator_index: validator_index, validator_index: validator_index,
shard: new_shard, shard: new_shard,
slot: s + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD.uint64 slot: s + SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD
) )
state.persistent_committee_reassignments.add(shard_reassignment_record) state.persistent_committee_reassignments.add(shard_reassignment_record)
@ -501,17 +466,13 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
state.persistent_committees[reassignment.shard.int].add( state.persistent_committees[reassignment.shard.int].add(
reassignment.validator_index) reassignment.validator_index)
block: # Finally... block: # Final updates
# Remove all attestation records older than slot `s`. # TODO Remove all attestation records older than slot `s`.
for i, v in state.validator_registry:
if v.balance < MIN_BALANCE.uint64 and v.status == ACTIVE:
exit_validator(i.Uint24, state, penalize=false, current_slot=blck.slot)
state.latest_block_hashes = state.latest_block_hashes[EPOCH_LENGTH..^1] state.latest_block_hashes = state.latest_block_hashes[EPOCH_LENGTH..^1]
state.latest_state_recalculation_slot.inc(EPOCH_LENGTH)
true true
func updateState*(state: BeaconState, blck: BeaconBlock): Option[BeaconState] = func updateState*(state: BeaconState, latest_block: BeaconBlock): Option[BeaconState] =
## Adjust `state` according to the information in `blck`. ## Adjust `state` according to the information in `blck`.
## Returns the new state, or `none` if the block is invalid. ## Returns the new state, or `none` if the block is invalid.
@ -523,14 +484,14 @@ func updateState*(state: BeaconState, blck: BeaconBlock): Option[BeaconState] =
# bool return values...) # bool return values...)
var state = state var state = state
# Block processing is split up into two phases - lightweight updates done # Slot processing is split up into two phases - lightweight updates done
# for each block, and bigger updates done for each epoch. # for each slot, and bigger updates done for each epoch.
# Lightweight updates that happen for every block # Lightweight updates that happen for every slot
if not processBlock(state, blck): return if not processSlot(state, latest_block): return
# Heavy updates that happen for every epoch # Heavy updates that happen for every epoch
if not processEpoch(state, blck): return if not processEpoch(state, latest_block): return
# All good, we can return the new state # All good, we can return the new state
some(state) some(state)

View File

@ -57,7 +57,7 @@ iterator changes*(log: ChangeLog): ChangeLogEntry =
yield if log.order.getBit(i): yield if log.order.getBit(i):
ChangeLogEntry(kind: Activation, pubkey: nextItem(added)) ChangeLogEntry(kind: Activation, pubkey: nextItem(added))
else: else:
ChangeLogEntry(kind: Exit, index: nextItem(removed)) ChangeLogEntry(kind: ValidatorSetDeltaFlags.Exit, index: nextItem(removed))
proc getValidatorChangeLog*(node: EthereumNode, changeLogHead: Eth2Digest): proc getValidatorChangeLog*(node: EthereumNode, changeLogHead: Eth2Digest):
Future[(Peer, ChangeLog)] {.async.} = Future[(Peer, ChangeLog)] {.async.} =

View File

@ -6,14 +6,16 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
milagro_crypto,
../beacon_chain/extras, ../beacon_chain/extras,
../beacon_chain/spec/[crypto, datatypes] ../beacon_chain/spec/[crypto, datatypes]
func makeValidatorPubKey(n: int): ValidatorPubKey = func makeValidatorPrivKey(n: int): ValidatorPrivKey =
result.point.x.a.g[0] = n result.x[0] = n
func makeInitialValidators*(n = EPOCH_LENGTH): seq[InitialValidator] = func makeInitialValidators*(n = EPOCH_LENGTH): seq[InitialValidator] =
for i in 0..<n: for i in 0..<n.int:
let key = makeValidatorPrivKey(i)
result.add InitialValidator( result.add InitialValidator(
pubkey: makeValidatorPubKey(i) pubkey: key.fromSigKey()
) )