@lodestar/beacon-node 1.44.0-dev.a879adb124 → 1.44.0-dev.b00d95d096
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/chain/emitter.d.ts +2 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +0 -7
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +0 -3
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +3 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/validation/block.d.ts +5 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +4 -14
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +10 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +749 -2
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +9 -2
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts +2 -2
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +25 -23
- package/lib/network/processor/index.js.map +1 -1
- package/lib/sync/types.d.ts +9 -1
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +9 -2
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +64 -30
- package/lib/sync/unknownBlock.js.map +1 -1
- package/package.json +15 -15
- package/src/chain/emitter.ts +3 -2
- package/src/chain/errors/blockError.ts +0 -4
- package/src/chain/forkChoice/index.ts +5 -0
- package/src/chain/validation/block.ts +12 -16
- package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
- package/src/metrics/metrics/lodestar.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +9 -2
- package/src/network/processor/index.ts +27 -27
- package/src/sync/types.ts +11 -2
- package/src/sync/unknownBlock.ts +70 -31
|
@@ -15,12 +15,17 @@ import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js
|
|
|
15
15
|
import {IBeaconChain} from "../interface.js";
|
|
16
16
|
import {RegenCaller} from "../regen/index.js";
|
|
17
17
|
|
|
18
|
+
export type GossipBlockValidationResult = {
|
|
19
|
+
/** Number of skipped slots between the block and its parent (blockSlot - parentSlot - 1) */
|
|
20
|
+
skippedSlots: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
18
23
|
export async function validateGossipBlock(
|
|
19
24
|
config: ChainForkConfig,
|
|
20
25
|
chain: IBeaconChain,
|
|
21
26
|
signedBlock: SignedBeaconBlock,
|
|
22
27
|
fork: ForkName
|
|
23
|
-
): Promise<
|
|
28
|
+
): Promise<GossipBlockValidationResult> {
|
|
24
29
|
const block = signedBlock.message;
|
|
25
30
|
const blockSlot = block.slot;
|
|
26
31
|
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
@@ -109,21 +114,6 @@ export async function validateGossipBlock(
|
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
// [IGNORE] The attestation head block is too far behind the attestation slot, causing many skip slots.
|
|
113
|
-
// This is deemed a DoS risk because we need to get the proposerShuffling. To get the shuffling we have
|
|
114
|
-
// to do a bunch of epoch transitions, the longer the distance between the parent and block,
|
|
115
|
-
// the more we have to do. epochTransitions are expensive ~750ms, so we must limit how many a
|
|
116
|
-
// single bad block can trigger
|
|
117
|
-
// Note: Ensure this check is done before calling chain.regen.getBlockSlotStat as this is the function that does various epoch transitions.
|
|
118
|
-
// Note: This validation check is not part of the spec.
|
|
119
|
-
if (chain.opts.maxSkipSlots != null && parentBlock.slot + chain.opts.maxSkipSlots < blockSlot) {
|
|
120
|
-
throw new BlockGossipError(GossipAction.IGNORE, {
|
|
121
|
-
code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS,
|
|
122
|
-
parentSlot: parentBlock.slot,
|
|
123
|
-
blockSlot,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
117
|
// [REJECT] The block is from a higher slot than its parent.
|
|
128
118
|
if (parentBlock.slot >= blockSlot) {
|
|
129
119
|
throw new BlockGossipError(GossipAction.REJECT, {
|
|
@@ -133,6 +123,10 @@ export async function validateGossipBlock(
|
|
|
133
123
|
});
|
|
134
124
|
}
|
|
135
125
|
|
|
126
|
+
// Number of skipped slots between block and parent (non-spec). Previously this gated blocks via
|
|
127
|
+
// maxSkipSlots; now the caller only observes it so legitimate post-skip blocks are no longer ignored.
|
|
128
|
+
const skippedSlots = blockSlot - parentBlock.slot - 1;
|
|
129
|
+
|
|
136
130
|
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
|
|
137
131
|
if (isForkPostDeneb(fork) && !isForkPostGloas(fork)) {
|
|
138
132
|
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
|
|
@@ -247,4 +241,6 @@ export async function validateGossipBlock(
|
|
|
247
241
|
}
|
|
248
242
|
|
|
249
243
|
chain.seenBlockProposers.add(blockSlot, proposerIndex);
|
|
244
|
+
|
|
245
|
+
return {skippedSlots};
|
|
250
246
|
}
|
|
@@ -35,8 +35,6 @@ async function validateExecutionPayloadEnvelope(
|
|
|
35
35
|
// [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via
|
|
36
36
|
// gossip or non-gossip sources) (a client MAY queue payload for processing once
|
|
37
37
|
// the block is retrieved).
|
|
38
|
-
// TODO GLOAS: Need to review this, we should queue the envelope for later
|
|
39
|
-
// processing if the block is not yet known, otherwise we would ignore it here
|
|
40
38
|
const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot);
|
|
41
39
|
if (block === null) {
|
|
42
40
|
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
|
|
@@ -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",
|
|
@@ -861,6 +866,12 @@ export function createLodestarMetrics(
|
|
|
861
866
|
labelNames: ["numBlobs"],
|
|
862
867
|
}),
|
|
863
868
|
|
|
869
|
+
skippedSlots: register.histogram({
|
|
870
|
+
name: "lodestar_gossip_block_skipped_slots",
|
|
871
|
+
help: "Number of skipped slots between a gossip block and its parent (blockSlot - parentSlot - 1)",
|
|
872
|
+
buckets: [0, 1, 2, 4, 8, 16, 32],
|
|
873
|
+
}),
|
|
874
|
+
|
|
864
875
|
processBlockErrors: register.gauge<{error: BlockErrorCode | "NOT_BLOCK_ERROR"}>({
|
|
865
876
|
name: "lodestar_gossip_block_process_block_errors",
|
|
866
877
|
help: "Count of errors, by error type, while processing blocks",
|
|
@@ -185,7 +185,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
185
185
|
peerIdStr,
|
|
186
186
|
});
|
|
187
187
|
try {
|
|
188
|
-
await validateGossipBlock(config, chain, signedBlock, fork);
|
|
188
|
+
const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
|
|
189
189
|
|
|
190
190
|
if (isForkPostGloas(fork)) {
|
|
191
191
|
chain.seenPayloadEnvelopeInputCache.add({
|
|
@@ -205,8 +205,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
205
205
|
|
|
206
206
|
metrics?.gossipBlock.gossipValidation.recvToValidation.observe(recvToValidation);
|
|
207
207
|
metrics?.gossipBlock.gossipValidation.validationTime.observe(validationTime);
|
|
208
|
+
metrics?.gossipBlock.skippedSlots.observe(skippedSlots);
|
|
208
209
|
|
|
209
|
-
logger.debug("Validated gossip block", {
|
|
210
|
+
logger.debug("Validated gossip block", {
|
|
211
|
+
...blockInputMeta,
|
|
212
|
+
...logCtx,
|
|
213
|
+
recvToValidation,
|
|
214
|
+
validationTime,
|
|
215
|
+
skippedSlots,
|
|
216
|
+
});
|
|
210
217
|
|
|
211
218
|
chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex});
|
|
212
219
|
|
|
@@ -186,9 +186,11 @@ export class NetworkProcessor {
|
|
|
186
186
|
// we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
|
|
187
187
|
// to be stored in this Map and reprocessed once the block comes
|
|
188
188
|
private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
189
|
+
private awaitingBlockMessageCount = 0;
|
|
189
190
|
// we may not receive the payload for messages that require the FULL payload variant to be processed,
|
|
190
191
|
// in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
|
|
191
192
|
private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
193
|
+
private awaitingPayloadMessageCount = 0;
|
|
192
194
|
private unknownBlocksBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
|
193
195
|
private unknownEnvelopesBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
|
194
196
|
|
|
@@ -228,8 +230,8 @@ export class NetworkProcessor {
|
|
|
228
230
|
metrics.gossipValidationQueue.keySize.set({topic}, this.gossipQueues[topic].keySize);
|
|
229
231
|
metrics.gossipValidationQueue.concurrency.set({topic}, this.gossipTopicConcurrency[topic]);
|
|
230
232
|
}
|
|
231
|
-
metrics.awaitingBlockGossipMessages.countPerSlot.set(this.
|
|
232
|
-
metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.
|
|
233
|
+
metrics.awaitingBlockGossipMessages.countPerSlot.set(this.awaitingBlockMessageCount);
|
|
234
|
+
metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.awaitingPayloadMessageCount);
|
|
233
235
|
// specific metric for beacon_attestation topic
|
|
234
236
|
metrics.gossipValidationQueue.keyAge.reset();
|
|
235
237
|
for (const ageMs of this.gossipQueues.beacon_attestation.getDataAgeMs()) {
|
|
@@ -297,7 +299,7 @@ export class NetworkProcessor {
|
|
|
297
299
|
return;
|
|
298
300
|
}
|
|
299
301
|
this.unknownEnvelopesBySlot.getOrDefault(slot).add(root);
|
|
300
|
-
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, peer, source});
|
|
302
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
|
@@ -497,7 +499,7 @@ export class NetworkProcessor {
|
|
|
497
499
|
this.pushPendingGossipsubMessageToQueue(message);
|
|
498
500
|
break;
|
|
499
501
|
case PreprocessAction.AwaitBlock: {
|
|
500
|
-
if (this.
|
|
502
|
+
if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
|
|
501
503
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
502
504
|
this.metrics?.awaitingBlockGossipMessages.reject.inc({
|
|
503
505
|
reason: ReprocessRejectReason.reached_limit,
|
|
@@ -509,10 +511,11 @@ export class NetworkProcessor {
|
|
|
509
511
|
this.metrics?.awaitingBlockGossipMessages.queue.inc({topic: topicType});
|
|
510
512
|
const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
|
|
511
513
|
awaitingGossipsubMessages.add(message);
|
|
514
|
+
this.awaitingBlockMessageCount++;
|
|
512
515
|
break;
|
|
513
516
|
}
|
|
514
517
|
case PreprocessAction.AwaitEnvelope: {
|
|
515
|
-
if (this.
|
|
518
|
+
if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
|
|
516
519
|
this.metrics?.awaitingPayloadGossipMessages.reject.inc({
|
|
517
520
|
reason: ReprocessRejectReason.reached_limit,
|
|
518
521
|
topic: topicType,
|
|
@@ -525,6 +528,7 @@ export class NetworkProcessor {
|
|
|
525
528
|
preprocessResult.root
|
|
526
529
|
);
|
|
527
530
|
awaitingPayloadGossipsubMessages.add(message);
|
|
531
|
+
this.awaitingPayloadMessageCount++;
|
|
528
532
|
break;
|
|
529
533
|
}
|
|
530
534
|
}
|
|
@@ -548,6 +552,12 @@ export class NetworkProcessor {
|
|
|
548
552
|
return;
|
|
549
553
|
}
|
|
550
554
|
|
|
555
|
+
// Atomically remove from map and update counter before async iteration to
|
|
556
|
+
// prevent double-decrement race with onClockSlot during yield points below
|
|
557
|
+
if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
|
|
558
|
+
this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
|
|
559
|
+
}
|
|
560
|
+
|
|
551
561
|
const nowSec = Date.now() / 1000;
|
|
552
562
|
let count = 0;
|
|
553
563
|
// TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
|
|
@@ -567,8 +577,6 @@ export class NetworkProcessor {
|
|
|
567
577
|
await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
|
|
568
578
|
}
|
|
569
579
|
}
|
|
570
|
-
|
|
571
|
-
this.awaitingMessagesByBlockRoot.delete(rootHex);
|
|
572
580
|
};
|
|
573
581
|
|
|
574
582
|
private onPayloadEnvelopeProcessed = async ({blockRoot: rootHex}: {blockRoot: RootHex}): Promise<void> => {
|
|
@@ -577,6 +585,12 @@ export class NetworkProcessor {
|
|
|
577
585
|
return;
|
|
578
586
|
}
|
|
579
587
|
|
|
588
|
+
// Atomically remove from map and update counter before async iteration to
|
|
589
|
+
// prevent double-decrement race with onClockSlot during yield points below
|
|
590
|
+
if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
|
|
591
|
+
this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
|
|
592
|
+
}
|
|
593
|
+
|
|
580
594
|
const nowSec = Date.now() / 1000;
|
|
581
595
|
let count = 0;
|
|
582
596
|
for (const message of waitingGossipsubMessages) {
|
|
@@ -593,8 +607,6 @@ export class NetworkProcessor {
|
|
|
593
607
|
await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
|
|
594
608
|
}
|
|
595
609
|
}
|
|
596
|
-
|
|
597
|
-
this.awaitingMessagesByPayloadBlockRoot.delete(rootHex);
|
|
598
610
|
};
|
|
599
611
|
|
|
600
612
|
private onClockSlot = (clockSlot: Slot): void => {
|
|
@@ -618,7 +630,9 @@ export class NetworkProcessor {
|
|
|
618
630
|
);
|
|
619
631
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
620
632
|
}
|
|
621
|
-
this.awaitingMessagesByBlockRoot.delete(rootHex)
|
|
633
|
+
if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
|
|
634
|
+
this.awaitingBlockMessageCount -= gossipMessages.size;
|
|
635
|
+
}
|
|
622
636
|
}
|
|
623
637
|
}
|
|
624
638
|
this.unknownBlocksBySlot.delete(slot);
|
|
@@ -641,7 +655,9 @@ export class NetworkProcessor {
|
|
|
641
655
|
);
|
|
642
656
|
// No need to report the dropped job to gossip. It will be eventually pruned from the mcache
|
|
643
657
|
}
|
|
644
|
-
this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)
|
|
658
|
+
if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
|
|
659
|
+
this.awaitingPayloadMessageCount -= gossipMessages.size;
|
|
660
|
+
}
|
|
645
661
|
}
|
|
646
662
|
}
|
|
647
663
|
this.unknownEnvelopesBySlot.delete(slot);
|
|
@@ -784,20 +800,4 @@ export class NetworkProcessor {
|
|
|
784
800
|
|
|
785
801
|
return null;
|
|
786
802
|
}
|
|
787
|
-
|
|
788
|
-
private get unknownBlockGossipsubMessagesCount(): number {
|
|
789
|
-
let count = 0;
|
|
790
|
-
for (const messages of this.awaitingMessagesByBlockRoot.values()) {
|
|
791
|
-
count += messages.size;
|
|
792
|
-
}
|
|
793
|
-
return count;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
private get unknownPayloadGossipsubMessagesCount(): number {
|
|
797
|
-
let count = 0;
|
|
798
|
-
for (const messages of this.awaitingMessagesByPayloadBlockRoot.values()) {
|
|
799
|
-
count += messages.size;
|
|
800
|
-
}
|
|
801
|
-
return count;
|
|
802
|
-
}
|
|
803
803
|
}
|
package/src/sync/types.ts
CHANGED
|
@@ -19,7 +19,14 @@ export enum PendingBlockType {
|
|
|
19
19
|
*/
|
|
20
20
|
INCOMPLETE_BLOCK_INPUT = "IncompleteBlockInput",
|
|
21
21
|
|
|
22
|
-
|
|
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
|
}
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -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.
|
|
221
|
-
this.metrics?.blockInputSync.
|
|
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.
|
|
232
|
-
this.metrics?.blockInputSync.
|
|
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",
|
|
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:
|
|
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
|
|
@@ -850,25 +873,28 @@ export class BlockInputSync {
|
|
|
850
873
|
}
|
|
851
874
|
|
|
852
875
|
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
853
|
-
if (!
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
876
|
+
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
877
|
+
// Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
|
|
878
|
+
// non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
|
|
879
|
+
// onBlockImported re-triggers the search to resume this envelope.
|
|
880
|
+
if (!this.pendingBlocks.has(rootHex)) {
|
|
881
|
+
this.addByRootHex(rootHex);
|
|
882
|
+
}
|
|
859
883
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
}
|
|
864
|
-
} else {
|
|
865
|
-
this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
|
|
866
|
-
root: rootHex,
|
|
867
|
-
});
|
|
884
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
885
|
+
if (pendingBlock && this.network.getConnectedPeers().length > 0) {
|
|
886
|
+
await this.downloadBlock(pendingBlock);
|
|
868
887
|
}
|
|
869
888
|
return;
|
|
870
889
|
}
|
|
871
890
|
|
|
891
|
+
if (!payloadInput) {
|
|
892
|
+
this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
|
|
893
|
+
root: rootHex,
|
|
894
|
+
});
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
872
898
|
if (!payloadInput.hasPayloadEnvelope()) {
|
|
873
899
|
const validationResult = await wrapError(
|
|
874
900
|
validateGossipExecutionPayloadEnvelope(this.chain, pendingPayload.envelope)
|
|
@@ -922,10 +948,12 @@ export class BlockInputSync {
|
|
|
922
948
|
return;
|
|
923
949
|
}
|
|
924
950
|
|
|
951
|
+
const payloadSlot = getPayloadSyncCacheItemSlot(payload);
|
|
925
952
|
const logCtx = {
|
|
926
|
-
slot:
|
|
953
|
+
slot: payloadSlot,
|
|
927
954
|
root: rootHex,
|
|
928
955
|
pendingPayloads: this.pendingPayloads.size,
|
|
956
|
+
...(typeof payloadSlot === "number" && {delaySec: this.chain.clock.secFromSlot(payloadSlot)}),
|
|
929
957
|
};
|
|
930
958
|
|
|
931
959
|
this.logger.verbose("BlockInputSync.downloadPayload()", logCtx);
|
|
@@ -953,7 +981,11 @@ export class BlockInputSync {
|
|
|
953
981
|
|
|
954
982
|
private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
|
|
955
983
|
const rootHex = pendingPayload.payloadInput.blockRootHex;
|
|
956
|
-
const logCtx = {
|
|
984
|
+
const logCtx = {
|
|
985
|
+
slot: pendingPayload.payloadInput.slot,
|
|
986
|
+
root: rootHex,
|
|
987
|
+
delaySec: this.chain.clock.secFromSlot(pendingPayload.payloadInput.slot),
|
|
988
|
+
};
|
|
957
989
|
|
|
958
990
|
if (pendingPayload.status !== PendingPayloadInputStatus.downloaded) {
|
|
959
991
|
this.logger.debug("Skipping payload processing before payload input is downloaded", {
|
|
@@ -980,6 +1012,7 @@ export class BlockInputSync {
|
|
|
980
1012
|
}
|
|
981
1013
|
|
|
982
1014
|
pendingPayload.status = PendingPayloadInputStatus.processing;
|
|
1015
|
+
this.logger.debug("Processing downloaded payload", logCtx);
|
|
983
1016
|
|
|
984
1017
|
const res = await wrapError(this.chain.processExecutionPayload(pendingPayload.payloadInput));
|
|
985
1018
|
if (!res.err) {
|
|
@@ -1073,11 +1106,11 @@ export class BlockInputSync {
|
|
|
1073
1106
|
}
|
|
1074
1107
|
|
|
1075
1108
|
payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
1076
|
-
if (!
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
//
|
|
1109
|
+
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
1110
|
+
// Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
|
|
1111
|
+
// envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
|
|
1112
|
+
// payloadInput may be seeded from the block body during download, so a non-null payloadInput does not
|
|
1113
|
+
// imply the block is imported.
|
|
1081
1114
|
return {
|
|
1082
1115
|
status: PendingPayloadInputStatus.waitingForBlock,
|
|
1083
1116
|
envelope,
|
|
@@ -1086,6 +1119,11 @@ export class BlockInputSync {
|
|
|
1086
1119
|
};
|
|
1087
1120
|
}
|
|
1088
1121
|
|
|
1122
|
+
if (!payloadInput) {
|
|
1123
|
+
// Block is in fork choice but no PayloadEnvelopeInput exists, should have been created during block import.
|
|
1124
|
+
throw new Error(`Missing PayloadEnvelopeInput for known block ${rootHex}`);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1089
1127
|
if (!payloadInput.hasPayloadEnvelope()) {
|
|
1090
1128
|
await validateGossipExecutionPayloadEnvelope(this.chain, envelope);
|
|
1091
1129
|
}
|
|
@@ -1117,6 +1155,7 @@ export class BlockInputSync {
|
|
|
1117
1155
|
rootHex,
|
|
1118
1156
|
peerId,
|
|
1119
1157
|
peerClient,
|
|
1158
|
+
...(typeof slot === "number" && {delaySec: this.chain.clock.secFromSlot(slot)}),
|
|
1120
1159
|
hasPayload: pendingPayload.payloadInput.hasPayloadEnvelope(),
|
|
1121
1160
|
hasAllData: pendingPayload.payloadInput.hasAllData(),
|
|
1122
1161
|
});
|
|
@@ -1288,7 +1327,7 @@ export class BlockInputSync {
|
|
|
1288
1327
|
this.metrics?.blockInputSync.fetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1289
1328
|
}
|
|
1290
1329
|
|
|
1291
|
-
const logCtx = {slot, rootHex, peerId, peerClient};
|
|
1330
|
+
const logCtx = {slot, rootHex, peerId, peerClient, delaySec: this.chain.clock.secFromSlot(slot)};
|
|
1292
1331
|
this.logger.verbose("BlockInputSync.fetchBlockInput: successful download", logCtx);
|
|
1293
1332
|
this.metrics?.blockInputSync.downloadByRoot.success.inc();
|
|
1294
1333
|
const warnings = downloadResult.warnings;
|