prool 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
- import { GenericContainer, PullPolicy, Wait, } from 'testcontainers';
1
+ import { GenericContainer, Network, PullPolicy, Wait, } from 'testcontainers';
2
2
  import * as Instance from '../Instance.js';
3
3
  import { command } from '../instances/tempo.js';
4
+ import { command as zoneCommand, } from '../instances/tempoZone.js';
4
5
  import * as ContainerOptions from './containerOptions.js';
5
6
  /**
6
7
  * Defines a Tempo instance.
@@ -86,4 +87,174 @@ export const tempo = Instance.define((parameters) => {
86
87
  },
87
88
  };
88
89
  });
90
+ /**
91
+ * Defines a Tempo Zone instance.
92
+ *
93
+ * Starts a Tempo dev L1 container and a zone container (`tempo-zone dev`) on a
94
+ * shared network, provisioning a fresh zone against the L1. Pass `l1.rpcUrl`
95
+ * to attach to an existing L1 instead (the URL must be reachable from inside
96
+ * the container, e.g. `ws://host.docker.internal:8546`).
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const instance = Instance.tempoZone({ port: 9545 })
101
+ * await instance.start()
102
+ * // ...
103
+ * await instance.stop()
104
+ * ```
105
+ */
106
+ export const tempoZone = Instance.define((parameters) => {
107
+ const { containerName = `tempo-zone.${crypto.randomUUID()}`, image = 'ghcr.io/tempoxyz/tempo-zone:latest', l1: l1Parameters, log: log_, startupTimeout, ...args } = parameters || {};
108
+ const log = (() => {
109
+ try {
110
+ return JSON.parse(log_);
111
+ }
112
+ catch {
113
+ return log_;
114
+ }
115
+ })();
116
+ const RUST_LOG = log && typeof log !== 'boolean' ? log : '';
117
+ // L1 ports inside the container (only reachable over the shared network).
118
+ const l1HttpPort = 8545;
119
+ const l1WsPort = 8546;
120
+ const name = 'tempo-zone';
121
+ let container;
122
+ let l1Container;
123
+ let network;
124
+ let privateRpcPort;
125
+ async function teardown() {
126
+ if (container)
127
+ await container.stop().catch(() => { });
128
+ if (l1Container)
129
+ await l1Container.stop().catch(() => { });
130
+ if (network)
131
+ await network.stop().catch(() => { });
132
+ container = undefined;
133
+ l1Container = undefined;
134
+ network = undefined;
135
+ privateRpcPort = undefined;
136
+ }
137
+ return {
138
+ _internal: {
139
+ args,
140
+ get l1() {
141
+ if (!l1Container)
142
+ return undefined;
143
+ return {
144
+ host: l1Container.getHost(),
145
+ port: l1Container.getMappedPort(l1HttpPort),
146
+ wsPort: l1Container.getMappedPort(l1WsPort),
147
+ };
148
+ },
149
+ get privateRpc() {
150
+ if (!container || !privateRpcPort)
151
+ return undefined;
152
+ return {
153
+ host: container.getHost(),
154
+ port: container.getMappedPort(privateRpcPort),
155
+ };
156
+ },
157
+ },
158
+ host: args.host ?? 'localhost',
159
+ name,
160
+ port: args.port ?? 9545,
161
+ async start({ port = args.port }, { emitter, setEndpoint }) {
162
+ const containerPort = port ?? 9545;
163
+ // Mirrors the `zoneCommand` default; serves authenticated `eth_*` + `zone_*`.
164
+ privateRpcPort =
165
+ args['privateRpc']?.port ??
166
+ containerPort + 3;
167
+ const timeout = ContainerOptions.resolveStartupTimeout(startupTimeout);
168
+ const logConsumer = (reject) => (stream) => {
169
+ stream.on('data', (data) => {
170
+ const message = data.toString();
171
+ emitter.emit('message', message);
172
+ emitter.emit('stdout', message);
173
+ if (log)
174
+ console.log(message);
175
+ if (message.includes('shutting down'))
176
+ reject(new Error(`Failed to start: ${message}`));
177
+ });
178
+ stream.on('error', (error) => {
179
+ if (log)
180
+ console.error(error.message);
181
+ emitter.emit('message', error.message);
182
+ emitter.emit('stderr', error.message);
183
+ reject(new Error(`Failed to start: ${error.message}`));
184
+ });
185
+ };
186
+ try {
187
+ // Start a dev L1 on a shared network unless attaching to an existing one.
188
+ let l1RpcUrl = l1Parameters?.rpcUrl;
189
+ if (!l1RpcUrl) {
190
+ network = await new Network().start();
191
+ l1Container = await new GenericContainer(l1Parameters?.image ?? 'ghcr.io/tempoxyz/tempo:latest')
192
+ .withPullPolicy(PullPolicy.alwaysPull())
193
+ .withPlatform('linux/x86_64')
194
+ .withNetwork(network)
195
+ .withNetworkAliases('l1')
196
+ .withExposedPorts(l1HttpPort, l1WsPort)
197
+ .withName(`${containerName}.l1`)
198
+ .withEnvironment({ RUST_LOG })
199
+ .withCommand(command({
200
+ port: l1HttpPort,
201
+ // The zone anchors to the L1 over WebSocket.
202
+ ws: [true, { addr: '0.0.0.0', api: 'all', port: l1WsPort }],
203
+ }))
204
+ .withWaitStrategy(Wait.forLogMessage(/Received (block|new payload) from consensus engine/))
205
+ .withLogConsumer(logConsumer(() => {
206
+ // L1 shutdown surfaces via the zone container failing to start.
207
+ }))
208
+ .withStartupTimeout(timeout)
209
+ .start();
210
+ l1RpcUrl = `ws://l1:${l1WsPort}`;
211
+ }
212
+ const promise = Promise.withResolvers();
213
+ let c = new GenericContainer(image)
214
+ .withPullPolicy(PullPolicy.alwaysPull())
215
+ .withPlatform('linux/x86_64')
216
+ .withExposedPorts(containerPort, privateRpcPort)
217
+ .withExtraHosts([
218
+ { host: 'host.docker.internal', ipAddress: 'host-gateway' },
219
+ ])
220
+ .withName(containerName)
221
+ .withEnvironment({ RUST_LOG })
222
+ .withCommand(zoneCommand({
223
+ ...args,
224
+ l1: {
225
+ ...(l1Parameters?.factoryAddress
226
+ ? { factoryAddress: l1Parameters.factoryAddress }
227
+ : {}),
228
+ rpcUrl: l1RpcUrl,
229
+ },
230
+ port: containerPort,
231
+ }))
232
+ // Fires after the public RPC; last server to start.
233
+ .withWaitStrategy(Wait.forLogMessage('Private zone RPC server started'))
234
+ .withLogConsumer(logConsumer(promise.reject))
235
+ .withStartupTimeout(timeout);
236
+ if (network)
237
+ c = c.withNetwork(network);
238
+ c.start()
239
+ .then((started) => {
240
+ container = started;
241
+ setEndpoint?.({
242
+ host: started.getHost(),
243
+ port: started.getMappedPort(containerPort),
244
+ });
245
+ promise.resolve();
246
+ })
247
+ .catch(promise.reject);
248
+ await promise.promise;
249
+ }
250
+ catch (error) {
251
+ await teardown();
252
+ throw error;
253
+ }
254
+ },
255
+ async stop() {
256
+ await teardown();
257
+ },
258
+ };
259
+ });
89
260
  //# sourceMappingURL=Instance.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Instance.js","sourceRoot":"","sources":["../../src/testcontainers/Instance.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,UAAU,EAEV,IAAI,GACL,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAA4B,MAAM,uBAAuB,CAAA;AACzE,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA;AAIzD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,UAA6B,EAAE,EAAE;IACrE,MAAM,EACJ,aAAa,GAAG,SAAS,MAAM,CAAC,UAAU,EAAE,EAAE,EAC9C,KAAK,GAAG,+BAA+B,EACvC,GAAG,EAAE,IAAI,EACT,cAAc,EACd,GAAG,IAAI,EACR,GAAG,UAAU,IAAI,EAAE,CAAA;IAEpB,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE;QAChB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAc,CAAC,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,CAAC,EAAE,CAAA;IACJ,MAAM,QAAQ,GAAG,GAAG,IAAI,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IAE3D,MAAM,IAAI,GAAG,OAAO,CAAA;IACpB,IAAI,SAA2C,CAAA;IAE/C,OAAO;QACL,SAAS,EAAE;YACT,IAAI;SACL;QACD,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,WAAW;QAC9B,IAAI;QACJ,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QACvB,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE;YACxD,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;YAE7C,MAAM,aAAa,GAAG,IAAI,IAAI,IAAI,CAAA;YAElC,MAAM,CAAC,GAAG,IAAI,gBAAgB,CAAC,KAAK,CAAC;iBAClC,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;iBACvC,YAAY,CAAC,cAAc,CAAC;iBAC5B,gBAAgB,CAAC,aAAa,CAAC;iBAC/B,cAAc,CAAC;gBACd,EAAE,IAAI,EAAE,sBAAsB,EAAE,SAAS,EAAE,cAAc,EAAE;aAC5D,CAAC;iBACD,QAAQ,CAAC,aAAa,CAAC;iBACvB,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAC;iBAC7B,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;iBACtD,gBAAgB,CACf,IAAI,CAAC,aAAa,CAChB,oDAAoD,CACrD,CACF;iBACA,eAAe,CAAC,CAAC,MAAM,EAAE,EAAE;gBAC1B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;oBACzB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;oBAC/B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;oBAChC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;oBAC/B,IAAI,GAAG;wBAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;oBAC7B,IAAI,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;wBACnC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC,CAAA;gBAC5D,CAAC,CAAC,CAAA;gBACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC3B,IAAI,GAAG;wBAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;oBACrC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;oBACtC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;oBACrC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;gBAChE,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC;iBACD,kBAAkB,CACjB,gBAAgB,CAAC,qBAAqB,CAAC,cAAc,CAAC,CACvD,CAAA;YAEH,CAAC,CAAC,KAAK,EAAE;iBACN,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChB,SAAS,GAAG,OAAO,CAAA;gBACnB,WAAW,EAAE,CAAC;oBACZ,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE;oBACvB,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC;iBAC3C,CAAC,CAAA;gBACF,OAAO,CAAC,OAAO,EAAE,CAAA;YACnB,CAAC,CAAC;iBACD,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;YAExB,OAAO,OAAO,CAAC,OAAO,CAAA;QACxB,CAAC;QACD,KAAK,CAAC,IAAI;YACR,IAAI,CAAC,SAAS;gBAAE,OAAM;YACtB,MAAM,SAAS,CAAC,IAAI,EAAE,CAAA;YACtB,SAAS,GAAG,SAAS,CAAA;QACvB,CAAC;KACF,CAAA;AACH,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"Instance.js","sourceRoot":"","sources":["../../src/testcontainers/Instance.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prool",
3
3
  "description": "HTTP testing instances for Ethereum",
4
- "version": "0.2.5",
4
+ "version": "0.2.7",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "files": [
package/src/Instance.ts CHANGED
@@ -3,6 +3,7 @@ import { EventEmitter } from 'eventemitter3'
3
3
  export { alto } from './instances/alto.js'
4
4
  export { anvil } from './instances/anvil.js'
5
5
  export { tempo } from './instances/tempo.js'
6
+ export { tempoZone } from './instances/tempoZone.js'
6
7
 
7
8
  type EventTypes = {
8
9
  exit: [code: number | null, signal: NodeJS.Signals | null]
@@ -2,6 +2,7 @@ import * as os from 'node:os'
2
2
  import getPort from 'get-port'
3
3
  import { Instance } from 'prool'
4
4
  import { afterEach, expect, test } from 'vitest'
5
+ import { type MessageEvent, WebSocket } from 'ws'
5
6
  import { command } from './tempo.js'
6
7
 
7
8
  const instances: Instance.Instance[] = []
@@ -140,12 +141,34 @@ test(
140
141
  },
141
142
  )
142
143
 
144
+ test(
145
+ 'behavior: serves websocket on http port',
146
+ { timeout: slowTestTimeout },
147
+ async () => {
148
+ const instance = defineInstance()
149
+ await instance.start()
150
+
151
+ const ws = new WebSocket(`ws://localhost:${port}`)
152
+ await new Promise((resolve, reject) => {
153
+ ws.addEventListener('open', resolve)
154
+ ws.addEventListener('error', reject)
155
+ })
156
+ ws.send(JSON.stringify({ id: 0, jsonrpc: '2.0', method: 'eth_chainId' }))
157
+ const { data } = await new Promise<MessageEvent>((resolve) =>
158
+ ws.addEventListener('message', resolve),
159
+ )
160
+ ws.close()
161
+
162
+ expect(JSON.parse(data.toString()).result).toBeDefined()
163
+ },
164
+ )
165
+
143
166
  const redact = (args: string[]) =>
144
167
  args.join(' ').replaceAll(os.tmpdir(), '<tmpdir>')
145
168
 
146
169
  test('command: default', () => {
147
170
  expect(redact(command({ port: 8545 }))).toMatchInlineSnapshot(
148
- `"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"`,
171
+ `"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"`,
149
172
  )
150
173
  })
151
174
 
@@ -155,7 +178,7 @@ test('command: behavior: faucet node address', () => {
155
178
  command({ faucet: { nodeAddress: 'http://localhost:1337' }, port: 8545 }),
156
179
  ),
157
180
  ).toMatchInlineSnapshot(
158
- `"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"`,
181
+ `"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"`,
159
182
  )
160
183
  })
161
184
 
@@ -163,7 +186,15 @@ test('command: behavior: faucet disabled', () => {
163
186
  expect(
164
187
  redact(command({ faucet: { enabled: false }, port: 8545 })),
165
188
  ).toMatchInlineSnapshot(
166
- `"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"`,
189
+ `"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"`,
190
+ )
191
+ })
192
+
193
+ test('command: behavior: ws port override', () => {
194
+ expect(
195
+ redact(command({ port: 8545, ws: [true, { port: 8565 }] })),
196
+ ).toMatchInlineSnapshot(
197
+ `"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"`,
167
198
  )
168
199
  })
169
200
 
@@ -171,6 +202,6 @@ test('command: behavior: http port override', () => {
171
202
  expect(
172
203
  redact(command({ http: { port: 1337 }, port: 8545 })),
173
204
  ).toMatchInlineSnapshot(
174
- `"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"`,
205
+ `"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"`,
175
206
  )
176
207
  })
@@ -45,9 +45,13 @@ export function command(parameters: tempo.Parameters): string[] {
45
45
  port: port!,
46
46
  },
47
47
  port: port! + 10,
48
- ws: {
49
- port: port! + 20,
50
- },
48
+ ws: [
49
+ true,
50
+ {
51
+ addr: '0.0.0.0',
52
+ api: 'all',
53
+ },
54
+ ],
51
55
  }
52
56
 
53
57
  const args = deepAssign(defaultParameters, rest)
@@ -58,6 +62,13 @@ export function command(parameters: tempo.Parameters): string[] {
58
62
  if (faucet?.enabled && !faucet.nodeAddress)
59
63
  faucet.nodeAddress = `http://localhost:${http.port}`
60
64
 
65
+ // WS defaults to the http port: one endpoint (and one proxied server port) serves both protocols.
66
+ const ws = args['ws'] as [true, { port?: number | undefined }?] | undefined
67
+ if (Array.isArray(ws) && ws[0] === true) {
68
+ ws[1] ??= {}
69
+ ws[1].port ??= http.port
70
+ }
71
+
61
72
  return [
62
73
  'node',
63
74
  ...toArgs(args, {
@@ -0,0 +1,60 @@
1
+ import * as os from 'node:os'
2
+ import { expect, test } from 'vitest'
3
+ import { command } from './tempoZone.js'
4
+
5
+ const redact = (args: string[]) =>
6
+ args.join(' ').replaceAll(os.tmpdir(), '<tmpdir>')
7
+
8
+ test('command: default', () => {
9
+ expect(redact(command({ port: 9545 }))).toMatchInlineSnapshot(
10
+ `"dev --datadir <tmpdir>/.prool/tempo-zone.9545 --http.addr 0.0.0.0 --http.port 9545 --l1.rpc-url ws://localhost:8546 --private-rpc.port 9548 -- --authrpc.port 9549 --ipcdisable"`,
11
+ )
12
+ })
13
+
14
+ test('command: behavior: l1 options', () => {
15
+ expect(
16
+ redact(
17
+ command({
18
+ l1: {
19
+ factoryAddress: '0x00000000000000000000000000000000000fac70',
20
+ rpcUrl: 'ws://l1:8546',
21
+ },
22
+ port: 9545,
23
+ }),
24
+ ),
25
+ ).toMatchInlineSnapshot(
26
+ `"dev --datadir <tmpdir>/.prool/tempo-zone.9545 --http.addr 0.0.0.0 --http.port 9545 --l1.rpc-url ws://l1:8546 --l1.factory-address 0x00000000000000000000000000000000000fac70 --private-rpc.port 9548 -- --authrpc.port 9549 --ipcdisable"`,
27
+ )
28
+ })
29
+
30
+ test('command: behavior: dev options', () => {
31
+ expect(
32
+ redact(
33
+ command({
34
+ dev: {
35
+ key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
36
+ token: '0x20c0000000000000000000000000000000000001',
37
+ },
38
+ port: 9545,
39
+ }),
40
+ ),
41
+ ).toMatchInlineSnapshot(
42
+ `"dev --datadir <tmpdir>/.prool/tempo-zone.9545 --http.addr 0.0.0.0 --http.port 9545 --l1.rpc-url ws://localhost:8546 --private-rpc.port 9548 --dev.key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --dev.token 0x20c0000000000000000000000000000000000001 -- --authrpc.port 9549 --ipcdisable"`,
43
+ )
44
+ })
45
+
46
+ test('command: behavior: node args', () => {
47
+ expect(
48
+ redact(command({ nodeArgs: ['--full'], port: 9545 })),
49
+ ).toMatchInlineSnapshot(
50
+ `"dev --datadir <tmpdir>/.prool/tempo-zone.9545 --http.addr 0.0.0.0 --http.port 9545 --l1.rpc-url ws://localhost:8546 --private-rpc.port 9548 -- --authrpc.port 9549 --ipcdisable --full"`,
51
+ )
52
+ })
53
+
54
+ test('command: behavior: private rpc port override', () => {
55
+ expect(
56
+ redact(command({ port: 9545, privateRpc: { port: 1337 } })),
57
+ ).toMatchInlineSnapshot(
58
+ `"dev --datadir <tmpdir>/.prool/tempo-zone.9545 --http.addr 0.0.0.0 --http.port 9545 --l1.rpc-url ws://localhost:8546 --private-rpc.port 1337 -- --authrpc.port 9549 --ipcdisable"`,
59
+ )
60
+ })
@@ -0,0 +1,208 @@
1
+ import * as os from 'node:os'
2
+ import * as path from 'node:path'
3
+ import * as Instance from '../Instance.js'
4
+ import { deepAssign, toArgs } from '../internal/utils.js'
5
+ import { execa } from '../processes/execa.js'
6
+
7
+ // `tempo-zone dev` derives the WS RPC (port + 1) and P2P (port + 2) ports from
8
+ // `http.port`. Each instance occupies the compact block [port, port + 4].
9
+ export function command(parameters: tempoZone.Parameters): string[] {
10
+ const { nodeArgs, port, ...rest } = parameters
11
+
12
+ const datadir = path.join(os.tmpdir(), '.prool', `tempo-zone.${port}`)
13
+ const defaultParameters = {
14
+ datadir,
15
+ http: {
16
+ addr: '0.0.0.0',
17
+ port: port!,
18
+ },
19
+ l1: {
20
+ rpcUrl: 'ws://localhost:8546',
21
+ },
22
+ privateRpc: {
23
+ port: port! + 3,
24
+ },
25
+ }
26
+
27
+ const args = deepAssign(defaultParameters, rest)
28
+
29
+ return [
30
+ 'dev',
31
+ ...toArgs(args, {
32
+ arraySeparator: null,
33
+ }),
34
+ // Forwarded to `tempo-zone node`; keeps concurrent instances from colliding.
35
+ '--',
36
+ '--authrpc.port',
37
+ String(port! + 4),
38
+ '--ipcdisable',
39
+ ...((nodeArgs as string[] | undefined) ?? []),
40
+ ]
41
+ }
42
+
43
+ /**
44
+ * Defines a Tempo Zone instance.
45
+ *
46
+ * Provisions a fresh zone against a Tempo dev L1 (`tempo-zone dev`) and runs
47
+ * the zone node. Requires a reachable Tempo dev L1 websocket RPC (`l1.rpcUrl`).
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * const instance = Instance.tempoZone({
52
+ * l1: { rpcUrl: 'ws://localhost:8546' },
53
+ * port: 9545,
54
+ * })
55
+ * await instance.start()
56
+ * // ...
57
+ * await instance.stop()
58
+ * ```
59
+ */
60
+ export const tempoZone = Instance.define(
61
+ (parameters?: tempoZone.Parameters) => {
62
+ const { binary = 'tempo-zone', log: log_, ...args } = parameters || {}
63
+
64
+ const log = (() => {
65
+ try {
66
+ return JSON.parse(log_ as string)
67
+ } catch {
68
+ return log_
69
+ }
70
+ })()
71
+ const RUST_LOG = log && typeof log !== 'boolean' ? log : ''
72
+
73
+ const name = 'tempo-zone'
74
+ const process = execa({ name })
75
+
76
+ return {
77
+ _internal: {
78
+ args,
79
+ get process() {
80
+ return process._internal.process
81
+ },
82
+ },
83
+ host: args.host ?? 'localhost',
84
+ name,
85
+ port: args.port ?? 9545,
86
+ async start({ port = args.port }, options) {
87
+ return await process.start(
88
+ ($) =>
89
+ $({
90
+ env: {
91
+ RUST_LOG,
92
+ },
93
+ })`${[binary, ...command({ ...args, port })]}`,
94
+ {
95
+ ...options,
96
+ // Resolve when the private zone RPC is listening (last server to start).
97
+ resolver({ process, reject, resolve }) {
98
+ let stderr = ''
99
+ process.stdout.on('data', (data) => {
100
+ const message = data.toString()
101
+ if (log) console.log(message)
102
+ if (message.includes('Private zone RPC server started'))
103
+ resolve()
104
+ if (message.includes('shutting down')) reject('shutting down')
105
+ })
106
+ process.stderr.on('data', (data) => {
107
+ const message = data.toString()
108
+ if (log) console.error(message)
109
+ stderr += message
110
+ })
111
+ // Provisioning failures (e.g. unreachable L1) exit non-zero before the RPC starts.
112
+ process.once('exit', (code) => {
113
+ if (code) reject(stderr || `exited with code ${code}`)
114
+ })
115
+ },
116
+ },
117
+ )
118
+ },
119
+ async stop() {
120
+ await process.stop()
121
+ },
122
+ }
123
+ },
124
+ )
125
+
126
+ export declare namespace tempoZone {
127
+ export type Parameters = {
128
+ /**
129
+ * Path or alias to the Tempo Zone binary.
130
+ */
131
+ binary?: string | undefined
132
+ /**
133
+ * Directory for genesis.json, zone.json, node data, and logs. Wiped on start.
134
+ */
135
+ datadir?: string | undefined
136
+ /**
137
+ * Dev provisioning options.
138
+ */
139
+ dev?:
140
+ | {
141
+ /**
142
+ * Dev private key (hex): L1 fee payer, factory deployer, portal admin,
143
+ * and zone sequencer.
144
+ */
145
+ key?: string | undefined
146
+ /**
147
+ * Initial TIP-20 token enabled on the portal.
148
+ * @default pathUSD (0x20c0000000000000000000000000000000000000)
149
+ */
150
+ token?: string | undefined
151
+ }
152
+ | undefined
153
+ /**
154
+ * Host the server will listen on.
155
+ */
156
+ host?: string | undefined
157
+ /**
158
+ * Tempo L1 options.
159
+ */
160
+ l1?:
161
+ | {
162
+ /**
163
+ * Existing ZoneFactory address on the L1. Deploys the bundled factory
164
+ * when omitted.
165
+ */
166
+ factoryAddress?: string | undefined
167
+ /**
168
+ * Tempo L1 WebSocket RPC URL.
169
+ * @default "ws://localhost:8546"
170
+ */
171
+ rpcUrl?: string | undefined
172
+ }
173
+ | undefined
174
+ /**
175
+ * Rust log level configuration (sets RUST_LOG environment variable).
176
+ * Can be a log level or a custom filter string.
177
+ */
178
+ log?:
179
+ | 'trace'
180
+ | 'debug'
181
+ | 'info'
182
+ | 'warn'
183
+ | 'error'
184
+ | (string & {})
185
+ | boolean
186
+ | undefined
187
+ /**
188
+ * Extra arguments forwarded to `tempo-zone node`.
189
+ */
190
+ nodeArgs?: string[] | undefined
191
+ /**
192
+ * Port the server will listen on.
193
+ */
194
+ port?: number | undefined
195
+ /**
196
+ * Private RPC options.
197
+ */
198
+ privateRpc?:
199
+ | {
200
+ /**
201
+ * Zone private RPC port.
202
+ * @default `port + 3`
203
+ */
204
+ port?: number | undefined
205
+ }
206
+ | undefined
207
+ } & Record<string, unknown>
208
+ }