Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validation methods for watcher config #425

Merged
merged 10 commits into from
Oct 16, 2023
1 change: 1 addition & 0 deletions packages/util/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export interface ServerConfig {
port: number;
mode: string;
kind: string;
enableConfigValidation: boolean;
checkpointing: boolean;
checkpointInterval: number;
subgraphPath: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/util/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export const KIND_LAZY = 'lazy';
export const DEFAULT_PREFETCH_BATCH_SIZE = 10;

export const DEFAULT_MAX_GQL_CACHE_SIZE = Math.pow(2, 20) * 8; // 8 MB

export const SUPPORTED_PAID_RPC_METHODS = ['eth_getBlockByHash', 'eth_getStorageAt', 'eth_getBlockByNumber'];
1 change: 1 addition & 0 deletions packages/util/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export * from './graph/types';
export * from './payments';
export * from './eth';
export * from './consensus';
export * from './validate-config';
128 changes: 128 additions & 0 deletions packages/util/src/validate-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { ethers } from 'ethers';
import { Client } from 'pg';
import debug from 'debug';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
import WebSocket from 'ws';
import path from 'path';
import fs from 'fs-extra';

import { SUPPORTED_PAID_RPC_METHODS } from './constants';

const log = debug('vulcanize:server');

async function validateContractDeployment (rpcEndpoint: string, contractInfo: {address:string, name?:string}, isWs: boolean): Promise<void> {
try {
let provider;
if (isWs) {
provider = new ethers.providers.WebSocketProvider(rpcEndpoint);
} else {
provider = new ethers.providers.JsonRpcProvider(rpcEndpoint);
}
const code = await provider.getCode(contractInfo.address);
if (code === '0x') {
log(`WARNING: Contract ${contractInfo.name ? contractInfo.name : ''} is not deployed at ${contractInfo.address}`);
} else {
log(`SUCCESS: Contract ${contractInfo.name ? contractInfo.name : ''} is deployed at ${contractInfo.address}`);
}
} catch (error) {
log(error);
}
}

function validateContractAddressFormat (contractInfo: {address:string, name?:string}): boolean {
if (ethers.utils.isAddress(contractInfo.address)) {
log(`SUCCESS: Address ${contractInfo.address} ${contractInfo.name ? `for ${contractInfo.name}` : ''} is in a valid format`);
return true;
} else {
log(`WARNING: Address ${contractInfo.address} ${contractInfo.name ? `for ${contractInfo.name}` : ''} is not in a valid format`);
return false;
}
}

export async function validateContracts (contractsArr: {address:string, name?:string}[], rpcProviderMutationEndpoint: string, isWs: boolean): Promise<void> {
contractsArr.forEach((contract) => {
const isValidFormat = validateContractAddressFormat(contract);

if (isValidFormat) {
validateContractDeployment(rpcProviderMutationEndpoint, contract, isWs);
}
});
}

export async function validateHttpEndpoint (endPoint: string, kind: string): Promise<void> {
try {
const response = await fetch(endPoint);
log(`SUCCESS: The ${endPoint} is up. Status ${response.status}`);
} catch (error:any) {
log(`WARNING: could not connect to ${endPoint}. Please check if the ${kind} is correct and up.`);
log(error);
}
}

async function checkDBEndpoint (connectionString: string, dbKind: string): Promise<void> {
const client = new Client({
connectionString
});

try {
await client.connect();
log(`SUCCESS: ${dbKind} endpoint is up.`);
} catch (error) {
log(`WARNING: Error connecting to ${dbKind} database. Please check if job queue config is setup and database is running \n`, error);
} finally {
await client.end();
}
}

export async function validateDatabaseEndpoint (database: PostgresConnectionOptions): Promise<void> {
const connectionString = `${database.type}://${database.username}:${database.password}@${database.host}:${database.port}/${database.database}`;
await checkDBEndpoint(connectionString, 'postgresQL');
}

export async function validateJobQueueEndpoint (connString: string): Promise<void> {
await checkDBEndpoint(connString, 'Job queue database');
}

async function checkWebSocket (wsEndpoint: string) {
const socket = new WebSocket(wsEndpoint);

return new Promise((resolve, reject) => {
socket.on('open', () => {
socket.close();
resolve(true);
});

socket.on('error', (error) => {
reject(error);
});
});
}

export async function validateWebSocketEndpoint (wsEndpoint: string): Promise<void> {
try {
await checkWebSocket(wsEndpoint);
log(`SUCCESS: The WebSocket endpoint ${wsEndpoint} is up.`);
} catch (error) {
log(`WARNING: Error connecting to websocket endpoint ${wsEndpoint}. Please check if server.p2p.nitro.chainUrl is correct.`, error);
}
}

export async function validatePaidRPCMethods (paidRPCMethods: string[]): Promise<void> {
paidRPCMethods.forEach((method) => {
if (SUPPORTED_PAID_RPC_METHODS.includes(method)) {
log(`SUCCESS: ${method} is a supported paid RPC method`);
} else {
log(`WARNING: ${method} is not a supported paid RPC method`);
}
});
}

export async function validateFilePath (configFile: string): Promise<void> {
const configFilePath = path.resolve(configFile);
const fileExists = await fs.pathExists(configFilePath);
if (!fileExists) {
log(`WARNING: Config file not found: ${configFilePath}`);
} else {
log(`SUCCESS: Config file found: ${configFilePath}`);
}
}