@lodestar/beacon-node 1.50.0-dev.7f24e06bb6 → 1.50.0-dev.82f05c43ff
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.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +6 -1
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- 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/pool/index.js +4 -4
- package/lib/api/impl/beacon/pool/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 +6 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +18 -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 +4 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +5 -3
- 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 +11 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.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 +9 -5
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +56 -21
- 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/errors/envelopeReconstructionError.d.ts +5 -0
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +2 -0
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +3 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +2 -0
- package/lib/chain/errors/gossipValidation.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/initState.d.ts +2 -9
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +5 -38
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/interface.d.ts +3 -2
- 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/opPool.d.ts +5 -4
- package/lib/chain/opPools/opPool.d.ts.map +1 -1
- package/lib/chain/opPools/opPool.js +62 -18
- package/lib/chain/opPools/opPool.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/opPools/utils.d.ts +4 -1
- package/lib/chain/opPools/utils.d.ts.map +1 -1
- package/lib/chain/opPools/utils.js +11 -1
- package/lib/chain/opPools/utils.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/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +5 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -2
- package/lib/chain/produceBlock/produceBlockBody.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/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.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/validation/attesterSlashing.d.ts +4 -4
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +3 -0
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.d.ts +3 -3
- package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +3 -0
- package/lib/chain/validation/proposerSlashing.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 +5 -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/db/repositories/proposerSlashing.d.ts +1 -0
- package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
- package/lib/db/repositories/proposerSlashing.js +5 -1
- package/lib/db/repositories/proposerSlashing.js.map +1 -1
- 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/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.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 +66 -67
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +7 -3
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +9 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +77 -10
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +41 -26
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.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/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +50 -18
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +14 -7
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +132 -66
- 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/lib/util/execution.d.ts +11 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +6 -2
- package/lib/util/execution.js.map +1 -1
- package/package.json +17 -17
- package/src/api/impl/beacon/blocks/index.ts +7 -4
- package/src/api/impl/beacon/index.ts +15 -0
- package/src/api/impl/beacon/pool/index.ts +4 -4
- 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 +26 -26
- 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 +9 -4
- package/src/chain/blocks/importBlock.ts +13 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
- package/src/chain/blocks/verifyBlock.ts +1 -0
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
- package/src/chain/chain.ts +76 -23
- package/src/chain/emitter.ts +7 -1
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/errors/gossipValidation.ts +2 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/initState.ts +8 -60
- package/src/chain/interface.ts +3 -2
- package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
- package/src/chain/opPools/opPool.ts +91 -18
- package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
- package/src/chain/opPools/utils.ts +31 -3
- package/src/chain/options.ts +3 -3
- package/src/chain/prepareNextSlot.ts +6 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
- package/src/chain/produceBlock/produceBlockBody.ts +8 -1
- package/src/chain/regen/regen.ts +12 -4
- package/src/chain/seenCache/seenGossipBlockInput.ts +23 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
- package/src/chain/shufflingCache.ts +5 -0
- package/src/chain/validation/attesterSlashing.ts +7 -4
- package/src/chain/validation/proposerSlashing.ts +7 -4
- package/src/chain/validatorMonitor.ts +75 -57
- package/src/db/beacon.ts +5 -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/db/repositories/proposerSlashing.ts +6 -1
- 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/index.ts +1 -1
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/metrics/metrics.ts +21 -6
- package/src/network/processor/gossipHandlers.ts +97 -80
- package/src/network/processor/gossipValidatorFn.ts +13 -3
- package/src/network/processor/index.ts +89 -10
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +42 -28
- 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/range/range.ts +50 -18
- package/src/sync/unknownBlock.ts +156 -71
- package/src/sync/utils/downloadByRange.ts +47 -8
- package/src/util/execution.ts +17 -9
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {ChainEvent, ChainEventData, IBeaconChain} from "../chain/index.js";
|
|
|
15
15
|
import {validateGloasBlockDataColumnSidecars} from "../chain/validation/dataColumnSidecar.js";
|
|
16
16
|
import {validateGossipExecutionPayloadEnvelope} from "../chain/validation/executionPayloadEnvelope.js";
|
|
17
17
|
import {Metrics} from "../metrics/index.js";
|
|
18
|
-
import {INetwork, NetworkEvent, NetworkEventData, prettyPrintPeerIdStr} from "../network/index.js";
|
|
18
|
+
import {INetwork, NetworkEvent, NetworkEventData, PeerAction, prettyPrintPeerIdStr} from "../network/index.js";
|
|
19
19
|
import {PeerSyncMeta} from "../network/peers/peersData.js";
|
|
20
20
|
import {MAX_PEERS_PER_ROOT} from "../network/processor/constants.js";
|
|
21
21
|
import {ClockEvent} from "../util/clock.js";
|
|
@@ -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
|
}
|
|
@@ -442,6 +446,11 @@ export class BlockInputSync {
|
|
|
442
446
|
};
|
|
443
447
|
|
|
444
448
|
private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
|
|
449
|
+
if (this.knownBadBlocks.has(rootHex)) {
|
|
450
|
+
this.logger.debug("Ignoring known bad block root", {root: rootHex, peerIdStr: peerIdStr ?? "unknown peer"});
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
|
|
445
454
|
let pendingBlock = this.pendingBlocks.get(rootHex);
|
|
446
455
|
let added = false;
|
|
447
456
|
if (!pendingBlock) {
|
|
@@ -477,6 +486,11 @@ export class BlockInputSync {
|
|
|
477
486
|
};
|
|
478
487
|
|
|
479
488
|
private addByBlockInput = (blockInput: IBlockInput, peerIdStr?: string): void => {
|
|
489
|
+
if (this.knownBadBlocks.has(blockInput.blockRootHex)) {
|
|
490
|
+
this.logger.debug("Ignoring known bad block", {slot: blockInput.slot, root: blockInput.blockRootHex, peerIdStr});
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
480
494
|
let pendingBlock = this.pendingBlocks.get(blockInput.blockRootHex);
|
|
481
495
|
// if entry is missing or was added via rootHex and now we have more complete information overwrite
|
|
482
496
|
// the existing information with the more complete cache entry
|
|
@@ -513,6 +527,15 @@ export class BlockInputSync {
|
|
|
513
527
|
};
|
|
514
528
|
|
|
515
529
|
private addByPayloadRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr, slot?: Slot): boolean => {
|
|
530
|
+
if (this.knownBadBlocks.has(rootHex)) {
|
|
531
|
+
this.logger.debug("Ignoring payload for known bad block root", {
|
|
532
|
+
slot: slot ?? "unknown",
|
|
533
|
+
root: rootHex,
|
|
534
|
+
peerIdStr,
|
|
535
|
+
});
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
516
539
|
let pendingPayload = this.pendingPayloads.get(rootHex);
|
|
517
540
|
let added = false;
|
|
518
541
|
if (!pendingPayload) {
|
|
@@ -581,6 +604,15 @@ export class BlockInputSync {
|
|
|
581
604
|
peerIdStr?: PeerIdStr,
|
|
582
605
|
envelope?: gloas.SignedExecutionPayloadEnvelope
|
|
583
606
|
): void => {
|
|
607
|
+
if (this.knownBadBlocks.has(payloadInput.blockRootHex)) {
|
|
608
|
+
this.logger.debug("Ignoring payload input for known bad block root", {
|
|
609
|
+
slot: payloadInput.slot,
|
|
610
|
+
root: payloadInput.blockRootHex,
|
|
611
|
+
peerIdStr,
|
|
612
|
+
});
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
584
616
|
const pendingPayload = this.toPendingPayloadInput(
|
|
585
617
|
payloadInput,
|
|
586
618
|
this.pendingPayloads.get(payloadInput.blockRootHex),
|
|
@@ -654,6 +686,7 @@ export class BlockInputSync {
|
|
|
654
686
|
return {kind: "ready"};
|
|
655
687
|
}
|
|
656
688
|
|
|
689
|
+
// The parent's payload is known but does not match `parentBlockHashHex`
|
|
657
690
|
if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
|
|
658
691
|
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
659
692
|
}
|
|
@@ -820,17 +853,20 @@ export class BlockInputSync {
|
|
|
820
853
|
}
|
|
821
854
|
};
|
|
822
855
|
|
|
823
|
-
|
|
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 {
|
|
824
860
|
this.clearRateLimitBackoffTimer();
|
|
825
861
|
|
|
826
862
|
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
827
|
-
return;
|
|
863
|
+
return false;
|
|
828
864
|
}
|
|
829
865
|
|
|
830
866
|
const now = Date.now();
|
|
831
867
|
const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
|
|
832
868
|
if (retryAt === null) {
|
|
833
|
-
return;
|
|
869
|
+
return false;
|
|
834
870
|
}
|
|
835
871
|
|
|
836
872
|
this.rateLimitBackoffTimeout = setTimeout(
|
|
@@ -841,6 +877,7 @@ export class BlockInputSync {
|
|
|
841
877
|
},
|
|
842
878
|
Math.max(0, retryAt - now)
|
|
843
879
|
);
|
|
880
|
+
return true;
|
|
844
881
|
}
|
|
845
882
|
|
|
846
883
|
private clearRateLimitBackoffTimer(): void {
|
|
@@ -1032,7 +1069,8 @@ export class BlockInputSync {
|
|
|
1032
1069
|
}
|
|
1033
1070
|
} else if (blockSlot <= finalizedSlot) {
|
|
1034
1071
|
// the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
|
|
1035
|
-
// we should found it through forkchoice. If not,
|
|
1072
|
+
// we should found it through forkchoice. If not, drop the chain without penalizing peers because pending entries are
|
|
1073
|
+
// block retained on failed download, so finalization can pass the block while it is still pending
|
|
1036
1074
|
// 0 - 1 - ... - n - finalizedSlot
|
|
1037
1075
|
// \
|
|
1038
1076
|
// parent 1 - parent 2 - ... - unknownParent block
|
|
@@ -1040,7 +1078,7 @@ export class BlockInputSync {
|
|
|
1040
1078
|
...logCtx2,
|
|
1041
1079
|
finalizedSlot,
|
|
1042
1080
|
});
|
|
1043
|
-
this.
|
|
1081
|
+
this.removeAllDescendants(block, DroppedItemReason.belowFinalized);
|
|
1044
1082
|
} else {
|
|
1045
1083
|
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
|
|
1046
1084
|
}
|
|
@@ -1051,7 +1089,11 @@ export class BlockInputSync {
|
|
|
1051
1089
|
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
1052
1090
|
}
|
|
1053
1091
|
this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
|
|
1054
|
-
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
|
+
}
|
|
1055
1097
|
return;
|
|
1056
1098
|
}
|
|
1057
1099
|
|
|
@@ -1172,15 +1214,18 @@ export class BlockInputSync {
|
|
|
1172
1214
|
break;
|
|
1173
1215
|
|
|
1174
1216
|
case BlockErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1175
|
-
//
|
|
1217
|
+
// Ban but do not downscore: optimistic-sync peers may forward and serve blocks whose payload is later
|
|
1218
|
+
// rejected by the execution engine. Per the spec, an INVALID execution payload SHOULD NOT cause a peer
|
|
1219
|
+
// to be down-scored or disconnected, only consensus-layer invalidity MAY.
|
|
1220
|
+
// https://github.com/ethereum/consensus-specs/blob/master/specs/bellatrix/p2p-interface.md#the-reqresp-domain
|
|
1176
1221
|
this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
|
|
1177
|
-
this.
|
|
1222
|
+
this.removeAndBanAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
|
|
1178
1223
|
break;
|
|
1179
1224
|
|
|
1180
1225
|
default:
|
|
1181
1226
|
// Block is not correct with respect to our chain. Log error loudly
|
|
1182
1227
|
this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
|
|
1183
|
-
this.
|
|
1228
|
+
this.removeAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
|
|
1184
1229
|
}
|
|
1185
1230
|
}
|
|
1186
1231
|
|
|
@@ -1331,6 +1376,14 @@ export class BlockInputSync {
|
|
|
1331
1376
|
if (!isPendingPayloadEnvelope(payload)) {
|
|
1332
1377
|
payload.status = PendingPayloadInputStatus.pending;
|
|
1333
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
|
+
}
|
|
1334
1387
|
}
|
|
1335
1388
|
|
|
1336
1389
|
private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
|
|
@@ -1393,20 +1446,23 @@ export class BlockInputSync {
|
|
|
1393
1446
|
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
1394
1447
|
break;
|
|
1395
1448
|
|
|
1449
|
+
// The invalid envelope is evicted from the seen cache in the cases below, otherwise the next
|
|
1450
|
+
// attempt would reuse it instead of fetching another envelope from peers
|
|
1396
1451
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1397
1452
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1453
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1398
1454
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
|
|
1399
1455
|
break;
|
|
1400
1456
|
|
|
1401
1457
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1402
1458
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1459
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1403
1460
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
|
|
1404
1461
|
break;
|
|
1405
1462
|
|
|
1406
1463
|
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1407
|
-
// TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
|
|
1408
|
-
// reintroducing envelope replacement / recreation flows.
|
|
1409
1464
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1465
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1410
1466
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
|
|
1411
1467
|
break;
|
|
1412
1468
|
|
|
@@ -1452,16 +1508,21 @@ export class BlockInputSync {
|
|
|
1452
1508
|
const pendingColumns = payloadInput?.hasAllData()
|
|
1453
1509
|
? new Set<number>()
|
|
1454
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();
|
|
1455
1513
|
// prefer peers that gossiped this payload root to us (#9923)
|
|
1456
1514
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
|
|
1457
1515
|
pendingColumns,
|
|
1458
1516
|
excludedPeers,
|
|
1459
|
-
cacheItem.peerIdStrings
|
|
1517
|
+
cacheItem.peerIdStrings,
|
|
1518
|
+
now
|
|
1460
1519
|
);
|
|
1461
1520
|
if (peerMeta === null) {
|
|
1462
|
-
|
|
1521
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
|
|
1522
|
+
if (retryAt !== null) {
|
|
1463
1523
|
throw new UnknownBlockRateLimitedError(
|
|
1464
|
-
`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
|
|
1465
1526
|
);
|
|
1466
1527
|
}
|
|
1467
1528
|
|
|
@@ -1478,11 +1539,11 @@ export class BlockInputSync {
|
|
|
1478
1539
|
}
|
|
1479
1540
|
|
|
1480
1541
|
const {peerId, client: peerClient} = peerMeta;
|
|
1481
|
-
cacheItem.peerIdStrings.add(peerId);
|
|
1482
1542
|
|
|
1483
1543
|
try {
|
|
1484
1544
|
if (!envelope) {
|
|
1485
1545
|
envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
|
|
1546
|
+
cacheItem.peerIdStrings.add(peerId);
|
|
1486
1547
|
const slotWasUnknown = typeof slot !== "number";
|
|
1487
1548
|
slot = envelope.message.payload.slotNumber;
|
|
1488
1549
|
if (slotWasUnknown) {
|
|
@@ -1524,6 +1585,8 @@ export class BlockInputSync {
|
|
|
1524
1585
|
const missing = pendingPayload.payloadInput.getMissingSampledColumnMeta().missing;
|
|
1525
1586
|
if (missing.length > 0) {
|
|
1526
1587
|
const columnSidecars = await this.fetchPayloadColumns(peerMeta, pendingPayload.payloadInput, missing);
|
|
1588
|
+
// toPendingPayloadInput copied cacheItem's set, so add to the object that is carried forward
|
|
1589
|
+
pendingPayload.peerIdStrings.add(peerId);
|
|
1527
1590
|
const seenTimestampSec = Date.now() / 1000;
|
|
1528
1591
|
for (const columnSidecar of columnSidecars) {
|
|
1529
1592
|
if (pendingPayload.payloadInput.hasColumn(columnSidecar.index)) {
|
|
@@ -1598,9 +1661,11 @@ export class BlockInputSync {
|
|
|
1598
1661
|
);
|
|
1599
1662
|
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
|
|
1600
1663
|
|
|
1601
|
-
|
|
1664
|
+
const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
|
|
1665
|
+
if (retryAt !== null) {
|
|
1602
1666
|
throw new UnknownBlockRateLimitedError(
|
|
1603
|
-
`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
|
|
1604
1669
|
);
|
|
1605
1670
|
}
|
|
1606
1671
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
@@ -1692,16 +1757,21 @@ export class BlockInputSync {
|
|
|
1692
1757
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
1693
1758
|
? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
|
|
1694
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();
|
|
1695
1762
|
// prefer peers that gossiped this block root to us (#9923)
|
|
1696
1763
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
|
|
1697
1764
|
pendingColumns,
|
|
1698
1765
|
excludedPeers,
|
|
1699
|
-
cacheItem.peerIdStrings
|
|
1766
|
+
cacheItem.peerIdStrings,
|
|
1767
|
+
now
|
|
1700
1768
|
);
|
|
1701
1769
|
if (peerMeta === null) {
|
|
1702
|
-
|
|
1770
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
|
|
1771
|
+
if (retryAt !== null) {
|
|
1703
1772
|
throw new UnknownBlockRateLimitedError(
|
|
1704
|
-
`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
|
|
1705
1775
|
);
|
|
1706
1776
|
}
|
|
1707
1777
|
|
|
@@ -1716,8 +1786,6 @@ export class BlockInputSync {
|
|
|
1716
1786
|
}
|
|
1717
1787
|
const {peerId, client: peerClient} = peerMeta;
|
|
1718
1788
|
|
|
1719
|
-
cacheItem.peerIdStrings.add(peerId);
|
|
1720
|
-
|
|
1721
1789
|
try {
|
|
1722
1790
|
const downloadResult = await downloadByRoot({
|
|
1723
1791
|
config: this.config,
|
|
@@ -1728,6 +1796,7 @@ export class BlockInputSync {
|
|
|
1728
1796
|
cacheItem,
|
|
1729
1797
|
});
|
|
1730
1798
|
cacheItem = downloadResult.result;
|
|
1799
|
+
cacheItem.peerIdStrings.add(peerId);
|
|
1731
1800
|
if (slot === undefined) {
|
|
1732
1801
|
slot = cacheItem.blockInput.slot;
|
|
1733
1802
|
// we were not able to observe the time into slot when starting the fetch, do it now
|
|
@@ -1808,8 +1877,9 @@ export class BlockInputSync {
|
|
|
1808
1877
|
|
|
1809
1878
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1810
1879
|
|
|
1811
|
-
|
|
1812
|
-
|
|
1880
|
+
const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
|
|
1881
|
+
if (retryAt !== null) {
|
|
1882
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`, retryAt);
|
|
1813
1883
|
}
|
|
1814
1884
|
|
|
1815
1885
|
if (!isPendingBlockInput(cacheItem)) {
|
|
@@ -1843,44 +1913,8 @@ export class BlockInputSync {
|
|
|
1843
1913
|
throw Error(message);
|
|
1844
1914
|
}
|
|
1845
1915
|
|
|
1846
|
-
/**
|
|
1847
|
-
* Gets all descendant blocks of `block` recursively from `pendingBlocks`.
|
|
1848
|
-
* Assumes that if a parent block does not exist or is not processable, all descendant blocks are bad too.
|
|
1849
|
-
* Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
|
|
1850
|
-
* referenced more than one bad block.
|
|
1851
|
-
*/
|
|
1852
|
-
private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
|
|
1853
|
-
// Get all blocks that are a descendant of this one
|
|
1854
|
-
const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
|
|
1855
|
-
// just console log and do not penalize on pending/bad blocks for debugging
|
|
1856
|
-
// console.log("removeAndDownscoreAllDescendants", {block});
|
|
1857
|
-
|
|
1858
|
-
for (const block of badPendingBlocks) {
|
|
1859
|
-
//
|
|
1860
|
-
// TODO(fulu): why is this commented out here?
|
|
1861
|
-
//
|
|
1862
|
-
// this.knownBadBlocks.add(block.blockRootHex);
|
|
1863
|
-
// for (const peerIdStr of block.peerIdStrings) {
|
|
1864
|
-
// // TODO: Refactor peerRpcScores to work with peerIdStr only
|
|
1865
|
-
// this.network.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadBlockByRoot");
|
|
1866
|
-
// }
|
|
1867
|
-
this.logger.debug("ignored Banning unknown block", {
|
|
1868
|
-
slot: getBlockInputSyncCacheItemSlot(block),
|
|
1869
|
-
root: getBlockInputSyncCacheItemRootHex(block),
|
|
1870
|
-
peerIdStrings: Array.from(block.peerIdStrings)
|
|
1871
|
-
.map((id) => prettyPrintPeerIdStr(id))
|
|
1872
|
-
.join(","),
|
|
1873
|
-
});
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
// Prune knownBadBlocks
|
|
1877
|
-
pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
1916
|
// Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
|
|
1881
1917
|
private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
|
|
1882
|
-
// Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
|
|
1883
|
-
// later validation/finalization logic decides when it can leave memory.
|
|
1884
1918
|
if (this.pendingPayloads.delete(rootHex)) {
|
|
1885
1919
|
this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
|
|
1886
1920
|
}
|
|
@@ -1903,13 +1937,61 @@ export class BlockInputSync {
|
|
|
1903
1937
|
}
|
|
1904
1938
|
}
|
|
1905
1939
|
|
|
1940
|
+
/**
|
|
1941
|
+
* Same as `removeAndBanAllDescendants`, and downscores every peer that referenced or served a banned item, once per
|
|
1942
|
+
* peer. Only call for consensus-layer invalidity, never for an INVALID execution payload:
|
|
1943
|
+
* https://github.com/ethereum/consensus-specs/blob/master/specs/bellatrix/p2p-interface.md#the-reqresp-domain
|
|
1944
|
+
*/
|
|
1945
|
+
private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
|
|
1946
|
+
const peersToReport = new Set<PeerIdStr>();
|
|
1947
|
+
for (const bannedBlock of this.removeAndBanAllDescendants(block, headReason)) {
|
|
1948
|
+
for (const peerIdStr of bannedBlock.peerIdStrings) {
|
|
1949
|
+
peersToReport.add(peerIdStr);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
for (const peerIdStr of peersToReport) {
|
|
1954
|
+
this.network.reportPeer(peerIdStr, PeerAction.LowToleranceError, headReason);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/**
|
|
1959
|
+
* Removes `block` and all its pending descendants and bans their roots in `knownBadBlocks`. Only call when the chain
|
|
1960
|
+
* is provably bad: a banned root is never searched for again. Returns the banned items.
|
|
1961
|
+
*/
|
|
1962
|
+
private removeAndBanAllDescendants(
|
|
1963
|
+
block: BlockInputSyncCacheItem,
|
|
1964
|
+
headReason: DroppedItemReason
|
|
1965
|
+
): BlockInputSyncCacheItem[] {
|
|
1966
|
+
const bannedBlocks: BlockInputSyncCacheItem[] = [];
|
|
1967
|
+
for (const badBlock of this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent)) {
|
|
1968
|
+
const rootHex = getBlockInputSyncCacheItemRootHex(badBlock);
|
|
1969
|
+
// a gossip import may race a failing sync path, never ban a root fork choice has imported
|
|
1970
|
+
if (this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
this.knownBadBlocks.add(rootHex);
|
|
1974
|
+
bannedBlocks.push(badBlock);
|
|
1975
|
+
this.logger.debug("Banning unknown block", {
|
|
1976
|
+
slot: getBlockInputSyncCacheItemSlot(badBlock),
|
|
1977
|
+
root: rootHex,
|
|
1978
|
+
reason: headReason,
|
|
1979
|
+
peerIdStrings: Array.from(badBlock.peerIdStrings)
|
|
1980
|
+
.map((id) => prettyPrintPeerIdStr(id))
|
|
1981
|
+
.join(","),
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
|
|
1986
|
+
return bannedBlocks;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1906
1989
|
private removeAllDescendants(
|
|
1907
1990
|
block: BlockInputSyncCacheItem,
|
|
1908
1991
|
headReason: DroppedItemReason,
|
|
1909
1992
|
descendantReason: DroppedItemReason = DroppedItemReason.invalidParent
|
|
1910
1993
|
): BlockInputSyncCacheItem[] {
|
|
1911
1994
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
1912
|
-
const slot = getBlockInputSyncCacheItemSlot(block);
|
|
1913
1995
|
// Get all blocks that are a descendant of this one (index 0 is the head item itself)
|
|
1914
1996
|
const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
|
|
1915
1997
|
|
|
@@ -1926,8 +2008,8 @@ export class BlockInputSync {
|
|
|
1926
2008
|
// Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
|
|
1927
2009
|
// importBlock()-owned lifecycle.
|
|
1928
2010
|
this.logger.debug("Removing bad/unknown/incomplete BlockInputSyncCacheItem", {
|
|
1929
|
-
slot,
|
|
1930
|
-
|
|
2011
|
+
slot: getBlockInputSyncCacheItemSlot(block),
|
|
2012
|
+
root: rootHex,
|
|
1931
2013
|
});
|
|
1932
2014
|
}
|
|
1933
2015
|
|
|
@@ -1988,8 +2070,11 @@ export class UnknownBlockPeerBalancer {
|
|
|
1988
2070
|
this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
|
|
1989
2071
|
}
|
|
1990
2072
|
|
|
1991
|
-
getNextRateLimitRetryAt(
|
|
1992
|
-
|
|
2073
|
+
getNextRateLimitRetryAt(
|
|
2074
|
+
pendingColumns?: Set<number>,
|
|
2075
|
+
excludedPeers?: Set<PeerIdStr>,
|
|
2076
|
+
now = Date.now()
|
|
2077
|
+
): number | null {
|
|
1993
2078
|
let retryAt: number | null = null;
|
|
1994
2079
|
|
|
1995
2080
|
for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
|
|
@@ -2028,9 +2113,10 @@ export class UnknownBlockPeerBalancer {
|
|
|
2028
2113
|
bestPeerForPendingColumns(
|
|
2029
2114
|
pendingColumns: Set<number>,
|
|
2030
2115
|
excludedPeers: Set<PeerIdStr>,
|
|
2031
|
-
preferredPeers?: Set<PeerIdStr
|
|
2116
|
+
preferredPeers?: Set<PeerIdStr>,
|
|
2117
|
+
now = Date.now()
|
|
2032
2118
|
): PeerSyncMeta | null {
|
|
2033
|
-
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
|
|
2119
|
+
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers, now);
|
|
2034
2120
|
if (eligiblePeers.length === 0) {
|
|
2035
2121
|
return null;
|
|
2036
2122
|
}
|
|
@@ -2071,8 +2157,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
2071
2157
|
return totalActiveRequests;
|
|
2072
2158
|
}
|
|
2073
2159
|
|
|
2074
|
-
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr
|
|
2075
|
-
const now = Date.now();
|
|
2160
|
+
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>, now: number): PeerIdStr[] {
|
|
2076
2161
|
let maxColumnCount = 0;
|
|
2077
2162
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
2078
2163
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
deneb,
|
|
17
17
|
fulu,
|
|
18
18
|
gloas,
|
|
19
|
+
isGloasBeaconBlock,
|
|
19
20
|
isGloasDataColumnSidecar,
|
|
20
21
|
phase0,
|
|
21
22
|
} from "@lodestar/types";
|
|
@@ -62,6 +63,7 @@ export type DownloadByRangeRequests = {
|
|
|
62
63
|
export type ParentPayloadCommitments = {
|
|
63
64
|
blockRoot: Uint8Array;
|
|
64
65
|
blockRootHex: RootHex;
|
|
66
|
+
blockHash: Uint8Array;
|
|
65
67
|
kzgCommitments: deneb.BlobKzgCommitments;
|
|
66
68
|
};
|
|
67
69
|
|
|
@@ -1171,6 +1173,8 @@ export enum DownloadByRangeErrorCode {
|
|
|
1171
1173
|
|
|
1172
1174
|
/** Envelope beaconBlockRoot does not match the block's root */
|
|
1173
1175
|
INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
|
|
1176
|
+
/** Envelope payload.blockHash does not match the block's bid */
|
|
1177
|
+
INVALID_ENVELOPE_BLOCK_HASH = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BLOCK_HASH",
|
|
1174
1178
|
|
|
1175
1179
|
/** Block segment + envelopes failed chain-segment linearity / FULL-chain checks */
|
|
1176
1180
|
INVALID_CHAIN_SEGMENT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_CHAIN_SEGMENT",
|
|
@@ -1272,6 +1276,12 @@ export type DownloadByRangeErrorType =
|
|
|
1272
1276
|
expected: string;
|
|
1273
1277
|
actual: string;
|
|
1274
1278
|
}
|
|
1279
|
+
| {
|
|
1280
|
+
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH;
|
|
1281
|
+
slot: Slot;
|
|
1282
|
+
expected: string;
|
|
1283
|
+
actual: string;
|
|
1284
|
+
}
|
|
1275
1285
|
| {
|
|
1276
1286
|
code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT;
|
|
1277
1287
|
slot: Slot;
|
|
@@ -1295,47 +1305,76 @@ export function validateEnvelopesByRangeResponse(
|
|
|
1295
1305
|
payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
|
|
1296
1306
|
parentPayloadCommitments?: ParentPayloadCommitments
|
|
1297
1307
|
): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
|
|
1298
|
-
// Build a map of slot ->
|
|
1299
|
-
const
|
|
1308
|
+
// Build a map of slot -> block root and bid block hash for all blocks in the batch
|
|
1309
|
+
const batchBlocksBySlot = new Map<Slot, {blockRoot: Uint8Array; block: SignedBeaconBlock | null}>();
|
|
1300
1310
|
if (batchBlocks) {
|
|
1301
1311
|
for (const blockInput of batchBlocks) {
|
|
1302
|
-
|
|
1312
|
+
batchBlocksBySlot.set(blockInput.slot, {
|
|
1313
|
+
blockRoot: fromHex(blockInput.blockRootHex),
|
|
1314
|
+
block: blockInput.hasBlock() ? blockInput.getBlock() : null,
|
|
1315
|
+
});
|
|
1303
1316
|
}
|
|
1304
1317
|
}
|
|
1305
1318
|
for (const {block, blockRoot} of validatedBlocks) {
|
|
1306
|
-
|
|
1319
|
+
batchBlocksBySlot.set(block.message.slot, {blockRoot, block});
|
|
1307
1320
|
}
|
|
1308
1321
|
|
|
1309
1322
|
const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
|
|
1310
1323
|
|
|
1311
1324
|
for (const payloadEnvelope of payloadEnvelopes) {
|
|
1312
1325
|
const slot = payloadEnvelope.message.payload.slotNumber;
|
|
1313
|
-
const
|
|
1326
|
+
const batchBlock = batchBlocksBySlot.get(slot);
|
|
1314
1327
|
|
|
1315
|
-
if (
|
|
1328
|
+
if (batchBlock === undefined) {
|
|
1316
1329
|
// Keep the requested dangling-parent envelope only when its beaconBlockRoot matches
|
|
1317
1330
|
// exactly. All other unrelated envelopes are dropped.
|
|
1318
1331
|
if (
|
|
1319
1332
|
parentPayloadCommitments !== undefined &&
|
|
1320
1333
|
byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayloadCommitments.blockRoot)
|
|
1321
1334
|
) {
|
|
1335
|
+
assertEnvelopeBlockHash(payloadEnvelope, parentPayloadCommitments.blockHash);
|
|
1322
1336
|
payloadEnvelopeMap.set(slot, payloadEnvelope);
|
|
1323
1337
|
}
|
|
1324
1338
|
continue;
|
|
1325
1339
|
}
|
|
1326
1340
|
|
|
1327
1341
|
// Verify beaconBlockRoot matches the block's root
|
|
1328
|
-
if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot,
|
|
1342
|
+
if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlock.blockRoot)) {
|
|
1329
1343
|
throw new DownloadByRangeError({
|
|
1330
1344
|
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
|
|
1331
1345
|
slot,
|
|
1332
|
-
expected: toRootHex(
|
|
1346
|
+
expected: toRootHex(batchBlock.blockRoot),
|
|
1333
1347
|
actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
|
|
1334
1348
|
});
|
|
1335
1349
|
}
|
|
1336
1350
|
|
|
1351
|
+
if (batchBlock.block !== null && isGloasBeaconBlock(batchBlock.block.message)) {
|
|
1352
|
+
assertEnvelopeBlockHash(
|
|
1353
|
+
payloadEnvelope,
|
|
1354
|
+
batchBlock.block.message.body.signedExecutionPayloadBid.message.blockHash
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1337
1358
|
payloadEnvelopeMap.set(slot, payloadEnvelope);
|
|
1338
1359
|
}
|
|
1339
1360
|
|
|
1340
1361
|
return payloadEnvelopeMap;
|
|
1341
1362
|
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Rejected before it is cached. In-batch, `assertLinearChainSegment` fails on a wrong hash without evicting the
|
|
1366
|
+
* envelope. For the dangling parent, the import evicts the parent's entry and range sync cannot recreate it.
|
|
1367
|
+
*/
|
|
1368
|
+
function assertEnvelopeBlockHash(
|
|
1369
|
+
payloadEnvelope: gloas.SignedExecutionPayloadEnvelope,
|
|
1370
|
+
bidBlockHash: Uint8Array
|
|
1371
|
+
): void {
|
|
1372
|
+
if (!byteArrayEquals(payloadEnvelope.message.payload.blockHash, bidBlockHash)) {
|
|
1373
|
+
throw new DownloadByRangeError({
|
|
1374
|
+
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH,
|
|
1375
|
+
slot: payloadEnvelope.message.payload.slotNumber,
|
|
1376
|
+
expected: toRootHex(bidBlockHash),
|
|
1377
|
+
actual: toRootHex(payloadEnvelope.message.payload.blockHash),
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
package/src/util/execution.ts
CHANGED
|
@@ -260,20 +260,21 @@ export async function getDataColumnSidecarsFromExecution(
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
/** engine_getPayloadBodiesByHashV2: every EL must accept requests of up to 32 hashes, larger ones may fail with -38004 */
|
|
263
|
-
const MAX_BODIES_PER_REQUEST = 32;
|
|
263
|
+
export const MAX_BODIES_PER_REQUEST = 32;
|
|
264
264
|
|
|
265
265
|
type SlotEnvelopeBytes = {slot: Slot; envelopeBytes: Uint8Array};
|
|
266
266
|
|
|
267
267
|
type RangeEntry = ArchivedEnvelope & {slot: Slot};
|
|
268
268
|
|
|
269
|
-
/** What
|
|
270
|
-
export type
|
|
269
|
+
/** What an envelope that cannot be rebuilt (EL-unavailable body or body root mismatch) means on a given serving path */
|
|
270
|
+
export type ReconstructMissPolicy = "throw" | "omit";
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
/**
|
|
273
|
+
* An archived envelope that could not be rebuilt. `"unavailable"`: the EL does not have the block or its
|
|
274
|
+
* block access list (BODY_UNAVAILABLE). `"mismatch"`: an EL body does not hash to its stored root, a local
|
|
275
|
+
* inconsistency (BODY_ROOT_MISMATCH). `error` carries the matching code for paths that surface it.
|
|
276
|
+
*/
|
|
277
|
+
export type RebuildMiss = {slot: Slot; reason: "unavailable" | "mismatch"; error: EnvelopeReconstructionError};
|
|
277
278
|
|
|
278
279
|
/**
|
|
279
280
|
* Stream finalized envelopes over [startSlot, endSlot) as serialized bytes, rebuilding header
|
|
@@ -405,7 +406,14 @@ async function reconstructEnvelopesBatch(
|
|
|
405
406
|
// A zero-length block access list cannot be valid, RLP encodes an empty list as 0xc0
|
|
406
407
|
if (body == null || body.withdrawals == null || body.blockAccessList == null || body.blockAccessList.length === 0) {
|
|
407
408
|
metrics?.payloadEnvelopeReconstruction.envelopes.inc({result: "unavailable"});
|
|
408
|
-
return {
|
|
409
|
+
return {
|
|
410
|
+
slot,
|
|
411
|
+
reason: "unavailable",
|
|
412
|
+
error: new EnvelopeReconstructionError(
|
|
413
|
+
{code: EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE, slot},
|
|
414
|
+
`execution client cannot serve the payload body or block access list for archived envelope slot=${slot}`
|
|
415
|
+
),
|
|
416
|
+
};
|
|
409
417
|
}
|
|
410
418
|
try {
|
|
411
419
|
const envelope = signedHeaderEnvelopeToFull(headerEnvelope, {
|