From 97e88ab5f01df93059f634b2f0730f3ffe72edc1 Mon Sep 17 00:00:00 2001 From: nikugogoi Date: Fri, 26 Aug 2022 12:02:39 +0530 Subject: [PATCH] Fixes and improvements for eden-watcher job-runner and compare CLI (#165) * Compare IPLD state entity without derived fields * Apply default limit to array relation fields in IPLD state entity * Mark block as complete after processing of block handler * Avoid re processing of block handler * Use LIMIT 1 in the query to get latest IPLD block * Replace eth_calls in eden-watcher with getStorageValue * Add checkpoint verification to export state CLI * Fix get diff blocks query when creating checkpoint * Fix subgraph staker sort and remove entities sequentially in reset CLI Co-authored-by: prathamesh0 --- .../templates/database-template.handlebars | 4 +- .../export-state-template.handlebars | 39 +++++++++++- packages/eden-watcher/src/cli/export-state.ts | 25 +++++++- .../eden-watcher/src/cli/reset-cmds/state.ts | 8 +-- packages/eden-watcher/src/database.ts | 4 +- packages/eden-watcher/src/hooks.ts | 10 +-- packages/eden-watcher/src/indexer.ts | 4 +- .../erc20-watcher/src/cli/reset-cmds/state.ts | 8 +-- .../erc721-watcher/src/cli/export-state.ts | 11 +++- .../src/cli/reset-cmds/state.ts | 8 +-- packages/erc721-watcher/src/database.ts | 4 +- .../environments/compare-cli-config.toml | 4 +- .../src/cli/compare/compare-blocks.ts | 29 +++------ packages/graph-node/src/cli/compare/utils.ts | 57 +++++++++++++++++- packages/graph-node/src/database.ts | 2 +- packages/graph-node/src/eden.test.ts | 9 ++- packages/graph-node/src/eth-call.test.ts | 3 +- packages/graph-node/src/loader.ts | 15 +++-- packages/graph-node/src/storage-call.test.ts | 3 +- packages/graph-node/src/utils.ts | 8 --- packages/graph-node/src/watcher.ts | 5 +- .../eden/EdenNetwork/EdenNetwork.wasm | Bin 155014 -> 156035 bytes .../EdenNetworkDistribution.wasm | Bin 142558 -> 142941 bytes .../EdenNetworkGovernance.wasm | Bin 174491 -> 174883 bytes .../test/subgraph/example1/package.json | 2 +- .../test/subgraph/example1/src/mapping.ts | 4 +- .../test/subgraph/example1/yarn.lock | 8 +-- .../src/cli/export-state.ts | 25 +++++++- .../src/cli/reset-cmds/state.ts | 8 +-- packages/graph-test-watcher/src/database.ts | 4 +- .../mobymask-watcher/src/cli/export-state.ts | 11 +++- .../src/cli/reset-cmds/state.ts | 8 +-- packages/mobymask-watcher/src/database.ts | 4 +- packages/util/src/common.ts | 9 ++- packages/util/src/database.ts | 23 +++---- packages/util/src/ipld-database.ts | 12 +++- packages/util/src/ipld-helper.ts | 54 +++++++++++++++++ packages/util/src/ipld-indexer.ts | 13 ++-- packages/util/src/misc.ts | 8 +++ packages/util/src/types.ts | 6 +- 40 files changed, 327 insertions(+), 132 deletions(-) diff --git a/packages/codegen/src/templates/database-template.handlebars b/packages/codegen/src/templates/database-template.handlebars index 211d1d06..4022807c 100644 --- a/packages/codegen/src/templates/database-template.handlebars +++ b/packages/codegen/src/templates/database-template.handlebars @@ -102,10 +102,10 @@ export class Database implements IPLDDatabaseInterface { } // Fetch all diff IPLDBlocks after the specified block number. - async getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise { + async getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise { const repo = this._conn.getRepository(IPLDBlock); - return this._baseDatabase.getDiffIPLDBlocksByBlocknumber(repo, contractAddress, blockNumber); + return this._baseDatabase.getDiffIPLDBlocksInRange(repo, contractAddress, startBlock, endBlock); } async saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlock): Promise { diff --git a/packages/codegen/src/templates/export-state-template.handlebars b/packages/codegen/src/templates/export-state-template.handlebars index 67b05e8c..6d0aad9c 100644 --- a/packages/codegen/src/templates/export-state-template.handlebars +++ b/packages/codegen/src/templates/export-state-template.handlebars @@ -9,7 +9,17 @@ import debug from 'debug'; import fs from 'fs'; import path from 'path'; -import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients, JobQueue, StateKind } from '@vulcanize/util'; +import { + Config, + DEFAULT_CONFIG_PATH, + getConfig, + initClients, + JobQueue, + {{#if (subgraphPath)}} + verifyCheckpointData, + {{/if}} + StateKind +} from '@vulcanize/util'; {{#if (subgraphPath)}} import { GraphWatcher, Database as GraphDatabase } from '@vulcanize/graph-node'; {{/if}} @@ -36,6 +46,20 @@ const main = async (): Promise => { alias: 'o', type: 'string', describe: 'Export file path' + }, + {{#if (subgraphPath)}} + verify: { + alias: 'v', + type: 'boolean', + describe: 'Verify checkpoint', + default: true + }, + {{/if}} + createCheckpoint: { + alias: 'c', + type: 'boolean', + describe: 'Create new checkpoint', + default: false } }).argv; @@ -98,13 +122,24 @@ const main = async (): Promise => { // Create and export checkpoint if checkpointing is on for the contract. if (contract.checkpoint) { - await indexer.createCheckpoint(contract.address, block.blockHash); + if (argv.createCheckpoint) { + log(`Creating checkpoint at block ${block.blockNumber}`); + await indexer.createCheckpoint(contract.address, block.blockHash); + } const ipldBlock = await indexer.getLatestIPLDBlock(contract.address, StateKind.Checkpoint, block.blockNumber); assert(ipldBlock); const data = indexer.getIPLDData(ipldBlock); + {{#if (subgraphPath)}} + if (argv.verify) { + log(`Verifying checkpoint data for contract ${contract.address}`); + await verifyCheckpointData(graphDb, ipldBlock.block, data); + log('Checkpoint data verified'); + } + + {{/if}} if (indexer.isIPFSConfigured()) { await indexer.pushToIPFS(data); } diff --git a/packages/eden-watcher/src/cli/export-state.ts b/packages/eden-watcher/src/cli/export-state.ts index 69240ada..67928335 100644 --- a/packages/eden-watcher/src/cli/export-state.ts +++ b/packages/eden-watcher/src/cli/export-state.ts @@ -9,7 +9,7 @@ import debug from 'debug'; import fs from 'fs'; import path from 'path'; -import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients, JobQueue, StateKind } from '@vulcanize/util'; +import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients, JobQueue, StateKind, verifyCheckpointData } from '@vulcanize/util'; import { GraphWatcher, Database as GraphDatabase } from '@vulcanize/graph-node'; import * as codec from '@ipld/dag-cbor'; @@ -34,6 +34,18 @@ const main = async (): Promise => { alias: 'o', type: 'string', describe: 'Export file path' + }, + createCheckpoint: { + alias: 'c', + type: 'boolean', + describe: 'Create new checkpoint', + default: false + }, + verify: { + alias: 'v', + type: 'boolean', + describe: 'Verify checkpoint', + default: true } }).argv; @@ -92,13 +104,22 @@ const main = async (): Promise => { // Create and export checkpoint if checkpointing is on for the contract. if (contract.checkpoint) { - await indexer.createCheckpoint(contract.address, block.blockHash); + if (argv.createCheckpoint) { + log(`Creating checkpoint at block ${block.blockNumber}`); + await indexer.createCheckpoint(contract.address, block.blockHash); + } const ipldBlock = await indexer.getLatestIPLDBlock(contract.address, StateKind.Checkpoint, block.blockNumber); assert(ipldBlock); const data = indexer.getIPLDData(ipldBlock); + if (argv.verify) { + log(`Verifying checkpoint data for contract ${contract.address}`); + await verifyCheckpointData(graphDb, ipldBlock.block, data); + log('Checkpoint data verified'); + } + if (indexer.isIPFSConfigured()) { await indexer.pushToIPFS(data); } diff --git a/packages/eden-watcher/src/cli/reset-cmds/state.ts b/packages/eden-watcher/src/cli/reset-cmds/state.ts index 907073ce..9b5683ba 100644 --- a/packages/eden-watcher/src/cli/reset-cmds/state.ts +++ b/packages/eden-watcher/src/cli/reset-cmds/state.ts @@ -83,11 +83,9 @@ export const handler = async (argv: any): Promise => { try { const entities = [BlockProgress, Producer, ProducerSet, ProducerSetChange, ProducerRewardCollectorChange, RewardScheduleEntry, RewardSchedule, ProducerEpoch, Block, Epoch, SlotClaim, Slot, Staker, Network, Distributor, Distribution, Claim, Slash, Account]; - const removeEntitiesPromise = entities.map(async entityClass => { - return db.removeEntities(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); - }); - - await Promise.all(removeEntitiesPromise); + for (const entity of entities) { + await db.removeEntities(dbTx, entity, { blockNumber: MoreThan(argv.blockNumber) }); + } const syncStatus = await indexer.getSyncStatus(); assert(syncStatus, 'Missing syncStatus'); diff --git a/packages/eden-watcher/src/database.ts b/packages/eden-watcher/src/database.ts index eb8e418c..79a84efe 100644 --- a/packages/eden-watcher/src/database.ts +++ b/packages/eden-watcher/src/database.ts @@ -62,10 +62,10 @@ export class Database implements IPLDDatabaseInterface { } // Fetch all diff IPLDBlocks after the specified block number. - async getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise { + async getDiffIPLDBlocksInRange (contractAddress: string, startblock: number, endBlock: number): Promise { const repo = this._conn.getRepository(IPLDBlock); - return this._baseDatabase.getDiffIPLDBlocksByBlocknumber(repo, contractAddress, blockNumber); + return this._baseDatabase.getDiffIPLDBlocksInRange(repo, contractAddress, startblock, endBlock); } async saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlock): Promise { diff --git a/packages/eden-watcher/src/hooks.ts b/packages/eden-watcher/src/hooks.ts index b5b5cf6b..9b5346b2 100644 --- a/packages/eden-watcher/src/hooks.ts +++ b/packages/eden-watcher/src/hooks.ts @@ -60,14 +60,14 @@ export async function createStateCheckpoint (indexer: Indexer, contractAddress: // Fetch the latest 'checkpoint' | 'init' for the contract to fetch diffs after it. let prevNonDiffBlock: IPLDBlockInterface; - let getDiffBlockNumber: number; - const checkpointBlock = await indexer.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint, block.blockNumber); + let diffStartBlockNumber: number; + const checkpointBlock = await indexer.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint, block.blockNumber - 1); if (checkpointBlock) { const checkpointBlockNumber = checkpointBlock.block.blockNumber; prevNonDiffBlock = checkpointBlock; - getDiffBlockNumber = checkpointBlockNumber; + diffStartBlockNumber = checkpointBlockNumber; // Update IPLD status map with the latest checkpoint info. // Essential while importing state as checkpoint at the snapshot block is added by import-state CLI. @@ -80,11 +80,11 @@ export async function createStateCheckpoint (indexer: Indexer, contractAddress: prevNonDiffBlock = initBlock; // Take block number previous to initial state block to include any diff state at that block. - getDiffBlockNumber = initBlock.block.blockNumber - 1; + diffStartBlockNumber = initBlock.block.blockNumber - 1; } // Fetching all diff blocks after the latest 'checkpoint' | 'init'. - const diffBlocks = await indexer.getDiffIPLDBlocksByBlocknumber(contractAddress, getDiffBlockNumber); + const diffBlocks = await indexer.getDiffIPLDBlocksInRange(contractAddress, diffStartBlockNumber, block.blockNumber); const prevNonDiffBlockData = codec.decode(Buffer.from(prevNonDiffBlock.data)) as any; const data = { diff --git a/packages/eden-watcher/src/indexer.ts b/packages/eden-watcher/src/indexer.ts index 2ae50c18..0e34681b 100644 --- a/packages/eden-watcher/src/indexer.ts +++ b/packages/eden-watcher/src/indexer.ts @@ -303,8 +303,8 @@ export class Indexer implements IPLDIndexerInterface { return this._baseIndexer.getIPLDBlockByCid(cid); } - async getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise { - return this._db.getDiffIPLDBlocksByBlocknumber(contractAddress, blockNumber); + async getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise { + return this._db.getDiffIPLDBlocksInRange(contractAddress, startBlock, endBlock); } getIPLDData (ipldBlock: IPLDBlock): any { diff --git a/packages/erc20-watcher/src/cli/reset-cmds/state.ts b/packages/erc20-watcher/src/cli/reset-cmds/state.ts index 6e648bd0..90a4b479 100644 --- a/packages/erc20-watcher/src/cli/reset-cmds/state.ts +++ b/packages/erc20-watcher/src/cli/reset-cmds/state.ts @@ -56,11 +56,9 @@ export const handler = async (argv: any): Promise => { const dbTx = await db.createTransactionRunner(); try { - const removeEntitiesPromise = [BlockProgress, Allowance, Balance].map(async entityClass => { - return db.removeEntities(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); - }); - - await Promise.all(removeEntitiesPromise); + for (const entity of [BlockProgress, Allowance, Balance]) { + await db.removeEntities(dbTx, entity, { blockNumber: MoreThan(argv.blockNumber) }); + } if (syncStatus.latestIndexedBlockNumber > blockProgress.blockNumber) { await indexer.updateSyncStatusIndexedBlock(blockProgress.blockHash, blockProgress.blockNumber, true); diff --git a/packages/erc721-watcher/src/cli/export-state.ts b/packages/erc721-watcher/src/cli/export-state.ts index 31ac9028..16c8956e 100644 --- a/packages/erc721-watcher/src/cli/export-state.ts +++ b/packages/erc721-watcher/src/cli/export-state.ts @@ -33,6 +33,12 @@ const main = async (): Promise => { alias: 'o', type: 'string', describe: 'Export file path' + }, + createCheckpoint: { + alias: 'c', + type: 'boolean', + describe: 'Create new checkpoint', + default: false } }).argv; @@ -83,7 +89,10 @@ const main = async (): Promise => { // Create and export checkpoint if checkpointing is on for the contract. if (contract.checkpoint) { - await indexer.createCheckpoint(contract.address, block.blockHash); + if (argv.createCheckpoint) { + log(`Creating checkpoint at block ${block.blockNumber}`); + await indexer.createCheckpoint(contract.address, block.blockHash); + } const ipldBlock = await indexer.getLatestIPLDBlock(contract.address, StateKind.Checkpoint, block.blockNumber); assert(ipldBlock); diff --git a/packages/erc721-watcher/src/cli/reset-cmds/state.ts b/packages/erc721-watcher/src/cli/reset-cmds/state.ts index 8ca609f0..d5ebc4b9 100644 --- a/packages/erc721-watcher/src/cli/reset-cmds/state.ts +++ b/packages/erc721-watcher/src/cli/reset-cmds/state.ts @@ -70,11 +70,9 @@ export const handler = async (argv: any): Promise => { try { const entities = [BlockProgress, SupportsInterface, BalanceOf, OwnerOf, GetApproved, IsApprovedForAll, Name, Symbol, TokenURI, _Name, _Symbol, _Owners, _Balances, _TokenApprovals, _OperatorApprovals]; - const removeEntitiesPromise = entities.map(async entityClass => { - return db.removeEntities(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); - }); - - await Promise.all(removeEntitiesPromise); + for (const entity of entities) { + await db.removeEntities(dbTx, entity, { blockNumber: MoreThan(argv.blockNumber) }); + } const syncStatus = await indexer.getSyncStatus(); assert(syncStatus, 'Missing syncStatus'); diff --git a/packages/erc721-watcher/src/database.ts b/packages/erc721-watcher/src/database.ts index 463f5b32..100f13a8 100644 --- a/packages/erc721-watcher/src/database.ts +++ b/packages/erc721-watcher/src/database.ts @@ -323,10 +323,10 @@ export class Database implements IPLDDatabaseInterface { } // Fetch all diff IPLDBlocks after the specified block number. - async getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise { + async getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise { const repo = this._conn.getRepository(IPLDBlock); - return this._baseDatabase.getDiffIPLDBlocksByBlocknumber(repo, contractAddress, blockNumber); + return this._baseDatabase.getDiffIPLDBlocksInRange(repo, contractAddress, startBlock, endBlock); } async saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlock): Promise { diff --git a/packages/graph-node/environments/compare-cli-config.toml b/packages/graph-node/environments/compare-cli-config.toml index 18abe269..48b4a96f 100644 --- a/packages/graph-node/environments/compare-cli-config.toml +++ b/packages/graph-node/environments/compare-cli-config.toml @@ -5,14 +5,14 @@ [queries] queryDir = "../graph-test-watcher/src/gql/queries" names = [] - idsEndpoint = "gqlEndpoint1" blockDelayInMs = 250 [watcher] - configpath = "../../graph-test-watcher/environments/local.toml" + configPath = "../../graph-test-watcher/environments/local.toml" entitiesDir = "../../graph-test-watcher/src/entity" endpoint = "gqlEndpoint2" verifyState = true + derivedFields = [] [cache] endpoint = "gqlEndpoint1" diff --git a/packages/graph-node/src/cli/compare/compare-blocks.ts b/packages/graph-node/src/cli/compare/compare-blocks.ts index c4f460d6..b7e03aec 100644 --- a/packages/graph-node/src/cli/compare/compare-blocks.ts +++ b/packages/graph-node/src/cli/compare/compare-blocks.ts @@ -9,11 +9,10 @@ import path from 'path'; import assert from 'assert'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; import _ from 'lodash'; -import omitDeep from 'omit-deep'; import { getConfig as getWatcherConfig, wait } from '@vulcanize/util'; import { GraphQLClient } from '@vulcanize/ipld-eth-client'; -import { compareObjects, compareQuery, Config, getBlockIPLDState as getIPLDStateByBlock, getClients, getConfig } from './utils'; +import { checkEntityInIPLDState, compareQuery, Config, getBlockIPLDState as getIPLDStateByBlock, getClients, getConfig } from './utils'; import { Database } from '../../database'; import { getSubgraphConfig } from '../../utils'; @@ -130,7 +129,12 @@ export const main = async (): Promise => { ); if (config.watcher.verifyState) { - await checkEntityInIPLDState(ipldStateByBlock, queryName, result, id, rawJson); + const ipldDiff = await checkEntityInIPLDState(ipldStateByBlock, queryName, result, id, rawJson, config.watcher.derivedFields); + + if (ipldDiff) { + log('Results mismatch for IPLD state:', ipldDiff); + diffFound = true; + } } if (diff) { @@ -167,22 +171,3 @@ export const main = async (): Promise => { process.exit(1); } }; - -const checkEntityInIPLDState = async ( - ipldState: {[key: string]: any}, - queryName: string, - entityResult: {[key: string]: any}, - id: string, - rawJson: boolean -) => { - const entityName = _.startCase(queryName); - const ipldEntity = ipldState[entityName][id]; - - // Filter __typename key in GQL result. - const resultEntity = omitDeep(entityResult[queryName], '__typename'); - const diff = compareObjects(ipldEntity, resultEntity, rawJson); - - if (diff) { - log('Results mismatch for IPLD state:', diff); - } -}; diff --git a/packages/graph-node/src/cli/compare/utils.ts b/packages/graph-node/src/cli/compare/utils.ts index 96f9bf45..8cc7cab4 100644 --- a/packages/graph-node/src/cli/compare/utils.ts +++ b/packages/graph-node/src/cli/compare/utils.ts @@ -9,12 +9,14 @@ import toml from 'toml'; import fs from 'fs-extra'; import { diffString, diff } from 'json-diff'; import _ from 'lodash'; +import omitDeep from 'omit-deep'; import { Config as CacheConfig, getCache } from '@vulcanize/cache'; import { GraphQLClient } from '@vulcanize/ipld-eth-client'; import { gql } from '@apollo/client/core'; import { Client } from './client'; +import { DEFAULT_LIMIT } from '../../database'; const IPLD_STATE_QUERY = ` query getState($blockHash: String!, $contractAddress: String!, $kind: String){ @@ -36,6 +38,11 @@ interface QueryConfig { blockDelayInMs: number; } +interface EntityDerivedFields { + entity: string; + fields: string[]; +} + export interface Config { endpoints: EndpointConfig; queries: QueryConfig; @@ -44,6 +51,7 @@ export interface Config { entitiesDir: string; verifyState: boolean; endpoint: keyof EndpointConfig; + derivedFields: EntityDerivedFields[] } cache: { endpoint: keyof EndpointConfig; @@ -154,6 +162,25 @@ export const getBlockIPLDState = async (client: GraphQLClient, contracts: string if (getState) { const data = JSON.parse(getState.data); + + // Apply default limit on array type relation fields. + Object.values(data.state) + .forEach((idEntityMap: any) => { + Object.values(idEntityMap) + .forEach((entity: any) => { + Object.values(entity) + .forEach(fieldValue => { + if ( + Array.isArray(fieldValue) && + fieldValue.length && + fieldValue[0].id + ) { + fieldValue.splice(DEFAULT_LIMIT); + } + }); + }); + }); + return data.state; } @@ -163,7 +190,35 @@ export const getBlockIPLDState = async (client: GraphQLClient, contracts: string return contractIPLDStates.reduce((acc, state) => _.merge(acc, state)); }; -export const compareObjects = (obj1: any, obj2: any, rawJson: boolean): string => { +export const checkEntityInIPLDState = async ( + ipldState: {[key: string]: any}, + queryName: string, + entityResult: {[key: string]: any}, + id: string, + rawJson: boolean, + derivedFields: EntityDerivedFields[] = [] +): Promise => { + const entityName = _.upperFirst(queryName); + const ipldEntity = ipldState[entityName][id]; + + // Filter __typename key in GQL result. + const resultEntity = omitDeep(entityResult[queryName], '__typename'); + + // Filter derived fields in GQL result. + derivedFields.forEach(({ entity, fields }) => { + if (entityName === entity) { + fields.forEach(field => { + delete resultEntity[field]; + }); + } + }); + + const diff = compareObjects(ipldEntity, resultEntity, rawJson); + + return diff; +}; + +const compareObjects = (obj1: any, obj2: any, rawJson: boolean): string => { if (rawJson) { const diffObj = diff(obj1, obj2); diff --git a/packages/graph-node/src/database.ts b/packages/graph-node/src/database.ts index 048326b7..aded4879 100644 --- a/packages/graph-node/src/database.ts +++ b/packages/graph-node/src/database.ts @@ -19,7 +19,7 @@ import { import { Block, fromEntityValue, toEntityValue } from './utils'; -const DEFAULT_LIMIT = 100; +export const DEFAULT_LIMIT = 100; export class Database { _config: ConnectionOptions diff --git a/packages/graph-node/src/eden.test.ts b/packages/graph-node/src/eden.test.ts index 4731efb7..9348b718 100644 --- a/packages/graph-node/src/eden.test.ts +++ b/packages/graph-node/src/eden.test.ts @@ -77,7 +77,8 @@ xdescribe('eden wasm loader tests', async () => { }, dataSource: { address: contractAddress, - network: 'mainnet' + network: 'mainnet', + name: 'EdenNetwork' } }; @@ -197,7 +198,8 @@ xdescribe('eden wasm loader tests', async () => { }, dataSource: { address: contractAddress, - network: 'mainnet' + network: 'mainnet', + name: 'EdenNetworkDistribution' } }; @@ -313,7 +315,8 @@ xdescribe('eden wasm loader tests', async () => { }, dataSource: { address: contractAddress, - network: 'mainnet' + network: 'mainnet', + name: 'EdenNetworkGovernance' } }; diff --git a/packages/graph-node/src/eth-call.test.ts b/packages/graph-node/src/eth-call.test.ts index 95f00295..2151dee6 100644 --- a/packages/graph-node/src/eth-call.test.ts +++ b/packages/graph-node/src/eth-call.test.ts @@ -29,7 +29,8 @@ xdescribe('eth-call wasm tests', () => { }, dataSource: { address: contractAddress, - network: 'mainnet' + network: 'mainnet', + name: 'Example1' } }; diff --git a/packages/graph-node/src/loader.ts b/packages/graph-node/src/loader.ts index 10fb24cf..3477f19e 100644 --- a/packages/graph-node/src/loader.ts +++ b/packages/graph-node/src/loader.ts @@ -15,7 +15,7 @@ import debug from 'debug'; import { BaseProvider } from '@ethersproject/providers'; import loader from '@vulcanize/assemblyscript/lib/loader'; -import { IndexerInterface, GraphDecimal, getGraphDigitsAndExp } from '@vulcanize/util'; +import { IndexerInterface, GraphDecimal, getGraphDigitsAndExp, jsonBigIntStringReplacer } from '@vulcanize/util'; import { TypeId, Level } from './types'; import { @@ -25,8 +25,7 @@ import { resolveEntityFieldConflicts, getEthereumTypes, jsonFromBytes, - getStorageValueType, - jsonBigIntStringReplacer + getStorageValueType } from './utils'; import { Database } from './database'; @@ -41,6 +40,7 @@ export interface GraphData { abis?: {[key: string]: ContractInterface}; dataSource: { network: string; + name: string; }; } @@ -261,10 +261,9 @@ export const instantiate = async ( return toEthereumValue(instanceExports, utils.ParamType.from(typesString), decoded); }, - 'ethereum.storageValue': async (contractName: number, contractAddress: number, variable: number, mappingKeys: number) => { - const contractNameString = __getString(contractName); - const address = await Address.wrap(contractAddress); - const addressStringPtr = await address.toHexString(); + 'ethereum.storageValue': async (variable: number, mappingKeys: number) => { + assert(context.contractAddress); + const addressStringPtr = await __newString(context.contractAddress); const addressString = __getString(addressStringPtr); const variableString = __getString(variable); @@ -276,7 +275,7 @@ export const instantiate = async ( }); const mappingKeyValues = await Promise.all(mappingKeyPromises); - const storageLayout = indexer.storageLayoutMap.get(contractNameString); + const storageLayout = indexer.storageLayoutMap.get(dataSource.name); assert(storageLayout); assert(context.block); diff --git a/packages/graph-node/src/storage-call.test.ts b/packages/graph-node/src/storage-call.test.ts index 4d432bd4..91c0199e 100644 --- a/packages/graph-node/src/storage-call.test.ts +++ b/packages/graph-node/src/storage-call.test.ts @@ -30,7 +30,8 @@ xdescribe('storage-call wasm tests', () => { }, dataSource: { address: contractAddress, - network: 'mainnet' + network: 'mainnet', + name: 'Example1' } }; diff --git a/packages/graph-node/src/utils.ts b/packages/graph-node/src/utils.ts index 6c6f991d..ede17b6c 100644 --- a/packages/graph-node/src/utils.ts +++ b/packages/graph-node/src/utils.ts @@ -798,11 +798,3 @@ const getEthereumType = (storageTypes: StorageLayout['types'], type: string, map return utils.ParamType.from(label); }; - -export const jsonBigIntStringReplacer = (_: string, value: any) => { - if (typeof value === 'bigint') { - return value.toString(); - } - - return value; -}; diff --git a/packages/graph-node/src/watcher.ts b/packages/graph-node/src/watcher.ts index b6384548..7de09028 100644 --- a/packages/graph-node/src/watcher.ts +++ b/packages/graph-node/src/watcher.ts @@ -52,7 +52,7 @@ export class GraphWatcher { // Create wasm instance and contract interface for each dataSource and template in subgraph yaml. const dataPromises = this._dataSources.map(async (dataSource: any) => { - const { source: { abi }, mapping, network } = dataSource; + const { source: { abi }, mapping, network, name } = dataSource; const { abis, file } = mapping; const abisMap = abis.reduce((acc: {[key: string]: ContractInterface}, abi: any) => { @@ -68,7 +68,8 @@ export class GraphWatcher { const data = { abis: abisMap, dataSource: { - network + network, + name } }; diff --git a/packages/graph-node/test/subgraph/eden/EdenNetwork/EdenNetwork.wasm b/packages/graph-node/test/subgraph/eden/EdenNetwork/EdenNetwork.wasm index 612a9fd170e3603bafb4f216a79f9fc09657771e..aa8fc0a9844c74f76c4af58acdb2429fcc9d2a48 100644 GIT binary patch delta 30248 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 delta 29354 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 diff --git a/packages/graph-node/test/subgraph/eden/EdenNetworkDistribution/EdenNetworkDistribution.wasm b/packages/graph-node/test/subgraph/eden/EdenNetworkDistribution/EdenNetworkDistribution.wasm index 7445c27a564e4a925dd40b287930809d8a4214f1..4a2b9f2f223b991a25de8185531ced482481548b 100644 GIT binary patch delta 17430 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 delta 17239 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 delta 20991 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 diff --git a/packages/graph-node/test/subgraph/example1/package.json b/packages/graph-node/test/subgraph/example1/package.json index 8a242889..6f81c051 100644 --- a/packages/graph-node/test/subgraph/example1/package.json +++ b/packages/graph-node/test/subgraph/example1/package.json @@ -10,7 +10,7 @@ "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 example1" }, "dependencies": { - "@graphprotocol/graph-ts": "npm:@vulcanize/graph-ts@0.22.1", + "@graphprotocol/graph-ts": "npm:@vulcanize/graph-ts@0.22.2", "@vulcanize/graph-cli": "0.22.5" } } diff --git a/packages/graph-node/test/subgraph/example1/src/mapping.ts b/packages/graph-node/test/subgraph/example1/src/mapping.ts index 20dca79c..69a2859a 100644 --- a/packages/graph-node/test/subgraph/example1/src/mapping.ts +++ b/packages/graph-node/test/subgraph/example1/src/mapping.ts @@ -165,7 +165,7 @@ export function testGetStorageValue (): void { // Bind the contract to the address. const contractAddress = dataSource.address(); const contract = Example1.bind(contractAddress); - const res = contract.getStorageValue('_test', []); + const res = ethereum.getStorageValue('_test', []); log.debug('Storage call result: {}', [res!.toBigInt().toString()]); } @@ -176,7 +176,7 @@ export function testMapStorageValue (): void { const contractAddress = dataSource.address(); const contract = Example1.bind(contractAddress); const addressValue = ethereum.Value.fromAddress(Address.zero()); - const res = contract.getStorageValue('addressUintMap', [addressValue]); + const res = ethereum.getStorageValue('addressUintMap', [addressValue]); log.debug('Storage call result: {}', [res!.toBigInt().toString()]); } diff --git a/packages/graph-node/test/subgraph/example1/yarn.lock b/packages/graph-node/test/subgraph/example1/yarn.lock index a5a76f26..f0a34df6 100644 --- a/packages/graph-node/test/subgraph/example1/yarn.lock +++ b/packages/graph-node/test/subgraph/example1/yarn.lock @@ -23,10 +23,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@graphprotocol/graph-ts@npm:@vulcanize/graph-ts@0.22.1": - version "0.22.1" - resolved "https://npm.pkg.github.com/download/@vulcanize/graph-ts/0.22.1/7a14baaab8b99d4a88e19620dc7200aa501fbecf#7a14baaab8b99d4a88e19620dc7200aa501fbecf" - integrity sha512-0CoKeFezskYjAsLmqfdxmS7q+gWy1V1wFgiNB4tMJSa2EiPTVG62qlPKkqTduApK2gZX9//rmE5Vb2xcF/v2+w== +"@graphprotocol/graph-ts@npm:@vulcanize/graph-ts@0.22.2": + version "0.22.2" + resolved "https://npm.pkg.github.com/download/@vulcanize/graph-ts/0.22.2/a403a4ef6a5742246c4a1c97695a2f55943eb3a7#a403a4ef6a5742246c4a1c97695a2f55943eb3a7" + integrity sha512-Fscv1owyoeAkS9QsLGXOalMZlb3j0Ge22z+wmpqA6zJHRiSUyyIyiarSz6e0ZTs761oFqqvt00dR6A/4xxf40A== dependencies: assemblyscript "0.19.10" diff --git a/packages/graph-test-watcher/src/cli/export-state.ts b/packages/graph-test-watcher/src/cli/export-state.ts index 69240ada..67928335 100644 --- a/packages/graph-test-watcher/src/cli/export-state.ts +++ b/packages/graph-test-watcher/src/cli/export-state.ts @@ -9,7 +9,7 @@ import debug from 'debug'; import fs from 'fs'; import path from 'path'; -import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients, JobQueue, StateKind } from '@vulcanize/util'; +import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients, JobQueue, StateKind, verifyCheckpointData } from '@vulcanize/util'; import { GraphWatcher, Database as GraphDatabase } from '@vulcanize/graph-node'; import * as codec from '@ipld/dag-cbor'; @@ -34,6 +34,18 @@ const main = async (): Promise => { alias: 'o', type: 'string', describe: 'Export file path' + }, + createCheckpoint: { + alias: 'c', + type: 'boolean', + describe: 'Create new checkpoint', + default: false + }, + verify: { + alias: 'v', + type: 'boolean', + describe: 'Verify checkpoint', + default: true } }).argv; @@ -92,13 +104,22 @@ const main = async (): Promise => { // Create and export checkpoint if checkpointing is on for the contract. if (contract.checkpoint) { - await indexer.createCheckpoint(contract.address, block.blockHash); + if (argv.createCheckpoint) { + log(`Creating checkpoint at block ${block.blockNumber}`); + await indexer.createCheckpoint(contract.address, block.blockHash); + } const ipldBlock = await indexer.getLatestIPLDBlock(contract.address, StateKind.Checkpoint, block.blockNumber); assert(ipldBlock); const data = indexer.getIPLDData(ipldBlock); + if (argv.verify) { + log(`Verifying checkpoint data for contract ${contract.address}`); + await verifyCheckpointData(graphDb, ipldBlock.block, data); + log('Checkpoint data verified'); + } + if (indexer.isIPFSConfigured()) { await indexer.pushToIPFS(data); } diff --git a/packages/graph-test-watcher/src/cli/reset-cmds/state.ts b/packages/graph-test-watcher/src/cli/reset-cmds/state.ts index e243cc74..04845791 100644 --- a/packages/graph-test-watcher/src/cli/reset-cmds/state.ts +++ b/packages/graph-test-watcher/src/cli/reset-cmds/state.ts @@ -71,11 +71,9 @@ export const handler = async (argv: any): Promise => { try { const entities = [BlockProgress, GetMethod, _Test, Author, Category, Blog]; - const removeEntitiesPromise = entities.map(async entityClass => { - return db.removeEntities(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); - }); - - await Promise.all(removeEntitiesPromise); + for (const entity of entities) { + await db.removeEntities(dbTx, entity, { blockNumber: MoreThan(argv.blockNumber) }); + } const syncStatus = await indexer.getSyncStatus(); assert(syncStatus, 'Missing syncStatus'); diff --git a/packages/graph-test-watcher/src/database.ts b/packages/graph-test-watcher/src/database.ts index dc5d265f..e1f13df4 100644 --- a/packages/graph-test-watcher/src/database.ts +++ b/packages/graph-test-watcher/src/database.ts @@ -96,10 +96,10 @@ export class Database implements IPLDDatabaseInterface { } // Fetch all diff IPLDBlocks after the specified block number. - async getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise { + async getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise { const repo = this._conn.getRepository(IPLDBlock); - return this._baseDatabase.getDiffIPLDBlocksByBlocknumber(repo, contractAddress, blockNumber); + return this._baseDatabase.getDiffIPLDBlocksInRange(repo, contractAddress, startBlock, endBlock); } async saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlock): Promise { diff --git a/packages/mobymask-watcher/src/cli/export-state.ts b/packages/mobymask-watcher/src/cli/export-state.ts index 31ac9028..16c8956e 100644 --- a/packages/mobymask-watcher/src/cli/export-state.ts +++ b/packages/mobymask-watcher/src/cli/export-state.ts @@ -33,6 +33,12 @@ const main = async (): Promise => { alias: 'o', type: 'string', describe: 'Export file path' + }, + createCheckpoint: { + alias: 'c', + type: 'boolean', + describe: 'Create new checkpoint', + default: false } }).argv; @@ -83,7 +89,10 @@ const main = async (): Promise => { // Create and export checkpoint if checkpointing is on for the contract. if (contract.checkpoint) { - await indexer.createCheckpoint(contract.address, block.blockHash); + if (argv.createCheckpoint) { + log(`Creating checkpoint at block ${block.blockNumber}`); + await indexer.createCheckpoint(contract.address, block.blockHash); + } const ipldBlock = await indexer.getLatestIPLDBlock(contract.address, StateKind.Checkpoint, block.blockNumber); assert(ipldBlock); diff --git a/packages/mobymask-watcher/src/cli/reset-cmds/state.ts b/packages/mobymask-watcher/src/cli/reset-cmds/state.ts index c0c29be4..58022bf7 100644 --- a/packages/mobymask-watcher/src/cli/reset-cmds/state.ts +++ b/packages/mobymask-watcher/src/cli/reset-cmds/state.ts @@ -61,11 +61,9 @@ export const handler = async (argv: any): Promise => { try { const entities = [BlockProgress, MultiNonce, _Owner, IsRevoked, IsPhisher, IsMember]; - const removeEntitiesPromise = entities.map(async entityClass => { - return db.removeEntities(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); - }); - - await Promise.all(removeEntitiesPromise); + for (const entity of entities) { + await db.removeEntities(dbTx, entity, { blockNumber: MoreThan(argv.blockNumber) }); + } const syncStatus = await indexer.getSyncStatus(); assert(syncStatus, 'Missing syncStatus'); diff --git a/packages/mobymask-watcher/src/database.ts b/packages/mobymask-watcher/src/database.ts index 2309093b..a766813a 100644 --- a/packages/mobymask-watcher/src/database.ts +++ b/packages/mobymask-watcher/src/database.ts @@ -155,10 +155,10 @@ export class Database implements IPLDDatabaseInterface { } // Fetch all diff IPLDBlocks after the specified block number. - async getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise { + async getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise { const repo = this._conn.getRepository(IPLDBlock); - return this._baseDatabase.getDiffIPLDBlocksByBlocknumber(repo, contractAddress, blockNumber); + return this._baseDatabase.getDiffIPLDBlocksInRange(repo, contractAddress, startBlock, endBlock); } async saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlock): Promise { diff --git a/packages/util/src/common.ts b/packages/util/src/common.ts index 796e158c..1289a5c5 100644 --- a/packages/util/src/common.ts +++ b/packages/util/src/common.ts @@ -111,7 +111,7 @@ export const processBlockByNumber = async ( */ export const processBatchEvents = async (indexer: IndexerInterface, block: BlockProgressInterface, eventsInBatch: number): Promise => { // Check if block processing is complete. - while (!block.isComplete) { + while (block.numProcessedEvents < block.numEvents) { console.time('time:common#processBacthEvents-fetching_events_batch'); // Fetch events in batches @@ -193,6 +193,11 @@ export const processBatchEvents = async (indexer: IndexerInterface, block: Block } if (indexer.processBlockAfterEvents) { - await indexer.processBlockAfterEvents(block.blockHash); + if (!block.isComplete || block.numEvents === 0) { + await indexer.processBlockAfterEvents(block.blockHash); + } } + + block.isComplete = true; + await indexer.updateBlockProgress(block, block.lastProcessedEventIndex); }; diff --git a/packages/util/src/database.ts b/packages/util/src/database.ts index 191def14..4cc6d375 100644 --- a/packages/util/src/database.ts +++ b/packages/util/src/database.ts @@ -179,22 +179,17 @@ export class Database { block.lastProcessedEventIndex = lastProcessedEventIndex; block.numProcessedEvents++; - if (block.numProcessedEvents >= block.numEvents) { - block.isComplete = true; - } - - const { generatedMaps } = await repo.createQueryBuilder() - .update() - .set(block) - .where('id = :id', { id: block.id }) - .whereEntity(block) - .returning('*') - .execute(); - - block = generatedMaps[0] as BlockProgressInterface; } - return block; + const { generatedMaps } = await repo.createQueryBuilder() + .update() + .set(block) + .where('id = :id', { id: block.id }) + .whereEntity(block) + .returning('*') + .execute(); + + return generatedMaps[0] as BlockProgressInterface; } async markBlocksAsPruned (repo: Repository, blocks: BlockProgressInterface[]): Promise { diff --git a/packages/util/src/ipld-database.ts b/packages/util/src/ipld-database.ts index 606a6f3e..bd806d20 100644 --- a/packages/util/src/ipld-database.ts +++ b/packages/util/src/ipld-database.ts @@ -2,7 +2,7 @@ // Copyright 2021 Vulcanize, Inc. // -import { FindConditions, MoreThan, Repository } from 'typeorm'; +import { Between, FindConditions, Repository } from 'typeorm'; import assert from 'assert'; import { IPLDBlockInterface, IpldStatusInterface, StateKind } from './types'; @@ -28,6 +28,9 @@ export class IPLDDatabase extends Database { : queryBuilder.andWhere('ipld_block.kind != :kind', { kind: StateKind.DiffStaged }) .addOrderBy('ipld_block.id', 'DESC'); + // Get the first entry. + queryBuilder.limit(1); + return queryBuilder.getOne(); } @@ -102,6 +105,9 @@ export class IPLDDatabase extends Database { ? queryBuilder.andWhere('ipld_block.kind = :kind', { kind }) : queryBuilder.addOrderBy('ipld_block.id', 'DESC'); + // Get the first entry. + queryBuilder.limit(1); + result = await queryBuilder.getOne(); } @@ -112,7 +118,7 @@ export class IPLDDatabase extends Database { return repo.find({ where, relations: ['block'] }); } - async getDiffIPLDBlocksByBlocknumber (repo: Repository, contractAddress: string, blockNumber: number): Promise { + async getDiffIPLDBlocksInRange (repo: Repository, contractAddress: string, startblock: number, endBlock: number): Promise { return repo.find({ relations: ['block'], where: { @@ -120,7 +126,7 @@ export class IPLDDatabase extends Database { kind: StateKind.Diff, block: { isPruned: false, - blockNumber: MoreThan(blockNumber) + blockNumber: Between(startblock + 1, endBlock) } }, order: { diff --git a/packages/util/src/ipld-helper.ts b/packages/util/src/ipld-helper.ts index 4e46e881..714533f9 100644 --- a/packages/util/src/ipld-helper.ts +++ b/packages/util/src/ipld-helper.ts @@ -1,4 +1,10 @@ import _ from 'lodash'; +import debug from 'debug'; + +import { BlockProgressInterface, GraphDatabaseInterface } from './types'; +import { jsonBigIntStringReplacer } from './misc'; + +const log = debug('vulcanize:ipld-helper'); export const updateStateForElementaryType = (initialObject: any, stateVariable: string, value: any): any => { const object = _.cloneDeep(initialObject); @@ -14,3 +20,51 @@ export const updateStateForMappingType = (initialObject: any, stateVariable: str // Use _.setWith() with Object as customizer as _.set() treats numeric value in path as an index to an array. return _.setWith(object, keys, value, Object); }; + +export const verifyCheckpointData = async (database: GraphDatabaseInterface, block: BlockProgressInterface, data: any) => { + const { state } = data; + + for (const [entityName, idEntityMap] of Object.entries(state)) { + for (const [id, ipldEntity] of Object.entries(idEntityMap as {[key: string]: any})) { + const entityData = await database.getEntity(entityName, id, block.blockHash) as any; + + // Compare entities. + const diffFound = Object.keys(ipldEntity) + .some(key => { + let ipldValue = ipldEntity[key]; + + if (key === 'blockNumber') { + entityData.blockNumber = entityData._blockNumber; + } + + if (key === 'blockHash') { + entityData.blockHash = entityData._blockHash; + } + + if (typeof ipldEntity[key] === 'object' && ipldEntity[key]?.id) { + ipldValue = ipldEntity[key].id; + } + + if ( + Array.isArray(ipldEntity[key]) && + ipldEntity[key].length && + ipldEntity[key][0].id + ) { + // Map IPLD entity 1 to N relation field array to match DB entity. + ipldValue = ipldEntity[key].map(({ id }: { id: string }) => id); + + // Sort DB entity 1 to N relation field array. + entityData[key] = entityData[key].sort((a: string, b: string) => a.localeCompare(b)); + } + + return JSON.stringify(ipldValue) !== JSON.stringify(entityData[key], jsonBigIntStringReplacer); + }); + + if (diffFound) { + const message = `Diff found for checkpoint at block ${block.blockNumber} in entity ${entityName} id ${id}`; + log(message); + throw new Error(message); + } + } + } +}; diff --git a/packages/util/src/ipld-indexer.ts b/packages/util/src/ipld-indexer.ts index 7a42b5a3..68bbf2e6 100644 --- a/packages/util/src/ipld-indexer.ts +++ b/packages/util/src/ipld-indexer.ts @@ -305,14 +305,14 @@ export class IPLDIndexer extends Indexer { // Fetch the latest 'checkpoint' | 'init' for the contract to fetch diffs after it. let prevNonDiffBlock: IPLDBlockInterface; - let getDiffBlockNumber: number; - const checkpointBlock = await this._ipldDb.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint, currentBlock.blockNumber); + let diffStartBlockNumber: number; + const checkpointBlock = await this._ipldDb.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint, currentBlock.blockNumber - 1); if (checkpointBlock) { const checkpointBlockNumber = checkpointBlock.block.blockNumber; prevNonDiffBlock = checkpointBlock; - getDiffBlockNumber = checkpointBlockNumber; + diffStartBlockNumber = checkpointBlockNumber; // Update IPLD status map with the latest checkpoint info. // Essential while importing state as checkpoint at the snapshot block is added by import-state CLI. @@ -325,11 +325,11 @@ export class IPLDIndexer extends Indexer { prevNonDiffBlock = initBlock; // Take block number previous to initial state block as the checkpoint is to be created in the same block. - getDiffBlockNumber = initBlock.block.blockNumber - 1; + diffStartBlockNumber = initBlock.block.blockNumber - 1; } // Fetching all diff blocks after the latest 'checkpoint' | 'init'. - const diffBlocks = await this._ipldDb.getDiffIPLDBlocksByBlocknumber(contractAddress, getDiffBlockNumber); + const diffBlocks = await this._ipldDb.getDiffIPLDBlocksInRange(contractAddress, diffStartBlockNumber, currentBlock.blockNumber); const prevNonDiffBlockData = codec.decode(Buffer.from(prevNonDiffBlock.data)) as any; const data = { @@ -358,7 +358,8 @@ export class IPLDIndexer extends Indexer { let currentIPLDBlock: IPLDBlockInterface | undefined; const prevIPLDBlockNumber = ipldStatus[kind]; - if (prevIPLDBlockNumber && prevIPLDBlockNumber === block.blockNumber) { + // Fetch from DB for previous IPLD block or for checkpoint kind. + if (kind === 'checkpoint' || (prevIPLDBlockNumber && prevIPLDBlockNumber === block.blockNumber)) { const currentIPLDBlocks = await this._ipldDb.getIPLDBlocks({ block, contractAddress, kind }); // There can be at most one IPLDBlock for a (block, contractAddress, kind) combination. diff --git a/packages/util/src/misc.ts b/packages/util/src/misc.ts index d8d55eb6..1e5d44ae 100644 --- a/packages/util/src/misc.ts +++ b/packages/util/src/misc.ts @@ -238,3 +238,11 @@ export const getFullTransaction = async (ethClient: EthClient, txHash: string): maxFeePerGas: txData.maxFeePerGas?.toString() }; }; + +export const jsonBigIntStringReplacer = (_: string, value: any) => { + if (typeof value === 'bigint') { + return value.toString(); + } + + return value; +}; diff --git a/packages/util/src/types.ts b/packages/util/src/types.ts index b1f0cda6..bf8f0d52 100644 --- a/packages/util/src/types.ts +++ b/packages/util/src/types.ts @@ -152,9 +152,13 @@ export interface DatabaseInterface { export interface IPLDDatabaseInterface extends DatabaseInterface { getLatestIPLDBlock (contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise getIPLDBlocks (where: FindConditions): Promise - getDiffIPLDBlocksByBlocknumber (contractAddress: string, blockNumber: number): Promise + getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise getNewIPLDBlock (): IPLDBlockInterface removeIPLDBlocks(dbTx: QueryRunner, blockNumber: number, kind: StateKind): Promise saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlockInterface): Promise getIPLDStatus (): Promise } + +export interface GraphDatabaseInterface { + getEntity (entity: (new () => Entity) | string, id: string, blockHash?: string): Promise; +}