datahaven/test/cli/handlers/deploy/index.ts
Steve Degosserie 5988691a2f
feat: Add deployment charts for StorageHub MSP, BSP & Indexer nodes (Local & Stagenet envs) (#160)
## Summary

This PR adds comprehensive Kubernetes deployment infrastructure for
StorageHub components, enabling deployment of the full StorageHub
network stack (MSP, BSP, Indexer, and Fisherman nodes) alongside
DataHaven nodes in both local and stagenet environments.

### What's Added

**1. New Helm Chart: StorageHub MSP Backend API**
(`deploy/charts/backend/`)
- REST API service for StorageHub operations
- Connects to PostgreSQL database for indexed blockchain data
- Connects to RPC nodes for real-time blockchain queries
- Configurable via TOML configuration file
- Supports environment-specific overrides
- Includes comprehensive documentation

**2. StorageHub Node Deployment Charts**
(`deploy/charts/node/storagehub/`)
- **MSP Node** (`sh-mspnode`): Main Service Provider nodes with charging
capabilities
- **BSP Node** (`sh-bspnode`): Backup Service Provider nodes for
redundancy
- **Indexer Node** (`sh-idxnode`): Full indexing node with PostgreSQL
integration
- **Fisherman Node** (`sh-fisherman`): Network monitoring and
verification node

**3. Environment Configurations**
- **Local environment** (`deploy/environments/local/`): Development
setup with hostpath storage
- **Stagenet environment** (`deploy/environments/stagenet/`):
Production-like setup with AWS EBS
- PostgreSQL database configurations for Indexer and Fisherman nodes
- Proper service discovery and network configuration

**4. Enhanced CLI Tooling** (`test/cli/`)
- New `deploy storagehub` command for deploying StorageHub components
- Updated `launch storagehub` command for local testing
- Interactive deployment with environment selection
- Automatic database provisioning via Bitnami PostgreSQL charts

**5. Node Configuration Improvements**
- Fork-aware transaction pool for DH boot & validator nodes
- Unsafe RPC methods exposed on MSP nodes (for provider operations)
- JWT secret support for MSP Backend authentication
- ECDSA key scheme for StorageHub BCSV keys (DataHaven compatibility)

### Architecture

```
StorageHub Stack:
├── MSP Nodes (2 replicas) → Storage providers with charging
├── BSP Nodes (2 replicas) → Backup storage providers
├── Indexer Node → Database indexing + PostgreSQL
├── Fisherman Node → Monitoring + PostgreSQL (shared with Indexer)
└── MSP Backend API → REST API for StorageHub operations
```

### Testing

**Local Testing**:
```bash
cd test
bun cli launch storagehub  # Interactive launcher
# or
bun cli deploy storagehub  # Deploy via Helm
```

**Stagenet Deployment**:
```bash
cd deploy
helm install sh-mspnode ./charts/node \
  -f ./charts/node/storagehub/sh-mspnode.yaml \
  -f ./environments/stagenet/sh-mspnode.yaml \
  -n datahaven-stagenet
```

### Breaking Changes

None - This is purely additive infrastructure.

### Migration Notes

For existing deployments:
1. DataHaven nodes now use `--pool-type fork-aware` flag
2. Bootnode and validator node configs updated accordingly
3. No action required for existing DataHaven-only deployments
2025-10-21 23:18:50 +03:00

121 lines
4.3 KiB
TypeScript

import type { Command } from "node_modules/@commander-js/extra-typings";
import { type DeployEnvironment, logger } from "utils";
import { createParameterCollection } from "utils/parameters";
import { LaunchedNetwork } from "../../../launcher/types/launchedNetwork";
import { checkBaseDependencies, deploymentChecks } from "../common/checks";
import { cleanup } from "./cleanup";
import { deployContracts } from "./contracts";
import { deployDataHavenSolochain } from "./datahaven";
import { deployKurtosis } from "./kurtosis";
import { setParametersFromCollection } from "./parameters";
import { deployRelayers } from "./relayer";
import { deployStorageHubComponents } from "./storagehub";
import { performValidatorOperations } from "./validator";
// Non-optional properties determined by having default values
export interface DeployOptions {
environment: DeployEnvironment;
isPrivateNetwork?: boolean;
kubeNamespace?: string;
kurtosisEnclaveName: string;
slotTime: number;
kurtosisNetworkArgs?: string;
verified?: boolean;
blockscout?: boolean;
datahavenImageTag: string;
elRpcUrl?: string;
clEndpoint?: string;
relayerImageTag: string;
// TODO: This shouldn't be necessary once the repo is public
dockerUsername?: string;
// TODO: This shouldn't be necessary once the repo is public
dockerPassword?: string;
// TODO: This shouldn't be necessary once the repo is public
dockerEmail?: string;
chainspec?: string;
skipCleanup: boolean;
skipKurtosis: boolean;
skipDatahavenSolochain: boolean;
skipContracts: boolean;
skipValidatorOperations: boolean;
skipSetParameters: boolean;
skipRelayers: boolean;
skipStorageHub: boolean;
}
const deployFunction = async (options: DeployOptions, launchedNetwork: LaunchedNetwork) => {
logger.debug("Running with options:");
logger.debug(options);
const timeStart = performance.now();
await checkBaseDependencies();
await deploymentChecks(options, launchedNetwork);
await cleanup(options, launchedNetwork);
// Create parameter collection to be used throughout the launch process
const parameterCollection = await createParameterCollection();
await deployKurtosis(options, launchedNetwork);
// Inside the deployDataHavenSolochain function, it will forward the port from the validator to the local machine.
// This is to allow the rest of the script to interact with the network.
// The cleanup function is returned to allow the script to clean up the port forwarding.
const validatorPortForwardCleanup = await deployDataHavenSolochain(options, launchedNetwork);
// TODO: Handle Blockscout and verifier parameters to verify contracts if that is the intention.
const blockscoutBackendUrl = undefined;
await deployContracts({
rpcUrl: launchedNetwork.elRpcUrl,
verified: options.verified,
blockscoutBackendUrl,
parameterCollection,
skipContracts: options.skipContracts
});
await performValidatorOperations(options, launchedNetwork.elRpcUrl);
await setParametersFromCollection({
collection: parameterCollection,
skipSetParameters: options.skipSetParameters
});
await deployRelayers(options, launchedNetwork);
await deployStorageHubComponents(options, launchedNetwork);
// Cleaning up the port forwarding for the validator.
await validatorPortForwardCleanup();
const fullEnd = performance.now();
const fullMinutes = ((fullEnd - timeStart) / (1000 * 60)).toFixed(1);
logger.success(`Deploy function completed successfully in ${fullMinutes} minutes`);
};
export const deploy = async (options: DeployOptions) => {
const run = new LaunchedNetwork();
await deployFunction(options, run);
};
export const deployPreActionHook = (
thisCmd: Command<[], DeployOptions & { [key: string]: any }>
) => {
const opts = thisCmd.opts();
if (opts.verified && !opts.blockscout) {
thisCmd.error("--verified requires --blockscout to be set");
}
opts.isPrivateNetwork = opts.environment === "local" || opts.environment === "stagenet";
if (opts.isPrivateNetwork && opts.kubeNamespace !== undefined) {
logger.warn(
"⚠️ --kube-namespace is not allowed in private networks (local and stagenet). The Kurtosis namespace will be used instead."
);
}
if (!opts.isPrivateNetwork && opts.elRpcUrl === undefined) {
thisCmd.error("--eth-rpc-url is required in public networks (testnet and mainnet)");
}
};