Skip to content

Commit

Permalink
cache beacon committee size calculation (#1363)
Browse files Browse the repository at this point in the history
* cache beacon committee size calculation

this fixes a bug in get_validator_churn_limit as well

* fix

* make committee counts consistently uint64

mixing feels like the worst of the two worlds
  • Loading branch information
arnetheduck authored Jul 23, 2020
1 parent e9193fc commit e0a18a3
Show file tree
Hide file tree
Showing 14 changed files with 84 additions and 66 deletions.
7 changes: 3 additions & 4 deletions beacon_chain/attestation_aggregation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ proc aggregate_attestations*(
doAssert slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= state.slot
doAssert state.slot >= slot

var cache = StateCache()
# TODO performance issue for future, via get_active_validator_indices(...)
doAssert index.uint64 < get_committee_count_at_slot(state, slot)
doAssert index.uint64 < get_committee_count_per_slot(state, slot, cache)

# TODO for testing purposes, refactor this into the condition check
# and just calculation
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#aggregation-selection
var cache = StateCache()
if not is_aggregator(state, slot, index, slot_signature, cache):
return none(AggregateAndProof)

Expand Down Expand Up @@ -171,8 +171,7 @@ proc isValidAttestation*(
epochInfo = blck.getEpochInfo(state)
requiredSubnetIndex =
compute_subnet_for_attestation(
get_committee_count_at_slot(
epochInfo.shuffled_active_validator_indices.len.uint64),
get_committee_count_per_slot(epochInfo),
attestation.data.slot, attestation.data.index.CommitteeIndex)

if requiredSubnetIndex != topicCommitteeIndex:
Expand Down
8 changes: 7 additions & 1 deletion beacon_chain/block_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import
extras, beacon_chain_db,
stew/results,
spec/[crypto, datatypes, digest, presets]
spec/[crypto, datatypes, digest, helpers, presets]


import
Expand Down Expand Up @@ -212,3 +212,9 @@ proc isValidBeaconBlock*(
current_slot: Slot, flags: UpdateFlags): Result[void, BlockError] =
isValidBeaconBlock(
pool.dag, pool.quarantine, signed_beacon_block, current_slot, flags)

func count_active_validators*(epochInfo: EpochRef): uint64 =
epochInfo.shuffled_active_validator_indices.len.uint64

func get_committee_count_per_slot*(epochInfo: EpochRef): uint64 =
get_committee_count_per_slot(count_active_validators(epochInfo))
4 changes: 2 additions & 2 deletions beacon_chain/mainchain_monitor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,8 @@ proc createBeaconStateAux(preset: RuntimePreset,
eth1Block.voteData.block_hash,
eth1Block.timestamp.uint64,
deposits, {})
let activeValidators = count_active_validator_indices(result[], GENESIS_EPOCH)
eth1Block.knownGoodDepositsCount = some activeValidators.uint64
let activeValidators = count_active_validators(result[], GENESIS_EPOCH, StateCache())
eth1Block.knownGoodDepositsCount = some activeValidators

proc createBeaconState(m: MainchainMonitor, eth1Block: Eth1Block): BeaconStateRef =
createBeaconStateAux(
Expand Down
15 changes: 8 additions & 7 deletions beacon_chain/spec/beaconstate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ func compute_activation_exit_epoch*(epoch: Epoch): Epoch =
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_validator_churn_limit
func get_validator_churn_limit(state: BeaconState, cache: var StateCache): uint64 =
# Return the validator churn limit for the current epoch.
max(MIN_PER_EPOCH_CHURN_LIMIT,
len(cache.shuffled_active_validator_indices).uint64 div CHURN_LIMIT_QUOTIENT)
max(
MIN_PER_EPOCH_CHURN_LIMIT,
count_active_validators(
state, state.get_current_epoch(), cache) div CHURN_LIMIT_QUOTIENT)

# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#initiate_validator_exit
func initiate_validator_exit*(state: var BeaconState,
Expand Down Expand Up @@ -588,12 +590,11 @@ proc check_attestation*(
attestation = shortLog(attestation)
trace "process_attestation: beginning"

let committee_count_at_slot =
get_committee_count_at_slot(get_shuffled_active_validator_indices(
state, state.get_current_epoch(), stateCache).len.uint64).uint64
if not (data.index < committee_count_at_slot):
let committees_per_slot =
get_committee_count_per_slot(state, data.slot, stateCache)
if not (data.index < committees_per_slot):
warn "Data index exceeds committee count",
committee_count = committee_count_at_slot
committee_count = committees_per_slot
return

if not isValidAttestationTargetEpoch(state.get_current_epoch(), data):
Expand Down
38 changes: 25 additions & 13 deletions beacon_chain/spec/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import
# Standard lib
math,
std/[math, sequtils, tables],
# Third-party
stew/endians2,
# Internal
Expand Down Expand Up @@ -57,10 +57,16 @@ func is_active_validator*(validator: Validator, epoch: Epoch): bool =
validator.activation_epoch <= epoch and epoch < validator.exit_epoch

# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_active_validator_indices
func count_active_validator_indices*(state: BeaconState, epoch: Epoch): int =
for val in state.validators:
if is_active_validator(val, epoch):
result += 1
func count_active_validators*(state: BeaconState,
epoch: Epoch,
cache: StateCache): uint64 =
if epoch in cache.shuffled_active_validator_indices:
try:
cache.shuffled_active_validator_indices[epoch].len.uint64
except KeyError:
raiseAssert "just checked"
else:
countIt(state.validators, is_active_validator(it, epoch)).uint64

# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_active_validator_indices
func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
Expand All @@ -70,26 +76,32 @@ func get_active_validator_indices*(state: BeaconState, epoch: Epoch):
if is_active_validator(val, epoch):
result.add idx.ValidatorIndex

# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_committee_count_at_slot
func get_committee_count_at_slot*(num_active_validators: uint64): uint64 =
# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_committee_count_per_slot
func get_committee_count_per_slot*(num_active_validators: uint64): uint64 =
clamp(
num_active_validators div SLOTS_PER_EPOCH div TARGET_COMMITTEE_SIZE,
1, MAX_COMMITTEES_PER_SLOT).uint64
1'u64, MAX_COMMITTEES_PER_SLOT)

func get_committee_count_at_slot*(state: BeaconState, slot: Slot): uint64 =
func get_committee_count_per_slot*(state: BeaconState,
epoch: Epoch,
cache: StateCache): uint64 =
# Return the number of committees at ``slot``.

# TODO this is mostly used in for loops which have indexes which then need to
# be converted to CommitteeIndex types for get_beacon_committee(...); replace
# with better and more type-safe use pattern, probably beginning with using a
# CommitteeIndex return type here.
let
epoch = compute_epoch_at_slot(slot)
active_validator_count = count_active_validator_indices(state, epoch)
result = get_committee_count_at_slot(active_validator_count.uint64)
active_validator_count = count_active_validators(state, epoch, cache)
result = get_committee_count_per_slot(active_validator_count)

# Otherwise, get_beacon_committee(...) cannot access some committees.
doAssert (SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT).uint64 >= result
doAssert (SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT) >= uint64(result)

func get_committee_count_per_slot*(state: BeaconState,
slot: Slot,
cache: StateCache): uint64 =
get_committee_count_per_slot(state, slot.compute_epoch_at_slot, cache)

# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/beacon-chain.md#get_current_epoch
func get_current_epoch*(state: BeaconState): Epoch =
Expand Down
6 changes: 4 additions & 2 deletions beacon_chain/spec/network.nim
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ func getAttestationTopic*(forkDigest: ForkDigest, subnetIndex: uint64):
except ValueError as e:
raiseAssert e.msg

func getAttestationTopic*(forkDigest: ForkDigest, attestation: Attestation, num_active_validators: uint64): string =
func getAttestationTopic*(forkDigest: ForkDigest,
attestation: Attestation,
num_active_validators: uint64): string =
getAttestationTopic(
forkDigest,
compute_subnet_for_attestation(
get_committee_count_at_slot(num_active_validators),
get_committee_count_per_slot(num_active_validators),
attestation.data.slot, attestation.data.index.CommitteeIndex))
17 changes: 9 additions & 8 deletions beacon_chain/spec/validator.nim
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ func get_beacon_committee*(
get_shuffled_active_validator_indices(state, epoch)

try:
let committee_count = get_committee_count_at_slot(
let committees_per_slot = get_committee_count_per_slot(
cache.shuffled_active_validator_indices[epoch].len.uint64)
compute_committee(
cache.shuffled_active_validator_indices[epoch],
get_seed(state, epoch, DOMAIN_BEACON_ATTESTER),
(slot mod SLOTS_PER_EPOCH) * committee_count +
(slot mod SLOTS_PER_EPOCH) * committees_per_slot +
index.uint64,
committee_count * SLOTS_PER_EPOCH
committees_per_slot * SLOTS_PER_EPOCH
)
except KeyError:
raiseAssert "values are added to cache before using them"
Expand Down Expand Up @@ -289,7 +289,7 @@ func get_committee_assignment*(

let start_slot = compute_start_slot_at_epoch(epoch)
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
for index in 0 ..< get_committee_count_at_slot(state, slot):
for index in 0'u64 ..< get_committee_count_per_slot(state, slot, cache):
let idx = index.CommitteeIndex
let committee = get_beacon_committee(state, slot, idx, cache)
if validator_index in committee:
Expand All @@ -306,16 +306,17 @@ func get_committee_assignments*(
var cache = StateCache()
let start_slot = compute_start_slot_at_epoch(epoch)

# get_committee_count_at_slot is constant throughout an epoch
let committee_count_at_slot = get_committee_count_at_slot(state, start_slot)
# get_committee_count_per_slot is constant throughout an epoch
let committees_per_slot =
get_committee_count_per_slot(state, start_slot, cache)

for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
for index in 0 ..< committee_count_at_slot:
for index in 0'u64 ..< committees_per_slot:
let idx = index.CommitteeIndex
if not disjoint(validator_indices,
get_beacon_committee(state, slot, idx, cache).toHashSet):
result.add(
(compute_subnet_for_attestation(committee_count_at_slot, slot, idx),
(compute_subnet_for_attestation(committees_per_slot, slot, idx),
slot))

# https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/validator.md#validator-assignments
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/validator_api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =

proc forSlot(slot: Slot, res: var seq[BeaconStatesCommitteesTuple]) =
if index == 0: # TODO this means if the parameter is missing (its optional)
let committees_per_slot = get_committee_count_at_slot(state, slot)
let committees_per_slot = get_committee_count_per_slot(state, slot, cache)
for committee_index in 0'u64..<committees_per_slot:
res.add(getCommittee(slot, committee_index.CommitteeIndex))
else:
Expand Down
15 changes: 6 additions & 9 deletions beacon_chain/validator_duties.nim
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ proc sendAttestation*(node: BeaconNode, attestation: Attestation) =
BlockSlot(blck: attestationBlck, slot: attestation.data.slot)):
node.sendAttestation(
attestation,
blck.getEpochInfo(state).shuffled_active_validator_indices.len.uint64)
count_active_validators(blck.getEpochInfo(state)))

proc createAndSendAttestation(node: BeaconNode,
fork: Fork,
Expand Down Expand Up @@ -331,13 +331,9 @@ proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
node.blockPool.withState(node.blockPool.tmpState, attestationHead):
var cache = getEpochCache(attestationHead.blck, state)
let
committees_per_slot = get_committee_count_at_slot(state, slot)
committees_per_slot = get_committee_count_per_slot(state, slot, cache)
num_active_validators =
try:
cache.shuffled_active_validator_indices[
slot.compute_epoch_at_slot].len.uint64
except KeyError:
raiseAssert "getEpochCache(...) didn't fill cache"
count_active_validators(state, slot.compute_epoch_at_slot, cache)

for committee_index in 0'u64..<committees_per_slot:
let committee = get_beacon_committee(
Expand Down Expand Up @@ -394,9 +390,10 @@ proc broadcastAggregatedAttestations(

let bs = BlockSlot(blck: aggregationHead, slot: aggregationSlot)
node.blockPool.withState(node.blockPool.tmpState, bs):
var cache = getEpochCache(aggregationHead, state)
let
committees_per_slot = get_committee_count_at_slot(state, aggregationSlot)
var cache = StateCache()
committees_per_slot =
get_committee_count_per_slot(state, aggregationSlot, cache)
for committee_index in 0'u64..<committees_per_slot:
let committee = get_beacon_committee(
state, aggregationSlot, committee_index.CommitteeIndex, cache)
Expand Down
5 changes: 3 additions & 2 deletions research/block_sim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import
../beacon_chain/[
attestation_pool, block_pool, beacon_node_types, beacon_chain_db,
interop, validator_pool],
../beacon_chain/block_pools/candidate_chains,
eth/db/[kvstore, kvstore_sqlite3],
../beacon_chain/ssz/[merkleization, ssz_serialization],
./simutils
Expand Down Expand Up @@ -67,8 +68,8 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
attestationHead = blockPool.head.blck.atSlot(slot)

blockPool.withState(blockPool.tmpState, attestationHead):
var cache = StateCache()
let committees_per_slot = get_committee_count_at_slot(state, slot)
var cache = getEpochCache(attestationHead.blck, state)
let committees_per_slot = get_committee_count_per_slot(state, slot, cache)

for committee_index in 0'u64..<committees_per_slot:
let committee = get_beacon_committee(
Expand Down
4 changes: 2 additions & 2 deletions research/state_sim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
# some variation
let
target_slot = state[].data.slot + MIN_ATTESTATION_INCLUSION_DELAY - 1
commitee_count = get_committee_count_at_slot(state[].data, target_slot)
committees_per_slot = get_committee_count_per_slot(state[].data, target_slot, cache)

let
scass = withTimerRet(timers[tShuffle]):
mapIt(
0 ..< commitee_count.int,
0 ..< committees_per_slot.int,
get_beacon_committee(state[].data, target_slot, it.CommitteeIndex, cache))

for i, scas in scass:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,13 @@ proc addMockAttestations*(
get_shuffled_active_validator_indices(state, epoch)
var remaining_balance = state.get_total_active_balance(cache).int64 * 2 div 3

let start_slot = compute_start_slot_at_epoch(epoch)
let
start_slot = compute_start_slot_at_epoch(epoch)
committees_per_slot = get_committee_count_per_slot(state, epoch, cache)

# for-loop of distinct type is broken: https://github.com/nim-lang/Nim/issues/12074
for slot in start_slot.uint64 ..< start_slot.uint64 + SLOTS_PER_EPOCH:
for index in 0 ..< get_committee_count_at_slot(state, slot.Slot):
# TODO: can we move cache out of the loops
var cache = StateCache()

for index in 0'u64 ..< committees_per_slot:
let committee = get_beacon_committee(
state, slot.Slot, index.CommitteeIndex, cache)

Expand Down
12 changes: 6 additions & 6 deletions tests/test_attestation_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,9 @@ suiteReport "Attestation pool processing" & preset():
doAssert: b10Add_clone.error == Duplicate

wrappedTimedTest "Trying to add a duplicate block from an old pruned epoch is tagged as an error":
var cache = StateCache()

blockpool[].addFlags {skipBLSValidation}
pool.forkChoice_v2.proto_array.prune_threshold = 1

var cache = StateCache()
let
b10 = makeTestBlock(state.data, blockPool[].tail.root, cache)
b10Add = blockpool[].addRawBlock(b10) do (
Expand Down Expand Up @@ -326,8 +324,10 @@ suiteReport "Attestation pool processing" & preset():
let start_slot = compute_start_slot_at_epoch(Epoch epoch)
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:

let new_block = makeTestBlock(state.data, block_root, cache, attestations = attestations)
let block_ok = state_transition(defaultRuntimePreset, state.data, new_block, {skipBLSValidation}, noRollback)
let new_block = makeTestBlock(
state.data, block_root, cache, attestations = attestations)
let block_ok = state_transition(
defaultRuntimePreset, state.data, new_block, {skipBLSValidation}, noRollback)
doAssert: block_ok

block_root = new_block.root
Expand All @@ -344,7 +344,7 @@ suiteReport "Attestation pool processing" & preset():
blockPool[].updateHead(head)

attestations.setlen(0)
for index in 0 ..< get_committee_count_at_slot(state.data.data, slot.Slot):
for index in 0'u64 ..< get_committee_count_per_slot(state.data.data, slot.Slot, cache):
let committee = get_beacon_committee(
state.data.data, state.data.data.slot, index.CommitteeIndex, cache)

Expand Down
8 changes: 4 additions & 4 deletions tests/testblockutil.nim
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ proc find_beacon_committee(
state: BeaconState, validator_index: ValidatorIndex,
cache: var StateCache): auto =
let epoch = compute_epoch_at_slot(state.slot)
for epoch_committee_index in 0'u64 ..< get_committee_count_at_slot(
state, epoch.compute_start_slot_at_epoch) * SLOTS_PER_EPOCH:
for epoch_committee_index in 0'u64 ..< get_committee_count_per_slot(
state, epoch, cache) * SLOTS_PER_EPOCH:
let
slot = ((epoch_committee_index mod SLOTS_PER_EPOCH) +
epoch.compute_start_slot_at_epoch.uint64).Slot
Expand All @@ -216,9 +216,9 @@ proc makeFullAttestations*(
# Create attestations in which the full committee participates for each shard
# that should be attested to during a particular slot
let
count = get_committee_count_at_slot(state, slot)
committees_per_slot = get_committee_count_per_slot(state, slot, cache)

for index in 0..<count:
for index in 0'u64..<committees_per_slot:
let
committee = get_beacon_committee(
state, slot, index.CommitteeIndex, cache)
Expand Down

0 comments on commit e0a18a3

Please sign in to comment.