From 915923cd81f82bbd4b4e98f2975ffd922dfb9b3c Mon Sep 17 00:00:00 2001 From: tersec Date: Sun, 24 Sep 2023 08:52:56 +0000 Subject: [PATCH] use participation flags to score attestations for blocks --- .../attestation_pool.nim | 183 ++++++++++++++---- 1 file changed, 140 insertions(+), 43 deletions(-) diff --git a/beacon_chain/consensus_object_pools/attestation_pool.nim b/beacon_chain/consensus_object_pools/attestation_pool.nim index c5cb4c7cd6..a24597a56f 100644 --- a/beacon_chain/consensus_object_pools/attestation_pool.nim +++ b/beacon_chain/consensus_object_pools/attestation_pool.nim @@ -10,15 +10,21 @@ import # Status libraries metrics, - chronicles, stew/byteutils, + chronicles, + stew/bitseqs as bs, # ssz_serialization defines BitSeqs too in bitseqs module + stew/byteutils, # Internal - ../spec/[ - beaconstate, eth2_merkleization, forks, state_transition_epoch, validator], + ../spec/[eth2_merkleization, forks], "."/[spec_cache, blockchain_dag, block_quarantine], ../fork_choice/fork_choice, ../beacon_clock -from std/sequtils import keepItIf, maxIndex +from std/sequtils import keepItIf, mapIt, maxIndex +from stew/bitseqs import combine +from ../spec/beaconstate import check_attestation, dependent_root +from ../spec/state_transition_epoch import compute_unrealized_finality +from ../spec/validator import + get_beacon_committee, get_committee_count_per_slot, get_committee_indices export blockchain_dag, fork_choice @@ -32,19 +38,22 @@ const type OnAttestationCallback = proc(data: Attestation) {.gcsafe, raises: [].} + CommitteeParticipationFlags = array[3, bs.BitSeq] + Validation = object ## Validations collect a set of signatures for a distict attestation - in ## eth2, a single bit is used to keep track of which signatures have been ## added to the aggregate meaning that only non-overlapping aggregates may ## be further combined. aggregation_bits: CommitteeValidatorsBits + committee_participation_flags: CommitteeParticipationFlags aggregate_signature: AggregateSignature AttestationEntry = object ## Each entry holds the known signatures for a particular, distinct vote data: AttestationData committee_len: int - singles: Table[int, CookedSig] ## \ + singles: Table[int, (CommitteeParticipationFlags, CookedSig)] ## \ ## On the attestation subnets, only attestations with a single vote are ## allowed - these can be collected separately to top up aggregates with - ## here we collect them by mapping index in committee to a vote @@ -239,18 +248,24 @@ func updateAggregates(entry: var AttestationEntry) = if entry.aggregates.len() == 0: # If there are singles, we can create an aggregate from them that will # represent our best knowledge about the current votes - for index_in_committee, signature in entry.singles: + for index_in_committee, foo in entry.singles: + let (flags, signature) = foo if entry.aggregates.len() == 0: # Create aggregate on first iteration.. + let empty_committee_bits = bs.BitSeq.init(entry.committee_len) entry.aggregates.add( Validation( aggregation_bits: CommitteeValidatorsBits.init(entry.committee_len), + committee_participation_flags: + [empty_committee_bits, empty_committee_bits, + empty_committee_bits], aggregate_signature: AggregateSignature.init(signature) )) else: entry.aggregates[0].aggregate_signature.aggregate(signature) entry.aggregates[0].aggregation_bits.setBit(index_in_committee) + entry.aggregates[0].committee_participation_flags = flags else: # There already exist aggregates - we'll try to top them up by adding # singles to them - for example, it may happen that we're being asked to @@ -259,10 +274,14 @@ func updateAggregates(entry: var AttestationEntry) = # In theory, also aggregates could be combined but finding the best # combination is hard, so we'll pragmatically use singles only here var updated = false - for index_in_committee, signature in entry.singles: + for index_in_committee, foo in entry.singles: + let (flags, signature) = foo for v in entry.aggregates.mitems(): if not v.aggregation_bits[index_in_committee]: v.aggregation_bits.setBit(index_in_committee) + combine(v.committee_participation_flags[0], flags[0]) + combine(v.committee_participation_flags[1], flags[1]) + combine(v.committee_participation_flags[2], flags[2]) v.aggregate_signature.aggregate(signature) updated = true @@ -294,12 +313,12 @@ func covers(entry: AttestationEntry, bits: CommitteeValidatorsBits): bool = proc addAttestation(entry: var AttestationEntry, attestation: Attestation, + committee_participation_flags: CommitteeParticipationFlags, signature: CookedSig): bool = logScope: attestation = shortLog(attestation) - let - singleIndex = oneIndex(attestation.aggregation_bits) + let singleIndex = oneIndex(attestation.aggregation_bits) if singleIndex.isSome(): if singleIndex.get() in entry.singles: @@ -313,7 +332,7 @@ proc addAttestation(entry: var AttestationEntry, singles = entry.singles.len(), aggregates = entry.aggregates.len() - entry.singles[singleIndex.get()] = signature + entry.singles[singleIndex.get()] = (committee_participation_flags, signature) else: # More than one vote in this attestation if entry.covers(attestation.aggregation_bits): @@ -326,6 +345,7 @@ proc addAttestation(entry: var AttestationEntry, entry.aggregates.add(Validation( aggregation_bits: attestation.aggregation_bits, + committee_participation_flags: committee_participation_flags, aggregate_signature: AggregateSignature.init(signature))) debug "Aggregate resolved", @@ -358,13 +378,53 @@ proc addAttestation*(pool: var AttestationPool, startingSlot = pool.startingSlot return - let attestation_data_root = hash_tree_root(attestation.data) + let + attestation_data_root = hash_tree_root(attestation.data) + empty_committee_bits = + bs.BitSeq.init(attestation.aggregation_bits.len) + current_epoch = pool.dag.headState.get_current_epoch + + template get_epoch_participation_flags(i: ValidatorIndex): auto = + withState(pool.dag.headState): + when consensusFork == ConsensusFork.Phase0: + # This is irrelevant when phase0, because the loop will just + # fill in from is_present properly; there's no actual + # participation flag information in phase0 + 7.ParticipationFlags + else: + if attestation.data.target.epoch == current_epoch: + forkyState.data.current_epoch_participation[i] + else: + forkyState.data.previous_epoch_participation[i] + + var cmf = [ + empty_committee_bits, empty_committee_bits, empty_committee_bits] + var attesting_index_count = 0 + + # attesting_indices will line up with attestation.aggregation_bits by + # construction via get_beacon_committee/get_attesting_indices + for (idx_in_committee, is_present) in attestation.aggregation_bits.pairs: + if attesting_index_count >= attesting_indices.len(): + #doAssert false + #debugEcho "FOO3: ", attesting_indices, " ", attesting_index_count, " ", $attestation.aggregation_bits + break + if not is_present: + continue + let validator_index = attesting_indices[attesting_index_count] + let flags = get_epoch_participation_flags(validator_index) + if has_flag(flags, TIMELY_SOURCE_FLAG_INDEX): + cmf[TIMELY_SOURCE_FLAG_INDEX.int].setBit(idx_in_committee) + if has_flag(flags, TIMELY_TARGET_FLAG_INDEX): + cmf[TIMELY_TARGET_FLAG_INDEX.int].setBit(idx_in_committee) + if has_flag(flags, TIMELY_HEAD_FLAG_INDEX): + cmf[TIMELY_HEAD_FLAG_INDEX.int].setBit(idx_in_committee) + inc attesting_index_count # TODO withValue is an abomination but hard to use anything else too without # creating an unnecessary AttestationEntry on the hot path and avoiding # multiple lookups pool.candidates[candidateIdx.get()].withValue(attestation_data_root, entry) do: - if not addAttestation(entry[], attestation, signature): + if not addAttestation(entry[], attestation, cmf, signature): return do: if not addAttestation( @@ -373,7 +433,7 @@ proc addAttestation*(pool: var AttestationPool, AttestationEntry( data: attestation.data, committee_len: attestation.aggregation_bits.len())), - attestation, signature): + attestation, cmf, signature): return pool.addForkChoiceVotes( @@ -438,7 +498,8 @@ iterator attestations*(pool: AttestationPool, slot: Opt[Slot], aggregation_bits: CommitteeValidatorsBits.init(entry.committee_len), data: entry.data) - for index, signature in entry.singles: + for index, foo in entry.singles: + let (participation_flags, signature) = foo singleAttestation.aggregation_bits.setBit(index) singleAttestation.signature = signature.toValidatorSig() yield singleAttestation @@ -449,7 +510,7 @@ iterator attestations*(pool: AttestationPool, slot: Opt[Slot], type AttestationCacheKey = (Slot, uint64) - AttestationCache = Table[AttestationCacheKey, CommitteeValidatorsBits] ##\ + AttestationCache = Table[AttestationCacheKey, CommitteeParticipationFlags] ##\ ## Cache for quick lookup during beacon block construction of attestations ## which have already been included, and therefore should be skipped. @@ -460,25 +521,40 @@ func getAttestationCacheKey(ad: AttestationData): AttestationCacheKey = func add( attCache: var AttestationCache, data: AttestationData, - aggregation_bits: CommitteeValidatorsBits) = + committee_participation_flags: CommitteeParticipationFlags) = let key = data.getAttestationCacheKey() attCache.withValue(key, v) do: - v[].incl(aggregation_bits) + doAssert committee_participation_flags.len() == v[].len(), + "committee participation flag length constructed to be consistent" + + combine(v[0], committee_participation_flags[0]) + combine(v[1], committee_participation_flags[1]) + combine(v[2], committee_participation_flags[2]) do: - attCache[key] = aggregation_bits + attCache[key] = committee_participation_flags func init( T: type AttestationCache, state: phase0.HashedBeaconState, _: StateCache): T = # Load attestations that are scheduled for being given rewards for + # As long as it's consistent, it's not particularly important what the + # mapping from validation bits to more granular participation flags is + template allParticipation(a: untyped): CommitteeParticipationFlags = + # pretend all timely flags were hit + var committee_bits = bs.BitSeq.init(a.len) + for idx, is_present in a: + if is_present: + committee_bits.setBit(idx) + [committee_bits, committee_bits, committee_bits] + for i in 0.. 0, "updateAggregates should have created at least one aggregate" var