diff --git a/clients/js/packages/client/__tests__/client.verify.test.ts b/clients/js/packages/client/__tests__/client.verify.test.ts index 0110c7171..e925fedc0 100644 --- a/clients/js/packages/client/__tests__/client.verify.test.ts +++ b/clients/js/packages/client/__tests__/client.verify.test.ts @@ -221,4 +221,124 @@ describe('StarshipClient verify', () => { await client.verify(); expectClient(ctx, 0); }); + + it('should handle ingress verification', async () => { + const { client, ctx } = createClient(); + client.dependencies.forEach((dep) => (dep.installed = true)); + + const configWithIngress = { + ...config.config, + ingress: { + enabled: true, + host: 'test.example.com', + type: 'nginx' + } + }; + + client.setConfig(configWithIngress); + + // Mock ingress endpoints + mockedAxios.get.mockImplementation((url) => { + // Chain REST endpoints + if (url.includes('rest.osmosis-1-genesis.test.example.com')) { + return Promise.resolve({ + status: 200, + data: { supply: [] } + }); + } + if (url.includes('rest.cosmos-2-genesis.test.example.com')) { + return Promise.resolve({ + status: 200, + data: { supply: [] } + }); + } + + // Chain RPC endpoints + if (url.includes('rpc.osmosis-1-genesis.test.example.com')) { + return Promise.resolve({ + status: 200, + data: { result: { node_info: {} } } + }); + } + if (url.includes('rpc.cosmos-2-genesis.test.example.com')) { + return Promise.resolve({ + status: 200, + data: { result: { node_info: {} } } + }); + } + + // Registry endpoint + if (url.includes('registry.test.example.com')) { + return Promise.resolve({ + status: 200, + data: { chains: [] } + }); + } + + // Explorer endpoint + if (url.includes('explorer.test.example.com')) { + return Promise.resolve({ + status: 200, + data: 'Ping Dashboard' + }); + } + + // Throw error for unhandled URLs + throw new Error(`Unhandled URL in mock: ${url}`); + }); + + await client.verify(); + expectClient(ctx, 0); + }); + + it('should handle ingress verification failure', async () => { + const { client, ctx } = createClient(); + client.dependencies.forEach((dep) => (dep.installed = true)); + + const configWithIngress = { + ...config.config, + ingress: { + enabled: true, + host: 'test.example.com', + type: 'nginx' + } + }; + + client.setConfig(configWithIngress); + + // Mock ingress endpoints with failures + mockedAxios.get.mockImplementation((url) => { + // Chain REST endpoints + if (url.includes('rest.osmosis-1-genesis.test.example.com')) { + return Promise.reject(new Error('Connection refused')); + } + if (url.includes('rest.cosmos-2-genesis.test.example.com')) { + return Promise.reject(new Error('Connection refused')); + } + + // Chain RPC endpoints + if (url.includes('rpc.osmosis-1-genesis.test.example.com')) { + return Promise.reject(new Error('Connection refused')); + } + if (url.includes('rpc.cosmos-2-genesis.test.example.com')) { + return Promise.reject(new Error('Connection refused')); + } + + // Registry endpoint + if (url.includes('registry.test.example.com')) { + return Promise.reject(new Error('Connection refused')); + } + + // Explorer endpoint + if (url.includes('explorer.test.example.com')) { + return Promise.reject(new Error('Connection refused')); + } + + // Throw error for unhandled URLs + throw new Error(`Unhandled URL in mock: ${url}`); + }); + + await client.verify(); + expectClient(ctx, 1); + }); }); diff --git a/clients/js/packages/client/src/config.ts b/clients/js/packages/client/src/config.ts index 5287cd7ca..f8975a8de 100644 --- a/clients/js/packages/client/src/config.ts +++ b/clients/js/packages/client/src/config.ts @@ -9,6 +9,8 @@ export interface Ports { grafana?: number; cometmock?: number; ws?: number; + http?: number; + [key: string]: number | undefined; } export interface Resources { @@ -134,12 +136,11 @@ export interface Monitoring { export interface Ingress { enabled: boolean; + host: string; type: string; - host?: string; certManager?: { - issuer?: string; + issuer: string; }; - resources?: Resources; } export interface Images { diff --git a/clients/js/packages/client/src/verifiers/chain.ts b/clients/js/packages/client/src/verifiers/chain.ts index daf2cce13..2e35b3047 100644 --- a/clients/js/packages/client/src/verifiers/chain.ts +++ b/clients/js/packages/client/src/verifiers/chain.ts @@ -1,19 +1,24 @@ import axios from 'axios'; -import { Chain } from '../config'; -import { handleAxiosError } from '../utils'; -import { ChainVerifierSet, VerificationResult } from './types'; +import { Chain, StarshipConfig } from '../config'; +import { + ChainVerifierSet, + handleAxiosError, + VerificationResult +} from './types'; +import { getServiceUrl } from './utils'; export const verifyChainRest = async ( - chain: Chain + chain: Chain, + config: StarshipConfig ): Promise => { - const port = chain.ports?.rest; const result: VerificationResult = { service: `chain-${chain.id}`, endpoint: 'rest', status: 'failure' }; + const port = chain.ports?.rest; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -21,40 +26,53 @@ export const verifyChainRest = async ( } try { - const response = await axios.get( - `http://localhost:${port}/cosmos/bank/v1beta1/supply` + const { baseUrl, path } = getServiceUrl( + config, + 'chain', + 'rest', + String(chain.id) ); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; if (response.status !== 200) { result.error = 'Failed to get chain supply'; return result; } - if (response.data.supply[0].amount > 0) { + + if (response.data.supply) { result.status = 'success'; - result.message = 'Chain supply is greater than 0'; + result.message = 'Chain REST is working'; return result; } result.status = 'failure'; - result.error = 'Chain supply not confirmed'; - result.details = response.data; + result.error = 'Invalid supply response'; return result; } catch (error) { - result.error = handleAxiosError(error); + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Chain REST service is not running'; + } else { + result.error = handleAxiosError(error); + } + } else { + result.error = 'Unknown error occurred'; + } return result; } }; export const verifyChainRpc = async ( - chain: Chain + chain: Chain, + config: StarshipConfig ): Promise => { - const port = chain.ports?.rpc; const result: VerificationResult = { service: `chain-${chain.id}`, endpoint: 'rpc', status: 'failure' }; + const port = chain.ports?.rpc; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -62,44 +80,53 @@ export const verifyChainRpc = async ( } try { - const response = await axios.get(`http://localhost:${port}/status`); + const { baseUrl, path } = getServiceUrl( + config, + 'chain', + 'rpc', + String(chain.id) + ); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; if (response.status !== 200) { - result.error = 'Failed to get chain node info'; + result.error = 'Failed to get chain status'; return result; } - const blockHeight = Number( - response.data.result?.sync_info?.latest_block_height || - response.data.result?.SyncInfo?.latest_block_height - ); - - if (blockHeight > 0) { + if (response.data.result?.sync_info) { result.status = 'success'; - result.message = 'Chain is synced'; + result.message = 'Chain RPC is working'; return result; } result.status = 'failure'; - result.error = 'Block height is 0'; - result.details = response.data; + result.error = 'Invalid status response'; return result; } catch (error) { - result.error = handleAxiosError(error); + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Chain RPC service is not running'; + } else { + result.error = handleAxiosError(error); + } + } else { + result.error = 'Unknown error occurred'; + } return result; } }; export const verifyChainFaucet = async ( - chain: Chain + chain: Chain, + config: StarshipConfig ): Promise => { - const port = chain.ports?.faucet; const result: VerificationResult = { service: `chain-${chain.id}`, endpoint: 'faucet', status: 'failure' }; + const port = chain.ports?.faucet; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -107,44 +134,53 @@ export const verifyChainFaucet = async ( } try { - const response = await axios.get(`http://localhost:${port}/status`); + const { baseUrl, path } = getServiceUrl( + config, + 'chain', + 'faucet', + String(chain.id) + ); + const response = await axios.get(`${baseUrl}${path}`); + result.details = response.data; if (response.status !== 200) { - result.error = 'Failed to get chain node info'; - return result; - } - - if (!response.data.chainId) { - result.error = 'Invalid response: chainId not found'; - result.details = response.data; + result.error = 'Failed to get faucet status'; return result; } - if (response.data.chainId === chain.id) { + if (response.data.chainId) { result.status = 'success'; result.message = 'Chain faucet is working'; return result; } result.status = 'failure'; - result.error = `Chain ID mismatch: expected ${chain.id}, got ${response.data.chainId}`; - result.details = response.data; + result.error = 'Invalid faucet response'; return result; } catch (error) { - result.error = handleAxiosError(error); + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Faucet service is not running'; + } else { + result.error = handleAxiosError(error); + } + } else { + result.error = 'Unknown error occurred'; + } return result; } }; export const verifyChainExposer = async ( - chain: Chain + chain: Chain, + config: StarshipConfig ): Promise => { - const port = chain.ports?.exposer; const result: VerificationResult = { service: `chain-${chain.id}`, endpoint: 'exposer', status: 'failure' }; + const port = chain.ports?.exposer; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -152,7 +188,13 @@ export const verifyChainExposer = async ( } try { - const response = await axios.get(`http://localhost:${port}/node_id`); + const { baseUrl, path } = getServiceUrl( + config, + 'chain', + 'exposer', + String(chain.id) + ); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; if (response.status !== 200) { result.error = 'Failed to get chain node id'; diff --git a/clients/js/packages/client/src/verifiers/explorer.ts b/clients/js/packages/client/src/verifiers/explorer.ts index 838547a63..5ab1dd9b5 100644 --- a/clients/js/packages/client/src/verifiers/explorer.ts +++ b/clients/js/packages/client/src/verifiers/explorer.ts @@ -1,19 +1,20 @@ import axios from 'axios'; -import { Explorer } from '../config'; -import { handleAxiosError } from '../utils'; -import { VerificationResult } from './types'; +import { Explorer, StarshipConfig } from '../config'; +import { handleAxiosError, VerificationResult } from './types'; +import { getServiceUrl } from './utils'; export const verifyExplorerRest = async ( - explorer: Explorer + explorer: Explorer, + config: StarshipConfig ): Promise => { - const port = explorer.ports?.rest; const result: VerificationResult = { service: 'explorer', - endpoint: 'rest', + endpoint: 'http', status: 'failure' }; + const port = explorer.ports?.http; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -21,13 +22,9 @@ export const verifyExplorerRest = async ( } try { - const response = await axios.get(`http://localhost:${port}`, { - headers: { - Accept: 'text/html' - } - }); + const { baseUrl, path } = getServiceUrl(config, 'explorer', 'http'); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; - if (response.status !== 200) { result.error = 'Failed to get explorer status'; return result; @@ -40,10 +37,18 @@ export const verifyExplorerRest = async ( } result.status = 'failure'; - result.error = 'Explorer is not working'; + result.error = 'Invalid explorer response'; return result; } catch (error) { - result.error = handleAxiosError(error); + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Explorer service is not running'; + } else { + result.error = handleAxiosError(error); + } + } else { + result.error = 'Unknown error occurred'; + } return result; } }; diff --git a/clients/js/packages/client/src/verifiers/index.ts b/clients/js/packages/client/src/verifiers/index.ts index e81b1a8a9..2773105d2 100644 --- a/clients/js/packages/client/src/verifiers/index.ts +++ b/clients/js/packages/client/src/verifiers/index.ts @@ -1,5 +1,6 @@ import { chainVerifiers } from './chain'; import { verifyExplorerRest } from './explorer'; +import { verifyIngress } from './ingress'; import { verifyRegistryRest } from './registry'; import { relayerVerifiers } from './relayer'; import { VerificationFunction, VerificationResult } from './types'; @@ -14,7 +15,7 @@ export const verifyChains: VerificationFunction = async (config) => { for (const chain of config.chains) { const verifierSet = chainVerifiers[chain.name] || chainVerifiers.default; for (const [, verifier] of Object.entries(verifierSet)) { - const result = await verifier(chain); + const result = await verifier(chain, config); results.push(result); } } @@ -31,7 +32,7 @@ export const verifyRelayers: VerificationFunction = async (config) => { for (const relayer of config.relayers) { for (const [, verifier] of Object.entries(relayerVerifiers)) { - const result = await verifier(relayer); + const result = await verifier(relayer, config); results.push(result); } } @@ -47,7 +48,7 @@ export const verifyRegistry: VerificationFunction = async (config) => { } const registryResults = await Promise.all([ - verifyRegistryRest(config.registry) + verifyRegistryRest(config.registry, config) ]); results.push(...registryResults); @@ -61,7 +62,7 @@ export const verifyExplorer: VerificationFunction = async (config) => { return results; } - const explorerResult = await verifyExplorerRest(config.explorer); + const explorerResult = await verifyExplorerRest(config.explorer, config); results.push(explorerResult); return results; }; @@ -71,12 +72,14 @@ export const verify: VerificationFunction = async (config) => { const relayerResults = await verifyRelayers(config); const registryResults = await verifyRegistry(config); const explorerResults = await verifyExplorer(config); + const ingressResults = await verifyIngress(config); return [ ...chainResults, ...relayerResults, ...registryResults, - ...explorerResults + ...explorerResults, + ...ingressResults ]; }; diff --git a/clients/js/packages/client/src/verifiers/ingress.ts b/clients/js/packages/client/src/verifiers/ingress.ts new file mode 100644 index 000000000..40b66bfd2 --- /dev/null +++ b/clients/js/packages/client/src/verifiers/ingress.ts @@ -0,0 +1,154 @@ +import axios from 'axios'; + +import { Chain, Relayer, StarshipConfig } from '../config'; +import { handleAxiosError, VerificationResult } from './types'; +import { getServiceUrl } from './utils'; + +const verifyIngressEndpoint = async ( + url: string, + service: string, + endpoint: string +): Promise => { + const result: VerificationResult = { + service, + endpoint, + status: 'failure' + }; + + try { + const response = await axios.get(url); + if (response.status === 200) { + result.status = 'success'; + result.message = `${endpoint} endpoint is accessible`; + return result; + } + + result.error = `Failed to access ${endpoint} endpoint`; + return result; + } catch (error) { + result.error = handleAxiosError(error); + return result; + } +}; + +export const verifyChainIngress = async ( + chain: Chain, + config: StarshipConfig +): Promise => { + const results: VerificationResult[] = []; + + // Verify REST endpoint + if (chain.ports?.rest) { + const { baseUrl, path } = getServiceUrl(config, 'chain', 'rest', String(chain.id)); + results.push( + await verifyIngressEndpoint(`${baseUrl}${path}`, `chain-${chain.id}`, 'rest') + ); + } + + // Verify RPC endpoint + if (chain.ports?.rpc) { + const { baseUrl, path } = getServiceUrl(config, 'chain', 'rpc', String(chain.id)); + results.push( + await verifyIngressEndpoint(`${baseUrl}${path}`, `chain-${chain.id}`, 'rpc') + ); + } + + // Verify Faucet endpoint + if (chain.ports?.faucet) { + const { baseUrl, path } = getServiceUrl(config, 'chain', 'faucet', String(chain.id)); + results.push( + await verifyIngressEndpoint(`${baseUrl}${path}`, `chain-${chain.id}`, 'faucet') + ); + } + + // Verify Exposer endpoint + if (chain.ports?.exposer) { + const { baseUrl, path } = getServiceUrl(config, 'chain', 'exposer', String(chain.id)); + results.push( + await verifyIngressEndpoint(`${baseUrl}${path}`, `chain-${chain.id}`, 'exposer') + ); + } + + return results; +}; + +export const verifyRelayerIngress = async ( + relayer: Relayer, + config: StarshipConfig +): Promise => { + const results: VerificationResult[] = []; + + if (relayer.type === 'hermes') { + // Verify REST endpoint + if (relayer.ports?.rest) { + const { baseUrl, path } = getServiceUrl(config, 'relayer', 'rest', relayer.chains[0]); + results.push( + await verifyIngressEndpoint(`${baseUrl}${path}`, `relayer-${relayer.name}`, 'rest') + ); + } + + // Verify Exposer endpoint + if (relayer.ports?.exposer) { + const { baseUrl, path } = getServiceUrl(config, 'relayer', 'exposer', relayer.chains[0]); + results.push( + await verifyIngressEndpoint(`${baseUrl}${path}`, `relayer-${relayer.name}`, 'exposer') + ); + } + } + + return results; +}; + +export const verifyRegistryIngress = async ( + config: StarshipConfig +): Promise => { + const results: VerificationResult[] = []; + + if (config.registry?.enabled) { + const { baseUrl, path } = getServiceUrl(config, 'registry', 'rest'); + results.push(await verifyIngressEndpoint(`${baseUrl}${path}`, 'registry', 'rest')); + } + + return results; +}; + +export const verifyExplorerIngress = async ( + config: StarshipConfig +): Promise => { + const results: VerificationResult[] = []; + + if (config.explorer?.enabled) { + const { baseUrl, path } = getServiceUrl(config, 'explorer', 'http'); + results.push(await verifyIngressEndpoint(`${baseUrl}${path}`, 'explorer', 'http')); + } + + return results; +}; + +export const verifyIngress = async ( + config: StarshipConfig +): Promise => { + const results: VerificationResult[] = []; + + if (!config.ingress?.enabled || !config.ingress?.host) { + return results; + } + + // Verify chain ingress endpoints + for (const chain of config.chains) { + results.push(...(await verifyChainIngress(chain, config))); + } + + // Verify relayer ingress endpoints + for (const relayer of config.relayers || []) { + results.push(...(await verifyRelayerIngress(relayer, config))); + } + + // Verify registry ingress endpoint + results.push(...(await verifyRegistryIngress(config))); + + // Verify explorer ingress endpoint + results.push(...(await verifyExplorerIngress(config))); + + return results; +}; diff --git a/clients/js/packages/client/src/verifiers/registry.ts b/clients/js/packages/client/src/verifiers/registry.ts index 66f7e1a9f..0d71979d1 100644 --- a/clients/js/packages/client/src/verifiers/registry.ts +++ b/clients/js/packages/client/src/verifiers/registry.ts @@ -1,19 +1,20 @@ import axios from 'axios'; -import { Registry } from '../config'; -import { handleAxiosError } from '../utils'; -import { VerificationResult } from './types'; +import { Registry, StarshipConfig } from '../config'; +import { handleAxiosError, VerificationResult } from './types'; +import { getServiceUrl } from './utils'; export const verifyRegistryRest = async ( - registry: Registry + registry: Registry, + config: StarshipConfig ): Promise => { - const port = registry.ports?.rest; const result: VerificationResult = { service: 'registry', endpoint: 'rest', status: 'failure' }; + const port = registry.ports?.rest; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -21,7 +22,8 @@ export const verifyRegistryRest = async ( } try { - const response = await axios.get(`http://localhost:${port}/chains`); + const { baseUrl, path } = getServiceUrl(config, 'registry', 'rest'); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; if (response.status !== 200) { result.error = 'Failed to get registry chains'; @@ -38,7 +40,15 @@ export const verifyRegistryRest = async ( result.error = 'Registry is not working'; return result; } catch (error) { - result.error = handleAxiosError(error); + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Registry service is not running'; + } else { + result.error = handleAxiosError(error); + } + } else { + result.error = 'Unknown error occurred'; + } return result; } }; diff --git a/clients/js/packages/client/src/verifiers/relayer.ts b/clients/js/packages/client/src/verifiers/relayer.ts index eaabcca11..8c06e5aff 100644 --- a/clients/js/packages/client/src/verifiers/relayer.ts +++ b/clients/js/packages/client/src/verifiers/relayer.ts @@ -1,19 +1,24 @@ import axios from 'axios'; -import { Relayer } from '../config'; -import { handleAxiosError } from '../utils'; -import { RelayerVerifierSet, VerificationResult } from './types'; +import { Relayer, StarshipConfig } from '../config'; +import { + handleAxiosError, + RelayerVerifierSet, + VerificationResult +} from './types'; +import { getServiceUrl } from './utils'; export const verifyRelayerRest = async ( - relayer: Relayer + relayer: Relayer, + config: StarshipConfig ): Promise => { - const port = relayer.ports?.rest; const result: VerificationResult = { service: `relayer-${relayer.name}`, endpoint: 'rest', status: 'failure' }; + const port = relayer.ports?.rest; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; @@ -21,28 +26,32 @@ export const verifyRelayerRest = async ( } try { - const response = await axios.get(`http://localhost:${port}/status`); + const { baseUrl, path } = getServiceUrl( + config, + 'relayer', + 'rest', + relayer.chains[0] + ); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; if (response.status !== 200) { result.error = 'Failed to get relayer status'; return result; } - if (response.data.connections && response.data.connections.length > 0) { + if (response.data.status === 'ok') { result.status = 'success'; - result.message = 'Relayer has active connections'; + result.message = 'Relayer REST is working'; return result; } result.status = 'failure'; - result.error = 'No active connections found'; + result.error = 'Invalid relayer status'; return result; } catch (error) { if (axios.isAxiosError(error)) { - if (error.response?.status === 404) { - result.error = 'Relayer endpoint not found'; - } else if (error.code === 'ECONNREFUSED') { - result.error = 'Relayer service is not running'; + if (error.code === 'ECONNREFUSED') { + result.error = 'Relayer REST service is not running'; } else { result.error = handleAxiosError(error); } @@ -54,23 +63,35 @@ export const verifyRelayerRest = async ( }; export const verifyRelayerExposer = async ( - relayer: Relayer + relayer: Relayer, + config: StarshipConfig ): Promise => { - const port = relayer.ports?.exposer; const result: VerificationResult = { service: `relayer-${relayer.name}`, endpoint: 'exposer', status: 'failure' }; + const port = relayer.ports?.exposer; if (!port) { result.status = 'skipped'; result.error = 'Port not found'; return result; } + if (!relayer.chains || relayer.chains.length === 0) { + result.status = 'skipped'; + result.error = 'No chains configured for relayer'; + return result; + } try { - const response = await axios.get(`http://localhost:${port}/config`); + const { baseUrl, path } = getServiceUrl( + config, + 'relayer', + 'exposer', + relayer.chains[0] + ); + const response = await axios.get(`${baseUrl}${path}`); result.details = response.data; if (response.status !== 200) { result.error = 'Failed to get relayer config'; @@ -88,11 +109,7 @@ export const verifyRelayerExposer = async ( return result; } catch (error) { if (axios.isAxiosError(error)) { - if (error.response?.status === 404) { - result.error = 'Relayer exposer endpoint not found'; - } else if (error.response?.status === 500) { - result.error = 'Relayer exposer service error'; - } else if (error.code === 'ECONNREFUSED') { + if (error.code === 'ECONNREFUSED') { result.error = 'Relayer exposer service is not running'; } else { result.error = handleAxiosError(error); diff --git a/clients/js/packages/client/src/verifiers/types.ts b/clients/js/packages/client/src/verifiers/types.ts index 2ff530747..4dd8a630e 100644 --- a/clients/js/packages/client/src/verifiers/types.ts +++ b/clients/js/packages/client/src/verifiers/types.ts @@ -14,9 +14,25 @@ export type VerificationFunction = ( ) => Promise; export type ChainVerifierSet = { - [K in keyof Ports]?: (chain: Chain) => Promise; + [K in keyof Ports]?: ( + chain: Chain, + config: StarshipConfig + ) => Promise; }; export type RelayerVerifierSet = { - [K in keyof Ports]?: (relayer: Relayer) => Promise; + [K in keyof Ports]?: ( + relayer: Relayer, + config: StarshipConfig + ) => Promise; +}; + +export const handleAxiosError = (error: any): string => { + if (error.response) { + return `HTTP ${error.response.status}: ${error.response.data?.message || error.message}`; + } + if (error.request) { + return `No response received: ${error.message}`; + } + return error.message || 'Unknown error occurred'; }; diff --git a/clients/js/packages/client/src/verifiers/utils.ts b/clients/js/packages/client/src/verifiers/utils.ts new file mode 100644 index 000000000..8350cc72d --- /dev/null +++ b/clients/js/packages/client/src/verifiers/utils.ts @@ -0,0 +1,144 @@ +import { StarshipConfig } from '../config'; + +export interface ServiceUrl { + baseUrl: string; + path: string; +} + +export const getServiceUrl = ( + config: StarshipConfig, + service: string, + endpoint: string, + chainId?: string +): ServiceUrl => { + const useIngress = config.ingress?.enabled && config.ingress?.host; + let host = 'localhost'; + if (useIngress && config.ingress) { + host = config.ingress.host.replace('*.', ''); + } + + switch (service) { + case 'chain': { + if (!chainId) throw new Error('Chain ID is required for chain service'); + const chain = config.chains.find((c) => c.id === chainId); + if (!chain) throw new Error(`Chain ${chainId} not found`); + + const port = chain.ports?.[endpoint]; + if (!port) throw new Error(`Port not found for ${endpoint}`); + + if (useIngress) { + switch (endpoint) { + case 'rest': + return { + baseUrl: `https://rest.${chainId}-genesis.${host}`, + path: '/cosmos/bank/v1beta1/supply' + }; + case 'rpc': + return { + baseUrl: `https://rpc.${chainId}-genesis.${host}`, + path: '/status' + }; + case 'faucet': + return { + baseUrl: `https://rest.${chainId}-genesis.${host}`, + path: '/faucet/status' + }; + case 'exposer': + return { + baseUrl: `https://rest.${chainId}-genesis.${host}`, + path: '/exposer/node_id' + }; + default: + throw new Error(`Unknown endpoint ${endpoint} for chain service`); + } + } + + return { + baseUrl: `http://localhost:${port}`, + path: + endpoint === 'rest' + ? '/cosmos/bank/v1beta1/supply' + : endpoint === 'rpc' + ? '/status' + : endpoint === 'faucet' + ? '/status' + : endpoint === 'exposer' + ? '/node_id' + : '' + }; + } + + case 'relayer': { + if (!chainId) throw new Error('Chain ID is required for relayer service'); + const relayer = config.relayers?.find((r) => r.chains.includes(chainId)); + if (!relayer) throw new Error(`Relayer for chain ${chainId} not found`); + + const port = relayer.ports?.[endpoint]; + if (!port) throw new Error(`Port not found for ${endpoint}`); + + if (useIngress) { + switch (endpoint) { + case 'rest': + return { + baseUrl: `https://rest.${relayer.type}-${relayer.name}.${host}`, + path: '/status' + }; + case 'exposer': + return { + baseUrl: `https://rest.${relayer.type}-${relayer.name}.${host}`, + path: '/exposer/config' + }; + default: + throw new Error(`Unknown endpoint ${endpoint} for relayer service`); + } + } + + return { + baseUrl: `http://localhost:${port}`, + path: + endpoint === 'rest' + ? '/status' + : endpoint === 'exposer' + ? '/config' + : '' + }; + } + + case 'registry': { + const port = config.registry?.ports?.rest; + if (!port) throw new Error('Registry REST port not found'); + + if (useIngress) { + return { + baseUrl: `https://registry.${host}`, + path: '/chains' + }; + } + + return { + baseUrl: `http://localhost:${port}`, + path: '/chains' + }; + } + + case 'explorer': { + const port = config.explorer?.ports?.http; + if (!port) throw new Error('Explorer HTTP port not found'); + + if (useIngress) { + return { + baseUrl: `https://explorer.${host}`, + path: '' + }; + } + + return { + baseUrl: `http://localhost:${port}`, + path: '' + }; + } + + default: + throw new Error(`Unknown service ${service}`); + } +};