diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index c605f9811ea0..7cddb452e0b6 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -14,6 +14,8 @@ on: env: GOERLI_RPC_DEFAULT_URL: https://goerli.infura.io/v3/84842078b09946638c03157f83405213 + GETH_DOCKER_IMAGE: ethereum/client-go:v1.11.6 + NETHERMIND_DOCKER_IMAGE: nethermind/nethermind:1.18.0 jobs: tests-main: @@ -56,7 +58,20 @@ jobs: # Run only on forks if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }} + - name: Run the e2e test environment + run: scripts/run_e2e_env.sh start + - name: E2E tests run: yarn test:e2e env: GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL!=0 && secrets.GOERLI_RPC_URL || env.GOERLI_RPC_DEFAULT_URL }} + + - name: Stop the e2e test environment + run: scripts/run_e2e_env.sh stop + + - name: Upload debug log test for test env + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: debug-e2e-test-logs + path: test-logs/e2e-test-env diff --git a/packages/cli/test/scripts/e2e_test_env.ts b/packages/cli/test/scripts/e2e_test_env.ts new file mode 100644 index 000000000000..87e7a9f2f7ca --- /dev/null +++ b/packages/cli/test/scripts/e2e_test_env.ts @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import path from "node:path"; +import {CLClient, ELClient} from "../utils/simulation/interfaces.js"; +import {SimulationEnvironment} from "../utils/simulation/SimulationEnvironment.js"; +import {getEstimatedTTD, logFilesDir} from "../utils/simulation/utils/index.js"; +import {connectAllNodes} from "../utils/simulation/utils/network.js"; + +const secondsPerSlot = 4; +const cliqueSealingPeriod = 5; +const genesisDelaySeconds = 30 * secondsPerSlot; +const altairForkEpoch = 1; +const bellatrixForkEpoch = 2; +const capellaForkEpoch = 3; +// Make sure bellatrix started before TTD reach +const additionalSlotsForTTD = 2; + +const ttd = getEstimatedTTD({ + genesisDelaySeconds, + bellatrixForkEpoch, + secondsPerSlot, + cliqueSealingPeriod, + additionalSlots: additionalSlotsForTTD, +}); + +const env = await SimulationEnvironment.initWithDefaults( + { + id: "e2e-test-env", + logsDir: path.join(logFilesDir, "e2e-test-env"), + chainConfig: { + ALTAIR_FORK_EPOCH: altairForkEpoch, + BELLATRIX_FORK_EPOCH: bellatrixForkEpoch, + CAPELLA_FORK_EPOCH: capellaForkEpoch, + GENESIS_DELAY: genesisDelaySeconds, + TERMINAL_TOTAL_DIFFICULTY: ttd, + }, + }, + [ + {id: "node-1", cl: CLClient.Lodestar, el: ELClient.Geth, keysCount: 32, mining: true}, + {id: "node-2", cl: CLClient.Lodestar, el: ELClient.Nethermind, keysCount: 32}, + ] +); + +await env.start({runTimeoutMs: 0}); +await connectAllNodes(env.nodes); diff --git a/packages/cli/test/utils/simulation/SimulationEnvironment.ts b/packages/cli/test/utils/simulation/SimulationEnvironment.ts index 312fb18da062..38e26d4582b8 100644 --- a/packages/cli/test/utils/simulation/SimulationEnvironment.ts +++ b/packages/cli/test/utils/simulation/SimulationEnvironment.ts @@ -122,15 +122,17 @@ export class SimulationEnvironment { async start(opts: StartOpts): Promise { const currentTime = Date.now(); - setTimeout(() => { - const slots = this.clock.getSlotFor((currentTime + opts.runTimeoutMs) / MS_IN_SEC); - const epoch = this.clock.getEpochForSlot(slots); - const slot = this.clock.getSlotIndexInEpoch(slots); - - this.stop(1, `Sim run timedout in ${opts.runTimeoutMs}ms (approx. ${epoch}/${slot}).`).catch((e) => - console.error("Error on stop", e) - ); - }, opts.runTimeoutMs); + if (opts.runTimeoutMs > 0) { + setTimeout(() => { + const slots = this.clock.getSlotFor((currentTime + opts.runTimeoutMs) / MS_IN_SEC); + const epoch = this.clock.getEpochForSlot(slots); + const slot = this.clock.getSlotIndexInEpoch(slots); + + this.stop(1, `Sim run timeout in ${opts.runTimeoutMs}ms (approx. ${epoch}/${slot}).`).catch((e) => + console.error("Error on stop", e) + ); + }, opts.runTimeoutMs); + } const msToGenesis = this.clock.msToGenesis(); const startTimeout = setTimeout(() => { diff --git a/packages/prover/package.json b/packages/prover/package.json index 3985d1c0d8dd..578b9a36fe2a 100644 --- a/packages/prover/package.json +++ b/packages/prover/package.json @@ -54,7 +54,7 @@ "test": "yarn test:unit && yarn test:e2e", "test:unit": "nyc --cache-dir .nyc_output/.cache -e .ts mocha 'test/unit/**/*.test.ts'", "test:browsers": "yarn karma start karma.config.cjs", - "test:e2e": "mocha 'test/e2e/**/*.test.ts'", + "test:e2e": "LODESTAR_PRESET=minimal mocha 'test/e2e/**/*.test.ts'", "check-readme": "typescript-docs-verifier", "generate-fixtures": "npx ts-node --esm scripts/generate_fixtures.ts" }, diff --git a/packages/prover/src/interfaces.ts b/packages/prover/src/interfaces.ts index 5b38f9971015..972e46facc53 100644 --- a/packages/prover/src/interfaces.ts +++ b/packages/prover/src/interfaces.ts @@ -1,4 +1,4 @@ -import {ChainForkConfig} from "@lodestar/config"; +import {ChainConfig} from "@lodestar/config"; import {NetworkName} from "@lodestar/config/networks"; import {Logger, LogLevel} from "@lodestar/utils"; import {ProofProvider} from "./proof_provider/proof_provider.js"; @@ -9,13 +9,15 @@ export enum LCTransport { P2P = "P2P", } +// Provide either network or config. This will be helpful to connect to a custom network +export type NetworkOrConfig = {network: NetworkName; config?: never} | {network?: never; config: Partial}; + export type RootProviderInitOptions = { - network: NetworkName; signal: AbortSignal; logger: Logger; - config?: ChainForkConfig; wsCheckpoint?: string; -} & ConsensusNodeOptions; +} & ConsensusNodeOptions & + NetworkOrConfig; // The `undefined` is necessary to match the types for the web3 1.x export type ELRequestHandler = ( @@ -57,7 +59,6 @@ export type ELVerifiedRequestHandlerOpts handler: ELRequestHandler; proofProvider: ProofProvider; logger: Logger; - network: NetworkName; }; export type ELVerifiedRequestHandler = ( @@ -71,3 +72,7 @@ export type LogOptions = {logger?: Logger; logLevel?: never} | {logLevel?: LogLe export type ConsensusNodeOptions = | {transport: LCTransport.Rest; urls: string[]} | {transport: LCTransport.P2P; bootnodes: string[]}; + +export type VerifiedExecutionInitOptions = LogOptions & + ConsensusNodeOptions & + NetworkOrConfig & {wsCheckpoint?: string; signal?: AbortSignal}; diff --git a/packages/prover/src/proof_provider/proof_provider.ts b/packages/prover/src/proof_provider/proof_provider.ts index 0e6930f6f54b..e8586d50243a 100644 --- a/packages/prover/src/proof_provider/proof_provider.ts +++ b/packages/prover/src/proof_provider/proof_provider.ts @@ -1,6 +1,6 @@ import {Api, getClient} from "@lodestar/api/beacon"; import {ChainForkConfig, createChainForkConfig} from "@lodestar/config"; -import {networksChainConfig} from "@lodestar/config/networks"; +import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {Lightclient, LightclientEvent, RunStatusCode} from "@lodestar/light-client"; import {LightClientRestTransport} from "@lodestar/light-client/transport"; import {isForkWithdrawals} from "@lodestar/params"; @@ -26,6 +26,8 @@ type RootProviderOptions = Omit & { export class ProofProvider { private store: PayloadStore; private logger: Logger; + readonly config: ChainForkConfig; + readonly network: NetworkName; // Make sure readyPromise doesn't throw unhandled exceptions private readyPromise?: Promise; @@ -34,6 +36,8 @@ export class ProofProvider { constructor(private opts: RootProviderOptions) { this.store = new PayloadStore({api: opts.api, logger: opts.logger}); this.logger = opts.logger; + this.config = opts.config; + this.network = opts.config.PRESET_BASE as NetworkName; } async waitToBeReady(): Promise { @@ -48,7 +52,11 @@ export class ProofProvider { network: opts.network, urls: opts.urls.join(","), }); - const config = createChainForkConfig(networksChainConfig[opts.network]); + + const config = opts.network + ? createChainForkConfig(networksChainConfig[opts.network]) + : createChainForkConfig(opts.config); + const api = getClient({urls: opts.urls}, {config}); const transport = new LightClientRestTransport(api); @@ -60,10 +68,8 @@ export class ProofProvider { }); provider.readyPromise = provider.sync(opts.wsCheckpoint).catch((e) => { - // TODO: will be replaced by logger in the next PR. - // eslint-disable-next-line no-console - console.error("Error while syncing", e); - return Promise.reject("Error while syncing"); + opts.logger.error("Error while syncing", e); + return Promise.reject(e); }); return provider; diff --git a/packages/prover/src/utils/evm.ts b/packages/prover/src/utils/evm.ts index df8faa601e57..a066fe39dc36 100644 --- a/packages/prover/src/utils/evm.ts +++ b/packages/prover/src/utils/evm.ts @@ -14,14 +14,8 @@ import {elRpc, getChainCommon, getTxType} from "./execution.js"; import {isValidResponse} from "./json_rpc.js"; import {isNullish, isValidAccount, isValidCodeHash, isValidStorageKeys} from "./validation.js"; -export async function createVM({ - proofProvider, - network, -}: { - proofProvider: ProofProvider; - network: NetworkName; -}): Promise { - const common = getChainCommon(network); +export async function createVM({proofProvider}: {proofProvider: ProofProvider}): Promise { + const common = getChainCommon(proofProvider.config.PRESET_BASE as string); const blockchain = await Blockchain.create({common}); // Connect blockchain object with existing proof provider for block history diff --git a/packages/prover/src/utils/execution.ts b/packages/prover/src/utils/execution.ts index 793cc04531a8..e0b6fea7300f 100644 --- a/packages/prover/src/utils/execution.ts +++ b/packages/prover/src/utils/execution.ts @@ -1,5 +1,4 @@ import {Common, CustomChain, Hardfork} from "@ethereumjs/common"; -import {NetworkName} from "@lodestar/config/networks"; import {ELRequestHandler} from "../interfaces.js"; import {ELApiHandlers, ELApiParams, ELApiReturn, ELResponse, ELResponseWithResult, ELTransaction} from "../types.js"; import {isValidResponse} from "./json_rpc.js"; @@ -68,7 +67,7 @@ export async function getELBlock( return block.result; } -export function getChainCommon(network: NetworkName): Common { +export function getChainCommon(network: string): Common { switch (network) { case "mainnet": case "goerli": @@ -76,6 +75,9 @@ export function getChainCommon(network: NetworkName): Common { case "sepolia": // TODO: Not sure how to detect the fork during runtime return new Common({chain: network, hardfork: Hardfork.Shanghai}); + case "minimal": + // TODO: Not sure how to detect the fork during runtime + return new Common({chain: "mainnet", hardfork: Hardfork.Shanghai}); case "gnosis": return new Common({chain: CustomChain.xDaiChain}); default: diff --git a/packages/prover/src/utils/process.ts b/packages/prover/src/utils/process.ts index ffb73dab99ef..93877e38ba5c 100644 --- a/packages/prover/src/utils/process.ts +++ b/packages/prover/src/utils/process.ts @@ -1,5 +1,4 @@ import {Logger} from "@lodestar/logger"; -import {NetworkName} from "@lodestar/config/networks"; import {ELRequestHandler, ELVerifiedRequestHandler} from "../interfaces.js"; import {ProofProvider} from "../proof_provider/proof_provider.js"; import {ELRequestPayload, ELResponse} from "../types.js"; @@ -28,13 +27,11 @@ export async function processAndVerifyRequest({ handler, proofProvider, logger, - network, }: { payload: ELRequestPayload; handler: ELRequestHandler; proofProvider: ProofProvider; logger: Logger; - network: NetworkName; }): Promise { await proofProvider.waitToBeReady(); logger.debug("Processing request", {method: payload.method, params: JSON.stringify(payload.params)}); @@ -42,7 +39,7 @@ export async function processAndVerifyRequest({ if (verifiedHandler !== undefined) { logger.debug("Verified request handler found", {method: payload.method}); - return verifiedHandler({payload, handler, proofProvider, logger, network}); + return verifiedHandler({payload, handler, proofProvider, logger}); } logger.warn("Verified request handler not found. Falling back to proxy.", {method: payload.method}); diff --git a/packages/prover/src/utils/validation.ts b/packages/prover/src/utils/validation.ts index 89cc496a8020..42026b0af839 100644 --- a/packages/prover/src/utils/validation.ts +++ b/packages/prover/src/utils/validation.ts @@ -3,9 +3,9 @@ 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 {ChainForkConfig} from "@lodestar/config"; import {ELBlock, ELProof, ELStorageProof, HexString} from "../types.js"; import {blockDataFromELBlock, bufferToHex, hexToBuffer, padLeft} from "./conversion.js"; import {getChainCommon} from "./execution.js"; @@ -95,14 +95,14 @@ export async function isValidBlock({ executionPayload, block, logger, - network, + config, }: { executionPayload: allForks.ExecutionPayload; block: ELBlock; logger: Logger; - network: NetworkName; + config: ChainForkConfig; }): Promise { - const common = getChainCommon(network); + const common = getChainCommon(config.PRESET_BASE); common.setHardforkByBlockNumber(executionPayload.blockNumber, undefined, executionPayload.timestamp); const blockObject = Block.fromBlockData(blockDataFromELBlock(block), {common}); diff --git a/packages/prover/src/utils/verification.ts b/packages/prover/src/utils/verification.ts index 40a8a43e78e1..226f73237979 100644 --- a/packages/prover/src/utils/verification.ts +++ b/packages/prover/src/utils/verification.ts @@ -1,4 +1,3 @@ -import {NetworkName} from "@lodestar/config/networks"; import {Logger} from "@lodestar/utils"; import {ELRequestHandlerAny} from "../interfaces.js"; import {ProofProvider} from "../proof_provider/proof_provider.js"; @@ -71,13 +70,11 @@ export async function verifyBlock({ proofProvider, logger, handler, - network, }: { payload: ELRequestPayload<[block: string | number, hydrated: boolean]>; handler: ELRequestHandlerAny; proofProvider: ProofProvider; logger: Logger; - network: NetworkName; }): Promise> { const executionPayload = await proofProvider.getExecutionPayload(payload.params[0]); const block = await getELBlock(handler, payload.params); @@ -90,7 +87,7 @@ export async function verifyBlock({ logger, block, executionPayload, - network, + config: proofProvider.config, }) ) { return {data: block, valid: true}; diff --git a/packages/prover/src/verified_requests/eth_call.ts b/packages/prover/src/verified_requests/eth_call.ts index ab87c9138159..e220bf3346e1 100644 --- a/packages/prover/src/verified_requests/eth_call.ts +++ b/packages/prover/src/verified_requests/eth_call.ts @@ -10,7 +10,6 @@ export const eth_call: ELVerifiedRequestHandler { const { params: [tx, block], @@ -20,7 +19,7 @@ export const eth_call: ELVerifiedRequestHandler = async ({handler, payload, logger, proofProvider, network}) => { +> = async ({handler, payload, logger, proofProvider}) => { const { params: [tx, block], } = payload; @@ -17,7 +17,7 @@ export const eth_estimateGas: ELVerifiedRequestHandler< try { // TODO: Optimize the creation of the evm - const evm = await createVM({proofProvider, network}); + const evm = await createVM({proofProvider}); const vmWithState = await getVMWithState({ handler: handler as unknown as ELApiHandlers["eth_getProof"], executionPayload, @@ -31,7 +31,7 @@ export const eth_estimateGas: ELVerifiedRequestHandler< tx, handler: handler as unknown as ELApiHandlers["eth_getBlockByHash"], executionPayload, - network, + network: proofProvider.network, }); return generateRPCResponseForPayload(payload, bigIntToHex(result.totalGasSpent)); diff --git a/packages/prover/src/verified_requests/eth_getBlockByHash.ts b/packages/prover/src/verified_requests/eth_getBlockByHash.ts index 3ff22d0bf680..9259e700b2a2 100644 --- a/packages/prover/src/verified_requests/eth_getBlockByHash.ts +++ b/packages/prover/src/verified_requests/eth_getBlockByHash.ts @@ -9,9 +9,8 @@ export const eth_getBlockByHash: ELVerifiedRequestHandler<[block: string, hydrat payload, logger, proofProvider, - network, }) => { - const result = await verifyBlock({payload, proofProvider, logger, handler, network}); + const result = await verifyBlock({payload, proofProvider, logger, handler}); if (result.valid) { return generateVerifiedResponseForPayload(payload, result.data); diff --git a/packages/prover/src/verified_requests/eth_getBlockByNumber.ts b/packages/prover/src/verified_requests/eth_getBlockByNumber.ts index 3c2e8778d511..b781afef3352 100644 --- a/packages/prover/src/verified_requests/eth_getBlockByNumber.ts +++ b/packages/prover/src/verified_requests/eth_getBlockByNumber.ts @@ -7,8 +7,8 @@ import {generateUnverifiedResponseForPayload, generateVerifiedResponseForPayload export const eth_getBlockByNumber: ELVerifiedRequestHandler< [block: string | number, hydrated: boolean], ELBlock -> = async ({handler, payload, logger, proofProvider, network}) => { - const result = await verifyBlock({payload, proofProvider, logger, handler, network}); +> = async ({handler, payload, logger, proofProvider}) => { + const result = await verifyBlock({payload, proofProvider, logger, handler}); if (result.valid) { return generateVerifiedResponseForPayload(payload, result.data); diff --git a/packages/prover/src/web3_provider.ts b/packages/prover/src/web3_provider.ts index 44aa96f392bc..ccaec17ad58b 100644 --- a/packages/prover/src/web3_provider.ts +++ b/packages/prover/src/web3_provider.ts @@ -1,15 +1,13 @@ -import {NetworkName} from "@lodestar/config/networks"; import {Logger} from "@lodestar/utils"; import {getBrowserLogger} from "@lodestar/logger/browser"; import {LogLevel} from "@lodestar/logger"; import { - ConsensusNodeOptions, EIP1193Provider, EthersProvider, - LogOptions, RequestProvider, SendAsyncProvider, SendProvider, + VerifiedExecutionInitOptions, Web3Provider, } from "./interfaces.js"; import {ProofProvider} from "./proof_provider/proof_provider.js"; @@ -24,60 +22,48 @@ import { import {processAndVerifyRequest} from "./utils/process.js"; import {logRequest, logResponse} from "./utils/json_rpc.js"; -type ProvableProviderInitOpts = {network?: NetworkName; wsCheckpoint?: string; signal?: AbortSignal} & LogOptions & - ConsensusNodeOptions; - -const defaultNetwork = "mainnet"; - export function createVerifiedExecutionProvider( provider: T, - opts: ProvableProviderInitOpts + opts: VerifiedExecutionInitOptions ): {provider: T; proofProvider: ProofProvider} { const signal = opts.signal ?? new AbortController().signal; const logger = opts.logger ?? getBrowserLogger({level: opts.logLevel ?? LogLevel.info}); - const network = opts.network ?? defaultNetwork; const proofProvider = ProofProvider.init({ ...opts, - network, signal, logger, }); if (isSendProvider(provider)) { logger.debug("Creating a provider which is recognized as legacy provider with 'send' method."); - return {provider: handleSendProvider(provider, proofProvider, logger, network) as T, proofProvider}; + return {provider: handleSendProvider(provider, proofProvider, logger) as T, proofProvider}; } if (isEthersProvider(provider)) { logger.debug("Creating a provider which is recognized as 'ethers' provider."); - return {provider: handleEthersProvider(provider, proofProvider, logger, network) as T, proofProvider}; + return {provider: handleEthersProvider(provider, proofProvider, logger) as T, proofProvider}; } if (isRequestProvider(provider)) { logger.debug("Creating a provider which is recognized as legacy provider with 'request' method."); - return {provider: handleRequestProvider(provider, proofProvider, logger, network) as T, proofProvider}; + return {provider: handleRequestProvider(provider, proofProvider, logger) as T, proofProvider}; } if (isSendAsyncProvider(provider)) { logger.debug("Creating a provider which is recognized as legacy provider with 'sendAsync' method."); - return {provider: handleSendAsyncProvider(provider, proofProvider, logger, network) as T, proofProvider}; + return {provider: handleSendAsyncProvider(provider, proofProvider, logger) as T, proofProvider}; } if (isEIP1193Provider(provider)) { logger.debug("Creating a provider which is recognized as 'EIP1193' provider."); - return {provider: handleEIP1193Provider(provider, proofProvider, logger, network) as T, proofProvider}; + return {provider: handleEIP1193Provider(provider, proofProvider, logger) as T, proofProvider}; } return {provider, proofProvider: proofProvider}; } -function handleSendProvider( - provider: SendProvider, - proofProvider: ProofProvider, - logger: Logger, - network: NetworkName -): SendProvider { +function handleSendProvider(provider: SendProvider, proofProvider: ProofProvider, logger: Logger): SendProvider { const send = provider.send.bind(provider); const handler = (payload: ELRequestPayload): Promise => new Promise((resolve, reject) => { @@ -93,7 +79,7 @@ function handleSendProvider( }); function newSend(payload: ELRequestPayload, callback: (err?: Error | null, response?: ELResponse) => void): void { - processAndVerifyRequest({payload, handler, proofProvider, logger, network}) + processAndVerifyRequest({payload, handler, proofProvider, logger}) .then((response) => callback(undefined, response)) .catch((err) => callback(err, undefined)); } @@ -104,8 +90,7 @@ function handleSendProvider( function handleRequestProvider( provider: RequestProvider, proofProvider: ProofProvider, - logger: Logger, - network: NetworkName + logger: Logger ): RequestProvider { const request = provider.request.bind(provider); const handler = (payload: ELRequestPayload): Promise => @@ -122,7 +107,7 @@ function handleRequestProvider( }); function newRequest(payload: ELRequestPayload, callback: (err?: Error | null, response?: ELResponse) => void): void { - processAndVerifyRequest({payload, handler, proofProvider, logger, network}) + processAndVerifyRequest({payload, handler, proofProvider, logger}) .then((response) => callback(undefined, response)) .catch((err) => callback(err, undefined)); } @@ -133,8 +118,7 @@ function handleRequestProvider( function handleSendAsyncProvider( provider: SendAsyncProvider, proofProvider: ProofProvider, - logger: Logger, - network: NetworkName + logger: Logger ): SendAsyncProvider { const sendAsync = provider.sendAsync.bind(provider); const handler = async (payload: ELRequestPayload): Promise => { @@ -145,7 +129,7 @@ function handleSendAsyncProvider( }; async function newSendAsync(payload: ELRequestPayload): Promise { - return processAndVerifyRequest({payload, handler, proofProvider, logger, network}); + return processAndVerifyRequest({payload, handler, proofProvider, logger}); } return Object.assign(provider, {sendAsync: newSendAsync}); @@ -154,8 +138,7 @@ function handleSendAsyncProvider( function handleEIP1193Provider( provider: EIP1193Provider, proofProvider: ProofProvider, - logger: Logger, - network: NetworkName + logger: Logger ): EIP1193Provider { const request = provider.request.bind(provider); const handler = async (payload: ELRequestPayload): Promise => { @@ -166,18 +149,13 @@ function handleEIP1193Provider( }; async function newRequest(payload: ELRequestPayload): Promise { - return processAndVerifyRequest({payload, handler, proofProvider, logger, network}); + return processAndVerifyRequest({payload, handler, proofProvider, logger}); } return Object.assign(provider, {request: newRequest}); } -function handleEthersProvider( - provider: EthersProvider, - proofProvider: ProofProvider, - logger: Logger, - network: NetworkName -): EthersProvider { +function handleEthersProvider(provider: EthersProvider, proofProvider: ProofProvider, logger: Logger): EthersProvider { const send = provider.send.bind(provider); const handler = async (payload: ELRequestPayload): Promise => { logRequest(payload, logger); @@ -192,7 +170,6 @@ function handleEthersProvider( handler, proofProvider, logger, - network, }); } diff --git a/packages/prover/src/web3_proxy.ts b/packages/prover/src/web3_proxy.ts index eb5080b434fa..d2af68239d17 100644 --- a/packages/prover/src/web3_proxy.ts +++ b/packages/prover/src/web3_proxy.ts @@ -2,35 +2,29 @@ import http from "node:http"; import https from "node:https"; import url from "node:url"; import httpProxy from "http-proxy"; -import {NetworkName} from "@lodestar/config/networks"; import {getNodeLogger} from "@lodestar/logger/node"; import {LogLevel} from "@lodestar/logger"; -import {ConsensusNodeOptions, LogOptions} from "./interfaces.js"; +import {VerifiedExecutionInitOptions} from "./interfaces.js"; import {ProofProvider} from "./proof_provider/proof_provider.js"; import {ELRequestPayload, ELResponse} from "./types.js"; import {generateRPCResponseForPayload, logRequest, logResponse} from "./utils/json_rpc.js"; import {fetchRequestPayload, fetchResponseBody} from "./utils/req_resp.js"; import {processAndVerifyRequest} from "./utils/process.js"; -export type VerifiedProxyOptions = { - network: NetworkName; +export type VerifiedProxyOptions = VerifiedExecutionInitOptions & { executionRpcUrl: string; - wsCheckpoint?: string; - signal?: AbortSignal; -} & LogOptions & - ConsensusNodeOptions; +}; export function createVerifiedExecutionProxy(opts: VerifiedProxyOptions): { server: http.Server; proofProvider: ProofProvider; } { - const {executionRpcUrl, network} = opts; + const {executionRpcUrl} = opts; const signal = opts.signal ?? new AbortController().signal; const logger = opts.logger ?? getNodeLogger({level: opts.logLevel ?? LogLevel.info}); const proofProvider = ProofProvider.init({ ...opts, - network, signal, logger, }); @@ -89,7 +83,7 @@ export function createVerifiedExecutionProxy(opts: VerifiedProxyOptions): { .then((data) => { payload = data; logger.debug("Received request", {method: payload.method}); - return processAndVerifyRequest({payload, proofProvider, handler, logger, network}); + return processAndVerifyRequest({payload, proofProvider, handler, logger}); }) .then((response) => { logger.debug("Sending response", {method: payload.method}); diff --git a/packages/prover/test/e2e/web3_provider.test.ts b/packages/prover/test/e2e/web3_provider.test.ts index cd10207bd254..9f015fe89ced 100644 --- a/packages/prover/test/e2e/web3_provider.test.ts +++ b/packages/prover/test/e2e/web3_provider.test.ts @@ -1,44 +1,82 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {request} from "node:http"; import {expect} from "chai"; import Web3 from "web3"; import {ethers} from "ethers"; +import {sleep} from "@lodestar/utils"; import {LCTransport} from "../../src/interfaces.js"; import {createVerifiedExecutionProvider} from "../../src/web3_provider.js"; -describe("web3_provider", () => { - describe("createVerifiedExecutionProvider", function () { - // As the code will try to sync the light client, it may take a while - this.timeout(10000); +const rpcURL = "http://0.0.0.0:8001"; +const beaconUrl = "http://0.0.0.0:5001"; +// Wait for at least teh capella fork to be started +const secondsPerSlot = 4; +const altairForkEpoch = 1; +const bellatrixForkEpoch = 2; +const capellaForkEpoch = 3; +const genesisDelaySeconds = 30 * secondsPerSlot; +const config = { + ALTAIR_FORK_EPOCH: altairForkEpoch, + BELLATRIX_FORK_EPOCH: bellatrixForkEpoch, + CAPELLA_FORK_EPOCH: capellaForkEpoch, + GENESIS_DELAY: genesisDelaySeconds, +}; +async function waitForEndpoint(url: string): Promise { + // eslint-disable-next-line no-constant-condition + while (true) { + const status = await new Promise((resolve) => { + const req = request(url, {method: "GET"}, (res) => { + resolve(res.statusCode); + }); + req.end(); + }); + if (status === 200) { + break; + } else { + await sleep(1000); + } + } +} + +describe("web3_provider", function () { + // Wait for at least teh capella fork to be started + this.timeout((capellaForkEpoch + 2) * 8 * 4 * 1000); + + before("wait for the capella fork", async () => { + // Wait for the two epoch of capella to pass so that the light client can sync from a finalized checkpoint + await waitForEndpoint(`${beaconUrl}/eth/v1/beacon/headers/${(capellaForkEpoch + 2) * 8}`); + }); + + describe("createVerifiedExecutionProvider", () => { describe("web3", () => { - it("should connect to the network and call non-verified method", async () => { - const {provider} = createVerifiedExecutionProvider( - new Web3.providers.HttpProvider("https://lodestar-mainnetrpc.chainsafe.io"), - { - transport: LCTransport.Rest, - urls: ["https://lodestar-mainnet.chainsafe.io"], - network: "mainnet", - } - ); + it("should connect to the network and call a non-verified method", async () => { + const {provider} = createVerifiedExecutionProvider(new Web3.providers.HttpProvider(rpcURL), { + transport: LCTransport.Rest, + urls: [beaconUrl], + config, + }); const web3 = new Web3(provider); + const accounts = await web3.eth.getAccounts(); // `getProof` will always remain the non-verified method // as we use it to create proof and verify - await expect(web3.eth.getProof("0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134", [], "latest")).fulfilled; + expect(accounts).not.to.be.empty; + await expect(web3.eth.getProof(accounts[0], [], "latest")).fulfilled; }); }); describe("ethers", () => { - it("should connect to the network and call non-verified method", async () => { - const {provider} = createVerifiedExecutionProvider( - new ethers.JsonRpcProvider("https://lodestar-mainnetrpc.chainsafe.io"), - { - transport: LCTransport.Rest, - urls: ["https://lodestar-mainnet.chainsafe.io"], - network: "mainnet", - } - ); - await expect(provider.send("eth_getProof", ["0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134", [], "latest"])) - .fulfilled; + it("should connect to the network and call a non-verified method", async () => { + const {provider} = createVerifiedExecutionProvider(new ethers.JsonRpcProvider(rpcURL), { + transport: LCTransport.Rest, + urls: [beaconUrl], + config, + }); + const accounts = await provider.listAccounts(); + + expect(accounts).not.to.be.empty; + await expect(provider.send("eth_getProof", [accounts[0].address, [], "latest"])).fulfilled; }); }); }); diff --git a/packages/prover/test/mocks/request_handler.ts b/packages/prover/test/mocks/request_handler.ts index 9d5fdd8c2f69..47b4d0f546e2 100644 --- a/packages/prover/test/mocks/request_handler.ts +++ b/packages/prover/test/mocks/request_handler.ts @@ -1,6 +1,7 @@ import sinon from "sinon"; import {NetworkName} from "@lodestar/config/networks"; import {ForkConfig} from "@lodestar/config"; +import {PresetName} from "@lodestar/params"; import {getEmptyLogger} from "@lodestar/logger/empty"; import {ELVerifiedRequestHandlerOpts} from "../../src/interfaces.js"; import {ProofProvider} from "../../src/proof_provider/proof_provider.js"; @@ -57,20 +58,16 @@ function getPayloadMatcher(expected: ELRequestPayload): sinon.SinonMatcher { const expectedItem = expected.params[i]; if (typeof item === "string" && typeof expectedItem === "string") { - if (item.toLowerCase() === expectedItem.toLowerCase()) { - continue; - } else { - return false; - } + if (item.toLowerCase() === expectedItem.toLowerCase()) continue; + + return false; } // Param is a transaction object if (typeof item === "object" && !isNullish((item as ELTransaction).to)) { - if (matchTransaction(item as ELTransaction, expectedItem as ELTransaction)) { - continue; - } else { - return false; - } + if (matchTransaction(item as ELTransaction, expectedItem as ELTransaction)) continue; + + return false; } } @@ -91,6 +88,12 @@ export function generateReqHandlerOptionsMock( logger: getEmptyLogger(), proofProvider: { getExecutionPayload: sinon.stub().resolves(executionPayload), + config: { + ...config, + // eslint-disable-next-line @typescript-eslint/naming-convention + PRESET_BASE: data.network as unknown as PresetName, + }, + network: data.network, } as unknown as ProofProvider, network: data.network as NetworkName, }; diff --git a/scripts/run_e2e_env.sh b/scripts/run_e2e_env.sh new file mode 100755 index 000000000000..180cda203d94 --- /dev/null +++ b/scripts/run_e2e_env.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +function start_app() { + mkdir -p test-logs/e2e-test-env + export LODESTAR_PRESET=minimal + nohup npx ts-node --esm packages/cli/test/scripts/e2e_test_env.ts > test-logs/e2e-test-env/simulation.out 2>&1 & + echo $! > test-logs/e2e-test-env/simulation.pid + echo "Wait for the node to be ready" + npx wait-port -t 60000 0.0.0.0:5001 +} + +function stop_app() { + kill -9 $(cat test-logs/e2e-test-env/simulation.pid) + # Incase the process pid file is not present + kill -9 $(lsof -t -i:5001) +} + + + +case "$1" in + start) start_app ;; + stop) stop_app ;; + *) echo "usage: $0 start|stop" >&2 + exit 1 + ;; +esac \ No newline at end of file