2021-10-28 11:01:56 +00:00
//
// Copyright 2021 Vulcanize, Inc.
//
2021-11-12 12:39:03 +00:00
import assert from 'assert' ;
2021-10-28 11:01:56 +00:00
import 'reflect-metadata' ;
import debug from 'debug' ;
import path from 'path' ;
import fs from 'fs' ;
2021-11-01 09:43:22 +00:00
import { ContractInterface , utils } from 'ethers' ;
2021-10-28 11:01:56 +00:00
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader' ;
2021-11-10 07:42:37 +00:00
import { EthClient } from '@vulcanize/ipld-eth-client' ;
2021-11-17 12:44:02 +00:00
import { IndexerInterface , getFullBlock } from '@vulcanize/util' ;
2021-10-28 11:01:56 +00:00
2021-11-17 05:25:05 +00:00
import { createBlock , createEvent , getSubgraphConfig , resolveEntityFieldConflicts } from './utils' ;
2021-11-10 07:42:37 +00:00
import { Context , instantiate } from './loader' ;
import { Database } from './database' ;
2021-11-01 09:43:22 +00:00
2021-10-28 11:01:56 +00:00
const log = debug ( 'vulcanize:graph-watcher' ) ;
2021-11-01 09:43:22 +00:00
interface DataSource {
instance : ResultObject & { exports : any } ,
contractInterface : utils.Interface
}
2021-10-28 11:01:56 +00:00
export class GraphWatcher {
2021-11-10 07:42:37 +00:00
_database : Database ;
2021-11-12 12:39:03 +00:00
_indexer? : IndexerInterface ;
2021-11-10 07:42:37 +00:00
_postgraphileClient : EthClient ;
2021-10-28 11:01:56 +00:00
_subgraphPath : string ;
2021-11-01 09:43:22 +00:00
_dataSources : any [ ] = [ ] ;
_dataSourceMap : { [ key : string ] : DataSource } = { } ;
2021-10-28 11:01:56 +00:00
2021-11-10 07:42:37 +00:00
_context : Context = {
event : { }
}
constructor ( database : Database , postgraphileClient : EthClient , subgraphPath : string ) {
this . _database = database ;
this . _postgraphileClient = postgraphileClient ;
2021-10-28 11:01:56 +00:00
this . _subgraphPath = subgraphPath ;
}
async init ( ) {
const { dataSources } = await getSubgraphConfig ( this . _subgraphPath ) ;
this . _dataSources = dataSources ;
2021-11-01 09:43:22 +00:00
// Create wasm instance and contract interface for each dataSource in subgraph yaml.
const dataPromises = this . _dataSources . map ( async ( dataSource : any ) = > {
const { source : { address , abi } , mapping } = dataSource ;
2021-10-28 11:01:56 +00:00
const { abis , file } = mapping ;
2021-11-01 09:43:22 +00:00
const abisMap = abis . reduce ( ( acc : { [ key : string ] : ContractInterface } , abi : any ) = > {
const { name , file } = abi ;
const abiFilePath = path . join ( this . _subgraphPath , file ) ;
acc [ name ] = JSON . parse ( fs . readFileSync ( abiFilePath ) . toString ( ) ) ;
return acc ;
} , { } ) ;
const contractInterface = new utils . Interface ( abisMap [ abi ] ) ;
2021-10-28 11:01:56 +00:00
const data = {
2021-11-01 09:43:22 +00:00
abis : abisMap ,
2021-10-28 11:01:56 +00:00
dataSource : {
address
}
} ;
const filePath = path . join ( this . _subgraphPath , file ) ;
2021-11-12 12:39:03 +00:00
assert ( this . _indexer ) ;
2021-11-01 09:43:22 +00:00
return {
2021-11-12 12:39:03 +00:00
instance : await instantiate ( this . _database , this . _indexer , this . _context , filePath , data ) ,
2021-11-01 09:43:22 +00:00
contractInterface
} ;
} , { } ) ;
const data = await Promise . all ( dataPromises ) ;
// Create a map from dataSource contract address to instance and contract interface.
this . _dataSourceMap = this . _dataSources . reduce ( ( acc : { [ key : string ] : DataSource } , dataSource : any , index : number ) = > {
const { instance } = data [ index ] ;
// Important to call _start for built subgraphs on instantiation!
// TODO: Check api version https://github.com/graphprotocol/graph-node/blob/6098daa8955bdfac597cec87080af5449807e874/runtime/wasm/src/module/mod.rs#L533
instance . exports . _start ( ) ;
const { source : { address } } = dataSource ;
acc [ address ] = data [ index ] ;
2021-10-28 11:01:56 +00:00
return acc ;
} , { } ) ;
}
2021-11-18 10:46:37 +00:00
async addContracts ( ) {
assert ( this . _indexer ? . watchContract ) ;
// Watching the contract(s).
for ( const dataSource of this . _dataSources ) {
const { source : { address , startBlock } , name } = dataSource ;
await this . _indexer . watchContract ( address , name , true , startBlock ) ;
}
}
2021-10-28 11:01:56 +00:00
async handleEvent ( eventData : any ) {
2021-11-01 09:43:22 +00:00
const { contract , event , eventSignature , block , tx , eventIndex } = eventData ;
2021-10-28 11:01:56 +00:00
2021-11-17 12:44:02 +00:00
// TODO: Use blockData fetched in handleBlock.
const blockData = await getFullBlock ( this . _postgraphileClient , block . hash ) ;
2021-11-10 07:42:37 +00:00
this . _context . event . block = blockData ;
2021-11-01 09:43:22 +00:00
// Get dataSource in subgraph yaml based on contract address.
2021-10-28 11:01:56 +00:00
const dataSource = this . _dataSources . find ( dataSource = > dataSource . source . address === contract ) ;
if ( ! dataSource ) {
log ( ` Subgraph doesnt have configuration for contract ${ contract } ` ) ;
return ;
}
2021-11-01 09:43:22 +00:00
// Get event handler based on event signature.
const eventHandler = dataSource . mapping . eventHandlers . find ( ( eventHandler : any ) = > eventHandler . event === eventSignature ) ;
if ( ! eventHandler ) {
log ( ` No handler configured in subgraph for event ${ eventSignature } ` ) ;
return ;
}
const { instance : { exports } , contractInterface } = this . _dataSourceMap [ contract ] ;
const eventFragment = contractInterface . getEvent ( eventSignature ) ;
const data = {
2021-11-10 07:42:37 +00:00
block : blockData ,
2021-11-23 05:53:47 +00:00
inputs : eventFragment.inputs ,
event ,
2021-11-01 09:43:22 +00:00
tx ,
eventIndex
} ;
2021-10-28 11:01:56 +00:00
2021-11-01 09:43:22 +00:00
// Create ethereum event to be passed to the wasm event handler.
const ethereumEvent = await createEvent ( exports , contract , data ) ;
2021-10-28 11:01:56 +00:00
2021-11-01 09:43:22 +00:00
await exports [ eventHandler . handler ] ( ethereumEvent ) ;
2021-10-28 11:01:56 +00:00
}
2021-11-10 07:42:37 +00:00
2021-11-15 06:41:56 +00:00
async handleBlock ( blockHash : string ) {
2021-11-17 12:44:02 +00:00
const blockData = await getFullBlock ( this . _postgraphileClient , blockHash ) ;
2021-11-15 06:41:56 +00:00
2021-11-18 10:46:37 +00:00
this . _context . event . block = blockData ;
2021-11-15 06:41:56 +00:00
// Call block handler(s) for each contract.
for ( const dataSource of this . _dataSources ) {
// Check if block handler(s) are configured.
if ( ! dataSource . mapping . blockHandlers ) {
continue ;
}
const { instance : { exports } } = this . _dataSourceMap [ dataSource . source . address ] ;
// Create ethereum block to be passed to a wasm block handler.
const ethereumBlock = await createBlock ( exports , blockData ) ;
// Call all the block handlers one after the another for a contract.
const blockHandlerPromises = dataSource . mapping . blockHandlers . map ( async ( blockHandler : any ) : Promise < void > = > {
await exports [ blockHandler . handler ] ( ethereumBlock ) ;
} ) ;
await Promise . all ( blockHandlerPromises ) ;
}
}
2021-11-12 12:39:03 +00:00
setIndexer ( indexer : IndexerInterface ) : void {
this . _indexer = indexer ;
}
2021-11-22 10:16:29 +00:00
async getEntity < Entity > ( entity : new ( ) = > Entity , id : string , blockHash : string , relations : { [ key : string ] : any } ) : Promise < any > {
2021-11-17 05:25:05 +00:00
// Get entity from the database.
2021-11-22 10:16:29 +00:00
const result = await this . _database . getEntityWithRelations ( entity , id , blockHash , relations ) as any ;
2021-11-17 05:25:05 +00:00
// Resolve any field name conflicts in the entity result.
return resolveEntityFieldConflicts ( result ) ;
2021-11-10 07:42:37 +00:00
}
2021-10-28 11:01:56 +00:00
}