mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-23 17:28:23 +00:00
fix: 🧑💻 Fix and improve bun cli logging and functionalities (#60)
This PR: 1. Generally improves the logging of the testing CLI, making the logs more concise and easier to follow, with clearer sections and separations. 2. Launches DataHaven solochain nodes at the beginning not the end. 3. Prompts the user if they want to launch DataHaven nodes and Snowbridge Relayers. --------- Co-authored-by: Tim B <79199034+timbrinded@users.noreply.github.com>
This commit is contained in:
parent
3776d80a2e
commit
e161accac2
19 changed files with 253 additions and 180 deletions
8
.github/workflows/task-e2e.yml
vendored
8
.github/workflows/task-e2e.yml
vendored
|
|
@ -71,6 +71,14 @@ jobs:
|
|||
with:
|
||||
name: datahaven-node-${{ inputs.binary-hash }}
|
||||
path: operator/target/release/
|
||||
|
||||
- name: Download snowbridge binary
|
||||
run: |
|
||||
docker create --name temp moonsonglabs/snowbridge-relayer:latest
|
||||
mkdir -p tmp/bin
|
||||
docker cp temp:/usr/local/bin/snowbridge-relay tmp/bin/
|
||||
chmod +x tmp/bin/snowbridge-relay
|
||||
- run: tmp/bin/snowbridge-relay --help
|
||||
- run: chmod +x ../operator/target/release/datahaven-node
|
||||
- run: ../operator/target/release/datahaven-node --help
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { $ } from "bun";
|
||||
import { logger } from "utils";
|
||||
import { logger, printDivider, printHeader } from "utils";
|
||||
|
||||
// ===== Checks =====
|
||||
export const checkDependencies = async (): Promise<void> => {
|
||||
printHeader("Environment Checks");
|
||||
|
||||
if (!(await checkKurtosisInstalled())) {
|
||||
logger.error("Kurtosis CLI is required to be installed: https://docs.kurtosis.com/install");
|
||||
throw Error("❌ Kurtosis CLI application not found.");
|
||||
|
|
@ -23,6 +25,7 @@ export const checkDependencies = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
logger.success("Forge is installed");
|
||||
printDivider();
|
||||
};
|
||||
|
||||
const checkKurtosisInstalled = async (): Promise<boolean> => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import fs from "node:fs";
|
|||
import path from "node:path";
|
||||
import { $ } from "bun";
|
||||
import invariant from "tiny-invariant";
|
||||
import { logger, printHeader } from "utils";
|
||||
import { confirmWithTimeout, logger, printDivider, printHeader } from "utils";
|
||||
import type { LaunchOptions } from ".";
|
||||
import type { LaunchedNetwork } from "./launchedNetwork";
|
||||
|
||||
|
|
@ -21,11 +21,39 @@ const COMMON_LAUNCH_ARGS = [
|
|||
const AUTHORITY_IDS = ["alice", "bob", "charlie", "dave", "eve"];
|
||||
|
||||
// TODO: This is very rough and will need something more substantial when we know what we want!
|
||||
export const performDatahavenOperations = async (
|
||||
/**
|
||||
* Launches a DataHaven solochain network for testing.
|
||||
*
|
||||
* @param options - Configuration options for launching the network.
|
||||
* @param launchedNetwork - An instance of LaunchedNetwork to track the network's state.
|
||||
*/
|
||||
export const launchDataHavenSolochain = async (
|
||||
options: LaunchOptions,
|
||||
launchedNetwork: LaunchedNetwork
|
||||
) => {
|
||||
printHeader("Starting Datahaven Network");
|
||||
printHeader("Starting DataHaven Network");
|
||||
|
||||
let shouldLaunchDataHaven = options.datahaven;
|
||||
if (shouldLaunchDataHaven === undefined) {
|
||||
shouldLaunchDataHaven = await confirmWithTimeout(
|
||||
"Do you want to launch the DataHaven network?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Using flag option: ${shouldLaunchDataHaven ? "will launch" : "will not launch"} DataHaven network`
|
||||
);
|
||||
}
|
||||
|
||||
if (!shouldLaunchDataHaven) {
|
||||
logger.info("Skipping DataHaven network launch. Done!");
|
||||
printDivider();
|
||||
return;
|
||||
}
|
||||
|
||||
// Kill any pre-existing datahaven processes if they exist
|
||||
await $`pkill datahaven`.nothrow().quiet();
|
||||
|
||||
invariant(options.datahavenBinPath, "❌ Datahaven binary path not defined");
|
||||
invariant(
|
||||
|
|
@ -59,7 +87,7 @@ export const performDatahavenOperations = async (
|
|||
|
||||
let completed = false;
|
||||
const file = Bun.file(logFilePath);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const pattern = "Running JSON-RPC server: addr=127.0.0.1:";
|
||||
const blob = await file.text();
|
||||
logger.debug(`Blob: ${blob}`);
|
||||
|
|
|
|||
|
|
@ -2,18 +2,12 @@ import type { Command } from "@commander-js/extra-typings";
|
|||
import { deployContracts } from "scripts/deploy-contracts";
|
||||
import sendTxn from "scripts/send-txn";
|
||||
import invariant from "tiny-invariant";
|
||||
import {
|
||||
ANVIL_FUNDED_ACCOUNTS,
|
||||
getPortFromKurtosis,
|
||||
logger,
|
||||
printDivider,
|
||||
printHeader
|
||||
} from "utils";
|
||||
import { ANVIL_FUNDED_ACCOUNTS, getPortFromKurtosis, logger } from "utils";
|
||||
import { checkDependencies } from "./checks";
|
||||
import { performDatahavenOperations } from "./datahaven";
|
||||
import { launchDataHavenSolochain } from "./datahaven";
|
||||
import { launchKurtosis } from "./kurtosis";
|
||||
import { LaunchedNetwork } from "./launchedNetwork";
|
||||
import { performRelayerOperations } from "./relayer";
|
||||
import { launchRelayers } from "./relayer";
|
||||
import { performSummaryOperations } from "./summary";
|
||||
import { performValidatorOperations } from "./validator";
|
||||
|
||||
|
|
@ -51,39 +45,26 @@ const launchFunction = async (options: LaunchOptions, launchedNetwork: LaunchedN
|
|||
|
||||
const timeStart = performance.now();
|
||||
|
||||
printHeader("Environment Checks");
|
||||
|
||||
await checkDependencies();
|
||||
|
||||
logger.trace("Launching Kurtosis enclave");
|
||||
await launchKurtosis(options);
|
||||
logger.trace("Kurtosis enclave launched");
|
||||
await launchDataHavenSolochain(options, launchedNetwork);
|
||||
|
||||
await launchKurtosis(options);
|
||||
|
||||
logger.trace("Send test transaction");
|
||||
printHeader("Setting Up Blockchain");
|
||||
logger.debug(`Using account ${ANVIL_FUNDED_ACCOUNTS[1].publicKey}`);
|
||||
const privateKey = ANVIL_FUNDED_ACCOUNTS[1].privateKey;
|
||||
const rethPublicPort = await getPortFromKurtosis("el-1-reth-lighthouse", "rpc");
|
||||
const networkRpcUrl = `http://127.0.0.1:${rethPublicPort}`;
|
||||
invariant(networkRpcUrl, "❌ Network RPC URL not found");
|
||||
|
||||
logger.info("💸 Sending test transaction...");
|
||||
await sendTxn(privateKey, networkRpcUrl);
|
||||
|
||||
printDivider();
|
||||
|
||||
logger.trace("Show completion information");
|
||||
const timeEnd = performance.now();
|
||||
const minutes = ((timeEnd - timeStart) / (1000 * 60)).toFixed(1);
|
||||
|
||||
logger.success(`Kurtosis network started successfully in ${minutes} minutes`);
|
||||
|
||||
logger.trace("Deploy contracts using the extracted function");
|
||||
let blockscoutBackendUrl: string | undefined = undefined;
|
||||
|
||||
if (options.blockscout === true) {
|
||||
const blockscoutPublicPort = await getPortFromKurtosis("blockscout", "http");
|
||||
blockscoutBackendUrl = `http://127.0.0.1:${blockscoutPublicPort}`;
|
||||
logger.trace("Blockscout backend URL:", blockscoutBackendUrl);
|
||||
} else if (options.verified) {
|
||||
logger.warn(
|
||||
"⚠️ Contract verification (--verified) requested, but Blockscout is disabled (--no-blockscout). Verification will be skipped."
|
||||
|
|
@ -97,34 +78,20 @@ const launchFunction = async (options: LaunchOptions, launchedNetwork: LaunchedN
|
|||
deployContracts: options.deployContracts
|
||||
});
|
||||
|
||||
if (contractsDeployed) {
|
||||
await performValidatorOperations(options, networkRpcUrl);
|
||||
} else if (options.setupValidators || options.fundValidators) {
|
||||
logger.warn(
|
||||
"⚠️ Validator operations requested but contracts were not deployed. Skipping validator operations."
|
||||
);
|
||||
}
|
||||
if (options.datahaven) {
|
||||
await performDatahavenOperations(options, launchedNetwork);
|
||||
}
|
||||
await performValidatorOperations(options, networkRpcUrl, contractsDeployed);
|
||||
|
||||
if (options.relayer) {
|
||||
await performRelayerOperations(options, launchedNetwork);
|
||||
}
|
||||
|
||||
printDivider();
|
||||
await launchRelayers(options, launchedNetwork);
|
||||
|
||||
performSummaryOperations(options, launchedNetwork);
|
||||
const fullEnd = performance.now();
|
||||
const fullMinutes = ((fullEnd - timeStart) / (1000 * 60)).toFixed(1);
|
||||
logger.info(`Launch function completed successfully in ${fullMinutes} minutes`);
|
||||
logger.success(`Launch function completed successfully in ${fullMinutes} minutes`);
|
||||
};
|
||||
|
||||
export const launch = async (options: LaunchOptions) => {
|
||||
const run = new LaunchedNetwork();
|
||||
try {
|
||||
await launchFunction(options, run);
|
||||
logger.success("Launch script completed successfully");
|
||||
} finally {
|
||||
await run.cleanup();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,15 @@ import { parse, stringify } from "yaml";
|
|||
export const launchKurtosis = async (
|
||||
options: LaunchOptions = {}
|
||||
): Promise<Record<string, KurtosisService>> => {
|
||||
printHeader("Starting Kurtosis Network");
|
||||
|
||||
if ((await checkKurtosisRunning()) && !options.alwaysClean) {
|
||||
logger.info("ℹ️ Kurtosis network is already running.");
|
||||
|
||||
logger.trace("Checking if launchKurtosis option was set via flags");
|
||||
if (options.launchKurtosis === false) {
|
||||
logger.info("Keeping existing Kurtosis enclave. Exiting...");
|
||||
logger.info("Keeping existing Kurtosis enclave.");
|
||||
printDivider();
|
||||
return getServicesFromKurtosis();
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +43,8 @@ export const launchKurtosis = async (
|
|||
);
|
||||
|
||||
if (!shouldRelaunch) {
|
||||
logger.info("Keeping existing Kurtosis enclave. Exiting...");
|
||||
logger.info("Keeping existing Kurtosis enclave.");
|
||||
printDivider();
|
||||
return getServicesFromKurtosis();
|
||||
}
|
||||
|
||||
|
|
@ -48,8 +52,6 @@ export const launchKurtosis = async (
|
|||
}
|
||||
}
|
||||
|
||||
printHeader("Starting Kurtosis Network");
|
||||
|
||||
if (!options.skipCleaning) {
|
||||
logger.info("🧹 Cleaning up Docker and Kurtosis environments...");
|
||||
logger.debug(await $`kurtosis enclave stop datahaven-ethereum`.nothrow().text());
|
||||
|
|
@ -69,7 +71,7 @@ export const launchKurtosis = async (
|
|||
|
||||
const configFile = await modifyConfig(options, "configs/kurtosis/minimal.yaml");
|
||||
|
||||
logger.info(`Using Kurtosis config file: ${configFile}`);
|
||||
logger.info(`⚙️ Using Kurtosis config file: ${configFile}`);
|
||||
|
||||
const { stderr, stdout, exitCode } =
|
||||
await $`kurtosis run github.com/ethpandaops/ethereum-package --args-file ${configFile} --enclave datahaven-ethereum`
|
||||
|
|
@ -85,6 +87,7 @@ export const launchKurtosis = async (
|
|||
logger.info("🔍 Gathering Kurtosis public ports...");
|
||||
const services = await getServicesFromKurtosis();
|
||||
|
||||
logger.success("Kurtosis network started successfully");
|
||||
printDivider();
|
||||
|
||||
return services;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class LaunchedNetwork {
|
|||
|
||||
async cleanup() {
|
||||
for (const process of this.processes) {
|
||||
logger.info(`Process is still running: ${process.pid}`);
|
||||
logger.debug(`Process is still running: ${process.pid}`);
|
||||
}
|
||||
|
||||
for (const fd of this.fileDescriptors) {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import {
|
|||
ANVIL_FUNDED_ACCOUNTS,
|
||||
type RelayerType,
|
||||
SUBSTRATE_FUNDED_ACCOUNTS,
|
||||
confirmWithTimeout,
|
||||
getPortFromKurtosis,
|
||||
logger,
|
||||
parseDeploymentsFile,
|
||||
parseRelayConfig,
|
||||
printDivider,
|
||||
printHeader
|
||||
} from "utils";
|
||||
import type { LaunchOptions } from ".";
|
||||
|
|
@ -22,12 +24,36 @@ type RelayerSpec = {
|
|||
pk: { type: "ethereum" | "substrate"; value: string };
|
||||
};
|
||||
|
||||
export const performRelayerOperations = async (
|
||||
options: LaunchOptions,
|
||||
launchedNetwork: LaunchedNetwork
|
||||
) => {
|
||||
/**
|
||||
* Launches Snowbridge relayers for the DataHaven network.
|
||||
*
|
||||
* @param options - Configuration options for launching the relayers.
|
||||
* @param launchedNetwork - An instance of LaunchedNetwork to track the network's state.
|
||||
*/
|
||||
export const launchRelayers = async (options: LaunchOptions, launchedNetwork: LaunchedNetwork) => {
|
||||
printHeader("Starting Snowbridge Relayers");
|
||||
logger.info("Preparing to generate configs");
|
||||
|
||||
let shouldLaunchRelayers = options.relayer;
|
||||
if (shouldLaunchRelayers === undefined) {
|
||||
shouldLaunchRelayers = await confirmWithTimeout(
|
||||
"Do you want to launch the Snowbridge relayers?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Using flag option: ${shouldLaunchRelayers ? "will launch" : "will not launch"} Snowbridge relayers`
|
||||
);
|
||||
}
|
||||
|
||||
if (!shouldLaunchRelayers) {
|
||||
logger.info("Skipping Snowbridge relayers launch. Done!");
|
||||
printDivider();
|
||||
return;
|
||||
}
|
||||
|
||||
// Kill any pre-existing relayer processes if they exist
|
||||
await $`pkill snowbridge-relay`.nothrow().quiet();
|
||||
|
||||
const anvilDeployments = await parseDeploymentsFile();
|
||||
const beefyClientAddress = anvilDeployments.BeefyClient;
|
||||
|
|
@ -155,4 +181,5 @@ export const performRelayerOperations = async (
|
|||
}
|
||||
|
||||
logger.success("Snowbridge relayers started");
|
||||
printDivider();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,23 +7,21 @@ export const performSummaryOperations = async (
|
|||
options: LaunchOptions,
|
||||
launchedNetwork: LaunchedNetwork
|
||||
) => {
|
||||
logger.trace("Display service information in a clean table");
|
||||
printHeader("Service Endpoints");
|
||||
|
||||
logger.trace("Filter services to display based on blockscout option");
|
||||
const servicesToDisplay = BASE_SERVICES;
|
||||
|
||||
if (options.blockscout === true) {
|
||||
servicesToDisplay.push(...["blockscout", "blockscout-frontend"]);
|
||||
}
|
||||
|
||||
if (options.datahaven === true) {
|
||||
const dhNodes = launchedNetwork.getDHNodes();
|
||||
for (const { id } of dhNodes) {
|
||||
servicesToDisplay.push(`datahaven-${id}`);
|
||||
}
|
||||
const dhNodes = launchedNetwork.getDHNodes();
|
||||
for (const { id } of dhNodes) {
|
||||
servicesToDisplay.push(`datahaven-${id}`);
|
||||
}
|
||||
|
||||
logger.trace("Services to display", servicesToDisplay);
|
||||
|
||||
const displayData: { service: string; ports: Record<string, number>; url: string }[] = [];
|
||||
for (const service of servicesToDisplay) {
|
||||
logger.debug(`Checking service: ${service}`);
|
||||
|
|
@ -106,5 +104,4 @@ export const performSummaryOperations = async (
|
|||
}
|
||||
|
||||
console.table(displayData);
|
||||
logger.debug("Summary completed successfully");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { fundValidators } from "scripts/fund-validators";
|
||||
import { setupValidators } from "scripts/setup-validators";
|
||||
import { updateValidatorSet } from "scripts/update-validator-set";
|
||||
import { confirmWithTimeout, logger } from "utils";
|
||||
import { confirmWithTimeout, logger, printDivider, printHeader } from "utils";
|
||||
import type { LaunchOptions } from "..";
|
||||
|
||||
export const performValidatorOperations = async (options: LaunchOptions, networkRpcUrl: string) => {
|
||||
logger.trace("Set up validators using the extracted function");
|
||||
export const performValidatorOperations = async (
|
||||
options: LaunchOptions,
|
||||
networkRpcUrl: string,
|
||||
contractsDeployed: boolean
|
||||
) => {
|
||||
// If not specified, prompt for funding
|
||||
let shouldFundValidators = options.fundValidators;
|
||||
let shouldSetupValidators = options.setupValidators;
|
||||
let shouldUpdateValidatorSet = options.updateValidatorSet;
|
||||
|
||||
logger.trace("If not specified, prompt for funding");
|
||||
if (shouldFundValidators === undefined) {
|
||||
shouldFundValidators = await confirmWithTimeout(
|
||||
"Do you want to fund validators with tokens and ETH?",
|
||||
|
|
@ -23,7 +23,23 @@ export const performValidatorOperations = async (options: LaunchOptions, network
|
|||
);
|
||||
}
|
||||
|
||||
logger.trace("If not specified, prompt for setup");
|
||||
if (shouldFundValidators) {
|
||||
if (!contractsDeployed) {
|
||||
logger.warn(
|
||||
"⚠️ Funding validators but contracts were not deployed in this CLI run. Could have unexpected results."
|
||||
);
|
||||
}
|
||||
|
||||
await fundValidators({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
} else {
|
||||
logger.debug("Skipping validator funding");
|
||||
printDivider();
|
||||
}
|
||||
|
||||
// If not specified, prompt for setup
|
||||
let shouldSetupValidators = options.setupValidators;
|
||||
if (shouldSetupValidators === undefined) {
|
||||
shouldSetupValidators = await confirmWithTimeout(
|
||||
"Do you want to register validators in EigenLayer?",
|
||||
|
|
@ -36,40 +52,47 @@ export const performValidatorOperations = async (options: LaunchOptions, network
|
|||
);
|
||||
}
|
||||
|
||||
logger.trace("If not specified, prompt for update");
|
||||
if (shouldUpdateValidatorSet === undefined) {
|
||||
shouldUpdateValidatorSet = await confirmWithTimeout(
|
||||
"Do you want to update the validator set on the substrate chain?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Using flag option: ${shouldUpdateValidatorSet ? "will update" : "will not update"} validator set`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldFundValidators) {
|
||||
await fundValidators({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
} else {
|
||||
logger.info("Skipping validator funding");
|
||||
}
|
||||
|
||||
if (shouldSetupValidators) {
|
||||
if (!contractsDeployed) {
|
||||
logger.warn(
|
||||
"⚠️ Setting up validators but contracts were not deployed in this CLI run. Could have unexpected results."
|
||||
);
|
||||
}
|
||||
|
||||
await setupValidators({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
|
||||
// If not specified, prompt for update
|
||||
let shouldUpdateValidatorSet = options.updateValidatorSet;
|
||||
if (shouldUpdateValidatorSet === undefined) {
|
||||
shouldUpdateValidatorSet = await confirmWithTimeout(
|
||||
"Do you want to update the validator set on the substrate chain?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Using flag option: ${shouldUpdateValidatorSet ? "will update" : "will not update"} validator set`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldUpdateValidatorSet) {
|
||||
if (!contractsDeployed) {
|
||||
logger.warn(
|
||||
"⚠️ Updating validator set but contracts were not deployed in this CLI run. Could have unexpected results."
|
||||
);
|
||||
}
|
||||
|
||||
await updateValidatorSet({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
} else {
|
||||
logger.info("Skipping validator set update");
|
||||
logger.debug("Skipping validator set update");
|
||||
printDivider();
|
||||
}
|
||||
} else {
|
||||
logger.info("Skipping validator setup");
|
||||
logger.debug("Skipping validator setup");
|
||||
printDivider();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ function parseIntValue(value: string): number {
|
|||
// So far we only have the launch command
|
||||
// we can expand this to more commands in the future
|
||||
const program = new Command()
|
||||
.option("--datahaven", "Enable Datahaven network to be launched")
|
||||
.option("-l, --launch-kurtosis", "Launch Kurtosis")
|
||||
.option("-d, --deploy-contracts", "Deploy smart contracts")
|
||||
.option("-f, --fund-validators", "Fund validators")
|
||||
|
|
@ -24,17 +25,16 @@ const program = new Command()
|
|||
.option("--no-update-validator-set", "Skip update validator set")
|
||||
.option("-b, --blockscout", "Enable Blockscout")
|
||||
.option("--slot-time <number>", "Set slot time in seconds", parseIntValue)
|
||||
.option("--datahaven", "Enable Datahaven network to be launched")
|
||||
.option("--kurtosis-network-args <value>", "CustomKurtosis network args")
|
||||
.option("-v, --verified", "Verify smart contracts with Blockscout")
|
||||
.option("--always-clean", "Always clean Kurtosis", false)
|
||||
.option("-q, --skip-cleaning", "Skip cleaning Kurtosis")
|
||||
.option("-r, --relayer", "Enable Relayer")
|
||||
.option(
|
||||
"--datahaven-bin-path <value>",
|
||||
"Path to the datahaven binary",
|
||||
"../operator/target/release/datahaven-node"
|
||||
)
|
||||
.option("-v, --verified", "Verify smart contracts with Blockscout")
|
||||
.option("--always-clean", "Always clean Kurtosis", false)
|
||||
.option("-q, --skip-cleaning", "Skip cleaning Kurtosis")
|
||||
.option("-r, --relayer", "Enable Relayer")
|
||||
.option(
|
||||
"-p, --relayer-bin-path <value>",
|
||||
"Path to the relayer binary",
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
{
|
||||
"source": {
|
||||
"beacon": {
|
||||
"endpoint": "http://127.0.0.1:32791",
|
||||
"stateEndpoint": "http://127.0.0.1:32791",
|
||||
"spec": {
|
||||
"syncCommitteeSize": 512,
|
||||
"slotsInEpoch": 32,
|
||||
"epochsPerSyncCommitteePeriod": 256,
|
||||
"forkVersions": {
|
||||
"deneb": 0,
|
||||
"electra": 0
|
||||
}
|
||||
},
|
||||
"datastore": {
|
||||
"location": "/home/timbo/workspace/moonsong/datahaven/test/tmp/facu-test",
|
||||
"maxEntries": 100
|
||||
}
|
||||
"source": {
|
||||
"beacon": {
|
||||
"endpoint": "http://127.0.0.1:33030",
|
||||
"stateEndpoint": "http://127.0.0.1:33030",
|
||||
"spec": {
|
||||
"syncCommitteeSize": 512,
|
||||
"slotsInEpoch": 32,
|
||||
"epochsPerSyncCommitteePeriod": 4,
|
||||
"forkVersions": {
|
||||
"deneb": 0,
|
||||
"electra": 18446744073709551615
|
||||
}
|
||||
},
|
||||
"sink": {
|
||||
"parachain": {
|
||||
"endpoint": "ws://127.0.0.1:9944",
|
||||
"maxWatchedExtrinsics": 8,
|
||||
"headerRedundancy": 20
|
||||
},
|
||||
"updateSlotInterval": 30
|
||||
},
|
||||
"datastore": {
|
||||
"location": "/Users/facundofarall/Desktop/Moonsong/datahaven/test/tmp/facu-test",
|
||||
"maxEntries": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"sink": {
|
||||
"parachain": {
|
||||
"endpoint": "ws://127.0.0.1:9944",
|
||||
"maxWatchedExtrinsics": 8,
|
||||
"headerRedundancy": 20
|
||||
},
|
||||
"updateSlotInterval": 30
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
"generate:wagmi": "wagmi generate",
|
||||
"generate:snowbridge-cfgs": "bun -e \"import {generateSnowbridgeConfigs} from './scripts/gen-snowbridge-cfgs.ts'; await generateSnowbridgeConfigs()\"",
|
||||
"start:e2e:verified": "bun cli --verified --blockscout --deploy-contracts --setup-validators --update-validator-set --fund-validators --slot-time 1",
|
||||
"start:e2e:ci": "bun cli -d --setup-validators --update-validator-set --fund-validators --always-clean --slot-time 2",
|
||||
"start:e2e:ci": "bun cli -d --setup-validators --update-validator-set --fund-validators --always-clean --slot-time 2 --datahaven --relayer",
|
||||
"start:e2e:minrelayer": "bun cli --relayer -d --no-setup-validators --no-update-validator-set --no-fund-validators --datahaven",
|
||||
"stop:e2e": "pkill datahaven ; kurtosis enclave stop datahaven-ethereum && kurtosis clean && kurtosis engine stop && docker container prune -f",
|
||||
"stop:e2e": "pkill datahaven ; pkill snowbridge-relay ; kurtosis enclave stop datahaven-ethereum && kurtosis clean && kurtosis engine stop && docker container prune -f",
|
||||
"stop:e2e:verified": "bun stop:e2e",
|
||||
"stop:e2e:minimal": "bun stop:e2e",
|
||||
"stop:e2e:quick": "kurtosis enclave stop datahaven-ethereum",
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ In this phase, we register validators as operators in EigenLayer and sync the va
|
|||
- Configures validator addresses and permissions
|
||||
|
||||
3. **Sync Validator Set to DataHaven**
|
||||
|
||||
- Use `update-validator-set.ts` script to sync validators
|
||||
- Calls `sendNewValidatorSet` function in the DataHavenServiceManager contract
|
||||
- Sends validator set through Snowbridge Gateway to DataHaven solochain
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { $ } from "bun";
|
||||
import invariant from "tiny-invariant";
|
||||
import { confirmWithTimeout, logger, printHeader, runShellCommandWithLogger } from "utils";
|
||||
import {
|
||||
confirmWithTimeout,
|
||||
logger,
|
||||
printDivider,
|
||||
printHeader,
|
||||
runShellCommandWithLogger
|
||||
} from "utils";
|
||||
|
||||
interface DeployContractsOptions {
|
||||
rpcUrl: string;
|
||||
|
|
@ -38,6 +44,8 @@ export const deployContracts = async (options: DeployContractsOptions): Promise<
|
|||
|
||||
if (!shouldDeployContracts) {
|
||||
logger.info("Skipping contract deployment. Done!");
|
||||
printDivider();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +84,8 @@ export const deployContracts = async (options: DeployContractsOptions): Promise<
|
|||
await runShellCommandWithLogger(deployCommand, { cwd: "../contracts" });
|
||||
|
||||
logger.success("Contracts deployed successfully");
|
||||
printDivider();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import path from "node:path";
|
|||
// Script to fund validators with tokens and ETH for local testing
|
||||
import { $ } from "bun";
|
||||
import invariant from "tiny-invariant";
|
||||
import { logger, printHeader } from "../utils/index";
|
||||
import { logger, printDivider, printHeader } from "../utils/index";
|
||||
|
||||
interface FundValidatorsOptions {
|
||||
rpcUrl: string;
|
||||
|
|
@ -167,8 +167,9 @@ export const fundValidators = async (options: FundValidatorsOptions): Promise<bo
|
|||
for (const validator of validators) {
|
||||
if (validator.publicKey !== tokenCreator) {
|
||||
const transferCmd = `${castExecutable} send --private-key ${creatorPrivateKey} ${underlyingTokenAddress} "transfer(address,uint256)" ${validator.publicKey} ${erc20TransferAmount} --rpc-url ${rpcUrl}`;
|
||||
const { exitCode: transferExitCode, stderr: transferStderr } =
|
||||
await $`sh -c ${transferCmd}`.nothrow();
|
||||
const { exitCode: transferExitCode, stderr: transferStderr } = await $`sh -c ${transferCmd}`
|
||||
.nothrow()
|
||||
.quiet();
|
||||
if (transferExitCode !== 0) {
|
||||
logger.error(
|
||||
`Failed to transfer tokens to validator ${validator.publicKey}: ${transferStderr.toString()}`
|
||||
|
|
@ -201,7 +202,7 @@ export const fundValidators = async (options: FundValidatorsOptions): Promise<bo
|
|||
if (BigInt(validatorEthBalance) === BigInt(0)) {
|
||||
const ethTransferCmd = `${castExecutable} send --private-key ${creatorPrivateKey} ${validator.publicKey} --value ${ethTransferAmount} --rpc-url ${rpcUrl}`;
|
||||
const { exitCode: ethTransferExitCode, stderr: ethTransferStderr } =
|
||||
await $`sh -c ${ethTransferCmd}`.nothrow();
|
||||
await $`sh -c ${ethTransferCmd}`.nothrow().quiet();
|
||||
if (ethTransferExitCode !== 0) {
|
||||
logger.error(
|
||||
`Failed to transfer ETH to validator ${validator.publicKey}: ${ethTransferStderr.toString()}`
|
||||
|
|
@ -225,7 +226,10 @@ export const fundValidators = async (options: FundValidatorsOptions): Promise<bo
|
|||
}
|
||||
}
|
||||
}
|
||||
logger.info("All validators have been funded with tokens");
|
||||
|
||||
logger.success("All validators have been funded with tokens");
|
||||
printDivider();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { generateRandomAccount, logger } from "utils";
|
||||
import { generateRandomAccount, logger, printDivider, printHeader } from "utils";
|
||||
import { http, createWalletClient, defineChain, parseEther, publicActions } from "viem";
|
||||
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
||||
import { privateKeyToAccount } from "viem/accounts";
|
||||
|
||||
export default async function main(privateKey: string, networkRpcUrl: string) {
|
||||
printHeader("Sending Test ETH Transaction");
|
||||
|
||||
const datahaven = defineChain({
|
||||
id: 3151908,
|
||||
name: "datahaven",
|
||||
|
|
@ -49,5 +51,7 @@ export default async function main(privateKey: string, networkRpcUrl: string) {
|
|||
logger.info(`Waiting for transaction ${hash} to be confirmed...`);
|
||||
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||
logger.info(`Transaction confirmed in block ${receipt.blockNumber}`);
|
||||
|
||||
printDivider();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { $ } from "bun";
|
||||
import invariant from "tiny-invariant";
|
||||
import { confirmWithTimeout, logger, printHeader, runShellCommandWithLogger } from "../utils/index";
|
||||
import { logger, printDivider, printHeader, runShellCommandWithLogger } from "../utils/index";
|
||||
|
||||
interface SetupValidatorsOptions {
|
||||
rpcUrl: string;
|
||||
|
|
@ -25,41 +24,28 @@ interface ValidatorConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Registers validators in EigenLayer
|
||||
* Registers validators in EigenLayer based on a configuration file.
|
||||
* This function reads validator details (public/private keys, optional solochain addresses)
|
||||
* from a JSON file. If `executeSignup` is true (or confirmed by user prompt),
|
||||
* it iterates through the configured validators and runs the
|
||||
* `script/transact/SignUpValidator.s.sol` forge script for each to register them.
|
||||
* Environment variables `OPERATOR_PRIVATE_KEY`, `OPERATOR_SOLOCHAIN_ADDRESS`, and `NETWORK`
|
||||
* are set for the forge script execution.
|
||||
*
|
||||
* @param options - Configuration options for setup
|
||||
* @param options.rpcUrl - The RPC URL to connect to
|
||||
* @param options.validatorsConfig - Path to JSON config file (uses default config if not provided)
|
||||
* @param options.executeSignup - Whether to run the SignUpValidator script
|
||||
* @returns Promise resolving to true if validators were set up successfully, false if skipped
|
||||
* @param options - Configuration options for the validator setup process.
|
||||
* @param options.rpcUrl - The RPC URL for the Ethereum network to interact with.
|
||||
* @param options.validatorsConfig - Optional path to the JSON file containing validator configurations.
|
||||
* Defaults to `../configs/validator-set.json` relative to this script.
|
||||
* @param options.executeSignup - Optional. If true, proceeds with registration. If false, skips.
|
||||
* If undefined, the user is prompted to confirm registration.
|
||||
* @param options.networkName - Optional network name used when executing underlying scripts (e.g., for setting the `NETWORK` environment variable).
|
||||
* Defaults to "anvil".
|
||||
* @returns A Promise resolving to `true` if the validator registration process was executed
|
||||
* (for all configured validators), or `false` if the registration was skipped
|
||||
* (either due to the `executeSignup` option or user declining the prompt).
|
||||
*/
|
||||
export const setupValidators = async (options: SetupValidatorsOptions): Promise<boolean> => {
|
||||
const {
|
||||
rpcUrl,
|
||||
validatorsConfig,
|
||||
executeSignup,
|
||||
networkName = "anvil",
|
||||
deploymentPath
|
||||
} = options;
|
||||
|
||||
// Check if executeSignup option was set via flags, or prompt if not
|
||||
let shouldExecuteSignup = executeSignup;
|
||||
if (shouldExecuteSignup === undefined) {
|
||||
shouldExecuteSignup = await confirmWithTimeout(
|
||||
"Do you want to register validators in EigenLayer?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Using flag option: ${shouldExecuteSignup ? "will register" : "will not register"} validators`
|
||||
);
|
||||
}
|
||||
|
||||
if (!shouldExecuteSignup) {
|
||||
logger.info("Skipping validator setup. Done!");
|
||||
return false;
|
||||
}
|
||||
const { rpcUrl, validatorsConfig, networkName = "anvil" } = options;
|
||||
|
||||
printHeader("Setting Up DataHaven Validators");
|
||||
|
||||
|
|
@ -130,11 +116,13 @@ export const setupValidators = async (options: SetupValidatorsOptions): Promise<
|
|||
const signupCommand = `forge script script/transact/SignUpValidator.s.sol --rpc-url ${rpcUrl} --broadcast --no-rpc-rate-limit --non-interactive`;
|
||||
logger.debug(`Running command: ${signupCommand}`);
|
||||
|
||||
await runShellCommandWithLogger(signupCommand, { env, cwd: "../contracts" });
|
||||
await runShellCommandWithLogger(signupCommand, { env, cwd: "../contracts", logLevel: "debug" });
|
||||
|
||||
logger.success(`Successfully registered validator ${validator.publicKey}`);
|
||||
}
|
||||
|
||||
printDivider();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import path from "node:path";
|
|||
// Update validator set on DataHaven substrate chain
|
||||
import { $ } from "bun";
|
||||
import invariant from "tiny-invariant";
|
||||
import { logger, printHeader } from "../utils/index";
|
||||
import { logger, printDivider, printHeader } from "../utils/index";
|
||||
|
||||
interface UpdateValidatorSetOptions {
|
||||
rpcUrl: string;
|
||||
|
|
@ -56,7 +56,7 @@ export const updateValidatorSet = async (options: UpdateValidatorSetOptions): Pr
|
|||
|
||||
logger.debug(`Running command: ${sendCommand}`);
|
||||
|
||||
const { exitCode, stderr } = await $`sh -c ${sendCommand}`.nothrow();
|
||||
const { exitCode, stderr } = await $`sh -c ${sendCommand}`.nothrow().quiet();
|
||||
|
||||
if (exitCode !== 0) {
|
||||
logger.error(`Failed to send validator set: ${stderr.toString()}`);
|
||||
|
|
@ -83,6 +83,8 @@ export const updateValidatorSet = async (options: UpdateValidatorSetOptions): Pr
|
|||
}
|
||||
*/
|
||||
|
||||
printDivider();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import { existsSync } from "node:fs";
|
|||
import { spawn } from "bun";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export type LogLevel = "info" | "debug" | "error" | "warn";
|
||||
|
||||
export const runShellCommandWithLogger = async (
|
||||
command: string,
|
||||
options?: { cwd?: string; env?: object }
|
||||
options?: { cwd?: string; env?: object; logLevel?: LogLevel }
|
||||
) => {
|
||||
const { cwd = ".", env = {} } = options || {};
|
||||
const { cwd = ".", env = {}, logLevel = "info" as LogLevel } = options || {};
|
||||
|
||||
try {
|
||||
if (!existsSync(cwd)) {
|
||||
|
|
@ -29,7 +31,8 @@ export const runShellCommandWithLogger = async (
|
|||
|
||||
const readStream = async (
|
||||
reader: typeof stdoutReader | typeof stderrReader,
|
||||
streamName: string
|
||||
streamName: string,
|
||||
logLevel: LogLevel
|
||||
) => {
|
||||
try {
|
||||
while (true) {
|
||||
|
|
@ -37,7 +40,9 @@ export const runShellCommandWithLogger = async (
|
|||
if (done) break;
|
||||
const text = new TextDecoder().decode(value);
|
||||
const trimmedText = text.trim();
|
||||
logger.info(trimmedText.includes("\n") ? `\n${trimmedText}` : trimmedText);
|
||||
if (trimmedText) {
|
||||
logger[logLevel](trimmedText.includes("\n") ? `\n${trimmedText}` : trimmedText);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error reading from ${streamName} stream:`, err);
|
||||
|
|
@ -46,7 +51,10 @@ export const runShellCommandWithLogger = async (
|
|||
}
|
||||
};
|
||||
|
||||
Promise.all([readStream(stdoutReader, "stdout"), readStream(stderrReader, "stderr")]);
|
||||
Promise.all([
|
||||
readStream(stdoutReader, "stdout", logLevel),
|
||||
readStream(stderrReader, "stderr", "error")
|
||||
]);
|
||||
|
||||
await proc.exited;
|
||||
} catch (err) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue