mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
This PR significantly refactors and improves the end-to-end testing framework and infrastructure. The primary focus was on simplifying the test suites, improving reliability through better resource management, and hardening the relayer infrastructure. All E2E tests are now passing on the CI and demonstrate consistent reliability when run locally. ### Key Changes #### 1. E2E Test Suite Refactor & Cleanup * **Simplified Test Logic**: Heavily refactored the core test suites (`native-token-transfer.test.ts`, `rewards-message.test.ts`, and `validator-set-update.test.ts`). The new implementation is much cleaner, utilizing shared helpers to reduce boilerplate. * **Utility Consolidation**: Removed redundant utility files (`storage.ts`, `rewards-helpers.ts`) and simplified `events.ts`. Event waiting now uses `rxjs` for Substrate and native `viem` watchers for Ethereum, which is more robust and easier to maintain. * **Better Connector Management**: Unified the creation and cleanup of test clients in `ConnectorFactory`. It now handles the lifecycle of WebSocket connections more gracefully, including clearing the `socketClientCache` to prevent reconnection noise during teardown. #### 2. Infrastructure & Stability * **Relayer Relaunch Policy**: Added a restart policy for Snowbridge relayer containers. They are now configured with `--restart on-failure:5`, ensuring that relayers automatically relaunch if they crash during the sensitive initialization phase. * **WebSocket Integration**: * Updated the `ConnectorFactory` to prefer **WebSockets** for the Ethereum public client, which is essential for efficient, event-heavy E2E testing. * Enhanced `launchKurtosisNetwork` to correctly identify and register the Execution Layer's WebSocket endpoint from Kurtosis. * **Disabled Contract Injection**: This PR temporarily disables the automatic injection of contracts into the genesis state by default. * *Reason*: I encountered issues generating a valid `state-diff.json` for the latest contract versions. Even after applying several workarounds, the injected state remained unstable. As a result, I've reverted to manual contract deployment during the launch sequence for better reliability for now. #### 3. Documentation & Maintenance * Removed obsolete documentation (`event-utilities-guide.md`) that no longer reflects the simplified event-handling API. * Cleaned up `test/launcher/validators.ts` and moved logic into more appropriate helpers. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
163 lines
5.3 KiB
TypeScript
163 lines
5.3 KiB
TypeScript
/**
|
|
* DataHaven utility functions for launching and managing validator nodes
|
|
*/
|
|
|
|
import { $ } from "bun";
|
|
import {
|
|
allocationManagerAbi,
|
|
dataHavenServiceManagerAbi,
|
|
delegationManagerAbi
|
|
} from "contract-bindings";
|
|
import type { TestConnectors } from "framework";
|
|
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";
|
|
|
|
export 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"
|
|
];
|
|
|
|
/** 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 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}`);
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
/** 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})`);
|
|
}
|