@lodestar/prover 1.35.0-dev.8ea34e52ba → 1.35.0-dev.91dadf81de
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.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/payload_store.d.ts.map +1 -0
- package/lib/proof_provider/proof_provider.d.ts.map +1 -0
- 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/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.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,210 @@
|
|
|
1
|
+
import {ApiClient, getClient} from "@lodestar/api/beacon";
|
|
2
|
+
import {ChainForkConfig, createChainForkConfig} from "@lodestar/config";
|
|
3
|
+
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
|
|
4
|
+
import {Lightclient, LightclientEvent, RunStatusCode} from "@lodestar/light-client";
|
|
5
|
+
import {LightClientRestTransport} from "@lodestar/light-client/transport";
|
|
6
|
+
import {ForkName, isForkPostCapella} from "@lodestar/params";
|
|
7
|
+
import {ExecutionPayload, LightClientHeader} from "@lodestar/types";
|
|
8
|
+
import {Logger} from "@lodestar/utils";
|
|
9
|
+
import {LCTransport, RootProviderInitOptions} from "../interfaces.js";
|
|
10
|
+
import {assertLightClient} from "../utils/assertion.js";
|
|
11
|
+
import {
|
|
12
|
+
fetchBlock,
|
|
13
|
+
getExecutionPayloads,
|
|
14
|
+
getGenesisData,
|
|
15
|
+
getSyncCheckpoint,
|
|
16
|
+
getUnFinalizedRangeForPayloads,
|
|
17
|
+
} from "../utils/consensus.js";
|
|
18
|
+
import {bufferToHex} from "../utils/conversion.js";
|
|
19
|
+
import {PayloadStore} from "./payload_store.js";
|
|
20
|
+
|
|
21
|
+
type RootProviderOptions = Omit<RootProviderInitOptions, "transport"> & {
|
|
22
|
+
transport: LightClientRestTransport;
|
|
23
|
+
api: ApiClient;
|
|
24
|
+
config: ChainForkConfig;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class ProofProvider {
|
|
28
|
+
private store: PayloadStore;
|
|
29
|
+
private logger: Logger;
|
|
30
|
+
// Make sure readyPromise doesn't throw unhandled exceptions
|
|
31
|
+
private readyPromise?: Promise<void>;
|
|
32
|
+
|
|
33
|
+
readonly config: ChainForkConfig;
|
|
34
|
+
readonly network: NetworkName;
|
|
35
|
+
readonly api: ApiClient;
|
|
36
|
+
|
|
37
|
+
lightClient?: Lightclient;
|
|
38
|
+
|
|
39
|
+
constructor(readonly opts: RootProviderOptions) {
|
|
40
|
+
this.store = new PayloadStore({api: opts.api, logger: opts.logger});
|
|
41
|
+
this.logger = opts.logger;
|
|
42
|
+
this.config = opts.config;
|
|
43
|
+
this.api = opts.api;
|
|
44
|
+
this.network = opts.config.PRESET_BASE as NetworkName;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async waitToBeReady(): Promise<void> {
|
|
48
|
+
return this.readyPromise;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static init(opts: RootProviderInitOptions): ProofProvider {
|
|
52
|
+
if (opts.transport === LCTransport.P2P) {
|
|
53
|
+
throw new Error("P2P mode not supported yet");
|
|
54
|
+
}
|
|
55
|
+
opts.logger.info("Creating ProofProvider instance with REST APIs", {
|
|
56
|
+
network: opts.network,
|
|
57
|
+
urls: opts.urls.join(","),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const config = opts.network
|
|
61
|
+
? createChainForkConfig(networksChainConfig[opts.network])
|
|
62
|
+
: createChainForkConfig(opts.config);
|
|
63
|
+
|
|
64
|
+
const api = getClient({urls: opts.urls}, {config});
|
|
65
|
+
const transport = new LightClientRestTransport(api);
|
|
66
|
+
|
|
67
|
+
const provider = new ProofProvider({
|
|
68
|
+
...opts,
|
|
69
|
+
config,
|
|
70
|
+
api,
|
|
71
|
+
transport,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
provider.readyPromise = provider.sync(opts.wsCheckpoint).catch((e) => {
|
|
75
|
+
opts.logger.error("Error while syncing", e);
|
|
76
|
+
return Promise.reject(e);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return provider;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async sync(wsCheckpoint?: string): Promise<void> {
|
|
83
|
+
if (this.lightClient !== undefined) {
|
|
84
|
+
throw Error("Light client already initialized and syncing.");
|
|
85
|
+
}
|
|
86
|
+
this.logger.info("Starting sync for proof provider");
|
|
87
|
+
const {api, config, transport} = this.opts;
|
|
88
|
+
const checkpointRoot = await getSyncCheckpoint(api, wsCheckpoint);
|
|
89
|
+
const genesisData = await getGenesisData(api);
|
|
90
|
+
|
|
91
|
+
this.logger.info("Initializing lightclient", {checkpointRoot: bufferToHex(checkpointRoot)});
|
|
92
|
+
this.lightClient = await Lightclient.initializeFromCheckpointRoot({
|
|
93
|
+
checkpointRoot,
|
|
94
|
+
config,
|
|
95
|
+
transport,
|
|
96
|
+
genesisData,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
assertLightClient(this.lightClient);
|
|
100
|
+
|
|
101
|
+
this.logger.info("Initiating lightclient");
|
|
102
|
+
// Wait for the lightclient to start
|
|
103
|
+
await this.lightClient?.start();
|
|
104
|
+
this.logger.info("Lightclient synced", this.getStatus());
|
|
105
|
+
this.registerEvents();
|
|
106
|
+
|
|
107
|
+
// Load the payloads from the CL
|
|
108
|
+
this.logger.info("Building EL payload history");
|
|
109
|
+
const {start, end} = await getUnFinalizedRangeForPayloads(this.lightClient);
|
|
110
|
+
const payloads = await getExecutionPayloads({
|
|
111
|
+
api: this.opts.api,
|
|
112
|
+
startSlot: start,
|
|
113
|
+
endSlot: end,
|
|
114
|
+
logger: this.logger,
|
|
115
|
+
});
|
|
116
|
+
for (const [slot, payload] of payloads.entries()) {
|
|
117
|
+
this.store.set(payload, slot, false);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Load the finalized payload from the CL
|
|
121
|
+
const finalizedSlot = this.lightClient.getFinalized().beacon.slot;
|
|
122
|
+
this.logger.debug("Getting finalized slot from lightclient", {finalizedSlot});
|
|
123
|
+
const block = await fetchBlock(this.opts.api, finalizedSlot);
|
|
124
|
+
if (block) {
|
|
125
|
+
this.store.set(block.message.body.executionPayload, finalizedSlot, true);
|
|
126
|
+
} else {
|
|
127
|
+
this.logger.error("Failed to fetch finalized block", finalizedSlot);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.logger.info("Proof provider ready");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getStatus(): {latest: number; finalized: number; status: RunStatusCode} {
|
|
134
|
+
if (!this.lightClient) {
|
|
135
|
+
return {
|
|
136
|
+
latest: 0,
|
|
137
|
+
finalized: 0,
|
|
138
|
+
status: RunStatusCode.uninitialized,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
latest: this.lightClient.getHead().beacon.slot,
|
|
144
|
+
finalized: this.lightClient.getFinalized().beacon.slot,
|
|
145
|
+
status: this.lightClient.status,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async getExecutionPayload(blockNumber: number | string | "finalized" | "latest"): Promise<ExecutionPayload> {
|
|
150
|
+
assertLightClient(this.lightClient);
|
|
151
|
+
|
|
152
|
+
if (typeof blockNumber === "string" && blockNumber === "finalized") {
|
|
153
|
+
const payload = this.store.finalized;
|
|
154
|
+
if (!payload) throw new Error("No finalized payload");
|
|
155
|
+
return payload;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof blockNumber === "string" && blockNumber === "latest") {
|
|
159
|
+
const payload = this.store.latest;
|
|
160
|
+
if (!payload) throw new Error("No latest payload");
|
|
161
|
+
return payload;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ((typeof blockNumber === "string" && blockNumber.startsWith("0x")) || typeof blockNumber === "number") {
|
|
165
|
+
const payload = await this.store.get(blockNumber);
|
|
166
|
+
if (!payload) throw new Error(`No payload for blockNumber ${blockNumber}`);
|
|
167
|
+
return payload;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
throw new Error(`Invalid blockNumber "${blockNumber}"`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async processLCHeader(lcHeader: LightClientHeader, finalized = false): Promise<void> {
|
|
174
|
+
const fork = this.opts.config.getForkName(lcHeader.beacon.slot);
|
|
175
|
+
|
|
176
|
+
if (!isForkPostCapella(fork)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const sszType = this.opts.config.getPostBellatrixForkTypes(lcHeader.beacon.slot).ExecutionPayloadHeader;
|
|
181
|
+
if (
|
|
182
|
+
isForkPostCapella(fork) &&
|
|
183
|
+
(!("execution" in lcHeader) || sszType.equals(lcHeader.execution, sszType.defaultValue()))
|
|
184
|
+
) {
|
|
185
|
+
throw new Error("Execution payload is required for execution fork");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await this.store.processLCHeader(lcHeader as LightClientHeader<ForkName.capella>, finalized);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private registerEvents(): void {
|
|
192
|
+
assertLightClient(this.lightClient);
|
|
193
|
+
|
|
194
|
+
this.opts.signal.addEventListener("abort", () => {
|
|
195
|
+
this.lightClient?.stop();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.lightClient.emitter.on(LightclientEvent.lightClientFinalityHeader, async (data) => {
|
|
199
|
+
await this.processLCHeader(data, true).catch((e) => {
|
|
200
|
+
this.logger.error("Error processing finality update", null, e);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.lightClient.emitter.on(LightclientEvent.lightClientOptimisticHeader, async (data) => {
|
|
205
|
+
await this.processLCHeader(data).catch((e) => {
|
|
206
|
+
this.logger.error("Error processing optimistic update", null, e);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {Web3ProviderType} from "../interfaces.js";
|
|
2
|
+
import {JsonRpcRequestOrBatch, JsonRpcResponseOrBatch} from "../types.js";
|
|
3
|
+
|
|
4
|
+
// Modern providers uses this structure e.g. Web3 4.x
|
|
5
|
+
export interface EIP1193Provider {
|
|
6
|
+
request: (payload: JsonRpcRequestOrBatch) => Promise<JsonRpcResponseOrBatch>;
|
|
7
|
+
}
|
|
8
|
+
export default {
|
|
9
|
+
name: "eip1193",
|
|
10
|
+
matched(provider): provider is EIP1193Provider {
|
|
11
|
+
return (
|
|
12
|
+
"request" in provider &&
|
|
13
|
+
typeof provider.request === "function" &&
|
|
14
|
+
provider.request.constructor.name === "AsyncFunction"
|
|
15
|
+
);
|
|
16
|
+
},
|
|
17
|
+
handler(provider) {
|
|
18
|
+
const request = provider.request.bind(provider);
|
|
19
|
+
|
|
20
|
+
return async (payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> => {
|
|
21
|
+
const response = await request(payload);
|
|
22
|
+
return response;
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
mutateProvider(provider, newHandler) {
|
|
26
|
+
Object.assign(provider, {
|
|
27
|
+
request: async function newRequest(payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
28
|
+
return newHandler(payload);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
} as Web3ProviderType<EIP1193Provider>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {Web3ProviderType} from "../interfaces.js";
|
|
2
|
+
import {JsonRpcRequestOrBatch, JsonRpcResponse, JsonRpcResponseOrBatch} from "../types.js";
|
|
3
|
+
import {isBatchRequest} from "../utils/json_rpc.js";
|
|
4
|
+
import web3JsProviderType from "./web3_js_provider_type.js";
|
|
5
|
+
|
|
6
|
+
export interface EthersProvider {
|
|
7
|
+
// Ethers provider does not have a public interface for batch requests
|
|
8
|
+
send(method: string, params: Array<unknown>): Promise<JsonRpcResponse>;
|
|
9
|
+
}
|
|
10
|
+
export default {
|
|
11
|
+
name: "ethers",
|
|
12
|
+
matched(provider): provider is EthersProvider {
|
|
13
|
+
return (
|
|
14
|
+
!web3JsProviderType.matched(provider) &&
|
|
15
|
+
"send" in provider &&
|
|
16
|
+
typeof provider.send === "function" &&
|
|
17
|
+
provider.send.length > 1 &&
|
|
18
|
+
provider.send.constructor.name === "AsyncFunction"
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
handler(provider) {
|
|
22
|
+
const send = provider.send.bind(provider);
|
|
23
|
+
|
|
24
|
+
return async (payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> => {
|
|
25
|
+
// Because ethers provider public interface does not support batch requests
|
|
26
|
+
// so we need to handle it manually
|
|
27
|
+
if (isBatchRequest(payload)) {
|
|
28
|
+
const responses = [];
|
|
29
|
+
for (const request of payload) {
|
|
30
|
+
responses.push(await send(request.method, request.params));
|
|
31
|
+
}
|
|
32
|
+
return responses;
|
|
33
|
+
}
|
|
34
|
+
return send(payload.method, payload.params);
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
mutateProvider(provider, newHandler) {
|
|
38
|
+
Object.assign(provider, {
|
|
39
|
+
send: function newSend(method: string, params: Array<unknown>): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
40
|
+
return newHandler({jsonrpc: "2.0", id: 0, method, params});
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
} as Web3ProviderType<EthersProvider>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {AnyWeb3Provider, Web3ProviderType} from "../interfaces.js";
|
|
2
|
+
import {JsonRpcRequest, JsonRpcRequestOrBatch, JsonRpcResponse, JsonRpcResponseOrBatch} from "../types.js";
|
|
3
|
+
import web3JsProviderType from "./web3_js_provider_type.js";
|
|
4
|
+
|
|
5
|
+
// Some providers uses `request` instead of the `send`. e.g. Ganache
|
|
6
|
+
interface RequestProvider {
|
|
7
|
+
request(
|
|
8
|
+
payload: JsonRpcRequestOrBatch,
|
|
9
|
+
callback: (err: Error | undefined, response: JsonRpcResponseOrBatch) => void
|
|
10
|
+
): void;
|
|
11
|
+
}
|
|
12
|
+
// The legacy Web3 1.x use this structure
|
|
13
|
+
interface SendProvider {
|
|
14
|
+
send(payload: JsonRpcRequest, callback: (err?: Error | null, response?: JsonRpcResponse) => void): void;
|
|
15
|
+
}
|
|
16
|
+
// Some legacy providers use this very old structure
|
|
17
|
+
interface SendAsyncProvider {
|
|
18
|
+
sendAsync(payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type LegacyProvider = RequestProvider | SendProvider | SendAsyncProvider;
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: "legacy",
|
|
25
|
+
matched(provider): provider is LegacyProvider {
|
|
26
|
+
return isRequestProvider(provider) || isSendProvider(provider) || isSendAsyncProvider(provider);
|
|
27
|
+
},
|
|
28
|
+
handler(provider) {
|
|
29
|
+
if (isRequestProvider(provider)) {
|
|
30
|
+
const request = provider.request.bind(provider);
|
|
31
|
+
return function newHandler(payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
request(payload, (err, response) => {
|
|
34
|
+
if (err) {
|
|
35
|
+
reject(err);
|
|
36
|
+
} else {
|
|
37
|
+
resolve(response);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (isSendProvider(provider)) {
|
|
44
|
+
const send = provider.send.bind(provider);
|
|
45
|
+
return function newHandler(payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
// web3 providers supports batch requests but don't have valid types
|
|
48
|
+
send(payload as JsonRpcRequest, (err, response) => {
|
|
49
|
+
if (err) {
|
|
50
|
+
reject(err);
|
|
51
|
+
} else {
|
|
52
|
+
resolve(response);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// sendAsync provider
|
|
60
|
+
const sendAsync = provider.sendAsync.bind(provider);
|
|
61
|
+
return async function newHandler(payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
62
|
+
const response = await sendAsync(payload);
|
|
63
|
+
return response;
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
mutateProvider(provider, newHandler) {
|
|
67
|
+
if (isRequestProvider(provider)) {
|
|
68
|
+
const newRequest = function newRequest(
|
|
69
|
+
payload: JsonRpcRequestOrBatch,
|
|
70
|
+
callback: (err?: Error | null, response?: JsonRpcResponseOrBatch) => void
|
|
71
|
+
): void {
|
|
72
|
+
newHandler(payload)
|
|
73
|
+
.then((response) => callback(undefined, response))
|
|
74
|
+
.catch((err) => callback(err, undefined));
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Object.assign(provider, {request: newRequest});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isSendProvider(provider)) {
|
|
81
|
+
const newSend = function newSend(
|
|
82
|
+
payload: JsonRpcRequestOrBatch,
|
|
83
|
+
callback: (err?: Error | null, response?: JsonRpcResponseOrBatch) => void
|
|
84
|
+
): void {
|
|
85
|
+
newHandler(payload)
|
|
86
|
+
.then((response) => callback(undefined, response))
|
|
87
|
+
.catch((err) => callback(err, undefined));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
Object.assign(provider, {send: newSend});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// sendAsync provider
|
|
94
|
+
Object.assign(provider, {sendAsync: newHandler});
|
|
95
|
+
},
|
|
96
|
+
} as Web3ProviderType<LegacyProvider>;
|
|
97
|
+
|
|
98
|
+
function isSendProvider(provider: AnyWeb3Provider): provider is SendProvider {
|
|
99
|
+
return (
|
|
100
|
+
!web3JsProviderType.matched(provider) &&
|
|
101
|
+
"send" in provider &&
|
|
102
|
+
typeof provider.send === "function" &&
|
|
103
|
+
provider.send.length > 1 &&
|
|
104
|
+
provider.send.constructor.name !== "AsyncFunction"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isRequestProvider(provider: AnyWeb3Provider): provider is RequestProvider {
|
|
109
|
+
return (
|
|
110
|
+
!web3JsProviderType.matched(provider) &&
|
|
111
|
+
"request" in provider &&
|
|
112
|
+
typeof provider.request === "function" &&
|
|
113
|
+
provider.request.length > 1
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isSendAsyncProvider(provider: AnyWeb3Provider): provider is SendAsyncProvider {
|
|
118
|
+
return (
|
|
119
|
+
"sendAsync" in provider &&
|
|
120
|
+
typeof provider.sendAsync === "function" &&
|
|
121
|
+
provider.sendAsync.constructor.name === "AsyncFunction"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {AnyWeb3Provider, Web3ProviderType} from "../interfaces.js";
|
|
2
|
+
import {JsonRpcRequest, JsonRpcRequestOrBatch, JsonRpcResponse, JsonRpcResponseOrBatch} from "../types.js";
|
|
3
|
+
import {isBatchRequest} from "../utils/json_rpc.js";
|
|
4
|
+
|
|
5
|
+
export interface Web3jsProvider {
|
|
6
|
+
request: (payload: JsonRpcRequest) => Promise<JsonRpcResponse>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: "web3js",
|
|
11
|
+
matched(provider): provider is Web3jsProvider {
|
|
12
|
+
return (
|
|
13
|
+
"isWeb3Provider" in provider.constructor &&
|
|
14
|
+
(provider.constructor as {isWeb3Provider: (provider: AnyWeb3Provider) => boolean}).isWeb3Provider(provider)
|
|
15
|
+
);
|
|
16
|
+
},
|
|
17
|
+
handler(provider) {
|
|
18
|
+
const request = provider.request.bind(provider);
|
|
19
|
+
|
|
20
|
+
return async (payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> => {
|
|
21
|
+
if (isBatchRequest(payload)) {
|
|
22
|
+
return Promise.all(payload.map((p) => request(p)));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return request(payload);
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
mutateProvider(provider, newHandler) {
|
|
29
|
+
Object.assign(provider, {
|
|
30
|
+
request: async function newRequest(payload: JsonRpcRequestOrBatch): Promise<JsonRpcResponseOrBatch | undefined> {
|
|
31
|
+
return newHandler(payload);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
} as Web3ProviderType<Web3jsProvider>;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
export type JsonRpcId = number | string;
|
|
2
|
+
export type JsonRpcVersion = string & ("2.0" | "1.0");
|
|
3
|
+
|
|
4
|
+
export interface JsonRpcRequestPayload<T = unknown[]> {
|
|
5
|
+
readonly jsonrpc: JsonRpcVersion;
|
|
6
|
+
readonly id: JsonRpcId;
|
|
7
|
+
readonly method: string;
|
|
8
|
+
readonly params: T;
|
|
9
|
+
readonly requestOptions?: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface JsonRpcNotificationPayload<T = unknown[]> {
|
|
13
|
+
readonly jsonrpc: JsonRpcVersion;
|
|
14
|
+
readonly method: string;
|
|
15
|
+
readonly params: T;
|
|
16
|
+
readonly requestOptions?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type JsonRpcRequest<T = unknown[]> = JsonRpcRequestPayload<T> | JsonRpcNotificationPayload<T>;
|
|
20
|
+
export type JsonRpcBatchRequest<T = unknown[]> = JsonRpcRequest<T>[];
|
|
21
|
+
|
|
22
|
+
// The request can be a single request, a notification
|
|
23
|
+
// or an array of requests and notifications as batch request
|
|
24
|
+
export type JsonRpcRequestOrBatch<T = unknown[]> = JsonRpcRequest<T> | JsonRpcBatchRequest<T>;
|
|
25
|
+
|
|
26
|
+
// Make the response compatible with different libraries, we don't use the readonly modifier
|
|
27
|
+
export interface JsonRpcResponseWithResultPayload<T> {
|
|
28
|
+
readonly id?: JsonRpcId;
|
|
29
|
+
jsonrpc: JsonRpcVersion;
|
|
30
|
+
result: T;
|
|
31
|
+
error?: never;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface JsonRpcErrorPayload<T> {
|
|
35
|
+
readonly code?: number;
|
|
36
|
+
readonly data?: T;
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface JsonRpcResponseWithErrorPayload<T> {
|
|
41
|
+
readonly id?: JsonRpcId;
|
|
42
|
+
jsonrpc: JsonRpcVersion;
|
|
43
|
+
result?: never;
|
|
44
|
+
error: JsonRpcErrorPayload<T>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Make the very flexible el response type to match different libraries easily
|
|
48
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
49
|
+
export type JsonRpcResponse<T = any, E = any> =
|
|
50
|
+
| JsonRpcResponseWithResultPayload<T>
|
|
51
|
+
| JsonRpcResponseWithErrorPayload<E>;
|
|
52
|
+
|
|
53
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
54
|
+
export type JsonRpcBatchResponse<T = any, E = any> = JsonRpcResponse<T, E>[];
|
|
55
|
+
|
|
56
|
+
// Response can be a single response or an array of responses in case of batch request
|
|
57
|
+
// Make the very flexible el response type to match different libraries easily
|
|
58
|
+
// biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
|
|
59
|
+
export type JsonRpcResponseOrBatch<T = any, E = any> = JsonRpcResponse<T, E> | JsonRpcBatchResponse<T, E>;
|
|
60
|
+
|
|
61
|
+
export type HexString = string;
|
|
62
|
+
|
|
63
|
+
export type ELBlockNumberOrTag = number | string | "latest" | "earliest" | "pending";
|
|
64
|
+
|
|
65
|
+
export interface ELProof {
|
|
66
|
+
readonly address: string;
|
|
67
|
+
readonly balance: string;
|
|
68
|
+
readonly codeHash: string;
|
|
69
|
+
readonly nonce: string;
|
|
70
|
+
readonly storageHash: string;
|
|
71
|
+
readonly accountProof: string[];
|
|
72
|
+
readonly storageProof: {
|
|
73
|
+
readonly key: string;
|
|
74
|
+
readonly value: string;
|
|
75
|
+
readonly proof: string[];
|
|
76
|
+
}[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ELTransaction {
|
|
80
|
+
readonly type: string;
|
|
81
|
+
readonly nonce: string;
|
|
82
|
+
readonly to: string | null;
|
|
83
|
+
readonly chainId?: string;
|
|
84
|
+
readonly input: string;
|
|
85
|
+
readonly value: string;
|
|
86
|
+
readonly gasPrice?: string;
|
|
87
|
+
readonly gas: string;
|
|
88
|
+
readonly maxFeePerGas?: string;
|
|
89
|
+
readonly maxPriorityFeePerGas?: string;
|
|
90
|
+
readonly blockHash: string;
|
|
91
|
+
readonly blockNumber: string;
|
|
92
|
+
readonly from: string;
|
|
93
|
+
readonly hash: string;
|
|
94
|
+
readonly r: string;
|
|
95
|
+
readonly s: string;
|
|
96
|
+
readonly v: string;
|
|
97
|
+
readonly transactionIndex: string;
|
|
98
|
+
readonly accessList?: {address: string; storageKeys: string[]}[];
|
|
99
|
+
readonly data?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ELWithdrawal {
|
|
103
|
+
readonly index: string;
|
|
104
|
+
readonly validatorIndex: string;
|
|
105
|
+
readonly address: string;
|
|
106
|
+
readonly amount: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ELBlock {
|
|
110
|
+
readonly parentHash: string;
|
|
111
|
+
readonly stateRoot: string;
|
|
112
|
+
readonly receiptsRoot: string;
|
|
113
|
+
readonly logsBloom: string;
|
|
114
|
+
readonly nonce: string;
|
|
115
|
+
readonly difficulty: string;
|
|
116
|
+
readonly totalDifficulty: string;
|
|
117
|
+
readonly number: string;
|
|
118
|
+
readonly gasLimit: string;
|
|
119
|
+
readonly gasUsed: string;
|
|
120
|
+
readonly timestamp: string;
|
|
121
|
+
readonly extraData?: Buffer | string;
|
|
122
|
+
readonly mixHash: string;
|
|
123
|
+
readonly hash: string;
|
|
124
|
+
readonly baseFeePerGas: string;
|
|
125
|
+
readonly miner: string;
|
|
126
|
+
readonly sha3Uncles: string;
|
|
127
|
+
readonly size: string;
|
|
128
|
+
readonly uncles: ELBlock[];
|
|
129
|
+
readonly transactions: ELTransaction[];
|
|
130
|
+
readonly transactionsRoot: string;
|
|
131
|
+
readonly withdrawals?: ELWithdrawal[];
|
|
132
|
+
readonly withdrawalsRoot?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ELAccessList {
|
|
136
|
+
readonly address: HexString;
|
|
137
|
+
readonly storageKeys: HexString[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface ELAccessListResponse {
|
|
141
|
+
readonly error: string;
|
|
142
|
+
readonly gasUsed: HexString;
|
|
143
|
+
readonly accessList: ELAccessList[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type ELStorageProof = Pick<ELProof, "storageHash" | "storageProof">;
|
|
147
|
+
|
|
148
|
+
export type ELApi = {
|
|
149
|
+
eth_getBalance: (address: string, block?: number | string) => string;
|
|
150
|
+
eth_createAccessList: (transaction: ELTransaction, block?: ELBlockNumberOrTag) => ELAccessListResponse;
|
|
151
|
+
eth_call: (transaction: ELTransaction, block?: ELBlockNumberOrTag) => HexString;
|
|
152
|
+
eth_estimateGas: (transaction: ELTransaction, block?: ELBlockNumberOrTag) => HexString;
|
|
153
|
+
eth_getCode: (address: string, block?: ELBlockNumberOrTag) => HexString;
|
|
154
|
+
eth_getProof: (address: string, storageKeys: string[], block?: ELBlockNumberOrTag) => ELProof;
|
|
155
|
+
eth_getBlockByNumber: (block: ELBlockNumberOrTag, hydrated?: boolean) => ELBlock | undefined;
|
|
156
|
+
eth_getBlockByHash: (block: string, hydrated?: boolean) => ELBlock | undefined;
|
|
157
|
+
};
|
|
158
|
+
export type ELApiParams = {
|
|
159
|
+
[K in keyof ELApi]: Parameters<ELApi[K]>;
|
|
160
|
+
};
|
|
161
|
+
export type ELApiReturn = {
|
|
162
|
+
[K in keyof ELApi]: ReturnType<ELApi[K]>;
|
|
163
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {Lightclient} from "@lodestar/light-client";
|
|
2
|
+
|
|
3
|
+
export function assertLightClient(client?: Lightclient): asserts client is Lightclient {
|
|
4
|
+
if (!client) {
|
|
5
|
+
throw new Error("Light client is not initialized yet.");
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isTruthy<T = unknown>(value: T): value is Exclude<T, undefined | null> {
|
|
10
|
+
return value !== undefined && value !== null && value !== false;
|
|
11
|
+
}
|