// // Copyright 2021 Vulcanize, Inc. // import assert from 'assert'; import { ValueTransformer } from 'typeorm'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { utils, getDefaultProvider, providers } from 'ethers'; import Decimal from 'decimal.js'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { DEFAULT_CONFIG_PATH } from './constants'; import { Config } from './config'; import { JobQueue } from './job-queue'; import { GraphDecimal } from './graph-decimal'; import * as EthDecoder from './eth'; /** * Method to wait for specified time. * @param time Time to wait in milliseconds */ export const wait = async (time: number): Promise => new Promise(resolve => setTimeout(resolve, time)); /** * Transformer used by typeorm entity for GraphDecimal type fields. */ export const graphDecimalTransformer: ValueTransformer = { to: (value?: GraphDecimal) => { if (value) { return value.toFixed(); } return value; }, from: (value?: string) => { if (value) { return new GraphDecimal(value); } return value; } }; /** * Transformer used by typeorm entity for Decimal type fields. */ export const decimalTransformer: ValueTransformer = { to: (value?: Decimal) => { if (value) { return value.toString(); } return value; }, from: (value?: string) => { if (value) { return new Decimal(value); } return value; } }; /** * Transformer used by typeorm entity for bigint type fields. */ export const bigintTransformer: ValueTransformer = { to: (value?: bigint) => { if (value) { return value.toString(); } return value; }, from: (value?: string) => { if (value) { return BigInt(value); } return value; } }; export const bigintArrayTransformer: ValueTransformer = { to: (valueArray?: bigint[]) => { if (valueArray) { return valueArray.map(value => bigintTransformer.to(value)); } return valueArray; }, from: (valueArray?: string[]) => { if (valueArray) { return valueArray.map(value => bigintTransformer.from(value)); } return valueArray; } }; export const decimalArrayTransformer: ValueTransformer = { to: (valueArray?: Decimal[]) => { if (valueArray) { return valueArray.map(value => decimalTransformer.to(value)); } return valueArray; }, from: (valueArray?: string[]) => { if (valueArray) { return valueArray.map(value => decimalTransformer.from(value)); } return valueArray; } }; export const resetJobs = async (config: Config): Promise => { const { jobQueue: jobQueueConfig } = config; const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig; assert(dbConnectionString, 'Missing job queue db connection string'); const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs }); await jobQueue.start(); await jobQueue.deleteAllJobs(); }; export const getResetYargs = (): yargs.Argv => { return yargs(hideBin(process.argv)) .parserConfiguration({ 'parse-numbers': false }).options({ configFile: { alias: 'f', type: 'string', require: true, demandOption: true, describe: 'configuration file path (toml)', default: DEFAULT_CONFIG_PATH } }); }; export const getCustomProvider = (network?: providers.Network | string, options?: any): providers.BaseProvider => { const provider = getDefaultProvider(network, options); provider.formatter = new CustomFormatter(); return provider; }; class CustomFormatter extends providers.Formatter { blockTag (blockTag: any): string { if (blockTag == null) { return 'latest'; } if (blockTag === 'earliest') { return '0x0'; } if (blockTag === 'latest' || blockTag === 'pending') { return blockTag; } if (typeof (blockTag) === 'number' || utils.isHexString(blockTag)) { // Return value if hex string is of block hash length. if (utils.isHexString(blockTag) && utils.hexDataLength(blockTag) === 32) { return blockTag; } return utils.hexValue(blockTag); } throw new Error('invalid blockTag'); } } export const getFullBlock = async (ethClient: EthClient, ethProvider: providers.BaseProvider, blockHash: string): Promise => { const { allEthHeaderCids: { nodes: [ fullBlock ] } } = await ethClient.getFullBlocks({ blockHash }); assert(fullBlock.blockByMhKey); // Deecode the header data. const header = EthDecoder.decodeHeader(EthDecoder.decodeData(fullBlock.blockByMhKey.data)); assert(header); // TODO: Calculate size from rlp encoded data provided by postgraphile. // Get block info from JSON RPC API provided by ipld-eth-server. const provider = ethProvider as providers.JsonRpcProvider; const { size } = await provider.send('eth_getBlockByHash', [blockHash, false]); return { headerId: fullBlock.id, cid: fullBlock.cid, blockNumber: fullBlock.blockNumber, blockHash: fullBlock.blockHash, parentHash: fullBlock.parentHash, timestamp: fullBlock.timestamp, stateRoot: fullBlock.stateRoot, td: fullBlock.td, txRoot: fullBlock.txRoot, receiptRoot: fullBlock.receiptRoot, uncleHash: fullBlock.uncleRoot, difficulty: header.Difficulty.toString(), gasLimit: header.GasLimit.toString(), gasUsed: header.GasUsed.toString(), author: header.Beneficiary, size: BigInt(size).toString() }; }; export const getFullTransaction = async (ethClient: EthClient, txHash: string): Promise => { const { ethTransactionCidByTxHash: fullTx } = await ethClient.getFullTransaction(txHash); assert(fullTx.blockByMhKey); // Decode the transaction data. const extraData = EthDecoder.decodeTransaction(EthDecoder.decodeData(fullTx.blockByMhKey.data)); assert(extraData); return { hash: txHash, from: fullTx.src, to: fullTx.dst, index: fullTx.index, value: extraData.Amount.toString(), gasLimit: extraData.GasLimit.toString(), gasPrice: extraData.GasPrice.toString(), input: extraData.Data }; };