16
1
mirror of https://github.com/cerc-io/watcher-ts synced 2025-03-19 09:19:23 +00:00

Get missing fields in transaction data for subgraph event handlers ()

* Get missing fields in event transaction

* Cache transaction data for event handlers in subgraph
This commit is contained in:
nikugogoi 2021-12-29 13:21:39 +05:30 committed by GitHub
parent 561c2c9066
commit 9b1aa29afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 128 additions and 35 deletions

View File

@ -42,9 +42,7 @@ export interface GraphData {
} }
export interface Context { export interface Context {
event: { block?: Block
block?: Block
}
} }
const log = debug('vulcanize:graph-node'); const log = debug('vulcanize:graph-node');
@ -71,8 +69,8 @@ export const instantiate = async (
const entityName = __getString(entity); const entityName = __getString(entity);
const entityId = __getString(id); const entityId = __getString(id);
assert(context.event.block); assert(context.block);
const entityData = await database.getEntity(entityName, entityId, context.event.block.blockHash); const entityData = await database.getEntity(entityName, entityId, context.block.blockHash);
if (!entityData) { if (!entityData) {
return null; return null;
@ -91,8 +89,8 @@ export const instantiate = async (
const entityInstance = await Entity.wrap(data); const entityInstance = await Entity.wrap(data);
assert(context.event.block); assert(context.block);
let dbData = await database.fromGraphEntity(instanceExports, context.event.block, entityName, entityInstance); let dbData = await database.fromGraphEntity(instanceExports, context.block, entityName, entityInstance);
await database.saveEntity(entityName, dbData); await database.saveEntity(entityName, dbData);
// Resolve any field name conflicts in the dbData for auto-diff. // Resolve any field name conflicts in the dbData for auto-diff.
@ -108,7 +106,7 @@ export const instantiate = async (
// Create an auto-diff. // Create an auto-diff.
assert(indexer.createDiffStaged); assert(indexer.createDiffStaged);
assert(dataSource?.address); assert(dataSource?.address);
await indexer.createDiffStaged(dataSource.address, context.event.block.blockHash, diffData); await indexer.createDiffStaged(dataSource.address, context.block.blockHash, diffData);
}, },
'log.log': (level: number, msg: number) => { 'log.log': (level: number, msg: number) => {
@ -161,10 +159,10 @@ export const instantiate = async (
functionParams = await Promise.all(functionParamsPromise); functionParams = await Promise.all(functionParamsPromise);
assert(context.event.block); assert(context.block);
// TODO: Check for function overloading. // TODO: Check for function overloading.
let result = await contract[functionName](...functionParams, { blockTag: context.event.block.blockHash }); let result = await contract[functionName](...functionParams, { blockTag: context.block.blockHash });
// Using function signature does not work. // Using function signature does not work.
const { outputs } = contract.interface.getFunction(functionName); const { outputs } = contract.interface.getFunction(functionName);

View File

@ -26,14 +26,19 @@ export const DECIMAL128_PMIN = '1e-6143';
// Maximum -ve decimal value. // Maximum -ve decimal value.
export const DECIMAL128_NMAX = '-1e-6143'; export const DECIMAL128_NMAX = '-1e-6143';
interface Transaction { export interface Transaction {
hash: string; hash: string;
index: number; index: number;
from: string; from: string;
to: string; to: string;
value: string;
gasLimit: string;
gasPrice: string;
input: string;
} }
export interface Block { export interface Block {
headerId: number;
blockHash: string; blockHash: string;
blockNumber: string; blockNumber: string;
timestamp: string; timestamp: string;
@ -231,16 +236,19 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
const txToStringPtr = await __newString(tx.to); const txToStringPtr = await __newString(tx.to);
const txTo = tx.to && await Address.fromString(txToStringPtr); const txTo = tx.to && await Address.fromString(txToStringPtr);
const txValuePtr = await BigInt.fromI32(0); const valueStringPtr = await __newString(tx.value);
const txGasLimitPtr = await BigInt.fromI32(0); const txValuePtr = await BigInt.fromString(valueStringPtr);
const txGasPricePtr = await BigInt.fromI32(0);
const txinputPtr = await Bytes.empty(); const gasLimitStringPtr = await __newString(tx.gasLimit);
const txGasLimitPtr = await BigInt.fromString(gasLimitStringPtr);
const gasPriceStringPtr = await __newString(tx.gasPrice);
const txGasPricePtr = await BigInt.fromString(gasPriceStringPtr);
const inputStringPtr = await __newString(tx.input);
const txInputByteArray = await ByteArray.fromHexString(inputStringPtr);
const txInputPtr = await Bytes.fromByteArray(txInputByteArray);
// Missing fields from watcher in transaction data:
// value
// gasLimit
// gasPrice
// input
const transaction = await ethereum.Transaction.__new( const transaction = await ethereum.Transaction.__new(
txHash, txHash,
txIndex, txIndex,
@ -249,7 +257,7 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
txValuePtr, txValuePtr,
txGasLimitPtr, txGasLimitPtr,
txGasPricePtr, txGasPricePtr,
txinputPtr txInputPtr
); );
const eventParamArrayPromise = inputs.map(async input => { const eventParamArrayPromise = inputs.map(async input => {

View File

@ -11,9 +11,9 @@ import { ContractInterface, utils, providers } from 'ethers';
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader'; import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
import { EthClient } from '@vulcanize/ipld-eth-client'; import { EthClient } from '@vulcanize/ipld-eth-client';
import { IndexerInterface, getFullBlock, BlockHeight, ServerConfig } from '@vulcanize/util'; import { IndexerInterface, getFullBlock, BlockHeight, ServerConfig, getFullTransaction } from '@vulcanize/util';
import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts } from './utils'; import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts, Transaction } from './utils';
import { Context, GraphData, instantiate } from './loader'; import { Context, GraphData, instantiate } from './loader';
import { Database } from './database'; import { Database } from './database';
@ -34,10 +34,9 @@ export class GraphWatcher {
_wasmRestartBlocksInterval: number; _wasmRestartBlocksInterval: number;
_dataSources: any[] = []; _dataSources: any[] = [];
_dataSourceMap: { [key: string]: DataSource } = {}; _dataSourceMap: { [key: string]: DataSource } = {};
_transactionsMap: Map<string, Transaction> = new Map()
_context: Context = { _context: Context = {}
event: {}
}
constructor (database: Database, postgraphileClient: EthClient, ethProvider: providers.BaseProvider, serverConfig: ServerConfig) { constructor (database: Database, postgraphileClient: EthClient, ethProvider: providers.BaseProvider, serverConfig: ServerConfig) {
this._database = database; this._database = database;
@ -119,12 +118,14 @@ export class GraphWatcher {
} }
async handleEvent (eventData: any) { async handleEvent (eventData: any) {
const { contract, event, eventSignature, block, tx, eventIndex } = eventData; const { contract, event, eventSignature, block, tx: { hash: txHash }, eventIndex } = eventData;
// TODO: Use blockData fetched in handleBlock. if (!this._context.block) {
const blockData = await getFullBlock(this._postgraphileClient, this._ethProvider, block.hash); this._context.block = await getFullBlock(this._postgraphileClient, this._ethProvider, block.hash);
}
this._context.event.block = blockData; const blockData = this._context.block;
assert(blockData);
// Get dataSource in subgraph yaml based on contract address. // Get dataSource in subgraph yaml based on contract address.
const dataSource = this._dataSources.find(dataSource => dataSource.source.address === contract); const dataSource = this._dataSources.find(dataSource => dataSource.source.address === contract);
@ -157,6 +158,8 @@ export class GraphWatcher {
const eventFragment = contractInterface.getEvent(eventSignature); const eventFragment = contractInterface.getEvent(eventSignature);
const tx = await this._getTransactionData(blockData.headerId, txHash);
const data = { const data = {
block: blockData, block: blockData,
inputs: eventFragment.inputs, inputs: eventFragment.inputs,
@ -174,7 +177,10 @@ export class GraphWatcher {
async handleBlock (blockHash: string) { async handleBlock (blockHash: string) {
const blockData = await getFullBlock(this._postgraphileClient, this._ethProvider, blockHash); const blockData = await getFullBlock(this._postgraphileClient, this._ethProvider, blockHash);
this._context.event.block = blockData; this._context.block = blockData;
// Clear transactions map on handling new block.
this._transactionsMap.clear();
// Call block handler(s) for each contract. // Call block handler(s) for each contract.
for (const dataSource of this._dataSources) { for (const dataSource of this._dataSources) {
@ -263,4 +269,18 @@ export class GraphWatcher {
throw error; throw error;
} }
} }
async _getTransactionData (headerId: number, txHash: string): Promise<Transaction> {
let transaction = this._transactionsMap.get(txHash);
if (transaction) {
return transaction;
}
transaction = await getFullTransaction(this._postgraphileClient, headerId, txHash);
assert(transaction);
this._transactionsMap.set(txHash, transaction);
return transaction;
}
} }

View File

@ -92,6 +92,16 @@ export class EthClient {
); );
} }
async getFullTransaction ({ headerId, txHash }: { headerId: number, txHash: string }): Promise<any> {
return this._graphqlClient.query(
ethQueries.getFullTransaction,
{
headerId,
txHash
}
);
}
async getBlockByHash (blockHash?: string): Promise<any> { async getBlockByHash (blockHash?: string): Promise<any> {
const { block } = await this._graphqlClient.query(ethQueries.getBlockByHash, { blockHash }); const { block } = await this._graphqlClient.query(ethQueries.getBlockByHash, { blockHash });
block.number = parseInt(block.number, 16); block.number = parseInt(block.number, 16);

View File

@ -86,6 +86,7 @@ export const getFullBlocks = gql`
query allEthHeaderCids($blockNumber: BigInt, $blockHash: String) { query allEthHeaderCids($blockNumber: BigInt, $blockHash: String) {
allEthHeaderCids(condition: { blockNumber: $blockNumber, blockHash: $blockHash }) { allEthHeaderCids(condition: { blockNumber: $blockNumber, blockHash: $blockHash }) {
nodes { nodes {
id
cid cid
blockNumber blockNumber
blockHash blockHash
@ -106,6 +107,21 @@ query allEthHeaderCids($blockNumber: BigInt, $blockHash: String) {
} }
`; `;
export const getFullTransaction = gql`
query ethTransactionCidByHeaderIdAndTxHash($headerId: Int!, $txHash: String!) {
ethTransactionCidByHeaderIdAndTxHash(headerId: $headerId, txHash: $txHash) {
cid
txHash
index
src
dst
blockByMhKey {
data
}
}
}
`;
export const getBlockByHash = gql` export const getBlockByHash = gql`
query block($blockHash: Bytes32) { query block($blockHash: Bytes32) {
block(hash: $blockHash) { block(hash: $blockHash) {
@ -158,6 +174,7 @@ export default {
getBlockWithTransactions, getBlockWithTransactions,
getBlocks, getBlocks,
getFullBlocks, getFullBlocks,
getFullTransaction,
getBlockByHash, getBlockByHash,
subscribeBlocks, subscribeBlocks,
subscribeTransactions subscribeTransactions

View File

@ -55,3 +55,19 @@ export function decodeHeader (rlp : Uint8Array): any {
export function decodeData (hexLiteral: string): Uint8Array { export function decodeData (hexLiteral: string): Uint8Array {
return Uint8Array.from(Buffer.from(hexLiteral.slice(2), 'hex')); return Uint8Array.from(Buffer.from(hexLiteral.slice(2), 'hex'));
} }
export function decodeTransaction (rlp : Uint8Array): any {
try {
const data = utils.RLP.decode(rlp);
return {
GasPrice: decodeInteger(data[1], BigInt(0)),
GasLimit: decodeInteger(data[2], BigInt(0)),
Amount: decodeInteger(data[4], BigInt(0)),
Data: data[5]
};
} catch (error: any) {
log(error);
return undefined;
}
}

View File

@ -225,15 +225,15 @@ export class JobRunner {
blockProgress = await this._indexer.fetchBlockEvents({ cid, blockHash, blockNumber, parentHash, blockTimestamp: timestamp }); blockProgress = await this._indexer.fetchBlockEvents({ cid, blockHash, blockNumber, parentHash, blockTimestamp: timestamp });
} }
if (this._indexer.processBlock) {
await this._indexer.processBlock(blockHash, blockNumber);
}
// Check if block has unprocessed events. // Check if block has unprocessed events.
if (blockProgress.numProcessedEvents < blockProgress.numEvents) { if (blockProgress.numProcessedEvents < blockProgress.numEvents) {
await this._jobQueue.pushJob(QUEUE_EVENT_PROCESSING, { kind: JOB_KIND_EVENTS, blockHash: blockProgress.blockHash, publish: true }); await this._jobQueue.pushJob(QUEUE_EVENT_PROCESSING, { kind: JOB_KIND_EVENTS, blockHash: blockProgress.blockHash, publish: true });
} }
if (this._indexer.processBlock) {
await this._indexer.processBlock(blockHash, blockNumber);
}
const indexBlockDuration = new Date().getTime() - indexBlockStartTime.getTime(); const indexBlockDuration = new Date().getTime() - indexBlockStartTime.getTime();
log(`time:job-runner#_indexBlock: ${indexBlockDuration}ms`); log(`time:job-runner#_indexBlock: ${indexBlockDuration}ms`);
} }

View File

@ -194,6 +194,7 @@ export const getFullBlock = async (ethClient: EthClient, ethProvider: providers.
const { size } = await provider.send('eth_getBlockByHash', [blockHash, false]); const { size } = await provider.send('eth_getBlockByHash', [blockHash, false]);
return { return {
headerId: fullBlock.id,
cid: fullBlock.cid, cid: fullBlock.cid,
blockNumber: fullBlock.blockNumber, blockNumber: fullBlock.blockNumber,
blockHash: fullBlock.blockHash, blockHash: fullBlock.blockHash,
@ -211,3 +212,26 @@ export const getFullBlock = async (ethClient: EthClient, ethProvider: providers.
size: BigInt(size).toString() size: BigInt(size).toString()
}; };
}; };
export const getFullTransaction = async (ethClient: EthClient, headerId: number, txHash: string): Promise<any> => {
const {
ethTransactionCidByHeaderIdAndTxHash: fullTx
} = await ethClient.getFullTransaction({ headerId, txHash });
assert(fullTx.blockByMhKey);
// Decode the transaction data.
const extraData = EthDecoder.decodeTransaction(EthDecoder.decodeData(fullTx.blockByMhKey.data));
assert(extraData);
return {
hash: txHash,
from: fullTx.src,
to: fullTx.dst,
index: fullTx.index,
value: extraData.Amount.toString(),
gasLimit: extraData.GasLimit.toString(),
gasPrice: extraData.GasPrice.toString(),
input: extraData.Data
};
};