fix: 🔌 CLI connection issues (#119)

### Problem
Introducing `--network` should make easy to container nodes to find each
other. But this change was made half-way for the relayers, and it was
using the external port to find the first datahaven node (usually
Alice). So:
- In cli launch, Alice node port mapping was left to random port `-p
9944` instead of `-p 9944:9944`.
- Relayers couldn't connect to DataHaven nodes because they were using
the external WS port (now random) instead of hitting the internal port
(which for a cli launch we actually fix it to 9944).

### Solution

- [x] **Fixed Docker port mapping**: Explicit `-p 9944:9944` for Alice
node under network `cli-launch`
- [x] **Enhanced container spec**: Added `internalPorts` tracking to
`LaunchedNetwork`
- [x] **Fixed relayer connections**: Use internal ports for container
communication
This commit is contained in:
Gonza Montiel 2025-07-21 15:02:25 +02:00 committed by GitHub
parent 9b311e00ef
commit 10362d3361
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 74 additions and 21 deletions

View file

@ -2,14 +2,13 @@ import path from "node:path";
import { $ } from "bun";
import invariant from "tiny-invariant";
import { logger, printDivider, printHeader } from "utils";
import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants";
import { waitFor } from "utils/waits";
import { isNetworkReady, setupDataHavenValidatorConfig } from "../../../launcher/datahaven";
import type { LaunchedNetwork } from "../../../launcher/types/launchedNetwork";
import { forwardPort } from "../common/kubernetes";
import type { DeployOptions } from ".";
const DEFAULT_PUBLIC_WS_PORT = 9944;
/**
* Deploys a DataHaven solochain network in a Kubernetes namespace.
*
@ -27,8 +26,8 @@ export const deployDataHavenSolochain = async (
// Forward port from validator to localhost, to interact with the network.
const { cleanup: validatorPortForwardCleanup } = await forwardPort(
"dh-validator-0",
DEFAULT_PUBLIC_WS_PORT,
DEFAULT_PUBLIC_WS_PORT,
DEFAULT_SUBSTRATE_WS_PORT,
DEFAULT_SUBSTRATE_WS_PORT,
launchedNetwork
);
@ -92,8 +91,8 @@ export const deployDataHavenSolochain = async (
// Forward port from validator to localhost, to interact with the network.
const { cleanup: validatorPortForwardCleanup } = await forwardPort(
"dh-validator-0",
DEFAULT_PUBLIC_WS_PORT,
DEFAULT_PUBLIC_WS_PORT,
DEFAULT_SUBSTRATE_WS_PORT,
DEFAULT_SUBSTRATE_WS_PORT,
launchedNetwork
);
@ -104,7 +103,7 @@ export const deployDataHavenSolochain = async (
await waitFor({
lambda: async () => {
logger.info(`📡 Checking if DataHaven is ready (timeout: ${timeoutMs / 1000}s)...`);
const isReady = await isNetworkReady(DEFAULT_PUBLIC_WS_PORT, timeoutMs);
const isReady = await isNetworkReady(DEFAULT_SUBSTRATE_WS_PORT, timeoutMs);
if (!isReady) {
logger.info(`⌛️ Node not ready, waiting ${delayMs / 1000}s to check again...`);
}
@ -116,7 +115,7 @@ export const deployDataHavenSolochain = async (
});
logger.success(
`DataHaven network started, primary node accessible on port ${DEFAULT_PUBLIC_WS_PORT}`
`DataHaven network started, primary node accessible on port ${DEFAULT_SUBSTRATE_WS_PORT}`
);
await registerNodes(launchedNetwork);
@ -174,7 +173,7 @@ const checkOrCreateKubernetesNamespace = async (namespace: string) => {
const registerNodes = async (launchedNetwork: LaunchedNetwork) => {
// Register the validator node, using the standard host WS port that we just forwarded.
launchedNetwork.addContainer("dh-validator-0", {
ws: DEFAULT_PUBLIC_WS_PORT
ws: DEFAULT_SUBSTRATE_WS_PORT
});
logger.info("📝 Node dh-validator-0 successfully registered in launchedNetwork.");
};

View file

@ -1,10 +1,8 @@
import { setDataHavenParameters } from "scripts/set-datahaven-parameters";
import { logger, printDivider, printHeader } from "utils";
import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants";
import type { ParameterCollection } from "utils/parameters";
// Standard ports for the substrate network
const DEFAULT_SUBSTRATE_WS_PORT = 9944;
/**
* A helper function to set DataHaven parameters from a ParameterCollection
*

View file

@ -1,5 +1,6 @@
import type { Command } from "@commander-js/extra-typings";
import { logger } from "utils";
import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants";
import { createParameterCollection } from "utils/parameters";
import { getBlockscoutUrl } from "../../../launcher/kurtosis";
import { LaunchedNetwork } from "../../../launcher/types/launchedNetwork";
@ -14,6 +15,16 @@ import { performValidatorOperations, performValidatorSetUpdate } from "./validat
export const NETWORK_ID = "cli-launch";
export interface NetworkOptions {
networkId: string;
dhInternalPort?: number;
}
export const CLI_NETWORK_OPTIONS: NetworkOptions = {
networkId: NETWORK_ID,
dhInternalPort: DEFAULT_SUBSTRATE_WS_PORT
};
// Non-optional properties should have default values set by the CLI
export interface LaunchOptions {
all?: boolean;

View file

@ -12,6 +12,7 @@ import {
logger,
waitForContainerToStart
} from "utils";
import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants";
import { waitFor } from "utils/waits";
import { type Hex, keccak256, toHex } from "viem";
import { publicKeyToAddress } from "viem/accounts";
@ -29,6 +30,30 @@ export interface DataHavenOptions {
datahavenBuildExtraArgs?: string;
}
/**
* Determines the port mapping for a DataHaven node based on the network type.
*
* For CLI-launch networks (networkId === "cli-launch"), only the alice node gets
* a fixed port mapping (9944:9944). For other networks, only the internal port is exposed
* and Docker assigns a random external port.
*
* @param nodeId - The node identifier (e.g., "alice", "bob")
* @param networkId - The network identifier
* @returns Array of port mapping arguments for Docker run command
*/
export const getPortMappingForNode = (nodeId: string, networkId: string): string[] => {
const isCliLaunch = networkId === "cli-launch";
if (isCliLaunch && nodeId === "alice") {
// For CLI-launch networks, only alice gets the fixed port mapping
return ["-p", `${DEFAULT_SUBSTRATE_WS_PORT}:${DEFAULT_SUBSTRATE_WS_PORT}`];
}
// For other networks or non-alice nodes, only expose internal port
// Docker will assign a random external port
return ["-p", `${DEFAULT_SUBSTRATE_WS_PORT}`];
};
/**
* Launches a local DataHaven solochain network for testing.
*
@ -103,7 +128,7 @@ export const launchLocalDataHavenSolochain = async (
containerName,
"--network",
dockerNetworkName,
...(id === "alice" ? ["-p", "9944"] : []),
...getPortMappingForNode(id, options.networkId),
options.datahavenImageTag,
`--${id}`,
...COMMON_LAUNCH_ARGS
@ -485,11 +510,15 @@ export const registerNodes = async (networkId: string, launchedNetwork: Launched
}
// Query the dynamic port and register
const dynamicPort = await getPublicPort(targetContainerName, 9944);
const dynamicPort = await getPublicPort(targetContainerName, DEFAULT_SUBSTRATE_WS_PORT);
logger.debug(
`Docker container ${targetContainerName} is running. Registering with dynamic port ${dynamicPort}.`
);
launchedNetwork.addContainer(targetContainerName, { ws: dynamicPort });
launchedNetwork.addContainer(
targetContainerName,
{ ws: dynamicPort },
{ ws: DEFAULT_SUBSTRATE_WS_PORT }
);
logger.info(
`📝 Node ${targetContainerName} successfully registered in ${networkId} as datahaven-alice`
);

View file

@ -7,6 +7,7 @@ import { getWsProvider } from "polkadot-api/ws-provider/web";
import invariant from "tiny-invariant";
import {
ANVIL_FUNDED_ACCOUNTS,
DEFAULT_SUBSTRATE_WS_PORT,
getEvmEcdsaSigner,
getPortFromKurtosis,
killExistingContainers,
@ -409,17 +410,20 @@ export const launchRelayers = async (
container.name.includes("datahaven")
);
let substrateWsPort: number;
let substrateWsInternalPort: number;
let substrateNodeId: string;
if (dhNodes.length === 0) {
logger.warn(
"⚠️ No DataHaven nodes found in launchedNetwork. Assuming DataHaven is running and defaulting to port 9944 for relayers."
`⚠️ No DataHaven nodes found in launchedNetwork. Assuming DataHaven is running and defaulting to ${DEFAULT_SUBSTRATE_WS_PORT} for relayers.`
);
substrateWsPort = 9944;
substrateWsPort = DEFAULT_SUBSTRATE_WS_PORT;
substrateWsInternalPort = DEFAULT_SUBSTRATE_WS_PORT;
substrateNodeId = "default (assumed)";
} else {
const firstDhNode = dhNodes[0];
substrateWsPort = firstDhNode.publicPorts.ws;
substrateWsInternalPort = firstDhNode.internalPorts.ws;
substrateNodeId = firstDhNode.name;
logger.info(
`🔌 Using DataHaven node ${substrateNodeId} on port ${substrateWsPort} for relayers and BEEFY check.`
@ -449,7 +453,9 @@ export const launchRelayers = async (
const ethElRpcEndpoint = `ws://host.docker.internal:${ethWsPort}`;
const ethClEndpoint = `http://host.docker.internal:${ethHttpPort}`;
const substrateWsEndpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
const substrateWsEndpoint = `ws://${substrateNodeId}:${substrateWsInternalPort}`;
logger.info(`🔗 Substrate endpoint for relayers: ${substrateWsEndpoint}`);
const relayersToStart: RelayerSpec[] = [
{

View file

@ -1,7 +1,11 @@
import invariant from "tiny-invariant";
import { logger, type RelayerType } from "utils";
type ContainerSpec = { name: string; publicPorts: Record<string, number> };
type ContainerSpec = {
name: string;
publicPorts: Record<string, number>;
internalPorts: Record<string, number>;
};
/**
* Represents the state and associated resources of a launched network environment,
@ -63,8 +67,12 @@ export class LaunchedNetwork {
return container.publicPorts.ws ?? -1;
}
addContainer(containerName: string, publicPorts: Record<string, number> = {}) {
this._containers.push({ name: containerName, publicPorts });
addContainer(
containerName: string,
publicPorts: Record<string, number> = {},
internalPorts: Record<string, number> = {}
) {
this._containers.push({ name: containerName, publicPorts, internalPorts });
}
public getPublicWsPort(): number {

View file

@ -1,3 +1,5 @@
export const DEFAULT_SUBSTRATE_WS_PORT = 9944;
export const ANVIL_FUNDED_ACCOUNTS = {
0: {
publicKey: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",