nimbus-eth2/beacon_chain/consensus_object_pools/exit_pool.nim

182 lines
6.9 KiB
Nim

# beacon_chain
# Copyright (c) 2020-2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [Defect].}
import
# Standard libraries
std/[deques, sets],
# Status libraries
chronicles,
# Internal
../spec/helpers,
../spec/datatypes/[phase0, altair, merge],
./blockchain_dag
export phase0, altair, merge, deques, sets, blockchain_dag
logScope: topics = "exitpool"
const
ATTESTER_SLASHINGS_BOUND* = MAX_ATTESTER_SLASHINGS * 4
PROPOSER_SLASHINGS_BOUND* = MAX_PROPOSER_SLASHINGS * 4
VOLUNTARY_EXITS_BOUND* = MAX_VOLUNTARY_EXITS * 4
type
OnVoluntaryExitCallback* =
proc(data: SignedVoluntaryExit) {.gcsafe, raises: [Defect].}
ExitPool* = object
## The exit pool tracks attester slashings, proposer slashings, and
## voluntary exits that could be added to a proposed block.
attester_slashings*: Deque[AttesterSlashing] ## \
## Not a function of chain DAG branch; just used as a FIFO queue for blocks
proposer_slashings*: Deque[ProposerSlashing] ## \
## Not a function of chain DAG branch; just used as a FIFO queue for blocks
voluntary_exits*: Deque[SignedVoluntaryExit] ## \
## Not a function of chain DAG branch; just used as a FIFO queue for blocks
prior_seen_attester_slashed_indices*: HashSet[uint64] ## \
## Records attester-slashed indices seen.
prior_seen_proposer_slashed_indices*: HashSet[uint64] ## \
## Records proposer-slashed indices seen.
prior_seen_voluntary_exit_indices*: HashSet[uint64] ##\
## Records voluntary exit indices seen.
dag*: ChainDAGRef
onVoluntaryExitReceived*: OnVoluntaryExitCallback
proc init*(T: type ExitPool, dag: ChainDAGRef,
onVoluntaryExit: OnVoluntaryExitCallback = nil): T =
## Initialize an ExitPool from the dag `headState`
T(
# Allow for filtering out some exit messages during block production
attester_slashings:
initDeque[AttesterSlashing](initialSize = ATTESTER_SLASHINGS_BOUND.int),
proposer_slashings:
initDeque[ProposerSlashing](initialSize = PROPOSER_SLASHINGS_BOUND.int),
voluntary_exits:
initDeque[SignedVoluntaryExit](initialSize = VOLUNTARY_EXITS_BOUND.int),
dag: dag,
onVoluntaryExitReceived: onVoluntaryExit
)
func addExitMessage(subpool: var auto, exitMessage, bound: auto) =
# Prefer newer to older exit messages
while subpool.lenu64 >= bound:
discard subpool.popFirst()
subpool.addLast(exitMessage)
doAssert subpool.lenu64 <= bound
iterator getValidatorIndices(attester_slashing: AttesterSlashing): uint64 =
let attestation_2_indices =
toHashSet(attester_slashing.attestation_2.attesting_indices.asSeq)
for validator_index in attester_slashing.attestation_1.attesting_indices.asSeq:
if validator_index notin attestation_2_indices:
continue
yield validator_index
iterator getValidatorIndices(proposer_slashing: ProposerSlashing): uint64 =
yield proposer_slashing.signed_header_1.message.proposer_index
iterator getValidatorIndices(voluntary_exit: SignedVoluntaryExit): uint64 =
yield voluntary_exit.message.validator_index
func isSeen*(pool: ExitPool, msg: AttesterSlashing): bool =
for idx in getValidatorIndices(msg):
# One index is enough!
if idx notin pool.prior_seen_attester_slashed_indices:
return false
true
func isSeen*(pool: ExitPool, msg: ProposerSlashing): bool =
msg.signed_header_1.message.proposer_index in
pool.prior_seen_proposer_slashed_indices
func isSeen*(pool: ExitPool, msg: SignedVoluntaryExit): bool =
msg.message.validator_index in
pool.prior_seen_voluntary_exit_indices
func addMessage*(pool: var ExitPool, msg: AttesterSlashing) =
for idx in getValidatorIndices(msg):
pool.prior_seen_attester_slashed_indices.incl idx
pool.attester_slashings.addExitMessage(msg, ATTESTER_SLASHINGS_BOUND)
func addMessage*(pool: var ExitPool, msg: ProposerSlashing) =
pool.prior_seen_proposer_slashed_indices.incl(
msg.signed_header_1.message.proposer_index)
pool.proposer_slashings.addExitMessage(msg, PROPOSER_SLASHINGS_BOUND)
func addMessage*(pool: var ExitPool, msg: SignedVoluntaryExit) =
pool.prior_seen_voluntary_exit_indices.incl(
msg.message.validator_index)
pool.voluntary_exits.addExitMessage(msg, VOLUNTARY_EXITS_BOUND)
func getExitMessagesForBlock(
subpool: var Deque, validators: auto, seen: var HashSet, output: var List) =
# Approach taken here is to simply collect messages, effectively, a circular
# buffer and only re-validate that they haven't already found themselves out
# of the network eventually via some exit message at block construction time
# at which point we use exit_epoch. It doesn't matter which of these message
# types has triggered that exit, as the validation on incoming messages will
# find it to either be IGNORE (if it's the same type of exit message) or, if
# it's a different type, REJECT. Neither is worth packaging into BeaconBlock
# messages we broadcast.
#
# Beyond that, no other criterion of the exit messages' validity changes from
# when they were created, so given that we validated them to start with, they
# otherwise remain as valid as when we received them. There's no need to thus
# re-validate them on their way out.
#
# This overall approach handles a scenario wherein we receive an exit message
# over gossip and put it in the pool; receive a block X, with that message in
# it, and select it as head; then orphan block X and build instead on X-1. If
# this occurs, only validating after the fact ensures that we still broadcast
# out those exit messages that were in orphaned block X by not having eagerly
# removed them, if we have the chance.
while subpool.len > 0 and output.len < output.maxLen:
# Prefer recent messages
let exit_message = subpool.popLast()
for slashed_index in getValidatorIndices(exit_message):
if validators.lenu64 <= slashed_index:
continue
if validators[slashed_index].exit_epoch != FAR_FUTURE_EPOCH:
continue
if seen.containsOrIncl(slashed_index):
continue
if not output.add exit_message:
break
subpool.clear()
func getBeaconBlockExits*(pool: var ExitPool, state: ForkyBeaconState): BeaconBlockExits =
var
indices: HashSet[uint64]
res: BeaconBlockExits
getExitMessagesForBlock(
pool.attester_slashings, state.validators.asSeq(), indices,
res.attester_slashings)
getExitMessagesForBlock(
pool.proposer_slashings, state.validators.asSeq(), indices,
res.proposer_slashings)
getExitMessagesForBlock(
pool.voluntary_exits, state.validators.asSeq(), indices,
res.voluntary_exits)
res