mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
## Summary Add Storage Hub basic end to end test. This PR also include some fixes to allow Storage Hub node and datahaven node to run on the same network (local chain). Before that one was running on dev and the other one on the local chain. ## What changed * Added `storagehub.test.ts` e2e test. In this file we explicitly start the storagehub node using the launch function already used in the CI * Added Storage Hub backend the flow so it can be used in the e2e test * Fix the `--chain local` vs `--chain dev` issue. The storagehub nodes were started on the dev network and therefore they were never syncing with the datahaven node * Fix the folder permission issue in the CI by fixing the folder name * Added StorageHub javascript lib --------- Co-authored-by: Gonza Montiel <gonzamontiel@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Co-authored-by: Gonza Montiel <gon.montiel@gmail.com>
239 lines
7.7 KiB
TypeScript
239 lines
7.7 KiB
TypeScript
/**
|
|
* E2E test helper functions for validator management
|
|
* These functions depend on TestConnectors and are only used in e2e tests
|
|
*/
|
|
|
|
import { $ } from "bun";
|
|
import {
|
|
allocationManagerAbi,
|
|
dataHavenServiceManagerAbi,
|
|
delegationManagerAbi,
|
|
strategyManagerAbi
|
|
} from "contract-bindings";
|
|
import { type Deployments, logger, waitForContainerToStart } from "utils";
|
|
import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants";
|
|
import { getPublicPort } from "utils/docker";
|
|
import { erc20Abi } from "viem";
|
|
import { privateKeyToAccount } from "viem/accounts";
|
|
import validatorSet from "../../configs/validator-set.json";
|
|
import type { LaunchedNetwork } from "../../launcher/types/launchedNetwork";
|
|
import { getOwnerAccount } from "../../launcher/validators";
|
|
import type { TestConnectors } from "./connectors";
|
|
|
|
/**
|
|
* Get validator info by name from validator set JSON
|
|
* @param name - Validator name (e.g., "alice", "bob")
|
|
* @returns Validator info
|
|
*/
|
|
export const getValidator = (name: string) => {
|
|
const node = validatorSet.validators.find((v) => v.solochainAuthorityName === name.toLowerCase());
|
|
if (!node) throw new Error(`Validator ${name} not found`);
|
|
return node;
|
|
};
|
|
|
|
/** Checks if a DataHaven validator container is running */
|
|
export const isValidatorRunning = async (name: string, networkId: string) =>
|
|
(await $`docker ps -q -f name=^datahaven-${name}-${networkId}`.text()).trim().length > 0;
|
|
|
|
/** Launches a single DataHaven validator node on demand */
|
|
export const launchDatahavenValidator = async (
|
|
name: string,
|
|
options: { launchedNetwork: LaunchedNetwork; datahavenImageTag?: string }
|
|
): Promise<void> => {
|
|
const { launchedNetwork, datahavenImageTag = "datahavenxyz/datahaven:local" } = options;
|
|
const nodeId = name.toLowerCase();
|
|
const containerName = `datahaven-${nodeId}-${launchedNetwork.networkId}`;
|
|
|
|
if (await isValidatorRunning(nodeId, launchedNetwork.networkId)) {
|
|
logger.warn(`⚠️ Node ${nodeId} is already running`);
|
|
return;
|
|
}
|
|
|
|
logger.debug(`Launching DataHaven validator node: ${nodeId}...`);
|
|
|
|
const COMMON_LAUNCH_ARGS = [
|
|
"--unsafe-force-node-key-generation",
|
|
"--tmp",
|
|
"--chain",
|
|
"local",
|
|
"--validator",
|
|
"--discover-local",
|
|
"--no-prometheus",
|
|
"--unsafe-rpc-external",
|
|
"--rpc-cors=all",
|
|
"--force-authoring",
|
|
"--no-telemetry",
|
|
"--enable-offchain-indexing=true"
|
|
];
|
|
|
|
const args = [
|
|
"run",
|
|
"-d",
|
|
"--name",
|
|
containerName,
|
|
"--network",
|
|
launchedNetwork.networkName,
|
|
"-p",
|
|
String(DEFAULT_SUBSTRATE_WS_PORT),
|
|
datahavenImageTag,
|
|
`--${nodeId}`,
|
|
...COMMON_LAUNCH_ARGS
|
|
];
|
|
|
|
await $`docker ${args}`.quiet();
|
|
|
|
await waitForContainerToStart(containerName);
|
|
|
|
const publicPort = await getPublicPort(containerName, DEFAULT_SUBSTRATE_WS_PORT);
|
|
launchedNetwork.addContainer(
|
|
containerName,
|
|
{ ws: publicPort },
|
|
{ ws: DEFAULT_SUBSTRATE_WS_PORT }
|
|
);
|
|
|
|
logger.debug(`DataHaven validator ${nodeId} launched on port ${publicPort}`);
|
|
};
|
|
|
|
/** Adds a validator to the EigenLayer allowlist */
|
|
export const addValidatorToAllowlist = async (
|
|
validatorName: string,
|
|
options: { connectors: TestConnectors; deployments: Deployments }
|
|
): Promise<void> => {
|
|
logger.debug(`Adding validator ${validatorName} to allowlist...`);
|
|
|
|
const { connectors, deployments } = options;
|
|
const validator = getValidator(validatorName);
|
|
const hash = await connectors.walletClient.writeContract({
|
|
address: deployments.ServiceManager as `0x${string}`,
|
|
abi: dataHavenServiceManagerAbi,
|
|
functionName: "addValidatorToAllowlist",
|
|
args: [validator.publicKey as `0x${string}`],
|
|
account: getOwnerAccount(),
|
|
chain: null
|
|
});
|
|
await connectors.publicClient.waitForTransactionReceipt({ hash });
|
|
|
|
logger.debug(`Validator ${validatorName} added to allowlist`);
|
|
};
|
|
|
|
/** Register an operator in EigenLayer and for operator sets */
|
|
export async function registerOperator(
|
|
validatorName: string,
|
|
options: { connectors: TestConnectors; deployments: Deployments }
|
|
): Promise<void> {
|
|
const { connectors, deployments } = options;
|
|
const validator = getValidator(validatorName);
|
|
const account = privateKeyToAccount(validator.privateKey as `0x${string}`);
|
|
const { publicClient, walletClient } = connectors;
|
|
|
|
// Deposit tokens into deployed strategies
|
|
const deployedStrategies = deployments.DeployedStrategies ?? [];
|
|
for (const strategy of deployedStrategies) {
|
|
const balance = await publicClient.readContract({
|
|
address: strategy.underlyingToken as `0x${string}`,
|
|
abi: erc20Abi,
|
|
functionName: "balanceOf",
|
|
args: [account.address]
|
|
});
|
|
|
|
if (balance > 0n) {
|
|
const depositAmount = balance / 10n;
|
|
|
|
const approveHash = await walletClient.writeContract({
|
|
address: strategy.underlyingToken as `0x${string}`,
|
|
abi: erc20Abi,
|
|
functionName: "approve",
|
|
args: [deployments.StrategyManager, depositAmount],
|
|
account,
|
|
chain: null
|
|
});
|
|
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
|
|
const depositHash = await walletClient.writeContract({
|
|
address: deployments.StrategyManager,
|
|
abi: strategyManagerAbi,
|
|
functionName: "depositIntoStrategy",
|
|
args: [
|
|
strategy.address as `0x${string}`,
|
|
strategy.underlyingToken as `0x${string}`,
|
|
depositAmount
|
|
],
|
|
account,
|
|
chain: null
|
|
});
|
|
await publicClient.waitForTransactionReceipt({ hash: depositHash });
|
|
logger.debug(`Deposited ${depositAmount} tokens into strategy ${strategy.address}`);
|
|
}
|
|
}
|
|
|
|
// Register as EigenLayer operator
|
|
const operatorHash = await walletClient.writeContract({
|
|
address: deployments.DelegationManager as `0x${string}`,
|
|
abi: delegationManagerAbi,
|
|
functionName: "registerAsOperator",
|
|
args: ["0x0000000000000000000000000000000000000000", 0, ""],
|
|
account,
|
|
chain: null
|
|
});
|
|
|
|
const operatorReceipt = await publicClient.waitForTransactionReceipt({
|
|
hash: operatorHash
|
|
});
|
|
if (operatorReceipt.status !== "success") {
|
|
throw new Error(`EigenLayer operator registration failed: ${operatorReceipt.status}`);
|
|
}
|
|
|
|
// Register for operator sets
|
|
const registerHash = await walletClient.writeContract({
|
|
address: deployments.AllocationManager as `0x${string}`,
|
|
abi: allocationManagerAbi,
|
|
functionName: "registerForOperatorSets",
|
|
args: [
|
|
validator.publicKey as `0x${string}`,
|
|
{
|
|
avs: deployments.ServiceManager as `0x${string}`,
|
|
operatorSetIds: [0],
|
|
data: validator.solochainAddress as `0x${string}`
|
|
}
|
|
],
|
|
account,
|
|
chain: null
|
|
});
|
|
|
|
const registerReceipt = await publicClient.waitForTransactionReceipt({ hash: registerHash });
|
|
if (registerReceipt.status !== "success") {
|
|
throw new Error(`Operator set registration failed: ${registerReceipt.status}`);
|
|
}
|
|
|
|
// Allocate full magnitude to the validator operator set
|
|
const strategyAddresses = deployedStrategies.map((s) => s.address as `0x${string}`);
|
|
const newMagnitudes = strategyAddresses.map(() => BigInt(1e18));
|
|
|
|
const allocateHash = await walletClient.writeContract({
|
|
address: deployments.AllocationManager as `0x${string}`,
|
|
abi: allocationManagerAbi,
|
|
functionName: "modifyAllocations",
|
|
args: [
|
|
account.address,
|
|
[
|
|
{
|
|
operatorSet: {
|
|
avs: deployments.ServiceManager as `0x${string}`,
|
|
id: 0
|
|
},
|
|
strategies: strategyAddresses,
|
|
newMagnitudes
|
|
}
|
|
]
|
|
],
|
|
account,
|
|
chain: null
|
|
});
|
|
|
|
const allocateReceipt = await publicClient.waitForTransactionReceipt({ hash: allocateHash });
|
|
if (allocateReceipt.status !== "success") {
|
|
throw new Error(`Magnitude allocation failed: ${allocateReceipt.status}`);
|
|
}
|
|
|
|
logger.debug(`Registered ${validatorName} as operator (gas: ${registerReceipt.gasUsed})`);
|
|
}
|