From 265581182a5604fc9936c786b7d4e8cea3ff3b01 Mon Sep 17 00:00:00 2001 From: undercover-cactus Date: Wed, 4 Feb 2026 15:56:25 +0100 Subject: [PATCH] test: launch backend in e2e tests and cli (#418) ## Summary We are now launching the MSP backend when starting stpragehub services. In this PR, we also fix the MSP and BSP node configuration and register it with the correct keys. ## What changed * Added a launch Backend MSP function that is called when launching storage hub services * Fix the wrong genesis error message in storagehub node by removing the `--chain dev` flags (so it can be launch of the same network as our local datahaven nodes). * Use the correct keys to register MSP and BSP. We were injecting different keys that the one we used for MSP and BSP registration leading to the MSP and BSP node to never fully register as storage providers. --------- Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> --- operator/node/Cargo.toml | 1 - test/cli/handlers/launch/storagehub.ts | 5 + test/launcher/storagehub-docker.ts | 156 +++++++++++++------------ test/scripts/fund-providers.ts | 51 ++------ 4 files changed, 94 insertions(+), 119 deletions(-) diff --git a/operator/node/Cargo.toml b/operator/node/Cargo.toml index 8c6df8ed..68861344 100644 --- a/operator/node/Cargo.toml +++ b/operator/node/Cargo.toml @@ -141,7 +141,6 @@ toml = { workspace = true } #### Needed to build static binaries #### pq-sys = { workspace = true, optional = true } - [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/test/cli/handlers/launch/storagehub.ts b/test/cli/handlers/launch/storagehub.ts index fc162dde..51eca67e 100644 --- a/test/cli/handlers/launch/storagehub.ts +++ b/test/cli/handlers/launch/storagehub.ts @@ -1,6 +1,7 @@ import { logger, printHeader } from "utils"; import type { DataHavenOptions } from "../../../launcher/datahaven"; import { + launchBackend, launchBspNode, launchFishermanNode, launchIndexerNode, @@ -99,5 +100,9 @@ async function launchStorageHubDocker( logger.info("📝 Registering providers..."); await registerProviders({ launchedNetwork }); + // Launch Backend MSP + logger.info("📦 Launching StorageHub Backend..."); + await launchBackend(datahavenOptions, launchedNetwork); + logger.success("All StorageHub components launched and registered"); } diff --git a/test/launcher/storagehub-docker.ts b/test/launcher/storagehub-docker.ts index c1de22a9..a9f64c0e 100644 --- a/test/launcher/storagehub-docker.ts +++ b/test/launcher/storagehub-docker.ts @@ -1,11 +1,15 @@ import { $ } from "bun"; import { getPublicPort, killExistingContainers, logger, waitForContainerToStart } from "utils"; -import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants"; +import { DEFAULT_SUBSTRATE_WS_PORT, SUBSTRATE_FUNDED_ACCOUNTS } from "utils/constants"; import { waitFor } from "utils/waits"; import type { DataHavenOptions } from "./datahaven"; import { isNetworkReady } from "./datahaven"; import type { LaunchedNetwork } from "./types/launchedNetwork"; +/** + * Important ! This is for local deployment only. We are using mDNS discovery when startinn node with the `--discover-local` flag + */ + /** * PostgreSQL configuration for StorageHub Indexer and Fisherman */ @@ -109,48 +113,25 @@ export const getPostgresUrl = (networkId: string): string => { * Injects a BCSV ECDSA key into a StorageHub provider node's keystore. * * @param containerName - Name of the Docker container - * @param seed - The seed phrase for key generation - * @param derivation - Key derivation path (e.g., "//Charlie") + * @param secretKey - The secret key (or private key) we want to add to the node */ export const injectStorageHubKey = async ( containerName: string, - seed: string, - derivation: string + secretKey: string ): Promise => { - logger.info(`🔑 Injecting key ${derivation} into ${containerName}...`); - - const suri = `${seed}${derivation}`; + logger.info(`🔑 Injecting key into ${containerName}...`); // Use Bun's $ directly with docker exec (no sh -c wrapper needed) // This properly handles the spaces in the seed phrase try { - await $`docker exec ${containerName} datahaven-node key insert --base-path /data --chain dev --key-type bcsv --scheme ecdsa --suri ${suri}`.nothrow(); - logger.success(`Key ${derivation} injected successfully`); + await $`docker exec ${containerName} datahaven-node key insert --base-path /data --key-type bcsv --scheme ecdsa --suri ${secretKey}`; + logger.success("Key injected successfully"); } catch (error) { - logger.error(`Failed to inject key ${derivation}: ${error}`); + logger.error(`Failed to inject key : ${error}`); throw error; } }; -/** - * Gets the bootnode address from a running validator node. - * - * For local development with Docker, nodes on the same network can discover each other - * via mDNS (--discover-local flag), so explicit bootnodes are optional. - * - * To use explicit bootnodes, we'd need to extract the peer ID from the validator node, - * which requires querying the RPC endpoint. For simplicity in local dev, we skip this. - * - * @param containerName - Name of the validator container (e.g., datahaven-alice-cli-launch) - * @returns Multiaddress string for bootnode, or empty string to skip bootnodes - */ -export const getBootnodeAddress = async (containerName: string): Promise => { - // For local Docker development, nodes discover each other via mDNS - // No explicit bootnode needed with --discover-local flag - logger.debug(`Skipping explicit bootnode for ${containerName} - using mDNS discovery`); - return ""; -}; - /** * Launches a StorageHub MSP (Main Storage Provider) node. * @@ -166,10 +147,6 @@ export const launchMspNode = async ( const containerName = `storagehub-msp-${options.networkId}`; const dockerNetworkName = `datahaven-${options.networkId}`; const wsPort = 9945; // External port for MSP node - const aliceContainer = `datahaven-alice-${options.networkId}`; - - // Get bootnode address (empty for local dev with mDNS discovery) - const bootnodeAddr = await getBootnodeAddress(aliceContainer); const command: string[] = [ "docker", @@ -182,10 +159,10 @@ export const launchMspNode = async ( "-p", `${wsPort}:${DEFAULT_SUBSTRATE_WS_PORT}`, options.datahavenImageTag, - "--chain", - "dev", "--name", "msp-charlie", + "--chain", + "local", "--rpc-port", `${DEFAULT_SUBSTRATE_WS_PORT}`, "--rpc-external", @@ -208,19 +185,13 @@ export const launchMspNode = async ( "1073741824" // 1 GiB ]; - // Only add bootnodes if we have a valid address - if (bootnodeAddr) { - command.push("--bootnodes", bootnodeAddr); - } - logger.debug(`Executing: ${command.join(" ")}`); await $`sh -c "${command.join(" ")}"`.nothrow(); await waitForContainerToStart(containerName); // Inject key - const seed = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; - await injectStorageHubKey(containerName, seed, "//Charlie"); + await injectStorageHubKey(containerName, SUBSTRATE_FUNDED_ACCOUNTS.CHARLETH.privateKey); // Restart container to load key logger.info("🔄 Restarting MSP node to load key..."); @@ -263,10 +234,6 @@ export const launchBspNode = async ( const containerName = `storagehub-bsp-${options.networkId}`; const dockerNetworkName = `datahaven-${options.networkId}`; const wsPort = 9946; // External port for BSP node - const aliceContainer = `datahaven-alice-${options.networkId}`; - - // Get bootnode address (empty for local dev with mDNS discovery) - const bootnodeAddr = await getBootnodeAddress(aliceContainer); const command: string[] = [ "docker", @@ -279,10 +246,10 @@ export const launchBspNode = async ( "-p", `${wsPort}:${DEFAULT_SUBSTRATE_WS_PORT}`, options.datahavenImageTag, - "--chain", - "dev", "--name", - "bsp-dave", + "bsp-dorothy", + "--chain", + "local", "--rpc-port", `${DEFAULT_SUBSTRATE_WS_PORT}`, "--rpc-external", @@ -303,19 +270,13 @@ export const launchBspNode = async ( "1073741824" // 1 GiB ]; - // Only add bootnodes if we have a valid address - if (bootnodeAddr) { - command.push("--bootnodes", bootnodeAddr); - } - logger.debug(`Executing: ${command.join(" ")}`); await $`sh -c "${command.join(" ")}"`.nothrow(); await waitForContainerToStart(containerName); - // Inject key (using Dave instead of Eve for BSP) - const seed = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; - await injectStorageHubKey(containerName, seed, "//Dave"); + // Inject key + await injectStorageHubKey(containerName, SUBSTRATE_FUNDED_ACCOUNTS.DOROTHY.privateKey); // Restart container to load key logger.info("🔄 Restarting BSP node to load key..."); @@ -358,10 +319,7 @@ export const launchIndexerNode = async ( const containerName = `storagehub-indexer-${options.networkId}`; const dockerNetworkName = `datahaven-${options.networkId}`; const wsPort = 9947; // External port for Indexer node - const aliceContainer = `datahaven-alice-${options.networkId}`; - // Get bootnode address (empty for local dev with mDNS discovery) and PostgreSQL URL - const bootnodeAddr = await getBootnodeAddress(aliceContainer); const postgresUrl = getPostgresUrl(options.networkId); const command: string[] = [ @@ -375,10 +333,10 @@ export const launchIndexerNode = async ( "-p", `${wsPort}:${DEFAULT_SUBSTRATE_WS_PORT}`, options.datahavenImageTag, - "--chain", - "dev", "--name", "indexer", + "--chain", + "local", "--rpc-port", `${DEFAULT_SUBSTRATE_WS_PORT}`, "--rpc-external", @@ -395,11 +353,6 @@ export const launchIndexerNode = async ( postgresUrl ]; - // Only add bootnodes if we have a valid address - if (bootnodeAddr) { - command.push("--bootnodes", bootnodeAddr); - } - logger.debug(`Executing: ${command.join(" ")}`); await $`sh -c "${command.join(" ")}"`.nothrow(); @@ -441,10 +394,7 @@ export const launchFishermanNode = async ( const containerName = `storagehub-fisherman-${options.networkId}`; const dockerNetworkName = `datahaven-${options.networkId}`; const wsPort = 9948; // External port for Fisherman node - const aliceContainer = `datahaven-alice-${options.networkId}`; - // Get bootnode address (empty for local dev with mDNS discovery) and PostgreSQL URL - const bootnodeAddr = await getBootnodeAddress(aliceContainer); const postgresUrl = getPostgresUrl(options.networkId); const command: string[] = [ @@ -459,7 +409,7 @@ export const launchFishermanNode = async ( `${wsPort}:${DEFAULT_SUBSTRATE_WS_PORT}`, options.datahavenImageTag, "--chain", - "dev", + "local", "--name", "fisherman", "--rpc-port", @@ -476,11 +426,6 @@ export const launchFishermanNode = async ( postgresUrl ]; - // Only add bootnodes if we have a valid address - if (bootnodeAddr) { - command.push("--bootnodes", bootnodeAddr); - } - logger.debug(`Executing: ${command.join(" ")}`); await $`sh -c "${command.join(" ")}"`.nothrow(); @@ -507,6 +452,63 @@ export const launchFishermanNode = async ( logger.success(`Fisherman node started on port ${wsPort}`); }; +/** + * Launches a StorageHub Backend container. + * + * @param options - Configuration options for launching the network + * @param launchedNetwork - The launched network instance to track the node + */ +export const launchBackend = async ( + options: DataHavenOptions, + launchedNetwork: LaunchedNetwork +): Promise => { + logger.info("🚀 Launching StorageHub Backend..."); + + const backendImage = "moonsonglabs/storage-hub-msp-backend:latest"; + const containerName = `storagehub-backend-${options.networkId}`; + const dockerNetworkName = `datahaven-${options.networkId}`; + const containerNameMSP = `storagehub-msp-${options.networkId}`; + const postgresUrl = getPostgresUrl(options.networkId); + const apiPort = 8080; + + const command: string[] = [ + "docker", + "run", + "-d", + "--name", + containerName, + "--network", + dockerNetworkName, + "-p", + `${apiPort}:8080`, + "-e", + "RUST_LOG=info", + backendImage, + "--chain", + "local", + "--log-format", + "text", + "--database-url", + postgresUrl, + "--rpc-url", + `ws://${containerNameMSP}:${DEFAULT_SUBSTRATE_WS_PORT}`, + "--msp-callback-url", + `http://${containerName}:8080`, + "--msp-trusted-file-transfer-server-url", + `http://${containerNameMSP}:7070` + ]; + + logger.debug(`Executing: ${command.join(" ")}`); + await $`sh -c "${command.join(" ")}"`.nothrow(); + + await waitForContainerToStart(containerName); + + // Register in launched network + launchedNetwork.addContainer(containerName, { http: apiPort }, { http: apiPort }); + + logger.success(`StorageHub Backend container started on port ${apiPort}`); +}; + /** * Stops and removes all StorageHub containers. * diff --git a/test/scripts/fund-providers.ts b/test/scripts/fund-providers.ts index 1487fd2e..098226ea 100644 --- a/test/scripts/fund-providers.ts +++ b/test/scripts/fund-providers.ts @@ -7,32 +7,6 @@ export interface FundProvidersOptions { launchedNetwork: LaunchedNetwork; } -/** - * Provider account information for MSP and BSP nodes. - * - * DataHaven uses AccountId20 (Ethereum-style 20-byte addresses). - * In dev chains, CHARLETH and DOROTHY are pre-funded development accounts - * that correspond to //Charlie and //Dave derivations. - * - * For StorageHub providers, we use: - * - CHARLETH (//Charlie equivalent) for MSP - * - DOROTHY (//Dave equivalent) for BSP (as //Eve might not have pre-funded AccountId20) - */ -const PROVIDER_ACCOUNTS = { - // MSP account (Charleth = Charlie in AccountId20 format) - msp: { - name: "Charleth", - address: SUBSTRATE_FUNDED_ACCOUNTS.CHARLETH.publicKey, // 20-byte address - derivation: "//Charlie" - }, - // BSP account (Dorothy = Dave in AccountId20 format, using instead of Eve) - bsp: { - name: "Dorothy", - address: SUBSTRATE_FUNDED_ACCOUNTS.DOROTHY.publicKey, // 20-byte address - derivation: "//Dave" // Using Dave instead of Eve for BSP - } -} as const; - /** * Minimum balance required for provider operations. * This includes: @@ -61,30 +35,34 @@ export async function fundProviders(options: FundProvidersOptions): Promise