mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-23 09:18:21 +00:00
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>
This commit is contained in:
parent
0623e320f6
commit
265581182a
4 changed files with 94 additions and 119 deletions
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void> => {
|
||||
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<string> => {
|
||||
// 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<void> => {
|
||||
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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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<void
|
|||
|
||||
try {
|
||||
// Check MSP account balance
|
||||
logger.info(`Checking MSP account (${PROVIDER_ACCOUNTS.msp.name})...`);
|
||||
const mspAccount = await typedApi.query.System.Account.getValue(PROVIDER_ACCOUNTS.msp.address);
|
||||
logger.info("Checking MSP account...");
|
||||
const mspAccount = await typedApi.query.System.Account.getValue(
|
||||
SUBSTRATE_FUNDED_ACCOUNTS.CHARLETH.publicKey
|
||||
);
|
||||
const mspBalance = mspAccount?.data?.free ?? BigInt(0);
|
||||
logger.debug(`MSP balance: ${mspBalance.toString()}`);
|
||||
|
||||
if (mspBalance < MIN_PROVIDER_BALANCE) {
|
||||
logger.warn(`MSP account has insufficient balance (${mspBalance} < ${MIN_PROVIDER_BALANCE})`);
|
||||
logger.info(
|
||||
"Note: In dev chains, //Charlie should be pre-funded. If balance is low, ensure the chain is properly initialized."
|
||||
"Note: In dev chains, Charleth account should be pre-funded. If balance is low, ensure the chain is properly initialized."
|
||||
);
|
||||
} else {
|
||||
logger.success(`MSP account has sufficient balance: ${mspBalance.toString()}`);
|
||||
}
|
||||
|
||||
// Check BSP account balance
|
||||
logger.info(`Checking BSP account (${PROVIDER_ACCOUNTS.bsp.name})...`);
|
||||
const bspAccount = await typedApi.query.System.Account.getValue(PROVIDER_ACCOUNTS.bsp.address);
|
||||
logger.info("Checking BSP account...");
|
||||
const bspAccount = await typedApi.query.System.Account.getValue(
|
||||
SUBSTRATE_FUNDED_ACCOUNTS.DOROTHY.publicKey
|
||||
);
|
||||
const bspBalance = bspAccount?.data?.free ?? BigInt(0);
|
||||
logger.debug(`BSP balance: ${bspBalance.toString()}`);
|
||||
|
||||
if (bspBalance < MIN_PROVIDER_BALANCE) {
|
||||
logger.warn(`BSP account has insufficient balance (${bspBalance} < ${MIN_PROVIDER_BALANCE})`);
|
||||
logger.info(
|
||||
"Note: In dev chains, //Eve should be pre-funded. If balance is low, ensure the chain is properly initialized."
|
||||
"Note: In dev chains, DOROTHY account should be pre-funded. If balance is low, ensure the chain is properly initialized."
|
||||
);
|
||||
} else {
|
||||
logger.success(`BSP account has sufficient balance: ${bspBalance.toString()}`);
|
||||
|
|
@ -98,12 +76,3 @@ export async function fundProviders(options: FundProvidersOptions): Promise<void
|
|||
client.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the provider account addresses.
|
||||
*
|
||||
* @returns Object containing MSP and BSP account information
|
||||
*/
|
||||
export function getProviderAccounts() {
|
||||
return PROVIDER_ACCOUNTS;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue