// // Copyright 2021 Vulcanize, Inc. // import assert from 'assert'; import debug from 'debug'; import { ethers } from 'ethers'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { GetStorageAt } from '@vulcanize/solidity-mapper'; import { TracingClient } from '@vulcanize/tracing-client'; import { addressesInTrace } from './util'; import { Database } from './database'; import { Trace } from './entity/Trace'; import { Account } from './entity/Account'; import { BlockProgress } from './entity/BlockProgress'; const log = debug('vulcanize:indexer'); export class Indexer { _db: Database _ethClient: EthClient _getStorageAt: GetStorageAt _tracingClient: TracingClient constructor (db: Database, ethClient: EthClient, tracingClient: TracingClient) { assert(db); assert(ethClient); assert(tracingClient); this._db = db; this._ethClient = ethClient; this._getStorageAt = this._ethClient.getStorageAt.bind(this._ethClient); this._tracingClient = tracingClient; } async isWatchedAddress (address : string): Promise { assert(address); return this._db.isWatchedAddress(ethers.utils.getAddress(address)); } async watchAddress (address: string, startingBlock: number): Promise { // Always use the checksum address (https://docs.ethers.io/v5/api/utils/address/#utils-getAddress). await this._db.saveAccount(ethers.utils.getAddress(address), startingBlock); return true; } async getTrace (txHash: string): Promise { return this._db.getTrace(txHash); } async traceTxAndIndexAppearances (txHash: string): Promise { let entity = await this._db.getTrace(txHash); if (entity) { log(`traceTx: db hit ${txHash}`); } else { log(`traceTx: db miss, fetching from tracing API server ${txHash}`); const tx = await this._tracingClient.getTx(txHash); const trace = await this._tracingClient.getTxTrace(txHash, 'callTraceWithAddresses', '15s'); await this._db.saveTrace({ txHash, blockNumber: tx.blockNumber, blockHash: tx.blockHash, trace: JSON.stringify(trace) }); entity = await this._db.getTrace(txHash); assert(entity); await this._indexAppearances(entity); } return entity; } async getAppearances (address: string, fromBlockNumber: number, toBlockNumber: number): Promise { return this._db.getAppearances(address, fromBlockNumber, toBlockNumber); } async getBlockProgress (blockHash: string): Promise { return this._db.getBlockProgress(blockHash); } async updateBlockProgress (blockHash: string): Promise { return this._db.updateBlockProgress(blockHash); } async _indexAppearances (trace: Trace): Promise { const traceObj = JSON.parse(trace.trace); // TODO: Check if tx has failed? const addresses = addressesInTrace(traceObj); trace.accounts = addresses.map((address: string) => { assert(address); const account = new Account(); account.address = ethers.utils.getAddress(address); account.startingBlock = trace.blockNumber; return account; }); return await this._db.saveTraceEntity(trace); } }