@lodestar/beacon-node 1.34.0-dev.0c7dfe25ea → 1.34.0-dev.11ca515d52

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.
Files changed (184) hide show
  1. package/lib/api/impl/beacon/blocks/index.js +52 -40
  2. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  3. package/lib/api/impl/beacon/pool/index.js +5 -5
  4. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  5. package/lib/api/impl/beacon/state/index.js +15 -16
  6. package/lib/api/impl/beacon/state/index.js.map +1 -1
  7. package/lib/api/impl/debug/index.js +2 -2
  8. package/lib/api/impl/debug/index.js.map +1 -1
  9. package/lib/api/impl/events/index.js +1 -1
  10. package/lib/api/impl/events/index.js.map +1 -1
  11. package/lib/api/impl/validator/index.js +64 -47
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/api/impl/validator/utils.d.ts +3 -3
  14. package/lib/api/impl/validator/utils.js +2 -2
  15. package/lib/api/impl/validator/utils.js.map +1 -1
  16. package/lib/chain/archiveStore/utils/archiveBlocks.js +19 -20
  17. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  18. package/lib/chain/blocks/utils/blowfishBanner.js +1 -0
  19. package/lib/chain/blocks/utils/blowfishBanner.js.map +1 -1
  20. package/lib/chain/blocks/utils/giraffeBanner.js +1 -0
  21. package/lib/chain/blocks/utils/giraffeBanner.js.map +1 -1
  22. package/lib/chain/blocks/utils/zebraBanner.d.ts +2 -0
  23. package/lib/chain/blocks/utils/zebraBanner.js +46 -0
  24. package/lib/chain/blocks/utils/zebraBanner.js.map +1 -0
  25. package/lib/chain/blocks/verifyBlock.js +18 -5
  26. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  27. package/lib/chain/blocks/writeBlockInputToDb.js +7 -34
  28. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  29. package/lib/chain/bls/multithread/index.js +2 -2
  30. package/lib/chain/bls/multithread/index.js.map +1 -1
  31. package/lib/chain/chain.d.ts +8 -80
  32. package/lib/chain/chain.js +52 -84
  33. package/lib/chain/chain.js.map +1 -1
  34. package/lib/chain/forkChoice/index.d.ts +2 -1
  35. package/lib/chain/forkChoice/index.js +2 -2
  36. package/lib/chain/forkChoice/index.js.map +1 -1
  37. package/lib/chain/interface.d.ts +3 -10
  38. package/lib/chain/interface.js.map +1 -1
  39. package/lib/chain/opPools/aggregatedAttestationPool.js +13 -3
  40. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  41. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  42. package/lib/chain/opPools/attestationPool.js +7 -7
  43. package/lib/chain/prepareNextSlot.js +4 -2
  44. package/lib/chain/prepareNextSlot.js.map +1 -1
  45. package/lib/chain/produceBlock/produceBlockBody.d.ts +30 -18
  46. package/lib/chain/produceBlock/produceBlockBody.js +27 -32
  47. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  48. package/lib/chain/produceBlock/validateBlobsAndKzgCommitments.d.ts +6 -4
  49. package/lib/chain/produceBlock/validateBlobsAndKzgCommitments.js +21 -23
  50. package/lib/chain/produceBlock/validateBlobsAndKzgCommitments.js.map +1 -1
  51. package/lib/chain/validation/aggregateAndProof.d.ts +1 -1
  52. package/lib/chain/validation/aggregateAndProof.js +8 -8
  53. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  54. package/lib/chain/validation/attestation.d.ts +3 -3
  55. package/lib/chain/validation/attestation.js +10 -10
  56. package/lib/chain/validation/attestation.js.map +1 -1
  57. package/lib/chain/validation/dataColumnSidecar.d.ts +2 -1
  58. package/lib/chain/validation/dataColumnSidecar.js +17 -8
  59. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  60. package/lib/db/beacon.d.ts +3 -3
  61. package/lib/db/beacon.js +3 -3
  62. package/lib/db/beacon.js.map +1 -1
  63. package/lib/db/interface.d.ts +3 -3
  64. package/lib/db/repositories/dataColumnSidecar.d.ts +26 -0
  65. package/lib/db/repositories/dataColumnSidecar.js +39 -0
  66. package/lib/db/repositories/dataColumnSidecar.js.map +1 -0
  67. package/lib/db/repositories/dataColumnSidecarArchive.d.ts +24 -0
  68. package/lib/db/repositories/dataColumnSidecarArchive.js +39 -0
  69. package/lib/db/repositories/dataColumnSidecarArchive.js.map +1 -0
  70. package/lib/db/repositories/index.d.ts +2 -2
  71. package/lib/db/repositories/index.js +2 -2
  72. package/lib/db/repositories/index.js.map +1 -1
  73. package/lib/db/repositories/stateArchive.js +1 -1
  74. package/lib/db/repositories/stateArchive.js.map +1 -1
  75. package/lib/execution/builder/http.d.ts +20 -4
  76. package/lib/execution/builder/http.js +30 -11
  77. package/lib/execution/builder/http.js.map +1 -1
  78. package/lib/execution/builder/interface.d.ts +5 -4
  79. package/lib/execution/engine/http.d.ts +2 -2
  80. package/lib/execution/engine/http.js +10 -5
  81. package/lib/execution/engine/http.js.map +1 -1
  82. package/lib/execution/engine/interface.d.ts +2 -11
  83. package/lib/execution/engine/mock.d.ts +4 -1
  84. package/lib/execution/engine/mock.js +54 -16
  85. package/lib/execution/engine/mock.js.map +1 -1
  86. package/lib/execution/engine/types.d.ts +5 -5
  87. package/lib/execution/engine/types.js +2 -2
  88. package/lib/execution/engine/types.js.map +1 -1
  89. package/lib/execution/engine/utils.js +1 -1
  90. package/lib/execution/engine/utils.js.map +1 -1
  91. package/lib/metrics/metrics/beacon.d.ts +2 -28
  92. package/lib/metrics/metrics/beacon.js +9 -75
  93. package/lib/metrics/metrics/beacon.js.map +1 -1
  94. package/lib/metrics/metrics/lodestar.d.ts +6 -0
  95. package/lib/metrics/metrics/lodestar.js +14 -0
  96. package/lib/metrics/metrics/lodestar.js.map +1 -1
  97. package/lib/metrics/metrics.d.ts +2 -1
  98. package/lib/metrics/metrics.js +3 -0
  99. package/lib/metrics/metrics.js.map +1 -1
  100. package/lib/network/core/networkCore.js +0 -1
  101. package/lib/network/core/networkCore.js.map +1 -1
  102. package/lib/network/core/networkCoreWorkerHandler.js +1 -1
  103. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  104. package/lib/network/gossip/scoringParameters.js.map +1 -1
  105. package/lib/network/gossip/topic.d.ts +620 -92
  106. package/lib/network/interface.d.ts +3 -3
  107. package/lib/network/network.d.ts +3 -3
  108. package/lib/network/network.js +1 -1
  109. package/lib/network/network.js.map +1 -1
  110. package/lib/network/options.js +2 -2
  111. package/lib/network/peers/discover.js +1 -1
  112. package/lib/network/peers/discover.js.map +1 -1
  113. package/lib/network/peers/peerManager.js +9 -6
  114. package/lib/network/peers/peerManager.js.map +1 -1
  115. package/lib/network/peers/utils/prioritizePeers.d.ts +2 -1
  116. package/lib/network/peers/utils/prioritizePeers.js +5 -5
  117. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  118. package/lib/network/processor/gossipHandlers.js +22 -19
  119. package/lib/network/processor/gossipHandlers.js.map +1 -1
  120. package/lib/network/reqresp/beaconBlocksMaybeBlobsByRange.d.ts +6 -3
  121. package/lib/network/reqresp/beaconBlocksMaybeBlobsByRange.js +45 -17
  122. package/lib/network/reqresp/beaconBlocksMaybeBlobsByRange.js.map +1 -1
  123. package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.d.ts +7 -2
  124. package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.js +7 -7
  125. package/lib/network/reqresp/beaconBlocksMaybeBlobsByRoot.js.map +1 -1
  126. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +3 -3
  127. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  128. package/lib/network/reqresp/handlers/beaconBlocksByRoot.d.ts +2 -2
  129. package/lib/network/reqresp/handlers/beaconBlocksByRoot.js.map +1 -1
  130. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts +2 -2
  131. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +2 -3
  132. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  133. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts +3 -3
  134. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +55 -46
  135. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  136. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts +2 -2
  137. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +43 -35
  138. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  139. package/lib/network/reqresp/handlers/index.js +4 -3
  140. package/lib/network/reqresp/handlers/index.js.map +1 -1
  141. package/lib/network/reqresp/rateLimit.js +11 -5
  142. package/lib/network/reqresp/rateLimit.js.map +1 -1
  143. package/lib/network/reqresp/types.d.ts +3 -3
  144. package/lib/network/reqresp/types.js +3 -3
  145. package/lib/network/reqresp/types.js.map +1 -1
  146. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +14 -0
  147. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +56 -0
  148. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -0
  149. package/lib/sync/backfill/backfill.js +1 -1
  150. package/lib/sync/backfill/backfill.js.map +1 -1
  151. package/lib/sync/range/chain.d.ts +1 -1
  152. package/lib/sync/range/chain.js +2 -2
  153. package/lib/sync/range/chain.js.map +1 -1
  154. package/lib/sync/range/range.js +2 -2
  155. package/lib/sync/range/range.js.map +1 -1
  156. package/lib/sync/range/utils/peerBalancer.d.ts +3 -1
  157. package/lib/sync/range/utils/peerBalancer.js +20 -1
  158. package/lib/sync/range/utils/peerBalancer.js.map +1 -1
  159. package/lib/sync/unknownBlock.d.ts +46 -4
  160. package/lib/sync/unknownBlock.js +305 -201
  161. package/lib/sync/unknownBlock.js.map +1 -1
  162. package/lib/util/blobs.d.ts +3 -13
  163. package/lib/util/blobs.js +9 -47
  164. package/lib/util/blobs.js.map +1 -1
  165. package/lib/util/dataColumns.d.ts +7 -5
  166. package/lib/util/dataColumns.js +36 -27
  167. package/lib/util/dataColumns.js.map +1 -1
  168. package/lib/util/queue/fnQueue.js +1 -1
  169. package/lib/util/queue/fnQueue.js.map +1 -1
  170. package/lib/util/queue/itemQueue.js +1 -1
  171. package/lib/util/queue/itemQueue.js.map +1 -1
  172. package/lib/util/sszBytes.d.ts +2 -0
  173. package/lib/util/sszBytes.js +23 -0
  174. package/lib/util/sszBytes.js.map +1 -1
  175. package/lib/util/types.d.ts +7 -0
  176. package/lib/util/types.js +3 -0
  177. package/lib/util/types.js.map +1 -1
  178. package/package.json +17 -18
  179. package/lib/db/repositories/dataColumnSidecars.d.ts +0 -47
  180. package/lib/db/repositories/dataColumnSidecars.js +0 -40
  181. package/lib/db/repositories/dataColumnSidecars.js.map +0 -1
  182. package/lib/db/repositories/dataColumnSidecarsArchive.d.ts +0 -15
  183. package/lib/db/repositories/dataColumnSidecarsArchive.js +0 -23
  184. package/lib/db/repositories/dataColumnSidecarsArchive.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { ForkName, INTERVALS_PER_SLOT, NUMBER_OF_COLUMNS } from "@lodestar/params";
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, connectedPeers).catch((e) => {
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(() => metrics.syncUnknownBlock.pendingBlocks.set(this.pendingBlocks.size));
117
- metrics.syncUnknownBlock.knownBadBlocks.addCollect(() => metrics.syncUnknownBlock.knownBadBlocks.set(this.knownBadBlocks.size));
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.triggerUnknownBlockSearch);
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.triggerUnknownBlockSearch);
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() {
@@ -150,7 +175,7 @@ export class UnknownBlockSync {
150
175
  }
151
176
  /**
152
177
  * When a blockInput comes with an unknown parent:
153
- * - add the block to pendingBlocks with status downloaded, blockRootHex as key. This is similar to
178
+ * - add the block to pendingBlocks with status downloaded or pending blockRootHex as key. This is similar to
154
179
  * an `onUnknownBlock` event, but the blocks is downloaded.
155
180
  * - add the parent root to pendingBlocks with status pending, parentBlockRootHex as key. This is
156
181
  * the same to an `onUnknownBlock` event with parentBlockRootHex as root.
@@ -163,14 +188,26 @@ export class UnknownBlockSync {
163
188
  // add 1 pending block with status downloaded
164
189
  let pendingBlock = this.pendingBlocks.get(blockRootHex);
165
190
  if (!pendingBlock) {
166
- pendingBlock = {
167
- blockRootHex,
168
- parentBlockRootHex,
169
- blockInput,
170
- peerIdStrs: new Set(),
171
- status: PendingBlockStatus.downloaded,
172
- downloadAttempts: 0,
173
- };
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
+ };
174
211
  this.pendingBlocks.set(blockRootHex, pendingBlock);
175
212
  this.logger.verbose("Added unknown block parent to pendingBlocks", {
176
213
  root: blockRootHex,
@@ -207,6 +244,7 @@ export class UnknownBlockSync {
207
244
  pendingBlock = {
208
245
  unknownBlockType,
209
246
  blockRootHex,
247
+ // this will be set after we download block
210
248
  parentBlockRootHex: null,
211
249
  blockInput,
212
250
  peerIdStrs: new Set(),
@@ -230,7 +268,7 @@ export class UnknownBlockSync {
230
268
  }
231
269
  return unknownBlockType;
232
270
  }
233
- async downloadBlock(block, allPeers) {
271
+ async downloadBlock(block) {
234
272
  if (block.status !== PendingBlockStatus.pending) {
235
273
  return;
236
274
  }
@@ -244,57 +282,12 @@ export class UnknownBlockSync {
244
282
  this.logger.verbose("Downloading unknown block", logCtx);
245
283
  block.status = PendingBlockStatus.fetching;
246
284
  let res;
247
- let connectedPeers;
248
285
  if (block.blockInput === null) {
249
- connectedPeers = allPeers;
250
286
  // we only have block root, and nothing else
251
- res = await wrapError(this.fetchUnknownBlockRoot(fromHex(block.blockRootHex), connectedPeers));
287
+ res = await wrapError(this.fetchUnknownBlockRoot(fromHex(block.blockRootHex)));
252
288
  }
253
289
  else {
254
- const { cachedData } = block.blockInput;
255
- if (cachedData.fork === ForkName.fulu) {
256
- const { dataColumnsCache } = cachedData;
257
- const sampledColumns = this.network.custodyConfig.sampledColumns;
258
- const neededColumns = sampledColumns.reduce((acc, elem) => {
259
- if (dataColumnsCache.get(elem) === undefined) {
260
- acc.push(elem);
261
- }
262
- return acc;
263
- }, []);
264
- connectedPeers =
265
- neededColumns.length <= 0
266
- ? allPeers
267
- : allPeers.filter((peer) => {
268
- const { custodyGroups: peerColumns } = this.network.getConnectedPeerSyncMeta(peer);
269
- const columns = peerColumns.reduce((acc, elem) => {
270
- if (neededColumns.includes(elem)) {
271
- acc.push(elem);
272
- }
273
- return acc;
274
- }, []);
275
- return columns.length > 0;
276
- });
277
- if (connectedPeers.length > 0) {
278
- this.logger.debug("Filtered peers to those having relevant columns for downloading data", {
279
- ...logCtx,
280
- allPeers: allPeers.length,
281
- connectedPeers: connectedPeers.length,
282
- });
283
- }
284
- else {
285
- this.logger.debug("Skipping download as no filtered peers having relevant data", {
286
- ...logCtx,
287
- allPeers: allPeers.length,
288
- connectedPeers: connectedPeers.length,
289
- neededColumns: neededColumns.join(" "),
290
- });
291
- return;
292
- }
293
- }
294
- else {
295
- connectedPeers = allPeers;
296
- }
297
- res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput, connectedPeers));
290
+ res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput));
298
291
  }
299
292
  if (res.err)
300
293
  this.metrics?.syncUnknownBlock.downloadedBlocksError.inc();
@@ -302,77 +295,57 @@ export class UnknownBlockSync {
302
295
  this.metrics?.syncUnknownBlock.downloadedBlocksSuccess.inc();
303
296
  if (!res.err) {
304
297
  const { blockInput, peerIdStr } = res.result;
298
+ // fetchUnknownBlockRoot and fetchUnavailableBlockInput should return available data BlockInput, throw error if not
305
299
  if (blockInput.type === BlockInputType.dataPromise) {
306
300
  // if there were any peers who would have had the missing datacolumns, it would have resulted in err
307
- block = {
308
- ...block,
309
- blockInput,
310
- unknownBlockType: PendingBlockType.UNKNOWN_DATA,
311
- };
312
- block.blockInput = blockInput;
313
- this.pendingBlocks.set(block.blockRootHex, block);
314
- block.status = PendingBlockStatus.pending;
315
- // parentSlot > finalizedSlot, continue downloading parent of parent
316
- block.downloadAttempts += this.config.CUSTODY_REQUIREMENT / NUMBER_OF_COLUMNS;
317
- const errorData = { root: block.blockRootHex, attempts: block.downloadAttempts, unknownBlockType };
318
- if (block.downloadAttempts > MAX_ATTEMPTS_PER_BLOCK) {
319
- // Give up on this block and assume it does not exist, penalizing all peers as if it was a bad block
320
- this.logger.debug("Ignoring unknown block after many failed downloads", errorData);
321
- this.removeAndDownscoreAllDescendants(block);
322
- }
323
- else {
324
- // Try again when a new peer connects, its status changes, or a new unknownBlockParent event happens
325
- this.logger.debug("Error downloading full unknown block", errorData);
326
- }
301
+ throw Error(`Expected BlockInput to be available, got dataPromise for ${block.blockRootHex}`);
327
302
  }
328
- else {
329
- block = {
330
- ...block,
331
- status: PendingBlockStatus.downloaded,
332
- blockInput,
333
- parentBlockRootHex: toRootHex(blockInput.block.message.parentRoot),
334
- };
335
- this.pendingBlocks.set(block.blockRootHex, block);
336
- const blockSlot = blockInput.block.message.slot;
337
- const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
338
- const delaySec = Date.now() / 1000 - (this.chain.genesisTime + blockSlot * this.config.SECONDS_PER_SLOT);
339
- this.metrics?.syncUnknownBlock.elapsedTimeTillReceived.observe(delaySec);
340
- const parentInForkchoice = this.chain.forkChoice.hasBlock(blockInput.block.message.parentRoot);
341
- this.logger.verbose("Downloaded unknown block", {
342
- root: block.blockRootHex,
343
- pendingBlocks: this.pendingBlocks.size,
344
- parentInForkchoice,
345
- blockInputType: blockInput.type,
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),
346
339
  unknownBlockType,
347
340
  });
348
- if (parentInForkchoice) {
349
- // Bingo! Process block. Add to pending blocks anyway for recycle the cache that prevents duplicate processing
350
- this.processBlock(block).catch((e) => {
351
- this.logger.debug("Unexpected error - process newly downloaded block", {}, e);
352
- });
353
- }
354
- else if (blockSlot <= finalizedSlot) {
355
- // the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
356
- // we should found it through forkchoice. If not, we should penalize all peers sending us this block chain
357
- // 0 - 1 - ... - n - finalizedSlot
358
- // \
359
- // parent 1 - parent 2 - ... - unknownParent block
360
- const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(blockInput.block.message);
361
- this.logger.debug("Downloaded block is before finalized slot", {
362
- finalizedSlot,
363
- blockSlot,
364
- parentRoot: toRootHex(blockRoot),
365
- unknownBlockType,
366
- });
367
- this.removeAndDownscoreAllDescendants(block);
368
- }
369
- else {
370
- this.onUnknownParent({ blockInput, peer: peerIdStr });
371
- }
341
+ this.removeAndDownscoreAllDescendants(block);
342
+ }
343
+ else {
344
+ this.onUnknownParent({ blockInput, peer: peerIdStr });
372
345
  }
373
346
  }
374
347
  else {
375
- // this allows to retry the download of the block
348
+ // block download has error, this allows to retry the download of the block
376
349
  block.status = PendingBlockStatus.pending;
377
350
  // parentSlot > finalizedSlot, continue downloading parent of parent
378
351
  block.downloadAttempts++;
@@ -391,9 +364,21 @@ export class UnknownBlockSync {
391
364
  /**
392
365
  * Send block to the processor awaiting completition. If processed successfully, send all children to the processor.
393
366
  * On error, remove and downscore all descendants.
367
+ * This function could run recursively for all descendant blocks
394
368
  */
395
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
396
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
+ }
397
382
  return;
398
383
  }
399
384
  pendingBlock.status = PendingBlockStatus.processing;
@@ -480,44 +465,27 @@ export class UnknownBlockSync {
480
465
  * - from deneb, fetch all missing blobs
481
466
  * - from peerDAS, fetch sampled colmns
482
467
  * TODO: this means we only have block root, and nothing else. Consider to reflect this in the function name
483
- * Will attempt a max of `MAX_ATTEMPTS_PER_BLOCK` on different peers if connectPeers.length > MAX_ATTEMPTS_PER_BLOCK.
468
+ * prefulu, will attempt a max of `MAX_ATTEMPTS_PER_BLOCK` on different peers, postfulu we may attempt more as defined in `getMaxDownloadAttempts()` function
484
469
  * Also verifies the received block root + returns the peer that provided the block for future downscoring.
485
470
  */
486
- async fetchUnknownBlockRoot(blockRoot, connectedPeers) {
487
- const shuffledPeers = shuffle(connectedPeers);
471
+ async fetchUnknownBlockRoot(blockRoot) {
488
472
  const blockRootHex = toRootHex(blockRoot);
489
- let lastError = null;
473
+ const excludedPeers = new Set();
490
474
  let partialDownload = null;
491
- let fetchedPeerId = null;
492
- for (let i = 0; i < MAX_ATTEMPTS_PER_BLOCK; i++) {
493
- const peerId = shuffledPeers[i % shuffledPeers.length];
494
- const { custodyGroups: peerColumns, client: peerClient } = this.network.getConnectedPeerSyncMeta(peerId);
495
- if (partialDownload !== null) {
496
- const [prevBlockInput] = partialDownload.blocks;
497
- if (prevBlockInput === undefined || prevBlockInput.type !== BlockInputType.dataPromise) {
498
- throw Error(`prevBlockInput=${prevBlockInput?.type} in partialDownload`);
499
- }
500
- const { cachedData } = prevBlockInput;
501
- if (cachedData.fork === ForkName.fulu) {
502
- const { dataColumnsCache } = cachedData;
503
- const sampledColumns = this.network.custodyConfig.sampledColumns;
504
- const neededColumns = sampledColumns.reduce((acc, elem) => {
505
- if (dataColumnsCache.get(elem) === undefined) {
506
- acc.push(elem);
507
- }
508
- return acc;
509
- }, []);
510
- const columns = peerColumns.reduce((acc, elem) => {
511
- if (neededColumns.includes(elem)) {
512
- acc.push(elem);
513
- }
514
- return acc;
515
- }, []);
516
- if (columns.length === 0) {
517
- continue;
518
- }
519
- }
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(", ")}`);
520
486
  }
487
+ const { peerId, client: peerClient } = peer;
488
+ excludedPeers.add(peerId);
521
489
  try {
522
490
  const { blocks: [blockInput], pendingDataColumns, } = await beaconBlocksMaybeBlobsByRoot(this.config, this.network, peerId, [blockRoot], partialDownload, peerClient, this.metrics, this.logger);
523
491
  // Peer does not have the block, try with next peer
@@ -526,10 +494,9 @@ export class UnknownBlockSync {
526
494
  }
527
495
  if (pendingDataColumns !== null) {
528
496
  partialDownload = { blocks: [blockInput], pendingDataColumns };
529
- fetchedPeerId = peerId;
530
497
  continue;
531
498
  }
532
- // Verify block root is correct
499
+ // data is available, verify block root is correct
533
500
  const block = blockInput.block.message;
534
501
  const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block);
535
502
  if (!byteArrayEquals(receivedBlockRoot, blockRoot)) {
@@ -541,29 +508,26 @@ export class UnknownBlockSync {
541
508
  this.logger.debug("Error fetching UnknownBlockRoot", { attempt: i, blockRootHex, peer: peerId }, e);
542
509
  lastError = e;
543
510
  }
511
+ finally {
512
+ this.peerBalancer.onRequestCompleted(peerId);
513
+ }
544
514
  }
545
515
  if (lastError) {
546
- lastError.message = `Error fetching UnknownBlockRoot after ${MAX_ATTEMPTS_PER_BLOCK} attempts: ${lastError.message}`;
516
+ lastError.message = `Error fetching UnknownBlockRoot after ${i} attempts: ${lastError.message}`;
547
517
  throw lastError;
548
518
  }
549
- if (partialDownload !== null && fetchedPeerId !== null) {
550
- const { blocks: [blockInput], } = partialDownload;
551
- return { blockInput, peerIdStr: fetchedPeerId };
552
- }
553
- 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}`);
554
520
  }
555
521
  /**
556
522
  * We have partial block input:
557
523
  * - we have block but not have all blobs (deneb) or needed columns (fulu)
558
524
  * - we don't have block and have some blobs (deneb) or some columns (fulu)
559
- * Fetches missing blobs for the blockinput, in future can also pull block is thats also missing
560
- * 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.
561
526
  */
562
- async fetchUnavailableBlockInput(unavailableBlockInput, connectedPeers) {
527
+ async fetchUnavailableBlockInput(unavailableBlockInput) {
563
528
  if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.dataPromise) {
564
529
  return { blockInput: unavailableBlockInput, peerIdStr: "" };
565
530
  }
566
- const shuffledPeers = shuffle(connectedPeers);
567
531
  let blockRootHex;
568
532
  let blobKzgCommitmentsLen;
569
533
  let blockRoot;
@@ -591,30 +555,16 @@ export class UnknownBlockSync {
591
555
  }
592
556
  }
593
557
  let lastError = null;
594
- for (let i = 0; i < MAX_ATTEMPTS_PER_BLOCK; i++) {
595
- const peerId = shuffledPeers[i % shuffledPeers.length];
596
- const { custodyGroups: peerColumns, client: peerClient } = this.network.getConnectedPeerSyncMeta(peerId);
597
- if (unavailableBlockInput.block !== null) {
598
- const { cachedData } = unavailableBlockInput;
599
- if (cachedData.fork === ForkName.fulu) {
600
- const { dataColumnsCache } = cachedData;
601
- const neededColumns = sampledColumns.reduce((acc, elem) => {
602
- if (dataColumnsCache.get(elem) === undefined) {
603
- acc.push(elem);
604
- }
605
- return acc;
606
- }, []);
607
- const columns = peerColumns.reduce((acc, elem) => {
608
- if (neededColumns.includes(elem)) {
609
- acc.push(elem);
610
- }
611
- return acc;
612
- }, []);
613
- if (columns.length === 0) {
614
- continue;
615
- }
616
- }
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(", ")}`);
617
565
  }
566
+ const { peerId, client: peerClient } = bestPeer;
567
+ excludedPeers.add(peerId);
618
568
  try {
619
569
  const blockInput = await unavailableBeaconBlobsByRoot(this.config, this.network, peerId, peerClient, unavailableBlockInput, {
620
570
  metrics: this.metrics,
@@ -624,16 +574,12 @@ export class UnknownBlockSync {
624
574
  blockInputsRetryTrackerCache: this.blockInputsRetryTrackerCache,
625
575
  engineGetBlobsCache: this.engineGetBlobsCache,
626
576
  });
627
- // Peer does not have the block, try with next peer
628
- if (blockInput === undefined) {
629
- continue;
630
- }
631
577
  if (unavailableBlockInput.block !== null && blockInput.type === BlockInputType.dataPromise) {
632
578
  // all datacolumns were not downloaded we can continue with other peers
633
579
  // as unavailableBlockInput.block's dataColumnsCache would be updated
634
580
  continue;
635
581
  }
636
- // Verify block root is correct
582
+ // data is available, verify block root is correct
637
583
  const block = blockInput.block.message;
638
584
  const receivedBlockRoot = this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block);
639
585
  if (!byteArrayEquals(receivedBlockRoot, blockRoot)) {
@@ -651,12 +597,15 @@ export class UnknownBlockSync {
651
597
  this.logger.debug("Error fetching UnavailableBlockInput", { attempt: i, blockRootHex, peer: peerId }, e);
652
598
  lastError = e;
653
599
  }
600
+ finally {
601
+ this.peerBalancer.onRequestCompleted(peerId);
602
+ }
654
603
  }
655
604
  if (lastError) {
656
- lastError.message = `Error fetching UnavailableBlockInput after ${MAX_ATTEMPTS_PER_BLOCK} attempts: ${lastError.message}`;
605
+ lastError.message = `Error fetching UnavailableBlockInput after ${i} attempts: ${lastError.message}`;
657
606
  throw lastError;
658
607
  }
659
- throw Error(`Error fetching UnavailableBlockInput after ${MAX_ATTEMPTS_PER_BLOCK}: unknown error`);
608
+ throw Error(`Error fetching UnavailableBlockInput after ${i}: unknown error`);
660
609
  }
661
610
  /**
662
611
  * Gets all descendant blocks of `block` recursively from `pendingBlocks`.
@@ -695,5 +644,160 @@ export class UnknownBlockSync {
695
644
  }
696
645
  return badPendingBlocks;
697
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
+ }
698
802
  }
699
803
  //# sourceMappingURL=unknownBlock.js.map