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