@lodestar/beacon-node 1.50.0-dev.d99080f29a → 1.50.0-dev.e8dec5f2fe
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/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js +13 -0
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +23 -3
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +26 -18
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +1 -0
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +23 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +14 -24
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +1 -1
- package/lib/chain/archiveStore/interface.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +7 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +1 -1
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +4 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +42 -5
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +7 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +1 -0
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +4 -4
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +2 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +2 -0
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +10 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -0
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +21 -0
- package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
- package/lib/chain/shufflingCache.d.ts +1 -0
- package/lib/chain/shufflingCache.d.ts.map +1 -1
- package/lib/chain/shufflingCache.js +4 -0
- package/lib/chain/shufflingCache.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +5 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +64 -54
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts +2 -0
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +3 -0
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/buckets.d.ts +2 -1
- package/lib/db/buckets.d.ts.map +1 -1
- package/lib/db/buckets.js +1 -0
- package/lib/db/buckets.js.map +1 -1
- package/lib/db/interface.d.ts +2 -0
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
- package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
- package/lib/db/repositories/earliestAvailableSlot.js +19 -0
- package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
- package/lib/execution/engine/disabled.d.ts +0 -2
- package/lib/execution/engine/disabled.d.ts.map +1 -1
- package/lib/execution/engine/disabled.js +0 -6
- package/lib/execution/engine/disabled.js.map +1 -1
- package/lib/execution/engine/http.d.ts +1 -3
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +2 -17
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -4
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/mock.d.ts +0 -2
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +0 -8
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/types.d.ts +0 -8
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +1 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +4 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/metrics/metrics.d.ts +7 -3
- package/lib/metrics/metrics.d.ts.map +1 -1
- package/lib/metrics/metrics.js +4 -4
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +59 -63
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/node/nodejs.d.ts +3 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +27 -4
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +6 -0
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +11 -0
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +6 -1
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +5 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +44 -20
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +8 -0
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +32 -9
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/package.json +17 -17
- package/src/api/impl/beacon/index.ts +15 -0
- package/src/api/impl/beacon/state/index.ts +31 -3
- package/src/api/impl/debug/index.ts +28 -19
- package/src/api/impl/validator/index.ts +24 -2
- package/src/chain/archiveStore/archiveStore.ts +19 -25
- package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -2
- package/src/chain/archiveStore/historicalState/worker.ts +1 -2
- package/src/chain/archiveStore/interface.ts +1 -1
- package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
- package/src/chain/blocks/importBlock.ts +9 -3
- package/src/chain/blocks/verifyBlock.ts +1 -0
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
- package/src/chain/chain.ts +59 -4
- package/src/chain/emitter.ts +7 -1
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/interface.ts +1 -0
- package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
- package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
- package/src/chain/options.ts +3 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
- package/src/chain/regen/regen.ts +12 -4
- package/src/chain/seenCache/seenGossipBlockInput.ts +23 -0
- package/src/chain/shufflingCache.ts +5 -0
- package/src/chain/validatorMonitor.ts +75 -57
- package/src/db/beacon.ts +3 -0
- package/src/db/buckets.ts +1 -0
- package/src/db/interface.ts +2 -0
- package/src/db/repositories/earliestAvailableSlot.ts +19 -0
- package/src/execution/engine/disabled.ts +0 -8
- package/src/execution/engine/http.ts +1 -30
- package/src/execution/engine/interface.ts +2 -6
- package/src/execution/engine/mock.ts +1 -19
- package/src/execution/engine/types.ts +0 -10
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/metrics/metrics.ts +21 -6
- package/src/network/processor/gossipHandlers.ts +90 -76
- package/src/node/nodejs.ts +35 -3
- package/src/sync/range/batch.ts +12 -0
- package/src/sync/range/chain.ts +7 -1
- package/src/sync/unknownBlock.ts +57 -21
- package/src/sync/utils/downloadByRange.ts +47 -8
|
@@ -12,9 +12,10 @@ import {
|
|
|
12
12
|
isForkPostElectra,
|
|
13
13
|
isForkPostGloas,
|
|
14
14
|
} from "@lodestar/params";
|
|
15
|
-
import {computeTimeAtSlot} from "@lodestar/state-transition";
|
|
15
|
+
import {computeTimeAtSlot, signedBlockToSignedHeader} from "@lodestar/state-transition";
|
|
16
16
|
import {
|
|
17
17
|
Root,
|
|
18
|
+
RootHex,
|
|
18
19
|
SignedBeaconBlock,
|
|
19
20
|
SingleAttestation,
|
|
20
21
|
Slot,
|
|
@@ -78,6 +79,7 @@ import {
|
|
|
78
79
|
validateGossipSyncCommittee,
|
|
79
80
|
validateGossipVoluntaryExit,
|
|
80
81
|
validateSyncCommitteeGossipContributionAndProof,
|
|
82
|
+
verifyBlockProposerSignature,
|
|
81
83
|
} from "../../chain/validation/index.js";
|
|
82
84
|
import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js";
|
|
83
85
|
import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js";
|
|
@@ -148,37 +150,16 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH
|
|
|
148
150
|
function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): SequentialGossipHandlers {
|
|
149
151
|
const {chain, config, metrics, logger, core} = modules;
|
|
150
152
|
|
|
151
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Add a gossip block to the seen caches. The proposer signature of the block MUST be verified.
|
|
155
|
+
*/
|
|
156
|
+
function cacheSignatureVerifiedBlock(
|
|
152
157
|
signedBlock: SignedBeaconBlock,
|
|
158
|
+
blockRootHex: RootHex,
|
|
153
159
|
fork: ForkName,
|
|
154
160
|
peerIdStr: string,
|
|
155
161
|
seenTimestampSec: number
|
|
156
|
-
):
|
|
157
|
-
const slot = signedBlock.message.slot;
|
|
158
|
-
const forkTypes = config.getForkTypes(slot);
|
|
159
|
-
const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
|
|
160
|
-
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
|
|
161
|
-
const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
|
|
162
|
-
|
|
163
|
-
// always set block to seen cache for all forks so that we don't need to download it
|
|
164
|
-
// TODO: validate block before adding to cache
|
|
165
|
-
// tracked in https://github.com/ChainSafe/lodestar/issues/7957
|
|
166
|
-
|
|
167
|
-
const logCtx = {
|
|
168
|
-
slot,
|
|
169
|
-
root: blockRootHex,
|
|
170
|
-
currentSlot: chain.clock.currentSlot,
|
|
171
|
-
peerId: peerIdStr,
|
|
172
|
-
delaySec,
|
|
173
|
-
recvToValLatency,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
logger.debug("Received gossip block", {...logCtx});
|
|
177
|
-
|
|
178
|
-
// optimistically add gossip block to the seen cache
|
|
179
|
-
// if validation fails, we will NOT forward this gossip block to peers
|
|
180
|
-
// - if PARENT_BLOCK_UNKNOWN error, blockInput will then be queued inside BlockInputSync. If the gossip block is really invalid, it will be pruned there
|
|
181
|
-
// - if other validator errors, blockInput will stay in the seen cache and will be pruned on finalization
|
|
162
|
+
): BlockInput {
|
|
182
163
|
const blockInput = chain.seenBlockInputCache.getByBlock({
|
|
183
164
|
block: signedBlock,
|
|
184
165
|
blockRootHex,
|
|
@@ -187,8 +168,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
187
168
|
peerIdStr,
|
|
188
169
|
});
|
|
189
170
|
|
|
190
|
-
//
|
|
191
|
-
// This ensures we have PayloadEnvelopeInput, even through "PARENT_BLOCK_UNKNOWN" error
|
|
171
|
+
// Also seed the payload-envelope cache, so we have PayloadEnvelopeInput even through "PARENT_BLOCK_UNKNOWN" error
|
|
192
172
|
// see https://github.com/ChainSafe/lodestar/issues/9475
|
|
193
173
|
if (isForkPostGloas(fork)) {
|
|
194
174
|
chain.seenPayloadEnvelopeInputCache.add({
|
|
@@ -202,8 +182,37 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
202
182
|
});
|
|
203
183
|
}
|
|
204
184
|
|
|
185
|
+
return blockInput;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function validateBeaconBlock(
|
|
189
|
+
signedBlock: SignedBeaconBlock,
|
|
190
|
+
fork: ForkName,
|
|
191
|
+
peerIdStr: string,
|
|
192
|
+
seenTimestampSec: number
|
|
193
|
+
): Promise<IBlockInput> {
|
|
194
|
+
const slot = signedBlock.message.slot;
|
|
195
|
+
const forkTypes = config.getForkTypes(slot);
|
|
196
|
+
const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
|
|
197
|
+
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
|
|
198
|
+
const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
|
|
199
|
+
|
|
200
|
+
const logCtx = {
|
|
201
|
+
slot,
|
|
202
|
+
root: blockRootHex,
|
|
203
|
+
currentSlot: chain.clock.currentSlot,
|
|
204
|
+
peerId: peerIdStr,
|
|
205
|
+
delaySec,
|
|
206
|
+
recvToValLatency,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
logger.debug("Received gossip block", {...logCtx});
|
|
210
|
+
|
|
211
|
+
chain.seenBlockInputCache.markValidatingBlock(blockRootHex);
|
|
212
|
+
|
|
205
213
|
try {
|
|
206
214
|
const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
|
|
215
|
+
const blockInput = cacheSignatureVerifiedBlock(signedBlock, blockRootHex, fork, peerIdStr, seenTimestampSec);
|
|
207
216
|
|
|
208
217
|
const blockInputMeta = blockInput.getLogMeta();
|
|
209
218
|
|
|
@@ -229,54 +238,54 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
229
238
|
if (e instanceof BlockGossipError) {
|
|
230
239
|
logger.debug("Gossip block has error", {slot, root: blockRootHex, code: e.type.code});
|
|
231
240
|
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
blockInput
|
|
241
|
+
e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
|
|
242
|
+
e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN
|
|
235
243
|
) {
|
|
244
|
+
// The block is retained for unknown parent sync without full validation. Authenticate it first, this
|
|
245
|
+
// does not need the parent state
|
|
246
|
+
const proposerIndex = signedBlock.message.proposerIndex;
|
|
247
|
+
await verifyBlockProposerSignature(chain, signedBlock, blockRootHex);
|
|
248
|
+
chain.seenBlockProposers.observeBlockRoot(
|
|
249
|
+
slot,
|
|
250
|
+
proposerIndex,
|
|
251
|
+
blockRootHex,
|
|
252
|
+
signedBlockToSignedHeader(config, signedBlock)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Check again after the signature verification so concurrent proposals cannot both be retained
|
|
256
|
+
if (chain.seenBlockProposers.isKnown(slot, proposerIndex)) {
|
|
257
|
+
if (chain.seenBlockProposers.isRepeatProposal(slot, proposerIndex, blockRootHex)) {
|
|
258
|
+
throw new BlockGossipError(GossipAction.IGNORE, {
|
|
259
|
+
code: BlockErrorCode.REPEAT_PROPOSAL,
|
|
260
|
+
proposerIndex,
|
|
261
|
+
root: blockRootHex,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.ALREADY_KNOWN, root: blockRootHex});
|
|
265
|
+
}
|
|
266
|
+
chain.seenBlockProposers.add(slot, proposerIndex, blockRootHex);
|
|
267
|
+
|
|
268
|
+
const blockInput = cacheSignatureVerifiedBlock(signedBlock, blockRootHex, fork, peerIdStr, seenTimestampSec);
|
|
236
269
|
chain.emitter.emit(ChainEvent.blockUnknownParent, {
|
|
237
270
|
blockInput,
|
|
238
271
|
peer: peerIdStr,
|
|
239
272
|
source: BlockInputSource.gossip,
|
|
240
273
|
});
|
|
241
|
-
// throw error (don't prune the blockInput)
|
|
242
274
|
throw e;
|
|
243
275
|
}
|
|
244
276
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
e.type.code === BlockErrorCode.REPEAT_PROPOSAL &&
|
|
254
|
-
!chain.seenBlockProposers.hasBlockRoot(slot, signedBlock.message.proposerIndex, blockRootHex)
|
|
255
|
-
) {
|
|
256
|
-
chain.seenBlockInputCache.remove(blockRootHex);
|
|
257
|
-
if (isForkPostGloas(fork)) {
|
|
258
|
-
chain.seenPayloadEnvelopeInputCache.remove(blockRootHex);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
throw e;
|
|
277
|
+
if (e.action === GossipAction.REJECT) {
|
|
278
|
+
chain.persistInvalidSszValue(
|
|
279
|
+
forkTypes.SignedBeaconBlock,
|
|
280
|
+
signedBlock,
|
|
281
|
+
`gossip_reject_slot_${slot}`,
|
|
282
|
+
blockRootHex
|
|
283
|
+
);
|
|
262
284
|
}
|
|
263
|
-
|
|
264
|
-
chain.persistInvalidSszValue(
|
|
265
|
-
forkTypes.SignedBeaconBlock,
|
|
266
|
-
signedBlock,
|
|
267
|
-
`gossip_reject_slot_${slot}`,
|
|
268
|
-
blockRootHex
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// REJECT or unexpected (non-BlockGossipError) error: drop the optimistically-added entries from
|
|
273
|
-
// both caches, keeping them consistent. The block may carry any parent root, so only its own entry is removed
|
|
274
|
-
chain.seenBlockInputCache.remove(blockRootHex);
|
|
275
|
-
if (isForkPostGloas(fork)) {
|
|
276
|
-
chain.seenPayloadEnvelopeInputCache.remove(blockRootHex);
|
|
277
285
|
}
|
|
278
286
|
throw e;
|
|
279
287
|
} finally {
|
|
288
|
+
chain.seenBlockInputCache.unmarkValidatingBlock(blockRootHex);
|
|
280
289
|
// The block received from the network may have established an equivocation, either by conflicting
|
|
281
290
|
// with a previously observed block root (REPEAT_PROPOSAL) or with a root observed during validation
|
|
282
291
|
const proposerIndex = signedBlock.message.proposerIndex;
|
|
@@ -752,18 +761,23 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
752
761
|
e.type.code === BlockErrorCode.REPEAT_PROPOSAL &&
|
|
753
762
|
// Only a signature-verified sibling recorded in the seen cache is imported. The cache holds at most two
|
|
754
763
|
// roots per proposer and slot, which bounds full imports over gossip to one alternate
|
|
755
|
-
chain.seenBlockProposers.hasBlockRoot(signedBlock.message.slot, e.type.proposerIndex, e.type.root)
|
|
764
|
+
chain.seenBlockProposers.hasBlockRoot(signedBlock.message.slot, e.type.proposerIndex, e.type.root) &&
|
|
765
|
+
// A sibling with an unknown parent cannot be imported, sync fetches it if its branch becomes relevant
|
|
766
|
+
chain.forkChoice.getBlockHexDefaultStatus(toRootHex(signedBlock.message.parentRoot)) !== null
|
|
756
767
|
) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
768
|
+
const blockInput = cacheSignatureVerifiedBlock(
|
|
769
|
+
signedBlock,
|
|
770
|
+
e.type.root,
|
|
771
|
+
topic.boundary.fork,
|
|
772
|
+
peerIdStr,
|
|
773
|
+
seenTimestampSec
|
|
774
|
+
);
|
|
775
|
+
// We're returning IGNORE (thrown below), so this block is not forwarded to peers. Unlike the
|
|
776
|
+
// happy path there is no rush to forward, so we don't need to wrap in callInNextEventLoop.
|
|
777
|
+
chain.serializedCache.set(signedBlock, serializedData);
|
|
778
|
+
// this is technically not a valid gossip block but gossip validation is a cheap subset of checks
|
|
779
|
+
// this runs the full state transition, so importing an equivocating-but-valid block here is safe.
|
|
780
|
+
handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
|
|
767
781
|
}
|
|
768
782
|
// rethrow so gossipValidatorFn maps IGNORE -> TopicValidatorResult.Ignore (message not forwarded)
|
|
769
783
|
throw e;
|
package/src/node/nodejs.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import {setMaxListeners} from "node:events";
|
|
2
2
|
import {PrivateKey} from "@libp2p/interface";
|
|
3
3
|
import {Registry} from "prom-client";
|
|
4
|
+
import {
|
|
5
|
+
init as initNativeMetrics,
|
|
6
|
+
registerLocalValidator as registerNativeLocalValidator,
|
|
7
|
+
scrapeMetrics as scrapeNativeMetrics,
|
|
8
|
+
unregisterLocalValidator as unregisterNativeLocalValidator,
|
|
9
|
+
} from "@chainsafe/lodestar-z/metrics";
|
|
4
10
|
import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
|
|
5
11
|
import {hasher} from "@chainsafe/persistent-merkle-tree";
|
|
6
12
|
import {BeaconApiMethods} from "@lodestar/api/beacon/server";
|
|
@@ -8,6 +14,7 @@ import {BeaconConfig} from "@lodestar/config";
|
|
|
8
14
|
import type {LoggerNode} from "@lodestar/logger/node";
|
|
9
15
|
import {ZERO_HASH_HEX} from "@lodestar/params";
|
|
10
16
|
import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
|
|
17
|
+
import {Slot} from "@lodestar/types";
|
|
11
18
|
import {sleep, toRootHex} from "@lodestar/utils";
|
|
12
19
|
import {ProcessShutdownCallback} from "@lodestar/validator";
|
|
13
20
|
import {BeaconRestApiServer, getApi} from "../api/index.js";
|
|
@@ -55,6 +62,7 @@ export type BeaconNodeInitModules = {
|
|
|
55
62
|
peerStoreDir?: string;
|
|
56
63
|
anchorState: IBeaconStateView;
|
|
57
64
|
isAnchorStateFinalized: boolean;
|
|
65
|
+
earliestAvailableSlot: Slot;
|
|
58
66
|
metricsRegistries?: Registry[];
|
|
59
67
|
};
|
|
60
68
|
|
|
@@ -153,6 +161,7 @@ export class BeaconNode {
|
|
|
153
161
|
peerStoreDir,
|
|
154
162
|
anchorState,
|
|
155
163
|
isAnchorStateFinalized,
|
|
164
|
+
earliestAvailableSlot,
|
|
156
165
|
metricsRegistries = [],
|
|
157
166
|
}: BeaconNodeInitModules): Promise<T> {
|
|
158
167
|
if (hasher.name !== "hashtree") {
|
|
@@ -171,7 +180,13 @@ export class BeaconNode {
|
|
|
171
180
|
// monitoring relies on metrics data
|
|
172
181
|
opts.monitoring.endpoint
|
|
173
182
|
) {
|
|
174
|
-
|
|
183
|
+
if (opts.chain.nativeStateTransition) {
|
|
184
|
+
initNativeMetrics();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries, {
|
|
188
|
+
includeStateTransitionMetrics: !opts.chain.nativeStateTransition,
|
|
189
|
+
});
|
|
175
190
|
initBeaconMetrics(metrics, anchorState);
|
|
176
191
|
// Since the db is instantiated before this, metrics must be injected manually afterwards
|
|
177
192
|
db.setMetrics(metrics.db, metrics.flatFileStore);
|
|
@@ -185,7 +200,13 @@ export class BeaconNode {
|
|
|
185
200
|
config,
|
|
186
201
|
anchorState.genesisTime,
|
|
187
202
|
logger.child({module: LoggerModule.vmon}),
|
|
188
|
-
opts.validatorMonitor
|
|
203
|
+
opts.validatorMonitor,
|
|
204
|
+
opts.chain.nativeStateTransition
|
|
205
|
+
? {
|
|
206
|
+
registerLocalValidator: registerNativeLocalValidator,
|
|
207
|
+
unregisterLocalValidator: unregisterNativeLocalValidator,
|
|
208
|
+
}
|
|
209
|
+
: null
|
|
189
210
|
)
|
|
190
211
|
: null;
|
|
191
212
|
|
|
@@ -248,6 +269,7 @@ export class BeaconNode {
|
|
|
248
269
|
validatorMonitor,
|
|
249
270
|
anchorState,
|
|
250
271
|
isAnchorStateFinalized,
|
|
272
|
+
earliestAvailableSlot,
|
|
251
273
|
executionEngine: initializeExecutionEngine(executionEngineOpts, {
|
|
252
274
|
metrics,
|
|
253
275
|
signal,
|
|
@@ -303,7 +325,17 @@ export class BeaconNode {
|
|
|
303
325
|
const metricsServer = opts.metrics.enabled
|
|
304
326
|
? await getHttpMetricsServer(opts.metrics, {
|
|
305
327
|
register: (metrics as Metrics).register,
|
|
306
|
-
getOtherMetrics: async () =>
|
|
328
|
+
getOtherMetrics: async () => {
|
|
329
|
+
const otherMetrics = await Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]);
|
|
330
|
+
if (opts.chain.nativeStateTransition) {
|
|
331
|
+
try {
|
|
332
|
+
otherMetrics.push(scrapeNativeMetrics());
|
|
333
|
+
} catch (e) {
|
|
334
|
+
logger.warn("Failed to scrape native state-transition metrics", {}, e as Error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return otherMetrics;
|
|
338
|
+
},
|
|
307
339
|
logger: logger.child({module: LoggerModule.metrics}),
|
|
308
340
|
})
|
|
309
341
|
: null;
|
package/src/sync/range/batch.ts
CHANGED
|
@@ -224,6 +224,7 @@ export class Batch {
|
|
|
224
224
|
return {
|
|
225
225
|
blockRoot: parentBlockRoot,
|
|
226
226
|
blockRootHex: toRootHex(parentBlockRoot),
|
|
227
|
+
blockHash: this.latestBid.blockHash,
|
|
227
228
|
kzgCommitments: this.latestBid.blobKzgCommitments,
|
|
228
229
|
};
|
|
229
230
|
}
|
|
@@ -773,6 +774,7 @@ export class Batch {
|
|
|
773
774
|
// remove any downloaded blocks and re-attempt
|
|
774
775
|
// TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
|
|
775
776
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
777
|
+
this.resetRequests();
|
|
776
778
|
}
|
|
777
779
|
|
|
778
780
|
private onProcessingError(attempt: FailedAttempt): void {
|
|
@@ -784,6 +786,16 @@ export class Batch {
|
|
|
784
786
|
// remove any downloaded blocks and re-attempt
|
|
785
787
|
// TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
|
|
786
788
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
789
|
+
this.resetRequests();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* After a partial download `requests` only cover the data still missing from the downloaded blocks,
|
|
794
|
+
* e.g. a single column. Once those blocks are dropped, request the whole batch again, otherwise
|
|
795
|
+
* every peer fails the retry for lack of blocks to validate the data against.
|
|
796
|
+
*/
|
|
797
|
+
private resetRequests(): void {
|
|
798
|
+
this.requests = this.getRequests([]);
|
|
787
799
|
}
|
|
788
800
|
|
|
789
801
|
/** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -225,6 +225,9 @@ export class SyncChain {
|
|
|
225
225
|
Math.floor((localFinalizedEpoch - this.lastEpochWithProcessBlocks) / EPOCHS_PER_BATCH) * EPOCHS_PER_BATCH;
|
|
226
226
|
this.advanceChain(lastEpochWithProcessBlocksAligned);
|
|
227
227
|
|
|
228
|
+
// stopSyncing() cleared the retry timer, but peers can still be in rate limit backoff
|
|
229
|
+
this.scheduleRateLimitBackoffRetry();
|
|
230
|
+
|
|
228
231
|
// Potentially download new batches and process pending
|
|
229
232
|
this.triggerBatchDownloader();
|
|
230
233
|
this.triggerBatchProcessor();
|
|
@@ -417,8 +420,10 @@ export class SyncChain {
|
|
|
417
420
|
this.rateLimitBackoffTimeout = setTimeout(
|
|
418
421
|
() => {
|
|
419
422
|
this.rateLimitBackoffTimeout = undefined;
|
|
420
|
-
|
|
423
|
+
// Must run before the downloader, it drops expired peers without triggering a download. If it ran
|
|
424
|
+
// after, a backoff expiring in between would leave the peer idle with no retry scheduled.
|
|
421
425
|
this.scheduleRateLimitBackoffRetry();
|
|
426
|
+
this.triggerBatchDownloader();
|
|
422
427
|
},
|
|
423
428
|
Math.max(0, retryAt - now)
|
|
424
429
|
);
|
|
@@ -581,6 +586,7 @@ export class SyncChain {
|
|
|
581
586
|
case DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS:
|
|
582
587
|
case DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH:
|
|
583
588
|
case DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT:
|
|
589
|
+
case DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH:
|
|
584
590
|
case DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT:
|
|
585
591
|
case BlobSidecarErrorCode.INCLUSION_PROOF_INVALID:
|
|
586
592
|
case BlobSidecarErrorCode.INVALID_KZG_PROOF_BATCH:
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -73,7 +73,11 @@ type AdvancePendingBlockResult =
|
|
|
73
73
|
| "removed";
|
|
74
74
|
|
|
75
75
|
class UnknownBlockRateLimitedError extends Error {
|
|
76
|
-
constructor(
|
|
76
|
+
constructor(
|
|
77
|
+
message: string,
|
|
78
|
+
/** Earliest backoff expiry seen when the download was deferred, it is retried once that has passed */
|
|
79
|
+
readonly retryAt: number
|
|
80
|
+
) {
|
|
77
81
|
super(message);
|
|
78
82
|
this.name = "UnknownBlockRateLimitedError";
|
|
79
83
|
}
|
|
@@ -849,17 +853,20 @@ export class BlockInputSync {
|
|
|
849
853
|
}
|
|
850
854
|
};
|
|
851
855
|
|
|
852
|
-
|
|
856
|
+
/**
|
|
857
|
+
* Returns false if there is no backoff left to wait for, in which case no retry is scheduled.
|
|
858
|
+
*/
|
|
859
|
+
private scheduleRateLimitBackoffRetry(): boolean {
|
|
853
860
|
this.clearRateLimitBackoffTimer();
|
|
854
861
|
|
|
855
862
|
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
856
|
-
return;
|
|
863
|
+
return false;
|
|
857
864
|
}
|
|
858
865
|
|
|
859
866
|
const now = Date.now();
|
|
860
867
|
const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
|
|
861
868
|
if (retryAt === null) {
|
|
862
|
-
return;
|
|
869
|
+
return false;
|
|
863
870
|
}
|
|
864
871
|
|
|
865
872
|
this.rateLimitBackoffTimeout = setTimeout(
|
|
@@ -870,6 +877,7 @@ export class BlockInputSync {
|
|
|
870
877
|
},
|
|
871
878
|
Math.max(0, retryAt - now)
|
|
872
879
|
);
|
|
880
|
+
return true;
|
|
873
881
|
}
|
|
874
882
|
|
|
875
883
|
private clearRateLimitBackoffTimer(): void {
|
|
@@ -1081,7 +1089,11 @@ export class BlockInputSync {
|
|
|
1081
1089
|
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
1082
1090
|
}
|
|
1083
1091
|
this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
|
|
1084
|
-
this.scheduleRateLimitBackoffRetry()
|
|
1092
|
+
if (!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now()) {
|
|
1093
|
+
// The backoff this download waited for expired in the meantime, a timer is only armed if another
|
|
1094
|
+
// peer is still in backoff and would retry it later than needed
|
|
1095
|
+
this.triggerUnknownBlockSearch();
|
|
1096
|
+
}
|
|
1085
1097
|
return;
|
|
1086
1098
|
}
|
|
1087
1099
|
|
|
@@ -1364,6 +1376,14 @@ export class BlockInputSync {
|
|
|
1364
1376
|
if (!isPendingPayloadEnvelope(payload)) {
|
|
1365
1377
|
payload.status = PendingPayloadInputStatus.pending;
|
|
1366
1378
|
}
|
|
1379
|
+
if (
|
|
1380
|
+
res.err instanceof UnknownBlockRateLimitedError &&
|
|
1381
|
+
(!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now())
|
|
1382
|
+
) {
|
|
1383
|
+
// The backoff this download waited for expired in the meantime, a timer is only armed if another
|
|
1384
|
+
// peer is still in backoff and would retry it later than needed
|
|
1385
|
+
this.triggerUnknownBlockSearch();
|
|
1386
|
+
}
|
|
1367
1387
|
}
|
|
1368
1388
|
|
|
1369
1389
|
private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
|
|
@@ -1488,16 +1508,21 @@ export class BlockInputSync {
|
|
|
1488
1508
|
const pendingColumns = payloadInput?.hasAllData()
|
|
1489
1509
|
? new Set<number>()
|
|
1490
1510
|
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
1511
|
+
// Same `now` for both calls, a backoff expiring in between would be reported as no peer available
|
|
1512
|
+
const now = Date.now();
|
|
1491
1513
|
// prefer peers that gossiped this payload root to us (#9923)
|
|
1492
1514
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
|
|
1493
1515
|
pendingColumns,
|
|
1494
1516
|
excludedPeers,
|
|
1495
|
-
cacheItem.peerIdStrings
|
|
1517
|
+
cacheItem.peerIdStrings,
|
|
1518
|
+
now
|
|
1496
1519
|
);
|
|
1497
1520
|
if (peerMeta === null) {
|
|
1498
|
-
|
|
1521
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
|
|
1522
|
+
if (retryAt !== null) {
|
|
1499
1523
|
throw new UnknownBlockRateLimitedError(
|
|
1500
|
-
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited
|
|
1524
|
+
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
|
|
1525
|
+
retryAt
|
|
1501
1526
|
);
|
|
1502
1527
|
}
|
|
1503
1528
|
|
|
@@ -1636,9 +1661,11 @@ export class BlockInputSync {
|
|
|
1636
1661
|
);
|
|
1637
1662
|
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
|
|
1638
1663
|
|
|
1639
|
-
|
|
1664
|
+
const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
|
|
1665
|
+
if (retryAt !== null) {
|
|
1640
1666
|
throw new UnknownBlockRateLimitedError(
|
|
1641
|
-
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited
|
|
1667
|
+
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`,
|
|
1668
|
+
retryAt
|
|
1642
1669
|
);
|
|
1643
1670
|
}
|
|
1644
1671
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
@@ -1730,16 +1757,21 @@ export class BlockInputSync {
|
|
|
1730
1757
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
1731
1758
|
? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
|
|
1732
1759
|
: defaultPendingColumns;
|
|
1760
|
+
// Same `now` for both calls, a backoff expiring in between would be reported as no peer available
|
|
1761
|
+
const now = Date.now();
|
|
1733
1762
|
// prefer peers that gossiped this block root to us (#9923)
|
|
1734
1763
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
|
|
1735
1764
|
pendingColumns,
|
|
1736
1765
|
excludedPeers,
|
|
1737
|
-
cacheItem.peerIdStrings
|
|
1766
|
+
cacheItem.peerIdStrings,
|
|
1767
|
+
now
|
|
1738
1768
|
);
|
|
1739
1769
|
if (peerMeta === null) {
|
|
1740
|
-
|
|
1770
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
|
|
1771
|
+
if (retryAt !== null) {
|
|
1741
1772
|
throw new UnknownBlockRateLimitedError(
|
|
1742
|
-
`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited
|
|
1773
|
+
`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
|
|
1774
|
+
retryAt
|
|
1743
1775
|
);
|
|
1744
1776
|
}
|
|
1745
1777
|
|
|
@@ -1845,8 +1877,9 @@ export class BlockInputSync {
|
|
|
1845
1877
|
|
|
1846
1878
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1847
1879
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1880
|
+
const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
|
|
1881
|
+
if (retryAt !== null) {
|
|
1882
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`, retryAt);
|
|
1850
1883
|
}
|
|
1851
1884
|
|
|
1852
1885
|
if (!isPendingBlockInput(cacheItem)) {
|
|
@@ -2037,8 +2070,11 @@ export class UnknownBlockPeerBalancer {
|
|
|
2037
2070
|
this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
|
|
2038
2071
|
}
|
|
2039
2072
|
|
|
2040
|
-
getNextRateLimitRetryAt(
|
|
2041
|
-
|
|
2073
|
+
getNextRateLimitRetryAt(
|
|
2074
|
+
pendingColumns?: Set<number>,
|
|
2075
|
+
excludedPeers?: Set<PeerIdStr>,
|
|
2076
|
+
now = Date.now()
|
|
2077
|
+
): number | null {
|
|
2042
2078
|
let retryAt: number | null = null;
|
|
2043
2079
|
|
|
2044
2080
|
for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
|
|
@@ -2077,9 +2113,10 @@ export class UnknownBlockPeerBalancer {
|
|
|
2077
2113
|
bestPeerForPendingColumns(
|
|
2078
2114
|
pendingColumns: Set<number>,
|
|
2079
2115
|
excludedPeers: Set<PeerIdStr>,
|
|
2080
|
-
preferredPeers?: Set<PeerIdStr
|
|
2116
|
+
preferredPeers?: Set<PeerIdStr>,
|
|
2117
|
+
now = Date.now()
|
|
2081
2118
|
): PeerSyncMeta | null {
|
|
2082
|
-
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
|
|
2119
|
+
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers, now);
|
|
2083
2120
|
if (eligiblePeers.length === 0) {
|
|
2084
2121
|
return null;
|
|
2085
2122
|
}
|
|
@@ -2120,8 +2157,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
2120
2157
|
return totalActiveRequests;
|
|
2121
2158
|
}
|
|
2122
2159
|
|
|
2123
|
-
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr
|
|
2124
|
-
const now = Date.now();
|
|
2160
|
+
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>, now: number): PeerIdStr[] {
|
|
2125
2161
|
let maxColumnCount = 0;
|
|
2126
2162
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
2127
2163
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|