@lodestar/beacon-node 1.51.0-dev.8d5cdd33be → 1.51.0-dev.8fdbd61e7e

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.
Files changed (41) hide show
  1. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  2. package/lib/chain/archiveStore/archiveStore.js +3 -4
  3. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  4. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +9 -2
  5. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  6. package/lib/chain/archiveStore/utils/pruneHistory.js +12 -4
  7. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  8. package/lib/chain/blocks/verifyBlocksSignatures.d.ts.map +1 -1
  9. package/lib/chain/blocks/verifyBlocksSignatures.js +3 -0
  10. package/lib/chain/blocks/verifyBlocksSignatures.js.map +1 -1
  11. package/lib/chain/bls/multithread/index.d.ts +1 -1
  12. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  13. package/lib/chain/bls/multithread/index.js +37 -11
  14. package/lib/chain/bls/multithread/index.js.map +1 -1
  15. package/lib/chain/validation/aggregateAndProof.js +1 -1
  16. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  17. package/lib/chain/validation/attestation.js +1 -1
  18. package/lib/chain/validation/attestation.js.map +1 -1
  19. package/lib/chain/validation/syncCommittee.d.ts.map +1 -1
  20. package/lib/chain/validation/syncCommittee.js +16 -10
  21. package/lib/chain/validation/syncCommittee.js.map +1 -1
  22. package/lib/chain/validation/syncCommitteeContributionAndProof.d.ts.map +1 -1
  23. package/lib/chain/validation/syncCommitteeContributionAndProof.js +11 -0
  24. package/lib/chain/validation/syncCommitteeContributionAndProof.js.map +1 -1
  25. package/lib/network/reqresp/handlers/beaconBlocksByHead.js +1 -1
  26. package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -1
  27. package/lib/sync/sync.d.ts +8 -0
  28. package/lib/sync/sync.d.ts.map +1 -1
  29. package/lib/sync/sync.js +42 -22
  30. package/lib/sync/sync.js.map +1 -1
  31. package/package.json +16 -16
  32. package/src/chain/archiveStore/archiveStore.ts +5 -4
  33. package/src/chain/archiveStore/utils/pruneHistory.ts +16 -6
  34. package/src/chain/blocks/verifyBlocksSignatures.ts +3 -0
  35. package/src/chain/bls/multithread/index.ts +34 -14
  36. package/src/chain/validation/aggregateAndProof.ts +1 -1
  37. package/src/chain/validation/attestation.ts +1 -1
  38. package/src/chain/validation/syncCommittee.ts +31 -15
  39. package/src/chain/validation/syncCommitteeContributionAndProof.ts +13 -0
  40. package/src/network/reqresp/handlers/beaconBlocksByHead.ts +1 -1
  41. package/src/sync/sync.ts +43 -22
@@ -72,7 +72,7 @@ async function validateAggregateAndProof(
72
72
  const attSlot = attData.slot;
73
73
 
74
74
  let committeeIndex: number | null;
75
- if (ForkSeq[fork] >= ForkSeq.gloas) {
75
+ if (ForkSeq[fork] >= ForkSeq.gloas && chain.config.getForkSeq(attSlot) >= ForkSeq.gloas) {
76
76
  // [REJECT] `aggregate.data.index < 2`.
77
77
  if (attData.index >= 2) {
78
78
  throw new AttestationError(GossipAction.REJECT, {
@@ -291,7 +291,7 @@ async function validateAttestationNoSignatureCheck(
291
291
  // api or first time validation of a gossip attestation
292
292
  committeeIndex = attestationOrCache.attestation.committeeIndex;
293
293
 
294
- if (isForkPostGloas(fork)) {
294
+ if (isForkPostGloas(fork) && isForkPostGloas(chain.config.getForkName(attSlot))) {
295
295
  // [REJECT] `attestation.data.index < 2`.
296
296
  if (attData.index >= 2) {
297
297
  throw new AttestationError(GossipAction.REJECT, {
@@ -1,7 +1,8 @@
1
1
  import {SYNC_COMMITTEE_SUBNET_COUNT, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
2
2
  import {IBeaconStateView, isStatePostAltair} from "@lodestar/state-transition";
3
- import {SubnetID, altair} from "@lodestar/types";
3
+ import {RootHex, Slot, SubnetID, ValidatorIndex, altair} from "@lodestar/types";
4
4
  import {toRootHex} from "@lodestar/utils";
5
+ import {Metrics} from "../../metrics/metrics.js";
5
6
  import {GossipAction, SyncCommitteeError, SyncCommitteeErrorCode} from "../errors/index.js";
6
7
  import {IBeaconChain} from "../interface.js";
7
8
  import {getSyncCommitteeSignatureSet} from "./signatureSets/index.js";
@@ -32,6 +33,33 @@ export async function validateGossipSyncCommittee(
32
33
 
33
34
  // [IGNORE] There has been no other valid sync committee signature for the declared slot for the validator referenced
34
35
  // by sync_committee_signature.validator_index.
36
+ assertSyncCommitteeMessageNotKnown(chain, slot, subnet, validatorIndex, messageRoot, chain.metrics);
37
+
38
+ // [REJECT] The subnet_id is valid for the given validator, i.e. subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index).
39
+ // Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee.
40
+ // > Checked in validateGossipSyncCommitteeExceptSig()
41
+
42
+ // [REJECT] The signature is valid for the message beacon_block_root for the validator referenced by validator_index.
43
+ await validateSyncCommitteeSigOnly(chain, headState, syncCommittee);
44
+
45
+ // Re-check after the await: concurrent messages from the same validator all pass the check above.
46
+ // Metrics were already counted by the first check.
47
+ assertSyncCommitteeMessageNotKnown(chain, slot, subnet, validatorIndex, messageRoot, null);
48
+
49
+ // Register this valid item as seen
50
+ chain.seenSyncCommitteeMessages.add(slot, subnet, validatorIndex, messageRoot);
51
+
52
+ return {indicesInSubcommittee};
53
+ }
54
+
55
+ function assertSyncCommitteeMessageNotKnown(
56
+ chain: IBeaconChain,
57
+ slot: Slot,
58
+ subnet: SubnetID,
59
+ validatorIndex: ValidatorIndex,
60
+ messageRoot: RootHex,
61
+ metrics: Metrics | null
62
+ ): void {
35
63
  const prevRoot = chain.seenSyncCommitteeMessages.get(slot, subnet, validatorIndex);
36
64
  if (prevRoot) {
37
65
  let shouldIgnore = false;
@@ -39,9 +67,9 @@ export async function validateGossipSyncCommittee(
39
67
  shouldIgnore = true;
40
68
  } else {
41
69
  const headRoot = chain.forkChoice.getHeadRoot();
42
- chain.metrics?.gossipSyncCommittee.equivocationCount.inc();
70
+ metrics?.gossipSyncCommittee.equivocationCount.inc();
43
71
  if (messageRoot === headRoot) {
44
- chain.metrics?.gossipSyncCommittee.equivocationToHeadCount.inc();
72
+ metrics?.gossipSyncCommittee.equivocationToHeadCount.inc();
45
73
  } else {
46
74
  shouldIgnore = true;
47
75
  }
@@ -57,18 +85,6 @@ export async function validateGossipSyncCommittee(
57
85
  });
58
86
  }
59
87
  }
60
-
61
- // [REJECT] The subnet_id is valid for the given validator, i.e. subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index).
62
- // Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee.
63
- // > Checked in validateGossipSyncCommitteeExceptSig()
64
-
65
- // [REJECT] The signature is valid for the message beacon_block_root for the validator referenced by validator_index.
66
- await validateSyncCommitteeSigOnly(chain, headState, syncCommittee);
67
-
68
- // Register this valid item as seen
69
- chain.seenSyncCommitteeMessages.add(slot, subnet, validatorIndex, messageRoot);
70
-
71
- return {indicesInSubcommittee};
72
88
  }
73
89
 
74
90
  export async function validateApiSyncCommittee(
@@ -92,6 +92,19 @@ export async function validateSyncCommitteeGossipContributionAndProof(
92
92
  });
93
93
  }
94
94
 
95
+ // Re-check after the await: concurrent contributions from the same aggregator all pass the checks above
96
+ if (chain.seenContributionAndProof.isAggregatorKnown(slot, subcommitteeIndex, aggregatorIndex)) {
97
+ throw new SyncCommitteeError(GossipAction.IGNORE, {
98
+ code: SyncCommitteeErrorCode.SYNC_COMMITTEE_AGGREGATOR_ALREADY_KNOWN,
99
+ });
100
+ }
101
+
102
+ if (!skipValidationKnownParticipants && chain.seenContributionAndProof.participantsKnown(contribution)) {
103
+ throw new SyncCommitteeError(GossipAction.IGNORE, {
104
+ code: SyncCommitteeErrorCode.SYNC_COMMITTEE_PARTICIPANTS_ALREADY_KNOWN,
105
+ });
106
+ }
107
+
95
108
  // no need to add to seenSyncCommittteeContributionCache here, gossip handler will do that
96
109
  chain.seenContributionAndProof.add(contributionAndProof, syncCommitteeParticipantIndices.length);
97
110
 
@@ -23,7 +23,7 @@ export async function* onBeaconBlocksByHead(
23
23
  let blockRootHex = requestedRootHex;
24
24
  const minimumRequestEpoch = Math.max(
25
25
  GENESIS_EPOCH,
26
- chain.clock.currentEpoch - chain.config.MIN_EPOCHS_FOR_BLOCK_REQUESTS
26
+ chain.clock.currentEpoch - chain.config.getMinEpochsForBlockRequests(chain.clock.currentEpoch)
27
27
  );
28
28
  const minimumRequestSlot = computeStartSlotAtEpoch(minimumRequestEpoch);
29
29
 
package/src/sync/sync.ts CHANGED
@@ -33,6 +33,8 @@ export class BeaconSync implements IBeaconSync {
33
33
  /** For metrics only */
34
34
  private readonly peerSyncType = new Map<string, PeerSyncType>();
35
35
  private readonly slotImportTolerance: Slot;
36
+ /** The requested subscription state, set while a change is being applied */
37
+ private pendingGossipSubscribed: boolean | undefined;
36
38
 
37
39
  constructor(opts: SyncOptions, modules: SyncModules) {
38
40
  const {config, chain, db, metrics, network, logger} = modules;
@@ -89,6 +91,7 @@ export class BeaconSync implements IBeaconSync {
89
91
  }
90
92
 
91
93
  close(): void {
94
+ this.pendingGossipSubscribed = undefined;
92
95
  this.network.events.off(NetworkEvent.peerConnected, this.addPeer);
93
96
  this.network.events.off(NetworkEvent.peerDisconnected, this.removePeer);
94
97
  this.chain.clock.off(ClockEvent.epoch, this.onClockEpoch);
@@ -296,17 +299,7 @@ export class BeaconSync implements IBeaconSync {
296
299
  if (state === SyncState.Synced && this.chain.clock.currentEpoch >= MIN_EPOCH_TO_START_GOSSIP) {
297
300
  this.chain.forkChoice.resumeFastConfirmation();
298
301
 
299
- if (!this.network.isSubscribedToGossipCoreTopics()) {
300
- this.network
301
- .subscribeGossipCoreTopics()
302
- .then(() => {
303
- this.metrics?.syncSwitchGossipSubscriptions.inc({action: "subscribed"});
304
- this.logger.info("Subscribed gossip core topics");
305
- })
306
- .catch((e) => {
307
- this.logger.error("Error subscribing to gossip core topics", {}, e);
308
- });
309
- }
302
+ this.setGossipSubscription(true);
310
303
 
311
304
  // also start searching for unknown blocks
312
305
  if (!this.unknownBlockSync.isSubscribedToNetwork()) {
@@ -322,17 +315,8 @@ export class BeaconSync implements IBeaconSync {
322
315
  // Same debounce as gossip: transient blips keep the rule running, only a real gap pauses it
323
316
  this.chain.forkChoice.pauseFastConfirmation();
324
317
 
325
- if (this.network.isSubscribedToGossipCoreTopics()) {
326
- this.logger.warn(`Node sync has fallen behind by ${syncDiff} slots`);
327
- this.network
328
- .unsubscribeGossipCoreTopics()
329
- .then(() => {
330
- this.metrics?.syncSwitchGossipSubscriptions.inc({action: "unsubscribed"});
331
- this.logger.info("Un-subscribed gossip core topics");
332
- })
333
- .catch((e) => {
334
- this.logger.error("Error unsubscribing to gossip core topics", {}, e);
335
- });
318
+ if (this.setGossipSubscription(false)) {
319
+ this.logger.warn("Node sync has fallen behind", {slots: syncDiff});
336
320
  }
337
321
 
338
322
  // also stop searching for unknown blocks
@@ -344,6 +328,43 @@ export class BeaconSync implements IBeaconSync {
344
328
  }
345
329
  };
346
330
 
331
+ private setGossipSubscription(subscribe: boolean): boolean {
332
+ if ((this.pendingGossipSubscribed ?? this.network.isSubscribedToGossipCoreTopics()) === subscribe) return false;
333
+ const applying = this.pendingGossipSubscribed !== undefined;
334
+ this.pendingGossipSubscribed = subscribe;
335
+ if (!applying) void this.applyGossipSubscription();
336
+ return true;
337
+ }
338
+
339
+ /**
340
+ * Applies the latest requested state one request at a time. Network skips a subscribe issued while an unsubscribe is
341
+ * in flight, so opposite requests must not overlap.
342
+ */
343
+ private async applyGossipSubscription(): Promise<void> {
344
+ for (;;) {
345
+ const subscribe = this.pendingGossipSubscribed;
346
+ if (subscribe === undefined || subscribe === this.network.isSubscribedToGossipCoreTopics()) break;
347
+ try {
348
+ await (subscribe ? this.network.subscribeGossipCoreTopics() : this.network.unsubscribeGossipCoreTopics());
349
+ } catch (e) {
350
+ this.logger.error(
351
+ subscribe ? "Error subscribing to gossip core topics" : "Error unsubscribing to gossip core topics",
352
+ {},
353
+ e as Error
354
+ );
355
+ // Leave a retry of the same request to the next sync state update
356
+ if (this.pendingGossipSubscribed === subscribe) break;
357
+ continue;
358
+ }
359
+ if (this.pendingGossipSubscribed === undefined || this.network.isSubscribedToGossipCoreTopics() !== subscribe) {
360
+ break;
361
+ }
362
+ this.metrics?.syncSwitchGossipSubscriptions.inc({action: subscribe ? "subscribed" : "unsubscribed"});
363
+ this.logger.info(subscribe ? "Subscribed gossip core topics" : "Un-subscribed gossip core topics");
364
+ }
365
+ this.pendingGossipSubscribed = undefined;
366
+ }
367
+
347
368
  private onClockEpoch = (): void => {
348
369
  // If a node witness the genesis event consider starting gossip
349
370
  // Also, ensure that updateSyncState is run at least once per epoch.