@lodestar/prover 1.35.0-dev.c88a6ed255 → 1.35.0-dev.c9deb9b59f
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/bin/lodestar-prover.js +3 -0
- package/lib/browser/index.d.ts.map +1 -0
- package/lib/cli/applyPreset.d.ts.map +1 -0
- package/lib/cli/cli.d.ts +3 -3
- package/lib/cli/cli.d.ts.map +1 -0
- package/lib/cli/cli.js +1 -1
- package/lib/cli/cli.js.map +1 -1
- package/lib/cli/cmds/index.d.ts.map +1 -0
- package/lib/cli/cmds/index.js.map +1 -1
- package/lib/cli/cmds/start/handler.d.ts.map +1 -0
- package/lib/cli/cmds/start/index.d.ts.map +1 -0
- package/lib/cli/cmds/start/options.d.ts.map +1 -0
- package/lib/cli/index.d.ts.map +1 -0
- package/lib/cli/index.js.map +1 -1
- package/lib/cli/options.d.ts.map +1 -0
- package/lib/constants.d.ts.map +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/interfaces.d.ts.map +1 -0
- package/lib/proof_provider/index.d.ts.map +1 -0
- package/lib/proof_provider/ordered_map.d.ts.map +1 -0
- package/lib/proof_provider/ordered_map.js +2 -0
- package/lib/proof_provider/ordered_map.js.map +1 -1
- package/lib/proof_provider/payload_store.d.ts.map +1 -0
- package/lib/proof_provider/payload_store.js +12 -11
- package/lib/proof_provider/payload_store.js.map +1 -1
- package/lib/proof_provider/proof_provider.d.ts.map +1 -0
- package/lib/proof_provider/proof_provider.js +9 -0
- package/lib/proof_provider/proof_provider.js.map +1 -1
- package/lib/provider_types/eip1193_provider_type.d.ts.map +1 -0
- package/lib/provider_types/ethers_provider_type.d.ts.map +1 -0
- package/lib/provider_types/legacy_provider_type.d.ts.map +1 -0
- package/lib/provider_types/web3_js_provider_type.d.ts.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils/assertion.d.ts.map +1 -0
- package/lib/utils/consensus.d.ts.map +1 -0
- package/lib/utils/conversion.d.ts.map +1 -0
- package/lib/utils/errors.d.ts.map +1 -0
- package/lib/utils/evm.d.ts.map +1 -0
- package/lib/utils/execution.d.ts.map +1 -0
- package/lib/utils/file.d.ts.map +1 -0
- package/lib/utils/file.js.map +1 -1
- package/lib/utils/gitData/gitDataPath.d.ts.map +1 -0
- package/lib/utils/gitData/index.d.ts.map +1 -0
- package/lib/utils/gitData/index.js.map +1 -1
- package/lib/utils/gitData/writeGitData.d.ts.map +1 -0
- package/lib/utils/json_rpc.d.ts.map +1 -0
- package/lib/utils/process.d.ts.map +1 -0
- package/lib/utils/req_resp.d.ts.map +1 -0
- package/lib/utils/rpc_provider.d.ts.map +1 -0
- package/lib/utils/rpc_provider.js +3 -1
- package/lib/utils/rpc_provider.js.map +1 -1
- package/lib/utils/validation.d.ts.map +1 -0
- package/lib/utils/validation.js.map +1 -1
- package/lib/utils/verification.d.ts.map +1 -0
- package/lib/utils/version.d.ts.map +1 -0
- package/lib/verified_requests/eth_call.d.ts.map +1 -0
- package/lib/verified_requests/eth_estimateGas.d.ts.map +1 -0
- package/lib/verified_requests/eth_getBalance.d.ts.map +1 -0
- package/lib/verified_requests/eth_getBlockByHash.d.ts.map +1 -0
- package/lib/verified_requests/eth_getBlockByNumber.d.ts.map +1 -0
- package/lib/verified_requests/eth_getCode.d.ts.map +1 -0
- package/lib/verified_requests/eth_getTransactionCount.d.ts.map +1 -0
- package/lib/web3_provider.d.ts.map +1 -0
- package/lib/web3_provider_inspector.d.ts.map +1 -0
- package/lib/web3_provider_inspector.js +2 -1
- package/lib/web3_provider_inspector.js.map +1 -1
- package/lib/web3_proxy.d.ts.map +1 -0
- package/lib/web3_proxy.js +1 -1
- package/lib/web3_proxy.js.map +1 -1
- package/package.json +16 -15
- package/src/browser/index.ts +3 -0
- package/src/cli/applyPreset.ts +83 -0
- package/src/cli/cli.ts +58 -0
- package/src/cli/cmds/index.ts +7 -0
- package/src/cli/cmds/start/handler.ts +27 -0
- package/src/cli/cmds/start/index.ts +18 -0
- package/src/cli/cmds/start/options.ts +85 -0
- package/src/cli/index.ts +30 -0
- package/src/cli/options.ts +73 -0
- package/src/constants.ts +6 -0
- package/src/index.ts +5 -0
- package/src/interfaces.ts +90 -0
- package/src/proof_provider/index.ts +1 -0
- package/src/proof_provider/ordered_map.ts +25 -0
- package/src/proof_provider/payload_store.ts +223 -0
- package/src/proof_provider/proof_provider.ts +210 -0
- package/src/provider_types/eip1193_provider_type.ts +32 -0
- package/src/provider_types/ethers_provider_type.ts +44 -0
- package/src/provider_types/legacy_provider_type.ts +123 -0
- package/src/provider_types/web3_js_provider_type.ts +35 -0
- package/src/types.ts +163 -0
- package/src/utils/assertion.ts +11 -0
- package/src/utils/consensus.ts +122 -0
- package/src/utils/conversion.ts +107 -0
- package/src/utils/errors.ts +4 -0
- package/src/utils/evm.ts +284 -0
- package/src/utils/execution.ts +76 -0
- package/src/utils/file.ts +51 -0
- package/src/utils/gitData/gitDataPath.ts +48 -0
- package/src/utils/gitData/index.ts +70 -0
- package/src/utils/gitData/writeGitData.ts +10 -0
- package/src/utils/json_rpc.ts +170 -0
- package/src/utils/process.ts +111 -0
- package/src/utils/req_resp.ts +34 -0
- package/src/utils/rpc_provider.ts +117 -0
- package/src/utils/validation.ts +161 -0
- package/src/utils/verification.ts +112 -0
- package/src/utils/version.ts +74 -0
- package/src/verified_requests/eth_call.ts +50 -0
- package/src/verified_requests/eth_estimateGas.ts +49 -0
- package/src/verified_requests/eth_getBalance.ts +26 -0
- package/src/verified_requests/eth_getBlockByHash.ts +24 -0
- package/src/verified_requests/eth_getBlockByNumber.ts +25 -0
- package/src/verified_requests/eth_getCode.ts +50 -0
- package/src/verified_requests/eth_getTransactionCount.ts +26 -0
- package/src/web3_provider.ts +58 -0
- package/src/web3_provider_inspector.ts +88 -0
- package/src/web3_proxy.ts +175 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {Common, CustomChain, Hardfork} from "@ethereumjs/common";
|
|
2
|
+
import {ELApiParams, ELApiReturn, ELTransaction} from "../types.js";
|
|
3
|
+
import {isValidResponse} from "./json_rpc.js";
|
|
4
|
+
import {ELRpcProvider} from "./rpc_provider.js";
|
|
5
|
+
import {isBlockNumber, isPresent} from "./validation.js";
|
|
6
|
+
|
|
7
|
+
export type Optional<T, K extends keyof T> = Omit<T, K> & {[P in keyof T]?: T[P] | undefined};
|
|
8
|
+
|
|
9
|
+
export async function getELCode(
|
|
10
|
+
rpc: ELRpcProvider,
|
|
11
|
+
args: ELApiParams["eth_getCode"]
|
|
12
|
+
): Promise<ELApiReturn["eth_getCode"]> {
|
|
13
|
+
const codeResult = await rpc.request("eth_getCode", args, {raiseError: false});
|
|
14
|
+
|
|
15
|
+
if (!isValidResponse(codeResult)) {
|
|
16
|
+
throw new Error(`Can not find code for address=${args[0]}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return codeResult.result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function getELProof(
|
|
23
|
+
rpc: ELRpcProvider,
|
|
24
|
+
args: ELApiParams["eth_getProof"]
|
|
25
|
+
): Promise<ELApiReturn["eth_getProof"]> {
|
|
26
|
+
const proof = await rpc.request("eth_getProof", args, {raiseError: false});
|
|
27
|
+
if (!isValidResponse(proof)) {
|
|
28
|
+
throw new Error(`Can not find proof for address=${args[0]}`);
|
|
29
|
+
}
|
|
30
|
+
return proof.result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getELBlock(
|
|
34
|
+
rpc: ELRpcProvider,
|
|
35
|
+
args: ELApiParams["eth_getBlockByNumber"]
|
|
36
|
+
): Promise<ELApiReturn["eth_getBlockByNumber"]> {
|
|
37
|
+
const block = await rpc.request(isBlockNumber(args[0]) ? "eth_getBlockByNumber" : "eth_getBlockByHash", args, {
|
|
38
|
+
raiseError: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!isValidResponse(block)) {
|
|
42
|
+
throw new Error(`Can not find block. id=${args[0]}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return block.result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getChainCommon(network: string): Common {
|
|
49
|
+
switch (network) {
|
|
50
|
+
case "mainnet":
|
|
51
|
+
case "sepolia":
|
|
52
|
+
case "holesky":
|
|
53
|
+
case "ephemery":
|
|
54
|
+
// TODO: Not sure how to detect the fork during runtime
|
|
55
|
+
return new Common({chain: network, hardfork: Hardfork.Shanghai});
|
|
56
|
+
case "minimal":
|
|
57
|
+
// TODO: Not sure how to detect the fork during runtime
|
|
58
|
+
return new Common({chain: "mainnet", hardfork: Hardfork.Shanghai});
|
|
59
|
+
case "gnosis":
|
|
60
|
+
return new Common({chain: CustomChain.xDaiChain});
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`Non supported network "${network}"`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getTxType(tx: ELTransaction): number {
|
|
67
|
+
if (isPresent(tx.maxFeePerGas) || isPresent(tx.maxPriorityFeePerGas)) {
|
|
68
|
+
return 2;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (isPresent(tx.accessList)) {
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
|
|
5
|
+
const {load, FAILSAFE_SCHEMA, Type} = yaml;
|
|
6
|
+
|
|
7
|
+
enum FileFormat {
|
|
8
|
+
json = "json",
|
|
9
|
+
yaml = "yaml",
|
|
10
|
+
yml = "yml",
|
|
11
|
+
toml = "toml",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const yamlSchema = FAILSAFE_SCHEMA.extend({
|
|
15
|
+
implicit: [
|
|
16
|
+
new Type("tag:yaml.org,2002:str", {
|
|
17
|
+
kind: "scalar",
|
|
18
|
+
construct: function construct(data) {
|
|
19
|
+
return data !== null ? data : "";
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse file contents as Json.
|
|
27
|
+
*/
|
|
28
|
+
function parse<T>(contents: string, fileFormat: FileFormat): T {
|
|
29
|
+
switch (fileFormat) {
|
|
30
|
+
case FileFormat.json:
|
|
31
|
+
return JSON.parse(contents) as T;
|
|
32
|
+
case FileFormat.yaml:
|
|
33
|
+
case FileFormat.yml:
|
|
34
|
+
return load(contents, {schema: yamlSchema}) as T;
|
|
35
|
+
default:
|
|
36
|
+
return contents as unknown as T;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read a JSON serializable object from a file
|
|
42
|
+
*
|
|
43
|
+
* Parse either from json, yaml, or toml
|
|
44
|
+
* Optional acceptedFormats object can be passed which can be an array of accepted formats, in future can be extended to include parseFn for the accepted formats
|
|
45
|
+
*/
|
|
46
|
+
export function readFile<T>(filepath: string, acceptedFormats?: string[]): T {
|
|
47
|
+
const fileFormat = path.extname(filepath).substr(1);
|
|
48
|
+
if (acceptedFormats && !acceptedFormats.includes(fileFormat)) throw new Error(`UnsupportedFileFormat: ${filepath}`);
|
|
49
|
+
const contents = fs.readFileSync(filepath, "utf-8");
|
|
50
|
+
return parse(contents, fileFormat as FileFormat);
|
|
51
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {fileURLToPath} from "node:url";
|
|
4
|
+
|
|
5
|
+
// Global variable __dirname no longer available in ES6 modules.
|
|
6
|
+
// Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
// Persist git data and distribute through NPM so CLI consumers can know exactly
|
|
10
|
+
// at what commit was this src build. This is used in the metrics and to log initially.
|
|
11
|
+
//
|
|
12
|
+
// - For NPM release (stable): Only the version is persisted. Once must then track the version's tag
|
|
13
|
+
// in Github to resolve that version to a specific commit. While this is okay, git-data.json gives
|
|
14
|
+
// a gurantee of the exact commit at build time.
|
|
15
|
+
//
|
|
16
|
+
// - For NPM release (dev): canary commits include the commit, so this feature is not really
|
|
17
|
+
// necessary. However, it's more cumbersome to have conditional logic on stable / dev.
|
|
18
|
+
//
|
|
19
|
+
// - For build from source: .git folder is available in the context of the built code, so it can extract
|
|
20
|
+
// branch and commit directly without the need for .git-data.json.
|
|
21
|
+
//
|
|
22
|
+
// - For build from source dockerized: This feature is required to know the branch and commit, since
|
|
23
|
+
// git data is not persisted past the build. However, .dockerignore prevents .git folder from being
|
|
24
|
+
// copied into the container's context, so .git-data.json can't be generated.
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WARNING!! If you change this path make sure to update:
|
|
28
|
+
* - 'packages/cli/package.json' -> .files -> `".git-data.json"`
|
|
29
|
+
*/
|
|
30
|
+
export const gitDataPath = path.resolve(__dirname, "../../../.git-data.json");
|
|
31
|
+
|
|
32
|
+
/** Git data type used to construct version information string and persistence. */
|
|
33
|
+
export type GitData = {
|
|
34
|
+
/** "developer-feature" */
|
|
35
|
+
branch: string;
|
|
36
|
+
/** "80c248bb392f512cc115d95059e22239a17bbd7d" */
|
|
37
|
+
commit: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Writes a persistent git data file. */
|
|
41
|
+
export function writeGitDataFile(gitData: GitData): void {
|
|
42
|
+
fs.writeFileSync(gitDataPath, JSON.stringify(gitData, null, 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reads the persistent git data file. */
|
|
46
|
+
export function readGitDataFile(): GitData {
|
|
47
|
+
return JSON.parse(fs.readFileSync(gitDataPath, "utf8")) as GitData;
|
|
48
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {execSync} from "node:child_process";
|
|
2
|
+
// This file is created in the build step and is distributed through NPM
|
|
3
|
+
// MUST be in sync with `-/gitDataPath.ts` and `package.json` files.
|
|
4
|
+
import {GitData, readGitDataFile} from "./gitDataPath.js";
|
|
5
|
+
|
|
6
|
+
/** Reads git data from a persisted file or local git data at build time. */
|
|
7
|
+
export function readAndGetGitData(): GitData {
|
|
8
|
+
try {
|
|
9
|
+
// Gets git data containing current branch and commit info from persistent file.
|
|
10
|
+
let persistedGitData: Partial<GitData>;
|
|
11
|
+
try {
|
|
12
|
+
persistedGitData = readGitDataFile();
|
|
13
|
+
} catch (_e) {
|
|
14
|
+
persistedGitData = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const currentGitData = getGitData();
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
// If the CLI is run from source, prioritze current git data
|
|
21
|
+
// over `.git-data.json` file, which might be stale here.
|
|
22
|
+
branch:
|
|
23
|
+
currentGitData.branch && currentGitData.branch.length > 0
|
|
24
|
+
? currentGitData.branch
|
|
25
|
+
: (persistedGitData.branch ?? ""),
|
|
26
|
+
commit:
|
|
27
|
+
currentGitData.commit && currentGitData.commit.length > 0
|
|
28
|
+
? currentGitData.commit
|
|
29
|
+
: (persistedGitData.commit ?? ""),
|
|
30
|
+
};
|
|
31
|
+
} catch (_e) {
|
|
32
|
+
return {
|
|
33
|
+
branch: "",
|
|
34
|
+
commit: "",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Gets git data containing current branch and commit info from CLI. */
|
|
40
|
+
export function getGitData(): GitData {
|
|
41
|
+
return {
|
|
42
|
+
branch: process.env.GIT_BRANCH ?? getBranch(),
|
|
43
|
+
commit: process.env.GIT_COMMIT ?? getCommit(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Tries to get branch from git CLI. */
|
|
48
|
+
function getBranch(): string {
|
|
49
|
+
try {
|
|
50
|
+
return shellSilent("git rev-parse --abbrev-ref HEAD");
|
|
51
|
+
} catch (_e) {
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Tries to get commit from git from git CLI. */
|
|
57
|
+
function getCommit(): string {
|
|
58
|
+
try {
|
|
59
|
+
return shellSilent("git rev-parse --verify HEAD");
|
|
60
|
+
} catch (_e) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Silent shell that won't pollute stdout, or stderr */
|
|
66
|
+
function shellSilent(cmd: string): string {
|
|
67
|
+
return execSync(cmd, {stdio: ["ignore", "pipe", "ignore"]})
|
|
68
|
+
.toString()
|
|
69
|
+
.trim();
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// For RATIONALE of this file, check packages/cli/src/util/gitData/gitDataPath.ts
|
|
4
|
+
// Persist exact commit in NPM distributions for easier tracking of the build
|
|
5
|
+
|
|
6
|
+
import {writeGitDataFile} from "./gitDataPath.js";
|
|
7
|
+
import {getGitData} from "./index.js";
|
|
8
|
+
|
|
9
|
+
// Script to write the git data file (json) used by the build procedures to persist git data.
|
|
10
|
+
writeGitDataFile(getGitData());
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {Logger} from "@lodestar/logger";
|
|
2
|
+
import {VERIFICATION_FAILED_RESPONSE_CODE} from "../constants.js";
|
|
3
|
+
import {
|
|
4
|
+
JsonRpcBatchRequest,
|
|
5
|
+
JsonRpcBatchResponse,
|
|
6
|
+
JsonRpcErrorPayload,
|
|
7
|
+
JsonRpcNotificationPayload,
|
|
8
|
+
JsonRpcRequest,
|
|
9
|
+
JsonRpcRequestOrBatch,
|
|
10
|
+
JsonRpcRequestPayload,
|
|
11
|
+
JsonRpcResponse,
|
|
12
|
+
JsonRpcResponseOrBatch,
|
|
13
|
+
JsonRpcResponseWithErrorPayload,
|
|
14
|
+
JsonRpcResponseWithResultPayload,
|
|
15
|
+
} from "../types.js";
|
|
16
|
+
import {isNullish} from "./validation.js";
|
|
17
|
+
|
|
18
|
+
export function getResponseForRequest<P, R, E = unknown>(
|
|
19
|
+
payload: JsonRpcRequest<P>,
|
|
20
|
+
res?: R,
|
|
21
|
+
error?: JsonRpcErrorPayload<E>
|
|
22
|
+
): JsonRpcResponse<R, E> {
|
|
23
|
+
// If it's a notification
|
|
24
|
+
if (!isRequest(payload)) {
|
|
25
|
+
throw new Error("Cannot generate response for notification");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isNullish(res) && isNullish(error)) {
|
|
29
|
+
return {
|
|
30
|
+
jsonrpc: payload.jsonrpc,
|
|
31
|
+
id: payload.id,
|
|
32
|
+
result: res,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!isNullish(error)) {
|
|
37
|
+
return {
|
|
38
|
+
jsonrpc: payload.jsonrpc,
|
|
39
|
+
id: payload.id,
|
|
40
|
+
error,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error("Either result or error must be defined.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getVerificationFailedMessage(method: string): string {
|
|
48
|
+
return `verification for '${method}' request failed.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isVerificationFailedError<P>(payload: JsonRpcResponseWithErrorPayload<P>): boolean {
|
|
52
|
+
return !isValidResponsePayload(payload) && payload.error.code === VERIFICATION_FAILED_RESPONSE_CODE;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getErrorResponseForRequestWithFailedVerification<P, D = unknown>(
|
|
56
|
+
payload: JsonRpcRequest<P>,
|
|
57
|
+
message: string,
|
|
58
|
+
data?: D
|
|
59
|
+
): JsonRpcResponseWithErrorPayload<D> {
|
|
60
|
+
return isNullish(data)
|
|
61
|
+
? (getResponseForRequest(payload, undefined, {
|
|
62
|
+
code: VERIFICATION_FAILED_RESPONSE_CODE,
|
|
63
|
+
message,
|
|
64
|
+
}) as JsonRpcResponseWithErrorPayload<D>)
|
|
65
|
+
: (getResponseForRequest(payload, undefined, {
|
|
66
|
+
code: VERIFICATION_FAILED_RESPONSE_CODE,
|
|
67
|
+
message,
|
|
68
|
+
data,
|
|
69
|
+
}) as JsonRpcResponseWithErrorPayload<D>);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isValidResponsePayload<R, E>(
|
|
73
|
+
response: JsonRpcResponse<R, E> | undefined
|
|
74
|
+
): response is JsonRpcResponseWithResultPayload<R> {
|
|
75
|
+
return !isNullish(response) && isNullish(response.error);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isValidResponse<R, E>(
|
|
79
|
+
response: JsonRpcResponseOrBatch<R, E> | undefined
|
|
80
|
+
): response is JsonRpcResponseWithResultPayload<R> | JsonRpcResponseWithResultPayload<R>[] {
|
|
81
|
+
return Array.isArray(response) ? response.every(isValidResponsePayload) : isValidResponsePayload(response);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function isValidBatchResponse<R, E>(
|
|
85
|
+
payload: JsonRpcBatchRequest,
|
|
86
|
+
response: JsonRpcBatchResponse<R, E>
|
|
87
|
+
): response is JsonRpcBatchResponse<R, E> | JsonRpcResponseWithResultPayload<R>[] {
|
|
88
|
+
for (const [index, req] of payload.entries()) {
|
|
89
|
+
if (isRequest(req) && (response[index].id !== req.id || !isValidResponse(response[index]))) return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function mergeBatchReqResp(
|
|
95
|
+
payload: JsonRpcBatchRequest,
|
|
96
|
+
response: JsonRpcBatchResponse
|
|
97
|
+
): {request: JsonRpcRequest; response: JsonRpcResponse}[] {
|
|
98
|
+
const result = [];
|
|
99
|
+
for (const [index, req] of payload.entries()) {
|
|
100
|
+
if (isRequest(req)) {
|
|
101
|
+
// Some providers return raw json-rpc response, some return only result
|
|
102
|
+
// we need to just merge the result back based on the provider
|
|
103
|
+
result.push({request: req, response: response[index]});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isNotification<P>(payload: JsonRpcRequest<P>): payload is JsonRpcNotificationPayload<P> {
|
|
110
|
+
return !("id" in payload);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function isRequest<P>(payload: JsonRpcRequest<P>): payload is JsonRpcRequestPayload<P> {
|
|
114
|
+
return "id" in payload;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isBatchRequest<P>(payload: JsonRpcRequestOrBatch<P>): payload is JsonRpcBatchRequest<P> {
|
|
118
|
+
return Array.isArray(payload);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isBatchResponse<R>(response: JsonRpcResponseOrBatch<R>): response is JsonRpcBatchResponse<R> {
|
|
122
|
+
return Array.isArray(response);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function logRequestPayload(payload: JsonRpcRequest, logger: Logger): void {
|
|
126
|
+
logger.debug("PR -> EL", {
|
|
127
|
+
id: isRequest(payload) ? payload.id : "notification",
|
|
128
|
+
method: payload.method,
|
|
129
|
+
params: JSON.stringify(payload.params),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function logRequest(payload: JsonRpcRequestOrBatch | undefined | null, logger: Logger): void {
|
|
134
|
+
if (payload === undefined || payload === null) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const p of isBatchRequest(payload) ? payload : [payload]) {
|
|
139
|
+
logRequestPayload(p, logger);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function logResponsePayload(response: JsonRpcResponse | null | undefined, logger: Logger): void {
|
|
144
|
+
if (response === undefined || response === null) {
|
|
145
|
+
logger.debug("PR <- EL (empty response)");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isValidResponse(response)) {
|
|
150
|
+
logger.debug("PR <- EL", {
|
|
151
|
+
id: response.id,
|
|
152
|
+
result: JSON.stringify(response.result),
|
|
153
|
+
});
|
|
154
|
+
} else {
|
|
155
|
+
logger.debug("PR <- E:", {
|
|
156
|
+
id: response.id,
|
|
157
|
+
error: JSON.stringify(response.error),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function logResponse(response: JsonRpcResponseOrBatch | undefined, logger: Logger): void {
|
|
163
|
+
if (response === undefined || response === null) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const p of isBatchResponse(response) ? response : [response]) {
|
|
168
|
+
logResponsePayload(p, logger);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {Logger} from "@lodestar/logger";
|
|
2
|
+
import {ELVerifiedRequestHandler} from "../interfaces.js";
|
|
3
|
+
import {ProofProvider} from "../proof_provider/proof_provider.js";
|
|
4
|
+
import {JsonRpcBatchRequest, JsonRpcBatchResponse, JsonRpcRequestOrBatch, JsonRpcResponseOrBatch} from "../types.js";
|
|
5
|
+
import {eth_call} from "../verified_requests/eth_call.js";
|
|
6
|
+
import {eth_estimateGas} from "../verified_requests/eth_estimateGas.js";
|
|
7
|
+
import {eth_getBalance} from "../verified_requests/eth_getBalance.js";
|
|
8
|
+
import {eth_getBlockByHash} from "../verified_requests/eth_getBlockByHash.js";
|
|
9
|
+
import {eth_getBlockByNumber} from "../verified_requests/eth_getBlockByNumber.js";
|
|
10
|
+
import {eth_getCode} from "../verified_requests/eth_getCode.js";
|
|
11
|
+
import {eth_getTransactionCount} from "../verified_requests/eth_getTransactionCount.js";
|
|
12
|
+
import {getResponseForRequest, isBatchRequest, isRequest} from "./json_rpc.js";
|
|
13
|
+
import {ELRpcProvider} from "./rpc_provider.js";
|
|
14
|
+
import {isNullish} from "./validation.js";
|
|
15
|
+
|
|
16
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
17
|
+
export const verifiableMethodHandlers: Record<string, ELVerifiedRequestHandler<any, any>> = {
|
|
18
|
+
eth_getBalance: eth_getBalance,
|
|
19
|
+
eth_getTransactionCount: eth_getTransactionCount,
|
|
20
|
+
eth_getBlockByHash: eth_getBlockByHash,
|
|
21
|
+
eth_getBlockByNumber: eth_getBlockByNumber,
|
|
22
|
+
eth_getCode: eth_getCode,
|
|
23
|
+
eth_call: eth_call,
|
|
24
|
+
eth_estimateGas: eth_estimateGas,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const verifiableMethods = Object.keys(verifiableMethodHandlers);
|
|
28
|
+
export const alwaysAllowedMethods = ["eth_subscribe", "eth_unsubscribe", "eth_getProof"];
|
|
29
|
+
|
|
30
|
+
export function splitRequestsInChunks(
|
|
31
|
+
payload: JsonRpcRequestOrBatch,
|
|
32
|
+
unverifiedWhitelist?: string[]
|
|
33
|
+
): {
|
|
34
|
+
verifiable: JsonRpcBatchRequest;
|
|
35
|
+
nonVerifiable: JsonRpcBatchRequest;
|
|
36
|
+
blocked: JsonRpcBatchRequest;
|
|
37
|
+
} {
|
|
38
|
+
const verifiable: JsonRpcBatchRequest = [];
|
|
39
|
+
const nonVerifiable: JsonRpcBatchRequest = [];
|
|
40
|
+
const blocked: JsonRpcBatchRequest = [];
|
|
41
|
+
|
|
42
|
+
for (const pay of isBatchRequest(payload) ? payload : [payload]) {
|
|
43
|
+
if (isRequest(pay) && verifiableMethods.includes(pay.method)) {
|
|
44
|
+
verifiable.push(pay);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If unverifiedWhitelist is not set that implies all methods are allowed
|
|
49
|
+
if ((isRequest(pay) && isNullish(unverifiedWhitelist)) || unverifiedWhitelist?.includes(pay.method)) {
|
|
50
|
+
nonVerifiable.push(pay);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (alwaysAllowedMethods.includes(pay.method)) {
|
|
55
|
+
nonVerifiable.push(pay);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
blocked.push(pay);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {verifiable, nonVerifiable, blocked};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function processAndVerifyRequest({
|
|
66
|
+
payload,
|
|
67
|
+
rpc,
|
|
68
|
+
proofProvider,
|
|
69
|
+
logger,
|
|
70
|
+
}: {
|
|
71
|
+
payload: JsonRpcRequestOrBatch;
|
|
72
|
+
rpc: ELRpcProvider;
|
|
73
|
+
proofProvider: ProofProvider;
|
|
74
|
+
logger: Logger;
|
|
75
|
+
}): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
76
|
+
await proofProvider.waitToBeReady();
|
|
77
|
+
|
|
78
|
+
const {verifiable, nonVerifiable, blocked} = splitRequestsInChunks(payload, proofProvider.opts.unverifiedWhitelist);
|
|
79
|
+
const verifiedResponses: JsonRpcBatchResponse = [];
|
|
80
|
+
const nonVerifiedResponses: JsonRpcBatchResponse = [];
|
|
81
|
+
const blockedResponses: JsonRpcBatchResponse = [];
|
|
82
|
+
|
|
83
|
+
for (const request of verifiable) {
|
|
84
|
+
logger.debug("Processing verifiable request", {
|
|
85
|
+
method: request.method,
|
|
86
|
+
params: JSON.stringify(request.params),
|
|
87
|
+
});
|
|
88
|
+
const verifiableRequestHandler = verifiableMethodHandlers[request.method];
|
|
89
|
+
const response = await verifiableRequestHandler({payload: request, rpc, proofProvider, logger});
|
|
90
|
+
verifiedResponses.push(response);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (nonVerifiable.length > 0) {
|
|
94
|
+
logger.warn("Forwarding non-verifiable requests to EL provider.", {count: nonVerifiable.length});
|
|
95
|
+
const response = await rpc.batchRequest(nonVerifiable, {raiseError: false});
|
|
96
|
+
nonVerifiedResponses.push(...response.map((r) => r.response));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const request of blocked) {
|
|
100
|
+
blockedResponses.push(
|
|
101
|
+
getResponseForRequest(request, undefined, {message: `Method "${request.method}" not allowed.`})
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const responses = [...verifiedResponses, ...nonVerifiedResponses, ...blockedResponses];
|
|
106
|
+
|
|
107
|
+
if (responses.length === 1) {
|
|
108
|
+
return responses[0];
|
|
109
|
+
}
|
|
110
|
+
return responses;
|
|
111
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import {JsonRpcRequestPayload, JsonRpcResponse} from "../types.js";
|
|
3
|
+
|
|
4
|
+
export const fetchRequestPayload = async (req: http.IncomingMessage): Promise<JsonRpcRequestPayload> => {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
let body = "";
|
|
7
|
+
req.on("data", (chunk) => {
|
|
8
|
+
body += chunk;
|
|
9
|
+
});
|
|
10
|
+
req.on("end", () => {
|
|
11
|
+
try {
|
|
12
|
+
resolve(JSON.parse(body) as JsonRpcRequestPayload);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
reject(err);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const fetchResponseBody = async (res: http.IncomingMessage): Promise<JsonRpcResponse> => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
let body = "";
|
|
23
|
+
res.on("data", (chunk) => {
|
|
24
|
+
body += chunk;
|
|
25
|
+
});
|
|
26
|
+
res.on("end", () => {
|
|
27
|
+
try {
|
|
28
|
+
resolve(JSON.parse(body) as JsonRpcResponse);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
reject(err);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {Logger} from "@lodestar/logger";
|
|
2
|
+
import {ZERO_ADDRESS} from "../constants.js";
|
|
3
|
+
import {ELRequestHandler} from "../interfaces.js";
|
|
4
|
+
import {
|
|
5
|
+
ELApi,
|
|
6
|
+
ELApiParams,
|
|
7
|
+
ELApiReturn,
|
|
8
|
+
JsonRpcBatchRequest,
|
|
9
|
+
JsonRpcBatchResponse,
|
|
10
|
+
JsonRpcRequest,
|
|
11
|
+
JsonRpcResponse,
|
|
12
|
+
JsonRpcResponseWithResultPayload,
|
|
13
|
+
} from "../types.js";
|
|
14
|
+
import {
|
|
15
|
+
isRequest,
|
|
16
|
+
isValidBatchResponse,
|
|
17
|
+
isValidResponse,
|
|
18
|
+
logRequest,
|
|
19
|
+
logResponse,
|
|
20
|
+
mergeBatchReqResp,
|
|
21
|
+
} from "./json_rpc.js";
|
|
22
|
+
import {isNullish} from "./validation.js";
|
|
23
|
+
|
|
24
|
+
export type Optional<T, K extends keyof T> = Omit<T, K> & {[P in keyof T]?: T[P] | undefined};
|
|
25
|
+
|
|
26
|
+
export class ELRpcProvider {
|
|
27
|
+
private handler: ELRequestHandler;
|
|
28
|
+
private logger: Logger;
|
|
29
|
+
|
|
30
|
+
private requestId = 0;
|
|
31
|
+
|
|
32
|
+
constructor(handler: ELRequestHandler, logger: Logger) {
|
|
33
|
+
this.handler = handler;
|
|
34
|
+
this.logger = logger;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Request the EL RPC Provider
|
|
39
|
+
*
|
|
40
|
+
* @template K
|
|
41
|
+
* @template E
|
|
42
|
+
* @param {K} method - RPC Method
|
|
43
|
+
* @param {ELApiParams[K]} params - RPC Params
|
|
44
|
+
* @param {{raiseError?: E}} [opts]
|
|
45
|
+
* @return {*} {Promise<E extends false ? JsonRpcResponse<ELApiReturn[K]> : JsonRpcResponseWithResultPayload<ELApiReturn[K]>>}
|
|
46
|
+
* @memberof ELRpc
|
|
47
|
+
*/
|
|
48
|
+
async request<K extends keyof ELApi, E extends boolean>(
|
|
49
|
+
method: K,
|
|
50
|
+
params: ELApiParams[K],
|
|
51
|
+
opts?: {raiseError?: E}
|
|
52
|
+
): Promise<E extends false ? JsonRpcResponse<ELApiReturn[K]> : JsonRpcResponseWithResultPayload<ELApiReturn[K]>> {
|
|
53
|
+
const {raiseError} = opts ?? {raiseError: true};
|
|
54
|
+
|
|
55
|
+
const payload: JsonRpcRequest = {jsonrpc: "2.0", method, params, id: this.getRequestId()};
|
|
56
|
+
logRequest(payload, this.logger);
|
|
57
|
+
|
|
58
|
+
const response = await this.handler(payload);
|
|
59
|
+
logResponse(response, this.logger);
|
|
60
|
+
|
|
61
|
+
if (raiseError && !isValidResponse(response)) {
|
|
62
|
+
throw new Error(`Invalid response from RPC. method=${method} params=${JSON.stringify(params)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return response as JsonRpcResponseWithResultPayload<ELApiReturn[K]>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async batchRequest<E extends boolean>(
|
|
69
|
+
input: JsonRpcBatchRequest,
|
|
70
|
+
opts: {raiseError: E}
|
|
71
|
+
): Promise<
|
|
72
|
+
E extends false
|
|
73
|
+
? {request: JsonRpcRequest; response: JsonRpcResponse}[]
|
|
74
|
+
: {request: JsonRpcRequest; response: JsonRpcResponseWithResultPayload<unknown>}[]
|
|
75
|
+
> {
|
|
76
|
+
const payloads: JsonRpcBatchRequest = [];
|
|
77
|
+
|
|
78
|
+
for (const req of input) {
|
|
79
|
+
if (isRequest(req) && isNullish(req.id)) {
|
|
80
|
+
payloads.push({jsonrpc: "2.0", method: req.method, params: req.params, id: this.getRequestId()});
|
|
81
|
+
} else {
|
|
82
|
+
payloads.push(req);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logRequest(payloads, this.logger);
|
|
87
|
+
const response = await this.handler(payloads);
|
|
88
|
+
logResponse(response, this.logger);
|
|
89
|
+
|
|
90
|
+
if (isNullish(response)) {
|
|
91
|
+
throw new Error("Invalid empty response from server.");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (opts.raiseError && !isValidBatchResponse(payloads, response as JsonRpcBatchResponse)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Invalid response from RPC. payload=${JSON.stringify(payloads)} response=${JSON.stringify(response)}}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return mergeBatchReqResp(payloads, response as JsonRpcBatchResponse) as E extends false
|
|
101
|
+
? {request: JsonRpcRequest; response: JsonRpcResponse}[]
|
|
102
|
+
: {request: JsonRpcRequest; response: JsonRpcResponseWithResultPayload<unknown>}[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async verifyCompatibility(): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
await this.request("eth_getProof", [ZERO_ADDRESS, [], "latest"], {raiseError: true});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
this.logger.error("Execution compatibility failed.", undefined, err as Error);
|
|
110
|
+
throw new Error("RPC does not support 'eth_getProof', which is required for the prover to work properly.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getRequestId(): string {
|
|
115
|
+
return (++this.requestId).toString();
|
|
116
|
+
}
|
|
117
|
+
}
|