@lodestar/beacon-node 1.29.0-dev.ae2b105931 → 1.29.0-dev.bb6038a1d1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/impl/beacon/blocks/index.d.ts +1 -1
- package/lib/api/impl/beacon/blocks/index.js +3 -3
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +2 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.js +12 -0
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/validator/index.js +10 -9
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js +3 -3
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +12 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/blocks/importBlock.js +3 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +2 -2
- package/lib/chain/blocks/types.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +2 -2
- package/lib/chain/blocks/verifyBlock.js +2 -2
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.d.ts +1 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.js +1 -2
- package/lib/chain/blocks/verifyBlocksDataAvailability.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +3 -2
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +4 -4
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +4 -1
- package/lib/chain/chain.js +15 -10
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -2
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -0
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts +32 -14
- package/lib/chain/opPools/aggregatedAttestationPool.js +245 -102
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/attestationPool.d.ts +4 -2
- package/lib/chain/opPools/attestationPool.js +4 -3
- package/lib/chain/opPools/attestationPool.js.map +1 -1
- package/lib/chain/options.d.ts +2 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +4 -4
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/regen/queued.d.ts +1 -0
- package/lib/chain/regen/queued.js +1 -0
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +3 -0
- package/lib/chain/regen/regen.js +5 -5
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/validation/attestation.js +6 -5
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/{metrics → chain}/validatorMonitor.d.ts +3 -2
- package/lib/{metrics → chain}/validatorMonitor.js +55 -52
- package/lib/chain/validatorMonitor.js.map +1 -0
- package/lib/metrics/metrics/beacon.js +1 -1
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +64 -13
- package/lib/metrics/metrics/lodestar.js +144 -31
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/metrics/metrics.d.ts +2 -6
- package/lib/metrics/metrics.js +2 -12
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/metrics/options.d.ts +2 -3
- package/lib/metrics/options.js.map +1 -1
- package/lib/network/core/metrics.d.ts +8 -1
- package/lib/network/core/metrics.js +10 -0
- package/lib/network/core/metrics.js.map +1 -1
- package/lib/network/core/networkCore.d.ts +3 -3
- package/lib/network/core/networkCore.js +9 -4
- package/lib/network/core/networkCore.js.map +1 -1
- package/lib/network/core/networkCoreWorker.js +5 -3
- package/lib/network/core/networkCoreWorker.js.map +1 -1
- package/lib/network/core/networkCoreWorkerHandler.d.ts +2 -2
- package/lib/network/core/networkCoreWorkerHandler.js +3 -3
- package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
- package/lib/network/core/types.d.ts +1 -1
- package/lib/network/discv5/index.d.ts +2 -3
- package/lib/network/discv5/index.js +4 -5
- package/lib/network/discv5/index.js.map +1 -1
- package/lib/network/discv5/types.d.ts +1 -1
- package/lib/network/discv5/worker.js +7 -6
- package/lib/network/discv5/worker.js.map +1 -1
- package/lib/network/gossip/gossipsub.js +4 -0
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/interface.d.ts +3 -2
- package/lib/network/libp2p/error.d.ts +51 -0
- package/lib/network/libp2p/error.js +56 -0
- package/lib/network/libp2p/error.js.map +1 -0
- package/lib/network/libp2p/index.d.ts +2 -2
- package/lib/network/libp2p/index.js +15 -9
- package/lib/network/libp2p/index.js.map +1 -1
- package/lib/network/network.d.ts +4 -4
- package/lib/network/network.js +7 -5
- package/lib/network/network.js.map +1 -1
- package/lib/network/peers/datastore.d.ts +2 -1
- package/lib/network/peers/datastore.js +1 -1
- package/lib/network/peers/datastore.js.map +1 -1
- package/lib/network/peers/discover.d.ts +7 -0
- package/lib/network/peers/discover.js +16 -5
- package/lib/network/peers/discover.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts +2 -1
- package/lib/network/peers/peerManager.js +1 -1
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/utils/getConnectedPeerIds.js +2 -2
- package/lib/network/peers/utils/getConnectedPeerIds.js.map +1 -1
- package/lib/network/processor/gossipHandlers.js +8 -7
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/util.d.ts +4 -1
- package/lib/network/util.js +2 -2
- package/lib/network/util.js.map +1 -1
- package/lib/node/nodejs.d.ts +7 -4
- package/lib/node/nodejs.js +11 -4
- package/lib/node/nodejs.js.map +1 -1
- package/lib/node/options.d.ts +2 -0
- package/lib/node/options.js +2 -0
- package/lib/node/options.js.map +1 -1
- package/lib/util/peerId.js +1 -1
- package/lib/util/peerId.js.map +1 -1
- package/package.json +31 -32
- package/lib/metrics/validatorMonitor.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { aggregateSignatures } from "@chainsafe/blst";
|
|
2
2
|
import { BitArray } from "@chainsafe/ssz";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { computeEpochAtSlot, computeStartSlotAtEpoch, getBlockRootAtSlot, } from "@lodestar/state-transition";
|
|
3
|
+
import { ForkName, ForkSeq, MAX_ATTESTATIONS, MAX_ATTESTATIONS_ELECTRA, MAX_COMMITTEES_PER_SLOT, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH, isForkPostDeneb, isForkPostElectra, } from "@lodestar/params";
|
|
4
|
+
import { computeEpochAtSlot, computeSlotsSinceEpochStart, computeStartSlotAtEpoch, getBlockRootAtSlot, } from "@lodestar/state-transition";
|
|
6
5
|
import { isElectraAttestation, ssz, } from "@lodestar/types";
|
|
7
6
|
import { assert, MapDef, toRootHex } from "@lodestar/utils";
|
|
8
7
|
import { IntersectResult, intersectUint8Arrays } from "../../util/bitArray.js";
|
|
8
|
+
import { getShufflingDependentRoot } from "../../util/dependentRoot.js";
|
|
9
9
|
import { InsertOutcome } from "./types.js";
|
|
10
10
|
import { pruneBySlot, signatureFromBytesNoCheck } from "./utils.js";
|
|
11
11
|
/**
|
|
@@ -15,6 +15,14 @@ import { pruneBySlot, signatureFromBytesNoCheck } from "./utils.js";
|
|
|
15
15
|
* how does participation looks like in attestations.
|
|
16
16
|
*/
|
|
17
17
|
const MAX_RETAINED_ATTESTATIONS_PER_GROUP = 4;
|
|
18
|
+
/**
|
|
19
|
+
* This is the same to MAX_RETAINED_ATTESTATIONS_PER_GROUP but for electra.
|
|
20
|
+
* As monitored in hoodi, max attestations per group could be up to > 10. But since electra we can
|
|
21
|
+
* consolidate attestations across committees, so we can just pick up to 8 attestations per group.
|
|
22
|
+
* Also the MatchingDataAttestationGroup.getAttestationsForBlock() is improved not to have to scan each
|
|
23
|
+
* committee member for previous slot.
|
|
24
|
+
*/
|
|
25
|
+
const MAX_RETAINED_ATTESTATIONS_PER_GROUP_ELECTRA = 8;
|
|
18
26
|
/**
|
|
19
27
|
* Pre-electra, each slot has 64 committees, and each block has 128 attestations max so in average
|
|
20
28
|
* we get 2 attestation per groups.
|
|
@@ -24,20 +32,30 @@ const MAX_RETAINED_ATTESTATIONS_PER_GROUP = 4;
|
|
|
24
32
|
*/
|
|
25
33
|
const MAX_ATTESTATIONS_PER_GROUP = 3;
|
|
26
34
|
/**
|
|
27
|
-
* For electra,
|
|
28
|
-
*
|
|
29
|
-
*
|
|
35
|
+
* For electra, there is on chain aggregation of attestations across committees, so we can just pick up to 8
|
|
36
|
+
* attestations per group, sort by scores to get first 8.
|
|
37
|
+
* The new algorithm helps not to include useless attestations so we usually cannot get up to 8.
|
|
38
|
+
* The more consolidations we have per block, the less likely we have to scan all slots in the pool.
|
|
39
|
+
* This is max attestations returned per group, it does not make sense to have this number greater
|
|
40
|
+
* than MAX_RETAINED_ATTESTATIONS_PER_GROUP_ELECTRA or MAX_ATTESTATIONS_ELECTRA.
|
|
30
41
|
*/
|
|
31
|
-
const MAX_ATTESTATIONS_PER_GROUP_ELECTRA =
|
|
42
|
+
const MAX_ATTESTATIONS_PER_GROUP_ELECTRA = Math.min(MAX_RETAINED_ATTESTATIONS_PER_GROUP_ELECTRA, MAX_ATTESTATIONS_ELECTRA);
|
|
43
|
+
export var ScannedSlotsTerminationReason;
|
|
44
|
+
(function (ScannedSlotsTerminationReason) {
|
|
45
|
+
ScannedSlotsTerminationReason["MaxConsolidationReached"] = "max_consolidation_reached";
|
|
46
|
+
ScannedSlotsTerminationReason["ScannedAllSlots"] = "scanned_all_slots";
|
|
47
|
+
ScannedSlotsTerminationReason["SlotBeforePreviousEpoch"] = "slot_before_previous_epoch";
|
|
48
|
+
})(ScannedSlotsTerminationReason || (ScannedSlotsTerminationReason = {}));
|
|
32
49
|
/**
|
|
33
50
|
* Maintain a pool of aggregated attestations. Attestations can be retrieved for inclusion in a block
|
|
34
|
-
* or api. The returned attestations are aggregated to
|
|
51
|
+
* or api. The returned attestations are aggregated to maximize the number of validators that can be
|
|
35
52
|
* included.
|
|
36
53
|
* Note that we want to remove attestations with attesters that were included in the chain.
|
|
37
54
|
*/
|
|
38
55
|
export class AggregatedAttestationPool {
|
|
39
|
-
constructor(config) {
|
|
56
|
+
constructor(config, metrics = null) {
|
|
40
57
|
this.config = config;
|
|
58
|
+
this.metrics = metrics;
|
|
41
59
|
/**
|
|
42
60
|
* post electra, different committees could have the same AttData and we have to consolidate attestations of the same
|
|
43
61
|
* data to be included in block, so we should group by data before index
|
|
@@ -45,20 +63,7 @@ export class AggregatedAttestationPool {
|
|
|
45
63
|
*/
|
|
46
64
|
this.attestationGroupByIndexByDataHexBySlot = new MapDef(() => new Map());
|
|
47
65
|
this.lowestPermissibleSlot = 0;
|
|
48
|
-
|
|
49
|
-
/** For metrics to track size of the pool */
|
|
50
|
-
getAttestationCount() {
|
|
51
|
-
let attestationCount = 0;
|
|
52
|
-
let attestationDataCount = 0;
|
|
53
|
-
for (const attestationGroupByIndexByDataHex of this.attestationGroupByIndexByDataHexBySlot.values()) {
|
|
54
|
-
for (const attestationGroupByIndex of attestationGroupByIndexByDataHex.values()) {
|
|
55
|
-
attestationDataCount += attestationGroupByIndex.size;
|
|
56
|
-
for (const attestationGroup of attestationGroupByIndex.values()) {
|
|
57
|
-
attestationCount += attestationGroup.getAttestationCount();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return { attestationCount, attestationDataCount };
|
|
66
|
+
metrics?.opPool.aggregatedAttestationPool.attDataPerSlot.addCollect(() => this.onScrapeMetrics(metrics));
|
|
62
67
|
}
|
|
63
68
|
add(attestation, dataRootHex, attestingIndicesCount, committee) {
|
|
64
69
|
const slot = attestation.data.slot;
|
|
@@ -90,7 +95,7 @@ export class AggregatedAttestationPool {
|
|
|
90
95
|
assert.notNull(committeeIndex, "Committee index should not be null in aggregated attestation pool");
|
|
91
96
|
let attestationGroup = attestationGroupByIndex.get(committeeIndex);
|
|
92
97
|
if (!attestationGroup) {
|
|
93
|
-
attestationGroup = new MatchingDataAttestationGroup(committee, attestation.data);
|
|
98
|
+
attestationGroup = new MatchingDataAttestationGroup(this.config, committee, attestation.data);
|
|
94
99
|
attestationGroupByIndex.set(committeeIndex, attestationGroup);
|
|
95
100
|
}
|
|
96
101
|
return attestationGroup.add({
|
|
@@ -100,9 +105,14 @@ export class AggregatedAttestationPool {
|
|
|
100
105
|
}
|
|
101
106
|
/** Remove attestations which are too old to be included in a block. */
|
|
102
107
|
prune(clockSlot) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
const fork = this.config.getForkName(clockSlot);
|
|
109
|
+
const slotsToRetain = isForkPostDeneb(fork)
|
|
110
|
+
? // Post deneb, attestations from current and previous epoch can be included
|
|
111
|
+
computeSlotsSinceEpochStart(clockSlot, computeEpochAtSlot(clockSlot) - 1)
|
|
112
|
+
: // Before deneb, only retain SLOTS_PER_EPOCH slots
|
|
113
|
+
SLOTS_PER_EPOCH;
|
|
114
|
+
pruneBySlot(this.attestationGroupByIndexByDataHexBySlot, clockSlot, slotsToRetain);
|
|
115
|
+
this.lowestPermissibleSlot = Math.max(clockSlot - slotsToRetain, 0);
|
|
106
116
|
}
|
|
107
117
|
getAttestationsForBlock(fork, forkChoice, state) {
|
|
108
118
|
const forkSeq = ForkSeq[fork];
|
|
@@ -141,16 +151,16 @@ export class AggregatedAttestationPool {
|
|
|
141
151
|
(ForkSeq[fork] >= ForkSeq.deneb || stateSlot <= slot + SLOTS_PER_EPOCH))) {
|
|
142
152
|
continue; // Invalid attestations
|
|
143
153
|
}
|
|
144
|
-
const
|
|
154
|
+
const inclusionDistance = stateSlot - slot;
|
|
145
155
|
for (const attestationGroupByIndex of attestationGroupByIndexByDataHash.values()) {
|
|
146
156
|
for (const [committeeIndex, attestationGroup] of attestationGroupByIndex.entries()) {
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
157
|
+
const notSeenCommitteeMembers = notSeenValidatorsFn(epoch, slot, committeeIndex);
|
|
158
|
+
if (notSeenCommitteeMembers === null || notSeenCommitteeMembers.size === 0) {
|
|
149
159
|
continue;
|
|
150
160
|
}
|
|
151
161
|
if (slotCount > 2 &&
|
|
152
162
|
attestationsByScore.length >= MAX_ATTESTATIONS &&
|
|
153
|
-
|
|
163
|
+
notSeenCommitteeMembers.size / inclusionDistance < minScore) {
|
|
154
164
|
// after 2 slots, there are a good chance that we have 2 * MAX_ATTESTATIONS attestations and break the for loop early
|
|
155
165
|
// if not, we may have to scan all slots in the pool
|
|
156
166
|
// if we have enough attestations and the max possible score is lower than scores of `attestationsByScore`, we should skip
|
|
@@ -167,8 +177,9 @@ export class AggregatedAttestationPool {
|
|
|
167
177
|
// These properties should not change after being validate in gossip
|
|
168
178
|
// IF they have to be validated, do it only with one attestation per group since same data
|
|
169
179
|
// The committeeCountPerSlot can be precomputed once per slot
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
const getAttestationsResult = attestationGroup.getAttestationsForBlock(fork, state.epochCtx.effectiveBalanceIncrements, notSeenCommitteeMembers, MAX_ATTESTATIONS_PER_GROUP);
|
|
181
|
+
for (const { attestation, newSeenEffectiveBalance } of getAttestationsResult.result) {
|
|
182
|
+
const score = newSeenEffectiveBalance / inclusionDistance;
|
|
172
183
|
if (score < minScore) {
|
|
173
184
|
minScore = score;
|
|
174
185
|
}
|
|
@@ -207,49 +218,56 @@ export class AggregatedAttestationPool {
|
|
|
207
218
|
const slots = Array.from(this.attestationGroupByIndexByDataHexBySlot.keys()).sort((a, b) => b - a);
|
|
208
219
|
// Track score of each `AttestationsConsolidation`
|
|
209
220
|
const consolidations = new Map();
|
|
210
|
-
let
|
|
211
|
-
let
|
|
221
|
+
let scannedSlots = 0;
|
|
222
|
+
let stopReason = null;
|
|
212
223
|
slot: for (const slot of slots) {
|
|
213
|
-
slotCount++;
|
|
214
224
|
const attestationGroupByIndexByDataHash = this.attestationGroupByIndexByDataHexBySlot.get(slot);
|
|
215
225
|
// should not happen
|
|
216
226
|
if (!attestationGroupByIndexByDataHash) {
|
|
217
227
|
throw Error(`No aggregated attestation pool for slot=${slot}`);
|
|
218
228
|
}
|
|
219
229
|
const epoch = computeEpochAtSlot(slot);
|
|
230
|
+
if (epoch < statePrevEpoch) {
|
|
231
|
+
// we process slot in desc order, this means next slot is not eligible, we should stop
|
|
232
|
+
stopReason = ScannedSlotsTerminationReason.SlotBeforePreviousEpoch;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
220
235
|
// validateAttestation condition: Attestation target epoch not in previous or current epoch
|
|
221
236
|
if (!(epoch === stateEpoch || epoch === statePrevEpoch)) {
|
|
222
237
|
continue; // Invalid attestations
|
|
223
238
|
}
|
|
224
239
|
// validateAttestation condition: Attestation slot not within inclusion window
|
|
225
240
|
if (!(slot + MIN_ATTESTATION_INCLUSION_DELAY <= stateSlot)) {
|
|
241
|
+
// this should not happen as slot is decreased so no need to track in metric
|
|
226
242
|
continue; // Invalid attestations
|
|
227
243
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
244
|
+
const inclusionDistance = stateSlot - slot;
|
|
245
|
+
let returnedAttestationsPerSlot = 0;
|
|
246
|
+
let totalAttestationsPerSlot = 0;
|
|
247
|
+
// CommitteeIndex 0 1 2 ... Consolidation (sameAttDataCons)
|
|
230
248
|
// Attestations att00 --- att10 --- att20 --- 0 (att 00 10 20)
|
|
231
249
|
// att01 --- - --- att21 --- 1 (att 01 __ 21)
|
|
232
250
|
// - --- - --- att22 --- 2 (att __ __ 22)
|
|
233
251
|
for (const attestationGroupByIndex of attestationGroupByIndexByDataHash.values()) {
|
|
234
252
|
// sameAttDataCons could be up to MAX_ATTESTATIONS_PER_GROUP_ELECTRA
|
|
235
253
|
const sameAttDataCons = [];
|
|
254
|
+
const allAttestationGroups = Array.from(attestationGroupByIndex.values());
|
|
255
|
+
if (allAttestationGroups.length === 0) {
|
|
256
|
+
this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.emptyAttestationData.inc();
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (!validateAttestationDataFn(allAttestationGroups[0].data)) {
|
|
260
|
+
this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.invalidAttestationData.inc();
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
236
263
|
for (const [committeeIndex, attestationGroup] of attestationGroupByIndex.entries()) {
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
if (slotCount > 2 &&
|
|
242
|
-
consolidations.size >= MAX_ATTESTATIONS_ELECTRA &&
|
|
243
|
-
notSeenAttestingIndices.size / slotDelta < minScore) {
|
|
244
|
-
// after 2 slots, there are a good chance that we have 2 * MAX_ATTESTATIONS_ELECTRA attestations and break the for loop early
|
|
245
|
-
// if not, we may have to scan all slots in the pool
|
|
246
|
-
// if we have enough attestations and the max possible score is lower than scores of `attestationsByScore`, we should skip
|
|
247
|
-
// otherwise it takes time to check attestation, add it and remove it later after the sort by score
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
if (!validateAttestationDataFn(attestationGroup.data)) {
|
|
264
|
+
const notSeenCommitteeMembers = notSeenValidatorsFn(epoch, slot, committeeIndex);
|
|
265
|
+
if (notSeenCommitteeMembers === null || notSeenCommitteeMembers.size === 0) {
|
|
266
|
+
this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.seenCommittees.inc();
|
|
251
267
|
continue;
|
|
252
268
|
}
|
|
269
|
+
// cannot apply this optimization like pre-electra because consolidation needs to be done across committees:
|
|
270
|
+
// "after 2 slots, there are a good chance that we have 2 * MAX_ATTESTATIONS_ELECTRA attestations and break the for loop early"
|
|
253
271
|
// TODO: Is it necessary to validateAttestation for:
|
|
254
272
|
// - Attestation committee index not within current committee count
|
|
255
273
|
// - Attestation aggregation bits length does not match committee length
|
|
@@ -257,39 +275,73 @@ export class AggregatedAttestationPool {
|
|
|
257
275
|
// These properties should not change after being validate in gossip
|
|
258
276
|
// IF they have to be validated, do it only with one attestation per group since same data
|
|
259
277
|
// The committeeCountPerSlot can be precomputed once per slot
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
278
|
+
const getAttestationGroupResult = attestationGroup.getAttestationsForBlock(fork, state.epochCtx.effectiveBalanceIncrements, notSeenCommitteeMembers, MAX_ATTESTATIONS_PER_GROUP_ELECTRA);
|
|
279
|
+
const attestationsSameGroup = getAttestationGroupResult.result;
|
|
280
|
+
returnedAttestationsPerSlot += attestationsSameGroup.length;
|
|
281
|
+
totalAttestationsPerSlot += getAttestationGroupResult.totalAttestations;
|
|
282
|
+
for (const [i, attestationNonParticipation] of attestationsSameGroup.entries()) {
|
|
283
|
+
// sameAttDataCons shares the same index for different committees so we use index `i` here
|
|
263
284
|
if (sameAttDataCons[i] === undefined) {
|
|
264
285
|
sameAttDataCons[i] = {
|
|
265
286
|
byCommittee: new Map(),
|
|
266
287
|
attData: attestationNonParticipation.attestation.data,
|
|
267
|
-
|
|
288
|
+
totalNewSeenEffectiveBalance: 0,
|
|
289
|
+
newSeenAttesters: 0,
|
|
290
|
+
notSeenAttesters: 0,
|
|
291
|
+
totalAttesters: 0,
|
|
268
292
|
};
|
|
269
293
|
}
|
|
270
|
-
sameAttDataCons[i]
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
consolidations.set(consolidation, score);
|
|
279
|
-
// Stop accumulating attestations there are enough that may have good scoring
|
|
280
|
-
if (consolidations.size >= MAX_ATTESTATIONS_ELECTRA * 2) {
|
|
281
|
-
break slot;
|
|
294
|
+
const sameAttDataCon = sameAttDataCons[i];
|
|
295
|
+
// committeeIndex was from a map so it should be unique, but just in case
|
|
296
|
+
if (!sameAttDataCon.byCommittee.has(committeeIndex)) {
|
|
297
|
+
sameAttDataCon.byCommittee.set(committeeIndex, attestationNonParticipation);
|
|
298
|
+
sameAttDataCon.totalNewSeenEffectiveBalance += attestationNonParticipation.newSeenEffectiveBalance;
|
|
299
|
+
sameAttDataCon.newSeenAttesters += attestationNonParticipation.newSeenAttesters;
|
|
300
|
+
sameAttDataCon.notSeenAttesters += attestationNonParticipation.notSeenCommitteeMembers.size;
|
|
301
|
+
sameAttDataCon.totalAttesters += attestationGroup.committee.length;
|
|
282
302
|
}
|
|
283
303
|
}
|
|
304
|
+
} // all committees are processed
|
|
305
|
+
this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.returnedAttestations.set({ inclusionDistance }, returnedAttestationsPerSlot);
|
|
306
|
+
this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.scannedAttestations.set({ inclusionDistance }, totalAttestationsPerSlot);
|
|
307
|
+
// after all committees are processed, we have a list of sameAttDataCons
|
|
308
|
+
for (const consolidation of sameAttDataCons) {
|
|
309
|
+
const score = consolidation.totalNewSeenEffectiveBalance / inclusionDistance;
|
|
310
|
+
consolidations.set(consolidation, score);
|
|
311
|
+
// Stop accumulating attestations there are enough that may have good scoring
|
|
312
|
+
if (consolidations.size >= MAX_ATTESTATIONS_ELECTRA * 2) {
|
|
313
|
+
stopReason = ScannedSlotsTerminationReason.MaxConsolidationReached;
|
|
314
|
+
break slot;
|
|
315
|
+
}
|
|
284
316
|
}
|
|
285
317
|
}
|
|
318
|
+
// finished processing a slot
|
|
319
|
+
scannedSlots++;
|
|
286
320
|
}
|
|
321
|
+
this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.totalConsolidations.set(consolidations.size);
|
|
287
322
|
const sortedConsolidationsByScore = Array.from(consolidations.entries())
|
|
288
323
|
.sort((a, b) => b[1] - a[1])
|
|
289
324
|
.map(([consolidation, _]) => consolidation)
|
|
290
325
|
.slice(0, MAX_ATTESTATIONS_ELECTRA);
|
|
291
326
|
// on chain aggregation is expensive, only do it after all
|
|
292
|
-
|
|
327
|
+
const packedAttestationsMetrics = this.metrics?.opPool.aggregatedAttestationPool.packedAttestations;
|
|
328
|
+
const packedAttestations = new Array(sortedConsolidationsByScore.length);
|
|
329
|
+
for (const [i, consolidation] of sortedConsolidationsByScore.entries()) {
|
|
330
|
+
packedAttestations[i] = aggregateConsolidation(consolidation);
|
|
331
|
+
// record metrics of packed attestations
|
|
332
|
+
packedAttestationsMetrics?.committeeCount.set({ index: i }, consolidation.byCommittee.size);
|
|
333
|
+
packedAttestationsMetrics?.totalAttesters.set({ index: i }, consolidation.totalAttesters);
|
|
334
|
+
packedAttestationsMetrics?.nonParticipation.set({ index: i }, consolidation.notSeenAttesters);
|
|
335
|
+
packedAttestationsMetrics?.inclusionDistance.set({ index: i }, stateSlot - packedAttestations[i].data.slot);
|
|
336
|
+
packedAttestationsMetrics?.newSeenAttesters.set({ index: i }, consolidation.newSeenAttesters);
|
|
337
|
+
packedAttestationsMetrics?.totalEffectiveBalance.set({ index: i }, consolidation.totalNewSeenEffectiveBalance);
|
|
338
|
+
}
|
|
339
|
+
if (stopReason === null) {
|
|
340
|
+
stopReason = ScannedSlotsTerminationReason.ScannedAllSlots;
|
|
341
|
+
}
|
|
342
|
+
packedAttestationsMetrics?.scannedSlots.set({ reason: stopReason }, scannedSlots);
|
|
343
|
+
packedAttestationsMetrics?.poolSlots.set(slots.length);
|
|
344
|
+
return packedAttestations;
|
|
293
345
|
}
|
|
294
346
|
/**
|
|
295
347
|
* Get all attestations optionally filtered by `attestation.data.slot`
|
|
@@ -318,6 +370,49 @@ export class AggregatedAttestationPool {
|
|
|
318
370
|
}
|
|
319
371
|
return attestations;
|
|
320
372
|
}
|
|
373
|
+
onScrapeMetrics(metrics) {
|
|
374
|
+
const poolMetrics = metrics.opPool.aggregatedAttestationPool;
|
|
375
|
+
const allSlots = Array.from(this.attestationGroupByIndexByDataHexBySlot.keys());
|
|
376
|
+
// last item is current slot, we want the previous one, if available.
|
|
377
|
+
const previousSlot = allSlots.length > 1 ? (allSlots.at(-2) ?? null) : null;
|
|
378
|
+
let attestationCount = 0;
|
|
379
|
+
let attestationDataCount = 0;
|
|
380
|
+
// always record the previous slot because the current slot may not be finished yet, we may receive more attestations
|
|
381
|
+
if (previousSlot !== null) {
|
|
382
|
+
const groupByIndexByDataHex = this.attestationGroupByIndexByDataHexBySlot.get(previousSlot);
|
|
383
|
+
if (groupByIndexByDataHex != null) {
|
|
384
|
+
poolMetrics.attDataPerSlot.set(groupByIndexByDataHex.size);
|
|
385
|
+
let maxAttestations = 0;
|
|
386
|
+
let committeeCount = 0;
|
|
387
|
+
for (const groupByIndex of groupByIndexByDataHex.values()) {
|
|
388
|
+
attestationDataCount += groupByIndex.size;
|
|
389
|
+
for (const group of groupByIndex.values()) {
|
|
390
|
+
const attestationCountInGroup = group.getAttestationCount();
|
|
391
|
+
maxAttestations = Math.max(maxAttestations, attestationCountInGroup);
|
|
392
|
+
poolMetrics.attestationsPerCommittee.observe(attestationCountInGroup);
|
|
393
|
+
committeeCount += 1;
|
|
394
|
+
attestationCount += attestationCountInGroup;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
poolMetrics.maxAttestationsPerCommittee.set(maxAttestations);
|
|
398
|
+
poolMetrics.committeesPerSlot.set(committeeCount);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (const [slot, attestationGroupByIndexByDataHex] of this.attestationGroupByIndexByDataHexBySlot) {
|
|
402
|
+
// We have already updated attestationDataCount and attestationCount when looping over `previousSlot`
|
|
403
|
+
if (slot === previousSlot) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
for (const attestationGroupByIndex of attestationGroupByIndexByDataHex.values()) {
|
|
407
|
+
attestationDataCount += attestationGroupByIndex.size;
|
|
408
|
+
for (const attestationGroup of attestationGroupByIndex.values()) {
|
|
409
|
+
attestationCount += attestationGroup.getAttestationCount();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
poolMetrics.size.set(attestationCount);
|
|
414
|
+
poolMetrics.uniqueData.set(attestationDataCount);
|
|
415
|
+
}
|
|
321
416
|
}
|
|
322
417
|
/**
|
|
323
418
|
* Maintain a pool of AggregatedAttestation which all share the same AttestationData.
|
|
@@ -326,9 +421,8 @@ export class AggregatedAttestationPool {
|
|
|
326
421
|
* Use committee instead of aggregationBits to improve performance.
|
|
327
422
|
*/
|
|
328
423
|
export class MatchingDataAttestationGroup {
|
|
329
|
-
constructor(
|
|
330
|
-
|
|
331
|
-
committee, data) {
|
|
424
|
+
constructor(config, committee, data) {
|
|
425
|
+
this.config = config;
|
|
332
426
|
this.committee = committee;
|
|
333
427
|
this.data = data;
|
|
334
428
|
this.attestations = [];
|
|
@@ -368,42 +462,86 @@ export class MatchingDataAttestationGroup {
|
|
|
368
462
|
this.attestations.splice(index, 1);
|
|
369
463
|
}
|
|
370
464
|
this.attestations.push(attestation);
|
|
465
|
+
const maxRetained = isForkPostElectra(this.config.getForkName(this.data.slot))
|
|
466
|
+
? MAX_RETAINED_ATTESTATIONS_PER_GROUP_ELECTRA
|
|
467
|
+
: MAX_RETAINED_ATTESTATIONS_PER_GROUP;
|
|
371
468
|
// Remove the attestations with less participation
|
|
372
|
-
if (this.attestations.length >
|
|
469
|
+
if (this.attestations.length > maxRetained) {
|
|
470
|
+
// ideally we should sort by effective balance but there is no state/effectiveBalance here
|
|
471
|
+
// it's rare to see > 8 attestations per group in electra anyway
|
|
373
472
|
this.attestations.sort((a, b) => b.trueBitsCount - a.trueBitsCount);
|
|
374
|
-
this.attestations.splice(
|
|
473
|
+
this.attestations.splice(maxRetained, this.attestations.length - maxRetained);
|
|
375
474
|
}
|
|
376
475
|
return InsertOutcome.NewData;
|
|
377
476
|
}
|
|
378
477
|
/**
|
|
379
478
|
* Get AttestationNonParticipant for this groups of same attestation data.
|
|
380
|
-
* @param
|
|
479
|
+
* @param notSeenCommitteeMembers not seen committee members, i.e. indices in the same committee (starting from 0 till (committee.size - 1))
|
|
381
480
|
* @returns an array of AttestationNonParticipant
|
|
382
481
|
*/
|
|
383
|
-
getAttestationsForBlock(fork,
|
|
482
|
+
getAttestationsForBlock(fork, effectiveBalanceIncrements, notSeenCommitteeMembers, maxAttestation) {
|
|
384
483
|
const attestations = [];
|
|
484
|
+
const excluded = new Set();
|
|
485
|
+
for (let i = 0; i < maxAttestation; i++) {
|
|
486
|
+
const mostValuableAttestation = this.getMostValuableAttestation(fork, effectiveBalanceIncrements, notSeenCommitteeMembers, excluded);
|
|
487
|
+
if (mostValuableAttestation === null) {
|
|
488
|
+
// stop looking for attestation because all attesters are seen or no attestation has missing attesters
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
attestations.push(mostValuableAttestation);
|
|
492
|
+
excluded.add(mostValuableAttestation.attestation);
|
|
493
|
+
// this will narrow down the notSeenCommitteeMembers for the next iteration
|
|
494
|
+
// so usually it will not take much time, however it could take more time during
|
|
495
|
+
// non-finality of the network when there is low participation, but in this case
|
|
496
|
+
// we pre-aggregate aggregated attestations and bound the total attestations per group
|
|
497
|
+
notSeenCommitteeMembers = mostValuableAttestation.notSeenCommitteeMembers;
|
|
498
|
+
}
|
|
499
|
+
return { result: attestations, totalAttestations: this.attestations.length };
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Select the attestation with the highest total effective balance of not seen validators.
|
|
503
|
+
*/
|
|
504
|
+
getMostValuableAttestation(fork, effectiveBalanceIncrements, notSeenCommitteeMembers, excluded) {
|
|
505
|
+
if (notSeenCommitteeMembers.size === 0) {
|
|
506
|
+
// no more attesters to consider
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
385
509
|
const isPostElectra = isForkPostElectra(fork);
|
|
510
|
+
let maxNewSeenEffectiveBalance = 0;
|
|
511
|
+
let mostValuableAttestation = null;
|
|
386
512
|
for (const { attestation } of this.attestations) {
|
|
387
513
|
if ((isPostElectra && !isElectraAttestation(attestation)) ||
|
|
388
514
|
(!isPostElectra && isElectraAttestation(attestation))) {
|
|
389
515
|
continue;
|
|
390
516
|
}
|
|
391
|
-
|
|
517
|
+
if (excluded.has(attestation)) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const notSeen = new Set();
|
|
521
|
+
// we prioritize total effective balance over attester count
|
|
522
|
+
let newSeenEffectiveBalance = 0;
|
|
523
|
+
let newSeenAttesters = 0;
|
|
392
524
|
const { aggregationBits } = attestation;
|
|
393
|
-
for (const notSeenIndex of
|
|
525
|
+
for (const notSeenIndex of notSeenCommitteeMembers) {
|
|
394
526
|
if (aggregationBits.get(notSeenIndex)) {
|
|
395
|
-
|
|
527
|
+
newSeenEffectiveBalance += effectiveBalanceIncrements[this.committee[notSeenIndex]];
|
|
528
|
+
newSeenAttesters++;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
notSeen.add(notSeenIndex);
|
|
396
532
|
}
|
|
397
533
|
}
|
|
398
|
-
if (
|
|
399
|
-
|
|
534
|
+
if (newSeenEffectiveBalance > maxNewSeenEffectiveBalance) {
|
|
535
|
+
maxNewSeenEffectiveBalance = newSeenEffectiveBalance;
|
|
536
|
+
mostValuableAttestation = {
|
|
537
|
+
attestation,
|
|
538
|
+
newSeenEffectiveBalance,
|
|
539
|
+
newSeenAttesters,
|
|
540
|
+
notSeenCommitteeMembers: notSeen,
|
|
541
|
+
};
|
|
400
542
|
}
|
|
401
543
|
}
|
|
402
|
-
|
|
403
|
-
if (attestations.length <= maxAttestation) {
|
|
404
|
-
return attestations;
|
|
405
|
-
}
|
|
406
|
-
return attestations.sort((a, b) => b.notSeenAttesterCount - a.notSeenAttesterCount).slice(0, maxAttestation);
|
|
544
|
+
return mostValuableAttestation;
|
|
407
545
|
}
|
|
408
546
|
/** Get attestations for API. */
|
|
409
547
|
getAttestations() {
|
|
@@ -449,13 +587,14 @@ export function aggregateConsolidation({ byCommittee, attData }) {
|
|
|
449
587
|
* has already attested or not.
|
|
450
588
|
*/
|
|
451
589
|
export function getNotSeenValidatorsFn(state) {
|
|
452
|
-
|
|
590
|
+
const stateSlot = state.slot;
|
|
591
|
+
if (state.config.getForkName(stateSlot) === ForkName.phase0) {
|
|
453
592
|
// Get attestations to be included in a phase0 block.
|
|
454
593
|
// As we are close to altair, this is not really important, it's mainly for e2e.
|
|
455
594
|
// The performance is not great due to the different BeaconState data structure to altair.
|
|
456
595
|
// check for phase0 block already
|
|
457
596
|
const phase0State = state;
|
|
458
|
-
const stateEpoch = computeEpochAtSlot(
|
|
597
|
+
const stateEpoch = computeEpochAtSlot(stateSlot);
|
|
459
598
|
const previousEpochParticipants = extractParticipationPhase0(phase0State.previousEpochAttestations.getAllReadonly(), state);
|
|
460
599
|
const currentEpochParticipants = extractParticipationPhase0(phase0State.currentEpochAttestations.getAllReadonly(), state);
|
|
461
600
|
return (epoch, slot, committeeIndex) => {
|
|
@@ -464,13 +603,13 @@ export function getNotSeenValidatorsFn(state) {
|
|
|
464
603
|
return null;
|
|
465
604
|
}
|
|
466
605
|
const committee = state.epochCtx.getBeaconCommittee(slot, committeeIndex);
|
|
467
|
-
const
|
|
606
|
+
const notSeenCommitteeMembers = new Set();
|
|
468
607
|
for (const [i, validatorIndex] of committee.entries()) {
|
|
469
608
|
if (!participants.has(validatorIndex)) {
|
|
470
|
-
|
|
609
|
+
notSeenCommitteeMembers.add(i);
|
|
471
610
|
}
|
|
472
611
|
}
|
|
473
|
-
return
|
|
612
|
+
return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
|
|
474
613
|
};
|
|
475
614
|
}
|
|
476
615
|
// altair and future forks
|
|
@@ -481,7 +620,7 @@ export function getNotSeenValidatorsFn(state) {
|
|
|
481
620
|
const altairState = state;
|
|
482
621
|
const previousParticipation = altairState.previousEpochParticipation.getAll();
|
|
483
622
|
const currentParticipation = altairState.currentEpochParticipation.getAll();
|
|
484
|
-
const stateEpoch = computeEpochAtSlot(
|
|
623
|
+
const stateEpoch = computeEpochAtSlot(stateSlot);
|
|
485
624
|
// this function could be called multiple times with same slot + committeeIndex
|
|
486
625
|
const cachedNotSeenValidators = new Map();
|
|
487
626
|
return (epoch, slot, committeeIndex) => {
|
|
@@ -490,22 +629,23 @@ export function getNotSeenValidatorsFn(state) {
|
|
|
490
629
|
return null;
|
|
491
630
|
}
|
|
492
631
|
const cacheKey = slot + "_" + committeeIndex;
|
|
493
|
-
let
|
|
494
|
-
if (
|
|
632
|
+
let notSeenCommitteeMembers = cachedNotSeenValidators.get(cacheKey);
|
|
633
|
+
if (notSeenCommitteeMembers != null) {
|
|
495
634
|
// if all validators are seen then return null, we don't need to check for any attestations of same committee again
|
|
496
|
-
return
|
|
635
|
+
return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
|
|
497
636
|
}
|
|
498
637
|
const committee = state.epochCtx.getBeaconCommittee(slot, committeeIndex);
|
|
499
|
-
|
|
638
|
+
notSeenCommitteeMembers = new Set();
|
|
500
639
|
for (const [i, validatorIndex] of committee.entries()) {
|
|
501
640
|
// no need to check flagIsTimelySource as if validator is not seen, it's participation status is 0
|
|
502
|
-
|
|
503
|
-
|
|
641
|
+
// attestations for the previous slot are not included in the state, so we don't need to check for them
|
|
642
|
+
if (slot === stateSlot - 1 || participationStatus[validatorIndex] === 0) {
|
|
643
|
+
notSeenCommitteeMembers.add(i);
|
|
504
644
|
}
|
|
505
645
|
}
|
|
506
|
-
cachedNotSeenValidators.set(cacheKey,
|
|
646
|
+
cachedNotSeenValidators.set(cacheKey, notSeenCommitteeMembers);
|
|
507
647
|
// if all validators are seen then return null, we don't need to check for any attestations of same committee again
|
|
508
|
-
return
|
|
648
|
+
return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
|
|
509
649
|
};
|
|
510
650
|
}
|
|
511
651
|
export function extractParticipationPhase0(attestations, state) {
|
|
@@ -551,8 +691,9 @@ export function getValidateAttestationDataFn(forkChoice, state) {
|
|
|
551
691
|
else {
|
|
552
692
|
return false;
|
|
553
693
|
}
|
|
554
|
-
if (!ssz.phase0.Checkpoint.equals(attData.source, justifiedCheckpoint))
|
|
694
|
+
if (!ssz.phase0.Checkpoint.equals(attData.source, justifiedCheckpoint)) {
|
|
555
695
|
return false;
|
|
696
|
+
}
|
|
556
697
|
// Shuffling can't have changed if we're in the first few epochs
|
|
557
698
|
// Also we can't look back 2 epochs if target epoch is 1 or less
|
|
558
699
|
if (stateEpoch < 2 || targetEpoch < 2) {
|
|
@@ -618,7 +759,9 @@ function isValidShuffling(forkChoice, state, blockRootHex, targetEpoch) {
|
|
|
618
759
|
}
|
|
619
760
|
let attestationDependentRoot;
|
|
620
761
|
try {
|
|
621
|
-
|
|
762
|
+
// should not use forkChoice.getDependentRoot directly, see https://github.com/ChainSafe/lodestar/issues/7651
|
|
763
|
+
// attestationDependentRoot = forkChoice.getDependentRoot(beaconBlock, EpochDifference.previous);
|
|
764
|
+
attestationDependentRoot = getShufflingDependentRoot(forkChoice, targetEpoch, computeEpochAtSlot(beaconBlock.slot), beaconBlock);
|
|
622
765
|
}
|
|
623
766
|
catch (_) {
|
|
624
767
|
// getDependent root may throw error if the dependent root of attestation data is prior to finalized slot
|