Skip to content

Commit

Permalink
ci: add support for the CI env for e2e tests (#5488)
Browse files Browse the repository at this point in the history
* Add support for the CI env for e2e tests

* Update the port number e2e tests node

* Update the script

* Update the e2e script

* Stop the e2e tests env

* Update the script to stop env

* Add a timeout for port

* Redirect nohup output

* Redirect nohup output

* Update the process signal

* Move the run bash script to a file

* Remove the unwanted wait time

* Update the variaable name

* Update the script name

* Update the test env

* Update the e2e env to start from capella

* Increase the genesis delay

* Pass three epoch to fix the sync comittee availability

* Increase the wait epoch

* Wait for the 3rd epoch

* Increase timeout

* Add lodestar preset value

* Fix env variable

* Set the forks in incremental sequence

* Increase the timeout

* Move wait logic to test file

* Fix the geth version

* Allow to pass custom config to support custom network

* Fix lint error

* Add minimal preset for the e2e tests
  • Loading branch information
nazarhussain authored May 30, 2023
1 parent e053608 commit 0e93c07
Show file tree
Hide file tree
Showing 20 changed files with 236 additions and 138 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
44 changes: 44 additions & 0 deletions packages/cli/test/scripts/e2e_test_env.ts
Original file line number Diff line number Diff line change
@@ -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);
20 changes: 11 additions & 9 deletions packages/cli/test/utils/simulation/SimulationEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,17 @@ export class SimulationEnvironment {

async start(opts: StartOpts): Promise<void> {
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(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/prover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
15 changes: 10 additions & 5 deletions packages/prover/src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<ChainConfig>};

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<Params = unknown[], Response = unknown> = (
Expand Down Expand Up @@ -57,7 +59,6 @@ export type ELVerifiedRequestHandlerOpts<Params = unknown[], Response = unknown>
handler: ELRequestHandler<Params, Response>;
proofProvider: ProofProvider;
logger: Logger;
network: NetworkName;
};

export type ELVerifiedRequestHandler<Params = unknown[], Response = unknown> = (
Expand All @@ -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};
18 changes: 12 additions & 6 deletions packages/prover/src/proof_provider/proof_provider.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -26,6 +26,8 @@ type RootProviderOptions = Omit<RootProviderInitOptions, "transport"> & {
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<void>;
Expand All @@ -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<void> {
Expand All @@ -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);

Expand All @@ -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;
Expand Down
10 changes: 2 additions & 8 deletions packages/prover/src/utils/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<VM> {
const common = getChainCommon(network);
export async function createVM({proofProvider}: {proofProvider: ProofProvider}): Promise<VM> {
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
Expand Down
6 changes: 4 additions & 2 deletions packages/prover/src/utils/execution.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -68,14 +67,17 @@ 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":
case "ropsten":
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:
Expand Down
5 changes: 1 addition & 4 deletions packages/prover/src/utils/process.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -28,21 +27,19 @@ export async function processAndVerifyRequest({
handler,
proofProvider,
logger,
network,
}: {
payload: ELRequestPayload;
handler: ELRequestHandler;
proofProvider: ProofProvider;
logger: Logger;
network: NetworkName;
}): Promise<ELResponse | undefined> {
await proofProvider.waitToBeReady();
logger.debug("Processing request", {method: payload.method, params: JSON.stringify(payload.params)});
const verifiedHandler = supportedELRequests[payload.method];

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});
Expand Down
8 changes: 4 additions & 4 deletions packages/prover/src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<boolean> {
const common = getChainCommon(network);
const common = getChainCommon(config.PRESET_BASE);
common.setHardforkByBlockNumber(executionPayload.blockNumber, undefined, executionPayload.timestamp);

const blockObject = Block.fromBlockData(blockDataFromELBlock(block), {common});
Expand Down
5 changes: 1 addition & 4 deletions packages/prover/src/utils/verification.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<VerificationResult<ELBlock>> {
const executionPayload = await proofProvider.getExecutionPayload(payload.params[0]);
const block = await getELBlock(handler, payload.params);
Expand All @@ -90,7 +87,7 @@ export async function verifyBlock({
logger,
block,
executionPayload,
network,
config: proofProvider.config,
})
) {
return {data: block, valid: true};
Expand Down
5 changes: 2 additions & 3 deletions packages/prover/src/verified_requests/eth_call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export const eth_call: ELVerifiedRequestHandler<ELApiParams["eth_call"], ELApiRe
payload,
logger,
proofProvider,
network,
}) => {
const {
params: [tx, block],
Expand All @@ -20,7 +19,7 @@ export const eth_call: ELVerifiedRequestHandler<ELApiParams["eth_call"], ELApiRe

try {
// TODO: Optimize the creation of the evm
const vm = await createVM({proofProvider, network});
const vm = await createVM({proofProvider});
const vmWithState = await getVMWithState({
handler: handler as unknown as ELApiHandlers["eth_getProof"],
executionPayload,
Expand All @@ -33,7 +32,7 @@ export const eth_call: ELVerifiedRequestHandler<ELApiParams["eth_call"], ELApiRe
tx,
handler: handler as unknown as ELApiHandlers["eth_getBlockByHash"],
executionPayload,
network,
network: proofProvider.network,
});

return generateRPCResponseForPayload(payload, bufferToHex(result.returnValue));
Expand Down
6 changes: 3 additions & 3 deletions packages/prover/src/verified_requests/eth_estimateGas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {generateRPCResponseForPayload, generateUnverifiedResponseForPayload} fro
export const eth_estimateGas: ELVerifiedRequestHandler<
ELApiParams["eth_estimateGas"],
ELApiReturn["eth_estimateGas"]
> = async ({handler, payload, logger, proofProvider, network}) => {
> = async ({handler, payload, logger, proofProvider}) => {
const {
params: [tx, block],
} = payload;
Expand All @@ -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,
Expand All @@ -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));
Expand Down
3 changes: 1 addition & 2 deletions packages/prover/src/verified_requests/eth_getBlockByHash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 0e93c07

Please sign in to comment.