import {ChildProcess} from "node:child_process"; import type {SecretKey} from "@chainsafe/bls/types"; import {Api} from "@lodestar/api"; import {Api as KeyManagerApi} from "@lodestar/api/keymanager"; import {IChainConfig, IChainForkConfig} from "@lodestar/config"; import {ForkName} from "@lodestar/params"; import {Slot, allForks, Epoch} from "@lodestar/types"; import {IBeaconArgs} from "../../../src/cmds/beacon/options.js"; import {IGlobalArgs} from "../../../src/options/index.js"; import {EpochClock} from "./EpochClock.js"; import {Eth1ProviderWithAdmin} from "./Eth1ProviderWithAdmin.js"; export type NodeId = string; export type SimulationInitOptions = { id: string; logsDir: string; chainConfig: AtLeast; }; export type SimulationOptions = { id: string; logsDir: string; rootDir: string; controller: AbortController; genesisTime: number; }; export enum CLClient { Lodestar = "lodestar", } export enum ELClient { Mock = "mock", Geth = "geth", Nethermind = "nethermind", } export enum ELStartMode { PreMerge = "pre-merge", PostMerge = "post-merge", } export type CLClientsOptions = { [CLClient.Lodestar]: Partial; }; export type ELClientsOptions = { [ELClient.Mock]: string[]; [ELClient.Geth]: string[]; [ELClient.Nethermind]: string[]; }; export interface NodePairOptions { keysCount: number; remote?: boolean; mining?: boolean; id: string; cl: C | {type: C; options: Partial>}; el: E | {type: E; options: Partial>}; } export type CLClientKeys = | {type: "local"; secretKeys: SecretKey[]} | {type: "remote"; secretKeys: SecretKey[]} | {type: "no-keys"}; export interface CLClientGeneratorOptions { id: string; dataDir: string; logFilePath: string; genesisStateFilePath: string; address: string; restPort: number; port: number; keyManagerPort: number; config: IChainForkConfig; keys: CLClientKeys; genesisTime: number; engineUrls: string[]; engineMock: boolean; jwtSecretHex: string; clientOptions: CLClientsOptions[C]; } export interface ELGeneratorGenesisOptions { ttd: bigint; cliqueSealingPeriod: number; clientOptions: ELClientsOptions[E]; } export interface ELGeneratorClientOptions extends ELGeneratorGenesisOptions { mode: ELStartMode; id: string; logFilePath: string; dataDir: string; jwtSecretHex: string; enginePort: number; ethPort: number; port: number; address: string; mining: boolean; clientOptions: ELClientsOptions[E]; } export interface CLNode { readonly client: CLClient; readonly id: string; readonly url: string; readonly api: Api; readonly keyManager: KeyManagerApi; readonly keys: CLClientKeys; readonly job: Job; } export interface ELNode { readonly client: E; readonly id: string; readonly ttd: bigint; readonly engineRpcUrl: string; readonly ethRpcUrl: string; readonly jwtSecretHex: string; readonly provider: E extends ELClient.Mock ? null : Eth1ProviderWithAdmin; readonly job: Job; } export interface NodePair { readonly id: string; readonly cl: CLNode; readonly el: ELNode; } export type CLClientGenerator = ( opts: CLClientGeneratorOptions, runner: Runner | Runner ) => CLNode; export type ELClientGenerator = ( opts: ELGeneratorClientOptions, runner: Runner | Runner ) => ELNode; export type HealthStatus = {ok: true} | {ok: false; reason: string; checkId: string}; export interface JobOptions { readonly id: string; readonly cli: { readonly command: string; readonly args: string[]; readonly env?: Record; }; readonly logs: { readonly stdoutFilePath: string; }; // Nested children runs in sequence, while the array of jobs runs in parallel readonly children?: JobOptions[]; // Will be called frequently to check the health of job startup // If not present then wait for the job to exit health?(): Promise; // Called once before the `job.start` is called bootstrap?(): Promise; // Called once before the `job.stop` is called teardown?(): Promise; } export interface Job { type: RunnerType; id: string; start(): Promise; stop(): Promise; } export enum RunnerType { ChildProcess = "child_process", Docker = "docker", } export type RunnerOptions = { [RunnerType.ChildProcess]: never; [RunnerType.Docker]: { image: string; dataVolumePath: string; exposePorts: number[]; dockerNetworkIp: string; }; }; export interface Runner { type: T; create: ( id: string, jobOptions: JobOptions[], ...options: RunnerOptions[T] extends never ? [undefined?] : [RunnerOptions[T]] ) => Job; on(event: RunnerEvent, cb: () => void | Promise): void; } export type RunnerEvent = "starting" | "started" | "stopping" | "stop"; export type AtLeast = Partial & Pick; export type SimulationCaptureInput = Record> = { fork: ForkName; slot: Slot; epoch: Epoch; block: allForks.SignedBeaconBlock; clock: EpochClock; node: NodePair; store: Record; forkConfig: IChainForkConfig; dependantStores: D; }; export type SimulationAssertionInput = Record> = { slot: Slot; epoch: Epoch; clock: EpochClock; nodes: NodePair[]; store: Record>; dependantStores: D; forkConfig: IChainForkConfig; }; export type SimulationMatcherInput = { slot: Slot; epoch: Epoch; clock: EpochClock; forkConfig: IChainForkConfig; }; export type AssertionMatcher = (input: SimulationMatcherInput) => boolean | {match: boolean; remove: boolean}; export type ExtractAssertionType = T extends SimulationAssertion ? A extends I ? B : never : never; export type ExtractAssertionId = T extends SimulationAssertion ? A : never; export type StoreType = Record< AssertionId, Record> >; export type StoreTypes> = { [Id in IDs]: Record>>; }; export interface SimulationAssertion< IdType extends string = string, ValueType = unknown, Dependencies extends SimulationAssertion[] = SimulationAssertion[] > { readonly id: IdType; capture?(input: SimulationCaptureInput>): Promise; match: AssertionMatcher; assert(input: SimulationAssertionInput>): Promise; dependencies?: Dependencies; } export interface SimulationAssertionError { slot: Slot; epoch: Epoch; assertionId: string; message: string; } export type ChildProcessWithJobOptions = {jobOptions: JobOptions; childProcess: ChildProcess}; export type Eth1GenesisBlock = { config: { chainId: number; clique: Record; terminalTotalDifficulty: string; }; alloc: Record; }; export abstract class SimulationReporter { constructor( protected options: { clock: EpochClock; forkConfig: IChainForkConfig; stores: StoreTypes; nodes: NodePair[]; errors: SimulationAssertionError[]; } ) {} abstract bootstrap(): void; abstract progress(slot: Slot): void; abstract summary(): void; }