mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-10 22:36:01 +00:00
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,
|
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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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 ###################################
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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.} =
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user