import Web3 from 'web3'; import logger from '../../../../../logger'; import { EVMTransactionStorage } from '../../models/transaction'; import { GethBlock, IAbiDecodedData, IEVMBlock, IEVMTransactionInProcess } from '../../types'; import { Callback, IJsonRpcRequest, IJsonRpcResponse, IRpc } from './index'; interface IGethTxTraceResponse { result: IGethTxTrace; } interface IGethTxTraceBase { from: string; gas: string; gasUsed: string; input: string; output?: string; to: string; type: 'CALL' | 'STATICCALL' | 'DELEGATECALL' | 'CREATE' | 'CREATE2'; value?: string; abiType?: IAbiDecodedData; } export interface IGethTxTrace extends IGethTxTraceBase { calls?: IGethTxTrace[]; } export interface IGethTxTraceFlat extends IGethTxTraceBase { depth: string; } export class GethRPC implements IRpc { web3: Web3; constructor(web3: Web3) { this.web3 = web3; } public getBlock(blockNumber: number): Promise { return (this.web3.eth.getBlock(blockNumber, true) as unknown) as Promise; } private async traceBlock(blockNumber: number): Promise { let result = [] as IGethTxTraceResponse[]; try { result = await this.send({ method: 'debug_traceBlockByNumber', params: [this.web3.utils.toHex(blockNumber), { tracer: 'callTracer' }], jsonrpc: '2.0', id: Date.now() + Math.round(Math.random() * 1000) }); } catch (e: any) { logger.debug('%o', e); } return result; } public async getTransactionsFromBlock(blockNumber: number): Promise { const tracedTxs = await this.traceBlock(blockNumber); const txs = tracedTxs && tracedTxs.length > 0 ? tracedTxs.filter(tx => tx.result) : []; return txs.map(tx => this.transactionFromGethTrace(tx)); } public send(data: IJsonRpcRequest) { return new Promise((resolve, reject) => { const provider = this.web3.eth.currentProvider as any; provider.send(data, function(err, data) { if (err || data.error) return reject(err || data.error); resolve(data.result as T); } as Callback); }); } private transactionFromGethTrace(tx: IGethTxTraceResponse) { const convertedTx = tx.result; convertedTx.abiType = EVMTransactionStorage.abiDecode(tx.result.input); for (let call of convertedTx.calls || []) { call.abiType = EVMTransactionStorage.abiDecode(tx.result.input); } return convertedTx; } public reconcileTraces(block: IEVMBlock, transactions: IEVMTransactionInProcess[], traces: IGethTxTrace[]) { // TODO calculate total block reward including fees block; for (let i in traces) { if (traces[i].calls) { let tx = transactions[i]; tx.calls = traces[i].calls!.flatMap((call, idx) => this.flattenTraceCalls(call, idx.toString())); } } return transactions; } private flattenTraceCalls(trace: IGethTxTrace, depth: string): IGethTxTraceFlat[] { const retval: IGethTxTraceFlat[] = []; const calls = trace.calls; delete trace.calls; trace.abiType = trace.input ? EVMTransactionStorage.abiDecode(trace.input) : undefined; if (trace.abiType) { for (let param of trace.abiType.params) { param.value = typeof param.value === 'string' ? param.value : JSON.stringify(param.value); if (param.value && param.value.length > 100) { // Need to truncate this so it doesn't blow up the index. param.value = param.value.substring(0, 100) + '...'; } } } (trace as IGethTxTraceFlat).depth = depth; retval.push(trace as IGethTxTraceFlat); if (calls) { retval.push(...calls.flatMap((call, idx) => this.flattenTraceCalls(call, depth + '_' + idx))); } return retval; } }