naive attestation pool, in preparation of fork choice integration (#125)

* move attestation pool to separate file
* combine attestations lazily when needed
* advance state when there's a gap while attesting
* compile beacon node with optimizations - it's tooo slow right now
* log when unable to keep up
This commit is contained in:
Jacek Sieka 2019-02-19 17:35:02 -06:00 committed by Mamy Ratsimbazafy
parent 1269e001ad
commit 4670d6c98a
7 changed files with 483 additions and 201 deletions

View File

@ -0,0 +1,322 @@
import
deques, options, sequtils, tables,
chronicles,
./spec/[beaconstate, datatypes, crypto, digest, helpers, validator], extras,
./beacon_chain_db, ./ssz
type
Validation* = object
aggregation_bitfield*: seq[byte]
custody_bitfield*: seq[byte] ##\
## Phase 1 - the handling of this field is probably broken..
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 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
AttestationEntry* = object
data*: AttestationData
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
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*: SlotNumber ## \
## Generally, we keep attestations only until a slot has been finalized -
## after that, they may no longer affect fork choice.
proc init*(T: type AttestationPool, dummy: int): T =
result.slots = initDeque[SlotData]()
proc overlaps(a, b: seq[byte]): bool =
for i in 0..<a.len:
if (a[i] and b[i]) > 0'u8: return true
proc combineBitfield(tgt: var seq[byte], src: seq[byte]) =
for i in 0 ..< tgt.len:
# TODO:
# when BLS signatures are combined, we must ensure that
# the same participant key is not included on both sides
tgt[i] = tgt[i] or src[i]
proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) =
# Combine the signature and participation bitfield, with the assumption that
# the same data is being signed!
assert tgt.data == src.data
# TODO:
# when BLS signatures are combined, we must ensure that
# the same participant key is not included on both sides
tgt.aggregation_bitfield.combineBitfield(src.aggregation_bitfield)
if skipValidation notin flags:
tgt.aggregate_signature.combine(src.aggregate_signature)
proc validate(
state: BeaconState, attestation: Attestation, flags: UpdateFlags): bool =
# TODO these validations should probably be done elsewhere, and really bad
# attestations should probably cause some sort of feedback to the network
# layer so they don't spread further.. is there a sliding scale here of
# badness?
# TODO half of this stuff is from beaconstate.validateAttestation - merge?
let attestationSlot = attestation.data.slot
if attestationSlot < state.finalized_epoch.get_epoch_start_slot():
debug "Old attestation",
attestationSlot = humaneSlotNum(attestationSlot),
attestationEpoch = humaneEpochNum(attestationSlot.slot_to_epoch),
stateSlot = humaneSlotNum(state.slot),
finalizedEpoch = humaneEpochNum(state.finalized_epoch)
return
# TODO what makes sense here? If an attestation is from the future with
# regards to the state, something is wrong - it's a bad attestation, we're
# desperatly behind or someone is sending bogus attestations...
if attestationSlot > state.slot + 64:
debug "Future attestation",
attestationSlot = humaneSlotNum(attestationSlot),
attestationEpoch = humaneEpochNum(attestationSlot.slot_to_epoch),
stateSlot = humaneSlotNum(state.slot),
finalizedEpoch = humaneEpochNum(state.finalized_epoch)
return
if not allIt(attestation.custody_bitfield, it == 0):
notice "Invalid custody bitfield for phase 0"
return false
if not anyIt(attestation.aggregation_bitfield, it != 0):
notice "Empty aggregation bitfield"
return false
let crosslink_committee = mapIt(
filterIt(get_crosslink_committees_at_slot(state, attestation.data.slot),
it.shard == attestation.data.shard),
it.committee)[0]
# Extra checks not in specs
# https://github.com/status-im/nim-beacon-chain/pull/105#issuecomment-462432544
if attestation.aggregation_bitfield.len != (crosslink_committee.len + 7) div 8:
notice "Invalid aggregation bitfield length",
attestationLen = attestation.aggregation_bitfield.len,
committeeLen = crosslink_committee.len
return false
if attestation.custody_bitfield.len != (crosslink_committee.len + 7) div 8:
notice "Invalid custody bitfield length",
attestationLen = attestation.aggregation_bitfield.len,
committeeLen = crosslink_committee.len
return false
# End extra checks
## the rest; turns into expensive NOP until then.
if skipValidation notin flags:
let
participants = get_attestation_participants(
state, attestation.data, attestation.aggregation_bitfield)
## TODO when the custody_bitfield assertion-to-emptiness disappears do this
## and fix the custody_bit_0_participants check to depend on it.
# custody_bit_1_participants = {nothing, always, because assertion above}
custody_bit_1_participants: seq[ValidatorIndex] = @[]
custody_bit_0_participants = participants
group_public_key = bls_aggregate_pubkeys(
participants.mapIt(state.validator_registry[it].pubkey))
# Verify that aggregate_signature verifies using the group pubkey.
if not bls_verify_multiple(
@[
bls_aggregate_pubkeys(mapIt(custody_bit_0_participants,
state.validator_registry[it].pubkey)),
bls_aggregate_pubkeys(mapIt(custody_bit_1_participants,
state.validator_registry[it].pubkey)),
],
@[
hash_tree_root(AttestationDataAndCustodyBit(
data: attestation.data, custody_bit: false)),
hash_tree_root(AttestationDataAndCustodyBit(
data: attestation.data, custody_bit: true)),
],
attestation.aggregate_signature,
get_domain(state.fork, slot_to_epoch(attestation.data.slot),
DOMAIN_ATTESTATION),
):
notice "Invalid signature", participants
return false
true
proc add*(pool: var AttestationPool,
attestation: Attestation,
state: BeaconState) =
if not validate(state, attestation, {skipValidation}): return
let
attestationSlot = attestation.data.slot
# We keep a sliding window of attestations, roughly from the last finalized
# epoch to now, because these are the attestations that may affect the voting
# outcome. Some of these attestations will already have been added to blocks,
# while others are fresh off the network.
doAssert attestationSlot >= pool.startingSlot,
"""
We should have checked in validate that attestation is newer than
finalized_slot and we never prune things before that, per below condition!
""" &
", attestationSlot: " & $humaneSlotNum(attestationSlot) &
", startingSlot: " & $humaneSlotNum(pool.startingSlot)
if pool.slots.len == 0:
# When receiving the first attestation, we want to avoid adding a lot of
# empty SlotData items, so we'll cheat a bit here
info "First attestation!",
attestationSlot = $humaneSlotNum(attestationSlot)
pool.startingSlot = attestationSlot
if pool.startingSlot + pool.slots.len.SlotNumber <= attestationSlot:
debug "Growing attestation pool",
attestationSlot = $humaneSlotNum(attestationSlot),
startingSlot = $humaneSlotNum(pool.startingSlot)
# Make sure there's a pool entry for every slot, even when there's a gap
while pool.startingSlot + pool.slots.len.SlotNumber <= attestationSlot:
pool.slots.addLast(SlotData())
if pool.startingSlot < state.finalized_epoch.get_epoch_start_slot():
debug "Pruning attestation pool",
startingSlot = $humaneSlotNum(pool.startingSlot),
finalizedSlot =
$humaneSlotNum(state.finalized_epoch.get_epoch_start_slot())
# TODO there should be a better way to remove a whole epoch of stuff..
while pool.startingSlot < state.finalized_epoch.get_epoch_start_slot():
pool.slots.popFirst()
pool.startingSlot += 1
let
slotData = addr pool.slots[attestationSlot - pool.startingSlot]
validation = Validation(
aggregation_bitfield: attestation.aggregation_bitfield,
custody_bitfield: attestation.custody_bitfield,
aggregate_signature: attestation.aggregate_signature)
var found = false
for a in slotData.attestations.mitems():
if a.data == attestation.data:
for v in a.validations:
if v.aggregation_bitfield.overlaps(validation.aggregation_bitfield):
# TODO this check is here so that later, when we combine signatures,
# there is no overlap (each validator must be represented once
# only). this is wrong - we could technically receive
# attestations that have already been combined (for example when
# feeding in attestations from blocks, which we're not doing yet)
# but then we'll also have to update the combine logic to deal
# with this complication.
debug "Ignoring overlapping attestation",
existingParticipants = get_attestation_participants(
state, a.data, v.aggregation_bitfield),
newParticipants = get_attestation_participants(
state, a.data, validation.aggregation_bitfield)
found = true
break
if not found:
a.validations.add(validation)
found = true
break
if not found:
slotData.attestations.add(AttestationEntry(
data: attestation.data,
validations: @[validation]
))
proc getAttestationsForBlock*(pool: AttestationPool,
lastState: BeaconState,
newBlockSlot: SlotNumber): seq[Attestation] =
if newBlockSlot - GENESIS_SLOT < MIN_ATTESTATION_INCLUSION_DELAY:
debug "Too early for attestations",
newBlockSlot = humaneSlotNum(newBlockSlot)
return
if pool.slots.len == 0: # startingSlot not set yet!
info "No attestations found (pool empty)",
newBlockSlot = humaneSlotNum(newBlockSlot)
return
let
# TODO in theory we could include attestations from other slots also, but
# we're currently not tracking which attestations have already been included
# in blocks on the fork we're aiming for.. this is a conservative approach
# that's guaranteed to not include any duplicates, because it's the first
# time the attestations are up for inclusion!
attestationSlot = newBlockSlot - MIN_ATTESTATION_INCLUSION_DELAY
if attestationSlot < pool.startingSlot or
attestationSlot >= pool.startingSlot + pool.slots.len.SlotNumber:
info "No attestations",
attestationSlot = humaneSlotNum(attestationSlot),
startingSlot = humaneSlotNum(pool.startingSlot),
endingSlot = humaneSlotNum(pool.startingSlot + pool.slots.len.SlotNumber)
return
let
slotDequeIdx = int(attestationSlot - pool.startingSlot)
slotData = pool.slots[slotDequeIdx]
for a in slotData.attestations:
var
attestation = Attestation(
aggregation_bitfield: a.validations[0].aggregation_bitfield,
data: a.data,
custody_bitfield: a.validations[0].custody_bitfield,
aggregate_signature: a.validations[0].aggregate_signature
)
for v in a.validations[1..^1]:
if not attestation.aggregation_bitfield.overlaps(
v.aggregation_bitfield):
attestation.aggregation_bitfield.combineBitfield(
v.aggregation_bitfield)
attestation.custody_bitfield.combineBitfield(v.custody_bitfield)
attestation.aggregate_signature.combine(v.aggregate_signature)
result.add(attestation)
if result.len >= MAX_ATTESTATIONS:
return

View File

@ -3,6 +3,7 @@ import
chronos, chronicles, confutils, eth/[p2p, keys],
spec/[datatypes, digest, crypto, beaconstate, helpers, validator], conf, time,
state_transition, fork_choice, ssz, beacon_chain_db, validator_pool, extras,
attestation_pool,
mainchain_monitor, sync_protocol, gossipsub_protocol, trusted_state_snapshots,
eth/trie/db, eth/trie/backends/rocksdb_backend
@ -30,7 +31,6 @@ const
stateStoragePeriod = EPOCH_LENGTH.uint64 * 10 # Save states once per this number of slots. TODO: Find a good number.
func shortHash(x: auto): string =
($x)[0..7]
@ -49,7 +49,7 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): T =
result.config = conf
result.attachedValidators = ValidatorPool.init
init result.attestationPool, GENESIS_SLOT
init result.attestationPool, 42 # TODO compile failure without the dummy int??
init result.mainchainMonitor, "", Port(0) # TODO: specify geth address and port
let trieDB = trieDB newChainDb(string conf.dataDir)
@ -160,24 +160,33 @@ proc makeAttestation(node: BeaconNode,
doAssert node != nil
doAssert validator != nil
if get_current_epoch(node.beaconState) == node.beaconState.justified_epoch:
return
var state = node.beaconState
let justifiedBlockRoot =
get_block_root(node.beaconState,
get_epoch_start_slot(node.beaconState.justified_epoch))
if state.slot < slot:
info "Filling slot gap for attestation",
slot = humaneSlotNum(slot),
stateSlot = humaneSlotNum(state.slot)
var attestationData = AttestationData(
slot: slot,
shard: shard,
beacon_block_root: node.headBlockRoot,
epoch_boundary_root: Eth2Digest(), # TODO
shard_block_root: Eth2Digest(), # TODO
latest_crosslink: Crosslink(), # TODO
justified_epoch: node.beaconState.justified_epoch,
justified_block_root: justifiedBlockRoot)
for s in state.slot ..< slot:
let ok = updateState(
state, node.headBlockRoot, none[BeaconBlock](), {skipValidation})
doAssert ok
let validatorSignature = await validator.signAttestation(attestationData)
let
justifiedBlockRoot =
get_block_root(state, get_epoch_start_slot(state.justified_epoch))
attestationData = AttestationData(
slot: slot,
shard: shard,
beacon_block_root: node.headBlockRoot,
epoch_boundary_root: Eth2Digest(), # TODO
shard_block_root: Eth2Digest(), # TODO
latest_crosslink: Crosslink(), # TODO
justified_epoch: state.justified_epoch,
justified_block_root: justifiedBlockRoot)
validatorSignature = await validator.signAttestation(attestationData)
var participationBitfield = repeat(0'u8, ceil_div8(committeeLen))
bitSet(participationBitfield, indexInCommittee)
@ -192,10 +201,12 @@ proc makeAttestation(node: BeaconNode,
await node.network.broadcast(topicAttestations, attestation)
info "Attestation sent", slot = humaneSlotNum(slot),
shard = shard,
validator = shortValidatorKey(node, validator.idx),
signature = shortHash(validatorSignature)
info "Attestation sent",
slot = humaneSlotNum(attestationData.slot),
shard = attestationData.shard,
validator = shortValidatorKey(node, validator.idx),
signature = shortHash(validatorSignature),
beaconBlockRoot = shortHash(attestationData.beacon_block_root)
proc proposeBlock(node: BeaconNode,
validator: AttachedValidator,
@ -206,16 +217,18 @@ proc proposeBlock(node: BeaconNode,
var state = node.beaconState
if node.beaconState.slot + 1 < slot:
info "Proposing block after slot gap",
if state.slot + 1 < slot:
info "Filling slot gap for block proposal",
slot = humaneSlotNum(slot),
stateSlot = node.beaconState.slot
for s in node.beaconState.slot + 1 ..< slot:
let ok = updateState(state, node.headBlockRoot, none[BeaconBlock](), {})
stateSlot = humaneSlotNum(state.slot)
for s in state.slot + 1 ..< slot:
let ok = updateState(
state, node.headBlockRoot, none[BeaconBlock](), {skipValidation})
doAssert ok
var blockBody = BeaconBlockBody(
attestations: node.attestationPool.getAttestationsForBlock(node.beaconState, slot))
attestations: node.attestationPool.getAttestationsForBlock(state, slot))
var newBlock = BeaconBlock(
slot: slot,
@ -225,7 +238,8 @@ proc proposeBlock(node: BeaconNode,
signature: ValidatorSig(), # we need the rest of the block first!
body: blockBody)
let ok = updateState(state, node.headBlockRoot, some(newBlock), {skipValidation})
let ok =
updateState(state, node.headBlockRoot, some(newBlock), {skipValidation})
doAssert ok # TODO: err, could this fail somehow?
newBlock.state_root = Eth2Digest(data: hash_tree_root(state))
@ -235,14 +249,15 @@ proc proposeBlock(node: BeaconNode,
shard: BEACON_CHAIN_SHARD_NUMBER,
blockRoot: hash_tree_root_final(newBlock))
newBlock.signature = await validator.signBlockProposal(node.beaconState.fork, signedData)
newBlock.signature = await validator.signBlockProposal(state.fork, signedData)
await node.network.broadcast(topicBeaconBlocks, newBlock)
info "Block proposed", slot = humaneSlotNum(slot),
stateRoot = shortHash(newBlock.state_root),
validator = shortValidatorKey(node, validator.idx),
idx = validator.idx
info "Block proposed",
slot = humaneSlotNum(slot),
stateRoot = shortHash(newBlock.state_root),
validator = shortValidatorKey(node, validator.idx),
idx = validator.idx
proc scheduleBlockProposal(node: BeaconNode,
slot: SlotNumber,
@ -253,15 +268,20 @@ proc scheduleBlockProposal(node: BeaconNode,
# internal `doAssert` starting to fail.
doAssert validator != nil
let at = node.beaconState.slotStart(slot)
let
at = node.beaconState.slotStart(slot)
now = fastEpochTime()
if now > at:
warn "Falling behind on block proposals", at, now, slot
info "Scheduling block proposal",
validator = shortValidatorKey(node, validator.idx),
idx = validator.idx,
slot = humaneSlotNum(slot),
fromNow = (at - fastEpochTime()) div 1000
fromNow = (at - now) div 1000
addTimer(node.beaconState.slotStart(slot)) do (x: pointer) {.gcsafe.}:
addTimer(at) do (x: pointer) {.gcsafe.}:
# TODO timers are generally not accurate / guaranteed to fire at the right
# time - need to guard here against early / late firings
doAssert validator != nil
@ -279,7 +299,20 @@ proc scheduleAttestation(node: BeaconNode,
# internal `doAssert` starting to fail.
doAssert validator != nil
addTimer(node.beaconState.slotMiddle(slot)) do (p: pointer) {.gcsafe.}:
let
at = node.beaconState.slotStart(slot)
now = fastEpochTime()
if now > at:
warn "Falling behind on attestations", at, now, slot
debug "Scheduling attestation",
validator = shortValidatorKey(node, validator.idx),
fromNow = (at - now) div 1000,
slot = humaneSlotNum(slot),
shard
addTimer(at) do (p: pointer) {.gcsafe.}:
doAssert validator != nil
asyncCheck makeAttestation(node, validator, slot,
shard, committeeLen, indexInCommittee)
@ -288,7 +321,8 @@ proc scheduleEpochActions(node: BeaconNode, epoch: EpochNumber) =
## This schedules the required block proposals and
## attestations from our attached validators.
doAssert node != nil
doAssert epoch >= GENESIS_EPOCH, "Epoch: " & $epoch & ", humane epoch: " & $humaneSlotNum(epoch)
doAssert epoch >= GENESIS_EPOCH,
"Epoch: " & $epoch & ", humane epoch: " & $humaneSlotNum(epoch)
debug "Scheduling epoch actions", epoch = humaneEpochNum(epoch)
@ -299,30 +333,28 @@ proc scheduleEpochActions(node: BeaconNode, epoch: EpochNumber) =
let start = if epoch == GENESIS_EPOCH: 1.uint64 else: 0.uint64
for i in start ..< EPOCH_LENGTH:
# Schedule block proposals
let slot = epoch * EPOCH_LENGTH + i
nextState.slot = slot
let proposerIdx = get_beacon_proposer_index(nextState, slot)
let validator = node.getAttachedValidator(proposerIdx)
nextState.slot = slot # ugly trick, see get_beacon_proposer_index
if validator != nil:
# TODO:
# Warm-up the proposer earlier to try to obtain previous
# missing blocks if necessary
scheduleBlockProposal(node, slot, validator)
block: # Schedule block proposals
let proposerIdx = get_beacon_proposer_index(nextState, slot)
let validator = node.getAttachedValidator(proposerIdx)
# Schedule attestations
if validator != nil:
# TODO:
# Warm-up the proposer earlier to try to obtain previous
# missing blocks if necessary
scheduleBlockProposal(node, slot, validator)
for crosslink_committee in get_crosslink_committees_at_slot(
node.beaconState, slot):
#for i, validatorIdx in shard.committee:
for i, validatorIdx in crosslink_committee.committee:
let validator = node.getAttachedValidator(validatorIdx)
if validator != nil:
#scheduleAttestation(node, validator, slot, shard.shard, shard.committee.len, i)
scheduleAttestation(
node, validator, slot, crosslink_committee.shard,
crosslink_committee.committee.len, i)
block: # Schedule attestations
for crosslink_committee in get_crosslink_committees_at_slot(
nextState, slot):
for i, validatorIdx in crosslink_committee.committee:
let validator = node.getAttachedValidator(validatorIdx)
if validator != nil:
scheduleAttestation(
node, validator, slot, crosslink_committee.shard,
crosslink_committee.committee.len, i)
node.lastScheduledEpoch = epoch
let
@ -344,9 +376,10 @@ proc stateNeedsSaving(s: BeaconState): bool =
proc processBlocks*(node: BeaconNode) =
node.network.subscribe(topicBeaconBlocks) do (newBlock: BeaconBlock):
let stateSlot = node.beaconState.slot
info "Block received", slot = humaneSlotNum(newBlock.slot),
stateRoot = shortHash(newBlock.state_root),
stateSlot
info "Block received",
slot = humaneSlotNum(newBlock.slot),
stateRoot = shortHash(newBlock.state_root),
stateSlot = humaneSlotNum(stateSlot)
# TODO: This should be replaced with the real fork-choice rule
if newBlock.slot <= stateSlot:
@ -359,7 +392,8 @@ proc processBlocks*(node: BeaconNode) =
if stateSlot + 1 < newBlock.slot:
info "Advancing state past slot gap",
blockSlot = humaneSlotNum(newBlock.slot),
stateSlot
stateSlot = humaneSlotNum(stateSlot)
for slot in stateSlot + 1 ..< newBlock.slot:
let ok = updateState(state, node.headBlockRoot, none[BeaconBlock](), {})
doAssert ok
@ -396,10 +430,12 @@ proc processBlocks*(node: BeaconNode) =
node.beaconState, a.data, a.aggregation_bitfield).
mapIt(shortValidatorKey(node, it))
info "Attestation received", slot = humaneSlotNum(a.data.slot),
shard = a.data.shard,
signature = shortHash(a.aggregate_signature),
participants
info "Attestation received",
slot = humaneSlotNum(a.data.slot),
shard = a.data.shard,
signature = shortHash(a.aggregate_signature),
participants,
beaconBlockRoot = shortHash(a.data.beacon_block_root)
node.attestationPool.add(a, node.beaconState)

View File

@ -1,132 +1,8 @@
import
deques, options, tables,
./spec/[datatypes, crypto, digest, helpers, validator], extras,
./beacon_chain_db
type
AttestationCandidate* = object
validator*: int
data*: AttestationData
signature*: ValidatorSig
AttestationPool* = object
# The Deque below stores all outstanding attestations per slot.
# In each slot, we have an array of all attestations indexed by their
# shard number. When we haven't received an attestation for a particular
# shard yet, the Option value will be `none`
attestations: Deque[array[SHARD_COUNT, Option[Attestation]]]
startingSlot: SlotNumber
# TODO:
# The compilicated Deque above is not needed.
#
# In fact, we can use a simple array with length SHARD_COUNT because
# in each epoch, each shard is going to receive attestations exactly once.
# Once the epoch is over, we can discard all attestations and start all
# over again (no need for `discardHistoryToSlot` too).
#
# 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 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
proc init*(T: type AttestationPool, startingSlot: SlotNumber): T =
result.attestations = initDeque[array[SHARD_COUNT, Option[Attestation]]]()
result.startingSlot = startingSlot
proc setLen*[T](d: var Deque[T], len: int) =
# TODO: The upstream `Deque` type should gain a proper resize API
let delta = len - d.len
if delta > 0:
for i in 0 ..< delta:
var defaultVal: T
d.addLast(defaultVal)
else:
d.shrink(fromLast = delta)
proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) =
# Combine the signature and participation bitfield, with the assumption that
# the same data is being signed!
# TODO similar code in work_pool, clean up
assert tgt.data == src.data
for i in 0 ..< tgt.aggregation_bitfield.len:
# TODO:
# when BLS signatures are combined, we must ensure that
# the same participant key is not included on both sides
tgt.aggregation_bitfield[i] =
tgt.aggregation_bitfield[i] or
src.aggregation_bitfield[i]
if skipValidation notin flags:
tgt.aggregate_signature.combine(src.aggregate_signature)
proc add*(pool: var AttestationPool,
attestation: Attestation,
beaconState: BeaconState) =
# The caller of this function is responsible for ensuring that
# the attestations will be given in a strictly slot increasing order:
doAssert attestation.data.slot >= pool.startingSlot
# TODO:
# Validate that the attestation is authentic (it's properly signed)
# and make sure that the validator is supposed to make an attestation
# for the specific shard/slot
let slotIdxInPool = int(attestation.data.slot - pool.startingSlot)
if slotIdxInPool >= pool.attestations.len:
pool.attestations.setLen(slotIdxInPool + 1)
let shard = attestation.data.shard
if pool.attestations[slotIdxInPool][shard].isSome:
combine(pool.attestations[slotIdxInPool][shard].get, attestation, {})
else:
pool.attestations[slotIdxInPool][shard] = some(attestation)
proc getAttestationsForBlock*(pool: AttestationPool,
lastState: BeaconState,
newBlockSlot: SlotNumber): seq[Attestation] =
if newBlockSlot < MIN_ATTESTATION_INCLUSION_DELAY or pool.attestations.len == 0:
return
doAssert newBlockSlot > lastState.slot
var
firstSlot = 0.SlotNumber
lastSlot = newBlockSlot - MIN_ATTESTATION_INCLUSION_DELAY
if pool.startingSlot + MIN_ATTESTATION_INCLUSION_DELAY <= lastState.slot:
firstSlot = lastState.slot - MIN_ATTESTATION_INCLUSION_DELAY
for slot in firstSlot .. lastSlot:
let slotDequeIdx = int(slot - pool.startingSlot)
if slotDequeIdx >= pool.attestations.len: return
let shardAndComittees = get_crosslink_committees_at_slot(lastState, slot)
for s in shardAndComittees:
if pool.attestations[slotDequeIdx][s.shard].isSome:
result.add pool.attestations[slotDequeIdx][s.shard].get
proc discardHistoryToSlot*(pool: var AttestationPool, slot: SlotNumber) =
## The index is treated inclusively
let slot = slot - MIN_ATTESTATION_INCLUSION_DELAY
if slot < pool.startingSlot:
return
let slotIdx = int(slot - pool.startingSlot)
pool.attestations.shrink(fromFirst = slotIdx + 1)
func getAttestationCandidate*(attestation: Attestation): AttestationCandidate =
# TODO: not complete AttestationCandidate object
result.data = attestation.data
result.signature = attestation.aggregate_signature
deques, options, sequtils, tables,
chronicles,
./spec/[beaconstate, datatypes, crypto, digest, helpers, validator], extras,
./attestation_pool, ./beacon_chain_db, ./ssz
# ##################################################################
# Specs
@ -204,12 +80,13 @@ func getAttestationVoteCount(
# while the following implementation will count such blockhash multiple times instead.
result = initCountTable[Eth2Digest]()
for slot in current_slot - pool.startingSlot ..< pool.attestations.len.uint64:
for attestation in pool.attestations[slot]:
if attestation.isSome:
# TODO iteration API that hides the startingSlot logic?
for slot in current_slot - pool.startingSlot ..< pool.slots.len.uint64:
for attestation in pool.slots[slot].attestations:
for validation in attestation.validations:
# Increase the block attestation counts by the number of validators aggregated
let voteCount = attestation.get.aggregation_bitfield.getVoteCount()
result.inc(attestation.get.data.beacon_block_root, voteCount)
let voteCount = validation.aggregation_bitfield.getVoteCount()
result.inc(attestation.data.beacon_block_root, voteCount)
proc lmdGhost*(
store: BeaconChainDB,

View File

@ -4,7 +4,7 @@ import
options, sequtils, random,
../tests/[testutil],
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers, validator],
../beacon_chain/[extras, ssz, state_transition, fork_choice]
../beacon_chain/[attestation_pool, extras, ssz, state_transition, fork_choice]
proc `%`(v: uint64): JsonNode = newJInt(v.BiggestInt)
proc `%`(v: Eth2Digest): JsonNode = newJString($v)

View File

@ -6,6 +6,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./test_attestation_pool,
./test_beaconstate,
./test_state_transition,
./test_helpers,

View File

@ -33,7 +33,7 @@ DEFS+="-d:SECONDS_PER_SLOT=${SECONDS_PER_SLOT:-4} " # Spec default: 6
if [[ -z "$SKIP_BUILDS" ]]; then
nim c -o:"$VALIDATOR_KEYGEN_BIN" $DEFS -d:release beacon_chain/validator_keygen
nim c -o:"$BEACON_NODE_BIN" $DEFS beacon_chain/beacon_node
nim c -o:"$BEACON_NODE_BIN" $DEFS --opt:speed beacon_chain/beacon_node
fi
if [ ! -f $STARTUP_FILE ]; then

View File

@ -0,0 +1,46 @@
# beacon_chain
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
options, sequtils, unittest,
./testutil,
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers, validator],
../beacon_chain/[attestation_pool, extras, state_transition, ssz]
suite "Attestation pool processing":
## For now just test that we can compile and execute block processing with
## mock data.
let
# Genesis state with minimal number of deposits
# TODO bls verification is a bit of a bottleneck here
genesisState = get_initial_beacon_state(
makeInitialDeposits(), 0, Eth1Data(), {skipValidation})
genesisBlock = makeGenesisBlock(genesisState)
genesisRoot = hash_tree_root_final(genesisBlock)
test "Can add and retrieve simple attestation":
var
pool = init(AttestationPool, 42)
state = genesisState
# Slot 0 is a finalized slot - won't be making attestations for it..
discard updateState(
state, genesisRoot, none(BeaconBlock), {skipValidation})
let
# Create an attestation for slot 1 signed by the only attester we have!
crosslink_committees = get_crosslink_committees_at_slot(state, state.slot)
attestation = makeAttestation(
state, genesisRoot, crosslink_committees[0].committee[0])
pool.add(attestation, state)
let attestations = pool.getAttestationsForBlock(
state, state.slot + MIN_ATTESTATION_INCLUSION_DELAY)
check:
attestations.len == 1