mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-08 12:28:05 +00:00
Changes to use packages in uniswap-watcher (#196)
* Changes to use util from uniswap-watcher * Refactor ResultIPLDBlock to util * Verify state in compare CLI for uniswap multiple queries * Prepare watcher-ts for publishing packages * Fix verify state in compare CLI for multiple entities query * Fix codegen util imports
This commit is contained in:
parent
978f0bb456
commit
e1aef1a7e9
@ -4,5 +4,10 @@
|
|||||||
],
|
],
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true
|
"useWorkspaces": true,
|
||||||
|
"command": {
|
||||||
|
"publish": {
|
||||||
|
"registry": "https://git.vdb.to/api/packages/cerc-io/npm/"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"build": "lerna run build --stream",
|
"build": "lerna run build --stream",
|
||||||
"build:watch": "lerna run build --stream --parallel -- -w",
|
"build:watch": "lerna run build --stream --parallel -- -w",
|
||||||
"db:reset": "sudo ./scripts/reset-dbs.sh",
|
"db:reset": "sudo ./scripts/reset-dbs.sh",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install",
|
||||||
|
"publish:workspace": "yarn build && lerna publish"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
packages/cache/.npmignore
vendored
Normal file
5
packages/cache/.npmignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/src/
|
||||||
|
index.ts
|
||||||
|
tsconfig.json
|
||||||
|
.eslintrc.json
|
||||||
|
.eslintignore
|
1
packages/cache/package.json
vendored
1
packages/cache/package.json
vendored
@ -2,7 +2,6 @@
|
|||||||
"name": "@cerc-io/cache",
|
"name": "@cerc-io/cache",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Generic object cache",
|
"description": "Generic object cache",
|
||||||
"private": true,
|
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||||||
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions } from 'typeorm';
|
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions } from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { IPLDDatabase as BaseDatabase, IPLDDatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Contract } from './entity/Contract';
|
import { Contract } from './entity/Contract';
|
||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
@ -18,7 +18,7 @@ import { IPLDBlock } from './entity/IPLDBlock';
|
|||||||
import { {{query.entityName}} } from './entity/{{query.entityName}}';
|
import { {{query.entityName}} } from './entity/{{query.entityName}}';
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
export class Database implements IPLDDatabaseInterface {
|
export class Database implements DatabaseInterface {
|
||||||
_config: ConnectionOptions;
|
_config: ConnectionOptions;
|
||||||
_conn!: Connection;
|
_conn!: Connection;
|
||||||
_baseDatabase: BaseDatabase;
|
_baseDatabase: BaseDatabase;
|
||||||
@ -183,11 +183,23 @@ export class Database implements IPLDDatabaseInterface {
|
|||||||
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
||||||
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
const eventRepo = queryRunner.manager.getRepository(Event);
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
|
return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise<void> {
|
||||||
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveEvents(eventRepo, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveBlockProgress(repo, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
||||||
|
@ -16,8 +16,8 @@ import * as codec from '@ipld/dag-cbor';
|
|||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
import {
|
import {
|
||||||
IPLDIndexer as BaseIndexer,
|
Indexer as BaseIndexer,
|
||||||
IPLDIndexerInterface,
|
IndexerInterface,
|
||||||
ValueResult,
|
ValueResult,
|
||||||
UNKNOWN_EVENT_NAME,
|
UNKNOWN_EVENT_NAME,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
@ -31,7 +31,8 @@ import {
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
IPFSClient,
|
IPFSClient,
|
||||||
StateKind,
|
StateKind,
|
||||||
IpldStatus as IpldStatusInterface
|
IpldStatus as IpldStatusInterface,
|
||||||
|
ResultIPLDBlock
|
||||||
} from '@cerc-io/util';
|
} from '@cerc-io/util';
|
||||||
{{#if (subgraphPath)}}
|
{{#if (subgraphPath)}}
|
||||||
import { GraphWatcher } from '@cerc-io/graph-node';
|
import { GraphWatcher } from '@cerc-io/graph-node';
|
||||||
@ -88,21 +89,7 @@ export type ResultEvent = {
|
|||||||
proof: string;
|
proof: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResultIPLDBlock = {
|
export class Indexer implements IndexerInterface {
|
||||||
block: {
|
|
||||||
cid: string;
|
|
||||||
hash: string;
|
|
||||||
number: number;
|
|
||||||
timestamp: number;
|
|
||||||
parentHash: string;
|
|
||||||
};
|
|
||||||
contractAddress: string;
|
|
||||||
cid: string;
|
|
||||||
kind: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Indexer implements IPLDIndexerInterface {
|
|
||||||
_db: Database
|
_db: Database
|
||||||
_ethClient: EthClient
|
_ethClient: EthClient
|
||||||
_ethProvider: BaseProvider
|
_ethProvider: BaseProvider
|
||||||
@ -603,7 +590,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<Contract | undefined> {
|
isWatchedContract (address : string): Contract | undefined {
|
||||||
return this._baseIndexer.isWatchedContract(address);
|
return this._baseIndexer.isWatchedContract(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,8 +642,8 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
return this._baseIndexer.fetchBlockEvents(block, this._fetchAndSaveEvents.bind(this));
|
return this._baseIndexer.fetchBlockWithEvents(block, this._fetchAndSaveEvents.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
||||||
@ -866,7 +853,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
const blockProgress = await this._db.saveEvents(dbTx, block, dbEvents);
|
const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents);
|
||||||
await dbTx.commitTransaction();
|
await dbTx.commitTransaction();
|
||||||
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||||||
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions } from 'typeorm';
|
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions } from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { IPLDDatabase as BaseDatabase, IPLDDatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Contract } from './entity/Contract';
|
import { Contract } from './entity/Contract';
|
||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
@ -15,7 +15,7 @@ import { IpldStatus } from './entity/IpldStatus';
|
|||||||
import { BlockProgress } from './entity/BlockProgress';
|
import { BlockProgress } from './entity/BlockProgress';
|
||||||
import { IPLDBlock } from './entity/IPLDBlock';
|
import { IPLDBlock } from './entity/IPLDBlock';
|
||||||
|
|
||||||
export class Database implements IPLDDatabaseInterface {
|
export class Database implements DatabaseInterface {
|
||||||
_config: ConnectionOptions;
|
_config: ConnectionOptions;
|
||||||
_conn!: Connection;
|
_conn!: Connection;
|
||||||
_baseDatabase: BaseDatabase;
|
_baseDatabase: BaseDatabase;
|
||||||
@ -143,11 +143,23 @@ export class Database implements IPLDDatabaseInterface {
|
|||||||
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
||||||
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
const eventRepo = queryRunner.manager.getRepository(Event);
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
|
return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise<void> {
|
||||||
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveEvents(eventRepo, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveBlockProgress(repo, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
||||||
|
@ -16,7 +16,7 @@ import * as codec from '@ipld/dag-cbor';
|
|||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
import {
|
import {
|
||||||
IPLDIndexer as BaseIndexer,
|
Indexer as BaseIndexer,
|
||||||
UNKNOWN_EVENT_NAME,
|
UNKNOWN_EVENT_NAME,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
JobQueue,
|
JobQueue,
|
||||||
@ -25,9 +25,10 @@ import {
|
|||||||
BlockHeight,
|
BlockHeight,
|
||||||
IPFSClient,
|
IPFSClient,
|
||||||
StateKind,
|
StateKind,
|
||||||
IPLDIndexerInterface,
|
IndexerInterface,
|
||||||
IpldStatus as IpldStatusInterface,
|
IpldStatus as IpldStatusInterface,
|
||||||
ValueResult
|
ValueResult,
|
||||||
|
ResultIPLDBlock
|
||||||
} from '@cerc-io/util';
|
} from '@cerc-io/util';
|
||||||
import { GraphWatcher } from '@cerc-io/graph-node';
|
import { GraphWatcher } from '@cerc-io/graph-node';
|
||||||
|
|
||||||
@ -90,21 +91,7 @@ export type ResultEvent = {
|
|||||||
proof: string;
|
proof: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResultIPLDBlock = {
|
export class Indexer implements IndexerInterface {
|
||||||
block: {
|
|
||||||
cid: string;
|
|
||||||
hash: string;
|
|
||||||
number: number;
|
|
||||||
timestamp: number;
|
|
||||||
parentHash: string;
|
|
||||||
};
|
|
||||||
contractAddress: string;
|
|
||||||
cid: string;
|
|
||||||
kind: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Indexer implements IPLDIndexerInterface {
|
|
||||||
_db: Database
|
_db: Database
|
||||||
_ethClient: EthClient
|
_ethClient: EthClient
|
||||||
_ethProvider: BaseProvider
|
_ethProvider: BaseProvider
|
||||||
@ -540,7 +527,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<Contract | undefined> {
|
isWatchedContract (address : string): Contract | undefined {
|
||||||
return this._baseIndexer.isWatchedContract(address);
|
return this._baseIndexer.isWatchedContract(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,8 +575,8 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
return this._baseIndexer.fetchBlockEvents(block, this._fetchAndSaveEvents.bind(this));
|
return this._baseIndexer.fetchBlockWithEvents(block, this._fetchAndSaveEvents.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
||||||
@ -1120,7 +1107,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
const blockProgress = await this._db.saveEvents(dbTx, block, dbEvents);
|
const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents);
|
||||||
await dbTx.commitTransaction();
|
await dbTx.commitTransaction();
|
||||||
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||||||
import { Connection, ConnectionOptions, DeepPartial, FindConditions, FindManyOptions, QueryRunner } from 'typeorm';
|
import { Connection, ConnectionOptions, DeepPartial, FindConditions, FindManyOptions, QueryRunner } from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { Database as BaseDatabase, QueryOptions, Where } from '@cerc-io/util';
|
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Allowance } from './entity/Allowance';
|
import { Allowance } from './entity/Allowance';
|
||||||
import { Balance } from './entity/Balance';
|
import { Balance } from './entity/Balance';
|
||||||
@ -14,8 +14,10 @@ import { Contract } from './entity/Contract';
|
|||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
import { SyncStatus } from './entity/SyncStatus';
|
import { SyncStatus } from './entity/SyncStatus';
|
||||||
import { BlockProgress } from './entity/BlockProgress';
|
import { BlockProgress } from './entity/BlockProgress';
|
||||||
|
import { IPLDBlock } from './entity/IPLDBlock';
|
||||||
|
import { IpldStatus } from './entity/IpldStatus';
|
||||||
|
|
||||||
export class Database {
|
export class Database implements DatabaseInterface {
|
||||||
_config: ConnectionOptions
|
_config: ConnectionOptions
|
||||||
_conn!: Connection
|
_conn!: Connection
|
||||||
_baseDatabase: BaseDatabase;
|
_baseDatabase: BaseDatabase;
|
||||||
@ -39,6 +41,47 @@ export class Database {
|
|||||||
return this._baseDatabase.close();
|
return this._baseDatabase.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNewIPLDBlock (): IPLDBlock {
|
||||||
|
return new IPLDBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPLDBlocks (where: FindConditions<IPLDBlock>): Promise<IPLDBlock[]> {
|
||||||
|
const repo = this._conn.getRepository(IPLDBlock);
|
||||||
|
|
||||||
|
return this._baseDatabase.getIPLDBlocks(repo, where);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestIPLDBlock (contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise<IPLDBlock | undefined> {
|
||||||
|
const repo = this._conn.getRepository(IPLDBlock);
|
||||||
|
|
||||||
|
return this._baseDatabase.getLatestIPLDBlock(repo, contractAddress, kind, blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all diff IPLDBlocks after the specified block number.
|
||||||
|
async getDiffIPLDBlocksInRange (contractAddress: string, startblock: number, endBlock: number): Promise<IPLDBlock[]> {
|
||||||
|
const repo = this._conn.getRepository(IPLDBlock);
|
||||||
|
|
||||||
|
return this._baseDatabase.getDiffIPLDBlocksInRange(repo, contractAddress, startblock, endBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveOrUpdateIPLDBlock (dbTx: QueryRunner, ipldBlock: IPLDBlock): Promise<IPLDBlock> {
|
||||||
|
const repo = dbTx.manager.getRepository(IPLDBlock);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveOrUpdateIPLDBlock(repo, ipldBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeIPLDBlocks (dbTx: QueryRunner, blockNumber: number, kind: string): Promise<void> {
|
||||||
|
const repo = dbTx.manager.getRepository(IPLDBlock);
|
||||||
|
|
||||||
|
await this._baseDatabase.removeIPLDBlocks(repo, blockNumber, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPLDStatus (): Promise<IpldStatus | undefined> {
|
||||||
|
const repo = this._conn.getRepository(IpldStatus);
|
||||||
|
|
||||||
|
return this._baseDatabase.getIPLDStatus(repo);
|
||||||
|
}
|
||||||
|
|
||||||
async getBalance ({ blockHash, token, owner }: { blockHash: string, token: string, owner: string }): Promise<Balance | undefined> {
|
async getBalance ({ blockHash, token, owner }: { blockHash: string, token: string, owner: string }): Promise<Balance | undefined> {
|
||||||
return this._conn.getRepository(Balance)
|
return this._conn.getRepository(Balance)
|
||||||
.createQueryBuilder('balance')
|
.createQueryBuilder('balance')
|
||||||
@ -107,11 +150,17 @@ export class Database {
|
|||||||
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
||||||
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
const eventRepo = queryRunner.manager.getRepository(Event);
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
|
return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise<void> {
|
||||||
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveEvents(eventRepo, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
||||||
@ -173,6 +222,12 @@ export class Database {
|
|||||||
return this._baseDatabase.getBlockProgressEntities(repo, where, options);
|
return this._baseDatabase.getBlockProgressEntities(repo, where, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveBlockProgress(repo, block);
|
||||||
|
}
|
||||||
|
|
||||||
async updateBlockProgress (queryRunner: QueryRunner, block: BlockProgress, lastProcessedEventIndex: number): Promise<BlockProgress> {
|
async updateBlockProgress (queryRunner: QueryRunner, block: BlockProgress, lastProcessedEventIndex: number): Promise<BlockProgress> {
|
||||||
const repo = queryRunner.manager.getRepository(BlockProgress);
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
36
packages/erc20-watcher/src/entity/IPLDBlock.ts
Normal file
36
packages/erc20-watcher/src/entity/IPLDBlock.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022 Vulcanize, Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
|
||||||
|
|
||||||
|
import { StateKind } from '@cerc-io/util';
|
||||||
|
|
||||||
|
import { BlockProgress } from './BlockProgress';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
@Index(['cid'], { unique: true })
|
||||||
|
@Index(['block', 'contractAddress'])
|
||||||
|
@Index(['block', 'contractAddress', 'kind'], { unique: true })
|
||||||
|
export class IPLDBlock {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@ManyToOne(() => BlockProgress, { onDelete: 'CASCADE' })
|
||||||
|
block!: BlockProgress;
|
||||||
|
|
||||||
|
@Column('varchar', { length: 42 })
|
||||||
|
contractAddress!: string;
|
||||||
|
|
||||||
|
@Column('varchar')
|
||||||
|
cid!: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: StateKind
|
||||||
|
})
|
||||||
|
kind!: StateKind;
|
||||||
|
|
||||||
|
@Column('bytea')
|
||||||
|
data!: Buffer;
|
||||||
|
}
|
20
packages/erc20-watcher/src/entity/IpldStatus.ts
Normal file
20
packages/erc20-watcher/src/entity/IpldStatus.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2022 Vulcanize, Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class IpldStatus {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@Column('integer')
|
||||||
|
latestHooksBlockNumber!: number;
|
||||||
|
|
||||||
|
@Column('integer', { nullable: true })
|
||||||
|
latestCheckpointBlockNumber!: number;
|
||||||
|
|
||||||
|
@Column('integer', { nullable: true })
|
||||||
|
latestIPFSBlockNumber!: number;
|
||||||
|
}
|
@ -12,7 +12,7 @@ import { BaseProvider } from '@ethersproject/providers';
|
|||||||
|
|
||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
import { IndexerInterface, Indexer as BaseIndexer, ValueResult, UNKNOWN_EVENT_NAME, JobQueue, Where, QueryOptions, ServerConfig } from '@cerc-io/util';
|
import { IndexerInterface, Indexer as BaseIndexer, ValueResult, UNKNOWN_EVENT_NAME, JobQueue, Where, QueryOptions, ServerConfig, IPFSClient, IpldStatus as IpldStatusInterface } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
@ -21,6 +21,7 @@ import { SyncStatus } from './entity/SyncStatus';
|
|||||||
import artifacts from './artifacts/ERC20.json';
|
import artifacts from './artifacts/ERC20.json';
|
||||||
import { BlockProgress } from './entity/BlockProgress';
|
import { BlockProgress } from './entity/BlockProgress';
|
||||||
import { Contract } from './entity/Contract';
|
import { Contract } from './entity/Contract';
|
||||||
|
import { IPLDBlock } from './entity/IPLDBlock';
|
||||||
|
|
||||||
const log = debug('vulcanize:indexer');
|
const log = debug('vulcanize:indexer');
|
||||||
const JSONbigNative = JSONbig({ useNativeBigInt: true });
|
const JSONbigNative = JSONbig({ useNativeBigInt: true });
|
||||||
@ -63,7 +64,8 @@ export class Indexer implements IndexerInterface {
|
|||||||
this._ethProvider = ethProvider;
|
this._ethProvider = ethProvider;
|
||||||
this._serverConfig = serverConfig;
|
this._serverConfig = serverConfig;
|
||||||
this._serverMode = serverConfig.mode;
|
this._serverMode = serverConfig.mode;
|
||||||
this._baseIndexer = new BaseIndexer(this._db, this._ethClient, this._ethProvider, jobQueue);
|
const ipfsClient = new IPFSClient(this._serverConfig.ipfsApiAddr);
|
||||||
|
this._baseIndexer = new BaseIndexer(serverConfig, this._db, this._ethClient, this._ethProvider, jobQueue, ipfsClient);
|
||||||
|
|
||||||
const { abi, storageLayout } = artifacts;
|
const { abi, storageLayout } = artifacts;
|
||||||
|
|
||||||
@ -248,6 +250,10 @@ export class Indexer implements IndexerInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIPLDData (ipldBlock: IPLDBlock): any {
|
||||||
|
return this._baseIndexer.getIPLDData(ipldBlock);
|
||||||
|
}
|
||||||
|
|
||||||
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
||||||
const { eventName, eventInfo, contract: token, block: { blockHash } } = event;
|
const { eventName, eventInfo, contract: token, block: { blockHash } } = event;
|
||||||
const eventFields = JSON.parse(eventInfo);
|
const eventFields = JSON.parse(eventInfo);
|
||||||
@ -278,6 +284,11 @@ export class Indexer implements IndexerInterface {
|
|||||||
await this.triggerIndexingOnEvent(event);
|
await this.triggerIndexingOnEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async processBlock (blockProgress: BlockProgress): Promise<void> {
|
||||||
|
// Call a function to create initial state for contracts.
|
||||||
|
await this._baseIndexer.createInit(this, blockProgress.blockHash, blockProgress.blockNumber);
|
||||||
|
}
|
||||||
|
|
||||||
parseEventNameAndArgs (kind: string, logObj: any): any {
|
parseEventNameAndArgs (kind: string, logObj: any): any {
|
||||||
const { topics, data } = logObj;
|
const { topics, data } = logObj;
|
||||||
const logDescription = this._contract.parseLog({ data, topics });
|
const logDescription = this._contract.parseLog({ data, topics });
|
||||||
@ -291,7 +302,7 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<Contract | undefined> {
|
isWatchedContract (address : string): Contract | undefined {
|
||||||
return this._baseIndexer.isWatchedContract(address);
|
return this._baseIndexer.isWatchedContract(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,6 +310,10 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.watchContract(address, kind, checkpoint, startingBlock);
|
return this._baseIndexer.watchContract(address, kind, checkpoint, startingBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateIPLDStatusMap (address: string, ipldStatus: IpldStatusInterface): Promise<void> {
|
||||||
|
await this._baseIndexer.updateIPLDStatusMap(address, ipldStatus);
|
||||||
|
}
|
||||||
|
|
||||||
cacheContract (contract: Contract): void {
|
cacheContract (contract: Contract): void {
|
||||||
return this._baseIndexer.cacheContract(contract);
|
return this._baseIndexer.cacheContract(contract);
|
||||||
}
|
}
|
||||||
@ -351,8 +366,8 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
return this._baseIndexer.fetchBlockEvents(block, this._fetchAndSaveEvents.bind(this));
|
return this._baseIndexer.fetchBlockWithEvents(block, this._fetchAndSaveEvents.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
||||||
@ -451,7 +466,7 @@ export class Indexer implements IndexerInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
const blockProgress = await this._db.saveEvents(dbTx, block, dbEvents);
|
const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents);
|
||||||
await dbTx.commitTransaction();
|
await dbTx.commitTransaction();
|
||||||
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||||||
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions, FindOneOptions, LessThanOrEqual } from 'typeorm';
|
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions, FindOneOptions, LessThanOrEqual } from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { IPLDDatabase as BaseDatabase, IPLDDatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Contract } from './entity/Contract';
|
import { Contract } from './entity/Contract';
|
||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
@ -30,7 +30,7 @@ import { _TokenApprovals } from './entity/_TokenApprovals';
|
|||||||
import { _OperatorApprovals } from './entity/_OperatorApprovals';
|
import { _OperatorApprovals } from './entity/_OperatorApprovals';
|
||||||
import { TransferCount } from './entity/TransferCount';
|
import { TransferCount } from './entity/TransferCount';
|
||||||
|
|
||||||
export class Database implements IPLDDatabaseInterface {
|
export class Database implements DatabaseInterface {
|
||||||
_config: ConnectionOptions;
|
_config: ConnectionOptions;
|
||||||
_conn!: Connection;
|
_conn!: Connection;
|
||||||
_baseDatabase: BaseDatabase;
|
_baseDatabase: BaseDatabase;
|
||||||
@ -398,11 +398,23 @@ export class Database implements IPLDDatabaseInterface {
|
|||||||
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
||||||
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
const eventRepo = queryRunner.manager.getRepository(Event);
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
|
return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise<void> {
|
||||||
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveEvents(eventRepo, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveBlockProgress(repo, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
||||||
|
@ -38,7 +38,7 @@ export async function createInitialState (indexer: Indexer, contractAddress: str
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook function to create state diff.
|
* Hook function to create state diff.
|
||||||
* @param indexer Indexer instance that contains methods to fetch the contract varaiable values.
|
* @param indexer Indexer instance that contains methods to fetch the contract variable values.
|
||||||
* @param blockHash Block hash of the concerned block.
|
* @param blockHash Block hash of the concerned block.
|
||||||
*/
|
*/
|
||||||
export async function createStateDiff (indexer: Indexer, blockHash: string): Promise<void> {
|
export async function createStateDiff (indexer: Indexer, blockHash: string): Promise<void> {
|
||||||
|
@ -14,8 +14,8 @@ import * as codec from '@ipld/dag-cbor';
|
|||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
import {
|
import {
|
||||||
IPLDIndexer as BaseIndexer,
|
Indexer as BaseIndexer,
|
||||||
IPLDIndexerInterface,
|
IndexerInterface,
|
||||||
ValueResult,
|
ValueResult,
|
||||||
UNKNOWN_EVENT_NAME,
|
UNKNOWN_EVENT_NAME,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
@ -27,7 +27,8 @@ import {
|
|||||||
BlockHeight,
|
BlockHeight,
|
||||||
IPFSClient,
|
IPFSClient,
|
||||||
StateKind,
|
StateKind,
|
||||||
IpldStatus as IpldStatusInterface
|
IpldStatus as IpldStatusInterface,
|
||||||
|
ResultIPLDBlock
|
||||||
} from '@cerc-io/util';
|
} from '@cerc-io/util';
|
||||||
|
|
||||||
import ERC721Artifacts from './artifacts/ERC721.json';
|
import ERC721Artifacts from './artifacts/ERC721.json';
|
||||||
@ -70,21 +71,7 @@ export type ResultEvent = {
|
|||||||
proof: string;
|
proof: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResultIPLDBlock = {
|
export class Indexer implements IndexerInterface {
|
||||||
block: {
|
|
||||||
cid: string;
|
|
||||||
hash: string;
|
|
||||||
number: number;
|
|
||||||
timestamp: number;
|
|
||||||
parentHash: string;
|
|
||||||
};
|
|
||||||
contractAddress: string;
|
|
||||||
cid: string;
|
|
||||||
kind: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Indexer implements IPLDIndexerInterface {
|
|
||||||
_db: Database
|
_db: Database
|
||||||
_ethClient: EthClient
|
_ethClient: EthClient
|
||||||
_ethProvider: BaseProvider
|
_ethProvider: BaseProvider
|
||||||
@ -907,7 +894,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<Contract | undefined> {
|
isWatchedContract (address : string): Contract | undefined {
|
||||||
return this._baseIndexer.isWatchedContract(address);
|
return this._baseIndexer.isWatchedContract(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -959,8 +946,8 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
return this._baseIndexer.fetchBlockEvents(block, this._fetchAndSaveEvents.bind(this));
|
return this._baseIndexer.fetchBlockWithEvents(block, this._fetchAndSaveEvents.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
||||||
@ -1098,7 +1085,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
const blockProgress = await this._db.saveEvents(dbTx, block, dbEvents);
|
const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents);
|
||||||
await dbTx.commitTransaction();
|
await dbTx.commitTransaction();
|
||||||
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
entitiesDir = "../../graph-test-watcher/dist/entity/*"
|
entitiesDir = "../../graph-test-watcher/dist/entity/*"
|
||||||
endpoint = "gqlEndpoint2"
|
endpoint = "gqlEndpoint2"
|
||||||
verifyState = true
|
verifyState = true
|
||||||
derivedFields = []
|
skipFields = []
|
||||||
|
|
||||||
[cache]
|
[cache]
|
||||||
endpoint = "gqlEndpoint1"
|
endpoint = "gqlEndpoint1"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphprotocol/graph-ts": "^0.22.0",
|
"@graphprotocol/graph-ts": "^0.22.0",
|
||||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||||
|
@ -12,7 +12,7 @@ import _ from 'lodash';
|
|||||||
import { getConfig as getWatcherConfig, wait } from '@cerc-io/util';
|
import { getConfig as getWatcherConfig, wait } from '@cerc-io/util';
|
||||||
import { GraphQLClient } from '@cerc-io/ipld-eth-client';
|
import { GraphQLClient } from '@cerc-io/ipld-eth-client';
|
||||||
|
|
||||||
import { checkEntityInIPLDState, compareQuery, Config, getIPLDsByBlock, checkIPLDMetaData, combineIPLDState, getClients, getConfig } from './utils';
|
import { checkGQLEntityInIPLDState, compareQuery, Config, getIPLDsByBlock, checkIPLDMetaData, combineIPLDState, getClients, getConfig, checkGQLEntitiesInIPLDState } from './utils';
|
||||||
import { Database } from '../../database';
|
import { Database } from '../../database';
|
||||||
import { getSubgraphConfig } from '../../utils';
|
import { getSubgraphConfig } from '../../utils';
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ export const main = async (): Promise<void> => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (config.watcher.verifyState) {
|
if (config.watcher.verifyState) {
|
||||||
const ipldDiff = await checkEntityInIPLDState(ipldStateByBlock, queryName, result, id, rawJson, config.watcher.derivedFields);
|
const ipldDiff = await checkGQLEntityInIPLDState(ipldStateByBlock, entityName, result[queryName], id, rawJson, config.watcher.skipFields);
|
||||||
|
|
||||||
if (ipldDiff) {
|
if (ipldDiff) {
|
||||||
log('Results mismatch for IPLD state:', ipldDiff);
|
log('Results mismatch for IPLD state:', ipldDiff);
|
||||||
@ -182,13 +182,24 @@ export const main = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (updatedEntities.has(entityName)) {
|
if (updatedEntities.has(entityName)) {
|
||||||
({ diff: resultDiff } = await compareQuery(
|
let result;
|
||||||
|
|
||||||
|
({ diff: resultDiff, result1: result } = await compareQuery(
|
||||||
clients,
|
clients,
|
||||||
queryName,
|
queryName,
|
||||||
{ block },
|
{ block },
|
||||||
rawJson,
|
rawJson,
|
||||||
timeDiff
|
timeDiff
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (config.watcher.verifyState) {
|
||||||
|
const ipldDiff = await checkGQLEntitiesInIPLDState(ipldStateByBlock, entityName, result[queryName], rawJson, config.watcher.skipFields);
|
||||||
|
|
||||||
|
if (ipldDiff) {
|
||||||
|
log('Results mismatch for IPLD state:', ipldDiff);
|
||||||
|
diffFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ interface QueryConfig {
|
|||||||
queryLimits: { [queryName: string]: number }
|
queryLimits: { [queryName: string]: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EntityDerivedFields {
|
interface EntitySkipFields {
|
||||||
entity: string;
|
entity: string;
|
||||||
fields: string[];
|
fields: string[];
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ export interface Config {
|
|||||||
entitiesDir: string;
|
entitiesDir: string;
|
||||||
verifyState: boolean;
|
verifyState: boolean;
|
||||||
endpoint: keyof EndpointConfig;
|
endpoint: keyof EndpointConfig;
|
||||||
derivedFields: EntityDerivedFields[]
|
skipFields: EntitySkipFields[]
|
||||||
}
|
}
|
||||||
cache: {
|
cache: {
|
||||||
endpoint: keyof EndpointConfig;
|
endpoint: keyof EndpointConfig;
|
||||||
@ -312,34 +312,69 @@ export const combineIPLDState = (contractIPLDs: {[key: string]: any}[]): {[key:
|
|||||||
return contractIPLDStates.reduce((acc, state) => _.merge(acc, state));
|
return contractIPLDStates.reduce((acc, state) => _.merge(acc, state));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkEntityInIPLDState = async (
|
export const checkGQLEntityInIPLDState = async (
|
||||||
ipldState: {[key: string]: any},
|
ipldState: {[key: string]: any},
|
||||||
queryName: string,
|
entityName: string,
|
||||||
entityResult: {[key: string]: any},
|
entityResult: {[key: string]: any},
|
||||||
id: string,
|
id: string,
|
||||||
rawJson: boolean,
|
rawJson: boolean,
|
||||||
derivedFields: EntityDerivedFields[] = []
|
skipFields: EntitySkipFields[] = []
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const entityName = _.upperFirst(queryName);
|
|
||||||
const ipldEntity = ipldState[entityName][id];
|
const ipldEntity = ipldState[entityName][id];
|
||||||
|
|
||||||
// Filter __typename key in GQL result.
|
// Filter __typename key in GQL result.
|
||||||
const resultEntity = omitDeep(entityResult[queryName], '__typename');
|
entityResult = omitDeep(entityResult, '__typename');
|
||||||
|
|
||||||
// Filter derived fields in GQL result.
|
// Filter skipped fields in state comaparison.
|
||||||
derivedFields.forEach(({ entity, fields }) => {
|
skipFields.forEach(({ entity, fields }) => {
|
||||||
if (entityName === entity) {
|
if (entityName === entity) {
|
||||||
fields.forEach(field => {
|
omitDeep(entityResult, fields);
|
||||||
delete resultEntity[field];
|
omitDeep(ipldEntity, fields);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const diff = compareObjects(resultEntity, ipldEntity, rawJson);
|
const diff = compareObjects(entityResult, ipldEntity, rawJson);
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkGQLEntitiesInIPLDState = async (
|
||||||
|
ipldState: {[key: string]: any},
|
||||||
|
entityName: string,
|
||||||
|
entitiesResult: any[],
|
||||||
|
rawJson: boolean,
|
||||||
|
skipFields: EntitySkipFields[] = []
|
||||||
|
): Promise<string> => {
|
||||||
|
// Form entities from state to compare with GQL result
|
||||||
|
const stateEntities = ipldState[entityName];
|
||||||
|
|
||||||
|
for (const entityResult of entitiesResult) {
|
||||||
|
const stateEntity = stateEntities[entityResult.id];
|
||||||
|
|
||||||
|
// Verify state if entity from GQL result is present in state.
|
||||||
|
if (stateEntity) {
|
||||||
|
// Filter __typename key in GQL result.
|
||||||
|
entitiesResult = omitDeep(entityResult, '__typename');
|
||||||
|
|
||||||
|
// Filter skipped fields in state comaparison.
|
||||||
|
skipFields.forEach(({ entity, fields }) => {
|
||||||
|
if (entityName === entity) {
|
||||||
|
omitDeep(entityResult, fields);
|
||||||
|
omitDeep(stateEntity, fields);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const diff = compareObjects(entityResult, stateEntity, rawJson);
|
||||||
|
|
||||||
|
if (diff) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
// obj1: expected
|
// obj1: expected
|
||||||
// obj2: actual
|
// obj2: actual
|
||||||
const compareObjects = (obj1: any, obj2: any, rawJson: boolean): string => {
|
const compareObjects = (obj1: any, obj2: any, rawJson: boolean): string => {
|
||||||
|
@ -100,6 +100,7 @@ export const instantiate = async (
|
|||||||
database.cacheUpdatedEntity(entityName, dbEntity);
|
database.cacheUpdatedEntity(entityName, dbEntity);
|
||||||
|
|
||||||
// Update the in-memory subgraph state if not disabled.
|
// Update the in-memory subgraph state if not disabled.
|
||||||
|
// TODO: enableSubgraphState
|
||||||
if (!indexer.serverConfig.disableSubgraphState) {
|
if (!indexer.serverConfig.disableSubgraphState) {
|
||||||
// Prepare diff data for the entity update
|
// Prepare diff data for the entity update
|
||||||
assert(indexer.getRelationsMap);
|
assert(indexer.getRelationsMap);
|
||||||
|
@ -12,7 +12,7 @@ import { SelectionNode } from 'graphql';
|
|||||||
|
|
||||||
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
|
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
|
||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { getFullBlock, BlockHeight, ServerConfig, getFullTransaction, QueryOptions, IPLDBlockInterface, IPLDIndexerInterface, BlockProgressInterface } from '@cerc-io/util';
|
import { getFullBlock, BlockHeight, ServerConfig, getFullTransaction, QueryOptions, IPLDBlockInterface, IndexerInterface, BlockProgressInterface } from '@cerc-io/util';
|
||||||
|
|
||||||
import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts, Transaction } from './utils';
|
import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts, Transaction } from './utils';
|
||||||
import { Context, GraphData, instantiate } from './loader';
|
import { Context, GraphData, instantiate } from './loader';
|
||||||
@ -28,7 +28,7 @@ interface DataSource {
|
|||||||
|
|
||||||
export class GraphWatcher {
|
export class GraphWatcher {
|
||||||
_database: Database;
|
_database: Database;
|
||||||
_indexer?: IPLDIndexerInterface;
|
_indexer?: IndexerInterface;
|
||||||
_ethClient: EthClient;
|
_ethClient: EthClient;
|
||||||
_ethProvider: providers.BaseProvider;
|
_ethProvider: providers.BaseProvider;
|
||||||
_subgraphPath: string;
|
_subgraphPath: string;
|
||||||
@ -254,7 +254,7 @@ export class GraphWatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setIndexer (indexer: IPLDIndexerInterface): void {
|
setIndexer (indexer: IndexerInterface): void {
|
||||||
this._indexer = indexer;
|
this._indexer = indexer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,10 @@ import {
|
|||||||
EventInterface,
|
EventInterface,
|
||||||
SyncStatusInterface,
|
SyncStatusInterface,
|
||||||
ServerConfig as ServerConfigInterface,
|
ServerConfig as ServerConfigInterface,
|
||||||
ValueResult
|
ValueResult,
|
||||||
|
ContractInterface,
|
||||||
|
IpldStatus as IpldStatusInterface,
|
||||||
|
IPLDBlockInterface
|
||||||
} from '@cerc-io/util';
|
} from '@cerc-io/util';
|
||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { GetStorageAt, getStorageValue, MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { GetStorageAt, getStorageValue, MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
@ -85,7 +88,7 @@ export class Indexer implements IndexerInterface {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: BlockProgressInterface): Promise<BlockProgressInterface> {
|
async fetchBlockWithEvents (block: BlockProgressInterface): Promise<BlockProgressInterface> {
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +156,22 @@ export class Indexer implements IndexerInterface {
|
|||||||
async processEvent (event: EventInterface): Promise<void> {
|
async processEvent (event: EventInterface): Promise<void> {
|
||||||
assert(event);
|
assert(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isWatchedContract (address : string): ContractInterface | undefined {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async processBlock (blockProgress: BlockProgressInterface): Promise<void> {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIPLDData (ipldBlock: IPLDBlockInterface): any {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateIPLDStatusMap (address: string, ipldStatus: IpldStatusInterface): Promise<void> {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncStatus implements SyncStatusInterface {
|
class SyncStatus implements SyncStatusInterface {
|
||||||
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||||||
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions } from 'typeorm';
|
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions } from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { IPLDDatabase as BaseDatabase, IPLDDatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Contract } from './entity/Contract';
|
import { Contract } from './entity/Contract';
|
||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
@ -18,7 +18,7 @@ import { IPLDBlock } from './entity/IPLDBlock';
|
|||||||
import { GetMethod } from './entity/GetMethod';
|
import { GetMethod } from './entity/GetMethod';
|
||||||
import { _Test } from './entity/_Test';
|
import { _Test } from './entity/_Test';
|
||||||
|
|
||||||
export class Database implements IPLDDatabaseInterface {
|
export class Database implements DatabaseInterface {
|
||||||
_config: ConnectionOptions;
|
_config: ConnectionOptions;
|
||||||
_conn!: Connection;
|
_conn!: Connection;
|
||||||
_baseDatabase: BaseDatabase;
|
_baseDatabase: BaseDatabase;
|
||||||
@ -171,11 +171,23 @@ export class Database implements IPLDDatabaseInterface {
|
|||||||
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
||||||
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
const eventRepo = queryRunner.manager.getRepository(Event);
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
|
return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise<void> {
|
||||||
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveEvents(eventRepo, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveBlockProgress(repo, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
||||||
|
@ -16,7 +16,7 @@ import * as codec from '@ipld/dag-cbor';
|
|||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { StorageLayout, MappingKey } from '@cerc-io/solidity-mapper';
|
import { StorageLayout, MappingKey } from '@cerc-io/solidity-mapper';
|
||||||
import {
|
import {
|
||||||
IPLDIndexer as BaseIndexer,
|
Indexer as BaseIndexer,
|
||||||
ValueResult,
|
ValueResult,
|
||||||
UNKNOWN_EVENT_NAME,
|
UNKNOWN_EVENT_NAME,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
@ -27,8 +27,9 @@ import {
|
|||||||
BlockHeight,
|
BlockHeight,
|
||||||
IPFSClient,
|
IPFSClient,
|
||||||
StateKind,
|
StateKind,
|
||||||
IPLDIndexerInterface,
|
IndexerInterface,
|
||||||
IpldStatus as IpldStatusInterface
|
IpldStatus as IpldStatusInterface,
|
||||||
|
ResultIPLDBlock
|
||||||
} from '@cerc-io/util';
|
} from '@cerc-io/util';
|
||||||
import { GraphWatcher } from '@cerc-io/graph-node';
|
import { GraphWatcher } from '@cerc-io/graph-node';
|
||||||
|
|
||||||
@ -74,21 +75,7 @@ export type ResultEvent = {
|
|||||||
proof: string;
|
proof: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResultIPLDBlock = {
|
export class Indexer implements IndexerInterface {
|
||||||
block: {
|
|
||||||
cid: string;
|
|
||||||
hash: string;
|
|
||||||
number: number;
|
|
||||||
timestamp: number;
|
|
||||||
parentHash: string;
|
|
||||||
};
|
|
||||||
contractAddress: string;
|
|
||||||
cid: string;
|
|
||||||
kind: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Indexer implements IPLDIndexerInterface {
|
|
||||||
_db: Database
|
_db: Database
|
||||||
_ethClient: EthClient
|
_ethClient: EthClient
|
||||||
_ethProvider: BaseProvider
|
_ethProvider: BaseProvider
|
||||||
@ -536,7 +523,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<Contract | undefined> {
|
isWatchedContract (address : string): Contract | undefined {
|
||||||
return this._baseIndexer.isWatchedContract(address);
|
return this._baseIndexer.isWatchedContract(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,8 +575,8 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
return this._baseIndexer.fetchBlockEvents(block, this._fetchAndSaveEvents.bind(this));
|
return this._baseIndexer.fetchBlockWithEvents(block, this._fetchAndSaveEvents.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
||||||
@ -820,7 +807,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
const blockProgress = await this._db.saveEvents(dbTx, block, dbEvents);
|
const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents);
|
||||||
await dbTx.commitTransaction();
|
await dbTx.commitTransaction();
|
||||||
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
|
|
||||||
|
5
packages/ipld-eth-client/.npmignore
Normal file
5
packages/ipld-eth-client/.npmignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/src/
|
||||||
|
index.ts
|
||||||
|
tsconfig.json
|
||||||
|
.eslintrc.json
|
||||||
|
.eslintignore
|
@ -2,7 +2,6 @@
|
|||||||
"name": "@cerc-io/ipld-eth-client",
|
"name": "@cerc-io/ipld-eth-client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "IPLD ETH Client",
|
"description": "IPLD ETH Client",
|
||||||
"private": true,
|
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||||||
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions, LessThanOrEqual } from 'typeorm';
|
import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions, LessThanOrEqual } from 'typeorm';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { IPLDDatabase as BaseDatabase, IPLDDatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, StateKind, Where } from '@cerc-io/util';
|
||||||
|
|
||||||
import { Contract } from './entity/Contract';
|
import { Contract } from './entity/Contract';
|
||||||
import { Event } from './entity/Event';
|
import { Event } from './entity/Event';
|
||||||
@ -20,7 +20,7 @@ import { IsRevoked } from './entity/IsRevoked';
|
|||||||
import { IsPhisher } from './entity/IsPhisher';
|
import { IsPhisher } from './entity/IsPhisher';
|
||||||
import { IsMember } from './entity/IsMember';
|
import { IsMember } from './entity/IsMember';
|
||||||
|
|
||||||
export class Database implements IPLDDatabaseInterface {
|
export class Database implements DatabaseInterface {
|
||||||
_config: ConnectionOptions;
|
_config: ConnectionOptions;
|
||||||
_conn!: Connection;
|
_conn!: Connection;
|
||||||
_baseDatabase: BaseDatabase;
|
_baseDatabase: BaseDatabase;
|
||||||
@ -230,11 +230,23 @@ export class Database implements IPLDDatabaseInterface {
|
|||||||
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
|
||||||
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
const eventRepo = queryRunner.manager.getRepository(Event);
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
|
return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise<void> {
|
||||||
|
const eventRepo = queryRunner.manager.getRepository(Event);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveEvents(eventRepo, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
|
const repo = queryRunner.manager.getRepository(BlockProgress);
|
||||||
|
|
||||||
|
return this._baseDatabase.saveBlockProgress(repo, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
|
||||||
|
@ -14,8 +14,8 @@ import * as codec from '@ipld/dag-cbor';
|
|||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
import {
|
import {
|
||||||
IPLDIndexer as BaseIndexer,
|
Indexer as BaseIndexer,
|
||||||
IPLDIndexerInterface,
|
IndexerInterface,
|
||||||
ValueResult,
|
ValueResult,
|
||||||
UNKNOWN_EVENT_NAME,
|
UNKNOWN_EVENT_NAME,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
@ -28,7 +28,8 @@ import {
|
|||||||
IPFSClient,
|
IPFSClient,
|
||||||
StateKind,
|
StateKind,
|
||||||
IpldStatus as IpldStatusInterface,
|
IpldStatus as IpldStatusInterface,
|
||||||
getFullTransaction
|
getFullTransaction,
|
||||||
|
ResultIPLDBlock
|
||||||
} from '@cerc-io/util';
|
} from '@cerc-io/util';
|
||||||
|
|
||||||
import PhisherRegistryArtifacts from './artifacts/PhisherRegistry.json';
|
import PhisherRegistryArtifacts from './artifacts/PhisherRegistry.json';
|
||||||
@ -75,21 +76,7 @@ export type ResultEvent = {
|
|||||||
proof: string;
|
proof: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResultIPLDBlock = {
|
export class Indexer implements IndexerInterface {
|
||||||
block: {
|
|
||||||
cid: string;
|
|
||||||
hash: string;
|
|
||||||
number: number;
|
|
||||||
timestamp: number;
|
|
||||||
parentHash: string;
|
|
||||||
};
|
|
||||||
contractAddress: string;
|
|
||||||
cid: string;
|
|
||||||
kind: string;
|
|
||||||
data: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Indexer implements IPLDIndexerInterface {
|
|
||||||
_db: Database
|
_db: Database
|
||||||
_ethClient: EthClient
|
_ethClient: EthClient
|
||||||
_ethProvider: JsonRpcProvider
|
_ethProvider: JsonRpcProvider
|
||||||
@ -634,7 +621,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
return this._baseIndexer.getEventsByFilter(blockHash, contract, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<Contract | undefined> {
|
isWatchedContract (address : string): Contract | undefined {
|
||||||
return this._baseIndexer.isWatchedContract(address);
|
return this._baseIndexer.isWatchedContract(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -686,8 +673,8 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
return this._baseIndexer.getBlocksAtHeight(height, isPruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgress>): Promise<BlockProgress> {
|
||||||
return this._baseIndexer.fetchBlockEvents(block, this._fetchAndSaveEvents.bind(this));
|
return this._baseIndexer.fetchBlockWithEvents(block, this._fetchAndSaveEvents.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<Event>> {
|
||||||
@ -842,7 +829,7 @@ export class Indexer implements IPLDIndexerInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.time('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
const blockProgress = await this._db.saveEvents(dbTx, block, dbEvents);
|
const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents);
|
||||||
await dbTx.commitTransaction();
|
await dbTx.commitTransaction();
|
||||||
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
console.timeEnd('time:indexer#_fetchAndSaveEvents-save-block-events');
|
||||||
|
|
||||||
|
10
packages/solidity-mapper/.npmignore
Normal file
10
packages/solidity-mapper/.npmignore
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/src/
|
||||||
|
hardhat.config.ts
|
||||||
|
tsconfig.json
|
||||||
|
.eslintrc.json
|
||||||
|
.eslintignore
|
||||||
|
.env
|
||||||
|
.env.example
|
||||||
|
cache
|
||||||
|
artifacts
|
||||||
|
test
|
@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
6
packages/util/.npmignore
Normal file
6
packages/util/.npmignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/src/
|
||||||
|
hardhat.config.ts
|
||||||
|
index.ts
|
||||||
|
tsconfig.json
|
||||||
|
.eslintrc.json
|
||||||
|
.eslintignore
|
@ -14,8 +14,6 @@ export * from './src/indexer';
|
|||||||
export * from './src/job-runner';
|
export * from './src/job-runner';
|
||||||
export * from './src/ipld-helper';
|
export * from './src/ipld-helper';
|
||||||
export * from './src/graph-decimal';
|
export * from './src/graph-decimal';
|
||||||
export * from './src/ipld-indexer';
|
|
||||||
export * from './src/ipld-database';
|
|
||||||
export * from './src/ipfs';
|
export * from './src/ipfs';
|
||||||
export * from './src/index-block';
|
export * from './src/index-block';
|
||||||
export * from './src/metrics';
|
export * from './src/metrics';
|
||||||
|
@ -22,6 +22,8 @@ export interface JobQueueConfig {
|
|||||||
maxCompletionLagInSecs: number;
|
maxCompletionLagInSecs: number;
|
||||||
jobDelayInMilliSecs?: number;
|
jobDelayInMilliSecs?: number;
|
||||||
eventsInBatch: number;
|
eventsInBatch: number;
|
||||||
|
lazyUpdateBlockProgress?: boolean;
|
||||||
|
subgraphEventsOrder: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import {
|
import {
|
||||||
|
Between,
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionOptions,
|
ConnectionOptions,
|
||||||
createConnection,
|
createConnection,
|
||||||
@ -18,8 +19,9 @@ import {
|
|||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { Pool } from 'pg';
|
||||||
|
|
||||||
import { BlockProgressInterface, ContractInterface, EventInterface, SyncStatusInterface } from './types';
|
import { BlockProgressInterface, ContractInterface, EventInterface, IPLDBlockInterface, IpldStatusInterface, StateKind, SyncStatusInterface } from './types';
|
||||||
import { MAX_REORG_DEPTH, UNKNOWN_EVENT_NAME } from './constants';
|
import { MAX_REORG_DEPTH, UNKNOWN_EVENT_NAME } from './constants';
|
||||||
import { blockProgressCount, eventCount } from './metrics';
|
import { blockProgressCount, eventCount } from './metrics';
|
||||||
|
|
||||||
@ -67,12 +69,22 @@ export type Relation = string | { property: string, alias: string }
|
|||||||
export class Database {
|
export class Database {
|
||||||
_config: ConnectionOptions
|
_config: ConnectionOptions
|
||||||
_conn!: Connection
|
_conn!: Connection
|
||||||
|
_pgPool: Pool
|
||||||
_blockCount = 0
|
_blockCount = 0
|
||||||
_eventCount = 0
|
_eventCount = 0
|
||||||
|
|
||||||
constructor (config: ConnectionOptions) {
|
constructor (config: ConnectionOptions) {
|
||||||
assert(config);
|
assert(config);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
|
assert(config.type === 'postgres');
|
||||||
|
|
||||||
|
this._pgPool = new Pool({
|
||||||
|
user: config.username,
|
||||||
|
host: config.host,
|
||||||
|
database: config.database,
|
||||||
|
password: config.password,
|
||||||
|
port: config.port
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async init (): Promise<Connection> {
|
async init (): Promise<Connection> {
|
||||||
@ -167,6 +179,13 @@ export class Database {
|
|||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (repo: Repository<BlockProgressInterface>, block: DeepPartial<BlockProgressInterface>): Promise<BlockProgressInterface> {
|
||||||
|
this._blockCount++;
|
||||||
|
blockProgressCount.set(this._blockCount);
|
||||||
|
|
||||||
|
return await repo.save(block);
|
||||||
|
}
|
||||||
|
|
||||||
async updateBlockProgress (repo: Repository<BlockProgressInterface>, block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface> {
|
async updateBlockProgress (repo: Repository<BlockProgressInterface>, block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface> {
|
||||||
if (!block.isComplete) {
|
if (!block.isComplete) {
|
||||||
if (lastProcessedEventIndex <= block.lastProcessedEventIndex) {
|
if (lastProcessedEventIndex <= block.lastProcessedEventIndex) {
|
||||||
@ -222,7 +241,7 @@ export class Database {
|
|||||||
return queryBuilder.getMany();
|
return queryBuilder.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents (blockRepo: Repository<BlockProgressInterface>, eventRepo: Repository<EventInterface>, block: DeepPartial<BlockProgressInterface>, events: DeepPartial<EventInterface>[]): Promise<BlockProgressInterface> {
|
async saveBlockWithEvents (blockRepo: Repository<BlockProgressInterface>, eventRepo: Repository<EventInterface>, block: DeepPartial<BlockProgressInterface>, events: DeepPartial<EventInterface>[]): Promise<BlockProgressInterface> {
|
||||||
const {
|
const {
|
||||||
cid,
|
cid,
|
||||||
blockHash,
|
blockHash,
|
||||||
@ -258,17 +277,18 @@ export class Database {
|
|||||||
this._blockCount++;
|
this._blockCount++;
|
||||||
blockProgressCount.set(this._blockCount);
|
blockProgressCount.set(this._blockCount);
|
||||||
|
|
||||||
let blockEventCount = 0;
|
|
||||||
|
|
||||||
// Bulk insert events.
|
// Bulk insert events.
|
||||||
events.forEach(event => {
|
events.forEach(event => {
|
||||||
event.block = blockProgress;
|
event.block = blockProgress;
|
||||||
|
|
||||||
if (event.eventName !== UNKNOWN_EVENT_NAME) {
|
|
||||||
blockEventCount++;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.saveEvents(eventRepo, events);
|
||||||
|
|
||||||
|
return blockProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEvents (eventRepo: Repository<EventInterface>, events: DeepPartial<EventInterface>[]): Promise<void> {
|
||||||
|
// Bulk insert events.
|
||||||
const eventBatches = _.chunk(events, INSERT_EVENTS_BATCH);
|
const eventBatches = _.chunk(events, INSERT_EVENTS_BATCH);
|
||||||
|
|
||||||
const insertPromises = eventBatches.map(async events => {
|
const insertPromises = eventBatches.map(async events => {
|
||||||
@ -280,10 +300,8 @@ export class Database {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(insertPromises);
|
await Promise.all(insertPromises);
|
||||||
this._eventCount += blockEventCount;
|
this._eventCount += events.filter(event => event.eventName !== UNKNOWN_EVENT_NAME).length;
|
||||||
eventCount.set(this._eventCount);
|
eventCount.set(this._eventCount);
|
||||||
|
|
||||||
return blockProgress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindConditions<Entity>): Promise<Entity[]> {
|
async getEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindConditions<Entity>): Promise<Entity[]> {
|
||||||
@ -540,22 +558,246 @@ export class Database {
|
|||||||
return repo.save(entity);
|
return repo.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchBlockCount (): Promise<void> {
|
async getLatestIPLDBlock (repo: Repository<IPLDBlockInterface>, contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise<IPLDBlockInterface | undefined> {
|
||||||
this._blockCount = await this._conn.getRepository('block_progress')
|
let queryBuilder = repo.createQueryBuilder('ipld_block')
|
||||||
.count();
|
.leftJoinAndSelect('ipld_block.block', 'block')
|
||||||
|
.where('block.is_pruned = false')
|
||||||
|
.andWhere('ipld_block.contract_address = :contractAddress', { contractAddress })
|
||||||
|
.orderBy('block.block_number', 'DESC');
|
||||||
|
|
||||||
blockProgressCount.set(this._blockCount);
|
// Filter out blocks after the provided block number.
|
||||||
|
if (blockNumber) {
|
||||||
|
queryBuilder.andWhere('block.block_number <= :blockNumber', { blockNumber });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchEventCount (): Promise<void> {
|
// Filter using kind if specified else avoid diff_staged block.
|
||||||
this._eventCount = await this._conn.getRepository('event')
|
queryBuilder = kind
|
||||||
.count({
|
? queryBuilder.andWhere('ipld_block.kind = :kind', { kind })
|
||||||
where: {
|
: queryBuilder.andWhere('ipld_block.kind != :kind', { kind: StateKind.DiffStaged });
|
||||||
eventName: Not(UNKNOWN_EVENT_NAME)
|
|
||||||
|
// Get the first three entries.
|
||||||
|
queryBuilder.limit(3);
|
||||||
|
|
||||||
|
const results = await queryBuilder.getMany();
|
||||||
|
|
||||||
|
if (results.length) {
|
||||||
|
// Sort by (block number desc, id desc) to get the latest entry.
|
||||||
|
// At same height, IPLD blocks are expected in order ['init', 'diff', 'checkpoint'],
|
||||||
|
// and are given preference in order ['checkpoint', 'diff', 'init']
|
||||||
|
results.sort((result1, result2) => {
|
||||||
|
if (result1.block.blockNumber === result2.block.blockNumber) {
|
||||||
|
return (result1.id > result2.id) ? -1 : 1;
|
||||||
|
} else {
|
||||||
|
return (result1.block.blockNumber > result2.block.blockNumber) ? -1 : 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventCount.set(this._eventCount);
|
return results[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrevIPLDBlock (repo: Repository<IPLDBlockInterface>, blockHash: string, contractAddress: string, kind?: string): Promise<IPLDBlockInterface | undefined> {
|
||||||
|
const heirerchicalQuery = `
|
||||||
|
WITH RECURSIVE cte_query AS
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
b.block_hash,
|
||||||
|
b.block_number,
|
||||||
|
b.parent_hash,
|
||||||
|
1 as depth,
|
||||||
|
i.id,
|
||||||
|
i.kind
|
||||||
|
FROM
|
||||||
|
block_progress b
|
||||||
|
LEFT JOIN
|
||||||
|
ipld_block i ON i.block_id = b.id
|
||||||
|
AND i.contract_address = $2
|
||||||
|
WHERE
|
||||||
|
b.block_hash = $1
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
b.block_hash,
|
||||||
|
b.block_number,
|
||||||
|
b.parent_hash,
|
||||||
|
c.depth + 1,
|
||||||
|
i.id,
|
||||||
|
i.kind
|
||||||
|
FROM
|
||||||
|
block_progress b
|
||||||
|
LEFT JOIN
|
||||||
|
ipld_block i
|
||||||
|
ON i.block_id = b.id
|
||||||
|
AND i.contract_address = $2
|
||||||
|
INNER JOIN
|
||||||
|
cte_query c ON c.parent_hash = b.block_hash
|
||||||
|
WHERE
|
||||||
|
c.depth < $3
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
block_number, id, kind
|
||||||
|
FROM
|
||||||
|
cte_query
|
||||||
|
ORDER BY block_number DESC, id DESC
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Fetching block and id for previous IPLDBlock in frothy region.
|
||||||
|
const queryResult = await repo.query(heirerchicalQuery, [blockHash, contractAddress, MAX_REORG_DEPTH]);
|
||||||
|
const latestRequiredResult = kind
|
||||||
|
? queryResult.find((obj: any) => obj.kind === kind)
|
||||||
|
: queryResult.find((obj: any) => obj.id);
|
||||||
|
|
||||||
|
let result: IPLDBlockInterface | undefined;
|
||||||
|
|
||||||
|
if (latestRequiredResult) {
|
||||||
|
result = await repo.findOne(latestRequiredResult.id, { relations: ['block'] });
|
||||||
|
} else {
|
||||||
|
// If IPLDBlock not found in frothy region get latest IPLDBlock in the pruned region.
|
||||||
|
// Filter out IPLDBlocks from pruned blocks.
|
||||||
|
const canonicalBlockNumber = queryResult.pop().block_number + 1;
|
||||||
|
|
||||||
|
let queryBuilder = repo.createQueryBuilder('ipld_block')
|
||||||
|
.leftJoinAndSelect('ipld_block.block', 'block')
|
||||||
|
.where('block.is_pruned = false')
|
||||||
|
.andWhere('ipld_block.contract_address = :contractAddress', { contractAddress })
|
||||||
|
.andWhere('block.block_number <= :canonicalBlockNumber', { canonicalBlockNumber })
|
||||||
|
.orderBy('block.block_number', 'DESC');
|
||||||
|
|
||||||
|
// Filter using kind if specified else order by id to give preference to checkpoint.
|
||||||
|
queryBuilder = kind
|
||||||
|
? queryBuilder.andWhere('ipld_block.kind = :kind', { kind })
|
||||||
|
: queryBuilder.addOrderBy('ipld_block.id', 'DESC');
|
||||||
|
|
||||||
|
// Get the first entry.
|
||||||
|
queryBuilder.limit(1);
|
||||||
|
|
||||||
|
result = await queryBuilder.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPLDBlocks (repo: Repository<IPLDBlockInterface>, where: FindConditions<IPLDBlockInterface>): Promise<IPLDBlockInterface[]> {
|
||||||
|
return repo.find({ where, relations: ['block'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDiffIPLDBlocksInRange (repo: Repository<IPLDBlockInterface>, contractAddress: string, startblock: number, endBlock: number): Promise<IPLDBlockInterface[]> {
|
||||||
|
return repo.find({
|
||||||
|
relations: ['block'],
|
||||||
|
where: {
|
||||||
|
contractAddress,
|
||||||
|
kind: StateKind.Diff,
|
||||||
|
block: {
|
||||||
|
isPruned: false,
|
||||||
|
blockNumber: Between(startblock + 1, endBlock)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
block: 'ASC'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveOrUpdateIPLDBlock (repo: Repository<IPLDBlockInterface>, ipldBlock: IPLDBlockInterface): Promise<IPLDBlockInterface> {
|
||||||
|
let updatedData: {[key: string]: any};
|
||||||
|
|
||||||
|
console.time('time:ipld-database#saveOrUpdateIPLDBlock-DB-query');
|
||||||
|
if (ipldBlock.id) {
|
||||||
|
// Using pg query as workaround for typeorm memory issue when saving checkpoint with large sized data.
|
||||||
|
const { rows } = await this._pgPool.query(`
|
||||||
|
UPDATE ipld_block
|
||||||
|
SET block_id = $1, contract_address = $2, cid = $3, kind = $4, data = $5
|
||||||
|
WHERE id = $6
|
||||||
|
RETURNING *
|
||||||
|
`, [ipldBlock.block.id, ipldBlock.contractAddress, ipldBlock.cid, ipldBlock.kind, ipldBlock.data, ipldBlock.id]);
|
||||||
|
|
||||||
|
updatedData = rows[0];
|
||||||
|
} else {
|
||||||
|
const { rows } = await this._pgPool.query(`
|
||||||
|
INSERT INTO ipld_block(block_id, contract_address, cid, kind, data)
|
||||||
|
VALUES($1, $2, $3, $4, $5)
|
||||||
|
RETURNING *
|
||||||
|
`, [ipldBlock.block.id, ipldBlock.contractAddress, ipldBlock.cid, ipldBlock.kind, ipldBlock.data]);
|
||||||
|
|
||||||
|
updatedData = rows[0];
|
||||||
|
}
|
||||||
|
console.timeEnd('time:ipld-database#saveOrUpdateIPLDBlock-DB-query');
|
||||||
|
|
||||||
|
assert(updatedData);
|
||||||
|
return {
|
||||||
|
block: ipldBlock.block,
|
||||||
|
contractAddress: updatedData.contract_address,
|
||||||
|
cid: updatedData.cid,
|
||||||
|
kind: updatedData.kind,
|
||||||
|
data: updatedData.data,
|
||||||
|
id: updatedData.id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeIPLDBlocks (repo: Repository<IPLDBlockInterface>, blockNumber: number, kind: string): Promise<void> {
|
||||||
|
const entities = await repo.find({ relations: ['block'], where: { block: { blockNumber }, kind } });
|
||||||
|
|
||||||
|
// Delete if entities found.
|
||||||
|
if (entities.length) {
|
||||||
|
await repo.delete(entities.map((entity) => entity.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeIPLDBlocksAfterBlock (repo: Repository<IPLDBlockInterface>, blockNumber: number): Promise<void> {
|
||||||
|
// Use raw SQL as TypeORM curently doesn't support delete via 'join' or 'using'
|
||||||
|
const deleteQuery = `
|
||||||
|
DELETE FROM
|
||||||
|
ipld_block
|
||||||
|
USING block_progress
|
||||||
|
WHERE
|
||||||
|
ipld_block.block_id = block_progress.id
|
||||||
|
AND block_progress.block_number > $1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
await repo.query(deleteQuery, [blockNumber]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPLDStatus (repo: Repository<IpldStatusInterface>): Promise<IpldStatusInterface | undefined> {
|
||||||
|
return repo.findOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateIPLDStatusHooksBlock (repo: Repository<IpldStatusInterface>, blockNumber: number, force?: boolean): Promise<IpldStatusInterface> {
|
||||||
|
let entity = await repo.findOne();
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
entity = repo.create({
|
||||||
|
latestHooksBlockNumber: blockNumber,
|
||||||
|
latestCheckpointBlockNumber: -1,
|
||||||
|
latestIPFSBlockNumber: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force || blockNumber > entity.latestHooksBlockNumber) {
|
||||||
|
entity.latestHooksBlockNumber = blockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateIPLDStatusCheckpointBlock (repo: Repository<IpldStatusInterface>, blockNumber: number, force?: boolean): Promise<IpldStatusInterface> {
|
||||||
|
const entity = await repo.findOne();
|
||||||
|
assert(entity);
|
||||||
|
|
||||||
|
if (force || blockNumber > entity.latestCheckpointBlockNumber) {
|
||||||
|
entity.latestCheckpointBlockNumber = blockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateIPLDStatusIPFSBlock (repo: Repository<IpldStatusInterface>, blockNumber: number, force?: boolean): Promise<IpldStatusInterface> {
|
||||||
|
const entity = await repo.findOne();
|
||||||
|
assert(entity);
|
||||||
|
|
||||||
|
if (force || blockNumber > entity.latestIPFSBlockNumber) {
|
||||||
|
entity.latestIPFSBlockNumber = blockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildQuery<Entity> (repo: Repository<Entity>, selectQueryBuilder: SelectQueryBuilder<Entity>, where: Where = {}): SelectQueryBuilder<Entity> {
|
buildQuery<Entity> (repo: Repository<Entity>, selectQueryBuilder: SelectQueryBuilder<Entity>, where: Where = {}): SelectQueryBuilder<Entity> {
|
||||||
@ -622,7 +864,8 @@ export class Database {
|
|||||||
orderQuery<Entity> (
|
orderQuery<Entity> (
|
||||||
repo: Repository<Entity>,
|
repo: Repository<Entity>,
|
||||||
selectQueryBuilder: SelectQueryBuilder<Entity>,
|
selectQueryBuilder: SelectQueryBuilder<Entity>,
|
||||||
orderOptions: { orderBy?: string, orderDirection?: string }
|
orderOptions: { orderBy?: string, orderDirection?: string },
|
||||||
|
columnPrefix = ''
|
||||||
): SelectQueryBuilder<Entity> {
|
): SelectQueryBuilder<Entity> {
|
||||||
const { orderBy, orderDirection } = orderOptions;
|
const { orderBy, orderDirection } = orderOptions;
|
||||||
assert(orderBy);
|
assert(orderBy);
|
||||||
@ -631,8 +874,26 @@ export class Database {
|
|||||||
assert(columnMetadata);
|
assert(columnMetadata);
|
||||||
|
|
||||||
return selectQueryBuilder.addOrderBy(
|
return selectQueryBuilder.addOrderBy(
|
||||||
`${selectQueryBuilder.alias}.${columnMetadata.propertyAliasName}`,
|
`"${selectQueryBuilder.alias}"."${columnPrefix}${columnMetadata.databaseName}"`,
|
||||||
orderDirection === 'desc' ? 'DESC' : 'ASC'
|
orderDirection === 'desc' ? 'DESC' : 'ASC'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _fetchBlockCount (): Promise<void> {
|
||||||
|
this._blockCount = await this._conn.getRepository('block_progress')
|
||||||
|
.count();
|
||||||
|
|
||||||
|
blockProgressCount.set(this._blockCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _fetchEventCount (): Promise<void> {
|
||||||
|
this._eventCount = await this._conn.getRepository('event')
|
||||||
|
.count({
|
||||||
|
where: {
|
||||||
|
eventName: Not(UNKNOWN_EVENT_NAME)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
eventCount.set(this._eventCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ const prefetchBlocks = async (
|
|||||||
const blockProgress = await indexer.getBlockProgress(blockHash);
|
const blockProgress = await indexer.getBlockProgress(blockHash);
|
||||||
|
|
||||||
if (!blockProgress) {
|
if (!blockProgress) {
|
||||||
await indexer.fetchBlockEvents({ cid, blockHash, blockNumber, parentHash, blockTimestamp: timestamp });
|
await indexer.fetchBlockWithEvents({ cid, blockHash, blockNumber, parentHash, blockTimestamp: timestamp });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export const indexBlock = async (
|
|||||||
|
|
||||||
// Check if blockProgress fetched from database.
|
// Check if blockProgress fetched from database.
|
||||||
if (!partialblockProgress.id) {
|
if (!partialblockProgress.id) {
|
||||||
blockProgress = await indexer.fetchBlockEvents(partialblockProgress);
|
blockProgress = await indexer.fetchBlockWithEvents(partialblockProgress);
|
||||||
} else {
|
} else {
|
||||||
blockProgress = partialblockProgress as BlockProgressInterface;
|
blockProgress = partialblockProgress as BlockProgressInterface;
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,20 @@ import assert from 'assert';
|
|||||||
import { DeepPartial, FindConditions, FindManyOptions } from 'typeorm';
|
import { DeepPartial, FindConditions, FindManyOptions } from 'typeorm';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { sha256 } from 'multiformats/hashes/sha2';
|
||||||
|
import { CID } from 'multiformats/cid';
|
||||||
|
|
||||||
|
import * as codec from '@ipld/dag-cbor';
|
||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
import { EthClient } from '@cerc-io/ipld-eth-client';
|
||||||
import { GetStorageAt, getStorageValue, StorageLayout } from '@cerc-io/solidity-mapper';
|
import { GetStorageAt, getStorageValue, StorageLayout } from '@cerc-io/solidity-mapper';
|
||||||
|
|
||||||
import { BlockProgressInterface, DatabaseInterface, EventInterface, SyncStatusInterface, ContractInterface } from './types';
|
import { BlockProgressInterface, DatabaseInterface, EventInterface, SyncStatusInterface, ContractInterface, IPLDBlockInterface, IndexerInterface, StateKind } from './types';
|
||||||
import { UNKNOWN_EVENT_NAME, JOB_KIND_CONTRACT, QUEUE_EVENT_PROCESSING } from './constants';
|
import { UNKNOWN_EVENT_NAME, JOB_KIND_CONTRACT, QUEUE_EVENT_PROCESSING } from './constants';
|
||||||
import { JobQueue } from './job-queue';
|
import { JobQueue } from './job-queue';
|
||||||
import { Where, QueryOptions } from './database';
|
import { Where, QueryOptions } from './database';
|
||||||
|
import { ServerConfig } from './config';
|
||||||
|
import { IPFSClient } from './ipfs';
|
||||||
|
|
||||||
const DEFAULT_MAX_EVENTS_BLOCK_RANGE = 1000;
|
const DEFAULT_MAX_EVENTS_BLOCK_RANGE = 1000;
|
||||||
|
|
||||||
@ -26,20 +32,54 @@ export interface ValueResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IpldStatus {
|
||||||
|
init?: number;
|
||||||
|
diff?: number;
|
||||||
|
checkpoint?: number;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
diff_staged?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResultIPLDBlock = {
|
||||||
|
block: {
|
||||||
|
cid: string;
|
||||||
|
hash: string;
|
||||||
|
number: number;
|
||||||
|
timestamp: number;
|
||||||
|
parentHash: string;
|
||||||
|
};
|
||||||
|
contractAddress: string;
|
||||||
|
cid: string;
|
||||||
|
kind: string;
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class Indexer {
|
export class Indexer {
|
||||||
|
_serverConfig: ServerConfig;
|
||||||
_db: DatabaseInterface;
|
_db: DatabaseInterface;
|
||||||
_ethClient: EthClient;
|
_ethClient: EthClient;
|
||||||
_getStorageAt: GetStorageAt;
|
_getStorageAt: GetStorageAt;
|
||||||
_ethProvider: ethers.providers.BaseProvider;
|
_ethProvider: ethers.providers.BaseProvider;
|
||||||
_jobQueue: JobQueue;
|
_jobQueue: JobQueue;
|
||||||
|
_ipfsClient: IPFSClient;
|
||||||
|
|
||||||
_watchedContracts: { [key: string]: ContractInterface } = {};
|
_watchedContracts: { [key: string]: ContractInterface } = {};
|
||||||
|
_ipldStatusMap: { [key: string]: IpldStatus } = {};
|
||||||
|
|
||||||
constructor (db: DatabaseInterface, ethClient: EthClient, ethProvider: ethers.providers.BaseProvider, jobQueue: JobQueue) {
|
constructor (
|
||||||
|
serverConfig: ServerConfig,
|
||||||
|
db: DatabaseInterface,
|
||||||
|
ethClient: EthClient,
|
||||||
|
ethProvider: ethers.providers.BaseProvider,
|
||||||
|
jobQueue: JobQueue,
|
||||||
|
ipfsClient: IPFSClient
|
||||||
|
) {
|
||||||
|
this._serverConfig = serverConfig;
|
||||||
this._db = db;
|
this._db = db;
|
||||||
this._ethClient = ethClient;
|
this._ethClient = ethClient;
|
||||||
this._ethProvider = ethProvider;
|
this._ethProvider = ethProvider;
|
||||||
this._jobQueue = jobQueue;
|
this._jobQueue = jobQueue;
|
||||||
|
this._ipfsClient = ipfsClient;
|
||||||
this._getStorageAt = this._ethClient.getStorageAt.bind(this._ethClient);
|
this._getStorageAt = this._ethClient.getStorageAt.bind(this._ethClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +232,7 @@ export class Indexer {
|
|||||||
return this._db.getEvent(id);
|
return this._db.getEvent(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBlockEvents (block: DeepPartial<BlockProgressInterface>, fetchAndSaveEvents: (block: DeepPartial<BlockProgressInterface>) => Promise<BlockProgressInterface>): Promise<BlockProgressInterface> {
|
async fetchBlockWithEvents (block: DeepPartial<BlockProgressInterface>, fetchAndSaveEvents: (block: DeepPartial<BlockProgressInterface>) => Promise<BlockProgressInterface>): Promise<BlockProgressInterface> {
|
||||||
assert(block.blockHash);
|
assert(block.blockHash);
|
||||||
|
|
||||||
log(`getBlockEvents: fetching from upstream server ${block.blockHash}`);
|
log(`getBlockEvents: fetching from upstream server ${block.blockHash}`);
|
||||||
@ -202,6 +242,35 @@ export class Indexer {
|
|||||||
return blockProgress;
|
return blockProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchBlockEvents (block: DeepPartial<BlockProgressInterface>, fetchEvents: (block: DeepPartial<BlockProgressInterface>) => Promise<DeepPartial<EventInterface>[]>): Promise<DeepPartial<EventInterface>[]> {
|
||||||
|
assert(block.blockHash);
|
||||||
|
|
||||||
|
log(`getBlockEvents: fetching from upstream server ${block.blockHash}`);
|
||||||
|
console.time('time:indexer#fetchBlockEvents-fetchAndSaveEvents');
|
||||||
|
const events = await fetchEvents(block);
|
||||||
|
console.timeEnd('time:indexer#fetchBlockEvents-fetchAndSaveEvents');
|
||||||
|
log(`getBlockEvents: fetched for block: ${block.blockHash} num events: ${events.length}`);
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBlockProgress (block: DeepPartial<BlockProgressInterface>): Promise<BlockProgressInterface> {
|
||||||
|
const dbTx = await this._db.createTransactionRunner();
|
||||||
|
let res;
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await this._db.saveBlockProgress(dbTx, block);
|
||||||
|
await dbTx.commitTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
await dbTx.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await dbTx.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<EventInterface>> {
|
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<EventInterface>> {
|
||||||
return this._db.getBlockEvents(blockHash, where, queryOptions);
|
return this._db.getBlockEvents(blockHash, where, queryOptions);
|
||||||
}
|
}
|
||||||
@ -288,6 +357,20 @@ export class Indexer {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveEvents (dbEvents: EventInterface[]): Promise<void> {
|
||||||
|
const dbTx = await this._db.createTransactionRunner();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._db.saveEvents(dbTx, dbEvents);
|
||||||
|
await dbTx.commitTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
await dbTx.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await dbTx.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> {
|
async getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> {
|
||||||
return this._db.getProcessedBlockCountForRange(fromBlockNumber, toBlockNumber);
|
return this._db.getProcessedBlockCountForRange(fromBlockNumber, toBlockNumber);
|
||||||
}
|
}
|
||||||
@ -304,7 +387,7 @@ export class Indexer {
|
|||||||
return this._db.getEventsInRange(fromBlockNumber, toBlockNumber);
|
return this._db.getEventsInRange(fromBlockNumber, toBlockNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isWatchedContract (address : string): Promise<ContractInterface | undefined> {
|
isWatchedContract (address : string): ContractInterface | undefined {
|
||||||
return this._watchedContracts[address];
|
return this._watchedContracts[address];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,6 +447,453 @@ export class Indexer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIPLDData (ipldBlock: IPLDBlockInterface): any {
|
||||||
|
return codec.decode(Buffer.from(ipldBlock.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushToIPFS (data: any): Promise<void> {
|
||||||
|
await this._ipfsClient.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
isIPFSConfigured (): boolean {
|
||||||
|
const ipfsAddr = this._serverConfig.ipfsApiAddr;
|
||||||
|
|
||||||
|
// Return false if ipfsAddr is undefined | null | empty string.
|
||||||
|
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestHooksProcessedBlock (): Promise<BlockProgressInterface> {
|
||||||
|
// Get current hookStatus.
|
||||||
|
const ipldStatus = await this._db.getIPLDStatus();
|
||||||
|
assert(ipldStatus, 'IPLD status not found');
|
||||||
|
|
||||||
|
// Get all the blocks at height hookStatus.latestProcessedBlockNumber.
|
||||||
|
const blocksAtHeight = await this.getBlocksAtHeight(ipldStatus.latestHooksBlockNumber, false);
|
||||||
|
|
||||||
|
// There can exactly one block at hookStatus.latestProcessedBlockNumber height.
|
||||||
|
assert(blocksAtHeight.length === 1);
|
||||||
|
|
||||||
|
return blocksAtHeight[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async processCheckpoint (indexer: IndexerInterface, blockHash: string, checkpointInterval: number): Promise<void> {
|
||||||
|
// Get all the contracts.
|
||||||
|
const contracts = Object.values(this._watchedContracts);
|
||||||
|
|
||||||
|
// Getting the block for checkpoint.
|
||||||
|
const block = await this.getBlockProgress(blockHash);
|
||||||
|
assert(block);
|
||||||
|
|
||||||
|
// For each contract, merge the diff till now to create a checkpoint.
|
||||||
|
for (const contract of contracts) {
|
||||||
|
// Get IPLD status for the contract.
|
||||||
|
const ipldStatus = this._ipldStatusMap[contract.address];
|
||||||
|
assert(ipldStatus, `IPLD status for contract ${contract.address} not found`);
|
||||||
|
|
||||||
|
const initBlockNumber = ipldStatus.init;
|
||||||
|
|
||||||
|
// Check if contract has checkpointing on.
|
||||||
|
// Check if it's time for a checkpoint or the init is in current block.
|
||||||
|
if (
|
||||||
|
contract.checkpoint &&
|
||||||
|
(block.blockNumber % checkpointInterval === 0 || initBlockNumber === block.blockNumber)
|
||||||
|
) {
|
||||||
|
await this.createCheckpoint(indexer, contract.address, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processCLICheckpoint (indexer: IndexerInterface, contractAddress: string, blockHash?: string): Promise<string | undefined> {
|
||||||
|
// Getting the block for checkpoint.
|
||||||
|
let block;
|
||||||
|
|
||||||
|
if (blockHash) {
|
||||||
|
block = await this.getBlockProgress(blockHash);
|
||||||
|
} else {
|
||||||
|
// In case of empty blockHash from checkpoint CLI, get the latest processed block from hookStatus for the checkpoint.
|
||||||
|
block = await this.getLatestHooksProcessedBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(block);
|
||||||
|
|
||||||
|
const checkpointBlockHash = await this.createCheckpoint(indexer, contractAddress, block);
|
||||||
|
assert(checkpointBlockHash, 'Checkpoint not created');
|
||||||
|
|
||||||
|
// Push checkpoint to IPFS if configured.
|
||||||
|
if (this.isIPFSConfigured()) {
|
||||||
|
const checkpointIPLDBlocks = await this._db.getIPLDBlocks({ block, contractAddress, kind: StateKind.Checkpoint });
|
||||||
|
|
||||||
|
// There can be at most one IPLDBlock for a (block, contractAddress, kind) combination.
|
||||||
|
assert(checkpointIPLDBlocks.length <= 1);
|
||||||
|
const checkpointIPLDBlock = checkpointIPLDBlocks[0];
|
||||||
|
|
||||||
|
const checkpointData = this.getIPLDData(checkpointIPLDBlock);
|
||||||
|
await this.pushToIPFS(checkpointData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkpointBlockHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createStateCheckpoint (contractAddress: string, block: BlockProgressInterface, data: any): Promise<void> {
|
||||||
|
// Get the contract.
|
||||||
|
const contract = this._watchedContracts[contractAddress];
|
||||||
|
assert(contract, `Contract ${contractAddress} not watched`);
|
||||||
|
|
||||||
|
if (block.blockNumber < contract.startingBlock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a checkpoint from the hook data without being concerned about diffs.
|
||||||
|
const ipldBlock = await this.prepareIPLDBlock(block, contractAddress, data, StateKind.Checkpoint);
|
||||||
|
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInit (
|
||||||
|
indexer: IndexerInterface,
|
||||||
|
blockHash: string,
|
||||||
|
blockNumber: number
|
||||||
|
): Promise<void> {
|
||||||
|
// Get all the contracts.
|
||||||
|
const contracts = Object.values(this._watchedContracts);
|
||||||
|
|
||||||
|
// Create an initial state for each contract.
|
||||||
|
for (const contract of contracts) {
|
||||||
|
// Check if contract has checkpointing on.
|
||||||
|
if (contract.checkpoint) {
|
||||||
|
// Check if starting block not reached yet.
|
||||||
|
if (blockNumber < contract.startingBlock) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get IPLD status for the contract.
|
||||||
|
const ipldStatus = this._ipldStatusMap[contract.address];
|
||||||
|
assert(ipldStatus, `IPLD status for contract ${contract.address} not found`);
|
||||||
|
|
||||||
|
// Check if a 'init' IPLDBlock already exists.
|
||||||
|
// Or if a 'diff' IPLDBlock already exists.
|
||||||
|
// Or if a 'checkpoint' IPLDBlock already exists.
|
||||||
|
// (A watcher with imported state won't have an init IPLDBlock, but it will have the imported checkpoint)
|
||||||
|
if (
|
||||||
|
ipldStatus.init ||
|
||||||
|
ipldStatus.diff ||
|
||||||
|
ipldStatus.checkpoint
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call initial state hook.
|
||||||
|
assert(indexer.processInitialState);
|
||||||
|
const stateData = await indexer.processInitialState(contract.address, blockHash);
|
||||||
|
|
||||||
|
const block = await this.getBlockProgress(blockHash);
|
||||||
|
assert(block);
|
||||||
|
|
||||||
|
const ipldBlock = await this.prepareIPLDBlock(block, contract.address, stateData, StateKind.Init);
|
||||||
|
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
||||||
|
|
||||||
|
// Push initial state to IPFS if configured.
|
||||||
|
if (this.isIPFSConfigured()) {
|
||||||
|
const ipldData = this.getIPLDData(ipldBlock);
|
||||||
|
await this.pushToIPFS(ipldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDiffStaged (contractAddress: string, blockHash: string, data: any): Promise<void> {
|
||||||
|
const block = await this.getBlockProgress(blockHash);
|
||||||
|
assert(block);
|
||||||
|
|
||||||
|
// Get the contract.
|
||||||
|
const contract = this._watchedContracts[contractAddress];
|
||||||
|
assert(contract, `Contract ${contractAddress} not watched`);
|
||||||
|
|
||||||
|
if (block.blockNumber < contract.startingBlock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a staged diff block.
|
||||||
|
const ipldBlock = await this.prepareIPLDBlock(block, contractAddress, data, StateKind.DiffStaged);
|
||||||
|
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
async finalizeDiffStaged (blockHash: string): Promise<void> {
|
||||||
|
const block = await this.getBlockProgress(blockHash);
|
||||||
|
assert(block);
|
||||||
|
|
||||||
|
// Get all the staged diff blocks for the given blockHash.
|
||||||
|
const stagedBlocks = await this._db.getIPLDBlocks({ block, kind: StateKind.DiffStaged });
|
||||||
|
|
||||||
|
// For each staged block, create a diff block.
|
||||||
|
for (const stagedBlock of stagedBlocks) {
|
||||||
|
const data = codec.decode(Buffer.from(stagedBlock.data));
|
||||||
|
await this.createDiff(stagedBlock.contractAddress, block, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all the staged diff blocks for current blockNumber.
|
||||||
|
// (Including staged diff blocks associated with pruned blocks)
|
||||||
|
await this.removeIPLDBlocks(block.blockNumber, StateKind.DiffStaged);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDiff (contractAddress: string, block: BlockProgressInterface, data: any): Promise<void> {
|
||||||
|
// Get the contract.
|
||||||
|
const contract = this._watchedContracts[contractAddress];
|
||||||
|
assert(contract, `Contract ${contractAddress} not watched`);
|
||||||
|
|
||||||
|
if (block.blockNumber < contract.startingBlock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get IPLD status for the contract.
|
||||||
|
const ipldStatus = this._ipldStatusMap[contractAddress];
|
||||||
|
assert(ipldStatus, `IPLD status for contract ${contractAddress} not found`);
|
||||||
|
|
||||||
|
// Get the latest checkpoint block number.
|
||||||
|
const checkpointBlockNumber = ipldStatus.checkpoint;
|
||||||
|
|
||||||
|
if (!checkpointBlockNumber) {
|
||||||
|
// Get the initial state block number.
|
||||||
|
const initBlockNumber = ipldStatus.init;
|
||||||
|
|
||||||
|
// There should be an initial state at least.
|
||||||
|
assert(initBlockNumber, 'No initial state found');
|
||||||
|
} else if (checkpointBlockNumber === block.blockNumber) {
|
||||||
|
// Check if the latest checkpoint is in the same block if block number is same.
|
||||||
|
const checkpoint = await this._db.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint);
|
||||||
|
assert(checkpoint);
|
||||||
|
|
||||||
|
assert(checkpoint.block.blockHash !== block.blockHash, 'Checkpoint already created for the block hash');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipldBlock = await this.prepareIPLDBlock(block, contractAddress, data, StateKind.Diff);
|
||||||
|
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCheckpoint (indexer: IndexerInterface, contractAddress: string, currentBlock: BlockProgressInterface): Promise<string | undefined> {
|
||||||
|
// Get the contract.
|
||||||
|
const contract = this._watchedContracts[contractAddress];
|
||||||
|
assert(contract, `Contract ${contractAddress} not watched`);
|
||||||
|
|
||||||
|
if (currentBlock.blockNumber < contract.startingBlock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the block is marked complete.
|
||||||
|
assert(currentBlock.isComplete, 'Block for a checkpoint should be marked as complete');
|
||||||
|
|
||||||
|
// Get current hookStatus.
|
||||||
|
const ipldStatus = await this._db.getIPLDStatus();
|
||||||
|
assert(ipldStatus);
|
||||||
|
|
||||||
|
// Make sure the hooks have been processed for the block.
|
||||||
|
assert(currentBlock.blockNumber <= ipldStatus.latestHooksBlockNumber, 'Block for a checkpoint should have hooks processed');
|
||||||
|
|
||||||
|
// Call state checkpoint hook and check if default checkpoint is disabled.
|
||||||
|
assert(indexer.processStateCheckpoint);
|
||||||
|
const disableDefaultCheckpoint = await indexer.processStateCheckpoint(contractAddress, currentBlock.blockHash);
|
||||||
|
|
||||||
|
if (disableDefaultCheckpoint) {
|
||||||
|
// Return if default checkpoint is disabled.
|
||||||
|
// Return block hash for checkpoint CLI.
|
||||||
|
return currentBlock.blockHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the latest 'checkpoint' | 'init' for the contract to fetch diffs after it.
|
||||||
|
let prevNonDiffBlock: IPLDBlockInterface;
|
||||||
|
let diffStartBlockNumber: number;
|
||||||
|
const checkpointBlock = await this._db.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint, currentBlock.blockNumber - 1);
|
||||||
|
|
||||||
|
if (checkpointBlock) {
|
||||||
|
const checkpointBlockNumber = checkpointBlock.block.blockNumber;
|
||||||
|
|
||||||
|
prevNonDiffBlock = checkpointBlock;
|
||||||
|
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.
|
||||||
|
// (job-runner won't have the updated ipld status)
|
||||||
|
this.updateIPLDStatusMap(contractAddress, { checkpoint: checkpointBlockNumber });
|
||||||
|
} else {
|
||||||
|
// There should be an initial state at least.
|
||||||
|
const initBlock = await this._db.getLatestIPLDBlock(contractAddress, StateKind.Init);
|
||||||
|
assert(initBlock, 'No initial state found');
|
||||||
|
|
||||||
|
prevNonDiffBlock = initBlock;
|
||||||
|
// Take block number previous to initial state block as the checkpoint is to be created in the same block.
|
||||||
|
diffStartBlockNumber = initBlock.block.blockNumber - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetching all diff blocks after the latest 'checkpoint' | 'init'.
|
||||||
|
const diffBlocks = await this._db.getDiffIPLDBlocksInRange(contractAddress, diffStartBlockNumber, currentBlock.blockNumber);
|
||||||
|
|
||||||
|
const prevNonDiffBlockData = codec.decode(Buffer.from(prevNonDiffBlock.data)) as any;
|
||||||
|
const data = {
|
||||||
|
state: prevNonDiffBlockData.state
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const diffBlock of diffBlocks) {
|
||||||
|
const diff = codec.decode(Buffer.from(diffBlock.data)) as any;
|
||||||
|
data.state = _.merge(data.state, diff.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipldBlock = await this.prepareIPLDBlock(currentBlock, contractAddress, data, StateKind.Checkpoint);
|
||||||
|
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
||||||
|
|
||||||
|
return currentBlock.blockHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepareIPLDBlock (block: BlockProgressInterface, contractAddress: string, data: any, kind: StateKind):Promise<any> {
|
||||||
|
console.time('time:ipld-indexer#prepareIPLDBlock');
|
||||||
|
let ipldBlock: IPLDBlockInterface;
|
||||||
|
|
||||||
|
// Get IPLD status for the contract.
|
||||||
|
const ipldStatus = this._ipldStatusMap[contractAddress];
|
||||||
|
assert(ipldStatus, `IPLD status for contract ${contractAddress} not found`);
|
||||||
|
|
||||||
|
// Get an existing 'init' | 'diff' | 'diff_staged' | 'checkpoint' IPLDBlock for current block, contractAddress to update.
|
||||||
|
let currentIPLDBlock: IPLDBlockInterface | undefined;
|
||||||
|
const prevIPLDBlockNumber = ipldStatus[kind];
|
||||||
|
|
||||||
|
// Fetch from DB for previous IPLD block or for checkpoint kind.
|
||||||
|
if (kind === 'checkpoint' || (prevIPLDBlockNumber && prevIPLDBlockNumber === block.blockNumber)) {
|
||||||
|
const currentIPLDBlocks = await this._db.getIPLDBlocks({ block, contractAddress, kind });
|
||||||
|
|
||||||
|
// There can be at most one IPLDBlock for a (block, contractAddress, kind) combination.
|
||||||
|
assert(currentIPLDBlocks.length <= 1);
|
||||||
|
currentIPLDBlock = currentIPLDBlocks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIPLDBlock) {
|
||||||
|
// Update current IPLDBlock of same kind if it exists.
|
||||||
|
ipldBlock = currentIPLDBlock;
|
||||||
|
|
||||||
|
// Update the data field.
|
||||||
|
const oldData = codec.decode(Buffer.from(ipldBlock.data));
|
||||||
|
data = _.merge(oldData, data);
|
||||||
|
} else {
|
||||||
|
// Create a new IPLDBlock instance.
|
||||||
|
ipldBlock = this._db.getNewIPLDBlock();
|
||||||
|
|
||||||
|
// Fetch the parent IPLDBlock.
|
||||||
|
const parentIPLDBlock = await this._db.getLatestIPLDBlock(contractAddress, null, block.blockNumber);
|
||||||
|
|
||||||
|
// Setting the meta-data for an IPLDBlock (done only once per IPLD block).
|
||||||
|
data.meta = {
|
||||||
|
id: contractAddress,
|
||||||
|
kind,
|
||||||
|
parent: {
|
||||||
|
'/': parentIPLDBlock ? parentIPLDBlock.cid : null
|
||||||
|
},
|
||||||
|
ethBlock: {
|
||||||
|
cid: {
|
||||||
|
'/': block.cid
|
||||||
|
},
|
||||||
|
num: block.blockNumber
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding the data using dag-cbor codec.
|
||||||
|
const bytes = codec.encode(data);
|
||||||
|
|
||||||
|
// Calculating sha256 (multi)hash of the encoded data.
|
||||||
|
const hash = await sha256.digest(bytes);
|
||||||
|
|
||||||
|
// Calculating the CID: v1, code: dag-cbor, hash.
|
||||||
|
const cid = CID.create(1, codec.code, hash);
|
||||||
|
|
||||||
|
// Update ipldBlock with new data.
|
||||||
|
ipldBlock = Object.assign(ipldBlock, {
|
||||||
|
block,
|
||||||
|
contractAddress,
|
||||||
|
cid: cid.toString(),
|
||||||
|
kind: data.meta.kind,
|
||||||
|
data: Buffer.from(bytes)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.timeEnd('time:ipld-indexer#prepareIPLDBlock');
|
||||||
|
return ipldBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPLDBlocksByHash (blockHash: string): Promise<IPLDBlockInterface[]> {
|
||||||
|
const block = await this.getBlockProgress(blockHash);
|
||||||
|
assert(block);
|
||||||
|
|
||||||
|
return this._db.getIPLDBlocks({ block });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIPLDBlockByCid (cid: string): Promise<IPLDBlockInterface | undefined> {
|
||||||
|
const ipldBlocks = await this._db.getIPLDBlocks({ cid });
|
||||||
|
|
||||||
|
// There can be only one IPLDBlock with a particular cid.
|
||||||
|
assert(ipldBlocks.length <= 1);
|
||||||
|
|
||||||
|
return ipldBlocks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveOrUpdateIPLDBlock (ipldBlock: IPLDBlockInterface): Promise<IPLDBlockInterface> {
|
||||||
|
const dbTx = await this._db.createTransactionRunner();
|
||||||
|
let res;
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await this._db.saveOrUpdateIPLDBlock(dbTx, ipldBlock);
|
||||||
|
|
||||||
|
await dbTx.commitTransaction();
|
||||||
|
|
||||||
|
// Get IPLD status for the contract.
|
||||||
|
const ipldStatus = this._ipldStatusMap[res.contractAddress];
|
||||||
|
assert(ipldStatus, `IPLD status for contract ${res.contractAddress} not found`);
|
||||||
|
|
||||||
|
// Update the IPLD status for the kind.
|
||||||
|
ipldStatus[res.kind] = res.block.blockNumber;
|
||||||
|
} catch (error) {
|
||||||
|
await dbTx.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await dbTx.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeIPLDBlocks (blockNumber: number, kind: StateKind): Promise<void> {
|
||||||
|
const dbTx = await this._db.createTransactionRunner();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._db.removeIPLDBlocks(dbTx, blockNumber, kind);
|
||||||
|
await dbTx.commitTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
await dbTx.rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await dbTx.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchIPLDStatus (): Promise<void> {
|
||||||
|
const contracts = Object.values(this._watchedContracts);
|
||||||
|
|
||||||
|
for (const contract of contracts) {
|
||||||
|
const initIPLDBlock = await this._db.getLatestIPLDBlock(contract.address, StateKind.Init);
|
||||||
|
const diffIPLDBlock = await this._db.getLatestIPLDBlock(contract.address, StateKind.Diff);
|
||||||
|
const diffStagedIPLDBlock = await this._db.getLatestIPLDBlock(contract.address, StateKind.DiffStaged);
|
||||||
|
const checkpointIPLDBlock = await this._db.getLatestIPLDBlock(contract.address, StateKind.Checkpoint);
|
||||||
|
|
||||||
|
this._ipldStatusMap[contract.address] = {
|
||||||
|
init: initIPLDBlock?.block.blockNumber,
|
||||||
|
diff: diffIPLDBlock?.block.blockNumber,
|
||||||
|
diff_staged: diffStagedIPLDBlock?.block.blockNumber,
|
||||||
|
checkpoint: checkpointIPLDBlock?.block.blockNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateIPLDStatusMap (address: string, ipldStatus: IpldStatus): Promise<void> {
|
||||||
|
// Get and update IPLD status for the contract.
|
||||||
|
const ipldStatusOld = this._ipldStatusMap[address];
|
||||||
|
this._ipldStatusMap[address] = _.merge(ipldStatusOld, ipldStatus);
|
||||||
|
}
|
||||||
|
|
||||||
parseEvent (logDescription: ethers.utils.LogDescription): { eventName: string, eventInfo: any } {
|
parseEvent (logDescription: ethers.utils.LogDescription): { eventName: string, eventInfo: any } {
|
||||||
const eventName = logDescription.name;
|
const eventName = logDescription.name;
|
||||||
|
|
||||||
|
@ -1,270 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2021 Vulcanize, Inc.
|
|
||||||
//
|
|
||||||
|
|
||||||
import { Between, ConnectionOptions, FindConditions, Repository } from 'typeorm';
|
|
||||||
import assert from 'assert';
|
|
||||||
import { Pool } from 'pg';
|
|
||||||
|
|
||||||
import { IPLDBlockInterface, IpldStatusInterface, StateKind } from './types';
|
|
||||||
import { Database } from './database';
|
|
||||||
import { MAX_REORG_DEPTH } from './constants';
|
|
||||||
|
|
||||||
export class IPLDDatabase extends Database {
|
|
||||||
_pgPool: Pool
|
|
||||||
|
|
||||||
constructor (config: ConnectionOptions) {
|
|
||||||
super(config);
|
|
||||||
|
|
||||||
assert(config.type === 'postgres');
|
|
||||||
this._pgPool = new Pool({
|
|
||||||
user: config.username,
|
|
||||||
host: config.host,
|
|
||||||
database: config.database,
|
|
||||||
password: config.password,
|
|
||||||
port: config.port
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLatestIPLDBlock (repo: Repository<IPLDBlockInterface>, contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise<IPLDBlockInterface | undefined> {
|
|
||||||
let queryBuilder = repo.createQueryBuilder('ipld_block')
|
|
||||||
.leftJoinAndSelect('ipld_block.block', 'block')
|
|
||||||
.where('block.is_pruned = false')
|
|
||||||
.andWhere('ipld_block.contract_address = :contractAddress', { contractAddress })
|
|
||||||
.orderBy('block.block_number', 'DESC');
|
|
||||||
|
|
||||||
// Filter out blocks after the provided block number.
|
|
||||||
if (blockNumber) {
|
|
||||||
queryBuilder.andWhere('block.block_number <= :blockNumber', { blockNumber });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter using kind if specified else avoid diff_staged block.
|
|
||||||
queryBuilder = kind
|
|
||||||
? queryBuilder.andWhere('ipld_block.kind = :kind', { kind })
|
|
||||||
: queryBuilder.andWhere('ipld_block.kind != :kind', { kind: StateKind.DiffStaged });
|
|
||||||
|
|
||||||
// Get the first three entries.
|
|
||||||
queryBuilder.limit(3);
|
|
||||||
|
|
||||||
const results = await queryBuilder.getMany();
|
|
||||||
|
|
||||||
if (results.length) {
|
|
||||||
// Sort by (block number desc, id desc) to get the latest entry.
|
|
||||||
// At same height, IPLD blocks are expected in order ['init', 'diff', 'checkpoint'],
|
|
||||||
// and are given preference in order ['checkpoint', 'diff', 'init']
|
|
||||||
results.sort((result1, result2) => {
|
|
||||||
if (result1.block.blockNumber === result2.block.blockNumber) {
|
|
||||||
return (result1.id > result2.id) ? -1 : 1;
|
|
||||||
} else {
|
|
||||||
return (result1.block.blockNumber > result2.block.blockNumber) ? -1 : 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return results[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPrevIPLDBlock (repo: Repository<IPLDBlockInterface>, blockHash: string, contractAddress: string, kind?: string): Promise<IPLDBlockInterface | undefined> {
|
|
||||||
const heirerchicalQuery = `
|
|
||||||
WITH RECURSIVE cte_query AS
|
|
||||||
(
|
|
||||||
SELECT
|
|
||||||
b.block_hash,
|
|
||||||
b.block_number,
|
|
||||||
b.parent_hash,
|
|
||||||
1 as depth,
|
|
||||||
i.id,
|
|
||||||
i.kind
|
|
||||||
FROM
|
|
||||||
block_progress b
|
|
||||||
LEFT JOIN
|
|
||||||
ipld_block i ON i.block_id = b.id
|
|
||||||
AND i.contract_address = $2
|
|
||||||
WHERE
|
|
||||||
b.block_hash = $1
|
|
||||||
UNION ALL
|
|
||||||
SELECT
|
|
||||||
b.block_hash,
|
|
||||||
b.block_number,
|
|
||||||
b.parent_hash,
|
|
||||||
c.depth + 1,
|
|
||||||
i.id,
|
|
||||||
i.kind
|
|
||||||
FROM
|
|
||||||
block_progress b
|
|
||||||
LEFT JOIN
|
|
||||||
ipld_block i
|
|
||||||
ON i.block_id = b.id
|
|
||||||
AND i.contract_address = $2
|
|
||||||
INNER JOIN
|
|
||||||
cte_query c ON c.parent_hash = b.block_hash
|
|
||||||
WHERE
|
|
||||||
c.depth < $3
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
block_number, id, kind
|
|
||||||
FROM
|
|
||||||
cte_query
|
|
||||||
ORDER BY block_number DESC, id DESC
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Fetching block and id for previous IPLDBlock in frothy region.
|
|
||||||
const queryResult = await repo.query(heirerchicalQuery, [blockHash, contractAddress, MAX_REORG_DEPTH]);
|
|
||||||
const latestRequiredResult = kind
|
|
||||||
? queryResult.find((obj: any) => obj.kind === kind)
|
|
||||||
: queryResult.find((obj: any) => obj.id);
|
|
||||||
|
|
||||||
let result: IPLDBlockInterface | undefined;
|
|
||||||
|
|
||||||
if (latestRequiredResult) {
|
|
||||||
result = await repo.findOne(latestRequiredResult.id, { relations: ['block'] });
|
|
||||||
} else {
|
|
||||||
// If IPLDBlock not found in frothy region get latest IPLDBlock in the pruned region.
|
|
||||||
// Filter out IPLDBlocks from pruned blocks.
|
|
||||||
const canonicalBlockNumber = queryResult.pop().block_number + 1;
|
|
||||||
|
|
||||||
let queryBuilder = repo.createQueryBuilder('ipld_block')
|
|
||||||
.leftJoinAndSelect('ipld_block.block', 'block')
|
|
||||||
.where('block.is_pruned = false')
|
|
||||||
.andWhere('ipld_block.contract_address = :contractAddress', { contractAddress })
|
|
||||||
.andWhere('block.block_number <= :canonicalBlockNumber', { canonicalBlockNumber })
|
|
||||||
.orderBy('block.block_number', 'DESC');
|
|
||||||
|
|
||||||
// Filter using kind if specified else order by id to give preference to checkpoint.
|
|
||||||
queryBuilder = kind
|
|
||||||
? queryBuilder.andWhere('ipld_block.kind = :kind', { kind })
|
|
||||||
: queryBuilder.addOrderBy('ipld_block.id', 'DESC');
|
|
||||||
|
|
||||||
// Get the first entry.
|
|
||||||
queryBuilder.limit(1);
|
|
||||||
|
|
||||||
result = await queryBuilder.getOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getIPLDBlocks (repo: Repository<IPLDBlockInterface>, where: FindConditions<IPLDBlockInterface>): Promise<IPLDBlockInterface[]> {
|
|
||||||
return repo.find({ where, relations: ['block'] });
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDiffIPLDBlocksInRange (repo: Repository<IPLDBlockInterface>, contractAddress: string, startblock: number, endBlock: number): Promise<IPLDBlockInterface[]> {
|
|
||||||
return repo.find({
|
|
||||||
relations: ['block'],
|
|
||||||
where: {
|
|
||||||
contractAddress,
|
|
||||||
kind: StateKind.Diff,
|
|
||||||
block: {
|
|
||||||
isPruned: false,
|
|
||||||
blockNumber: Between(startblock + 1, endBlock)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
order: {
|
|
||||||
block: 'ASC'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveOrUpdateIPLDBlock (repo: Repository<IPLDBlockInterface>, ipldBlock: IPLDBlockInterface): Promise<IPLDBlockInterface> {
|
|
||||||
let updatedData: {[key: string]: any};
|
|
||||||
|
|
||||||
console.time('time:ipld-database#saveOrUpdateIPLDBlock-DB-query');
|
|
||||||
if (ipldBlock.id) {
|
|
||||||
// Using pg query as workaround for typeorm memory issue when saving checkpoint with large sized data.
|
|
||||||
const { rows } = await this._pgPool.query(`
|
|
||||||
UPDATE ipld_block
|
|
||||||
SET block_id = $1, contract_address = $2, cid = $3, kind = $4, data = $5
|
|
||||||
WHERE id = $6
|
|
||||||
RETURNING *
|
|
||||||
`, [ipldBlock.block.id, ipldBlock.contractAddress, ipldBlock.cid, ipldBlock.kind, ipldBlock.data, ipldBlock.id]);
|
|
||||||
|
|
||||||
updatedData = rows[0];
|
|
||||||
} else {
|
|
||||||
const { rows } = await this._pgPool.query(`
|
|
||||||
INSERT INTO ipld_block(block_id, contract_address, cid, kind, data)
|
|
||||||
VALUES($1, $2, $3, $4, $5)
|
|
||||||
RETURNING *
|
|
||||||
`, [ipldBlock.block.id, ipldBlock.contractAddress, ipldBlock.cid, ipldBlock.kind, ipldBlock.data]);
|
|
||||||
|
|
||||||
updatedData = rows[0];
|
|
||||||
}
|
|
||||||
console.timeEnd('time:ipld-database#saveOrUpdateIPLDBlock-DB-query');
|
|
||||||
|
|
||||||
assert(updatedData);
|
|
||||||
return {
|
|
||||||
block: ipldBlock.block,
|
|
||||||
contractAddress: updatedData.contract_address,
|
|
||||||
cid: updatedData.cid,
|
|
||||||
kind: updatedData.kind,
|
|
||||||
data: updatedData.data,
|
|
||||||
id: updatedData.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeIPLDBlocks (repo: Repository<IPLDBlockInterface>, blockNumber: number, kind: string): Promise<void> {
|
|
||||||
const entities = await repo.find({ relations: ['block'], where: { block: { blockNumber }, kind } });
|
|
||||||
|
|
||||||
// Delete if entities found.
|
|
||||||
if (entities.length) {
|
|
||||||
await repo.delete(entities.map((entity) => entity.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeIPLDBlocksAfterBlock (repo: Repository<IPLDBlockInterface>, blockNumber: number): Promise<void> {
|
|
||||||
// Use raw SQL as TypeORM curently doesn't support delete via 'join' or 'using'
|
|
||||||
const deleteQuery = `
|
|
||||||
DELETE FROM
|
|
||||||
ipld_block
|
|
||||||
USING block_progress
|
|
||||||
WHERE
|
|
||||||
ipld_block.block_id = block_progress.id
|
|
||||||
AND block_progress.block_number > $1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
await repo.query(deleteQuery, [blockNumber]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getIPLDStatus (repo: Repository<IpldStatusInterface>): Promise<IpldStatusInterface | undefined> {
|
|
||||||
return repo.findOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateIPLDStatusHooksBlock (repo: Repository<IpldStatusInterface>, blockNumber: number, force?: boolean): Promise<IpldStatusInterface> {
|
|
||||||
let entity = await repo.findOne();
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
entity = repo.create({
|
|
||||||
latestHooksBlockNumber: blockNumber,
|
|
||||||
latestCheckpointBlockNumber: -1,
|
|
||||||
latestIPFSBlockNumber: -1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force || blockNumber > entity.latestHooksBlockNumber) {
|
|
||||||
entity.latestHooksBlockNumber = blockNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateIPLDStatusCheckpointBlock (repo: Repository<IpldStatusInterface>, blockNumber: number, force?: boolean): Promise<IpldStatusInterface> {
|
|
||||||
const entity = await repo.findOne();
|
|
||||||
assert(entity);
|
|
||||||
|
|
||||||
if (force || blockNumber > entity.latestCheckpointBlockNumber) {
|
|
||||||
entity.latestCheckpointBlockNumber = blockNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateIPLDStatusIPFSBlock (repo: Repository<IpldStatusInterface>, blockNumber: number, force?: boolean): Promise<IpldStatusInterface> {
|
|
||||||
const entity = await repo.findOne();
|
|
||||||
assert(entity);
|
|
||||||
|
|
||||||
if (force || blockNumber > entity.latestIPFSBlockNumber) {
|
|
||||||
entity.latestIPFSBlockNumber = blockNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo.save(entity);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,501 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2021 Vulcanize, Inc.
|
|
||||||
//
|
|
||||||
|
|
||||||
import assert from 'assert';
|
|
||||||
import { ethers } from 'ethers';
|
|
||||||
import { sha256 } from 'multiformats/hashes/sha2';
|
|
||||||
import { CID } from 'multiformats/cid';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { EthClient } from '@cerc-io/ipld-eth-client';
|
|
||||||
import * as codec from '@ipld/dag-cbor';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IPLDDatabaseInterface,
|
|
||||||
IndexerInterface,
|
|
||||||
BlockProgressInterface,
|
|
||||||
IPLDBlockInterface,
|
|
||||||
StateKind
|
|
||||||
} from './types';
|
|
||||||
import { Indexer } from './indexer';
|
|
||||||
import { ServerConfig } from './config';
|
|
||||||
import { IPFSClient } from './ipfs';
|
|
||||||
import { JobQueue } from './job-queue';
|
|
||||||
|
|
||||||
export interface IpldStatus {
|
|
||||||
init?: number;
|
|
||||||
diff?: number;
|
|
||||||
checkpoint?: number;
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
diff_staged?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IPLDIndexer extends Indexer {
|
|
||||||
_serverConfig: ServerConfig;
|
|
||||||
_ipldDb: IPLDDatabaseInterface;
|
|
||||||
_ipfsClient: IPFSClient;
|
|
||||||
_ipldStatusMap: { [key: string]: IpldStatus } = {};
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
serverConfig: ServerConfig,
|
|
||||||
ipldDb: IPLDDatabaseInterface,
|
|
||||||
ethClient: EthClient,
|
|
||||||
ethProvider: ethers.providers.BaseProvider,
|
|
||||||
jobQueue: JobQueue,
|
|
||||||
ipfsClient: IPFSClient
|
|
||||||
) {
|
|
||||||
super(ipldDb, ethClient, ethProvider, jobQueue);
|
|
||||||
|
|
||||||
this._serverConfig = serverConfig;
|
|
||||||
this._ipldDb = ipldDb;
|
|
||||||
this._ipfsClient = ipfsClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
getIPLDData (ipldBlock: IPLDBlockInterface): any {
|
|
||||||
return codec.decode(Buffer.from(ipldBlock.data));
|
|
||||||
}
|
|
||||||
|
|
||||||
async pushToIPFS (data: any): Promise<void> {
|
|
||||||
await this._ipfsClient.push(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
isIPFSConfigured (): boolean {
|
|
||||||
const ipfsAddr = this._serverConfig.ipfsApiAddr;
|
|
||||||
|
|
||||||
// Return false if ipfsAddr is undefined | null | empty string.
|
|
||||||
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLatestHooksProcessedBlock (): Promise<BlockProgressInterface> {
|
|
||||||
// Get current hookStatus.
|
|
||||||
const ipldStatus = await this._ipldDb.getIPLDStatus();
|
|
||||||
assert(ipldStatus, 'IPLD status not found');
|
|
||||||
|
|
||||||
// Get all the blocks at height hookStatus.latestProcessedBlockNumber.
|
|
||||||
const blocksAtHeight = await this.getBlocksAtHeight(ipldStatus.latestHooksBlockNumber, false);
|
|
||||||
|
|
||||||
// There can exactly one block at hookStatus.latestProcessedBlockNumber height.
|
|
||||||
assert(blocksAtHeight.length === 1);
|
|
||||||
|
|
||||||
return blocksAtHeight[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async processCheckpoint (indexer: IndexerInterface, blockHash: string, checkpointInterval: number): Promise<void> {
|
|
||||||
// Get all the contracts.
|
|
||||||
const contracts = Object.values(this._watchedContracts);
|
|
||||||
|
|
||||||
// Getting the block for checkpoint.
|
|
||||||
const block = await this.getBlockProgress(blockHash);
|
|
||||||
assert(block);
|
|
||||||
|
|
||||||
// For each contract, merge the diff till now to create a checkpoint.
|
|
||||||
for (const contract of contracts) {
|
|
||||||
// Get IPLD status for the contract.
|
|
||||||
const ipldStatus = this._ipldStatusMap[contract.address];
|
|
||||||
assert(ipldStatus, `IPLD status for contract ${contract.address} not found`);
|
|
||||||
|
|
||||||
const initBlockNumber = ipldStatus.init;
|
|
||||||
|
|
||||||
// Check if contract has checkpointing on.
|
|
||||||
// Check if it's time for a checkpoint or the init is in current block.
|
|
||||||
if (
|
|
||||||
contract.checkpoint &&
|
|
||||||
(block.blockNumber % checkpointInterval === 0 || initBlockNumber === block.blockNumber)
|
|
||||||
) {
|
|
||||||
await this.createCheckpoint(indexer, contract.address, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async processCLICheckpoint (indexer: IndexerInterface, contractAddress: string, blockHash?: string): Promise<string | undefined> {
|
|
||||||
// Getting the block for checkpoint.
|
|
||||||
let block;
|
|
||||||
|
|
||||||
if (blockHash) {
|
|
||||||
block = await this.getBlockProgress(blockHash);
|
|
||||||
} else {
|
|
||||||
// In case of empty blockHash from checkpoint CLI, get the latest processed block from hookStatus for the checkpoint.
|
|
||||||
block = await this.getLatestHooksProcessedBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(block);
|
|
||||||
|
|
||||||
const checkpointBlockHash = await this.createCheckpoint(indexer, contractAddress, block);
|
|
||||||
assert(checkpointBlockHash, 'Checkpoint not created');
|
|
||||||
|
|
||||||
// Push checkpoint to IPFS if configured.
|
|
||||||
if (this.isIPFSConfigured()) {
|
|
||||||
const checkpointIPLDBlocks = await this._ipldDb.getIPLDBlocks({ block, contractAddress, kind: StateKind.Checkpoint });
|
|
||||||
|
|
||||||
// There can be at most one IPLDBlock for a (block, contractAddress, kind) combination.
|
|
||||||
assert(checkpointIPLDBlocks.length <= 1);
|
|
||||||
const checkpointIPLDBlock = checkpointIPLDBlocks[0];
|
|
||||||
|
|
||||||
const checkpointData = this.getIPLDData(checkpointIPLDBlock);
|
|
||||||
await this.pushToIPFS(checkpointData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return checkpointBlockHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createStateCheckpoint (contractAddress: string, block: BlockProgressInterface, data: any): Promise<void> {
|
|
||||||
// Get the contract.
|
|
||||||
const contract = this._watchedContracts[contractAddress];
|
|
||||||
assert(contract, `Contract ${contractAddress} not watched`);
|
|
||||||
|
|
||||||
if (block.blockNumber < contract.startingBlock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a checkpoint from the hook data without being concerned about diffs.
|
|
||||||
const ipldBlock = await this.prepareIPLDBlock(block, contractAddress, data, StateKind.Checkpoint);
|
|
||||||
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createInit (
|
|
||||||
indexer: IndexerInterface,
|
|
||||||
blockHash: string,
|
|
||||||
blockNumber: number
|
|
||||||
): Promise<void> {
|
|
||||||
// Get all the contracts.
|
|
||||||
const contracts = Object.values(this._watchedContracts);
|
|
||||||
|
|
||||||
// Create an initial state for each contract.
|
|
||||||
for (const contract of contracts) {
|
|
||||||
// Check if contract has checkpointing on.
|
|
||||||
if (contract.checkpoint) {
|
|
||||||
// Check if starting block not reached yet.
|
|
||||||
if (blockNumber < contract.startingBlock) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get IPLD status for the contract.
|
|
||||||
const ipldStatus = this._ipldStatusMap[contract.address];
|
|
||||||
assert(ipldStatus, `IPLD status for contract ${contract.address} not found`);
|
|
||||||
|
|
||||||
// Check if a 'init' IPLDBlock already exists.
|
|
||||||
// Or if a 'diff' IPLDBlock already exists.
|
|
||||||
// Or if a 'checkpoint' IPLDBlock already exists.
|
|
||||||
// (A watcher with imported state won't have an init IPLDBlock, but it will have the imported checkpoint)
|
|
||||||
if (
|
|
||||||
ipldStatus.init ||
|
|
||||||
ipldStatus.diff ||
|
|
||||||
ipldStatus.checkpoint
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call initial state hook.
|
|
||||||
assert(indexer.processInitialState);
|
|
||||||
const stateData = await indexer.processInitialState(contract.address, blockHash);
|
|
||||||
|
|
||||||
const block = await this.getBlockProgress(blockHash);
|
|
||||||
assert(block);
|
|
||||||
|
|
||||||
const ipldBlock = await this.prepareIPLDBlock(block, contract.address, stateData, StateKind.Init);
|
|
||||||
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
|
||||||
|
|
||||||
// Push initial state to IPFS if configured.
|
|
||||||
if (this.isIPFSConfigured()) {
|
|
||||||
const ipldData = this.getIPLDData(ipldBlock);
|
|
||||||
await this.pushToIPFS(ipldData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDiffStaged (contractAddress: string, blockHash: string, data: any): Promise<void> {
|
|
||||||
const block = await this.getBlockProgress(blockHash);
|
|
||||||
assert(block);
|
|
||||||
|
|
||||||
// Get the contract.
|
|
||||||
const contract = this._watchedContracts[contractAddress];
|
|
||||||
assert(contract, `Contract ${contractAddress} not watched`);
|
|
||||||
|
|
||||||
if (block.blockNumber < contract.startingBlock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a staged diff block.
|
|
||||||
const ipldBlock = await this.prepareIPLDBlock(block, contractAddress, data, StateKind.DiffStaged);
|
|
||||||
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
async finalizeDiffStaged (blockHash: string): Promise<void> {
|
|
||||||
const block = await this.getBlockProgress(blockHash);
|
|
||||||
assert(block);
|
|
||||||
|
|
||||||
// Get all the staged diff blocks for the given blockHash.
|
|
||||||
const stagedBlocks = await this._ipldDb.getIPLDBlocks({ block, kind: StateKind.DiffStaged });
|
|
||||||
|
|
||||||
// For each staged block, create a diff block.
|
|
||||||
for (const stagedBlock of stagedBlocks) {
|
|
||||||
const data = codec.decode(Buffer.from(stagedBlock.data));
|
|
||||||
await this.createDiff(stagedBlock.contractAddress, block, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all the staged diff blocks for current blockNumber.
|
|
||||||
// (Including staged diff blocks associated with pruned blocks)
|
|
||||||
await this.removeIPLDBlocks(block.blockNumber, StateKind.DiffStaged);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDiff (contractAddress: string, block: BlockProgressInterface, data: any): Promise<void> {
|
|
||||||
// Get the contract.
|
|
||||||
const contract = this._watchedContracts[contractAddress];
|
|
||||||
assert(contract, `Contract ${contractAddress} not watched`);
|
|
||||||
|
|
||||||
if (block.blockNumber < contract.startingBlock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get IPLD status for the contract.
|
|
||||||
const ipldStatus = this._ipldStatusMap[contractAddress];
|
|
||||||
assert(ipldStatus, `IPLD status for contract ${contractAddress} not found`);
|
|
||||||
|
|
||||||
// Get the latest checkpoint block number.
|
|
||||||
const checkpointBlockNumber = ipldStatus.checkpoint;
|
|
||||||
|
|
||||||
if (!checkpointBlockNumber) {
|
|
||||||
// Get the initial state block number.
|
|
||||||
const initBlockNumber = ipldStatus.init;
|
|
||||||
|
|
||||||
// There should be an initial state at least.
|
|
||||||
assert(initBlockNumber, 'No initial state found');
|
|
||||||
} else if (checkpointBlockNumber === block.blockNumber) {
|
|
||||||
// Check if the latest checkpoint is in the same block if block number is same.
|
|
||||||
const checkpoint = await this._ipldDb.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint);
|
|
||||||
assert(checkpoint);
|
|
||||||
|
|
||||||
assert(checkpoint.block.blockHash !== block.blockHash, 'Checkpoint already created for the block hash');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipldBlock = await this.prepareIPLDBlock(block, contractAddress, data, StateKind.Diff);
|
|
||||||
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createCheckpoint (indexer: IndexerInterface, contractAddress: string, currentBlock: BlockProgressInterface): Promise<string | undefined> {
|
|
||||||
// Get the contract.
|
|
||||||
const contract = this._watchedContracts[contractAddress];
|
|
||||||
assert(contract, `Contract ${contractAddress} not watched`);
|
|
||||||
|
|
||||||
if (currentBlock.blockNumber < contract.startingBlock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the block is marked complete.
|
|
||||||
assert(currentBlock.isComplete, 'Block for a checkpoint should be marked as complete');
|
|
||||||
|
|
||||||
// Get current hookStatus.
|
|
||||||
const ipldStatus = await this._ipldDb.getIPLDStatus();
|
|
||||||
assert(ipldStatus);
|
|
||||||
|
|
||||||
// Make sure the hooks have been processed for the block.
|
|
||||||
assert(currentBlock.blockNumber <= ipldStatus.latestHooksBlockNumber, 'Block for a checkpoint should have hooks processed');
|
|
||||||
|
|
||||||
// Call state checkpoint hook and check if default checkpoint is disabled.
|
|
||||||
assert(indexer.processStateCheckpoint);
|
|
||||||
const disableDefaultCheckpoint = await indexer.processStateCheckpoint(contractAddress, currentBlock.blockHash);
|
|
||||||
|
|
||||||
if (disableDefaultCheckpoint) {
|
|
||||||
// Return if default checkpoint is disabled.
|
|
||||||
// Return block hash for checkpoint CLI.
|
|
||||||
return currentBlock.blockHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the latest 'checkpoint' | 'init' for the contract to fetch diffs after it.
|
|
||||||
let prevNonDiffBlock: IPLDBlockInterface;
|
|
||||||
let diffStartBlockNumber: number;
|
|
||||||
const checkpointBlock = await this._ipldDb.getLatestIPLDBlock(contractAddress, StateKind.Checkpoint, currentBlock.blockNumber - 1);
|
|
||||||
|
|
||||||
if (checkpointBlock) {
|
|
||||||
const checkpointBlockNumber = checkpointBlock.block.blockNumber;
|
|
||||||
|
|
||||||
prevNonDiffBlock = checkpointBlock;
|
|
||||||
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.
|
|
||||||
// (job-runner won't have the updated ipld status)
|
|
||||||
this.updateIPLDStatusMap(contractAddress, { checkpoint: checkpointBlockNumber });
|
|
||||||
} else {
|
|
||||||
// There should be an initial state at least.
|
|
||||||
const initBlock = await this._ipldDb.getLatestIPLDBlock(contractAddress, StateKind.Init);
|
|
||||||
assert(initBlock, 'No initial state found');
|
|
||||||
|
|
||||||
prevNonDiffBlock = initBlock;
|
|
||||||
// Take block number previous to initial state block as the checkpoint is to be created in the same block.
|
|
||||||
diffStartBlockNumber = initBlock.block.blockNumber - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetching all diff blocks after the latest 'checkpoint' | 'init'.
|
|
||||||
const diffBlocks = await this._ipldDb.getDiffIPLDBlocksInRange(contractAddress, diffStartBlockNumber, currentBlock.blockNumber);
|
|
||||||
|
|
||||||
const prevNonDiffBlockData = codec.decode(Buffer.from(prevNonDiffBlock.data)) as any;
|
|
||||||
const data = {
|
|
||||||
state: prevNonDiffBlockData.state
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const diffBlock of diffBlocks) {
|
|
||||||
const diff = codec.decode(Buffer.from(diffBlock.data)) as any;
|
|
||||||
data.state = _.merge(data.state, diff.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipldBlock = await this.prepareIPLDBlock(currentBlock, contractAddress, data, StateKind.Checkpoint);
|
|
||||||
await this.saveOrUpdateIPLDBlock(ipldBlock);
|
|
||||||
|
|
||||||
return currentBlock.blockHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepareIPLDBlock (block: BlockProgressInterface, contractAddress: string, data: any, kind: StateKind):Promise<any> {
|
|
||||||
console.time('time:ipld-indexer#prepareIPLDBlock');
|
|
||||||
let ipldBlock: IPLDBlockInterface;
|
|
||||||
|
|
||||||
// Get IPLD status for the contract.
|
|
||||||
const ipldStatus = this._ipldStatusMap[contractAddress];
|
|
||||||
assert(ipldStatus, `IPLD status for contract ${contractAddress} not found`);
|
|
||||||
|
|
||||||
// Get an existing 'init' | 'diff' | 'diff_staged' | 'checkpoint' IPLDBlock for current block, contractAddress to update.
|
|
||||||
let currentIPLDBlock: IPLDBlockInterface | undefined;
|
|
||||||
const prevIPLDBlockNumber = ipldStatus[kind];
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
assert(currentIPLDBlocks.length <= 1);
|
|
||||||
currentIPLDBlock = currentIPLDBlocks[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentIPLDBlock) {
|
|
||||||
// Update current IPLDBlock of same kind if it exists.
|
|
||||||
ipldBlock = currentIPLDBlock;
|
|
||||||
|
|
||||||
// Update the data field.
|
|
||||||
const oldData = codec.decode(Buffer.from(ipldBlock.data));
|
|
||||||
data = _.merge(oldData, data);
|
|
||||||
} else {
|
|
||||||
// Create a new IPLDBlock instance.
|
|
||||||
ipldBlock = this._ipldDb.getNewIPLDBlock();
|
|
||||||
|
|
||||||
// Fetch the parent IPLDBlock.
|
|
||||||
const parentIPLDBlock = await this._ipldDb.getLatestIPLDBlock(contractAddress, null, block.blockNumber);
|
|
||||||
|
|
||||||
// Setting the meta-data for an IPLDBlock (done only once per IPLD block).
|
|
||||||
data.meta = {
|
|
||||||
id: contractAddress,
|
|
||||||
kind,
|
|
||||||
parent: {
|
|
||||||
'/': parentIPLDBlock ? parentIPLDBlock.cid : null
|
|
||||||
},
|
|
||||||
ethBlock: {
|
|
||||||
cid: {
|
|
||||||
'/': block.cid
|
|
||||||
},
|
|
||||||
num: block.blockNumber
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoding the data using dag-cbor codec.
|
|
||||||
const bytes = codec.encode(data);
|
|
||||||
|
|
||||||
// Calculating sha256 (multi)hash of the encoded data.
|
|
||||||
const hash = await sha256.digest(bytes);
|
|
||||||
|
|
||||||
// Calculating the CID: v1, code: dag-cbor, hash.
|
|
||||||
const cid = CID.create(1, codec.code, hash);
|
|
||||||
|
|
||||||
// Update ipldBlock with new data.
|
|
||||||
ipldBlock = Object.assign(ipldBlock, {
|
|
||||||
block,
|
|
||||||
contractAddress,
|
|
||||||
cid: cid.toString(),
|
|
||||||
kind: data.meta.kind,
|
|
||||||
data: Buffer.from(bytes)
|
|
||||||
});
|
|
||||||
|
|
||||||
console.timeEnd('time:ipld-indexer#prepareIPLDBlock');
|
|
||||||
return ipldBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getIPLDBlocksByHash (blockHash: string): Promise<IPLDBlockInterface[]> {
|
|
||||||
const block = await this.getBlockProgress(blockHash);
|
|
||||||
assert(block);
|
|
||||||
|
|
||||||
return this._ipldDb.getIPLDBlocks({ block });
|
|
||||||
}
|
|
||||||
|
|
||||||
async getIPLDBlockByCid (cid: string): Promise<IPLDBlockInterface | undefined> {
|
|
||||||
const ipldBlocks = await this._ipldDb.getIPLDBlocks({ cid });
|
|
||||||
|
|
||||||
// There can be only one IPLDBlock with a particular cid.
|
|
||||||
assert(ipldBlocks.length <= 1);
|
|
||||||
|
|
||||||
return ipldBlocks[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveOrUpdateIPLDBlock (ipldBlock: IPLDBlockInterface): Promise<IPLDBlockInterface> {
|
|
||||||
const dbTx = await this._db.createTransactionRunner();
|
|
||||||
let res;
|
|
||||||
|
|
||||||
try {
|
|
||||||
res = await this._ipldDb.saveOrUpdateIPLDBlock(dbTx, ipldBlock);
|
|
||||||
|
|
||||||
await dbTx.commitTransaction();
|
|
||||||
|
|
||||||
// Get IPLD status for the contract.
|
|
||||||
const ipldStatus = this._ipldStatusMap[res.contractAddress];
|
|
||||||
assert(ipldStatus, `IPLD status for contract ${res.contractAddress} not found`);
|
|
||||||
|
|
||||||
// Update the IPLD status for the kind.
|
|
||||||
ipldStatus[res.kind] = res.block.blockNumber;
|
|
||||||
} catch (error) {
|
|
||||||
await dbTx.rollbackTransaction();
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
await dbTx.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeIPLDBlocks (blockNumber: number, kind: StateKind): Promise<void> {
|
|
||||||
const dbTx = await this._db.createTransactionRunner();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this._ipldDb.removeIPLDBlocks(dbTx, blockNumber, kind);
|
|
||||||
await dbTx.commitTransaction();
|
|
||||||
} catch (error) {
|
|
||||||
await dbTx.rollbackTransaction();
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
await dbTx.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchIPLDStatus (): Promise<void> {
|
|
||||||
const contracts = Object.values(this._watchedContracts);
|
|
||||||
|
|
||||||
for (const contract of contracts) {
|
|
||||||
const initIPLDBlock = await this._ipldDb.getLatestIPLDBlock(contract.address, StateKind.Init);
|
|
||||||
const diffIPLDBlock = await this._ipldDb.getLatestIPLDBlock(contract.address, StateKind.Diff);
|
|
||||||
const diffStagedIPLDBlock = await this._ipldDb.getLatestIPLDBlock(contract.address, StateKind.DiffStaged);
|
|
||||||
const checkpointIPLDBlock = await this._ipldDb.getLatestIPLDBlock(contract.address, StateKind.Checkpoint);
|
|
||||||
|
|
||||||
this._ipldStatusMap[contract.address] = {
|
|
||||||
init: initIPLDBlock?.block.blockNumber,
|
|
||||||
diff: diffIPLDBlock?.block.blockNumber,
|
|
||||||
diff_staged: diffStagedIPLDBlock?.block.blockNumber,
|
|
||||||
checkpoint: checkpointIPLDBlock?.block.blockNumber
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateIPLDStatusMap (address: string, ipldStatus: IpldStatus): Promise<void> {
|
|
||||||
// Get and update IPLD status for the contract.
|
|
||||||
const ipldStatusOld = this._ipldStatusMap[address];
|
|
||||||
this._ipldStatusMap[address] = _.merge(ipldStatusOld, ipldStatus);
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,7 +17,7 @@ import {
|
|||||||
QUEUE_EVENT_PROCESSING
|
QUEUE_EVENT_PROCESSING
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { JobQueue } from './job-queue';
|
import { JobQueue } from './job-queue';
|
||||||
import { EventInterface, IndexerInterface, IPLDIndexerInterface, SyncStatusInterface } from './types';
|
import { EventInterface, IndexerInterface, SyncStatusInterface } from './types';
|
||||||
import { wait } from './misc';
|
import { wait } from './misc';
|
||||||
import { createPruningJob, processBatchEvents } from './common';
|
import { createPruningJob, processBatchEvents } from './common';
|
||||||
import { lastBlockNumEvents, lastBlockProcessDuration, lastProcessedBlockNumber } from './metrics';
|
import { lastBlockNumEvents, lastBlockProcessDuration, lastProcessedBlockNumber } from './metrics';
|
||||||
@ -25,7 +25,7 @@ import { lastBlockNumEvents, lastBlockProcessDuration, lastProcessedBlockNumber
|
|||||||
const log = debug('vulcanize:job-runner');
|
const log = debug('vulcanize:job-runner');
|
||||||
|
|
||||||
export class JobRunner {
|
export class JobRunner {
|
||||||
_indexer: IndexerInterface | IPLDIndexerInterface
|
_indexer: IndexerInterface
|
||||||
_jobQueue: JobQueue
|
_jobQueue: JobQueue
|
||||||
_jobQueueConfig: JobQueueConfig
|
_jobQueueConfig: JobQueueConfig
|
||||||
_blockProcessStartTime?: Date
|
_blockProcessStartTime?: Date
|
||||||
@ -246,13 +246,11 @@ export class JobRunner {
|
|||||||
// Delay required to process block.
|
// Delay required to process block.
|
||||||
await wait(jobDelayInMilliSecs);
|
await wait(jobDelayInMilliSecs);
|
||||||
console.time('time:job-runner#_indexBlock-fetch-block-events');
|
console.time('time:job-runner#_indexBlock-fetch-block-events');
|
||||||
blockProgress = await this._indexer.fetchBlockEvents({ cid, blockHash, blockNumber, parentHash, blockTimestamp: timestamp });
|
blockProgress = await this._indexer.fetchBlockWithEvents({ cid, blockHash, blockNumber, parentHash, blockTimestamp: timestamp });
|
||||||
console.timeEnd('time:job-runner#_indexBlock-fetch-block-events');
|
console.timeEnd('time:job-runner#_indexBlock-fetch-block-events');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._indexer.processBlock) {
|
|
||||||
await this._indexer.processBlock(blockProgress);
|
await this._indexer.processBlock(blockProgress);
|
||||||
}
|
|
||||||
|
|
||||||
// Push job to event processing queue.
|
// Push job to event processing queue.
|
||||||
// Block with all events processed or no events will not be processed again due to check in _processEvents.
|
// Block with all events processed or no events will not be processed again due to check in _processEvents.
|
||||||
@ -299,7 +297,7 @@ export class JobRunner {
|
|||||||
assert(this._indexer.cacheContract);
|
assert(this._indexer.cacheContract);
|
||||||
this._indexer.cacheContract(contract);
|
this._indexer.cacheContract(contract);
|
||||||
|
|
||||||
const ipldIndexer = this._indexer as IPLDIndexerInterface;
|
const ipldIndexer = this._indexer;
|
||||||
if (ipldIndexer.updateIPLDStatusMap) {
|
if (ipldIndexer.updateIPLDStatusMap) {
|
||||||
ipldIndexer.updateIPLDStatusMap(contract.address, {});
|
ipldIndexer.updateIPLDStatusMap(contract.address, {});
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,11 @@ export const eventProcessingLoadEntityDBQueryDuration = new client.Histogram({
|
|||||||
help: 'Duration of DB query made in event processing'
|
help: 'Duration of DB query made in event processing'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const eventProcessingEthCallDuration = new client.Histogram({
|
||||||
|
name: 'event_processing_eth_call_duration_seconds',
|
||||||
|
help: 'Duration of eth_calls made in event processing'
|
||||||
|
});
|
||||||
|
|
||||||
// Export metrics on a server
|
// Export metrics on a server
|
||||||
const app: Application = express();
|
const app: Application = express();
|
||||||
|
|
||||||
@ -131,7 +136,7 @@ const registerDBSizeMetrics = async ({ database, jobQueue }: Config): Promise<vo
|
|||||||
// eslint-disable-next-line no-new
|
// eslint-disable-next-line no-new
|
||||||
new client.Gauge({
|
new client.Gauge({
|
||||||
name: 'database_size_bytes',
|
name: 'database_size_bytes',
|
||||||
help: 'Total entries in event table',
|
help: 'Watcher database sizes in bytes',
|
||||||
labelNames: ['type'] as const,
|
labelNames: ['type'] as const,
|
||||||
async collect () {
|
async collect () {
|
||||||
const [
|
const [
|
||||||
|
@ -7,8 +7,7 @@ import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper';
|
|||||||
|
|
||||||
import { ServerConfig } from './config';
|
import { ServerConfig } from './config';
|
||||||
import { Where, QueryOptions } from './database';
|
import { Where, QueryOptions } from './database';
|
||||||
import { IpldStatus } from './ipld-indexer';
|
import { ValueResult, IpldStatus } from './indexer';
|
||||||
import { ValueResult } from './indexer';
|
|
||||||
|
|
||||||
export enum StateKind {
|
export enum StateKind {
|
||||||
Diff = 'diff',
|
Diff = 'diff',
|
||||||
@ -91,7 +90,7 @@ export interface IndexerInterface {
|
|||||||
getBlocksAtHeight (height: number, isPruned: boolean): Promise<BlockProgressInterface[]>;
|
getBlocksAtHeight (height: number, isPruned: boolean): Promise<BlockProgressInterface[]>;
|
||||||
getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<EventInterface>>
|
getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Array<EventInterface>>
|
||||||
getAncestorAtDepth (blockHash: string, depth: number): Promise<string>
|
getAncestorAtDepth (blockHash: string, depth: number): Promise<string>
|
||||||
fetchBlockEvents (block: DeepPartial<BlockProgressInterface>): Promise<BlockProgressInterface>
|
fetchBlockWithEvents (block: DeepPartial<BlockProgressInterface>): Promise<BlockProgressInterface>
|
||||||
removeUnknownEvents (block: BlockProgressInterface): Promise<void>
|
removeUnknownEvents (block: BlockProgressInterface): Promise<void>
|
||||||
updateBlockProgress (block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface>
|
updateBlockProgress (block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface>
|
||||||
updateSyncStatusChainHead (blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>
|
updateSyncStatusChainHead (blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>
|
||||||
@ -101,7 +100,7 @@ export interface IndexerInterface {
|
|||||||
saveEventEntity (dbEvent: EventInterface): Promise<EventInterface>;
|
saveEventEntity (dbEvent: EventInterface): Promise<EventInterface>;
|
||||||
processEvent (event: EventInterface): Promise<void>;
|
processEvent (event: EventInterface): Promise<void>;
|
||||||
parseEventNameAndArgs?: (kind: string, logObj: any) => any;
|
parseEventNameAndArgs?: (kind: string, logObj: any) => any;
|
||||||
isWatchedContract?: (address: string) => Promise<ContractInterface | undefined>;
|
isWatchedContract: (address: string) => ContractInterface | undefined;
|
||||||
getContractsByKind?: (kind: string) => ContractInterface[];
|
getContractsByKind?: (kind: string) => ContractInterface[];
|
||||||
cacheContract?: (contract: ContractInterface) => void;
|
cacheContract?: (contract: ContractInterface) => void;
|
||||||
watchContract?: (address: string, kind: string, checkpoint: boolean, startingBlock: number) => Promise<void>
|
watchContract?: (address: string, kind: string, checkpoint: boolean, startingBlock: number) => Promise<void>
|
||||||
@ -110,13 +109,10 @@ export interface IndexerInterface {
|
|||||||
createDiffStaged?: (contractAddress: string, blockHash: string, data: any) => Promise<void>
|
createDiffStaged?: (contractAddress: string, blockHash: string, data: any) => Promise<void>
|
||||||
processInitialState?: (contractAddress: string, blockHash: string) => Promise<any>
|
processInitialState?: (contractAddress: string, blockHash: string) => Promise<any>
|
||||||
processStateCheckpoint?: (contractAddress: string, blockHash: string) => Promise<boolean>
|
processStateCheckpoint?: (contractAddress: string, blockHash: string) => Promise<boolean>
|
||||||
processBlock?: (blockProgres: BlockProgressInterface) => Promise<void>
|
processBlock: (blockProgres: BlockProgressInterface) => Promise<void>
|
||||||
processBlockAfterEvents?: (blockHash: string) => Promise<void>
|
processBlockAfterEvents?: (blockHash: string) => Promise<void>
|
||||||
getStorageValue (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult>
|
getStorageValue (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise<ValueResult>
|
||||||
updateSubgraphState?: (contractAddress: string, data: any) => void
|
updateSubgraphState?: (contractAddress: string, data: any) => void
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPLDIndexerInterface extends IndexerInterface {
|
|
||||||
updateIPLDStatusMap (address: string, ipldStatus: IpldStatus): Promise<void>
|
updateIPLDStatusMap (address: string, ipldStatus: IpldStatus): Promise<void>
|
||||||
getIPLDData (ipldBlock: IPLDBlockInterface): any
|
getIPLDData (ipldBlock: IPLDBlockInterface): any
|
||||||
}
|
}
|
||||||
@ -140,18 +136,17 @@ export interface DatabaseInterface {
|
|||||||
getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }>;
|
getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }>;
|
||||||
getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise<Array<EventInterface>>;
|
getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise<Array<EventInterface>>;
|
||||||
markBlocksAsPruned (queryRunner: QueryRunner, blocks: BlockProgressInterface[]): Promise<void>;
|
markBlocksAsPruned (queryRunner: QueryRunner, blocks: BlockProgressInterface[]): Promise<void>;
|
||||||
|
saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial<BlockProgressInterface>): Promise<BlockProgressInterface>;
|
||||||
updateBlockProgress (queryRunner: QueryRunner, block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface>
|
updateBlockProgress (queryRunner: QueryRunner, block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface>
|
||||||
updateSyncStatusIndexedBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>;
|
updateSyncStatusIndexedBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>;
|
||||||
updateSyncStatusChainHead (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>;
|
updateSyncStatusChainHead (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>;
|
||||||
updateSyncStatusCanonicalBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>;
|
updateSyncStatusCanonicalBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force?: boolean): Promise<SyncStatusInterface>;
|
||||||
saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgressInterface>, events: DeepPartial<EventInterface>[]): Promise<BlockProgressInterface>;
|
saveEvents (queryRunner: QueryRunner, events: DeepPartial<EventInterface>[]): Promise<void>;
|
||||||
|
saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgressInterface>, events: DeepPartial<EventInterface>[]): Promise<BlockProgressInterface>;
|
||||||
saveEventEntity (queryRunner: QueryRunner, entity: EventInterface): Promise<EventInterface>;
|
saveEventEntity (queryRunner: QueryRunner, entity: EventInterface): Promise<EventInterface>;
|
||||||
removeEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindManyOptions<Entity> | FindConditions<Entity>): Promise<void>;
|
removeEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindManyOptions<Entity> | FindConditions<Entity>): Promise<void>;
|
||||||
getContracts?: () => Promise<ContractInterface[]>
|
getContracts?: () => Promise<ContractInterface[]>
|
||||||
saveContract?: (queryRunner: QueryRunner, contractAddress: string, kind: string, checkpoint: boolean, startingBlock: number) => Promise<ContractInterface>
|
saveContract?: (queryRunner: QueryRunner, contractAddress: string, kind: string, checkpoint: boolean, startingBlock: number) => Promise<ContractInterface>
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPLDDatabaseInterface extends DatabaseInterface {
|
|
||||||
getLatestIPLDBlock (contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise<IPLDBlockInterface | undefined>
|
getLatestIPLDBlock (contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise<IPLDBlockInterface | undefined>
|
||||||
getIPLDBlocks (where: FindConditions<IPLDBlockInterface>): Promise<IPLDBlockInterface[]>
|
getIPLDBlocks (where: FindConditions<IPLDBlockInterface>): Promise<IPLDBlockInterface[]>
|
||||||
getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise<IPLDBlockInterface[]>
|
getDiffIPLDBlocksInRange (contractAddress: string, startBlock: number, endBlock: number): Promise<IPLDBlockInterface[]>
|
||||||
|
Loading…
Reference in New Issue
Block a user