diff --git a/operator/runtime/stagenet/src/configs/mod.rs b/operator/runtime/stagenet/src/configs/mod.rs index 124d25f5..0e80adab 100644 --- a/operator/runtime/stagenet/src/configs/mod.rs +++ b/operator/runtime/stagenet/src/configs/mod.rs @@ -640,7 +640,9 @@ impl pallet_evm_chain_id::Config for Runtime {} // --- Snowbridge Config Constants & Parameter Types --- parameter_types! { - pub UniversalLocation: InteriorLocation = Here; + // TODO: Change this to the actual network ID of DataHaven + pub const ThisNetwork: NetworkId = NetworkId::Polkadot; + pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetwork::get())].into(); pub InboundDeliveryCost: BalanceOf = 0; pub RootLocation: Location = Location::here(); pub Parameters: PricingParameters = PricingParameters { diff --git a/test/.papi/descriptors/package.json b/test/.papi/descriptors/package.json index 4627254d..f734406b 100644 --- a/test/.papi/descriptors/package.json +++ b/test/.papi/descriptors/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-autogenerated.7209825829100564812", + "version": "0.1.0-autogenerated.13919917606265561517", "name": "@polkadot-api/descriptors", "files": [ "dist" diff --git a/test/.papi/metadata/datahaven.scale b/test/.papi/metadata/datahaven.scale index 49122fbe..038c651e 100644 Binary files a/test/.papi/metadata/datahaven.scale and b/test/.papi/metadata/datahaven.scale differ diff --git a/test/cli/handlers/launch/relayer.ts b/test/cli/handlers/launch/relayer.ts index 1f5cd9d0..3afe6e91 100644 --- a/test/cli/handlers/launch/relayer.ts +++ b/test/cli/handlers/launch/relayer.ts @@ -33,12 +33,14 @@ type RelayerSpec = { type: RelayerType; config: string; pk: { type: "ethereum" | "substrate"; value: string }; + secondaryPk?: { type: "ethereum" | "substrate"; value: string }; }; const RELAYER_CONFIG_DIR = "tmp/configs"; const RELAYER_CONFIG_PATHS = { BEACON: path.join(RELAYER_CONFIG_DIR, "beacon-relay.json"), - BEEFY: path.join(RELAYER_CONFIG_DIR, "beefy-relay.json") + BEEFY: path.join(RELAYER_CONFIG_DIR, "beefy-relay.json"), + SOLOCHAIN: path.join(RELAYER_CONFIG_DIR, "solochain-relay.json") }; const INITIAL_CHECKPOINT_FILE = "dump-initial-checkpoint.json"; const INITIAL_CHECKPOINT_DIR = "tmp/beacon-checkpoint"; @@ -129,7 +131,20 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La config: RELAYER_CONFIG_PATHS.BEACON, pk: { type: "substrate", - value: SUBSTRATE_FUNDED_ACCOUNTS.ALITH.privateKey + value: SUBSTRATE_FUNDED_ACCOUNTS.BALTATHAR.privateKey + } + }, + { + name: "relayer-⛓️", + type: "solochain", + config: RELAYER_CONFIG_PATHS.SOLOCHAIN, + pk: { + type: "ethereum", + value: ANVIL_FUNDED_ACCOUNTS[1].privateKey + }, + secondaryPk: { + type: "substrate", + value: SUBSTRATE_FUNDED_ACCOUNTS.CHARLETH.privateKey } } ]; @@ -163,23 +178,46 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La `Fetched ports: ETH WS=${ethWsPort}, ETH HTTP=${ethHttpPort}, Substrate WS=${substrateWsPort} (from DataHaven node)` ); - if (type === "beacon") { - const cfg = parseRelayConfig(json, type); - cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`; - cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`; - cfg.source.beacon.datastore.location = "/data"; - cfg.sink.parachain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`; + switch (type) { + case "beacon": + { + const cfg = parseRelayConfig(json, type); + cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`; + cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`; + cfg.source.beacon.datastore.location = "/data"; + cfg.sink.parachain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`; - await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4)); - logger.success(`Updated beacon config written to ${outputFilePath}`); - } else { - const cfg = parseRelayConfig(json, type); - cfg.source.polkadot.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`; - cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`; - cfg.sink.contracts.BeefyClient = beefyClientAddress; - cfg.sink.contracts.Gateway = gatewayAddress; - await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4)); - logger.success(`Updated beefy config written to ${outputFilePath}`); + await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4)); + logger.success(`Updated beacon config written to ${outputFilePath}`); + } + break; + case "beefy": + { + const cfg = parseRelayConfig(json, type); + cfg.source.polkadot.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`; + cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`; + cfg.sink.contracts.BeefyClient = beefyClientAddress; + cfg.sink.contracts.Gateway = gatewayAddress; + await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4)); + logger.success(`Updated beefy config written to ${outputFilePath}`); + } + break; + case "solochain": + { + const cfg = parseRelayConfig(json, type); + cfg.source.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`; + cfg.source.solochain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`; + cfg.source.contracts.BeefyClient = beefyClientAddress; + cfg.source.contracts.Gateway = gatewayAddress; + cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`; + cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`; + cfg.source.beacon.datastore.location = datastorePath; + cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`; + cfg.sink.contracts.Gateway = gatewayAddress; + await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4)); + logger.success(`Updated solochain config written to ${outputFilePath}`); + } + break; } } @@ -187,7 +225,7 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La await initEthClientPallet(options, launchedNetwork); - for (const { config, name, type, pk } of relayersToStart) { + for (const { config, name, type, pk, secondaryPk } of relayersToStart) { try { const containerName = `snowbridge-${type}-relay`; logger.info(`🚀 Starting relayer ${containerName} ...`); @@ -228,6 +266,10 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La pk.value ]; + if (type === "solochain" && secondaryPk) { + relayerCommandArgs.push("--substrate.private-key", secondaryPk.value); + } + const command: string[] = [ ...commandBase, ...volumeMounts, diff --git a/test/cli/handlers/stop/index.ts b/test/cli/handlers/stop/index.ts index 7e379e3b..f42b290d 100644 --- a/test/cli/handlers/stop/index.ts +++ b/test/cli/handlers/stop/index.ts @@ -50,16 +50,16 @@ export const stopDockerComponents = async (type: keyof typeof COMPONENTS, option const name = COMPONENTS[type].componentName; const imageName = COMPONENTS[type].imageName; logger.debug(`Checking currently running ${name} ...`); - const relayers = await getContainersMatchingImage(imageName); - logger.info(`🔎 Found ${relayers.length} containers(s) running`); - if (relayers.length === 0) { + const components = await getContainersMatchingImage(imageName); + logger.info(`🔎 Found ${components.length} containers(s) running the ${name}`); + if (components.length === 0) { logger.info(`🤷‍ No ${name} containers found running`); return; } let shouldStopComponent = options.all || options[COMPONENTS[type].optionName]; if (shouldStopComponent === undefined) { shouldStopComponent = await confirmWithTimeout( - `Do you want to stop the ${imageName} relayers?`, + `Do you want to stop the ${imageName} containers?`, true, 10 ); @@ -80,7 +80,7 @@ export const stopDockerComponents = async (type: keyof typeof COMPONENTS, option remaining.length === 0, `❌ ${remaining.length} containers are still running and have not been stopped.` ); - logger.info(`🪓 ${relayers.length} ${name} containers stopped successfully`); + logger.info(`🪓 ${components.length} ${name} containers stopped successfully`); }; const removeDockerNetwork = async (networkName: string, options: StopOptions) => { diff --git a/test/configs/snowbridge/solochain-relay.json b/test/configs/snowbridge/solochain-relay.json new file mode 100644 index 00000000..8716653b --- /dev/null +++ b/test/configs/snowbridge/solochain-relay.json @@ -0,0 +1,49 @@ +{ + "source": { + "ethereum": { + "endpoint": "" + }, + "solochain": { + "endpoint": "" + }, + "contracts": { + "BeefyClient": "", + "Gateway": "" + }, + "beacon": { + "endpoint": "http://127.0.0.1:33030", + "stateEndpoint": "http://127.0.0.1:33030", + "spec": { + "syncCommitteeSize": 512, + "slotsInEpoch": 32, + "epochsPerSyncCommitteePeriod": 256, + "forkVersions": { + "deneb": 0, + "electra": 0 + } + }, + "datastore": { + "location": "/Users/tdemeco/Desktop/Moonsong/datahaven/test/tmp/tobi-test", + "maxEntries": 100 + } + } + }, + "sink": { + "ethereum": { + "endpoint": "" + }, + "contracts": { + "Gateway": "" + } + }, + "schedule": { + "id": 0, + "totalRelayerCount": 1, + "sleepInterval": 10 + }, + "reward-address": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "ofac": { + "enabled": false, + "apiKey": "" + } +} \ No newline at end of file diff --git a/test/configs/snowbridge/substrate-relay.json b/test/configs/snowbridge/substrate-relay.json deleted file mode 100644 index 76aa70b1..00000000 --- a/test/configs/snowbridge/substrate-relay.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "source": { - "ethereum": { - "endpoint": "" - }, - "polkadot": { - "endpoint": "" - }, - "contracts": { - "BeefyClient": "", - "Gateway": "" - }, - "channel-id": "" - }, - "sink": { - "ethereum": { - "endpoint": "" - }, - "contracts": { - "Gateway": "" - } - }, - "schedule": { - "id": 0, - "totalRelayerCount": 1, - "sleepInterval": 10 - } -} diff --git a/test/scripts/cargo-crossbuild.ts b/test/scripts/cargo-crossbuild.ts index d372fd21..bc1ff6d1 100644 --- a/test/scripts/cargo-crossbuild.ts +++ b/test/scripts/cargo-crossbuild.ts @@ -52,7 +52,11 @@ export const cargoCrossbuild = async (options: { datahavenBuildExtraArgs?: strin const target = "x86_64-unknown-linux-gnu"; await addRustupTarget(target); - const command = `cargo build --target ${target} --release`; + + // Get additional arguments from command line + const additionalArgs = options.datahavenBuildExtraArgs ?? ""; + + const command = `cargo build --target ${target} --release ${additionalArgs}`; logger.debug(`Running build command: ${command}`); if (LOG_LEVEL === "debug") { diff --git a/test/scripts/gen-snowbridge-cfgs.ts b/test/scripts/gen-snowbridge-cfgs.ts deleted file mode 100644 index e35b2f33..00000000 --- a/test/scripts/gen-snowbridge-cfgs.ts +++ /dev/null @@ -1,453 +0,0 @@ -import { mkdir, writeFile } from "node:fs/promises"; -import { join } from "node:path"; -import { parseArgs } from "node:util"; -import { spawn } from "bun"; -import { logger } from "utils"; -import { z } from "zod"; - -// ---- Zod Schemas for Validation ---- -const beefyRelaySchema = z - .object({ - sink: z.object({ - contracts: z.object({ - BeefyClient: z.string().optional(), - Gateway: z.string().optional() - }), - ethereum: z.object({ - endpoint: z.string(), - "gas-limit": z.string() - }) - }), - source: z.object({ - polkadot: z.object({ - endpoint: z.string() - }) - }) - }) - .describe("Beefy Relay Configuration"); - -const beaconRelaySchema = z - .object({ - source: z.object({ - beacon: z.object({ - endpoint: z.string(), - spec: z.object({ - forkVersions: z.object({ - electra: z.number() - }) - }), - datastore: z.object({ - location: z.string() - }) - }) - }), - sink: z.object({ - parachain: z.object({ - endpoint: z.string() - }) - }) - }) - .describe("Beacon Relay Configuration"); - -const executionRelaySchema = z - .object({ - source: z.object({ - ethereum: z.object({ - endpoint: z.string() - }), - contracts: z.object({ - Gateway: z.string() - }), - "channel-id": z.string(), - beacon: z.object({ - datastore: z.object({ - location: z.string() - }) - }) - }), - sink: z.object({ - parachain: z.object({ - endpoint: z.string() - }) - }), - schedule: z.object({ - id: z.number() - }) - }) - .describe("Execution Layer Relay Configuration"); - -const substrateRelaySchema = z - .object({ - source: z.object({ - ethereum: z.object({ - endpoint: z.string() - }), - polkadot: z.object({ - endpoint: z.string() - }), - contracts: z.object({ - BeefyClient: z.string(), - Gateway: z.string() - }), - "channel-id": z.string() - }), - sink: z.object({ - contracts: z.object({ - Gateway: z.string() - }), - ethereum: z.object({ - endpoint: z.string() - }) - }) - }) - .describe("Substrate Relay Configuration"); - -const beaconFinalitySchema = z - .object({ - execution_optimistic: z.boolean(), - finalized: z.boolean(), - data: z.object({ - previous_justified: z.object({ - epoch: z.string(), - root: z.string() - }), - current_justified: z.object({ - epoch: z.string(), - root: z.string() - }), - finalized: z.object({ - epoch: z.string(), - root: z.string() - }) - }) - }) - .describe("Beacon Finality Configuration"); - -// ---- Configuration Options ---- -interface SnowbridgeConfigOptions { - outputDir: string; - assetsDir: string; - logsDir: string; - relayBin: string; - ethEndpointWs: string; - ethGasLimit: string; - relaychainEndpoint: string; - beaconEndpointHttp: string; - ethWriterEndpoint: string; - primaryGovernanceChannelId: string; - secondaryGovernanceChannelId: string; - dataStoreDir: string; - beaconWaitTimeoutSeconds: number; - beaconElectraForkVersion: number; - executionScheduleId: number; -} - -const DEFAULT_OPTIONS = { - outputDir: "tmp/output", - assetsDir: "configs/snowbridge", - logsDir: "tmp/logs", - relayBin: "relay", - ethEndpointWs: "ws://localhost:8545", - ethGasLimit: "8000000", - relaychainEndpoint: "ws://localhost:9944", - beaconEndpointHttp: "http://localhost:5052", - ethWriterEndpoint: "", - primaryGovernanceChannelId: "0", - secondaryGovernanceChannelId: "1", - beaconWaitTimeoutSeconds: 300, - beaconElectraForkVersion: 0, - executionScheduleId: 0 -}; - -/** - * Retrieves a Snowbridge contract address from environment variables. - */ -async function getSnowbridgeAddressFromEnv(name: string): Promise { - const envVarName = `SNOWBRIDGE_${name.toUpperCase()}_ADDRESS`; - const address = process.env[envVarName]; - if (!address) { - logger.warn(`Environment variable ${envVarName} not set. Using empty string.`); - } - return address || ""; -} - -/** - * Reads, validates, updates, and writes a JSON configuration file. - */ -async function updateJsonConfig( - templateName: string, - outputName: string, - schema: z.ZodType, - updateFn: (obj: T) => void | Promise, - options: { assetsDir: string; outputDir: string } -): Promise { - const templatePath = join(options.assetsDir, templateName); - const outputPath = join(options.outputDir, outputName); - - try { - logger.trace({ templatePath, outputPath }, "Read config template"); - const obj = await import(templatePath, { with: { type: "json" } }); - logger.trace( - { rawConfig: obj.default }, - `Attempting to parse ${templateName} config with Zod schema` - ); - const config = schema.parse(obj.default); - logger.debug(`Successfully parsed ${schema.description} `); - - await updateFn(config); - logger.trace({ config }, "Updated config object"); - - await writeFile(outputPath, JSON.stringify(config, null, 2)); - logger.debug(`Wrote configuration to ${outputPath}`); - } catch (error) { - logger.error( - { err: error, templatePath, outputPath }, - `Failed to update/write config ${outputName}` - ); - throw error; - } -} - -/** - * Configures all relayer components - */ -async function configRelayer(options: SnowbridgeConfigOptions): Promise { - logger.info("Starting configuration generation..."); - - // Ensure all required directories exist - logger.debug("Ensuring all required directories exist"); - for (const dir of [options.outputDir, options.assetsDir, options.logsDir, options.dataStoreDir]) { - await mkdir(dir, { recursive: true }); - logger.debug(`Ensured directory exists: ${dir}`); - } - - const commonOptions = { - assetsDir: options.assetsDir, - outputDir: options.outputDir - }; - - // Beefy relay - logger.debug("Configuring Beefy relay..."); - await updateJsonConfig( - "beefy-relay.json", - "beefy-relay.json", - beefyRelaySchema, - async (obj) => { - obj.sink.contracts.BeefyClient = await getSnowbridgeAddressFromEnv("BeefyClient"); - obj.sink.contracts.Gateway = await getSnowbridgeAddressFromEnv("GatewayProxy"); - obj.sink.ethereum.endpoint = options.ethEndpointWs; - obj.sink.ethereum["gas-limit"] = options.ethGasLimit; - obj.source.polkadot.endpoint = options.relaychainEndpoint; - }, - commonOptions - ); - - // Beacon relay - logger.debug("Configuring Beacon relay..."); - await updateJsonConfig( - "beacon-relay.json", - "beacon-relay.json", - beaconRelaySchema, - (obj) => { - obj.source.beacon.endpoint = options.beaconEndpointHttp; - obj.source.beacon.spec.forkVersions.electra = options.beaconElectraForkVersion; - obj.source.beacon.datastore.location = options.dataStoreDir; - obj.sink.parachain.endpoint = options.relaychainEndpoint; - }, - commonOptions - ); - - // Execution relay - logger.debug("Configuring Execution relay..."); - await updateJsonConfig( - "execution-relay.json", - "execution-relay.json", - executionRelaySchema, - async (obj) => { - obj.source.ethereum.endpoint = options.ethEndpointWs; - obj.source.contracts.Gateway = await getSnowbridgeAddressFromEnv("GatewayProxy"); - obj.source["channel-id"] = options.primaryGovernanceChannelId; - obj.source.beacon.datastore.location = options.dataStoreDir; - obj.sink.parachain.endpoint = options.relaychainEndpoint; - obj.schedule.id = options.executionScheduleId; - }, - commonOptions - ); - - // Substrate relay - primary - logger.debug("Configuring Primary Substrate relay..."); - await updateJsonConfig( - "substrate-relay.json", - "substrate-relay-primary.json", - substrateRelaySchema, - async (obj) => { - obj.source.ethereum.endpoint = options.ethEndpointWs; - obj.source.polkadot.endpoint = options.relaychainEndpoint; - obj.source.contracts.BeefyClient = await getSnowbridgeAddressFromEnv("BeefyClient"); - obj.source.contracts.Gateway = await getSnowbridgeAddressFromEnv("GatewayProxy"); - obj.source["channel-id"] = options.primaryGovernanceChannelId; - obj.sink.contracts.Gateway = await getSnowbridgeAddressFromEnv("GatewayProxy"); - obj.sink.ethereum.endpoint = options.ethWriterEndpoint; - }, - commonOptions - ); - - // Substrate relay - secondary - logger.debug("Configuring Secondary Substrate relay..."); - await updateJsonConfig( - "substrate-relay.json", - "substrate-relay-secondary.json", - substrateRelaySchema, - async (obj) => { - obj.source.ethereum.endpoint = options.ethEndpointWs; - obj.source.polkadot.endpoint = options.relaychainEndpoint; - obj.source.contracts.BeefyClient = await getSnowbridgeAddressFromEnv("BeefyClient"); - obj.source.contracts.Gateway = await getSnowbridgeAddressFromEnv("GatewayProxy"); - obj.source["channel-id"] = options.secondaryGovernanceChannelId; - obj.sink.contracts.Gateway = await getSnowbridgeAddressFromEnv("GatewayProxy"); - obj.sink.ethereum.endpoint = options.ethWriterEndpoint; - }, - commonOptions - ); - - logger.info("Finished configuration generation."); -} - -/** - * Waits for the Beacon chain to reach finality before proceeding - */ -async function waitBeaconChainReady(options: SnowbridgeConfigOptions): Promise { - logger.info("Waiting for Beacon chain finality..."); - let initialBeaconBlock = ""; - const maxAttempts = options.beaconWaitTimeoutSeconds; - - for (let i = 0; i < maxAttempts; i++) { - try { - const res = await fetch( - `${options.beaconEndpointHttp}/eth/v1/beacon/states/head/finality_checkpoints` - ); - const json = await res.json(); - const parsed = beaconFinalitySchema.parse(json); - initialBeaconBlock = parsed.data.finalized.root || ""; - - logger.trace({ attempt: i + 1, initialBeaconBlock }, "Checked beacon finality"); - - if ( - initialBeaconBlock && - initialBeaconBlock !== "0x0000000000000000000000000000000000000000000000000000000000000000" - ) { - logger.info(`Beacon chain finalized. Finalized root: ${initialBeaconBlock}`); - return; - } - } catch (_error) { - logger.trace({ attempt: i + 1 }, "Beacon finality check failed or not ready, retrying..."); - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - - throw new Error( - `❌ Beacon chain not ready after ${options.beaconWaitTimeoutSeconds} seconds timeout` - ); -} - -/** - * Generates a beacon checkpoint using the relay binary - */ -async function writeBeaconCheckpoint(options: SnowbridgeConfigOptions): Promise { - logger.info("Generating beacon checkpoint..."); - const cmdArgs = [ - options.relayBin, - "generate-beacon-checkpoint", - "--config", - join(options.outputDir, "beacon-relay.json"), - "--export-json" - ]; - - logger.debug({ command: cmdArgs.join(" ") }, "Spawning process to generate beacon checkpoint"); - - const proc = spawn({ - cmd: cmdArgs, - cwd: options.outputDir, - stdout: "pipe", - stderr: "pipe" - }); - - await proc.exited; - logger.info("Beacon checkpoint generated."); -} - -/** - * Main function to generate Snowbridge configurations - */ -export async function generateSnowbridgeConfigs( - customOptions: Partial> = {} -): Promise { - // Merge default options with custom options - const mergedOptions = { ...DEFAULT_OPTIONS, ...customOptions }; - - // Add derived options - const options: SnowbridgeConfigOptions = { - ...mergedOptions, - ethWriterEndpoint: mergedOptions.ethWriterEndpoint || mergedOptions.ethEndpointWs, - dataStoreDir: join(mergedOptions.outputDir, "relayer_data") - }; - - logger.debug({ options }, "Resolved configuration values"); - - logger.info("Starting Snowbridge config generation script..."); - - try { - await configRelayer(options); - await waitBeaconChainReady(options); - await writeBeaconCheckpoint(options); - logger.info("Snowbridge config generation script finished successfully."); - } catch (error) { - logger.error({ err: error }, "Snowbridge config generation script failed"); - throw error; - } -} - -// Check if we're running this file directly -if (import.meta.url === `file://${process.argv[1]}`) { - logger.trace("Parsing command line arguments"); - - const { values } = parseArgs({ - options: { - outputDir: { type: "string" }, - assetsDir: { type: "string" }, - logsDir: { type: "string" }, - relayBin: { type: "string" }, - ethEndpointWs: { type: "string" }, - ethGasLimit: { type: "string" }, - relaychainEndpoint: { type: "string" }, - beaconEndpointHttp: { type: "string" }, - ethWriterEndpoint: { type: "string" }, - primaryGovernanceChannelId: { type: "string" }, - secondaryGovernanceChannelId: { type: "string" } - }, - args: process.argv.slice(2) - }); - - // Convert string arguments to appropriate types - const options: Partial> = {}; - - // Only add properties that were actually provided - if (values.outputDir) options.outputDir = values.outputDir; - if (values.assetsDir) options.assetsDir = values.assetsDir; - if (values.logsDir) options.logsDir = values.logsDir; - if (values.relayBin) options.relayBin = values.relayBin; - if (values.ethEndpointWs) options.ethEndpointWs = values.ethEndpointWs; - if (values.ethGasLimit) options.ethGasLimit = values.ethGasLimit; - if (values.relaychainEndpoint) options.relaychainEndpoint = values.relaychainEndpoint; - if (values.beaconEndpointHttp) options.beaconEndpointHttp = values.beaconEndpointHttp; - if (values.ethWriterEndpoint) options.ethWriterEndpoint = values.ethWriterEndpoint; - if (values.primaryGovernanceChannelId) - options.primaryGovernanceChannelId = values.primaryGovernanceChannelId; - if (values.secondaryGovernanceChannelId) - options.secondaryGovernanceChannelId = values.secondaryGovernanceChannelId; - - generateSnowbridgeConfigs(options).catch((error) => { - console.error("Failed to generate Snowbridge configs:", error); - process.exit(1); - }); -} diff --git a/test/scripts/set-datahaven-parameters.ts b/test/scripts/set-datahaven-parameters.ts index 469bde74..a86f4a92 100644 --- a/test/scripts/set-datahaven-parameters.ts +++ b/test/scripts/set-datahaven-parameters.ts @@ -4,7 +4,6 @@ import { createClient } from "polkadot-api"; import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat"; import { getWsProvider } from "polkadot-api/ws-provider/web"; import invariant from "tiny-invariant"; - import { confirmWithTimeout, getEvmEcdsaSigner, @@ -95,7 +94,8 @@ export const setDataHavenParameters = async ( try { for (const param of parameters) { - logger.info(`Attempting to set parameter: ${String(param.name)} = ${String(param.value)}`); + // TODO: Add a graceful way to print the value of the parameter, since it won't always be representable as a hex string + logger.info(`Attempting to set parameter: ${String(param.name)} = ${param.value.asHex()}`); const setParameterArgs: any = { key_value: { diff --git a/test/scripts/snowbridge-relayer.ts b/test/scripts/snowbridge-relayer.ts deleted file mode 100644 index 6ff53d3c..00000000 --- a/test/scripts/snowbridge-relayer.ts +++ /dev/null @@ -1,100 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { $ } from "bun"; -import { Octokit } from "octokit"; -import invariant from "tiny-invariant"; -import { logger, printHeader } from "utils"; - -const IMAGE_NAME = "snowbridge-relay:local"; -const RELATIVE_DOCKER_FILE_PATH = "../../docker/SnowbridgeRelayer.dockerfile"; -const CONTEXT = "../.."; -const TMP_DIR = path.resolve(__dirname, "../tmp"); -const RELAY_BINARY_PATH = path.resolve(TMP_DIR, "snowbridge-relay"); - -//Downloads the latest snowbridge-relay binary from SnowFork's GitHub releases -async function downloadRelayBinary() { - printHeader("Downloading latest snowbridge-relay binary"); - if (!fs.existsSync(TMP_DIR)) { - fs.mkdirSync(TMP_DIR, { recursive: true }); - } - - const octokit = new Octokit(); - - try { - logger.info("Fetching latest release info from Snowfork/snowbridge"); - const latestRelease = await octokit.rest.repos.getLatestRelease({ - owner: "Snowfork", - repo: "snowbridge" - }); - const tagName = latestRelease.data.tag_name; - logger.info(`🔎 Found latest release: ${tagName}`); - - const relayAsset = latestRelease.data.assets.find((asset) => asset.name === "snowbridge-relay"); - - if (!relayAsset) { - throw new Error("Could not find snowbridge-relay asset in the latest release"); - } - - logger.info( - `Downloading snowbridge-relay (${Math.round((relayAsset.size / 1024 / 1024) * 100) / 100} MB)` - ); - - const response = await fetch(relayAsset.browser_download_url); - if (!response.ok) { - throw new Error(`Failed to download: ${response.statusText}`); - } - - const buffer = await response.arrayBuffer(); - await Bun.write(RELAY_BINARY_PATH, buffer); - - await $`chmod +x ${RELAY_BINARY_PATH}`; - - logger.success(`Successfully downloaded snowbridge-relay ${tagName} to ${RELAY_BINARY_PATH}`); - return RELAY_BINARY_PATH; - } catch (error: any) { - logger.error(`Failed to download snowbridge-relay: ${error.message}`); - throw error; - } -} - -// This can be run with `bun build:docker:relayer` or via a script by importing the below function -export default async function buildRelayer() { - await downloadRelayBinary(); - - printHeader(`Running docker-build at: ${__dirname}`); - const dockerfilePath = path.resolve(__dirname, RELATIVE_DOCKER_FILE_PATH); - const contextPath = path.resolve(__dirname, CONTEXT); - - const file = Bun.file(dockerfilePath); - invariant(await file.exists(), `Dockerfile not found at ${dockerfilePath}`); - logger.debug(`Dockerfile found at ${dockerfilePath}`); - - const dockerCommand = `docker build -t ${IMAGE_NAME} -f ${dockerfilePath} ${contextPath}`; - logger.debug(`Executing docker command: ${dockerCommand}`); - const { stdout, stderr, exitCode } = await $`sh -c ${dockerCommand}`.nothrow().quiet(); - - if (exitCode !== 0) { - logger.error(`Docker build failed with exit code ${exitCode}`); - logger.error(`stdout: ${stdout.toString()}`); - logger.error(`stderr: ${stderr.toString()}`); - process.exit(exitCode); - } - - logger.info("Docker build action completed"); - - const { - exitCode: runExitCode, - stdout: runStdout, - stderr: runStderr - } = await $`sh -c docker run ${IMAGE_NAME}`.quiet().nothrow(); - - if (runExitCode !== 0) { - logger.error(`Docker run failed with exit code ${runExitCode}`); - logger.error(`stdout: ${runStdout.toString()}`); - logger.error(`stderr: ${runStderr.toString()}`); - process.exit(runExitCode); - } - - logger.info("Docker run action completed"); - logger.success("Docker image built successfully"); -} diff --git a/test/utils/docker.ts b/test/utils/docker.ts index 83a52002..c8a72e9a 100644 --- a/test/utils/docker.ts +++ b/test/utils/docker.ts @@ -66,7 +66,7 @@ export const getServicesFromDocker = async (): Promise => { }; export const getContainersMatchingImage = async (imageName: string) => { - const containers = await docker.listContainers(); + const containers = await docker.listContainers({ all: true }); const matches = containers.filter((container) => container.Image.includes(imageName)); return matches; }; diff --git a/test/utils/parser.ts b/test/utils/parser.ts index 18586c8a..36e8e498 100644 --- a/test/utils/parser.ts +++ b/test/utils/parser.ts @@ -56,7 +56,58 @@ export const BeefyRelayConfigSchema = z.object({ }); export type BeefyRelayConfig = z.infer; -export type RelayerType = "beefy" | "beacon"; +export const SolochainRelayConfigSchema = z.object({ + source: z.object({ + ethereum: z.object({ + endpoint: z.string() + }), + solochain: z.object({ + endpoint: z.string() + }), + contracts: z.object({ + BeefyClient: z.string(), + Gateway: z.string() + }), + beacon: z.object({ + endpoint: z.string(), + stateEndpoint: z.string(), + spec: z.object({ + syncCommitteeSize: z.number(), + slotsInEpoch: z.number(), + epochsPerSyncCommitteePeriod: z.number(), + forkVersions: z.object({ + deneb: z.number(), + electra: z.number() + }) + }), + datastore: z.object({ + location: z.string(), + maxEntries: z.number() + }) + }) + }), + sink: z.object({ + contracts: z.object({ + Gateway: z.string() + }), + ethereum: z.object({ + endpoint: z.string() + }) + }), + schedule: z.object({ + id: z.number(), + totalRelayerCount: z.number(), + sleepInterval: z.number() + }), + "reward-address": z.string(), + ofac: z.object({ + enabled: z.boolean(), + apiKey: z.string() + }) +}); +export type SolochainRelayConfig = z.infer; + +export type RelayerType = "beefy" | "beacon" | "solochain"; /** * Parse beacon relay configuration @@ -80,6 +131,17 @@ function parseBeefyConfig(config: unknown): BeefyRelayConfig { throw new Error(`Failed to parse config as BeefyRelayConfig: ${result.error.message}`); } +/** + * Parse solochain relay configuration + */ +function parseSolochainConfig(config: unknown): SolochainRelayConfig { + const result = SolochainRelayConfigSchema.safeParse(config); + if (result.success) { + return result.data; + } + throw new Error(`Failed to parse config as SolochainRelayConfig: ${result.error.message}`); +} + /** * Type Guard to check if a config object is a BeaconRelayConfig */ @@ -91,13 +153,18 @@ export function isBeaconConfig( export function parseRelayConfig(config: unknown, type: "beacon"): BeaconRelayConfig; export function parseRelayConfig(config: unknown, type: "beefy"): BeefyRelayConfig; +export function parseRelayConfig(config: unknown, type: "solochain"): SolochainRelayConfig; export function parseRelayConfig( config: unknown, type: RelayerType -): BeaconRelayConfig | BeefyRelayConfig; +): BeaconRelayConfig | BeefyRelayConfig | SolochainRelayConfig; export function parseRelayConfig( config: unknown, type: RelayerType -): BeaconRelayConfig | BeefyRelayConfig { - return type === "beacon" ? parseBeaconConfig(config) : parseBeefyConfig(config); +): BeaconRelayConfig | BeefyRelayConfig | SolochainRelayConfig { + return type === "beacon" + ? parseBeaconConfig(config) + : type === "beefy" + ? parseBeefyConfig(config) + : parseSolochainConfig(config); }