mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
## Summary Reorganizes the test directory structure for better clarity and maintainability: - **Rename `test/datahaven/` → `test/moonwall/`**: Clearly identifies these as Moonwall single-node tests - **Move `test/framework/` → `test/e2e/framework/`**: Groups e2e test utilities under a dedicated folder - **Move `test/suites/` → `test/e2e/suites/`**: Groups e2e test suites with the framework - **Add `test/e2e/framework/validators.ts`**: Extracts validator test helpers from utils into the e2e framework - **Update documentation**: README.md and E2E_FRAMEWORK_OVERVIEW.md reflect the new structure ### New Directory Structure ``` test/ ├── e2e/ │ ├── suites/ # E2E test suites (Kurtosis-based) │ └── framework/ # E2E test utilities & helpers ├── moonwall/ # Moonwall single-node tests ├── launcher/ # Network deployment tools └── ... ``` --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
164 lines
5.4 KiB
TypeScript
164 lines
5.4 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
|
|
} from "contract-bindings";
|
|
import { type Deployments, logger, waitForContainerToStart } from "utils";
|
|
import { DEFAULT_SUBSTRATE_WS_PORT } from "utils/constants";
|
|
import { getPublicPort } from "utils/docker";
|
|
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",
|
|
"--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}`);
|
|
|
|
// Register as EigenLayer operator
|
|
const operatorHash = await connectors.walletClient.writeContract({
|
|
address: deployments.DelegationManager as `0x${string}`,
|
|
abi: delegationManagerAbi,
|
|
functionName: "registerAsOperator",
|
|
args: ["0x0000000000000000000000000000000000000000", 0, ""],
|
|
account,
|
|
chain: null
|
|
});
|
|
|
|
const operatorReceipt = await connectors.publicClient.waitForTransactionReceipt({
|
|
hash: operatorHash
|
|
});
|
|
if (operatorReceipt.status !== "success") {
|
|
throw new Error(`EigenLayer operator registration failed: ${operatorReceipt.status}`);
|
|
}
|
|
|
|
// Register for operator sets
|
|
const hash = await connectors.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 receipt = await connectors.publicClient.waitForTransactionReceipt({ hash });
|
|
if (receipt.status !== "success") {
|
|
throw new Error(`Operator set registration failed: ${receipt.status}`);
|
|
}
|
|
|
|
logger.debug(`Registered ${validatorName} as operator (gas: ${receipt.gasUsed})`);
|
|
}
|