@lodestar/beacon-node 1.49.0-dev.4d35c8b82e → 1.49.0-dev.57ffc39074
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.js +2 -2
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +9 -7
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.js +4 -0
- package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +14 -3
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/index.js +2 -2
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/emitter.d.ts +10 -0
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +4 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/attesterSlashingError.d.ts +4 -1
- package/lib/chain/errors/attesterSlashingError.d.ts.map +1 -1
- package/lib/chain/errors/attesterSlashingError.js +1 -0
- package/lib/chain/errors/attesterSlashingError.js.map +1 -1
- package/lib/chain/errors/payloadAttestation.d.ts +4 -0
- package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
- package/lib/chain/errors/payloadAttestation.js +1 -0
- package/lib/chain/errors/payloadAttestation.js.map +1 -1
- package/lib/chain/errors/proposerSlashingError.d.ts +4 -1
- package/lib/chain/errors/proposerSlashingError.d.ts.map +1 -1
- package/lib/chain/errors/proposerSlashingError.js +1 -0
- package/lib/chain/errors/proposerSlashingError.js.map +1 -1
- package/lib/chain/options.d.ts +0 -4
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +0 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -6
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +2 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +3 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +1 -2
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +8 -1
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +1 -2
- package/lib/chain/validation/proposerSlashing.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +2 -2
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/repositories/blockArchive.d.ts +5 -0
- package/lib/db/repositories/blockArchive.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +36 -1
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +19 -4
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +38 -1
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +1 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +5 -10
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +16 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +9 -6
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -8
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +16 -5
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +2 -0
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +88 -7
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +6 -2
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +62 -24
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +19 -5
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/sync/types.d.ts +39 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +42 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +16 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +186 -13
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/sszBytes.d.ts +5 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +13 -1
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/blocks/index.ts +2 -2
- package/src/api/impl/validator/index.ts +9 -7
- package/src/chain/archiveStore/historicalState/metrics.ts +4 -0
- package/src/chain/archiveStore/utils/pruneHistory.ts +14 -4
- package/src/chain/blocks/index.ts +2 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -1
- package/src/chain/emitter.ts +7 -2
- package/src/chain/errors/attesterSlashingError.ts +3 -1
- package/src/chain/errors/payloadAttestation.ts +2 -0
- package/src/chain/errors/proposerSlashingError.ts +3 -1
- package/src/chain/options.ts +0 -5
- package/src/chain/produceBlock/produceBlockBody.ts +17 -6
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +3 -2
- package/src/chain/validation/attesterSlashing.ts +1 -2
- package/src/chain/validation/payloadAttestationMessage.ts +9 -0
- package/src/chain/validation/proposerSlashing.ts +1 -2
- package/src/chain/validatorMonitor.ts +12 -2
- package/src/db/repositories/blockArchive.ts +38 -1
- package/src/metrics/metrics/lodestar.ts +46 -3
- package/src/network/gossip/encoding.ts +5 -8
- package/src/network/gossip/gossipsub.ts +19 -2
- package/src/network/gossip/topic.ts +25 -7
- package/src/network/processor/gossipHandlers.ts +18 -5
- package/src/network/processor/gossipValidatorFn.ts +111 -7
- package/src/network/processor/index.ts +81 -24
- package/src/network/reqresp/protocols.ts +31 -6
- package/src/sync/types.ts +44 -3
- package/src/sync/unknownBlock.ts +209 -13
- package/src/util/sszBytes.ts +18 -0
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
|
|
|
5
5
|
import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
|
|
6
6
|
import {computeTimeAtSlot} from "@lodestar/state-transition";
|
|
7
7
|
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
8
|
-
import {Logger, fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
|
|
8
|
+
import {Logger, fromHex, isErrorAborted, prettyPrintIndices, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
|
|
9
9
|
import {isBlockInputBlobs, isBlockInputColumns} from "../chain/blocks/blockInput/blockInput.js";
|
|
10
10
|
import {BlockInputSource, IBlockInput} from "../chain/blocks/blockInput/types.js";
|
|
11
11
|
import {PayloadError, PayloadErrorCode} from "../chain/blocks/importExecutionPayload.js";
|
|
@@ -18,6 +18,7 @@ import {Metrics} from "../metrics/index.js";
|
|
|
18
18
|
import {INetwork, NetworkEvent, NetworkEventData, prettyPrintPeerIdStr} from "../network/index.js";
|
|
19
19
|
import {PeerSyncMeta} from "../network/peers/peersData.js";
|
|
20
20
|
import {MAX_PEERS_PER_ROOT} from "../network/processor/constants.js";
|
|
21
|
+
import {ClockEvent} from "../util/clock.js";
|
|
21
22
|
import {PeerIdStr} from "../util/peerId.js";
|
|
22
23
|
import {shuffle} from "../util/shuffle.js";
|
|
23
24
|
import {sortBy} from "../util/sortBy.js";
|
|
@@ -26,7 +27,10 @@ import {MAX_CONCURRENT_REQUESTS} from "./constants.js";
|
|
|
26
27
|
import {SyncOptions} from "./options.js";
|
|
27
28
|
import {
|
|
28
29
|
BlockInputSyncCacheItem,
|
|
30
|
+
DeferredPayloadResult,
|
|
31
|
+
DownloadResult,
|
|
29
32
|
DroppedItemReason,
|
|
33
|
+
FetchResult,
|
|
30
34
|
PayloadSyncCacheItem,
|
|
31
35
|
PendingBlockInput,
|
|
32
36
|
PendingBlockInputStatus,
|
|
@@ -55,6 +59,11 @@ const MAX_PENDING_BLOCKS = 100;
|
|
|
55
59
|
*/
|
|
56
60
|
const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
|
|
57
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Poll pending payloads of the current slot this often, between PAYLOAD_DUE and the end of the slot.
|
|
64
|
+
*/
|
|
65
|
+
export const PAYLOAD_POLL_INTERVAL_MS = 1_000;
|
|
66
|
+
|
|
58
67
|
type AdvancePendingBlockResult =
|
|
59
68
|
| "ready"
|
|
60
69
|
| "queued_block"
|
|
@@ -63,14 +72,6 @@ type AdvancePendingBlockResult =
|
|
|
63
72
|
| "blocked"
|
|
64
73
|
| "removed";
|
|
65
74
|
|
|
66
|
-
enum FetchResult {
|
|
67
|
-
SuccessResolved = "success_resolved",
|
|
68
|
-
SuccessMissingParent = "success_missing_parent",
|
|
69
|
-
SuccessLate = "success_late",
|
|
70
|
-
FailureTriedAllPeers = "failure_tried_all_peers",
|
|
71
|
-
FailureMaxAttempts = "failure_max_attempts",
|
|
72
|
-
}
|
|
73
|
-
|
|
74
75
|
class UnknownBlockRateLimitedError extends Error {
|
|
75
76
|
constructor(message: string) {
|
|
76
77
|
super(message);
|
|
@@ -122,6 +123,11 @@ export class BlockInputSync {
|
|
|
122
123
|
private subscribedToNetworkEvents = false;
|
|
123
124
|
private peerBalancer: UnknownBlockPeerBalancer;
|
|
124
125
|
private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Search unknown payload starting at PAYLOAD_DUE_BPS.
|
|
128
|
+
*/
|
|
129
|
+
private readonly deferredPayloadRoots = new Map<RootHex, {slot: Slot; source: BlockInputSource; peer?: PeerIdStr}>();
|
|
130
|
+
private payloadPollAbortController: AbortController | undefined;
|
|
125
131
|
|
|
126
132
|
constructor(
|
|
127
133
|
private readonly config: ChainForkConfig,
|
|
@@ -158,6 +164,7 @@ export class BlockInputSync {
|
|
|
158
164
|
this.logger.verbose("BlockInputSync enabled.");
|
|
159
165
|
this.chain.emitter.on(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
160
166
|
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
167
|
+
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRootSlot, this.onUnknownEnvelopeBlockRootSlot);
|
|
161
168
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
162
169
|
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
163
170
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
@@ -166,6 +173,7 @@ export class BlockInputSync {
|
|
|
166
173
|
this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
167
174
|
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
168
175
|
this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
176
|
+
this.chain.clock.on(ClockEvent.slot, this.onClockSlot);
|
|
169
177
|
|
|
170
178
|
// Seed the balancer with peers that connected before we subscribed
|
|
171
179
|
for (const peerId of this.network.getConnectedPeers()) {
|
|
@@ -177,14 +185,22 @@ export class BlockInputSync {
|
|
|
177
185
|
}
|
|
178
186
|
|
|
179
187
|
this.subscribedToNetworkEvents = true;
|
|
188
|
+
// we subscribe mid-slot, and a root can be deferred at any point in the slot, so start this
|
|
189
|
+
// slot's loop now instead of waiting for the next ClockEvent.slot
|
|
190
|
+
this.onClockSlot(this.chain.clock.currentSlot);
|
|
180
191
|
}
|
|
181
192
|
}
|
|
182
193
|
|
|
183
194
|
unsubscribeFromNetwork(): void {
|
|
184
195
|
this.logger.verbose("BlockInputSync disabled.");
|
|
185
196
|
this.clearRateLimitBackoffTimer();
|
|
197
|
+
this.chain.clock.off(ClockEvent.slot, this.onClockSlot);
|
|
198
|
+
this.payloadPollAbortController?.abort();
|
|
199
|
+
this.payloadPollAbortController = undefined;
|
|
200
|
+
this.deferredPayloadRoots.clear();
|
|
186
201
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
187
202
|
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
203
|
+
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRootSlot, this.onUnknownEnvelopeBlockRootSlot);
|
|
188
204
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
189
205
|
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
190
206
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
@@ -248,6 +264,33 @@ export class BlockInputSync {
|
|
|
248
264
|
}
|
|
249
265
|
};
|
|
250
266
|
|
|
267
|
+
private onUnknownEnvelopeBlockRootSlot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRootSlot]): void => {
|
|
268
|
+
try {
|
|
269
|
+
if (data.slot === this.chain.clock.currentSlot) {
|
|
270
|
+
// we receive the optimistic search, the poll loop searches it at PAYLOAD_DUE or on its next
|
|
271
|
+
// tick. If that loop already returned - every root it watched was imported - this root waits
|
|
272
|
+
// for the next slot, where onClockSlot moves it to pendingPayloads. That is not the normal
|
|
273
|
+
// scenario, and in a forking condition it only delays the search of the extra root
|
|
274
|
+
const deferred = this.deferredPayloadRoots.get(data.rootHex);
|
|
275
|
+
if (deferred === undefined) {
|
|
276
|
+
this.deferredPayloadRoots.set(data.rootHex, {slot: data.slot, source: data.source, peer: data.peer});
|
|
277
|
+
} else if (deferred.peer === undefined) {
|
|
278
|
+
deferred.peer = data.peer;
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
|
|
284
|
+
if (isNewRoot) {
|
|
285
|
+
this.triggerUnknownBlockSearch();
|
|
286
|
+
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT_SLOT});
|
|
287
|
+
this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
|
|
288
|
+
}
|
|
289
|
+
} catch (e) {
|
|
290
|
+
this.logger.debug("Error handling unknownEnvelopeBlockRootSlot event", {}, e as Error);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
251
294
|
private onIncompletePayloadEnvelope = (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]): void => {
|
|
252
295
|
try {
|
|
253
296
|
this.addByPayloadInput(data.payloadInput, data.peer);
|
|
@@ -791,6 +834,118 @@ export class BlockInputSync {
|
|
|
791
834
|
}
|
|
792
835
|
}
|
|
793
836
|
|
|
837
|
+
private onClockSlot = (slot: Slot): void => {
|
|
838
|
+
if (!isForkPostGloas(this.config.getForkName(slot))) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
// end the previous slot's loop, wherever it is: its sleep is bound to this same controller
|
|
842
|
+
this.payloadPollAbortController?.abort();
|
|
843
|
+
this.payloadPollAbortController = new AbortController();
|
|
844
|
+
// entries from an earlier slot is not polled
|
|
845
|
+
for (const [rootHex, {slot: deferredSlot, source, peer}] of this.deferredPayloadRoots) {
|
|
846
|
+
if (deferredSlot < slot) {
|
|
847
|
+
if (!this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
848
|
+
this.addDeferredPayloadRoot(rootHex, deferredSlot, source, peer);
|
|
849
|
+
}
|
|
850
|
+
this.deferredPayloadRoots.delete(rootHex);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
this.pollPayloadsAtSlot(slot, this.payloadPollAbortController.signal).catch((e) => {
|
|
854
|
+
if (!isErrorAborted(e)) {
|
|
855
|
+
this.logger.debug("Error polling payloads", {slot}, e as Error);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Poll unknown payloads of the current slot.
|
|
862
|
+
*/
|
|
863
|
+
private async pollPayloadsAtSlot(slot: Slot, signal: AbortSignal): Promise<void> {
|
|
864
|
+
// fixed tick count so the loop cannot outlive its slot: for mainnet, it's 6 ticks
|
|
865
|
+
const tickCount = Math.floor(
|
|
866
|
+
(this.config.SLOT_DURATION_MS - this.config.getPayloadDueMs()) / PAYLOAD_POLL_INTERVAL_MS
|
|
867
|
+
);
|
|
868
|
+
// a root still missing on several ticks is searched on each of them, recorded once
|
|
869
|
+
const searchedRoots = new Set<RootHex>();
|
|
870
|
+
let polls = 0;
|
|
871
|
+
|
|
872
|
+
try {
|
|
873
|
+
await sleep(Math.max(0, this.config.getPayloadDueMs() - this.chain.clock.msFromSlot(slot)), signal);
|
|
874
|
+
|
|
875
|
+
for (polls = 0; polls < tickCount; polls++) {
|
|
876
|
+
let importedCount = 0;
|
|
877
|
+
for (const [rootHex, {slot: deferredSlot, source, peer}] of this.deferredPayloadRoots) {
|
|
878
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
879
|
+
importedCount++;
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
// copy to the regular pendingPayloads so that we can search
|
|
883
|
+
this.addDeferredPayloadRoot(rootHex, deferredSlot, source, peer);
|
|
884
|
+
searchedRoots.add(rootHex);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// an empty deferredPayloadRoots means there is no optimistic search comes before PAYLOAD_DUE
|
|
888
|
+
// we have to poll until the payload of current slot is imported or slot end
|
|
889
|
+
if (this.deferredPayloadRoots.size > 0 && importedCount === this.deferredPayloadRoots.size) {
|
|
890
|
+
if (searchedRoots.size > 0) {
|
|
891
|
+
// the backup search was needed
|
|
892
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({
|
|
893
|
+
result: DeferredPayloadResult.ImportedAfterSearch,
|
|
894
|
+
});
|
|
895
|
+
this.metrics?.blockInputSync.deferredPayloadPolls.observe(polls);
|
|
896
|
+
this.logger.verbose("Payload imported after unknown sync search", {
|
|
897
|
+
slot,
|
|
898
|
+
roots: Array.from(searchedRoots).join(","),
|
|
899
|
+
polls,
|
|
900
|
+
});
|
|
901
|
+
} else {
|
|
902
|
+
// this happens most of the time on devnet
|
|
903
|
+
// gossip delivered everything before PAYLOAD_DUE
|
|
904
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({
|
|
905
|
+
result: DeferredPayloadResult.ImportedBeforeSearch,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
this.triggerUnknownBlockSearch();
|
|
912
|
+
if (polls < tickCount - 1) {
|
|
913
|
+
// no sleep after the last tick: the loop would otherwise end right at the slot boundary,
|
|
914
|
+
// where onClockSlot aborts it
|
|
915
|
+
await sleep(PAYLOAD_POLL_INTERVAL_MS, signal);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// reaching here means the early return never fired
|
|
920
|
+
if (this.deferredPayloadRoots.size > 0) {
|
|
921
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({result: DeferredPayloadResult.Unresolved});
|
|
922
|
+
this.logger.debug("Deferred payloads still missing at end of slot", {
|
|
923
|
+
slot,
|
|
924
|
+
roots: Array.from(this.deferredPayloadRoots.keys()).join(","),
|
|
925
|
+
});
|
|
926
|
+
} else {
|
|
927
|
+
// no optimistic search and no payload import ended the loop, eg a skipped slot
|
|
928
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({result: DeferredPayloadResult.ResolvedNoRoot});
|
|
929
|
+
this.logger.debug("No deferred payloads at slot", {
|
|
930
|
+
slot,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
} catch (e) {
|
|
934
|
+
if (!isErrorAborted(e)) {
|
|
935
|
+
throw e;
|
|
936
|
+
}
|
|
937
|
+
this.logger.verbose("Payload poll aborted", {slot, polls, msIntoSlot: this.chain.clock.msFromSlot(slot)});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
private addDeferredPayloadRoot(rootHex: RootHex, slot: Slot, source: BlockInputSource, peer?: PeerIdStr): void {
|
|
942
|
+
const isNewRoot = this.addByPayloadRootHex(rootHex, peer, slot);
|
|
943
|
+
if (isNewRoot) {
|
|
944
|
+
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.DEFERRED_PAYLOAD_BLOCK_ROOT});
|
|
945
|
+
this.metrics?.blockInputSync.payloadSource.inc({source});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
794
949
|
private async downloadBlock(block: BlockInputSyncCacheItem): Promise<void> {
|
|
795
950
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
796
951
|
return;
|
|
@@ -1125,15 +1280,25 @@ export class BlockInputSync {
|
|
|
1125
1280
|
return;
|
|
1126
1281
|
}
|
|
1127
1282
|
this.pendingPayloads.set(resultRootHex, pendingPayload);
|
|
1283
|
+
const result = isPendingPayloadEnvelope(pendingPayload)
|
|
1284
|
+
? DownloadResult.WaitingForBlock
|
|
1285
|
+
: this.chain.forkChoice.hasPayloadHexUnsafe(resultRootHex)
|
|
1286
|
+
? DownloadResult.Late
|
|
1287
|
+
: DownloadResult.Resolved;
|
|
1288
|
+
this.metrics?.blockInputSync.downloadedPayloadsResult.inc({result});
|
|
1128
1289
|
|
|
1129
1290
|
if (isPendingPayloadEnvelope(pendingPayload)) {
|
|
1130
1291
|
await this.reconcilePayloadEnvelope(pendingPayload);
|
|
1131
1292
|
} else if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1293
|
+
this.metrics?.blockInputSync.elapsedTimeTillPayloadReceived.observe(
|
|
1294
|
+
Date.now() / 1000 - computeTimeAtSlot(this.config, pendingPayload.payloadInput.slot, this.chain.genesisTime)
|
|
1295
|
+
);
|
|
1132
1296
|
await this.processPayload(pendingPayload);
|
|
1133
1297
|
}
|
|
1134
1298
|
return;
|
|
1135
1299
|
}
|
|
1136
1300
|
|
|
1301
|
+
this.metrics?.blockInputSync.downloadedPayloadsResult.inc({result: DownloadResult.Failed});
|
|
1137
1302
|
this.logger.debug("Ignoring unknown payload root after failed download", logCtx, res.err);
|
|
1138
1303
|
if (!isPendingPayloadEnvelope(payload)) {
|
|
1139
1304
|
payload.status = PendingPayloadInputStatus.pending;
|
|
@@ -1248,6 +1413,11 @@ export class BlockInputSync {
|
|
|
1248
1413
|
: await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1249
1414
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1250
1415
|
|
|
1416
|
+
const fetchStartSec = Date.now() / 1000;
|
|
1417
|
+
if (typeof slot === "number") {
|
|
1418
|
+
this.metrics?.blockInputSync.payloadFetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1251
1421
|
let i = 0;
|
|
1252
1422
|
let deferredByRateLimit = false;
|
|
1253
1423
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
@@ -1271,6 +1441,11 @@ export class BlockInputSync {
|
|
|
1271
1441
|
pendingColumns.size > 0
|
|
1272
1442
|
? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1273
1443
|
: "no peer available to download pending payload";
|
|
1444
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe(
|
|
1445
|
+
{result: FetchResult.FailureTriedAllPeers},
|
|
1446
|
+
Date.now() / 1000 - fetchStartSec
|
|
1447
|
+
);
|
|
1448
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureTriedAllPeers}, i);
|
|
1274
1449
|
throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
|
|
1275
1450
|
}
|
|
1276
1451
|
|
|
@@ -1280,7 +1455,12 @@ export class BlockInputSync {
|
|
|
1280
1455
|
try {
|
|
1281
1456
|
if (!envelope) {
|
|
1282
1457
|
envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
|
|
1458
|
+
const slotWasUnknown = typeof slot !== "number";
|
|
1283
1459
|
slot = envelope.message.payload.slotNumber;
|
|
1460
|
+
if (slotWasUnknown) {
|
|
1461
|
+
// we were not able to observe the time into slot when starting the fetch, do it now
|
|
1462
|
+
this.metrics?.blockInputSync.payloadFetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1463
|
+
}
|
|
1284
1464
|
}
|
|
1285
1465
|
|
|
1286
1466
|
payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
@@ -1289,6 +1469,11 @@ export class BlockInputSync {
|
|
|
1289
1469
|
// envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
|
|
1290
1470
|
// payloadInput may be seeded from the block body during download, so a non-null payloadInput does not
|
|
1291
1471
|
// imply the block is imported.
|
|
1472
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe(
|
|
1473
|
+
{result: FetchResult.SuccessWaitingForBlock},
|
|
1474
|
+
Date.now() / 1000 - fetchStartSec
|
|
1475
|
+
);
|
|
1476
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.SuccessWaitingForBlock}, i);
|
|
1292
1477
|
return {
|
|
1293
1478
|
status: PendingPayloadInputStatus.waitingForBlock,
|
|
1294
1479
|
envelope,
|
|
@@ -1339,6 +1524,12 @@ export class BlockInputSync {
|
|
|
1339
1524
|
});
|
|
1340
1525
|
|
|
1341
1526
|
if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1527
|
+
// late = the payload got imported (via gossip) while we were downloading, same as the block path
|
|
1528
|
+
const result = this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)
|
|
1529
|
+
? FetchResult.SuccessLate
|
|
1530
|
+
: FetchResult.SuccessResolved;
|
|
1531
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe({result}, Date.now() / 1000 - fetchStartSec);
|
|
1532
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result}, i);
|
|
1342
1533
|
return pendingPayload;
|
|
1343
1534
|
}
|
|
1344
1535
|
|
|
@@ -1373,12 +1564,17 @@ export class BlockInputSync {
|
|
|
1373
1564
|
}
|
|
1374
1565
|
}
|
|
1375
1566
|
|
|
1567
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe(
|
|
1568
|
+
{result: FetchResult.FailureMaxAttempts},
|
|
1569
|
+
Date.now() / 1000 - fetchStartSec
|
|
1570
|
+
);
|
|
1571
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
|
|
1572
|
+
|
|
1376
1573
|
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1377
1574
|
throw new UnknownBlockRateLimitedError(
|
|
1378
1575
|
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
|
|
1379
1576
|
);
|
|
1380
1577
|
}
|
|
1381
|
-
|
|
1382
1578
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
1383
1579
|
}
|
|
1384
1580
|
|
|
@@ -1483,7 +1679,7 @@ export class BlockInputSync {
|
|
|
1483
1679
|
|
|
1484
1680
|
// no more peer with needed columns to try, throw error
|
|
1485
1681
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
1486
|
-
this.metrics?.blockInputSync.
|
|
1682
|
+
this.metrics?.blockInputSync.fetchTime.observe(
|
|
1487
1683
|
{result: FetchResult.FailureTriedAllPeers},
|
|
1488
1684
|
Date.now() / 1000 - fetchStartSec
|
|
1489
1685
|
);
|
|
@@ -1572,7 +1768,7 @@ export class BlockInputSync {
|
|
|
1572
1768
|
: this.chain.forkChoice.hasBlockHex(cacheItem.blockInput.parentRootHex)
|
|
1573
1769
|
? FetchResult.SuccessResolved
|
|
1574
1770
|
: FetchResult.SuccessMissingParent;
|
|
1575
|
-
this.metrics?.blockInputSync.
|
|
1771
|
+
this.metrics?.blockInputSync.fetchTime.observe({result}, Date.now() / 1000 - fetchStartSec);
|
|
1576
1772
|
this.metrics?.blockInputSync.fetchPeers.set({result}, i);
|
|
1577
1773
|
return cacheItem;
|
|
1578
1774
|
}
|
|
@@ -1606,7 +1802,7 @@ export class BlockInputSync {
|
|
|
1606
1802
|
}
|
|
1607
1803
|
}
|
|
1608
1804
|
|
|
1609
|
-
this.metrics?.blockInputSync.
|
|
1805
|
+
this.metrics?.blockInputSync.fetchTime.observe(
|
|
1610
1806
|
{result: FetchResult.FailureMaxAttempts},
|
|
1611
1807
|
Date.now() / 1000 - fetchStartSec
|
|
1612
1808
|
);
|
package/src/util/sszBytes.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {
|
|
4
|
+
BYTES_PER_CELL,
|
|
4
5
|
BYTES_PER_FIELD_ELEMENT,
|
|
5
6
|
FIELD_ELEMENTS_PER_BLOB,
|
|
6
7
|
ForkName,
|
|
@@ -551,6 +552,23 @@ export function getBeaconBlockRootFromDataColumnSidecarSerialized(data: Uint8Arr
|
|
|
551
552
|
return "0x" + blockRootBuf.toString("hex");
|
|
552
553
|
}
|
|
553
554
|
|
|
555
|
+
// beaconBlockRoot is the last fixed field in the Gloas layout
|
|
556
|
+
const DATA_COLUMN_SIDECAR_GLOAS_FIXED_SIZE = BEACON_BLOCK_ROOT_POSITION_IN_GLOAS_DATA_COLUMN_SIDECAR + ROOT_SIZE; // 56 bytes
|
|
557
|
+
const KZG_PROOF_SIZE = 48; // Bytes48
|
|
558
|
+
const DATA_COLUMN_SIDECAR_GLOAS_PER_BLOB_SIZE = BYTES_PER_CELL + KZG_PROOF_SIZE; // 2048 + 48 = 2096 bytes
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Gloas sidecar size bound uses the largest blob limit across the entire schedule, including future entries.
|
|
562
|
+
* https://github.com/ethereum/consensus-specs/pull/5613
|
|
563
|
+
*/
|
|
564
|
+
export function computeMaxGloasDataColumnSidecarSize(config: ChainForkConfig): number {
|
|
565
|
+
const maxBlobs = config.BLOB_SCHEDULE.reduce(
|
|
566
|
+
(max, entry) => Math.max(max, entry.MAX_BLOBS_PER_BLOCK),
|
|
567
|
+
config.MAX_BLOBS_PER_BLOCK_ELECTRA
|
|
568
|
+
);
|
|
569
|
+
return DATA_COLUMN_SIDECAR_GLOAS_FIXED_SIZE + maxBlobs * DATA_COLUMN_SIDECAR_GLOAS_PER_BLOB_SIZE;
|
|
570
|
+
}
|
|
571
|
+
|
|
554
572
|
/**
|
|
555
573
|
* SignedExecutionPayloadEnvelope SSZ Layout:
|
|
556
574
|
* ├─ 4 bytes: message offset (points to byte 100)
|