@lodestar/beacon-node 1.34.0-dev.25bf0b65b8 → 1.34.0-dev.26ea9fa05b
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 +58 -37
- 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 +15 -16
- 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 +39 -28
- 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.d.ts +8 -80
- package/lib/chain/chain.js +51 -76
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +3 -10
- package/lib/chain/interface.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 +4 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +30 -18
- package/lib/chain/produceBlock/produceBlockBody.js +27 -32
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/produceBlock/validateBlobsAndKzgCommitments.d.ts +6 -4
- package/lib/chain/produceBlock/validateBlobsAndKzgCommitments.js +22 -21
- package/lib/chain/produceBlock/validateBlobsAndKzgCommitments.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/builder/http.d.ts +20 -4
- package/lib/execution/builder/http.js +30 -11
- package/lib/execution/builder/http.js.map +1 -1
- package/lib/execution/builder/interface.d.ts +5 -4
- package/lib/execution/engine/http.d.ts +2 -2
- package/lib/execution/engine/http.js +10 -5
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -11
- 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 +5 -5
- package/lib/execution/engine/types.js +2 -2
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +2 -5
- package/lib/metrics/metrics/beacon.js +9 -14
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +6 -0
- package/lib/metrics/metrics/lodestar.js +20 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/core/networkCore.d.ts +5 -0
- package/lib/network/core/networkCore.js +44 -14
- package/lib/network/core/networkCore.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts +2 -2
- package/lib/network/gossip/gossipsub.js +8 -6
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/scoringParameters.d.ts +6 -2
- package/lib/network/gossip/scoringParameters.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +586 -53
- package/lib/network/gossip/topic.js +15 -6
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/interface.d.ts +3 -3
- package/lib/network/network.d.ts +3 -3
- package/lib/network/network.js +6 -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 +23 -11
- 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 +44 -35
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRange.d.ts +6 -3
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRange.js +45 -17
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRange.js.map +1 -1
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.d.ts +7 -2
- package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.js +7 -7
- 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/network/subnets/interface.js +2 -2
- package/lib/network/subnets/interface.js.map +1 -1
- package/lib/sync/range/chain.d.ts +1 -1
- package/lib/sync/range/chain.js +2 -2
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.js +2 -2
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/range/utils/peerBalancer.d.ts +3 -1
- package/lib/sync/range/utils/peerBalancer.js +20 -1
- package/lib/sync/range/utils/peerBalancer.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +46 -4
- package/lib/sync/unknownBlock.js +306 -203
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/blobs.d.ts +3 -13
- package/lib/util/blobs.js +9 -47
- package/lib/util/blobs.js.map +1 -1
- package/lib/util/dataColumns.d.ts +7 -5
- package/lib/util/dataColumns.js +37 -28
- 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 +19 -20
- 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,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ForkName, INTERVALS_PER_SLOT, NUMBER_OF_COLUMNS } from "@lodestar/params";
|
|
1
|
+
import { ForkName, ForkSeq, INTERVALS_PER_SLOT } from "@lodestar/params";
|
|
3
2
|
import { fromHex, pruneSetToMax, toRootHex } from "@lodestar/utils";
|
|
4
3
|
import { sleep } from "@lodestar/utils";
|
|
5
4
|
import { BlockInputType } from "../chain/blocks/types.js";
|
|
@@ -8,7 +7,9 @@ import { NetworkEvent } from "../network/index.js";
|
|
|
8
7
|
import { beaconBlocksMaybeBlobsByRoot, unavailableBeaconBlobsByRoot, } from "../network/reqresp/beaconBlocksMaybeBlobsByRoot.js";
|
|
9
8
|
import { byteArrayEquals } from "../util/bytes.js";
|
|
10
9
|
import { shuffle } from "../util/shuffle.js";
|
|
10
|
+
import { sortBy } from "../util/sortBy.js";
|
|
11
11
|
import { wrapError } from "../util/wrapError.js";
|
|
12
|
+
import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
|
|
12
13
|
import { PendingBlockStatus, PendingBlockType } from "./interface.js";
|
|
13
14
|
import { getAllDescendantBlocks, getDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
|
|
14
15
|
const MAX_ATTEMPTS_PER_BLOCK = 5;
|
|
@@ -69,6 +70,21 @@ export class UnknownBlockSync {
|
|
|
69
70
|
this.logger.debug("Error handling unknownBlockParent event", {}, e);
|
|
70
71
|
}
|
|
71
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
|
+
};
|
|
72
88
|
/**
|
|
73
89
|
* Gather tip parent blocks with unknown parent and do a search for all of them
|
|
74
90
|
*/
|
|
@@ -106,16 +122,22 @@ export class UnknownBlockSync {
|
|
|
106
122
|
}
|
|
107
123
|
// most of the time there is exactly 1 unknown block
|
|
108
124
|
for (const block of unknowns) {
|
|
109
|
-
this.downloadBlock(block
|
|
125
|
+
this.downloadBlock(block).catch((e) => {
|
|
110
126
|
this.logger.debug("Unexpected error - downloadBlock", { root: block.blockRootHex }, e);
|
|
111
127
|
});
|
|
112
128
|
}
|
|
113
129
|
};
|
|
114
130
|
this.maxPendingBlocks = opts?.maxPendingBlocks ?? MAX_PENDING_BLOCKS;
|
|
115
131
|
this.proposerBoostSecWindow = this.config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT;
|
|
132
|
+
this.peerBalancer = new UnknownBlockPeerBalancer(this.network.custodyConfig);
|
|
116
133
|
if (metrics) {
|
|
117
|
-
metrics.syncUnknownBlock.pendingBlocks.addCollect(() =>
|
|
118
|
-
|
|
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
|
+
});
|
|
119
141
|
}
|
|
120
142
|
}
|
|
121
143
|
subscribeToNetwork() {
|
|
@@ -126,7 +148,8 @@ export class UnknownBlockSync {
|
|
|
126
148
|
this.network.events.on(NetworkEvent.unknownBlock, this.onUnknownBlock);
|
|
127
149
|
this.network.events.on(NetworkEvent.unknownBlockInput, this.onUnknownBlockInput);
|
|
128
150
|
this.network.events.on(NetworkEvent.unknownBlockParent, this.onUnknownParent);
|
|
129
|
-
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);
|
|
130
153
|
this.subscribedToNetworkEvents = true;
|
|
131
154
|
}
|
|
132
155
|
}
|
|
@@ -139,7 +162,8 @@ export class UnknownBlockSync {
|
|
|
139
162
|
this.network.events.off(NetworkEvent.unknownBlock, this.onUnknownBlock);
|
|
140
163
|
this.network.events.off(NetworkEvent.unknownBlockInput, this.onUnknownBlockInput);
|
|
141
164
|
this.network.events.off(NetworkEvent.unknownBlockParent, this.onUnknownParent);
|
|
142
|
-
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);
|
|
143
167
|
this.subscribedToNetworkEvents = false;
|
|
144
168
|
}
|
|
145
169
|
close() {
|
|
@@ -151,7 +175,7 @@ export class UnknownBlockSync {
|
|
|
151
175
|
}
|
|
152
176
|
/**
|
|
153
177
|
* When a blockInput comes with an unknown parent:
|
|
154
|
-
* - add the block to pendingBlocks with status downloaded
|
|
178
|
+
* - add the block to pendingBlocks with status downloaded or pending blockRootHex as key. This is similar to
|
|
155
179
|
* an `onUnknownBlock` event, but the blocks is downloaded.
|
|
156
180
|
* - add the parent root to pendingBlocks with status pending, parentBlockRootHex as key. This is
|
|
157
181
|
* the same to an `onUnknownBlock` event with parentBlockRootHex as root.
|
|
@@ -164,14 +188,26 @@ export class UnknownBlockSync {
|
|
|
164
188
|
// add 1 pending block with status downloaded
|
|
165
189
|
let pendingBlock = this.pendingBlocks.get(blockRootHex);
|
|
166
190
|
if (!pendingBlock) {
|
|
167
|
-
pendingBlock =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
pendingBlock =
|
|
192
|
+
blockInput.type === BlockInputType.dataPromise
|
|
193
|
+
? {
|
|
194
|
+
unknownBlockType: PendingBlockType.UNKNOWN_DATA,
|
|
195
|
+
blockRootHex,
|
|
196
|
+
// this will be set after we download block
|
|
197
|
+
parentBlockRootHex: null,
|
|
198
|
+
blockInput,
|
|
199
|
+
peerIdStrs: new Set(),
|
|
200
|
+
status: PendingBlockStatus.pending,
|
|
201
|
+
downloadAttempts: 0,
|
|
202
|
+
}
|
|
203
|
+
: {
|
|
204
|
+
blockRootHex,
|
|
205
|
+
parentBlockRootHex,
|
|
206
|
+
blockInput,
|
|
207
|
+
peerIdStrs: new Set(),
|
|
208
|
+
status: PendingBlockStatus.downloaded,
|
|
209
|
+
downloadAttempts: 0,
|
|
210
|
+
};
|
|
175
211
|
this.pendingBlocks.set(blockRootHex, pendingBlock);
|
|
176
212
|
this.logger.verbose("Added unknown block parent to pendingBlocks", {
|
|
177
213
|
root: blockRootHex,
|
|
@@ -194,7 +230,7 @@ export class UnknownBlockSync {
|
|
|
194
230
|
else {
|
|
195
231
|
if (blockInputOrRootHex.block !== null) {
|
|
196
232
|
const { block } = blockInputOrRootHex;
|
|
197
|
-
blockRootHex =
|
|
233
|
+
blockRootHex = toRootHex(this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message));
|
|
198
234
|
unknownBlockType = PendingBlockType.UNKNOWN_DATA;
|
|
199
235
|
}
|
|
200
236
|
else {
|
|
@@ -208,6 +244,7 @@ export class UnknownBlockSync {
|
|
|
208
244
|
pendingBlock = {
|
|
209
245
|
unknownBlockType,
|
|
210
246
|
blockRootHex,
|
|
247
|
+
// this will be set after we download block
|
|
211
248
|
parentBlockRootHex: null,
|
|
212
249
|
blockInput,
|
|
213
250
|
peerIdStrs: new Set(),
|
|
@@ -231,7 +268,7 @@ export class UnknownBlockSync {
|
|
|
231
268
|
}
|
|
232
269
|
return unknownBlockType;
|
|
233
270
|
}
|
|
234
|
-
async downloadBlock(block
|
|
271
|
+
async downloadBlock(block) {
|
|
235
272
|
if (block.status !== PendingBlockStatus.pending) {
|
|
236
273
|
return;
|
|
237
274
|
}
|
|
@@ -245,57 +282,12 @@ export class UnknownBlockSync {
|
|
|
245
282
|
this.logger.verbose("Downloading unknown block", logCtx);
|
|
246
283
|
block.status = PendingBlockStatus.fetching;
|
|
247
284
|
let res;
|
|
248
|
-
let connectedPeers;
|
|
249
285
|
if (block.blockInput === null) {
|
|
250
|
-
connectedPeers = allPeers;
|
|
251
286
|
// we only have block root, and nothing else
|
|
252
|
-
res = await wrapError(this.fetchUnknownBlockRoot(
|
|
287
|
+
res = await wrapError(this.fetchUnknownBlockRoot(fromHex(block.blockRootHex)));
|
|
253
288
|
}
|
|
254
289
|
else {
|
|
255
|
-
|
|
256
|
-
if (cachedData.fork === ForkName.fulu) {
|
|
257
|
-
const { dataColumnsCache } = cachedData;
|
|
258
|
-
const sampledColumns = this.network.custodyConfig.sampledColumns;
|
|
259
|
-
const neededColumns = sampledColumns.reduce((acc, elem) => {
|
|
260
|
-
if (dataColumnsCache.get(elem) === undefined) {
|
|
261
|
-
acc.push(elem);
|
|
262
|
-
}
|
|
263
|
-
return acc;
|
|
264
|
-
}, []);
|
|
265
|
-
connectedPeers =
|
|
266
|
-
neededColumns.length <= 0
|
|
267
|
-
? allPeers
|
|
268
|
-
: allPeers.filter((peer) => {
|
|
269
|
-
const { custodyGroups: peerColumns } = this.network.getConnectedPeerSyncMeta(peer);
|
|
270
|
-
const columns = peerColumns.reduce((acc, elem) => {
|
|
271
|
-
if (neededColumns.includes(elem)) {
|
|
272
|
-
acc.push(elem);
|
|
273
|
-
}
|
|
274
|
-
return acc;
|
|
275
|
-
}, []);
|
|
276
|
-
return columns.length > 0;
|
|
277
|
-
});
|
|
278
|
-
if (connectedPeers.length > 0) {
|
|
279
|
-
this.logger.debug("Filtered peers to those having relevant columns for downloading data", {
|
|
280
|
-
...logCtx,
|
|
281
|
-
allPeers: allPeers.length,
|
|
282
|
-
connectedPeers: connectedPeers.length,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
this.logger.debug("Skipping download as no filtered peers having relevant data", {
|
|
287
|
-
...logCtx,
|
|
288
|
-
allPeers: allPeers.length,
|
|
289
|
-
connectedPeers: connectedPeers.length,
|
|
290
|
-
neededColumns: neededColumns.join(" "),
|
|
291
|
-
});
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
connectedPeers = allPeers;
|
|
297
|
-
}
|
|
298
|
-
res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput, connectedPeers));
|
|
290
|
+
res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput));
|
|
299
291
|
}
|
|
300
292
|
if (res.err)
|
|
301
293
|
this.metrics?.syncUnknownBlock.downloadedBlocksError.inc();
|
|
@@ -303,77 +295,57 @@ export class UnknownBlockSync {
|
|
|
303
295
|
this.metrics?.syncUnknownBlock.downloadedBlocksSuccess.inc();
|
|
304
296
|
if (!res.err) {
|
|
305
297
|
const { blockInput, peerIdStr } = res.result;
|
|
298
|
+
// fetchUnknownBlockRoot and fetchUnavailableBlockInput should return available data BlockInput, throw error if not
|
|
306
299
|
if (blockInput.type === BlockInputType.dataPromise) {
|
|
307
300
|
// if there were any peers who would have had the missing datacolumns, it would have resulted in err
|
|
308
|
-
|
|
309
|
-
...block,
|
|
310
|
-
blockInput,
|
|
311
|
-
unknownBlockType: PendingBlockType.UNKNOWN_DATA,
|
|
312
|
-
};
|
|
313
|
-
block.blockInput = blockInput;
|
|
314
|
-
this.pendingBlocks.set(block.blockRootHex, block);
|
|
315
|
-
block.status = PendingBlockStatus.pending;
|
|
316
|
-
// parentSlot > finalizedSlot, continue downloading parent of parent
|
|
317
|
-
block.downloadAttempts += this.config.CUSTODY_REQUIREMENT / NUMBER_OF_COLUMNS;
|
|
318
|
-
const errorData = { root: block.blockRootHex, attempts: block.downloadAttempts, unknownBlockType };
|
|
319
|
-
if (block.downloadAttempts > MAX_ATTEMPTS_PER_BLOCK) {
|
|
320
|
-
// Give up on this block and assume it does not exist, penalizing all peers as if it was a bad block
|
|
321
|
-
this.logger.debug("Ignoring unknown block after many failed downloads", errorData);
|
|
322
|
-
this.removeAndDownscoreAllDescendants(block);
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
// Try again when a new peer connects, its status changes, or a new unknownBlockParent event happens
|
|
326
|
-
this.logger.debug("Error downloading full unknown block", errorData);
|
|
327
|
-
}
|
|
301
|
+
throw Error(`Expected BlockInput to be available, got dataPromise for ${block.blockRootHex}`);
|
|
328
302
|
}
|
|
329
|
-
|
|
330
|
-
block
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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),
|
|
347
339
|
unknownBlockType,
|
|
348
340
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
else if (blockSlot <= finalizedSlot) {
|
|
356
|
-
// the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
|
|
357
|
-
// we should found it through forkchoice. If not, we should penalize all peers sending us this block chain
|
|
358
|
-
// 0 - 1 - ... - n - finalizedSlot
|
|
359
|
-
// \
|
|
360
|
-
// parent 1 - parent 2 - ... - unknownParent block
|
|
361
|
-
const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(blockInput.block.message);
|
|
362
|
-
this.logger.debug("Downloaded block is before finalized slot", {
|
|
363
|
-
finalizedSlot,
|
|
364
|
-
blockSlot,
|
|
365
|
-
parentRoot: toHexString(blockRoot),
|
|
366
|
-
unknownBlockType,
|
|
367
|
-
});
|
|
368
|
-
this.removeAndDownscoreAllDescendants(block);
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
this.onUnknownParent({ blockInput, peer: peerIdStr });
|
|
372
|
-
}
|
|
341
|
+
this.removeAndDownscoreAllDescendants(block);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.onUnknownParent({ blockInput, peer: peerIdStr });
|
|
373
345
|
}
|
|
374
346
|
}
|
|
375
347
|
else {
|
|
376
|
-
// this allows to retry the download of the block
|
|
348
|
+
// block download has error, this allows to retry the download of the block
|
|
377
349
|
block.status = PendingBlockStatus.pending;
|
|
378
350
|
// parentSlot > finalizedSlot, continue downloading parent of parent
|
|
379
351
|
block.downloadAttempts++;
|
|
@@ -392,9 +364,21 @@ export class UnknownBlockSync {
|
|
|
392
364
|
/**
|
|
393
365
|
* Send block to the processor awaiting completition. If processed successfully, send all children to the processor.
|
|
394
366
|
* On error, remove and downscore all descendants.
|
|
367
|
+
* This function could run recursively for all descendant blocks
|
|
395
368
|
*/
|
|
396
369
|
async processBlock(pendingBlock) {
|
|
370
|
+
// pending block status is `downloaded` right after `downloadBlock`
|
|
371
|
+
// but could be `pending` if added by `onUnknownBlockParent` event and this function is called recursively
|
|
397
372
|
if (pendingBlock.status !== PendingBlockStatus.downloaded) {
|
|
373
|
+
if (pendingBlock.status === PendingBlockStatus.pending) {
|
|
374
|
+
const connectedPeers = this.network.getConnectedPeers();
|
|
375
|
+
if (connectedPeers.length === 0) {
|
|
376
|
+
this.logger.debug("No connected peers, skipping download block", { blockRoot: pendingBlock.blockRootHex });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// if the download is a success we'll call `processBlock()` for this block
|
|
380
|
+
await this.downloadBlock(pendingBlock);
|
|
381
|
+
}
|
|
398
382
|
return;
|
|
399
383
|
}
|
|
400
384
|
pendingBlock.status = PendingBlockStatus.processing;
|
|
@@ -481,44 +465,27 @@ export class UnknownBlockSync {
|
|
|
481
465
|
* - from deneb, fetch all missing blobs
|
|
482
466
|
* - from peerDAS, fetch sampled colmns
|
|
483
467
|
* TODO: this means we only have block root, and nothing else. Consider to reflect this in the function name
|
|
484
|
-
*
|
|
468
|
+
* prefulu, will attempt a max of `MAX_ATTEMPTS_PER_BLOCK` on different peers, postfulu we may attempt more as defined in `getMaxDownloadAttempts()` function
|
|
485
469
|
* Also verifies the received block root + returns the peer that provided the block for future downscoring.
|
|
486
470
|
*/
|
|
487
|
-
async fetchUnknownBlockRoot(blockRoot
|
|
488
|
-
const shuffledPeers = shuffle(connectedPeers);
|
|
471
|
+
async fetchUnknownBlockRoot(blockRoot) {
|
|
489
472
|
const blockRootHex = toRootHex(blockRoot);
|
|
490
|
-
|
|
473
|
+
const excludedPeers = new Set();
|
|
491
474
|
let partialDownload = null;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const { dataColumnsCache } = cachedData;
|
|
504
|
-
const sampledColumns = this.network.custodyConfig.sampledColumns;
|
|
505
|
-
const neededColumns = sampledColumns.reduce((acc, elem) => {
|
|
506
|
-
if (dataColumnsCache.get(elem) === undefined) {
|
|
507
|
-
acc.push(elem);
|
|
508
|
-
}
|
|
509
|
-
return acc;
|
|
510
|
-
}, []);
|
|
511
|
-
const columns = peerColumns.reduce((acc, elem) => {
|
|
512
|
-
if (neededColumns.includes(elem)) {
|
|
513
|
-
acc.push(elem);
|
|
514
|
-
}
|
|
515
|
-
return acc;
|
|
516
|
-
}, []);
|
|
517
|
-
if (columns.length === 0) {
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
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(", ")}`);
|
|
521
486
|
}
|
|
487
|
+
const { peerId, client: peerClient } = peer;
|
|
488
|
+
excludedPeers.add(peerId);
|
|
522
489
|
try {
|
|
523
490
|
const { blocks: [blockInput], pendingDataColumns, } = await beaconBlocksMaybeBlobsByRoot(this.config, this.network, peerId, [blockRoot], partialDownload, peerClient, this.metrics, this.logger);
|
|
524
491
|
// Peer does not have the block, try with next peer
|
|
@@ -527,10 +494,9 @@ export class UnknownBlockSync {
|
|
|
527
494
|
}
|
|
528
495
|
if (pendingDataColumns !== null) {
|
|
529
496
|
partialDownload = { blocks: [blockInput], pendingDataColumns };
|
|
530
|
-
fetchedPeerId = peerId;
|
|
531
497
|
continue;
|
|
532
498
|
}
|
|
533
|
-
//
|
|
499
|
+
// data is available, verify block root is correct
|
|
534
500
|
const block = blockInput.block.message;
|
|
535
501
|
const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block);
|
|
536
502
|
if (!byteArrayEquals(receivedBlockRoot, blockRoot)) {
|
|
@@ -542,29 +508,26 @@ export class UnknownBlockSync {
|
|
|
542
508
|
this.logger.debug("Error fetching UnknownBlockRoot", { attempt: i, blockRootHex, peer: peerId }, e);
|
|
543
509
|
lastError = e;
|
|
544
510
|
}
|
|
511
|
+
finally {
|
|
512
|
+
this.peerBalancer.onRequestCompleted(peerId);
|
|
513
|
+
}
|
|
545
514
|
}
|
|
546
515
|
if (lastError) {
|
|
547
|
-
lastError.message = `Error fetching UnknownBlockRoot after ${
|
|
516
|
+
lastError.message = `Error fetching UnknownBlockRoot after ${i} attempts: ${lastError.message}`;
|
|
548
517
|
throw lastError;
|
|
549
518
|
}
|
|
550
|
-
|
|
551
|
-
const { blocks: [blockInput], } = partialDownload;
|
|
552
|
-
return { blockInput, peerIdStr: fetchedPeerId };
|
|
553
|
-
}
|
|
554
|
-
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}`);
|
|
555
520
|
}
|
|
556
521
|
/**
|
|
557
522
|
* We have partial block input:
|
|
558
523
|
* - we have block but not have all blobs (deneb) or needed columns (fulu)
|
|
559
524
|
* - we don't have block and have some blobs (deneb) or some columns (fulu)
|
|
560
|
-
* Fetches missing
|
|
561
|
-
* 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.
|
|
562
526
|
*/
|
|
563
|
-
async fetchUnavailableBlockInput(unavailableBlockInput
|
|
527
|
+
async fetchUnavailableBlockInput(unavailableBlockInput) {
|
|
564
528
|
if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.dataPromise) {
|
|
565
529
|
return { blockInput: unavailableBlockInput, peerIdStr: "" };
|
|
566
530
|
}
|
|
567
|
-
const shuffledPeers = shuffle(connectedPeers);
|
|
568
531
|
let blockRootHex;
|
|
569
532
|
let blobKzgCommitmentsLen;
|
|
570
533
|
let blockRoot;
|
|
@@ -592,30 +555,16 @@ export class UnknownBlockSync {
|
|
|
592
555
|
}
|
|
593
556
|
}
|
|
594
557
|
let lastError = null;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const neededColumns = sampledColumns.reduce((acc, elem) => {
|
|
603
|
-
if (dataColumnsCache.get(elem) === undefined) {
|
|
604
|
-
acc.push(elem);
|
|
605
|
-
}
|
|
606
|
-
return acc;
|
|
607
|
-
}, []);
|
|
608
|
-
const columns = peerColumns.reduce((acc, elem) => {
|
|
609
|
-
if (neededColumns.includes(elem)) {
|
|
610
|
-
acc.push(elem);
|
|
611
|
-
}
|
|
612
|
-
return acc;
|
|
613
|
-
}, []);
|
|
614
|
-
if (columns.length === 0) {
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
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(", ")}`);
|
|
618
565
|
}
|
|
566
|
+
const { peerId, client: peerClient } = bestPeer;
|
|
567
|
+
excludedPeers.add(peerId);
|
|
619
568
|
try {
|
|
620
569
|
const blockInput = await unavailableBeaconBlobsByRoot(this.config, this.network, peerId, peerClient, unavailableBlockInput, {
|
|
621
570
|
metrics: this.metrics,
|
|
@@ -625,16 +574,12 @@ export class UnknownBlockSync {
|
|
|
625
574
|
blockInputsRetryTrackerCache: this.blockInputsRetryTrackerCache,
|
|
626
575
|
engineGetBlobsCache: this.engineGetBlobsCache,
|
|
627
576
|
});
|
|
628
|
-
// Peer does not have the block, try with next peer
|
|
629
|
-
if (blockInput === undefined) {
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
577
|
if (unavailableBlockInput.block !== null && blockInput.type === BlockInputType.dataPromise) {
|
|
633
578
|
// all datacolumns were not downloaded we can continue with other peers
|
|
634
579
|
// as unavailableBlockInput.block's dataColumnsCache would be updated
|
|
635
580
|
continue;
|
|
636
581
|
}
|
|
637
|
-
//
|
|
582
|
+
// data is available, verify block root is correct
|
|
638
583
|
const block = blockInput.block.message;
|
|
639
584
|
const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block);
|
|
640
585
|
if (!byteArrayEquals(receivedBlockRoot, blockRoot)) {
|
|
@@ -652,12 +597,15 @@ export class UnknownBlockSync {
|
|
|
652
597
|
this.logger.debug("Error fetching UnavailableBlockInput", { attempt: i, blockRootHex, peer: peerId }, e);
|
|
653
598
|
lastError = e;
|
|
654
599
|
}
|
|
600
|
+
finally {
|
|
601
|
+
this.peerBalancer.onRequestCompleted(peerId);
|
|
602
|
+
}
|
|
655
603
|
}
|
|
656
604
|
if (lastError) {
|
|
657
|
-
lastError.message = `Error fetching UnavailableBlockInput after ${
|
|
605
|
+
lastError.message = `Error fetching UnavailableBlockInput after ${i} attempts: ${lastError.message}`;
|
|
658
606
|
throw lastError;
|
|
659
607
|
}
|
|
660
|
-
throw Error(`Error fetching UnavailableBlockInput after ${
|
|
608
|
+
throw Error(`Error fetching UnavailableBlockInput after ${i}: unknown error`);
|
|
661
609
|
}
|
|
662
610
|
/**
|
|
663
611
|
* Gets all descendant blocks of `block` recursively from `pendingBlocks`.
|
|
@@ -696,5 +644,160 @@ export class UnknownBlockSync {
|
|
|
696
644
|
}
|
|
697
645
|
return badPendingBlocks;
|
|
698
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
|
+
}
|
|
699
802
|
}
|
|
700
803
|
//# sourceMappingURL=unknownBlock.js.map
|