@@ -34,7 +34,9 @@ import {
34
34
ssz ,
35
35
} from "@lodestar/types" ;
36
36
import { assert , MapDef , toRootHex } from "@lodestar/utils" ;
37
+ import { Metrics } from "../../metrics/metrics.js" ;
37
38
import { IntersectResult , intersectUint8Arrays } from "../../util/bitArray.js" ;
39
+ import { getShufflingDependentRoot } from "../../util/dependentRoot.js" ;
38
40
import { InsertOutcome } from "./types.js" ;
39
41
import { pruneBySlot , signatureFromBytesNoCheck } from "./utils.js" ;
40
42
@@ -104,21 +106,11 @@ export class AggregatedAttestationPool {
104
106
> ( ( ) => new Map < DataRootHex , Map < CommitteeIndex , MatchingDataAttestationGroup > > ( ) ) ;
105
107
private lowestPermissibleSlot = 0 ;
106
108
107
- constructor ( private readonly config : ChainForkConfig ) { }
108
-
109
- /** For metrics to track size of the pool */
110
- getAttestationCount ( ) : { attestationCount : number ; attestationDataCount : number } {
111
- let attestationCount = 0 ;
112
- let attestationDataCount = 0 ;
113
- for ( const attestationGroupByIndexByDataHex of this . attestationGroupByIndexByDataHexBySlot . values ( ) ) {
114
- for ( const attestationGroupByIndex of attestationGroupByIndexByDataHex . values ( ) ) {
115
- attestationDataCount += attestationGroupByIndex . size ;
116
- for ( const attestationGroup of attestationGroupByIndex . values ( ) ) {
117
- attestationCount += attestationGroup . getAttestationCount ( ) ;
118
- }
119
- }
120
- }
121
- return { attestationCount, attestationDataCount} ;
109
+ constructor (
110
+ private readonly config : ChainForkConfig ,
111
+ private readonly metrics : Metrics | null = null
112
+ ) {
113
+ metrics ?. opPool . aggregatedAttestationPool . attDataPerSlot . addCollect ( ( ) => this . onScrapeMetrics ( metrics ) ) ;
122
114
}
123
115
124
116
add (
@@ -317,10 +309,7 @@ export class AggregatedAttestationPool {
317
309
const slots = Array . from ( this . attestationGroupByIndexByDataHexBySlot . keys ( ) ) . sort ( ( a , b ) => b - a ) ;
318
310
// Track score of each `AttestationsConsolidation`
319
311
const consolidations = new Map < AttestationsConsolidation , number > ( ) ;
320
- let minScore = Number . MAX_SAFE_INTEGER ;
321
- let slotCount = 0 ;
322
312
slot: for ( const slot of slots ) {
323
- slotCount ++ ;
324
313
const attestationGroupByIndexByDataHash = this . attestationGroupByIndexByDataHexBySlot . get ( slot ) ;
325
314
// should not happen
326
315
if ( ! attestationGroupByIndexByDataHash ) {
@@ -338,34 +327,30 @@ export class AggregatedAttestationPool {
338
327
}
339
328
340
329
const slotDelta = stateSlot - slot ;
341
- // CommitteeIndex 0 1 2 ... Consolidation
330
+ // CommitteeIndex 0 1 2 ... Consolidationn (sameAttDataCons)
342
331
// Attestations att00 --- att10 --- att20 --- 0 (att 00 10 20)
343
332
// att01 --- - --- att21 --- 1 (att 01 __ 21)
344
333
// - --- - --- att22 --- 2 (att __ __ 22)
345
334
for ( const attestationGroupByIndex of attestationGroupByIndexByDataHash . values ( ) ) {
346
335
// sameAttDataCons could be up to MAX_ATTESTATIONS_PER_GROUP_ELECTRA
347
336
const sameAttDataCons : AttestationsConsolidation [ ] = [ ] ;
337
+ const allAttestationGroups = Array . from ( attestationGroupByIndex . values ( ) ) ;
338
+ if ( allAttestationGroups . length === 0 ) {
339
+ continue ;
340
+ }
341
+
342
+ if ( ! validateAttestationDataFn ( allAttestationGroups [ 0 ] . data ) ) {
343
+ continue ;
344
+ }
345
+
348
346
for ( const [ committeeIndex , attestationGroup ] of attestationGroupByIndex . entries ( ) ) {
349
347
const notSeenAttestingIndices = notSeenValidatorsFn ( epoch , slot , committeeIndex ) ;
350
348
if ( notSeenAttestingIndices === null || notSeenAttestingIndices . size === 0 ) {
351
349
continue ;
352
350
}
353
351
354
- if (
355
- slotCount > 2 &&
356
- consolidations . size >= MAX_ATTESTATIONS_ELECTRA &&
357
- notSeenAttestingIndices . size / slotDelta < minScore
358
- ) {
359
- // after 2 slots, there are a good chance that we have 2 * MAX_ATTESTATIONS_ELECTRA attestations and break the for loop early
360
- // if not, we may have to scan all slots in the pool
361
- // if we have enough attestations and the max possible score is lower than scores of `attestationsByScore`, we should skip
362
- // otherwise it takes time to check attestation, add it and remove it later after the sort by score
363
- continue ;
364
- }
365
-
366
- if ( ! validateAttestationDataFn ( attestationGroup . data ) ) {
367
- continue ;
368
- }
352
+ // cannot apply this optimization like pre-electra because consolidation needs to be done across committees:
353
+ // "after 2 slots, there are a good chance that we have 2 * MAX_ATTESTATIONS_ELECTRA attestations and break the for loop early"
369
354
370
355
// TODO: Is it necessary to validateAttestation for:
371
356
// - Attestation committee index not within current committee count
@@ -387,18 +372,16 @@ export class AggregatedAttestationPool {
387
372
sameAttDataCons [ i ] . byCommittee . set ( committeeIndex , attestationNonParticipation ) ;
388
373
sameAttDataCons [ i ] . totalNotSeenCount += attestationNonParticipation . notSeenAttesterCount ;
389
374
}
390
- for ( const consolidation of sameAttDataCons ) {
391
- const score = consolidation . totalNotSeenCount / slotDelta ;
392
- if ( score < minScore ) {
393
- minScore = score ;
394
- }
375
+ } // all committees are processed
395
376
396
- consolidations . set ( consolidation , score ) ;
377
+ // after all committees are processed, we have a list of sameAttDataCons
378
+ for ( const consolidation of sameAttDataCons ) {
379
+ const score = consolidation . totalNotSeenCount / slotDelta ;
380
+ consolidations . set ( consolidation , score ) ;
397
381
398
- // Stop accumulating attestations there are enough that may have good scoring
399
- if ( consolidations . size >= MAX_ATTESTATIONS_ELECTRA * 2 ) {
400
- break slot;
401
- }
382
+ // Stop accumulating attestations there are enough that may have good scoring
383
+ if ( consolidations . size >= MAX_ATTESTATIONS_ELECTRA * 2 ) {
384
+ break slot;
402
385
}
403
386
}
404
387
}
@@ -440,6 +423,53 @@ export class AggregatedAttestationPool {
440
423
}
441
424
return attestations ;
442
425
}
426
+
427
+ private onScrapeMetrics ( { opPool} : Metrics ) : void {
428
+ const metrics = opPool . aggregatedAttestationPool ;
429
+ const allSlots = Array . from ( this . attestationGroupByIndexByDataHexBySlot . keys ( ) ) ;
430
+
431
+ // always record the previous slot because the current slot may not be finished yet, we may receive more attestations
432
+ if ( allSlots . length > 1 ) {
433
+ // last item is current slot, we want the previous one
434
+ const previousSlot = allSlots . at ( - 2 ) ;
435
+ if ( previousSlot == null ) {
436
+ // only happen right after we start the node
437
+ return ;
438
+ }
439
+
440
+ const groupByIndexByDataHex = this . attestationGroupByIndexByDataHexBySlot . get ( previousSlot ) ;
441
+ if ( groupByIndexByDataHex != null ) {
442
+ metrics . attDataPerSlot . set ( groupByIndexByDataHex . size ) ;
443
+
444
+ let maxAttestations = 0 ;
445
+ let committeeCount = 0 ;
446
+ for ( const groupByIndex of groupByIndexByDataHex . values ( ) ) {
447
+ for ( const group of groupByIndex . values ( ) ) {
448
+ const attestationCount = group . getAttestationCount ( ) ;
449
+ maxAttestations = Math . max ( maxAttestations , attestationCount ) ;
450
+ metrics . attestationsPerCommittee . observe ( attestationCount ) ;
451
+ committeeCount += 1 ;
452
+ }
453
+ }
454
+ metrics . maxAttestationsPerCommittee . set ( maxAttestations ) ;
455
+ metrics . committeesPerSlot . set ( committeeCount ) ;
456
+ }
457
+ }
458
+
459
+ let attestationCount = 0 ;
460
+ let attestationDataCount = 0 ;
461
+ for ( const attestationGroupByIndexByDataHex of this . attestationGroupByIndexByDataHexBySlot . values ( ) ) {
462
+ for ( const attestationGroupByIndex of attestationGroupByIndexByDataHex . values ( ) ) {
463
+ attestationDataCount += attestationGroupByIndex . size ;
464
+ for ( const attestationGroup of attestationGroupByIndex . values ( ) ) {
465
+ attestationCount += attestationGroup . getAttestationCount ( ) ;
466
+ }
467
+ }
468
+ }
469
+
470
+ metrics . poolSize . set ( attestationCount ) ;
471
+ metrics . poolUniqueData . set ( attestationDataCount ) ;
472
+ }
443
473
}
444
474
445
475
interface AttestationWithIndex {
@@ -815,7 +845,14 @@ function isValidShuffling(
815
845
816
846
let attestationDependentRoot : string ;
817
847
try {
818
- attestationDependentRoot = forkChoice . getDependentRoot ( beaconBlock , EpochDifference . previous ) ;
848
+ // should not use forkChoice.getDependentRoot directly, see https://github.com/ChainSafe/lodestar/issues/7651
849
+ // attestationDependentRoot = forkChoice.getDependentRoot(beaconBlock, EpochDifference.previous);
850
+ attestationDependentRoot = getShufflingDependentRoot (
851
+ forkChoice ,
852
+ targetEpoch ,
853
+ computeEpochAtSlot ( beaconBlock . slot ) ,
854
+ beaconBlock
855
+ ) ;
819
856
} catch ( _ ) {
820
857
// getDependent root may throw error if the dependent root of attestation data is prior to finalized slot
821
858
// ignore this attestation data in that case since we're not sure it's compatible to the state
0 commit comments