@lodestar/prover 1.35.0-dev.e18102ed8c → 1.35.0-dev.f2a741bbe4

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.
Files changed (98) hide show
  1. package/bin/lodestar-prover.js +3 -0
  2. package/lib/browser/index.d.ts.map +1 -0
  3. package/lib/cli/applyPreset.d.ts.map +1 -0
  4. package/lib/cli/cli.d.ts.map +1 -0
  5. package/lib/cli/cmds/index.d.ts.map +1 -0
  6. package/lib/cli/cmds/start/handler.d.ts.map +1 -0
  7. package/lib/cli/cmds/start/index.d.ts.map +1 -0
  8. package/lib/cli/cmds/start/options.d.ts.map +1 -0
  9. package/lib/cli/index.d.ts.map +1 -0
  10. package/lib/cli/options.d.ts.map +1 -0
  11. package/lib/constants.d.ts.map +1 -0
  12. package/lib/index.d.ts.map +1 -0
  13. package/lib/interfaces.d.ts.map +1 -0
  14. package/lib/proof_provider/index.d.ts.map +1 -0
  15. package/lib/proof_provider/ordered_map.d.ts.map +1 -0
  16. package/lib/proof_provider/payload_store.d.ts.map +1 -0
  17. package/lib/proof_provider/proof_provider.d.ts.map +1 -0
  18. package/lib/provider_types/eip1193_provider_type.d.ts.map +1 -0
  19. package/lib/provider_types/ethers_provider_type.d.ts.map +1 -0
  20. package/lib/provider_types/legacy_provider_type.d.ts.map +1 -0
  21. package/lib/provider_types/web3_js_provider_type.d.ts.map +1 -0
  22. package/lib/types.d.ts.map +1 -0
  23. package/lib/utils/assertion.d.ts.map +1 -0
  24. package/lib/utils/consensus.d.ts.map +1 -0
  25. package/lib/utils/conversion.d.ts.map +1 -0
  26. package/lib/utils/errors.d.ts.map +1 -0
  27. package/lib/utils/evm.d.ts.map +1 -0
  28. package/lib/utils/execution.d.ts.map +1 -0
  29. package/lib/utils/file.d.ts.map +1 -0
  30. package/lib/utils/gitData/gitDataPath.d.ts.map +1 -0
  31. package/lib/utils/gitData/index.d.ts.map +1 -0
  32. package/lib/utils/gitData/writeGitData.d.ts.map +1 -0
  33. package/lib/utils/json_rpc.d.ts.map +1 -0
  34. package/lib/utils/process.d.ts.map +1 -0
  35. package/lib/utils/req_resp.d.ts.map +1 -0
  36. package/lib/utils/rpc_provider.d.ts.map +1 -0
  37. package/lib/utils/validation.d.ts.map +1 -0
  38. package/lib/utils/verification.d.ts.map +1 -0
  39. package/lib/utils/version.d.ts.map +1 -0
  40. package/lib/verified_requests/eth_call.d.ts.map +1 -0
  41. package/lib/verified_requests/eth_estimateGas.d.ts.map +1 -0
  42. package/lib/verified_requests/eth_getBalance.d.ts.map +1 -0
  43. package/lib/verified_requests/eth_getBlockByHash.d.ts.map +1 -0
  44. package/lib/verified_requests/eth_getBlockByNumber.d.ts.map +1 -0
  45. package/lib/verified_requests/eth_getCode.d.ts.map +1 -0
  46. package/lib/verified_requests/eth_getTransactionCount.d.ts.map +1 -0
  47. package/lib/web3_provider.d.ts.map +1 -0
  48. package/lib/web3_provider_inspector.d.ts.map +1 -0
  49. package/lib/web3_proxy.d.ts.map +1 -0
  50. package/package.json +16 -15
  51. package/src/browser/index.ts +3 -0
  52. package/src/cli/applyPreset.ts +83 -0
  53. package/src/cli/cli.ts +58 -0
  54. package/src/cli/cmds/index.ts +7 -0
  55. package/src/cli/cmds/start/handler.ts +27 -0
  56. package/src/cli/cmds/start/index.ts +18 -0
  57. package/src/cli/cmds/start/options.ts +85 -0
  58. package/src/cli/index.ts +30 -0
  59. package/src/cli/options.ts +73 -0
  60. package/src/constants.ts +6 -0
  61. package/src/index.ts +5 -0
  62. package/src/interfaces.ts +90 -0
  63. package/src/proof_provider/index.ts +1 -0
  64. package/src/proof_provider/ordered_map.ts +25 -0
  65. package/src/proof_provider/payload_store.ts +223 -0
  66. package/src/proof_provider/proof_provider.ts +210 -0
  67. package/src/provider_types/eip1193_provider_type.ts +32 -0
  68. package/src/provider_types/ethers_provider_type.ts +44 -0
  69. package/src/provider_types/legacy_provider_type.ts +123 -0
  70. package/src/provider_types/web3_js_provider_type.ts +35 -0
  71. package/src/types.ts +163 -0
  72. package/src/utils/assertion.ts +11 -0
  73. package/src/utils/consensus.ts +122 -0
  74. package/src/utils/conversion.ts +107 -0
  75. package/src/utils/errors.ts +4 -0
  76. package/src/utils/evm.ts +284 -0
  77. package/src/utils/execution.ts +76 -0
  78. package/src/utils/file.ts +51 -0
  79. package/src/utils/gitData/gitDataPath.ts +48 -0
  80. package/src/utils/gitData/index.ts +70 -0
  81. package/src/utils/gitData/writeGitData.ts +10 -0
  82. package/src/utils/json_rpc.ts +170 -0
  83. package/src/utils/process.ts +111 -0
  84. package/src/utils/req_resp.ts +34 -0
  85. package/src/utils/rpc_provider.ts +117 -0
  86. package/src/utils/validation.ts +161 -0
  87. package/src/utils/verification.ts +112 -0
  88. package/src/utils/version.ts +74 -0
  89. package/src/verified_requests/eth_call.ts +50 -0
  90. package/src/verified_requests/eth_estimateGas.ts +49 -0
  91. package/src/verified_requests/eth_getBalance.ts +26 -0
  92. package/src/verified_requests/eth_getBlockByHash.ts +24 -0
  93. package/src/verified_requests/eth_getBlockByNumber.ts +25 -0
  94. package/src/verified_requests/eth_getCode.ts +50 -0
  95. package/src/verified_requests/eth_getTransactionCount.ts +26 -0
  96. package/src/web3_provider.ts +58 -0
  97. package/src/web3_provider_inspector.ts +88 -0
  98. package/src/web3_proxy.ts +175 -0
@@ -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
+ }
@@ -0,0 +1,161 @@
1
+ import {Block} from "@ethereumjs/block";
2
+ import {RLP} from "@ethereumjs/rlp";
3
+ import {Trie} from "@ethereumjs/trie";
4
+ import {Account, KECCAK256_NULL_S} from "@ethereumjs/util";
5
+ import {keccak256} from "ethereum-cryptography/keccak.js";
6
+ import {ChainForkConfig} from "@lodestar/config";
7
+ import {Bytes32, ExecutionPayload} from "@lodestar/types";
8
+ import {Logger} from "@lodestar/utils";
9
+ import {ELBlock, ELProof, ELStorageProof, HexString} from "../types.js";
10
+ import {blockDataFromELBlock, bufferToHex, hexToBuffer, padLeft} from "./conversion.js";
11
+ import {getChainCommon} from "./execution.js";
12
+
13
+ const emptyAccountSerialize = new Account().serialize();
14
+ const storageKeyLength = 32;
15
+
16
+ export function isBlockNumber(block: number | string): boolean {
17
+ if (typeof block === "number") {
18
+ return true;
19
+ }
20
+
21
+ // If block is hex and less than 32 byte long it is a block number, else it's a block hash
22
+ return hexToBuffer(block).byteLength < 32;
23
+ }
24
+
25
+ export async function isValidAccount({
26
+ address,
27
+ stateRoot,
28
+ proof,
29
+ logger,
30
+ }: {
31
+ address: HexString;
32
+ stateRoot: Bytes32;
33
+ proof: ELProof;
34
+ logger: Logger;
35
+ }): Promise<boolean> {
36
+ const trie = await Trie.create();
37
+ const key = keccak256(hexToBuffer(address));
38
+
39
+ try {
40
+ const expectedAccountRLP = await trie.verifyProof(
41
+ Buffer.from(stateRoot),
42
+ Buffer.from(key),
43
+ proof.accountProof.map(hexToBuffer)
44
+ );
45
+
46
+ // Shresth Agrawal (2022) Patronum source code. https://github.com/lightclients/patronum
47
+ const account = Account.fromAccountData({
48
+ nonce: BigInt(proof.nonce),
49
+ balance: BigInt(proof.balance),
50
+ storageRoot: proof.storageHash,
51
+ codeHash: proof.codeHash,
52
+ });
53
+ return account.serialize().equals(expectedAccountRLP ? expectedAccountRLP : emptyAccountSerialize);
54
+ } catch (err) {
55
+ logger.error("Error verifying account proof", undefined, err as Error);
56
+ return false;
57
+ }
58
+ }
59
+
60
+ export async function isValidStorageKeys({
61
+ storageKeys,
62
+ proof,
63
+ logger,
64
+ }: {
65
+ storageKeys: HexString[];
66
+ proof: ELStorageProof;
67
+ logger: Logger;
68
+ }): Promise<boolean> {
69
+ const trie = await Trie.create();
70
+
71
+ for (let i = 0; i < storageKeys.length; i++) {
72
+ const sp = proof.storageProof[i];
73
+ const key = keccak256(padLeft(hexToBuffer(storageKeys[i]), storageKeyLength));
74
+ try {
75
+ const expectedStorageRLP = await trie.verifyProof(
76
+ hexToBuffer(proof.storageHash),
77
+ Buffer.from(key),
78
+ sp.proof.map(hexToBuffer)
79
+ );
80
+
81
+ // buffer.equals is not compatible with Uint8Array for browser
82
+ // so we need to convert the output of RLP.encode to Buffer first
83
+ const isStorageValid =
84
+ (!expectedStorageRLP && sp.value === "0x0") ||
85
+ (!!expectedStorageRLP && expectedStorageRLP.equals(Buffer.from(RLP.encode(sp.value))));
86
+ if (!isStorageValid) return false;
87
+ } catch (err) {
88
+ logger.error("Error verifying storage keys", undefined, err as Error);
89
+ return false;
90
+ }
91
+ }
92
+
93
+ return true;
94
+ }
95
+
96
+ export async function isValidBlock({
97
+ executionPayload,
98
+ block,
99
+ logger,
100
+ config,
101
+ }: {
102
+ executionPayload: ExecutionPayload;
103
+ block: ELBlock;
104
+ logger: Logger;
105
+ config: ChainForkConfig;
106
+ }): Promise<boolean> {
107
+ if (bufferToHex(executionPayload.blockHash) !== block.hash) {
108
+ logger.error("Block hash does not match", {
109
+ rpcBlockHash: block.hash,
110
+ beaconExecutionBlockHash: bufferToHex(executionPayload.blockHash),
111
+ });
112
+
113
+ return false;
114
+ }
115
+
116
+ if (bufferToHex(executionPayload.parentHash) !== block.parentHash) {
117
+ logger.error("Block parent hash does not match", {
118
+ rpcBlockHash: block.parentHash,
119
+ beaconExecutionBlockHash: bufferToHex(executionPayload.parentHash),
120
+ });
121
+
122
+ return false;
123
+ }
124
+
125
+ const common = getChainCommon(config.PRESET_BASE);
126
+ common.setHardforkByBlockNumber(executionPayload.blockNumber, undefined, executionPayload.timestamp);
127
+ const blockObject = Block.fromBlockData(blockDataFromELBlock(block), {common});
128
+
129
+ if (!(await blockObject.validateTransactionsTrie())) {
130
+ logger.error("Block transactions could not be verified.", {
131
+ blockHash: bufferToHex(blockObject.hash()),
132
+ blockNumber: blockObject.header.number,
133
+ });
134
+
135
+ return false;
136
+ }
137
+
138
+ return true;
139
+ }
140
+
141
+ export async function isValidCodeHash({
142
+ codeHash,
143
+ codeResponse,
144
+ }: {
145
+ codeHash: string;
146
+ codeResponse: string;
147
+ logger: Logger;
148
+ }): Promise<boolean> {
149
+ // if there is no code hash for that address
150
+ if (codeResponse === "0x" && codeHash === `0x${KECCAK256_NULL_S}`) return true;
151
+
152
+ return bufferToHex(keccak256(hexToBuffer(codeResponse))) === codeHash;
153
+ }
154
+
155
+ export function isNullish<T>(val: T | undefined | null): val is null | undefined {
156
+ return val === null || val === undefined;
157
+ }
158
+
159
+ export function isPresent<T>(val: T | undefined | null): val is T {
160
+ return !isNullish(val);
161
+ }
@@ -0,0 +1,112 @@
1
+ import {Logger} from "@lodestar/utils";
2
+ import {ProofProvider} from "../proof_provider/proof_provider.js";
3
+ import {ELBlock, ELProof, HexString, JsonRpcRequest} from "../types.js";
4
+ import {bufferToHex} from "./conversion.js";
5
+ import {getELBlock, getELCode, getELProof} from "./execution.js";
6
+ import {ELRpcProvider} from "./rpc_provider.js";
7
+ import {isValidAccount, isValidBlock, isValidCodeHash, isValidStorageKeys} from "./validation.js";
8
+
9
+ type VerificationResult<T> = {data: T; valid: true} | {valid: false; data?: undefined};
10
+
11
+ export async function verifyAccount({
12
+ address,
13
+ proofProvider,
14
+ logger,
15
+ rpc,
16
+ block,
17
+ }: {
18
+ address: HexString;
19
+ rpc: ELRpcProvider;
20
+ proofProvider: ProofProvider;
21
+ logger: Logger;
22
+ block?: number | string;
23
+ }): Promise<VerificationResult<ELProof>> {
24
+ try {
25
+ const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
26
+ const proof = await getELProof(rpc, [address, [], bufferToHex(executionPayload.blockHash)]);
27
+ const validAccount = await isValidAccount({
28
+ address: address,
29
+ stateRoot: executionPayload.stateRoot,
30
+ proof,
31
+ logger,
32
+ });
33
+
34
+ // If account is invalid don't check the storage
35
+ const validStorage = validAccount && (await isValidStorageKeys({storageKeys: [], proof, logger}));
36
+
37
+ if (validAccount && validStorage) {
38
+ return {data: proof, valid: true};
39
+ }
40
+
41
+ return {valid: false};
42
+ } catch (err) {
43
+ logger.error("Error while verifying account", {address}, err as Error);
44
+ return {valid: false};
45
+ }
46
+ }
47
+
48
+ export async function verifyCode({
49
+ address,
50
+ proofProvider,
51
+ logger,
52
+ rpc,
53
+ codeHash,
54
+ block,
55
+ }: {
56
+ address: HexString;
57
+ rpc: ELRpcProvider;
58
+ proofProvider: ProofProvider;
59
+ logger: Logger;
60
+ codeHash: HexString;
61
+ block?: number | string;
62
+ }): Promise<VerificationResult<string>> {
63
+ try {
64
+ const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
65
+ const code = await getELCode(rpc, [address, bufferToHex(executionPayload.blockHash)]);
66
+
67
+ if (await isValidCodeHash({codeHash, codeResponse: code, logger})) {
68
+ return {data: code, valid: true};
69
+ }
70
+ return {valid: false};
71
+ } catch (err) {
72
+ logger.error("Error while verifying code", {address}, err as Error);
73
+ return {valid: false};
74
+ }
75
+ }
76
+
77
+ export async function verifyBlock({
78
+ payload,
79
+ proofProvider,
80
+ logger,
81
+ rpc,
82
+ }: {
83
+ payload: JsonRpcRequest<[block: string | number, hydrated: boolean]>;
84
+ rpc: ELRpcProvider;
85
+ proofProvider: ProofProvider;
86
+ logger: Logger;
87
+ }): Promise<VerificationResult<ELBlock>> {
88
+ try {
89
+ const blockNumber = payload.params[0];
90
+ const executionPayload = await proofProvider.getExecutionPayload(blockNumber);
91
+ const block = await getELBlock(rpc, [blockNumber, true]); // Always request hydrated blocks as we need access to `transactions` details
92
+
93
+ // If response is not valid from the EL we don't need to verify it
94
+ if (!block) return {data: block, valid: false};
95
+
96
+ if (
97
+ await isValidBlock({
98
+ logger,
99
+ block,
100
+ executionPayload,
101
+ config: proofProvider.config,
102
+ })
103
+ ) {
104
+ return {data: block, valid: true};
105
+ }
106
+
107
+ return {valid: false};
108
+ } catch (err) {
109
+ logger.error("Error while verifying block", {block: payload.params[0]}, err as Error);
110
+ return {valid: false};
111
+ }
112
+ }