feat: add call support to verified requests for prover (#5462)

* Update the readme with details

* Restructure utility methods to make consistent pattern

* Add call support for verified requests

* Update the evm logic

* Update the logger data type

* Add more structured tests

* Move e2e tests to mainnet to make them stable for now till setup CI

* Update readme

* Fix a typo
This commit is contained in:
Nazar Hussain
2023-05-15 15:01:13 +02:00
committed by GitHub
parent 249aa7579d
commit 375d660ed1
43 changed files with 19258 additions and 1851 deletions

View File

@@ -18,12 +18,14 @@ import Web3 from "web3";
import {createVerifiedExecutionProvider, LCTransport} from "@lodestar/prover";
const {provider, proofProvider} = createVerifiedExecutionProvider(
new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io"), {
transport: LCTransport.Rest,
urls: ["https://lodestar-sepolia.chainsafe.io"],
network: "sepolia",
wsCheckpoint: "trusted-checkpoint"
});
new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io"),
{
transport: LCTransport.Rest,
urls: ["https://lodestar-sepolia.chainsafe.io"],
network: "sepolia",
wsCheckpoint: "trusted-checkpoint",
}
);
const web3 = new Web3(provider);
@@ -35,7 +37,7 @@ console.log({balance, address});
You can also invoke the package as binary.
```bash
npm -i g @lodestar/prover
npm -i g @lodestar/prover
lodestar-prover start \
--network sepolia \
@@ -45,6 +47,67 @@ lodestar-prover start \
--port 8080
```
## Supported Web3 Methods
✅ - Completed
⌛ - Todo
➡️ - Request will be forward to EL without any intermediary manipulations.
| Group | Method | Status | Version |
| ----------------- | --------------------------------------- | ------ | ------- |
| Block | eth_getBlockByHash | ✅ | v0 |
| | eth_getBlockByNumber | ✅ | v0 |
| | eth_getBlockTransactionCountByHash | ⌛ | v2 |
| | eth_getBlockTransactionCountByNumber | ⌛ | v2 |
| | eth_getUncleCountByBlockHash | ⌛ | v2 |
| | eth_getUncleCountByBlockNumber | ⌛ | v2 |
| Chain/Network | eth_chainId | ➡️ |
| | eth_syncing | ⌛ | v1 |
| | eth_coinbase | ⌛ | v2 |
| | eth_accounts | ➡️ |
| | eth_blockNumber | ➡️ |
| Call and Estimate | eth_call | ✅ | v0 |
| | eth_estimateGas | ⌛ | v0 |
| | eth_createAccessList | ⌛ | v2 |
| | eth_gasPrice | ⌛ | v1 |
| | eth_maxPriorityFeePerGas | ⌛ | v1 |
| | eth_feeHistory | ⌛ | v2 |
| Filters | eth_newFilter | ⌛ | v2 |
| | eth_newBlockFilter | ⌛ | v2 |
| | eth_newPendingTransactionFilter | ⌛ | v2 |
| | eth_uninstallFilter | ⌛ | v2 |
| | eth_getFilterChanges | ⌛ | v2 |
| | eth_getFilterLogs | ⌛ | v2 |
| | eth_getLogs | ⌛ | v1 |
| Mining | eth_mining | ➡️ |
| | eth_hashrate | ➡️ |
| | eth_getWork | ➡️ |
| | eth_submitWork | ➡️ |
| | eth_submitHashrate | ➡️ |
| Signing | eth_sign | ➡️ |
| | eth_signTransaction | ➡️ |
| State | eth_getBalance | ✅ | v0 |
| | eth_getStorageAt | ⌛ | v1 |
| | eth_getTransactionCount | ⌛ | v2 |
| | eth_getCode | ✅ | v0 |
| | eth_getProof | ➡️ |
| Transactions | eth_sendTransaction | ➡️ |
| | eth_sendRawTransaction | ➡️ |
| | eth_getTransactionByHash | ⌛ | v2 |
| | eth_getTransactionByBlockHashAndIndex | ⌛ | v2 |
| | eth_getTransactionByBlockNumberAndIndex | ⌛ | v2 |
| | eth_getTransactionReceipt | ⌛ | v2 |
## Non-supported features
- Currently does not support batch requests.
## Warnings
- To use this prover the ehtereum provider must support the `eth_getProof` method. Unfortunately, Infura does not currently support this endpoint. As an alternative, we suggest using Alchemy.
## Prerequisites
- [Lerna](https://github.com/lerna/lerna)

View File

@@ -58,11 +58,13 @@
"generate-fixtures": "npx ts-node --esm scripts/generate_fixtures.ts"
},
"dependencies": {
"@ethereumjs/block": "^4.2.1",
"@ethereumjs/common": "^3.1.1",
"@ethereumjs/block": "^4.2.2",
"@ethereumjs/common": "^3.1.2",
"@ethereumjs/rlp": "^4.0.1",
"@ethereumjs/trie": "^5.0.4",
"@ethereumjs/util": "^8.0.5",
"@ethereumjs/trie": "^5.0.5",
"@ethereumjs/util": "^8.0.6",
"@ethereumjs/vm": "^6.4.2",
"@ethereumjs/blockchain": "^6.2.2",
"@lodestar/api": "^1.8.0",
"@lodestar/config": "^1.8.0",
"@lodestar/light-client": "^1.8.0",

View File

@@ -23,41 +23,104 @@ const networkURLs: Record<NETWORK, {beacon: string; rpc: string}> = {
let idIndex = Math.floor(Math.random() * 1000000);
async function rawEth(network: NETWORK, payload: Record<string, unknown>): Promise<Record<string, unknown>> {
return (await axios({url: networkURLs[network].rpc, method: "post", data: payload, responseType: "json"}))
.data as Record<string, unknown>;
async function rawEth(
network: NETWORK,
method: string,
params: unknown[]
): Promise<{payload: Record<string, unknown>; response: Record<string, unknown>}> {
const payload = {id: idIndex++, jsonrpc: "2.0", method, params};
const response = (
await axios({
url: networkURLs[network].rpc,
method: "post",
data: payload,
responseType: "json",
})
).data as Record<string, unknown>;
return {payload, response};
}
async function rawBeacon(network: NETWORK, path: string): Promise<Record<string, unknown>> {
return (await axios.get(`${networkURLs[network].beacon}/${path}`)).data as Record<string, unknown>;
}
async function generateFixture(
label: string,
{method, params}: {method: string; params: unknown[]},
{slot}: {slot: number | string},
network: NETWORK = "sepolia"
): Promise<void> {
const request = {id: idIndex++, jsonrpc: "2.0", method, params};
const response = await rawEth(network, request);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
const executionPayload: unknown = ((await rawBeacon(network, `eth/v2/beacon/blocks/${slot}`)) as any).data.message
.body.execution_payload;
type Generator = (opts: {latest: string; finalized: string}) => {
request: {method: string; params: unknown[]};
slot: number | string;
dependentRequests?: {method: string; params: unknown[]}[];
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
const headers: unknown = ((await rawBeacon(network, `eth/v1/beacon/headers/${slot}`)) as any).data;
type Block = {hash: string; number: string};
const getBlockByNumber = async (network: NETWORK, number: string | number, dehydrate = false): Promise<Block> => {
const {response} = await rawEth(network, "eth_getBlockByNumber", [number, dehydrate]);
if (response.error || !response.result) {
throw new Error("Invalid response" + JSON.stringify(response));
}
return response.result as Block;
};
const getBlockByHash = async (network: NETWORK, hash: string | number, dehydrate = false): Promise<Block> => {
const {response} = await rawEth(network, "eth_getBlockByHash", [hash, dehydrate]);
if (response.error || !response.result) {
throw new Error("Invalid response" + JSON.stringify(response));
}
return response.result as Block;
};
async function generateFixture(label: string, generator: Generator, network: NETWORK = "sepolia"): Promise<void> {
const latest = await getBlockByNumber(network, "latest");
const finalized = await getBlockByNumber(network, "finalized");
const opts = generator({latest: latest.hash, finalized: finalized.hash});
const slot = opts.slot;
const {payload: request, response} = await rawEth(network, opts.request.method, opts.request.params);
if (response.error || !response.result) {
throw new Error("Invalid response" + JSON.stringify(response));
}
const beacon = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
executionPayload: ((await rawBeacon(network, `eth/v2/beacon/blocks/${slot}`)) as any).data.message.body
.execution_payload,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
headers: ((await rawBeacon(network, `eth/v1/beacon/headers/${slot}`)) as any).data,
};
const payloadBlock = await getBlockByHash(
network,
// eslint-disable-next-line @typescript-eslint/naming-convention
(beacon.executionPayload as {block_hash: string}).block_hash,
true
);
const execution = {
block: payloadBlock,
};
const dependentRequests = [];
for (const {method, params} of opts.dependentRequests ?? []) {
const {payload, response} = await rawEth(network, method, params);
if (response.error || !response.result) {
throw new Error("Invalid response" + JSON.stringify(response));
}
dependentRequests.push({payload, response});
}
const fixture = {
label,
network,
request,
response,
executionPayload,
headers,
beacon,
execution,
dependentRequests,
};
const dir = path.join(__dirname, "..", "test/fixtures", network);
@@ -67,56 +130,92 @@ async function generateFixture(
console.info("Generated fixture:", label);
}
await generateFixture(
"eth_getBlock_with_no_accessList",
{
await generateFixture("eth_getBlock_with_no_accessList", () => ({
request: {
method: "eth_getBlockByHash",
params: ["0x75b10426177f0f4bd8683999e2c7c597007c6e7c4551d6336c0f880b12c6f3bf", true],
},
{slot: 2144468}
);
slot: 2144468,
}));
await generateFixture(
"eth_getBlock_with_contractCreation",
{
await generateFixture("eth_getBlock_with_contractCreation", () => ({
request: {
method: "eth_getBlockByHash",
params: ["0x3a0225b38d5927a37cc95fd48254e83c4e9b70115918a103d9fd7e36464030d4", true],
},
{slot: 625024}
);
slot: 625024,
}));
await generateFixture("eth_getBalance_eoa", ({latest}) => ({
request: {method: "eth_getBalance", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", latest]},
slot: "head",
dependentRequests: [{method: "eth_getProof", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", [], latest]}],
}));
await generateFixture("eth_getBalance_contract", ({latest}) => ({
request: {method: "eth_getBalance", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", latest]},
slot: "head",
dependentRequests: [{method: "eth_getProof", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", [], latest]}],
}));
await generateFixture("eth_getCode", ({latest}) => ({
request: {method: "eth_getCode", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", latest]},
slot: "head",
dependentRequests: [{method: "eth_getProof", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", [], latest]}],
}));
await generateFixture("eth_getTransactionCount", ({latest}) => ({
request: {method: "eth_getTransactionCount", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", latest]},
slot: "head",
dependentRequests: [{method: "eth_getProof", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", [], latest]}],
}));
await generateFixture(
"eth_getBalance_eoa",
{method: "eth_getBalance", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", "latest"]},
{slot: "head"}
);
await generateFixture(
"eth_getBalance_eoa",
{method: "eth_getBalance", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", "latest"]},
{slot: "head"}
);
await generateFixture(
"eth_getBalance_eoa_proof",
{method: "eth_getProof", params: ["0xC4bFccB1668d6E464F33a76baDD8C8D7D341e04A", [], "latest"]},
{slot: "head"}
);
await generateFixture(
"eth_getBalance_contract",
{method: "eth_getBalance", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", "latest"]},
{slot: "head"}
);
await generateFixture(
"eth_getBalance_contract_proof",
{method: "eth_getProof", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", [], "latest"]},
{slot: "head"}
);
await generateFixture(
"eth_getCode",
{method: "eth_getCode", params: ["0xa54aeF0dAB669e8e1A164BCcB323549a818a0497", "latest"]},
{slot: "head"}
"eth_call",
({latest}) => ({
request: {
method: "eth_call",
params: [
{
data: "0xe6cb901300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005",
to: "0xade2a9c8b033d60ffcdb8cfc974dd87b2a9c1f27",
},
latest,
],
},
slot: "head",
dependentRequests: [
{
method: "eth_createAccessList",
params: [
{
to: "0xade2a9c8b033d60ffcdb8cfc974dd87b2a9c1f27",
from: "0x0000000000000000000000000000000000000000",
data: "0xe6cb901300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005",
value: undefined,
gas: "0x1c9c380",
gasPrice: "0x0",
},
latest,
],
},
{
method: "eth_getProof",
params: ["0x0000000000000000000000000000000000000000", [], latest],
},
{
method: "eth_getCode",
params: ["0x0000000000000000000000000000000000000000", latest],
},
{
method: "eth_getProof",
params: ["0xade2a9c8b033d60ffcdb8cfc974dd87b2a9c1f27", [], latest],
},
{
method: "eth_getCode",
params: ["0xade2a9c8b033d60ffcdb8cfc974dd87b2a9c1f27", latest],
},
],
}),
"mainnet"
);

View File

@@ -2,3 +2,4 @@
export const MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128;
export const MAX_PAYLOAD_HISTORY = 32;
export const UNVERIFIED_RESPONSE_CODE = -33091;
export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

View File

@@ -22,6 +22,9 @@ export type ELRequestHandler<Params = unknown[], Response = unknown> = (
payload: ELRequestPayload<Params>
) => Promise<ELResponse<Response> | undefined>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ELRequestHandlerAny = ELRequestHandler<any, any>;
// Modern providers uses this structure e.g. Web3 4.x
export interface EIP1193Provider {
request: (payload: ELRequestPayload) => Promise<ELResponse>;

View File

@@ -100,6 +100,8 @@ export class PayloadStore {
if (latestPayload && latestPayload.blockNumber < payload.blockNumber) {
this.latestBlockRoot = blockRoot;
}
} else {
this.latestBlockRoot = blockRoot;
}
if (finalized) {

View File

@@ -1,23 +1,39 @@
import {ELRequestHandler} from "./interfaces.js";
export interface ELRequestPayload<T = unknown[]> {
readonly jsonrpc: string & ("2.0" | "1.0");
readonly jsonrpc: string | ("2.0" | "1.0");
readonly id: number | string;
readonly method: string;
readonly params: T;
readonly requestOptions?: unknown;
}
// Make the very flexible el response type to match different libraries easily
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ELResponse<T = any, E = any> = {
export type ELResponseWithResult<T> = {
readonly id: number | string;
jsonrpc: string;
result?: T;
error?: {
result: T;
error?: never;
};
export type ELResponseWithError<T> = {
readonly id: number | string;
jsonrpc: string;
result?: never;
error: {
readonly code?: number;
readonly data?: E;
readonly data?: T;
readonly message: string;
};
};
// Make the very flexible el response type to match different libraries easily
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ELResponse<T = any, E = any> = ELResponseWithResult<T> | ELResponseWithError<E>;
export type HexString = string;
export type ELBlockNumberOrTag = number | string | "latest" | "earliest" | "pending";
export interface ELProof {
readonly address: string;
readonly balance: string;
@@ -52,14 +68,20 @@ export interface ELTransaction {
readonly v: string;
readonly transactionIndex: string;
readonly accessList?: {address: string; storageKeys: string[]}[];
readonly data?: string;
}
export interface ELWithdrawal {
readonly index: string;
readonly validatorIndex: string;
readonly address: string;
readonly amount: string;
}
export interface ELBlock {
readonly parentHash: string;
readonly transactionsRoot: string;
readonly stateRoot: string;
readonly receiptsRoot: string;
readonly withdrawalsRoot: string;
readonly logsBloom: string;
readonly nonce: string;
readonly difficulty: string;
@@ -77,6 +99,40 @@ export interface ELBlock {
readonly size: string;
readonly uncles: ELBlock[];
readonly transactions: ELTransaction[];
readonly transactionsRoot: string;
readonly withdrawals?: ELWithdrawal[];
readonly withdrawalsRoot?: string;
}
export interface ELAccessList {
readonly address: HexString;
readonly storageKeys: HexString[];
}
export interface ELAccessListResponse {
readonly error: string;
readonly gasUsed: HexString;
readonly accessList: ELAccessList[];
}
export type ELStorageProof = Pick<ELProof, "storageHash" | "storageProof">;
export type HexString = string;
/* eslint-disable @typescript-eslint/naming-convention */
export type ELApi = {
eth_createAccessList: (transaction: ELTransaction, block?: ELBlockNumberOrTag) => ELAccessListResponse;
call: (transaction: ELTransaction, block?: ELBlockNumberOrTag) => HexString;
eth_getCode: (address: string, block?: ELBlockNumberOrTag) => HexString;
eth_getProof: (address: string, storageKeys: string[], block?: ELBlockNumberOrTag) => ELProof;
eth_getBlockByNumber: (block: ELBlockNumberOrTag, hydrated?: boolean) => ELBlock | undefined;
eth_getBlockByHash: (block: string, hydrated?: boolean) => ELBlock | undefined;
};
export type ELApiParams = {
[K in keyof ELApi]: Parameters<ELApi[K]>;
};
export type ELApiReturn = {
[K in keyof ELApi]: ReturnType<ELApi[K]>;
};
export type ELApiHandlers = {
[K in keyof ELApi]: ELRequestHandler<ELApiParams[K], ELApiReturn[K]>;
};
/* eslint-enable @typescript-eslint/naming-convention */

View File

@@ -45,7 +45,12 @@ export async function getExecutionPayloads({
logger: Logger;
}): Promise<Record<number, allForks.ExecutionPayload>> {
[startSlot, endSlot] = [Math.min(startSlot, endSlot), Math.max(startSlot, endSlot)];
logger.debug("Fetching EL payloads", {startSlot, endSlot});
if (startSlot === endSlot) {
logger.debug("Fetching EL payload", {slot: startSlot});
} else {
logger.debug("Fetching EL payloads", {startSlot, endSlot});
}
const payloads: Record<number, allForks.ExecutionPayload> = {};
let slot = endSlot;
@@ -95,14 +100,13 @@ export async function getGenesisData(api: Pick<Api, "beacon">): Promise<GenesisD
}
export async function getSyncCheckpoint(api: Pick<Api, "beacon">, checkpoint?: string): Promise<Bytes32> {
if (checkpoint && checkpoint.length !== 32) {
throw Error(`Checkpoint root must be 32 bytes long: ${checkpoint.length}`);
}
let syncCheckpoint: Uint8Array;
let syncCheckpoint: Bytes32 | undefined = checkpoint ? hexToBuffer(checkpoint) : undefined;
if (checkpoint) {
syncCheckpoint = hexToBuffer(checkpoint);
} else {
if (syncCheckpoint && syncCheckpoint.byteLength !== 32) {
throw Error(`Checkpoint root must be 32 bytes. length=${syncCheckpoint.byteLength}`);
}
if (!syncCheckpoint) {
const res = await api.beacon.getStateFinalityCheckpoints("head");
ApiError.assert(res);
syncCheckpoint = res.response.data.finalized.root;

View File

@@ -2,29 +2,39 @@ import {BlockData, HeaderData} from "@ethereumjs/block";
import {ELBlock, ELTransaction} from "../types.js";
import {isTruthy} from "./assertion.js";
export function numberToHex(n: number | bigint): string {
return "0x" + n.toString(16);
export function numberToHex(num: number | bigint): string {
return "0x" + num.toString(16);
}
export function hexToNumber(n: string): number {
return n.startsWith("0x") ? parseInt(n.slice(2), 16) : parseInt(n, 16);
export function hexToNumber(num: string): number {
return num.startsWith("0x") ? parseInt(num.slice(2), 16) : parseInt(num, 16);
}
export function hexToBigInt(num: string): bigint {
return num.startsWith("0x") ? BigInt(num) : BigInt(`0x${num}`);
}
export function bigIntToHex(num: bigint): string {
return `0x${num.toString(16)}`;
}
export function bufferToHex(buffer: Buffer | Uint8Array): string {
return "0x" + Buffer.from(buffer).toString("hex");
}
export function hexToBuffer(v: string): Buffer {
return Buffer.from(v.replace("0x", ""), "hex");
export function hexToBuffer(val: string): Buffer {
return Buffer.from(val.replace("0x", ""), "hex");
}
export function padLeft(v: Uint8Array, length: number): Uint8Array {
export function padLeft<T extends Buffer | Uint8Array>(v: T, length: number): T {
const buf = Buffer.alloc(length);
Buffer.from(v).copy(buf, length - v.length);
return buf;
if (Buffer.isBuffer(v)) return buf as T;
return Uint8Array.from(buf) as T;
}
// TODO: fix blockInfo type
export function headerDataFromELBlock(blockInfo: ELBlock): HeaderData {
return {
parentHash: blockInfo.parentHash,
@@ -55,7 +65,7 @@ export function txDataFromELBlock(txInfo: ELTransaction) {
gasPrice: isTruthy(txInfo.gasPrice) ? BigInt(txInfo.gasPrice) : null,
gasLimit: txInfo.gas,
to: isTruthy(txInfo.to) ? padLeft(hexToBuffer(txInfo.to), 20) : undefined,
value: BigInt(txInfo.value),
value: txInfo.value ? BigInt(txInfo.value) : undefined,
maxFeePerGas: isTruthy(txInfo.maxFeePerGas) ? BigInt(txInfo.maxFeePerGas) : undefined,
maxPriorityFeePerGas: isTruthy(txInfo.maxPriorityFeePerGas) ? BigInt(txInfo.maxPriorityFeePerGas) : undefined,
};
@@ -67,3 +77,21 @@ export function blockDataFromELBlock(blockInfo: ELBlock): BlockData {
transactions: blockInfo.transactions.map(txDataFromELBlock) as BlockData["transactions"],
};
}
export function cleanObject<T extends Record<string, unknown> | unknown[]>(obj: T): T {
const isNullify = (v: unknown): boolean => v === undefined || v === null;
if (Array.isArray(obj)) return obj.filter((v) => isNullify(v)) as T;
if (typeof obj === "object") {
for (const key of Object.keys(obj)) {
if (isNullify(obj[key])) {
delete obj[key];
} else if (typeof obj[key] === "object") {
cleanObject(obj[key] as Record<string, unknown>);
}
}
}
return obj;
}

View File

@@ -0,0 +1,208 @@
import {Blockchain} from "@ethereumjs/blockchain";
import {Account, Address} from "@ethereumjs/util";
import {VM} from "@ethereumjs/vm";
import {NetworkName} from "@lodestar/config/networks";
import {allForks} from "@lodestar/types";
import {Logger} from "@lodestar/utils";
import {ZERO_ADDRESS} from "../constants.js";
import {ProofProvider} from "../proof_provider/proof_provider.js";
import {ELApiHandlers, ELBlock, ELProof, ELTransaction, HexString} from "../types.js";
import {bufferToHex, cleanObject, hexToBigInt, hexToBuffer, numberToHex, padLeft} from "./conversion.js";
import {elRpc, getChainCommon} from "./execution.js";
import {isValidResponse} from "./json_rpc.js";
import {isValidAccount, isValidCodeHash, isValidStorageKeys} from "./validation.js";
export async function createEVM({
proofProvider,
network,
}: {
proofProvider: ProofProvider;
network: NetworkName;
}): Promise<VM> {
const common = getChainCommon(network);
const blockchain = await Blockchain.create({common});
// Connect blockchain object with existing proof provider for block history
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
(blockchain as any).getBlock = async (blockId: number) => {
const payload = await proofProvider.getExecutionPayload(blockId);
return {
hash: () => payload.blockHash,
};
};
const vm = await VM.create({common, blockchain});
return vm;
}
export async function getEVMWithState({
handler,
executionPayload,
tx,
evm,
logger,
}: {
handler: ELApiHandlers["eth_getProof"] | ELApiHandlers["eth_getCode"] | ELApiHandlers["eth_createAccessList"];
evm: VM;
executionPayload: allForks.ExecutionPayload;
tx: ELTransaction;
logger: Logger;
}): Promise<VM> {
const {stateRoot, blockHash, gasLimit} = executionPayload;
const blockHashHex = bufferToHex(blockHash);
// Create Access List for the contract call
const accessListTx = cleanObject({
to: tx.to,
from: tx.from ?? ZERO_ADDRESS,
data: tx.data,
value: tx.value,
gas: tx.gas ? tx.gas : numberToHex(gasLimit),
gasPrice: "0x0",
}) as ELTransaction;
const response = await elRpc(handler as ELApiHandlers["eth_createAccessList"], "eth_createAccessList", [
accessListTx,
blockHashHex,
]);
if (!isValidResponse(response) || response.result.error) {
throw new Error("Invalid response from RPC");
}
const storageKeysMap: Record<string, string[]> = {};
for (const {address, storageKeys} of response.result.accessList) {
storageKeysMap[address] = storageKeys;
}
if (storageKeysMap[tx.from ?? ZERO_ADDRESS] === undefined) {
storageKeysMap[tx.from ?? ZERO_ADDRESS] = [];
}
if (tx.to && storageKeysMap[tx.to] === undefined) {
storageKeysMap[tx.to] = [];
}
// TODO: When we support batch requests, process with a single request
const proofsAndCodes: Record<string, {proof: ELProof; code: string}> = {};
for (const [address, storageKeys] of Object.entries(storageKeysMap)) {
const {result: proof} = await elRpc(
handler as ELApiHandlers["eth_getProof"],
"eth_getProof",
[address, storageKeys, blockHashHex],
true
);
const validAccount = await isValidAccount({address, proof, logger, stateRoot});
const validStorage = validAccount && (await isValidStorageKeys({storageKeys, proof, logger}));
if (!validAccount || !validStorage) {
throw new Error(`Invalid account: ${address}`);
}
const {result: codeResponse} = await elRpc(
handler as ELApiHandlers["eth_getCode"],
"eth_getCode",
[address, blockHashHex],
true
);
if (!(await isValidCodeHash({codeResponse, logger, codeHash: proof.codeHash}))) {
throw new Error(`Invalid code hash: ${address}`);
}
proofsAndCodes[address] = {proof, code: codeResponse};
}
await evm.stateManager.checkpoint();
for (const [addressHex, {proof, code}] of Object.entries(proofsAndCodes)) {
const address = Address.fromString(addressHex);
const codeBuffer = hexToBuffer(code);
const account = Account.fromAccountData({
nonce: BigInt(proof.nonce),
balance: BigInt(proof.balance),
codeHash: proof.codeHash,
});
await evm.stateManager.putAccount(address, account);
for (const {key, value} of proof.storageProof) {
await evm.stateManager.putContractStorage(
address,
padLeft(hexToBuffer(key), 32),
padLeft(hexToBuffer(value), 32)
);
}
if (codeBuffer.byteLength !== 0) await evm.stateManager.putContractCode(address, codeBuffer);
}
await evm.stateManager.commit();
return evm;
}
export async function executeEVM({
handler,
tx,
evm,
executionPayload,
}: {
handler: ELApiHandlers["eth_getBlockByHash"];
tx: ELTransaction;
evm: VM;
executionPayload: allForks.ExecutionPayload;
}): Promise<HexString> {
const {from, to, gas, gasPrice, maxPriorityFeePerGas, value, data} = tx;
const {result: block} = await elRpc(
handler,
"eth_getBlockByHash",
[bufferToHex(executionPayload.blockHash), true],
true
);
if (!block) {
throw new Error(`Block not found: ${bufferToHex(executionPayload.blockHash)}`);
}
const {execResult} = await evm.evm.runCall({
caller: from ? Address.fromString(from) : undefined,
to: to ? Address.fromString(to) : undefined,
gasLimit: hexToBigInt(gas ?? block.gasLimit),
gasPrice: hexToBigInt(gasPrice ?? maxPriorityFeePerGas ?? "0x0"),
value: hexToBigInt(value ?? "0x0"),
data: data ? hexToBuffer(data) : undefined,
block: {
header: evmBlockHeaderFromELBlock(block, executionPayload),
},
});
if (execResult.exceptionError) {
throw new Error(execResult.exceptionError.error);
}
return bufferToHex(execResult.returnValue);
}
export function evmBlockHeaderFromELBlock(
block: ELBlock,
executionPayload: allForks.ExecutionPayload
): {
number: bigint;
cliqueSigner(): Address;
coinbase: Address;
timestamp: bigint;
difficulty: bigint;
prevRandao: Buffer;
gasLimit: bigint;
baseFeePerGas?: bigint;
} {
return {
number: hexToBigInt(block.number),
cliqueSigner: () => Address.fromString(block.miner),
timestamp: hexToBigInt(block.timestamp),
difficulty: hexToBigInt(block.difficulty),
gasLimit: hexToBigInt(block.gasLimit),
baseFeePerGas: block.baseFeePerGas ? hexToBigInt(block.baseFeePerGas) : undefined,
prevRandao: Buffer.from(executionPayload.prevRandao),
// TODO: Fix the coinbase address
coinbase: Address.fromString(block.miner),
};
}

View File

@@ -1,140 +1,84 @@
import {Logger} from "@lodestar/utils";
import {Common, CustomChain, Hardfork} from "@ethereumjs/common";
import {NetworkName} from "@lodestar/config/networks";
import {ELRequestHandler} from "../interfaces.js";
import {ProofProvider} from "../proof_provider/proof_provider.js";
import {ELBlock, ELProof, ELRequestPayload, ELResponse, HexString} from "../types.js";
import {bufferToHex} from "./conversion.js";
import {ELApiHandlers, ELApiParams, ELApiReturn, ELResponse, ELResponseWithResult} from "../types.js";
import {isValidResponse} from "./json_rpc.js";
import {isValidAccount, isValidBlock, isValidCodeHash, isValidStorageKeys} from "./verification.js";
import {isBlockNumber} from "./validation.js";
export async function getELCode(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: ELRequestHandler<[address: string, block: number | string], string>,
args: [address: string, block: number | string]
): Promise<string> {
export function getRequestId(): string {
// TODO: Find better way to generate random id
const codeResult = await handler({
return (Math.random() * 10000).toFixed(0);
}
export async function elRpc<
P,
R,
E extends boolean = false,
Return = E extends true ? ELResponseWithResult<R> : ELResponse<R> | undefined
>(handler: ELRequestHandler<P, R>, method: string, args: P, raiseError?: E): Promise<Return> {
const response = await handler({
jsonrpc: "2.0",
method: "eth_getCode",
method,
params: args,
id: (Math.random() * 10000).toFixed(0),
id: getRequestId(),
});
if (!codeResult || !codeResult.result) {
throw new Error("Can not find code for given address.");
if (raiseError && !isValidResponse(response)) {
throw new Error(`Invalid response from RPC. method=${method} args=${JSON.stringify(args)}`);
}
return response as Return;
}
export async function getELCode(
handler: ELApiHandlers["eth_getCode"],
args: ELApiParams["eth_getCode"]
): Promise<ELApiReturn["eth_getCode"]> {
const codeResult = await elRpc(handler, "eth_getCode", args);
if (!isValidResponse(codeResult)) {
throw new Error(`Can not find code. address=${args[0]}`);
}
return codeResult.result;
}
export async function getELProof(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: ELRequestHandler<any, any>,
args: [address: string, storageKeys: string[], block: number | string]
): Promise<ELProof> {
// TODO: Find better way to generate random id
const proof = await handler({
jsonrpc: "2.0",
method: "eth_getProof",
params: args,
id: (Math.random() * 10000).toFixed(0),
});
if (!proof) {
throw new Error("Can not find proof for given address.");
handler: ELApiHandlers["eth_getProof"],
args: ELApiParams["eth_getProof"]
): Promise<ELApiReturn["eth_getProof"]> {
const proof = await elRpc(handler, "eth_getProof", args);
if (!isValidResponse(proof)) {
throw new Error(`Can not find proof for address=${args[0]}`);
}
return proof.result as ELProof;
return proof.result;
}
export async function fetchAndVerifyAccount({
address,
proofProvider,
logger,
handler,
block,
}: {
address: HexString;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: ELRequestHandler<any, any>;
proofProvider: ProofProvider;
logger: Logger;
block?: number | string;
}): Promise<{data: ELProof; valid: true} | {valid: false; data?: undefined}> {
const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
const proof = await getELProof(handler, [address, [], bufferToHex(executionPayload.blockHash)]);
export async function getELBlock(
handler: ELApiHandlers["eth_getBlockByNumber"],
args: ELApiParams["eth_getBlockByNumber"]
): Promise<ELApiReturn["eth_getBlockByNumber"]> {
const block = await elRpc(handler, isBlockNumber(args[0]) ? "eth_getBlockByNumber" : "eth_getBlockByHash", args);
if (
(await isValidAccount({
address: address,
stateRoot: executionPayload.stateRoot,
proof,
logger,
})) &&
(await isValidStorageKeys({storageKeys: [], proof, logger}))
) {
return {data: proof, valid: true};
if (!isValidResponse(block)) {
throw new Error(`Can not find block. id=${args[0]}`);
}
return {valid: false};
return block.result;
}
export async function fetchAndVerifyCode({
address,
proofProvider,
logger,
handler,
codeHash,
block,
}: {
address: HexString;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: ELRequestHandler<any, any>;
proofProvider: ProofProvider;
logger: Logger;
codeHash: HexString;
block?: number | string;
}): Promise<{data: string; valid: true} | {valid: false; data?: undefined}> {
const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
const code = await getELCode(handler, [address, bufferToHex(executionPayload.blockHash)]);
if (await isValidCodeHash({codeHash, codeResponse: code, logger})) {
return {data: code, valid: true};
export function getChainCommon(network: NetworkName): Common {
switch (network) {
case "mainnet":
case "goerli":
case "ropsten":
case "sepolia":
// TODO: Not sure how to detect the fork during runtime
return new Common({chain: network, hardfork: Hardfork.Shanghai});
case "gnosis":
return new Common({chain: CustomChain.xDaiChain});
default:
throw new Error(`Non supported network "${network}"`);
}
return {valid: false};
}
export async function fetchAndVerifyBlock({
payload,
proofProvider,
logger,
handler,
network,
}: {
payload: ELRequestPayload<[block: string | number, hydrated: boolean]>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handler: ELRequestHandler<any, any>;
proofProvider: ProofProvider;
logger: Logger;
network: NetworkName;
}): Promise<{data: ELResponse<ELBlock>; valid: true} | {valid: false; data?: undefined}> {
const executionPayload = await proofProvider.getExecutionPayload(payload.params[0]);
const elResponse = await (handler as ELRequestHandler<[block: string | number, hydrated: boolean], ELBlock>)(payload);
// If response is not valid from the EL we don't need to verify it
if (elResponse && !isValidResponse(elResponse)) return {data: elResponse, valid: true};
if (
elResponse &&
elResponse.result &&
(await isValidBlock({
logger,
block: elResponse.result,
executionPayload,
network,
}))
) {
return {data: elResponse, valid: true};
}
return {valid: false};
}

View File

@@ -1,5 +1,7 @@
import {LogData, Logger} from "@lodestar/utils";
import {UNVERIFIED_RESPONSE_CODE} from "../constants.js";
import {ELRequestPayload, ELResponse} from "../types.js";
import {ELResponseWithError} from "../types.js";
import {ELRequestPayload, ELResponse, ELResponseWithResult} from "../types.js";
export function generateRPCResponseForPayload<P, R, E = unknown>(
payload: ELRequestPayload<P>,
@@ -9,25 +11,42 @@ export function generateRPCResponseForPayload<P, R, E = unknown>(
readonly data?: E;
readonly message: string;
}
): ELResponse<R> {
return error
? {
jsonrpc: payload.jsonrpc,
id: payload.id,
error,
}
: {
jsonrpc: payload.jsonrpc,
id: payload.id,
result: res,
};
): ELResponse<R, E> {
if (res !== undefined && error === undefined) {
return {
jsonrpc: payload.jsonrpc,
id: payload.id,
result: res,
};
}
if (error !== undefined) {
return {
jsonrpc: payload.jsonrpc,
id: payload.id,
error,
};
}
throw new Error("Either result or error must be defined.");
}
export function generateVerifiedResponseForPayload<D, P>(
payload: ELRequestPayload<P>,
res: D
): ELResponseWithResult<D> {
return {
jsonrpc: payload.jsonrpc,
id: payload.id,
result: res,
};
}
export function generateUnverifiedResponseForPayload<P, D = unknown>(
payload: ELRequestPayload<P>,
message: string,
data?: D
): ELResponse<never, D> {
): ELResponseWithError<D> {
return data !== undefined || data !== null
? {
jsonrpc: payload.jsonrpc,
@@ -48,6 +67,33 @@ export function generateUnverifiedResponseForPayload<P, D = unknown>(
};
}
export function isValidResponse<R, E>(response: ELResponse<R, E>): response is ELResponse<R, never> {
return response.error === undefined;
export function isValidResponse<R, E>(response: ELResponse<R, E> | undefined): response is ELResponseWithResult<R> {
return response !== undefined && response.error === undefined;
}
export function logRequest(payload: ELRequestPayload, logger: Logger): void {
logger.debug("PR -> EL", {
id: payload.id,
method: payload.method,
params: payload.params,
} as unknown as LogData);
}
export function logResponse(response: ELResponse | null | undefined, logger: Logger): void {
if (response === undefined || response === null) {
logger.debug("PR <- EL (empty response)");
return;
}
if (isValidResponse(response)) {
logger.debug("PR <- EL", {
id: response.id,
result: response.result as Record<string, string> | string,
} as unknown as LogData);
} else {
logger.debug("PR <- E:", {
id: response.id,
error: response.error,
} as unknown as LogData);
}
}

View File

@@ -1,4 +1,4 @@
import {Logger} from "@lodestar/utils";
import {LogData, Logger} from "@lodestar/utils";
import {NetworkName} from "@lodestar/config/networks";
import {ELRequestHandler, ELVerifiedRequestHandler} from "../interfaces.js";
import {ProofProvider} from "../proof_provider/proof_provider.js";
@@ -8,6 +8,7 @@ import {eth_getTransactionCount} from "../verified_requests/eth_getTransactionCo
import {eth_getBlockByHash} from "../verified_requests/eth_getBlockByHash.js";
import {eth_getBlockByNumber} from "../verified_requests/eth_getBlockByNumber.js";
import {eth_getCode} from "../verified_requests/eth_getCode.js";
import {eth_call} from "../verified_requests/eth_call.js";
/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any */
export const supportedELRequests: Record<string, ELVerifiedRequestHandler<any, any>> = {
@@ -16,6 +17,7 @@ export const supportedELRequests: Record<string, ELVerifiedRequestHandler<any, a
eth_getBlockByHash: eth_getBlockByHash,
eth_getBlockByNumber: eth_getBlockByNumber,
eth_getCode: eth_getCode,
eth_call: eth_call,
};
/* eslint-enable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any*/
@@ -33,11 +35,11 @@ export async function processAndVerifyRequest({
network: NetworkName;
}): Promise<ELResponse | undefined> {
await proofProvider.waitToBeReady();
logger.debug("Processing request", {method: payload.method, params: JSON.stringify(payload.params)});
logger.debug("Processing request", {method: payload.method, params: payload.params} as unknown as LogData);
const verifiedHandler = supportedELRequests[payload.method];
if (verifiedHandler !== undefined) {
logger.verbose("Verified request handler found", {method: payload.method});
logger.debug("Verified request handler found", {method: payload.method});
return verifiedHandler({payload, handler, proofProvider, logger, network});
}

View File

@@ -0,0 +1,152 @@
import {Block} from "@ethereumjs/block";
import {RLP} from "@ethereumjs/rlp";
import {Trie} from "@ethereumjs/trie";
import {Account, KECCAK256_NULL_S} from "@ethereumjs/util";
import {keccak256} from "ethereum-cryptography/keccak.js";
import {NetworkName} from "@lodestar/config/networks";
import {Bytes32, allForks} from "@lodestar/types";
import {Logger} from "@lodestar/utils";
import {ELBlock, ELProof, ELStorageProof, HexString} from "../types.js";
import {blockDataFromELBlock, bufferToHex, hexToBuffer, padLeft} from "./conversion.js";
import {getChainCommon} from "./execution.js";
const emptyAccountSerialize = new Account().serialize();
const storageKeyLength = 32;
export function isBlockNumber(block: number | string): boolean {
if (typeof block === "number") {
return true;
}
// If block is hex and less than 32 byte long it is a block number, else it's a block hash
return hexToBuffer(block).byteLength < 32;
}
export async function isValidAccount({
address,
stateRoot,
proof,
logger,
}: {
address: HexString;
stateRoot: Bytes32;
proof: ELProof;
logger: Logger;
}): Promise<boolean> {
const trie = await Trie.create();
const key = keccak256(hexToBuffer(address));
try {
const expectedAccountRLP = await trie.verifyProof(
Buffer.from(stateRoot),
Buffer.from(key),
proof.accountProof.map(hexToBuffer)
);
// Shresth Agrawal (2022) Patronum source code. https://github.com/lightclients/patronum
const account = Account.fromAccountData({
nonce: BigInt(proof.nonce),
balance: BigInt(proof.balance),
storageRoot: proof.storageHash,
codeHash: proof.codeHash,
});
return account.serialize().equals(expectedAccountRLP ? expectedAccountRLP : emptyAccountSerialize);
} catch (err) {
logger.error("Error verifying account proof", undefined, err as Error);
return false;
}
}
export async function isValidStorageKeys({
storageKeys,
proof,
logger,
}: {
storageKeys: HexString[];
proof: ELStorageProof;
logger: Logger;
}): Promise<boolean> {
const trie = await Trie.create();
for (let i = 0; i < storageKeys.length; i++) {
const sp = proof.storageProof[i];
const key = keccak256(padLeft(hexToBuffer(storageKeys[i]), storageKeyLength));
try {
const expectedStorageRLP = await trie.verifyProof(
hexToBuffer(proof.storageHash),
Buffer.from(key),
sp.proof.map(hexToBuffer)
);
const isStorageValid =
(!expectedStorageRLP && sp.value === "0x0") ||
(!!expectedStorageRLP && expectedStorageRLP.equals(RLP.encode(sp.value)));
if (!isStorageValid) return false;
} catch (err) {
logger.error("Error verifying storage keys", undefined, err as Error);
return false;
}
}
return true;
}
export async function isValidBlock({
executionPayload,
block,
logger,
network,
}: {
executionPayload: allForks.ExecutionPayload;
block: ELBlock;
logger: Logger;
network: NetworkName;
}): Promise<boolean> {
const common = getChainCommon(network);
common.setHardforkByBlockNumber(executionPayload.blockNumber, undefined, executionPayload.timestamp);
const blockObject = Block.fromBlockData(blockDataFromELBlock(block), {common});
if (bufferToHex(executionPayload.blockHash) !== bufferToHex(blockObject.hash())) {
logger.error("Block hash does not match", {
rpcBlockHash: bufferToHex(blockObject.hash()),
beaconExecutionBlockHash: bufferToHex(executionPayload.blockHash),
});
return false;
}
if (bufferToHex(executionPayload.parentHash) !== bufferToHex(blockObject.header.parentHash)) {
logger.error("Block parent hash does not match", {
rpcBlockHash: bufferToHex(blockObject.header.parentHash),
beaconExecutionBlockHash: bufferToHex(executionPayload.parentHash),
});
return false;
}
if (!(await blockObject.validateTransactionsTrie())) {
logger.error("Block transactions could not be verified.", {
blockHash: bufferToHex(blockObject.hash()),
blockNumber: blockObject.header.number,
});
return false;
}
return true;
}
export async function isValidCodeHash({
codeHash,
codeResponse,
}: {
codeHash: string;
codeResponse: string;
logger: Logger;
}): Promise<boolean> {
// if there is no code hash for that address
if (codeResponse === "0x" && codeHash === `0x${KECCAK256_NULL_S}`) return true;
return bufferToHex(keccak256(hexToBuffer(codeResponse))) === codeHash;
}

View File

@@ -1,158 +1,100 @@
import {Block} from "@ethereumjs/block";
import {Common, CustomChain, Hardfork} from "@ethereumjs/common";
import {RLP} from "@ethereumjs/rlp";
import {Trie} from "@ethereumjs/trie";
import {Account, KECCAK256_NULL_S} from "@ethereumjs/util";
import {keccak256} from "ethereum-cryptography/keccak.js";
import {NetworkName} from "@lodestar/config/networks";
import {Bytes32, allForks} from "@lodestar/types";
import {Logger} from "@lodestar/utils";
import {ELBlock, ELProof, ELStorageProof, HexString} from "../types.js";
import {blockDataFromELBlock, bufferToHex, hexToBuffer, padLeft} from "./conversion.js";
import {ELRequestHandlerAny} from "../interfaces.js";
import {ProofProvider} from "../proof_provider/proof_provider.js";
import {ELBlock, ELProof, ELRequestPayload, HexString} from "../types.js";
import {bufferToHex} from "./conversion.js";
import {getELBlock, getELCode, getELProof} from "./execution.js";
import {isValidAccount, isValidBlock, isValidCodeHash, isValidStorageKeys} from "./validation.js";
const emptyAccountSerialize = new Account().serialize();
const storageKeyLength = 32;
type VerificationResult<T> = {data: T; valid: true} | {valid: false; data?: undefined};
export async function isValidAccount({
export async function verifyAccount({
address,
stateRoot,
proof,
proofProvider,
logger,
handler,
block,
}: {
address: HexString;
stateRoot: Bytes32;
proof: ELProof;
handler: ELRequestHandlerAny;
proofProvider: ProofProvider;
logger: Logger;
}): Promise<boolean> {
const trie = await Trie.create();
const key = keccak256(hexToBuffer(address));
block?: number | string;
}): Promise<VerificationResult<ELProof>> {
const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
const proof = await getELProof(handler, [address, [], bufferToHex(executionPayload.blockHash)]);
const validAccount = await isValidAccount({
address: address,
stateRoot: executionPayload.stateRoot,
proof,
logger,
});
try {
const expectedAccountRLP = await trie.verifyProof(
Buffer.from(stateRoot),
Buffer.from(key),
proof.accountProof.map(hexToBuffer)
);
// If account is invalid don't check the storage
const validStorage = validAccount && (await isValidStorageKeys({storageKeys: [], proof, logger}));
// Shresth Agrawal (2022) Patronum source code. https://github.com/lightclients/patronum
const account = Account.fromAccountData({
nonce: BigInt(proof.nonce),
balance: BigInt(proof.balance),
storageRoot: proof.storageHash,
codeHash: proof.codeHash,
});
return account.serialize().equals(expectedAccountRLP ? expectedAccountRLP : emptyAccountSerialize);
} catch (err) {
logger.error("Error verifying account proof", undefined, err as Error);
return false;
if (validAccount && validStorage) {
return {data: proof, valid: true};
}
return {valid: false};
}
export async function isValidStorageKeys({
storageKeys,
proof,
export async function verifyCode({
address,
proofProvider,
logger,
}: {
storageKeys: HexString[];
proof: ELStorageProof;
logger: Logger;
}): Promise<boolean> {
const trie = await Trie.create();
for (let i = 0; i < storageKeys.length; i++) {
const sp = proof.storageProof[i];
const key = keccak256(padLeft(hexToBuffer(storageKeys[i]), storageKeyLength));
try {
const expectedStorageRLP = await trie.verifyProof(
hexToBuffer(proof.storageHash),
Buffer.from(key),
sp.proof.map(hexToBuffer)
);
const isStorageValid =
(!expectedStorageRLP && sp.value === "0x0") ||
(!!expectedStorageRLP && expectedStorageRLP.equals(RLP.encode(sp.value)));
if (!isStorageValid) return false;
} catch (err) {
logger.error("Error verifying storage keys", undefined, err as Error);
return false;
}
}
return true;
}
function networkToChainCommon(network: NetworkName): Common {
switch (network) {
case "mainnet":
case "goerli":
case "ropsten":
case "sepolia":
// TODO: Not sure how to detect the fork during runtime
return new Common({chain: network, hardfork: Hardfork.Shanghai});
case "gnosis":
return new Common({chain: CustomChain.xDaiChain});
default:
throw new Error(`Non supported network "${network}"`);
}
}
export async function isValidBlock({
executionPayload,
handler,
codeHash,
block,
}: {
address: HexString;
handler: ELRequestHandlerAny;
proofProvider: ProofProvider;
logger: Logger;
codeHash: HexString;
block?: number | string;
}): Promise<VerificationResult<string>> {
const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
const code = await getELCode(handler, [address, bufferToHex(executionPayload.blockHash)]);
if (await isValidCodeHash({codeHash, codeResponse: code, logger})) {
return {data: code, valid: true};
}
return {valid: false};
}
export async function verifyBlock({
payload,
proofProvider,
logger,
handler,
network,
}: {
executionPayload: allForks.ExecutionPayload;
block: ELBlock;
payload: ELRequestPayload<[block: string | number, hydrated: boolean]>;
handler: ELRequestHandlerAny;
proofProvider: ProofProvider;
logger: Logger;
network: NetworkName;
}): Promise<boolean> {
const common = networkToChainCommon(network);
common.setHardforkByBlockNumber(executionPayload.blockNumber, undefined, executionPayload.timestamp);
}): Promise<VerificationResult<ELBlock>> {
const executionPayload = await proofProvider.getExecutionPayload(payload.params[0]);
const block = await getELBlock(handler, payload.params);
const blockObject = Block.fromBlockData(blockDataFromELBlock(block), {common});
// If response is not valid from the EL we don't need to verify it
if (!block) return {data: block, valid: false};
if (bufferToHex(executionPayload.blockHash) !== bufferToHex(blockObject.hash())) {
logger.error("Block hash does not match", {
rpcBlockHash: bufferToHex(blockObject.hash()),
beaconExecutionBlockHash: bufferToHex(executionPayload.blockHash),
});
return false;
if (
await isValidBlock({
logger,
block,
executionPayload,
network,
})
) {
return {data: block, valid: true};
}
if (bufferToHex(executionPayload.parentHash) !== bufferToHex(blockObject.header.parentHash)) {
logger.error("Block parent hash does not match", {
rpcBlockHash: bufferToHex(blockObject.header.parentHash),
beaconExecutionBlockHash: bufferToHex(executionPayload.parentHash),
});
return false;
}
if (!(await blockObject.validateTransactionsTrie())) {
logger.error("Block transactions could not be verified.", {
blockHash: bufferToHex(blockObject.hash()),
blockNumber: blockObject.header.number,
});
return false;
}
return true;
}
export async function isValidCodeHash({
codeHash,
codeResponse,
}: {
codeHash: string;
codeResponse: string;
logger: Logger;
}): Promise<boolean> {
// if there is no code hash for that address
if (codeResponse === "0x" && codeHash === `0x${KECCAK256_NULL_S}`) return true;
return bufferToHex(keccak256(hexToBuffer(codeResponse))) === codeHash;
return {valid: false};
}

View File

@@ -0,0 +1,46 @@
import {ELVerifiedRequestHandler} from "../interfaces.js";
import {ELApiHandlers, ELApiParams, ELApiReturn} from "../types.js";
import {createEVM, executeEVM, getEVMWithState} from "../utils/evm.js";
import {generateRPCResponseForPayload, generateUnverifiedResponseForPayload} from "../utils/json_rpc.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
export const eth_call: ELVerifiedRequestHandler<ELApiParams["call"], ELApiReturn["call"]> = async ({
handler,
payload,
logger,
proofProvider,
network,
}) => {
const {
params: [tx, block],
} = payload;
// We assume that standard tx validation already been done by the caller (web3, ethers.js, etc.)
const executionPayload = await proofProvider.getExecutionPayload(block ?? "latest");
try {
// TODO: Optimize the creation of the evm
const evm = await createEVM({proofProvider, network});
const evmWithState = await getEVMWithState({
handler: handler as unknown as ELApiHandlers["eth_getProof"],
executionPayload,
tx,
evm,
logger,
});
const result = await executeEVM({
evm: evmWithState,
tx,
handler: handler as unknown as ELApiHandlers["eth_getBlockByHash"],
executionPayload,
});
return generateRPCResponseForPayload(payload, result);
} catch (err) {
logger.error(
"Request could not be verified.",
{method: payload.method, params: JSON.stringify(payload.params)},
err as Error
);
return generateUnverifiedResponseForPayload(payload, "eth_call request can not be verified.");
}
};

View File

@@ -1,5 +1,5 @@
import {ELVerifiedRequestHandler} from "../interfaces.js";
import {fetchAndVerifyAccount} from "../utils/execution.js";
import {verifyAccount} from "../utils/verification.js";
import {generateRPCResponseForPayload, generateUnverifiedResponseForPayload} from "../utils/json_rpc.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -12,12 +12,12 @@ export const eth_getBalance: ELVerifiedRequestHandler<[address: string, block?:
const {
params: [address, block],
} = payload;
const result = await fetchAndVerifyAccount({proofProvider, logger, handler, address, block});
const result = await verifyAccount({proofProvider, logger, handler, address, block});
if (result.valid) {
return generateRPCResponseForPayload(payload, result.data.balance);
}
logger.error("Request could not be verified.");
logger.error("Request could not be verified.", {method: payload.method, params: JSON.stringify(payload.params)});
return generateUnverifiedResponseForPayload(payload, "eth_getBalance request can not be verified.");
};

View File

@@ -1,7 +1,7 @@
import {ELVerifiedRequestHandler} from "../interfaces.js";
import {ELBlock} from "../types.js";
import {fetchAndVerifyBlock} from "../utils/execution.js";
import {generateUnverifiedResponseForPayload} from "../utils/json_rpc.js";
import {verifyBlock} from "../utils/verification.js";
import {generateUnverifiedResponseForPayload, generateVerifiedResponseForPayload} from "../utils/json_rpc.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
export const eth_getBlockByHash: ELVerifiedRequestHandler<[block: string, hydrated: boolean], ELBlock> = async ({
@@ -11,12 +11,12 @@ export const eth_getBlockByHash: ELVerifiedRequestHandler<[block: string, hydrat
proofProvider,
network,
}) => {
const result = await fetchAndVerifyBlock({payload, proofProvider, logger, handler, network});
const result = await verifyBlock({payload, proofProvider, logger, handler, network});
if (result.valid) {
return result.data;
return generateVerifiedResponseForPayload(payload, result.data);
}
logger.error("Request could not be verified.");
logger.error("Request could not be verified.", {method: payload.method, params: JSON.stringify(payload.params)});
return generateUnverifiedResponseForPayload(payload, "eth_getBlockByHash request can not be verified.");
};

View File

@@ -1,19 +1,19 @@
import {ELVerifiedRequestHandler} from "../interfaces.js";
import {ELBlock} from "../types.js";
import {fetchAndVerifyBlock} from "../utils/execution.js";
import {generateUnverifiedResponseForPayload} from "../utils/json_rpc.js";
import {verifyBlock} from "../utils/verification.js";
import {generateUnverifiedResponseForPayload, generateVerifiedResponseForPayload} from "../utils/json_rpc.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
export const eth_getBlockByNumber: ELVerifiedRequestHandler<
[block: string | number, hydrated: boolean],
ELBlock
> = async ({handler, payload, logger, proofProvider, network}) => {
const result = await fetchAndVerifyBlock({payload, proofProvider, logger, handler, network});
const result = await verifyBlock({payload, proofProvider, logger, handler, network});
if (result.valid) {
return result.data;
return generateVerifiedResponseForPayload(payload, result.data);
}
logger.error("Request could not be verified.");
logger.error("Request could not be verified.", {method: payload.method, params: JSON.stringify(payload.params)});
return generateUnverifiedResponseForPayload(payload, "eth_getBlockByNumber request can not be verified.");
};

View File

@@ -1,5 +1,5 @@
import {ELVerifiedRequestHandler} from "../interfaces.js";
import {fetchAndVerifyAccount, fetchAndVerifyCode} from "../utils/execution.js";
import {verifyAccount, verifyCode} from "../utils/verification.js";
import {generateRPCResponseForPayload, generateUnverifiedResponseForPayload} from "../utils/json_rpc.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -13,7 +13,7 @@ export const eth_getCode: ELVerifiedRequestHandler<[address: string, block?: num
params: [address, block],
} = payload;
// TODO: When batch requests are supported merged these two requests into one
const accountProof = await fetchAndVerifyAccount({
const accountProof = await verifyAccount({
proofProvider,
logger,
handler,
@@ -22,11 +22,11 @@ export const eth_getCode: ELVerifiedRequestHandler<[address: string, block?: num
});
if (!accountProof.valid) {
logger.error("Request could not be verified.");
logger.error("Request could not be verified.", {method: payload.method, params: JSON.stringify(payload.params)});
return generateUnverifiedResponseForPayload(payload, "account for eth_getCode request can not be verified.");
}
const codeProof = await fetchAndVerifyCode({
const codeProof = await verifyCode({
proofProvider,
logger,
handler,
@@ -39,6 +39,6 @@ export const eth_getCode: ELVerifiedRequestHandler<[address: string, block?: num
return generateRPCResponseForPayload(payload, codeProof.data);
}
logger.error("Request could not be verified.");
logger.error("Request could not be verified.", {method: payload.method, params: JSON.stringify(payload.params)});
return generateUnverifiedResponseForPayload(payload, "eth_getCode request can not be verified.");
};

View File

@@ -1,5 +1,5 @@
import {ELVerifiedRequestHandler} from "../interfaces.js";
import {fetchAndVerifyAccount} from "../utils/execution.js";
import {verifyAccount} from "../utils/verification.js";
import {generateRPCResponseForPayload, generateUnverifiedResponseForPayload} from "../utils/json_rpc.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -10,12 +10,12 @@ export const eth_getTransactionCount: ELVerifiedRequestHandler<
const {
params: [address, block],
} = payload;
const result = await fetchAndVerifyAccount({proofProvider, logger, handler, address, block});
const result = await verifyAccount({proofProvider, logger, handler, address, block});
if (result.valid) {
return generateRPCResponseForPayload(payload, result.data.nonce);
}
logger.error("Request could not be verified.");
logger.error("Request could not be verified.", {method: payload.method, params: JSON.stringify(payload.params)});
return generateUnverifiedResponseForPayload(payload, "eth_getTransactionCount request can not be verified.");
};

View File

@@ -21,6 +21,7 @@ import {
} from "./utils/assertion.js";
import {getLogger} from "./utils/logger.js";
import {processAndVerifyRequest} from "./utils/process.js";
import {logRequest, logResponse} from "./utils/json_rpc.js";
type ProvableProviderInitOpts = {network?: NetworkName; wsCheckpoint?: string; signal?: AbortSignal} & LogOptions &
ConsensusNodeOptions;
@@ -79,7 +80,9 @@ function handleSendProvider(
const send = provider.send.bind(provider);
const handler = (payload: ELRequestPayload): Promise<ELResponse | undefined> =>
new Promise((resolve, reject) => {
logRequest(payload, logger);
send(payload, (err, response) => {
logResponse(response, logger);
if (err) {
reject(err);
} else {
@@ -106,7 +109,9 @@ function handleRequestProvider(
const request = provider.request.bind(provider);
const handler = (payload: ELRequestPayload): Promise<ELResponse | undefined> =>
new Promise((resolve, reject) => {
logRequest(payload, logger);
request(payload, (err, response) => {
logResponse(response, logger);
if (err) {
reject(err);
} else {
@@ -131,7 +136,12 @@ function handleSendAsyncProvider(
network: NetworkName
): SendAsyncProvider {
const sendAsync = provider.sendAsync.bind(provider);
const handler = (payload: ELRequestPayload): Promise<ELResponse | undefined> => sendAsync(payload);
const handler = async (payload: ELRequestPayload): Promise<ELResponse | undefined> => {
logRequest(payload, logger);
const response = await sendAsync(payload);
logResponse(response, logger);
return response;
};
async function newSendAsync(payload: ELRequestPayload): Promise<ELResponse | undefined> {
return processAndVerifyRequest({payload, handler, proofProvider, logger, network});
@@ -147,7 +157,12 @@ function handleEIP1193Provider(
network: NetworkName
): EIP1193Provider {
const request = provider.request.bind(provider);
const handler = (payload: ELRequestPayload): Promise<ELResponse | undefined> => request(payload);
const handler = async (payload: ELRequestPayload): Promise<ELResponse | undefined> => {
logRequest(payload, logger);
const response = await request(payload);
logResponse(response, logger);
return response;
};
async function newRequest(payload: ELRequestPayload): Promise<ELResponse | undefined> {
return processAndVerifyRequest({payload, handler, proofProvider, logger, network});
@@ -163,7 +178,12 @@ function handleEthersProvider(
network: NetworkName
): EthersProvider {
const send = provider.send.bind(provider);
const handler = (payload: ELRequestPayload): Promise<ELResponse | undefined> => send(payload.method, payload.params);
const handler = async (payload: ELRequestPayload): Promise<ELResponse | undefined> => {
logRequest(payload, logger);
const response = await send(payload.method, payload.params);
logResponse(response, logger);
return response;
};
async function newSend(method: string, params: Array<unknown>): Promise<ELResponse | undefined> {
return processAndVerifyRequest({

View File

@@ -6,7 +6,7 @@ import {NetworkName} from "@lodestar/config/networks";
import {ConsensusNodeOptions, LogOptions} from "./interfaces.js";
import {ProofProvider} from "./proof_provider/proof_provider.js";
import {ELRequestPayload, ELResponse} from "./types.js";
import {generateRPCResponseForPayload} from "./utils/json_rpc.js";
import {generateRPCResponseForPayload, logRequest, logResponse} from "./utils/json_rpc.js";
import {getLogger} from "./utils/logger.js";
import {fetchRequestPayload, fetchResponseBody} from "./utils/req_resp.js";
import {processAndVerifyRequest} from "./utils/process.js";
@@ -61,10 +61,15 @@ export function createVerifiedExecutionProxy(opts: VerifiedProxyOptions): {
},
},
(res) => {
fetchResponseBody(res).then(resolve).catch(reject);
fetchResponseBody(res)
.then((response) => {
logResponse(response, logger);
resolve(response);
})
.catch(reject);
}
);
logger.debug("Sending request to proxy endpoint", {method: payload.method});
logRequest(payload, logger);
req.write(JSON.stringify(payload));
req.end();
});
@@ -73,7 +78,7 @@ export function createVerifiedExecutionProxy(opts: VerifiedProxyOptions): {
logger.info("Creating http server");
const proxyServer = http.createServer(function proxyRequestHandler(req, res) {
if (req.url === "/proxy") {
logger.verbose("Forwarding request to execution layer");
logger.debug("Forwarding request to execution layer");
proxy.web(req, res);
return;
}

View File

@@ -12,11 +12,11 @@ describe("web3_provider", () => {
describe("web3", () => {
it("should connect to the network and call non-verified method", async () => {
const {provider} = createVerifiedExecutionProvider(
new Web3.providers.HttpProvider("https://lodestar-sepoliarpc.chainsafe.io"),
new Web3.providers.HttpProvider("https://lodestar-mainnetrpc.chainsafe.io"),
{
transport: LCTransport.Rest,
urls: ["https://lodestar-sepolia.chainsafe.io"],
network: "sepolia",
urls: ["https://lodestar-mainnet.chainsafe.io"],
network: "mainnet",
}
);
@@ -30,11 +30,11 @@ describe("web3_provider", () => {
describe("ethers", () => {
it("should connect to the network and call non-verified method", async () => {
const {provider} = createVerifiedExecutionProvider(
new ethers.JsonRpcProvider("https://lodestar-sepoliarpc.chainsafe.io"),
new ethers.JsonRpcProvider("https://lodestar-mainnetrpc.chainsafe.io"),
{
transport: LCTransport.Rest,
urls: ["https://lodestar-sepolia.chainsafe.io"],
network: "sepolia",
urls: ["https://lodestar-mainnet.chainsafe.io"],
network: "mainnet",
}
);
await expect(provider.send("eth_getProof", ["0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134", [], "latest"]))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,75 @@
import sinon from "sinon";
import {NetworkName} from "@lodestar/config/networks";
import {ForkConfig} from "@lodestar/config";
import {ELVerifiedRequestHandlerOpts} from "../../src/interfaces.js";
import {createMockLogger} from "../mocks/logger_mock.js";
import {ProofProvider} from "../../src/proof_provider/proof_provider.js";
import {ELRequestPayload, ELResponse} from "../../src/types.js";
import {ELBlock} from "../../src/types.js";
type Writeable<T> = {
-readonly [K in keyof T]?: T[K] extends object ? Writeable<T[K]> : T[K];
};
export interface TestFixture<R = unknown, P = unknown[]> {
label: string;
network: string;
request: ELRequestPayload<P>;
response: Writeable<ELResponse<R>>;
execution: {
block: ELBlock;
};
beacon: {
executionPayload: Record<string, unknown>;
headers: {header: {message: {slot: string}}};
};
dependentRequests: {payload: ELRequestPayload; response: Writeable<ELResponse>}[];
}
export function generateReqHandlerOptionsMock(
data: TestFixture,
config: ForkConfig
): Omit<ELVerifiedRequestHandlerOpts<any, any>, "payload"> {
const executionPayload = config
.getExecutionForkTypes(parseInt(data.beacon.headers.header.message.slot))
.ExecutionPayload.fromJson(data.beacon.executionPayload);
const options = {
handler: sinon.stub(),
logger: createMockLogger(),
proofProvider: {
getExecutionPayload: sinon.stub().resolves(executionPayload),
} as unknown as ProofProvider,
network: data.network as NetworkName,
};
options.handler
.withArgs({jsonrpc: sinon.match.any, id: sinon.match.any, method: data.request.method, params: data.request.params})
.resolves(data.response);
for (const req of data.dependentRequests) {
options.handler
.withArgs({jsonrpc: sinon.match.any, id: sinon.match.any, method: req.payload.method, params: req.payload.params})
.resolves(req.response);
}
options.handler
.withArgs({
jsonrpc: sinon.match.any,
id: sinon.match.any,
method: "eth_getBlockByNumber",
params: [data.execution.block.number, true],
})
.resolves({id: 1233, jsonrpc: "2.0", result: data.execution.block});
options.handler
.withArgs({
jsonrpc: sinon.match.any,
id: sinon.match.any,
method: "eth_getBlockByHash",
params: [data.execution.block.hash, true],
})
.resolves({id: 1233, jsonrpc: "2.0", result: data.execution.block});
return options;
}

View File

@@ -3,15 +3,15 @@ import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import deepmerge from "deepmerge";
import {ELProof, ELStorageProof} from "../../../src/types.js";
import {isValidAccount, isValidStorageKeys} from "../../../src/utils/verification.js";
import {isValidAccount, isValidStorageKeys} from "../../../src/utils/validation.js";
import {invalidStorageProof, validStorageProof} from "../../fixtures/index.js";
import {createMockLogger} from "../../mocks/logger_mock.js";
import eoaProof from "../../fixtures/sepolia/eth_getBalance_eoa_proof.json" assert {type: "json"};
import eoaProof from "../../fixtures/sepolia/eth_getBalance_eoa.json" assert {type: "json"};
import {hexToBuffer} from "../../../src/utils/conversion.js";
const address = eoaProof.request.params[0] as string;
const validAccountProof = eoaProof.response.result as unknown as ELProof;
const validStateRoot = hexToBuffer(eoaProof.executionPayload.state_root);
const validAccountProof = eoaProof.dependentRequests[0].response.result as unknown as ELProof;
const validStateRoot = hexToBuffer(eoaProof.beacon.executionPayload.state_root);
const invalidAccountProof = deepmerge(validAccountProof, {});
delete invalidAccountProof.accountProof[0];

View File

@@ -0,0 +1,62 @@
import {expect} from "chai";
import deepmerge from "deepmerge";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {ELTransaction} from "../../../lib/types.js";
import {UNVERIFIED_RESPONSE_CODE} from "../../../src/constants.js";
import {eth_call} from "../../../src/verified_requests/eth_call.js";
import ethCallCase1 from "../../fixtures/mainnet/eth_call.json" assert {type: "json"};
import {generateReqHandlerOptionsMock} from "../../mocks/request_handler.js";
const testCases = [ethCallCase1];
describe("verified_requests / eth_call", () => {
for (const t of testCases) {
describe(t.label, () => {
it("should return the valid json-rpc response for a valid call", async () => {
const testCase = deepmerge({}, t);
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const options = generateReqHandlerOptionsMock(testCase, config);
const response = await eth_call({
...options,
payload: {
...testCase.request,
params: testCase.request.params as [ELTransaction, string],
},
});
expect(response).to.eql(testCase.response);
});
it("should return the json-rpc response with error for an invalid call", async () => {
const testCase = deepmerge(t, {});
// Temper the responses to make them invalid
for (const tx of testCase.dependentRequests) {
if (tx.payload.method === "eth_getCode") {
tx.response.result = `${tx.response.result}12`;
}
}
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const options = generateReqHandlerOptionsMock(testCase, config);
const response = await eth_call({
...options,
payload: {
...testCase.request,
params: testCase.request.params as [ELTransaction, string],
},
});
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_call request can not be verified."},
});
});
});
}
});

View File

@@ -1,79 +1,51 @@
import {expect} from "chai";
import sinon from "sinon";
import deepmerge from "deepmerge";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {Logger} from "@lodestar/utils";
import {UNVERIFIED_RESPONSE_CODE} from "../../../src/constants.js";
import {ELVerifiedRequestHandlerOpts} from "../../../src/interfaces.js";
import {eth_getBalance} from "../../../src/verified_requests/eth_getBalance.js";
import eth_getBalance_eoa from "../../fixtures/sepolia/eth_getBalance_eoa_proof.json" assert {type: "json"};
import eth_getBalance_contract from "../../fixtures/sepolia/eth_getBalance_contract_proof.json" assert {type: "json"};
import {createMockLogger} from "../../mocks/logger_mock.js";
import eth_getBalance_eoa from "../../fixtures/sepolia/eth_getBalance_eoa.json" assert {type: "json"};
import eth_getBalance_contract from "../../fixtures/sepolia/eth_getBalance_contract.json" assert {type: "json"};
import {generateReqHandlerOptionsMock} from "../../mocks/request_handler.js";
const testCases = [eth_getBalance_eoa, eth_getBalance_contract];
describe("verified_requests / eth_getBalance", () => {
let options: {handler: sinon.SinonStub; logger: Logger; proofProvider: {getExecutionPayload: sinon.SinonStub}};
beforeEach(() => {
options = {
handler: sinon.stub(),
logger: createMockLogger(),
proofProvider: {getExecutionPayload: sinon.stub()},
};
});
for (const testCase of testCases) {
describe(testCase.label, () => {
it("should return the valid json-rpc response for a valid account", async () => {
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const data = deepmerge({}, testCase);
const config = createForkConfig(networksChainConfig[data.network as NetworkName]);
const options = generateReqHandlerOptionsMock(data, config);
options.handler.resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBalance({
const response = await eth_getBalance({
...options,
payload: {
...testCase.request,
method: "eth_getBalance",
params: [testCase.request.params[0], "latest"],
...data.request,
params: [data.request.params[0], data.request.params[1]],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[address: string, block?: string | number | undefined], string>);
expect(result).to.eql({...result, result: testCase.response.result.balance});
});
expect(response).to.eql(data.response);
});
it("should return the json-rpc response with error for an invalid account", async () => {
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const data = deepmerge({}, testCase);
// Temporarily remove the accountProof to make the request invalid
delete data.dependentRequests[0].response.result.accountProof[0];
const config = createForkConfig(networksChainConfig[data.network as NetworkName]);
const options = generateReqHandlerOptionsMock(data, config);
const response = deepmerge({}, testCase.response);
// Change the proof to be invalidated with state
delete response.result.accountProof[0];
options.handler.resolves(response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBalance({
const response = await eth_getBalance({
...options,
payload: {
...testCase.request,
method: "eth_getBalance",
params: [testCase.request.params[0], "latest"],
...data.request,
params: [data.request.params[0], data.request.params[1]],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[address: string, block?: string | number | undefined], string>);
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
id: data.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBalance request can not be verified."},
});
});

View File

@@ -1,74 +1,54 @@
import {expect} from "chai";
import sinon from "sinon";
import deepmerge from "deepmerge";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {Logger} from "@lodestar/utils";
import {UNVERIFIED_RESPONSE_CODE} from "../../../src/constants.js";
import {ELVerifiedRequestHandlerOpts} from "../../../src/interfaces.js";
import {ELBlock} from "../../../src/types.js";
import {eth_getBlockByHash} from "../../../src/verified_requests/eth_getBlockByHash.js";
import eth_getBlock_with_contractCreation from "../../fixtures/sepolia/eth_getBlock_with_contractCreation.json" assert {type: "json"};
import eth_getBlock_with_no_accessList from "../../fixtures/sepolia/eth_getBlock_with_no_accessList.json" assert {type: "json"};
import {createMockLogger} from "../../mocks/logger_mock.js";
import {TestFixture, generateReqHandlerOptionsMock} from "../../mocks/request_handler.js";
import {ELBlock} from "../../../src/types.js";
const testCases = [eth_getBlock_with_no_accessList, eth_getBlock_with_contractCreation];
const testCases = [eth_getBlock_with_no_accessList, eth_getBlock_with_contractCreation] as [
TestFixture<ELBlock>,
TestFixture<ELBlock>
];
describe("verified_requests / eth_getBlockByHash", () => {
let options: {handler: sinon.SinonStub; logger: Logger; proofProvider: {getExecutionPayload: sinon.SinonStub}};
beforeEach(() => {
options = {
handler: sinon.stub(),
logger: createMockLogger(),
proofProvider: {getExecutionPayload: sinon.stub()},
};
});
for (const testCase of testCases) {
describe(testCase.label, () => {
for (const t of testCases) {
describe(t.label, () => {
it("should return the valid json-rpc response for a valid block", async () => {
const testCase = deepmerge({}, t);
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
options.handler.resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByHash({
const response = await eth_getBlockByHash({
...options,
payload: testCase.request,
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string, hydrated: boolean], ELBlock>);
expect(result).to.eql(testCase.response);
payload: {
...testCase.request,
params: testCase.request.params as [string, boolean],
},
});
expect(response).to.eql(testCase.response);
});
it("should return the json-rpc response with error for an invalid block header with valid execution payload", async () => {
// Temper the block body
const testCase = deepmerge(t, {
execution: {block: {parentHash: "0x123490ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d1234"}},
});
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
const temperedResponse = {
...testCase.response,
result: {
...testCase.response.result,
parentHash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4",
},
};
options.handler.resolves(temperedResponse);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByHash({
const response = await eth_getBlockByHash({
...options,
payload: testCase.request,
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string, hydrated: boolean], ELBlock>);
payload: {
...testCase.request,
params: testCase.request.params as [string, boolean],
},
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBlockByHash request can not be verified."},
@@ -76,24 +56,22 @@ describe("verified_requests / eth_getBlockByHash", () => {
});
it("should return the json-rpc response with error for an invalid block body with valid execution payload", async () => {
// Temper the block body
const testCase = deepmerge(t, {
execution: {block: {transactions: [{to: "0xd86e1fedb1120369ff5175b74f4413cb74fcacdb"}]}},
});
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
const temperedResponse = deepmerge<typeof testCase.response, unknown>(testCase.response, {});
temperedResponse.result.transactions[0].to = "0xd86e1fedb1120369ff5175b74f4413cb74fcacdb";
options.handler.resolves(temperedResponse);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByHash({
const response = await eth_getBlockByHash({
...options,
payload: testCase.request,
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string, hydrated: boolean], ELBlock>);
payload: {
...testCase.request,
params: testCase.request.params as [string, boolean],
},
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBlockByHash request can not be verified."},
@@ -101,27 +79,22 @@ describe("verified_requests / eth_getBlockByHash", () => {
});
it("should return the json-rpc response with error for an valid block with invalid execution payload", async () => {
const testCase = deepmerge({}, t);
// Temper the execution payload
testCase.beacon.executionPayload.parent_hash =
"0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4";
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const temperedPayload = {
...testCase.executionPayload,
// eslint-disable-next-line @typescript-eslint/naming-convention
parent_hash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4",
};
const options = generateReqHandlerOptionsMock(testCase, config);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(temperedPayload);
options.handler.resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByHash({
const response = await eth_getBlockByHash({
...options,
payload: testCase.request,
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string, hydrated: boolean], ELBlock>);
payload: {
...testCase.request,
params: testCase.request.params as [string, boolean],
},
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBlockByHash request can not be verified."},

View File

@@ -1,80 +1,54 @@
import {expect} from "chai";
import sinon from "sinon";
import deepmerge from "deepmerge";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {Logger} from "@lodestar/utils";
import {UNVERIFIED_RESPONSE_CODE} from "../../../src/constants.js";
import {ELVerifiedRequestHandlerOpts} from "../../../src/interfaces.js";
import {ELBlock} from "../../../src/types.js";
import {eth_getBlockByNumber} from "../../../src/verified_requests/eth_getBlockByNumber.js";
import eth_getBlock_with_contractCreation from "../../fixtures/sepolia/eth_getBlock_with_contractCreation.json" assert {type: "json"};
import eth_getBlock_with_no_accessList from "../../fixtures/sepolia/eth_getBlock_with_no_accessList.json" assert {type: "json"};
import {createMockLogger} from "../../mocks/logger_mock.js";
import {eth_getBlockByNumber} from "../../../src/verified_requests/eth_getBlockByNumber.js";
import {TestFixture, generateReqHandlerOptionsMock} from "../../mocks/request_handler.js";
const testCases = [eth_getBlock_with_no_accessList, eth_getBlock_with_contractCreation];
const testCases = [eth_getBlock_with_no_accessList, eth_getBlock_with_contractCreation] as [
TestFixture<ELBlock>,
TestFixture<ELBlock>
];
describe("verified_requests / eth_getBlockByNumber", () => {
let options: {handler: sinon.SinonStub; logger: Logger; proofProvider: {getExecutionPayload: sinon.SinonStub}};
beforeEach(() => {
options = {
handler: sinon.stub(),
logger: createMockLogger(),
proofProvider: {getExecutionPayload: sinon.stub()},
};
});
for (const testCase of testCases) {
describe(testCase.label, () => {
for (const t of testCases) {
describe(t.label, () => {
it("should return the valid json-rpc response for a valid block", async () => {
const testCase = deepmerge({}, t);
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
options.handler.resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByNumber({
const response = await eth_getBlockByNumber({
...options,
payload: {
...testCase.request,
params: [testCase.executionPayload.block_number, false],
params: testCase.request.params as [string | number, boolean],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string | number, hydrated: boolean], ELBlock>);
expect(result).to.eql(testCase.response);
});
expect(response).to.eql(testCase.response);
});
it("should return the json-rpc response with error for an invalid block header with valid execution payload", async () => {
// Temper the block body
const testCase = deepmerge(t, {
execution: {block: {parentHash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4"}},
});
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
const temperedResponse = {
...testCase.response,
result: {
...testCase.response.result,
parentHash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4",
},
};
options.handler.resolves(temperedResponse);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByNumber({
const response = await eth_getBlockByNumber({
...options,
payload: {
...testCase.request,
params: [testCase.executionPayload.block_number, false],
params: testCase.request.params as [string | number, boolean],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string | number, hydrated: boolean], ELBlock>);
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBlockByNumber request can not be verified."},
@@ -82,27 +56,22 @@ describe("verified_requests / eth_getBlockByNumber", () => {
});
it("should return the json-rpc response with error for an invalid block body with valid execution payload", async () => {
// Temper the block body
const testCase = deepmerge(t, {
execution: {block: {transactions: [{to: "0xd86e1fedb1120369ff5175b74f4413cb74fcacdb"}]}},
});
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
const temperedResponse = deepmerge<typeof testCase.response, unknown>(testCase.response, {});
temperedResponse.result.transactions[0].to = "0xd86e1fedb1120369ff5175b74f4413cb74fcacdb";
options.handler.resolves(temperedResponse);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByNumber({
const response = await eth_getBlockByNumber({
...options,
payload: {
...testCase.request,
params: [testCase.executionPayload.block_number, false],
params: testCase.request.params as [string | number, boolean],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string | number, hydrated: boolean], ELBlock>);
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBlockByNumber request can not be verified."},
@@ -110,30 +79,25 @@ describe("verified_requests / eth_getBlockByNumber", () => {
});
it("should return the json-rpc response with error for an valid block with invalid execution payload", async () => {
// Temper the execution payload
const testCase = deepmerge(t, {
beacon: {
// eslint-disable-next-line @typescript-eslint/naming-convention
executionPayload: {parent_hash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4"},
},
});
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const temperedPayload = {
...testCase.executionPayload,
// eslint-disable-next-line @typescript-eslint/naming-convention
parent_hash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4",
};
const options = generateReqHandlerOptionsMock(testCase, config);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(temperedPayload);
options.handler.resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getBlockByNumber({
const response = await eth_getBlockByNumber({
...options,
payload: {
...testCase.request,
params: [testCase.executionPayload.block_number, false],
params: testCase.request.params as [string | number, boolean],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[block: string | number, hydrated: boolean], ELBlock>);
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getBlockByNumber request can not be verified."},

View File

@@ -1,70 +1,47 @@
import {expect} from "chai";
import sinon from "sinon";
import deepmerge from "deepmerge";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {Logger} from "@lodestar/utils";
import {UNVERIFIED_RESPONSE_CODE} from "../../../src/constants.js";
import {ELVerifiedRequestHandlerOpts} from "../../../src/interfaces.js";
import {eth_getCode} from "../../../src/verified_requests/eth_getCode.js";
import eth_getCodeCase1 from "../../fixtures/sepolia/eth_getCode.json" assert {type: "json"};
import ethContractProof from "../../fixtures/sepolia/eth_getBalance_contract_proof.json" assert {type: "json"};
import {createMockLogger} from "../../mocks/logger_mock.js";
import ethGetCodeCase1 from "../../fixtures/sepolia/eth_getCode.json" assert {type: "json"};
import {generateReqHandlerOptionsMock} from "../../mocks/request_handler.js";
const testCases = [eth_getCodeCase1];
const testCases = [ethGetCodeCase1];
describe("verified_requests / eth_getCode", () => {
let options: {handler: sinon.SinonStub; logger: Logger; proofProvider: {getExecutionPayload: sinon.SinonStub}};
beforeEach(() => {
options = {
handler: sinon.stub(),
logger: createMockLogger(),
proofProvider: {getExecutionPayload: sinon.stub()},
};
});
for (const testCase of testCases) {
describe(testCase.label, () => {
for (const t of testCases) {
describe(t.label, () => {
it("should return the valid json-rpc response for a valid account", async () => {
const testCase = deepmerge({}, t);
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
options.handler.onFirstCall().resolves(ethContractProof.response);
options.handler.onSecondCall().resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getCode({
const response = await eth_getCode({
...options,
payload: testCase.request,
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[address: string, block?: string | number | undefined], string>);
payload: {
...testCase.request,
params: testCase.request.params as [string, string],
},
});
expect(result).to.eql(testCase.response);
expect(response).to.eql(testCase.response);
});
it("should return the json-rpc response with error for an invalid account", async () => {
const testCase = deepmerge(t, {response: {result: t.response.result + "1234fe"}});
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
const temperedResponse = deepmerge({}, testCase.response);
temperedResponse.result = temperedResponse.result + "1234fe";
options.handler.onFirstCall().resolves(ethContractProof.response);
options.handler.onSecondCall().resolves(temperedResponse);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getCode({
const response = await eth_getCode({
...options,
payload: testCase.request,
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[address: string, block?: string | number | undefined], string>);
payload: {
...testCase.request,
params: testCase.request.params as [string, string],
},
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getCode request can not be verified."},

View File

@@ -1,77 +1,49 @@
import {expect} from "chai";
import sinon from "sinon";
import deepmerge from "deepmerge";
import {createForkConfig} from "@lodestar/config";
import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
import {Logger} from "@lodestar/utils";
import {UNVERIFIED_RESPONSE_CODE} from "../../../src/constants.js";
import {ELVerifiedRequestHandlerOpts} from "../../../src/interfaces.js";
import eth_getBalance_eoa from "../../fixtures/sepolia/eth_getBalance_eoa_proof.json" assert {type: "json"};
import eth_getBalance_contract from "../../fixtures/sepolia/eth_getBalance_contract_proof.json" assert {type: "json"};
import {createMockLogger} from "../../mocks/logger_mock.js";
import {eth_getTransactionCount} from "../../../src/verified_requests/eth_getTransactionCount.js";
import getTransactionCountCase1 from "../../fixtures/sepolia/eth_getTransactionCount.json" assert {type: "json"};
import {generateReqHandlerOptionsMock} from "../../mocks/request_handler.js";
const testCases = [eth_getBalance_eoa, eth_getBalance_contract];
const testCases = [getTransactionCountCase1];
describe("verified_requests / eth_getTransactionCount", () => {
let options: {handler: sinon.SinonStub; logger: Logger; proofProvider: {getExecutionPayload: sinon.SinonStub}};
beforeEach(() => {
options = {
handler: sinon.stub(),
logger: createMockLogger(),
proofProvider: {getExecutionPayload: sinon.stub()},
};
});
for (const testCase of testCases) {
describe(testCase.label, () => {
for (const t of testCases) {
describe(t.label, () => {
it("should return the valid json-rpc response for a valid account", async () => {
const testCase = deepmerge({}, t);
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
options.handler.resolves(testCase.response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getTransactionCount({
const response = await eth_getTransactionCount({
...options,
payload: {
...testCase.request,
method: "eth_getTransactionCount",
params: [testCase.request.params[0], "latest"],
params: testCase.request.params as [string, string],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[address: string, block?: string | number | undefined], string>);
});
expect(result).to.eql({...result, result: testCase.response.result.nonce});
expect(response).to.eql(testCase.response);
});
it("should return the json-rpc response with error for an invalid account", async () => {
const testCase = deepmerge({}, t);
delete testCase.dependentRequests[0].response.result.accountProof[0];
const config = createForkConfig(networksChainConfig[testCase.network as NetworkName]);
const executionPayload = config
.getExecutionForkTypes(parseInt(testCase.headers.header.message.slot))
.ExecutionPayload.fromJson(testCase.executionPayload);
const options = generateReqHandlerOptionsMock(testCase, config);
const response = deepmerge({}, testCase.response);
// Change the proof to be invalidated with state
delete response.result.accountProof[0];
options.handler.resolves(response);
options.proofProvider.getExecutionPayload.resolves(executionPayload);
const result = await eth_getTransactionCount({
const response = await eth_getTransactionCount({
...options,
payload: {
...testCase.request,
method: "eth_getTransactionCount",
params: [testCase.request.params[0], "latest"],
params: testCase.request.params as [string, string],
},
network: testCase.network,
} as unknown as ELVerifiedRequestHandlerOpts<[address: string, block?: string | number | undefined], string>);
});
expect(result).to.eql({
expect(response).to.eql({
jsonrpc: "2.0",
id: testCase.request.id,
error: {code: UNVERIFIED_RESPONSE_CODE, message: "eth_getTransactionCount request can not be verified."},

View File

@@ -6,7 +6,13 @@ const MAX_DEPTH = 0;
type LogDataBasic = string | number | bigint | boolean | null | undefined;
export type LogData = LogDataBasic | Record<string, LogDataBasic> | LogDataBasic[] | Record<string, LogDataBasic>[];
export type LogData =
| LogDataBasic
| LogDataBasic[]
| Record<string, LogDataBasic>
| Record<string, LogDataBasic>[]
| Record<string, LogDataBasic[]>
| Record<string, LogDataBasic[]>[];
/**
* Renders any log Context to JSON up to one level of depth.

277
yarn.lock
View File

@@ -429,6 +429,11 @@
resolved "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz"
integrity sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==
"@chainsafe/as-sha256@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.4.1.tgz#cfc0737e25f8c206767bdb6703e7943e5d44513e"
integrity sha512-IqeeGwQihK6Y2EYLFofqs2eY2ep1I2MvQXHzOAI+5iQN51OZlUkrLgyAugu2x86xZewDk5xas7lNczkzFzF62w==
"@chainsafe/bls-hd-key@^0.2.0":
version "0.2.1"
resolved "https://registry.npmjs.org/@chainsafe/bls-hd-key/-/bls-hd-key-0.2.1.tgz"
@@ -601,13 +606,6 @@
dependencies:
"@chainsafe/is-ip" "^2.0.1"
"@chainsafe/persistent-merkle-tree@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz#4c9ee80cc57cd3be7208d98c40014ad38f36f7ff"
integrity sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==
dependencies:
"@chainsafe/as-sha256" "^0.3.1"
"@chainsafe/persistent-merkle-tree@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz#2b4a62c9489a5739dedd197250d8d2f5427e9f63"
@@ -615,6 +613,14 @@
dependencies:
"@chainsafe/as-sha256" "^0.3.1"
"@chainsafe/persistent-merkle-tree@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.6.1.tgz#37bde25cf6cbe1660ad84311aa73157dc86ec7f2"
integrity sha512-gcENLemRR13+1MED2NeZBMA7FRS0xQPM7L2vhMqvKkjqtFT4YfjSVADq5U0iLuQLhFUJEMVuA8fbv5v+TN6O9A==
dependencies:
"@chainsafe/as-sha256" "^0.4.1"
"@noble/hashes" "^1.3.0"
"@chainsafe/persistent-ts@^0.19.1":
version "0.19.1"
resolved "https://registry.npmjs.org/@chainsafe/persistent-ts/-/persistent-ts-0.19.1.tgz"
@@ -632,15 +638,6 @@
buffer-from "^1.1.1"
snappy "^6.3.5"
"@chainsafe/ssz@0.9.4":
version "0.9.4"
resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.9.4.tgz#696a8db46d6975b600f8309ad3a12f7c0e310497"
integrity sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==
dependencies:
"@chainsafe/as-sha256" "^0.3.1"
"@chainsafe/persistent-merkle-tree" "^0.4.2"
case "^1.6.3"
"@chainsafe/ssz@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.10.2.tgz#c782929e1bb25fec66ba72e75934b31fd087579e"
@@ -649,6 +646,14 @@
"@chainsafe/as-sha256" "^0.3.1"
"@chainsafe/persistent-merkle-tree" "^0.5.0"
"@chainsafe/ssz@^0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.11.1.tgz#d4aec883af2ec5196ae67b96242c467da20b2476"
integrity sha512-cB8dBkgGN6ZoeOKuk+rIRHKN0L5i9JLGeC0Lui71QX0TuLcQKwgbfkUexpyJxnGFatWf8yeJxlOjozMn/OTP0g==
dependencies:
"@chainsafe/as-sha256" "^0.4.1"
"@chainsafe/persistent-merkle-tree" "^0.6.1"
"@chainsafe/threads@^1.10.0":
version "1.10.0"
resolved "https://registry.npmjs.org/@chainsafe/threads/-/threads-1.10.0.tgz"
@@ -743,18 +748,36 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d"
integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==
"@ethereumjs/block@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-4.2.1.tgz#cb0d8b770fe69c61e6e41d6693d285de76b3a7cb"
integrity sha512-Z/Ty8EkD8o5tvEX5JPrr0pvf60JkSxmwV231aBZ744N75SLvq54dTu/Gk7azC/2xaWhSu1dOp5D5+bryqgG5Cg==
"@ethereumjs/block@^4.2.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-4.2.2.tgz#fddecd34ed559f84ab8eb13098a6dee51a1360ae"
integrity sha512-kMxjeUwJSuLMwnavok5W17ayMNXXsu3hWsllK33XtZgoqt4ywvGo6ABh+xVEqwq/nn/iKuryCpDYYKEyXeFOlA==
dependencies:
"@ethereumjs/common" "^3.1.1"
"@ethereumjs/common" "^3.1.2"
"@ethereumjs/rlp" "^4.0.1"
"@ethereumjs/trie" "^5.0.4"
"@ethereumjs/tx" "^4.1.1"
"@ethereumjs/util" "^8.0.5"
ethereum-cryptography "^1.1.2"
ethers "^5.7.1"
"@ethereumjs/trie" "^5.0.5"
"@ethereumjs/tx" "^4.1.2"
"@ethereumjs/util" "^8.0.6"
ethereum-cryptography "^2.0.0"
"@ethereumjs/blockchain@^6.2.2":
version "6.2.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/blockchain/-/blockchain-6.2.2.tgz#68897a802839b217967083958022601a12afa0ed"
integrity sha512-w1Zjskk35hr0qe0Zfwb88qrEFQJNMo73YrsqtJuBap+WamibEsw0rVuN4Ch+o8Dc66An+8rpk5SxEIK7PHF7KQ==
dependencies:
"@ethereumjs/block" "^4.2.2"
"@ethereumjs/common" "^3.1.2"
"@ethereumjs/ethash" "^2.0.5"
"@ethereumjs/rlp" "^4.0.1"
"@ethereumjs/trie" "^5.0.5"
"@ethereumjs/tx" "^4.1.2"
"@ethereumjs/util" "^8.0.6"
abstract-level "^1.0.3"
debug "^4.3.3"
ethereum-cryptography "^2.0.0"
level "^8.0.0"
lru-cache "^5.1.1"
memory-level "^1.0.0"
"@ethereumjs/common@2.5.0":
version "2.5.0"
@@ -772,28 +795,66 @@
crc-32 "^1.2.0"
ethereumjs-util "^7.1.5"
"@ethereumjs/common@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-3.1.1.tgz#6f754c8933727ad781f63ca3929caab542fe184e"
integrity sha512-iEl4gQtcrj2udNhEizs04z7WA15ez1QoXL0XzaCyaNgwRyXezIg1DnfNeZUUpJnkrOF/0rYXyq2UFSLxt1NPQg==
"@ethereumjs/common@^3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-3.1.2.tgz#c810301b78bcb7526bd690c6d7eb3f4a3c70839d"
integrity sha512-YV+bZfRlFhAXg+FfwC5r4UQKVj4OG7vDP5/JvvNXLLbYpNplH5Vca9jD0L+ab8y0YlTYJMQM1ALyHFu3AE3eBA==
dependencies:
"@ethereumjs/util" "^8.0.5"
"@ethereumjs/util" "^8.0.6"
crc-32 "^1.2.0"
"@ethereumjs/ethash@^2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@ethereumjs/ethash/-/ethash-2.0.5.tgz#577b9d470eea6b61f77d624b58ac90929d6e857d"
integrity sha512-JIPr39Zd9lULLftyzPGHUQmdziElqNWk0EkO1BAw3yns4TVx+BxCYZOkRQ55fuIFeKcXBupAI9V+7xdvIT2CPw==
dependencies:
"@ethereumjs/block" "^4.2.2"
"@ethereumjs/rlp" "^4.0.1"
"@ethereumjs/util" "^8.0.6"
abstract-level "^1.0.3"
bigint-crypto-utils "^3.2.2"
ethereum-cryptography "^2.0.0"
"@ethereumjs/evm@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/evm/-/evm-1.3.2.tgz#3123190b0d021122b183534d7b040a3b241905b8"
integrity sha512-9PzshkvwO8YBkSD9+vyhJuzM6hxfZlljGnuUbXQlTSGEod7we8BRyzJW53W7nw/WRw5U6wf9Q2fpWypfZFkrbw==
dependencies:
"@ethereumjs/common" "^3.1.2"
"@ethereumjs/tx" "^4.1.2"
"@ethereumjs/util" "^8.0.6"
"@ethersproject/providers" "^5.7.1"
debug "^4.3.3"
ethereum-cryptography "^2.0.0"
mcl-wasm "^0.7.1"
rustbn.js "~0.2.0"
"@ethereumjs/rlp@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41"
integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==
"@ethereumjs/trie@^5.0.4":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethereumjs/trie/-/trie-5.0.4.tgz#eb06ce2c7957f6a8f07c0db43fb0335b256f3705"
integrity sha512-ycYtAF7BJAu9eaCtrEX+efE5xEQEfItRXXHBcTSMHsF7NfLHcniI0S7KUVYXbJ6imczBmnMHeggCqv8PYQbbOw==
"@ethereumjs/statemanager@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@ethereumjs/statemanager/-/statemanager-1.0.5.tgz#4496a315d27e60d9a3a036dbe82899f6f20dd2df"
integrity sha512-TVkx9Kgc2NtObCzUTTqrpUggNLnftdmxZybzKPd565Bh98FJJB30FrVkWdPwaIV8oB1d9ADtthttfx5Y/kY9gw==
dependencies:
"@ethereumjs/common" "^3.1.2"
"@ethereumjs/rlp" "^4.0.1"
debug "^4.3.3"
ethereum-cryptography "^2.0.0"
ethers "^5.7.1"
js-sdsl "^4.1.4"
"@ethereumjs/trie@^5.0.5":
version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethereumjs/trie/-/trie-5.0.5.tgz#c232a4913871ffc45bf52cccd214fe5aa24cb3e2"
integrity sha512-H3gHtYxJVGfkT4H05LTJfD1W6h9WZYNkfhTUyAYruNZKFitkSHUM/bEFWH/GIhxt5SAkf283F5uJOx7X2Fr6pQ==
dependencies:
"@ethereumjs/rlp" "^4.0.1"
"@ethereumjs/util" "^8.0.5"
"@ethereumjs/util" "^8.0.6"
"@types/readable-stream" "^2.3.13"
ethereum-cryptography "^1.1.2"
ethereum-cryptography "^2.0.0"
readable-stream "^3.6.0"
"@ethereumjs/tx@3.3.2":
@@ -804,26 +865,45 @@
"@ethereumjs/common" "^2.5.0"
ethereumjs-util "^7.1.2"
"@ethereumjs/tx@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-4.1.1.tgz#d1b5bf2c4fd3618f2f333b66e262848530d4686a"
integrity sha512-QDj7nuROfoeyK83RObMA0XCZ+LUDdneNkSCIekO498uEKTY25FxI4Whduc/6j0wdd4IqpQvkq+/7vxSULjGIBQ==
"@ethereumjs/tx@^4.1.2":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-4.1.2.tgz#10bc6741b74d2404331b82b87f9b2c26177b6f90"
integrity sha512-PWWyO9lAFOiLwk7nB9OQisoJUsuvMz2PN2v4/ILbBpzamC5Ug79OddVq9r4rKvIDLPY+bn4NFerxBJg29+sjaA==
dependencies:
"@chainsafe/ssz" "0.9.4"
"@ethereumjs/common" "^3.1.1"
"@chainsafe/ssz" "^0.11.1"
"@ethereumjs/common" "^3.1.2"
"@ethereumjs/rlp" "^4.0.1"
"@ethereumjs/util" "^8.0.5"
"@ethersproject/providers" "^5.7.2"
ethereum-cryptography "^1.1.2"
"@ethereumjs/util" "^8.0.6"
ethereum-cryptography "^2.0.0"
"@ethereumjs/util@^8.0.5":
version "8.0.5"
resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.0.5.tgz#b9088fc687cc13f0c1243d6133d145dfcf3fe446"
integrity sha512-259rXKK3b3D8HRVdRmlOEi6QFvwxdt304hhrEAmpZhsj7ufXEOTIc9JRZPMnXatKjECokdLNBcDOFBeBSzAIaw==
"@ethereumjs/util@^8.0.6":
version "8.0.6"
resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.0.6.tgz#f9716ed34235ea05eff8353bc5d483e5a6455989"
integrity sha512-zFLG/gXtF3QUC7iKFn4PT6HCr+DEnlCbwUGKGtXoqjA+64T+e0FuqMjlo4bQIY2ngRzk3EtudKdGYC4g31ehhg==
dependencies:
"@chainsafe/ssz" "0.9.4"
"@chainsafe/ssz" "^0.11.1"
"@ethereumjs/rlp" "^4.0.1"
ethereum-cryptography "^1.1.2"
ethereum-cryptography "^2.0.0"
micro-ftch "^0.3.1"
"@ethereumjs/vm@^6.4.2":
version "6.4.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/vm/-/vm-6.4.2.tgz#9898105a96f0975d561db69319331944db4bfafc"
integrity sha512-kTzOvJfNpUQHi2a0SbglYNWHIEOg5j3NlN80KU0IrdagWAeaEqz6Jj5XVN5lBs4VAfwXNdf+56xYtMg8Nate7Q==
dependencies:
"@ethereumjs/block" "^4.2.2"
"@ethereumjs/blockchain" "^6.2.2"
"@ethereumjs/common" "^3.1.2"
"@ethereumjs/evm" "^1.3.2"
"@ethereumjs/rlp" "^4.0.1"
"@ethereumjs/statemanager" "^1.0.5"
"@ethereumjs/trie" "^5.0.5"
"@ethereumjs/tx" "^4.1.2"
"@ethereumjs/util" "^8.0.6"
debug "^4.3.3"
ethereum-cryptography "^2.0.0"
mcl-wasm "^0.7.1"
rustbn.js "~0.2.0"
"@ethersproject/abi@5.1.2", "@ethersproject/abi@^5.1.0":
version "5.1.2"
@@ -1231,7 +1311,7 @@
bech32 "1.1.4"
ws "7.2.3"
"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2":
"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1":
version "5.7.2"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb"
integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==
@@ -2281,6 +2361,13 @@
uint8arrays "^4.0.2"
varint "^6.0.0"
"@noble/curves@1.0.0", "@noble/curves@~1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932"
integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==
dependencies:
"@noble/hashes" "1.3.0"
"@noble/ed25519@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.6.0.tgz#b55f7c9e532b478bf1d7c4f609e1f3a37850b583"
@@ -2296,16 +2383,16 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
"@noble/hashes@1.3.0", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.0", "@noble/hashes@~1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1"
integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==
"@noble/hashes@^1.0.0", "@noble/hashes@~1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz"
integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==
"@noble/hashes@^1.2.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1"
integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==
"@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
@@ -3004,6 +3091,15 @@
"@noble/secp256k1" "~1.7.0"
"@scure/base" "~1.1.0"
"@scure/bip32@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.0.tgz#6c8d980ef3f290987736acd0ee2e0f0d50068d87"
integrity sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==
dependencies:
"@noble/curves" "~1.0.0"
"@noble/hashes" "~1.3.0"
"@scure/base" "~1.1.0"
"@scure/bip39@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5"
@@ -3012,6 +3108,14 @@
"@noble/hashes" "~1.2.0"
"@scure/base" "~1.1.0"
"@scure/bip39@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b"
integrity sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==
dependencies:
"@noble/hashes" "~1.3.0"
"@scure/base" "~1.1.0"
"@scure/bip39@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.0.0.tgz"
@@ -4056,7 +4160,7 @@ abortcontroller-polyfill@^1.7.3:
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed"
integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==
abstract-level@^1.0.2, abstract-level@^1.0.3:
abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741"
integrity sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==
@@ -4710,6 +4814,11 @@ bigint-buffer@^1.1.5:
dependencies:
bindings "^1.3.0"
bigint-crypto-utils@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz#e30a49ec38357c6981cd3da5aaa6480b1f752ee4"
integrity sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==
bignumber.js@^9.0.0:
version "9.1.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6"
@@ -7166,7 +7275,7 @@ ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1"
setimmediate "^1.0.5"
ethereum-cryptography@^1.1.2, ethereum-cryptography@^1.2.0:
ethereum-cryptography@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz#5ccfa183e85fdaf9f9b299a79430c044268c9b3a"
integrity sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==
@@ -7176,6 +7285,16 @@ ethereum-cryptography@^1.1.2, ethereum-cryptography@^1.2.0:
"@scure/bip32" "1.1.5"
"@scure/bip39" "1.1.1"
ethereum-cryptography@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.0.0.tgz#e052b49fa81affae29402e977b8d3a31f88612b6"
integrity sha512-g25m4EtfQGjstWgVE1aIz7XYYjf3kH5kG17ULWVB5dH6uLahsoltOhACzSxyDV+fhn4gbR4xRrOXGe6r2uh4Bg==
dependencies:
"@noble/curves" "1.0.0"
"@noble/hashes" "1.3.0"
"@scure/bip32" "1.3.0"
"@scure/bip39" "1.2.0"
ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2, ethereumjs-util@^7.1.5:
version "7.1.5"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181"
@@ -7962,6 +8081,11 @@ function.prototype.name@^1.1.5:
es-abstract "^1.19.0"
functions-have-names "^1.2.2"
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==
functions-have-names@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
@@ -10399,6 +10523,13 @@ lowercase-keys@^3.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -10545,6 +10676,11 @@ matcher@^3.0.0:
dependencies:
escape-string-regexp "^4.0.0"
mcl-wasm@^0.7.1:
version "0.7.9"
resolved "https://registry.yarnpkg.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz#c1588ce90042a8700c3b60e40efb339fc07ab87f"
integrity sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz"
@@ -10559,6 +10695,15 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
memory-level@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/memory-level/-/memory-level-1.0.0.tgz#7323c3fd368f9af2f71c3cd76ba403a17ac41692"
integrity sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==
dependencies:
abstract-level "^1.0.0"
functional-red-black-tree "^1.0.1"
module-error "^1.0.1"
memorystream@^0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz"
@@ -10608,6 +10753,11 @@ methods@^1.1.2, methods@~1.1.2:
resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micro-ftch@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f"
integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==
micromatch@^4.0.0, micromatch@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
@@ -13382,6 +13532,11 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
rustbn.js@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca"
integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==
rxjs@^7.2.0:
version "7.3.0"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.3.0.tgz"
@@ -15917,7 +16072,7 @@ yaeti@^0.0.6:
resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==
yallist@^3.0.0, yallist@^3.1.1:
yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==