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:
parent
d18ca32ff5
commit
7ea51d5b0b
|
@ -99,7 +99,7 @@ proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator =
|
|||
|
||||
proc makeAttestation(node: BeaconNode,
|
||||
validator: AttachedValidator) {.async.} =
|
||||
var attestation: Attestation
|
||||
var attestation: AttestationCandidate
|
||||
attestation.validator = validator.idx
|
||||
|
||||
# TODO: Populate attestation.data
|
||||
|
@ -126,21 +126,22 @@ proc proposeBlock(node: BeaconNode,
|
|||
node.mainchainMonitor.getBeaconBlockRef()
|
||||
|
||||
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,
|
||||
# the attestations from individual validators have to be merged.
|
||||
# proposal.attestations.add a
|
||||
discard
|
||||
|
||||
for r in node.mainchainMonitor.getValidatorActions(
|
||||
node.headBlock.candidate_pow_receipt_root,
|
||||
proposal.candidate_pow_receipt_root):
|
||||
proposal.specials.add r
|
||||
# TODO update after spec change removed specials
|
||||
# for r in node.mainchainMonitor.getValidatorActions(
|
||||
# node.headBlock.candidate_pow_receipt_root,
|
||||
# proposal.candidate_pow_receipt_root):
|
||||
# proposal.specials.add r
|
||||
|
||||
var signedData: ProposalSignedData
|
||||
# TODO: populate the signed data
|
||||
|
||||
proposal.proposer_signature = await validator.signBlockProposal(signedData)
|
||||
proposal.signature = await validator.signBlockProposal(signedData)
|
||||
await node.network.broadcast(topicBeaconBlocks, proposal)
|
||||
|
||||
proc scheduleCycleActions(node: BeaconNode) =
|
||||
|
@ -153,7 +154,7 @@ proc scheduleCycleActions(node: BeaconNode) =
|
|||
let
|
||||
slot = cycleStart + i
|
||||
proposerIdx = get_beacon_proposer_index(node.beaconState, slot.uint64)
|
||||
attachedValidator = node.getAttachedValidator(proposerIdx.int)
|
||||
attachedValidator = node.getAttachedValidator(proposerIdx)
|
||||
|
||||
if attachedValidator != nil:
|
||||
# TODO:
|
||||
|
|
|
@ -19,6 +19,6 @@ type
|
|||
## Eth1 validator registration contract output
|
||||
pubkey*: ValidatorPubKey
|
||||
deposit_size*: uint64
|
||||
proof_of_possession*: seq[byte]
|
||||
proof_of_possession*: ValidatorSig
|
||||
withdrawal_credentials*: Eth2Digest
|
||||
randao_commitment*: Eth2Digest
|
||||
|
|
|
@ -3,17 +3,17 @@ import
|
|||
spec/[datatypes, crypto]
|
||||
|
||||
type
|
||||
Attestation* = object
|
||||
AttestationCandidate* = object
|
||||
validator*: int
|
||||
data*: AttestationData
|
||||
signature*: ValidatorSig
|
||||
|
||||
AttestationPool* = object
|
||||
attestations: Deque[seq[Attestation]]
|
||||
attestations: Deque[seq[AttestationCandidate]]
|
||||
startingSlot: int
|
||||
|
||||
proc init*(T: type AttestationPool, startingSlot: int): T =
|
||||
result.attestationsPerSlot = initDeque[seq[Attestation]]()
|
||||
result.attestationsPerSlot = initDeque[seq[AttestationCandidate]]()
|
||||
result.startingSlot = startingSlot
|
||||
|
||||
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)
|
||||
|
||||
proc add*(pool: var AttestationPool,
|
||||
attestation: Attestation,
|
||||
attestation: AttestationCandidate,
|
||||
beaconState: BeaconState) =
|
||||
# The caller of this function is responsible for ensuring that
|
||||
# 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
|
||||
|
||||
iterator each*(pool: AttestationPool,
|
||||
firstSlot, lastSlot: int): Attestation =
|
||||
firstSlot, lastSlot: int): AttestationCandidate =
|
||||
## Both indices are treated inclusively
|
||||
## TODO: this should return a lent value
|
||||
doAssert firstSlot <= lastSlot
|
||||
|
|
|
@ -21,9 +21,10 @@ proc getBeaconBlockRef*(m: MainchainMonitor): Eth2Digest =
|
|||
# This should be a simple accessor for the reference kept above
|
||||
discard
|
||||
|
||||
iterator getValidatorActions*(m: MainchainMonitor,
|
||||
fromBlock, toBlock: Eth2Digest): SpecialRecord =
|
||||
# It's probably better if this doesn't return a SpecialRecord, but
|
||||
# rather a more readable description of the change that can be packed
|
||||
# in a SpecialRecord by the client of the API.
|
||||
discard
|
||||
# TODO update after spec change removed Specials
|
||||
# iterator getValidatorActions*(m: MainchainMonitor,
|
||||
# fromBlock, toBlock: Eth2Digest): SpecialRecord =
|
||||
# # It's probably better if this doesn't return a SpecialRecord, but
|
||||
# # rather a more readable description of the change that can be packed
|
||||
# # in a SpecialRecord by the client of the API.
|
||||
# discard
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import
|
||||
math, sequtils,
|
||||
../extras,
|
||||
./datatypes, ./digest, ./helpers, ./validator
|
||||
../extras, ../ssz,
|
||||
./crypto, ./datatypes, ./digest, ./helpers, ./validator
|
||||
|
||||
func on_startup*(initial_validator_entries: openArray[InitialValidator],
|
||||
genesis_time: uint64,
|
||||
|
@ -96,14 +96,12 @@ func on_startup*(initial_validator_entries: openArray[InitialValidator],
|
|||
)
|
||||
|
||||
func get_block_hash*(state: BeaconState,
|
||||
current_block: BeaconBlock,
|
||||
slot: uint64): Eth2Digest =
|
||||
let earliest_slot_in_array =
|
||||
current_block.slot.int - state.latest_block_hashes.len
|
||||
assert earliest_slot_in_array <= slot.int
|
||||
assert slot < current_block.slot
|
||||
|
||||
state.latest_block_hashes[slot.int - earliest_slot_in_array]
|
||||
state.slot - len(state.latest_block_hashes).uint64
|
||||
assert earliest_slot_in_array <= slot
|
||||
assert slot < state.slot
|
||||
state.latest_block_hashes[(slot - earliest_slot_in_array).int]
|
||||
|
||||
func append_to_recent_block_hashes*(old_block_hashes: seq[Eth2Digest],
|
||||
parent_slot, current_slot: uint64,
|
||||
|
@ -139,15 +137,76 @@ func get_attestation_participants*(state: BeaconState,
|
|||
result.add(vindex)
|
||||
return # found the shard, we're done
|
||||
|
||||
func change_validators*(state: var BeaconState,
|
||||
current_slot: uint64) =
|
||||
## Change validator registry.
|
||||
func process_ejections*(state: var BeaconState) =
|
||||
## Iterate through the validator registry
|
||||
## and eject active validators with balance below ``EJECTION_BALANCE``.
|
||||
|
||||
let res = get_changed_validators(
|
||||
state.validator_registry,
|
||||
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,
|
||||
current_slot
|
||||
)
|
||||
state.validator_registry = res.validators
|
||||
state.latest_penalized_exit_balances = res.latest_penalized_exit_balances
|
||||
state.validator_registry_delta_chain_tip) =
|
||||
get_updated_validator_registry(
|
||||
state.validator_registry,
|
||||
state.latest_penalized_exit_balances,
|
||||
state.validator_registry_delta_chain_tip,
|
||||
state.slot
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -20,5 +20,22 @@ type
|
|||
template hash*(k: ValidatorPubKey|ValidatorPrivKey): Hash =
|
||||
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)
|
||||
|
|
|
@ -27,86 +27,224 @@ import
|
|||
intsets, eth_common, math,
|
||||
./crypto, ./digest
|
||||
|
||||
const
|
||||
SHARD_COUNT* = 1024 # a constant referring to the number of shards
|
||||
TARGET_COMMITTEE_SIZE* = 2^8 # validators
|
||||
MAX_ATTESTATIONS_PER_BLOCK* = 2^7 # attestations
|
||||
MIN_BALANCE* = 2^4 # ETH
|
||||
MAX_BALANCE_CHURN_QUOTIENT* = 2^5 # ETH
|
||||
GWEI_PER_ETH* = 10^9 # Gwei/ETH
|
||||
BEACON_CHAIN_SHARD_NUMBER* = not 0'u64
|
||||
# TODO Data types:
|
||||
# Presently, we're reusing the data types from the serialization (uint64) in the
|
||||
# objects we pass around to the beacon chain logic, thus keeping the two
|
||||
# similar. This is convenient for keeping up with the specification, but
|
||||
# will eventually need a more robust approach such that we don't run into
|
||||
# over- and underflows.
|
||||
# Some of the open questions are being tracked here:
|
||||
# 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 #
|
||||
MIN_DEPOSIT* = 2^0 #
|
||||
MAX_DEPOSIT* = 2^5 #
|
||||
const
|
||||
SHARD_COUNT* = 1024 ##\
|
||||
## 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_FORK_VERSION* = 0 #
|
||||
INITIAL_SLOT_NUMBER* = 0 #
|
||||
ZERO_HASH* = Eth2Digest()
|
||||
INITIAL_FORK_VERSION* = 0'u64
|
||||
INITIAL_SLOT_NUMBER* = 0'u64
|
||||
ZERO_HASH* = Eth2Digest()
|
||||
|
||||
# Time constants
|
||||
SLOT_DURATION* = 6 # seconds
|
||||
MIN_ATTESTATION_INCLUSION_DELAY* = 4 # slots (~25 minutes)
|
||||
EPOCH_LENGTH* = 64 # slots (~6.4 minutes)
|
||||
MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL* = 2^8 # slots (~25.6 minutes)
|
||||
POW_RECEIPT_ROOT_VOTING_PERIOD* = 2^10 # slots (~1.7 hours)
|
||||
SHARD_PERSISTENT_COMMITTEE_CHANGE_PERIOD* = 2^17 # slots (~9 days)
|
||||
COLLECTIVE_PENALTY_CALCULATION_PERIOD* = 2^20 # slots (~2.4 months)
|
||||
ZERO_BALANCE_VALIDATOR_TTL* = 2^22 # slots (~290 days)
|
||||
SLOT_DURATION* = 6 ## \
|
||||
## TODO consistent time unit across projects, similar to C++ chrono?
|
||||
|
||||
MIN_ATTESTATION_INCLUSION_DELAY* = 4'u64 ##\
|
||||
## (24 seconds)
|
||||
## Number of slots that attestations stay in the attestation
|
||||
## pool before being added to a block
|
||||
|
||||
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
|
||||
BASE_REWARD_QUOTIENT* = 2^11 # per-cycle interest rate assuming all validators are
|
||||
# participating, assuming total deposits of 1 ETH. It
|
||||
# corresponds to ~2.57% annual interest assuming 10
|
||||
# million participating ETH.
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT* = 2^9 # ?
|
||||
INCLUDER_REWARD_QUOTIENT* = 2^3 #
|
||||
INACTIVITY_PENALTY_QUOTIENT* = 2^34 #
|
||||
BASE_REWARD_QUOTIENT* = 2'u64^11 ##\
|
||||
## per-cycle interest rate assuming all validators are participating, assuming
|
||||
## total deposits of 1 ETH. It corresponds to ~2.57% annual interest assuming
|
||||
## 10 million participating ETH.
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT* = 2'u64^9
|
||||
INCLUDER_REWARD_QUOTIENT* = 2'u64^3
|
||||
INACTIVITY_PENALTY_QUOTIENT* = 2'u64^34
|
||||
|
||||
type
|
||||
Uint24* = range[0'u32 .. 0xFFFFFF'u32] # TODO: wrap-around
|
||||
|
||||
BeaconBlock* = object
|
||||
slot*: uint64 # Slot number
|
||||
randao_reveal*: Eth2Digest # Proposer RANDAO reveal
|
||||
candidate_pow_receipt_root*: Eth2Digest # Recent PoW receipt root
|
||||
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 # State root
|
||||
attestations*: seq[AttestationRecord] # Attestations
|
||||
specials*: seq[SpecialRecord] # Specials (e.g. logouts, penalties)
|
||||
proposer_signature*: ValidatorSig # Proposer signature
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#data-structures
|
||||
ProposerSlashing* = object
|
||||
proposer_index*: Uint24
|
||||
proposal_data_1*: ProposalSignedData
|
||||
proposal_signature_1*: ValidatorSig
|
||||
proposal_data_2*: ProposalSignedData
|
||||
proposal_signature_2*: ValidatorSig
|
||||
|
||||
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
|
||||
participation_bitfield*: seq[byte] # Attester participation bitfield
|
||||
custody_bitfield*: seq[byte] # Proof of custody bitfield
|
||||
aggregate_signature*: ValidatorSig # BLS aggregate signature
|
||||
|
||||
AttestationData* = object
|
||||
slot*: uint64 # Slot number
|
||||
shard*: uint64 # Shard number
|
||||
beacon_block_hash*: Eth2Digest # Hash of the block we're signing
|
||||
epoch_boundary_hash*: Eth2Digest # Hash of the ancestor at the cycle boundary
|
||||
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
|
||||
slot*: uint64
|
||||
shard*: uint64
|
||||
beacon_block_hash*: Eth2Digest ##\
|
||||
## Hash of the block we're signing
|
||||
|
||||
epoch_boundary_hash*: Eth2Digest ##\
|
||||
## Hash of the ancestor at the cycle boundary
|
||||
|
||||
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
|
||||
slot*: uint64 # Slot number
|
||||
shard*: uint64 # Shard number (or `BEACON_CHAIN_SHARD_NUMBER` for beacon chain)
|
||||
block_hash*: Eth2Digest # Block hash
|
||||
|
||||
SpecialRecord* = object
|
||||
kind*: SpecialRecordType # Kind
|
||||
data*: seq[byte] # Data
|
||||
slot*: uint64
|
||||
shard*: uint64 ##\
|
||||
## Shard number (or `BEACON_CHAIN_SHARD_NUMBER` for beacon chain)
|
||||
block_hash*: Eth2Digest
|
||||
|
||||
BeaconState* = object
|
||||
slot*: uint64
|
||||
genesis_time*: uint64
|
||||
fork_data*: ForkData ##\
|
||||
## For versioning hard forks
|
||||
|
||||
# Validator registry
|
||||
validator_registry*: seq[ValidatorRecord]
|
||||
validator_registry_latest_change_slot*: uint64
|
||||
|
@ -126,7 +264,7 @@ type
|
|||
# Finality
|
||||
previous_justified_slot*: uint64
|
||||
justified_slot*: uint64
|
||||
justified_slot_bitfield*: uint64
|
||||
justification_bitfield*: uint64
|
||||
finalized_slot*: uint64
|
||||
|
||||
latest_crosslinks*: array[SHARD_COUNT, CrosslinkRecord]
|
||||
|
@ -140,10 +278,6 @@ type
|
|||
processed_pow_receipt_root*: Eth2Digest
|
||||
candidate_pow_receipt_roots*: seq[CandidatePoWReceiptRootRecord]
|
||||
|
||||
genesis_time*: uint64
|
||||
fork_data*: ForkData ##\
|
||||
## For versioning hard forks
|
||||
|
||||
ValidatorRecord* = object
|
||||
pubkey*: ValidatorPubKey # Public key
|
||||
withdrawal_credentials*: Eth2Digest # Withdrawal credentials
|
||||
|
@ -200,6 +334,12 @@ type
|
|||
Activation = 0
|
||||
Exit = 1
|
||||
|
||||
SignatureDomain* {.pure.} = enum
|
||||
DOMAIN_DEPOSIT = 0
|
||||
DOMAIN_ATTESTATION = 1
|
||||
DOMAIN_PROPOSAL = 2
|
||||
DOMAIN_EXIT = 3
|
||||
|
||||
# Note:
|
||||
# We use IntSet from Nim Standard library which are efficient sparse bitsets.
|
||||
# See: https://nim-lang.org/docs/intsets.html
|
||||
|
@ -236,4 +376,3 @@ when true:
|
|||
|
||||
proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} =
|
||||
discard
|
||||
|
||||
|
|
|
@ -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
|
||||
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?
|
||||
(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 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
|
||||
|
|
|
@ -12,6 +12,9 @@ import
|
|||
../ssz,
|
||||
./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] =
|
||||
for i, v in validators:
|
||||
if v.balance == 0 and
|
||||
|
@ -23,7 +26,7 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
|
|||
fork_data: ForkData,
|
||||
pubkey: ValidatorPubKey,
|
||||
deposit: uint64,
|
||||
proof_of_possession: seq[byte],
|
||||
proof_of_possession: ValidatorSig,
|
||||
withdrawal_credentials: Eth2Digest,
|
||||
randao_commitment: Eth2Digest,
|
||||
status: ValidatorStatusCodes,
|
||||
|
@ -32,26 +35,20 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
|
|||
# TODO Spec candidate: inefficient API
|
||||
#
|
||||
# Check that validator really did register
|
||||
# let signed_message = signed_message = bytes32(pubkey) + withdrawal_credentials + randao_commitment
|
||||
# assert BLSVerify(pub=pubkey,
|
||||
# msg=hash(signed_message),
|
||||
# sig=proof_of_possession,
|
||||
# domain=get_domain(
|
||||
# fork_data,
|
||||
# current_slot,
|
||||
# DOMAIN_DEPOSIT
|
||||
# ))
|
||||
# TODO fix tests and enable (nightmare)
|
||||
# let msg = hashSSZ((pubkey, withdrawal_credentials, randao_commitment))
|
||||
# assert BLSVerify(
|
||||
# pubkey, msg, proof_of_possession,
|
||||
# get_domain(fork_data, current_slot, DOMAIN_DEPOSIT))
|
||||
|
||||
var new_validators = current_validators
|
||||
var validators_copy = current_validators
|
||||
|
||||
for index, val in new_validators.mpairs():
|
||||
if val.pubkey == pubkey:
|
||||
# assert deposit_size >= MIN_TOPUP_SIZE
|
||||
# assert val.status != WITHDRAWN
|
||||
# assert val.withdrawal_credentials == withdrawal_credentials
|
||||
for index, validator in validators_copy.mpairs():
|
||||
if validator.pubkey == pubkey:
|
||||
assert validator.withdrawal_credentials == withdrawal_credentials
|
||||
|
||||
val.balance.inc(deposit.int)
|
||||
return (new_validators, index)
|
||||
validator.balance += deposit
|
||||
return (validators_copy, index)
|
||||
|
||||
# new validator
|
||||
let
|
||||
|
@ -66,18 +63,18 @@ func get_new_validators*(current_validators: seq[ValidatorRecord],
|
|||
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:
|
||||
new_validators.add(rec)
|
||||
(new_validators, len(new_validators) - 1)
|
||||
validators_copy.add(rec)
|
||||
(validators_copy, len(validators_copy) - 1)
|
||||
else:
|
||||
new_validators[index.get()] = rec
|
||||
(new_validators, index.get())
|
||||
validators_copy[index.get()] = rec
|
||||
(validators_copy, index.get())
|
||||
|
||||
func get_active_validator_indices*(validators: openArray[ValidatorRecord]): seq[Uint24] =
|
||||
## Select the active validators
|
||||
for idx, val in validators:
|
||||
if val.status in {ACTIVE, ACTIVE_PENDING_EXIT}:
|
||||
if is_active_validator(val):
|
||||
result.add idx.Uint24
|
||||
|
||||
func get_new_shuffling*(seed: Eth2Digest,
|
||||
|
@ -109,7 +106,7 @@ func get_new_shuffling*(seed: Eth2Digest,
|
|||
var committees = newSeq[ShardCommittee](shard_indices.len)
|
||||
for shard_position, indices in shard_indices:
|
||||
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].total_validator_count =
|
||||
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(flag.uint8)
|
||||
h.update hashSSZ(index)
|
||||
# TODO h.update hashSSZ(pubkey)
|
||||
h.update hashSSZ(pubkey)
|
||||
|
||||
func get_effective_balance*(validator: ValidatorRecord): uint64 =
|
||||
min(validator.balance, MAX_DEPOSIT.uint64)
|
||||
min(validator.balance, MAX_DEPOSIT)
|
||||
|
||||
func exit_validator*(index: Uint24,
|
||||
state: var BeaconState,
|
||||
penalize: bool,
|
||||
current_slot: uint64) =
|
||||
new_status: ValidatorStatusCodes) =
|
||||
## Remove the validator with the given `index` from `state`.
|
||||
## Note that this function mutates `state`.
|
||||
|
||||
state.validator_registry_exit_count.inc()
|
||||
state.validator_registry_exit_count += 1
|
||||
|
||||
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
|
||||
|
||||
# Remove validator from persistent committees
|
||||
|
@ -154,34 +151,30 @@ func exit_validator*(index: Uint24,
|
|||
committee.delete(i)
|
||||
break
|
||||
|
||||
if penalize:
|
||||
validator.status = EXITED_WITH_PENALTY
|
||||
if new_status == EXITED_WITH_PENALTY:
|
||||
state.latest_penalized_exit_balances[
|
||||
(current_slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD.uint64).int].inc(
|
||||
get_effective_balance(validator).int)
|
||||
(state.slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int] +=
|
||||
get_effective_balance(validator[])
|
||||
|
||||
var
|
||||
whistleblower =
|
||||
state.validator_registry[get_beacon_proposer_index(state, current_slot).int]
|
||||
whistleblower = addr state.validator_registry[
|
||||
get_beacon_proposer_index(state, state.slot)]
|
||||
whistleblower_reward =
|
||||
validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT.uint64
|
||||
whistleblower.balance.inc(whistleblower_reward.int)
|
||||
validator.balance.dec(whistleblower_reward.int)
|
||||
else:
|
||||
validator.status = ACTIVE_PENDING_EXIT
|
||||
validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT
|
||||
|
||||
whistleblower.balance += whistleblower_reward
|
||||
validator.balance -= whistleblower_reward
|
||||
|
||||
state.validator_registry_delta_chain_tip =
|
||||
get_new_validator_registry_delta_chain_tip(
|
||||
state.validator_registry_delta_chain_tip,
|
||||
index,
|
||||
validator.pubkey,
|
||||
EXIT,
|
||||
)
|
||||
state.validator_registry_delta_chain_tip, index, validator.pubkey,
|
||||
ValidatorSetDeltaFlags.EXIT)
|
||||
|
||||
func get_changed_validators*(validators: seq[ValidatorRecord],
|
||||
latest_penalized_exit_balances: seq[uint64],
|
||||
validator_registry_delta_chain_tip: Eth2Digest,
|
||||
current_slot: uint64):
|
||||
func get_updated_validator_registry*(
|
||||
validator_registry: seq[ValidatorRecord],
|
||||
latest_penalized_exit_balances: seq[uint64],
|
||||
validator_registry_delta_chain_tip: Eth2Digest,
|
||||
current_slot: uint64):
|
||||
tuple[
|
||||
validators: seq[ValidatorRecord],
|
||||
latest_penalized_exit_balances: seq[uint64],
|
||||
|
@ -190,77 +183,85 @@ func get_changed_validators*(validators: seq[ValidatorRecord],
|
|||
## `validator_registry_delta_chain_tip`.
|
||||
|
||||
# TODO inefficient
|
||||
var validators = validators
|
||||
var
|
||||
validator_registry = validator_registry
|
||||
latest_penalized_exit_balances = latest_penalized_exit_balances
|
||||
|
||||
# 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
|
||||
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)
|
||||
let max_balance_churn = max(
|
||||
(MAX_DEPOSIT * GWEI_PER_ETH).uint64,
|
||||
MAX_DEPOSIT * GWEI_PER_ETH,
|
||||
total_balance div (2 * MAX_BALANCE_CHURN_QUOTIENT)
|
||||
)
|
||||
|
||||
# Activate validators within the allowable balance churn
|
||||
var balance_churn = 0'u64
|
||||
var validator_registry_delta_chain_tip = validator_registry_delta_chain_tip
|
||||
for i in 0..<len(validators):
|
||||
if validators[i].status == PENDING_ACTIVATION and
|
||||
validators[i].balance >= MAX_DEPOSIT.uint64:
|
||||
for i in 0..<len(validator_registry):
|
||||
if validator_registry[i].status == PENDING_ACTIVATION and
|
||||
validator_registry[i].balance >= MAX_DEPOSIT:
|
||||
# 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:
|
||||
break
|
||||
|
||||
# Activate validator
|
||||
validators[i].status = ACTIVE
|
||||
validators[i].latest_status_change_slot = current_slot
|
||||
validator_registry[i].status = ACTIVE
|
||||
validator_registry[i].latest_status_change_slot = current_slot
|
||||
validator_registry_delta_chain_tip =
|
||||
get_new_validator_registry_delta_chain_tip(
|
||||
validator_registry_delta_chain_tip,
|
||||
i.Uint24,
|
||||
validators[i].pubkey,
|
||||
validator_registry[i].pubkey,
|
||||
ACTIVATION,
|
||||
)
|
||||
|
||||
# Exit validators within the allowable balance churn
|
||||
balance_churn = 0
|
||||
for i in 0..<len(validators):
|
||||
if validators[i].status == ACTIVE_PENDING_EXIT:
|
||||
for i in 0..<len(validator_registry):
|
||||
if validator_registry[i].status == ACTIVE_PENDING_EXIT:
|
||||
# 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:
|
||||
break
|
||||
|
||||
# Exit validator
|
||||
validators[i].status = EXITED_WITHOUT_PENALTY
|
||||
validators[i].latest_status_change_slot = current_slot
|
||||
validator_registry[i].status = EXITED_WITHOUT_PENALTY
|
||||
validator_registry[i].latest_status_change_slot = current_slot
|
||||
validator_registry_delta_chain_tip =
|
||||
get_new_validator_registry_delta_chain_tip(
|
||||
validator_registry_delta_chain_tip,
|
||||
i.Uint24,
|
||||
validators[i].pubkey,
|
||||
EXIT,
|
||||
validator_registry[i].pubkey,
|
||||
ValidatorSetDeltaFlags.EXIT,
|
||||
)
|
||||
|
||||
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods
|
||||
let period_index = current_slot.int div COLLECTIVE_PENALTY_CALCULATION_PERIOD
|
||||
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal
|
||||
# periods
|
||||
let period_index =
|
||||
(current_slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int
|
||||
let total_penalties = (
|
||||
(latest_penalized_exit_balances[period_index]) +
|
||||
(if period_index >= 1: latest_penalized_exit_balances[period_index - 1] else: 0) +
|
||||
(if period_index >= 2: latest_penalized_exit_balances[period_index - 2] else: 0)
|
||||
(if period_index >= 1:
|
||||
latest_penalized_exit_balances[period_index - 1] else: 0) +
|
||||
(if period_index >= 2:
|
||||
latest_penalized_exit_balances[period_index - 2] else: 0)
|
||||
)
|
||||
|
||||
# Calculate penalties for slashed validators
|
||||
func to_penalize(v: ValidatorRecord): bool =
|
||||
v.status == EXITED_WITH_PENALTY
|
||||
var validators_to_penalize = filter(validators, to_penalize)
|
||||
for v in validators_to_penalize.mitems():
|
||||
v.balance.dec(
|
||||
for v in validator_registry.mitems():
|
||||
if not to_penalize(v): continue
|
||||
v.balance -=
|
||||
(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)
|
||||
|
|
|
@ -209,6 +209,7 @@ func hashSSZ*[T: not enum](x: T): array[32, byte] =
|
|||
else:
|
||||
## Containers have their fields recursively hashed, concatenated and hashed
|
||||
# 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]]]
|
||||
for name, field in x.fieldPairs:
|
||||
fields.add (name, @(hashSSZ(field)))
|
||||
|
@ -227,36 +228,17 @@ func hashSSZ*(x: enum): array[32, byte] =
|
|||
withHash:
|
||||
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
|
||||
## as of https://github.com/ethereum/beacon_chain/pull/133/files
|
||||
## This is a "stub" needed for BeaconBlock hashing
|
||||
x.getRaw().hash()
|
||||
|
||||
func hashSSZ*(x: AttestationRecord): array[32, byte] =
|
||||
func hashSSZ*(x: ValidatorSig): 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 BeaconBlock hashing
|
||||
withHash:
|
||||
# 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)
|
||||
x.getRaw().hash()
|
||||
|
||||
# ################### Tree hash ###################################
|
||||
|
||||
|
|
|
@ -40,104 +40,55 @@ import
|
|||
milagro_crypto
|
||||
|
||||
func processAttestations(state: var BeaconState,
|
||||
blck: BeaconBlock,
|
||||
parent_slot: uint64): bool =
|
||||
blck: BeaconBlock): bool =
|
||||
# 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
|
||||
# committee of validators.
|
||||
# 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
|
||||
# bulk.
|
||||
if blck.attestations.len > MAX_ATTESTATIONS_PER_BLOCK:
|
||||
if blck.body.attestations.len > MAX_ATTESTATIONS_PER_BLOCK:
|
||||
return
|
||||
|
||||
var res: seq[PendingAttestationRecord]
|
||||
for attestation in blck.attestations:
|
||||
if attestation.data.slot <= blck.slot - MIN_ATTESTATION_INCLUSION_DELAY:
|
||||
return
|
||||
if not allIt(blck.body.attestations, checkAttestation(state, it)):
|
||||
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
|
||||
# TODO no rollback in case of errors
|
||||
state.latest_attestations.add PendingAttestationRecord(
|
||||
data: attestation.data,
|
||||
participation_bitfield: attestation.participation_bitfield,
|
||||
custody_bitfield: attestation.custody_bitfield,
|
||||
slot_included: blck.slot
|
||||
# All checks passed - update state
|
||||
state.latest_attestations.add mapIt(blck.body.attestations,
|
||||
PendingAttestationRecord(
|
||||
data: it.data,
|
||||
participation_bitfield: it.participation_bitfield,
|
||||
custody_bitfield: it.custody_bitfield,
|
||||
slot_included: state.slot
|
||||
)
|
||||
)
|
||||
|
||||
true
|
||||
|
||||
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
|
||||
blck_without_sig.proposer_signature = ValidatorSig()
|
||||
blck_without_sig.signature = ValidatorSig()
|
||||
|
||||
let
|
||||
proposal_hash = hashSSZ(ProposalSignedData(
|
||||
slot: blck.slot,
|
||||
slot: state.slot,
|
||||
shard: BEACON_CHAIN_SHARD,
|
||||
block_hash: Eth2Digest(data: hashSSZ(blck_without_sig))
|
||||
))
|
||||
|
||||
verifyMessage(
|
||||
blck.proposer_signature, proposal_hash,
|
||||
state.validator_registry[get_beacon_proposer_index(state, blck.slot).int].pubkey)
|
||||
let validator_idx = get_beacon_proposer_index(state, state.slot)
|
||||
BLSVerify(
|
||||
state.validator_registry[validator_idx].pubkey,
|
||||
proposal_hash, blck.signature,
|
||||
get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))
|
||||
|
||||
func processRandaoReveal(state: var BeaconState,
|
||||
blck: BeaconBlock,
|
||||
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()
|
||||
|
||||
blck: BeaconBlock): bool =
|
||||
let
|
||||
proposer_index = get_beacon_proposer_index(state, blck.slot)
|
||||
proposer = state.validator_registry[proposer_index.int].addr
|
||||
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_skips + 1) !=
|
||||
|
@ -156,7 +107,7 @@ func processRandaoReveal(state: var BeaconState,
|
|||
func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock): bool =
|
||||
for x in state.candidate_pow_receipt_roots.mitems():
|
||||
if blck.candidate_pow_receipt_root == x.candidate_pow_receipt_root:
|
||||
x.votes.inc
|
||||
x.votes += 1
|
||||
return true
|
||||
|
||||
state.candidate_pow_receipt_roots.add CandidatePoWReceiptRootRecord(
|
||||
|
@ -165,42 +116,50 @@ func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
)
|
||||
return true
|
||||
|
||||
func processSpecials(state: var BeaconState, blck: BeaconBlock): bool =
|
||||
# TODO incoming spec changes here..
|
||||
true
|
||||
|
||||
func processBlock(state: var BeaconState, blck: BeaconBlock): bool =
|
||||
## When a new block is received, all participants must verify that the block
|
||||
## makes sense and update their state accordingly. This function will return
|
||||
## the new state, unless something breaks along the way
|
||||
if not processAttestations(state, blck):
|
||||
false
|
||||
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
|
||||
|
||||
let
|
||||
parent_hash = blck.ancestor_hashes[0]
|
||||
slot = blck.slot
|
||||
parent_slot = slot - 1 # TODO Not!! can skip slots...
|
||||
# TODO actually get parent block, which means fixing up BeaconState refs above;
|
||||
latest_hash = Eth2Digest(data: hashSSZ(latest_block))
|
||||
|
||||
state.latest_block_hashes =
|
||||
append_to_recent_block_hashes(state.latest_block_hashes, parent_slot, slot,
|
||||
parent_hash)
|
||||
state.slot += 1
|
||||
state.latest_block_hashes.add latest_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
|
||||
|
||||
true
|
||||
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
|
||||
|
||||
func flatten[T](v: openArray[seq[T]]): seq[T] =
|
||||
# TODO not in nim - doh.
|
||||
|
@ -255,13 +214,13 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
active_validator_indices =
|
||||
get_active_validator_indices(state.validator_registry)
|
||||
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`.)
|
||||
reward_quotient = BASE_REWARD_QUOTIENT * int_sqrt(total_balance_in_eth)
|
||||
|
||||
# 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 =
|
||||
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
|
||||
assert false # shouldn't happen..
|
||||
|
||||
block: # Adjust justified slots and crosslink status
|
||||
var new_justified_slot: Option[uint64]
|
||||
# TODO where's that bitfield type when you need it?
|
||||
# TODO what happens with the bits that drop off..?
|
||||
state.justified_slot_bitfield = state.justified_slot_bitfield shl 1
|
||||
|
||||
if 3'u64 * previous_epoch_boundary_attesting_balance >= 2'u64 * total_balance:
|
||||
# TODO spec says "flip the second lowest bit to 1" and does "AND", wrong?
|
||||
state.justified_slot_bitfield = state.justified_slot_bitfield or 2
|
||||
new_justified_slot = some(s - EPOCH_LENGTH)
|
||||
|
||||
if 3'u64 * this_epoch_boundary_attesting_balance >= 2'u64 * total_balance:
|
||||
# TODO spec says "flip the second lowest bit to 1" and does "AND", wrong?
|
||||
state.justified_slot_bitfield = state.justified_slot_bitfield or 1
|
||||
new_justified_slot = some(s)
|
||||
|
||||
if state.justified_slot == s - EPOCH_LENGTH and
|
||||
state.justified_slot_bitfield mod 4 == 3:
|
||||
state.finalized_slot = state.justified_slot
|
||||
if state.justified_slot == s - EPOCH_LENGTH - EPOCH_LENGTH and
|
||||
state.justified_slot_bitfield mod 8 == 7:
|
||||
state.finalized_slot = state.justified_slot
|
||||
|
||||
if state.justified_slot == s - EPOCH_LENGTH - 2 * EPOCH_LENGTH and
|
||||
state.justified_slot_bitfield mod 16 in [15'u64, 14]:
|
||||
state.finalized_slot = state.justified_slot
|
||||
block: # Receipt roots
|
||||
if state.slot mod POW_RECEIPT_ROOT_VOTING_PERIOD == 0:
|
||||
for x in state.candidate_pow_receipt_roots:
|
||||
if x.votes * 2 >= POW_RECEIPT_ROOT_VOTING_PERIOD:
|
||||
state.processed_pow_receipt_root = x.candidate_pow_receipt_root
|
||||
break
|
||||
state.candidate_pow_receipt_roots = @[]
|
||||
|
||||
block: # Justification
|
||||
state.previous_justified_slot = state.justified_slot
|
||||
|
||||
if new_justified_slot.isSome():
|
||||
state.justified_slot = new_justified_slot.get()
|
||||
# TODO where's that bitfield type when you need it?
|
||||
# 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:
|
||||
# TODO or just state.shard_committees_at_slots[s]?
|
||||
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(
|
||||
slot: state.latest_state_recalculation_slot + EPOCH_LENGTH,
|
||||
shard_block_hash: winning_hash(obj))
|
||||
|
||||
block: # Balance recalculations related to FFG rewards
|
||||
block: # Justification and finalization rewards and penalties
|
||||
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:
|
||||
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]) *
|
||||
previous_epoch_boundary_attesting_balance div total_balance,
|
||||
inclusion_distance(v)).int)
|
||||
inclusion_distance(v))
|
||||
|
||||
for v in active_validator_indices:
|
||||
if v notin previous_epoch_boundary_attesters:
|
||||
state.validator_registry[v].balance.dec(
|
||||
base_reward(state.validator_registry[v]).int)
|
||||
state.validator_registry[v].balance -=
|
||||
base_reward(state.validator_registry[v])
|
||||
else:
|
||||
# Any validator in `prev_cycle_boundary_attesters` sees their balance
|
||||
# unchanged.
|
||||
|
@ -413,63 +376,63 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
if (v.status == ACTIVE and
|
||||
vindex.Uint24 notin previous_epoch_boundary_attesters) or
|
||||
v.status == EXITED_WITH_PENALTY:
|
||||
v.balance.dec(
|
||||
(base_reward(v) + get_effective_balance(v) * time_since_finality div
|
||||
INACTIVITY_PENALTY_QUOTIENT.uint64).int)
|
||||
v.balance -= base_reward(v) +
|
||||
get_effective_balance(v) * slots_since_finality div
|
||||
INACTIVITY_PENALTY_QUOTIENT
|
||||
|
||||
for v in previous_epoch_boundary_attesters:
|
||||
let proposer_index = get_beacon_proposer_index(state, inclusion_slot(v))
|
||||
state.validator_registry[proposer_index].balance.inc(
|
||||
(base_reward(state.validator_registry[v]) div INCLUDER_REWARD_QUOTIENT.uint64).int)
|
||||
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
|
||||
|
||||
block: # Balance recalculations related to crosslink rewards
|
||||
block: # Crosslink rewards and penalties
|
||||
for sac in state.shard_committees_at_slots[0 ..< EPOCH_LENGTH]:
|
||||
for obj in sac:
|
||||
for vindex in obj.committee:
|
||||
let v = state.validator_registry[vindex].addr
|
||||
|
||||
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),
|
||||
inclusion_distance(vindex)).int)
|
||||
inclusion_distance(vindex))
|
||||
else:
|
||||
v.balance.dec(base_reward(v[]).int)
|
||||
v.balance -= base_reward(v[])
|
||||
|
||||
block: # Ethereum 1.0 chain related rules
|
||||
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
|
||||
|
||||
block: # Validator registry change
|
||||
if state.finalized_slot > state.validator_registry_latest_change_slot and
|
||||
allIt(state.shard_committees_at_slots,
|
||||
allIt(it,
|
||||
state.latest_crosslinks[it.shard].slot >
|
||||
state.validator_registry_latest_change_slot)):
|
||||
state.change_validators(s)
|
||||
state.validator_registry_latest_change_slot = s + EPOCH_LENGTH
|
||||
update_validator_registry(state)
|
||||
state.validator_registry_latest_change_slot = state.slot
|
||||
for i in 0..<EPOCH_LENGTH:
|
||||
state.shard_committees_at_slots[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(
|
||||
state.next_seed, state.validator_registry, next_start_shard):
|
||||
state.shard_committees_at_slots[i + EPOCH_LENGTH] = v
|
||||
|
||||
state.next_seed = state.randao_mix
|
||||
|
||||
else:
|
||||
# If a validator registry change does NOT happen
|
||||
for i in 0..<EPOCH_LENGTH:
|
||||
state.shard_committees_at_slots[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
|
||||
if time_since_finality * EPOCH_LENGTH <= MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL.uint64 or
|
||||
is_power_of_2(time_since_finality):
|
||||
if slots_since_finality * EPOCH_LENGTH <=
|
||||
MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL or
|
||||
is_power_of_2(slots_since_finality):
|
||||
for i, v in get_new_shuffling(
|
||||
state.next_seed, state.validator_registry, start_shard):
|
||||
state.shard_committees_at_slots[i + EPOCH_LENGTH] = v
|
||||
|
@ -478,7 +441,9 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
|
||||
block: # Proposer reshuffling
|
||||
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:
|
||||
# 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
|
||||
|
@ -487,7 +452,7 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
let shard_reassignment_record = ShardReassignmentRecord(
|
||||
validator_index: validator_index,
|
||||
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)
|
||||
|
||||
|
@ -501,17 +466,13 @@ func processEpoch(state: var BeaconState, blck: BeaconBlock): bool =
|
|||
state.persistent_committees[reassignment.shard.int].add(
|
||||
reassignment.validator_index)
|
||||
|
||||
block: # Finally...
|
||||
# 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)
|
||||
block: # Final updates
|
||||
# TODO Remove all attestation records older than slot `s`.
|
||||
state.latest_block_hashes = state.latest_block_hashes[EPOCH_LENGTH..^1]
|
||||
state.latest_state_recalculation_slot.inc(EPOCH_LENGTH)
|
||||
|
||||
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`.
|
||||
## 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...)
|
||||
var state = state
|
||||
|
||||
# Block processing is split up into two phases - lightweight updates done
|
||||
# for each block, and bigger updates done for each epoch.
|
||||
# Slot processing is split up into two phases - lightweight updates done
|
||||
# for each slot, and bigger updates done for each epoch.
|
||||
|
||||
# Lightweight updates that happen for every block
|
||||
if not processBlock(state, blck): return
|
||||
# Lightweight updates that happen for every slot
|
||||
if not processSlot(state, latest_block): return
|
||||
|
||||
# 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
|
||||
some(state)
|
||||
|
|
|
@ -57,7 +57,7 @@ iterator changes*(log: ChangeLog): ChangeLogEntry =
|
|||
yield if log.order.getBit(i):
|
||||
ChangeLogEntry(kind: Activation, pubkey: nextItem(added))
|
||||
else:
|
||||
ChangeLogEntry(kind: Exit, index: nextItem(removed))
|
||||
ChangeLogEntry(kind: ValidatorSetDeltaFlags.Exit, index: nextItem(removed))
|
||||
|
||||
proc getValidatorChangeLog*(node: EthereumNode, changeLogHead: Eth2Digest):
|
||||
Future[(Peer, ChangeLog)] {.async.} =
|
||||
|
|
|
@ -6,14 +6,16 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
milagro_crypto,
|
||||
../beacon_chain/extras,
|
||||
../beacon_chain/spec/[crypto, datatypes]
|
||||
|
||||
func makeValidatorPubKey(n: int): ValidatorPubKey =
|
||||
result.point.x.a.g[0] = n
|
||||
func makeValidatorPrivKey(n: int): ValidatorPrivKey =
|
||||
result.x[0] = n
|
||||
|
||||
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(
|
||||
pubkey: makeValidatorPubKey(i)
|
||||
pubkey: key.fromSigKey()
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue