mirror of
https://github.com/cerc-io/watcher-ts
synced 2026-09-08 16:54:08 +00:00
Support events handlers in multiple data sources for a contract address (#526)
* Support processing events in multiple subgraph datasources for a single contract address * Fix parsing event topic in graph-node watcher * Update codegen templates * Fix dummy indexer method in graph-node test * Upgrade package versions to 0.2.102
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@cerc-io/graph-node",
|
||||
"version": "0.2.101",
|
||||
"version": "0.2.102",
|
||||
"main": "dist/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@cerc-io/solidity-mapper": "^0.2.101",
|
||||
"@cerc-io/solidity-mapper": "^0.2.102",
|
||||
"@ethersproject/providers": "^5.4.4",
|
||||
"@graphprotocol/graph-ts": "^0.22.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||
@@ -51,9 +51,9 @@
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@cerc-io/assemblyscript": "0.19.10-watcher-ts-0.1.2",
|
||||
"@cerc-io/cache": "^0.2.101",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.101",
|
||||
"@cerc-io/util": "^0.2.101",
|
||||
"@cerc-io/cache": "^0.2.102",
|
||||
"@cerc-io/ipld-eth-client": "^0.2.102",
|
||||
"@cerc-io/util": "^0.2.102",
|
||||
"@types/json-diff": "^0.5.2",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"bn.js": "^4.11.9",
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface Context {
|
||||
rpcSupportsBlockHashParam: boolean;
|
||||
block?: Block;
|
||||
contractAddress?: string;
|
||||
dataSourceName?: string;
|
||||
}
|
||||
|
||||
const log = debug('vulcanize:graph-node');
|
||||
@@ -719,13 +720,14 @@ export const instantiate = async (
|
||||
},
|
||||
'dataSource.context': async () => {
|
||||
assert(context.contractAddress);
|
||||
const contract = indexer.isWatchedContract(context.contractAddress);
|
||||
const watchedContracts = indexer.isContractAddressWatched(context.contractAddress);
|
||||
const dataSourceContract = watchedContracts?.find(contract => contract.kind === context.dataSourceName);
|
||||
|
||||
if (!contract) {
|
||||
if (!dataSourceContract) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return database.toGraphContext(instanceExports, contract.context);
|
||||
return database.toGraphContext(instanceExports, dataSourceContract.context);
|
||||
},
|
||||
'dataSource.network': async () => {
|
||||
assert(dataSource);
|
||||
|
||||
@@ -169,7 +169,6 @@ export class GraphWatcher {
|
||||
async addContracts () {
|
||||
assert(this._indexer);
|
||||
assert(this._indexer.watchContract);
|
||||
assert(this._indexer.isWatchedContract);
|
||||
|
||||
// Watching the contract(s) if not watched already.
|
||||
for (const dataSource of this._dataSources) {
|
||||
@@ -177,7 +176,7 @@ export class GraphWatcher {
|
||||
|
||||
// Skip for templates as they are added dynamically.
|
||||
if (address) {
|
||||
const watchedContract = await this._indexer.isWatchedContract(address);
|
||||
const watchedContract = this._indexer.isContractAddressWatched(address);
|
||||
|
||||
if (!watchedContract) {
|
||||
await this._indexer.watchContract(address, name, true, startBlock);
|
||||
@@ -197,64 +196,79 @@ export class GraphWatcher {
|
||||
const blockData = this._context.block;
|
||||
assert(blockData);
|
||||
|
||||
assert(this._indexer && this._indexer.isWatchedContract);
|
||||
const watchedContract = this._indexer.isWatchedContract(contract);
|
||||
assert(watchedContract);
|
||||
assert(this._indexer);
|
||||
const watchedContracts = this._indexer.isContractAddressWatched(contract);
|
||||
assert(watchedContracts);
|
||||
|
||||
// Get dataSource in subgraph yaml based on contract address.
|
||||
const dataSource = this._dataSources.find(dataSource => dataSource.name === watchedContract.kind);
|
||||
// Get dataSources in subgraph yaml based on contract kind (same as dataSource.name)
|
||||
const dataSources = this._dataSources
|
||||
.filter(dataSource => watchedContracts.some(contract => contract.kind === dataSource.name));
|
||||
|
||||
if (!dataSource) {
|
||||
if (!dataSources.length) {
|
||||
log(`Subgraph doesn't have configuration for contract ${contract}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._context.contractAddress = contract;
|
||||
for (const dataSource of dataSources) {
|
||||
this._context.contractAddress = contract;
|
||||
this._context.dataSourceName = dataSource.name;
|
||||
|
||||
const { instance, contractInterface } = this._dataSourceMap[watchedContract.kind];
|
||||
assert(instance);
|
||||
const { exports: instanceExports } = instance;
|
||||
const { instance, contractInterface } = this._dataSourceMap[dataSource.name];
|
||||
assert(instance);
|
||||
const { exports: instanceExports } = instance;
|
||||
let eventTopic: string;
|
||||
|
||||
// Get event handler based on event topic (from event signature).
|
||||
const eventTopic = contractInterface.getEventTopic(eventSignature);
|
||||
const eventHandler = dataSource.mapping.eventHandlers.find((eventHandler: any) => {
|
||||
// The event signature we get from logDescription is different than that given in the subgraph yaml file.
|
||||
// For eg. event in subgraph.yaml: Stake(indexed address,uint256); from logDescription: Stake(address,uint256)
|
||||
// ethers.js doesn't recognize the subgraph event signature with indexed keyword before param type.
|
||||
// Match event topics from cleaned subgraph event signature (Stake(indexed address,uint256) -> Stake(address,uint256)).
|
||||
const subgraphEventTopic = contractInterface.getEventTopic(eventHandler.event.replace(/indexed /g, ''));
|
||||
try {
|
||||
eventTopic = contractInterface.getEventTopic(eventSignature);
|
||||
} catch (err) {
|
||||
// Continue loop only if no matching event found
|
||||
if (!((err as Error).message.includes('no matching event'))) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return subgraphEventTopic === eventTopic;
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!eventHandler) {
|
||||
log(`No handler configured in subgraph for event ${eventSignature}`);
|
||||
return;
|
||||
}
|
||||
// Get event handler based on event topic (from event signature).
|
||||
const eventHandler = dataSource.mapping.eventHandlers.find((eventHandler: any) => {
|
||||
// The event signature we get from logDescription is different than that given in the subgraph yaml file.
|
||||
// For eg. event in subgraph.yaml: Stake(indexed address,uint256); from logDescription: Stake(address,uint256)
|
||||
// ethers.js doesn't recognize the subgraph event signature with indexed keyword before param type.
|
||||
// Match event topics from cleaned subgraph event signature (Stake(indexed address,uint256) -> Stake(address,uint256)).
|
||||
const subgraphEventTopic = contractInterface.getEventTopic(eventHandler.event.replace(/indexed /g, ''));
|
||||
|
||||
const eventFragment = contractInterface.getEvent(eventSignature);
|
||||
return subgraphEventTopic === eventTopic;
|
||||
});
|
||||
|
||||
const tx = this._getTransactionData(txHash, extraData.ethFullTransactions);
|
||||
if (!eventHandler) {
|
||||
log(`No handler configured in subgraph for event ${eventSignature}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
block: blockData,
|
||||
inputs: eventFragment.inputs,
|
||||
event,
|
||||
tx,
|
||||
eventIndex
|
||||
};
|
||||
const eventFragment = contractInterface.getEvent(eventSignature);
|
||||
|
||||
// Create ethereum event to be passed to the wasm event handler.
|
||||
console.time(`time:graph-watcher#handleEvent-createEvent-block-${block.number}-event-${eventSignature}`);
|
||||
const ethereumEvent = await createEvent(instanceExports, contract, data);
|
||||
console.timeEnd(`time:graph-watcher#handleEvent-createEvent-block-${block.number}-event-${eventSignature}`);
|
||||
try {
|
||||
console.time(`time:graph-watcher#handleEvent-exec-${dataSource.name}-event-handler-${eventSignature}`);
|
||||
await this._handleMemoryError(instanceExports[eventHandler.handler](ethereumEvent), dataSource.name);
|
||||
console.timeEnd(`time:graph-watcher#handleEvent-exec-${dataSource.name}-event-handler-${eventSignature}`);
|
||||
} catch (error) {
|
||||
this._clearCachedEntities();
|
||||
throw error;
|
||||
const tx = this._getTransactionData(txHash, extraData.ethFullTransactions);
|
||||
|
||||
const data = {
|
||||
block: blockData,
|
||||
inputs: eventFragment.inputs,
|
||||
event,
|
||||
tx,
|
||||
eventIndex
|
||||
};
|
||||
|
||||
// Create ethereum event to be passed to the wasm event handler.
|
||||
console.time(`time:graph-watcher#handleEvent-createEvent-block-${block.number}-event-${eventSignature}`);
|
||||
const ethereumEvent = await createEvent(instanceExports, contract, data);
|
||||
console.timeEnd(`time:graph-watcher#handleEvent-createEvent-block-${block.number}-event-${eventSignature}`);
|
||||
try {
|
||||
console.time(`time:graph-watcher#handleEvent-exec-${dataSource.name}-event-handler-${eventSignature}`);
|
||||
await this._handleMemoryError(instanceExports[eventHandler.handler](ethereumEvent), dataSource.name);
|
||||
console.timeEnd(`time:graph-watcher#handleEvent-exec-${dataSource.name}-event-handler-${eventSignature}`);
|
||||
} catch (error) {
|
||||
this._clearCachedEntities();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,6 +325,7 @@ export class GraphWatcher {
|
||||
|
||||
for (const contractAddress of contractAddressList) {
|
||||
this._context.contractAddress = contractAddress;
|
||||
this._context.dataSourceName = dataSource.name;
|
||||
|
||||
// Call all the block handlers one after another for a contract.
|
||||
const blockHandlerPromises = dataSource.mapping.blockHandlers.map(async (blockHandler: any): Promise<void> => {
|
||||
|
||||
@@ -248,7 +248,7 @@ export class Indexer implements IndexerInterface {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
isWatchedContract (address : string): ContractInterface | undefined {
|
||||
isContractAddressWatched (address : string): ContractInterface[] | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user