@lodestar/beacon-node 1.34.0-dev.61783cf265 → 1.34.0-dev.6398efaba5
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 +3 -5
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +5 -5
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.js +9 -4
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/debug/index.js +2 -2
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.js +29 -23
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/impl/validator/utils.d.ts +3 -3
- package/lib/api/impl/validator/utils.js +2 -2
- package/lib/api/impl/validator/utils.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +19 -20
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/utils/zebraBanner.d.ts +2 -0
- package/lib/chain/blocks/utils/zebraBanner.js +45 -0
- package/lib/chain/blocks/utils/zebraBanner.js.map +1 -0
- package/lib/chain/blocks/verifyBlock.js +18 -5
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.js +7 -34
- package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
- package/lib/chain/chain.js +28 -20
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts +2 -1
- package/lib/chain/forkChoice/index.js +2 -2
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +13 -3
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/attestationPool.d.ts +1 -1
- package/lib/chain/opPools/attestationPool.js +7 -7
- package/lib/chain/prepareNextSlot.js +2 -1
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/validation/aggregateAndProof.d.ts +1 -1
- package/lib/chain/validation/aggregateAndProof.js +8 -8
- package/lib/chain/validation/aggregateAndProof.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts +3 -3
- package/lib/chain/validation/attestation.js +10 -10
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.d.ts +2 -1
- package/lib/chain/validation/dataColumnSidecar.js +17 -8
- package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
- package/lib/db/beacon.d.ts +3 -3
- package/lib/db/beacon.js +3 -3
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/interface.d.ts +3 -3
- package/lib/db/repositories/dataColumnSidecar.d.ts +26 -0
- package/lib/db/repositories/dataColumnSidecar.js +39 -0
- package/lib/db/repositories/dataColumnSidecar.js.map +1 -0
- package/lib/db/repositories/dataColumnSidecarArchive.d.ts +24 -0
- package/lib/db/repositories/dataColumnSidecarArchive.js +39 -0
- package/lib/db/repositories/dataColumnSidecarArchive.js.map +1 -0
- package/lib/db/repositories/index.d.ts +2 -2
- package/lib/db/repositories/index.js +2 -2
- package/lib/db/repositories/index.js.map +1 -1
- package/lib/execution/engine/http.js +10 -5
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/mock.d.ts +4 -1
- package/lib/execution/engine/mock.js +54 -16
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/types.d.ts +3 -3
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +1 -23
- package/lib/metrics/metrics/beacon.js +5 -61
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +5 -0
- package/lib/metrics/metrics/lodestar.js +14 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/metrics/metrics.d.ts +2 -1
- package/lib/metrics/metrics.js +3 -0
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +579 -51
- package/lib/network/interface.d.ts +3 -3
- package/lib/network/network.d.ts +3 -3
- package/lib/network/network.js +1 -1
- package/lib/network/network.js.map +1 -1
- package/lib/network/options.js +2 -2
- package/lib/network/peers/discover.js +1 -1
- package/lib/network/peers/discover.js.map +1 -1
- package/lib/network/peers/peerManager.js +9 -6
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts +2 -1
- package/lib/network/peers/utils/prioritizePeers.js +5 -5
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.js +13 -13
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.d.ts +7 -2
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.js +4 -4
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +3 -3
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts +2 -2
- package/lib/network/reqresp/handlers/beaconBlocksByRoot.js.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts +2 -2
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js +2 -3
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts +3 -3
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +27 -46
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts +2 -2
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +7 -21
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
- package/lib/network/reqresp/handlers/index.js +4 -3
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/rateLimit.js +11 -5
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/types.d.ts +3 -3
- package/lib/network/reqresp/types.js +3 -3
- package/lib/network/reqresp/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +44 -3
- package/lib/sync/unknownBlock.js +272 -193
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/dataColumns.d.ts +3 -3
- package/lib/util/dataColumns.js +30 -26
- package/lib/util/dataColumns.js.map +1 -1
- package/lib/util/types.d.ts +7 -0
- package/lib/util/types.js +3 -0
- package/lib/util/types.js.map +1 -1
- package/package.json +17 -18
- package/lib/db/repositories/dataColumnSidecars.d.ts +0 -47
- package/lib/db/repositories/dataColumnSidecars.js +0 -40
- package/lib/db/repositories/dataColumnSidecars.js.map +0 -1
- package/lib/db/repositories/dataColumnSidecarsArchive.d.ts +0 -15
- package/lib/db/repositories/dataColumnSidecarsArchive.js +0 -23
- package/lib/db/repositories/dataColumnSidecarsArchive.js.map +0 -1
package/lib/sync/unknownBlock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ForkName,
|
|
1
|
+
import { ForkName, ForkSeq, INTERVALS_PER_SLOT } from "@lodestar/params";
|
|
2
2
|
import { fromHex, pruneSetToMax, toRootHex } from "@lodestar/utils";
|
|
3
3
|
import { sleep } from "@lodestar/utils";
|
|
4
4
|
import { BlockInputType } from "../chain/blocks/types.js";
|
|
@@ -7,7 +7,9 @@ import { NetworkEvent } from "../network/index.js";
|
|
|
7
7
|
import { beaconBlocksMaybeBlobsByRoot, unavailableBeaconBlobsByRoot, } from "../network/reqresp/beaconBlocksMaybeBlobsByRoot.js";
|
|
8
8
|
import { byteArrayEquals } from "../util/bytes.js";
|
|
9
9
|
import { shuffle } from "../util/shuffle.js";
|
|
10
|
+
import { sortBy } from "../util/sortBy.js";
|
|
10
11
|
import { wrapError } from "../util/wrapError.js";
|
|
12
|
+
import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
|
|
11
13
|
import { PendingBlockStatus, PendingBlockType } from "./interface.js";
|
|
12
14
|
import { getAllDescendantBlocks, getDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
|
|
13
15
|
const MAX_ATTEMPTS_PER_BLOCK = 5;
|
|
@@ -68,6 +70,21 @@ export class UnknownBlockSync {
|
|
|
68
70
|
this.logger.debug("Error handling unknownBlockParent event", {}, e);
|
|
69
71
|
}
|
|
70
72
|
};
|
|
73
|
+
this.onPeerConnected = (data) => {
|
|
74
|
+
try {
|
|
75
|
+
const peerId = data.peer;
|
|
76
|
+
const peerSyncMeta = this.network.getConnectedPeerSyncMeta(peerId);
|
|
77
|
+
this.peerBalancer.onPeerConnected(data.peer, peerSyncMeta);
|
|
78
|
+
this.triggerUnknownBlockSearch();
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
this.logger.debug("Error handling peerConnected event", {}, e);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
this.onPeerDisconnected = (data) => {
|
|
85
|
+
const peerId = data.peer;
|
|
86
|
+
this.peerBalancer.onPeerDisconnected(peerId);
|
|
87
|
+
};
|
|
71
88
|
/**
|
|
72
89
|
* Gather tip parent blocks with unknown parent and do a search for all of them
|
|
73
90
|
*/
|
|
@@ -105,16 +122,22 @@ export class UnknownBlockSync {
|
|
|
105
122
|
}
|
|
106
123
|
// most of the time there is exactly 1 unknown block
|
|
107
124
|
for (const block of unknowns) {
|
|
108
|
-
this.downloadBlock(block
|
|
125
|
+
this.downloadBlock(block).catch((e) => {
|
|
109
126
|
this.logger.debug("Unexpected error - downloadBlock", { root: block.blockRootHex }, e);
|
|
110
127
|
});
|
|
111
128
|
}
|
|
112
129
|
};
|
|
113
130
|
this.maxPendingBlocks = opts?.maxPendingBlocks ?? MAX_PENDING_BLOCKS;
|
|
114
131
|
this.proposerBoostSecWindow = this.config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT;
|
|
132
|
+
this.peerBalancer = new UnknownBlockPeerBalancer(this.network.custodyConfig);
|
|
115
133
|
if (metrics) {
|
|
116
|
-
metrics.syncUnknownBlock.pendingBlocks.addCollect(() =>
|
|
117
|
-
|
|
134
|
+
metrics.syncUnknownBlock.pendingBlocks.addCollect(() => {
|
|
135
|
+
metrics.syncUnknownBlock.pendingBlocks.set(this.pendingBlocks.size);
|
|
136
|
+
metrics.syncUnknownBlock.knownBadBlocks.set(this.knownBadBlocks.size);
|
|
137
|
+
metrics.syncUnknownBlock.peerBalancer.peersMetaCount.set(this.peerBalancer.peersMeta.size);
|
|
138
|
+
metrics.syncUnknownBlock.peerBalancer.peersActiveRequestCount.set(this.peerBalancer.activeRequests.size);
|
|
139
|
+
metrics.syncUnknownBlock.peerBalancer.totalActiveRequests.set(this.peerBalancer.getTotalActiveRequests());
|
|
140
|
+
});
|
|
118
141
|
}
|
|
119
142
|
}
|
|
120
143
|
subscribeToNetwork() {
|
|
@@ -125,7 +148,8 @@ export class UnknownBlockSync {
|
|
|
125
148
|
this.network.events.on(NetworkEvent.unknownBlock, this.onUnknownBlock);
|
|
126
149
|
this.network.events.on(NetworkEvent.unknownBlockInput, this.onUnknownBlockInput);
|
|
127
150
|
this.network.events.on(NetworkEvent.unknownBlockParent, this.onUnknownParent);
|
|
128
|
-
this.network.events.on(NetworkEvent.peerConnected, this.
|
|
151
|
+
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
152
|
+
this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
129
153
|
this.subscribedToNetworkEvents = true;
|
|
130
154
|
}
|
|
131
155
|
}
|
|
@@ -138,7 +162,8 @@ export class UnknownBlockSync {
|
|
|
138
162
|
this.network.events.off(NetworkEvent.unknownBlock, this.onUnknownBlock);
|
|
139
163
|
this.network.events.off(NetworkEvent.unknownBlockInput, this.onUnknownBlockInput);
|
|
140
164
|
this.network.events.off(NetworkEvent.unknownBlockParent, this.onUnknownParent);
|
|
141
|
-
this.network.events.off(NetworkEvent.peerConnected, this.
|
|
165
|
+
this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
166
|
+
this.network.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
142
167
|
this.subscribedToNetworkEvents = false;
|
|
143
168
|
}
|
|
144
169
|
close() {
|
|
@@ -243,7 +268,7 @@ export class UnknownBlockSync {
|
|
|
243
268
|
}
|
|
244
269
|
return unknownBlockType;
|
|
245
270
|
}
|
|
246
|
-
async downloadBlock(block
|
|
271
|
+
async downloadBlock(block) {
|
|
247
272
|
if (block.status !== PendingBlockStatus.pending) {
|
|
248
273
|
return;
|
|
249
274
|
}
|
|
@@ -257,57 +282,12 @@ export class UnknownBlockSync {
|
|
|
257
282
|
this.logger.verbose("Downloading unknown block", logCtx);
|
|
258
283
|
block.status = PendingBlockStatus.fetching;
|
|
259
284
|
let res;
|
|
260
|
-
let connectedPeers;
|
|
261
285
|
if (block.blockInput === null) {
|
|
262
|
-
connectedPeers = allPeers;
|
|
263
286
|
// we only have block root, and nothing else
|
|
264
|
-
res = await wrapError(this.fetchUnknownBlockRoot(fromHex(block.blockRootHex)
|
|
287
|
+
res = await wrapError(this.fetchUnknownBlockRoot(fromHex(block.blockRootHex)));
|
|
265
288
|
}
|
|
266
289
|
else {
|
|
267
|
-
|
|
268
|
-
if (cachedData.fork === ForkName.fulu) {
|
|
269
|
-
const { dataColumnsCache } = cachedData;
|
|
270
|
-
const sampledColumns = this.network.custodyConfig.sampledColumns;
|
|
271
|
-
const neededColumns = sampledColumns.reduce((acc, elem) => {
|
|
272
|
-
if (dataColumnsCache.get(elem) === undefined) {
|
|
273
|
-
acc.push(elem);
|
|
274
|
-
}
|
|
275
|
-
return acc;
|
|
276
|
-
}, []);
|
|
277
|
-
connectedPeers =
|
|
278
|
-
neededColumns.length <= 0
|
|
279
|
-
? allPeers
|
|
280
|
-
: allPeers.filter((peer) => {
|
|
281
|
-
const { custodyGroups: peerColumns } = this.network.getConnectedPeerSyncMeta(peer);
|
|
282
|
-
const columns = peerColumns.reduce((acc, elem) => {
|
|
283
|
-
if (neededColumns.includes(elem)) {
|
|
284
|
-
acc.push(elem);
|
|
285
|
-
}
|
|
286
|
-
return acc;
|
|
287
|
-
}, []);
|
|
288
|
-
return columns.length > 0;
|
|
289
|
-
});
|
|
290
|
-
if (connectedPeers.length > 0) {
|
|
291
|
-
this.logger.debug("Filtered peers to those having relevant columns for downloading data", {
|
|
292
|
-
...logCtx,
|
|
293
|
-
allPeers: allPeers.length,
|
|
294
|
-
connectedPeers: connectedPeers.length,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
this.logger.debug("Skipping download as no filtered peers having relevant data", {
|
|
299
|
-
...logCtx,
|
|
300
|
-
allPeers: allPeers.length,
|
|
301
|
-
connectedPeers: connectedPeers.length,
|
|
302
|
-
neededColumns: neededColumns.join(" "),
|
|
303
|
-
});
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
connectedPeers = allPeers;
|
|
309
|
-
}
|
|
310
|
-
res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput, connectedPeers));
|
|
290
|
+
res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput));
|
|
311
291
|
}
|
|
312
292
|
if (res.err)
|
|
313
293
|
this.metrics?.syncUnknownBlock.downloadedBlocksError.inc();
|
|
@@ -315,77 +295,57 @@ export class UnknownBlockSync {
|
|
|
315
295
|
this.metrics?.syncUnknownBlock.downloadedBlocksSuccess.inc();
|
|
316
296
|
if (!res.err) {
|
|
317
297
|
const { blockInput, peerIdStr } = res.result;
|
|
298
|
+
// fetchUnknownBlockRoot and fetchUnavailableBlockInput should return available data BlockInput, throw error if not
|
|
318
299
|
if (blockInput.type === BlockInputType.dataPromise) {
|
|
319
300
|
// if there were any peers who would have had the missing datacolumns, it would have resulted in err
|
|
320
|
-
|
|
321
|
-
...block,
|
|
322
|
-
blockInput,
|
|
323
|
-
unknownBlockType: PendingBlockType.UNKNOWN_DATA,
|
|
324
|
-
};
|
|
325
|
-
block.blockInput = blockInput;
|
|
326
|
-
this.pendingBlocks.set(block.blockRootHex, block);
|
|
327
|
-
block.status = PendingBlockStatus.pending;
|
|
328
|
-
// parentSlot > finalizedSlot, continue downloading parent of parent
|
|
329
|
-
block.downloadAttempts += this.config.CUSTODY_REQUIREMENT / NUMBER_OF_COLUMNS;
|
|
330
|
-
const errorData = { root: block.blockRootHex, attempts: block.downloadAttempts, unknownBlockType };
|
|
331
|
-
if (block.downloadAttempts > MAX_ATTEMPTS_PER_BLOCK) {
|
|
332
|
-
// Give up on this block and assume it does not exist, penalizing all peers as if it was a bad block
|
|
333
|
-
this.logger.debug("Ignoring unknown block after many failed downloads", errorData);
|
|
334
|
-
this.removeAndDownscoreAllDescendants(block);
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
// Try again when a new peer connects, its status changes, or a new unknownBlockParent event happens
|
|
338
|
-
this.logger.debug("Error downloading full unknown block", errorData);
|
|
339
|
-
}
|
|
301
|
+
throw Error(`Expected BlockInput to be available, got dataPromise for ${block.blockRootHex}`);
|
|
340
302
|
}
|
|
341
|
-
|
|
342
|
-
block
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
303
|
+
block = {
|
|
304
|
+
...block,
|
|
305
|
+
status: PendingBlockStatus.downloaded,
|
|
306
|
+
blockInput,
|
|
307
|
+
parentBlockRootHex: toRootHex(blockInput.block.message.parentRoot),
|
|
308
|
+
};
|
|
309
|
+
this.pendingBlocks.set(block.blockRootHex, block);
|
|
310
|
+
const blockSlot = blockInput.block.message.slot;
|
|
311
|
+
const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
|
|
312
|
+
const delaySec = Date.now() / 1000 - (this.chain.genesisTime + blockSlot * this.config.SECONDS_PER_SLOT);
|
|
313
|
+
this.metrics?.syncUnknownBlock.elapsedTimeTillReceived.observe(delaySec);
|
|
314
|
+
const parentInForkchoice = this.chain.forkChoice.hasBlock(blockInput.block.message.parentRoot);
|
|
315
|
+
this.logger.verbose("Downloaded unknown block", {
|
|
316
|
+
root: block.blockRootHex,
|
|
317
|
+
pendingBlocks: this.pendingBlocks.size,
|
|
318
|
+
parentInForkchoice,
|
|
319
|
+
blockInputType: blockInput.type,
|
|
320
|
+
unknownBlockType,
|
|
321
|
+
});
|
|
322
|
+
if (parentInForkchoice) {
|
|
323
|
+
// Bingo! Process block. Add to pending blocks anyway for recycle the cache that prevents duplicate processing
|
|
324
|
+
this.processBlock(block).catch((e) => {
|
|
325
|
+
this.logger.debug("Unexpected error - process newly downloaded block", {}, e);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
else if (blockSlot <= finalizedSlot) {
|
|
329
|
+
// the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
|
|
330
|
+
// we should found it through forkchoice. If not, we should penalize all peers sending us this block chain
|
|
331
|
+
// 0 - 1 - ... - n - finalizedSlot
|
|
332
|
+
// \
|
|
333
|
+
// parent 1 - parent 2 - ... - unknownParent block
|
|
334
|
+
const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(blockInput.block.message);
|
|
335
|
+
this.logger.debug("Downloaded block is before finalized slot", {
|
|
336
|
+
finalizedSlot,
|
|
337
|
+
blockSlot,
|
|
338
|
+
parentRoot: toRootHex(blockRoot),
|
|
359
339
|
unknownBlockType,
|
|
360
340
|
});
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
else if (blockSlot <= finalizedSlot) {
|
|
368
|
-
// the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
|
|
369
|
-
// we should found it through forkchoice. If not, we should penalize all peers sending us this block chain
|
|
370
|
-
// 0 - 1 - ... - n - finalizedSlot
|
|
371
|
-
// \
|
|
372
|
-
// parent 1 - parent 2 - ... - unknownParent block
|
|
373
|
-
const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(blockInput.block.message);
|
|
374
|
-
this.logger.debug("Downloaded block is before finalized slot", {
|
|
375
|
-
finalizedSlot,
|
|
376
|
-
blockSlot,
|
|
377
|
-
parentRoot: toRootHex(blockRoot),
|
|
378
|
-
unknownBlockType,
|
|
379
|
-
});
|
|
380
|
-
this.removeAndDownscoreAllDescendants(block);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
this.onUnknownParent({ blockInput, peer: peerIdStr });
|
|
384
|
-
}
|
|
341
|
+
this.removeAndDownscoreAllDescendants(block);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.onUnknownParent({ blockInput, peer: peerIdStr });
|
|
385
345
|
}
|
|
386
346
|
}
|
|
387
347
|
else {
|
|
388
|
-
// this allows to retry the download of the block
|
|
348
|
+
// block download has error, this allows to retry the download of the block
|
|
389
349
|
block.status = PendingBlockStatus.pending;
|
|
390
350
|
// parentSlot > finalizedSlot, continue downloading parent of parent
|
|
391
351
|
block.downloadAttempts++;
|
|
@@ -417,7 +377,7 @@ export class UnknownBlockSync {
|
|
|
417
377
|
return;
|
|
418
378
|
}
|
|
419
379
|
// if the download is a success we'll call `processBlock()` for this block
|
|
420
|
-
await this.downloadBlock(pendingBlock
|
|
380
|
+
await this.downloadBlock(pendingBlock);
|
|
421
381
|
}
|
|
422
382
|
return;
|
|
423
383
|
}
|
|
@@ -505,44 +465,27 @@ export class UnknownBlockSync {
|
|
|
505
465
|
* - from deneb, fetch all missing blobs
|
|
506
466
|
* - from peerDAS, fetch sampled colmns
|
|
507
467
|
* TODO: this means we only have block root, and nothing else. Consider to reflect this in the function name
|
|
508
|
-
*
|
|
468
|
+
* prefulu, will attempt a max of `MAX_ATTEMPTS_PER_BLOCK` on different peers, postfulu we may attempt more as defined in `getMaxDownloadAttempts()` function
|
|
509
469
|
* Also verifies the received block root + returns the peer that provided the block for future downscoring.
|
|
510
470
|
*/
|
|
511
|
-
async fetchUnknownBlockRoot(blockRoot
|
|
512
|
-
const shuffledPeers = shuffle(connectedPeers);
|
|
471
|
+
async fetchUnknownBlockRoot(blockRoot) {
|
|
513
472
|
const blockRootHex = toRootHex(blockRoot);
|
|
514
|
-
|
|
473
|
+
const excludedPeers = new Set();
|
|
515
474
|
let partialDownload = null;
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const { dataColumnsCache } = cachedData;
|
|
528
|
-
const sampledColumns = this.network.custodyConfig.sampledColumns;
|
|
529
|
-
const neededColumns = sampledColumns.reduce((acc, elem) => {
|
|
530
|
-
if (dataColumnsCache.get(elem) === undefined) {
|
|
531
|
-
acc.push(elem);
|
|
532
|
-
}
|
|
533
|
-
return acc;
|
|
534
|
-
}, []);
|
|
535
|
-
const columns = peerColumns.reduce((acc, elem) => {
|
|
536
|
-
if (neededColumns.includes(elem)) {
|
|
537
|
-
acc.push(elem);
|
|
538
|
-
}
|
|
539
|
-
return acc;
|
|
540
|
-
}, []);
|
|
541
|
-
if (columns.length === 0) {
|
|
542
|
-
continue;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
475
|
+
const defaultPendingColumns = this.config.getForkSeq(this.chain.clock.currentSlot) >= ForkSeq.fulu
|
|
476
|
+
? new Set(this.network.custodyConfig.sampleGroups)
|
|
477
|
+
: null;
|
|
478
|
+
let lastError = null;
|
|
479
|
+
let i = 0;
|
|
480
|
+
while (i++ < this.getMaxDownloadAttempts()) {
|
|
481
|
+
// pendingDataColumns is null prefulu
|
|
482
|
+
const peer = this.peerBalancer.bestPeerForPendingColumns(partialDownload ? new Set(partialDownload.pendingDataColumns) : defaultPendingColumns, excludedPeers);
|
|
483
|
+
if (peer === null) {
|
|
484
|
+
// no more peer with needed columns to try, throw error
|
|
485
|
+
throw Error(`Error fetching UnknownBlockRoot after ${i}: cannot find peer with needed columns ${partialDownload?.pendingDataColumns.join(", ")}`);
|
|
545
486
|
}
|
|
487
|
+
const { peerId, client: peerClient } = peer;
|
|
488
|
+
excludedPeers.add(peerId);
|
|
546
489
|
try {
|
|
547
490
|
const { blocks: [blockInput], pendingDataColumns, } = await beaconBlocksMaybeBlobsByRoot(this.config, this.network, peerId, [blockRoot], partialDownload, peerClient, this.metrics, this.logger);
|
|
548
491
|
// Peer does not have the block, try with next peer
|
|
@@ -551,10 +494,9 @@ export class UnknownBlockSync {
|
|
|
551
494
|
}
|
|
552
495
|
if (pendingDataColumns !== null) {
|
|
553
496
|
partialDownload = { blocks: [blockInput], pendingDataColumns };
|
|
554
|
-
fetchedPeerId = peerId;
|
|
555
497
|
continue;
|
|
556
498
|
}
|
|
557
|
-
//
|
|
499
|
+
// data is available, verify block root is correct
|
|
558
500
|
const block = blockInput.block.message;
|
|
559
501
|
const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block);
|
|
560
502
|
if (!byteArrayEquals(receivedBlockRoot, blockRoot)) {
|
|
@@ -566,29 +508,26 @@ export class UnknownBlockSync {
|
|
|
566
508
|
this.logger.debug("Error fetching UnknownBlockRoot", { attempt: i, blockRootHex, peer: peerId }, e);
|
|
567
509
|
lastError = e;
|
|
568
510
|
}
|
|
511
|
+
finally {
|
|
512
|
+
this.peerBalancer.onRequestCompleted(peerId);
|
|
513
|
+
}
|
|
569
514
|
}
|
|
570
515
|
if (lastError) {
|
|
571
|
-
lastError.message = `Error fetching UnknownBlockRoot after ${
|
|
516
|
+
lastError.message = `Error fetching UnknownBlockRoot after ${i} attempts: ${lastError.message}`;
|
|
572
517
|
throw lastError;
|
|
573
518
|
}
|
|
574
|
-
|
|
575
|
-
const { blocks: [blockInput], } = partialDownload;
|
|
576
|
-
return { blockInput, peerIdStr: fetchedPeerId };
|
|
577
|
-
}
|
|
578
|
-
throw Error(`Error fetching UnknownBlockRoot after ${MAX_ATTEMPTS_PER_BLOCK}: unknown error because either partialDownload is null=${partialDownload === null} or fetchedPeerId is null=${fetchedPeerId === null} `);
|
|
519
|
+
throw Error(`Error fetching UnknownBlockRoot after ${i}: cannot download all blobs or data columns for block ${blockRootHex}`);
|
|
579
520
|
}
|
|
580
521
|
/**
|
|
581
522
|
* We have partial block input:
|
|
582
523
|
* - we have block but not have all blobs (deneb) or needed columns (fulu)
|
|
583
524
|
* - we don't have block and have some blobs (deneb) or some columns (fulu)
|
|
584
|
-
* Fetches missing
|
|
585
|
-
* along with the blobs (i.e. only some blobs are available)
|
|
525
|
+
* Fetches missing block/data columns/block for the blockinput. This function returns either preData or availableData BlockInput.
|
|
586
526
|
*/
|
|
587
|
-
async fetchUnavailableBlockInput(unavailableBlockInput
|
|
527
|
+
async fetchUnavailableBlockInput(unavailableBlockInput) {
|
|
588
528
|
if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.dataPromise) {
|
|
589
529
|
return { blockInput: unavailableBlockInput, peerIdStr: "" };
|
|
590
530
|
}
|
|
591
|
-
const shuffledPeers = shuffle(connectedPeers);
|
|
592
531
|
let blockRootHex;
|
|
593
532
|
let blobKzgCommitmentsLen;
|
|
594
533
|
let blockRoot;
|
|
@@ -616,30 +555,16 @@ export class UnknownBlockSync {
|
|
|
616
555
|
}
|
|
617
556
|
}
|
|
618
557
|
let lastError = null;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const neededColumns = sampledColumns.reduce((acc, elem) => {
|
|
627
|
-
if (dataColumnsCache.get(elem) === undefined) {
|
|
628
|
-
acc.push(elem);
|
|
629
|
-
}
|
|
630
|
-
return acc;
|
|
631
|
-
}, []);
|
|
632
|
-
const columns = peerColumns.reduce((acc, elem) => {
|
|
633
|
-
if (neededColumns.includes(elem)) {
|
|
634
|
-
acc.push(elem);
|
|
635
|
-
}
|
|
636
|
-
return acc;
|
|
637
|
-
}, []);
|
|
638
|
-
if (columns.length === 0) {
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
558
|
+
let i = 0;
|
|
559
|
+
const excludedPeers = new Set();
|
|
560
|
+
while (i++ < this.getMaxDownloadAttempts()) {
|
|
561
|
+
const bestPeer = this.peerBalancer.bestPeerForBlockInput(unavailableBlockInput, excludedPeers);
|
|
562
|
+
if (bestPeer === null) {
|
|
563
|
+
// no more peer to try, throw error
|
|
564
|
+
throw Error(`Error fetching UnavailableBlockInput after ${i}: cannot find peer with needed columns ${sampledColumns.join(", ")}`);
|
|
642
565
|
}
|
|
566
|
+
const { peerId, client: peerClient } = bestPeer;
|
|
567
|
+
excludedPeers.add(peerId);
|
|
643
568
|
try {
|
|
644
569
|
const blockInput = await unavailableBeaconBlobsByRoot(this.config, this.network, peerId, peerClient, unavailableBlockInput, {
|
|
645
570
|
metrics: this.metrics,
|
|
@@ -649,16 +574,12 @@ export class UnknownBlockSync {
|
|
|
649
574
|
blockInputsRetryTrackerCache: this.blockInputsRetryTrackerCache,
|
|
650
575
|
engineGetBlobsCache: this.engineGetBlobsCache,
|
|
651
576
|
});
|
|
652
|
-
// Peer does not have the block, try with next peer
|
|
653
|
-
if (blockInput === undefined) {
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
577
|
if (unavailableBlockInput.block !== null && blockInput.type === BlockInputType.dataPromise) {
|
|
657
578
|
// all datacolumns were not downloaded we can continue with other peers
|
|
658
579
|
// as unavailableBlockInput.block's dataColumnsCache would be updated
|
|
659
580
|
continue;
|
|
660
581
|
}
|
|
661
|
-
//
|
|
582
|
+
// data is available, verify block root is correct
|
|
662
583
|
const block = blockInput.block.message;
|
|
663
584
|
const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block);
|
|
664
585
|
if (!byteArrayEquals(receivedBlockRoot, blockRoot)) {
|
|
@@ -676,12 +597,15 @@ export class UnknownBlockSync {
|
|
|
676
597
|
this.logger.debug("Error fetching UnavailableBlockInput", { attempt: i, blockRootHex, peer: peerId }, e);
|
|
677
598
|
lastError = e;
|
|
678
599
|
}
|
|
600
|
+
finally {
|
|
601
|
+
this.peerBalancer.onRequestCompleted(peerId);
|
|
602
|
+
}
|
|
679
603
|
}
|
|
680
604
|
if (lastError) {
|
|
681
|
-
lastError.message = `Error fetching UnavailableBlockInput after ${
|
|
605
|
+
lastError.message = `Error fetching UnavailableBlockInput after ${i} attempts: ${lastError.message}`;
|
|
682
606
|
throw lastError;
|
|
683
607
|
}
|
|
684
|
-
throw Error(`Error fetching UnavailableBlockInput after ${
|
|
608
|
+
throw Error(`Error fetching UnavailableBlockInput after ${i}: unknown error`);
|
|
685
609
|
}
|
|
686
610
|
/**
|
|
687
611
|
* Gets all descendant blocks of `block` recursively from `pendingBlocks`.
|
|
@@ -720,5 +644,160 @@ export class UnknownBlockSync {
|
|
|
720
644
|
}
|
|
721
645
|
return badPendingBlocks;
|
|
722
646
|
}
|
|
647
|
+
getMaxDownloadAttempts() {
|
|
648
|
+
if (this.config.getForkSeq(this.chain.clock.currentSlot) < ForkSeq.fulu) {
|
|
649
|
+
return MAX_ATTEMPTS_PER_BLOCK;
|
|
650
|
+
}
|
|
651
|
+
// TODO: I consider max 20 downloads per block for a supernode is enough for devnets
|
|
652
|
+
// review this computation for public testnets or mainnet
|
|
653
|
+
return Math.min(20, (MAX_ATTEMPTS_PER_BLOCK * this.network.custodyConfig.sampleGroups.length) / this.config.SAMPLES_PER_SLOT);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Class to track active byRoots requests and balance them across eligible peers.
|
|
658
|
+
*/
|
|
659
|
+
export class UnknownBlockPeerBalancer {
|
|
660
|
+
constructor(custodyConfig) {
|
|
661
|
+
this.peersMeta = new Map();
|
|
662
|
+
this.activeRequests = new Map();
|
|
663
|
+
this.custodyConfig = custodyConfig;
|
|
664
|
+
}
|
|
665
|
+
/** Trigger on each peer re-status */
|
|
666
|
+
onPeerConnected(peerId, syncMeta) {
|
|
667
|
+
this.peersMeta.set(peerId, syncMeta);
|
|
668
|
+
if (!this.activeRequests.has(peerId)) {
|
|
669
|
+
this.activeRequests.set(peerId, 0);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
onPeerDisconnected(peerId) {
|
|
673
|
+
this.peersMeta.delete(peerId);
|
|
674
|
+
this.activeRequests.delete(peerId);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* called from fetchUnknownBlockRoot() where we only have block root and nothing else
|
|
678
|
+
* excludedPeers are the peers that we requested already so we don't want to try again
|
|
679
|
+
* pendingColumns is empty for prefulu, or the 1st time we we download a block by root
|
|
680
|
+
*/
|
|
681
|
+
bestPeerForPendingColumns(pendingColumns, excludedPeers) {
|
|
682
|
+
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
|
|
683
|
+
if (eligiblePeers.length === 0) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
const sortedEligiblePeers = sortBy(shuffle(eligiblePeers),
|
|
687
|
+
// prefer peers with least active req
|
|
688
|
+
(peerId) => this.activeRequests.get(peerId) ?? 0);
|
|
689
|
+
const bestPeerId = sortedEligiblePeers[0];
|
|
690
|
+
this.onRequest(bestPeerId);
|
|
691
|
+
return this.peersMeta.get(bestPeerId) ?? null;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* called from fetchUnavailableBlockInput() where we have either BlockInput or NullBlockInput
|
|
695
|
+
* excludedPeers are the peers that we requested already so we don't want to try again
|
|
696
|
+
*/
|
|
697
|
+
bestPeerForBlockInput(unavailableBlockInput, excludedPeers) {
|
|
698
|
+
let cachedData = undefined;
|
|
699
|
+
if (unavailableBlockInput.block === null) {
|
|
700
|
+
// NullBlockInput
|
|
701
|
+
cachedData = unavailableBlockInput.cachedData;
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
// BlockInput
|
|
705
|
+
if (unavailableBlockInput.type !== BlockInputType.dataPromise) {
|
|
706
|
+
throw Error(`bestPeerForBlockInput called with BlockInput type ${unavailableBlockInput.type}, expected dataPromise`);
|
|
707
|
+
}
|
|
708
|
+
cachedData = unavailableBlockInput.cachedData;
|
|
709
|
+
}
|
|
710
|
+
const eligiblePeers = [];
|
|
711
|
+
if (cachedData.fork === ForkName.fulu) {
|
|
712
|
+
// cached data is CachedDataColumns
|
|
713
|
+
const { dataColumnsCache } = cachedData;
|
|
714
|
+
const pendingDataColumns = new Set();
|
|
715
|
+
for (const column of this.custodyConfig.sampledColumns) {
|
|
716
|
+
if (!dataColumnsCache.has(column)) {
|
|
717
|
+
pendingDataColumns.add(column);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
// there could be no pending column in case of NullBlockInput
|
|
721
|
+
eligiblePeers.push(...this.filterPeers(pendingDataColumns, excludedPeers));
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
// prefulu
|
|
725
|
+
const pendingDataColumns = null;
|
|
726
|
+
eligiblePeers.push(...this.filterPeers(pendingDataColumns, excludedPeers));
|
|
727
|
+
}
|
|
728
|
+
if (eligiblePeers.length === 0) {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
const sortedEligiblePeers = sortBy(shuffle(eligiblePeers),
|
|
732
|
+
// prefer peers with least active req
|
|
733
|
+
(peerId) => this.activeRequests.get(peerId) ?? 0);
|
|
734
|
+
const bestPeerId = sortedEligiblePeers[0];
|
|
735
|
+
this.onRequest(bestPeerId);
|
|
736
|
+
return this.peersMeta.get(bestPeerId) ?? null;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Consumers don't need to call this method directly, it is called internally by bestPeer*() methods
|
|
740
|
+
* make this public for testing
|
|
741
|
+
*/
|
|
742
|
+
onRequest(peerId) {
|
|
743
|
+
this.activeRequests.set(peerId, (this.activeRequests.get(peerId) ?? 0) + 1);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Consumers should call this method when a request is completed for a peer.
|
|
747
|
+
*/
|
|
748
|
+
onRequestCompleted(peerId) {
|
|
749
|
+
this.activeRequests.set(peerId, Math.max(0, (this.activeRequests.get(peerId) ?? 1) - 1));
|
|
750
|
+
}
|
|
751
|
+
getTotalActiveRequests() {
|
|
752
|
+
let totalActiveRequests = 0;
|
|
753
|
+
for (const count of this.activeRequests.values()) {
|
|
754
|
+
totalActiveRequests += count;
|
|
755
|
+
}
|
|
756
|
+
return totalActiveRequests;
|
|
757
|
+
}
|
|
758
|
+
// pendingDataColumns could be null for prefulu
|
|
759
|
+
filterPeers(pendingDataColumns, excludedPeers) {
|
|
760
|
+
let maxColumnCount = 0;
|
|
761
|
+
const considerPeers = [];
|
|
762
|
+
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
763
|
+
if (excludedPeers.has(peerId)) {
|
|
764
|
+
// made request to this peer already
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const activeRequests = this.activeRequests.get(peerId) ?? 0;
|
|
768
|
+
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
769
|
+
// should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (pendingDataColumns === null || pendingDataColumns.size === 0) {
|
|
773
|
+
// prefulu, no pending columns
|
|
774
|
+
considerPeers.push({ peerId, columnCount: 0 });
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
// postfulu, find peers that have custody columns that we need
|
|
778
|
+
const { custodyGroups: peerColumns } = syncMeta;
|
|
779
|
+
// check if the peer has all needed columns
|
|
780
|
+
// get match
|
|
781
|
+
const columns = peerColumns.reduce((acc, elem) => {
|
|
782
|
+
if (pendingDataColumns.has(elem)) {
|
|
783
|
+
acc.push(elem);
|
|
784
|
+
}
|
|
785
|
+
return acc;
|
|
786
|
+
}, []);
|
|
787
|
+
if (columns.length > 0) {
|
|
788
|
+
if (columns.length > maxColumnCount) {
|
|
789
|
+
maxColumnCount = columns.length;
|
|
790
|
+
}
|
|
791
|
+
considerPeers.push({ peerId, columnCount: columns.length });
|
|
792
|
+
}
|
|
793
|
+
} // end for
|
|
794
|
+
const eligiblePeers = [];
|
|
795
|
+
for (const { peerId, columnCount } of considerPeers) {
|
|
796
|
+
if (columnCount === maxColumnCount) {
|
|
797
|
+
eligiblePeers.push(peerId);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return eligiblePeers;
|
|
801
|
+
}
|
|
723
802
|
}
|
|
724
803
|
//# sourceMappingURL=unknownBlock.js.map
|