@lodestar/beacon-node 1.43.0-dev.aef3645690 → 1.43.0-dev.b0e1143d06
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 +17 -9
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +45 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +2 -2
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +0 -1
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +1 -1
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +68 -5
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/GetBlobsTracker.d.ts +1 -1
- package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
- package/lib/chain/GetBlobsTracker.js +1 -2
- package/lib/chain/GetBlobsTracker.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +4 -4
- package/lib/chain/archiveStore/interface.d.ts.map +1 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +4 -4
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +2 -2
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +110 -58
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +4 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +38 -58
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +32 -14
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +101 -90
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts +5 -3
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +58 -26
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +15 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +48 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +7 -5
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +16 -21
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +21 -2
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +92 -12
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +5 -3
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +50 -7
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +25 -5
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +24 -0
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -0
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +80 -0
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +30 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +2 -11
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -1
- package/lib/chain/chain.d.ts +8 -6
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +59 -49
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +16 -15
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -4
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/attestationError.d.ts +8 -1
- package/lib/chain/errors/attestationError.d.ts.map +1 -1
- package/lib/chain/errors/attestationError.js +4 -0
- package/lib/chain/errors/attestationError.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +8 -1
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -0
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +5 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +1 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/errors/index.d.ts +1 -0
- package/lib/chain/errors/index.d.ts.map +1 -1
- package/lib/chain/errors/index.js +1 -0
- package/lib/chain/errors/index.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +40 -0
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
- package/lib/chain/errors/proposerPreferences.js +14 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -0
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +11 -15
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +6 -1
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/interface.d.ts +7 -5
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +26 -4
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +47 -23
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +13 -8
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +68 -25
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/errors.d.ts +1 -11
- package/lib/chain/regen/errors.d.ts.map +1 -1
- package/lib/chain/regen/errors.js +0 -2
- package/lib/chain/regen/errors.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +7 -12
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +1 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/regen/queued.d.ts +6 -11
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +9 -44
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +0 -5
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +8 -38
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +1 -0
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +1 -0
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +24 -7
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +69 -17
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
- package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
- package/lib/chain/stateCache/datastore/db.d.ts +5 -4
- package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/db.js +10 -32
- package/lib/chain/stateCache/datastore/db.js.map +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/file.js +5 -5
- package/lib/chain/stateCache/datastore/file.js.map +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
- package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +120 -216
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/stateCache/types.d.ts +8 -15
- package/lib/chain/stateCache/types.d.ts.map +1 -1
- package/lib/chain/stateCache/types.js.map +1 -1
- package/lib/chain/validation/aggregateAndProof.js +12 -0
- package/lib/chain/validation/aggregateAndProof.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts.map +1 -1
- package/lib/chain/validation/attestation.js +12 -0
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +1 -0
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +24 -9
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +21 -11
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +4 -3
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts +8 -0
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
- package/lib/chain/validation/proposerPreferences.js +91 -0
- package/lib/chain/validation/proposerPreferences.js.map +1 -0
- package/lib/db/repositories/executionPayloadEnvelopeArchive.js +1 -1
- package/lib/db/repositories/executionPayloadEnvelopeArchive.js.map +1 -1
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +21 -14
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +1 -0
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +6 -0
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/types.d.ts +20 -0
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.js +18 -0
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +5 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +9 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/interface.d.ts +7 -1
- package/lib/network/gossip/interface.d.ts.map +1 -1
- package/lib/network/gossip/interface.js +1 -0
- package/lib/network/gossip/interface.js.map +1 -1
- package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
- package/lib/network/gossip/scoringParameters.js +12 -1
- package/lib/network/gossip/scoringParameters.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +33 -2
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +6 -0
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/interface.d.ts +2 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +2 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +9 -1
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +91 -23
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
- package/lib/network/processor/gossipQueues/index.js +5 -0
- package/lib/network/processor/gossipQueues/index.js.map +1 -1
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +6 -5
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +5 -0
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/interface.d.ts +1 -1
- package/lib/network/reqresp/interface.js +1 -1
- package/lib/network/reqresp/protocols.d.ts +1 -0
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +5 -0
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
- package/lib/network/reqresp/rateLimit.js +4 -0
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/score.d.ts.map +1 -1
- package/lib/network/reqresp/score.js +1 -0
- package/lib/network/reqresp/score.js.map +1 -1
- package/lib/network/reqresp/types.d.ts +3 -0
- package/lib/network/reqresp/types.d.ts.map +1 -1
- package/lib/network/reqresp/types.js +3 -0
- package/lib/network/reqresp/types.js.map +1 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +6 -4
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/constants.d.ts +7 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +7 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +55 -8
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +291 -66
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +22 -4
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +103 -11
- 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 +71 -11
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/range/utils/peerBalancer.d.ts +2 -1
- package/lib/sync/range/utils/peerBalancer.d.ts.map +1 -1
- package/lib/sync/range/utils/peerBalancer.js +8 -4
- package/lib/sync/range/utils/peerBalancer.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/types.d.ts +34 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +34 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +29 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +727 -61
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +73 -10
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +213 -26
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +16 -2
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.js +0 -9
- package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
- package/lib/sync/utils/rateLimit.d.ts +2 -0
- package/lib/sync/utils/rateLimit.d.ts.map +1 -0
- package/lib/sync/utils/rateLimit.js +15 -0
- package/lib/sync/utils/rateLimit.js.map +1 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +20 -5
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +18 -17
- package/src/api/impl/beacon/blocks/index.ts +22 -9
- package/src/api/impl/beacon/pool/index.ts +83 -1
- package/src/api/impl/beacon/state/utils.ts +2 -2
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/api/impl/validator/index.ts +84 -6
- package/src/chain/GetBlobsTracker.ts +1 -2
- package/src/chain/archiveStore/archiveStore.ts +5 -5
- package/src/chain/archiveStore/interface.ts +4 -4
- package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +6 -8
- package/src/chain/archiveStore/utils/archiveBlocks.ts +153 -94
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +38 -83
- package/src/chain/blocks/importExecutionPayload.ts +127 -107
- package/src/chain/blocks/index.ts +72 -24
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +64 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +7 -6
- package/src/chain/blocks/types.ts +16 -26
- package/src/chain/blocks/utils/chainSegment.ts +117 -17
- package/src/chain/blocks/verifyBlock.ts +70 -9
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +26 -7
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +137 -0
- package/src/chain/blocks/verifyPayloadsDataAvailability.ts +41 -0
- package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +9 -18
- package/src/chain/chain.ts +79 -71
- package/src/chain/emitter.ts +15 -14
- package/src/chain/errors/attestationError.ts +6 -1
- package/src/chain/errors/blockError.ts +4 -1
- package/src/chain/errors/executionPayloadBid.ts +6 -0
- package/src/chain/errors/executionPayloadEnvelope.ts +6 -0
- package/src/chain/errors/index.ts +1 -0
- package/src/chain/errors/proposerPreferences.ts +47 -0
- package/src/chain/forkChoice/index.ts +8 -20
- package/src/chain/initState.ts +9 -1
- package/src/chain/interface.ts +11 -3
- package/src/chain/opPools/payloadAttestationPool.ts +29 -8
- package/src/chain/prepareNextSlot.ts +55 -24
- package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
- package/src/chain/produceBlock/produceBlockBody.ts +91 -27
- package/src/chain/regen/errors.ts +1 -6
- package/src/chain/regen/interface.ts +7 -12
- package/src/chain/regen/queued.ts +14 -55
- package/src/chain/regen/regen.ts +10 -43
- package/src/chain/seenCache/index.ts +1 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +89 -21
- package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
- package/src/chain/stateCache/datastore/db.ts +10 -33
- package/src/chain/stateCache/datastore/file.ts +5 -6
- package/src/chain/stateCache/datastore/types.ts +2 -3
- package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
- package/src/chain/stateCache/persistentCheckpointsCache.ts +139 -247
- package/src/chain/stateCache/types.ts +8 -14
- package/src/chain/validation/aggregateAndProof.ts +13 -0
- package/src/chain/validation/attestation.ts +13 -0
- package/src/chain/validation/block.ts +1 -0
- package/src/chain/validation/executionPayloadBid.ts +25 -8
- package/src/chain/validation/executionPayloadEnvelope.ts +22 -12
- package/src/chain/validation/payloadAttestationMessage.ts +5 -3
- package/src/chain/validation/proposerPreferences.ts +110 -0
- package/src/db/repositories/executionPayloadEnvelopeArchive.ts +1 -1
- package/src/execution/engine/http.ts +21 -14
- package/src/execution/engine/interface.ts +1 -0
- package/src/execution/engine/mock.ts +8 -1
- package/src/execution/engine/types.ts +41 -0
- package/src/metrics/metrics/lodestar.ts +10 -0
- package/src/network/gossip/interface.ts +6 -0
- package/src/network/gossip/scoringParameters.ts +14 -1
- package/src/network/gossip/topic.ts +6 -0
- package/src/network/interface.ts +2 -0
- package/src/network/network.ts +24 -1
- package/src/network/processor/gossipHandlers.ts +114 -28
- package/src/network/processor/gossipQueues/index.ts +5 -0
- package/src/network/processor/index.ts +6 -5
- package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
- package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
- package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
- package/src/network/reqresp/handlers/index.ts +5 -0
- package/src/network/reqresp/interface.ts +1 -1
- package/src/network/reqresp/protocols.ts +6 -0
- package/src/network/reqresp/rateLimit.ts +4 -0
- package/src/network/reqresp/score.ts +1 -0
- package/src/network/reqresp/types.ts +5 -0
- package/src/node/nodejs.ts +6 -4
- package/src/sync/constants.ts +8 -4
- package/src/sync/range/batch.ts +388 -75
- package/src/sync/range/chain.ts +131 -14
- package/src/sync/range/range.ts +86 -11
- package/src/sync/range/utils/peerBalancer.ts +9 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/types.ts +72 -0
- package/src/sync/unknownBlock.ts +915 -65
- package/src/sync/utils/downloadByRange.ts +386 -39
- package/src/sync/utils/downloadByRoot.ts +24 -2
- package/src/sync/utils/pendingBlocksTree.ts +0 -15
- package/src/sync/utils/rateLimit.ts +16 -0
- package/src/util/sszBytes.ts +25 -5
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import {routes} from "@lodestar/api";
|
|
1
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
3
|
import {ForkSeq} from "@lodestar/params";
|
|
3
4
|
import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
|
|
4
5
|
import {computeTimeAtSlot} from "@lodestar/state-transition";
|
|
5
|
-
import {RootHex} from "@lodestar/types";
|
|
6
|
-
import {Logger, prettyPrintIndices, pruneSetToMax, sleep} from "@lodestar/utils";
|
|
6
|
+
import {RootHex, gloas} from "@lodestar/types";
|
|
7
|
+
import {Logger, fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
|
|
7
8
|
import {isBlockInputBlobs, isBlockInputColumns} from "../chain/blocks/blockInput/blockInput.js";
|
|
8
9
|
import {BlockInputSource, IBlockInput} from "../chain/blocks/blockInput/types.js";
|
|
10
|
+
import {PayloadError, PayloadErrorCode} from "../chain/blocks/importExecutionPayload.js";
|
|
11
|
+
import {PayloadEnvelopeInput, PayloadEnvelopeInputSource} from "../chain/blocks/payloadEnvelopeInput/index.js";
|
|
9
12
|
import {BlockError, BlockErrorCode} from "../chain/errors/index.js";
|
|
10
13
|
import {ChainEvent, ChainEventData, IBeaconChain} from "../chain/index.js";
|
|
14
|
+
import {validateGloasBlockDataColumnSidecars} from "../chain/validation/dataColumnSidecar.js";
|
|
15
|
+
import {validateGossipExecutionPayloadEnvelope} from "../chain/validation/executionPayloadEnvelope.js";
|
|
11
16
|
import {Metrics} from "../metrics/index.js";
|
|
12
17
|
import {INetwork, NetworkEvent, NetworkEventData, prettyPrintPeerIdStr} from "../network/index.js";
|
|
13
18
|
import {PeerSyncMeta} from "../network/peers/peersData.js";
|
|
@@ -19,20 +24,38 @@ import {MAX_CONCURRENT_REQUESTS} from "./constants.js";
|
|
|
19
24
|
import {SyncOptions} from "./options.js";
|
|
20
25
|
import {
|
|
21
26
|
BlockInputSyncCacheItem,
|
|
27
|
+
PayloadSyncCacheItem,
|
|
22
28
|
PendingBlockInput,
|
|
23
29
|
PendingBlockInputStatus,
|
|
24
30
|
PendingBlockType,
|
|
31
|
+
PendingPayloadEnvelope,
|
|
32
|
+
PendingPayloadInput,
|
|
33
|
+
PendingPayloadInputStatus,
|
|
34
|
+
PendingPayloadRootHex,
|
|
25
35
|
getBlockInputSyncCacheItemRootHex,
|
|
26
36
|
getBlockInputSyncCacheItemSlot,
|
|
37
|
+
getPayloadSyncCacheItemRootHex,
|
|
38
|
+
getPayloadSyncCacheItemSlot,
|
|
27
39
|
isPendingBlockInput,
|
|
40
|
+
isPendingPayloadEnvelope,
|
|
41
|
+
isPendingPayloadInput,
|
|
28
42
|
} from "./types.js";
|
|
29
43
|
import {DownloadByRootError, downloadByRoot} from "./utils/downloadByRoot.js";
|
|
30
|
-
import {getAllDescendantBlocks,
|
|
44
|
+
import {getAllDescendantBlocks, getUnknownAndAncestorBlocks} from "./utils/pendingBlocksTree.js";
|
|
45
|
+
import {getRateLimitedUntilMs} from "./utils/rateLimit.js";
|
|
31
46
|
|
|
32
47
|
const MAX_ATTEMPTS_PER_BLOCK = 5;
|
|
33
48
|
const MAX_KNOWN_BAD_BLOCKS = 500;
|
|
34
49
|
const MAX_PENDING_BLOCKS = 100;
|
|
35
50
|
|
|
51
|
+
type AdvancePendingBlockResult =
|
|
52
|
+
| "ready"
|
|
53
|
+
| "queued_block"
|
|
54
|
+
| "queued_parent_block"
|
|
55
|
+
| "queued_parent_payload"
|
|
56
|
+
| "blocked"
|
|
57
|
+
| "removed";
|
|
58
|
+
|
|
36
59
|
enum FetchResult {
|
|
37
60
|
SuccessResolved = "success_resolved",
|
|
38
61
|
SuccessMissingParent = "success_missing_parent",
|
|
@@ -41,6 +64,13 @@ enum FetchResult {
|
|
|
41
64
|
FailureMaxAttempts = "failure_max_attempts",
|
|
42
65
|
}
|
|
43
66
|
|
|
67
|
+
class UnknownBlockRateLimitedError extends Error {
|
|
68
|
+
constructor(message: string) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "UnknownBlockRateLimitedError";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
/**
|
|
45
75
|
* BlockInputSync is a class that handles ReqResp to find blocks and data related to a specific blockRoot. The
|
|
46
76
|
* blockRoot may have been found via object gossip, or the API. Gossip objects that can trigger a search are block,
|
|
@@ -78,10 +108,13 @@ export class BlockInputSync {
|
|
|
78
108
|
* block RootHex -> PendingBlock. To avoid finding same root at the same time
|
|
79
109
|
*/
|
|
80
110
|
private readonly pendingBlocks = new Map<RootHex, BlockInputSyncCacheItem>();
|
|
111
|
+
// Payload sync is keyed by beacon block root as well, so block and payload queues can unblock each other.
|
|
112
|
+
private readonly pendingPayloads = new Map<RootHex, PayloadSyncCacheItem>();
|
|
81
113
|
private readonly knownBadBlocks = new Set<RootHex>();
|
|
82
114
|
private readonly maxPendingBlocks;
|
|
83
115
|
private subscribedToNetworkEvents = false;
|
|
84
116
|
private peerBalancer: UnknownBlockPeerBalancer;
|
|
117
|
+
private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
|
|
85
118
|
|
|
86
119
|
constructor(
|
|
87
120
|
private readonly config: ChainForkConfig,
|
|
@@ -98,6 +131,9 @@ export class BlockInputSync {
|
|
|
98
131
|
metrics.blockInputSync.pendingBlocks.addCollect(() =>
|
|
99
132
|
metrics.blockInputSync.pendingBlocks.set(this.pendingBlocks.size)
|
|
100
133
|
);
|
|
134
|
+
metrics.blockInputSync.pendingPayloads.addCollect(() =>
|
|
135
|
+
metrics.blockInputSync.pendingPayloads.set(this.pendingPayloads.size)
|
|
136
|
+
);
|
|
101
137
|
metrics.blockInputSync.knownBadBlocks.addCollect(() =>
|
|
102
138
|
metrics.blockInputSync.knownBadBlocks.set(this.knownBadBlocks.size)
|
|
103
139
|
);
|
|
@@ -114,8 +150,12 @@ export class BlockInputSync {
|
|
|
114
150
|
if (!this.subscribedToNetworkEvents) {
|
|
115
151
|
this.logger.verbose("BlockInputSync enabled.");
|
|
116
152
|
this.chain.emitter.on(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
153
|
+
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
117
154
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
155
|
+
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
118
156
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
157
|
+
this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
|
|
158
|
+
this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
119
159
|
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
120
160
|
this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
121
161
|
this.subscribedToNetworkEvents = true;
|
|
@@ -124,9 +164,14 @@ export class BlockInputSync {
|
|
|
124
164
|
|
|
125
165
|
unsubscribeFromNetwork(): void {
|
|
126
166
|
this.logger.verbose("BlockInputSync disabled.");
|
|
167
|
+
this.clearRateLimitBackoffTimer();
|
|
127
168
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
169
|
+
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
128
170
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
171
|
+
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
129
172
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
173
|
+
this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
|
|
174
|
+
this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
130
175
|
this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
131
176
|
this.network.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
132
177
|
this.subscribedToNetworkEvents = false;
|
|
@@ -168,12 +213,55 @@ export class BlockInputSync {
|
|
|
168
213
|
}
|
|
169
214
|
};
|
|
170
215
|
|
|
216
|
+
private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
|
|
217
|
+
try {
|
|
218
|
+
this.addByPayloadRootHex(data.rootHex, data.peer);
|
|
219
|
+
this.triggerUnknownBlockSearch();
|
|
220
|
+
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_DATA});
|
|
221
|
+
this.metrics?.blockInputSync.source.inc({source: data.source});
|
|
222
|
+
} catch (e) {
|
|
223
|
+
this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e as Error);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
private onIncompletePayloadEnvelope = (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]): void => {
|
|
228
|
+
try {
|
|
229
|
+
this.addByPayloadInput(data.payloadInput, data.peer);
|
|
230
|
+
this.triggerUnknownBlockSearch();
|
|
231
|
+
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_DATA});
|
|
232
|
+
this.metrics?.blockInputSync.source.inc({source: data.source});
|
|
233
|
+
} catch (e) {
|
|
234
|
+
this.logger.debug("Error handling incompletePayloadEnvelope event", {}, e as Error);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
171
238
|
/**
|
|
172
239
|
* Process an unknownBlockParent event and register the block in `pendingBlocks` Map.
|
|
173
240
|
*/
|
|
174
241
|
private onUnknownParent = (data: ChainEventData[ChainEvent.blockUnknownParent]): void => {
|
|
175
242
|
try {
|
|
176
|
-
this.
|
|
243
|
+
const missingDependency = this.getMissingBlockDependency(data.blockInput);
|
|
244
|
+
if (missingDependency.kind === "invalidParentPayload") {
|
|
245
|
+
this.addByBlockInput(data.blockInput, data.peer);
|
|
246
|
+
|
|
247
|
+
const pendingBlock = this.pendingBlocks.get(data.blockInput.blockRootHex);
|
|
248
|
+
if (pendingBlock && isPendingBlockInput(pendingBlock)) {
|
|
249
|
+
this.logger.debug("Ignoring block with conflicting parent payload hash", {
|
|
250
|
+
slot: pendingBlock.blockInput.slot,
|
|
251
|
+
root: pendingBlock.blockInput.blockRootHex,
|
|
252
|
+
parentRoot: missingDependency.parentRootHex,
|
|
253
|
+
parentBlockHash: missingDependency.parentBlockHashHex,
|
|
254
|
+
});
|
|
255
|
+
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (missingDependency.kind === "parentPayload") {
|
|
261
|
+
this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
|
|
262
|
+
} else if (missingDependency.kind === "parentBlock") {
|
|
263
|
+
this.addByRootHex(missingDependency.rootHex, data.peer);
|
|
264
|
+
}
|
|
177
265
|
this.addByBlockInput(data.blockInput, data.peer);
|
|
178
266
|
this.triggerUnknownBlockSearch();
|
|
179
267
|
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PARENT});
|
|
@@ -183,8 +271,22 @@ export class BlockInputSync {
|
|
|
183
271
|
}
|
|
184
272
|
};
|
|
185
273
|
|
|
186
|
-
private
|
|
274
|
+
private onBlockImported = (): void => {
|
|
275
|
+
if (this.pendingPayloads.size > 0) {
|
|
276
|
+
this.triggerUnknownBlockSearch();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
private onPayloadImported = ({
|
|
281
|
+
blockRoot,
|
|
282
|
+
}: routes.events.EventData[routes.events.EventType.executionPayload]): void => {
|
|
283
|
+
this.pendingPayloads.delete(blockRoot);
|
|
284
|
+
this.triggerUnknownBlockSearch();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
|
|
187
288
|
let pendingBlock = this.pendingBlocks.get(rootHex);
|
|
289
|
+
let added = false;
|
|
188
290
|
if (!pendingBlock) {
|
|
189
291
|
pendingBlock = {
|
|
190
292
|
status: PendingBlockInputStatus.pending,
|
|
@@ -193,6 +295,7 @@ export class BlockInputSync {
|
|
|
193
295
|
timeAddedSec: Date.now() / 1000,
|
|
194
296
|
};
|
|
195
297
|
this.pendingBlocks.set(rootHex, pendingBlock);
|
|
298
|
+
added = true;
|
|
196
299
|
|
|
197
300
|
this.logger.verbose("Added new rootHex to BlockInputSync.pendingBlocks", {
|
|
198
301
|
root: pendingBlock.rootHex,
|
|
@@ -210,6 +313,7 @@ export class BlockInputSync {
|
|
|
210
313
|
if (prunedItemCount > 0) {
|
|
211
314
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
|
|
212
315
|
}
|
|
316
|
+
return added;
|
|
213
317
|
};
|
|
214
318
|
|
|
215
319
|
private addByBlockInput = (blockInput: IBlockInput, peerIdStr?: string): void => {
|
|
@@ -242,6 +346,58 @@ export class BlockInputSync {
|
|
|
242
346
|
}
|
|
243
347
|
};
|
|
244
348
|
|
|
349
|
+
private addByPayloadRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
|
|
350
|
+
let pendingPayload = this.pendingPayloads.get(rootHex);
|
|
351
|
+
let added = false;
|
|
352
|
+
if (!pendingPayload) {
|
|
353
|
+
pendingPayload = {
|
|
354
|
+
status: PendingPayloadInputStatus.pending,
|
|
355
|
+
rootHex,
|
|
356
|
+
peerIdStrings: new Set(),
|
|
357
|
+
timeAddedSec: Date.now() / 1000,
|
|
358
|
+
};
|
|
359
|
+
this.pendingPayloads.set(rootHex, pendingPayload);
|
|
360
|
+
added = true;
|
|
361
|
+
|
|
362
|
+
this.logger.verbose("Added new payload rootHex to BlockInputSync.pendingPayloads", {
|
|
363
|
+
root: rootHex,
|
|
364
|
+
peerIdStr: peerIdStr ?? "unknown peer",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (peerIdStr) {
|
|
369
|
+
pendingPayload.peerIdStrings.add(peerIdStr);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
|
|
373
|
+
if (prunedItemCount > 0) {
|
|
374
|
+
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
|
|
375
|
+
}
|
|
376
|
+
return added;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
private addByPayloadInput = (
|
|
380
|
+
payloadInput: PayloadEnvelopeInput,
|
|
381
|
+
peerIdStr?: PeerIdStr,
|
|
382
|
+
envelope?: gloas.SignedExecutionPayloadEnvelope
|
|
383
|
+
): void => {
|
|
384
|
+
const pendingPayload = this.toPendingPayloadInput(
|
|
385
|
+
payloadInput,
|
|
386
|
+
this.pendingPayloads.get(payloadInput.blockRootHex),
|
|
387
|
+
envelope
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (peerIdStr) {
|
|
391
|
+
pendingPayload.peerIdStrings.add(peerIdStr);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
|
|
395
|
+
const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
|
|
396
|
+
if (prunedItemCount > 0) {
|
|
397
|
+
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
245
401
|
private onPeerConnected = (data: NetworkEventData[NetworkEvent.peerConnected]): void => {
|
|
246
402
|
try {
|
|
247
403
|
const peerId = data.peer;
|
|
@@ -256,56 +412,242 @@ export class BlockInputSync {
|
|
|
256
412
|
private onPeerDisconnected = (data: NetworkEventData[NetworkEvent.peerDisconnected]): void => {
|
|
257
413
|
const peerId = data.peer;
|
|
258
414
|
this.peerBalancer.onPeerDisconnected(peerId);
|
|
415
|
+
this.scheduleRateLimitBackoffRetry();
|
|
259
416
|
};
|
|
260
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Post-gloas, a locally complete block can still be blocked on its parent's execution payload lineage.
|
|
420
|
+
* Distinguish which dependency is missing so the scheduler can enqueue the right follow-up work.
|
|
421
|
+
*/
|
|
422
|
+
private getMissingBlockDependency(
|
|
423
|
+
blockInput: IBlockInput
|
|
424
|
+
):
|
|
425
|
+
| {kind: "ready"}
|
|
426
|
+
| {kind: "block" | "parentBlock" | "parentPayload"; rootHex: RootHex}
|
|
427
|
+
| {kind: "invalidParentPayload"; parentRootHex: RootHex; parentBlockHashHex: RootHex} {
|
|
428
|
+
const parentRootHex = blockInput.parentRootHex;
|
|
429
|
+
if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
|
|
430
|
+
return {kind: "parentBlock", rootHex: parentRootHex};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!blockInput.hasBlock()) {
|
|
434
|
+
return {kind: "block", rootHex: blockInput.blockRootHex};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (this.config.getForkSeq(blockInput.slot) < ForkSeq.gloas) {
|
|
438
|
+
return {kind: "ready"};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const block = blockInput.getBlock() as gloas.SignedBeaconBlock;
|
|
442
|
+
const parentBlockHashHex = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
|
|
443
|
+
if (this.chain.forkChoice.getBlockHexAndBlockHash(parentRootHex, parentBlockHashHex) !== null) {
|
|
444
|
+
return {kind: "ready"};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
|
|
448
|
+
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
|
|
452
|
+
if (parentPayloadInput) {
|
|
453
|
+
if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
|
|
454
|
+
return {kind: "parentPayload", rootHex: parentRootHex};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {kind: "parentPayload", rootHex: parentRootHex};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private advancePendingBlock(pendingBlock: PendingBlockInput): AdvancePendingBlockResult {
|
|
464
|
+
const missingDependency = this.getMissingBlockDependency(pendingBlock.blockInput);
|
|
465
|
+
|
|
466
|
+
switch (missingDependency.kind) {
|
|
467
|
+
case "ready":
|
|
468
|
+
return "ready";
|
|
469
|
+
|
|
470
|
+
case "block":
|
|
471
|
+
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
472
|
+
return "queued_block";
|
|
473
|
+
|
|
474
|
+
case "parentBlock": {
|
|
475
|
+
let added = this.addByRootHex(missingDependency.rootHex);
|
|
476
|
+
for (const peerIdStr of pendingBlock.peerIdStrings) {
|
|
477
|
+
added = this.addByRootHex(missingDependency.rootHex, peerIdStr) || added;
|
|
478
|
+
}
|
|
479
|
+
return added ? "queued_parent_block" : "blocked";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
case "parentPayload": {
|
|
483
|
+
let added = this.addByPayloadRootHex(missingDependency.rootHex);
|
|
484
|
+
for (const peerIdStr of pendingBlock.peerIdStrings) {
|
|
485
|
+
added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
|
|
486
|
+
}
|
|
487
|
+
return added ? "queued_parent_payload" : "blocked";
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
case "invalidParentPayload":
|
|
491
|
+
this.logger.debug("Removing block with conflicting parent payload hash", {
|
|
492
|
+
slot: pendingBlock.blockInput.slot,
|
|
493
|
+
root: pendingBlock.blockInput.blockRootHex,
|
|
494
|
+
parentRoot: missingDependency.parentRootHex,
|
|
495
|
+
parentBlockHash: missingDependency.parentBlockHashHex,
|
|
496
|
+
});
|
|
497
|
+
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
498
|
+
return "removed";
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private toPendingPayloadInput(
|
|
503
|
+
payloadInput: PayloadEnvelopeInput,
|
|
504
|
+
previous?: PayloadSyncCacheItem,
|
|
505
|
+
envelope?: gloas.SignedExecutionPayloadEnvelope
|
|
506
|
+
): PendingPayloadInput {
|
|
507
|
+
// Normalize every payload queueing path into the same cache shape while preserving first-seen
|
|
508
|
+
// timing and peer provenance from any earlier by-root or envelope-only entry.
|
|
509
|
+
const queuedEnvelope = envelope ?? (previous && isPendingPayloadEnvelope(previous) ? previous.envelope : undefined);
|
|
510
|
+
|
|
511
|
+
if (queuedEnvelope && !payloadInput.hasPayloadEnvelope()) {
|
|
512
|
+
payloadInput.addPayloadEnvelope({
|
|
513
|
+
envelope: queuedEnvelope,
|
|
514
|
+
source: PayloadEnvelopeInputSource.byRoot,
|
|
515
|
+
seenTimestampSec: Date.now() / 1000,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
status: payloadInput.isComplete() ? PendingPayloadInputStatus.downloaded : PendingPayloadInputStatus.pending,
|
|
521
|
+
payloadInput,
|
|
522
|
+
timeAddedSec: previous?.timeAddedSec ?? Date.now() / 1000,
|
|
523
|
+
timeSyncedSec: payloadInput.isComplete() ? Date.now() / 1000 : undefined,
|
|
524
|
+
peerIdStrings: new Set(previous?.peerIdStrings ?? []),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
261
528
|
/**
|
|
262
529
|
* Gather tip parent blocks with unknown parent and do a search for all of them
|
|
263
530
|
*/
|
|
264
531
|
private triggerUnknownBlockSearch = (): void => {
|
|
265
532
|
// Cheap early stop to prevent calling the network.getConnectedPeers()
|
|
266
|
-
if (this.pendingBlocks.size === 0) {
|
|
533
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
267
534
|
return;
|
|
268
535
|
}
|
|
269
536
|
|
|
270
|
-
// If the node loses all peers with pending unknown blocks, the sync will stall
|
|
537
|
+
// If the node loses all peers with pending unknown blocks or payloads, the sync will stall
|
|
271
538
|
const connectedPeers = this.network.getConnectedPeers();
|
|
272
|
-
|
|
273
|
-
this.logger.debug("No connected peers, skipping unknown block search.");
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
539
|
+
const hasConnectedPeers = connectedPeers.length > 0;
|
|
276
540
|
|
|
277
541
|
const {unknowns, ancestors} = getUnknownAndAncestorBlocks(this.pendingBlocks);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (this.chain.forkChoice.hasBlockHex(block.blockInput.parentRootHex)) {
|
|
542
|
+
let processedBlocks = 0;
|
|
543
|
+
let shouldRerunBlockSearch = false;
|
|
544
|
+
|
|
545
|
+
for (const block of ancestors) {
|
|
546
|
+
const advanceResult = this.advancePendingBlock(block);
|
|
547
|
+
switch (advanceResult) {
|
|
548
|
+
case "ready":
|
|
286
549
|
processedBlocks++;
|
|
287
|
-
this.
|
|
550
|
+
this.processReadyBlock(block).catch((e) => {
|
|
288
551
|
this.logger.debug("Unexpected error - process old downloaded block", {}, e);
|
|
289
552
|
});
|
|
290
|
-
|
|
553
|
+
break;
|
|
554
|
+
|
|
555
|
+
case "queued_block":
|
|
556
|
+
case "queued_parent_block":
|
|
557
|
+
shouldRerunBlockSearch = true;
|
|
558
|
+
break;
|
|
559
|
+
|
|
560
|
+
case "queued_parent_payload":
|
|
561
|
+
case "blocked":
|
|
562
|
+
case "removed":
|
|
563
|
+
break;
|
|
291
564
|
}
|
|
565
|
+
}
|
|
292
566
|
|
|
567
|
+
if (unknowns.length > 0) {
|
|
568
|
+
if (!hasConnectedPeers) {
|
|
569
|
+
this.logger.debug("No connected peers, skipping unknown block download.");
|
|
570
|
+
} else {
|
|
571
|
+
// Most of the time there is exactly 1 unknown block
|
|
572
|
+
for (const block of unknowns) {
|
|
573
|
+
this.downloadBlock(block).catch((e) => {
|
|
574
|
+
this.logger.debug("Unexpected error - downloadBlock", {root: getBlockInputSyncCacheItemRootHex(block)}, e);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else if (ancestors.length > 0) {
|
|
579
|
+
// It's rare when there is no unknown block
|
|
580
|
+
// see https://github.com/ChainSafe/lodestar/issues/5649#issuecomment-1594213550
|
|
293
581
|
this.logger.verbose("No unknown block, process ancestor downloaded blocks", {
|
|
294
582
|
pendingBlocks: this.pendingBlocks.size,
|
|
295
583
|
ancestorBlocks: ancestors.length,
|
|
296
584
|
processedBlocks,
|
|
297
585
|
});
|
|
298
|
-
return;
|
|
299
586
|
}
|
|
300
587
|
|
|
301
|
-
//
|
|
302
|
-
for (const
|
|
303
|
-
|
|
304
|
-
this.
|
|
588
|
+
// Blocks can unblock payloads and payloads can unblock blocks, so every scheduler pass services both queues.
|
|
589
|
+
for (const payload of Array.from(this.pendingPayloads.values())) {
|
|
590
|
+
if (isPendingPayloadInput(payload) && payload.status === PendingPayloadInputStatus.downloaded) {
|
|
591
|
+
this.processPayload(payload).catch((e) => {
|
|
592
|
+
this.logger.debug("Unexpected error - process downloaded payload", {}, e);
|
|
593
|
+
});
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (isPendingPayloadEnvelope(payload)) {
|
|
598
|
+
this.reconcilePayloadEnvelope(payload).catch((e) => {
|
|
599
|
+
this.logger.debug("Unexpected error - reconcile pending payload envelope", {}, e);
|
|
600
|
+
});
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!hasConnectedPeers) {
|
|
605
|
+
this.logger.debug("No connected peers, skipping unknown payload download.", {
|
|
606
|
+
root: getPayloadSyncCacheItemRootHex(payload),
|
|
607
|
+
});
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
this.downloadPayload(payload).catch((e) => {
|
|
612
|
+
this.logger.debug("Unexpected error - downloadPayload", {root: getPayloadSyncCacheItemRootHex(payload)}, e);
|
|
305
613
|
});
|
|
306
614
|
}
|
|
615
|
+
|
|
616
|
+
if (shouldRerunBlockSearch) {
|
|
617
|
+
this.triggerUnknownBlockSearch();
|
|
618
|
+
}
|
|
307
619
|
};
|
|
308
620
|
|
|
621
|
+
private scheduleRateLimitBackoffRetry(): void {
|
|
622
|
+
this.clearRateLimitBackoffTimer();
|
|
623
|
+
|
|
624
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const now = Date.now();
|
|
629
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
|
|
630
|
+
if (retryAt === null) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
this.rateLimitBackoffTimeout = setTimeout(
|
|
635
|
+
() => {
|
|
636
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
637
|
+
this.triggerUnknownBlockSearch();
|
|
638
|
+
this.scheduleRateLimitBackoffRetry();
|
|
639
|
+
},
|
|
640
|
+
Math.max(0, retryAt - now)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private clearRateLimitBackoffTimer(): void {
|
|
645
|
+
if (this.rateLimitBackoffTimeout !== undefined) {
|
|
646
|
+
clearTimeout(this.rateLimitBackoffTimeout);
|
|
647
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
309
651
|
private async downloadBlock(block: BlockInputSyncCacheItem): Promise<void> {
|
|
310
652
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
311
653
|
return;
|
|
@@ -342,10 +684,26 @@ export class BlockInputSync {
|
|
|
342
684
|
this.logger.verbose("Downloaded unknown block", logCtx2);
|
|
343
685
|
|
|
344
686
|
if (parentInForkChoice) {
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
687
|
+
// If the direct parent is already in fork choice, let the block state machine decide if
|
|
688
|
+
// the next step is block import, parent payload download, or branch removal.
|
|
689
|
+
const advanceResult = this.advancePendingBlock(pending);
|
|
690
|
+
switch (advanceResult) {
|
|
691
|
+
case "ready":
|
|
692
|
+
this.processReadyBlock(pending).catch((e) => {
|
|
693
|
+
this.logger.debug("Unexpected error - process newly downloaded block", logCtx2, e);
|
|
694
|
+
});
|
|
695
|
+
break;
|
|
696
|
+
|
|
697
|
+
case "queued_block":
|
|
698
|
+
case "queued_parent_block":
|
|
699
|
+
case "queued_parent_payload":
|
|
700
|
+
this.triggerUnknownBlockSearch();
|
|
701
|
+
break;
|
|
702
|
+
|
|
703
|
+
case "blocked":
|
|
704
|
+
case "removed":
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
349
707
|
} else if (blockSlot <= finalizedSlot) {
|
|
350
708
|
// the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
|
|
351
709
|
// we should found it through forkchoice. If not, we should penalize all peers sending us this block chain
|
|
@@ -361,6 +719,16 @@ export class BlockInputSync {
|
|
|
361
719
|
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
|
|
362
720
|
}
|
|
363
721
|
} else {
|
|
722
|
+
if (res.err instanceof UnknownBlockRateLimitedError) {
|
|
723
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
724
|
+
if (pendingBlock) {
|
|
725
|
+
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
726
|
+
}
|
|
727
|
+
this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
|
|
728
|
+
this.scheduleRateLimitBackoffRetry();
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
364
732
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
365
733
|
this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
|
|
366
734
|
this.removeAndDownScoreAllDescendants(block);
|
|
@@ -368,26 +736,11 @@ export class BlockInputSync {
|
|
|
368
736
|
}
|
|
369
737
|
|
|
370
738
|
/**
|
|
371
|
-
*
|
|
372
|
-
* On error, remove and downscore
|
|
373
|
-
* This function could run recursively for all descendant blocks
|
|
739
|
+
* Import a block that has already passed the local dependency checks in BlockInputSync.
|
|
740
|
+
* On error, remove and downscore descendants as appropriate for the failure type.
|
|
374
741
|
*/
|
|
375
|
-
private async
|
|
376
|
-
// pending block status is `downloaded` right after `downloadBlock`
|
|
377
|
-
// but could be `pending` if added by `onUnknownBlockParent` event and this function is called recursively
|
|
742
|
+
private async processReadyBlock(pendingBlock: PendingBlockInput): Promise<void> {
|
|
378
743
|
if (pendingBlock.status !== PendingBlockInputStatus.downloaded) {
|
|
379
|
-
if (pendingBlock.status === PendingBlockInputStatus.pending) {
|
|
380
|
-
const connectedPeers = this.network.getConnectedPeers();
|
|
381
|
-
if (connectedPeers.length === 0) {
|
|
382
|
-
this.logger.debug("No connected peers, skipping download block", {
|
|
383
|
-
slot: pendingBlock.blockInput.slot,
|
|
384
|
-
blockRoot: pendingBlock.blockInput.blockRootHex,
|
|
385
|
-
});
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
// if the download is a success we'll call `processBlock()` for this block
|
|
389
|
-
await this.downloadBlock(pendingBlock);
|
|
390
|
-
}
|
|
391
744
|
return;
|
|
392
745
|
}
|
|
393
746
|
|
|
@@ -432,15 +785,9 @@ export class BlockInputSync {
|
|
|
432
785
|
if (!res.err) {
|
|
433
786
|
// no need to update status to "processed", delete anyway
|
|
434
787
|
this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
|
|
435
|
-
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
if (isPendingBlockInput(descendantBlock)) {
|
|
439
|
-
this.processBlock(descendantBlock).catch((e) => {
|
|
440
|
-
this.logger.debug("Unexpected error - process descendant block", {}, e);
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
}
|
|
788
|
+
// Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
|
|
789
|
+
// are advanced through the same dependency checks as every other pending item.
|
|
790
|
+
this.triggerUnknownBlockSearch();
|
|
444
791
|
} else {
|
|
445
792
|
const errorData = {slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex};
|
|
446
793
|
if (res.err instanceof BlockError) {
|
|
@@ -456,6 +803,19 @@ export class BlockInputSync {
|
|
|
456
803
|
pendingBlock.status = PendingBlockInputStatus.downloaded;
|
|
457
804
|
break;
|
|
458
805
|
|
|
806
|
+
case BlockErrorCode.PARENT_PAYLOAD_UNKNOWN:
|
|
807
|
+
this.logger.error(
|
|
808
|
+
"processReadyBlock() hit unexpected parent payload dependency after readiness checks",
|
|
809
|
+
{
|
|
810
|
+
...errorData,
|
|
811
|
+
parentRoot: pendingBlock.blockInput.parentRootHex,
|
|
812
|
+
parentBlockHash: res.err.type.parentBlockHash,
|
|
813
|
+
},
|
|
814
|
+
res.err
|
|
815
|
+
);
|
|
816
|
+
pendingBlock.status = PendingBlockInputStatus.downloaded;
|
|
817
|
+
break;
|
|
818
|
+
|
|
459
819
|
case BlockErrorCode.EXECUTION_ENGINE_ERROR:
|
|
460
820
|
// Removing the block(s) without penalizing the peers, hoping for EL to
|
|
461
821
|
// recover on a latter download + verify attempt
|
|
@@ -477,6 +837,393 @@ export class BlockInputSync {
|
|
|
477
837
|
}
|
|
478
838
|
}
|
|
479
839
|
|
|
840
|
+
/**
|
|
841
|
+
* Reconcile an envelope-first payload entry once the block import path has seeded its
|
|
842
|
+
* PayloadEnvelopeInput. This may queue block download, validate the speculative envelope, or
|
|
843
|
+
* downgrade back to by-root fetching when the cached envelope does not match the imported block.
|
|
844
|
+
*/
|
|
845
|
+
private async reconcilePayloadEnvelope(pendingPayload: PendingPayloadEnvelope): Promise<void> {
|
|
846
|
+
const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
|
|
847
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
848
|
+
this.pendingPayloads.delete(rootHex);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
853
|
+
if (!payloadInput) {
|
|
854
|
+
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
855
|
+
// Column commitments live on the block body, so an envelope-only entry has to pull the block first.
|
|
856
|
+
if (!this.pendingBlocks.has(rootHex)) {
|
|
857
|
+
this.addByRootHex(rootHex);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
861
|
+
if (pendingBlock && this.network.getConnectedPeers().length > 0) {
|
|
862
|
+
await this.downloadBlock(pendingBlock);
|
|
863
|
+
}
|
|
864
|
+
} else {
|
|
865
|
+
this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
|
|
866
|
+
root: rootHex,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (!payloadInput.hasPayloadEnvelope()) {
|
|
873
|
+
const validationResult = await wrapError(
|
|
874
|
+
validateGossipExecutionPayloadEnvelope(this.chain, pendingPayload.envelope)
|
|
875
|
+
);
|
|
876
|
+
if (validationResult.err) {
|
|
877
|
+
this.logger.debug(
|
|
878
|
+
"Pending payload envelope failed validation after block import, refetching by root",
|
|
879
|
+
{slot: pendingPayload.envelope.message.payload.slotNumber, root: rootHex},
|
|
880
|
+
validationResult.err
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
const pendingPayloadByRoot: PendingPayloadRootHex = {
|
|
884
|
+
status: PendingPayloadInputStatus.pending,
|
|
885
|
+
rootHex,
|
|
886
|
+
timeAddedSec: pendingPayload.timeAddedSec,
|
|
887
|
+
peerIdStrings: new Set(pendingPayload.peerIdStrings),
|
|
888
|
+
};
|
|
889
|
+
this.pendingPayloads.set(rootHex, pendingPayloadByRoot);
|
|
890
|
+
|
|
891
|
+
if (this.network.getConnectedPeers().length > 0) {
|
|
892
|
+
await this.downloadPayload(pendingPayloadByRoot);
|
|
893
|
+
}
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const upgradedPayload = this.toPendingPayloadInput(payloadInput, pendingPayload, pendingPayload.envelope);
|
|
899
|
+
this.pendingPayloads.set(rootHex, upgradedPayload);
|
|
900
|
+
|
|
901
|
+
if (upgradedPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
902
|
+
await this.processPayload(upgradedPayload);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
await this.downloadPayload(upgradedPayload);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private async downloadPayload(payload: PayloadSyncCacheItem): Promise<void> {
|
|
910
|
+
if (isPendingPayloadEnvelope(payload)) {
|
|
911
|
+
await this.reconcilePayloadEnvelope(payload);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const rootHex = getPayloadSyncCacheItemRootHex(payload);
|
|
916
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
917
|
+
this.pendingPayloads.delete(rootHex);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (payload.status !== PendingPayloadInputStatus.pending) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const logCtx = {
|
|
926
|
+
slot: getPayloadSyncCacheItemSlot(payload),
|
|
927
|
+
root: rootHex,
|
|
928
|
+
pendingPayloads: this.pendingPayloads.size,
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
this.logger.verbose("BlockInputSync.downloadPayload()", logCtx);
|
|
932
|
+
|
|
933
|
+
payload.status = PendingPayloadInputStatus.fetching;
|
|
934
|
+
|
|
935
|
+
const res = await wrapError(this.fetchPayloadInput(payload));
|
|
936
|
+
if (!res.err) {
|
|
937
|
+
const pendingPayload = res.result;
|
|
938
|
+
this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
|
|
939
|
+
|
|
940
|
+
if (isPendingPayloadEnvelope(pendingPayload)) {
|
|
941
|
+
await this.reconcilePayloadEnvelope(pendingPayload);
|
|
942
|
+
} else if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
943
|
+
await this.processPayload(pendingPayload);
|
|
944
|
+
}
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
this.logger.debug("Ignoring unknown payload root after failed download", logCtx, res.err);
|
|
949
|
+
if (!isPendingPayloadEnvelope(payload)) {
|
|
950
|
+
payload.status = PendingPayloadInputStatus.pending;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
|
|
955
|
+
const rootHex = pendingPayload.payloadInput.blockRootHex;
|
|
956
|
+
const logCtx = {slot: pendingPayload.payloadInput.slot, root: rootHex};
|
|
957
|
+
|
|
958
|
+
if (pendingPayload.status !== PendingPayloadInputStatus.downloaded) {
|
|
959
|
+
this.logger.debug("Skipping payload processing before payload input is downloaded", {
|
|
960
|
+
...logCtx,
|
|
961
|
+
status: pendingPayload.status,
|
|
962
|
+
});
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
967
|
+
this.logger.debug("Payload already imported while processing unknown payload", logCtx);
|
|
968
|
+
this.pendingPayloads.delete(rootHex);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
973
|
+
this.logger.debug("Payload input is ready before its block is in fork choice", logCtx);
|
|
974
|
+
const added = this.addByRootHex(rootHex);
|
|
975
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
976
|
+
if (added) {
|
|
977
|
+
this.triggerUnknownBlockSearch();
|
|
978
|
+
}
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
pendingPayload.status = PendingPayloadInputStatus.processing;
|
|
983
|
+
|
|
984
|
+
const res = await wrapError(this.chain.processExecutionPayload(pendingPayload.payloadInput));
|
|
985
|
+
if (!res.err) {
|
|
986
|
+
this.logger.debug("Processed payload from unknown sync", logCtx);
|
|
987
|
+
this.pendingPayloads.delete(rootHex);
|
|
988
|
+
this.triggerUnknownBlockSearch();
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (res.err instanceof PayloadError) {
|
|
993
|
+
switch (res.err.type.code) {
|
|
994
|
+
case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
|
|
995
|
+
// Payload sync discovered the block dependency before the block queue did. Re-enqueue the
|
|
996
|
+
// block and keep the payload ready so the scheduler can retry once the block reaches fork choice.
|
|
997
|
+
if (this.addByRootHex(rootHex)) {
|
|
998
|
+
this.triggerUnknownBlockSearch();
|
|
999
|
+
}
|
|
1000
|
+
// Keep the payload out of any synchronous requeue pass; a later scheduler pass will retry it.
|
|
1001
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
1002
|
+
break;
|
|
1003
|
+
|
|
1004
|
+
case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
|
|
1005
|
+
this.logger.debug("Execution engine error while processing payload from unknown sync", logCtx, res.err);
|
|
1006
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
1007
|
+
break;
|
|
1008
|
+
|
|
1009
|
+
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1010
|
+
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1011
|
+
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1012
|
+
// TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
|
|
1013
|
+
// reintroducing envelope replacement / recreation flows.
|
|
1014
|
+
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1015
|
+
this.removePendingPayloadAndDescendants(rootHex);
|
|
1016
|
+
break;
|
|
1017
|
+
|
|
1018
|
+
default:
|
|
1019
|
+
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1020
|
+
this.pendingPayloads.delete(rootHex);
|
|
1021
|
+
}
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
this.logger.debug("Unknown error processing payload from unknown sync", logCtx, res.err);
|
|
1026
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Download payload material keyed by beacon block root. Unlike block download, payload sync may
|
|
1031
|
+
* already have a locally cached envelope or partial columns, so each attempt starts from local state
|
|
1032
|
+
* and only asks peers for the remaining pieces.
|
|
1033
|
+
*/
|
|
1034
|
+
private async fetchPayloadInput(
|
|
1035
|
+
cacheItem: PendingPayloadInput | PendingPayloadRootHex
|
|
1036
|
+
): Promise<PendingPayloadInput | PendingPayloadEnvelope> {
|
|
1037
|
+
const rootHex = getPayloadSyncCacheItemRootHex(cacheItem);
|
|
1038
|
+
const blockRoot = fromHex(rootHex);
|
|
1039
|
+
const excludedPeers = new Set<PeerIdStr>();
|
|
1040
|
+
|
|
1041
|
+
let slot = getPayloadSyncCacheItemSlot(cacheItem);
|
|
1042
|
+
let payloadInput = isPendingPayloadInput(cacheItem)
|
|
1043
|
+
? cacheItem.payloadInput
|
|
1044
|
+
: this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
1045
|
+
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1046
|
+
|
|
1047
|
+
let i = 0;
|
|
1048
|
+
let deferredByRateLimit = false;
|
|
1049
|
+
while (i++ < this.getMaxDownloadAttempts()) {
|
|
1050
|
+
const pendingColumns = payloadInput?.hasAllData()
|
|
1051
|
+
? new Set<number>()
|
|
1052
|
+
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
1053
|
+
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1054
|
+
if (peerMeta === null) {
|
|
1055
|
+
if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
|
|
1056
|
+
throw new UnknownBlockRateLimitedError(
|
|
1057
|
+
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
throw Error(
|
|
1062
|
+
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const {peerId, client: peerClient} = peerMeta;
|
|
1067
|
+
cacheItem.peerIdStrings.add(peerId);
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
if (!envelope) {
|
|
1071
|
+
envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
|
|
1072
|
+
slot = envelope.message.payload.slotNumber;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
1076
|
+
if (!payloadInput) {
|
|
1077
|
+
if (this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
1078
|
+
throw new Error(`Missing PayloadEnvelopeInput for known block ${rootHex}`);
|
|
1079
|
+
}
|
|
1080
|
+
// Keep the validated envelope around, but wait for the block body before turning it into a full payload input.
|
|
1081
|
+
return {
|
|
1082
|
+
status: PendingPayloadInputStatus.waitingForBlock,
|
|
1083
|
+
envelope,
|
|
1084
|
+
timeAddedSec: cacheItem.timeAddedSec,
|
|
1085
|
+
peerIdStrings: cacheItem.peerIdStrings,
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (!payloadInput.hasPayloadEnvelope()) {
|
|
1090
|
+
await validateGossipExecutionPayloadEnvelope(this.chain, envelope);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
let pendingPayload = this.toPendingPayloadInput(payloadInput, cacheItem, envelope);
|
|
1094
|
+
if (!pendingPayload.payloadInput.hasAllData()) {
|
|
1095
|
+
const missing = pendingPayload.payloadInput.getMissingSampledColumnMeta().missing;
|
|
1096
|
+
if (missing.length > 0) {
|
|
1097
|
+
const columnSidecars = await this.fetchPayloadColumns(peerMeta, pendingPayload.payloadInput, missing);
|
|
1098
|
+
const seenTimestampSec = Date.now() / 1000;
|
|
1099
|
+
for (const columnSidecar of columnSidecars) {
|
|
1100
|
+
if (pendingPayload.payloadInput.hasColumn(columnSidecar.index)) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
pendingPayload.payloadInput.addColumn({
|
|
1105
|
+
columnSidecar,
|
|
1106
|
+
source: PayloadEnvelopeInputSource.byRoot,
|
|
1107
|
+
seenTimestampSec,
|
|
1108
|
+
peerIdStr: peerId,
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
pendingPayload = this.toPendingPayloadInput(pendingPayload.payloadInput, pendingPayload);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
this.logger.verbose("BlockInputSync.fetchPayloadInput: successful download", {
|
|
1116
|
+
slot,
|
|
1117
|
+
rootHex,
|
|
1118
|
+
peerId,
|
|
1119
|
+
peerClient,
|
|
1120
|
+
hasPayload: pendingPayload.payloadInput.hasPayloadEnvelope(),
|
|
1121
|
+
hasAllData: pendingPayload.payloadInput.hasAllData(),
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1125
|
+
return pendingPayload;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
cacheItem = pendingPayload;
|
|
1129
|
+
payloadInput = pendingPayload.payloadInput;
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
this.logger.debug(
|
|
1132
|
+
"Error downloading payload in BlockInputSync.fetchPayloadInput",
|
|
1133
|
+
{slot, rootHex, attempt: i, peer: peerId, peerClient},
|
|
1134
|
+
e as Error
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(e);
|
|
1138
|
+
if (rateLimitedUntilMs !== null) {
|
|
1139
|
+
deferredByRateLimit = true;
|
|
1140
|
+
this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
|
|
1141
|
+
this.scheduleRateLimitBackoffRetry();
|
|
1142
|
+
} else if (e instanceof RequestError) {
|
|
1143
|
+
switch (e.type.code) {
|
|
1144
|
+
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1145
|
+
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
1146
|
+
break;
|
|
1147
|
+
default:
|
|
1148
|
+
excludedPeers.add(peerId);
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
} else {
|
|
1152
|
+
excludedPeers.add(peerId);
|
|
1153
|
+
}
|
|
1154
|
+
} finally {
|
|
1155
|
+
this.peerBalancer.onRequestCompleted(peerId);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1160
|
+
throw new UnknownBlockRateLimitedError(
|
|
1161
|
+
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
private async fetchExecutionPayloadEnvelope(
|
|
1169
|
+
peerIdStr: PeerIdStr,
|
|
1170
|
+
blockRoot: Uint8Array,
|
|
1171
|
+
rootHex: RootHex
|
|
1172
|
+
): Promise<gloas.SignedExecutionPayloadEnvelope> {
|
|
1173
|
+
const response = await this.network.sendExecutionPayloadEnvelopesByRoot(peerIdStr, [blockRoot]);
|
|
1174
|
+
const envelope = response.at(0);
|
|
1175
|
+
if (!envelope) {
|
|
1176
|
+
throw new Error(`Missing execution payload envelope for root=${rootHex}`);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const receivedRootHex = toRootHex(envelope.message.beaconBlockRoot);
|
|
1180
|
+
if (receivedRootHex !== rootHex) {
|
|
1181
|
+
throw new Error(`Execution payload envelope root mismatch requested=${rootHex} received=${receivedRootHex}`);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return envelope;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private async fetchPayloadColumns(
|
|
1188
|
+
peerMeta: PeerSyncMeta,
|
|
1189
|
+
payloadInput: PayloadEnvelopeInput,
|
|
1190
|
+
missing: number[]
|
|
1191
|
+
): Promise<gloas.DataColumnSidecar[]> {
|
|
1192
|
+
const {peerId: peerIdStr} = peerMeta;
|
|
1193
|
+
const peerColumns = new Set(peerMeta.custodyColumns ?? []);
|
|
1194
|
+
const requestedColumns = missing.filter((columnIndex) => peerColumns.has(columnIndex));
|
|
1195
|
+
if (requestedColumns.length === 0) {
|
|
1196
|
+
return [];
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const columnSidecars = (await this.network.sendDataColumnSidecarsByRoot(peerIdStr, [
|
|
1200
|
+
{blockRoot: fromHex(payloadInput.blockRootHex), columns: requestedColumns},
|
|
1201
|
+
])) as gloas.DataColumnSidecar[];
|
|
1202
|
+
|
|
1203
|
+
if (columnSidecars.length === 0) {
|
|
1204
|
+
throw new Error(`No data column sidecars returned for payload root=${payloadInput.blockRootHex}`);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const requestedColumnsSet = new Set(requestedColumns);
|
|
1208
|
+
const extraColumns = columnSidecars.filter((columnSidecar) => !requestedColumnsSet.has(columnSidecar.index));
|
|
1209
|
+
if (extraColumns.length > 0) {
|
|
1210
|
+
throw new Error(
|
|
1211
|
+
`Received unexpected payload data columns indices=${prettyPrintIndices(extraColumns.map((column) => column.index))}`
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// PayloadEnvelopeInput already carries the block slot, root, and commitments, so reuse the
|
|
1216
|
+
// block-based Gloas validator rather than maintaining a second payload-specific variant.
|
|
1217
|
+
await validateGloasBlockDataColumnSidecars(
|
|
1218
|
+
payloadInput.slot,
|
|
1219
|
+
fromHex(payloadInput.blockRootHex),
|
|
1220
|
+
payloadInput.getBlobKzgCommitments(),
|
|
1221
|
+
columnSidecars,
|
|
1222
|
+
this.chain.metrics?.peerDas
|
|
1223
|
+
);
|
|
1224
|
+
return columnSidecars;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
480
1227
|
/**
|
|
481
1228
|
* From a set of shuffled peers:
|
|
482
1229
|
* - fetch the block
|
|
@@ -498,6 +1245,7 @@ export class BlockInputSync {
|
|
|
498
1245
|
}
|
|
499
1246
|
|
|
500
1247
|
let i = 0;
|
|
1248
|
+
let deferredByRateLimit = false;
|
|
501
1249
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
502
1250
|
const pendingColumns =
|
|
503
1251
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
@@ -505,6 +1253,12 @@ export class BlockInputSync {
|
|
|
505
1253
|
: defaultPendingColumns;
|
|
506
1254
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
507
1255
|
if (peerMeta === null) {
|
|
1256
|
+
if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
|
|
1257
|
+
throw new UnknownBlockRateLimitedError(
|
|
1258
|
+
`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
508
1262
|
// no more peer with needed columns to try, throw error
|
|
509
1263
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
510
1264
|
this.metrics?.blockInputSync.fetchTimeSec.observe(
|
|
@@ -560,14 +1314,23 @@ export class BlockInputSync {
|
|
|
560
1314
|
} else if (e instanceof RequestError) {
|
|
561
1315
|
// should look into req_resp metrics in this case
|
|
562
1316
|
downloadByRootMetrics?.error.inc({code: "req_resp", client: peerClient});
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
1317
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(e);
|
|
1318
|
+
if (rateLimitedUntilMs !== null) {
|
|
1319
|
+
deferredByRateLimit = true;
|
|
1320
|
+
this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
|
|
1321
|
+
this.scheduleRateLimitBackoffRetry();
|
|
1322
|
+
} else {
|
|
1323
|
+
switch (e.type.code) {
|
|
1324
|
+
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1325
|
+
case RequestErrorCode.RESP_RATE_LIMITED:
|
|
1326
|
+
case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
|
|
1327
|
+
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
1328
|
+
// do not exclude peer for these errors
|
|
1329
|
+
break;
|
|
1330
|
+
default:
|
|
1331
|
+
excludedPeers.add(peerId);
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
571
1334
|
}
|
|
572
1335
|
} else {
|
|
573
1336
|
// investigate if this happens
|
|
@@ -595,6 +1358,10 @@ export class BlockInputSync {
|
|
|
595
1358
|
|
|
596
1359
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
597
1360
|
|
|
1361
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1362
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
598
1365
|
if (!isPendingBlockInput(cacheItem)) {
|
|
599
1366
|
throw Error(`${message} No block and no data was found.`);
|
|
600
1367
|
}
|
|
@@ -660,6 +1427,28 @@ export class BlockInputSync {
|
|
|
660
1427
|
pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
|
|
661
1428
|
}
|
|
662
1429
|
|
|
1430
|
+
// Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
|
|
1431
|
+
private removePendingPayloadAndDescendants(rootHex: RootHex): void {
|
|
1432
|
+
// Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
|
|
1433
|
+
// later validation/finalization logic decides when it can leave memory.
|
|
1434
|
+
this.pendingPayloads.delete(rootHex);
|
|
1435
|
+
|
|
1436
|
+
const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
|
|
1437
|
+
this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
|
|
1438
|
+
|
|
1439
|
+
for (const block of badPendingBlocks) {
|
|
1440
|
+
const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
1441
|
+
this.pendingBlocks.delete(descendantRootHex);
|
|
1442
|
+
this.pendingPayloads.delete(descendantRootHex);
|
|
1443
|
+
this.chain.seenBlockInputCache.prune(descendantRootHex);
|
|
1444
|
+
this.logger.debug("Removing pending descendant after invalid parent payload", {
|
|
1445
|
+
slot: getBlockInputSyncCacheItemSlot(block),
|
|
1446
|
+
blockRoot: descendantRootHex,
|
|
1447
|
+
parentPayloadRoot: rootHex,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
663
1452
|
private removeAllDescendants(block: BlockInputSyncCacheItem): BlockInputSyncCacheItem[] {
|
|
664
1453
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
665
1454
|
const slot = getBlockInputSyncCacheItemSlot(block);
|
|
@@ -671,7 +1460,10 @@ export class BlockInputSync {
|
|
|
671
1460
|
for (const block of badPendingBlocks) {
|
|
672
1461
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
673
1462
|
this.pendingBlocks.delete(rootHex);
|
|
1463
|
+
this.pendingPayloads.delete(rootHex);
|
|
674
1464
|
this.chain.seenBlockInputCache.prune(rootHex);
|
|
1465
|
+
// Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
|
|
1466
|
+
// importBlock()-owned lifecycle.
|
|
675
1467
|
this.logger.debug("Removing bad/unknown/incomplete BlockInputSyncCacheItem", {
|
|
676
1468
|
slot,
|
|
677
1469
|
blockRoot: rootHex,
|
|
@@ -701,10 +1493,12 @@ export class BlockInputSync {
|
|
|
701
1493
|
export class UnknownBlockPeerBalancer {
|
|
702
1494
|
readonly peersMeta: Map<PeerIdStr, PeerSyncMeta>;
|
|
703
1495
|
readonly activeRequests: Map<PeerIdStr, number>;
|
|
1496
|
+
readonly rateLimitedUntilByPeer: Map<PeerIdStr, number>;
|
|
704
1497
|
|
|
705
1498
|
constructor() {
|
|
706
1499
|
this.peersMeta = new Map();
|
|
707
1500
|
this.activeRequests = new Map();
|
|
1501
|
+
this.rateLimitedUntilByPeer = new Map();
|
|
708
1502
|
}
|
|
709
1503
|
|
|
710
1504
|
/** Trigger on each peer re-status */
|
|
@@ -719,6 +1513,41 @@ export class UnknownBlockPeerBalancer {
|
|
|
719
1513
|
onPeerDisconnected(peerId: PeerIdStr): void {
|
|
720
1514
|
this.peersMeta.delete(peerId);
|
|
721
1515
|
this.activeRequests.delete(peerId);
|
|
1516
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
onRateLimited(peerId: PeerIdStr, rateLimitedUntilMs: number): void {
|
|
1520
|
+
this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
getNextRateLimitRetryAt(pendingColumns?: Set<number>, excludedPeers?: Set<PeerIdStr>): number | null {
|
|
1524
|
+
const now = Date.now();
|
|
1525
|
+
let retryAt: number | null = null;
|
|
1526
|
+
|
|
1527
|
+
for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
|
|
1528
|
+
if (rateLimitedUntil <= now) {
|
|
1529
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (excludedPeers?.has(peerId)) {
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const syncMeta = this.peersMeta.get(peerId);
|
|
1538
|
+
if (syncMeta === undefined) {
|
|
1539
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
if (pendingColumns !== undefined && !this.peerHasPendingColumns(syncMeta, pendingColumns)) {
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
retryAt = Math.min(retryAt ?? rateLimitedUntil, rateLimitedUntil);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
return retryAt;
|
|
722
1551
|
}
|
|
723
1552
|
|
|
724
1553
|
/**
|
|
@@ -767,6 +1596,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
767
1596
|
}
|
|
768
1597
|
|
|
769
1598
|
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
|
|
1599
|
+
const now = Date.now();
|
|
770
1600
|
let maxColumnCount = 0;
|
|
771
1601
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
772
1602
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -775,12 +1605,24 @@ export class UnknownBlockPeerBalancer {
|
|
|
775
1605
|
continue;
|
|
776
1606
|
}
|
|
777
1607
|
|
|
1608
|
+
const rateLimitedUntil = this.rateLimitedUntilByPeer.get(peerId);
|
|
1609
|
+
if (rateLimitedUntil !== undefined) {
|
|
1610
|
+
if (now < rateLimitedUntil) {
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
778
1616
|
const activeRequests = this.activeRequests.get(peerId) ?? 0;
|
|
779
1617
|
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
780
1618
|
// should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
|
|
781
1619
|
continue;
|
|
782
1620
|
}
|
|
783
1621
|
|
|
1622
|
+
if (!this.peerHasPendingColumns(syncMeta, pendingDataColumns)) {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
784
1626
|
if (pendingDataColumns.size === 0) {
|
|
785
1627
|
considerPeers.push({peerId, columnCount: 0});
|
|
786
1628
|
continue;
|
|
@@ -814,4 +1656,12 @@ export class UnknownBlockPeerBalancer {
|
|
|
814
1656
|
|
|
815
1657
|
return eligiblePeers;
|
|
816
1658
|
}
|
|
1659
|
+
|
|
1660
|
+
private peerHasPendingColumns(syncMeta: PeerSyncMeta, pendingDataColumns: Set<number>): boolean {
|
|
1661
|
+
if (pendingDataColumns.size === 0) {
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
return syncMeta.custodyColumns.some((column) => pendingDataColumns.has(column));
|
|
1666
|
+
}
|
|
817
1667
|
}
|