2021-08-12 09:58:13 +00:00
|
|
|
//
|
|
|
|
// Copyright 2021 Vulcanize, Inc.
|
|
|
|
//
|
|
|
|
|
2021-08-19 07:57:32 +00:00
|
|
|
import assert from 'assert';
|
2021-08-25 07:17:53 +00:00
|
|
|
import {
|
|
|
|
Brackets,
|
|
|
|
Connection,
|
|
|
|
ConnectionOptions,
|
|
|
|
createConnection,
|
|
|
|
DeepPartial,
|
|
|
|
FindConditions,
|
2021-10-20 12:19:44 +00:00
|
|
|
FindManyOptions,
|
2021-08-25 07:17:53 +00:00
|
|
|
In,
|
|
|
|
QueryRunner,
|
2021-12-13 10:08:34 +00:00
|
|
|
Repository,
|
|
|
|
SelectQueryBuilder
|
2021-08-25 07:17:53 +00:00
|
|
|
} from 'typeorm';
|
2021-08-19 07:57:32 +00:00
|
|
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
2021-08-24 06:25:29 +00:00
|
|
|
import _ from 'lodash';
|
2021-07-13 06:31:54 +00:00
|
|
|
|
2021-08-25 07:17:53 +00:00
|
|
|
import { BlockProgressInterface, ContractInterface, EventInterface, SyncStatusInterface } from './types';
|
2021-09-21 11:13:55 +00:00
|
|
|
import { MAX_REORG_DEPTH, UNKNOWN_EVENT_NAME } from './constants';
|
2021-08-19 07:57:32 +00:00
|
|
|
|
2021-08-25 07:17:53 +00:00
|
|
|
const DEFAULT_LIMIT = 100;
|
|
|
|
const DEFAULT_SKIP = 0;
|
|
|
|
|
|
|
|
const OPERATOR_MAP = {
|
|
|
|
equals: '=',
|
|
|
|
gt: '>',
|
|
|
|
lt: '<',
|
|
|
|
gte: '>=',
|
|
|
|
lte: '<=',
|
|
|
|
in: 'IN',
|
|
|
|
contains: 'LIKE',
|
|
|
|
starts: 'LIKE',
|
|
|
|
ends: 'LIKE'
|
|
|
|
};
|
|
|
|
|
2021-12-16 11:46:48 +00:00
|
|
|
const INSERT_EVENTS_BATCH = 100;
|
|
|
|
|
2021-08-25 07:17:53 +00:00
|
|
|
export interface BlockHeight {
|
|
|
|
number?: number;
|
|
|
|
hash?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum OrderDirection {
|
|
|
|
asc = 'asc',
|
|
|
|
desc = 'desc'
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface QueryOptions {
|
|
|
|
limit?: number;
|
|
|
|
skip?: number;
|
|
|
|
orderBy?: string;
|
|
|
|
orderDirection?: OrderDirection;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Where {
|
|
|
|
[key: string]: [{
|
|
|
|
value: any;
|
|
|
|
not: boolean;
|
|
|
|
operator: keyof typeof OPERATOR_MAP;
|
|
|
|
}]
|
|
|
|
}
|
2021-08-24 06:25:29 +00:00
|
|
|
|
2021-08-26 10:06:16 +00:00
|
|
|
export type Relation = string | { property: string, alias: string }
|
|
|
|
|
2021-08-19 07:57:32 +00:00
|
|
|
export class Database {
|
|
|
|
_config: ConnectionOptions
|
|
|
|
_conn!: Connection
|
|
|
|
|
|
|
|
constructor (config: ConnectionOptions) {
|
|
|
|
assert(config);
|
|
|
|
this._config = config;
|
|
|
|
}
|
|
|
|
|
|
|
|
async init (): Promise<Connection> {
|
|
|
|
assert(!this._conn);
|
|
|
|
|
|
|
|
this._conn = await createConnection({
|
|
|
|
...this._config,
|
|
|
|
namingStrategy: new SnakeNamingStrategy()
|
|
|
|
});
|
|
|
|
|
|
|
|
return this._conn;
|
|
|
|
}
|
|
|
|
|
|
|
|
async close (): Promise<void> {
|
|
|
|
return this._conn.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
async createTransactionRunner (): Promise<QueryRunner> {
|
|
|
|
const queryRunner = this._conn.createQueryRunner();
|
|
|
|
await queryRunner.connect();
|
|
|
|
await queryRunner.startTransaction();
|
|
|
|
return queryRunner;
|
|
|
|
}
|
|
|
|
|
2021-08-20 13:32:57 +00:00
|
|
|
async getSyncStatus (repo: Repository<SyncStatusInterface>): Promise<SyncStatusInterface | undefined> {
|
|
|
|
return repo.findOne();
|
|
|
|
}
|
|
|
|
|
2021-10-20 10:36:03 +00:00
|
|
|
async updateSyncStatusIndexedBlock (repo: Repository<SyncStatusInterface>, blockHash: string, blockNumber: number, force = false): Promise<SyncStatusInterface> {
|
2021-08-19 07:57:32 +00:00
|
|
|
const entity = await repo.findOne();
|
|
|
|
assert(entity);
|
|
|
|
|
2021-10-20 10:36:03 +00:00
|
|
|
if (force || blockNumber >= entity.latestIndexedBlockNumber) {
|
2021-08-19 07:57:32 +00:00
|
|
|
entity.latestIndexedBlockHash = blockHash;
|
|
|
|
entity.latestIndexedBlockNumber = blockNumber;
|
2021-07-13 06:31:54 +00:00
|
|
|
}
|
|
|
|
|
2021-08-19 07:57:32 +00:00
|
|
|
return await repo.save(entity);
|
|
|
|
}
|
|
|
|
|
2021-10-20 10:36:03 +00:00
|
|
|
async updateSyncStatusCanonicalBlock (repo: Repository<SyncStatusInterface>, blockHash: string, blockNumber: number, force = false): Promise<SyncStatusInterface> {
|
2021-08-19 07:57:32 +00:00
|
|
|
const entity = await repo.findOne();
|
|
|
|
assert(entity);
|
|
|
|
|
2021-10-20 10:36:03 +00:00
|
|
|
if (force || blockNumber >= entity.latestCanonicalBlockNumber) {
|
2021-08-19 07:57:32 +00:00
|
|
|
entity.latestCanonicalBlockHash = blockHash;
|
|
|
|
entity.latestCanonicalBlockNumber = blockNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
return await repo.save(entity);
|
|
|
|
}
|
|
|
|
|
2021-12-17 06:27:09 +00:00
|
|
|
async updateSyncStatusChainHead (repo: Repository<SyncStatusInterface>, blockHash: string, blockNumber: number, force = false): Promise<SyncStatusInterface> {
|
2021-08-19 07:57:32 +00:00
|
|
|
let entity = await repo.findOne();
|
|
|
|
if (!entity) {
|
|
|
|
entity = repo.create({
|
|
|
|
chainHeadBlockHash: blockHash,
|
|
|
|
chainHeadBlockNumber: blockNumber,
|
|
|
|
latestCanonicalBlockHash: blockHash,
|
|
|
|
latestCanonicalBlockNumber: blockNumber,
|
|
|
|
latestIndexedBlockHash: '',
|
|
|
|
latestIndexedBlockNumber: -1
|
|
|
|
});
|
2021-07-13 06:31:54 +00:00
|
|
|
}
|
|
|
|
|
2021-12-17 06:27:09 +00:00
|
|
|
if (force || blockNumber >= entity.chainHeadBlockNumber) {
|
2021-08-19 07:57:32 +00:00
|
|
|
entity.chainHeadBlockHash = blockHash;
|
|
|
|
entity.chainHeadBlockNumber = blockNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
return await repo.save(entity);
|
|
|
|
}
|
|
|
|
|
2021-08-20 13:32:57 +00:00
|
|
|
async getBlockProgress (repo: Repository<BlockProgressInterface>, blockHash: string): Promise<BlockProgressInterface | undefined> {
|
|
|
|
return repo.findOne({ where: { blockHash } });
|
|
|
|
}
|
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
async getBlockProgressEntities (repo: Repository<BlockProgressInterface>, where: FindConditions<BlockProgressInterface>, options: FindManyOptions<BlockProgressInterface>): Promise<BlockProgressInterface[]> {
|
|
|
|
options.where = where;
|
|
|
|
|
|
|
|
return repo.find(options);
|
|
|
|
}
|
|
|
|
|
2021-08-19 07:57:32 +00:00
|
|
|
async getBlocksAtHeight (repo: Repository<BlockProgressInterface>, height: number, isPruned: boolean): Promise<BlockProgressInterface[]> {
|
|
|
|
return repo.createQueryBuilder('block_progress')
|
|
|
|
.where('block_number = :height AND is_pruned = :isPruned', { height, isPruned })
|
|
|
|
.getMany();
|
|
|
|
}
|
|
|
|
|
2021-12-10 05:14:10 +00:00
|
|
|
async updateBlockProgress (repo: Repository<BlockProgressInterface>, block: BlockProgressInterface, lastProcessedEventIndex: number): Promise<BlockProgressInterface> {
|
2021-12-08 05:41:29 +00:00
|
|
|
if (!block.isComplete) {
|
|
|
|
if (lastProcessedEventIndex <= block.lastProcessedEventIndex) {
|
|
|
|
throw new Error(`Events processed out of order ${block.blockHash}, was ${block.lastProcessedEventIndex}, got ${lastProcessedEventIndex}`);
|
2021-08-20 13:32:57 +00:00
|
|
|
}
|
|
|
|
|
2021-12-08 05:41:29 +00:00
|
|
|
block.lastProcessedEventIndex = lastProcessedEventIndex;
|
|
|
|
block.numProcessedEvents++;
|
|
|
|
if (block.numProcessedEvents >= block.numEvents) {
|
|
|
|
block.isComplete = true;
|
2021-08-20 13:32:57 +00:00
|
|
|
}
|
|
|
|
|
2021-12-10 05:14:10 +00:00
|
|
|
const { generatedMaps } = await repo.createQueryBuilder()
|
2021-12-13 10:08:34 +00:00
|
|
|
.update()
|
2021-12-10 05:14:10 +00:00
|
|
|
.set(block)
|
|
|
|
.where('id = :id', { id: block.id })
|
|
|
|
.whereEntity(block)
|
|
|
|
.returning('*')
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
block = generatedMaps[0] as BlockProgressInterface;
|
2021-08-20 13:32:57 +00:00
|
|
|
}
|
2021-12-10 05:14:10 +00:00
|
|
|
|
|
|
|
return block;
|
2021-08-20 13:32:57 +00:00
|
|
|
}
|
|
|
|
|
2021-08-23 11:36:35 +00:00
|
|
|
async markBlocksAsPruned (repo: Repository<BlockProgressInterface>, blocks: BlockProgressInterface[]): Promise<void> {
|
|
|
|
const ids = blocks.map(({ id }) => id);
|
|
|
|
|
|
|
|
await repo.update({ id: In(ids) }, { isPruned: true });
|
2021-07-13 06:31:54 +00:00
|
|
|
}
|
2021-08-20 06:56:37 +00:00
|
|
|
|
2021-08-20 13:32:57 +00:00
|
|
|
async getEvent (repo: Repository<EventInterface>, id: string): Promise<EventInterface | undefined> {
|
|
|
|
return repo.findOne(id, { relations: ['block'] });
|
|
|
|
}
|
2021-08-20 06:56:37 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
async getBlockEvents (repo: Repository<EventInterface>, blockHash: string, where: Where = {}, queryOptions: QueryOptions = {}): Promise<EventInterface[]> {
|
|
|
|
let queryBuilder = repo.createQueryBuilder('event')
|
|
|
|
.innerJoinAndSelect('event.block', 'block')
|
|
|
|
.where('block.block_hash = :blockHash AND block.is_pruned = false', { blockHash });
|
2021-09-21 11:13:55 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
queryBuilder = this._buildQuery(repo, queryBuilder, where, queryOptions);
|
|
|
|
queryBuilder.addOrderBy('event.id', 'ASC');
|
2021-12-10 05:14:10 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
const { limit = DEFAULT_LIMIT, skip = DEFAULT_SKIP } = queryOptions;
|
2021-12-10 05:14:10 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
queryBuilder = queryBuilder.offset(skip)
|
|
|
|
.limit(limit);
|
2021-12-10 05:14:10 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
return queryBuilder.getMany();
|
2021-08-20 06:56:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
async saveEvents (blockRepo: Repository<BlockProgressInterface>, eventRepo: Repository<EventInterface>, block: DeepPartial<BlockProgressInterface>, events: DeepPartial<EventInterface>[]): Promise<BlockProgressInterface> {
|
2021-08-20 13:32:57 +00:00
|
|
|
const {
|
2021-10-12 10:32:56 +00:00
|
|
|
cid,
|
2021-08-20 13:32:57 +00:00
|
|
|
blockHash,
|
|
|
|
blockNumber,
|
|
|
|
blockTimestamp,
|
|
|
|
parentHash
|
|
|
|
} = block;
|
|
|
|
|
|
|
|
assert(blockHash);
|
2021-09-09 05:21:58 +00:00
|
|
|
assert(blockNumber !== undefined);
|
|
|
|
assert(blockNumber > -1);
|
|
|
|
assert(blockTimestamp !== undefined);
|
|
|
|
assert(blockTimestamp > -1);
|
2021-08-20 13:32:57 +00:00
|
|
|
|
|
|
|
// In a transaction:
|
|
|
|
// (1) Save all the events in the database.
|
|
|
|
// (2) Add an entry to the block progress table.
|
|
|
|
const numEvents = events.length;
|
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
const entity = blockRepo.create({
|
2021-10-12 10:32:56 +00:00
|
|
|
cid,
|
2021-12-13 10:08:34 +00:00
|
|
|
blockHash,
|
|
|
|
parentHash,
|
|
|
|
blockNumber,
|
|
|
|
blockTimestamp,
|
|
|
|
numEvents,
|
|
|
|
numProcessedEvents: 0,
|
|
|
|
lastProcessedEventIndex: -1,
|
|
|
|
isComplete: (numEvents === 0)
|
|
|
|
});
|
2021-08-20 13:32:57 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
const blockProgress = await blockRepo.save(entity);
|
2021-08-20 13:32:57 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
// Bulk insert events.
|
|
|
|
events.forEach(event => {
|
|
|
|
event.block = blockProgress;
|
|
|
|
});
|
|
|
|
|
2021-12-16 11:46:48 +00:00
|
|
|
const eventBatches = _.chunk(events, INSERT_EVENTS_BATCH);
|
|
|
|
|
|
|
|
const insertPromises = eventBatches.map(async events => {
|
|
|
|
await eventRepo.createQueryBuilder()
|
|
|
|
.insert()
|
|
|
|
.values(events)
|
|
|
|
.updateEntity(false)
|
|
|
|
.execute();
|
|
|
|
});
|
|
|
|
|
|
|
|
await Promise.all(insertPromises);
|
2021-12-13 10:08:34 +00:00
|
|
|
|
|
|
|
return blockProgress;
|
2021-08-20 13:32:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindConditions<Entity>): Promise<Entity[]> {
|
2021-08-20 06:56:37 +00:00
|
|
|
const repo = queryRunner.manager.getRepository(entity);
|
|
|
|
|
|
|
|
const entities = await repo.find(findConditions);
|
2021-08-20 13:32:57 +00:00
|
|
|
return entities;
|
2021-08-20 06:56:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async isEntityEmpty<Entity> (entity: new () => Entity): Promise<boolean> {
|
2021-08-20 13:32:57 +00:00
|
|
|
const queryRunner = this._conn.createQueryRunner();
|
|
|
|
|
2021-08-20 06:56:37 +00:00
|
|
|
try {
|
2021-08-20 13:32:57 +00:00
|
|
|
await queryRunner.connect();
|
|
|
|
const data = await this.getEntities(queryRunner, entity);
|
2021-08-20 06:56:37 +00:00
|
|
|
|
|
|
|
if (data.length > 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-08-20 13:32:57 +00:00
|
|
|
|
2021-08-20 06:56:37 +00:00
|
|
|
return true;
|
|
|
|
} finally {
|
2021-08-20 13:32:57 +00:00
|
|
|
await queryRunner.release();
|
2021-08-20 06:56:37 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-20 13:32:57 +00:00
|
|
|
|
2021-10-20 12:19:44 +00:00
|
|
|
async removeEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindManyOptions<Entity> | FindConditions<Entity>): Promise<void> {
|
2021-08-20 13:32:57 +00:00
|
|
|
const repo = queryRunner.manager.getRepository(entity);
|
|
|
|
|
|
|
|
const entities = await repo.find(findConditions);
|
|
|
|
await repo.remove(entities);
|
|
|
|
}
|
2021-08-23 11:36:35 +00:00
|
|
|
|
|
|
|
async getAncestorAtDepth (blockHash: string, depth: number): Promise<string> {
|
|
|
|
const heirerchicalQuery = `
|
|
|
|
WITH RECURSIVE cte_query AS
|
|
|
|
(
|
|
|
|
SELECT
|
|
|
|
block_hash,
|
|
|
|
block_number,
|
|
|
|
parent_hash,
|
|
|
|
0 as depth
|
|
|
|
FROM
|
|
|
|
block_progress
|
|
|
|
WHERE
|
|
|
|
block_hash = $1
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
b.block_hash,
|
|
|
|
b.block_number,
|
|
|
|
b.parent_hash,
|
|
|
|
c.depth + 1
|
|
|
|
FROM
|
|
|
|
block_progress b
|
|
|
|
INNER JOIN
|
|
|
|
cte_query c ON c.parent_hash = b.block_hash
|
|
|
|
WHERE
|
|
|
|
c.depth < $2
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
block_hash, block_number
|
|
|
|
FROM
|
|
|
|
cte_query
|
|
|
|
ORDER BY block_number ASC
|
|
|
|
LIMIT 1;
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Get ancestor block hash using heirarchical query.
|
|
|
|
const [{ block_hash: ancestorBlockHash }] = await this._conn.query(heirerchicalQuery, [blockHash, depth]);
|
|
|
|
|
|
|
|
return ancestorBlockHash;
|
|
|
|
}
|
2021-08-24 06:25:29 +00:00
|
|
|
|
|
|
|
async getProcessedBlockCountForRange (repo: Repository<BlockProgressInterface>, fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> {
|
|
|
|
const blockNumbers = _.range(fromBlockNumber, toBlockNumber + 1);
|
|
|
|
const expected = blockNumbers.length;
|
|
|
|
|
|
|
|
const { count: actual } = await repo
|
|
|
|
.createQueryBuilder('block_progress')
|
|
|
|
.select('COUNT(DISTINCT(block_number))', 'count')
|
|
|
|
.where('block_number IN (:...blockNumbers) AND is_complete = :isComplete', { blockNumbers, isComplete: true })
|
|
|
|
.getRawOne();
|
|
|
|
|
|
|
|
return { expected, actual: parseInt(actual) };
|
|
|
|
}
|
|
|
|
|
|
|
|
async getEventsInRange (repo: Repository<EventInterface>, fromBlockNumber: number, toBlockNumber: number): Promise<Array<EventInterface>> {
|
|
|
|
const events = repo.createQueryBuilder('event')
|
|
|
|
.innerJoinAndSelect('event.block', 'block')
|
2021-08-25 07:17:53 +00:00
|
|
|
.where('block_number >= :fromBlockNumber AND block_number <= :toBlockNumber AND event_name <> :eventName AND is_pruned = false', {
|
2021-08-24 06:25:29 +00:00
|
|
|
fromBlockNumber,
|
|
|
|
toBlockNumber,
|
|
|
|
eventName: UNKNOWN_EVENT_NAME
|
|
|
|
})
|
|
|
|
.addOrderBy('event.id', 'ASC')
|
|
|
|
.getMany();
|
|
|
|
|
|
|
|
return events;
|
|
|
|
}
|
|
|
|
|
|
|
|
async saveEventEntity (repo: Repository<EventInterface>, entity: EventInterface): Promise<EventInterface> {
|
|
|
|
return await repo.save(entity);
|
|
|
|
}
|
2021-08-25 07:17:53 +00:00
|
|
|
|
2021-08-26 10:06:16 +00:00
|
|
|
async getModelEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: Relation[] = []): Promise<Entity[]> {
|
2021-08-25 07:17:53 +00:00
|
|
|
const repo = queryRunner.manager.getRepository(entity);
|
|
|
|
const { tableName } = repo.metadata;
|
|
|
|
|
|
|
|
let subQuery = repo.createQueryBuilder('subTable')
|
|
|
|
.select('MAX(subTable.block_number)')
|
|
|
|
.where(`subTable.id = ${tableName}.id`);
|
|
|
|
|
|
|
|
if (block.hash) {
|
|
|
|
const { canonicalBlockNumber, blockHashes } = await this.getFrothyRegion(queryRunner, block.hash);
|
|
|
|
|
|
|
|
subQuery = subQuery
|
|
|
|
.andWhere(new Brackets(qb => {
|
|
|
|
qb.where('subTable.block_hash IN (:...blockHashes)', { blockHashes })
|
|
|
|
.orWhere('subTable.block_number <= :canonicalBlockNumber', { canonicalBlockNumber });
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (block.number) {
|
|
|
|
subQuery = subQuery.andWhere('subTable.block_number <= :blockNumber', { blockNumber: block.number });
|
|
|
|
}
|
|
|
|
|
|
|
|
let selectQueryBuilder = repo.createQueryBuilder(tableName)
|
|
|
|
.where(`${tableName}.block_number IN (${subQuery.getQuery()})`)
|
|
|
|
.setParameters(subQuery.getParameters());
|
|
|
|
|
|
|
|
relations.forEach(relation => {
|
2021-08-26 10:06:16 +00:00
|
|
|
let alias, property;
|
|
|
|
|
|
|
|
if (typeof relation === 'string') {
|
|
|
|
[, alias] = relation.split('.');
|
|
|
|
property = relation;
|
|
|
|
} else {
|
|
|
|
alias = relation.alias;
|
|
|
|
property = relation.property;
|
|
|
|
}
|
|
|
|
|
|
|
|
selectQueryBuilder = selectQueryBuilder.leftJoinAndSelect(property, alias);
|
2021-08-25 07:17:53 +00:00
|
|
|
});
|
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
selectQueryBuilder = this._buildQuery(repo, selectQueryBuilder, where, queryOptions);
|
2021-08-25 07:17:53 +00:00
|
|
|
|
2021-12-13 10:08:34 +00:00
|
|
|
const { limit = DEFAULT_LIMIT, skip = DEFAULT_SKIP } = queryOptions;
|
2021-08-25 07:17:53 +00:00
|
|
|
|
|
|
|
selectQueryBuilder = selectQueryBuilder.skip(skip)
|
|
|
|
.take(limit);
|
|
|
|
|
|
|
|
return selectQueryBuilder.getMany();
|
|
|
|
}
|
|
|
|
|
|
|
|
async getPrevEntityVersion<Entity> (queryRunner: QueryRunner, repo: Repository<Entity>, findOptions: { [key: string]: any }): Promise<Entity | undefined> {
|
|
|
|
// Hierarchical query for getting the entity in the frothy region.
|
|
|
|
const heirerchicalQuery = `
|
|
|
|
WITH RECURSIVE cte_query AS
|
|
|
|
(
|
|
|
|
SELECT
|
|
|
|
b.block_hash,
|
|
|
|
b.block_number,
|
|
|
|
b.parent_hash,
|
|
|
|
1 as depth,
|
|
|
|
e.id
|
|
|
|
FROM
|
|
|
|
block_progress b
|
|
|
|
LEFT JOIN
|
2021-12-16 11:46:48 +00:00
|
|
|
${repo.metadata.tableName} e
|
|
|
|
ON e.block_hash = b.block_hash
|
|
|
|
AND e.id = $2
|
2021-08-25 07:17:53 +00:00
|
|
|
WHERE
|
|
|
|
b.block_hash = $1
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
b.block_hash,
|
|
|
|
b.block_number,
|
|
|
|
b.parent_hash,
|
|
|
|
c.depth + 1,
|
|
|
|
e.id
|
|
|
|
FROM
|
|
|
|
block_progress b
|
|
|
|
LEFT JOIN
|
|
|
|
${repo.metadata.tableName} e
|
|
|
|
ON e.block_hash = b.block_hash
|
|
|
|
AND e.id = $2
|
|
|
|
INNER JOIN
|
|
|
|
cte_query c ON c.parent_hash = b.block_hash
|
|
|
|
WHERE
|
|
|
|
c.id IS NULL AND c.depth < $3
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
block_hash, block_number, id
|
|
|
|
FROM
|
|
|
|
cte_query
|
|
|
|
ORDER BY block_number ASC
|
|
|
|
LIMIT 1;
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Fetching blockHash for previous entity in frothy region.
|
|
|
|
const [{ block_hash: blockHash, block_number: blockNumber, id }] = await queryRunner.query(heirerchicalQuery, [findOptions.where.blockHash, findOptions.where.id, MAX_REORG_DEPTH]);
|
|
|
|
|
|
|
|
if (id) {
|
|
|
|
// Entity found in frothy region.
|
|
|
|
findOptions.where.blockHash = blockHash;
|
|
|
|
} else {
|
|
|
|
// If entity not found in frothy region get latest entity in the pruned region.
|
|
|
|
// Filter out entities from pruned blocks.
|
|
|
|
const canonicalBlockNumber = blockNumber + 1;
|
2021-10-12 10:32:56 +00:00
|
|
|
|
2021-08-25 07:17:53 +00:00
|
|
|
const entityInPrunedRegion:any = await repo.createQueryBuilder('entity')
|
|
|
|
.innerJoinAndSelect('block_progress', 'block', 'block.block_hash = entity.block_hash')
|
|
|
|
.where('block.is_pruned = false')
|
|
|
|
.andWhere('entity.id = :id', { id: findOptions.where.id })
|
|
|
|
.andWhere('entity.block_number <= :canonicalBlockNumber', { canonicalBlockNumber })
|
|
|
|
.orderBy('entity.block_number', 'DESC')
|
|
|
|
.limit(1)
|
|
|
|
.getOne();
|
|
|
|
|
|
|
|
findOptions.where.blockHash = entityInPrunedRegion?.blockHash;
|
|
|
|
}
|
|
|
|
|
|
|
|
return repo.findOne(findOptions);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getFrothyRegion (queryRunner: QueryRunner, blockHash: string): Promise<{ canonicalBlockNumber: number, blockHashes: string[] }> {
|
|
|
|
const heirerchicalQuery = `
|
|
|
|
WITH RECURSIVE cte_query AS
|
|
|
|
(
|
|
|
|
SELECT
|
|
|
|
block_hash,
|
|
|
|
block_number,
|
|
|
|
parent_hash,
|
|
|
|
1 as depth
|
|
|
|
FROM
|
|
|
|
block_progress
|
|
|
|
WHERE
|
|
|
|
block_hash = $1
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
b.block_hash,
|
|
|
|
b.block_number,
|
|
|
|
b.parent_hash,
|
|
|
|
c.depth + 1
|
|
|
|
FROM
|
|
|
|
block_progress b
|
|
|
|
INNER JOIN
|
|
|
|
cte_query c ON c.parent_hash = b.block_hash
|
|
|
|
WHERE
|
|
|
|
c.depth < $2
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
block_hash, block_number
|
|
|
|
FROM
|
|
|
|
cte_query;
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Get blocks in the frothy region using heirarchical query.
|
|
|
|
const blocks = await queryRunner.query(heirerchicalQuery, [blockHash, MAX_REORG_DEPTH]);
|
|
|
|
const blockHashes = blocks.map(({ block_hash: blockHash }: any) => blockHash);
|
|
|
|
|
|
|
|
// Canonical block is the block after the last block in frothy region.
|
|
|
|
const canonicalBlockNumber = blocks[blocks.length - 1].block_number + 1;
|
|
|
|
|
|
|
|
return { canonicalBlockNumber, blockHashes };
|
|
|
|
}
|
|
|
|
|
2021-12-08 05:41:29 +00:00
|
|
|
async getContracts (repo: Repository<ContractInterface>): Promise<ContractInterface[]> {
|
2021-09-21 11:13:55 +00:00
|
|
|
return repo.createQueryBuilder('contract')
|
2021-12-08 05:41:29 +00:00
|
|
|
.getMany();
|
2021-09-21 11:13:55 +00:00
|
|
|
}
|
|
|
|
|
2021-10-20 12:25:54 +00:00
|
|
|
async saveContract (repo: Repository<ContractInterface>, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<ContractInterface> {
|
2021-12-08 05:41:29 +00:00
|
|
|
const contract = await repo
|
2021-08-25 07:17:53 +00:00
|
|
|
.createQueryBuilder()
|
|
|
|
.where('address = :address', { address })
|
2021-12-08 05:41:29 +00:00
|
|
|
.getOne();
|
|
|
|
|
2021-10-20 12:25:54 +00:00
|
|
|
const entity = repo.create({ address, kind, checkpoint, startingBlock });
|
2021-08-25 07:17:53 +00:00
|
|
|
|
2021-12-08 05:41:29 +00:00
|
|
|
// If contract already present, overwrite fields.
|
|
|
|
if (contract) {
|
|
|
|
entity.id = contract.id;
|
2021-08-25 07:17:53 +00:00
|
|
|
}
|
2021-12-08 05:41:29 +00:00
|
|
|
|
|
|
|
return repo.save(entity);
|
2021-08-25 07:17:53 +00:00
|
|
|
}
|
2021-12-13 10:08:34 +00:00
|
|
|
|
|
|
|
_buildQuery<Entity> (repo: Repository<Entity>, selectQueryBuilder: SelectQueryBuilder<Entity>, where: Where = {}, queryOptions: QueryOptions = {}): SelectQueryBuilder<Entity> {
|
|
|
|
const { tableName } = repo.metadata;
|
|
|
|
|
|
|
|
Object.entries(where).forEach(([field, filters]) => {
|
|
|
|
filters.forEach((filter, index) => {
|
|
|
|
// Form the where clause.
|
|
|
|
let { not, operator, value } = filter;
|
|
|
|
const columnMetadata = repo.metadata.findColumnWithPropertyName(field);
|
|
|
|
assert(columnMetadata);
|
|
|
|
let whereClause = `${tableName}.${columnMetadata.propertyAliasName} `;
|
|
|
|
|
|
|
|
if (not) {
|
|
|
|
if (operator === 'equals') {
|
|
|
|
whereClause += '!';
|
|
|
|
} else {
|
|
|
|
whereClause += 'NOT ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
whereClause += `${OPERATOR_MAP[operator]} `;
|
|
|
|
|
|
|
|
if (['contains', 'starts'].some(el => el === operator)) {
|
|
|
|
whereClause += '%:';
|
|
|
|
} else if (operator === 'in') {
|
|
|
|
whereClause += '(:...';
|
|
|
|
} else {
|
|
|
|
// Convert to string type value as bigint type throws error in query.
|
|
|
|
value = value.toString();
|
|
|
|
|
|
|
|
whereClause += ':';
|
|
|
|
}
|
|
|
|
|
|
|
|
const variableName = `${field}${index}`;
|
|
|
|
whereClause += variableName;
|
|
|
|
|
|
|
|
if (['contains', 'ends'].some(el => el === operator)) {
|
|
|
|
whereClause += '%';
|
|
|
|
} else if (operator === 'in') {
|
|
|
|
whereClause += ')';
|
|
|
|
|
|
|
|
if (!value.length) {
|
|
|
|
whereClause = 'FALSE';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
selectQueryBuilder = selectQueryBuilder.andWhere(whereClause, { [variableName]: value });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const { orderBy, orderDirection } = queryOptions;
|
|
|
|
|
|
|
|
if (orderBy) {
|
|
|
|
const columnMetadata = repo.metadata.findColumnWithPropertyName(orderBy);
|
|
|
|
assert(columnMetadata);
|
|
|
|
selectQueryBuilder = selectQueryBuilder.orderBy(`${tableName}.${columnMetadata.propertyAliasName}`, orderDirection === 'desc' ? 'DESC' : 'ASC');
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectQueryBuilder;
|
|
|
|
}
|
2021-08-19 07:57:32 +00:00
|
|
|
}
|