@lodestar/beacon-node 1.50.0-dev.fda8006fc3 → 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 +7 -2
- package/lib/api/impl/beacon/blocks/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 +26 -5
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +42 -3
- 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 +13 -23
- package/lib/chain/archiveStore/archiveStore.js.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 +6 -6
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/chain.d.ts +3 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +55 -19
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +3 -1
- 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 +4 -0
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +13 -1
- 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/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.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/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/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/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/http.d.ts +45 -13
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +236 -198
- 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 +1 -1
- 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/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 -0
- 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/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/node/nodejs.d.ts +3 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +2 -1
- 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 +5 -5
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +11 -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 +7 -5
- 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/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 +17 -16
- package/src/api/impl/beacon/blocks/index.ts +10 -3
- package/src/api/impl/beacon/state/index.ts +41 -5
- package/src/api/impl/validator/index.ts +57 -5
- package/src/chain/archiveStore/archiveStore.ts +18 -24
- 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 +7 -6
- package/src/chain/chain.ts +68 -18
- package/src/chain/interface.ts +4 -1
- package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
- package/src/chain/opPools/opPool.ts +1 -31
- package/src/chain/opPools/proposerPreferencesPool.ts +20 -1
- package/src/chain/opPools/utils.ts +31 -0
- package/src/chain/prepareNextSlot.ts +15 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +9 -2
- package/src/chain/produceBlock/produceBlockBody.ts +78 -39
- package/src/chain/seenCache/seenGossipBlockInput.ts +25 -1
- package/src/chain/shufflingCache.ts +84 -22
- 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/http.ts +304 -271
- package/src/execution/engine/index.ts +41 -7
- package/src/execution/engine/interface.ts +1 -1
- package/src/execution/engine/jsonRpcTransport.ts +247 -0
- 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 -74
- package/src/execution/engine/utils.ts +12 -2
- package/src/metrics/metrics/lodestar.ts +23 -0
- 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/node/nodejs.ts +4 -0
- package/src/node/options.ts +10 -1
- package/src/sync/range/batch.ts +14 -19
- package/src/sync/range/chain.ts +9 -7
- 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/metadata.ts +2 -1
- package/src/util/sszBytes.ts +25 -12
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import {Logger} from "@lodestar/logger";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ForkName,
|
|
4
|
+
ForkPostFulu,
|
|
5
|
+
ForkPreFulu,
|
|
6
|
+
ForkSeq,
|
|
7
|
+
SLOTS_PER_EPOCH,
|
|
8
|
+
isForkPostBellatrix,
|
|
9
|
+
isForkPostFulu,
|
|
10
|
+
} from "@lodestar/params";
|
|
3
11
|
import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei} from "@lodestar/types";
|
|
4
12
|
import {BlobAndProof} from "@lodestar/types/deneb";
|
|
5
13
|
import {BlobAndProofV2} from "@lodestar/types/fulu";
|
|
6
|
-
import {
|
|
14
|
+
import {isErrorAborted} from "@lodestar/utils";
|
|
7
15
|
import {Metrics} from "../../metrics/index.js";
|
|
8
16
|
import {EPOCHS_PER_BATCH} from "../../sync/constants.js";
|
|
9
17
|
import {getLodestarClientVersion} from "../../util/metadata.js";
|
|
18
|
+
import {isQueueErrorAborted} from "../../util/queue/errors.js";
|
|
10
19
|
import {JobItemQueue} from "../../util/queue/index.js";
|
|
11
20
|
import {
|
|
12
|
-
ClientCode,
|
|
13
21
|
ClientVersion,
|
|
14
22
|
ExecutePayloadResponse,
|
|
15
23
|
ExecutionEngineState,
|
|
@@ -19,32 +27,14 @@ import {
|
|
|
19
27
|
PayloadId,
|
|
20
28
|
VersionedHashes,
|
|
21
29
|
} from "./interface.js";
|
|
22
|
-
import {
|
|
23
|
-
ErrorJsonRpcResponse,
|
|
24
|
-
HttpRpcError,
|
|
25
|
-
IJsonRpcHttpClient,
|
|
26
|
-
JsonRpcHttpClientEvent,
|
|
27
|
-
ReqOpts,
|
|
28
|
-
} from "./jsonRpcHttpClient.js";
|
|
30
|
+
import {ErrorJsonRpcResponse, HttpRpcError, JsonRpcHttpClientEvent} from "./jsonRpcHttpClient.js";
|
|
29
31
|
import {PayloadIdCache} from "./payloadIdCache.js";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
deserializeBlobAndProofs,
|
|
37
|
-
deserializeBlobAndProofsV2,
|
|
38
|
-
deserializeBlobAndProofsV2IntoBytes,
|
|
39
|
-
deserializeExecutionPayloadBodyV2,
|
|
40
|
-
parseExecutionPayload,
|
|
41
|
-
serializeBeaconBlockRoot,
|
|
42
|
-
serializeExecutionPayload,
|
|
43
|
-
serializeExecutionRequests,
|
|
44
|
-
serializePayloadAttributes,
|
|
45
|
-
serializeVersionedHashes,
|
|
46
|
-
} from "./types.js";
|
|
47
|
-
import {bytesToData, getExecutionEngineState} from "./utils.js";
|
|
32
|
+
import {EngineRestError, EngineRestResponseError, isRetryableEngineRestError} from "./restHttpClient.js";
|
|
33
|
+
import {EngineCapabilities, RestEngineTransport} from "./restTransport.js";
|
|
34
|
+
import {executionForkName} from "./sszTypes.js";
|
|
35
|
+
import {IEngineTransport, PayloadStatusResult} from "./transport.js";
|
|
36
|
+
import {ExecutionPayloadBodyV2, serializePayloadAttributes} from "./types.js";
|
|
37
|
+
import {getExecutionEngineState} from "./utils.js";
|
|
48
38
|
|
|
49
39
|
export type ExecutionEngineModules = {
|
|
50
40
|
signal: AbortSignal;
|
|
@@ -52,11 +42,21 @@ export type ExecutionEngineModules = {
|
|
|
52
42
|
logger: Logger;
|
|
53
43
|
};
|
|
54
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Engine API transport selection
|
|
47
|
+
* - `auto`: negotiate REST with one execution URL; use JSON-RPC for multiple URLs or unadvertised features
|
|
48
|
+
* - `ssz`: always use the REST API with SSZ encoding
|
|
49
|
+
* - `json-rpc`: always use the legacy JSON-RPC API
|
|
50
|
+
*/
|
|
51
|
+
export type EngineApiMode = "auto" | "ssz" | "json-rpc";
|
|
52
|
+
export const engineApiModes: EngineApiMode[] = ["auto", "ssz", "json-rpc"];
|
|
53
|
+
|
|
55
54
|
export type ExecutionEngineHttpOpts = {
|
|
56
55
|
urls: string[];
|
|
57
56
|
retries: number;
|
|
58
57
|
retryDelay: number;
|
|
59
58
|
timeout?: number;
|
|
59
|
+
engineApi?: EngineApiMode;
|
|
60
60
|
/**
|
|
61
61
|
* 256 bit jwt secret in hex format without the leading 0x. If provided, the execution engine
|
|
62
62
|
* rpc requests will be bundled by an authorization header having a fresh jwt token on each
|
|
@@ -92,38 +92,37 @@ export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = {
|
|
|
92
92
|
retries: 2,
|
|
93
93
|
retryDelay: 2000,
|
|
94
94
|
timeout: 12000,
|
|
95
|
+
engineApi: "auto",
|
|
95
96
|
};
|
|
96
97
|
|
|
98
|
+
export type ExecutionEngineTransports = {
|
|
99
|
+
jsonRpc: IEngineTransport;
|
|
100
|
+
rest?: RestEngineTransport;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
type RestSupport =
|
|
104
|
+
| {state: "pending"; error?: Error}
|
|
105
|
+
| {state: "unsupported"}
|
|
106
|
+
| {state: "supported"; capabilities: EngineCapabilities};
|
|
107
|
+
|
|
97
108
|
/**
|
|
98
109
|
* Size for the serializing queue for fcUs and new payloads, the max length could be equal to
|
|
99
110
|
* EPOCHS_PER_BATCH * 2 in case new payloads are also not awaited serially
|
|
100
111
|
*/
|
|
101
112
|
const QUEUE_MAX_LENGTH = EPOCHS_PER_BATCH * SLOTS_PER_EPOCH * 2;
|
|
102
113
|
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
* Clients must support at least 128 versionedHashes, so we avoid sending more
|
|
106
|
-
* https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-3
|
|
107
|
-
*/
|
|
108
|
-
const MAX_VERSIONED_HASHES = 128;
|
|
109
|
-
|
|
110
|
-
// Define static options once to prevent extra allocations
|
|
111
|
-
const notifyNewPayloadOpts: ReqOpts = {routeId: "notifyNewPayload"};
|
|
112
|
-
const forkchoiceUpdatedV1Opts: ReqOpts = {routeId: "forkchoiceUpdated"};
|
|
113
|
-
const getPayloadOpts: ReqOpts = {routeId: "getPayload"};
|
|
114
|
-
const getPayloadBodiesByHashOpts: ReqOpts = {routeId: "getPayloadBodiesByHash"};
|
|
115
|
-
const getBlobsV1Opts: ReqOpts = {routeId: "getBlobsV1"};
|
|
116
|
-
const getBlobsV2Opts: ReqOpts = {routeId: "getBlobsV2"};
|
|
117
|
-
const getClientVersionOpts: ReqOpts = {routeId: "getClientVersion"};
|
|
114
|
+
/** Minimum delay between capabilities probes while the execution client is unreachable */
|
|
115
|
+
const REST_PROBE_RETRY_MS = 12_000;
|
|
118
116
|
|
|
119
117
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* -
|
|
123
|
-
* - Error codes improvement proposal
|
|
118
|
+
* Drives the execution client over the engine API, either the legacy `engine_*` JSON-RPC methods
|
|
119
|
+
* or the REST API with SSZ encoded bodies.
|
|
120
|
+
* https://github.com/ethereum/execution-apis/tree/main/src/engine
|
|
124
121
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
122
|
+
* In `auto` mode a capabilities response from a server without the REST API, a 4xx other than
|
|
123
|
+
* 401/403, selects JSON-RPC until the EL reconnects. Transient discovery failures use JSON-RPC while
|
|
124
|
+
* awaiting another probe; authentication failures fail visibly, malformed capabilities fall back to
|
|
125
|
+
* JSON-RPC with a warning. Forks and blob revisions the EL does not advertise also use JSON-RPC.
|
|
127
126
|
*/
|
|
128
127
|
export class ExecutionEngineHttp implements IExecutionEngine {
|
|
129
128
|
private logger: Logger;
|
|
@@ -147,42 +146,55 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
147
146
|
* the order of new payloads and fcUs is pretty important to EL, this queue will serialize the calls in the
|
|
148
147
|
* order with which we make them.
|
|
149
148
|
*/
|
|
150
|
-
private readonly
|
|
149
|
+
private readonly queue: JobItemQueue<[() => Promise<unknown>], unknown>;
|
|
151
150
|
|
|
152
|
-
private
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
private readonly jsonRpc: IEngineTransport;
|
|
152
|
+
private readonly rest: RestEngineTransport | null;
|
|
153
|
+
private readonly engineApi: EngineApiMode;
|
|
154
|
+
private restSupport: RestSupport = {state: "pending"};
|
|
155
|
+
private restProbe: Promise<RestSupport> | null = null;
|
|
156
|
+
private lastRestProbeMs = Number.NEGATIVE_INFINITY;
|
|
157
|
+
private readonly loggedRestFallbacks = new Set<ForkName | "v1" | "v2">();
|
|
158
158
|
|
|
159
159
|
constructor(
|
|
160
|
-
|
|
160
|
+
{jsonRpc, rest}: ExecutionEngineTransports,
|
|
161
161
|
{metrics, signal, logger}: ExecutionEngineModules,
|
|
162
162
|
private readonly opts?: ExecutionEngineHttpOpts
|
|
163
163
|
) {
|
|
164
|
-
this.
|
|
165
|
-
|
|
164
|
+
this.queue = new JobItemQueue<[() => Promise<unknown>], unknown>(
|
|
165
|
+
(job) => job(),
|
|
166
166
|
{maxLength: QUEUE_MAX_LENGTH, maxConcurrency: 1, noYieldIfOneItem: true, signal},
|
|
167
167
|
metrics?.engineHttpProcessorQueue
|
|
168
168
|
);
|
|
169
169
|
this.logger = logger;
|
|
170
170
|
this.metrics = metrics ?? null;
|
|
171
|
+
this.jsonRpc = jsonRpc;
|
|
172
|
+
this.rest = rest ?? null;
|
|
173
|
+
this.engineApi = opts?.engineApi ?? "auto";
|
|
171
174
|
|
|
172
|
-
this.
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
+
if (this.engineApi === "ssz" && this.rest === null) {
|
|
176
|
+
throw Error("REST transport is required for engineApi=ssz");
|
|
177
|
+
}
|
|
175
178
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// This statement should only be called first time receiving response after startup
|
|
180
|
-
this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
|
|
181
|
-
this.logger.debug("Unable to get execution client version", {}, e);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
this.updateEngineState(getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state}));
|
|
179
|
+
// REST errors are handled in withTransport after compatibility fallback.
|
|
180
|
+
this.jsonRpc.emitter.on(JsonRpcHttpClientEvent.ERROR, ({error}) => {
|
|
181
|
+
this.updateEngineState(getExecutionEngineState({payloadError: error, oldState: this.state}), error);
|
|
185
182
|
});
|
|
183
|
+
|
|
184
|
+
for (const transport of [this.jsonRpc, this.rest]) {
|
|
185
|
+
transport?.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => {
|
|
186
|
+
if (this.clientVersion === undefined) {
|
|
187
|
+
this.clientVersion = null;
|
|
188
|
+
// This statement should only be called first time receiving response after startup
|
|
189
|
+
this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
|
|
190
|
+
this.logger.debug("Unable to get execution client version", {}, e);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
this.updateEngineState(
|
|
194
|
+
getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state})
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
/**
|
|
@@ -214,20 +226,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
214
226
|
parentBlockRoot?: Root,
|
|
215
227
|
executionRequests?: ExecutionRequests
|
|
216
228
|
): Promise<ExecutePayloadResponse> {
|
|
217
|
-
|
|
218
|
-
ForkSeq[fork] >= ForkSeq.gloas
|
|
219
|
-
? "engine_newPayloadV5"
|
|
220
|
-
: ForkSeq[fork] >= ForkSeq.electra
|
|
221
|
-
? "engine_newPayloadV4"
|
|
222
|
-
: ForkSeq[fork] >= ForkSeq.deneb
|
|
223
|
-
? "engine_newPayloadV3"
|
|
224
|
-
: ForkSeq[fork] >= ForkSeq.capella
|
|
225
|
-
? "engine_newPayloadV2"
|
|
226
|
-
: "engine_newPayloadV1";
|
|
227
|
-
|
|
228
|
-
const serializedExecutionPayload = serializeExecutionPayload(fork, executionPayload);
|
|
229
|
-
|
|
230
|
-
let engineRequest: EngineRequest;
|
|
229
|
+
// Validate before queueing, argument errors must not be reported as an unavailable execution client
|
|
231
230
|
if (ForkSeq[fork] >= ForkSeq.deneb) {
|
|
232
231
|
if (versionedHashes === undefined) {
|
|
233
232
|
throw Error(`versionedHashes required in notifyNewPayload for fork=${fork}`);
|
|
@@ -235,49 +234,31 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
235
234
|
if (parentBlockRoot === undefined) {
|
|
236
235
|
throw Error(`parentBlockRoot required in notifyNewPayload for fork=${fork}`);
|
|
237
236
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const parentBeaconBlockRoot = serializeBeaconBlockRoot(parentBlockRoot);
|
|
241
|
-
|
|
242
|
-
if (ForkSeq[fork] >= ForkSeq.electra) {
|
|
243
|
-
if (executionRequests === undefined) {
|
|
244
|
-
throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
|
|
245
|
-
}
|
|
246
|
-
const serializedExecutionRequests = serializeExecutionRequests(fork, executionRequests);
|
|
247
|
-
engineRequest = {
|
|
248
|
-
method: ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4",
|
|
249
|
-
params: [
|
|
250
|
-
serializedExecutionPayload,
|
|
251
|
-
serializedVersionedHashes,
|
|
252
|
-
parentBeaconBlockRoot,
|
|
253
|
-
serializedExecutionRequests,
|
|
254
|
-
],
|
|
255
|
-
methodOpts: notifyNewPayloadOpts,
|
|
256
|
-
};
|
|
257
|
-
} else {
|
|
258
|
-
engineRequest = {
|
|
259
|
-
method: "engine_newPayloadV3",
|
|
260
|
-
params: [serializedExecutionPayload, serializedVersionedHashes, parentBeaconBlockRoot],
|
|
261
|
-
methodOpts: notifyNewPayloadOpts,
|
|
262
|
-
};
|
|
237
|
+
if (ForkSeq[fork] >= ForkSeq.electra && executionRequests === undefined) {
|
|
238
|
+
throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
|
|
263
239
|
}
|
|
264
|
-
} else {
|
|
265
|
-
const method = ForkSeq[fork] >= ForkSeq.capella ? "engine_newPayloadV2" : "engine_newPayloadV1";
|
|
266
|
-
engineRequest = {
|
|
267
|
-
method,
|
|
268
|
-
params: [serializedExecutionPayload],
|
|
269
|
-
methodOpts: notifyNewPayloadOpts,
|
|
270
|
-
};
|
|
271
240
|
}
|
|
272
241
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
})
|
|
242
|
+
let result: PayloadStatusResult;
|
|
243
|
+
try {
|
|
244
|
+
result = await this.enqueue(() =>
|
|
245
|
+
this.withTransport(fork, undefined, (transport) =>
|
|
246
|
+
transport.newPayload(fork, executionPayload, versionedHashes, parentBlockRoot, executionRequests)
|
|
247
|
+
)
|
|
248
|
+
);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
const status = isEngineResponseError(e as Error)
|
|
251
|
+
? ExecutionPayloadStatus.ELERROR
|
|
252
|
+
: ExecutionPayloadStatus.UNAVAILABLE;
|
|
253
|
+
// Only newPayload treats an unreachable execution client, including a timeout, as offline
|
|
254
|
+
const newState =
|
|
255
|
+
status === ExecutionPayloadStatus.UNAVAILABLE && !isErrorAborted(e) && !isQueueErrorAborted(e)
|
|
256
|
+
? getExecutionEngineState({payloadStatus: status, oldState: this.state})
|
|
257
|
+
: getExecutionEngineState({payloadError: e, oldState: this.state});
|
|
258
|
+
this.updateEngineState(newState, e as Error);
|
|
259
|
+
return {status, latestValidHash: null, validationError: (e as Error).message};
|
|
260
|
+
}
|
|
261
|
+
const {status, latestValidHash, validationError} = result;
|
|
281
262
|
|
|
282
263
|
this.updateEngineState(getExecutionEngineState({payloadStatus: status, oldState: this.state}));
|
|
283
264
|
|
|
@@ -346,32 +327,14 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
346
327
|
finalizedBlockHash: RootHex,
|
|
347
328
|
payloadAttributes?: PayloadAttributes
|
|
348
329
|
): Promise<PayloadId | null> {
|
|
349
|
-
// Once on capella, should this need to be permanently switched to v2 when payload attrs
|
|
350
|
-
// not provided
|
|
351
|
-
const method =
|
|
352
|
-
ForkSeq[fork] >= ForkSeq.gloas
|
|
353
|
-
? "engine_forkchoiceUpdatedV4"
|
|
354
|
-
: ForkSeq[fork] >= ForkSeq.deneb
|
|
355
|
-
? "engine_forkchoiceUpdatedV3"
|
|
356
|
-
: ForkSeq[fork] >= ForkSeq.capella
|
|
357
|
-
? "engine_forkchoiceUpdatedV2"
|
|
358
|
-
: "engine_forkchoiceUpdatedV1";
|
|
359
|
-
const payloadAttributesRpc = payloadAttributes ? serializePayloadAttributes(payloadAttributes) : undefined;
|
|
360
|
-
// If we are just fcUing and not asking execution for payload, retry is not required
|
|
361
|
-
// and we can move on, as the next fcU will be issued soon on the new slot
|
|
362
|
-
const fcUReqOpts =
|
|
363
|
-
payloadAttributes !== undefined ? forkchoiceUpdatedV1Opts : {...forkchoiceUpdatedV1Opts, retries: 0};
|
|
364
|
-
|
|
365
|
-
const request = this.rpcFetchQueue.push({
|
|
366
|
-
method,
|
|
367
|
-
params: [{headBlockHash, safeBlockHash, finalizedBlockHash}, payloadAttributesRpc],
|
|
368
|
-
methodOpts: fcUReqOpts,
|
|
369
|
-
}) as Promise<EngineApiRpcReturnTypes[typeof method]>;
|
|
370
|
-
|
|
371
330
|
const {
|
|
372
|
-
payloadStatus: {status,
|
|
331
|
+
payloadStatus: {status, validationError},
|
|
373
332
|
payloadId,
|
|
374
|
-
} = await
|
|
333
|
+
} = await this.enqueue(() =>
|
|
334
|
+
this.withTransport(fork, undefined, (transport) =>
|
|
335
|
+
transport.forkchoiceUpdated(fork, headBlockHash, safeBlockHash, finalizedBlockHash, payloadAttributes)
|
|
336
|
+
)
|
|
337
|
+
);
|
|
375
338
|
|
|
376
339
|
this.updateEngineState(getExecutionEngineState({payloadStatus: status, oldState: this.state}));
|
|
377
340
|
this.metrics?.engineNotifyForkchoiceUpdateResult.inc({result: status});
|
|
@@ -379,15 +342,16 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
379
342
|
switch (status) {
|
|
380
343
|
case ExecutionPayloadStatus.VALID:
|
|
381
344
|
// if payloadAttributes are provided, a valid payloadId is expected
|
|
382
|
-
if (
|
|
383
|
-
if (
|
|
345
|
+
if (payloadAttributes) {
|
|
346
|
+
if (payloadId === null) {
|
|
384
347
|
throw Error(`Received invalid payloadId=${payloadId}`);
|
|
385
348
|
}
|
|
386
349
|
|
|
350
|
+
const payloadAttributesRpc = serializePayloadAttributes(payloadAttributes);
|
|
387
351
|
this.payloadIdCache.add({headBlockHash, finalizedBlockHash, ...payloadAttributesRpc}, payloadId);
|
|
388
352
|
void this.prunePayloadIdCache();
|
|
389
353
|
}
|
|
390
|
-
return payloadId
|
|
354
|
+
return payloadId;
|
|
391
355
|
|
|
392
356
|
case ExecutionPayloadStatus.SYNCING:
|
|
393
357
|
// Throw error on syncing if requested to produce a block, else silently ignore
|
|
@@ -425,40 +389,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
425
389
|
executionRequests?: ExecutionRequests;
|
|
426
390
|
shouldOverrideBuilder?: boolean;
|
|
427
391
|
}> {
|
|
428
|
-
|
|
429
|
-
switch (fork) {
|
|
430
|
-
case ForkName.phase0:
|
|
431
|
-
case ForkName.altair:
|
|
432
|
-
case ForkName.bellatrix:
|
|
433
|
-
method = "engine_getPayloadV1";
|
|
434
|
-
break;
|
|
435
|
-
case ForkName.capella:
|
|
436
|
-
method = "engine_getPayloadV2";
|
|
437
|
-
break;
|
|
438
|
-
case ForkName.deneb:
|
|
439
|
-
method = "engine_getPayloadV3";
|
|
440
|
-
break;
|
|
441
|
-
case ForkName.electra:
|
|
442
|
-
method = "engine_getPayloadV4";
|
|
443
|
-
break;
|
|
444
|
-
case ForkName.fulu:
|
|
445
|
-
method = "engine_getPayloadV5";
|
|
446
|
-
break;
|
|
447
|
-
default:
|
|
448
|
-
method = "engine_getPayloadV6";
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
const payloadResponse = await this.rpc.fetchWithRetries<
|
|
452
|
-
EngineApiRpcReturnTypes[typeof method],
|
|
453
|
-
EngineApiRpcParamTypes[typeof method]
|
|
454
|
-
>(
|
|
455
|
-
{
|
|
456
|
-
method,
|
|
457
|
-
params: [payloadId],
|
|
458
|
-
},
|
|
459
|
-
getPayloadOpts
|
|
460
|
-
);
|
|
461
|
-
return parseExecutionPayload(fork, payloadResponse);
|
|
392
|
+
return this.withTransport(fork, undefined, (transport) => transport.getPayload(fork, payloadId));
|
|
462
393
|
}
|
|
463
394
|
|
|
464
395
|
async prunePayloadIdCache(): Promise<void> {
|
|
@@ -466,13 +397,9 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
466
397
|
}
|
|
467
398
|
|
|
468
399
|
async getPayloadBodiesByHashV2(blockHashes: RootHex[]): Promise<(ExecutionPayloadBodyV2 | null)[]> {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
EngineApiRpcReturnTypes[typeof method],
|
|
473
|
-
EngineApiRpcParamTypes[typeof method]
|
|
474
|
-
>({method, params: [blockHashes]}, getPayloadBodiesByHashOpts);
|
|
475
|
-
return response.map(deserializeExecutionPayloadBodyV2);
|
|
400
|
+
return this.withTransport(ForkName.gloas, undefined, (transport) =>
|
|
401
|
+
transport.getPayloadBodiesByHashV2(blockHashes)
|
|
402
|
+
);
|
|
476
403
|
}
|
|
477
404
|
|
|
478
405
|
async getBlobs(
|
|
@@ -490,95 +417,168 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
490
417
|
versionedHashes: VersionedHashes,
|
|
491
418
|
buffers?: Uint8Array[]
|
|
492
419
|
): Promise<BlobAndProofV2[] | (BlobAndProof | null)[] | null> {
|
|
493
|
-
assertReqSizeLimit(versionedHashes.length, MAX_VERSIONED_HASHES);
|
|
494
|
-
const versionedHashesHex = versionedHashes.map(bytesToData);
|
|
495
420
|
if (isForkPostFulu(fork)) {
|
|
496
|
-
return
|
|
421
|
+
return this.withTransport(undefined, "v2", (transport) => transport.getBlobsV2(versionedHashes, buffers));
|
|
497
422
|
}
|
|
498
|
-
return
|
|
423
|
+
return this.withTransport(undefined, "v1", (transport) => transport.getBlobsV1(versionedHashes));
|
|
499
424
|
}
|
|
500
425
|
|
|
501
|
-
private
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
EngineApiRpcParamTypes["engine_getBlobsV1"]
|
|
505
|
-
>(
|
|
506
|
-
{
|
|
507
|
-
method: "engine_getBlobsV1",
|
|
508
|
-
params: [versionedHashesHex],
|
|
509
|
-
},
|
|
510
|
-
getBlobsV1Opts
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
const invalidLength = response.length !== versionedHashesHex.length;
|
|
426
|
+
private enqueue<T>(job: () => Promise<T>): Promise<T> {
|
|
427
|
+
return this.queue.push(job) as Promise<T>;
|
|
428
|
+
}
|
|
514
429
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Run a call on the selected transport. An execution client that advertises a fork in its
|
|
432
|
+
* capabilities but rejects it with `unsupported-fork` is not spec compliant, keep the node
|
|
433
|
+
* functional by serving that fork over JSON-RPC from then on.
|
|
434
|
+
*/
|
|
435
|
+
private async withTransport<T>(
|
|
436
|
+
fork: ForkName | undefined,
|
|
437
|
+
blobsRevision: "v1" | "v2" | undefined,
|
|
438
|
+
fn: (transport: IEngineTransport) => Promise<T>
|
|
439
|
+
): Promise<T> {
|
|
440
|
+
const transport = await this.getTransport(fork, blobsRevision);
|
|
441
|
+
this.metrics?.engineApiRequests.inc({transport: transport === this.rest ? "ssz" : "json-rpc"});
|
|
442
|
+
try {
|
|
443
|
+
return await fn(transport);
|
|
444
|
+
} catch (e) {
|
|
445
|
+
if (fork !== undefined && transport === this.rest && this.engineApi === "auto" && isUnsupportedForkError(e)) {
|
|
446
|
+
this.disableRestForFork(fork, e);
|
|
447
|
+
this.metrics?.engineApiRequests.inc({transport: "json-rpc"});
|
|
448
|
+
return fn(this.jsonRpc);
|
|
449
|
+
}
|
|
450
|
+
if (transport === this.rest) {
|
|
451
|
+
this.updateEngineState(getExecutionEngineState({payloadError: e, oldState: this.state}), e as Error);
|
|
452
|
+
}
|
|
453
|
+
throw e;
|
|
519
454
|
}
|
|
455
|
+
}
|
|
520
456
|
|
|
521
|
-
|
|
457
|
+
private disableRestForFork(fork: ForkName, e: EngineRestError): void {
|
|
458
|
+
const executionFork = isForkPostBellatrix(fork) ? executionForkName[fork] : null;
|
|
459
|
+
if (this.restSupport.state === "supported" && executionFork !== null) {
|
|
460
|
+
this.restSupport.capabilities.supportedForks.delete(executionFork);
|
|
461
|
+
}
|
|
462
|
+
if (!this.loggedRestFallbacks.has(fork)) {
|
|
463
|
+
this.loggedRestFallbacks.add(fork);
|
|
464
|
+
this.logger.debug("REST engine API rejected an advertised fork, using JSON-RPC until reconnect", {
|
|
465
|
+
fork,
|
|
466
|
+
executionFork,
|
|
467
|
+
status: e.status,
|
|
468
|
+
type: e.type,
|
|
469
|
+
detail: e.detail,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
522
472
|
}
|
|
523
473
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
474
|
+
/**
|
|
475
|
+
* Pick the transport for a call. Fork-scoped calls only go over REST if the execution client
|
|
476
|
+
* advertises that fork, blob requests only if it serves the needed `/blobs/vN` revision.
|
|
477
|
+
*/
|
|
478
|
+
private async getTransport(fork?: ForkName, blobsRevision?: "v1" | "v2"): Promise<IEngineTransport> {
|
|
479
|
+
if (this.rest === null || this.engineApi === "json-rpc") {
|
|
480
|
+
return this.jsonRpc;
|
|
481
|
+
}
|
|
482
|
+
const restSupport = await this.probeRestSupport(this.rest);
|
|
483
|
+
if (restSupport.state === "pending" && restSupport.error) {
|
|
484
|
+
this.updateEngineState(
|
|
485
|
+
getExecutionEngineState({payloadError: restSupport.error, oldState: this.state}),
|
|
486
|
+
restSupport.error
|
|
487
|
+
);
|
|
488
|
+
throw restSupport.error;
|
|
489
|
+
}
|
|
490
|
+
if (this.engineApi === "ssz") {
|
|
491
|
+
return this.rest;
|
|
492
|
+
}
|
|
493
|
+
if (restSupport.state !== "supported") {
|
|
494
|
+
return this.jsonRpc;
|
|
495
|
+
}
|
|
530
496
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
497
|
+
if (fork !== undefined) {
|
|
498
|
+
const executionFork = isForkPostBellatrix(fork) ? executionForkName[fork] : null;
|
|
499
|
+
if (executionFork === null || !restSupport.capabilities.supportedForks.has(executionFork)) {
|
|
500
|
+
if (!this.loggedRestFallbacks.has(fork)) {
|
|
501
|
+
this.loggedRestFallbacks.add(fork);
|
|
502
|
+
this.logger.debug("Using JSON-RPC for a fork not advertised by the REST engine API", {fork, executionFork});
|
|
534
503
|
}
|
|
504
|
+
return this.jsonRpc;
|
|
535
505
|
}
|
|
536
506
|
}
|
|
507
|
+
if (blobsRevision !== undefined && !restSupport.capabilities.blobsRevisions.has(blobsRevision)) {
|
|
508
|
+
if (!this.loggedRestFallbacks.has(blobsRevision)) {
|
|
509
|
+
this.loggedRestFallbacks.add(blobsRevision);
|
|
510
|
+
this.logger.debug("Using JSON-RPC for a blob revision not advertised by the REST engine API", {blobsRevision});
|
|
511
|
+
}
|
|
512
|
+
return this.jsonRpc;
|
|
513
|
+
}
|
|
537
514
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
EngineApiRpcParamTypes["engine_getBlobsV2"]
|
|
541
|
-
>(
|
|
542
|
-
{
|
|
543
|
-
method: "engine_getBlobsV2",
|
|
544
|
-
params: [versionedHashesHex],
|
|
545
|
-
},
|
|
546
|
-
getBlobsV2Opts
|
|
547
|
-
);
|
|
548
|
-
|
|
549
|
-
// engine_getBlobsV2 does not return partial responses. It returns null if any blob is not found
|
|
550
|
-
const invalidLength = !!response && response.length !== versionedHashesHex.length;
|
|
515
|
+
return this.rest;
|
|
516
|
+
}
|
|
551
517
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
this.
|
|
555
|
-
throw Error(error);
|
|
518
|
+
private probeRestSupport(rest: RestEngineTransport): Promise<RestSupport> {
|
|
519
|
+
if (this.restSupport.state !== "pending") {
|
|
520
|
+
return Promise.resolve(this.restSupport);
|
|
556
521
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return null;
|
|
522
|
+
if (this.restProbe !== null) {
|
|
523
|
+
return this.restProbe;
|
|
560
524
|
}
|
|
561
|
-
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
return response.map((data, i) => deserializeBlobAndProofsV2IntoBytes(data, buffers[i]));
|
|
525
|
+
// Transport errors keep the probe pending, do not retry on every call while the EL is down
|
|
526
|
+
if (Date.now() - this.lastRestProbeMs < REST_PROBE_RETRY_MS) {
|
|
527
|
+
return Promise.resolve(this.restSupport);
|
|
565
528
|
}
|
|
566
529
|
|
|
567
|
-
|
|
530
|
+
this.restProbe = rest
|
|
531
|
+
.getCapabilities()
|
|
532
|
+
.then(
|
|
533
|
+
(capabilities): RestSupport => {
|
|
534
|
+
this.restSupport = {state: "supported", capabilities};
|
|
535
|
+
this.loggedRestFallbacks.clear();
|
|
536
|
+
this.logger.debug("Discovered REST engine API capabilities", {
|
|
537
|
+
supportedForks: Array.from(capabilities.supportedForks).join(","),
|
|
538
|
+
blobsRevisions: Array.from(capabilities.blobsRevisions).join(","),
|
|
539
|
+
...capabilities.limits,
|
|
540
|
+
});
|
|
541
|
+
return this.restSupport;
|
|
542
|
+
},
|
|
543
|
+
(e: Error): RestSupport => {
|
|
544
|
+
if (this.engineApi === "auto" && isRestApiAbsent(e)) {
|
|
545
|
+
this.restSupport = {state: "unsupported"};
|
|
546
|
+
this.logger.debug("Execution client does not support engine API over REST, using JSON-RPC", {
|
|
547
|
+
status: e.status,
|
|
548
|
+
type: e.type ?? "unknown",
|
|
549
|
+
});
|
|
550
|
+
} else if (this.engineApi === "auto" && e instanceof EngineRestResponseError) {
|
|
551
|
+
// The REST API answered but cannot be trusted, auto mode must keep the node on a working transport
|
|
552
|
+
this.restSupport = {state: "unsupported"};
|
|
553
|
+
this.logger.warn("Invalid engine API capabilities, using JSON-RPC until reconnect", {}, e);
|
|
554
|
+
} else {
|
|
555
|
+
const transient = isRetryableEngineRestError(e);
|
|
556
|
+
this.restSupport = {state: "pending", error: this.engineApi === "auto" && transient ? undefined : e};
|
|
557
|
+
this.logger.debug(
|
|
558
|
+
"Unable to probe engine API capabilities",
|
|
559
|
+
{
|
|
560
|
+
engineApi: this.engineApi,
|
|
561
|
+
fallback: this.restSupport.error ? "none" : "json-rpc",
|
|
562
|
+
retryAfterMs: REST_PROBE_RETRY_MS,
|
|
563
|
+
},
|
|
564
|
+
e
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
return this.restSupport;
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
.finally(() => {
|
|
571
|
+
this.restProbe = null;
|
|
572
|
+
this.lastRestProbeMs = Date.now();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
return this.restProbe;
|
|
568
576
|
}
|
|
569
577
|
|
|
570
578
|
private async getClientVersion(clientVersion: ClientVersion): Promise<ClientVersion[]> {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
EngineApiRpcReturnTypes[typeof method],
|
|
575
|
-
EngineApiRpcParamTypes[typeof method]
|
|
576
|
-
>({method, params: [{...clientVersion, commit: `0x${clientVersion.commit}`}]}, getClientVersionOpts);
|
|
577
|
-
|
|
578
|
-
const clientVersions = response.map((cv) => {
|
|
579
|
-
const code = cv.code in ClientCode ? ClientCode[cv.code as keyof typeof ClientCode] : ClientCode.XX;
|
|
580
|
-
return {code, name: cv.name, version: cv.version, commit: strip0xPrefix(cv.commit)};
|
|
581
|
-
});
|
|
579
|
+
const clientVersions = await this.withTransport(undefined, undefined, (transport) =>
|
|
580
|
+
transport.getClientVersion(clientVersion)
|
|
581
|
+
);
|
|
582
582
|
|
|
583
583
|
if (clientVersions.length === 0) {
|
|
584
584
|
throw Error("Received empty client versions array");
|
|
@@ -590,7 +590,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
590
590
|
return clientVersions;
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
private updateEngineState(newState: ExecutionEngineState): void {
|
|
593
|
+
private updateEngineState(newState: ExecutionEngineState, error?: Error): void {
|
|
594
594
|
const oldState = this.state;
|
|
595
595
|
|
|
596
596
|
if (oldState === newState) return;
|
|
@@ -598,22 +598,33 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
598
598
|
switch (newState) {
|
|
599
599
|
case ExecutionEngineState.ONLINE:
|
|
600
600
|
this.logger.info("Execution client became online", {oldState, newState});
|
|
601
|
+
if (oldState === ExecutionEngineState.AUTH_FAILED) {
|
|
602
|
+
this.restSupport = {state: "pending"};
|
|
603
|
+
this.lastRestProbeMs = Number.NEGATIVE_INFINITY;
|
|
604
|
+
}
|
|
601
605
|
this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
|
|
602
606
|
this.logger.debug("Unable to get execution client version", {}, e);
|
|
603
607
|
this.clientVersion = null;
|
|
604
608
|
});
|
|
605
609
|
break;
|
|
606
610
|
case ExecutionEngineState.OFFLINE:
|
|
607
|
-
|
|
611
|
+
// Reprobe before choosing a transport for the next call after a disconnect.
|
|
612
|
+
this.restSupport = {state: "pending"};
|
|
613
|
+
this.lastRestProbeMs = Number.NEGATIVE_INFINITY;
|
|
614
|
+
this.logger.error("Execution client went offline", {oldState, newState}, error);
|
|
608
615
|
break;
|
|
609
616
|
case ExecutionEngineState.SYNCED:
|
|
610
617
|
this.logger.info("Execution client is synced", {oldState, newState});
|
|
611
618
|
break;
|
|
612
619
|
case ExecutionEngineState.SYNCING:
|
|
613
|
-
this.logger.warn(
|
|
620
|
+
this.logger.warn(
|
|
621
|
+
error ? "Execution client request failed" : "Execution client is syncing",
|
|
622
|
+
{oldState, newState},
|
|
623
|
+
error
|
|
624
|
+
);
|
|
614
625
|
break;
|
|
615
626
|
case ExecutionEngineState.AUTH_FAILED:
|
|
616
|
-
this.logger.error("Execution client authentication failed", {oldState, newState});
|
|
627
|
+
this.logger.error("Execution client authentication failed", {oldState, newState}, error);
|
|
617
628
|
break;
|
|
618
629
|
}
|
|
619
630
|
|
|
@@ -621,10 +632,32 @@ export class ExecutionEngineHttp implements IExecutionEngine {
|
|
|
621
632
|
}
|
|
622
633
|
}
|
|
623
634
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
635
|
+
/** The execution client answered, as opposed to being unreachable */
|
|
636
|
+
function isEngineResponseError(e: Error): boolean {
|
|
637
|
+
return (
|
|
638
|
+
e instanceof HttpRpcError ||
|
|
639
|
+
e instanceof ErrorJsonRpcResponse ||
|
|
640
|
+
e instanceof EngineRestError ||
|
|
641
|
+
e instanceof EngineRestResponseError
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Legacy JSON-RPC servers and proxies reject the capabilities probe in different ways, a 404 or
|
|
647
|
+
* another client error such as 405. Neither serves the REST API. Authentication failures and
|
|
648
|
+
* transient client errors say nothing about REST support.
|
|
649
|
+
*/
|
|
650
|
+
function isRestApiAbsent(e: Error): e is EngineRestError {
|
|
651
|
+
return (
|
|
652
|
+
e instanceof EngineRestError &&
|
|
653
|
+
e.status >= 400 &&
|
|
654
|
+
e.status < 500 &&
|
|
655
|
+
e.status !== 401 &&
|
|
656
|
+
e.status !== 403 &&
|
|
657
|
+
!isRetryableEngineRestError(e)
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function isUnsupportedForkError(e: unknown): e is EngineRestError {
|
|
662
|
+
return e instanceof EngineRestError && e.status === 400 && e.type === "/engine-api/errors/unsupported-fork";
|
|
663
|
+
}
|