From d14d564dc6ab0d1aaf6ae5f6daf71574611c3a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Fri, 17 Nov 2023 19:29:30 -0300 Subject: [PATCH 01/25] fix: slew of type errors. --- relayer/application-standard.ts | 42 ++++++----- relayer/application.ts | 81 ++++++++++++---------- relayer/compose.middleware.ts | 8 ++- relayer/middleware/missedVaasV3/check.ts | 24 +++---- relayer/middleware/missedVaasV3/storage.ts | 4 +- relayer/middleware/missedVaasV3/worker.ts | 2 +- relayer/rpc/http-client.ts | 6 +- relayer/rpc/wormholescan-client.ts | 14 +++- relayer/utils.ts | 18 +++++ 9 files changed, 123 insertions(+), 76 deletions(-) diff --git a/relayer/application-standard.ts b/relayer/application-standard.ts index 9cf78d02..39eb848c 100644 --- a/relayer/application-standard.ts +++ b/relayer/application-standard.ts @@ -44,7 +44,7 @@ export interface StandardMissedVaaOpts { export interface StandardRelayerAppOpts extends RelayerAppOpts { name: string; - spyEndpoint?: string; + spyEndpoint: string; logger?: Logger; privateKeys?: Partial<{ [k in ChainId]: any[]; @@ -64,14 +64,14 @@ export interface StandardRelayerAppOpts extends RelayerAppOpts { maxFailedQueueSize?: number; } -const defaultOpts: Partial = { +const defaultOpts = { spyEndpoint: "localhost:7073", workflows: { retries: 3, }, fetchSourceTxhash: true, logger: defaultLogger, -}; +} satisfies Partial; export type StandardRelayerContext = LoggingContext & StorageContext & @@ -80,18 +80,20 @@ export type StandardRelayerContext = LoggingContext & WalletContext & SourceTxContext; +type MakeOptional = Omit & Partial; + export class StandardRelayerApp< ContextT extends StandardRelayerContext = StandardRelayerContext, > extends RelayerApp { private readonly store: RedisStorage; private readonly mergedRegistry: Registry; - constructor(env: Environment, opts: StandardRelayerAppOpts) { + constructor(env: Environment, opts: MakeOptional) { // take logger out before merging because of recursive call stack const logger = opts.logger ?? defaultLogger; delete opts.logger; // now we can merge - opts = mergeDeep({}, [defaultOpts, opts]); + const options = mergeDeep({}, [defaultOpts, opts]); const { privateKeys, @@ -105,14 +107,14 @@ export class StandardRelayerApp< retryBackoffOptions, maxCompletedQueueSize, maxFailedQueueSize, - } = opts; - super(env, opts); + } = options; + super(env, options); this.store = new RedisStorage({ redis, redisClusterEndpoints, redisCluster, - attempts: opts.workflows.retries ?? 3, + attempts: options.workflows?.retries ?? 3, namespace: name, queueName: `${name}-relays`, exponentialBackoff: retryBackoffOptions, @@ -131,13 +133,13 @@ export class StandardRelayerApp< redisCluster, redisClusterEndpoints, wormholeRpcs, - concurrency: opts.missedVaaOptions?.concurrency, - checkInterval: opts.missedVaaOptions?.checkInterval, - fetchVaaRetries: opts.missedVaaOptions?.fetchVaaRetries, - vaasFetchConcurrency: opts.missedVaaOptions?.vaasFetchConcurrency, + concurrency: options.missedVaaOptions?.concurrency, + checkInterval: options.missedVaaOptions?.checkInterval, + fetchVaaRetries: options.missedVaaOptions?.fetchVaaRetries, + vaasFetchConcurrency: options.missedVaaOptions?.vaasFetchConcurrency, storagePrefix: this.store.getPrefix(), - startingSequenceConfig: opts.missedVaaOptions?.startingSequenceConfig, - forceSeenKeysReindex: opts.missedVaaOptions?.forceSeenKeysReindex, + startingSequenceConfig: options.missedVaaOptions?.startingSequenceConfig, + forceSeenKeysReindex: options.missedVaaOptions?.forceSeenKeysReindex, }); } @@ -149,9 +151,11 @@ export class StandardRelayerApp< this.spy(spyEndpoint); this.useStorage(this.store); this.logger(logger); - this.use(logging(logger)); // <-- logging middleware - this.use(providers(opts.providers, Object.keys(opts.privateKeys ?? {}))); - if (opts.privateKeys && Object.keys(opts.privateKeys).length) { + this.use(logging(logger)); + this.use(providers(options.providers, Object.keys(privateKeys ?? {}))); + + // You need valid private keys to turn on the wallet middleware + if (privateKeys !== undefined && Object.keys(privateKeys).length > 0) { this.use( wallets(env, { logger, @@ -160,7 +164,7 @@ export class StandardRelayerApp< tokensByChain, metrics: { enabled: true, registry: this.metricsRegistry }, }), - ); // <-- you need valid private keys to turn on this middleware + ); } this.use(tokenBridgeContracts()); this.use( @@ -171,7 +175,7 @@ export class StandardRelayerApp< redisClusterEndpoints, }), ); - if (opts.fetchSourceTxhash) { + if (options.fetchSourceTxhash) { this.use(sourceTx()); } } diff --git a/relayer/application.ts b/relayer/application.ts index 34486a73..0d347711 100644 --- a/relayer/application.ts +++ b/relayer/application.ts @@ -14,12 +14,12 @@ import { compose, composeError, ErrorMiddleware, + isErrorMiddlewareList, Middleware, Next, } from "./compose.middleware.js"; import { Context } from "./context.js"; import { Logger } from "winston"; -import { BigNumber } from "ethers"; import { createSpyRPCServiceClient, subscribeSignedVAA, @@ -29,7 +29,6 @@ import { encodeEmitterAddress, mergeDeep, parseVaaWithBytes, - sleep, } from "./utils.js"; import { FailFastGrpcTransportFactory } from "./rpc/fail-fast-grpc-transport.js"; import { defaultLogger } from "./logging.js"; @@ -41,12 +40,13 @@ import { Environment } from "./environment.js"; import { SpyRPCServiceClient } from "@certusone/wormhole-spydk/lib/cjs/proto/spy/v1/spy.js"; import { Registry } from "prom-client"; import { createRelayerMetrics, RelayerMetrics } from "./application.metrics.js"; +import { FetchVaaFn } from "./context.js"; export { UnrecoverableError }; export interface RelayerAppOpts { - wormholeRpcs?: string[]; - concurrency?: number; + wormholeRpcs: string[]; + concurrency: number; } export type FetchaVaasOpts = { @@ -101,15 +101,15 @@ export enum RelayerEvents { export type ListenerFn = (vaa: ParsedVaaWithBytes, job?: RelayJob) => void; export class RelayerApp extends EventEmitter { - storage: Storage; + storage?: Storage; filters: { emitterFilter?: { chainId?: ChainId; emitterAddress?: string }; }[] = []; - private pipeline?: Middleware; - private errorPipeline?: ErrorMiddleware; + private pipeline?: Middleware; + private errorPipeline?: ErrorMiddleware; private chainRouters: Partial>> = {}; private spyUrl?: string; - private rootLogger: Logger; + private rootLogger?: Logger; private opts: RelayerAppOpts; private vaaFilters: FilterFN[] = []; private alreadyFilteredCache = new LRUCache({ max: 1000 }); @@ -118,7 +118,7 @@ export class RelayerApp extends EventEmitter { constructor( public env: Environment = Environment.TESTNET, - opts: RelayerAppOpts = {}, + opts: Partial = {}, ) { super(); this.opts = mergeDeep({}, [defaultOpts(env), opts]); @@ -160,7 +160,7 @@ export class RelayerApp extends EventEmitter { isOk = await filter(vaa); } catch (e: any) { isOk = false; - this.rootLogger.debug( + this.rootLogger?.debug( `filter ${i} of ${this.vaaFilters.length} threw an exception`, { emitterChain, @@ -174,7 +174,7 @@ export class RelayerApp extends EventEmitter { } if (!isOk) { this.alreadyFilteredCache.set(id, true); - this.rootLogger.debug( + this.rootLogger?.debug( `Vaa was skipped by filter ${i + 1} of ${this.vaaFilters.length}`, { emitterChain, emitterAddress, sequence }, ); @@ -231,26 +231,29 @@ export class RelayerApp extends EventEmitter { * @param middleware */ use(...middleware: Middleware[] | ErrorMiddleware[]) { - if (!middleware.length) { + if (middleware.length === 0) { return; } + // TODO: actually check that we don't receive a mixed list of middleware? + // Only useful if we think we have JS only consumers + // adding error middleware - if (middleware[0].length > 2) { + if (isErrorMiddlewareList(middleware)) { if (this.errorPipeline) { - (middleware as ErrorMiddleware[]).unshift(this.errorPipeline); + middleware.unshift(this.errorPipeline); } this.errorPipeline = composeError( - middleware as ErrorMiddleware[], + middleware, ); return; } // adding regular middleware - if (this.pipeline) { - (middleware as Middleware[]).unshift(this.pipeline); + if (this.pipeline !== undefined) { + middleware.unshift(this.pipeline); } - this.pipeline = compose(middleware as Middleware[]); + this.pipeline = compose(middleware); } fetchVaas(opts: FetchaVaasOpts): Promise { @@ -271,14 +274,15 @@ export class RelayerApp extends EventEmitter { * @param retryTimeout backoff between retries * @param retries number of attempts */ - async fetchVaa( - chain: ChainId | string, - emitterAddress: Buffer | string, - sequence: bigint | string | BigNumber, + public readonly fetchVaa: FetchVaaFn = async function fetchVaa( + this: RelayerApp, + chain, + emitterAddress, + sequence, { retryTimeout = 100, retries = 2, - }: { retryTimeout: number; retries: number } = { + } = { retryTimeout: 100, retries: 2, }, @@ -335,7 +339,7 @@ export class RelayerApp extends EventEmitter { ): Promise { const parsedVaa = parseVaaWithBytes(vaa); - let ctx: Context = { + const ctx: Context = { config: { spyFilters: await this.spyFilters(), }, @@ -348,12 +352,12 @@ export class RelayerApp extends EventEmitter { vaa: parsedVaa, vaaBytes: vaa, }; - Object.assign(ctx, opts); + const ctxt = Object.assign(ctx, opts) as ContextT; try { - await this.pipeline?.(ctx, () => {}); + await this.pipeline?.(ctxt, () => {}); this.emit(RelayerEvents.Completed, parsedVaa, opts?.storage?.job); } catch (e) { - this.errorPipeline?.(e, ctx, () => {}); + this.errorPipeline?.(e as Error, ctxt, () => {}); this.emit(RelayerEvents.Failed, parsedVaa, opts?.storage?.job); throw e; } @@ -398,10 +402,14 @@ export class RelayerApp extends EventEmitter { const chainName = coalesceChainName(chainIdOrName); const chainId = coalesceChainId(chainIdOrName); const env = this.env.toUpperCase() as "MAINNET" | "TESTNET" | "DEVNET"; - let address = + const address = chainId === CHAIN_ID_SUI ? emitterCapByEnv[this.env] // sui is different from evm in that you can't use the package id or state id, you have to use the emitter cap : CONTRACTS[env][chainName].token_bridge; + + if (address === undefined) { + throw new Error("Could not find token bridge address."); + } this.chain(chainId).address(address, ...handlers); } return this; @@ -412,7 +420,7 @@ export class RelayerApp extends EventEmitter { > { const spyFilters = new Set(); for (const chainRouter of Object.values(this.chainRouters)) { - for (const filter of await chainRouter.spyFilters()) { + for (const filter of chainRouter.spyFilters()) { spyFilters.add(filter); } } @@ -469,9 +477,12 @@ export class RelayerApp extends EventEmitter { private generateChainRoutes(): Middleware { return async (ctx: ContextT, next: Next) => { - let router = this.chainRouters[ctx.vaa.emitterChain as ChainId]; + if (ctx.vaa === undefined) { + throw new Error("No VAA in context."); + } + const router = this.chainRouters[ctx.vaa.emitterChain as ChainId]; if (!router) { - this.rootLogger.error( + this.rootLogger?.error( "received a vaa but we don't have a router for it", ); return; @@ -550,7 +561,7 @@ export class RelayerApp extends EventEmitter { * Stop the worker from grabbing more jobs and wait until it finishes with the ones that it has. */ stop() { - return this.storage.stopWorker(); + return this.storage?.stopWorker(); } private onVaaFromQueue = async (job: RelayJob) => { @@ -607,10 +618,10 @@ class ChainRouter { /** * Translates seconds into human readable format of seconds, minutes, hours, days, and years * - * @param {number} seconds The number of seconds to be processed - * @return {string} The phrase describing the amount of time + * @param seconds The number of seconds to be processed + * @return The phrase describing the amount of time */ -function secondsToHuman(seconds: number) { +function secondsToHuman(seconds: number): string { const levels: [number, string][] = [ [Math.floor(seconds / 31536000), "years"], [Math.floor((seconds % 31536000) / 86400), "days"], diff --git a/relayer/compose.middleware.ts b/relayer/compose.middleware.ts index 99ea5ef8..e4a899ec 100644 --- a/relayer/compose.middleware.ts +++ b/relayer/compose.middleware.ts @@ -40,10 +40,16 @@ export function composeError( if (i === middleware.length) { return next(); } - let fn = middleware[i]; + const fn = middleware[i]; return fn(err, ctx, callNext.bind(null, i + 1)); } return callNext(0); }; } + +export function isErrorMiddlewareList(list: any[]): list is ErrorMiddleware[] { + return list.every((f) => { + return f.length > 2; + }); +} diff --git a/relayer/middleware/missedVaasV3/check.ts b/relayer/middleware/missedVaasV3/check.ts index ef67f34f..00d05837 100644 --- a/relayer/middleware/missedVaasV3/check.ts +++ b/relayer/middleware/missedVaasV3/check.ts @@ -8,7 +8,7 @@ import { RelayerEvents, SerializableVaaId, } from "../../application.js"; -import { mapConcurrent } from "../../utils.js"; +import { mapConcurrent, max } from "../../utils.js"; import { MissedVaaRunStats, tryFetchVaa } from "./helpers.js"; import { batchMarkAsFailedToRecover, @@ -31,7 +31,7 @@ export async function checkForMissedVaas( opts: MissedVaaOpts, prefix: string, wormholescan: Wormholescan, - previousSafeSequence?: bigint | null, + previousSafeSequence?: bigint, logger?: Logger, ): Promise { const { emitterChain, emitterAddress } = filter; @@ -60,9 +60,9 @@ export async function checkForMissedVaas( const failedToReprocess: string[] = []; let missingSequences: bigint[] = []; - if (seenSequences.length) { + if (seenSequences.length > 0) { const first = - previousSafeSequence !== null && previousSafeSequence < seenSequences[0] + previousSafeSequence !== undefined && previousSafeSequence < seenSequences[0] ? previousSafeSequence : seenSequences[0]; const last = seenSequences[seenSequences.length - 1]; @@ -72,7 +72,7 @@ export async function checkForMissedVaas( // Check if there is any leap between the sequences seen, // and try reprocessing them if any: if ( - previousSafeSequence !== null && + previousSafeSequence !== undefined && previousSafeSequence < seenSequences[0] ) { seenSequences.unshift(previousSafeSequence); @@ -143,15 +143,13 @@ export async function checkForMissedVaas( // look ahead of greatest seen sequence in case the next vaa was missed // continue looking ahead until a vaa can't be fetched - const lastSeq = seenSequences[seenSequences.length - 1] + const lastSeq = seenSequences.length > 0 ? seenSequences[seenSequences.length - 1] : null; - let lookAheadSequence = - lastSeq && startingSeqConfig - ? lastSeq > startingSeqConfig - ? lastSeq - : startingSeqConfig // same as Math.max, which doesn't support bigint + const lookAheadSequence = + lastSeq !== null && startingSeqConfig !== undefined + ? max(lastSeq, startingSeqConfig) : lastSeq || startingSeqConfig; const { @@ -289,12 +287,12 @@ async function lookAhead( `Looking ahead for missed VAAs from sequence: ${lastSeenSequence}`, ); - let latestVaas = await wormholescan.listVaas( + const latestVaas = await wormholescan.listVaas( filter.emitterChain, filter.emitterAddress, { pageSize: maxLookAhead, retries: maxRetries }, ); - if (latestVaas.error) { + if ("error" in latestVaas) { logger?.error( `Error FETCHING Look Ahead VAAs. Error: ${latestVaas.error.message}`, latestVaas.error, diff --git a/relayer/middleware/missedVaasV3/storage.ts b/relayer/middleware/missedVaasV3/storage.ts index cd625e95..19e5f0bc 100644 --- a/relayer/middleware/missedVaasV3/storage.ts +++ b/relayer/middleware/missedVaasV3/storage.ts @@ -186,13 +186,13 @@ export async function calculateStartingIndex( prefix: string, emitterChain: number, emitterAddress: string, - lastSafeSequence?: bigint | null, + lastSafeSequence?: bigint, ) { const key = getSeenVaaKey(prefix, emitterChain, emitterAddress); let indexToStartFrom: number | null = null; - if (lastSafeSequence) { + if (lastSafeSequence !== undefined) { indexToStartFrom = await redis.zrank(key, lastSafeSequence.toString()); } diff --git a/relayer/middleware/missedVaasV3/worker.ts b/relayer/middleware/missedVaasV3/worker.ts index 87e7dc8d..ce5ab5f9 100644 --- a/relayer/middleware/missedVaasV3/worker.ts +++ b/relayer/middleware/missedVaasV3/worker.ts @@ -39,7 +39,7 @@ const DEFAULT_PREFIX = "MissedVaaWorkerV3"; export interface MissedVaaOpts extends RedisConnectionOpts { registry?: Registry; logger?: Logger; - wormholeRpcs?: string[]; + wormholeRpcs: string[]; wormscanUrl?: string; // How many "source" chains will be scanned for missed VAAs concurrently. concurrency?: number; diff --git a/relayer/rpc/http-client.ts b/relayer/rpc/http-client.ts index 91143416..a1b944f9 100644 --- a/relayer/rpc/http-client.ts +++ b/relayer/rpc/http-client.ts @@ -1,4 +1,5 @@ import { setTimeout } from "timers/promises"; +import { printError } from "../utils.js"; /** * A simple HTTP client with exponential backoff retries and 429 handling. @@ -36,11 +37,11 @@ export class HttpClient { }); } catch (err) { // Connection / timeout error: - throw new HttpClientError(err); + throw new HttpClientError(printError(err)); } if (!response.ok) { - throw new HttpClientError(null, response, await response.json()); + throw new HttpClientError(undefined, response, await response.json()); } return await response.json(); @@ -78,6 +79,7 @@ export class HttpClient { throw err; } } + throw new Error("Failed to execute HTTP request."); } } diff --git a/relayer/rpc/wormholescan-client.ts b/relayer/rpc/wormholescan-client.ts index 95790f28..73277059 100644 --- a/relayer/rpc/wormholescan-client.ts +++ b/relayer/rpc/wormholescan-client.ts @@ -115,14 +115,21 @@ export type WormholescanOptions = { noCache?: boolean; }; -class WormholescanVaaResponse { +/** + * Raw response model. + */ +interface WormholescanVaaResponse { id: string; sequence: bigint; vaa: string; emitterAddr: string; emitterChain: number; + txHash?: string; } +/** + * Parsed response model. + */ export type WormholescanVaa = { id: string; sequence: bigint; @@ -133,6 +140,7 @@ export type WormholescanVaa = { }; export type WormholescanResult = { - error?: HttpClientError; - data?: T; + error: HttpClientError; +} | { + data: T; }; diff --git a/relayer/utils.ts b/relayer/utils.ts index 8f2a1648..eff353de 100644 --- a/relayer/utils.ts +++ b/relayer/utils.ts @@ -12,6 +12,7 @@ import { deriveWormholeEmitterKey } from "@certusone/wormhole-sdk/lib/cjs/solana import { zeroPad } from "ethers/lib/utils.js"; import { ParsedVaaWithBytes } from "./application.js"; import { ethers } from "ethers"; +import { inspect } from "util"; export function encodeEmitterAddress( chainId: wormholeSdk.ChainId, @@ -208,3 +209,20 @@ export async function mapConcurrent( const promises = new Array(concurrency).fill(0).map(evaluateNext); await Promise.all(promises); } + +export function printError(error: unknown): string { + if (error instanceof Error) { + return `${error?.stack || error.message}`; + } + + // Prints nested properties until a depth of 2 by default. + return inspect(error); +} + +export function min(lhs: bigint, rhs: bigint): bigint { + return lhs < rhs ? lhs : rhs; +} + +export function max(lhs: bigint, rhs: bigint): bigint { + return lhs < rhs ? rhs : lhs; +} \ No newline at end of file From 41823d8bc9c3c37d4a895f83abec30f1c51b066d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Fri, 17 Nov 2023 19:30:02 -0300 Subject: [PATCH 02/25] fix: typescript typecheck and watch invocations --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 709f6c02..e120040a 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "test-redis": "docker run --rm -p 6301:6379 --name relayer-engine-test -d redis; npm run test; docker kill relayer-engine-test", "test": "jest --silent=false", "test-watch": "jest --silent=false --watch", - "build": "tsc -b ./tsconfig.cjs.json && tsc -b ./tsconfig.esm.json && bin/create-package.json.sh", - "watch": "tsc --watch", - "typecheck": "tsc --noEmit --skipLibCheck", + "build": "tsc --build ./tsconfig.cjs.json && tsc --build ./tsconfig.esm.json && bin/create-package.json.sh", + "watch": "tsc --watch --project ./tsconfig.esm.json", + "typecheck": "tsc --noEmit --skipLibCheck --project ./tsconfig.esm.json", "prettier": "prettier --write $(git diff main --name-only --diff-filter u | grep '.ts$' | xargs)", "mainnet-spy": "docker run --platform=linux/amd64 -p 7073:7073 --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest spy --nodeKey /node.key --spyRPC \"[::]:7073\" --network /wormhole/mainnet/2 --bootstrap /dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7", "testnet-spy": "docker run --platform=linux/amd64 -p 7073:7073 --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest spy --nodeKey /node.key --spyRPC \"[::]:7073\" --network /wormhole/testnet/2/1 --bootstrap /dns4/wormhole-testnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWAkB9ynDur1Jtoa97LBUp8RXdhzS5uHgAfdTquJbrbN7i", From 0f31b972739deab4abde2cd76514f369775e0966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Fri, 17 Nov 2023 23:03:35 -0300 Subject: [PATCH 03/25] fix: squashes even more type errors. --- relayer/application-standard.ts | 14 +++---- relayer/application.ts | 2 +- relayer/bundle-fetcher.helper.ts | 27 ++++++------ relayer/middleware/legacy-plugin/config.ts | 4 +- .../legacy-plugin/legacy-plugin.middleware.ts | 6 +-- relayer/middleware/metrics.middleware.ts | 14 ++++--- relayer/middleware/providers.middleware.ts | 14 ++++--- relayer/middleware/source-tx.middleware.ts | 42 ++++++++++--------- relayer/utils.ts | 2 + 9 files changed, 68 insertions(+), 57 deletions(-) diff --git a/relayer/application-standard.ts b/relayer/application-standard.ts index 39eb848c..a98f7d21 100644 --- a/relayer/application-standard.ts +++ b/relayer/application-standard.ts @@ -1,4 +1,4 @@ -import { RelayerApp, RelayerAppOpts } from "./application.js"; +import { RelayerApp, RelayerAppOpts, defaultOpts } from "./application.js"; import { logging, LoggingContext, @@ -22,7 +22,7 @@ import { } from "./storage/redis-storage.js"; import { ChainId } from "@certusone/wormhole-sdk"; import { ClusterNode, ClusterOptions, RedisOptions } from "ioredis"; -import { mergeDeep } from "./utils.js"; +import { MakeOptional, mergeDeep } from "./utils.js"; import { defaultLogger } from "./logging.js"; import { BullMQAdapter } from "@bull-board/api/bullMQAdapter.js"; import { KoaAdapter } from "@bull-board/koa"; @@ -64,7 +64,7 @@ export interface StandardRelayerAppOpts extends RelayerAppOpts { maxFailedQueueSize?: number; } -const defaultOpts = { +const defaultStdOpts = { spyEndpoint: "localhost:7073", workflows: { retries: 3, @@ -73,6 +73,8 @@ const defaultOpts = { logger: defaultLogger, } satisfies Partial; +type FullDefaultOpts = typeof defaultStdOpts & ReturnType; + export type StandardRelayerContext = LoggingContext & StorageContext & TokenBridgeContext & @@ -80,20 +82,18 @@ export type StandardRelayerContext = LoggingContext & WalletContext & SourceTxContext; -type MakeOptional = Omit & Partial; - export class StandardRelayerApp< ContextT extends StandardRelayerContext = StandardRelayerContext, > extends RelayerApp { private readonly store: RedisStorage; private readonly mergedRegistry: Registry; - constructor(env: Environment, opts: MakeOptional) { + constructor(env: Environment, opts: MakeOptional) { // take logger out before merging because of recursive call stack const logger = opts.logger ?? defaultLogger; delete opts.logger; // now we can merge - const options = mergeDeep({}, [defaultOpts, opts]); + const options = mergeDeep({}, [defaultStdOpts, opts]); const { privateKeys, diff --git a/relayer/application.ts b/relayer/application.ts index 0d347711..9241fefb 100644 --- a/relayer/application.ts +++ b/relayer/application.ts @@ -70,7 +70,7 @@ export const defaultWormscanUrl = { [Environment.DEVNET]: "https://api.testnet.wormholescan.io", }; -const defaultOpts = (env: Environment): RelayerAppOpts => ({ +export const defaultOpts = (env: Environment) => ({ wormholeRpcs: defaultWormholeRpcs[env], concurrency: 1, }); diff --git a/relayer/bundle-fetcher.helper.ts b/relayer/bundle-fetcher.helper.ts index b6569a53..d34d7fcc 100644 --- a/relayer/bundle-fetcher.helper.ts +++ b/relayer/bundle-fetcher.helper.ts @@ -1,13 +1,9 @@ import { ChainId, ParsedVaa } from "@certusone/wormhole-sdk"; import { FetchVaaFn } from "./context.js"; -import { EngineError, parseVaaWithBytes, sleep } from "./utils.js"; +import { EngineError, MakeOptional, parseVaaWithBytes, sleep } from "./utils.js"; import { ParsedVaaWithBytes } from "./application.js"; -export type VaaId = { - emitterChain: ParsedVaa["emitterChain"]; - emitterAddress: ParsedVaa["emitterAddress"]; - sequence: ParsedVaa["sequence"]; -}; +export type VaaId = Pick; export type SerializedBatchFetcher = { vaaBytes: string[]; @@ -18,15 +14,15 @@ export type SerializedBatchFetcher = { // If you pass in a hash, go to the blockchain, read logs and transform into array of ids // then fetch the vaas corresponding to those ids interface VaaBundlerOpts { - maxAttempts?: number; - delayBetweenAttemptsInMs?: number; + maxAttempts: number; + delayBetweenAttemptsInMs: number; vaaIds: VaaId[]; } -const defaultOpts: Omit = { +const defaultOpts = { maxAttempts: 10, delayBetweenAttemptsInMs: 1000, -}; +} satisfies Partial; export class VaaBundleFetcher { private readonly fetchedVaas: Record = {}; @@ -34,8 +30,11 @@ export class VaaBundleFetcher { private readonly fetchErrors: Record = {}; private opts: VaaBundlerOpts; - constructor(private fetchVaa: FetchVaaFn, opts?: VaaBundlerOpts) { - this.opts = Object.assign({}, defaultOpts, opts); + constructor(private fetchVaa: FetchVaaFn, opts: MakeOptional) { + this.opts = { + ...defaultOpts, + ...opts + }; for (const id of this.opts.vaaIds) { this.pendingVaas[this.idToKey(id)] = id; @@ -118,13 +117,13 @@ export class VaaBundleFetcher { id.sequence, ); } catch (e) { - this.fetchErrors[this.idToKey(id)] = e; + this.fetchErrors[this.idToKey(id)] = e as Error; return null; } }), ); - const vaas = fetched.filter(vaa => vaa !== null); + const vaas = fetched.filter(vaa => vaa !== null) as ParsedVaaWithBytes[]; this.addVaaPayloads(vaas); return this.isComplete; } diff --git a/relayer/middleware/legacy-plugin/config.ts b/relayer/middleware/legacy-plugin/config.ts index 46adc182..6a79b64a 100644 --- a/relayer/middleware/legacy-plugin/config.ts +++ b/relayer/middleware/legacy-plugin/config.ts @@ -143,9 +143,9 @@ export async function run(args: RunArgs, env: Environment): Promise { } : undefined, redisClusterEndpoints: redis?.cluster ? [redis.host] : undefined, - spyEndpoint: listenerEnv.spyServiceHost, + spyEndpoint: listenerEnv?.spyServiceHost, logger: defaultLogger, - privateKeys: executorEnv.privateKeys, + privateKeys: executorEnv?.privateKeys, }); const [pluginName, pluginFn] = Object.entries(args.plugins)[0]; diff --git a/relayer/middleware/legacy-plugin/legacy-plugin.middleware.ts b/relayer/middleware/legacy-plugin/legacy-plugin.middleware.ts index 8579c146..a868377b 100644 --- a/relayer/middleware/legacy-plugin/legacy-plugin.middleware.ts +++ b/relayer/middleware/legacy-plugin/legacy-plugin.middleware.ts @@ -121,7 +121,7 @@ function providersShimToLegacy(providers: Providers): LegacyProviders { solana: providers.solana.length > 0 ? providers.solana[0] - : (undefined as Connection), + : (undefined as unknown as Connection), untyped: Object.fromEntries( Object.entries(providers.untyped).map(([chain, rpcs]) => [ chain, @@ -134,11 +134,11 @@ function providersShimToLegacy(providers: Providers): LegacyProviders { sui: providers.sui.length > 0 ? providers.sui[0] - : (undefined as JsonRpcProvider), + : (undefined as unknown as JsonRpcProvider), sei: providers.sei.length > 0 ? providers.sei[0] - : (undefined as CosmWasmClient), + : (undefined as unknown as CosmWasmClient), }; } diff --git a/relayer/middleware/metrics.middleware.ts b/relayer/middleware/metrics.middleware.ts index d7cca727..7f460cf1 100644 --- a/relayer/middleware/metrics.middleware.ts +++ b/relayer/middleware/metrics.middleware.ts @@ -22,7 +22,7 @@ class MeasuredRelayJob { vaaTimestamp(): number { // vaa.timestamp is in seconds - return this.job?.data?.parsedVaa?.timestamp * 1000 ?? 0; + return (this.job?.data?.parsedVaa?.timestamp ?? 0) * 1000; } relayJobTimestamp(): number { @@ -167,18 +167,22 @@ export function metrics( const metricsMiddleware = async (ctx: C, next: Next) => { processedVaasTotal.inc(); - let failure: Error = null; + let failure: unknown; const startTime = Date.now(); try { await next(); } catch (e) { - failure = e; + if (e === undefined) { + failure = new Error("Got thrown undefined while calling next metrics middleware."); + } else { + failure = e; + } } const job = new MeasuredRelayJob(startTime, Date.now(), ctx.storage.job); - const labels = await getLabels(ctx, opts, failure !== null); + const labels = await getLabels(ctx, opts, failure !== undefined); processingDuration.labels(labels).observe(job.processingTime()); @@ -190,7 +194,7 @@ export function metrics( relayDuration.labels(labels).observe(job.relayingTime()); } - if (failure) { + if (failure !== undefined) { finishedVaasTotal .labels({ ...labels, terminal: `${job.hasReachedMaxAttempts()}` }) .inc(); diff --git a/relayer/middleware/providers.middleware.ts b/relayer/middleware/providers.middleware.ts index c3bccff0..d40797c9 100644 --- a/relayer/middleware/providers.middleware.ts +++ b/relayer/middleware/providers.middleware.ts @@ -32,6 +32,7 @@ import { Environment } from "../environment.js"; import { getCosmWasmClient } from "@sei-js/core"; import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate"; import { Logger } from "winston"; +import { printError } from "../utils.js"; export interface Providers { evm: Partial>; @@ -245,8 +246,8 @@ async function buildProviders( providers.sui = endpoints.map((url, i) => { let conn = new sui.Connection({ fullnode: url, - faucet: (faucets && faucets[i]) || faucets[0], // try to map to the same index, otherwise use the first (if user only provided one faucet but multiple endpoints) - websocket: (websockets && websockets[i]) || websockets[0], // same as above + faucet: faucets?.at(i) || faucets?.at(0), // try to map to the same index, otherwise use the first (if user only provided one faucet but multiple endpoints) + websocket: websockets?.at(i) || websockets?.at(0), // same as above }); return new sui.JsonRpcProvider(conn); }); @@ -259,13 +260,14 @@ async function buildProviders( providers.untyped[chainId] = endpoints.map(c => ({ rpcUrl: c })); } } catch (error) { - error.originalStack = error.stack; - error.stack = new Error().stack; + if (error instanceof Error) { + (error as any).originalStack = error.stack; + Error.captureStackTrace(error); + } logger?.error( `Failed to initialize provider for chain: ${chainIdStr} - endpoints: ${maskRPCEndpoints( endpoints, - )}. Error: `, - error, + )}. Error: ${printError(error)}` ); throw error; } diff --git a/relayer/middleware/source-tx.middleware.ts b/relayer/middleware/source-tx.middleware.ts index 4e49b790..d9accdd7 100644 --- a/relayer/middleware/source-tx.middleware.ts +++ b/relayer/middleware/source-tx.middleware.ts @@ -10,8 +10,9 @@ import { import { Logger } from "winston"; import { Environment } from "../environment.js"; import { LRUCache } from "lru-cache"; -import { ParsedVaaWithBytes } from "../application.js"; +import { ParsedVaaWithBytes, defaultWormscanUrl } from "../application.js"; import { WormholescanClient } from "../rpc/wormholescan-client.js"; +import { printError } from "../utils.js"; export interface SourceTxOpts { wormscanEndpoint: string; @@ -25,35 +26,29 @@ export interface SourceTxContext extends Context { sourceTxHash?: string; } -export const wormscanEndpoints: { [k in Environment]: string | undefined } = { - [Environment.MAINNET]: "https://api.wormholescan.io", - [Environment.TESTNET]: "https://api.testnet.wormholescan.io", - [Environment.DEVNET]: undefined, -}; - -const defaultOptsByEnv: { [k in Environment]: Partial } = { +const defaultOptsByEnv = { [Environment.MAINNET]: { - wormscanEndpoint: wormscanEndpoints[Environment.MAINNET], + wormscanEndpoint: defaultWormscanUrl[Environment.MAINNET], retries: 5, initialDelay: 1_000, maxDelay: 45_000, timeout: 5_000, }, [Environment.TESTNET]: { - wormscanEndpoint: wormscanEndpoints[Environment.TESTNET], + wormscanEndpoint: defaultWormscanUrl[Environment.TESTNET], retries: 3, initialDelay: 1_000, maxDelay: 30_000, timeout: 3_000, }, [Environment.DEVNET]: { - wormscanEndpoint: wormscanEndpoints[Environment.DEVNET], + wormscanEndpoint: defaultWormscanUrl[Environment.DEVNET], retries: 3, initialDelay: 500, maxDelay: 10_000, timeout: 2_000, }, -}; +} satisfies { [k in Environment]: Partial }; function ifVAAFinalized(vaa: ParsedVaaWithBytes) { const { consistencyLevel, emitterChain } = vaa; @@ -76,7 +71,16 @@ export function sourceTx( return async (ctx, next) => { if (!opts) { // initialize options now that we know the environment from context - opts = Object.assign({}, defaultOptsByEnv[ctx.env], optsWithoutDefaults); + opts = { + ...defaultOptsByEnv[ctx.env], + ...optsWithoutDefaults, + }; + } + + if (ctx.vaa === undefined) { + ctx.logger?.debug("Didn't get a VAA id. Skipping tx hash fetch."); + await next(); + return; } const vaaId = `${ctx.vaa.id.emitterChain}-${ctx.vaa.id.emitterAddress}-${ctx.vaa.id.sequence}`; @@ -97,8 +101,8 @@ export function sourceTx( emitterChain, emitterAddress, sequence, - ctx.logger, ctx.env, + ctx.logger, opts, ); if (txHash === "") { @@ -119,8 +123,8 @@ export async function fetchVaaHash( emitterChain: number, emitterAddress: Buffer, sequence: bigint, - logger: Logger, env: Environment, + logger?: Logger, sourceTxOpts?: SourceTxOpts, ) { if (!wormholescan) { @@ -139,12 +143,12 @@ export async function fetchVaaHash( sequence, ); - if (response.error) { - logger.error("Error fetching tx hash: " + response.error); + if ("error" in response) { + logger?.error(`Error fetching tx hash: ${printError(response.error)}`); throw response.error; } - let txHash = response.data?.txHash || ""; + let txHash = response.data.txHash || ""; if ( isEVMChain(emitterChain as ChainId) && @@ -154,7 +158,7 @@ export async function fetchVaaHash( txHash = `0x${txHash}`; } - logger.debug("Source Transaction Hash: " + txHash || "Not Found"); + logger?.debug("Source Transaction Hash: " + txHash || "Not Found"); return txHash; } diff --git a/relayer/utils.ts b/relayer/utils.ts index eff353de..04be4fc1 100644 --- a/relayer/utils.ts +++ b/relayer/utils.ts @@ -14,6 +14,8 @@ import { ParsedVaaWithBytes } from "./application.js"; import { ethers } from "ethers"; import { inspect } from "util"; +export type MakeOptional = Omit & Partial>> & Partial>; + export function encodeEmitterAddress( chainId: wormholeSdk.ChainId, emitterAddressStr: string, From 61d4cbbed42fb53a92f617a18a669853afacc479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Sat, 18 Nov 2023 00:09:39 -0300 Subject: [PATCH 04/25] fix: yet more type fixes --- relayer/middleware/staging-area.middleware.ts | 29 ++++++++++++------- relayer/middleware/tokenBridge.middleware.ts | 10 +++++-- .../middleware/wallet/wallet-management.ts | 4 ++- relayer/middleware/wallet/walletToolBox.ts | 8 ++++- relayer/rpc/fail-fast-grpc-transport.ts | 4 +++ relayer/storage/redis-storage.ts | 18 ++++++------ 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/relayer/middleware/staging-area.middleware.ts b/relayer/middleware/staging-area.middleware.ts index ca23912a..9a43ede7 100644 --- a/relayer/middleware/staging-area.middleware.ts +++ b/relayer/middleware/staging-area.middleware.ts @@ -14,24 +14,31 @@ export interface StagingAreaContext extends Context { kv: StagingAreaKeyLock; } -export interface StagingAreaOpts { - redisClusterEndpoints?: ClusterNode[]; - redisCluster?: ClusterOptions; +export type StagingAreaOpts = ( + | { + redisClusterEndpoints: ClusterNode[]; + redisCluster: ClusterOptions; + } + | {} +) & { redis?: RedisOptions; namespace?: string; -} +}; export function stagingArea( opts: StagingAreaOpts = {}, ): Middleware { - opts.redis = opts.redis || { host: "localhost", port: 6379 }; + const options = { + redis: { host: "localhost", port: 6379 }, + ...opts, + } // TODO: maybe refactor redis pool for all plugins that rely on it. const factory = { create: async function () { - const redis = opts.redisCluster + const redis = "redisCluster" in opts ? new Redis.Cluster(opts.redisClusterEndpoints, opts.redisCluster) - : new Redis(opts.redis); + : new Redis(options.redis); return redis; }, destroy: async function () { @@ -51,8 +58,8 @@ export function stagingArea( ctx.kv = new DefaultStagingAreaKeyLock( redis, - ctx.logger, opts.namespace ?? "default", + ctx.logger, ); try { ctx.logger?.debug("Staging area attached to context"); @@ -83,8 +90,8 @@ class DefaultStagingAreaKeyLock implements StagingAreaKeyLock { constructor( private readonly redis: Redis | Cluster, - readonly logger: Logger, namespace: string, + readonly logger?: Logger, ) { this.stagingAreaKey = `stagingAreas:${sanitize(namespace)}`; } @@ -117,14 +124,14 @@ class DefaultStagingAreaKeyLock implements StagingAreaKeyLock { }; return tx ? await op((tx as unknown as Tx).redis) : op(this.redis); } catch (e) { - // Figure out how to catch wath error in ioredis + // Figure out how to catch watch error in ioredis // if (e instanceof WatchError) { // // todo: retry in this case? // this.logger.warn("Staging area key was mutated while executing"); // } else { // this.logger.error("Error while reading and writing staging area keys"); // } - this.logger.error(e); + this.logger?.error(e); throw e; } } diff --git a/relayer/middleware/tokenBridge.middleware.ts b/relayer/middleware/tokenBridge.middleware.ts index a502efb3..d3b04785 100644 --- a/relayer/middleware/tokenBridge.middleware.ts +++ b/relayer/middleware/tokenBridge.middleware.ts @@ -136,11 +136,15 @@ export function tokenBridgeContracts(): Middleware { } // User might or might not use sui, so a provider for sui // might not be present. - if (!suiState && ctx.providers.sui[0]) { - suiState = await getObjectFields( + if (suiState === undefined && ctx.providers.sui.length > 0) { + const fields = await getObjectFields( ctx.providers.sui[0], - CONTRACTS[ctx.env.toUpperCase() as "MAINNET"].sui.token_bridge, + CONTRACTS[ctx.env.toUpperCase() as "MAINNET" | "TESTNET" | "DEVNET"].sui.token_bridge, ); + if (fields === null) { + throw new UnrecoverableError("Couldn't read ") + } + suiState = fields; tokenBridgeEmitterCapSui = suiState?.emitter_cap.fields.id.id; } if (!evmContracts) { diff --git a/relayer/middleware/wallet/wallet-management.ts b/relayer/middleware/wallet/wallet-management.ts index 1e8ab749..0f273bff 100644 --- a/relayer/middleware/wallet/wallet-management.ts +++ b/relayer/middleware/wallet/wallet-management.ts @@ -30,6 +30,8 @@ import { } from "@certusone/wormhole-sdk/lib/cjs/utils/consts.js"; import { Environment } from "../../environment.js"; +export type MetricsOptions = (WalletManagerFullConfig["options"] & {}) ["metrics"] & {}; + const networks = { [Environment.MAINNET]: { [CHAIN_ID_ETH]: "mainnet", @@ -140,7 +142,7 @@ export function startWalletManagement( env: Environment, privateKeys: PrivateKeys, tokensByChain?: TokensByChain, - metricsOpts?: WalletManagerFullConfig["options"]["metrics"], + metricsOpts?: MetricsOptions, logger?: Logger, ): IClientWalletManager | ILibraryWalletManager { const wallets = buildWalletsConfig(env, privateKeys, tokensByChain); diff --git a/relayer/middleware/wallet/walletToolBox.ts b/relayer/middleware/wallet/walletToolBox.ts index cba2df63..f5425cc9 100644 --- a/relayer/middleware/wallet/walletToolBox.ts +++ b/relayer/middleware/wallet/walletToolBox.ts @@ -44,6 +44,8 @@ export async function createWalletToolbox( const seiPkBuf = Buffer.from(privateKey, "hex"); return createSeiWalletToolBox(providers, seiPkBuf); } + + throw new Error(`Unknown chain id ${chainId}`); } function createEVMWalletToolBox( @@ -51,7 +53,11 @@ function createEVMWalletToolBox( privateKey: string, chainId: wh.EVMChainId, ): WalletToolBox { - const wallet = new ethers.Wallet(privateKey, providers.evm[chainId][0]); + const chainProviders = providers.evm[chainId]; + if (chainProviders === undefined || chainProviders.length === 0) { + throw new Error(`No provider found for chain ${chainId}`); + } + const wallet = new ethers.Wallet(privateKey, chainProviders[0]); return { ...providers, wallet: wallet, diff --git a/relayer/rpc/fail-fast-grpc-transport.ts b/relayer/rpc/fail-fast-grpc-transport.ts index f65a41bd..518b324e 100644 --- a/relayer/rpc/fail-fast-grpc-transport.ts +++ b/relayer/rpc/fail-fast-grpc-transport.ts @@ -85,6 +85,10 @@ class TimeoutableTransport implements pkg.grpc.Transport { } sendMessage(msgBytes: Uint8Array): void { + if (this.request === undefined) { + throw new Error("Attempted to send a message without a request context"); + } + if ( !this.options.methodDefinition.requestStream && !this.options.methodDefinition.responseStream diff --git a/relayer/storage/redis-storage.ts b/relayer/storage/redis-storage.ts index ff56a2c7..2069f1df 100644 --- a/relayer/storage/redis-storage.ts +++ b/relayer/storage/redis-storage.ts @@ -89,11 +89,11 @@ const defaultOptions: Partial = { }; export class RedisStorage implements Storage { - logger: Logger; + logger?: Logger; vaaQueue: Queue; public registry: Registry; - workerId: string; - private worker: Worker; + workerId?: string; + private worker?: Worker; private readonly prefix: string; private readonly redis: Cluster | Redis; private metrics: StorageMetrics; @@ -109,7 +109,7 @@ export class RedisStorage implements Storage { this.opts.redis.maxRetriesPerRequest = null; //Added because of: DEPRECATION WARNING! Your redis options maxRetriesPerRequest must be null. On the next versions having this settings will throw an exception this.prefix = `{${this.opts.namespace ?? this.opts.queueName}}`; this.redis = - this.opts.redisClusterEndpoints?.length > 0 + this.opts.redisClusterEndpoints !== undefined && this.opts.redisClusterEndpoints.length > 0 ? new Redis.Cluster( this.opts.redisClusterEndpoints, this.opts.redisCluster, @@ -169,7 +169,7 @@ export class RedisStorage implements Storage { return { attempts: 0, data: { vaaBytes, parsedVaa }, - id: job.id, + id: job.id!, name: job.name, log: job.log.bind(job), receivedAt: startTime, @@ -218,7 +218,7 @@ export class RedisStorage implements Storage { sequence: parsedVaa.sequence.toString(), }); } else { - this.logger.debug("Received job with no parsedVaa"); + this.logger?.debug("Received job with no parsedVaa"); } const vaaBytes = Buffer.from(job.data.vaaBytes, "base64"); @@ -228,7 +228,7 @@ export class RedisStorage implements Storage { vaaBytes, parsedVaa: parseVaa(vaaBytes), }, - id: job.id, + id: job.id!, maxAttempts: this.opts.attempts, name: job.name, receivedAt: job.timestamp, @@ -253,11 +253,11 @@ export class RedisStorage implements Storage { async stopWorker() { await this.worker?.close(); - this.worker = null; + this.worker = undefined; } async spawnGaugeUpdateWorker(ms = 5000) { - while (this.worker !== null) { + while (this.worker !== undefined) { await this.updateGauges(); await sleep(ms); } From c4fc7b8ad73be5440912f49c3ee7d087f041224b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Sat, 18 Nov 2023 00:10:30 -0300 Subject: [PATCH 05/25] misc: enables strict typechecking. --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 386f90ba..98184e25 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "resolveJsonModule": true, "downlevelIteration": true, "sourceMap": true, + "strict": true, "allowSyntheticDefaultImports": true }, "include": ["relayer"], From 4a011e0fc3c968a4800779d4490e7b54f53bc05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Mon, 27 Nov 2023 11:59:27 -0300 Subject: [PATCH 06/25] fix: improves some types and voids confusing null with undefined --- relayer/middleware/tokenBridge.middleware.ts | 21 ++++++++++++++------ relayer/middleware/wallet/wallet.worker.ts | 8 ++++---- relayer/utils.ts | 16 +++++++++++---- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/relayer/middleware/tokenBridge.middleware.ts b/relayer/middleware/tokenBridge.middleware.ts index d3b04785..62a458a3 100644 --- a/relayer/middleware/tokenBridge.middleware.ts +++ b/relayer/middleware/tokenBridge.middleware.ts @@ -114,12 +114,12 @@ function isTokenBridgeVaa(env: Environment, vaa: ParsedVaa): boolean { function tryToParseTokenTransferVaa( vaaBytes: SignedVaa, -): ParsedTokenTransferVaa | null { +): ParsedTokenTransferVaa | undefined { try { return parseTokenTransferVaa(vaaBytes); } catch (e) { // it may not be a token transfer vaa. TODO Maybe we want to do something to support attestations etc. - return null; + return undefined; } } @@ -152,11 +152,20 @@ export function tokenBridgeContracts(): Middleware { evmContracts = instantiateReadEvmContracts(ctx.env, ctx.providers.evm); ctx.logger?.debug(`Token Bridge Contracts initialized`); } - let parsedTokenTransferVaa = null; - let payload = null; + + // TODO: should we actually allow these fields to be undefined in the context type? + if (ctx.vaa === undefined) { + throw new UnrecoverableError("Parsed VAA is undefined."); + } + if (ctx.vaaBytes === undefined) { + throw new UnrecoverableError("Raw VAA is undefined."); + } + + let parsedTokenTransferVaa; + let payload; if (isTokenBridgeVaa(ctx.env, ctx.vaa)) { parsedTokenTransferVaa = tryToParseTokenTransferVaa(ctx.vaaBytes); - if (parsedTokenTransferVaa) { + if (parsedTokenTransferVaa !== undefined) { payload = { payloadType: parsedTokenTransferVaa.payloadType, amount: parsedTokenTransferVaa.amount, @@ -180,7 +189,7 @@ export function tokenBridgeContracts(): Middleware { }, }, vaa: parsedTokenTransferVaa, - payload: payload, + payload, }; ctx.logger?.debug("Token Bridge contracts attached to context"); await next(); diff --git a/relayer/middleware/wallet/wallet.worker.ts b/relayer/middleware/wallet/wallet.worker.ts index a204eed3..fadf23dd 100644 --- a/relayer/middleware/wallet/wallet.worker.ts +++ b/relayer/middleware/wallet/wallet.worker.ts @@ -12,7 +12,7 @@ export async function spawnWalletWorker( actionQueue: Queue>, providers: Providers, workerInfo: WorkerInfo, - logger: Logger, + logger?: Logger, ): Promise { const workerIntervalMS = DEFAULT_WORKER_INTERVAL_MS; const walletToolBox = await createWalletToolbox( @@ -35,16 +35,16 @@ export async function spawnWalletWorker( walletToolBox, workerInfo.targetChainId, ); - logger.debug(`Action ${actionWithCont.pluginName} completed`, { + logger?.debug(`Action ${actionWithCont.pluginName} completed`, { action: actionWithCont, }); actionWithCont.resolve(result); } catch (e) { - logger.error(`Unexpected error while executing chain action:`, e); + logger?.error(`Unexpected error while executing chain action:`, e); actionWithCont.reject(e); } } catch (e) { - logger.error("", e); + logger?.error("", e); // wait longer between loop iterations on error await sleep(DEFAULT_WORKER_RESTART_MS); } diff --git a/relayer/utils.ts b/relayer/utils.ts index 04be4fc1..845bcb93 100644 --- a/relayer/utils.ts +++ b/relayer/utils.ts @@ -61,7 +61,7 @@ export function sleep(ms: number) { * @param item * @returns {boolean} */ -export function isObject(item: any) { +export function isObject(item: T): item is T & ({} | null) { return item && typeof item === "object" && !Array.isArray(item); } @@ -93,9 +93,17 @@ export function mergeDeep( if (isObject(target) && isObject(source)) { for (const key in source) { - if (isObject(source[key])) { - if (!target[key]) Object.assign(target, { [key]: {} }); - mergeDeep(target[key], [source[key]], maxDepth - 1); + const sourceProp = source[key]; + if (isObject(sourceProp)) { + // We need to cast because the narrowed type for the key is lost in the object. + const targetProp: T[Extract] = + target[key] || ({ [key]: {} } as T[Extract]); + if (target[key] === undefined) target[key] = targetProp; + mergeDeep]>( + targetProp, + [sourceProp], + maxDepth - 1, + ); } else { Object.assign(target, { [key]: source[key] }); } From 8e9e8cdb2aed0a9401cf2010005e7350896dc8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Mon, 27 Nov 2023 12:00:40 -0300 Subject: [PATCH 07/25] fix: completes partial error message --- relayer/middleware/tokenBridge.middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/middleware/tokenBridge.middleware.ts b/relayer/middleware/tokenBridge.middleware.ts index 62a458a3..b1749b17 100644 --- a/relayer/middleware/tokenBridge.middleware.ts +++ b/relayer/middleware/tokenBridge.middleware.ts @@ -142,7 +142,7 @@ export function tokenBridgeContracts(): Middleware { CONTRACTS[ctx.env.toUpperCase() as "MAINNET" | "TESTNET" | "DEVNET"].sui.token_bridge, ); if (fields === null) { - throw new UnrecoverableError("Couldn't read ") + throw new UnrecoverableError("Couldn't read Sui object field"); } suiState = fields; tokenBridgeEmitterCapSui = suiState?.emitter_cap.fields.id.id; From cc6a2f4e74a75f2a0536a5851c835daa19467e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 29 Nov 2023 17:37:12 -0300 Subject: [PATCH 08/25] fix: fixes a few more type errors --- relayer/middleware/missedVaasV3/check.ts | 6 +++--- relayer/middleware/missedVaasV3/storage.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/relayer/middleware/missedVaasV3/check.ts b/relayer/middleware/missedVaasV3/check.ts index 00d05837..36883702 100644 --- a/relayer/middleware/missedVaasV3/check.ts +++ b/relayer/middleware/missedVaasV3/check.ts @@ -264,10 +264,10 @@ export async function registerEventListeners( } async function lookAhead( - lastSeenSequence: bigint, + lastSeenSequence: bigint | undefined, filter: FilterIdentifier, wormholescan: Wormholescan, - maxRetries: number, + maxRetries: number | undefined, maxLookAhead: number = 10, processVaa: ProcessVaaFn, logger?: Logger, @@ -275,7 +275,7 @@ async function lookAhead( const lookAheadSequences: string[] = []; const processed: string[] = []; let failedToRecover: string[] = []; - if (!lastSeenSequence) { + if (lastSeenSequence === undefined) { logger?.warn( `No VAAs seen and no starting sequence was configured. Won't look ahead for missed VAAs.`, ); diff --git a/relayer/middleware/missedVaasV3/storage.ts b/relayer/middleware/missedVaasV3/storage.ts index 19e5f0bc..93c020d1 100644 --- a/relayer/middleware/missedVaasV3/storage.ts +++ b/relayer/middleware/missedVaasV3/storage.ts @@ -139,7 +139,7 @@ export async function tryGetLastSafeSequence( emitterChain: number, emitterAddress: string, logger?: Logger, -): Promise { +): Promise { // since safe sequence is not critical, we'll swallow the error const key = getSafeSequenceKey(prefix, emitterChain, emitterAddress); let lastSafeSequence: string | null; @@ -150,10 +150,10 @@ export async function tryGetLastSafeSequence( `Error getting last safe sequence for chain: ${emitterChain}`, error, ); - return null; + return undefined; } - return lastSafeSequence ? BigInt(lastSafeSequence) : null; + return lastSafeSequence !== null ? BigInt(lastSafeSequence) : undefined; } export async function tryGetExistingFailedSequences( From 171cd40ced6cd75a746f2a9425ef9e14e6cf06c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 29 Nov 2023 17:37:28 -0300 Subject: [PATCH 09/25] misc: enables composite build for typescript compiler --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 98184e25..680e6430 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "downlevelIteration": true, "sourceMap": true, "strict": true, + "composite": true, "allowSyntheticDefaultImports": true }, "include": ["relayer"], From ae2c3a50702a8f0ae29fe0753dfc8095c6205fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Mon, 27 Nov 2023 12:01:12 -0300 Subject: [PATCH 10/25] misc: prettier run --- relayer/application-standard.ts | 13 ++++++++++--- relayer/application.ts | 17 ++++------------- relayer/bundle-fetcher.helper.ts | 19 +++++++++++++++---- relayer/compose.middleware.ts | 6 ++++-- relayer/middleware/metrics.middleware.ts | 4 +++- relayer/middleware/missedVaasV3/check.ts | 8 ++++---- relayer/middleware/providers.middleware.ts | 2 +- relayer/middleware/staging-area.middleware.ts | 9 +++++---- relayer/middleware/tokenBridge.middleware.ts | 3 ++- .../middleware/wallet/wallet-management.ts | 3 ++- relayer/rpc/wormholescan-client.ts | 12 +++++++----- relayer/storage/redis-storage.ts | 3 ++- relayer/utils.ts | 6 ++++-- 13 files changed, 63 insertions(+), 42 deletions(-) diff --git a/relayer/application-standard.ts b/relayer/application-standard.ts index a98f7d21..b14d048d 100644 --- a/relayer/application-standard.ts +++ b/relayer/application-standard.ts @@ -88,12 +88,18 @@ export class StandardRelayerApp< private readonly store: RedisStorage; private readonly mergedRegistry: Registry; - constructor(env: Environment, opts: MakeOptional) { + constructor( + env: Environment, + opts: MakeOptional, + ) { // take logger out before merging because of recursive call stack const logger = opts.logger ?? defaultLogger; delete opts.logger; // now we can merge - const options = mergeDeep({}, [defaultStdOpts, opts]); + const options = mergeDeep({}, [ + defaultStdOpts, + opts, + ]); const { privateKeys, @@ -138,7 +144,8 @@ export class StandardRelayerApp< fetchVaaRetries: options.missedVaaOptions?.fetchVaaRetries, vaasFetchConcurrency: options.missedVaaOptions?.vaasFetchConcurrency, storagePrefix: this.store.getPrefix(), - startingSequenceConfig: options.missedVaaOptions?.startingSequenceConfig, + startingSequenceConfig: + options.missedVaaOptions?.startingSequenceConfig, forceSeenKeysReindex: options.missedVaaOptions?.forceSeenKeysReindex, }); } diff --git a/relayer/application.ts b/relayer/application.ts index 9241fefb..dc11900c 100644 --- a/relayer/application.ts +++ b/relayer/application.ts @@ -25,11 +25,7 @@ import { subscribeSignedVAA, } from "@certusone/wormhole-spydk"; import { UnrecoverableError } from "bullmq"; -import { - encodeEmitterAddress, - mergeDeep, - parseVaaWithBytes, -} from "./utils.js"; +import { encodeEmitterAddress, mergeDeep, parseVaaWithBytes } from "./utils.js"; import { FailFastGrpcTransportFactory } from "./rpc/fail-fast-grpc-transport.js"; import { defaultLogger } from "./logging.js"; import { VaaBundleFetcher, VaaId } from "./bundle-fetcher.helper.js"; @@ -243,9 +239,7 @@ export class RelayerApp extends EventEmitter { if (this.errorPipeline) { middleware.unshift(this.errorPipeline); } - this.errorPipeline = composeError( - middleware, - ); + this.errorPipeline = composeError(middleware); return; } @@ -279,10 +273,7 @@ export class RelayerApp extends EventEmitter { chain, emitterAddress, sequence, - { - retryTimeout = 100, - retries = 2, - } = { + { retryTimeout = 100, retries = 2 } = { retryTimeout: 100, retries: 2, }, @@ -298,7 +289,7 @@ export class RelayerApp extends EventEmitter { ); return parseVaaWithBytes(res.vaaBytes); - } + }; /** * processVaa allows you to put a VAA through the pipeline leveraging storage if needed. diff --git a/relayer/bundle-fetcher.helper.ts b/relayer/bundle-fetcher.helper.ts index d34d7fcc..0fe763e8 100644 --- a/relayer/bundle-fetcher.helper.ts +++ b/relayer/bundle-fetcher.helper.ts @@ -1,9 +1,17 @@ import { ChainId, ParsedVaa } from "@certusone/wormhole-sdk"; import { FetchVaaFn } from "./context.js"; -import { EngineError, MakeOptional, parseVaaWithBytes, sleep } from "./utils.js"; +import { + EngineError, + MakeOptional, + parseVaaWithBytes, + sleep, +} from "./utils.js"; import { ParsedVaaWithBytes } from "./application.js"; -export type VaaId = Pick; +export type VaaId = Pick< + ParsedVaa, + "emitterChain" | "emitterAddress" | "sequence" +>; export type SerializedBatchFetcher = { vaaBytes: string[]; @@ -30,10 +38,13 @@ export class VaaBundleFetcher { private readonly fetchErrors: Record = {}; private opts: VaaBundlerOpts; - constructor(private fetchVaa: FetchVaaFn, opts: MakeOptional) { + constructor( + private fetchVaa: FetchVaaFn, + opts: MakeOptional, + ) { this.opts = { ...defaultOpts, - ...opts + ...opts, }; for (const id of this.opts.vaaIds) { diff --git a/relayer/compose.middleware.ts b/relayer/compose.middleware.ts index e4a899ec..cefbd239 100644 --- a/relayer/compose.middleware.ts +++ b/relayer/compose.middleware.ts @@ -48,8 +48,10 @@ export function composeError( }; } -export function isErrorMiddlewareList(list: any[]): list is ErrorMiddleware[] { - return list.every((f) => { +export function isErrorMiddlewareList( + list: any[], +): list is ErrorMiddleware[] { + return list.every(f => { return f.length > 2; }); } diff --git a/relayer/middleware/metrics.middleware.ts b/relayer/middleware/metrics.middleware.ts index 7f460cf1..a9eb1aec 100644 --- a/relayer/middleware/metrics.middleware.ts +++ b/relayer/middleware/metrics.middleware.ts @@ -175,7 +175,9 @@ export function metrics( await next(); } catch (e) { if (e === undefined) { - failure = new Error("Got thrown undefined while calling next metrics middleware."); + failure = new Error( + "Got thrown undefined while calling next metrics middleware.", + ); } else { failure = e; } diff --git a/relayer/middleware/missedVaasV3/check.ts b/relayer/middleware/missedVaasV3/check.ts index 36883702..38344076 100644 --- a/relayer/middleware/missedVaasV3/check.ts +++ b/relayer/middleware/missedVaasV3/check.ts @@ -62,7 +62,8 @@ export async function checkForMissedVaas( if (seenSequences.length > 0) { const first = - previousSafeSequence !== undefined && previousSafeSequence < seenSequences[0] + previousSafeSequence !== undefined && + previousSafeSequence < seenSequences[0] ? previousSafeSequence : seenSequences[0]; const last = seenSequences[seenSequences.length - 1]; @@ -143,9 +144,8 @@ export async function checkForMissedVaas( // look ahead of greatest seen sequence in case the next vaa was missed // continue looking ahead until a vaa can't be fetched - const lastSeq = seenSequences.length > 0 - ? seenSequences[seenSequences.length - 1] - : null; + const lastSeq = + seenSequences.length > 0 ? seenSequences[seenSequences.length - 1] : null; const lookAheadSequence = lastSeq !== null && startingSeqConfig !== undefined diff --git a/relayer/middleware/providers.middleware.ts b/relayer/middleware/providers.middleware.ts index d40797c9..52d370be 100644 --- a/relayer/middleware/providers.middleware.ts +++ b/relayer/middleware/providers.middleware.ts @@ -267,7 +267,7 @@ async function buildProviders( logger?.error( `Failed to initialize provider for chain: ${chainIdStr} - endpoints: ${maskRPCEndpoints( endpoints, - )}. Error: ${printError(error)}` + )}. Error: ${printError(error)}`, ); throw error; } diff --git a/relayer/middleware/staging-area.middleware.ts b/relayer/middleware/staging-area.middleware.ts index 9a43ede7..b0b612e9 100644 --- a/relayer/middleware/staging-area.middleware.ts +++ b/relayer/middleware/staging-area.middleware.ts @@ -31,14 +31,15 @@ export function stagingArea( const options = { redis: { host: "localhost", port: 6379 }, ...opts, - } + }; // TODO: maybe refactor redis pool for all plugins that rely on it. const factory = { create: async function () { - const redis = "redisCluster" in opts - ? new Redis.Cluster(opts.redisClusterEndpoints, opts.redisCluster) - : new Redis(options.redis); + const redis = + "redisCluster" in opts + ? new Redis.Cluster(opts.redisClusterEndpoints, opts.redisCluster) + : new Redis(options.redis); return redis; }, destroy: async function () { diff --git a/relayer/middleware/tokenBridge.middleware.ts b/relayer/middleware/tokenBridge.middleware.ts index b1749b17..7813315e 100644 --- a/relayer/middleware/tokenBridge.middleware.ts +++ b/relayer/middleware/tokenBridge.middleware.ts @@ -139,7 +139,8 @@ export function tokenBridgeContracts(): Middleware { if (suiState === undefined && ctx.providers.sui.length > 0) { const fields = await getObjectFields( ctx.providers.sui[0], - CONTRACTS[ctx.env.toUpperCase() as "MAINNET" | "TESTNET" | "DEVNET"].sui.token_bridge, + CONTRACTS[ctx.env.toUpperCase() as "MAINNET" | "TESTNET" | "DEVNET"].sui + .token_bridge, ); if (fields === null) { throw new UnrecoverableError("Couldn't read Sui object field"); diff --git a/relayer/middleware/wallet/wallet-management.ts b/relayer/middleware/wallet/wallet-management.ts index 0f273bff..38edaf60 100644 --- a/relayer/middleware/wallet/wallet-management.ts +++ b/relayer/middleware/wallet/wallet-management.ts @@ -30,7 +30,8 @@ import { } from "@certusone/wormhole-sdk/lib/cjs/utils/consts.js"; import { Environment } from "../../environment.js"; -export type MetricsOptions = (WalletManagerFullConfig["options"] & {}) ["metrics"] & {}; +export type MetricsOptions = + (WalletManagerFullConfig["options"] & {})["metrics"] & {}; const networks = { [Environment.MAINNET]: { diff --git a/relayer/rpc/wormholescan-client.ts b/relayer/rpc/wormholescan-client.ts index 73277059..f2033ba0 100644 --- a/relayer/rpc/wormholescan-client.ts +++ b/relayer/rpc/wormholescan-client.ts @@ -139,8 +139,10 @@ export type WormholescanVaa = { txHash?: string; }; -export type WormholescanResult = { - error: HttpClientError; -} | { - data: T; -}; +export type WormholescanResult = + | { + error: HttpClientError; + } + | { + data: T; + }; diff --git a/relayer/storage/redis-storage.ts b/relayer/storage/redis-storage.ts index 2069f1df..9e1dc271 100644 --- a/relayer/storage/redis-storage.ts +++ b/relayer/storage/redis-storage.ts @@ -109,7 +109,8 @@ export class RedisStorage implements Storage { this.opts.redis.maxRetriesPerRequest = null; //Added because of: DEPRECATION WARNING! Your redis options maxRetriesPerRequest must be null. On the next versions having this settings will throw an exception this.prefix = `{${this.opts.namespace ?? this.opts.queueName}}`; this.redis = - this.opts.redisClusterEndpoints !== undefined && this.opts.redisClusterEndpoints.length > 0 + this.opts.redisClusterEndpoints !== undefined && + this.opts.redisClusterEndpoints.length > 0 ? new Redis.Cluster( this.opts.redisClusterEndpoints, this.opts.redisCluster, diff --git a/relayer/utils.ts b/relayer/utils.ts index 845bcb93..8d421a51 100644 --- a/relayer/utils.ts +++ b/relayer/utils.ts @@ -14,7 +14,9 @@ import { ParsedVaaWithBytes } from "./application.js"; import { ethers } from "ethers"; import { inspect } from "util"; -export type MakeOptional = Omit & Partial>> & Partial>; +export type MakeOptional = Omit & + Partial>> & + Partial>; export function encodeEmitterAddress( chainId: wormholeSdk.ChainId, @@ -235,4 +237,4 @@ export function min(lhs: bigint, rhs: bigint): bigint { export function max(lhs: bigint, rhs: bigint): bigint { return lhs < rhs ? rhs : lhs; -} \ No newline at end of file +} From 889ab25483e6e6cebdac53b94c80e05843f7850e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 29 Nov 2023 21:18:51 -0300 Subject: [PATCH 11/25] misc: removes use of bs58 --- relayer/middleware/wallet/wallet-management.ts | 4 ++-- relayer/middleware/wallet/walletToolBox.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/relayer/middleware/wallet/wallet-management.ts b/relayer/middleware/wallet/wallet-management.ts index 38edaf60..e6f41605 100644 --- a/relayer/middleware/wallet/wallet-management.ts +++ b/relayer/middleware/wallet/wallet-management.ts @@ -5,7 +5,7 @@ import { WalletManagerFullConfig, } from "@xlabs-xyz/wallet-monitor"; import { Logger } from "winston"; -import * as bs58 from "bs58"; +import { ethers } from "ethers"; import { CHAIN_ID_BSC, @@ -106,7 +106,7 @@ function buildWalletsConfig( try { secretKey = new Uint8Array(JSON.parse(key)); } catch (e) { - secretKey = bs58.decode(key); + secretKey = ethers.utils.base58.decode(key); } chainWallets.push({ diff --git a/relayer/middleware/wallet/walletToolBox.ts b/relayer/middleware/wallet/walletToolBox.ts index f5425cc9..d3f69582 100644 --- a/relayer/middleware/wallet/walletToolBox.ts +++ b/relayer/middleware/wallet/walletToolBox.ts @@ -1,5 +1,4 @@ import * as wh from "@certusone/wormhole-sdk"; -import * as bs58 from "bs58"; import { ethers } from "ethers"; import * as solana from "@solana/web3.js"; import { Providers } from "../providers.middleware.js"; @@ -32,7 +31,7 @@ export async function createWalletToolbox( case wh.CHAIN_ID_SOLANA: let secretKey; try { - secretKey = bs58.decode(privateKey); + secretKey = ethers.utils.base58.decode(privateKey); } catch (e) { secretKey = new Uint8Array(JSON.parse(privateKey)); } From be2dbd4714fda65d04c7a7aa6473354f572c94a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Wed, 29 Nov 2023 21:19:24 -0300 Subject: [PATCH 12/25] misc: removes obsolete dependencies --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index e120040a..1ec46d3d 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,6 @@ "@certusone/wormhole-sdk-proto-node": "^0.0.6", "@cloudnc/grpc-web-testing-toolbox": "^2.2.0", "@jest/globals": "^29.6.4", - "@types/bluebird": "^3.5.38", - "@types/bs58": "^4.0.1", "@types/jest": "^29.5.4", "@types/koa": "^2.13.8", "@types/node": "^20.6.0", From 8539df3361b0f3527eb75f60dc78db0a1bd36534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 14:48:05 -0300 Subject: [PATCH 13/25] misc: adds typed-asserts to type some asserts in our tests --- package-lock.json | 56 +++++++++++++++++------------------------------ package.json | 5 +++-- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 88073478..c807c851 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,8 +32,6 @@ "@certusone/wormhole-sdk-proto-node": "^0.0.6", "@cloudnc/grpc-web-testing-toolbox": "^2.2.0", "@jest/globals": "^29.6.4", - "@types/bluebird": "^3.5.38", - "@types/bs58": "^4.0.1", "@types/jest": "^29.5.4", "@types/koa": "^2.13.8", "@types/node": "^20.6.0", @@ -41,7 +39,8 @@ "jest": "^29.6.4", "prettier": "^2.8.8", "ts-jest": "^29.1.1", - "typescript": "^5.2.2" + "typed-assert": "^1.0.9", + "typescript": "^5.3.2" } }, "node_modules/@ampproject/remapping": { @@ -4224,12 +4223,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/bluebird": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.38.tgz", - "integrity": "sha512-yR/Kxc0dd4FfwtEoLZMoqJbM/VE/W7hXn/MIjb+axcwag0iFmSPK7OBUZq1YWLynJUoWQkfUrI7T0HDqGApNSg==", - "dev": true - }, "node_modules/@types/bn.js": { "version": "5.1.1", "license": "MIT", @@ -4246,14 +4239,6 @@ "@types/node": "*" } }, - "node_modules/@types/bs58": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "base-x": "^3.0.6" - } - }, "node_modules/@types/connect": { "version": "3.4.35", "license": "MIT", @@ -11251,14 +11236,20 @@ "node": ">= 0.6" } }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, "node_modules/typeforce": { "version": "1.18.0", "license": "MIT" }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14895,12 +14886,6 @@ "@babel/types": "^7.20.7" } }, - "@types/bluebird": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.38.tgz", - "integrity": "sha512-yR/Kxc0dd4FfwtEoLZMoqJbM/VE/W7hXn/MIjb+axcwag0iFmSPK7OBUZq1YWLynJUoWQkfUrI7T0HDqGApNSg==", - "dev": true - }, "@types/bn.js": { "version": "5.1.1", "requires": { @@ -14915,13 +14900,6 @@ "@types/node": "*" } }, - "@types/bs58": { - "version": "4.0.1", - "dev": true, - "requires": { - "base-x": "^3.0.6" - } - }, "@types/connect": { "version": "3.4.35", "requires": { @@ -19866,13 +19844,19 @@ "mime-types": "~2.1.24" } }, + "typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, "typeforce": { "version": "1.18.0" }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==" }, "u3": { "version": "0.1.1" diff --git a/package.json b/package.json index 1ec46d3d..267ddf6d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test-watch": "jest --silent=false --watch", "build": "tsc --build ./tsconfig.cjs.json && tsc --build ./tsconfig.esm.json && bin/create-package.json.sh", "watch": "tsc --watch --project ./tsconfig.esm.json", - "typecheck": "tsc --noEmit --skipLibCheck --project ./tsconfig.esm.json", + "typecheck": "tsc --build ./tsconfig.esm.json && tsc --build ./test/tsconfig.json", "prettier": "prettier --write $(git diff main --name-only --diff-filter u | grep '.ts$' | xargs)", "mainnet-spy": "docker run --platform=linux/amd64 -p 7073:7073 --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest spy --nodeKey /node.key --spyRPC \"[::]:7073\" --network /wormhole/mainnet/2 --bootstrap /dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7", "testnet-spy": "docker run --platform=linux/amd64 -p 7073:7073 --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest spy --nodeKey /node.key --spyRPC \"[::]:7073\" --network /wormhole/testnet/2/1 --bootstrap /dns4/wormhole-testnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWAkB9ynDur1Jtoa97LBUp8RXdhzS5uHgAfdTquJbrbN7i", @@ -64,6 +64,7 @@ "jest": "^29.6.4", "prettier": "^2.8.8", "ts-jest": "^29.1.1", - "typescript": "^5.2.2" + "typed-assert": "^1.0.9", + "typescript": "^5.3.2" } } From a48fd5ad85280fe377645aeb561eeb6e5310c856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 14:50:32 -0300 Subject: [PATCH 14/25] misc: adds tsconfig for building tests --- .gitignore | 5 ++++- test/tsconfig.json | 10 ++++++++++ tsconfig.cjs.json | 9 ++++++++- tsconfig.esm.json | 9 ++++++++- tsconfig.json | 9 +-------- 5 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 test/tsconfig.json diff --git a/.gitignore b/.gitignore index f7e6cde0..70551d32 100644 --- a/.gitignore +++ b/.gitignore @@ -79,4 +79,7 @@ jspm_packages/ .pnp.* .idea -.vscode \ No newline at end of file +.vscode + +# Test typescript output +test/out \ No newline at end of file diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..90c59009 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "..", + "outDir": "out", + "module": "NodeNext" + }, + "include": [".", "../relayer"], + "exclude": [] +} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 7207b011..b6841410 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -4,5 +4,12 @@ "outDir": "./lib/cjs", "module": "CommonJS", "moduleResolution": "node" - } + }, + "include": ["relayer"], + "exclude": [ + "node_modules", + "**/__tests__/*", + "relayer/**/*.test.ts", + "test/*" + ] } diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 4ea9661c..b065cf8b 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -3,5 +3,12 @@ "compilerOptions": { "outDir": "./lib/esm", "module": "NodeNext" - } + }, + "include": ["relayer"], + "exclude": [ + "node_modules", + "**/__tests__/*", + "relayer/**/*.test.ts", + "test/*" + ] } diff --git a/tsconfig.json b/tsconfig.json index 680e6430..7b028ae5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,12 +16,5 @@ "strict": true, "composite": true, "allowSyntheticDefaultImports": true - }, - "include": ["relayer"], - "exclude": [ - "node_modules", - "**/__tests__/*", - "relayer/**/*.test.ts", - "test/*" - ] + } } From 050c1ea284bcbd47201eed2c1bb0df84c1615a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 14:51:38 -0300 Subject: [PATCH 15/25] fix: fixes type errors in tests --- test/get-signed-vaa.integration.test.ts | 4 +- test/infrastructure/mock-wormscan-api.ts | 2 +- test/middleware/metrics.middleware.test.ts | 14 +++---- test/middleware/missedVaasV3/check.test.ts | 11 +++--- test/middleware/missedVaasV3/helpers.test.ts | 34 +++++++--------- test/middleware/missedVaasV3/storage.test.ts | 2 +- test/middleware/missedVaasV3/worker.test.ts | 20 +++++----- test/redis-storage.test.ts | 4 +- .../wormholescan-client.integration.test.ts | 39 +++++++++++++++---- test/utils/map-concurrent.test.ts | 2 +- 10 files changed, 78 insertions(+), 54 deletions(-) diff --git a/test/get-signed-vaa.integration.test.ts b/test/get-signed-vaa.integration.test.ts index bfb7be91..1f537f3a 100644 --- a/test/get-signed-vaa.integration.test.ts +++ b/test/get-signed-vaa.integration.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeAll, beforeEach, test } from "@jest/globals"; import { getSignedVAA } from "@certusone/wormhole-sdk"; -import { FailFastGrpcTransportFactory } from "../relayer/rpc/fail-fast-grpc-transport"; -import { WormholeMock } from "./infrastructure/mock-wormscan-api"; +import { FailFastGrpcTransportFactory } from "../relayer/rpc/fail-fast-grpc-transport.js"; +import { WormholeMock } from "./infrastructure/mock-wormscan-api.js"; describe("getSignedVaa", () => { const server = new WormholeMock(); diff --git a/test/infrastructure/mock-wormscan-api.ts b/test/infrastructure/mock-wormscan-api.ts index 5aa9bd11..5d9b8d5c 100644 --- a/test/infrastructure/mock-wormscan-api.ts +++ b/test/infrastructure/mock-wormscan-api.ts @@ -1,7 +1,7 @@ import * as http from "http"; import { setTimeout } from "timers/promises"; import { grpcResponseToBuffer } from "@cloudnc/grpc-web-testing-toolbox/base"; -import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-node/lib/cjs/publicrpc/v1/publicrpc"; +import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-node/lib/esm/publicrpc/v1/publicrpc.js"; type WormholeMockConfig = { uri: string; diff --git a/test/middleware/metrics.middleware.test.ts b/test/middleware/metrics.middleware.test.ts index f0f18ba7..4701f34c 100644 --- a/test/middleware/metrics.middleware.test.ts +++ b/test/middleware/metrics.middleware.test.ts @@ -4,14 +4,14 @@ import { MetricLabelsOpts, metrics, MetricsOpts, -} from "../../relayer/middleware/metrics.middleware"; -import { Middleware, Next } from "../../relayer/compose.middleware"; +} from "../../relayer/middleware/metrics.middleware.js"; +import { Middleware, Next } from "../../relayer/compose.middleware.js"; import { parseVaa } from "@certusone/wormhole-sdk"; -import { StorageContext } from "../../relayer/storage/storage"; -import { ParsedVaaWithBytes } from "../../relayer/application"; -import { Environment } from "../../relayer/environment"; -import { sleep } from "../../relayer/utils"; -import { VaaFactory } from "../vaa-factory"; +import { StorageContext } from "../../relayer/storage/storage.js"; +import { ParsedVaaWithBytes } from "../../relayer/application.js"; +import { Environment } from "../../relayer/environment.js"; +import { sleep } from "../../relayer/utils.js"; +import { VaaFactory } from "../vaa-factory.js"; type TestContext = StorageContext & { target?: string }; diff --git a/test/middleware/missedVaasV3/check.test.ts b/test/middleware/missedVaasV3/check.test.ts index ef079e77..d12ad240 100644 --- a/test/middleware/missedVaasV3/check.test.ts +++ b/test/middleware/missedVaasV3/check.test.ts @@ -3,19 +3,19 @@ import { jest, describe, test, beforeEach } from "@jest/globals"; import { ProcessVaaFn, checkForMissedVaas, -} from "../../../relayer/middleware/missedVaasV3/check"; +} from "../../../relayer/middleware/missedVaasV3/check.js"; import { batchMarkAsSeen, batchMarkAsFailedToRecover, getAllProcessedSeqsInOrder, -} from "../../../relayer/middleware/missedVaasV3/storage"; -import { tryFetchVaa } from "../../../relayer/middleware/missedVaasV3/helpers"; +} from "../../../relayer/middleware/missedVaasV3/storage.js"; +import { tryFetchVaa } from "../../../relayer/middleware/missedVaasV3/helpers.js"; import { Redis } from "ioredis"; import { Wormholescan, WormholescanVaa, -} from "../../../relayer/rpc/wormholescan-client"; -import { HttpClientError } from "../../../relayer/rpc/http-client"; +} from "../../../relayer/rpc/wormholescan-client.js"; +import { HttpClientError } from "../../../relayer/rpc/http-client.js"; jest.mock("../../../relayer/middleware/missedVaasV3/storage"); jest.mock("../../../relayer/middleware/missedVaasV3/helpers"); @@ -91,6 +91,7 @@ describe("MissedVaaV3.check", () => { storagePrefix: prefix, startingSequenceConfig: {}, maxLookAhead: 10, + wormholeRpcs: [], }; return { diff --git a/test/middleware/missedVaasV3/helpers.test.ts b/test/middleware/missedVaasV3/helpers.test.ts index 30540ee0..a70199c5 100644 --- a/test/middleware/missedVaasV3/helpers.test.ts +++ b/test/middleware/missedVaasV3/helpers.test.ts @@ -1,10 +1,6 @@ import { jest, describe, test } from "@jest/globals"; -import { - calculateSequenceStats, - MissedVaaRunStats, - SequenceStats, -} from "../../../relayer/middleware/missedVaasV3/helpers"; +import { calculateSequenceStats } from "../../../relayer/middleware/missedVaasV3/helpers.js"; describe("MissedVaaV3.helpers", () => { afterEach(() => { @@ -30,14 +26,14 @@ describe("MissedVaaV3.helpers", () => { return { runStats, failedToFetchSequences, previousSafeSequence }; } - test("lastSafeSequence: if there are missing sequences, lastSafeSequence is the last sequence sequence failed to fetch", async () => { + test("lastSafeSequence: if there are missing sequences, lastSafeSequence is the last sequence sequence failed to fetch", () => { const failedToFetchSequenceMock = 100; const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest({ failedToFetchSequences: [100], }); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, @@ -46,7 +42,7 @@ describe("MissedVaaV3.helpers", () => { expect(result.lastSafeSequence).toEqual(failedToFetchSequenceMock - 1); }); - test("lastSafeSequence: if sequences failed to recover, lastSafeSequence is the last sequence before the missing sequence", async () => { + test("lastSafeSequence: if sequences failed to recover, lastSafeSequence is the last sequence before the missing sequence", () => { const missingSequenceMock = 100; const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest({ @@ -56,7 +52,7 @@ describe("MissedVaaV3.helpers", () => { }, }); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, @@ -65,7 +61,7 @@ describe("MissedVaaV3.helpers", () => { expect(result.lastSafeSequence).toEqual(missingSequenceMock - 1); }); - test("lastSafeSequence: if sequences failed to re-process, lastSafeSequence is the last sequence before the missing sequence", async () => { + test("lastSafeSequence: if sequences failed to re-process, lastSafeSequence is the last sequence before the missing sequence", () => { const missingSequenceMock = 100; const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest({ @@ -75,7 +71,7 @@ describe("MissedVaaV3.helpers", () => { }, }); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, @@ -84,7 +80,7 @@ describe("MissedVaaV3.helpers", () => { expect(result.lastSafeSequence).toEqual(missingSequenceMock - 1); }); - test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead found any vaa it's sequence will be used as the last safe sequence", async () => { + test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead found any vaa it's sequence will be used as the last safe sequence", () => { const greatestLookAheadSequence = 103; const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest({ @@ -93,7 +89,7 @@ describe("MissedVaaV3.helpers", () => { }, }); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, @@ -102,14 +98,14 @@ describe("MissedVaaV3.helpers", () => { expect(result.lastSafeSequence).toEqual(greatestLookAheadSequence); }); - test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead didnt find vaas but there are seen VAAs we'll use the greatest seen vaa sequence", async () => { + test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead didnt find vaas but there are seen VAAs we'll use the greatest seen vaa sequence", () => { const greatestSeenSequence = 103; const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest({ runStats: { seenSequences: [101, 102, greatestSeenSequence] }, }); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, @@ -118,14 +114,14 @@ describe("MissedVaaV3.helpers", () => { expect(result.lastSafeSequence).toEqual(greatestSeenSequence); }); - test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead didnt find vaas and there are no seen sequences, we'll use the previous safe sequence if there is one", async () => { + test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead didnt find vaas and there are no seen sequences, we'll use the previous safe sequence if there is one", () => { const safeSequenceMock = 105n; const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest({ previousSafeSequence: safeSequenceMock, }); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, @@ -136,11 +132,11 @@ describe("MissedVaaV3.helpers", () => { ); }); - test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead didnt find vaas and there are no seen sequences nor previous safe sequence, we'll set safe sequence to 0", async () => { + test("lastSafeSequence: if no sequence present failures (fetch, recover or reprocess) and lookahead didnt find vaas and there are no seen sequences nor previous safe sequence, we'll set safe sequence to 0", () => { const { runStats, failedToFetchSequences, previousSafeSequence } = prepareTest(); - const result = await calculateSequenceStats( + const result = calculateSequenceStats( runStats, failedToFetchSequences, previousSafeSequence, diff --git a/test/middleware/missedVaasV3/storage.test.ts b/test/middleware/missedVaasV3/storage.test.ts index 9f78f76e..3fc13858 100644 --- a/test/middleware/missedVaasV3/storage.test.ts +++ b/test/middleware/missedVaasV3/storage.test.ts @@ -9,7 +9,7 @@ import { tryGetExistingFailedSequences, getAllProcessedSeqsInOrder, calculateStartingIndex, -} from "../../../relayer/middleware/missedVaasV3/storage"; +} from "../../../relayer/middleware/missedVaasV3/storage.js"; import { Logger } from "winston"; describe("MissedVaaV3.storage", () => { diff --git a/test/middleware/missedVaasV3/worker.test.ts b/test/middleware/missedVaasV3/worker.test.ts index 279b6d86..5eee7a56 100644 --- a/test/middleware/missedVaasV3/worker.test.ts +++ b/test/middleware/missedVaasV3/worker.test.ts @@ -3,22 +3,19 @@ import { jest, describe, test } from "@jest/globals"; import { ProcessVaaFn, checkForMissedVaas, -} from "../../../relayer/middleware/missedVaasV3/check"; +} from "../../../relayer/middleware/missedVaasV3/check.js"; import { tryGetLastSafeSequence, trySetLastSafeSequence, updateSeenSequences, tryGetExistingFailedSequences, -} from "../../../relayer/middleware/missedVaasV3/storage"; -import { calculateSequenceStats } from "../../../relayer/middleware/missedVaasV3/helpers"; -import { runMissedVaaCheck } from "../../../relayer/middleware/missedVaasV3/worker"; +} from "../../../relayer/middleware/missedVaasV3/storage.js"; +import { calculateSequenceStats } from "../../../relayer/middleware/missedVaasV3/helpers.js"; +import { runMissedVaaCheck } from "../../../relayer/middleware/missedVaasV3/worker.js"; import { Redis } from "ioredis"; import { Logger } from "winston"; -import { - Wormholescan, - WormholescanVaa, -} from "../../../relayer/rpc/wormholescan-client"; +import { WormholescanVaa } from "../../../relayer/rpc/wormholescan-client.js"; jest.mock("../../../relayer/middleware/missedVaasV3/storage"); jest.mock("../../../relayer/middleware/missedVaasV3/helpers"); @@ -87,7 +84,12 @@ describe("MissedVaaV3.worker", () => { const emitterChain = 1; const emitterAddress = "foo"; const prefix = "bar"; - const opts = {}; + const opts = { + storagePrefix: prefix, + startingSequenceConfig: {}, + maxLookAhead: 10, + wormholeRpcs: [], + }; if (useStoragePrefix) Object.assign(opts, { storagePrefix: prefix }); diff --git a/test/redis-storage.test.ts b/test/redis-storage.test.ts index c610f955..3d964080 100644 --- a/test/redis-storage.test.ts +++ b/test/redis-storage.test.ts @@ -1,6 +1,6 @@ import { describe } from "@jest/globals"; -import { RedisStorage } from "../relayer/storage/redis-storage"; -import { VaaFactory } from "./vaa-factory"; +import { RedisStorage } from "../relayer/storage/redis-storage.js"; +import { VaaFactory } from "./vaa-factory.js"; jest.mock("ioredis", () => { const redis = class RedisMock { diff --git a/test/rpc/wormholescan-client.integration.test.ts b/test/rpc/wormholescan-client.integration.test.ts index d8919815..a37f6e53 100644 --- a/test/rpc/wormholescan-client.integration.test.ts +++ b/test/rpc/wormholescan-client.integration.test.ts @@ -1,11 +1,22 @@ import { beforeEach, beforeAll, afterAll, describe, test } from "@jest/globals"; -import { WormholescanClient } from "../../relayer/rpc/wormholescan-client"; -import { WormholeMock } from "../infrastructure/mock-wormscan-api"; -import { HttpClientError } from "../../relayer/rpc/http-client"; +import { WormholescanClient } from "../../relayer/rpc/wormholescan-client.js"; +import { WormholeMock } from "../infrastructure/mock-wormscan-api.js"; +import { HttpClientError } from "../../relayer/rpc/http-client.js"; +import tassert from "typed-assert"; let client: WormholescanClient; let timeout: number = 100; // 100ms +function assertPropertyIsDefined( + x: T, + prop: K, + message: string, +): asserts x is Extract { + if (typeof x !== "object" || x === null || !(prop in x)) { + throw new Error(message); + } +} + describe("wormholescan-client", () => { const server = new WormholeMock(); let url: string; @@ -28,9 +39,13 @@ describe("wormholescan-client", () => { 8, "6241ffdc032b693bfb8544858f0403dec86f2e1720af9f34f8d65fe574b6238c", ); - expect(vaasResponse?.data).toBeDefined(); - expect(vaasResponse?.data?.length).toBeGreaterThan(0); - expect(vaasResponse?.data[0].vaa.toString()).not.toContain("object"); + assertPropertyIsDefined( + vaasResponse, + "data", + "Data field is missing from response.", + ); + expect(vaasResponse.data?.length).toBeGreaterThan(0); + expect(vaasResponse.data[0].vaa.toString()).not.toContain("object"); }); test("should fail if request fails and no retries set", async () => { @@ -38,6 +53,11 @@ describe("wormholescan-client", () => { server.respondWith(expectedStatus, { message: "Internal Server Error" }); const vaasResponse = await client.listVaas(8, "100", { retries: 0 }); + assertPropertyIsDefined( + vaasResponse, + "error", + "Expected an internal server error.", + ); expect((vaasResponse.error as HttpClientError).status).toBe(expectedStatus); }); @@ -57,7 +77,7 @@ describe("wormholescan-client", () => { const vaasResponse = await client.listVaas(8, "1000", { retries: 2 }); - expect(vaasResponse.error).toBeUndefined(); + assertPropertyIsDefined(vaasResponse, "data", "Expected a valid response."); expect(JSON.stringify(vaasResponse.data)).toBe( JSON.stringify(expectedData), ); @@ -75,6 +95,11 @@ describe("wormholescan-client", () => { maxDelay: 500, }); + assertPropertyIsDefined( + vaasResponse, + "error", + "Expected a 'too many requests' error.", + ); expect((vaasResponse.error as HttpClientError).status).toBe(429); }); }); diff --git a/test/utils/map-concurrent.test.ts b/test/utils/map-concurrent.test.ts index 705f420f..ef67a4eb 100644 --- a/test/utils/map-concurrent.test.ts +++ b/test/utils/map-concurrent.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "@jest/globals"; -import { mapConcurrent } from "../../relayer/utils"; +import { mapConcurrent } from "../../relayer/utils.js"; describe("mapConcurrent", () => { it("should map when concurrency below array length", async () => { From 3251d7dc87cd7ff1e4c566455b8ca0da13e4aa51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 15:04:55 -0300 Subject: [PATCH 16/25] ci: fix tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 267ddf6d..53182370 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "redis": "docker run --rm -p 6379:6379 --name redis-docker -d redis", "test-redis": "docker run --rm -p 6301:6379 --name relayer-engine-test -d redis; npm run test; docker kill relayer-engine-test", - "test": "jest --silent=false", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --silent=false", "test-watch": "jest --silent=false --watch", "build": "tsc --build ./tsconfig.cjs.json && tsc --build ./tsconfig.esm.json && bin/create-package.json.sh", "watch": "tsc --watch --project ./tsconfig.esm.json", From eef39ac42f213be5674742bdb29fad57c247365a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 15:18:48 -0300 Subject: [PATCH 17/25] ci: fixes jest ESM compatibility --- jest.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.js b/jest.config.js index 19a9fd96..89f11857 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,7 @@ export default { moduleNameMapper: { "^(\\.\\.?\\/.+)\\.js$": "$1", }, + extensionsToTreatAsEsm: [".ts"], transform: { // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` From 924d005ae066585298236daaa40284ffa71415e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 20:23:24 -0300 Subject: [PATCH 18/25] ci: fixes tests --- jest.config.js | 2 +- package-lock.json | 18 +++++++++--------- package.json | 8 ++++---- test/tsconfig.json | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/jest.config.js b/jest.config.js index 89f11857..cfbf7690 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ export default { - preset: "ts-jest/presets/default-esm", // or other ESM presets + preset: "ts-jest/presets/js-with-ts-esm", // or other ESM presets testEnvironment: "node", moduleNameMapper: { "^(\\.\\.?\\/.+)\\.js$": "$1", diff --git a/package-lock.json b/package-lock.json index c807c851..3690fda3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,12 +31,12 @@ "devDependencies": { "@certusone/wormhole-sdk-proto-node": "^0.0.6", "@cloudnc/grpc-web-testing-toolbox": "^2.2.0", - "@jest/globals": "^29.6.4", - "@types/jest": "^29.5.4", + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.10", "@types/koa": "^2.13.8", "@types/node": "^20.6.0", "@types/winston": "^2.4.4", - "jest": "^29.6.4", + "jest": "^29.7.0", "prettier": "^2.8.8", "ts-jest": "^29.1.1", "typed-assert": "^1.0.9", @@ -4324,9 +4324,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.6", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", - "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", + "version": "29.5.10", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.10.tgz", + "integrity": "sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -14975,9 +14975,9 @@ } }, "@types/jest": { - "version": "29.5.6", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.6.tgz", - "integrity": "sha512-/t9NnzkOpXb4Nfvg17ieHE6EeSjDS2SGSpNYfoLbUAeL/EOueU/RSdOWFpfQTXBEM7BguYW1XQ0EbM+6RlIh6w==", + "version": "29.5.10", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.10.tgz", + "integrity": "sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==", "dev": true, "requires": { "expect": "^29.0.0", diff --git a/package.json b/package.json index 53182370..ad4e25c7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "redis": "docker run --rm -p 6379:6379 --name redis-docker -d redis", "test-redis": "docker run --rm -p 6301:6379 --name relayer-engine-test -d redis; npm run test; docker kill relayer-engine-test", - "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --silent=false", + "test": "jest --silent=false", "test-watch": "jest --silent=false --watch", "build": "tsc --build ./tsconfig.cjs.json && tsc --build ./tsconfig.esm.json && bin/create-package.json.sh", "watch": "tsc --watch --project ./tsconfig.esm.json", @@ -56,12 +56,12 @@ "devDependencies": { "@certusone/wormhole-sdk-proto-node": "^0.0.6", "@cloudnc/grpc-web-testing-toolbox": "^2.2.0", - "@jest/globals": "^29.6.4", - "@types/jest": "^29.5.4", + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.10", "@types/koa": "^2.13.8", "@types/node": "^20.6.0", "@types/winston": "^2.4.4", - "jest": "^29.6.4", + "jest": "^29.7.0", "prettier": "^2.8.8", "ts-jest": "^29.1.1", "typed-assert": "^1.0.9", diff --git a/test/tsconfig.json b/test/tsconfig.json index 90c59009..1f20ca87 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "rootDir": "..", - "outDir": "out", + "outDir": "../test-out", "module": "NodeNext" }, "include": [".", "../relayer"], From 752546412a188432949c4aaaa3c258ffbff27b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 20:23:48 -0300 Subject: [PATCH 19/25] fix: fixes edge case where the sequence is not set --- relayer/middleware/missedVaasV3/storage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relayer/middleware/missedVaasV3/storage.ts b/relayer/middleware/missedVaasV3/storage.ts index 93c020d1..88225fd7 100644 --- a/relayer/middleware/missedVaasV3/storage.ts +++ b/relayer/middleware/missedVaasV3/storage.ts @@ -142,7 +142,7 @@ export async function tryGetLastSafeSequence( ): Promise { // since safe sequence is not critical, we'll swallow the error const key = getSafeSequenceKey(prefix, emitterChain, emitterAddress); - let lastSafeSequence: string | null; + let lastSafeSequence: string | null | undefined; try { lastSafeSequence = await redis.get(key); } catch (error) { @@ -153,7 +153,7 @@ export async function tryGetLastSafeSequence( return undefined; } - return lastSafeSequence !== null ? BigInt(lastSafeSequence) : undefined; + return lastSafeSequence !== null && lastSafeSequence !== undefined ? BigInt(lastSafeSequence) : undefined; } export async function tryGetExistingFailedSequences( From 2870f3135c6e4716bf29d5347a41a3cf0ef23517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 20:25:37 -0300 Subject: [PATCH 20/25] ci: fixes tests --- test/infrastructure/mock-wormscan-api.ts | 4 +++- test/middleware/metrics.middleware.test.ts | 2 +- test/middleware/missedVaasV3/check.test.ts | 6 +++--- test/middleware/missedVaasV3/helpers.test.ts | 2 +- test/middleware/missedVaasV3/storage.test.ts | 14 +++++++------- test/middleware/missedVaasV3/worker.test.ts | 2 +- test/redis-storage.test.ts | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/test/infrastructure/mock-wormscan-api.ts b/test/infrastructure/mock-wormscan-api.ts index 5d9b8d5c..a88feb83 100644 --- a/test/infrastructure/mock-wormscan-api.ts +++ b/test/infrastructure/mock-wormscan-api.ts @@ -1,7 +1,7 @@ import * as http from "http"; import { setTimeout } from "timers/promises"; import { grpcResponseToBuffer } from "@cloudnc/grpc-web-testing-toolbox/base"; -import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-node/lib/esm/publicrpc/v1/publicrpc.js"; +import { GetSignedVAAResponse } from "@certusone/wormhole-sdk-proto-node/lib/cjs/publicrpc/v1/publicrpc.js"; type WormholeMockConfig = { uri: string; @@ -50,6 +50,7 @@ export class WormholeMock { }).finish(), }), ); + return; } if (req.url?.includes("api/v1/vaas") && req.url?.includes("pageSize")) { @@ -70,6 +71,7 @@ export class WormholeMock { ], }), ); + return; } res.writeHead(404); diff --git a/test/middleware/metrics.middleware.test.ts b/test/middleware/metrics.middleware.test.ts index 4701f34c..2cd703bf 100644 --- a/test/middleware/metrics.middleware.test.ts +++ b/test/middleware/metrics.middleware.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, test } from "@jest/globals"; +import { beforeEach, describe, test, jest } from "@jest/globals"; import { MetricValue, Registry } from "prom-client"; import { MetricLabelsOpts, diff --git a/test/middleware/missedVaasV3/check.test.ts b/test/middleware/missedVaasV3/check.test.ts index d12ad240..b4fe4d1d 100644 --- a/test/middleware/missedVaasV3/check.test.ts +++ b/test/middleware/missedVaasV3/check.test.ts @@ -1,4 +1,4 @@ -import { jest, describe, test, beforeEach } from "@jest/globals"; +import { jest, describe, test, afterEach, beforeEach } from "@jest/globals"; import { ProcessVaaFn, @@ -17,8 +17,8 @@ import { } from "../../../relayer/rpc/wormholescan-client.js"; import { HttpClientError } from "../../../relayer/rpc/http-client.js"; -jest.mock("../../../relayer/middleware/missedVaasV3/storage"); -jest.mock("../../../relayer/middleware/missedVaasV3/helpers"); +jest.mock("../../../relayer/middleware/missedVaasV3/storage.js"); +jest.mock("../../../relayer/middleware/missedVaasV3/helpers.js"); const batchMarkAsSeenMock = batchMarkAsSeen as jest.MockedFunction< typeof batchMarkAsSeen diff --git a/test/middleware/missedVaasV3/helpers.test.ts b/test/middleware/missedVaasV3/helpers.test.ts index a70199c5..d0386b37 100644 --- a/test/middleware/missedVaasV3/helpers.test.ts +++ b/test/middleware/missedVaasV3/helpers.test.ts @@ -1,4 +1,4 @@ -import { jest, describe, test } from "@jest/globals"; +import { jest, describe, test, afterEach } from "@jest/globals"; import { calculateSequenceStats } from "../../../relayer/middleware/missedVaasV3/helpers.js"; diff --git a/test/middleware/missedVaasV3/storage.test.ts b/test/middleware/missedVaasV3/storage.test.ts index 3fc13858..845d15f7 100644 --- a/test/middleware/missedVaasV3/storage.test.ts +++ b/test/middleware/missedVaasV3/storage.test.ts @@ -1,4 +1,4 @@ -import { jest, describe, test } from "@jest/globals"; +import { jest, describe, test, afterEach } from "@jest/globals"; import { Redis } from "ioredis"; import { @@ -263,8 +263,8 @@ describe("MissedVaaV3.storage", () => { describe("tryGetLastSafeSequence", () => { const redis = { get: jest.fn() }; - function prepareTest(getResult?: string, overrides: any = {}) { - redis.get.mockImplementation(async () => getResult || undefined); + function prepareTest(getResult?: string | null, overrides: any = {}) { + redis.get.mockImplementation(async () => getResult); const prefix = "foo"; const emitterChain = "bar"; @@ -295,8 +295,8 @@ describe("MissedVaaV3.storage", () => { expect(safeSequence).toEqual(BigInt(safeSequenceMock)); }); - test("It returns null if there is no safe sequence on redis", async () => { - const safeSequenceMock: undefined = undefined; + test("It returns undefined if there is no safe sequence on redis", async () => { + const safeSequenceMock: null = null; const { prefix, emitterChain, emitterAddress } = prepareTest(safeSequenceMock); @@ -309,7 +309,7 @@ describe("MissedVaaV3.storage", () => { ); expect(redis.get).toHaveBeenCalledTimes(1); - expect(safeSequence).toEqual(null); + expect(safeSequence).toEqual(undefined); }); test("It returns null if redis throws", async () => { @@ -328,7 +328,7 @@ describe("MissedVaaV3.storage", () => { ); expect(redis.get).toHaveBeenCalledTimes(1); - expect(safeSequence).toEqual(null); + expect(safeSequence).toEqual(undefined); }); }); diff --git a/test/middleware/missedVaasV3/worker.test.ts b/test/middleware/missedVaasV3/worker.test.ts index 5eee7a56..044f9710 100644 --- a/test/middleware/missedVaasV3/worker.test.ts +++ b/test/middleware/missedVaasV3/worker.test.ts @@ -1,4 +1,4 @@ -import { jest, describe, test } from "@jest/globals"; +import { jest, describe, test, afterEach } from "@jest/globals"; import { ProcessVaaFn, diff --git a/test/redis-storage.test.ts b/test/redis-storage.test.ts index 3d964080..b0f12c52 100644 --- a/test/redis-storage.test.ts +++ b/test/redis-storage.test.ts @@ -1,4 +1,4 @@ -import { describe } from "@jest/globals"; +import { afterEach, describe, jest } from "@jest/globals"; import { RedisStorage } from "../relayer/storage/redis-storage.js"; import { VaaFactory } from "./vaa-factory.js"; From 1767aea575dc409eaf28eff03bbf6a1b53fb460c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Thu, 30 Nov 2023 20:27:13 -0300 Subject: [PATCH 21/25] misc: prettier run --- relayer/middleware/missedVaasV3/storage.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/relayer/middleware/missedVaasV3/storage.ts b/relayer/middleware/missedVaasV3/storage.ts index 88225fd7..f420253f 100644 --- a/relayer/middleware/missedVaasV3/storage.ts +++ b/relayer/middleware/missedVaasV3/storage.ts @@ -153,7 +153,9 @@ export async function tryGetLastSafeSequence( return undefined; } - return lastSafeSequence !== null && lastSafeSequence !== undefined ? BigInt(lastSafeSequence) : undefined; + return lastSafeSequence !== null && lastSafeSequence !== undefined + ? BigInt(lastSafeSequence) + : undefined; } export async function tryGetExistingFailedSequences( From 271ffe144f82a698011c4e5a8e5e1d139b672482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Fri, 1 Dec 2023 09:53:37 -0300 Subject: [PATCH 22/25] misc: updates .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 70551d32..fb078aee 100644 --- a/.gitignore +++ b/.gitignore @@ -82,4 +82,4 @@ jspm_packages/ .vscode # Test typescript output -test/out \ No newline at end of file +test-out \ No newline at end of file From 7e7c5967fc1903c99881a50e42ba4d717287e997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Fri, 1 Dec 2023 09:58:02 -0300 Subject: [PATCH 23/25] misc: typing improvements --- relayer/application.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/application.ts b/relayer/application.ts index dc11900c..ff9dd747 100644 --- a/relayer/application.ts +++ b/relayer/application.ts @@ -69,7 +69,7 @@ export const defaultWormscanUrl = { export const defaultOpts = (env: Environment) => ({ wormholeRpcs: defaultWormholeRpcs[env], concurrency: 1, -}); +} satisfies RelayerAppOpts); export interface SerializableVaaId { emitterChain: ChainId; From 18769be54a777f5ac85d4cc5f4ab7a525f36c9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Sat, 2 Dec 2023 13:25:43 -0300 Subject: [PATCH 24/25] misc: simplify build command --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ad4e25c7..9acf06c0 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "test-redis": "docker run --rm -p 6301:6379 --name relayer-engine-test -d redis; npm run test; docker kill relayer-engine-test", "test": "jest --silent=false", "test-watch": "jest --silent=false --watch", - "build": "tsc --build ./tsconfig.cjs.json && tsc --build ./tsconfig.esm.json && bin/create-package.json.sh", + "build": "tsc --build ./tsconfig.cjs.json ./tsconfig.esm.json && bin/create-package.json.sh", "watch": "tsc --watch --project ./tsconfig.esm.json", - "typecheck": "tsc --build ./tsconfig.esm.json && tsc --build ./test/tsconfig.json", + "typecheck": "tsc --build ./tsconfig.esm.json ./test/tsconfig.json", "prettier": "prettier --write $(git diff main --name-only --diff-filter u | grep '.ts$' | xargs)", "mainnet-spy": "docker run --platform=linux/amd64 -p 7073:7073 --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest spy --nodeKey /node.key --spyRPC \"[::]:7073\" --network /wormhole/mainnet/2 --bootstrap /dns4/wormhole-mainnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWQp644DK27fd3d4Km3jr7gHiuJJ5ZGmy8hH4py7fP4FP7", "testnet-spy": "docker run --platform=linux/amd64 -p 7073:7073 --entrypoint /guardiand ghcr.io/wormhole-foundation/guardiand:latest spy --nodeKey /node.key --spyRPC \"[::]:7073\" --network /wormhole/testnet/2/1 --bootstrap /dns4/wormhole-testnet-v2-bootstrap.certus.one/udp/8999/quic/p2p/12D3KooWAkB9ynDur1Jtoa97LBUp8RXdhzS5uHgAfdTquJbrbN7i", From 282917433b04db027bd317f4bae5148ec07663e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Claudio=20Nale?= Date: Tue, 5 Dec 2023 17:03:52 -0300 Subject: [PATCH 25/25] misc: prettier run --- relayer/application.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/relayer/application.ts b/relayer/application.ts index ff9dd747..532139c9 100644 --- a/relayer/application.ts +++ b/relayer/application.ts @@ -66,10 +66,11 @@ export const defaultWormscanUrl = { [Environment.DEVNET]: "https://api.testnet.wormholescan.io", }; -export const defaultOpts = (env: Environment) => ({ - wormholeRpcs: defaultWormholeRpcs[env], - concurrency: 1, -} satisfies RelayerAppOpts); +export const defaultOpts = (env: Environment) => + ({ + wormholeRpcs: defaultWormholeRpcs[env], + concurrency: 1, + } satisfies RelayerAppOpts); export interface SerializableVaaId { emitterChain: ChainId;