Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions src/instances/tempo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as os from 'node:os'
import getPort from 'get-port'
import { Instance } from 'prool'
import { afterEach, expect, test } from 'vitest'
import { type MessageEvent, WebSocket } from 'ws'
import { command } from './tempo.js'

const instances: Instance.Instance[] = []
Expand Down Expand Up @@ -140,12 +141,34 @@ test(
},
)

test(
'behavior: serves websocket on http port',
{ timeout: slowTestTimeout },
async () => {
const instance = defineInstance()
await instance.start()

const ws = new WebSocket(`ws://localhost:${port}`)
await new Promise((resolve, reject) => {
ws.addEventListener('open', resolve)
ws.addEventListener('error', reject)
})
ws.send(JSON.stringify({ id: 0, jsonrpc: '2.0', method: 'eth_chainId' }))
const { data } = await new Promise<MessageEvent>((resolve) =>
ws.addEventListener('message', resolve),
)
ws.close()

expect(JSON.parse(data.toString()).result).toBeDefined()
},
)

const redact = (args: string[]) =>
args.join(' ').replaceAll(os.tmpdir(), '<tmpdir>')

test('command: default', () => {
expect(redact(command({ port: 8545 }))).toMatchInlineSnapshot(
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:8545 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws.port 8565"`,
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:8545 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws --ws.addr 0.0.0.0 --ws.api all --ws.port 8545"`,
)
})

Expand All @@ -155,22 +178,30 @@ test('command: behavior: faucet node address', () => {
command({ faucet: { nodeAddress: 'http://localhost:1337' }, port: 8545 }),
),
).toMatchInlineSnapshot(
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:1337 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws.port 8565"`,
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:1337 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws --ws.addr 0.0.0.0 --ws.api all --ws.port 8545"`,
)
})

test('command: behavior: faucet disabled', () => {
expect(
redact(command({ faucet: { enabled: false }, port: 8545 })),
).toMatchInlineSnapshot(
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled false --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws.port 8565"`,
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled false --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws --ws.addr 0.0.0.0 --ws.api all --ws.port 8545"`,
)
})

test('command: behavior: ws port override', () => {
expect(
redact(command({ port: 8545, ws: [true, { port: 8565 }] })),
).toMatchInlineSnapshot(
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:8545 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 8545 --port 8555 --ws --ws.port 8565"`,
)
})

test('command: behavior: http port override', () => {
expect(
redact(command({ http: { port: 1337 }, port: 8545 })),
).toMatchInlineSnapshot(
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:1337 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 1337 --port 8555 --ws.port 8565"`,
`"node --authrpc.port 8575 --datadir <tmpdir>/.prool/tempo.8545 --dev --dev.block-time 50ms --engine.disable-precompile-cache --engine.legacy-state-root --faucet.address 0x20c0000000000000000000000000000000000000 0x20c0000000000000000000000000000000000001 0x20c0000000000000000000000000000000000002 0x20c0000000000000000000000000000000000003 --faucet.amount 1000000000000 --faucet.enabled --faucet.private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --faucet.node-address http://localhost:1337 --http.addr 0.0.0.0 --http.api all --http.corsdomain * --http.port 1337 --port 8555 --ws --ws.addr 0.0.0.0 --ws.api all --ws.port 1337"`,
)
})
17 changes: 14 additions & 3 deletions src/instances/tempo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ export function command(parameters: tempo.Parameters): string[] {
port: port!,
},
port: port! + 10,
ws: {
port: port! + 20,
},
ws: [
true,
{
addr: '0.0.0.0',
api: 'all',
},
],
}

const args = deepAssign(defaultParameters, rest)
Expand All @@ -58,6 +62,13 @@ export function command(parameters: tempo.Parameters): string[] {
if (faucet?.enabled && !faucet.nodeAddress)
faucet.nodeAddress = `http://localhost:${http.port}`

// WS defaults to the http port: one endpoint (and one proxied server port) serves both protocols.
const ws = args['ws'] as [true, { port?: number | undefined }?] | undefined
if (Array.isArray(ws) && ws[0] === true) {
ws[1] ??= {}
ws[1].port ??= http.port
}

return [
'node',
...toArgs(args, {
Expand Down
5 changes: 3 additions & 2 deletions src/instances/tempoZone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,14 @@ export const tempoZone = Instance.define(
})`${[binary, ...command({ ...args, port })]}`,
{
...options,
// Resolve when the zone RPC server is listening (fires after provisioning).
// Resolve when the private zone RPC is listening (last server to start).
resolver({ process, reject, resolve }) {
let stderr = ''
process.stdout.on('data', (data) => {
const message = data.toString()
if (log) console.log(message)
if (message.includes('RPC HTTP server started')) resolve()
if (message.includes('Private zone RPC server started'))
resolve()
if (message.includes('shutting down')) reject('shutting down')
})
process.stderr.on('data', (data) => {
Expand Down
18 changes: 18 additions & 0 deletions src/testcontainers/Instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
for (const instance of instances) await instance.stop().catch(() => {})
})

test('default', { timeout: slowTestTimeout }, async () => {

Check failure on line 20 in src/testcontainers/Instance.test.ts

View workflow job for this annotation

GitHub Actions / Verify / Test

src/testcontainers/Instance.test.ts > default

Error: Test timed out in 30000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ src/testcontainers/Instance.test.ts:20:1
const messages: string[] = []
const stdouts: string[] = []

Expand Down Expand Up @@ -167,6 +167,24 @@
const { result: blockNumber } = await rpc('eth_blockNumber', [])
expect(blockNumber).toBeDefined()

// Private RPC (auth-gated `eth_*` + `zone_*`) is exposed; reachability only (no token).
const { privateRpc } = instance._internal
expect(privateRpc).toBeDefined()
const response = await fetch(
`http://${privateRpc!.host}:${privateRpc!.port}`,
{
body: JSON.stringify({
id: 0,
jsonrpc: '2.0',
method: 'zone_getZoneInfo',
params: [],
}),
headers: { 'Content-Type': 'application/json' },
method: 'POST',
},
)
expect(response.status).toBeDefined()

await instance.stop()
expect(instance.status).toEqual('stopped')
})
Expand Down
20 changes: 18 additions & 2 deletions src/testcontainers/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export const tempoZone = Instance.define(
let container: StartedTestContainer | undefined
let l1Container: StartedTestContainer | undefined
let network: StartedNetwork | undefined
let privateRpcPort: number | undefined

async function teardown() {
if (container) await container.stop().catch(() => {})
Expand All @@ -190,6 +191,7 @@ export const tempoZone = Instance.define(
container = undefined
l1Container = undefined
network = undefined
privateRpcPort = undefined
}

return {
Expand All @@ -203,12 +205,23 @@ export const tempoZone = Instance.define(
wsPort: l1Container.getMappedPort(l1WsPort),
}
},
get privateRpc() {
if (!container || !privateRpcPort) return undefined
return {
host: container.getHost(),
port: container.getMappedPort(privateRpcPort),
}
},
},
host: args.host ?? 'localhost',
name,
port: args.port ?? 9545,
async start({ port = args.port }, { emitter, setEndpoint }) {
const containerPort = port ?? 9545
// Mirrors the `zoneCommand` default; serves authenticated `eth_*` + `zone_*`.
privateRpcPort =
(args['privateRpc'] as { port?: number } | undefined)?.port ??
containerPort + 3
const timeout = ContainerOptions.resolveStartupTimeout(startupTimeout)

const logConsumer =
Expand Down Expand Up @@ -272,7 +285,7 @@ export const tempoZone = Instance.define(
let c = new GenericContainer(image)
.withPullPolicy(PullPolicy.alwaysPull())
.withPlatform('linux/x86_64')
.withExposedPorts(containerPort)
.withExposedPorts(containerPort, privateRpcPort)
.withExtraHosts([
{ host: 'host.docker.internal', ipAddress: 'host-gateway' },
])
Expand All @@ -290,7 +303,10 @@ export const tempoZone = Instance.define(
port: containerPort,
}),
)
.withWaitStrategy(Wait.forLogMessage('RPC HTTP server started'))
// Fires after the public RPC; last server to start.
.withWaitStrategy(
Wait.forLogMessage('Private zone RPC server started'),
)
.withLogConsumer(logConsumer(promise.reject))
.withStartupTimeout(timeout)
if (network) c = c.withNetwork(network)
Expand Down
Loading