datahaven/test/launcher/validators.ts

290 lines
9.5 KiB
TypeScript
Raw Permalink Normal View History

test: Update validator set e2e test (#126) ## Add E2E validator-set update flow - feat: `test/utils/validators.ts` for on-demand validator orchestration. - feat: `test/suites/validator-set-update.test.ts` covering allowlist → register → update. - some minor launcher updates: avoid docker cache, add `--platform` when building datahaven image, avoid sending validator-set update on launch. - Helpers: ABI shortcut in `test/utils/contracts.ts`; config tweaks in `test/configs/validator-set.json`. - Minor cleanup/formatting across `test/launcher/*`, `test/scripts/setup-validators.ts`, and related tests. - added `keepAlive` flag to `BaseTestSuite`, in order to avoid tearing down the network while debugging. Defaults, obviously, to false. - added a `failOnTomeout` option on to waitForDataHavenEvents() so the test fails of the timeout is reached and no event was captured. ### Coverage - The test simulates an scenario in which we have two active authorities (alice and bob), which are running, and registered as operators, which is the normal state after the chain launches. Then: - It launches two more nodes (charlie and dave) - It add the nodes to allowlist and register them as operators - It sends the validator set update message - Checks that the validator update message was propagated through the gateway and arrived the external-validators pallet - Checks that the chain continues producing blocks ### Notes The last test case has a timeout of 10 minutes. This is to respect propagation times of the message through the relayers. We are testing that the external validators pallet actually updated the validator set. Locally, I could expect 5~6 minutes, I just wanted to be on the safe side. CI is passing showing that this was enough indeed. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
2025-10-02 11:23:40 +00:00
import {
allocationManagerAbi,
dataHavenServiceManagerAbi,
delegationManagerAbi
} from "contract-bindings";
import type { TestConnectors } from "framework";
test: 🏗️ Setup e2e testing framework (#104) ## Implement E2E Testing Framework with Isolated Networks ### Summary Refactors the existing E2E testing infrastructure to provide isolated test environments with parallel execution support. Each test suite now runs in its own network namespace, preventing resource conflicts. ### Key Changes - **New Testing Framework** (`test/framework/`): Base classes for test lifecycle management with automatic setup/teardown - **Launcher Module** (`test/launcher/`): Extracted network orchestration logic from CLI handlers for reusability - **Parallel Execution**: Added `test-parallel.ts` script with concurrency limits to prevent resource exhaustion - **Test Isolation**: Each suite gets unique network IDs (format: `suiteName-timestamp`) and Docker networks - **Improved Test Organization**: Migrated tests to new framework, deprecated old test structure ### Test Improvements - Added 4 new test suites demonstrating framework usage. : - `contracts.test.ts` - Smart contract deployment/interaction - `datahaven-substrate.test.ts` - Substrate API operations - `cross-chain.test.ts` - Snowbridge cross-chain messaging - `ethereum-basic.test.ts` - Ethereum network operations > [!WARNING] The test suites themselves are bad and shouldn't be consider examples of good tests. They were AI generated just to test the concurrency of test runners ### Documentation - Added comprehensive framework overview (`E2E_FRAMEWORK_OVERVIEW.md`) - Updated README with parallel testing commands - Added test patterns and best practices ### Breaking Changes - Old test suites moved to `e2e - DEPRECATED/` directory - Test execution now requires extending `BaseTestSuite` class ### Testing Run tests with: `bun test:e2e` or `bun test:e2e:parallel` (with concurrency limits) ### TODO - [ ] Implement good test examples. - [ ] Implement useful test utils (like waiting for an event to show up in DataHaven or Ethereum). - [ ] Enforce tests with CI (currently cannot be done due to intermittent error when sending a transaction with PAPI). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: undercover-cactus <lola@moonsonglabs.com>
2025-07-16 16:51:07 +00:00
import { fundValidators as fundValidatorsScript } from "scripts/fund-validators";
import { setupValidators as setupValidatorsScript } from "scripts/setup-validators";
import { updateValidatorSet as updateValidatorSetScript } from "scripts/update-validator-set";
test: Update validator set e2e test (#126) ## Add E2E validator-set update flow - feat: `test/utils/validators.ts` for on-demand validator orchestration. - feat: `test/suites/validator-set-update.test.ts` covering allowlist → register → update. - some minor launcher updates: avoid docker cache, add `--platform` when building datahaven image, avoid sending validator-set update on launch. - Helpers: ABI shortcut in `test/utils/contracts.ts`; config tweaks in `test/configs/validator-set.json`. - Minor cleanup/formatting across `test/launcher/*`, `test/scripts/setup-validators.ts`, and related tests. - added `keepAlive` flag to `BaseTestSuite`, in order to avoid tearing down the network while debugging. Defaults, obviously, to false. - added a `failOnTomeout` option on to waitForDataHavenEvents() so the test fails of the timeout is reached and no event was captured. ### Coverage - The test simulates an scenario in which we have two active authorities (alice and bob), which are running, and registered as operators, which is the normal state after the chain launches. Then: - It launches two more nodes (charlie and dave) - It add the nodes to allowlist and register them as operators - It sends the validator set update message - Checks that the validator update message was propagated through the gateway and arrived the external-validators pallet - Checks that the chain continues producing blocks ### Notes The last test case has a timeout of 10 minutes. This is to respect propagation times of the message through the relayers. We are testing that the external validators pallet actually updated the validator set. Locally, I could expect 5~6 minutes, I just wanted to be on the safe side. CI is passing showing that this was enough indeed. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
2025-10-02 11:23:40 +00:00
import {
ANVIL_FUNDED_ACCOUNTS,
type Deployments,
getValidatorInfoByName,
logger,
type TestAccounts
} from "utils";
import { privateKeyToAccount } from "viem/accounts";
test: 🏗️ Setup e2e testing framework (#104) ## Implement E2E Testing Framework with Isolated Networks ### Summary Refactors the existing E2E testing infrastructure to provide isolated test environments with parallel execution support. Each test suite now runs in its own network namespace, preventing resource conflicts. ### Key Changes - **New Testing Framework** (`test/framework/`): Base classes for test lifecycle management with automatic setup/teardown - **Launcher Module** (`test/launcher/`): Extracted network orchestration logic from CLI handlers for reusability - **Parallel Execution**: Added `test-parallel.ts` script with concurrency limits to prevent resource exhaustion - **Test Isolation**: Each suite gets unique network IDs (format: `suiteName-timestamp`) and Docker networks - **Improved Test Organization**: Migrated tests to new framework, deprecated old test structure ### Test Improvements - Added 4 new test suites demonstrating framework usage. : - `contracts.test.ts` - Smart contract deployment/interaction - `datahaven-substrate.test.ts` - Substrate API operations - `cross-chain.test.ts` - Snowbridge cross-chain messaging - `ethereum-basic.test.ts` - Ethereum network operations > [!WARNING] The test suites themselves are bad and shouldn't be consider examples of good tests. They were AI generated just to test the concurrency of test runners ### Documentation - Added comprehensive framework overview (`E2E_FRAMEWORK_OVERVIEW.md`) - Updated README with parallel testing commands - Added test patterns and best practices ### Breaking Changes - Old test suites moved to `e2e - DEPRECATED/` directory - Test execution now requires extending `BaseTestSuite` class ### Testing Run tests with: `bun test:e2e` or `bun test:e2e:parallel` (with concurrency limits) ### TODO - [ ] Implement good test examples. - [ ] Implement useful test utils (like waiting for an event to show up in DataHaven or Ethereum). - [ ] Enforce tests with CI (currently cannot be done due to intermittent error when sending a transaction with PAPI). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: undercover-cactus <lola@moonsonglabs.com>
2025-07-16 16:51:07 +00:00
/**
* Configuration options for validator operations.
*/
export interface ValidatorOptions {
rpcUrl: string;
}
test: Update validator set e2e test (#126) ## Add E2E validator-set update flow - feat: `test/utils/validators.ts` for on-demand validator orchestration. - feat: `test/suites/validator-set-update.test.ts` covering allowlist → register → update. - some minor launcher updates: avoid docker cache, add `--platform` when building datahaven image, avoid sending validator-set update on launch. - Helpers: ABI shortcut in `test/utils/contracts.ts`; config tweaks in `test/configs/validator-set.json`. - Minor cleanup/formatting across `test/launcher/*`, `test/scripts/setup-validators.ts`, and related tests. - added `keepAlive` flag to `BaseTestSuite`, in order to avoid tearing down the network while debugging. Defaults, obviously, to false. - added a `failOnTomeout` option on to waitForDataHavenEvents() so the test fails of the timeout is reached and no event was captured. ### Coverage - The test simulates an scenario in which we have two active authorities (alice and bob), which are running, and registered as operators, which is the normal state after the chain launches. Then: - It launches two more nodes (charlie and dave) - It add the nodes to allowlist and register them as operators - It sends the validator set update message - Checks that the validator update message was propagated through the gateway and arrived the external-validators pallet - Checks that the chain continues producing blocks ### Notes The last test case has a timeout of 10 minutes. This is to respect propagation times of the message through the relayers. We are testing that the external validators pallet actually updated the validator set. Locally, I could expect 5~6 minutes, I just wanted to be on the safe side. CI is passing showing that this was enough indeed. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
2025-10-02 11:23:40 +00:00
export interface ValidatorOptionsExt extends ValidatorOptions {
connectors: TestConnectors;
deployments: Deployments;
}
test: 🏗️ Setup e2e testing framework (#104) ## Implement E2E Testing Framework with Isolated Networks ### Summary Refactors the existing E2E testing infrastructure to provide isolated test environments with parallel execution support. Each test suite now runs in its own network namespace, preventing resource conflicts. ### Key Changes - **New Testing Framework** (`test/framework/`): Base classes for test lifecycle management with automatic setup/teardown - **Launcher Module** (`test/launcher/`): Extracted network orchestration logic from CLI handlers for reusability - **Parallel Execution**: Added `test-parallel.ts` script with concurrency limits to prevent resource exhaustion - **Test Isolation**: Each suite gets unique network IDs (format: `suiteName-timestamp`) and Docker networks - **Improved Test Organization**: Migrated tests to new framework, deprecated old test structure ### Test Improvements - Added 4 new test suites demonstrating framework usage. : - `contracts.test.ts` - Smart contract deployment/interaction - `datahaven-substrate.test.ts` - Substrate API operations - `cross-chain.test.ts` - Snowbridge cross-chain messaging - `ethereum-basic.test.ts` - Ethereum network operations > [!WARNING] The test suites themselves are bad and shouldn't be consider examples of good tests. They were AI generated just to test the concurrency of test runners ### Documentation - Added comprehensive framework overview (`E2E_FRAMEWORK_OVERVIEW.md`) - Updated README with parallel testing commands - Added test patterns and best practices ### Breaking Changes - Old test suites moved to `e2e - DEPRECATED/` directory - Test execution now requires extending `BaseTestSuite` class ### Testing Run tests with: `bun test:e2e` or `bun test:e2e:parallel` (with concurrency limits) ### TODO - [ ] Implement good test examples. - [ ] Implement useful test utils (like waiting for an event to show up in DataHaven or Ethereum). - [ ] Enforce tests with CI (currently cannot be done due to intermittent error when sending a transaction with PAPI). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: undercover-cactus <lola@moonsonglabs.com>
2025-07-16 16:51:07 +00:00
/**
* Funds validators with tokens and ETH.
*
* This function ensures validators have the necessary funds to operate by:
* - Sending ETH for gas fees
* - Sending required tokens for staking
* - Verifying balances after funding
*
* @param options - Configuration options for funding
* @param options.rpcUrl - The RPC URL of the Ethereum network
*
* @throws {Error} If funding transactions fail
* @throws {Error} If the network is unreachable
*/
export const fundValidators = async (options: ValidatorOptions): Promise<void> => {
logger.info("💰 Funding validators with tokens and ETH...");
await fundValidatorsScript({
rpcUrl: options.rpcUrl
});
};
/**
* Registers validators in the EigenLayer protocol.
*
* This function handles the validator registration process:
* - Creates operator registrations in EigenLayer
* - Registers operators with the AVS (Actively Validated Service)
* - Sets up delegation relationships
* - Configures operator metadata
*
* @param options - Configuration options for setup
* @param options.rpcUrl - The RPC URL of the Ethereum network
*
* @throws {Error} If registration transactions fail
* @throws {Error} If validators are already registered
* @throws {Error} If required contracts are not deployed
*/
export const setupValidators = async (options: ValidatorOptions): Promise<void> => {
logger.info("📝 Registering validators in EigenLayer...");
await setupValidatorsScript({
rpcUrl: options.rpcUrl
});
};
/**
* Updates the validator set on the Substrate chain.
*
* This function synchronizes the validator set between Ethereum and Substrate:
* - Fetches the current validator set from EigenLayer
* - Prepares validator set update transaction
* - Submits the update through the bridge
* - Waits for confirmation on the Substrate side
*
* @param options - Configuration options for the update
* @param options.rpcUrl - The RPC URL of the Ethereum network
*
* @throws {Error} If the update transaction fails
* @throws {Error} If the bridge is not initialized
* @throws {Error} If validators are not properly registered
*/
export const updateValidatorSet = async (options: ValidatorOptions): Promise<void> => {
logger.info("🔄 Updating validator set on Substrate chain...");
await updateValidatorSetScript({
rpcUrl: options.rpcUrl
});
};
test: Update validator set e2e test (#126) ## Add E2E validator-set update flow - feat: `test/utils/validators.ts` for on-demand validator orchestration. - feat: `test/suites/validator-set-update.test.ts` covering allowlist → register → update. - some minor launcher updates: avoid docker cache, add `--platform` when building datahaven image, avoid sending validator-set update on launch. - Helpers: ABI shortcut in `test/utils/contracts.ts`; config tweaks in `test/configs/validator-set.json`. - Minor cleanup/formatting across `test/launcher/*`, `test/scripts/setup-validators.ts`, and related tests. - added `keepAlive` flag to `BaseTestSuite`, in order to avoid tearing down the network while debugging. Defaults, obviously, to false. - added a `failOnTomeout` option on to waitForDataHavenEvents() so the test fails of the timeout is reached and no event was captured. ### Coverage - The test simulates an scenario in which we have two active authorities (alice and bob), which are running, and registered as operators, which is the normal state after the chain launches. Then: - It launches two more nodes (charlie and dave) - It add the nodes to allowlist and register them as operators - It sends the validator set update message - Checks that the validator update message was propagated through the gateway and arrived the external-validators pallet - Checks that the chain continues producing blocks ### Notes The last test case has a timeout of 10 minutes. This is to respect propagation times of the message through the relayers. We are testing that the external validators pallet actually updated the validator set. Locally, I could expect 5~6 minutes, I just wanted to be on the safe side. CI is passing showing that this was enough indeed. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
2025-10-02 11:23:40 +00:00
/**
* Gets the owner account for validator operations.
*/
export function getOwnerAccount() {
return privateKeyToAccount(ANVIL_FUNDED_ACCOUNTS[6].privateKey as `0x${string}`);
}
/**
* Registers a single operator in EigenLayer and for operator sets.
*
* @param validatorName - The name of the validator to register
* @param options - Extended validator options including connectors and deployments
* @throws {Error} If registration transactions fail
*/
export async function registerSingleOperator(
validatorName: TestAccounts,
options: ValidatorOptionsExt
): Promise<void> {
const { connectors, deployments } = options;
const validator = getValidatorInfoByName(
await Bun.file("./configs/validator-set.json").json(),
validatorName
);
logger.info(`🔧 Registering ${validator.publicKey} as operator...`);
try {
const operatorHash = await connectors.walletClient.writeContract({
address: deployments.DelegationManager as `0x${string}`,
abi: delegationManagerAbi,
functionName: "registerAsOperator",
args: [
"0x0000000000000000000000000000000000000000", // initDelegationApprover (no approver)
0, // allocationDelay
"" // metadataURI
],
account: privateKeyToAccount(validator.privateKey as `0x${string}`),
chain: null
});
const operatorReceipt = await connectors.publicClient.waitForTransactionReceipt({
hash: operatorHash
});
if (operatorReceipt.status !== "success") {
throw new Error(
`EigenLayer operator registration failed with status: ${operatorReceipt.status}`
);
}
logger.success(`Registered ${validator.publicKey} as EigenLayer operator`);
logger.info(`🔧 Registering ${validator.publicKey} 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: privateKeyToAccount(validator.privateKey as `0x${string}`),
chain: null
});
logger.info(`📝 Transaction hash for operator set registration: ${hash}`);
const receipt = await connectors.publicClient.waitForTransactionReceipt({ hash });
logger.info(
`📋 Operator set registration receipt: status=${receipt.status}, gasUsed=${receipt.gasUsed}`
);
if (receipt.status === "success") {
logger.success(`Registered ${validator.publicKey} for operator sets`);
}
} catch (error) {
logger.warn(`Failed to register ${validator.publicKey} for operator sets: ${error}`);
throw error;
}
}
/**
* Checks if the service manager has the specified operator.
*
* @param validatorName - The name of the validator to check
* @param options - Extended validator options including connectors and deployments
* @returns Promise resolving to true if the operator exists
*/
export async function serviceManagerHasOperator(
validatorName: TestAccounts,
options: ValidatorOptionsExt
): Promise<boolean> {
const { connectors, deployments } = options;
const validator = getValidatorInfoByName(
await Bun.file("./configs/validator-set.json").json(),
validatorName
);
const validatorEthAddressToSolochainAddress = await connectors.publicClient.readContract({
address: deployments.ServiceManager as `0x${string}`,
abi: dataHavenServiceManagerAbi,
functionName: "validatorEthAddressToSolochainAddress",
args: [validator.publicKey as `0x${string}`]
});
return (
validatorEthAddressToSolochainAddress.toLowerCase() === validator.solochainAddress.toLowerCase()
);
}
/**
* Adds a validator to the allowlist.
*
* @param validatorName - The name of the validator to add
* @param options - Extended validator options including connectors and deployments
* @throws {Error} If the allowlist transaction fails
*/
export async function addValidatorToAllowlist(
validatorName: TestAccounts,
options: ValidatorOptionsExt
): Promise<void> {
const { connectors, deployments } = options;
const validator = getValidatorInfoByName(
await Bun.file("./configs/validator-set.json").json(),
validatorName
);
logger.info(`🔧 Adding ${validatorName} (${validator.publicKey}) to allowlist...`);
try {
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
});
logger.info(`📝 Transaction hash for allowlist: ${hash}`);
const receipt = await connectors.publicClient.waitForTransactionReceipt({ hash });
logger.info(
`📋 Allowlist transaction receipt: status=${receipt.status}, gasUsed=${receipt.gasUsed}`
);
if (receipt.status === "success") {
logger.success(`Added ${validator.publicKey} to allowlist`);
} else {
logger.error(`Failed to add ${validator.publicKey} to allowlist`);
throw new Error(`Transaction failed with status: ${receipt.status}`);
}
} catch (error) {
logger.error(`Error adding ${validatorName} to allowlist: ${error}`);
throw error;
}
}
/**
* Checks if a validator is in the allowlist.
*
* @param validatorName - The name of the validator to check
* @param options - Extended validator options including connectors and deployments
* @returns Promise resolving to true if the validator is allowlisted
*/
export async function isValidatorInAllowlist(
validatorName: TestAccounts,
options: ValidatorOptionsExt
): Promise<boolean> {
const { connectors, deployments } = options;
const validator = getValidatorInfoByName(
await Bun.file("./configs/validator-set.json").json(),
validatorName
);
logger.info(`🔍 Checking allowlist status for ${validatorName} (${validator.publicKey})...`);
const isAllowlisted = await connectors.publicClient.readContract({
address: deployments.ServiceManager as `0x${string}`,
abi: dataHavenServiceManagerAbi,
functionName: "validatorsAllowlist",
args: [validator.publicKey as `0x${string}`]
});
logger.info(`📋 Allowlist status for ${validatorName}: ${isAllowlisted}`);
return isAllowlisted;
}