241 lines
8.4 KiB
Nim
241 lines
8.4 KiB
Nim
{.push raises: [Defect].}
|
|
|
|
import
|
|
deques, tables,
|
|
stew/[endians2, byteutils], chronicles,
|
|
spec/[datatypes, crypto, digest],
|
|
beacon_chain_db, extras
|
|
|
|
type
|
|
# #############################################
|
|
#
|
|
# Attestation Pool
|
|
#
|
|
# #############################################
|
|
Validation* = object
|
|
aggregation_bits*: CommitteeValidatorsBits
|
|
aggregate_signature*: ValidatorSig
|
|
|
|
# Per Danny as of 2018-12-21:
|
|
# 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 bitlist 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
|
|
|
|
AttestationEntry* = object
|
|
data*: AttestationData
|
|
blck*: BlockRef
|
|
validations*: seq[Validation] ## \
|
|
## Instead of aggregating the signatures eagerly, we simply dump them in
|
|
## this seq and aggregate only when needed
|
|
## TODO there are obvious caching opportunities here..
|
|
|
|
SlotData* = object
|
|
attestations*: seq[AttestationEntry] ## \
|
|
## Depending on the world view of the various validators, they may have
|
|
## voted on different states - here we collect all the different
|
|
## combinations that validators have come up with so that later, we can
|
|
## count how popular each world view is (fork choice)
|
|
## TODO this could be a Table[AttestationData, seq[Validation] or something
|
|
## less naive
|
|
|
|
UnresolvedAttestation* = object
|
|
attestation*: Attestation
|
|
tries*: int
|
|
|
|
AttestationPool* = object
|
|
## The attestation pool keeps all attestations that are known to the
|
|
## client - each attestation counts as votes towards the fork choice
|
|
## rule that determines which block we consider to be the head. The pool
|
|
## contains both votes that have been included in the chain and those that
|
|
## have not.
|
|
|
|
slots*: Deque[SlotData] ## \
|
|
## We keep one item per slot such that indexing matches slot number
|
|
## together with startingSlot
|
|
|
|
startingSlot*: Slot ## \
|
|
## Generally, we keep attestations only until a slot has been finalized -
|
|
## after that, they may no longer affect fork choice.
|
|
|
|
blockPool*: BlockPool
|
|
|
|
unresolved*: Table[Eth2Digest, UnresolvedAttestation]
|
|
|
|
latestAttestations*: Table[ValidatorPubKey, BlockRef] ##\
|
|
## Map that keeps track of the most recent vote of each attester - see
|
|
## fork_choice
|
|
|
|
# #############################################
|
|
#
|
|
# Block Pool
|
|
#
|
|
# #############################################
|
|
BlockPool* = ref object
|
|
## Pool of blocks responsible for keeping a graph of resolved blocks as well
|
|
## as candidates that may yet become part of that graph.
|
|
## Currently, this type works as a facade to the BeaconChainDB, making
|
|
## assumptions about the block composition therein.
|
|
##
|
|
## The general idea here is that blocks known to us are divided into two
|
|
## camps - unresolved and resolved. When we start the chain, we have a
|
|
## genesis state that serves as the root of the graph we're interested in.
|
|
## Every block that belongs to that chain will have a path to that block -
|
|
## conversely, blocks that do not are not interesting to us.
|
|
##
|
|
## As the chain progresses, some states become finalized as part of the
|
|
## consensus process. One way to think of that is that the blocks that
|
|
## come before them are no longer relevant, and the finalized state
|
|
## is the new genesis from which we build. Thus, instead of tracing a path
|
|
## to genesis, we can trace a path to any finalized block that follows - we
|
|
## call the oldest such block a tail block.
|
|
##
|
|
## It's important to note that blocks may arrive in any order due to
|
|
## chainging network conditions - we counter this by buffering unresolved
|
|
## blocks for some time while trying to establish a path.
|
|
##
|
|
## Once a path is established, the block becomes resolved. We store the
|
|
## graph in memory, in the form of BlockRef objects. This is also when
|
|
## we forward the block for storage in the database
|
|
##
|
|
## TODO evaluate the split of responsibilities between the two
|
|
## TODO prune the graph as tail moves
|
|
|
|
pending*: Table[Eth2Digest, SignedBeaconBlock] ##\
|
|
## Blocks that have passed validation but that we lack a link back to tail
|
|
## for - when we receive a "missing link", we can use this data to build
|
|
## an entire branch
|
|
|
|
missing*: Table[Eth2Digest, MissingBlock] ##\
|
|
## Roots of blocks that we would like to have (either parent_root of
|
|
## unresolved blocks or block roots of attestations)
|
|
|
|
blocks*: Table[Eth2Digest, BlockRef] ##\
|
|
## Tree of blocks pointing back to a finalized block on the chain we're
|
|
## interested in - we call that block the tail
|
|
|
|
tail*: BlockRef ##\
|
|
## The earliest finalized block we know about
|
|
|
|
head*: Head ##\
|
|
## The latest block we know about, that's been chosen as a head by the fork
|
|
## choice rule
|
|
|
|
finalizedHead*: BlockSlot ##\
|
|
## The latest block that was finalized according to the block in head
|
|
## Ancestors of this block are guaranteed to have 1 child only.
|
|
|
|
db*: BeaconChainDB
|
|
|
|
cachedStates*: seq[tuple[blockRoot: Eth2Digest, slot: Slot,
|
|
state: ref HashedBeaconState]]
|
|
|
|
heads*: seq[Head]
|
|
|
|
inAdd*: bool
|
|
|
|
headState*: StateData ##\
|
|
## State given by the head block; only update in `updateHead`, not anywhere
|
|
## else via `withState`
|
|
|
|
justifiedState*: StateData ## Latest justified state, as seen from the head
|
|
|
|
tmpState*: StateData ## Scratchpad - may be any state
|
|
|
|
updateFlags*: UpdateFlags
|
|
|
|
MissingBlock* = object
|
|
slots*: uint64 # number of slots that are suspected missing
|
|
tries*: int
|
|
|
|
BlockRef* = ref object
|
|
## Node in object graph guaranteed to lead back to tail block, and to have
|
|
## a corresponding entry in database.
|
|
## Block graph should form a tree - in particular, there are no cycles.
|
|
|
|
root*: Eth2Digest ##\
|
|
## Root that can be used to retrieve block data from database
|
|
|
|
parent*: BlockRef ##\
|
|
## Not nil, except for the tail
|
|
|
|
children*: seq[BlockRef]
|
|
# TODO do we strictly need this?
|
|
|
|
slot*: Slot # TODO could calculate this by walking to root, but..
|
|
|
|
BlockData* = object
|
|
## Body and graph in one
|
|
|
|
data*: SignedBeaconBlock
|
|
refs*: BlockRef
|
|
|
|
StateData* = object
|
|
data*: HashedBeaconState
|
|
|
|
blck*: BlockRef ##\
|
|
## The block associated with the state found in data - normally
|
|
## `blck.state_root == data.root` but the state might have been advanced
|
|
## further with empty slots invalidating this condition.
|
|
|
|
BlockSlot* = object
|
|
## Unique identifier for a particular fork and time in the block chain -
|
|
## normally, there's a block for every slot, but in the case a block is not
|
|
## produced, the chain progresses anyway, producing a new state for every
|
|
## slot.
|
|
blck*: BlockRef
|
|
slot*: Slot ##\
|
|
## Slot time for this BlockSlot which may differ from blck.slot when time
|
|
## has advanced without blocks
|
|
|
|
Head* = object
|
|
blck*: BlockRef
|
|
justified*: BlockSlot
|
|
|
|
# #############################################
|
|
#
|
|
# Validator Pool
|
|
#
|
|
# #############################################
|
|
ValidatorKind* = enum
|
|
inProcess
|
|
remote
|
|
|
|
ValidatorConnection* = object
|
|
|
|
AttachedValidator* = ref object
|
|
pubKey*: ValidatorPubKey
|
|
|
|
case kind*: ValidatorKind
|
|
of inProcess:
|
|
privKey*: ValidatorPrivKey
|
|
else:
|
|
connection*: ValidatorConnection
|
|
|
|
ValidatorPool* = object
|
|
validators*: Table[ValidatorPubKey, AttachedValidator]
|
|
|
|
FetchRecord* = object
|
|
root*: Eth2Digest
|
|
historySlots*: uint64
|
|
|
|
proc shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)
|
|
|
|
proc shortLog*(v: BlockSlot): string =
|
|
if v.blck.slot == v.slot:
|
|
v.blck.root.data[0..3].toHex() & ":" & $v.blck.slot
|
|
else: # There was a gap - log it
|
|
v.blck.root.data[0..3].toHex() & ":" & $v.blck.slot & "@" & $v.slot
|
|
|
|
proc shortLog*(v: BlockRef): string =
|
|
v.root.data[0..3].toHex() & ":" & $v.slot
|
|
|
|
chronicles.formatIt BlockSlot: shortLog(it)
|
|
chronicles.formatIt BlockRef: shortLog(it)
|