render get_beacon_proposer_index(...) return value an option and remove overly aggressive assertion formerly causing crashes when missing validators (#617)

* render get_beacon_proposer_index(...) return value an option and remove overly aggressive assertion formerly causing crashes when missing validators

* follow addBlock(...) after refactoring

* [skip ci] fix typo
This commit is contained in:
Dustin Brody 2019-12-04 10:49:59 +00:00 committed by Mamy Ratsimbazafy
parent 6b56e19572
commit c762c0232d
6 changed files with 59 additions and 33 deletions

View File

@ -645,9 +645,12 @@ proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
# revisit this - we should be able to advance behind # revisit this - we should be able to advance behind
var cache = get_empty_per_epoch_cache() var cache = get_empty_per_epoch_cache()
node.blockPool.withState(node.stateCache, BlockSlot(blck: head, slot: slot)): node.blockPool.withState(node.stateCache, BlockSlot(blck: head, slot: slot)):
let let proposerIdx = get_beacon_proposer_index(state, cache)
proposerIdx = get_beacon_proposer_index(state, cache) if proposerIdx.isNone:
validator = node.getAttachedValidator(state, proposerIdx) debug "Missing proposer index"
return head
let validator = node.getAttachedValidator(state, proposerIdx.get)
if validator != nil: if validator != nil:
return await proposeBlock(node, validator, head, slot) return await proposeBlock(node, validator, head, slot)
@ -655,7 +658,7 @@ proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
trace "Expecting block proposal", trace "Expecting block proposal",
headRoot = shortLog(head.root), headRoot = shortLog(head.root),
slot = shortLog(slot), slot = shortLog(slot),
proposer = shortLog(state.validators[proposerIdx].pubKey), proposer = shortLog(state.validators[proposerIdx.get].pubKey),
cat = "consensus", cat = "consensus",
pcs = "wait_for_proposal" pcs = "wait_for_proposal"

View File

@ -6,7 +6,7 @@
# 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
tables, algorithm, math, sequtils, tables, algorithm, math, sequtils, options,
json_serialization/std/sets, chronicles, stew/bitseqs, json_serialization/std/sets, chronicles, stew/bitseqs,
../extras, ../ssz, ../extras, ../ssz,
./crypto, ./datatypes, ./digest, ./helpers, ./validator ./crypto, ./datatypes, ./digest, ./helpers, ./validator
@ -171,14 +171,19 @@ proc slash_validator*(state: var BeaconState, slashed_index: ValidatorIndex,
decrease_balance(state, slashed_index, decrease_balance(state, slashed_index,
validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT) validator.effective_balance div MIN_SLASHING_PENALTY_QUOTIENT)
# The rest doesn't make sense without there being any proposer index, so skip
let proposer_index = get_beacon_proposer_index(state, stateCache)
if proposer_index.isNone:
debug "No beacon proposer index and probably no active validators"
return
let let
proposer_index = get_beacon_proposer_index(state, stateCache)
# Spec has whistleblower_index as optional param, but it's never used. # Spec has whistleblower_index as optional param, but it's never used.
whistleblower_index = proposer_index whistleblower_index = proposer_index.get
whistleblowing_reward = whistleblowing_reward =
(validator.effective_balance div WHISTLEBLOWER_REWARD_QUOTIENT).Gwei (validator.effective_balance div WHISTLEBLOWER_REWARD_QUOTIENT).Gwei
proposer_reward = whistleblowing_reward div PROPOSER_REWARD_QUOTIENT proposer_reward = whistleblowing_reward div PROPOSER_REWARD_QUOTIENT
increase_balance(state, proposer_index, proposer_reward) increase_balance(state, proposer_index.get, proposer_reward)
# TODO: evaluate if spec bug / underflow can be triggered # TODO: evaluate if spec bug / underflow can be triggered
doAssert(whistleblowing_reward >= proposer_reward, "Spec bug: underflow in slash_validator") doAssert(whistleblowing_reward >= proposer_reward, "Spec bug: underflow in slash_validator")
increase_balance( increase_balance(
@ -492,6 +497,12 @@ proc process_attestation*(
# reused when looking for suitable blocks to include in attestations. # reused when looking for suitable blocks to include in attestations.
# TODO don't log warnings when looking for attestations (return # TODO don't log warnings when looking for attestations (return
# Result[void, cstring] instead of logging in check_attestation?) # Result[void, cstring] instead of logging in check_attestation?)
let proposer_index = get_beacon_proposer_index(state, stateCache)
if proposer_index.isNone:
debug "No beacon proposer index and probably no active validators"
return false
if check_attestation(state, attestation, flags, stateCache): if check_attestation(state, attestation, flags, stateCache):
let let
attestation_slot = attestation.data.slot attestation_slot = attestation.data.slot
@ -499,7 +510,7 @@ proc process_attestation*(
data: attestation.data, data: attestation.data,
aggregation_bits: attestation.aggregation_bits, aggregation_bits: attestation.aggregation_bits,
inclusion_delay: state.slot - attestation_slot, inclusion_delay: state.slot - attestation_slot,
proposer_index: get_beacon_proposer_index(state, stateCache).uint64, proposer_index: proposer_index.get.uint64,
) )
if attestation.data.target.epoch == get_current_epoch(state): if attestation.data.target.epoch == get_current_epoch(state):

View File

@ -32,8 +32,8 @@
# improvements to be made - other than that, keep things similar to spec for # improvements to be made - other than that, keep things similar to spec for
# now. # now.
import # TODO - cleanup imports import
algorithm, collections/sets, chronicles, sequtils, sets, tables, algorithm, collections/sets, chronicles, options, sequtils, sets, tables,
../extras, ../ssz, metrics, ../extras, ../ssz, metrics,
beaconstate, crypto, datatypes, digest, helpers, validator beaconstate, crypto, datatypes, digest, helpers, validator
@ -76,10 +76,13 @@ proc process_block_header*(
signature: BlsValue[Signature](kind: OpaqueBlob) signature: BlsValue[Signature](kind: OpaqueBlob)
) )
# Verify proposer is not slashed # Verify proposer is not slashed
let proposer = let proposer_index = get_beacon_proposer_index(state, stateCache)
state.validators[get_beacon_proposer_index(state, stateCache)] if proposer_index.isNone:
debug "Block header: proposer missing"
return false
let proposer = state.validators[proposer_index.get]
if proposer.slashed: if proposer.slashed:
notice "Block header: proposer slashed" notice "Block header: proposer slashed"
return false return false
@ -105,7 +108,12 @@ proc process_randao(
let let
epoch = state.get_current_epoch() epoch = state.get_current_epoch()
proposer_index = get_beacon_proposer_index(state, stateCache) proposer_index = get_beacon_proposer_index(state, stateCache)
proposer = addr state.validators[proposer_index]
if proposer_index.isNone:
debug "Proposer index missing, probably along with any active validators"
return false
let proposer = addr state.validators[proposer_index.get]
# Verify that the provided randao value is valid # Verify that the provided randao value is valid
if skipValidation notin flags: if skipValidation notin flags:

View File

@ -148,11 +148,12 @@ func get_empty_per_epoch_cache*(): StateCache =
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_beacon-chain.md#compute_proposer_index # https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_beacon-chain.md#compute_proposer_index
func compute_proposer_index(state: BeaconState, indices: seq[ValidatorIndex], func compute_proposer_index(state: BeaconState, indices: seq[ValidatorIndex],
seed: Eth2Digest, stateCache: var StateCache): ValidatorIndex = seed: Eth2Digest, stateCache: var StateCache): Option[ValidatorIndex] =
# Return from ``indices`` a random index sampled by effective balance. # Return from ``indices`` a random index sampled by effective balance.
const MAX_RANDOM_BYTE = 255 const MAX_RANDOM_BYTE = 255
doAssert len(indices) > 0 if len(indices) == 0:
return none(ValidatorIndex)
# TODO fixme; should only be run once per slot and cached # TODO fixme; should only be run once per slot and cached
# There's exactly one beacon proposer per slot. # There's exactly one beacon proposer per slot.
@ -175,12 +176,12 @@ func compute_proposer_index(state: BeaconState, indices: seq[ValidatorIndex],
state.validators[candidate_index].effective_balance state.validators[candidate_index].effective_balance
if effective_balance * MAX_RANDOM_BYTE >= if effective_balance * MAX_RANDOM_BYTE >=
MAX_EFFECTIVE_BALANCE * random_byte: MAX_EFFECTIVE_BALANCE * random_byte:
return candidate_index return some(candidate_index)
i += 1 i += 1
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_beacon-chain.md#get_beacon_proposer_index # https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/core/0_beacon-chain.md#get_beacon_proposer_index
func get_beacon_proposer_index*(state: BeaconState, stateCache: var StateCache): func get_beacon_proposer_index*(state: BeaconState, stateCache: var StateCache):
ValidatorIndex = Option[ValidatorIndex] =
# Return the beacon proposer index at the current slot. # Return the beacon proposer index at the current slot.
let epoch = get_current_epoch(state) let epoch = get_current_epoch(state)

View File

@ -6,6 +6,7 @@
# 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
options,
# Specs # Specs
../../beacon_chain/spec/[datatypes, crypto, helpers, validator], ../../beacon_chain/spec/[datatypes, crypto, helpers, validator],
# Internals # Internals
@ -60,10 +61,10 @@ proc signMockBlock*(
blck: var BeaconBlock blck: var BeaconBlock
) = ) =
var proposer_index: ValidatorIndex
var emptyCache = get_empty_per_epoch_cache() var emptyCache = get_empty_per_epoch_cache()
let proposer_index =
if blck.slot == state.slot: if blck.slot == state.slot:
proposer_index = get_beacon_proposer_index(state, emptyCache) get_beacon_proposer_index(state, emptyCache)
else: else:
# Stub to get proposer index of future slot # Stub to get proposer index of future slot
# Note: this relies on ``let`` deep-copying the state # Note: this relies on ``let`` deep-copying the state
@ -71,9 +72,10 @@ proc signMockBlock*(
# and not contain ref objects or pointers # and not contain ref objects or pointers
var stubState = state var stubState = state
process_slots(stub_state, blck.slot) process_slots(stub_state, blck.slot)
proposer_index = get_beacon_proposer_index(stub_state, emptyCache) get_beacon_proposer_index(stub_state, emptyCache)
signMockBlockImpl(state, blck, proposer_index) # In tests, just let this throw if appropriate
signMockBlockImpl(state, blck, proposer_index.get)
proc mockBlock*( proc mockBlock*(
state: BeaconState, state: BeaconState,

View File

@ -6,7 +6,7 @@
# 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
stew/endians2, options, stew/endians2,
chronicles, eth/trie/[db], chronicles, eth/trie/[db],
../beacon_chain/[beacon_chain_db, block_pool, extras, ssz, state_transition, ../beacon_chain/[beacon_chain_db, block_pool, extras, ssz, state_transition,
validator_pool], validator_pool],
@ -87,7 +87,8 @@ proc addBlock*(
let let
# Index from the new state, but registry from the old state.. hmm... # Index from the new state, but registry from the old state.. hmm...
proposer = state.validators[proposer_index] # In tests, let this throw
proposer = state.validators[proposer_index.get]
privKey = hackPrivKey(proposer) privKey = hackPrivKey(proposer)
# TODO ugly hack; API needs rethinking # TODO ugly hack; API needs rethinking