From ec64945421ebeccb691ce60c0568e5438728042c Mon Sep 17 00:00:00 2001 From: Pablo Mendez Date: Tue, 4 Mar 2025 12:12:47 +0100 Subject: [PATCH 1/2] deprecate e2e test --- .../endToEndTest/dappmanagerTestApi.ts | 199 --------- .../endToEndTest/ensureDappnodeEnvironment.ts | 131 ------ .../endToEndTest/executeTests.ts | 157 ------- .../githubActions/endToEndTest/index.ts | 119 ------ .../githubActions/endToEndTest/params.ts | 183 -------- .../endToEndTest/testCheckers.ts | 171 -------- .../githubActions/endToEndTest/types.ts | 390 ------------------ .../githubActions/endToEndTest/utils.ts | 92 ----- src/commands/githubActions/index.ts | 7 +- src/params.ts | 7 +- 10 files changed, 5 insertions(+), 1451 deletions(-) delete mode 100644 src/commands/githubActions/endToEndTest/dappmanagerTestApi.ts delete mode 100644 src/commands/githubActions/endToEndTest/ensureDappnodeEnvironment.ts delete mode 100644 src/commands/githubActions/endToEndTest/executeTests.ts delete mode 100644 src/commands/githubActions/endToEndTest/index.ts delete mode 100644 src/commands/githubActions/endToEndTest/params.ts delete mode 100644 src/commands/githubActions/endToEndTest/testCheckers.ts delete mode 100644 src/commands/githubActions/endToEndTest/types.ts delete mode 100644 src/commands/githubActions/endToEndTest/utils.ts diff --git a/src/commands/githubActions/endToEndTest/dappmanagerTestApi.ts b/src/commands/githubActions/endToEndTest/dappmanagerTestApi.ts deleted file mode 100644 index 2560b247..00000000 --- a/src/commands/githubActions/endToEndTest/dappmanagerTestApi.ts +++ /dev/null @@ -1,199 +0,0 @@ -import got, { Response } from "got"; -import { - StakerConfigSet, - StakerConfigGet, - InstalledPackageDetailData, - InstalledPackageDataApiReturn, - PackageToInstall, - IpfsRepository -} from "./types.js"; -import { Network } from "@dappnode/types"; - -export class DappmanagerTestApi { - url: string; - - constructor(url: string) { - this.url = url; - } - - /** - * Health check of the DAppNode - */ - async healthCheck(): Promise { - const response = await got(`${this.url}/ping`); - if (response.statusCode !== 200) throw Error("Health check failed"); - } - - /** - * Get the list of installed packages - * - * @returns list of packages - * @throws Error if request fails - */ - async packagesGet(): Promise { - return JSON.parse( - (await (await this.ensureSuccess(await got(`${this.url}/packagesGet`))) - .body) as string - ) as InstalledPackageDataApiReturn[]; - } - - /** - * Get a package by its dnpName - * - * @param dnpName name of the package - * @returns package object - * @throws Error if package not found - */ - async packageGet(dnpName: string): Promise { - return JSON.parse( - (await ( - await this.ensureSuccess( - await got(`${this.url}/packageGet`, { json: { dnpName } }) - ) - ).body) as string - ) as InstalledPackageDetailData; - } - - /** - * Installs a package - * - * @param dnpName name of the package. It may be an ipfs hash or a dnpName - * @param version version of the package - * @param userSettings user settings for the package - * @throws Error if request fails - */ - async packageInstall({ - dnpName, - version, - userSettings - }: PackageToInstall): Promise { - const options = { - BYPASS_RESOLVER: true, - BYPASS_CORE_RESTRICTION: true, - BYPASS_SIGNED_RESTRICTION: true - }; - - await this.ensureSuccess( - await got.post(`${this.url}/packageInstall`, { - json: { - name: dnpName, - version, - options, - userSettings - } - }) - ); - } - - /** - * Removes a package - * - * @param dnpName name of the package - * @param deleteVolumes delete volumes of the package - * @throws Error if request fails - */ - async packageRemove({ - dnpName, - deleteVolumes = true - }: { - dnpName: string; - deleteVolumes?: boolean; - }): Promise { - await this.ensureSuccess( - await got.post(`${this.url}/packageRemove`, { - json: { dnpName, deleteVolumes } - }) - ); - } - - /** - * Sets staker config - * - * @param stakerConfig staker config - * @throws Error if request fails - */ - async stakerConfigSet( - stakerConfig: StakerConfigSet - ): Promise { - await this.ensureSuccess( - await got.post(`${this.url}/stakerConfigSet`, { - json: { stakerConfig: stakerConfig } - }) - ); - } - - /** - * Gets staker config - * - * @param network network to get the staker config - * @returns staker config - * @throws Error if request fails - */ - async stakerConfigGet( - network: T - ): Promise> { - return JSON.parse( - (await ( - await this.ensureSuccess( - await got.post(`${this.url}/stakerConfigGet`, { json: { network } }) - ) - ).body) as string - ) as StakerConfigGet; - } - - /** - * Get the IPFS client target - * - * @returns IPFS client target - * @throws Error if request fails - */ - async ipfsClientTargetGet(): Promise { - return JSON.parse( - (await ( - await this.ensureSuccess(await got(`${this.url}/ipfsClientTargetGet`)) - ).body) as string - ) as IpfsRepository; - } - - /** - * Set the IPFS client target - * - * @param ipfsRepository IPFS repository - * @param deleteLocalIpfsClient delete local IPFS client - * - * @throws Error if request fails - */ - async ipfsClientTargetSet({ - ipfsRepository, - deleteLocalIpfsClient - }: { - ipfsRepository: IpfsRepository; - deleteLocalIpfsClient?: boolean; - }): Promise { - await this.ensureSuccess( - await got.post(`${this.url}/ipfsClientTargetSet`, { - json: { ipfsRepository, deleteLocalIpfsClient } - }) - ); - } - - async packageRestartVolumes({ dnpName }: { dnpName: string }): Promise { - await this.ensureSuccess( - await got.post(`${this.url}/packageRestartVolumes`, { - json: { dnpName } - }) - ); - } - - /** - * Middleware: throw error if http code !==2xx - */ - private async ensureSuccess(response: Response): Promise { - if (response.statusCode < 200 || response.statusCode >= 300) { - throw Error( - `Request failed with code ${response.statusCode}: ${response.statusMessage}` - ); - } - return response; - } -} diff --git a/src/commands/githubActions/endToEndTest/ensureDappnodeEnvironment.ts b/src/commands/githubActions/endToEndTest/ensureDappnodeEnvironment.ts deleted file mode 100644 index ebb8de2f..00000000 --- a/src/commands/githubActions/endToEndTest/ensureDappnodeEnvironment.ts +++ /dev/null @@ -1,131 +0,0 @@ -import chalk from "chalk"; -import { DappmanagerTestApi } from "./dappmanagerTestApi.js"; -import { - nonStakerPackagesSetup, - getStakerConfigByNetwork, - packagesToKeep -} from "./params.js"; -import { IpfsClientTarget } from "./types.js"; -import got from "got"; -import { Network } from "@dappnode/types"; - -/** - * Ensure that the DAppNode environment is ready to run the integration tests - */ -export async function ensureDappnodeEnvironment({ - dappmanagerTestApi, - network -}: { - dappmanagerTestApi: DappmanagerTestApi; - network?: Network; -}): Promise { - // Check the Bind container IP address is in the /etc/resolv.conf file - await ensureDockerAliasesResolveFromHost(); - // Check dappmanager is running - await dappmanagerTestApi.healthCheck(); - // Make sure extra pkgs are removed - await ensureOnlyDefaultPkgsInstalled(dappmanagerTestApi, network); - // Ensure that the Staker configurations are persisted - if (network) await persistStakerConfigs(dappmanagerTestApi, network); - // Ensure that the Staker packages are installed - await ensureNonStakerPkgsAreInstalled(dappmanagerTestApi); - // Ensure IPFS is running and IPFS repository is in local mode - await ensureIpfsInLocalMode(dappmanagerTestApi); -} - -/** - * Checks dappmanager alias request resolves from host to an HTTP 200 - */ -async function ensureDockerAliasesResolveFromHost(): Promise { - const dappmanagerAlias = "dappmanager.dappnode"; - try { - const response = await got(`http://${dappmanagerAlias}`); - if (response.statusCode < 200 || response.statusCode >= 300) - throw Error(`Response status code is ${response.statusCode}`); - } catch (e) { - throw Error(`Could not resolve ${dappmanagerAlias} from host: ${e}`); - } -} - -/** - * Ensure that the Staker configurations are persisted - */ -async function persistStakerConfigs( - dappmanagerTestApi: DappmanagerTestApi, - network: Network -): Promise { - console.log(` - Persisting ${network} staker configuration`); - const stakerConfig = getStakerConfigByNetwork(network); - await dappmanagerTestApi.stakerConfigSet(stakerConfig); -} - -/** - * Ensure only required packages are installed (Staker configs from prater mainnet and gnosis) - */ -async function ensureOnlyDefaultPkgsInstalled( - dappmanagerTestApi: DappmanagerTestApi, - network?: Network -): Promise { - const installedPackages = await dappmanagerTestApi.packagesGet(); - const _packagesToKeep = packagesToKeep(network); - console.log( - chalk.dim( - ` - Packages to keep in dappnode environment: ${_packagesToKeep}` - ) - ); - - for (const installedPackage of installedPackages) { - if (!_packagesToKeep.includes(installedPackage.dnpName)) { - console.log( - chalk.dim( - ` - Removing package ${installedPackage.dnpName} from the DAppNode environment` - ) - ); - await dappmanagerTestApi.packageRemove({ - dnpName: installedPackage.dnpName, - deleteVolumes: true - }); - } - } -} - -/** - * Ensure the non Staker packages needed are also installed (install them if not) - */ -async function ensureNonStakerPkgsAreInstalled( - dappmanagerTestApi: DappmanagerTestApi -): Promise { - const installedPackages = await dappmanagerTestApi.packagesGet(); - - for (const pkg of nonStakerPackagesSetup) - if (!installedPackages.some(({ dnpName }) => dnpName === pkg)) { - console.log( - chalk.dim(` - Installing package ${pkg} in the DAppNode environment`) - ); - await dappmanagerTestApi.packageInstall({ - dnpName: pkg, - version: "latest" - }); - } -} - -/** - * Ensure IPFS is running and IPFS repository is in local mode - */ -async function ensureIpfsInLocalMode( - dappmanagerTestApi: DappmanagerTestApi -): Promise { - const ipfsMode = await dappmanagerTestApi.ipfsClientTargetGet(); - - if (ipfsMode.ipfsClientTarget !== "local") { - console.log( - chalk.dim(" - IPFS is not in local mode. Switching to local mode...") - ); - ipfsMode.ipfsClientTarget = IpfsClientTarget.local; - - await dappmanagerTestApi.ipfsClientTargetSet({ - ipfsRepository: ipfsMode, - deleteLocalIpfsClient: false - }); - } -} diff --git a/src/commands/githubActions/endToEndTest/executeTests.ts b/src/commands/githubActions/endToEndTest/executeTests.ts deleted file mode 100644 index f1077972..00000000 --- a/src/commands/githubActions/endToEndTest/executeTests.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { DappmanagerTestApi } from "./dappmanagerTestApi.js"; -import chalk from "chalk"; -import { - getIsStakerPkg, - printPackageMetadata, - setStakerConfig, - getExecuteProofOfAttestation -} from "./utils.js"; -import { executeTestCheckers } from "./testCheckers.js"; -import { Manifest, Compose, Network } from "@dappnode/types"; - -/** - * Execute the tests for the integration test workflow. These tests require - * a previous setup of the environment. - * Tests to execute: - * - Install package from scratch - * - Update package to the given hash from production - * For each test, the following checks are performed: - * - Container status (running, healthy) - * - No error logs after a timeout - * - Healthcheck endpoint returns 200 - */ -export async function executeEndToEndTests({ - dappmanagerTestApi, - releaseMultiHash, - manifest, - compose, - errorLogsTimeout, - healthCheckUrl, - environmentByService, - network -}: { - dappmanagerTestApi: DappmanagerTestApi; - releaseMultiHash: string; - manifest: Manifest; - compose: Compose; - errorLogsTimeout: number; - healthCheckUrl?: string; - environmentByService?: Record; - network?: Network; -}): Promise { - const errors: Error[] = []; - const { name } = manifest; - const isStakerPkg = getIsStakerPkg(name); - - printPackageMetadata(manifest, releaseMultiHash); - - // TEST: Install package from scratch - console.log(chalk.dim("\nTEST: Install pkg from scratch")); - await testPackageInstallFromScratch( - dappmanagerTestApi, - releaseMultiHash, - isStakerPkg, - name, - compose, - errorLogsTimeout, - healthCheckUrl, - network, - environmentByService - ).catch(e => errors.push(e)); - - // Skip update test if running in test environment, dappnodesdk package name is not published - if (process.env.ENVIRONMENT !== "TEST") { - // Update package to the given hash - console.log(chalk.dim("\nTEST: Install production pkg and update")); - await testPackageInstallAndUpdate( - dappmanagerTestApi, - releaseMultiHash, - isStakerPkg, - name, - compose, - errorLogsTimeout, - healthCheckUrl, - network, - environmentByService - ).catch(e => errors.push(e)); - } - - // Throw aggregated error if any - if (errors.length > 0) throw Error(errors.map(e => e.message).join("\n")); -} - -async function testPackageInstallAndUpdate( - dappmanagerTestApi: DappmanagerTestApi, - releaseMultiHash: string, - isStakerPkg: boolean, - dnpName: string, - compose: Compose, - errorLogsTimeout: number, - healthCheckUrl?: string, - network?: Network, - environmentByService?: Record -): Promise { - // Remove package, if not found it will throw an error but it's ok - await dappmanagerTestApi - .packageRemove({ dnpName }) - .catch(() => console.log(" - Package already removed")); - await dappmanagerTestApi.packageInstall({ - dnpName, - version: "latest", // Install production version - userSettings: { environment: environmentByService } - }); - await dappmanagerTestApi.packageInstall({ - dnpName, - version: releaseMultiHash, // Install test version - userSettings: { environment: environmentByService } - }); - // Set staker config if staker package (Volumes not removed to simulate a real update) - if (isStakerPkg && network) - await setStakerConfig(dnpName, dappmanagerTestApi, network, false); - await executeTestCheckers({ - dnpName, - compose, - errorLogsTimeout, - healthCheckUrl, - network, - // We may execute proof of attestation if test is testPackageInstallAndUpdate - executeProofOfAttestation: getExecuteProofOfAttestation({ - network, - dnpName, - isStakerPkg - }) - }); -} - -async function testPackageInstallFromScratch( - dappmanagerTestApi: DappmanagerTestApi, - releaseMultiHash: string, - isStakerPkg: boolean, - dnpName: string, - compose: Compose, - errorLogsTimeout: number, - healthCheckUrl?: string, - network?: Network, - environmentByService?: Record -): Promise { - // Remove package, if not found it will throw an error but it's ok - await dappmanagerTestApi - .packageRemove({ dnpName }) - .catch(() => console.log(" - Package already removed")); - await dappmanagerTestApi.packageInstall({ - dnpName, - version: releaseMultiHash, - userSettings: { environment: environmentByService || {} } - }); - // Set staker config if staker package (Volumes removed to simulate a real install) - if (isStakerPkg && network) - await setStakerConfig(dnpName, dappmanagerTestApi, network, true); - await executeTestCheckers({ - dnpName, - compose, - errorLogsTimeout, - healthCheckUrl, - network, - executeProofOfAttestation: false // never execute proof of attestation on install from scratch - }); -} diff --git a/src/commands/githubActions/endToEndTest/index.ts b/src/commands/githubActions/endToEndTest/index.ts deleted file mode 100644 index 3b1f40dd..00000000 --- a/src/commands/githubActions/endToEndTest/index.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { CommandModule } from "yargs"; -import { CliGlobalOptions } from "../../../types.js"; -import { ensureDappnodeEnvironment } from "./ensureDappnodeEnvironment.js"; -import { readCompose, readManifest } from "../../../files/index.js"; -import { buildHandler } from "../../build/handler.js"; -import { executeEndToEndTests } from "./executeTests.js"; -import { DappmanagerTestApi } from "./dappmanagerTestApi.js"; -import { localDappmanagerTestApiUrl, localIpfsApiUrl } from "./params.js"; -import chalk from "chalk"; -import { Network, networks } from "@dappnode/types"; - -interface CliCommandOptions extends CliGlobalOptions { - healthCheckUrl?: string; - errorLogsTimeout: number; - environmentByService?: string; - network?: string; -} - -export const endToEndTest: CommandModule< - CliGlobalOptions, - CliCommandOptions -> = { - command: "test-end-to-end", - describe: "Run end to end tests (Install from scratch and update)", - builder: { - healthCheckUrl: { - type: "string", - describe: - "Optional health check URL, if the HTTP code is not 200, the test will fail" - }, - errorLogsTimeout: { - describe: - "Timeout in seconds to wait for error logs to appear. If error logs appear after the timeout, the test will fail", - type: "number", - default: 30 - }, - network: { - describe: - "Network to use for the test if any. Available values are mainnet, prater, gnosis", - type: "string" - }, - environmentByService: { - describe: - "Environments by service to install the package with. JSON format", - nargs: 1, - type: "string", - default: "{}" - } - }, - handler: async (args): Promise => await gaTestEndToEndHandler(args) -}; - -export async function gaTestEndToEndHandler({ - dir, - healthCheckUrl, - errorLogsTimeout, - environmentByService, - network -}: CliCommandOptions): Promise { - if (network && !networks.includes(network as Network)) - throw Error(`Invalid network ${network}. Available values are ${networks}`); - const dappmanagerTestApi = new DappmanagerTestApi(localDappmanagerTestApiUrl); - const compose = readCompose([{ dir }]); - const { manifest } = readManifest([{ dir }]); - const environmentByServiceParsed: Record< - string, - string - > = environmentByService ? JSON.parse(environmentByService) : {}; - - try { - // Build and upload - console.log(chalk.dim("\nBuilding and uploading package...")); - const buildResult = await buildHandler({ - dir, - provider: localIpfsApiUrl, - upload_to: "ipfs", - verbose: false - }); - - // Ensure test-integration environment is clean - console.log( - chalk.dim("\nCleaning test-integration environment before starting") - ); - await ensureDappnodeEnvironment({ - dappmanagerTestApi, - network: network as Network - }); - - // TODO: Do this for every releaseHash obtained - const { variant, releaseMultiHash } = buildResult[0]; - - if (!releaseMultiHash) - throw Error( - `Could not execute end-to-end tests. No releaseMultiHash found for variant ${variant}` - ); - - await executeEndToEndTests({ - dappmanagerTestApi, - releaseMultiHash, // TODO: Do this for every releaseHash obtained - manifest, - compose, - healthCheckUrl, - errorLogsTimeout, - environmentByService: environmentByServiceParsed, - network: network as Network - }); - } catch (e) { - throw Error(`Error on test-integration: ${e}`); - } finally { - // Ensure test-integration environment is cleaned - console.log( - chalk.dim("\nCleaning test-integration environment before exiting") - ); - await ensureDappnodeEnvironment({ - dappmanagerTestApi, - network: network as Network - }); - } -} diff --git a/src/commands/githubActions/endToEndTest/params.ts b/src/commands/githubActions/endToEndTest/params.ts deleted file mode 100644 index 2f876a50..00000000 --- a/src/commands/githubActions/endToEndTest/params.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Network } from "@dappnode/types"; -import { StakerConfigSet } from "./types.js"; - -export const localIpfsApiUrl = `http://172.33.1.5:5001`; -export const localDappmanagerTestApiUrl = `http://172.33.1.7:7000`; - -/** - * Get the staker config for the network∫ - * @param network - * @returns freezen object with the staker config for the network - */ -export const getStakerConfigByNetwork = ( - network: Network -): StakerConfigSet => { - return network === "mainnet" - ? stakerMainnetConfig - : network === "gnosis" - ? stakerGnosisConfig - : stakerPraterConfig; -}; - -export const getDefaultExecClient = (network: Network): string => { - return network === "mainnet" - ? "geth.dnp.dappnode.eth" - : network === "gnosis" - ? "nethermind-xdai.dnp.dappnode.eth" - : "goerli-besu.dnp.dappnode.eth"; -}; - -const stakerMainnetConfig: StakerConfigSet<"mainnet"> = Object.freeze({ - network: "mainnet", - feeRecipient: "0x0000000000000000000000000000000000000001", - executionClient: Object.freeze({ - dnpName: "geth.dnp.dappnode.eth", - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true - }), - consensusClient: Object.freeze({ - dnpName: "prysm.dnp.dappnode.eth", - useCheckpointSync: true, - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true - }), - enableWeb3signer: true, - mevBoost: Object.freeze({ - dnpName: "mev-boost.dnp.dappnode.eth", - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true, - relays: [ - "https://0xa7ab7a996c8584251c8f925da3170bdfd6ebc75d50f5ddc4050a6fdc77f2a3b5fce2cc750d0865e05d7228af97d69561@agnostic-relay.net", - "https://0xa1559ace749633b997cb3fdacffb890aeebdb0f5a3b6aaa7eeeaf1a38af0a8fe88b9e4b1f61f236d2e64d95733327a62@relay.ultrasound.money", - "https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net", - "https://0x8b5d2e73e2a3a55c6c87b8b6eb92e0149a125c852751db1422fa951e42a09b82c142c3ea98d0d9930b056a3bc9896b8f@bloxroute.max-profit.blxrbdn.com", - "https://0xad0a8bb54565c2211cee576363f3a347089d2f07cf72679d16911d740262694cadb62d7fd7483f27afd714ca0f1b9118@bloxroute.ethical.blxrbdn.com", - "https://0xb0b07cd0abef743db4260b0ed50619cf6ad4d82064cb4fbec9d3ec530f7c5e6793d9f286c4e082c0244ffb9f2658fe88@bloxroute.regulated.blxrbdn.com", - "https://0x9000009807ed12c1f08bf4e81c6da3ba8e3fc3d953898ce0102433094e5f22f21102ec057841fcb81978ed1ea0fa8246@builder-relay-mainnet.blocknative.com", - "https://0xb3ee7afcf27f1f1259ac1787876318c6584ee353097a50ed84f51a1f21a323b3736f271a895c7ce918c038e4265918be@relay.edennetwork.io", - "https://0x84e78cb2ad883861c9eeeb7d1b22a8e02332637448f84144e245d20dff1eb97d7abdde96d4e7f80934e5554e11915c56@relayooor.wtf" - ] - }) -}); - -const stakerPraterConfig: StakerConfigSet<"prater"> = Object.freeze({ - network: "prater", - feeRecipient: "0x0000000000000000000000000000000000000001", - executionClient: Object.freeze({ - dnpName: "goerli-besu.dnp.dappnode.eth", - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true - }), - consensusClient: Object.freeze({ - dnpName: "lighthouse-prater.dnp.dappnode.eth", - useCheckpointSync: true, - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true - }), - enableWeb3signer: true, - mevBoost: Object.freeze({ - dnpName: "mev-boost-goerli.dnp.dappnode.eth", - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true, - relays: [ - "https://0xafa4c6985aa049fb79dd37010438cfebeb0f2bd42b115b89dd678dab0670c1de38da0c4e9138c9290a398ecd9a0b3110@builder-relay-goerli.flashbots.net", - "https://0x821f2a65afb70e7f2e820a925a9b4c80a159620582c1766b1b09729fec178b11ea22abb3a51f07b288be815a1a2ff516@bloxroute.max-profit.builder.goerli.blxrbdn.com", - "https://0x8f7b17a74569b7a57e9bdafd2e159380759f5dc3ccbd4bf600414147e8c4e1dc6ebada83c0139ac15850eb6c975e82d0@builder-relay-goerli.blocknative.com", - "https://0xb1d229d9c21298a87846c7022ebeef277dfc321fe674fa45312e20b5b6c400bfde9383f801848d7837ed5fc449083a12@relay-goerli.edennetwork.io", - "https://0x8a72a5ec3e2909fff931c8b42c9e0e6c6e660ac48a98016777fc63a73316b3ffb5c622495106277f8dbcc17a06e92ca3@goerli-relay.securerpc.com/" - ] - }) -}); - -const stakerGnosisConfig: StakerConfigSet<"gnosis"> = Object.freeze({ - network: "gnosis", - feeRecipient: "0x0000000000000000000000000000000000000001", - executionClient: Object.freeze({ - dnpName: "nethermind-xdai.dnp.dappnode.eth", - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true - }), - consensusClient: Object.freeze({ - dnpName: "teku-gnosis.dnp.dappnode.eth", - useCheckpointSync: true, - status: "ok", - avatarUrl: "", - isInstalled: true, - isRunning: true, - isSelected: true, - isUpdated: true - }), - enableWeb3signer: true -}); - -const corePackages = [ - "dappmanager.dnp.dappnode.eth", - "ipfs.dnp.dappnode.eth", - "vpn.dnp.dappnode.eth", - "bind.dnp.dappnode.eth", - "core.dnp.dappnode.eth", - "https.dnp.dappnode.eth", - "wifi.dnp.dappnode.eth", - "wireguard.dnp.dappnode.eth" -] as const; - -export const nonStakerPackagesSetup = [ - "dms.dnp.dappnode.eth", - "dappnode-exporter.dnp.dappnode.eth", - "ethical-metrics.dnp.dappnode.eth" -] as const; - -const stakerPkgsToKeep = (network: Network): string[] => { - return network === "mainnet" - ? [ - "geth.dnp.dappnode.eth", - "prysm.dnp.dappnode.eth", - "mev-boost.dnp.dappnode.eth", - "web3signer.dnp.dappnode.eth" - ] - : network === "gnosis" - ? [ - "nethermind-xdai.dnp.dappnode.eth", - "teku-gnosis.dnp.dappnode.eth", - "web3signer-gnosis.dnp.dappnode.eth" - ] - : [ - "goerli-besu.dnp.dappnode.eth", - "lighthouse-prater.dnp.dappnode.eth", - "mev-boost-goerli.dnp.dappnode.eth", - "web3signer-prater.dnp.dappnode.eth" - ]; -}; - -export const packagesToKeep = (network: Network | undefined): string[] => - network - ? [...corePackages, ...nonStakerPackagesSetup, ...stakerPkgsToKeep(network)] - : [...corePackages, ...nonStakerPackagesSetup]; diff --git a/src/commands/githubActions/endToEndTest/testCheckers.ts b/src/commands/githubActions/endToEndTest/testCheckers.ts deleted file mode 100644 index 1e195c23..00000000 --- a/src/commands/githubActions/endToEndTest/testCheckers.ts +++ /dev/null @@ -1,171 +0,0 @@ -import chalk from "chalk"; -import Docker from "dockerode"; -import got from "got"; -import { ValidatorData } from "./types.js"; -import { Compose, Network, getContainerName } from "@dappnode/types"; - -export async function executeTestCheckers({ - dnpName, - compose, - errorLogsTimeout, - healthCheckUrl, - network, - executeProofOfAttestation -}: { - dnpName: string; - compose: Compose; - errorLogsTimeout: number; - healthCheckUrl?: string; - network?: Network; - executeProofOfAttestation: boolean; -}): Promise { - const errors: Error[] = []; - const docker = new Docker(); - for (const service of Object.keys(compose.services)) { - const containerName = getContainerName({ - dnpName, - serviceName: service, - isCore: false - }); - await ensureContainerStatus(containerName, docker) - .then(() => - console.log(chalk.green(` ✓ Container ${containerName} is running`)) - ) - .catch(e => errors.push(e)); - await ensureNoErrorLogs(containerName, docker, errorLogsTimeout) - .then(() => - console.log(chalk.green(` ✓ No error logs found in ${containerName}`)) - ) - .catch(e => errors.push(e)); - } - if (healthCheckUrl) - await ensureHealthCheck(healthCheckUrl) - .then(() => - console.log(chalk.green(` ✓ Healthcheck endpoint returned 200`)) - ) - .catch(e => errors.push(e)); - - if (executeProofOfAttestation) { - await attestanceProof(network) - .then(() => console.log(chalk.green(` ✓ Executing attestation proof`))) - .catch(e => errors.push(e)); - } - - // Throw aggregated error if any - if (errors.length > 0) { - const errorMessages = errors.map(e => e.message).join("\n"); - throw Error(errorMessages); - } -} - -async function ensureContainerStatus( - containerName: string, - docker: Docker -): Promise { - for (let i = 0; i < 10; i++) { - const container = await docker.getContainer(containerName).inspect(); - if (container.State.Status !== "running") { - const errorMessage = `Container ${containerName} is not running. Status: ${container.State.Status}`; - console.error(chalk.red(` x ${errorMessage}`)); - throw Error(errorMessage); - } - await new Promise(resolve => setTimeout(resolve, 1000)); - } -} - -async function ensureHealthCheck(healthCheckUrl: string): Promise { - const res = await got(healthCheckUrl).catch(e => { - const errorMessage = `Healthcheck endpoint returned ${e.message}`; - console.error(chalk.red(` x ${errorMessage}`)); - throw Error(errorMessage); - }); - if (res.statusCode < 200 || res.statusCode > 299) { - const errorMessage = `HTTP code !== 2XX. Healthcheck endpoint returned ${res.statusCode}`; - console.error(chalk.red(` x ${errorMessage}`)); - throw Error(errorMessage); - } -} - -async function ensureNoErrorLogs( - containerName: string, - docker: Docker, - errorLogsTimeout: number -): Promise { - // maximum number of seconds to wait for the container to be running smoothly and check its logs are 120 seconds - errorLogsTimeout > 120 && (errorLogsTimeout = 120); - - // Wait the timeout to ensure that no error logs are produced - await new Promise(resolve => setTimeout(resolve, errorLogsTimeout * 1000)); - - const logs = ( - await docker.getContainer(containerName).logs({ - follow: false, - stdout: true, - stderr: true, - timestamps: true - }) - ).toString(); - - // collect all the error logs print them and throw an error if any - const errorRegex = /.*\s+error.*/gi; - const errorLogs = logs.match(errorRegex); - - if (errorLogs) { - const errorMessage = `Error logs found in ${containerName}`; - console.error(chalk.red(` x ${errorMessage}`)); - errorLogs.forEach(errorLog => console.error(chalk.red(` ${errorLog}`))); - throw Error(errorMessage); - } -} - -/** - * Test that the validators are attesting after doppelganger protection + 18 minutes max. - */ -export async function attestanceProof(network?: Network): Promise { - if (!process.env.VALIDATOR_INDEX) - throw new Error(`Validator index is nullish`); - else if (!network) throw new Error(`Network is nullish`); - - // Wait for doppleganger protection to end, set to 15 min - console.log(chalk.grey(` - Waiting for doppleganger protection to end`)); - await new Promise(resolve => setTimeout(resolve, 15 * 60 * 1000)); - const endpoint = - network === "prater" - ? `https://prater.beaconcha.in/api/v1/validator/${process.env.VALIDATOR_INDEX}` - : network === "gnosis" - ? `https://beacon.gnosischain.com/api/v1/validator/${process.env.VALIDATOR_INDEX}` - : `https://beaconcha.in/api/v1/validator/${process.env.VALIDATOR_INDEX}`; - - for (let i = 1; i <= 9; i++) { - console.log(chalk.grey(` - Checking if validator is active #${i}`)); - const response = await got(endpoint, { - headers: { - Accept: "application/json" - }, - responseType: "json" - }); - - // Check if the response is valid - if (response.statusCode < 200 || response.statusCode >= 300) - throw new Error( - `Error while fetching validator data. Beaconcha.in returned ${response.statusCode}` - ); - - const data = response.body as ValidatorData; - if (data.data.status === "active_online") { - console.log(chalk.green(` ✓ Validator is active`)); - return; // Exit the loop if the validator is active - } else { - console.log( - chalk.yellow( - ` - Validator is not active yet. Retrying (minutes passed: ${i * 2})` - ) - ); - } - await new Promise(resolve => setTimeout(resolve, 2 * 60 * 1000)); // Wait for 2 minutes after each iteration - } - // This will only be reached if the validator is not active after 20 minutes - const errorMessage = `Validator is not active after 20 minutes`; - console.error(chalk.red(` x ${errorMessage}`)); - throw new Error(errorMessage); -} diff --git a/src/commands/githubActions/endToEndTest/types.ts b/src/commands/githubActions/endToEndTest/types.ts deleted file mode 100644 index c7344b06..00000000 --- a/src/commands/githubActions/endToEndTest/types.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { - Manifest, - Compose, - SetupWizard, - PackageBackup, - ConsensusClientMainnet, - executionClientsMainnet, - executionClientsPrater, - executionClientsGnosis, - ConsensusClientGnosis, - ConsensusClientPrater, - ExecutionClientGnosis, - ExecutionClientMainnet, - ExecutionClientPrater, - MevBoostMainnet, - MevBoostPrater, - SignerGnosis, - SignerMainnet, - SignerPrater, - Network, - consensusClientsMainnet, - consensusClientsGnosis, - consensusClientsPrater -} from "@dappnode/types"; - -export const executionPkgs = [ - ...executionClientsMainnet, - ...executionClientsPrater, - ...executionClientsGnosis -] as const; -export type ExecutionPkg = typeof executionPkgs[number]; - -export const consensusPkgs = [ - ...consensusClientsMainnet, - ...consensusClientsPrater, - ...consensusClientsGnosis -] as const; -export type ConsensusPkg = typeof consensusPkgs[number]; - -// TODO: below types are duplicated from dappmanager - -// stakers items -export type StakerType = "execution" | "consensus" | "signer" | "mev-boost"; -export type ExecutionClient = T extends "mainnet" - ? ExecutionClientMainnet - : T extends "gnosis" - ? ExecutionClientGnosis - : T extends "prater" - ? ExecutionClientPrater - : never; -export type ConsensusClient = T extends "mainnet" - ? ConsensusClientMainnet - : T extends "gnosis" - ? ConsensusClientGnosis - : T extends "prater" - ? ConsensusClientPrater - : never; -export type Signer = T extends "mainnet" - ? SignerMainnet - : T extends "gnosis" - ? SignerGnosis - : T extends "prater" - ? SignerPrater - : never; -export type MevBoost = T extends "mainnet" - ? MevBoostMainnet - : T extends "prater" - ? MevBoostPrater - : never; -export type StakerItem = - | StakerItemOk - | StakerItemError; -interface StakerExecution { - dnpName: ExecutionClient; -} -interface StakerConsensus { - dnpName: ConsensusClient; - useCheckpointSync?: boolean; -} -interface StakerSigner { - dnpName: Signer; -} -interface StakerMevBoost { - dnpName: MevBoost; - relays?: string[]; -} - -export interface StakerConfigGet { - executionClients: StakerItem[]; - consensusClients: StakerItem[]; - web3Signer: StakerItem; - mevBoost: StakerItem; - feeRecipient: string; -} - -export interface StakerConfigSet { - network: T; - feeRecipient: string; - executionClient: StakerItemOk; // Modify original type to be mandatory - consensusClient: StakerItemOk; // Modify original type to be mandatory - mevBoost?: StakerItemOk; - enableWeb3signer: boolean; // Modify original type to be mandatory -} - -export interface PackageRelease { - dnpName: string; - reqVersion: string; - semVersion: string; - imageFile: DistributedFile; - avatarFile?: DistributedFile; - metadata: Manifest; - compose: Compose; - warnings: ReleaseWarnings; - origin?: string; - isCore: boolean; - /** Release is from safe origin OR has trusted signature */ - signedSafe: boolean; - signatureStatus: ReleaseSignatureStatus; -} - -export type StakerItemData = Pick< - PackageRelease, - | "dnpName" - | "reqVersion" - | "semVersion" - | "imageFile" - | "avatarFile" - | "metadata" - | "warnings" - | "origin" - | "signedSafe" ->; - -type StakerItemBasic< - T extends Network, - P extends StakerType -> = P extends "execution" - ? StakerExecution - : P extends "consensus" - ? StakerConsensus - : P extends "signer" - ? StakerSigner - : P extends "mev-boost" - ? StakerMevBoost - : never; - -export type StakerItemError = { - status: "error"; - error: string; -} & StakerItemBasic; - -export type StakerItemOk = { - status: "ok"; - avatarUrl: string; - isInstalled: boolean; - isUpdated: boolean; - isRunning: boolean; - data?: StakerItemData; - isSelected: boolean; -} & StakerItemBasic; - -// SIGNATURES - -export type DistributedFileSource = "ipfs" | "swarm"; -export interface DistributedFile { - hash: string; - source: DistributedFileSource; - size: number; -} - -interface ReleaseWarnings { - /** - * If a core package does not come from the DAppNode Package APM registry - */ - coreFromForeignRegistry?: boolean; - /** - * If the requested name does not match the manifest name - */ - requestNameMismatch?: boolean; -} - -export declare enum ReleaseSignatureStatusCode { - notSigned = "notSigned", - signedByKnownKey = "signedByKnownKey", - signedByUnknownKey = "signedByUnknownKey" -} - -export type ReleaseSignatureStatus = - | { - status: ReleaseSignatureStatusCode.notSigned; - } - | { - status: ReleaseSignatureStatusCode.signedByKnownKey; - keyName: string; - } - | { - status: ReleaseSignatureStatusCode.signedByUnknownKey; - signatureProtocol: string; - key: string; - }; - -// PACKAGES - -export interface InstalledPackageDataApiReturn extends InstalledPackageData { - updateAvailable: UpdateAvailable | null; -} - -export interface UpdateAvailable { - newVersion: string; - upstreamVersion?: string; -} - -export type InstalledPackageData = Pick< - PackageContainer, - | "dnpName" - | "instanceName" - | "version" - | "isDnp" - | "isCore" - | "dependencies" - | "avatarUrl" - | "origin" - | "chain" - | "domainAlias" - | "canBeFullnode" -> & { - containers: PackageContainer[]; -}; - -export interface PackageContainer { - /** - * Docker container ID - * ``` - * "3edc051920c61e02ff9c42cf35caf4f48f693d65f44d6652de29e9024f051405" - * ``` - */ - containerId: string; - /** - * Docker container name - * ``` - * "DAppNodeCore-mypackage.dnp.dappnode.eth" - * ``` - */ - containerName: string; - /** - * ENS domain name of this container's package - * ``` - * "mypackage.dnp.dappnode.eth" - * ``` - */ - dnpName: string; - /** - * Docker compose service name of this container, as declared in its package docker-compose - * ``` - * "frontend" - * ``` - */ - serviceName: string; - /** - * Name given by the user when installing an instance of a package - * ``` - * "my-package-test-instance" - * ``` - */ - instanceName: string; - /** - * Semantic version of this container's package - * ``` - * "0.1.0" - * ``` - */ - version: string; - created: number; - image: string; - ip?: string; - state: any; - running: boolean; - exitCode: number | null; - ports: any[]; - volumes: any[]; - networks: { - name: string; - ip: string; - }[]; - isDnp: boolean; - isCore: boolean; - defaultEnvironment?: any; - defaultPorts?: any[]; - defaultVolumes?: any[]; - dependencies: any; - avatarUrl: string; - origin?: string; - chain?: any; - domainAlias?: string[]; - canBeFullnode?: boolean; - isMain?: boolean; - dockerTimeout?: number; -} - -export interface InstalledPackageDetailData extends InstalledPackageData { - setupWizard?: SetupWizard; - userSettings?: UserSettings; - gettingStarted?: string; - gettingStartedShow?: boolean; - backup?: PackageBackup[]; - /** Checks if there are volumes to be removed on this DNP */ - areThereVolumesToRemove: boolean; - dependantsOf: string[]; - updateAvailable: UpdateAvailable | null; - notRemovable: boolean; - manifest?: Manifest; - /** Arbitrary data sent by the package */ - packageSentData: Record; -} - -export interface PackageToInstall { - dnpName: string; - version: string; - userSettings?: { - environment?: Record; - ports?: Record; - volumes?: Record; - }; -} - -export interface UserSettings { - environment?: { - [serviceName: string]: { - /** - * ```js - * { MODE: "VALUE_SET_BEFORE" } - * ``` - */ - [envName: string]: string; - }; - }; - portMappings?: { - [serviceName: string]: { - /** - * ```js - * { "8443": "8443", "8443/udp": "8443" }, - * ``` - */ - [containerPortAndType: string]: string; - }; - }; - namedVolumeMountpoints?: { - /** - * ```js - * { data: "/media/usb0" } - * ``` - */ - [volumeName: string]: string; - }; - allNamedVolumeMountpoint?: string; - fileUploads?: { - [serviceName: string]: { - /** - * ```js - * { "/usr/src/app/config.json": "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D" } - * ``` - */ - [containerPath: string]: string; - }; - }; - domainAlias?: string[]; - legacyBindVolumes?: { - [serviceName: string]: { - [volumeName: string]: string; - }; - }; -} - -// IPFS - -export enum IpfsClientTarget { - local = "local", - remote = "remote" -} -export interface IpfsRepository { - ipfsClientTarget: IpfsClientTarget; - ipfsGateway: string; -} - -export interface ValidatorData { - status: string; - data: { - status: string; - }; -} diff --git a/src/commands/githubActions/endToEndTest/utils.ts b/src/commands/githubActions/endToEndTest/utils.ts deleted file mode 100644 index c68add70..00000000 --- a/src/commands/githubActions/endToEndTest/utils.ts +++ /dev/null @@ -1,92 +0,0 @@ -import chalk from "chalk"; -import { Network, stakerPkgs } from "@dappnode/types"; -import { DappmanagerTestApi } from "./dappmanagerTestApi.js"; -import { getDefaultExecClient, getStakerConfigByNetwork } from "./params.js"; -import { cloneDeep } from "lodash-es"; -import { Manifest } from "@dappnode/types"; -import { - ConsensusPkg, - ExecutionPkg, - StakerConfigSet, - consensusPkgs, - executionPkgs -} from "./types.js"; - -export function printPackageMetadata( - manifest: Manifest, - releaseMultiHash: string -): void { - console.log( - chalk.dim( - ` -Package metadata: - - Package: ${manifest.name} - - Version: ${manifest.version} - - Upstream version: ${manifest.upstreamVersion ?? "N/A"} - - Release: ${releaseMultiHash} - ` - ) - ); -} - -export function getIsStakerPkg(dnpName: string): boolean { - if (!dnpName) throw Error("dnpName must be defined"); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return stakerPkgs.includes(dnpName as any); -} - -export async function setStakerConfig( - dnpName: string, - dappmanagerTestApi: DappmanagerTestApi, - network: Network, - removeConsensusVolumes?: boolean -): Promise { - const mutableStakerConfig: StakerConfigSet = cloneDeep( - getStakerConfigByNetwork(network) - ); - - if (executionPkgs.find(executionClient => executionClient === dnpName)) - mutableStakerConfig.executionClient.dnpName = dnpName as ExecutionPkg; - else if (consensusPkgs.find(consensusClient => consensusClient === dnpName)) - mutableStakerConfig.consensusClient.dnpName = dnpName as ConsensusPkg; - - if (removeConsensusVolumes) - await dappmanagerTestApi.packageRestartVolumes({ - dnpName: mutableStakerConfig.consensusClient.dnpName - }); - - await dappmanagerTestApi.stakerConfigSet(mutableStakerConfig); -} - -/** - * Get should executes proof of attestation - * Skip proof of attestation if: - * - network is undefined - * - running in test environment - * - im not a staker package - * - im an execution package but not the one specified in the staker config - * @param param0 - * @returns - */ -export function getExecuteProofOfAttestation({ - network, - dnpName, - isStakerPkg -}: { - network?: Network; - dnpName: string; - isStakerPkg: boolean; -}): boolean { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const isExecutionPkg = executionPkgs.includes(dnpName as any); - if ( - !network || - network === "mainnet" || - process.env.TEST || - !isStakerPkg || - (isExecutionPkg && dnpName !== getDefaultExecClient(network)) - ) - return false; - - return true; -} diff --git a/src/commands/githubActions/index.ts b/src/commands/githubActions/index.ts index 532cc6f0..a16dd8ec 100644 --- a/src/commands/githubActions/index.ts +++ b/src/commands/githubActions/index.ts @@ -2,7 +2,6 @@ import { CommandModule } from "yargs"; import { CliGlobalOptions } from "../../types.js"; import { gaBuild } from "./build/index.js"; import { gaBumpUpstream } from "./bumpUpstream/index.js"; -import { endToEndTest } from "./endToEndTest/index.js"; export const githubActions: CommandModule< CliGlobalOptions, @@ -12,11 +11,7 @@ export const githubActions: CommandModule< describe: "Github actions tooling to be run in CI. Uses a specific set of options for internal DAppNode use. Caution: options may change without notice.", builder: yargs => - yargs - .command(endToEndTest) - .command(gaBuild) - .command(gaBumpUpstream) - .demandCommand(1), + yargs.command(gaBuild).command(gaBumpUpstream).demandCommand(1), handler: async (): Promise => { throw Error("Requires 1 command"); } diff --git a/src/params.ts b/src/params.ts index f8330349..3b393ffe 100644 --- a/src/params.ts +++ b/src/params.ts @@ -3,8 +3,8 @@ import { ManifestFormat } from "./files/manifest/types.js"; export * from "./files/compose/params.js"; -export class CliError extends Error { } -export class YargsError extends Error { } +export class CliError extends Error {} +export class YargsError extends Error {} // Github Actions params @@ -43,5 +43,6 @@ export const releaseFilesDefaultNames: { disclaimer: "disclaimer.md", gettingStarted: "getting-started.md", grafanaDashboards: "grafana-dashboard.json", - prometheusTargets: "prometheus-targets.json" + prometheusTargets: "prometheus-targets.json", + notifications: "notifications.yaml" } as const); From 241f07d7b5a54f926999ba7a27de5e939ab7108d Mon Sep 17 00:00:00 2001 From: Pablo Mendez Date: Tue, 4 Mar 2025 12:19:57 +0100 Subject: [PATCH 2/2] remove e2e test files --- .../gaEndToEndTest/endToEndTest.test.ts | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 test/commands/gaEndToEndTest/endToEndTest.test.ts diff --git a/test/commands/gaEndToEndTest/endToEndTest.test.ts b/test/commands/gaEndToEndTest/endToEndTest.test.ts deleted file mode 100644 index e4016154..00000000 --- a/test/commands/gaEndToEndTest/endToEndTest.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { expect } from "chai"; -import { gaTestEndToEndHandler } from "../../../src/commands/githubActions/endToEndTest/index.js"; -import { attestanceProof } from "../../../src/commands/githubActions/endToEndTest/testCheckers.js"; -import { testDir, cleanTestDir } from "../../testUtils.js"; -import { initHandler } from "../../../src/commands/init/handler.js"; - -// This test must be run in a dappnode environment otherwise it will fail -describe.skip("command / gaEndToEndTest", function () { - // tests could take a while, set timeout to 20 minutes - this.timeout(1200 * 1000); - - before(async () => { - cleanTestDir(); - await initHandler({ dir: testDir, yes: true, force: true }); - }); - - it("should execute end to end tests on a real dappnode environment", async () => { - await gaTestEndToEndHandler({ - dir: testDir, - // healthCheckUrl: "http://dappnodesdk.public.dappnode", - errorLogsTimeout: 30 - }); - expect(true).to.equal(true); - }); - - it("validator shouldnt return any errors while trying to attest", async () => { - expect(await attestanceProof("prater")).to.not.throw; - }); -});