@lodestar/beacon-node 1.50.0-dev.fd4f342a40 → 1.50.0-dev.fe4e7408b4
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 +13 -3
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js +13 -0
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +28 -6
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +26 -18
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +1 -0
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +65 -4
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +14 -24
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +1 -1
- package/lib/chain/archiveStore/interface.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/beaconProposerCache.d.ts +6 -0
- package/lib/chain/beaconProposerCache.d.ts.map +1 -1
- package/lib/chain/beaconProposerCache.js +16 -0
- package/lib/chain/beaconProposerCache.js.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -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 +14 -8
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +7 -7
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +10 -5
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +102 -33
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +7 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +2 -0
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +6 -3
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/opPool.d.ts.map +1 -1
- package/lib/chain/opPools/opPool.js +1 -23
- package/lib/chain/opPools/opPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +8 -4
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +27 -12
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/opPools/utils.d.ts +11 -0
- package/lib/chain/opPools/utils.d.ts.map +1 -1
- package/lib/chain/opPools/utils.js +22 -0
- package/lib/chain/opPools/utils.js.map +1 -1
- package/lib/chain/options.d.ts +2 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +11 -3
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +8 -0
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +48 -34
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +2 -0
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +10 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -0
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +22 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +3 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +13 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/shufflingCache.d.ts +18 -8
- package/lib/chain/shufflingCache.d.ts.map +1 -1
- package/lib/chain/shufflingCache.js +72 -18
- package/lib/chain/shufflingCache.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +5 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +64 -54
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts +4 -1
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +6 -1
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/buckets.d.ts +3 -1
- package/lib/db/buckets.d.ts.map +1 -1
- package/lib/db/buckets.js +2 -0
- package/lib/db/buckets.js.map +1 -1
- package/lib/db/interface.d.ts +4 -1
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +2 -2
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +3 -3
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.js +1 -2
- package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
- package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
- package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
- package/lib/db/repositories/earliestAvailableSlot.js +19 -0
- package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
- package/lib/db/repositories/index.d.ts +1 -0
- package/lib/db/repositories/index.d.ts.map +1 -1
- package/lib/db/repositories/index.js +1 -0
- package/lib/db/repositories/index.js.map +1 -1
- package/lib/db/repositories/proposerPreferences.d.ts +14 -0
- package/lib/db/repositories/proposerPreferences.d.ts.map +1 -0
- package/lib/db/repositories/proposerPreferences.js +21 -0
- package/lib/db/repositories/proposerPreferences.js.map +1 -0
- package/lib/execution/engine/blobVersionedHashes.d.ts +9 -0
- package/lib/execution/engine/blobVersionedHashes.d.ts.map +1 -0
- package/lib/execution/engine/blobVersionedHashes.js +74 -0
- package/lib/execution/engine/blobVersionedHashes.js.map +1 -0
- package/lib/execution/engine/disabled.d.ts +0 -2
- package/lib/execution/engine/disabled.d.ts.map +1 -1
- package/lib/execution/engine/disabled.js +0 -6
- package/lib/execution/engine/disabled.js.map +1 -1
- package/lib/execution/engine/http.d.ts +46 -16
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +236 -213
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/index.d.ts +3 -2
- package/lib/execution/engine/index.d.ts.map +1 -1
- package/lib/execution/engine/index.js +33 -8
- package/lib/execution/engine/index.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -4
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/jsonRpcTransport.d.ts +25 -0
- package/lib/execution/engine/jsonRpcTransport.d.ts.map +1 -0
- package/lib/execution/engine/jsonRpcTransport.js +158 -0
- package/lib/execution/engine/jsonRpcTransport.js.map +1 -0
- package/lib/execution/engine/mock.d.ts +0 -2
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +0 -8
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/restHttpClient.d.ts +71 -0
- package/lib/execution/engine/restHttpClient.d.ts.map +1 -0
- package/lib/execution/engine/restHttpClient.js +201 -0
- package/lib/execution/engine/restHttpClient.js.map +1 -0
- package/lib/execution/engine/restTransport.d.ts +46 -0
- package/lib/execution/engine/restTransport.d.ts.map +1 -0
- package/lib/execution/engine/restTransport.js +399 -0
- package/lib/execution/engine/restTransport.js.map +1 -0
- package/lib/execution/engine/sszTypes.d.ts +1261 -0
- package/lib/execution/engine/sszTypes.d.ts.map +1 -0
- package/lib/execution/engine/sszTypes.js +216 -0
- package/lib/execution/engine/sszTypes.js.map +1 -0
- package/lib/execution/engine/transport.d.ts +39 -0
- package/lib/execution/engine/transport.d.ts.map +1 -0
- package/lib/execution/engine/transport.js +2 -0
- package/lib/execution/engine/transport.js.map +1 -0
- package/lib/execution/engine/types.d.ts +2 -8
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.js +24 -57
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/execution/engine/utils.d.ts.map +1 -1
- package/lib/execution/engine/utils.js +9 -2
- package/lib/execution/engine/utils.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +10 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +22 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/metrics/metrics.d.ts +7 -3
- package/lib/metrics/metrics.d.ts.map +1 -1
- package/lib/metrics/metrics.js +4 -4
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/core/events.d.ts +5 -1
- package/lib/network/core/events.d.ts.map +1 -1
- package/lib/network/core/events.js +8 -0
- package/lib/network/core/events.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +0 -14
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +60 -64
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.js +3 -3
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/node/nodejs.d.ts +3 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +27 -4
- package/lib/node/nodejs.js.map +1 -1
- package/lib/node/options.d.ts +3 -2
- package/lib/node/options.d.ts.map +1 -1
- package/lib/node/options.js +2 -2
- package/lib/node/options.js.map +1 -1
- package/lib/sync/range/batch.d.ts +11 -5
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +21 -17
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +4 -4
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +12 -6
- 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 +25 -11
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +5 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +44 -20
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +38 -14
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +84 -39
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/util/asyncIterableToEvents.d.ts +10 -2
- package/lib/util/asyncIterableToEvents.d.ts.map +1 -1
- package/lib/util/asyncIterableToEvents.js +29 -3
- package/lib/util/asyncIterableToEvents.js.map +1 -1
- package/lib/util/execution.d.ts +9 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +5 -1
- package/lib/util/execution.js.map +1 -1
- package/lib/util/metadata.d.ts.map +1 -1
- package/lib/util/metadata.js +2 -1
- package/lib/util/metadata.js.map +1 -1
- package/lib/util/sszBytes.d.ts +2 -2
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +21 -14
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +18 -17
- package/src/api/impl/beacon/blocks/index.ts +17 -7
- package/src/api/impl/beacon/index.ts +15 -0
- package/src/api/impl/beacon/state/index.ts +43 -6
- package/src/api/impl/debug/index.ts +28 -19
- package/src/api/impl/validator/index.ts +81 -7
- package/src/chain/archiveStore/archiveStore.ts +19 -25
- package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -2
- package/src/chain/archiveStore/historicalState/worker.ts +1 -2
- package/src/chain/archiveStore/interface.ts +1 -1
- package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
- package/src/chain/beaconProposerCache.ts +19 -1
- package/src/chain/blocks/blockInput/blockInput.ts +1 -1
- package/src/chain/blocks/importBlock.ts +17 -7
- package/src/chain/blocks/verifyBlock.ts +8 -6
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
- package/src/chain/chain.ts +132 -35
- package/src/chain/emitter.ts +7 -1
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/interface.ts +7 -3
- package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
- package/src/chain/opPools/opPool.ts +1 -31
- package/src/chain/opPools/proposerPreferencesPool.ts +34 -11
- package/src/chain/opPools/utils.ts +31 -0
- package/src/chain/options.ts +3 -3
- package/src/chain/prepareNextSlot.ts +15 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +10 -3
- package/src/chain/produceBlock/produceBlockBody.ts +78 -39
- package/src/chain/regen/regen.ts +12 -4
- package/src/chain/seenCache/seenGossipBlockInput.ts +25 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +20 -13
- package/src/chain/shufflingCache.ts +84 -22
- package/src/chain/validatorMonitor.ts +75 -57
- package/src/db/beacon.ts +6 -0
- package/src/db/buckets.ts +2 -0
- package/src/db/interface.ts +4 -0
- package/src/db/repositories/blockArchive.ts +2 -2
- package/src/db/repositories/blockArchiveIndex.ts +2 -3
- package/src/db/repositories/earliestAvailableSlot.ts +19 -0
- package/src/db/repositories/index.ts +1 -0
- package/src/db/repositories/proposerPreferences.ts +23 -0
- package/src/execution/engine/blobVersionedHashes.ts +72 -0
- package/src/execution/engine/disabled.ts +0 -8
- package/src/execution/engine/http.ts +304 -300
- package/src/execution/engine/index.ts +41 -7
- package/src/execution/engine/interface.ts +2 -6
- package/src/execution/engine/jsonRpcTransport.ts +247 -0
- package/src/execution/engine/mock.ts +1 -19
- package/src/execution/engine/restHttpClient.ts +269 -0
- package/src/execution/engine/restTransport.ts +544 -0
- package/src/execution/engine/sszTypes.ts +354 -0
- package/src/execution/engine/transport.ts +61 -0
- package/src/execution/engine/types.ts +34 -84
- package/src/execution/engine/utils.ts +12 -2
- package/src/metrics/metrics/lodestar.ts +23 -0
- package/src/metrics/metrics.ts +21 -6
- package/src/network/core/events.ts +16 -1
- package/src/network/processor/gossipHandlers.ts +91 -77
- package/src/network/processor/index.ts +3 -3
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
- package/src/node/nodejs.ts +35 -3
- package/src/node/options.ts +10 -1
- package/src/sync/range/batch.ts +25 -19
- package/src/sync/range/chain.ts +15 -8
- package/src/sync/range/range.ts +26 -12
- package/src/sync/unknownBlock.ts +57 -21
- package/src/sync/utils/downloadByRange.ts +123 -58
- package/src/util/asyncIterableToEvents.ts +44 -5
- package/src/util/execution.ts +16 -8
- package/src/util/metadata.ts +2 -1
- package/src/util/sszBytes.ts +25 -12
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {fromHex, toPrintableUrl} from "@lodestar/utils";
|
|
1
|
+
import {LodestarError, fromHex, toPrintableUrl} from "@lodestar/utils";
|
|
2
|
+
import {getLodestarClientVersion} from "../../util/metadata.js";
|
|
2
3
|
import {ExecutionEngineDisabled} from "./disabled.js";
|
|
3
4
|
import {
|
|
5
|
+
EngineApiMode,
|
|
4
6
|
ExecutionEngineHttp,
|
|
5
7
|
ExecutionEngineHttpOpts,
|
|
6
8
|
ExecutionEngineModules,
|
|
7
9
|
defaultExecutionEngineHttpOpts,
|
|
10
|
+
engineApiModes,
|
|
8
11
|
} from "./http.js";
|
|
9
12
|
import {IExecutionEngine} from "./interface.js";
|
|
10
13
|
import {JsonRpcHttpClient} from "./jsonRpcHttpClient.js";
|
|
14
|
+
import {JsonRpcEngineTransport} from "./jsonRpcTransport.js";
|
|
11
15
|
import {ExecutionEngineMockBackend, ExecutionEngineMockOpts} from "./mock.js";
|
|
16
|
+
import {EngineRestHttpClient} from "./restHttpClient.js";
|
|
17
|
+
import {RestEngineTransport, formatClientVersionHeader} from "./restTransport.js";
|
|
12
18
|
import {ExecutionEngineMockJsonRpcClient, JsonRpcBackend} from "./utils.js";
|
|
13
19
|
|
|
14
|
-
export {ExecutionEngineHttp, ExecutionEngineDisabled, defaultExecutionEngineHttpOpts};
|
|
20
|
+
export {ExecutionEngineHttp, ExecutionEngineDisabled, defaultExecutionEngineHttpOpts, engineApiModes};
|
|
21
|
+
export type {EngineApiMode};
|
|
15
22
|
|
|
16
23
|
export type ExecutionEngineOpts =
|
|
17
24
|
| ({mode?: "http"} & ExecutionEngineHttpOpts)
|
|
@@ -24,23 +31,50 @@ export function getExecutionEngineFromBackend(
|
|
|
24
31
|
modules: ExecutionEngineModules
|
|
25
32
|
): IExecutionEngine {
|
|
26
33
|
const rpc = new ExecutionEngineMockJsonRpcClient(backend);
|
|
27
|
-
return new ExecutionEngineHttp(rpc, modules);
|
|
34
|
+
return new ExecutionEngineHttp({jsonRpc: new JsonRpcEngineTransport(rpc)}, modules);
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
export function getExecutionEngineHttp(
|
|
31
38
|
opts: ExecutionEngineHttpOpts,
|
|
32
39
|
modules: ExecutionEngineModules
|
|
33
40
|
): IExecutionEngine {
|
|
41
|
+
const jwtSecret = opts.jwtSecretHex ? fromHex(opts.jwtSecretHex) : undefined;
|
|
42
|
+
const metrics = modules.metrics?.executionEnginerHttpClient;
|
|
43
|
+
const engineApi = opts.engineApi ?? "auto";
|
|
44
|
+
if (engineApi === "ssz" && opts.urls.length !== 1) {
|
|
45
|
+
throw new LodestarError({code: "ENGINE_REST_REQUIRES_SINGLE_URL", count: opts.urls.length});
|
|
46
|
+
}
|
|
47
|
+
if (engineApi === "auto" && opts.urls.length > 1) {
|
|
48
|
+
modules.logger.debug("Using JSON-RPC for multiple execution URLs", {count: opts.urls.length});
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
const rpc = new JsonRpcHttpClient(opts.urls, {
|
|
35
52
|
...opts,
|
|
36
53
|
signal: modules.signal,
|
|
37
|
-
metrics
|
|
38
|
-
jwtSecret
|
|
54
|
+
metrics,
|
|
55
|
+
jwtSecret,
|
|
39
56
|
jwtId: opts.jwtId,
|
|
40
57
|
jwtVersion: opts.jwtVersion,
|
|
41
58
|
});
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
|
|
60
|
+
const rest =
|
|
61
|
+
engineApi === "json-rpc" || opts.urls.length !== 1
|
|
62
|
+
? undefined
|
|
63
|
+
: new RestEngineTransport(
|
|
64
|
+
new EngineRestHttpClient(opts.urls, {
|
|
65
|
+
signal: modules.signal,
|
|
66
|
+
timeout: opts.timeout,
|
|
67
|
+
retries: opts.retries,
|
|
68
|
+
retryDelay: opts.retryDelay,
|
|
69
|
+
jwtSecret,
|
|
70
|
+
jwtId: opts.jwtId,
|
|
71
|
+
clientVersion: formatClientVersionHeader(getLodestarClientVersion(opts)),
|
|
72
|
+
metrics,
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
modules.logger.info("Execution client", {urls: opts.urls.map(toPrintableUrl).toString(), engineApi});
|
|
77
|
+
return new ExecutionEngineHttp({jsonRpc: new JsonRpcEngineTransport(rpc), rest}, modules, opts);
|
|
44
78
|
}
|
|
45
79
|
|
|
46
80
|
export function initializeExecutionEngine(
|
|
@@ -13,7 +13,7 @@ import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, ca
|
|
|
13
13
|
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
14
14
|
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
15
15
|
import {PayloadId, PayloadIdCache, WithdrawalV1} from "./payloadIdCache.js";
|
|
16
|
-
import {
|
|
16
|
+
import {ExecutionPayloadBodyV2} from "./types.js";
|
|
17
17
|
import {DATA} from "./utils.js";
|
|
18
18
|
|
|
19
19
|
export {PayloadIdCache, type PayloadId, type WithdrawalV1};
|
|
@@ -170,13 +170,9 @@ export interface IExecutionEngine {
|
|
|
170
170
|
shouldOverrideBuilder?: boolean;
|
|
171
171
|
}>;
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/** Amsterdam: like V1 but also returns the block access list (null if the EL no longer has it) */
|
|
173
|
+
/** Amsterdam: includes the block access list; REST returns null for bodies outside the Amsterdam era. */
|
|
176
174
|
getPayloadBodiesByHashV2(blockHash: DATA[]): Promise<(ExecutionPayloadBodyV2 | null)[]>;
|
|
177
175
|
|
|
178
|
-
getPayloadBodiesByRange(fork: ForkName, start: number, count: number): Promise<(ExecutionPayloadBody | null)[]>;
|
|
179
|
-
|
|
180
176
|
getBlobs(
|
|
181
177
|
fork: ForkPostFulu,
|
|
182
178
|
versionedHashes: VersionedHashes,
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import {ForkName, ForkSeq} from "@lodestar/params";
|
|
2
|
+
import {ExecutionPayload, ExecutionRequests, Root, RootHex} from "@lodestar/types";
|
|
3
|
+
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
4
|
+
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
5
|
+
import {strip0xPrefix} from "@lodestar/utils";
|
|
6
|
+
import {ClientCode, ClientVersion, PayloadAttributes, PayloadId, VersionedHashes} from "./interface.js";
|
|
7
|
+
import {IJsonRpcHttpClient, JsonRpcHttpClientEventEmitter, ReqOpts} from "./jsonRpcHttpClient.js";
|
|
8
|
+
import {ForkchoiceUpdatedResult, GetPayloadResult, IEngineTransport, PayloadStatusResult} from "./transport.js";
|
|
9
|
+
import {
|
|
10
|
+
BLOB_AND_PROOF_V2_RPC_BYTES,
|
|
11
|
+
EngineApiRpcParamTypes,
|
|
12
|
+
EngineApiRpcReturnTypes,
|
|
13
|
+
ExecutionPayloadBodyV2,
|
|
14
|
+
assertReqSizeLimit,
|
|
15
|
+
deserializeBlobAndProofs,
|
|
16
|
+
deserializeBlobAndProofsV2,
|
|
17
|
+
deserializeBlobAndProofsV2IntoBytes,
|
|
18
|
+
deserializeExecutionPayloadBodyV2,
|
|
19
|
+
parseExecutionPayload,
|
|
20
|
+
serializeBeaconBlockRoot,
|
|
21
|
+
serializeExecutionPayload,
|
|
22
|
+
serializeExecutionRequests,
|
|
23
|
+
serializePayloadAttributes,
|
|
24
|
+
serializeVersionedHashes,
|
|
25
|
+
} from "./types.js";
|
|
26
|
+
import {bytesToData} from "./utils.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Maximum number of version hashes that can be sent in a getBlobs request
|
|
30
|
+
* Clients must support at least 128 versionedHashes, so we avoid sending more
|
|
31
|
+
* https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-3
|
|
32
|
+
*/
|
|
33
|
+
const MAX_VERSIONED_HASHES = 128;
|
|
34
|
+
|
|
35
|
+
// Define static options once to prevent extra allocations
|
|
36
|
+
const notifyNewPayloadOpts: ReqOpts = {routeId: "notifyNewPayload"};
|
|
37
|
+
const forkchoiceUpdatedOpts: ReqOpts = {routeId: "forkchoiceUpdated"};
|
|
38
|
+
const getPayloadOpts: ReqOpts = {routeId: "getPayload"};
|
|
39
|
+
const getPayloadBodiesByHashOpts: ReqOpts = {routeId: "getPayloadBodiesByHash"};
|
|
40
|
+
const getBlobsV1Opts: ReqOpts = {routeId: "getBlobsV1"};
|
|
41
|
+
const getBlobsV2Opts: ReqOpts = {routeId: "getBlobsV2"};
|
|
42
|
+
const getClientVersionOpts: ReqOpts = {routeId: "getClientVersion"};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Legacy `engine_*` JSON-RPC methods
|
|
46
|
+
* https://github.com/ethereum/execution-apis/tree/main/src/engine
|
|
47
|
+
*/
|
|
48
|
+
export class JsonRpcEngineTransport implements IEngineTransport {
|
|
49
|
+
constructor(private readonly rpc: IJsonRpcHttpClient) {}
|
|
50
|
+
|
|
51
|
+
get emitter(): JsonRpcHttpClientEventEmitter {
|
|
52
|
+
return this.rpc.emitter;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async newPayload(
|
|
56
|
+
fork: ForkName,
|
|
57
|
+
executionPayload: ExecutionPayload,
|
|
58
|
+
versionedHashes?: VersionedHashes,
|
|
59
|
+
parentBeaconBlockRoot?: Root,
|
|
60
|
+
executionRequests?: ExecutionRequests
|
|
61
|
+
): Promise<PayloadStatusResult> {
|
|
62
|
+
const serializedExecutionPayload = serializeExecutionPayload(fork, executionPayload);
|
|
63
|
+
|
|
64
|
+
if (ForkSeq[fork] >= ForkSeq.deneb) {
|
|
65
|
+
if (versionedHashes === undefined) {
|
|
66
|
+
throw Error(`versionedHashes required in notifyNewPayload for fork=${fork}`);
|
|
67
|
+
}
|
|
68
|
+
if (parentBeaconBlockRoot === undefined) {
|
|
69
|
+
throw Error(`parentBlockRoot required in notifyNewPayload for fork=${fork}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const serializedVersionedHashes = serializeVersionedHashes(versionedHashes);
|
|
73
|
+
const serializedParentBeaconBlockRoot = serializeBeaconBlockRoot(parentBeaconBlockRoot);
|
|
74
|
+
|
|
75
|
+
if (ForkSeq[fork] >= ForkSeq.electra) {
|
|
76
|
+
if (executionRequests === undefined) {
|
|
77
|
+
throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
|
|
78
|
+
}
|
|
79
|
+
const method = ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4";
|
|
80
|
+
return this.rpc.fetchWithRetries<EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method]>(
|
|
81
|
+
{
|
|
82
|
+
method,
|
|
83
|
+
params: [
|
|
84
|
+
serializedExecutionPayload,
|
|
85
|
+
serializedVersionedHashes,
|
|
86
|
+
serializedParentBeaconBlockRoot,
|
|
87
|
+
serializeExecutionRequests(fork, executionRequests),
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
notifyNewPayloadOpts
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const method = "engine_newPayloadV3";
|
|
95
|
+
return this.rpc.fetchWithRetries<EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method]>(
|
|
96
|
+
{method, params: [serializedExecutionPayload, serializedVersionedHashes, serializedParentBeaconBlockRoot]},
|
|
97
|
+
notifyNewPayloadOpts
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const method = ForkSeq[fork] >= ForkSeq.capella ? "engine_newPayloadV2" : "engine_newPayloadV1";
|
|
102
|
+
return this.rpc.fetchWithRetries<EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method]>(
|
|
103
|
+
{method, params: [serializedExecutionPayload]},
|
|
104
|
+
notifyNewPayloadOpts
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async forkchoiceUpdated(
|
|
109
|
+
fork: ForkName,
|
|
110
|
+
headBlockHash: RootHex,
|
|
111
|
+
safeBlockHash: RootHex,
|
|
112
|
+
finalizedBlockHash: RootHex,
|
|
113
|
+
payloadAttributes?: PayloadAttributes
|
|
114
|
+
): Promise<ForkchoiceUpdatedResult> {
|
|
115
|
+
const method =
|
|
116
|
+
ForkSeq[fork] >= ForkSeq.gloas
|
|
117
|
+
? "engine_forkchoiceUpdatedV4"
|
|
118
|
+
: ForkSeq[fork] >= ForkSeq.deneb
|
|
119
|
+
? "engine_forkchoiceUpdatedV3"
|
|
120
|
+
: ForkSeq[fork] >= ForkSeq.capella
|
|
121
|
+
? "engine_forkchoiceUpdatedV2"
|
|
122
|
+
: "engine_forkchoiceUpdatedV1";
|
|
123
|
+
const payloadAttributesRpc = payloadAttributes ? serializePayloadAttributes(payloadAttributes) : undefined;
|
|
124
|
+
// If we are just fcUing and not asking execution for payload, retry is not required
|
|
125
|
+
// and we can move on, as the next fcU will be issued soon on the new slot
|
|
126
|
+
const fcUReqOpts = payloadAttributes !== undefined ? forkchoiceUpdatedOpts : {...forkchoiceUpdatedOpts, retries: 0};
|
|
127
|
+
|
|
128
|
+
const {payloadStatus, payloadId} = await this.rpc.fetchWithRetries<
|
|
129
|
+
EngineApiRpcReturnTypes[typeof method],
|
|
130
|
+
EngineApiRpcParamTypes[typeof method]
|
|
131
|
+
>({method, params: [{headBlockHash, safeBlockHash, finalizedBlockHash}, payloadAttributesRpc]}, fcUReqOpts);
|
|
132
|
+
|
|
133
|
+
return {payloadStatus, payloadId: payloadId && payloadId !== "0x" ? payloadId : null};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getPayload(fork: ForkName, payloadId: PayloadId): Promise<GetPayloadResult> {
|
|
137
|
+
let method: keyof EngineApiRpcReturnTypes;
|
|
138
|
+
switch (fork) {
|
|
139
|
+
case ForkName.phase0:
|
|
140
|
+
case ForkName.altair:
|
|
141
|
+
case ForkName.bellatrix:
|
|
142
|
+
method = "engine_getPayloadV1";
|
|
143
|
+
break;
|
|
144
|
+
case ForkName.capella:
|
|
145
|
+
method = "engine_getPayloadV2";
|
|
146
|
+
break;
|
|
147
|
+
case ForkName.deneb:
|
|
148
|
+
method = "engine_getPayloadV3";
|
|
149
|
+
break;
|
|
150
|
+
case ForkName.electra:
|
|
151
|
+
method = "engine_getPayloadV4";
|
|
152
|
+
break;
|
|
153
|
+
case ForkName.fulu:
|
|
154
|
+
method = "engine_getPayloadV5";
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
method = "engine_getPayloadV6";
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
const payloadResponse = await this.rpc.fetchWithRetries<
|
|
161
|
+
EngineApiRpcReturnTypes[typeof method],
|
|
162
|
+
EngineApiRpcParamTypes[typeof method]
|
|
163
|
+
>({method, params: [payloadId]}, getPayloadOpts);
|
|
164
|
+
return parseExecutionPayload(fork, payloadResponse);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getPayloadBodiesByHashV2(blockHashes: RootHex[]): Promise<(ExecutionPayloadBodyV2 | null)[]> {
|
|
168
|
+
const method = "engine_getPayloadBodiesByHashV2";
|
|
169
|
+
assertReqSizeLimit(blockHashes.length, 32);
|
|
170
|
+
const response = await this.rpc.fetchWithRetries<
|
|
171
|
+
EngineApiRpcReturnTypes[typeof method],
|
|
172
|
+
EngineApiRpcParamTypes[typeof method]
|
|
173
|
+
>({method, params: [blockHashes]}, getPayloadBodiesByHashOpts);
|
|
174
|
+
return response.map(deserializeExecutionPayloadBodyV2);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async getBlobsV1(versionedHashes: VersionedHashes): Promise<(BlobAndProof | null)[]> {
|
|
178
|
+
assertReqSizeLimit(versionedHashes.length, MAX_VERSIONED_HASHES);
|
|
179
|
+
const versionedHashesHex = versionedHashes.map(bytesToData);
|
|
180
|
+
const response = await this.rpc.fetchWithRetries<
|
|
181
|
+
EngineApiRpcReturnTypes["engine_getBlobsV1"],
|
|
182
|
+
EngineApiRpcParamTypes["engine_getBlobsV1"]
|
|
183
|
+
>({method: "engine_getBlobsV1", params: [versionedHashesHex]}, getBlobsV1Opts);
|
|
184
|
+
|
|
185
|
+
if (response.length !== versionedHashesHex.length) {
|
|
186
|
+
throw Error(
|
|
187
|
+
`Invalid engine_getBlobsV1 response length=${response.length} versionedHashes=${versionedHashesHex.length}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return response.map(deserializeBlobAndProofs);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async getBlobsV2(versionedHashes: VersionedHashes, buffers?: Uint8Array[]): Promise<BlobAndProofV2[] | null> {
|
|
195
|
+
assertReqSizeLimit(versionedHashes.length, MAX_VERSIONED_HASHES);
|
|
196
|
+
const versionedHashesHex = versionedHashes.map(bytesToData);
|
|
197
|
+
if (buffers) {
|
|
198
|
+
// Callers preallocate one buffer per max blobs of the epoch, only the first entries are used
|
|
199
|
+
if (buffers.length < versionedHashesHex.length) {
|
|
200
|
+
throw Error(`Not enough buffers length=${buffers.length} versionedHashes=${versionedHashesHex.length}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for (const [i, buffer] of buffers.entries()) {
|
|
204
|
+
if (buffer.length !== BLOB_AND_PROOF_V2_RPC_BYTES) {
|
|
205
|
+
throw Error(`Invalid buffer[${i}] length=${buffer.length} expected=${BLOB_AND_PROOF_V2_RPC_BYTES}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const response = await this.rpc.fetchWithRetries<
|
|
211
|
+
EngineApiRpcReturnTypes["engine_getBlobsV2"],
|
|
212
|
+
EngineApiRpcParamTypes["engine_getBlobsV2"]
|
|
213
|
+
>({method: "engine_getBlobsV2", params: [versionedHashesHex]}, getBlobsV2Opts);
|
|
214
|
+
|
|
215
|
+
// engine_getBlobsV2 does not return partial responses. It returns null if any blob is not found
|
|
216
|
+
if (response == null) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (response.length !== versionedHashesHex.length) {
|
|
221
|
+
throw Error(
|
|
222
|
+
`Invalid engine_getBlobsV2 response length=${response.length} versionedHashes=${versionedHashesHex.length}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (buffers) {
|
|
227
|
+
// getBlobsV2() is designed to called once per slot so we expect to have buffers
|
|
228
|
+
return response.map((data, i) => deserializeBlobAndProofsV2IntoBytes(data, buffers[i]));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return response.map(deserializeBlobAndProofsV2);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async getClientVersion(clientVersion: ClientVersion): Promise<ClientVersion[]> {
|
|
235
|
+
const method = "engine_getClientVersionV1";
|
|
236
|
+
|
|
237
|
+
const response = await this.rpc.fetchWithRetries<
|
|
238
|
+
EngineApiRpcReturnTypes[typeof method],
|
|
239
|
+
EngineApiRpcParamTypes[typeof method]
|
|
240
|
+
>({method, params: [{...clientVersion, commit: `0x${clientVersion.commit}`}]}, getClientVersionOpts);
|
|
241
|
+
|
|
242
|
+
return response.map((cv) => {
|
|
243
|
+
const code = cv.code in ClientCode ? ClientCode[cv.code as keyof typeof ClientCode] : ClientCode.XX;
|
|
244
|
+
return {code, name: cv.name, version: cv.version, commit: strip0xPrefix(cv.commit)};
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
BlobsBundleRpc,
|
|
23
23
|
EngineApiRpcParamTypes,
|
|
24
24
|
EngineApiRpcReturnTypes,
|
|
25
|
-
ExecutionPayloadBodyRpc,
|
|
26
25
|
ExecutionPayloadBodyV2Rpc,
|
|
27
26
|
ExecutionPayloadRpc,
|
|
28
27
|
ExecutionRequestsRpc,
|
|
@@ -50,7 +49,7 @@ type ExecutionBlock = {
|
|
|
50
49
|
blockHash: RootHex;
|
|
51
50
|
timestamp: number;
|
|
52
51
|
blockNumber: number;
|
|
53
|
-
/** Bodies as received via newPayload, served back by
|
|
52
|
+
/** Bodies as received via newPayload, served back by engine_getPayloadBodiesByHashV2 */
|
|
54
53
|
body: ExecutionPayloadBodyV2Rpc;
|
|
55
54
|
};
|
|
56
55
|
|
|
@@ -148,23 +147,13 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
148
147
|
engine_getPayloadV4: this.getPayloadV5.bind(this),
|
|
149
148
|
engine_getPayloadV5: this.getPayloadV5.bind(this),
|
|
150
149
|
engine_getPayloadV6: this.getPayloadV5.bind(this),
|
|
151
|
-
engine_getPayloadBodiesByHashV1: this.getPayloadBodiesByHash.bind(this),
|
|
152
150
|
engine_getPayloadBodiesByHashV2: this.getPayloadBodiesByHashV2.bind(this),
|
|
153
|
-
engine_getPayloadBodiesByRangeV1: this.getPayloadBodiesByRange.bind(this),
|
|
154
151
|
engine_getClientVersionV1: this.getClientVersionV1.bind(this),
|
|
155
152
|
engine_getBlobsV1: this.getBlobs.bind(this),
|
|
156
153
|
engine_getBlobsV2: this.getBlobsV2.bind(this),
|
|
157
154
|
};
|
|
158
155
|
}
|
|
159
156
|
|
|
160
|
-
private getPayloadBodiesByHash(
|
|
161
|
-
blockHashes: EngineApiRpcParamTypes["engine_getPayloadBodiesByHashV1"][0]
|
|
162
|
-
): EngineApiRpcReturnTypes["engine_getPayloadBodiesByHashV1"] {
|
|
163
|
-
return this.getPayloadBodiesByHashV2(blockHashes).map((body) =>
|
|
164
|
-
body ? {transactions: body.transactions, withdrawals: body.withdrawals} : null
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
157
|
private getPayloadBodiesByHashV2(
|
|
169
158
|
blockHashes: EngineApiRpcParamTypes["engine_getPayloadBodiesByHashV2"][0]
|
|
170
159
|
): EngineApiRpcReturnTypes["engine_getPayloadBodiesByHashV2"] {
|
|
@@ -172,13 +161,6 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
|
|
|
172
161
|
return blockHashes.map((hash) => this.validBlocks.get(hash)?.body ?? null);
|
|
173
162
|
}
|
|
174
163
|
|
|
175
|
-
private getPayloadBodiesByRange(
|
|
176
|
-
_start: EngineApiRpcParamTypes["engine_getPayloadBodiesByRangeV1"][0],
|
|
177
|
-
_count: EngineApiRpcParamTypes["engine_getPayloadBodiesByRangeV1"][1]
|
|
178
|
-
): EngineApiRpcReturnTypes["engine_getPayloadBodiesByRangeV1"] {
|
|
179
|
-
return [] as ExecutionPayloadBodyRpc[];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
164
|
/**
|
|
183
165
|
* Mock manipulator to add predefined responses before execution engine client calls
|
|
184
166
|
*/
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {ErrorAborted, FetchError, LodestarError, TimeoutError, fetch, isValidHttpUrl, retry} from "@lodestar/utils";
|
|
2
|
+
import {
|
|
3
|
+
JsonRpcHttpClientEvent,
|
|
4
|
+
JsonRpcHttpClientEventEmitter,
|
|
5
|
+
JsonRpcHttpClientMetrics,
|
|
6
|
+
ReqOpts,
|
|
7
|
+
} from "./jsonRpcHttpClient.js";
|
|
8
|
+
import {JwtClaim, encodeJwtToken} from "./jwt.js";
|
|
9
|
+
import {ExecutionForkName} from "./sszTypes.js";
|
|
10
|
+
|
|
11
|
+
export const ENGINE_REST_BASE_PATH = "/engine/v1";
|
|
12
|
+
export const EXECUTION_VERSION_HEADER = "Eth-Execution-Version";
|
|
13
|
+
export const CLIENT_VERSION_HEADER = "X-Engine-Client-Version";
|
|
14
|
+
export const MEDIA_TYPE_SSZ = "application/octet-stream";
|
|
15
|
+
export const MEDIA_TYPE_JSON = "application/json";
|
|
16
|
+
|
|
17
|
+
const REQUEST_TIMEOUT = 30 * 1000;
|
|
18
|
+
/** Limits the amount of error detail printed with REST errors */
|
|
19
|
+
const MAX_ERROR_DETAIL_LENGTH = 500;
|
|
20
|
+
|
|
21
|
+
export type EngineRestRequest = {
|
|
22
|
+
method: "GET" | "POST";
|
|
23
|
+
/** Path relative to the `/engine/v1` base path */
|
|
24
|
+
path: string;
|
|
25
|
+
query?: Record<string, string | number>;
|
|
26
|
+
/** Sets the `Eth-Execution-Version` header on fork-scoped endpoints */
|
|
27
|
+
executionFork?: ExecutionForkName;
|
|
28
|
+
/** SSZ encoded request body */
|
|
29
|
+
body?: Uint8Array;
|
|
30
|
+
responseType: "ssz" | "json";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type EngineRestResponse = {
|
|
34
|
+
status: number;
|
|
35
|
+
/** Empty on `204 No Content` */
|
|
36
|
+
body: Uint8Array;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Non-2xx response, `type` and `detail` are taken from the problem+json body when present */
|
|
40
|
+
export class EngineRestError extends Error {
|
|
41
|
+
constructor(
|
|
42
|
+
readonly status: number,
|
|
43
|
+
readonly type: string | null,
|
|
44
|
+
readonly detail: string | null,
|
|
45
|
+
routeId: string
|
|
46
|
+
) {
|
|
47
|
+
super(
|
|
48
|
+
`Engine REST error: status=${status} type=${type ?? "unknown"}${detail ? ` detail=${detail}` : ""}, ${routeId}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class EngineRestResponseError extends LodestarError<{
|
|
54
|
+
code: "ENGINE_REST_INVALID_RESPONSE";
|
|
55
|
+
routeId: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
}> {
|
|
58
|
+
constructor(routeId: string, reason: string) {
|
|
59
|
+
super(
|
|
60
|
+
{code: "ENGINE_REST_INVALID_RESPONSE", routeId, reason},
|
|
61
|
+
`Invalid engine REST response: ${reason}, ${routeId}`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type EngineRestHttpClientOpts = {
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
timeout?: number;
|
|
69
|
+
retries?: number;
|
|
70
|
+
retryDelay?: number;
|
|
71
|
+
/** HS256 secret, a fresh token is generated for every request as ELs check `iat` freshness */
|
|
72
|
+
jwtSecret?: Uint8Array;
|
|
73
|
+
/** Included as `id` claim if `jwtSecret` is provided */
|
|
74
|
+
jwtId?: string;
|
|
75
|
+
/** Value of the `X-Engine-Client-Version` header, the `clv` JWT claim is removed in the REST API */
|
|
76
|
+
clientVersion?: string;
|
|
77
|
+
metrics?: JsonRpcHttpClientMetrics | null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* HTTP/1.1 client for the REST engine API. Only transport concerns live here, encoding of
|
|
82
|
+
* request and response bodies is up to the caller.
|
|
83
|
+
*/
|
|
84
|
+
export class EngineRestHttpClient {
|
|
85
|
+
readonly emitter = new JsonRpcHttpClientEventEmitter();
|
|
86
|
+
private readonly metrics: JsonRpcHttpClientMetrics | null;
|
|
87
|
+
|
|
88
|
+
constructor(
|
|
89
|
+
private readonly urls: string[],
|
|
90
|
+
private readonly opts: EngineRestHttpClientOpts = {}
|
|
91
|
+
) {
|
|
92
|
+
if (urls.length === 0) {
|
|
93
|
+
throw Error("No urls provided to EngineRestHttpClient");
|
|
94
|
+
}
|
|
95
|
+
for (const [i, url] of urls.entries()) {
|
|
96
|
+
if (!isValidHttpUrl(url)) {
|
|
97
|
+
throw Error(`EngineRestHttpClient.urls[${i}] must be a valid URL: ${url}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
this.metrics = opts.metrics ?? null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Perform request with retries, emitting response and error events consumed by the engine state tracking */
|
|
104
|
+
async request(req: EngineRestRequest, opts?: ReqOpts): Promise<EngineRestResponse> {
|
|
105
|
+
try {
|
|
106
|
+
const response = await this.requestWithRetries(req, opts);
|
|
107
|
+
this.emitter.emit(JsonRpcHttpClientEvent.RESPONSE, {payload: req, response});
|
|
108
|
+
return response;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.emitter.emit(JsonRpcHttpClientEvent.ERROR, {payload: req, error: error as Error});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Perform request with retries without emitting events, used to probe support for the REST API */
|
|
116
|
+
async requestWithRetries(req: EngineRestRequest, opts?: ReqOpts): Promise<EngineRestResponse> {
|
|
117
|
+
const routeId = opts?.routeId ?? "unknown";
|
|
118
|
+
return retry(() => this.requestAnyUrl(req, opts), {
|
|
119
|
+
retries: opts?.retries ?? this.opts.retries ?? 0,
|
|
120
|
+
retryDelay: opts?.retryDelay ?? this.opts.retryDelay,
|
|
121
|
+
shouldRetry: opts?.shouldRetry ?? isRetryableEngineRestError,
|
|
122
|
+
signal: this.opts.signal,
|
|
123
|
+
onRetry: () => {
|
|
124
|
+
this.metrics?.retryCount.inc({routeId});
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async requestAnyUrl(req: EngineRestRequest, opts?: ReqOpts): Promise<EngineRestResponse> {
|
|
130
|
+
const routeId = opts?.routeId ?? "unknown";
|
|
131
|
+
let lastError: Error | null = null;
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < this.urls.length; i++) {
|
|
134
|
+
if (i > 0) {
|
|
135
|
+
this.metrics?.requestUsedFallbackUrl.inc({routeId});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
return await this.requestOneUrl(this.urls[i], req, opts);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
if (!isRetryableEngineRestError(e as Error)) {
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
lastError = e as Error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
throw lastError ?? Error("Unknown error");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async requestOneUrl(baseUrl: string, req: EngineRestRequest, opts?: ReqOpts): Promise<EngineRestResponse> {
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const timeout = setTimeout(() => controller.abort(), opts?.timeout ?? this.opts.timeout ?? REQUEST_TIMEOUT);
|
|
153
|
+
|
|
154
|
+
// Not AbortSignal.any(), Node retains every composite until the long-lived parent signal aborts
|
|
155
|
+
const onParentSignalAbort = (): void => controller.abort();
|
|
156
|
+
this.opts.signal?.addEventListener("abort", onParentSignalAbort, {once: true});
|
|
157
|
+
if (this.opts.signal?.aborted) {
|
|
158
|
+
controller.abort();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const routeId = opts?.routeId ?? "unknown";
|
|
162
|
+
const timer = this.metrics?.requestTime.startTimer({routeId});
|
|
163
|
+
this.metrics?.activeRequests.inc({routeId}, 1);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const url = new URL(`${ENGINE_REST_BASE_PATH}${req.path}`, baseUrl);
|
|
167
|
+
for (const [key, value] of Object.entries(req.query ?? {})) {
|
|
168
|
+
url.searchParams.set(key, String(value));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const headers: Record<string, string> = {
|
|
172
|
+
Accept: req.responseType === "ssz" ? MEDIA_TYPE_SSZ : MEDIA_TYPE_JSON,
|
|
173
|
+
};
|
|
174
|
+
if (req.body !== undefined) {
|
|
175
|
+
headers["Content-Type"] = MEDIA_TYPE_SSZ;
|
|
176
|
+
}
|
|
177
|
+
if (req.executionFork !== undefined) {
|
|
178
|
+
headers[EXECUTION_VERSION_HEADER] = req.executionFork;
|
|
179
|
+
}
|
|
180
|
+
if (this.opts.clientVersion !== undefined) {
|
|
181
|
+
headers[CLIENT_VERSION_HEADER] = this.opts.clientVersion;
|
|
182
|
+
}
|
|
183
|
+
if (this.opts.jwtSecret) {
|
|
184
|
+
const jwtClaim: JwtClaim = {iat: Math.floor(Date.now() / 1000), id: this.opts.jwtId};
|
|
185
|
+
headers.Authorization = `Bearer ${encodeJwtToken(jwtClaim, this.opts.jwtSecret)}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.metrics?.requestBytes.inc({routeId}, req.body?.length ?? 0);
|
|
189
|
+
|
|
190
|
+
const res = await fetch(url, {
|
|
191
|
+
method: req.method,
|
|
192
|
+
body: req.body as BodyInit | undefined,
|
|
193
|
+
headers,
|
|
194
|
+
signal: controller.signal,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const streamTimer = this.metrics?.streamTime.startTimer({routeId});
|
|
198
|
+
let body: Uint8Array;
|
|
199
|
+
try {
|
|
200
|
+
body = new Uint8Array(await res.arrayBuffer());
|
|
201
|
+
} catch (e) {
|
|
202
|
+
throw new FetchError(url, e);
|
|
203
|
+
} finally {
|
|
204
|
+
streamTimer?.();
|
|
205
|
+
}
|
|
206
|
+
this.metrics?.responseBytes.inc({routeId}, body.length);
|
|
207
|
+
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
const {type, detail} = parseProblemBody(body);
|
|
210
|
+
throw new EngineRestError(res.status, type, detail, routeId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (res.status !== 200 && res.status !== 204) {
|
|
214
|
+
throw new EngineRestResponseError(routeId, `Unexpected status ${res.status}`);
|
|
215
|
+
}
|
|
216
|
+
const contentType = res.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase();
|
|
217
|
+
const expectedContentType = req.responseType === "ssz" ? MEDIA_TYPE_SSZ : MEDIA_TYPE_JSON;
|
|
218
|
+
if (res.status !== 204 && contentType !== expectedContentType) {
|
|
219
|
+
throw new EngineRestResponseError(routeId, `Unexpected content type ${contentType}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {status: res.status, body};
|
|
223
|
+
} catch (e) {
|
|
224
|
+
this.metrics?.requestErrors.inc({routeId});
|
|
225
|
+
if (controller.signal.aborted) {
|
|
226
|
+
if (this.opts.signal?.aborted) {
|
|
227
|
+
throw new ErrorAborted("request");
|
|
228
|
+
}
|
|
229
|
+
throw new TimeoutError("request");
|
|
230
|
+
}
|
|
231
|
+
throw e;
|
|
232
|
+
} finally {
|
|
233
|
+
timer?.();
|
|
234
|
+
this.metrics?.activeRequests.dec({routeId}, 1);
|
|
235
|
+
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
this.opts.signal?.removeEventListener("abort", onParentSignalAbort);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Client errors are deterministic apart from timeouts and rate limits, transport failures and server errors are retried */
|
|
243
|
+
export function isRetryableEngineRestError(e: Error): boolean {
|
|
244
|
+
return (
|
|
245
|
+
(e instanceof EngineRestError && (e.status >= 500 || e.status === 408 || e.status === 429)) ||
|
|
246
|
+
e instanceof TimeoutError ||
|
|
247
|
+
(e instanceof FetchError && e.type !== "input" && e.type !== "aborted")
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseProblemBody(body: Uint8Array): {type: string | null; detail: string | null} {
|
|
252
|
+
const text = new TextDecoder().decode(body);
|
|
253
|
+
try {
|
|
254
|
+
const problem: unknown = JSON.parse(text);
|
|
255
|
+
if (typeof problem === "object" && problem !== null) {
|
|
256
|
+
return {
|
|
257
|
+
type: "type" in problem && typeof problem.type === "string" ? problem.type : null,
|
|
258
|
+
detail:
|
|
259
|
+
("detail" in problem && typeof problem.detail === "string" ? problem.detail : text).slice(
|
|
260
|
+
0,
|
|
261
|
+
MAX_ERROR_DETAIL_LENGTH
|
|
262
|
+
) || null,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
// Legacy endpoints and proxies may return plain text instead of a problem document.
|
|
267
|
+
}
|
|
268
|
+
return {type: null, detail: text.slice(0, MAX_ERROR_DETAIL_LENGTH) || null};
|
|
269
|
+
}
|