From 9f55e1033920ce71fab800b77f27ed3bc96fe73a Mon Sep 17 00:00:00 2001 From: Tobi Demeco <50408393+TDemeco@users.noreply.github.com> Date: Thu, 29 May 2025 15:14:46 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20Enabling=20solochain=20rela?= =?UTF-8?q?yer=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR: - Adds launching the new `solochain` relayer in the relayers' script, with its config files and schemas. - Updates the relayer launching to use switch-case logic since we are now going to be running 4 relayers, making it cleaner. - Minor CLI fixes (adding additional args to build command for Linux, improving naming, deleting idle containers instead of only active ones). - Deletes unused files `substrate-relay.json` (now `solochain-relay.json`), `gen-snowbridge-cfgs.ts` and `snowbridge-relayer.ts` (now Snowbridge binary and docker image generation can be done exclusively from our [Snowbridge repo](https://github.com/Moonsong-Labs/snowbridge)) - Updates the `UniversalLocation` of our stagenet runtime to be under the global consensus for XCM instead of actually being the global consensus. This makes it so we can actually use the Snowbridge System pallets to queue up messages. For mainnet we are going to want to have our own Network ID instead of using Polkadot's. > [!WARNING] > ~~All in all these changes allows us to run the solochain relayer, but it won't work without the refactor that's being worked on in https://github.com/Moonsong-Labs/snowbridge/pull/14. I'd advise waiting for that PR to be merged before merging this one.~~ MERGED ✅ --- operator/runtime/stagenet/src/configs/mod.rs | 4 +- test/.papi/descriptors/package.json | 2 +- test/.papi/metadata/datahaven.scale | Bin 355970 -> 358035 bytes test/cli/handlers/launch/relayer.ts | 80 +++- test/cli/handlers/stop/index.ts | 10 +- test/configs/snowbridge/solochain-relay.json | 49 ++ test/configs/snowbridge/substrate-relay.json | 28 -- test/scripts/cargo-crossbuild.ts | 6 +- test/scripts/gen-snowbridge-cfgs.ts | 453 ------------------- test/scripts/set-datahaven-parameters.ts | 4 +- test/scripts/snowbridge-relayer.ts | 100 ---- test/utils/docker.ts | 2 +- test/utils/parser.ts | 75 ++- 13 files changed, 198 insertions(+), 615 deletions(-) create mode 100644 test/configs/snowbridge/solochain-relay.json delete mode 100644 test/configs/snowbridge/substrate-relay.json delete mode 100644 test/scripts/gen-snowbridge-cfgs.ts delete mode 100644 test/scripts/snowbridge-relayer.ts 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 49122fbec846fb4c9ba7d08cdcad371cdfc53da7..038c651eed3ebde5c8ef6b80bff7a09008949ce9 100644 GIT binary patch delta 17376 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 Q&@>g1Iw-zX2W`^-1z4tYM*si- delta 15562 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 W;r+TEX3RfaN9+Q&WBneR^8Wy(<7JQl 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); }