@lodestar/beacon-node 1.44.0-rc.0 → 1.44.0-rc.1

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/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.44.0-rc.0",
14
+ "version": "1.44.0-rc.1",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -134,17 +134,17 @@
134
134
  "@libp2p/peer-id": "^6.0.4",
135
135
  "@libp2p/prometheus-metrics": "^5.0.14",
136
136
  "@libp2p/tcp": "^11.0.13",
137
- "@lodestar/api": "^1.44.0-rc.0",
138
- "@lodestar/config": "^1.44.0-rc.0",
139
- "@lodestar/db": "^1.44.0-rc.0",
140
- "@lodestar/fork-choice": "^1.44.0-rc.0",
141
- "@lodestar/logger": "^1.44.0-rc.0",
142
- "@lodestar/params": "^1.44.0-rc.0",
143
- "@lodestar/reqresp": "^1.44.0-rc.0",
144
- "@lodestar/state-transition": "^1.44.0-rc.0",
145
- "@lodestar/types": "^1.44.0-rc.0",
146
- "@lodestar/utils": "^1.44.0-rc.0",
147
- "@lodestar/validator": "^1.44.0-rc.0",
137
+ "@lodestar/api": "^1.44.0-rc.1",
138
+ "@lodestar/config": "^1.44.0-rc.1",
139
+ "@lodestar/db": "^1.44.0-rc.1",
140
+ "@lodestar/fork-choice": "^1.44.0-rc.1",
141
+ "@lodestar/logger": "^1.44.0-rc.1",
142
+ "@lodestar/params": "^1.44.0-rc.1",
143
+ "@lodestar/reqresp": "^1.44.0-rc.1",
144
+ "@lodestar/state-transition": "^1.44.0-rc.1",
145
+ "@lodestar/types": "^1.44.0-rc.1",
146
+ "@lodestar/utils": "^1.44.0-rc.1",
147
+ "@lodestar/validator": "^1.44.0-rc.1",
148
148
  "@multiformats/multiaddr": "^13.0.1",
149
149
  "datastore-core": "^11.0.2",
150
150
  "datastore-fs": "^11.0.2",
@@ -167,7 +167,7 @@
167
167
  "@libp2p/interface-internal": "^3.0.13",
168
168
  "@libp2p/logger": "^6.2.2",
169
169
  "@libp2p/utils": "^7.0.13",
170
- "@lodestar/spec-test-util": "^1.44.0-rc.0",
170
+ "@lodestar/spec-test-util": "^1.44.0-rc.1",
171
171
  "@types/js-yaml": "^4.0.5",
172
172
  "@types/qs": "^6.9.7",
173
173
  "@types/tmp": "^0.2.3",
@@ -184,5 +184,5 @@
184
184
  "beacon",
185
185
  "blockchain"
186
186
  ],
187
- "gitHead": "e8e273378f32a471620bf5f0cf617c16f7ce64e1"
187
+ "gitHead": "e182418f23147858b333923c7841c0a0a4aa813c"
188
188
  }
@@ -1207,7 +1207,10 @@ export function getValidatorApi(
1207
1207
  const isPostFulu = isForkPostFulu(config.getForkName(startSlot));
1208
1208
  const maxFutureEpoch = isPostFulu && nearNextEpoch && opts?.v2 ? nextEpoch + 1 : nextEpoch;
1209
1209
  if (currentEpoch >= 0 && epoch > maxFutureEpoch) {
1210
- throw new ApiError(400, `Requested epoch ${epoch} must not be more than one epoch in the future`);
1210
+ throw new ApiError(
1211
+ 400,
1212
+ `Requested epoch ${epoch} must not be more than ${maxFutureEpoch}, currentEpoch=${currentEpoch}, v2=${opts?.v2 ?? false}`
1213
+ );
1211
1214
  }
1212
1215
 
1213
1216
  const head = chain.forkChoice.getHead();
@@ -1291,17 +1294,35 @@ export function getValidatorApi(
1291
1294
  duties.push({slot: startSlot + i, validatorIndex: indexes[i], pubkey: pubkeys[i]});
1292
1295
  }
1293
1296
 
1294
- // Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
1295
- // It should be set to the latest block applied to `self` or the genesis block root.
1296
- const dependentRoot =
1297
- // In v2 the dependent root is different after fulu due to deterministic proposer lookahead
1298
- proposerShufflingDecisionRoot(opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0, state, epoch) ||
1299
- (await getGenesisBlockRoot(state));
1297
+ // In v2 the dependent root is different after fulu due to deterministic proposer lookahead
1298
+ let dependentRoot = proposerShufflingDecisionRoot(
1299
+ opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0,
1300
+ state,
1301
+ epoch
1302
+ );
1303
+ const logCtx = {
1304
+ epoch,
1305
+ stateSlot: state.slot,
1306
+ stateEpoch: state.epoch,
1307
+ v2: opts?.v2 ?? false,
1308
+ };
1309
+ if (dependentRoot === null) {
1310
+ // fallback to get_proposer_duties() v1, also in lodestar v1.43
1311
+ logger.verbose("Proposer duties decision root not in state, falling back to state epoch", logCtx);
1312
+ dependentRoot = proposerShufflingDecisionRoot(ForkName.phase0, state, state.epoch);
1313
+ }
1314
+ if (dependentRoot === null) {
1315
+ logger.verbose("Proposer duties decision root not in state, falling back to genesis block root", logCtx);
1316
+ dependentRoot = await getGenesisBlockRoot(state);
1317
+ }
1318
+
1319
+ const dependentRootHex = toRootHex(dependentRoot);
1320
+ logger.verbose("Computed proposer duties decision root", {...logCtx, dependentRoot: dependentRootHex});
1300
1321
 
1301
1322
  return {
1302
1323
  data: duties,
1303
1324
  meta: {
1304
- dependentRoot: toRootHex(dependentRoot),
1325
+ dependentRoot: dependentRootHex,
1305
1326
  executionOptimistic: isOptimisticBlock(head),
1306
1327
  },
1307
1328
  };
@@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {routes} from "@lodestar/api";
4
4
  import {CheckpointWithHex} from "@lodestar/fork-choice";
5
5
  import {IBeaconStateView} from "@lodestar/state-transition";
6
- import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
6
+ import {DataColumnSidecar, RootHex, Slot, deneb, phase0} from "@lodestar/types";
7
7
  import {PeerIdStr} from "../util/peerId.js";
8
8
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
9
9
  import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
@@ -94,7 +94,8 @@ export type ChainEventData = {
94
94
  peer: PeerIdStr;
95
95
  source: BlockInputSource;
96
96
  };
97
- [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
97
+ // slot is the message slot, not necessarily the envelope's slot, but useful as a logging/prune hint
98
+ [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; slot: Slot; peer?: PeerIdStr; source: BlockInputSource};
98
99
  };
99
100
 
100
101
  export type IChainEvents = ApiEvents & {
@@ -610,6 +610,11 @@ export function createLodestarMetrics(
610
610
  help: "The origination source of one of the BlockInputSync triggers",
611
611
  labelNames: ["source"],
612
612
  }),
613
+ payloadSource: register.counter<{source: BlockInputSource}>({
614
+ name: "lodestar_payload_input_sync_source_total",
615
+ help: "Count of payload (execution payload envelope) sync triggers, labeled by their source",
616
+ labelNames: ["source"],
617
+ }),
613
618
  pendingBlocks: register.gauge({
614
619
  name: "lodestar_sync_unknown_block_pending_blocks_size",
615
620
  help: "Current size of UnknownBlockSync pending blocks cache",
@@ -299,7 +299,7 @@ export class NetworkProcessor {
299
299
  return;
300
300
  }
301
301
  this.unknownEnvelopesBySlot.getOrDefault(slot).add(root);
302
- this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, peer, source});
302
+ this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
303
303
  }
304
304
 
305
305
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
package/src/sync/types.ts CHANGED
@@ -19,7 +19,14 @@ export enum PendingBlockType {
19
19
  */
20
20
  INCOMPLETE_BLOCK_INPUT = "IncompleteBlockInput",
21
21
 
22
- UNKNOWN_DATA = "unknown_data",
22
+ /**
23
+ * Payload analog of UNKNOWN_BLOCK_ROOT: we have a beacon block root but not its execution payload envelope.
24
+ */
25
+ UNKNOWN_PAYLOAD_BLOCK_ROOT = "unknown_payload_block_root",
26
+ /**
27
+ * Payload analog of INCOMPLETE_BLOCK_INPUT: we have a partial payload input that did not complete in time.
28
+ */
29
+ INCOMPLETE_PAYLOAD_ENVELOPE = "incomplete_payload_envelope",
23
30
  }
24
31
 
25
32
  export enum PendingBlockInputStatus {
@@ -70,6 +77,8 @@ export type PendingPayloadInput = {
70
77
  export type PendingPayloadRootHex = {
71
78
  status: PendingPayloadInputStatus.pending | PendingPayloadInputStatus.fetching;
72
79
  rootHex: RootHex;
80
+ // message slot hint, may be missing when resolving a parent payload
81
+ slot?: Slot;
73
82
  timeAddedSec: number;
74
83
  timeSyncedSec?: number;
75
84
  peerIdStrings: Set<string>;
@@ -125,5 +134,5 @@ export function getPayloadSyncCacheItemSlot(payload: PayloadSyncCacheItem): Slot
125
134
  return payload.envelope.message.payload.slotNumber;
126
135
  }
127
136
 
128
- return "unknown";
137
+ return payload.slot ?? "unknown";
129
138
  }
@@ -3,7 +3,7 @@ import {ChainForkConfig} from "@lodestar/config";
3
3
  import {ForkSeq} from "@lodestar/params";
4
4
  import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
5
5
  import {computeTimeAtSlot} from "@lodestar/state-transition";
6
- import {RootHex, gloas} from "@lodestar/types";
6
+ import {RootHex, Slot, gloas} from "@lodestar/types";
7
7
  import {Logger, fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
8
8
  import {isBlockInputBlobs, isBlockInputColumns} from "../chain/blocks/blockInput/blockInput.js";
9
9
  import {BlockInputSource, IBlockInput} from "../chain/blocks/blockInput/types.js";
@@ -215,10 +215,10 @@ export class BlockInputSync {
215
215
 
216
216
  private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
217
217
  try {
218
- this.addByPayloadRootHex(data.rootHex, data.peer);
218
+ this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
219
219
  this.triggerUnknownBlockSearch();
220
- this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_DATA});
221
- this.metrics?.blockInputSync.source.inc({source: data.source});
220
+ this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
221
+ this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
222
222
  } catch (e) {
223
223
  this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e as Error);
224
224
  }
@@ -228,8 +228,8 @@ export class BlockInputSync {
228
228
  try {
229
229
  this.addByPayloadInput(data.payloadInput, data.peer);
230
230
  this.triggerUnknownBlockSearch();
231
- this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_DATA});
232
- this.metrics?.blockInputSync.source.inc({source: data.source});
231
+ this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.INCOMPLETE_PAYLOAD_ENVELOPE});
232
+ this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
233
233
  } catch (e) {
234
234
  this.logger.debug("Error handling incompletePayloadEnvelope event", {}, e as Error);
235
235
  }
@@ -331,7 +331,10 @@ export class BlockInputSync {
331
331
  };
332
332
  this.pendingBlocks.set(blockInput.blockRootHex, pendingBlock);
333
333
 
334
- this.logger.verbose("Added blockInput to BlockInputSync.pendingBlocks", pendingBlock.blockInput.getLogMeta());
334
+ this.logger.verbose("Added blockInput to BlockInputSync.pendingBlocks", {
335
+ ...pendingBlock.blockInput.getLogMeta(),
336
+ delaySec: this.chain.clock.secFromSlot(blockInput.slot),
337
+ });
335
338
  }
336
339
 
337
340
  if (peerIdStr) {
@@ -346,13 +349,14 @@ export class BlockInputSync {
346
349
  }
347
350
  };
348
351
 
349
- private addByPayloadRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
352
+ private addByPayloadRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr, slot?: Slot): boolean => {
350
353
  let pendingPayload = this.pendingPayloads.get(rootHex);
351
354
  let added = false;
352
355
  if (!pendingPayload) {
353
356
  pendingPayload = {
354
357
  status: PendingPayloadInputStatus.pending,
355
358
  rootHex,
359
+ slot,
356
360
  peerIdStrings: new Set(),
357
361
  timeAddedSec: Date.now() / 1000,
358
362
  };
@@ -360,6 +364,8 @@ export class BlockInputSync {
360
364
  added = true;
361
365
 
362
366
  this.logger.verbose("Added new payload rootHex to BlockInputSync.pendingPayloads", {
367
+ slot: slot ?? "unknown",
368
+ delaySec: slot != null ? this.chain.clock.secFromSlot(slot) : "unknown",
363
369
  root: rootHex,
364
370
  peerIdStr: peerIdStr ?? "unknown peer",
365
371
  });
@@ -392,6 +398,13 @@ export class BlockInputSync {
392
398
  }
393
399
 
394
400
  this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
401
+
402
+ this.logger.verbose("Added payloadInput to BlockInputSync.pendingPayloads", {
403
+ slot: payloadInput.slot,
404
+ root: payloadInput.blockRootHex,
405
+ delaySec: this.chain.clock.secFromSlot(payloadInput.slot),
406
+ });
407
+
395
408
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
396
409
  if (prunedItemCount > 0) {
397
410
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
@@ -654,10 +667,12 @@ export class BlockInputSync {
654
667
  }
655
668
 
656
669
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
670
+ const blockSlot = getBlockInputSyncCacheItemSlot(block);
657
671
  const logCtx = {
658
- slot: getBlockInputSyncCacheItemSlot(block),
672
+ slot: blockSlot,
659
673
  root: rootHex,
660
674
  pendingBlocks: this.pendingBlocks.size,
675
+ ...(typeof blockSlot === "number" && {delaySec: this.chain.clock.secFromSlot(blockSlot)}),
661
676
  };
662
677
 
663
678
  this.logger.verbose("BlockInputSync.downloadBlock()", logCtx);
@@ -679,6 +694,7 @@ export class BlockInputSync {
679
694
  const logCtx2 = {
680
695
  ...logCtx,
681
696
  slot: blockSlot,
697
+ delaySec,
682
698
  parentInForkChoice,
683
699
  };
684
700
  this.logger.verbose("Downloaded unknown block", logCtx2);
@@ -748,6 +764,12 @@ export class BlockInputSync {
748
764
  // this prevents unbundling attack
749
765
  // see https://lighthouse-blog.sigmaprime.io/mev-unbundling-rpc.html
750
766
  const {slot: blockSlot, proposerIndex} = pendingBlock.blockInput.getBlock().message;
767
+ const logCtx = {
768
+ slot: blockSlot,
769
+ root: pendingBlock.blockInput.blockRootHex,
770
+ delaySec: this.chain.clock.secFromSlot(blockSlot),
771
+ };
772
+ this.logger.verbose("Processing downloaded block", logCtx);
751
773
  const fork = this.config.getForkName(blockSlot);
752
774
  const proposerBoostWindowMs = this.config.getAttestationDueMs(fork);
753
775
  if (
@@ -783,6 +805,7 @@ export class BlockInputSync {
783
805
  else this.metrics?.blockInputSync.processedBlocksSuccess.inc();
784
806
 
785
807
  if (!res.err) {
808
+ this.logger.verbose("Processed block from unknown sync", logCtx);
786
809
  // no need to update status to "processed", delete anyway
787
810
  this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
788
811
  // Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
@@ -925,10 +948,12 @@ export class BlockInputSync {
925
948
  return;
926
949
  }
927
950
 
951
+ const payloadSlot = getPayloadSyncCacheItemSlot(payload);
928
952
  const logCtx = {
929
- slot: getPayloadSyncCacheItemSlot(payload),
953
+ slot: payloadSlot,
930
954
  root: rootHex,
931
955
  pendingPayloads: this.pendingPayloads.size,
956
+ ...(typeof payloadSlot === "number" && {delaySec: this.chain.clock.secFromSlot(payloadSlot)}),
932
957
  };
933
958
 
934
959
  this.logger.verbose("BlockInputSync.downloadPayload()", logCtx);
@@ -956,7 +981,11 @@ export class BlockInputSync {
956
981
 
957
982
  private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
958
983
  const rootHex = pendingPayload.payloadInput.blockRootHex;
959
- const logCtx = {slot: pendingPayload.payloadInput.slot, root: rootHex};
984
+ const logCtx = {
985
+ slot: pendingPayload.payloadInput.slot,
986
+ root: rootHex,
987
+ delaySec: this.chain.clock.secFromSlot(pendingPayload.payloadInput.slot),
988
+ };
960
989
 
961
990
  if (pendingPayload.status !== PendingPayloadInputStatus.downloaded) {
962
991
  this.logger.debug("Skipping payload processing before payload input is downloaded", {
@@ -983,6 +1012,7 @@ export class BlockInputSync {
983
1012
  }
984
1013
 
985
1014
  pendingPayload.status = PendingPayloadInputStatus.processing;
1015
+ this.logger.debug("Processing downloaded payload", logCtx);
986
1016
 
987
1017
  const res = await wrapError(this.chain.processExecutionPayload(pendingPayload.payloadInput));
988
1018
  if (!res.err) {
@@ -1125,6 +1155,7 @@ export class BlockInputSync {
1125
1155
  rootHex,
1126
1156
  peerId,
1127
1157
  peerClient,
1158
+ ...(typeof slot === "number" && {delaySec: this.chain.clock.secFromSlot(slot)}),
1128
1159
  hasPayload: pendingPayload.payloadInput.hasPayloadEnvelope(),
1129
1160
  hasAllData: pendingPayload.payloadInput.hasAllData(),
1130
1161
  });
@@ -1296,7 +1327,7 @@ export class BlockInputSync {
1296
1327
  this.metrics?.blockInputSync.fetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
1297
1328
  }
1298
1329
 
1299
- const logCtx = {slot, rootHex, peerId, peerClient};
1330
+ const logCtx = {slot, rootHex, peerId, peerClient, delaySec: this.chain.clock.secFromSlot(slot)};
1300
1331
  this.logger.verbose("BlockInputSync.fetchBlockInput: successful download", logCtx);
1301
1332
  this.metrics?.blockInputSync.downloadByRoot.success.inc();
1302
1333
  const warnings = downloadResult.warnings;