spec updates (#48)
* spec updates * balances move out to separate seq * bunch of placeholders for proof-of-custody / phase1 * fix inclusion distance adjustment * modify state in-place in `updateState` (tests spent over 80% time copying state! now it's down to 25-50) * document several conditions and conversations * some renames here and there to follow spec
This commit is contained in:
parent
110abf9ffd
commit
12a819c110
|
@ -7,7 +7,9 @@ installDirs = @["beacon_chain", "research"]
|
|||
bin = @[
|
||||
"beacon_chain/beacon_node",
|
||||
"beacon_chain/validator_keygen",
|
||||
"research/state_sim"]
|
||||
"research/serialized_sizes",
|
||||
"research/state_sim",
|
||||
]
|
||||
|
||||
### Dependencies
|
||||
requires "nim >= 0.18.0",
|
||||
|
|
|
@ -19,7 +19,7 @@ proc lastFinalizedState*(db: BeaconChainDB): BeaconStateRef =
|
|||
let stateFile = db.dataRoot / "BeaconState.json"
|
||||
if fileExists stateFile:
|
||||
new result
|
||||
Json.loadFile(stateFile, result[])
|
||||
# TODO serialization error: Json.loadFile(stateFile, result[])
|
||||
except:
|
||||
error "Failed to load the latest finalized state",
|
||||
err = getCurrentExceptionMsg()
|
||||
|
|
|
@ -170,7 +170,7 @@ proc proposeBlock(node: BeaconNode,
|
|||
proc scheduleCycleActions(node: BeaconNode) =
|
||||
## This schedules the required block proposals and
|
||||
## attestations from our attached validators.
|
||||
let cycleStart = node.beaconState.latest_state_recalculation_slot.int
|
||||
let cycleStart = node.beaconState.slot.int
|
||||
|
||||
for i in 0 ..< EPOCH_LENGTH:
|
||||
# Schedule block proposals
|
||||
|
|
|
@ -90,7 +90,7 @@ template mustBeFilePath(input: TaintedString) =
|
|||
template handledAsJsonFilename(T: untyped) {.dirty.} =
|
||||
proc parseCmdArg*(_: type T, input: TaintedString): T =
|
||||
input.mustBeFilePath
|
||||
return Json.loadFile(string(input), T)
|
||||
#return Json.loadFile(string(input), T)
|
||||
|
||||
handledAsJsonFilename BeaconState
|
||||
handledAsJsonFilename ChainStartupData
|
||||
|
|
|
@ -10,6 +10,41 @@ import
|
|||
../extras, ../ssz,
|
||||
./crypto, ./datatypes, ./digest, ./helpers, ./validator
|
||||
|
||||
func get_effective_balance*(state: BeaconState, index: Uint24): uint64 =
|
||||
# Validators collect rewards which increases their balance but not their
|
||||
# influence. Validators may also lose balance if they fail to do their duty
|
||||
# in which case their influence decreases. Once they drop below a certain
|
||||
# balance, they're removed from the validator registry.
|
||||
min(state.validator_balances[index], MAX_DEPOSIT * GWEI_PER_ETH)
|
||||
|
||||
func sum_effective_balances*(
|
||||
state: BeaconState, validator_indices: openArray[Uint24]): uint64 =
|
||||
# TODO spec - add as helper? Used pretty often
|
||||
for index in validator_indices:
|
||||
result += get_effective_balance(state, index)
|
||||
|
||||
func validate_proof_of_possession(state: BeaconState,
|
||||
pubkey: ValidatorPubKey,
|
||||
proof_of_possession: ValidatorSig,
|
||||
withdrawal_credentials: Eth2Digest,
|
||||
randao_commitment: Eth2Digest): bool =
|
||||
let proof_of_possession_data = DepositInput(
|
||||
pubkey: pubkey,
|
||||
withdrawal_credentials: withdrawal_credentials,
|
||||
randao_commitment: randao_commitment
|
||||
)
|
||||
|
||||
bls_verify(
|
||||
pubkey,
|
||||
hash_tree_root_final(proof_of_possession_data).data,
|
||||
proof_of_possession,
|
||||
get_domain(
|
||||
state.fork_data,
|
||||
state.slot,
|
||||
DOMAIN_DEPOSIT,
|
||||
)
|
||||
)
|
||||
|
||||
func process_deposit(state: var BeaconState,
|
||||
pubkey: ValidatorPubKey,
|
||||
deposit: uint64,
|
||||
|
@ -17,11 +52,9 @@ func process_deposit(state: var BeaconState,
|
|||
withdrawal_credentials: Eth2Digest,
|
||||
randao_commitment: Eth2Digest): Uint24 =
|
||||
## Process a deposit from Ethereum 1.0.
|
||||
let msg = hash_tree_root_final(
|
||||
(pubkey, withdrawal_credentials, randao_commitment))
|
||||
assert bls_verify(
|
||||
pubkey, msg.data, proof_of_possession,
|
||||
get_domain(state.fork_data, state.slot, DOMAIN_DEPOSIT))
|
||||
doAssert validate_proof_of_possession(
|
||||
state, pubkey, proof_of_possession, withdrawal_credentials,
|
||||
randao_commitment)
|
||||
|
||||
let validator_pubkeys = mapIt(state.validator_registry, it.pubkey)
|
||||
|
||||
|
@ -32,26 +65,29 @@ func process_deposit(state: var BeaconState,
|
|||
withdrawal_credentials: withdrawal_credentials,
|
||||
randao_commitment: randao_commitment,
|
||||
randao_layers: 0,
|
||||
balance: deposit,
|
||||
status: PENDING_ACTIVATION,
|
||||
latest_status_change_slot: state.slot,
|
||||
exit_count: 0
|
||||
)
|
||||
|
||||
let index = min_empty_validator_index(state.validator_registry, state.slot)
|
||||
let index = min_empty_validator_index(
|
||||
state.validator_registry, state.validator_balances, state.slot)
|
||||
if index.isNone():
|
||||
state.validator_registry.add(validator)
|
||||
state.validator_balances.add(deposit)
|
||||
(len(state.validator_registry) - 1).Uint24
|
||||
else:
|
||||
state.validator_registry[index.get()] = validator
|
||||
state.validator_balances[index.get()] = deposit
|
||||
index.get().Uint24
|
||||
else:
|
||||
# Increase balance by deposit
|
||||
let index = validator_pubkeys.find(pubkey)
|
||||
let validator = addr state.validator_registry[index]
|
||||
assert validator.withdrawal_credentials == withdrawal_credentials
|
||||
assert state.validator_registry[index].withdrawal_credentials ==
|
||||
withdrawal_credentials
|
||||
|
||||
validator.balance += deposit
|
||||
state.validator_balances[index] += deposit
|
||||
index.Uint24
|
||||
|
||||
func activate_validator(state: var BeaconState,
|
||||
|
@ -83,10 +119,9 @@ func initiate_validator_exit(state: var BeaconState,
|
|||
validator.latest_status_change_slot = state.slot
|
||||
|
||||
func exit_validator(state: var BeaconState,
|
||||
index: Uint24,
|
||||
new_status: ValidatorStatusCodes) =
|
||||
index: Uint24,
|
||||
new_status: ValidatorStatusCodes) =
|
||||
## Exit the validator with the given ``index``.
|
||||
## Note that this function mutates ``state``.
|
||||
|
||||
let
|
||||
validator = addr state.validator_registry[index]
|
||||
|
@ -101,16 +136,16 @@ func exit_validator(state: var BeaconState,
|
|||
if new_status == EXITED_WITH_PENALTY:
|
||||
state.latest_penalized_exit_balances[
|
||||
(state.slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int] +=
|
||||
get_effective_balance(validator[])
|
||||
get_effective_balance(state, index)
|
||||
|
||||
let
|
||||
whistleblower = addr state.validator_registry[
|
||||
get_beacon_proposer_index(state, state.slot)]
|
||||
whistleblower_index =
|
||||
get_beacon_proposer_index(state, state.slot)
|
||||
whistleblower_reward =
|
||||
validator.balance div WHISTLEBLOWER_REWARD_QUOTIENT
|
||||
get_effective_balance(state, index) div WHISTLEBLOWER_REWARD_QUOTIENT
|
||||
|
||||
whistleblower.balance += whistleblower_reward
|
||||
validator.balance -= whistleblower_reward
|
||||
state.validator_balances[whistleblower_index] += whistleblower_reward
|
||||
state.validator_balances[index] -= whistleblower_reward
|
||||
|
||||
if prev_status == EXITED_WITHOUT_PENALTY:
|
||||
return
|
||||
|
@ -136,14 +171,14 @@ func exit_validator(state: var BeaconState,
|
|||
func update_validator_status*(state: var BeaconState,
|
||||
index: Uint24,
|
||||
new_status: ValidatorStatusCodes) =
|
||||
## Update the validator status with the given ``index`` to ``new_status``.
|
||||
## Update the validator status with the given ``index`` to ``new_status``.
|
||||
## Handle other general accounting related to this status update.
|
||||
if new_status == ACTIVE:
|
||||
activate_validator(state, index)
|
||||
activate_validator(state, index)
|
||||
if new_status == ACTIVE_PENDING_EXIT:
|
||||
initiate_validator_exit(state, index)
|
||||
initiate_validator_exit(state, index)
|
||||
if new_status in [EXITED_WITH_PENALTY, EXITED_WITHOUT_PENALTY]:
|
||||
exit_validator(state, index, new_status)
|
||||
exit_validator(state, index, new_status)
|
||||
|
||||
func get_initial_beacon_state*(
|
||||
initial_validator_deposits: openArray[Deposit],
|
||||
|
@ -184,9 +219,6 @@ func get_initial_beacon_state*(
|
|||
justified_slot: INITIAL_SLOT_NUMBER,
|
||||
finalized_slot: INITIAL_SLOT_NUMBER,
|
||||
|
||||
# Recent state
|
||||
latest_state_recalculation_slot: INITIAL_SLOT_NUMBER,
|
||||
|
||||
# PoW receipt root
|
||||
processed_pow_receipt_root: processed_pow_receipt_root,
|
||||
)
|
||||
|
@ -195,13 +227,13 @@ func get_initial_beacon_state*(
|
|||
for deposit in initial_validator_deposits:
|
||||
let validator_index = process_deposit(
|
||||
state,
|
||||
deposit.deposit_data.deposit_parameters.pubkey,
|
||||
deposit.deposit_data.deposit_input.pubkey,
|
||||
deposit.deposit_data.value,
|
||||
deposit.deposit_data.deposit_parameters.proof_of_possession,
|
||||
deposit.deposit_data.deposit_parameters.withdrawal_credentials,
|
||||
deposit.deposit_data.deposit_parameters.randao_commitment
|
||||
deposit.deposit_data.deposit_input.proof_of_possession,
|
||||
deposit.deposit_data.deposit_input.withdrawal_credentials,
|
||||
deposit.deposit_data.deposit_input.randao_commitment
|
||||
)
|
||||
if state.validator_registry[validator_index].balance >= MAX_DEPOSIT:
|
||||
if state.validator_balances[validator_index] >= MAX_DEPOSIT:
|
||||
update_validator_status(state, validator_index, ACTIVE)
|
||||
|
||||
# set initial committee shuffling
|
||||
|
@ -258,27 +290,70 @@ func process_ejections*(state: var BeaconState) =
|
|||
## Iterate through the validator registry
|
||||
## and eject active validators with balance below ``EJECTION_BALANCE``.
|
||||
|
||||
for i, v in state.validator_registry.mpairs():
|
||||
if is_active_validator(v) and v.balance < EJECTION_BALANCE:
|
||||
exit_validator(state, i.Uint24, EXITED_WITHOUT_PENALTY)
|
||||
for index in get_active_validator_indices(state.validator_registry):
|
||||
if state.validator_balances[index] < EJECTION_BALANCE:
|
||||
exit_validator(state, index, EXITED_WITHOUT_PENALTY)
|
||||
|
||||
func update_validator_registry*(state: var BeaconState) =
|
||||
# Update validator registry.
|
||||
# Note that this function mutates ``state``.
|
||||
let
|
||||
active_validator_indices =
|
||||
get_active_validator_indices(state.validator_registry)
|
||||
# The total effective balance of active validators
|
||||
total_balance = sum_effective_balances(state, active_validator_indices)
|
||||
|
||||
(state.validator_registry,
|
||||
state.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
|
||||
)
|
||||
# The maximum balance churn in Gwei (for deposits and exits separately)
|
||||
max_balance_churn = max(
|
||||
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
|
||||
for index, validator in state.validator_registry:
|
||||
if validator.status == PENDING_ACTIVATION and
|
||||
state.validator_balances[index] >= MAX_DEPOSIT * GWEI_PER_ETH:
|
||||
# Check the balance churn would be within the allowance
|
||||
balance_churn += get_effective_balance(state, index.Uint24)
|
||||
if balance_churn > max_balance_churn:
|
||||
break
|
||||
|
||||
# Activate validator
|
||||
update_validator_status(state, index.Uint24, ACTIVE)
|
||||
|
||||
# Exit validators within the allowable balance churn
|
||||
balance_churn = 0
|
||||
for index, validator in state.validator_registry:
|
||||
if validator.status == ACTIVE_PENDING_EXIT:
|
||||
# Check the balance churn would be within the allowance
|
||||
balance_churn += get_effective_balance(state, index.Uint24)
|
||||
if balance_churn > max_balance_churn:
|
||||
break
|
||||
|
||||
# Exit validator
|
||||
update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY)
|
||||
|
||||
# Calculate the total ETH that has been penalized in the last ~2-3 withdrawal periods
|
||||
let
|
||||
period_index = (state.slot div COLLECTIVE_PENALTY_CALCULATION_PERIOD).int
|
||||
total_penalties = (
|
||||
(state.latest_penalized_exit_balances[period_index]) +
|
||||
(if period_index >= 1:
|
||||
state.latest_penalized_exit_balances[period_index - 1] else: 0) +
|
||||
(if period_index >= 2:
|
||||
state.latest_penalized_exit_balances[period_index - 2] else: 0)
|
||||
)
|
||||
|
||||
# Calculate penalties for slashed validators
|
||||
for index, validator in state.validator_registry:
|
||||
if validator.status == EXITED_WITH_PENALTY:
|
||||
state.validator_balances[index] -=
|
||||
get_effective_balance(state, index.Uint24) *
|
||||
min(total_penalties * 3, total_balance) div total_balance
|
||||
|
||||
proc checkAttestation*(state: BeaconState, attestation: Attestation): bool =
|
||||
## Check that an attestation follows the rules of being included in the state
|
||||
## at the current slot.
|
||||
## at the current slot. When acting as a proposer, the same rules need to
|
||||
## be followed!
|
||||
##
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestations-1
|
||||
|
||||
|
|
|
@ -9,6 +9,41 @@
|
|||
# cryptography in the spec is in flux, with sizes and test vectors still being
|
||||
# hashed out. This layer helps isolate those chagnes.
|
||||
|
||||
# Useful conversation about BLS signatures (TODO: condense this)
|
||||
#
|
||||
# I can probably google this somehow, but bls signatures, anyone knows off the
|
||||
# top of their head if they have to be combined one by one, or can two group
|
||||
# signatures be combined? what happens to overlap then?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 12:00
|
||||
# Yeah, you can do any linear combination of signatures. but you have to
|
||||
# remember the linear combination of pubkeys that constructed
|
||||
# if you have two instances of a signature from pubkey p, then you need 2*p in
|
||||
# the group pubkey
|
||||
# because the attestation bitfield is only 1 bit per pubkey right now,
|
||||
# attestations do not support this
|
||||
# it could be extended to support N overlaps up to N times per pubkey if we
|
||||
# had N bits per validator instead of 1
|
||||
# We are shying away from this for the time being. If there end up being
|
||||
# substantial difficulties in network layer aggregation, then adding bits
|
||||
# to aid in supporting overlaps is one potential solution
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 12:02
|
||||
# ah nice, you anticipated my followup question there :) so it's not a
|
||||
# straight-off set union operation
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 12:02
|
||||
# depending on the particular network level troubles we run into
|
||||
# right
|
||||
# aggregatng sigs and pubkeys are both just ec adds
|
||||
# https://github.com/ethereum/py-evm/blob/d82b10ae361cde6abbac62f171fcea7809c4e3cf/eth/_utils/bls.py#L191-L202
|
||||
# subtractions work too (i suppose this is obvious). You can linearly combine
|
||||
# sigs or pubs in any way
|
||||
|
||||
|
||||
import
|
||||
hashes,
|
||||
milagro_crypto, json_serialization
|
||||
|
|
|
@ -50,8 +50,16 @@ const
|
|||
## 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 ##\
|
||||
TARGET_COMMITTEE_SIZE* = 2^7 ##\
|
||||
## Number of validators in the committee attesting to one shard
|
||||
## Per spec:
|
||||
## For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds
|
||||
## [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf);
|
||||
## with sufficient active validators (at least
|
||||
## `EPOCH_LENGTH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures
|
||||
## committee sizes at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness
|
||||
## with a Verifiable Delay Function (VDF) will improve committee robustness
|
||||
## and lower the safe minimum committee size.)
|
||||
|
||||
EJECTION_BALANCE* = 2'u64^4 ##\
|
||||
## Once the balance of a validator drops below this, it will be ejected from
|
||||
|
@ -71,10 +79,6 @@ const
|
|||
LATEST_BLOCK_ROOTS_LENGTH* = 2'u64^13
|
||||
LATEST_RANDAO_MIXES_LENGTH* = 2'u64^13
|
||||
|
||||
MIN_BALANCE* = 2'u64^4 ##\
|
||||
## Minimum balance in ETH before a validator is removed from the validator
|
||||
## pool
|
||||
|
||||
DEPOSIT_CONTRACT_TREE_DEPTH* = 2^5
|
||||
|
||||
MIN_DEPOSIT* = 2'u64^0 ##\
|
||||
|
@ -93,19 +97,23 @@ const
|
|||
SLOT_DURATION* = 6 ## \
|
||||
## TODO consistent time unit across projects, similar to C++ chrono?
|
||||
|
||||
MIN_ATTESTATION_INCLUSION_DELAY* = 4'u64 ##\
|
||||
MIN_ATTESTATION_INCLUSION_DELAY* = 2'u64^2 ##\
|
||||
## (24 seconds)
|
||||
## Number of slots that attestations stay in the attestation
|
||||
## pool before being added to a block
|
||||
## pool before being added to a block.
|
||||
## The attestation delay exists so that there is time for attestations to
|
||||
## propagate before the block is created.
|
||||
## When creating an attestation, the validator will look at the best
|
||||
## information known to at that time, and may not revise it during the same
|
||||
## slot (see `is_double_vote`) - the delay gives the validator a chance to
|
||||
## wait towards the end of the slot and still have time to publish the
|
||||
## attestation.
|
||||
|
||||
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)
|
||||
|
||||
|
@ -125,7 +133,7 @@ const
|
|||
## ETH in every epoch.
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT* = 2'u64^9
|
||||
INCLUDER_REWARD_QUOTIENT* = 2'u64^3
|
||||
INACTIVITY_PENALTY_QUOTIENT* = 2'u64^34
|
||||
INACTIVITY_PENALTY_QUOTIENT* = 2'u64^24
|
||||
|
||||
MAX_PROPOSER_SLASHINGS* = 2^4
|
||||
MAX_CASPER_SLASHINGS* = 2^4
|
||||
|
@ -190,6 +198,10 @@ type
|
|||
justified_block_root*: Eth2Digest ##\
|
||||
## Hash of last justified beacon block
|
||||
|
||||
AttestationDataAndCustodyBit* = object
|
||||
data*: AttestationData
|
||||
poc_bit: bool
|
||||
|
||||
Deposit* = object
|
||||
merkle_branch*: seq[Eth2Digest] ##\
|
||||
## Receipt Merkle branch
|
||||
|
@ -200,17 +212,17 @@ type
|
|||
deposit_data*: DepositData
|
||||
|
||||
DepositData* = object
|
||||
deposit_parameters*: DepositParameters
|
||||
deposit_input*: DepositInput
|
||||
value*: uint64 ## Value in Gwei
|
||||
timestamp*: uint64 # Timestamp from deposit contract
|
||||
|
||||
DepositParameters* = object
|
||||
DepositInput* = object
|
||||
pubkey*: ValidatorPubKey
|
||||
proof_of_possession*: ValidatorSig ##\
|
||||
## BLS proof of possession (a BLS signature)
|
||||
|
||||
withdrawal_credentials*: Eth2Digest
|
||||
randao_commitment*: Eth2Digest # Initial RANDAO commitment
|
||||
poc_commitment*: Eth2Digest
|
||||
proof_of_possession*: ValidatorSig ##\
|
||||
## BLS proof of possession (a BLS signature)
|
||||
|
||||
Exit* = object
|
||||
# Minimum slot for processing exit
|
||||
|
@ -248,9 +260,17 @@ type
|
|||
proposer_slashings*: seq[ProposerSlashing]
|
||||
casper_slashings*: seq[CasperSlashing]
|
||||
attestations*: seq[Attestation]
|
||||
poc_seed_changes*: seq[ProofOfCustodySeedChange]
|
||||
poc_challenges*: seq[ProofOfCustodyChallenge]
|
||||
poc_responses*: seq[ProofOfCustodyResponse]
|
||||
deposits*: seq[Deposit]
|
||||
exits*: seq[Exit]
|
||||
|
||||
# Phase1:
|
||||
ProofOfCustodySeedChange* = object
|
||||
ProofOfCustodyChallenge* = object
|
||||
ProofOfCustodyResponse* = object
|
||||
|
||||
ProposalSignedData* = object
|
||||
slot*: uint64
|
||||
shard*: uint64 ##\
|
||||
|
@ -265,6 +285,9 @@ type
|
|||
|
||||
# Validator registry
|
||||
validator_registry*: seq[ValidatorRecord]
|
||||
validator_balances*: seq[uint64] ##\
|
||||
## Validator balances in Gwei!
|
||||
|
||||
validator_registry_latest_change_slot*: uint64
|
||||
validator_registry_exit_count*: uint64
|
||||
validator_registry_delta_chain_tip*: Eth2Digest ##\
|
||||
|
@ -272,6 +295,8 @@ type
|
|||
|
||||
# Randomness and committees
|
||||
latest_randao_mixes*: array[LATEST_BLOCK_ROOTS_LENGTH.int, Eth2Digest]
|
||||
latest_vdf_outputs*: array[
|
||||
(LATEST_RANDAO_MIXES_LENGTH div EPOCH_LENGTH).int, Eth2Digest]
|
||||
|
||||
shard_committees_at_slots*: array[2 * EPOCH_LENGTH, seq[ShardCommittee]] ## \
|
||||
## Committee members and their assigned shard, per slot, covers 2 cycles
|
||||
|
@ -280,6 +305,8 @@ type
|
|||
persistent_committees*: seq[seq[Uint24]]
|
||||
persistent_committee_reassignments*: seq[ShardReassignmentRecord]
|
||||
|
||||
poc_challenges*: seq[ProofOfCustodyChallenge]
|
||||
|
||||
# Finality
|
||||
previous_justified_slot*: uint64
|
||||
justified_slot*: uint64
|
||||
|
@ -287,7 +314,6 @@ type
|
|||
finalized_slot*: uint64
|
||||
|
||||
latest_crosslinks*: array[SHARD_COUNT, CrosslinkRecord]
|
||||
latest_state_recalculation_slot*: uint64
|
||||
latest_block_roots*: array[LATEST_BLOCK_ROOTS_LENGTH.int, Eth2Digest] ##\
|
||||
## Needed to process attestations, older to newer
|
||||
|
||||
|
@ -315,7 +341,6 @@ type
|
|||
## Number of proposals the proposer missed, and thus the number of times to
|
||||
## apply hash function to randao reveal
|
||||
|
||||
balance*: uint64 # Balance in Gwei
|
||||
status*: ValidatorStatusCodes
|
||||
latest_status_change_slot*: uint64 ##\
|
||||
## Slot when validator last changed status (or 0)
|
||||
|
@ -323,6 +348,11 @@ type
|
|||
exit_count*: uint64 ##\
|
||||
## Exit counter when validator exited (or 0)
|
||||
|
||||
poc_commitment*: Eth2Digest
|
||||
|
||||
last_poc_change_slot*: uint64
|
||||
second_last_poc_change_slot*: uint64
|
||||
|
||||
CrosslinkRecord* = object
|
||||
slot*: uint64
|
||||
shard_block_root*: Eth2Digest ##\
|
||||
|
@ -349,7 +379,7 @@ type
|
|||
|
||||
CandidatePoWReceiptRootRecord* = object
|
||||
candidate_pow_receipt_root*: Eth2Digest # Candidate PoW receipt root
|
||||
votes*: uint64 # Vote count
|
||||
vote_count*: uint64 # Vote count
|
||||
|
||||
PendingAttestationRecord* = object
|
||||
data*: AttestationData # Signed data
|
||||
|
@ -375,12 +405,6 @@ type
|
|||
EXITED_WITHOUT_PENALTY = 3
|
||||
EXITED_WITH_PENALTY = 4
|
||||
|
||||
SpecialRecordType* {.pure.} = enum
|
||||
Logout = 0
|
||||
CasperSlashing = 1
|
||||
RandaoChange = 2
|
||||
DepositProof = 3
|
||||
|
||||
ValidatorSetDeltaFlags* {.pure.} = enum
|
||||
Activation = 0
|
||||
Exit = 1
|
||||
|
|
|
@ -101,6 +101,9 @@ func repeat_hash*(v: Eth2Digest, n: SomeInteger): Eth2Digest =
|
|||
dec n
|
||||
|
||||
func get_shard_committees_index*(state: BeaconState, slot: uint64): uint64 =
|
||||
## Warning: as it stands, this helper only works during state updates _after_
|
||||
## state.slot has been incremented but before shard_committees_at_slots has
|
||||
## been updated!
|
||||
# TODO spec unsigned-unsafe here
|
||||
doAssert slot + (state.slot mod EPOCH_LENGTH) + EPOCH_LENGTH > state.slot
|
||||
slot + (state.slot mod EPOCH_LENGTH) + EPOCH_LENGTH - state.slot
|
||||
|
@ -159,6 +162,8 @@ proc is_double_vote*(attestation_data_1: AttestationData,
|
|||
## Assumes ``attestation_data_1`` is distinct from ``attestation_data_2``.
|
||||
## Returns True if the provided ``AttestationData`` are slashable
|
||||
## due to a 'double vote'.
|
||||
## A double vote is when a validator votes for two attestations within the
|
||||
## same slot - doing so means risking getting slashed.
|
||||
attestation_data_1.slot == attestation_data_2.slot
|
||||
|
||||
proc is_surround_vote*(attestation_data_1: AttestationData,
|
||||
|
|
|
@ -15,9 +15,12 @@ import
|
|||
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],
|
||||
validator_balances: seq[uint64],
|
||||
current_slot: uint64): Option[int] =
|
||||
for i, v in validators:
|
||||
if v.balance == 0 and
|
||||
if validator_balances[i] == 0 and
|
||||
v.latest_status_change_slot +
|
||||
ZERO_BALANCE_VALIDATOR_TTL.uint64 <= current_slot:
|
||||
return some(i)
|
||||
|
@ -77,102 +80,3 @@ func get_new_validator_registry_delta_chain_tip*(
|
|||
pubkey: pubkey,
|
||||
flag: flag
|
||||
))
|
||||
|
||||
func get_effective_balance*(validator: ValidatorRecord): uint64 =
|
||||
min(validator.balance, MAX_DEPOSIT * GWEI_PER_ETH)
|
||||
|
||||
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],
|
||||
validator_registry_delta_chain_tip: Eth2Digest] =
|
||||
## Return changed validator registry and `latest_penalized_exit_balances`,
|
||||
## `validator_registry_delta_chain_tip`.
|
||||
|
||||
# TODO inefficient
|
||||
var
|
||||
validator_registry = validator_registry
|
||||
latest_penalized_exit_balances = latest_penalized_exit_balances
|
||||
|
||||
# The active 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(validator_registry[it])))
|
||||
|
||||
# The maximum balance churn in Gwei (for deposits and exits separately)
|
||||
let max_balance_churn = max(
|
||||
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(validator_registry):
|
||||
if validator_registry[i].status == PENDING_ACTIVATION and
|
||||
validator_registry[i].balance >= MAX_DEPOSIT * GWEI_PER_ETH:
|
||||
# Check the balance churn would be within the allowance
|
||||
balance_churn += get_effective_balance(validator_registry[i])
|
||||
if balance_churn > max_balance_churn:
|
||||
break
|
||||
|
||||
# Activate validator
|
||||
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,
|
||||
validator_registry[i].pubkey,
|
||||
ACTIVATION,
|
||||
)
|
||||
|
||||
# Exit validators within the allowable balance churn
|
||||
balance_churn = 0
|
||||
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 += get_effective_balance(validator_registry[i])
|
||||
if balance_churn > max_balance_churn:
|
||||
break
|
||||
|
||||
# Exit validator
|
||||
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,
|
||||
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 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)
|
||||
)
|
||||
|
||||
# Calculate penalties for slashed validators
|
||||
func to_penalize(v: ValidatorRecord): bool =
|
||||
v.status == EXITED_WITH_PENALTY
|
||||
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)
|
||||
|
||||
(validator_registry, latest_penalized_exit_balances,
|
||||
validator_registry_delta_chain_tip)
|
||||
|
|
|
@ -35,6 +35,9 @@ func toBytesSSZ(x: Uint24): array[3, byte] =
|
|||
result[1] = byte((v shr 8) and 0xff)
|
||||
result[0] = byte((v shr 16) and 0xff)
|
||||
|
||||
func toBytesSSZ(x: bool): array[1, byte] =
|
||||
[if x: 1'u8 else: 0'u8]
|
||||
|
||||
func toBytesSSZ(x: EthAddress): array[sizeof(x), byte] = x
|
||||
func toBytesSSZ(x: Eth2Digest): array[32, byte] = x.data
|
||||
|
||||
|
@ -51,7 +54,8 @@ type
|
|||
# validator keys ends up going..
|
||||
# TODO can't put ranges like Uint24 in here:
|
||||
# https://github.com/nim-lang/Nim/issues/10027
|
||||
SomeInteger | EthAddress | Eth2Digest | ValidatorPubKey | ValidatorSig
|
||||
SomeInteger | EthAddress | Eth2Digest | ValidatorPubKey | ValidatorSig |
|
||||
bool
|
||||
|
||||
func sszLen(v: TrivialTypes): int = toBytesSSZ(v).len
|
||||
func sszLen(v: Uint24): int = toBytesSSZ(v).len
|
||||
|
@ -85,6 +89,11 @@ func fromBytesSSZUnsafe(T: typedesc[SomeInteger], data: pointer): T =
|
|||
elif result.sizeof == 1: copyMem(result.addr, alignedBuf, sizeof(result))
|
||||
else: {.fatal: "Unsupported type deserialization: " & $(type(result)).name.}
|
||||
|
||||
func fromBytesSSZUnsafe(T: typedesc[bool], data: pointer): T =
|
||||
# TODO: spec doesn't say what to do if the value is >1 - we'll use the C
|
||||
# definition for now, but maybe this should be a parse error instead?
|
||||
fromBytesSSZUnsafe(uint8, data) != 0
|
||||
|
||||
func fromBytesSSZUnsafe(T: typedesc[Uint24], data: pointer): T =
|
||||
## Integers are all encoded as bigendian and not padded
|
||||
var tmp: uint32
|
||||
|
@ -251,7 +260,7 @@ func merkleHash[T](lst: openArray[T]): array[32, byte]
|
|||
|
||||
# ################### Hashing interface ###################################
|
||||
|
||||
func hash_tree_root*(x: SomeInteger): array[sizeof(x), byte] =
|
||||
func hash_tree_root*(x: SomeInteger | bool): array[sizeof(x), byte] =
|
||||
## Convert directly to bytes the size of the int. (e.g. ``uint16 = 2 bytes``)
|
||||
## All integers are serialized as **big endian**.
|
||||
toBytesSSZ(x)
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
# * We mix procedural and functional styles for no good reason, except that the
|
||||
# spec does so also.
|
||||
# * There are no tests, and likely lots of bugs.
|
||||
# * For validators, sometimes indices are used and sometimes instances - this
|
||||
# causes unnecessary friction in some sections
|
||||
# * For indices, we get a mix of uint64, Uint24 and int - this is currently
|
||||
# swept under the rug with casts
|
||||
# * The spec uses uint64 for data types, but functions in the spec often assume
|
||||
|
@ -40,7 +38,12 @@ import
|
|||
|
||||
type
|
||||
UpdateFlag* = enum
|
||||
skipValidation
|
||||
skipValidation ##\
|
||||
## The `skipValidation` flag is used to skip over certain checks that are
|
||||
## normally done when an untrusted block arrives from the network. The
|
||||
## primary use case for this flag is when a proposer must propose a new
|
||||
## block - in order to do so, it needs to update the state as if the block
|
||||
## was valid, before it can sign it.
|
||||
|
||||
UpdateFlags* = set[UpdateFlag]
|
||||
|
||||
|
@ -49,12 +52,12 @@ func flatten[T](v: openArray[seq[T]]): seq[T] =
|
|||
for x in v: result.add x
|
||||
|
||||
func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
||||
## When creating the block, the proposer will sign a version of the block that
|
||||
## When creating a block, the proposer will sign a version of the block that
|
||||
## doesn't contain the data (chicken and egg), then add the signature to that
|
||||
## block.
|
||||
|
||||
## block. Here, we check that the signature is correct by repeating the same
|
||||
## process.
|
||||
##
|
||||
## 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.signature = ValidatorSig()
|
||||
|
||||
|
@ -74,16 +77,17 @@ func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
|
|||
|
||||
func processRandao(
|
||||
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
|
||||
## When a validator signs up, they will commit an hash to the block,
|
||||
## the randao_commitment - this hash is the result of a secret value
|
||||
## hashed n times.
|
||||
## When a validator signs up, they will include a hash number together with
|
||||
## the deposit - the randao_commitment. The commitment is formed by hashing
|
||||
## a secret value N times.
|
||||
## The first time the proposer proposes a block, they will hash their secret
|
||||
## value n-1 times, and provide that as "reveal" - now everyone else can
|
||||
## verify the reveal by hashing once.
|
||||
## value N-1 times, and provide the reuslt as "reveal" - now everyone else can
|
||||
## verify that the reveal matches the commitment by hashing it once.
|
||||
## The next time the proposer proposes, they will reveal the secret value
|
||||
## hashed n-2 times and so on, and everyone will verify that it matches n-1.
|
||||
## hashed N-2 times and so on, and everyone will verify that it matches N-1.
|
||||
## The previous reveal has now become the commitment!
|
||||
##
|
||||
## Effectively, the block proposer can only reveal n - 1 times, so better pick
|
||||
## Effectively, the block proposer can only reveal N-1 times, so better pick
|
||||
## a large N!
|
||||
##
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#randao
|
||||
|
@ -112,12 +116,12 @@ func processPoWReceiptRoot(state: var BeaconState, blck: BeaconBlock) =
|
|||
|
||||
for x in state.candidate_pow_receipt_roots.mitems():
|
||||
if blck.candidate_pow_receipt_root == x.candidate_pow_receipt_root:
|
||||
x.votes += 1
|
||||
x.vote_count += 1
|
||||
return
|
||||
|
||||
state.candidate_pow_receipt_roots.add CandidatePoWReceiptRootRecord(
|
||||
candidate_pow_receipt_root: blck.candidate_pow_receipt_root,
|
||||
votes: 1
|
||||
vote_count: 1
|
||||
)
|
||||
|
||||
proc processProposerSlashings(state: var BeaconState, blck: BeaconBlock): bool =
|
||||
|
@ -306,12 +310,13 @@ proc process_ejections(state: var BeaconState) =
|
|||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#ejections
|
||||
|
||||
for index, validator in state.validator_registry:
|
||||
if is_active_validator(validator) and validator.balance < EJECTION_BALANCE:
|
||||
update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY)
|
||||
if is_active_validator(validator) and
|
||||
state.validator_balances[index] < EJECTION_BALANCE:
|
||||
update_validator_status(state, index.Uint24, EXITED_WITHOUT_PENALTY)
|
||||
|
||||
func processSlot(state: var BeaconState, previous_block_root: Eth2Digest) =
|
||||
## 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
|
||||
## slot, a proposer creates 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.
|
||||
##
|
||||
|
@ -383,7 +388,7 @@ proc processBlock(
|
|||
|
||||
return true
|
||||
|
||||
func get_attesters(
|
||||
func get_attester_indices(
|
||||
state: BeaconState,
|
||||
attestations: openArray[PendingAttestationRecord]): seq[Uint24] =
|
||||
# Union of attesters that participated in some attestations
|
||||
|
@ -391,9 +396,6 @@ func get_attesters(
|
|||
deduplicate(flatten(mapIt(attestations,
|
||||
get_attestation_participants(state, it.data, it.participation_bitfield))))
|
||||
|
||||
func adjust_for_inclusion_distance[T](magnitude: T, dist: T): T =
|
||||
magnitude div 2 + (magnitude div 2) * MIN_ATTESTATION_INCLUSION_DELAY div dist
|
||||
|
||||
func boundary_attestations(
|
||||
state: BeaconState, boundary_hash: Eth2Digest,
|
||||
attestations: openArray[PendingAttestationRecord]
|
||||
|
@ -403,13 +405,6 @@ func boundary_attestations(
|
|||
it.data.epoch_boundary_root == boundary_hash and
|
||||
it.data.justified_slot == state.justified_slot)
|
||||
|
||||
func sum_effective_balances(
|
||||
state: BeaconState, validator_indices: openArray[Uint24]): uint64 =
|
||||
# TODO spec - add as helper?
|
||||
sum(mapIt(
|
||||
validator_indices, get_effective_balance(state.validator_registry[it]))
|
||||
)
|
||||
|
||||
func lowerThan(candidate, current: Eth2Digest): bool =
|
||||
# return true iff candidate is "lower" than current, per spec rule:
|
||||
# "ties broken by favoring lower `shard_block_root` values"
|
||||
|
@ -418,6 +413,18 @@ func lowerThan(candidate, current: Eth2Digest): bool =
|
|||
if v > candidate.data[i]: return true
|
||||
return false
|
||||
|
||||
func inclusion_slot(state: BeaconState, v: Uint24): uint64 =
|
||||
for a in state.latest_attestations:
|
||||
if v in get_attestation_participants(state, a.data, a.participation_bitfield):
|
||||
return a.slot_included
|
||||
doAssert false # shouldn't happen..
|
||||
|
||||
func inclusion_distance(state: BeaconState, v: Uint24): uint64 =
|
||||
for a in state.latest_attestations:
|
||||
if v in get_attestation_participants(state, a.data, a.participation_bitfield):
|
||||
return a.slot_included - a.data.slot
|
||||
doAssert false # shouldn't happen..
|
||||
|
||||
func processEpoch(state: var BeaconState) =
|
||||
## https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#per-epoch-processing
|
||||
|
||||
|
@ -435,14 +442,14 @@ func processEpoch(state: var BeaconState) =
|
|||
base_reward_quotient =
|
||||
BASE_REWARD_QUOTIENT * integer_squareroot(total_balance_in_eth)
|
||||
|
||||
func base_reward(v: ValidatorRecord): uint64 =
|
||||
get_effective_balance(v) div base_reward_quotient.uint64 div 4
|
||||
func base_reward(state: BeaconState, index: Uint24): uint64 =
|
||||
get_effective_balance(state, index) div base_reward_quotient.uint64 div 4
|
||||
|
||||
func inactivity_penalty(
|
||||
v: ValidatorRecord, slots_since_finality: uint64): uint64 =
|
||||
base_reward(v) +
|
||||
get_effective_balance(v) *
|
||||
slots_since_finality div INACTIVITY_PENALTY_QUOTIENT
|
||||
state: BeaconState, index: Uint24, epochs_since_finality: uint64): uint64 =
|
||||
base_reward(state, index) +
|
||||
get_effective_balance(state, index) *
|
||||
epochs_since_finality div INACTIVITY_PENALTY_QUOTIENT div 2
|
||||
|
||||
# TODO doing this with iterators failed:
|
||||
# https://github.com/nim-lang/Nim/issues/9827
|
||||
|
@ -457,11 +464,11 @@ func processEpoch(state: var BeaconState) =
|
|||
state, get_block_root(state, state.slot-EPOCH_LENGTH),
|
||||
this_epoch_attestations)
|
||||
|
||||
this_epoch_boundary_attesters =
|
||||
get_attesters(state, this_epoch_attestations)
|
||||
this_epoch_boundary_attester_indices =
|
||||
get_attester_indices(state, this_epoch_attestations)
|
||||
|
||||
this_epoch_boundary_attesting_balance =
|
||||
sum_effective_balances(state, this_epoch_boundary_attesters)
|
||||
sum_effective_balances(state, this_epoch_boundary_attester_indices)
|
||||
|
||||
let
|
||||
previous_epoch_attestations = filterIt(
|
||||
|
@ -470,8 +477,8 @@ func processEpoch(state: var BeaconState) =
|
|||
it.data.slot + EPOCH_LENGTH < state.slot)
|
||||
|
||||
let
|
||||
previous_epoch_attesters =
|
||||
get_attesters(state, previous_epoch_attestations)
|
||||
previous_epoch_attester_indices =
|
||||
get_attester_indices(state, previous_epoch_attestations)
|
||||
|
||||
let # Previous epoch justified
|
||||
previous_epoch_justified_attestations = filterIt(
|
||||
|
@ -479,11 +486,11 @@ func processEpoch(state: var BeaconState) =
|
|||
it.data.justified_slot == state.previous_justified_slot
|
||||
)
|
||||
|
||||
previous_epoch_justified_attesters =
|
||||
get_attesters(state, previous_epoch_justified_attestations)
|
||||
previous_epoch_justified_attester_indices =
|
||||
get_attester_indices(state, previous_epoch_justified_attestations)
|
||||
|
||||
previous_epoch_justified_attesting_balance =
|
||||
sum_effective_balances(state, previous_epoch_justified_attesters)
|
||||
sum_effective_balances(state, previous_epoch_justified_attester_indices)
|
||||
|
||||
let # Previous epoch boundary
|
||||
# TODO check this with spec...
|
||||
|
@ -494,11 +501,11 @@ func processEpoch(state: var BeaconState) =
|
|||
state, get_block_root(state, negative_uint_hack),
|
||||
previous_epoch_attestations)
|
||||
|
||||
previous_epoch_boundary_attesters =
|
||||
get_attesters(state, previous_epoch_boundary_attestations)
|
||||
previous_epoch_boundary_attester_indices =
|
||||
get_attester_indices(state, previous_epoch_boundary_attestations)
|
||||
|
||||
previous_epoch_boundary_attesting_balance =
|
||||
sum_effective_balances(state, previous_epoch_boundary_attesters)
|
||||
sum_effective_balances(state, previous_epoch_boundary_attester_indices)
|
||||
|
||||
let # Previous epoch head
|
||||
previous_epoch_head_attestations =
|
||||
|
@ -506,32 +513,33 @@ func processEpoch(state: var BeaconState) =
|
|||
previous_epoch_attestations,
|
||||
it.data.beacon_block_root == get_block_root(state, it.data.slot))
|
||||
|
||||
previous_epoch_head_attesters =
|
||||
get_attesters(state, previous_epoch_head_attestations)
|
||||
previous_epoch_head_attester_indices =
|
||||
get_attester_indices(state, previous_epoch_head_attestations)
|
||||
|
||||
previous_epoch_head_attesting_balance =
|
||||
sum_effective_balances(state, previous_epoch_head_attesters)
|
||||
sum_effective_balances(state, previous_epoch_head_attester_indices)
|
||||
|
||||
# TODO this is really hairy - we cannot capture `state` directly, but we
|
||||
# can capture a pointer to it - this is safe because we don't leak
|
||||
# these closures outside this scope, but still..
|
||||
let statePtr = state.addr
|
||||
func attesting_validators(
|
||||
func attesting_validator_indices(
|
||||
shard_committee: ShardCommittee, shard_block_root: Eth2Digest): seq[Uint24] =
|
||||
let shard_block_attestations =
|
||||
filterIt(concat(this_epoch_attestations, previous_epoch_attestations),
|
||||
it.data.shard == shard_committee.shard and
|
||||
it.data.shard_block_root == shard_block_root)
|
||||
get_attesters(statePtr[], shard_block_attestations)
|
||||
get_attester_indices(statePtr[], shard_block_attestations)
|
||||
|
||||
func winning_hash(obj: ShardCommittee): Eth2Digest =
|
||||
# * Let `winning_hash(obj)` be the winning `shard_block_root` value.
|
||||
# ... such that `sum([get_effective_balance(v) for v in attesting_validators(obj, shard_block_root)])`
|
||||
# is maximized (ties broken by favoring lower `shard_block_root` values).
|
||||
func winning_root(shard_committee: ShardCommittee): Eth2Digest =
|
||||
# * Let `winning_root(shard_committee)` be equal to the value of
|
||||
# `shard_block_root` such that
|
||||
# `sum([get_effective_balance(state, i) for i in attesting_validator_indices(shard_committee, shard_block_root)])`
|
||||
# is maximized (ties broken by favoring lower `shard_block_root` values).
|
||||
let candidates =
|
||||
mapIt(
|
||||
filterIt(concat(this_epoch_attestations, previous_epoch_attestations),
|
||||
it.data.shard == obj.shard),
|
||||
it.data.shard == shard_committee.shard),
|
||||
it.data.shard_block_root)
|
||||
|
||||
# TODO not covered by spec!
|
||||
|
@ -540,39 +548,30 @@ func processEpoch(state: var BeaconState) =
|
|||
|
||||
var max_hash = candidates[0]
|
||||
var max_val =
|
||||
sum_effective_balances(statePtr[], attesting_validators(obj, max_hash))
|
||||
sum_effective_balances(
|
||||
statePtr[], attesting_validator_indices(shard_committee, max_hash))
|
||||
for candidate in candidates[1..^1]:
|
||||
let val = sum_effective_balances(statePtr[], attesting_validators(obj, candidate))
|
||||
let val = sum_effective_balances(
|
||||
statePtr[], attesting_validator_indices(shard_committee, candidate))
|
||||
if val > max_val or (val == max_val and candidate.lowerThan(max_hash)):
|
||||
max_hash = candidate
|
||||
max_val = val
|
||||
max_hash
|
||||
|
||||
func attesting_validators(obj: ShardCommittee): seq[Uint24] =
|
||||
attesting_validators(obj, winning_hash(obj))
|
||||
func attesting_validator_indices(shard_committee: ShardCommittee): seq[Uint24] =
|
||||
attesting_validator_indices(shard_committee, winning_root(shard_committee))
|
||||
|
||||
func total_attesting_balance(obj: ShardCommittee): uint64 =
|
||||
sum_effective_balances(statePtr[], attesting_validators(obj))
|
||||
func total_attesting_balance(shard_committee: ShardCommittee): uint64 =
|
||||
sum_effective_balances(
|
||||
statePtr[], attesting_validator_indices(shard_committee))
|
||||
|
||||
func total_balance_sac(obj: ShardCommittee): uint64 =
|
||||
sum_effective_balances(statePtr[], obj.committee)
|
||||
|
||||
func inclusion_slot(v: Uint24): uint64 =
|
||||
for a in statePtr[].latest_attestations:
|
||||
if v in get_attestation_participants(statePtr[], a.data, a.participation_bitfield):
|
||||
return a.slot_included
|
||||
doAssert false # shouldn't happen..
|
||||
|
||||
func inclusion_distance(v: Uint24): uint64 =
|
||||
for a in statePtr[].latest_attestations:
|
||||
if v in get_attestation_participants(statePtr[], a.data, a.participation_bitfield):
|
||||
return a.slot_included - a.data.slot
|
||||
doAssert false # shouldn't happen..
|
||||
func total_balance_sac(shard_committee: ShardCommittee): uint64 =
|
||||
sum_effective_balances(statePtr[], shard_committee.committee)
|
||||
|
||||
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:
|
||||
if x.vote_count * 2 >= POW_RECEIPT_ROOT_VOTING_PERIOD:
|
||||
state.processed_pow_receipt_root = x.candidate_pow_receipt_root
|
||||
break
|
||||
state.candidate_pow_receipt_roots = @[]
|
||||
|
@ -612,76 +611,80 @@ func processEpoch(state: var BeaconState) =
|
|||
if 3'u64 * total_attesting_balance(shard_committee) >=
|
||||
2'u64 * total_balance_sac(shard_committee):
|
||||
state.latest_crosslinks[shard_committee.shard] = CrosslinkRecord(
|
||||
slot: state.latest_state_recalculation_slot + EPOCH_LENGTH,
|
||||
shard_block_root: winning_hash(shard_committee))
|
||||
slot: state.slot + EPOCH_LENGTH,
|
||||
shard_block_root: winning_root(shard_committee))
|
||||
|
||||
block: # Justification and finalization
|
||||
let
|
||||
slots_since_finality = state.slot - state.finalized_slot
|
||||
epochs_since_finality =
|
||||
(state.slot - state.finalized_slot) div EPOCH_LENGTH
|
||||
|
||||
proc update_balance(attesters: openArray[Uint24], attesting_balance: uint64) =
|
||||
# TODO Spec - add helper?
|
||||
for v in attesters:
|
||||
statePtr.validator_registry[v].balance += adjust_for_inclusion_distance(
|
||||
base_reward(statePtr.validator_registry[v]) *
|
||||
attesting_balance div total_balance, inclusion_distance(v))
|
||||
statePtr.validator_balances[v] +=
|
||||
base_reward(statePtr[], v) *
|
||||
attesting_balance div total_balance
|
||||
|
||||
for v in active_validator_indices:
|
||||
if v notin attesters:
|
||||
statePtr.validator_registry[v].balance -=
|
||||
base_reward(statePtr.validator_registry[v])
|
||||
statePtr.validator_balances[v] -= base_reward(statePtr[], v)
|
||||
|
||||
if slots_since_finality <= 4'u64 * EPOCH_LENGTH:
|
||||
if epochs_since_finality <= 4'u64 * EPOCH_LENGTH:
|
||||
# Expected FFG source
|
||||
update_balance(
|
||||
previous_epoch_justified_attesters,
|
||||
previous_epoch_justified_attester_indices,
|
||||
previous_epoch_justified_attesting_balance)
|
||||
|
||||
# Expected FFG target:
|
||||
update_balance(
|
||||
previous_epoch_boundary_attesters,
|
||||
previous_epoch_boundary_attester_indices,
|
||||
previous_epoch_boundary_attesting_balance)
|
||||
|
||||
# Expected beacon chain head:
|
||||
update_balance(
|
||||
previous_epoch_head_attesters,
|
||||
previous_epoch_head_attester_indices,
|
||||
previous_epoch_head_attesting_balance)
|
||||
|
||||
# Inclusion distance
|
||||
for v in previous_epoch_attester_indices:
|
||||
statePtr.validator_balances[v] +=
|
||||
base_reward(state, v) *
|
||||
MIN_ATTESTATION_INCLUSION_DELAY div inclusion_distance(state, v)
|
||||
|
||||
else:
|
||||
for v in active_validator_indices:
|
||||
let validator = addr state.validator_registry[v]
|
||||
if v notin previous_epoch_justified_attesters:
|
||||
validator[].balance -=
|
||||
inactivity_penalty(validator[], slots_since_finality)
|
||||
if v notin previous_epoch_boundary_attesters:
|
||||
validator[].balance -=
|
||||
inactivity_penalty(validator[], slots_since_finality)
|
||||
if v notin previous_epoch_head_attesters:
|
||||
validator[].balance -=
|
||||
inactivity_penalty(validator[], slots_since_finality)
|
||||
if validator[].status == EXITED_WITH_PENALTY:
|
||||
validator[].balance -=
|
||||
3'u64 * inactivity_penalty(validator[], slots_since_finality)
|
||||
for index in active_validator_indices:
|
||||
if index notin previous_epoch_justified_attester_indices:
|
||||
state.validator_balances[index] -=
|
||||
inactivity_penalty(state, index, epochs_since_finality)
|
||||
if index notin previous_epoch_boundary_attester_indices:
|
||||
state.validator_balances[index] -=
|
||||
inactivity_penalty(state, index, epochs_since_finality)
|
||||
if index notin previous_epoch_head_attester_indices:
|
||||
state.validator_balances[index] -=
|
||||
inactivity_penalty(state, index, epochs_since_finality)
|
||||
if state.validator_registry[index].status == EXITED_WITH_PENALTY:
|
||||
state.validator_balances[index] -=
|
||||
3'u64 * inactivity_penalty(state, index, epochs_since_finality)
|
||||
|
||||
block: # Attestation inclusion
|
||||
for v in previous_epoch_attesters:
|
||||
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
|
||||
for v in previous_epoch_attester_indices:
|
||||
let proposer_index =
|
||||
get_beacon_proposer_index(state, inclusion_slot(state, v))
|
||||
state.validator_balances[proposer_index] +=
|
||||
base_reward(state, v) div INCLUDER_REWARD_QUOTIENT
|
||||
|
||||
block: # Crosslinks
|
||||
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 += adjust_for_inclusion_distance(
|
||||
base_reward(v[]) * total_attesting_balance(obj) div
|
||||
total_balance_sac(obj),
|
||||
inclusion_distance(vindex))
|
||||
for shard_committee in sac:
|
||||
for index in shard_committee.committee:
|
||||
if index in attesting_validator_indices(shard_committee):
|
||||
state.validator_balances[index] +=
|
||||
base_reward(state, index) *
|
||||
total_attesting_balance(shard_committee) div
|
||||
total_balance_sac(shard_committee)
|
||||
else:
|
||||
v.balance -= base_reward(v[])
|
||||
state.validator_balances[index] -= base_reward(state, index)
|
||||
|
||||
block: # Validator registry
|
||||
if state.finalized_slot > state.validator_registry_latest_change_slot and
|
||||
|
@ -690,7 +693,9 @@ func processEpoch(state: var BeaconState) =
|
|||
state.latest_crosslinks[it.shard].slot >
|
||||
state.validator_registry_latest_change_slot)):
|
||||
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]
|
||||
|
@ -709,12 +714,13 @@ func processEpoch(state: var BeaconState) =
|
|||
state.shard_committees_at_slots[i] =
|
||||
state.shard_committees_at_slots[EPOCH_LENGTH + i]
|
||||
|
||||
let slots_since_finality =
|
||||
state.slot - state.validator_registry_latest_change_slot
|
||||
let start_shard = state.shard_committees_at_slots[0][0].shard
|
||||
if slots_since_finality * EPOCH_LENGTH <=
|
||||
MIN_VALIDATOR_REGISTRY_CHANGE_INTERVAL or
|
||||
is_power_of_2(slots_since_finality):
|
||||
let
|
||||
epochs_since_last_registry_change =
|
||||
(state.slot - state.validator_registry_latest_change_slot) div
|
||||
EPOCH_LENGTH
|
||||
start_shard = state.shard_committees_at_slots[0][0].shard
|
||||
|
||||
if is_power_of_2(epochs_since_last_registry_change):
|
||||
for i, v in get_new_shuffling(
|
||||
state.latest_randao_mixes[
|
||||
(state.slot - EPOCH_LENGTH) mod LATEST_RANDAO_MIXES_LENGTH],
|
||||
|
@ -764,9 +770,8 @@ proc verifyStateRoot(state: BeaconState, blck: BeaconBlock): bool =
|
|||
else:
|
||||
true
|
||||
|
||||
proc updateState*(state: BeaconState, previous_block_root: Eth2Digest,
|
||||
new_block: Option[BeaconBlock], flags: UpdateFlags):
|
||||
tuple[state: BeaconState, block_ok: bool] =
|
||||
proc updateState*(state: var BeaconState, previous_block_root: Eth2Digest,
|
||||
new_block: Option[BeaconBlock], flags: UpdateFlags): bool =
|
||||
## Time in the beacon chain moves by slots. Every time (haha.) that happens,
|
||||
## we will update the beacon state. Normally, the state updates will be driven
|
||||
## by the contents of a new block, but it may happen that the block goes
|
||||
|
@ -792,10 +797,10 @@ proc updateState*(state: BeaconState, previous_block_root: Eth2Digest,
|
|||
# bool return values...)
|
||||
# TODO There's a discussion about what this function should do, and when:
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/284
|
||||
var new_state = state
|
||||
var old_state = state
|
||||
|
||||
# Per-slot updates - these happen regardless if there is a block or not
|
||||
processSlot(new_state, previous_block_root)
|
||||
processSlot(state, previous_block_root)
|
||||
|
||||
if new_block.isSome():
|
||||
# Block updates - these happen when there's a new block being suggested
|
||||
|
@ -804,26 +809,171 @@ proc updateState*(state: BeaconState, previous_block_root: Eth2Digest,
|
|||
# that the block is sane.
|
||||
# TODO what should happen if block processing fails?
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/293
|
||||
if processBlock(new_state, new_block.get(), flags):
|
||||
if processBlock(state, new_block.get(), flags):
|
||||
# Block ok so far, proceed with state update
|
||||
processEpoch(new_state)
|
||||
processEpoch(state)
|
||||
|
||||
# This is a bit awkward - at the end of processing we verify that the
|
||||
# state we arrive at is what the block producer thought it would be -
|
||||
# meaning that potentially, it could fail verification
|
||||
if skipValidation in flags or verifyStateRoot(new_state, new_block.get()):
|
||||
if skipValidation in flags or verifyStateRoot(state, new_block.get()):
|
||||
# State root is what it should be - we're done!
|
||||
return (new_state, true)
|
||||
return true
|
||||
|
||||
# Block processing failed, have to start over
|
||||
new_state = state
|
||||
processSlot(new_state, previous_block_root)
|
||||
processEpoch(new_state)
|
||||
(new_state, false)
|
||||
state = old_state
|
||||
processSlot(state, previous_block_root)
|
||||
processEpoch(state)
|
||||
false
|
||||
else:
|
||||
# Skip all per-block processing. Move directly to epoch processing
|
||||
# prison. Do not do any block updates when passing go.
|
||||
|
||||
# Heavy updates that happen for every epoch - these never fail (or so we hope)
|
||||
processEpoch(new_state)
|
||||
(new_state, true)
|
||||
processEpoch(state)
|
||||
true
|
||||
|
||||
# TODO document this:
|
||||
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 11:32
|
||||
# question about making attestations: in the attestation we carry slot and a justified_slot - just to make sure, this justified_slot is the slot that was justified when the state was at slot, not whatever the client may be seeing now? effectively, because we're attesting to MIN_ATTESTATION_INCLUSION_DELAYold states, it might be that we know about a newer justified slot, but don't include it - correct?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 11:34
|
||||
# You are attesting to what you see as the head of the chain at that slot
|
||||
# The MIN_ATTESTATION_INCLUSION_DELAY is just how many slots must past before this message can be included on chain
|
||||
# so whatever the justified_slot was inside the state that was associate with the head you are attesting to
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 11:37
|
||||
# can I revise an attestation, once I get new information (about the shard or state)?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 11:37
|
||||
# We are attesting to the exact current state at that slot. The MIN_ATTESTATION_INCLUSION_DELAY is to attempt to reduce centralization risk in light of fast block times (ensure attestations have time to fully propagate so fair playing field on including them on chain)
|
||||
# No, if you create two attestations for the same slot, you can be slashed
|
||||
# https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#is_double_vote
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 11:39
|
||||
# is there any interest for me to publish my attestation before MIN_ATTESTATION_INCLUSION_DELAY-1 time has passed?
|
||||
# (apart from the risk of it not being picked up on time)
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 11:40
|
||||
|
||||
# that’s the main risk.
|
||||
|
||||
# Note, we’re a bit unsure about MIN_ATTESTATION_INCLUSION_DELAY because it might open up an attestors timing strategy too much. In the case where MIN_ATTESTATION_INCLUSION_DELAY is removed, we just set it to 1
|
||||
# part of validator honesty assumption is to attest during your slot. That said, a rational actor might act in any number of interesting ways..
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 11:59
|
||||
# I can probably google this somehow, but bls signatures, anyone knows off the top of their head if they have to be combined one by one, or can two group signatures be combined? what happens to overlap then?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 12:00
|
||||
# Yeah, you can do any linear combination of signatures. but you have to remember the linear combination of pubkeys that constructed
|
||||
# if you have two instances of a signature from pubkey p, then you need 2*p in the group pubkey
|
||||
# because the attestation bitfield is only 1 bit per pubkey right now, attestations do not support this
|
||||
# it could be extended to support N overlaps up to N times per pubkey if we had N bits per validator instead of 1
|
||||
# We are shying away from this for the time being. If there end up being substantial difficulties in network layer aggregation, then adding bits to aid in supporting overlaps is one potential solution
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 12:02
|
||||
# ah nice, you anticipated my followup question there :) so it's not a straight-off set union operation
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 12:02
|
||||
# depending on the particular network level troubles we run into
|
||||
# right
|
||||
# aggregatng sigs and pubkeys are both just ec adds https://github.com/ethereum/py-evm/blob/d82b10ae361cde6abbac62f171fcea7809c4e3cf/eth/_utils/bls.py#L191-L202
|
||||
# subtractions work too (i suppose this is obvious). You can linearly combine sigs or pubs in any way
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 12:05
|
||||
# hm.. well, because one thing I'm thinking of is the scenario where someone attests to some shard head and I receive that attestation.. now, let's say that's an honest attestation, but within that same slot, I have more fresh information about a shard for example.. now, I can either sign the data in the old attestation or churn out a new one, risking that neither of these get enough votes to be useful - does that sound.. accurate?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 12:08
|
||||
|
||||
# So you won’t just be signing the head of the shard. This isn’t specified yet, but it would be targeting some recent epoch boundary to ensure higher chance of consensus.
|
||||
|
||||
# If your recent info is about a better fork in the shard than the one you see the other attester signed, then you are better off signing that fork because if it is winning in your few of the shard chain fork choice, then you would assume it is winning in the view of most attesters shard fork choice
|
||||
# If some strange circumstance arose in which you saw a majority of attestations that signed something you think is unexpected before you signed, a rational actor might defect to this majority. An honest actor would sign what they believe to be true
|
||||
# in practice, the actor would have to wait some amount of time past when they should have attested to gather this info.
|
||||
# also, at the end of the day the validator has to compute the non-outsourcable proof of custody bit, so if the other validators are signing off on some shard chain fork they don’t know about, then they can’t attest to that data anyway
|
||||
# (for fear of signing a bad custody bit)
|
||||
# so their rational move is to just attest to the data they acutally know about and can accurately compute their proof of custody bit on
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 12:58
|
||||
# what's justified_block_root doing in attestation_data? isn't that available already as get_block_root(state, attestation.data.justified_slot)?
|
||||
# also, when we sign hash_tree_root(attestation.data) + bytes1(0) - what's the purpose of the 0 byte, given we have domain already?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 13:03
|
||||
# 0 byte is a stub for the proof of custody bit in phase 0
|
||||
# If the attestation is included in a short range fork but still votes for the chain it is added to’s justified_block_root/slot, then we want to count the casper vote
|
||||
# likely if I see the head of the chain as different from what ends up being the canonical chain, my view of the latest justified block might still be in accordance with the canonical chain
|
||||
# if my attesation is included in a fork, the head i voted on doesn’t necessarily lead back to the justified block in the fork. Without including justified_block_root, my vote could be used in any fork for the same epoch even if the block at that justified_slot height was different
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 13:14
|
||||
# Long story short, because attestations can be included in forks of the head they are actually attesting to, we can’t be sure of the justified_block that was being voted on by just usng the justified_slot. The security of properties of Casper FFG require that the voter makes a firm commitment to the actual source block, not just the height of the source block
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 13:23
|
||||
# ok. that's quite a piece. I'm gonna have to draw some diagrams I think :)
|
||||
# ah. ok. actually makes sense.. I think :)
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 13:31
|
||||
# how does that interact then with the following check:
|
||||
|
||||
# Verify that attestation.data.justified_block_root is equal to get_block_root(state, attestation.data.justified_slot).
|
||||
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 13:32
|
||||
# ah, my bad above. We only include an attestation on chain if it is for the correct source
|
||||
# That’s one of the bare minimum requirements to get it included on chain. Without the justified_block_root, we can’t do that check
|
||||
# essentially that checks if this attestation is relevant at all to the current fork’s consensus.
|
||||
# if the justified_block is wrong, then we know the target of the vote and the head of the attestation are wrong too
|
||||
# sorry for the slight mix up there
|
||||
# logic still holds — the justified_slot alone is not actually a firm commitment to a particular chain history. You need the associated hash
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 13:35
|
||||
# can't you just follow Block.parent_root?
|
||||
# well, that, and ultimately.. Block.state_root
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 13:37
|
||||
# The block the attestation is included in might not be for the same fork the attestation was made
|
||||
# we first make sure that the attestation and the block that it’s included in match at the justified_slot. if not, throw it out
|
||||
# then in the incentives, we give some extra reward if the epoch_boundary_root and the chain match
|
||||
# and some extra reward if the beacon_block_root match
|
||||
# if all three match, then the attestation is fully agreeing with the canonical chain. +1 casper vote and strengthening the head of the fork choice
|
||||
# if just justified_block_root and epoch_boundary_root match then the attestation agrees enough to successfully cast an ffg vote
|
||||
# if just justified_block_root match, then at least the attestation agrees on the base of the fork choice, but this isn’t enough to cast an FFG vote
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 13:41
|
||||
|
||||
# if not, throw it out
|
||||
|
||||
# it = block or attestation?
|
||||
# Danny Ryan
|
||||
# @djrtwo
|
||||
# Dec 21 13:42
|
||||
# well, if you are buildling the block ,you shouldn’t include it (thus throw it out of current consideration). If you are validating a block you just received and that conditon fails for an attestation, throw the block out because it included a bad attestation and is thus invalid
|
||||
# The block producer knows when producing the block if they are including bad attestations or other data that will fail state transition
|
||||
# and should not do that
|
||||
# Jacek Sieka
|
||||
# @arnetheduck
|
||||
# Dec 21 13:43
|
||||
# yeah, that makes sense, just checking
|
||||
# ok, I think I'm gonna let that sink in a bit before asking more questions.. thanks :)
|
||||
|
|
|
@ -59,7 +59,7 @@ proc main() =
|
|||
deposit_data: DepositData(
|
||||
value: MAX_DEPOSIT * GWEI_PER_ETH,
|
||||
timestamp: now(),
|
||||
deposit_parameters: DepositParameters(
|
||||
deposit_input: DepositInput(
|
||||
pubkey: pubKey,
|
||||
proof_of_possession: proofOfPossession,
|
||||
withdrawal_credentials: withdrawalCredentials,
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import
|
||||
../beacon_chain/[ssz],
|
||||
../beacon_chain/spec/[beaconstate, digest],
|
||||
../tests/testutil
|
||||
../beacon_chain/spec/[beaconstate, datatypes, digest],
|
||||
../tests/testutil,
|
||||
cligen
|
||||
|
||||
proc stateSize(deposits: int) =
|
||||
let state = get_initial_beacon_state(
|
||||
proc stateSize(deposits: int, maxContent = false) =
|
||||
var state = get_initial_beacon_state(
|
||||
makeInitialDeposits(deposits), 0, Eth2Digest())
|
||||
|
||||
if maxContent:
|
||||
# TODO verify this is correct, but generally we collect up to two epochs
|
||||
# of attestations, and each block has a cap on the number of
|
||||
# attestations it may hold, so we'll just add so many of them
|
||||
state.latest_attestations.setLen(MAX_ATTESTATIONS * EPOCH_LENGTH * 2)
|
||||
let validatorsPerCommittee =
|
||||
len(state.shard_committees_at_slots[0][0].committee) # close enough..
|
||||
for a in state.latest_attestations.mitems():
|
||||
a.participation_bitfield.setLen(validatorsPerCommittee)
|
||||
echo "Validators: ", deposits, ", total: ", state.serialize().len
|
||||
|
||||
stateSize(1000)
|
||||
dispatch(stateSize)
|
||||
|
|
|
@ -23,38 +23,39 @@ suite "Block processing":
|
|||
genesisBlock = makeGenesisBlock(genesisState)
|
||||
|
||||
test "Passes from genesis state, no block":
|
||||
let
|
||||
var
|
||||
state = genesisState
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_block_root = hash_tree_root_final(genesisBlock)
|
||||
new_state = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
let block_ok =
|
||||
updateState(state, previous_block_root, none(BeaconBlock), {})
|
||||
check:
|
||||
new_state.block_ok
|
||||
block_ok
|
||||
|
||||
new_state.state.slot == state.slot + 1
|
||||
state.slot == genesisState.slot + 1
|
||||
|
||||
# When proposer skips their proposal, randao layer is still peeled!
|
||||
new_state.state.validator_registry[proposer_index].randao_layers ==
|
||||
state.validator_registry[proposer_index].randao_layers + 1
|
||||
state.validator_registry[proposer_index].randao_layers ==
|
||||
genesisState.validator_registry[proposer_index].randao_layers + 1
|
||||
|
||||
test "Passes from genesis state, empty block":
|
||||
let
|
||||
var
|
||||
state = genesisState
|
||||
proposer_index = getNextBeaconProposerIndex(state)
|
||||
previous_block_root = hash_tree_root_final(genesisBlock)
|
||||
new_block = makeBlock(state, previous_block_root, BeaconBlockBody())
|
||||
new_state = updateState(
|
||||
state, previous_block_root, some(new_block), {})
|
||||
|
||||
let block_ok = updateState(
|
||||
state, previous_block_root, some(new_block), {})
|
||||
|
||||
check:
|
||||
new_state.block_ok
|
||||
block_ok
|
||||
|
||||
new_state.state.slot == state.slot + 1
|
||||
state.slot == genesisState.slot + 1
|
||||
|
||||
# Proposer proposed, no need for additional peeling
|
||||
new_state.state.validator_registry[proposer_index].randao_layers ==
|
||||
state.validator_registry[proposer_index].randao_layers
|
||||
state.validator_registry[proposer_index].randao_layers ==
|
||||
genesisState.validator_registry[proposer_index].randao_layers
|
||||
|
||||
test "Passes through epoch update, no block":
|
||||
var
|
||||
|
@ -62,11 +63,10 @@ suite "Block processing":
|
|||
previous_block_root = hash_tree_root_final(genesisBlock)
|
||||
|
||||
for i in 1..EPOCH_LENGTH.int:
|
||||
let new_state = updateState(
|
||||
let block_ok = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
check:
|
||||
new_state.block_ok
|
||||
state = new_state.state
|
||||
block_ok
|
||||
|
||||
check:
|
||||
state.slot == genesisState.slot + EPOCH_LENGTH
|
||||
|
@ -79,14 +79,13 @@ suite "Block processing":
|
|||
for i in 1..EPOCH_LENGTH.int:
|
||||
var new_block = makeBlock(state, previous_block_root, BeaconBlockBody())
|
||||
|
||||
let new_state = updateState(
|
||||
let block_ok = updateState(
|
||||
state, previous_block_root, some(new_block), {})
|
||||
|
||||
check:
|
||||
new_state.block_ok
|
||||
state = new_state.state
|
||||
if new_state.block_ok:
|
||||
previous_block_root = hash_tree_root_final(new_block)
|
||||
block_ok
|
||||
|
||||
previous_block_root = hash_tree_root_final(new_block)
|
||||
|
||||
check:
|
||||
state.slot == genesisState.slot + EPOCH_LENGTH
|
||||
|
@ -97,8 +96,8 @@ suite "Block processing":
|
|||
previous_block_root = hash_tree_root_final(genesisBlock)
|
||||
|
||||
# Slot 0 is a finalized slot - won't be making attestations for it..
|
||||
state = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {}).state
|
||||
discard updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
|
||||
let
|
||||
# Create an attestation for slot 1 signed by the only attester we have!
|
||||
|
@ -109,14 +108,14 @@ suite "Block processing":
|
|||
# Some time needs to pass before attestations are included - this is
|
||||
# to let the attestation propagate properly to interested participants
|
||||
while state.slot < MIN_ATTESTATION_INCLUSION_DELAY + 1:
|
||||
state = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {}).state
|
||||
discard updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
|
||||
let
|
||||
new_block = makeBlock(state, previous_block_root, BeaconBlockBody(
|
||||
attestations: @[attestation]
|
||||
))
|
||||
state = updateState(state, previous_block_root, some(new_block), {}).state
|
||||
discard updateState(state, previous_block_root, some(new_block), {})
|
||||
|
||||
check:
|
||||
state.latest_attestations.len == 1
|
||||
|
@ -124,8 +123,8 @@ suite "Block processing":
|
|||
# TODO Can't run more than 127 for now:
|
||||
# https://github.com/ethereum/eth2.0-specs/issues/352
|
||||
while state.slot < 127:
|
||||
state = updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {}).state
|
||||
discard updateState(
|
||||
state, previous_block_root, none(BeaconBlock), {})
|
||||
|
||||
# Would need to process more epochs for the attestation to be removed from
|
||||
# the state! (per above bug)
|
||||
|
|
|
@ -46,12 +46,17 @@ func makeDeposit(i: int): Deposit =
|
|||
pubkey = privkey.fromSigKey()
|
||||
withdrawal_credentials = makeFakeHash(i)
|
||||
randao_commitment = repeat_hash(withdrawal_credentials, randaoRounds)
|
||||
pop = signMessage(privkey, hash_tree_root(
|
||||
(pubkey, withdrawal_credentials, randao_commitment)))
|
||||
proof_of_possession_data = DepositInput(
|
||||
pubkey: pubkey,
|
||||
withdrawal_credentials: withdrawal_credentials,
|
||||
randao_commitment: randao_commitment
|
||||
)
|
||||
pop = signMessage(
|
||||
privkey, hash_tree_root_final(proof_of_possession_data).data)
|
||||
|
||||
Deposit(
|
||||
deposit_data: DepositData(
|
||||
deposit_parameters: DepositParameters(
|
||||
deposit_input: DepositInput(
|
||||
pubkey: pubkey,
|
||||
proof_of_possession: pop,
|
||||
withdrawal_credentials: withdrawal_credentials,
|
||||
|
@ -72,10 +77,9 @@ func makeGenesisBlock*(state: BeaconState): BeaconBlock =
|
|||
)
|
||||
|
||||
func getNextBeaconProposerIndex*(state: BeaconState): Uint24 =
|
||||
# TODO: this works but looks wrong - we update the slot in the state without
|
||||
# updating corresponding data - this works because the state update
|
||||
# code does the same - updates slot, then uses that slot when calling
|
||||
# beacon_proposer_index, then finally updates the shuffling at the end!
|
||||
# TODO: This is a special version of get_beacon_proposer_index that takes into
|
||||
# account the partial update done at the start of slot processing -
|
||||
# see get_shard_committees_index
|
||||
var next_state = state
|
||||
next_state.slot += 1
|
||||
get_beacon_proposer_index(next_state, next_state.slot)
|
||||
|
@ -94,7 +98,7 @@ proc addBlock*(
|
|||
|
||||
let
|
||||
# Index from the new state, but registry from the old state.. hmm...
|
||||
proposer = state.validator_registry[getNextBeaconProposerIndex(state)]
|
||||
proposer = state.validator_registry[proposer_index]
|
||||
|
||||
var
|
||||
# In order to reuse the state transition function, we first create a dummy
|
||||
|
@ -110,8 +114,7 @@ proc addBlock*(
|
|||
body: body
|
||||
)
|
||||
|
||||
var block_ok: bool
|
||||
(state, block_ok) = updateState(
|
||||
let block_ok = updateState(
|
||||
state, previous_block_root, some(new_block), {skipValidation})
|
||||
assert block_ok
|
||||
|
||||
|
@ -166,9 +169,10 @@ proc find_shard_committee(
|
|||
proc makeAttestation*(
|
||||
state: BeaconState, beacon_block_root: Eth2Digest,
|
||||
validator_index: Uint24): Attestation =
|
||||
var new_state = state
|
||||
|
||||
let new_state = updateState(
|
||||
state, beacon_block_root, none(BeaconBlock), {skipValidation})
|
||||
let block_ok = updateState(
|
||||
new_state, beacon_block_root, none(BeaconBlock), {skipValidation})
|
||||
let
|
||||
sac = find_shard_committee(
|
||||
get_shard_committees_at_slot(state, state.slot), validator_index)
|
||||
|
@ -184,7 +188,7 @@ proc makeAttestation*(
|
|||
latest_crosslink_root: Eth2Digest(), # TODO
|
||||
justified_slot: state.justified_slot,
|
||||
justified_block_root:
|
||||
get_block_root(new_state.state, state.justified_slot),
|
||||
get_block_root(new_state, state.justified_slot),
|
||||
)
|
||||
|
||||
assert sac_index != -1, "find_shard_committe should guarantee this"
|
||||
|
|
Loading…
Reference in New Issue