rework generic relayer re app config

This commit is contained in:
Joe Howarth 2023-04-12 16:57:40 +00:00 committed by derpy-duck
parent 40c2a3dc02
commit e9b9c4fa35
4 changed files with 209 additions and 134 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ build
**/node_modules
/aptos/.aptos/**
.idea
.vscode
.dccache
.arcconfig
*.iml

View File

@ -18,14 +18,7 @@ import { rootLogger } from "./log";
import { processGenericRelayerVaa } from "./processor";
import { Logger } from "winston";
import * as deepCopy from "clone";
import {
ContractConfigEntry,
getAppConfig,
getContractsJson,
getEnvironment,
getEnvironmentOptions,
init,
} from "./env";
import { loadAppConfig } from "./env";
export type GRContext = StandardRelayerContext & {
relayProviders: Record<EVMChainId, string>;
@ -33,25 +26,8 @@ export type GRContext = StandardRelayerContext & {
};
async function main() {
await init();
const app = new StandardRelayerApp<GRContext>(
getEnvironment(),
getAppConfig()
);
const opts = getEnvironmentOptions();
// Build contract address maps
const contracts = getContractsJson();
const relayProviders = {} as Record<EVMChainId, string>;
const wormholeRelayers = {} as Record<EVMChainId, string>;
contracts.relayProviders.forEach(
({ chainId, address }: ContractConfigEntry) =>
(relayProviders[chainId] = address)
);
contracts.coreRelayers.forEach(
({ chainId, address }: ContractConfigEntry) =>
(wormholeRelayers[chainId] = address)
);
const { env, opts, relayProviders, wormholeRelayers } = await loadAppConfig();
const app = new StandardRelayerApp<GRContext>(env, opts);
// Set up middleware
app.use(async (ctx: GRContext, next: Next) => {

View File

@ -1,29 +1,25 @@
import * as fs from "fs/promises";
import yargs from "yargs";
import * as Koa from "koa";
import {
Environment,
Next,
StandardRelayerApp,
StandardRelayerContext,
ProvidersOpts,
RedisOptions,
StandardRelayerAppOpts,
} from "wormhole-relayer";
import { defaultLogger } from "wormhole-relayer/lib/logging";
import {
CHAIN_ID_ETH,
CHAIN_ID_BSC,
EVMChainId,
tryNativeToHexString,
} from "@certusone/wormhole-sdk";
import { rootLogger } from "./log";
import { processGenericRelayerVaa } from "./processor";
import { Logger } from "winston";
import * as deepCopy from "clone";
import { ClusterOptions } from "ioredis";
export type Opts = {
const SCRIPTS_DIR = "../../../ethereum/ts-scripts/relayer";
type Opts = {
flag: Flag;
};
export enum Flag {
enum Flag {
TiltKub = "tiltkub",
Tilt = "tilt",
Testnet = "testnet",
@ -31,117 +27,201 @@ export enum Flag {
Mainnet = "mainnet",
}
export type ContractConfigEntry = { chainId: EVMChainId; address: "string" };
export type ContractsJson = {
type ContractConfigEntry = { chainId: EVMChainId; address: "string" };
type ContractsJson = {
relayProviders: ContractConfigEntry[];
coreRelayers: ContractConfigEntry[];
mockIntegrations: ContractConfigEntry[];
};
export function getEnvironmentOptions() {
interface GRRelayerAppConfig {
contractsJsonPath: string;
name: string;
spyEndpoint: string;
wormholeRpcs: [string];
providers: ProvidersOpts;
fetchSourceTxhash: boolean;
logLevel: string;
redis: RedisOptions;
redisCluster?: StandardRelayerAppOpts["redisCluster"];
redisClusterEndpoints?: StandardRelayerAppOpts["redisClusterEndpoints"];
}
const defaults: { [key in Flag]: GRRelayerAppConfig } = {
[Flag.TiltKub]: {
name: "GenericRelayer",
contractsJsonPath: `${SCRIPTS_DIR}/config/${Flag.TiltKub}/contracts.json`,
spyEndpoint: "spy:7072",
logLevel: "debug",
wormholeRpcs: ["http://guardian:7071"],
providers: {
chains: {
[CHAIN_ID_ETH]: {
endpoints: ["http://eth-devnet:8545/"],
},
[CHAIN_ID_BSC]: {
endpoints: ["http://eth-devnet2:8545/"],
},
},
},
fetchSourceTxhash: false,
redis: { host: "redis", port: 6379 },
},
[Flag.Tilt]: {
name: "GenericRelayer",
contractsJsonPath: `${SCRIPTS_DIR}/config/${Flag.Tilt}/contracts.json`,
logLevel: "debug",
spyEndpoint: "localhost:7072",
wormholeRpcs: ["http://localhost:7071"],
providers: {
chains: {
[CHAIN_ID_ETH]: {
endpoints: ["http://localhost:8545/"],
},
[CHAIN_ID_BSC]: {
endpoints: ["http://localhost:8546/"],
},
},
},
fetchSourceTxhash: false,
redis: {},
},
[Flag.K8sTestnet]: {} as any,
[Flag.Testnet]: {} as any,
[Flag.Mainnet]: {} as any,
};
// async function loadAndMergeConfig(flag: Flag): Promise<GRRelayerAppConfig> {
// const file = await fs.readFile(`./configs/${flag}.json`, {
// encoding: "utf-8",
// });
// const config = JSON.parse(file);
// return mergeDeep({}, [defaults[flag] ?? {}, config]) as GRRelayerAppConfig;
// }
export async function loadAppConfig(): Promise<{
env: Environment;
opts: StandardRelayerAppOpts;
relayProviders: Record<EVMChainId, string>;
wormholeRelayers: Record<EVMChainId, string>;
}> {
const { flag } = getEnvironmentOptions();
const config = await loadAndMergeConfig(flag);
const contracts = await loadJson<ContractsJson>(config.contractsJsonPath);
const relayProviders = {} as Record<EVMChainId, string>;
const wormholeRelayers = {} as Record<EVMChainId, string>;
contracts.relayProviders.forEach(
({ chainId, address }: ContractConfigEntry) =>
(relayProviders[chainId] = address)
);
contracts.coreRelayers.forEach(
({ chainId, address }: ContractConfigEntry) =>
(wormholeRelayers[chainId] = address)
);
return {
relayProviders,
wormholeRelayers,
env: flagToEnvironment(flag),
opts: {
...config,
privateKeys: privateKeys(contracts),
},
};
}
function getEnvironmentOptions(): Opts {
let opts = yargs(process.argv.slice(2)).argv as unknown as Opts;
if (opts.flag == undefined) {
opts.flag = process.env.GR_RE_FLAG as Flag;
}
if (!validateStringEnum(Flag, opts.flag)) {
throw new Error("Unrecognized flag variant: " + opts.flag);
}
return opts;
}
let initialized = false;
let contracts: ContractsJson;
export async function init() {
const opts = getEnvironmentOptions();
contracts = await loadContractsJson(opts.flag);
initialized = true;
}
function uninitialized() {
throw new Error("init function was not called.");
}
function loadAndMergeConfig(flag: Flag): GRRelayerAppConfig {
const base = defaults[flag];
const isRedisCluster = !!process.env.REDIS_CLUSTER_ENDPOINTS;
return {
name: process.env.GENERIC_RELAYER_NAME || base.name,
// env: process.env.NODE_ENV?.trim()?.toLowerCase() || "local",
contractsJsonPath:
process.env.CONTRACTS_JSON_PATH || base.contractsJsonPath,
logLevel: process.env.LOG_LEVEL || base.logLevel,
spyEndpoint: process.env.SPY_URL || base.spyEndpoint,
wormholeRpcs: process.env.WORMHOLE_RPCS
? JSON.parse(process.env.WORMHOLE_RPCS)
: base.wormholeRpcs,
providers: process.env.BLOCKCHAIN_PROVIDERS
? JSON.parse(process.env.BLOCKCHAIN_PROVIDERS)
: base.providers,
fetchSourceTxhash: process.env.FETCH_SOURCE_TX_HASH
? JSON.parse(process.env.FETCH_SOURCE_TX_HASH)
: base.fetchSourceTxhash,
// concurrency: Number(process.env.RELAY_CONCURRENCY) || 5,
// influx: {
// url: process.env.INFLUXDB_URL,
// org: process.env.INFLUXDB_ORG,
// bucket: process.env.INFLUXDB_BUCKET,
// token: process.env.INFLUXDB_TOKEN,
// },
export function getAppConfig() {
const contracts = getContractsJson();
const options = getEnvironmentOptions();
if (options.flag == Flag.TiltKub) {
return {
name: "GenericRelayer",
privateKeys: privateKeys(contracts),
spyEndpoint: "spy:7072",
wormholeRpcs: ["http://guardian:7071"],
providers: {
chains: {
[CHAIN_ID_ETH]: {
endpoints: ["http://eth-devnet:8545/"],
redisClusterEndpoints: process.env.REDIS_CLUSTER_ENDPOINTS?.split(","), // "url1:port,url2:port"
redisCluster: isRedisCluster
? <ClusterOptions>{
dnsLookup: (address: any, callback: any) => callback(null, address),
slotsRefreshTimeout: 1000,
redisOptions: {
tls: process.env.REDIS_TLS ? {} : undefined,
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD,
},
[CHAIN_ID_BSC]: {
endpoints: ["http://eth-devnet2:8545/"],
},
},
},
logger: defaultLogger,
fetchSourceTxhash: false,
redis: { host: "redis", port: 6379 },
// redisCluster: {},
// redisClusterEndpoints: [],
};
//else assume localhost / tilt
} else {
return {
name: "GenericRelayer",
privateKeys: privateKeys(contracts),
spyEndpoint: "localhost:7072",
wormholeRpcs: ["http://localhost:7071"],
providers: {
chains: {
[CHAIN_ID_ETH]: {
endpoints: ["http://localhost:8545/"],
},
[CHAIN_ID_BSC]: {
endpoints: ["http://localhost:8546/"],
},
},
},
logger: defaultLogger,
fetchSourceTxhash: false,
redis: {},
// redisCluster: {},
// redisClusterEndpoints: [],
};
}
}
: undefined,
redis: <RedisOptions>{
tls: process.env.REDIS_TLS ? {} : undefined,
host: process.env.REDIS_HOST ? undefined : process.env.REDIS_HOST,
port: process.env.REDIS_CLUSTER_ENDPOINTS
? undefined
: Number(process.env.REDIS_PORT) || undefined,
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD,
},
};
}
//internal only
async function loadContractsJson(flag: Flag): Promise<ContractsJson> {
if ((flag = Flag.TiltKub)) {
flag = Flag.Tilt; //TiltKub contracts are the same as tilt
}
return JSON.parse(
await fs.readFile(`${SCRIPTS_DIR}/config/${flag}/contracts.json`, {
encoding: "utf-8",
})
) as ContractsJson;
}
export function getContractsJson(): ContractsJson {
if (!initialized || !contracts) {
uninitialized();
}
return contracts;
}
function privateKeys(contracts: ContractsJson) {
function privateKeys(contracts: ContractsJson): {
[k in Partial<EVMChainId>]: string[];
} {
const chainIds = new Set(contracts.coreRelayers.map((r) => r.chainId));
//TODO not this
const privateKey =
"6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1"; //private key 1 for tilt //process.env["PRIVATE_KEY"]! as string;
const privateKeys = {} as Record<EVMChainId, [string]>;
let privateKeysArray = [] as string[];
if (process.env.EVM_PRIVATE_KEYS) {
privateKeysArray = JSON.parse(process.env.EVM_PRIVATE_KEYS);
} else if (process.env.EVM_PRIVATE_KEY) {
privateKeysArray = [process.env.EVM_PRIVATE_KEY];
} else if (process.env.PRIVATE_KEY) {
// tilt
privateKeysArray = [process.env.PRIVATE_KEY];
} else {
// Todo: remove this
// tilt evm private key
console.log(
"Warning: using tilt private key because no others were specified"
);
privateKeysArray = [
"6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1",
];
}
const privateKeys = {} as Record<EVMChainId, string[]>;
for (const chainId of chainIds) {
privateKeys[chainId] = [privateKey];
privateKeys[chainId] = privateKeysArray;
}
return privateKeys;
}
export function getEnvironment() {
const options = getEnvironmentOptions();
return flagToEnvironment(options.flag);
}
function flagToEnvironment(flag: Flag): Environment {
switch (flag) {
case Flag.K8sTestnet:
@ -157,4 +237,22 @@ function flagToEnvironment(flag: Flag): Environment {
}
}
const SCRIPTS_DIR = "../../../ethereum/ts-scripts/relayer";
function validateStringEnum<O extends Object>(
enumObject: O,
passed: string
): boolean {
for (const value of Object.values(enumObject)) {
if (value === passed) {
return true;
}
}
return false;
}
function loadJson<T>(path: string): Promise<T> {
return fs
.readFile(path, {
encoding: "utf-8",
})
.then(JSON.parse) as Promise<T>;
}

View File

@ -13,7 +13,7 @@ import { GRContext } from "./app";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
import { GetARGsTypeFromFactory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts/commons";
import { getAppConfig } from "./env";
import { loadAppConfig } from "./env";
import { ethers } from "ethers";
export async function processGenericRelayerVaa(ctx: GRContext, next: Next) {
@ -60,7 +60,7 @@ async function processDelivery(ctx: GRContext) {
const results: Uint8Array[] = [];
const appConfig = getAppConfig();
const appConfig = loadAppConfig();
//TODO not anything even resembling this
try {