bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
122 lines (102 loc) • 3.85 kB
text/typescript
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<GethBlock> {
return (this.web3.eth.getBlock(blockNumber, true) as unknown) as Promise<GethBlock>;
}
private async traceBlock(blockNumber: number): Promise<IGethTxTraceResponse[]> {
let result = [] as IGethTxTraceResponse[];
try {
result = await this.send<IGethTxTraceResponse[]>({
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<IGethTxTrace[]> {
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<T>(data: IJsonRpcRequest) {
return new Promise<T>((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<IJsonRpcResponse>);
});
}
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;
}
}