2021-06-17 12:26:38 +00:00
|
|
|
import assert from 'assert';
|
2021-06-18 11:34:02 +00:00
|
|
|
import debug from 'debug';
|
2021-06-17 12:26:38 +00:00
|
|
|
import { ethers } from 'ethers';
|
|
|
|
import { PubSub } from 'apollo-server-express';
|
|
|
|
|
|
|
|
import { EthClient } from '@vulcanize/ipld-eth-client';
|
|
|
|
import { GetStorageAt } from '@vulcanize/solidity-mapper';
|
2021-06-18 11:34:02 +00:00
|
|
|
import { TracingClient } from '@vulcanize/tracing-client';
|
2021-06-17 12:26:38 +00:00
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
import { addressesInTrace } from './util';
|
2021-06-17 12:26:38 +00:00
|
|
|
import { Database } from './database';
|
2021-06-21 10:08:36 +00:00
|
|
|
import { Trace } from './entity/Trace';
|
|
|
|
import { Account } from './entity/Account';
|
2021-06-17 12:26:38 +00:00
|
|
|
|
2021-06-18 11:34:02 +00:00
|
|
|
const log = debug('vulcanize:indexer');
|
2021-06-17 12:26:38 +00:00
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
const AddressEvent = 'address_event';
|
2021-06-21 10:08:36 +00:00
|
|
|
|
2021-06-17 12:26:38 +00:00
|
|
|
export class Indexer {
|
|
|
|
_db: Database
|
|
|
|
_ethClient: EthClient
|
|
|
|
_pubsub: PubSub
|
|
|
|
_getStorageAt: GetStorageAt
|
2021-06-18 11:34:02 +00:00
|
|
|
_tracingClient: TracingClient
|
2021-06-17 12:26:38 +00:00
|
|
|
|
2021-06-18 11:34:02 +00:00
|
|
|
constructor (db: Database, ethClient: EthClient, pubsub: PubSub, tracingClient: TracingClient) {
|
2021-06-17 12:26:38 +00:00
|
|
|
assert(db);
|
|
|
|
assert(ethClient);
|
|
|
|
assert(pubsub);
|
2021-06-18 11:34:02 +00:00
|
|
|
assert(tracingClient);
|
2021-06-17 12:26:38 +00:00
|
|
|
|
|
|
|
this._db = db;
|
|
|
|
this._ethClient = ethClient;
|
|
|
|
this._pubsub = pubsub;
|
|
|
|
this._getStorageAt = this._ethClient.getStorageAt.bind(this._ethClient);
|
2021-06-18 11:34:02 +00:00
|
|
|
this._tracingClient = tracingClient;
|
2021-06-17 12:26:38 +00:00
|
|
|
}
|
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
getAddressEventIterator (): AsyncIterator<any> {
|
|
|
|
return this._pubsub.asyncIterator([AddressEvent]);
|
2021-06-17 12:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async isWatchedAddress (address : string): Promise<boolean> {
|
|
|
|
assert(address);
|
|
|
|
|
|
|
|
return this._db.isWatchedAddress(ethers.utils.getAddress(address));
|
|
|
|
}
|
|
|
|
|
|
|
|
async watchAddress (address: string, startingBlock: number): Promise<boolean> {
|
|
|
|
// Always use the checksum address (https://docs.ethers.io/v5/api/utils/address/#utils-getAddress).
|
2021-06-21 10:08:36 +00:00
|
|
|
await this._db.saveAccount(ethers.utils.getAddress(address), startingBlock);
|
2021-06-17 12:26:38 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2021-06-18 11:34:02 +00:00
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
async getTrace (txHash: string): Promise<Trace | undefined> {
|
|
|
|
return this._db.getTrace(txHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
async publishAddressEventToSubscribers (txHash: string): Promise<void> {
|
|
|
|
const traceObj = await this._db.getTrace(txHash);
|
|
|
|
if (!traceObj) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { blockNumber, blockHash, trace } = traceObj;
|
|
|
|
|
|
|
|
for (let i = 0; i < traceObj.accounts.length; i++) {
|
|
|
|
const account = traceObj.accounts[i];
|
|
|
|
|
|
|
|
log(`pushing tx ${txHash} event to GQL subscribers for address ${account.address}`);
|
|
|
|
|
|
|
|
// Publishing the event here will result in pushing the payload to GQL subscribers for `onAddressEvent(address)`.
|
|
|
|
await this._pubsub.publish(AddressEvent, {
|
|
|
|
onAddressEvent: {
|
|
|
|
address: account.address,
|
|
|
|
txTrace: {
|
|
|
|
txHash,
|
|
|
|
blockHash,
|
|
|
|
blockNumber,
|
|
|
|
trace
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async traceTxAndIndexAppearances (txHash: string): Promise<Trace> {
|
2021-06-18 11:34:02 +00:00
|
|
|
let entity = await this._db.getTrace(txHash);
|
|
|
|
if (entity) {
|
|
|
|
log('traceTx: db hit');
|
|
|
|
} else {
|
|
|
|
log('traceTx: db miss, fetching from tracing API server');
|
|
|
|
|
|
|
|
const tx = await this._tracingClient.getTx(txHash);
|
|
|
|
const trace = await this._tracingClient.getTxTrace(txHash, 'callTraceWithAddresses', '15s');
|
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
await this._db.saveTrace({
|
2021-06-18 11:34:02 +00:00
|
|
|
txHash,
|
|
|
|
blockNumber: tx.blockNumber,
|
|
|
|
blockHash: tx.blockHash,
|
|
|
|
trace: JSON.stringify(trace)
|
|
|
|
});
|
2021-06-21 10:08:36 +00:00
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
entity = await this._db.getTrace(txHash);
|
|
|
|
|
|
|
|
assert(entity);
|
2021-06-21 10:08:36 +00:00
|
|
|
await this.indexAppearances(entity);
|
2021-06-18 11:34:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
return entity;
|
2021-06-18 11:34:02 +00:00
|
|
|
}
|
2021-06-21 10:08:36 +00:00
|
|
|
|
|
|
|
async getAppearances (address: string, fromBlockNumber: number, toBlockNumber: number): Promise<Trace[]> {
|
|
|
|
return this._db.getAppearances(address, fromBlockNumber, toBlockNumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
async indexAppearances (trace: Trace): Promise<Trace> {
|
|
|
|
const traceObj = JSON.parse(trace.trace);
|
|
|
|
|
2021-06-22 08:34:48 +00:00
|
|
|
// TODO: Check if tx has failed?
|
|
|
|
const addresses = addressesInTrace(traceObj);
|
|
|
|
|
|
|
|
trace.accounts = addresses.map((address: string) => {
|
2021-06-21 13:25:13 +00:00
|
|
|
assert(address);
|
|
|
|
|
2021-06-21 10:08:36 +00:00
|
|
|
const account = new Account();
|
2021-06-21 13:25:13 +00:00
|
|
|
account.address = ethers.utils.getAddress(address);
|
2021-06-21 10:08:36 +00:00
|
|
|
account.startingBlock = trace.blockNumber;
|
|
|
|
|
|
|
|
return account;
|
|
|
|
});
|
|
|
|
|
|
|
|
return await this._db.saveTraceEntity(trace);
|
|
|
|
}
|
2021-06-17 12:26:38 +00:00
|
|
|
}
|