From bbda4c45068729bee6e990d50712a8869fbb3ecc Mon Sep 17 00:00:00 2001 From: "italiano@oplabs.co" Date: Wed, 2 Oct 2024 13:14:20 -0400 Subject: [PATCH 1/5] Adds supersim instance --- src/exports/instances.ts | 1 + src/instances/supersim.test.ts | 110 +++++++++++++++++++++++++++++++++ src/instances/supersim.ts | 104 +++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 src/instances/supersim.test.ts create mode 100644 src/instances/supersim.ts diff --git a/src/exports/instances.ts b/src/exports/instances.ts index 9eaae9f..e011ab5 100644 --- a/src/exports/instances.ts +++ b/src/exports/instances.ts @@ -3,3 +3,4 @@ export { anvil, type AnvilParameters } from '../instances/anvil.js' export { rundler, type RundlerParameters } from '../instances/rundler.js' export { silius, type SiliusParameters } from '../instances/silius.js' export { stackup, type StackupParameters } from '../instances/stackup.js' +export { supersim, type SupersimParameters } from '../instances/supersim.js' diff --git a/src/instances/supersim.test.ts b/src/instances/supersim.test.ts new file mode 100644 index 0000000..f79cf8d --- /dev/null +++ b/src/instances/supersim.test.ts @@ -0,0 +1,110 @@ +import { afterEach, expect, test } from 'vitest' +import type { Instance } from '../instance.js' +import { type SupersimParameters, supersim } from './supersim.js' + +const instances: Instance[] = [] + +const defineInstance = (parameters: SupersimParameters = {}) => { + const instance = supersim(parameters) + instances.push(instance) + return instance +} + +afterEach(async () => { + for (const instance of instances) await instance.stop().catch(() => {}) +}) + +test('default', async () => { + const messages: string[] = [] + const stdouts: string[] = [] + + const instance = defineInstance() + + instance.on('message', (m) => messages.push(m)) + instance.on('stdout', (m) => stdouts.push(m)) + + expect(instance.messages.get()).toMatchInlineSnapshot('[]') + + await instance.start() + expect(instance.status).toEqual('started') + + expect(messages.join('')).toBeDefined() + expect(stdouts.join('')).toBeDefined() + expect(instance.messages.get().join('')).toBeDefined() + + await instance.stop() + expect(instance.status).toEqual('stopped') + + expect(messages.join('')).toBeDefined() + expect(stdouts.join('')).toBeDefined() + expect(instance.messages.get()).toMatchInlineSnapshot('[]') +}) + +test('behavior: start supersim in forked mode', async () => { + const messages: string[] = [] + + const instance = defineInstance({ + fork: { + chains: ['base', 'op'], + }, + }) + + instance.on('message', (m) => messages.push(m)) + + await instance.start() + + expect(messages.join('')).toContain('chain.id=10') + expect(messages.join('')).toContain('chain.id=8453') +}) + +test('behavior: start supersim with different ports', async () => { + const instance = defineInstance({ + l1Port: 9000, + l2StartingPort: 9001, + }) + await instance.start() +}) + +test('behavior: start and stop multiple times', async () => { + const instance = defineInstance() + + await instance.start() + await instance.stop() + await instance.start() + await instance.stop() + await instance.start() + await instance.stop() + await instance.start() + await instance.stop() +}) + +test('behavior: exit', async () => { + const instance = defineInstance() + + let exitCode: number | null | undefined = undefined + instance.on('exit', (code) => { + exitCode = code + }) + + await instance.start() + expect(instance.status).toEqual('started') + + instance._internal.process.kill() + + await new Promise((res) => setTimeout(res, 100)) + expect(instance.status).toEqual('stopped') + expect(typeof exitCode !== 'undefined').toBeTruthy() +}) + +test('behavior: exit when status is starting', async () => { + const instance = defineInstance() + + const promise = instance.start() + expect(instance.status).toEqual('starting') + + instance._internal.process.kill() + + await expect(promise).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Failed to start process "supersim": exited]`, + ) +}) diff --git a/src/instances/supersim.ts b/src/instances/supersim.ts new file mode 100644 index 0000000..92a9e99 --- /dev/null +++ b/src/instances/supersim.ts @@ -0,0 +1,104 @@ +import { defineInstance } from '../instance.js' +import { execa } from '../processes/execa.js' +import { toArgs } from '../utils.js' + +export type SupersimParameters = { + /** The host the server will listen on. */ + host?: string + + /** Listening port for the L1 instance. `0` binds to any available port */ + l1Port?: number + + /** Starting port to increment from for L2 chains. `0` binds each chain to any available port */ + l2StartingPort?: number + + /** Locally fork a network in the superchain registry */ + fork?: { + /** L1 height to fork the superchain (bounds L2 time). `0` for latest */ + l1ForkHeight?: number + + /** chains to fork in the superchain, example mainnet options: [base, lyra, metal, mode, op, orderly, race, tbn, zora] */ + chains: string[] + + /** superchain network. example options: mainnet, sepolia, sepolia-dev-0 */ + network?: string + } + + interop?: { + /** Automatically relay messages sent to the L2ToL2CrossDomainMessenger using account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 */ + autorelay?: boolean + } +} + +const DEFAULT_HOST = 'localhost' +const DEFAULT_PORT = 8545 + +/** + * Defines an Supersim instance. + * + * @example + * ```ts + * const instance = supersim() + * await instance.start() + * // ... + * await instance.stop() + * ``` + */ +export const supersim = defineInstance((parameters?: SupersimParameters) => { + const binary = 'supersim' + + const name = 'supersim' + const process = execa({ name }) + + const supersimArgs = { + l1Port: parameters?.l1Port, + l2StartingPort: parameters?.l2StartingPort, + interop: parameters?.interop ? { ...parameters.interop } : undefined, + ...parameters?.fork, + } + + const args = toArgs({ + // ports + 'l1.port': parameters?.l1Port, + 'l2.starting.port': parameters?.l2StartingPort, + + // interop + 'interop.autorelay': parameters?.interop?.autorelay, + + // fork + 'l1.fork.height': parameters?.fork?.l1ForkHeight, + chains: parameters?.fork?.chains, + network: parameters?.fork?.network, + }) + + if (parameters?.fork) { + args.unshift('fork') + } + + return { + _internal: { + parameters, + get process() { + return process._internal.process + }, + }, + host: parameters?.host ?? DEFAULT_HOST, + name, + port: supersimArgs.l1Port ?? DEFAULT_PORT, + async start(_, options) { + return await process.start(($) => $(binary, args), { + ...options, + resolver({ process, reject, resolve }) { + process.stdout.on('data', (data) => { + const message = data.toString() + if (message.includes('supersim is ready')) resolve() + }) + process.stderr.on('data', (data) => reject(data.toString())) + }, + }) + }, + async stop() { + await process.stop() + }, + } +}) From a96e651df98c2b9c5ec03943c454d9944c46626b Mon Sep 17 00:00:00 2001 From: "italiano@oplabs.co" Date: Wed, 2 Oct 2024 13:14:59 -0400 Subject: [PATCH 2/5] install supersim in CI --- .github/workflows/verify.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 1ae044a..f0da86c 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -63,6 +63,12 @@ jobs: - name: Set up Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Set up Supersim + uses: jaxxstorm/action-install-gh-release@v1.12.0 + with: + repo: ethereum-optimism/supersim + platform: linux + - name: Set up Rundler uses: jaxxstorm/action-install-gh-release@v1.12.0 with: From ae34711701d3f4316a5e85442ae618a6506f55e1 Mon Sep 17 00:00:00 2001 From: "italiano@oplabs.co" Date: Wed, 2 Oct 2024 13:20:57 -0400 Subject: [PATCH 3/5] Adds changeset for supersim --- .changeset/eighty-elephants-sit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eighty-elephants-sit.md diff --git a/.changeset/eighty-elephants-sit.md b/.changeset/eighty-elephants-sit.md new file mode 100644 index 0000000..8384b31 --- /dev/null +++ b/.changeset/eighty-elephants-sit.md @@ -0,0 +1,5 @@ +--- +"prool": patch +--- + +Added supersim instance support From fafbca4aa703766d116f57fe8747f680fd937c8f Mon Sep 17 00:00:00 2001 From: "italiano@oplabs.co" Date: Wed, 2 Oct 2024 13:26:42 -0400 Subject: [PATCH 4/5] Add supersim to README --- src/README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/README.md b/src/README.md index 0678505..d0f6fba 100644 --- a/src/README.md +++ b/src/README.md @@ -38,7 +38,7 @@ Prool is a library that provides programmatic HTTP testing instances for Ethereu Prool contains a set of pre-configured instances that can be used to simulate Ethereum server environments, being: -- **Local Execution Nodes:** [`anvil`](#anvil-execution-node) +- **Local Execution Nodes:** [`anvil`](#anvil-execution-node), [`supersim`](#supersim-op-execution-nodes) - **Bundler Nodes:** [`alto`](#alto-bundler-node), [`rundler`](#rundler-bundler-node), [`silius`](#silius-bundler-node), [`stackup`](#stackup-bundler-node) - **Indexer Nodes:** `ponder`⚠️ @@ -51,6 +51,7 @@ You can also create your own custom instances by using the [`defineInstance` fun - [Install](#install) - [Getting Started](#getting-started) - [Anvil (Execution Node)](#anvil-execution-node) + - [Supersim (OP Execution Nodes)](#supersim-op-execution-nodes) - [Alto (Bundler Node)](#alto-bundler-node) - [Rundler (Bundler Node)](#rundler-bundler-node) - [Silius (Bundler Node)](#silius-bundler-node) @@ -106,6 +107,36 @@ await server.start() See [`AnvilParameters`](https://github.com/wevm/prool/blob/801ede06ded8b2cb2d59c95294aae795e548897c/src/instances/anvil.ts#L5). +### Supersim (OP Execution Nodes) + +#### Requirements + +- [Foundry](https://getfoundry.sh/) binary installed + - Download: `curl -L https://foundry.paradigm.xyz | bash` +- [Supersim](https://github.com/ethereum-optimism/supersim?tab=readme-ov-file#2-install-supersim) binary installed + +#### Usage + +```ts +import { createServer } from 'prool' +import { supersim } from 'prool/instances' + +const server = createServer({ + instance: supersim(), +}) + +await server.start() +// Instances accessible at: +// "http://localhost:8545/1" +// "http://localhost:8545/2" +// "http://localhost:8545/3" +// "http://localhost:8545/n" +``` + +#### Parameters + +See [`SupersimParameters`](https://github.com/wevm/prool/blob/ae34711701d3f4316a5e85442ae618a6506f55e1/src/instances/supersim.ts#L5). + ### Alto (Bundler Node) #### Requirements From 76b84e964e1013f76545f6cc2cf7c0ab0cf0e302 Mon Sep 17 00:00:00 2001 From: "italiano@oplabs.co" Date: Wed, 2 Oct 2024 13:34:57 -0400 Subject: [PATCH 5/5] remove supersimArgs obj --- src/instances/supersim.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/instances/supersim.ts b/src/instances/supersim.ts index 92a9e99..d0c672c 100644 --- a/src/instances/supersim.ts +++ b/src/instances/supersim.ts @@ -50,13 +50,6 @@ export const supersim = defineInstance((parameters?: SupersimParameters) => { const name = 'supersim' const process = execa({ name }) - const supersimArgs = { - l1Port: parameters?.l1Port, - l2StartingPort: parameters?.l2StartingPort, - interop: parameters?.interop ? { ...parameters.interop } : undefined, - ...parameters?.fork, - } - const args = toArgs({ // ports 'l1.port': parameters?.l1Port, @@ -84,7 +77,7 @@ export const supersim = defineInstance((parameters?: SupersimParameters) => { }, host: parameters?.host ?? DEFAULT_HOST, name, - port: supersimArgs.l1Port ?? DEFAULT_PORT, + port: parameters?.l1Port ?? DEFAULT_PORT, async start(_, options) { return await process.start(($) => $(binary, args), { ...options,