moonwall 1.0.0-dev.0
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.
- package/LICENSE +681 -0
- package/README.md +54 -0
- package/config_schema.json +811 -0
- package/dist/api/constants/accounts.d.ts +36 -0
- package/dist/api/constants/accounts.d.ts.map +1 -0
- package/dist/api/constants/accounts.js +67 -0
- package/dist/api/constants/chain.d.ts +134 -0
- package/dist/api/constants/chain.d.ts.map +1 -0
- package/dist/api/constants/chain.js +149 -0
- package/dist/api/constants/index.d.ts +4 -0
- package/dist/api/constants/index.d.ts.map +1 -0
- package/dist/api/constants/index.js +3 -0
- package/dist/api/constants/smartContract.d.ts +29 -0
- package/dist/api/constants/smartContract.d.ts.map +1 -0
- package/dist/api/constants/smartContract.js +118 -0
- package/dist/api/testing/blocks.d.ts +59 -0
- package/dist/api/testing/blocks.d.ts.map +1 -0
- package/dist/api/testing/blocks.js +147 -0
- package/dist/api/testing/contracts.d.ts +5 -0
- package/dist/api/testing/contracts.d.ts.map +1 -0
- package/dist/api/testing/contracts.js +32 -0
- package/dist/api/testing/ethers.d.ts +3 -0
- package/dist/api/testing/ethers.d.ts.map +1 -0
- package/dist/api/testing/ethers.js +38 -0
- package/dist/api/testing/events.d.ts +12 -0
- package/dist/api/testing/events.d.ts.map +1 -0
- package/dist/api/testing/events.js +23 -0
- package/dist/api/testing/extrinsics.d.ts +5 -0
- package/dist/api/testing/extrinsics.d.ts.map +1 -0
- package/dist/api/testing/extrinsics.js +10 -0
- package/dist/api/testing/index.d.ts +9 -0
- package/dist/api/testing/index.d.ts.map +1 -0
- package/dist/api/testing/index.js +8 -0
- package/dist/api/testing/jumping.d.ts +8 -0
- package/dist/api/testing/jumping.d.ts.map +1 -0
- package/dist/api/testing/jumping.js +78 -0
- package/dist/api/testing/providers.d.ts +18 -0
- package/dist/api/testing/providers.d.ts.map +1 -0
- package/dist/api/testing/providers.js +34 -0
- package/dist/api/testing/viem.d.ts +139 -0
- package/dist/api/testing/viem.d.ts.map +1 -0
- package/dist/api/testing/viem.js +247 -0
- package/dist/api/types/config.d.ts +609 -0
- package/dist/api/types/config.d.ts.map +1 -0
- package/dist/api/types/config.js +1 -0
- package/dist/api/types/context.d.ts +125 -0
- package/dist/api/types/context.d.ts.map +1 -0
- package/dist/api/types/context.js +1 -0
- package/dist/api/types/contracts.d.ts +66 -0
- package/dist/api/types/contracts.d.ts.map +1 -0
- package/dist/api/types/contracts.js +1 -0
- package/dist/api/types/eth.d.ts +3 -0
- package/dist/api/types/eth.d.ts.map +1 -0
- package/dist/api/types/eth.js +1 -0
- package/dist/api/types/foundations.d.ts +11 -0
- package/dist/api/types/foundations.d.ts.map +1 -0
- package/dist/api/types/foundations.js +1 -0
- package/dist/api/types/helpers.d.ts +7 -0
- package/dist/api/types/helpers.d.ts.map +1 -0
- package/dist/api/types/helpers.js +1 -0
- package/dist/api/types/index.d.ts +8 -0
- package/dist/api/types/index.d.ts.map +1 -0
- package/dist/api/types/index.js +7 -0
- package/dist/api/types/runner.d.ts +490 -0
- package/dist/api/types/runner.d.ts.map +1 -0
- package/dist/api/types/runner.js +1 -0
- package/dist/cli/cmds/components/LogViewer.d.ts +17 -0
- package/dist/cli/cmds/components/LogViewer.d.ts.map +1 -0
- package/dist/cli/cmds/components/LogViewer.js +171 -0
- package/dist/cli/cmds/entrypoint.d.ts +6 -0
- package/dist/cli/cmds/entrypoint.d.ts.map +1 -0
- package/dist/cli/cmds/entrypoint.js +192 -0
- package/dist/cli/cmds/interactiveCmds/chopsticksIntCmds.d.ts +2 -0
- package/dist/cli/cmds/interactiveCmds/chopsticksIntCmds.d.ts.map +1 -0
- package/dist/cli/cmds/interactiveCmds/chopsticksIntCmds.js +117 -0
- package/dist/cli/cmds/interactiveCmds/devIntCmds.d.ts +2 -0
- package/dist/cli/cmds/interactiveCmds/devIntCmds.d.ts.map +1 -0
- package/dist/cli/cmds/interactiveCmds/devIntCmds.js +103 -0
- package/dist/cli/cmds/interactiveCmds/index.d.ts +4 -0
- package/dist/cli/cmds/interactiveCmds/index.d.ts.map +1 -0
- package/dist/cli/cmds/interactiveCmds/index.js +3 -0
- package/dist/cli/cmds/interactiveCmds/zombieIntCmds.d.ts +2 -0
- package/dist/cli/cmds/interactiveCmds/zombieIntCmds.d.ts.map +1 -0
- package/dist/cli/cmds/interactiveCmds/zombieIntCmds.js +32 -0
- package/dist/cli/cmds/main.d.ts +2 -0
- package/dist/cli/cmds/main.d.ts.map +1 -0
- package/dist/cli/cmds/main.js +336 -0
- package/dist/cli/cmds/runNetwork.d.ts +3 -0
- package/dist/cli/cmds/runNetwork.d.ts.map +1 -0
- package/dist/cli/cmds/runNetwork.js +292 -0
- package/dist/cli/cmds/runTests.d.ts +12 -0
- package/dist/cli/cmds/runTests.d.ts.map +1 -0
- package/dist/cli/cmds/runTests.js +257 -0
- package/dist/cli/internal/cmdFunctions/downloader.d.ts +4 -0
- package/dist/cli/internal/cmdFunctions/downloader.d.ts.map +1 -0
- package/dist/cli/internal/cmdFunctions/downloader.js +49 -0
- package/dist/cli/internal/cmdFunctions/fetchArtifact.d.ts +10 -0
- package/dist/cli/internal/cmdFunctions/fetchArtifact.d.ts.map +1 -0
- package/dist/cli/internal/cmdFunctions/fetchArtifact.js +145 -0
- package/dist/cli/internal/cmdFunctions/index.d.ts +5 -0
- package/dist/cli/internal/cmdFunctions/index.d.ts.map +1 -0
- package/dist/cli/internal/cmdFunctions/index.js +4 -0
- package/dist/cli/internal/cmdFunctions/initialisation.d.ts +20 -0
- package/dist/cli/internal/cmdFunctions/initialisation.d.ts.map +1 -0
- package/dist/cli/internal/cmdFunctions/initialisation.js +150 -0
- package/dist/cli/internal/cmdFunctions/tempLogs.d.ts +3 -0
- package/dist/cli/internal/cmdFunctions/tempLogs.d.ts.map +1 -0
- package/dist/cli/internal/cmdFunctions/tempLogs.js +37 -0
- package/dist/cli/internal/commandParsers.d.ts +59 -0
- package/dist/cli/internal/commandParsers.d.ts.map +1 -0
- package/dist/cli/internal/commandParsers.js +305 -0
- package/dist/cli/internal/deriveTestIds.d.ts +8 -0
- package/dist/cli/internal/deriveTestIds.d.ts.map +1 -0
- package/dist/cli/internal/deriveTestIds.js +123 -0
- package/dist/cli/internal/effect/index.d.ts +6 -0
- package/dist/cli/internal/effect/index.d.ts.map +1 -0
- package/dist/cli/internal/effect/index.js +5 -0
- package/dist/cli/internal/fileCheckers.d.ts +11 -0
- package/dist/cli/internal/fileCheckers.d.ts.map +1 -0
- package/dist/cli/internal/fileCheckers.js +165 -0
- package/dist/cli/internal/foundations/index.d.ts +4 -0
- package/dist/cli/internal/foundations/index.d.ts.map +1 -0
- package/dist/cli/internal/foundations/index.js +4 -0
- package/dist/cli/internal/index.d.ts +12 -0
- package/dist/cli/internal/index.d.ts.map +1 -0
- package/dist/cli/internal/index.js +11 -0
- package/dist/cli/internal/launcherCommon.d.ts +4 -0
- package/dist/cli/internal/launcherCommon.d.ts.map +1 -0
- package/dist/cli/internal/launcherCommon.js +130 -0
- package/dist/cli/internal/localNode.d.ts +16 -0
- package/dist/cli/internal/localNode.d.ts.map +1 -0
- package/dist/cli/internal/localNode.js +362 -0
- package/dist/cli/internal/logging.d.ts +2 -0
- package/dist/cli/internal/logging.d.ts.map +1 -0
- package/dist/cli/internal/logging.js +26 -0
- package/dist/cli/internal/node.d.ts +33 -0
- package/dist/cli/internal/node.d.ts.map +1 -0
- package/dist/cli/internal/node.js +228 -0
- package/dist/cli/internal/processHelpers.d.ts +17 -0
- package/dist/cli/internal/processHelpers.d.ts.map +1 -0
- package/dist/cli/internal/processHelpers.js +56 -0
- package/dist/cli/internal/providerFactories.d.ts +48 -0
- package/dist/cli/internal/providerFactories.d.ts.map +1 -0
- package/dist/cli/internal/providerFactories.js +442 -0
- package/dist/cli/internal/testIdParser.d.ts +34 -0
- package/dist/cli/internal/testIdParser.d.ts.map +1 -0
- package/dist/cli/internal/testIdParser.js +167 -0
- package/dist/cli/lib/binariesHelpers.d.ts +15 -0
- package/dist/cli/lib/binariesHelpers.d.ts.map +1 -0
- package/dist/cli/lib/binariesHelpers.js +99 -0
- package/dist/cli/lib/configReader.d.ts +8 -0
- package/dist/cli/lib/configReader.d.ts.map +1 -0
- package/dist/cli/lib/configReader.js +115 -0
- package/dist/cli/lib/contractFunctions.d.ts +2 -0
- package/dist/cli/lib/contractFunctions.d.ts.map +1 -0
- package/dist/cli/lib/contractFunctions.js +2 -0
- package/dist/cli/lib/globalContext.d.ts +46 -0
- package/dist/cli/lib/globalContext.d.ts.map +1 -0
- package/dist/cli/lib/globalContext.js +710 -0
- package/dist/cli/lib/governanceProcedures.d.ts +27 -0
- package/dist/cli/lib/governanceProcedures.d.ts.map +1 -0
- package/dist/cli/lib/governanceProcedures.js +458 -0
- package/dist/cli/lib/handlers/index.d.ts +5 -0
- package/dist/cli/lib/handlers/index.d.ts.map +1 -0
- package/dist/cli/lib/handlers/index.js +5 -0
- package/dist/cli/lib/repoDefinitions/index.d.ts +6 -0
- package/dist/cli/lib/repoDefinitions/index.d.ts.map +1 -0
- package/dist/cli/lib/repoDefinitions/index.js +21 -0
- package/dist/cli/lib/repoDefinitions/moonbeam.d.ts +4 -0
- package/dist/cli/lib/repoDefinitions/moonbeam.d.ts.map +1 -0
- package/dist/cli/lib/repoDefinitions/moonbeam.js +30 -0
- package/dist/cli/lib/repoDefinitions/polkadot.d.ts +4 -0
- package/dist/cli/lib/repoDefinitions/polkadot.d.ts.map +1 -0
- package/dist/cli/lib/repoDefinitions/polkadot.js +11 -0
- package/dist/cli/lib/repoDefinitions/tanssi.d.ts +4 -0
- package/dist/cli/lib/repoDefinitions/tanssi.d.ts.map +1 -0
- package/dist/cli/lib/repoDefinitions/tanssi.js +14 -0
- package/dist/cli/lib/rpcFunctions.d.ts +2 -0
- package/dist/cli/lib/rpcFunctions.d.ts.map +1 -0
- package/dist/cli/lib/rpcFunctions.js +26 -0
- package/dist/cli/lib/runnerContext.d.ts +32 -0
- package/dist/cli/lib/runnerContext.d.ts.map +1 -0
- package/dist/cli/lib/runnerContext.js +156 -0
- package/dist/cli/lib/shardManager.d.ts +40 -0
- package/dist/cli/lib/shardManager.d.ts.map +1 -0
- package/dist/cli/lib/shardManager.js +80 -0
- package/dist/cli/lib/upgradeProcedures.d.ts +5 -0
- package/dist/cli/lib/upgradeProcedures.d.ts.map +1 -0
- package/dist/cli/lib/upgradeProcedures.js +221 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/contracts/contractInteraction.d.ts +17 -0
- package/dist/contracts/contractInteraction.d.ts.map +1 -0
- package/dist/contracts/contractInteraction.js +170 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/foundations/chopsticks/handler.d.ts +3 -0
- package/dist/foundations/chopsticks/handler.d.ts.map +1 -0
- package/dist/foundations/chopsticks/handler.js +93 -0
- package/dist/foundations/chopsticks/helpers.d.ts +27 -0
- package/dist/foundations/chopsticks/helpers.d.ts.map +1 -0
- package/dist/foundations/chopsticks/helpers.js +133 -0
- package/dist/foundations/dev/handler.d.ts +3 -0
- package/dist/foundations/dev/handler.d.ts.map +1 -0
- package/dist/foundations/dev/handler.js +136 -0
- package/dist/foundations/dev/helpers.d.ts +27 -0
- package/dist/foundations/dev/helpers.d.ts.map +1 -0
- package/dist/foundations/dev/helpers.js +161 -0
- package/dist/foundations/read-only/handler.d.ts +3 -0
- package/dist/foundations/read-only/handler.d.ts.map +1 -0
- package/dist/foundations/read-only/handler.js +32 -0
- package/dist/foundations/zombie/handler.d.ts +3 -0
- package/dist/foundations/zombie/handler.d.ts.map +1 -0
- package/dist/foundations/zombie/handler.js +92 -0
- package/dist/foundations/zombie/helpers.d.ts +16 -0
- package/dist/foundations/zombie/helpers.d.ts.map +1 -0
- package/dist/foundations/zombie/helpers.js +97 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/internal/common.d.ts +3 -0
- package/dist/internal/common.d.ts.map +1 -0
- package/dist/internal/common.js +41 -0
- package/dist/internal/index.d.ts +4 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +3 -0
- package/dist/internal/logger.d.ts +24 -0
- package/dist/internal/logger.d.ts.map +1 -0
- package/dist/internal/logger.js +66 -0
- package/dist/internal/logging.d.ts +7 -0
- package/dist/internal/logging.d.ts.map +1 -0
- package/dist/internal/logging.js +36 -0
- package/dist/moondebug.d.ts +3 -0
- package/dist/moondebug.d.ts.map +1 -0
- package/dist/moondebug.js +2 -0
- package/dist/services/cache/FileLock.d.ts +11 -0
- package/dist/services/cache/FileLock.d.ts.map +1 -0
- package/dist/services/cache/FileLock.js +68 -0
- package/dist/services/cache/StartupCacheService.d.ts +23 -0
- package/dist/services/cache/StartupCacheService.d.ts.map +1 -0
- package/dist/services/cache/StartupCacheService.js +159 -0
- package/dist/services/cache/index.d.ts +3 -0
- package/dist/services/cache/index.d.ts.map +1 -0
- package/dist/services/cache/index.js +2 -0
- package/dist/services/chopsticks/ChopsticksMultiChain.d.ts +158 -0
- package/dist/services/chopsticks/ChopsticksMultiChain.d.ts.map +1 -0
- package/dist/services/chopsticks/ChopsticksMultiChain.js +282 -0
- package/dist/services/chopsticks/ChopsticksService.d.ts +313 -0
- package/dist/services/chopsticks/ChopsticksService.d.ts.map +1 -0
- package/dist/services/chopsticks/ChopsticksService.js +77 -0
- package/dist/services/chopsticks/chopsticksConfigParser.d.ts +40 -0
- package/dist/services/chopsticks/chopsticksConfigParser.d.ts.map +1 -0
- package/dist/services/chopsticks/chopsticksConfigParser.js +201 -0
- package/dist/services/chopsticks/index.d.ts +5 -0
- package/dist/services/chopsticks/index.d.ts.map +1 -0
- package/dist/services/chopsticks/index.js +4 -0
- package/dist/services/chopsticks/launchChopsticksEffect.d.ts +225 -0
- package/dist/services/chopsticks/launchChopsticksEffect.d.ts.map +1 -0
- package/dist/services/chopsticks/launchChopsticksEffect.js +623 -0
- package/dist/services/config/configAccessors.d.ts +41 -0
- package/dist/services/config/configAccessors.d.ts.map +1 -0
- package/dist/services/config/configAccessors.js +149 -0
- package/dist/services/config/index.d.ts +2 -0
- package/dist/services/config/index.d.ts.map +1 -0
- package/dist/services/config/index.js +1 -0
- package/dist/services/errors.d.ts +72 -0
- package/dist/services/errors.d.ts.map +1 -0
- package/dist/services/errors.js +31 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +6 -0
- package/dist/services/network/NodeReadinessService.d.ts +35 -0
- package/dist/services/network/NodeReadinessService.d.ts.map +1 -0
- package/dist/services/network/NodeReadinessService.js +120 -0
- package/dist/services/network/PortDiscoveryService.d.ts +22 -0
- package/dist/services/network/PortDiscoveryService.d.ts.map +1 -0
- package/dist/services/network/PortDiscoveryService.js +77 -0
- package/dist/services/network/RpcPortDiscoveryService.d.ts +25 -0
- package/dist/services/network/RpcPortDiscoveryService.d.ts.map +1 -0
- package/dist/services/network/RpcPortDiscoveryService.js +136 -0
- package/dist/services/network/index.d.ts +4 -0
- package/dist/services/network/index.d.ts.map +1 -0
- package/dist/services/network/index.js +3 -0
- package/dist/services/process/ProcessManagerService.d.ts +49 -0
- package/dist/services/process/ProcessManagerService.d.ts.map +1 -0
- package/dist/services/process/ProcessManagerService.js +162 -0
- package/dist/services/process/index.d.ts +3 -0
- package/dist/services/process/index.d.ts.map +1 -0
- package/dist/services/process/index.js +2 -0
- package/dist/services/process/launchNodeEffect.d.ts +40 -0
- package/dist/services/process/launchNodeEffect.d.ts.map +1 -0
- package/dist/services/process/launchNodeEffect.js +86 -0
- package/dist/util/functions/index.d.ts +3 -0
- package/dist/util/functions/index.d.ts.map +1 -0
- package/dist/util/functions/index.js +4 -0
- package/dist/util/index.d.ts +4 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +2 -0
- package/package.json +157 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Command, Socket } from "@effect/platform";
|
|
2
|
+
import * as NodeCommandExecutor from "@effect/platform-node/NodeCommandExecutor";
|
|
3
|
+
import * as NodeContext from "@effect/platform-node/NodeContext";
|
|
4
|
+
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
|
|
5
|
+
import * as NodeSocket from "@effect/platform-node/NodeSocket";
|
|
6
|
+
import { createLogger } from "../../util/index.js";
|
|
7
|
+
import { Context, Deferred, Effect, Layer, Option, Schedule } from "effect";
|
|
8
|
+
import { PortDiscoveryError } from "../errors.js";
|
|
9
|
+
const logger = createLogger({ name: "RpcPortDiscoveryService" });
|
|
10
|
+
const debug = logger.debug.bind(logger);
|
|
11
|
+
/**
|
|
12
|
+
* Service for discovering RPC ports by testing actual connectivity
|
|
13
|
+
*/
|
|
14
|
+
export class RpcPortDiscoveryService extends Context.Tag("RpcPortDiscoveryService")() {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse ports from lsof output
|
|
18
|
+
*/
|
|
19
|
+
const parsePortsFromLsof = (stdout) => {
|
|
20
|
+
const regex = /(?:.+):(\d+)/;
|
|
21
|
+
return stdout.split("\n").flatMap((line) => {
|
|
22
|
+
const match = line.match(regex);
|
|
23
|
+
return match ? [Number.parseInt(match[1], 10)] : [];
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get all listening ports for a process
|
|
28
|
+
*/
|
|
29
|
+
const getAllPorts = (pid) => Command.make("lsof", "-p", `${pid}`, "-n", "-P").pipe(Command.pipeTo(Command.make("grep", "LISTEN")), Command.string, Effect.map(parsePortsFromLsof), Effect.flatMap((ports) => ports.length === 0
|
|
30
|
+
? Effect.fail(new PortDiscoveryError({
|
|
31
|
+
cause: new Error("No listening ports found"),
|
|
32
|
+
pid,
|
|
33
|
+
attempts: 1,
|
|
34
|
+
}))
|
|
35
|
+
: Effect.succeed(ports)), Effect.catchAll((cause) => Effect.fail(new PortDiscoveryError({
|
|
36
|
+
cause,
|
|
37
|
+
pid,
|
|
38
|
+
attempts: 1,
|
|
39
|
+
}))));
|
|
40
|
+
/**
|
|
41
|
+
* Test a single RPC method on a socket
|
|
42
|
+
*/
|
|
43
|
+
const testRpcMethod = (method) => Effect.flatMap(Deferred.make(), (responseDeferred) => Effect.flatMap(Socket.Socket, (socket) => Effect.flatMap(socket.writer, (writer) => {
|
|
44
|
+
const request = new TextEncoder().encode(JSON.stringify({
|
|
45
|
+
jsonrpc: "2.0",
|
|
46
|
+
id: Math.floor(Math.random() * 10000),
|
|
47
|
+
method,
|
|
48
|
+
params: [],
|
|
49
|
+
}));
|
|
50
|
+
const handleMessages = socket.runRaw((data) => Effect.try(() => {
|
|
51
|
+
const message = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
52
|
+
const response = JSON.parse(message);
|
|
53
|
+
return response.jsonrpc === "2.0" && !response.error;
|
|
54
|
+
}).pipe(Effect.orElseSucceed(() => false), Effect.flatMap((shouldSucceed) => shouldSucceed ? Deferred.succeed(responseDeferred, true) : Effect.void)));
|
|
55
|
+
return Effect.all([
|
|
56
|
+
Effect.fork(handleMessages),
|
|
57
|
+
Effect.flatMap(writer(request), () => Effect.void),
|
|
58
|
+
Deferred.await(responseDeferred).pipe(Effect.timeoutOption("3 seconds"), Effect.map((opt) => (opt._tag === "Some" ? opt.value : false))),
|
|
59
|
+
]).pipe(Effect.map(([_, __, result]) => result), Effect.catchAll((cause) => Effect.fail(new PortDiscoveryError({
|
|
60
|
+
cause,
|
|
61
|
+
pid: 0,
|
|
62
|
+
attempts: 1,
|
|
63
|
+
}))));
|
|
64
|
+
})));
|
|
65
|
+
/**
|
|
66
|
+
* Test if a port responds to RPC calls using Effect Socket
|
|
67
|
+
*/
|
|
68
|
+
const testRpcPort = (port, isEthereumChain) => Effect.scoped(Effect.provide(Effect.flatMap(testRpcMethod("system_chain"), (success) => {
|
|
69
|
+
if (success) {
|
|
70
|
+
debug(`Port ${port} responded to system_chain`);
|
|
71
|
+
return Effect.succeed(port);
|
|
72
|
+
}
|
|
73
|
+
if (!isEthereumChain) {
|
|
74
|
+
return Effect.fail(new PortDiscoveryError({
|
|
75
|
+
cause: new Error(`Port ${port} did not respond to system_chain`),
|
|
76
|
+
pid: 0,
|
|
77
|
+
attempts: 1,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
// If Ethereum chain, try eth_chainId
|
|
81
|
+
return Effect.flatMap(testRpcMethod("eth_chainId"), (ethSuccess) => {
|
|
82
|
+
if (ethSuccess) {
|
|
83
|
+
debug(`Port ${port} responded to eth_chainId`);
|
|
84
|
+
return Effect.succeed(port);
|
|
85
|
+
}
|
|
86
|
+
return Effect.fail(new PortDiscoveryError({
|
|
87
|
+
cause: new Error(`Port ${port} did not respond to eth_chainId`),
|
|
88
|
+
pid: 0,
|
|
89
|
+
attempts: 1,
|
|
90
|
+
}));
|
|
91
|
+
});
|
|
92
|
+
}), NodeSocket.layerWebSocket(`ws://localhost:${port}`))).pipe(Effect.timeoutOption("7 seconds"), Effect.flatMap((opt) => Option.match(opt, {
|
|
93
|
+
onNone: () => Effect.fail(new PortDiscoveryError({
|
|
94
|
+
cause: new Error(`Port ${port} connection timeout`),
|
|
95
|
+
pid: 0,
|
|
96
|
+
attempts: 1,
|
|
97
|
+
})),
|
|
98
|
+
onSome: (val) => Effect.succeed(val),
|
|
99
|
+
})));
|
|
100
|
+
/**
|
|
101
|
+
* Discover RPC port by racing tests on all candidate ports
|
|
102
|
+
*/
|
|
103
|
+
const discoverRpcPortWithRace = (config) => {
|
|
104
|
+
const maxAttempts = config.maxAttempts || 2400;
|
|
105
|
+
return getAllPorts(config.pid).pipe(Effect.flatMap((allPorts) => {
|
|
106
|
+
debug(`Discovered ports: ${allPorts.join(", ")}`);
|
|
107
|
+
const candidatePorts = allPorts.filter((p) => p >= 1024 && p <= 65535 && p !== 30333 && p !== 9615 // Exclude p2p & metrics port
|
|
108
|
+
);
|
|
109
|
+
if (candidatePorts.length === 0) {
|
|
110
|
+
return Effect.fail(new PortDiscoveryError({
|
|
111
|
+
cause: new Error(`No candidate RPC ports found in: ${allPorts.join(", ")}`),
|
|
112
|
+
pid: config.pid,
|
|
113
|
+
attempts: 1,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
debug(`Testing candidate ports: ${candidatePorts.join(", ")}`);
|
|
117
|
+
// Race all port tests - first one to respond wins
|
|
118
|
+
return Effect.raceAll(candidatePorts.map((port) => testRpcPort(port, config.isEthereumChain))).pipe(Effect.catchAll((_error) => Effect.fail(new PortDiscoveryError({
|
|
119
|
+
cause: new Error(`All candidate ports failed RPC test: ${candidatePorts.join(", ")}`),
|
|
120
|
+
pid: config.pid,
|
|
121
|
+
attempts: 1,
|
|
122
|
+
}))));
|
|
123
|
+
}),
|
|
124
|
+
// Retry the entire discovery process
|
|
125
|
+
Effect.retry(Schedule.fixed("50 millis").pipe(Schedule.compose(Schedule.recurs(maxAttempts - 1)))), Effect.catchAll((error) => Effect.fail(new PortDiscoveryError({
|
|
126
|
+
cause: error,
|
|
127
|
+
pid: config.pid,
|
|
128
|
+
attempts: maxAttempts,
|
|
129
|
+
}))), Effect.provide(NodeCommandExecutor.layer.pipe(Layer.provide(NodeContext.layer), Layer.provide(NodeFileSystem.layer))));
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Live implementation of RpcPortDiscoveryService
|
|
133
|
+
*/
|
|
134
|
+
export const RpcPortDiscoveryServiceLive = Layer.succeed(RpcPortDiscoveryService, {
|
|
135
|
+
discoverRpcPort: discoverRpcPortWithRace,
|
|
136
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/network/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,0CAA0C;AAC1C,6CAA6C"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { type ChildProcess } from "node:child_process";
|
|
3
|
+
import { NodeLaunchError, ProcessError } from "../errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extended ChildProcess with Moonwall metadata
|
|
6
|
+
*/
|
|
7
|
+
export interface MoonwallProcess extends ChildProcess {
|
|
8
|
+
isMoonwallTerminating?: boolean;
|
|
9
|
+
moonwallTerminationReason?: string;
|
|
10
|
+
effectCleanup?: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for launching a process
|
|
14
|
+
*/
|
|
15
|
+
export interface ProcessConfig {
|
|
16
|
+
readonly command: string;
|
|
17
|
+
readonly args: ReadonlyArray<string>;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly logDirectory?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Result of launching a process
|
|
23
|
+
*/
|
|
24
|
+
export interface ProcessLaunchResult {
|
|
25
|
+
readonly process: MoonwallProcess;
|
|
26
|
+
readonly logPath: string;
|
|
27
|
+
}
|
|
28
|
+
declare const ProcessManagerService_base: Context.TagClass<ProcessManagerService, "ProcessManagerService", {
|
|
29
|
+
/**
|
|
30
|
+
* Launch a process with manual cleanup function
|
|
31
|
+
* @param config Process configuration
|
|
32
|
+
* @returns Object containing the process launch result and a cleanup effect to be called manually
|
|
33
|
+
*/
|
|
34
|
+
readonly launch: (config: ProcessConfig) => Effect.Effect<{
|
|
35
|
+
result: ProcessLaunchResult;
|
|
36
|
+
cleanup: Effect.Effect<void, ProcessError>;
|
|
37
|
+
}, NodeLaunchError | ProcessError>;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Service for managing process lifecycle with manual cleanup
|
|
41
|
+
*/
|
|
42
|
+
export declare class ProcessManagerService extends ProcessManagerService_base {
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Live implementation of ProcessManagerService
|
|
46
|
+
*/
|
|
47
|
+
export declare const ProcessManagerServiceLive: Layer.Layer<ProcessManagerService, never, never>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=ProcessManagerService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcessManagerService.d.ts","sourceRoot":"","sources":["../../../src/services/process/ProcessManagerService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAQ,MAAM,QAAQ,CAAC;AAGtD,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,qBAAqB;AAK7D;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;;IAQG;;;;OAIG;qBACc,CACf,MAAM,EAAE,aAAa,KAClB,MAAM,CAAC,MAAM,CAChB;QAAE,MAAM,EAAE,mBAAmB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;KAAE,EAC3E,eAAe,GAAG,YAAY,CAC/B;;AAhBL;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,0BAexC;CAAG;AAmON;;GAEG;AACH,eAAO,MAAM,yBAAyB,kDAEpC,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Context, Effect, Layer, pipe } from "effect";
|
|
2
|
+
import { Path } from "@effect/platform";
|
|
3
|
+
import { NodePath } from "@effect/platform-node";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import { NodeLaunchError, ProcessError } from "../errors.js";
|
|
7
|
+
import { createLogger } from "../../util/index.js";
|
|
8
|
+
const logger = createLogger({ name: "ProcessManagerService" });
|
|
9
|
+
/**
|
|
10
|
+
* Service for managing process lifecycle with manual cleanup
|
|
11
|
+
*/
|
|
12
|
+
export class ProcessManagerService extends Context.Tag("ProcessManagerService")() {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate log file path for a process using Effect Path
|
|
16
|
+
*/
|
|
17
|
+
const getLogPath = (config, pid) => pipe(Effect.all([Path.Path, Effect.sync(() => process.cwd())]), Effect.map(([pathService, cwd]) => {
|
|
18
|
+
const dirPath = config.logDirectory || pathService.join(cwd, "tmp", "node_logs");
|
|
19
|
+
const portArg = config.args.find((a) => a.includes("port"));
|
|
20
|
+
const port = portArg?.split("=")[1] || "unknown";
|
|
21
|
+
const baseName = pathService.basename(config.command);
|
|
22
|
+
return pathService
|
|
23
|
+
.join(dirPath, `${baseName}_node_${port}_${pid}.log`)
|
|
24
|
+
.replace(/node_node_undefined/g, "chopsticks");
|
|
25
|
+
}), Effect.provide(NodePath.layer), Effect.mapError((cause) => new ProcessError({
|
|
26
|
+
cause,
|
|
27
|
+
operation: "spawn",
|
|
28
|
+
})));
|
|
29
|
+
/**
|
|
30
|
+
* Ensure log directory exists - using fs.promises for test compatibility
|
|
31
|
+
*/
|
|
32
|
+
const ensureLogDirectory = (dirPath) => Effect.tryPromise({
|
|
33
|
+
try: async () => {
|
|
34
|
+
try {
|
|
35
|
+
await fs.promises.access(dirPath);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
await fs.promises.mkdir(dirPath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
catch: (cause) => new ProcessError({
|
|
42
|
+
cause,
|
|
43
|
+
operation: "spawn",
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Spawn a child process - using node:child_process for test compatibility
|
|
48
|
+
*/
|
|
49
|
+
const spawnProcess = (config) => Effect.try({
|
|
50
|
+
try: () => {
|
|
51
|
+
const child = spawn(config.command, [...config.args]);
|
|
52
|
+
// Check for immediate spawn errors
|
|
53
|
+
child.on("error", (error) => {
|
|
54
|
+
logger.error(`Process spawn error: ${error}`);
|
|
55
|
+
});
|
|
56
|
+
return child;
|
|
57
|
+
},
|
|
58
|
+
catch: (cause) => new NodeLaunchError({
|
|
59
|
+
cause,
|
|
60
|
+
command: config.command,
|
|
61
|
+
args: [...config.args],
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const createLogStream = (logPath) => Effect.try({
|
|
65
|
+
try: () => fs.createWriteStream(logPath, { flags: "a" }),
|
|
66
|
+
catch: (cause) => new ProcessError({
|
|
67
|
+
cause,
|
|
68
|
+
operation: "spawn",
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Setup log handlers for process stdout/stderr using native WriteStream
|
|
73
|
+
*/
|
|
74
|
+
const setupLogHandlers = (process, logStream) => Effect.sync(() => {
|
|
75
|
+
const logHandler = (chunk) => {
|
|
76
|
+
logStream.write(chunk);
|
|
77
|
+
};
|
|
78
|
+
process.stdout?.on("data", logHandler);
|
|
79
|
+
process.stderr?.on("data", logHandler);
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Helper to construct exit message based on exit context
|
|
83
|
+
*/
|
|
84
|
+
const constructExitMessage = (process, code, signal) => {
|
|
85
|
+
const timestamp = new Date().toISOString();
|
|
86
|
+
if (process.isMoonwallTerminating) {
|
|
87
|
+
return `${timestamp} [moonwall] process killed. reason: ${process.moonwallTerminationReason || "unknown"}\n`;
|
|
88
|
+
}
|
|
89
|
+
if (code !== null) {
|
|
90
|
+
return `${timestamp} [moonwall] process closed with status code ${code}\n`;
|
|
91
|
+
}
|
|
92
|
+
if (signal !== null) {
|
|
93
|
+
return `${timestamp} [moonwall] process terminated by signal ${signal}\n`;
|
|
94
|
+
}
|
|
95
|
+
return `${timestamp} [moonwall] process closed unexpectedly\n`;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Setup exit handler using native WriteStream
|
|
99
|
+
* The stream naturally survives Effect runtime shutdown
|
|
100
|
+
*/
|
|
101
|
+
const setupExitHandler = (process, logStream) => Effect.sync(() => {
|
|
102
|
+
process.once("close", (code, signal) => {
|
|
103
|
+
const message = constructExitMessage(process, code, signal);
|
|
104
|
+
logStream.end(message);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* Kill a process gracefully with timeout
|
|
109
|
+
*/
|
|
110
|
+
const killProcess = (process, logStream, reason) => Effect.sync(() => {
|
|
111
|
+
process.isMoonwallTerminating = true;
|
|
112
|
+
process.moonwallTerminationReason = reason;
|
|
113
|
+
}).pipe(Effect.flatMap(() => process.pid
|
|
114
|
+
? Effect.try({
|
|
115
|
+
try: () => {
|
|
116
|
+
process.kill("SIGTERM");
|
|
117
|
+
// Give process time to exit and trigger close handler
|
|
118
|
+
// Close handler will write final message and close stream
|
|
119
|
+
},
|
|
120
|
+
catch: (cause) => new ProcessError({
|
|
121
|
+
cause,
|
|
122
|
+
pid: process.pid,
|
|
123
|
+
operation: "kill",
|
|
124
|
+
}),
|
|
125
|
+
})
|
|
126
|
+
: Effect.sync(() => logStream.end())));
|
|
127
|
+
/**
|
|
128
|
+
* Launch a process with manual cleanup function
|
|
129
|
+
*
|
|
130
|
+
* This function spawns the process and returns both the process info and a cleanup effect.
|
|
131
|
+
* The cleanup effect should be executed manually when the process needs to be terminated.
|
|
132
|
+
* This allows the process to outlive the Effect scope and remain running for the test suite duration.
|
|
133
|
+
*/
|
|
134
|
+
const launchProcess = (config) => pipe(Effect.all([Path.Path, Effect.sync(() => config.logDirectory || undefined)]), Effect.flatMap(([pathService, customLogDir]) => {
|
|
135
|
+
const dirPath = customLogDir || pathService.join(process.cwd(), "tmp", "node_logs");
|
|
136
|
+
return pipe(ensureLogDirectory(dirPath), Effect.flatMap(() => spawnProcess(config)), Effect.flatMap((childProcess) => {
|
|
137
|
+
// Ensure PID is available before proceeding
|
|
138
|
+
if (childProcess.pid === undefined) {
|
|
139
|
+
return Effect.fail(new ProcessError({
|
|
140
|
+
cause: new Error("Process PID is undefined after spawn"),
|
|
141
|
+
operation: "spawn",
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
return pipe(getLogPath(config, childProcess.pid), Effect.flatMap((logPath) => pipe(createLogStream(logPath), Effect.flatMap((logStream) => pipe(setupLogHandlers(childProcess, logStream), Effect.flatMap(() => setupExitHandler(childProcess, logStream)), Effect.map(() => {
|
|
145
|
+
const processInfo = {
|
|
146
|
+
process: childProcess,
|
|
147
|
+
logPath,
|
|
148
|
+
};
|
|
149
|
+
// Create cleanup effect that can be called manually later
|
|
150
|
+
const cleanup = pipe(killProcess(childProcess, logStream, "Manual cleanup requested"), Effect.catchAll((error) => Effect.sync(() => {
|
|
151
|
+
logger.error(`Failed to cleanly kill process: ${error}`);
|
|
152
|
+
})));
|
|
153
|
+
return { result: processInfo, cleanup };
|
|
154
|
+
}))))));
|
|
155
|
+
}));
|
|
156
|
+
}), Effect.provide(NodePath.layer));
|
|
157
|
+
/**
|
|
158
|
+
* Live implementation of ProcessManagerService
|
|
159
|
+
*/
|
|
160
|
+
export const ProcessManagerServiceLive = Layer.succeed(ProcessManagerService, {
|
|
161
|
+
launch: launchProcess,
|
|
162
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/process/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,sCAAsC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified Effect-based node launcher for integration with existing code
|
|
3
|
+
*/
|
|
4
|
+
import type { DevLaunchSpec } from "../../api/types/index.js";
|
|
5
|
+
import type { ChildProcess } from "node:child_process";
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for launching a node
|
|
8
|
+
*/
|
|
9
|
+
export interface LaunchNodeConfig {
|
|
10
|
+
readonly command: string;
|
|
11
|
+
readonly args: string[];
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly launchSpec?: DevLaunchSpec;
|
|
14
|
+
readonly isEthereumChain: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Result from launching a node
|
|
18
|
+
*/
|
|
19
|
+
export interface LaunchNodeResult {
|
|
20
|
+
readonly runningNode: ChildProcess;
|
|
21
|
+
readonly port: number;
|
|
22
|
+
readonly logPath: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Launch a blockchain node with Effect-based lifecycle management
|
|
26
|
+
*
|
|
27
|
+
* This function provides automatic resource management:
|
|
28
|
+
* - Process spawning with port 0 (OS assigns available port)
|
|
29
|
+
* - Port discovery via lsof
|
|
30
|
+
* - WebSocket readiness check
|
|
31
|
+
* - Automatic cleanup on scope closure
|
|
32
|
+
*
|
|
33
|
+
* @param config Node launch configuration
|
|
34
|
+
* @returns Promise resolving to node info and cleanup function
|
|
35
|
+
*/
|
|
36
|
+
export declare function launchNodeEffect(config: LaunchNodeConfig): Promise<{
|
|
37
|
+
result: LaunchNodeResult;
|
|
38
|
+
cleanup: () => Promise<void>;
|
|
39
|
+
}>;
|
|
40
|
+
//# sourceMappingURL=launchNodeEffect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchNodeEffect.d.ts","sourceRoot":"","sources":["../../../src/services/process/launchNodeEffect.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,iCAAiC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAYvD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAOD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAgHrE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified Effect-based node launcher for integration with existing code
|
|
3
|
+
*/
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
|
+
import { ProcessManagerService, ProcessManagerServiceLive } from "./ProcessManagerService.js";
|
|
6
|
+
import { RpcPortDiscoveryService, RpcPortDiscoveryServiceLive, } from "../network/RpcPortDiscoveryService.js";
|
|
7
|
+
import { ProcessError } from "../errors.js";
|
|
8
|
+
import { createLogger } from "../../util/index.js";
|
|
9
|
+
const logger = createLogger({ name: "launchNodeEffect" });
|
|
10
|
+
const debug = logger.debug.bind(logger);
|
|
11
|
+
/**
|
|
12
|
+
* Combined layer with all dependencies
|
|
13
|
+
*/
|
|
14
|
+
const AllServicesLive = Layer.mergeAll(ProcessManagerServiceLive, RpcPortDiscoveryServiceLive);
|
|
15
|
+
/**
|
|
16
|
+
* Launch a blockchain node with Effect-based lifecycle management
|
|
17
|
+
*
|
|
18
|
+
* This function provides automatic resource management:
|
|
19
|
+
* - Process spawning with port 0 (OS assigns available port)
|
|
20
|
+
* - Port discovery via lsof
|
|
21
|
+
* - WebSocket readiness check
|
|
22
|
+
* - Automatic cleanup on scope closure
|
|
23
|
+
*
|
|
24
|
+
* @param config Node launch configuration
|
|
25
|
+
* @returns Promise resolving to node info and cleanup function
|
|
26
|
+
*/
|
|
27
|
+
export async function launchNodeEffect(config) {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
logger.debug(`[T+0ms] Starting with command: ${config.command}, name: ${config.name}`);
|
|
30
|
+
const nodeConfig = {
|
|
31
|
+
isChopsticks: config.args.some((arg) => arg.includes("chopsticks.cjs")),
|
|
32
|
+
hasRpcPort: config.args.some((arg) => arg.includes("--rpc-port")),
|
|
33
|
+
hasPort: config.args.some((arg) => arg.includes("--port")),
|
|
34
|
+
};
|
|
35
|
+
const finalArgs = !nodeConfig.isChopsticks && !nodeConfig.hasRpcPort
|
|
36
|
+
? [
|
|
37
|
+
...config.args,
|
|
38
|
+
// If MOONWALL_RPC_PORT was pre-allocated by LaunchCommandParser, respect it; otherwise fall back to 0.
|
|
39
|
+
process.env.MOONWALL_RPC_PORT
|
|
40
|
+
? `--rpc-port=${process.env.MOONWALL_RPC_PORT}`
|
|
41
|
+
: "--rpc-port=0",
|
|
42
|
+
]
|
|
43
|
+
: config.args; // Chopsticks uses YAML config, or port already configured
|
|
44
|
+
debug(`Final args: ${JSON.stringify(finalArgs)}`);
|
|
45
|
+
const program = ProcessManagerService.pipe(Effect.flatMap((processManager) => Effect.sync(() => logger.debug(`[T+${Date.now() - startTime}ms] Launching process...`)).pipe(Effect.flatMap(() => processManager.launch({
|
|
46
|
+
command: config.command,
|
|
47
|
+
args: finalArgs,
|
|
48
|
+
name: config.name,
|
|
49
|
+
})))), Effect.flatMap(({ result: processResult, cleanup: processCleanup }) => Effect.sync(() => logger.debug(`[T+${Date.now() - startTime}ms] Process launched with PID: ${processResult.process.pid}`)).pipe(Effect.flatMap(() => {
|
|
50
|
+
const pid = processResult.process.pid;
|
|
51
|
+
if (pid === undefined) {
|
|
52
|
+
return Effect.fail(new ProcessError({
|
|
53
|
+
cause: new Error("Process PID is undefined after launch"),
|
|
54
|
+
operation: "check",
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
return RpcPortDiscoveryService.pipe(Effect.flatMap((rpcDiscovery) => Effect.sync(() => logger.debug(`[T+${Date.now() - startTime}ms] Discovering RPC port for PID ${pid}...`)).pipe(Effect.flatMap(() => rpcDiscovery.discoverRpcPort({
|
|
58
|
+
pid,
|
|
59
|
+
isEthereumChain: config.isEthereumChain,
|
|
60
|
+
maxAttempts: 600, // Match PortDiscoveryService: 600 × 200ms = 120s
|
|
61
|
+
})))), Effect.mapError((error) => new ProcessError({
|
|
62
|
+
cause: error,
|
|
63
|
+
pid,
|
|
64
|
+
operation: "check",
|
|
65
|
+
})), Effect.flatMap((port) => Effect.sync(() => logger.debug(`[T+${Date.now() - startTime}ms] Discovered and validated RPC port: ${port}`)).pipe(Effect.map(() => ({
|
|
66
|
+
processInfo: {
|
|
67
|
+
process: processResult.process,
|
|
68
|
+
port,
|
|
69
|
+
logPath: processResult.logPath,
|
|
70
|
+
},
|
|
71
|
+
cleanup: processCleanup,
|
|
72
|
+
})))));
|
|
73
|
+
})))).pipe(Effect.provide(AllServicesLive));
|
|
74
|
+
// Run without Scope - cleanup is returned as a function
|
|
75
|
+
// We can't simply use scopes because:
|
|
76
|
+
// 1) when this effect is run in beforeAll() hook, we want the node to persist during test
|
|
77
|
+
// 2) If we hoist scope to outside, we accidentally spawn it when describe block is processed during test collection
|
|
78
|
+
return Effect.runPromise(Effect.map(program, ({ processInfo, cleanup }) => ({
|
|
79
|
+
result: {
|
|
80
|
+
runningNode: processInfo.process,
|
|
81
|
+
port: processInfo.port,
|
|
82
|
+
logPath: processInfo.logPath,
|
|
83
|
+
},
|
|
84
|
+
cleanup: () => Effect.runPromise(cleanup),
|
|
85
|
+
})));
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/util/functions/index.ts"],"names":[],"mappings":"AACA,2CAA2C;AAG3C,wCAAwC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/util/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,0CAA0C;AAC1C,qCAAqC"}
|