diff --git a/src/checkpoint.ts b/src/checkpoint.ts index ae60c94..d69d931 100644 --- a/src/checkpoint.ts +++ b/src/checkpoint.ts @@ -17,6 +17,7 @@ import { sleep } from './utils/helpers'; import { ContractSourceConfig, CheckpointConfig, CheckpointOptions, TemplateSource } from './types'; import { getTableName } from './utils/database'; +const INDEXER_NAME = 'default'; const SCHEMA_VERSION = 1; const BLOCK_PRELOAD_START_RANGE = 1000; @@ -162,7 +163,7 @@ export default class Checkpoint { await this.validateStore(); await this.indexer.getProvider().init(); - const templateSources = await this.store.getTemplateSources(); + const templateSources = await this.store.getTemplateSources(INDEXER_NAME); await Promise.all( templateSources.map(source => this.executeTemplate( @@ -200,9 +201,9 @@ export default class Checkpoint { this.log.debug('reset'); await this.store.createStore(); - await this.store.setMetadata(MetadataId.LastIndexedBlock, 0); - await this.store.setMetadata(MetadataId.SchemaVersion, SCHEMA_VERSION); - await this.store.removeBlocks(); + await this.store.setMetadata(INDEXER_NAME, MetadataId.LastIndexedBlock, 0); + await this.store.setMetadata(INDEXER_NAME, MetadataId.SchemaVersion, SCHEMA_VERSION); + await this.store.removeBlocks(INDEXER_NAME); await this.entityController.createEntityStores(this.knex); } @@ -216,7 +217,7 @@ export default class Checkpoint { */ public async resetMetadata() { await this.store.resetStore(); - await this.store.setMetadata(MetadataId.SchemaVersion, SCHEMA_VERSION); + await this.store.setMetadata(INDEXER_NAME, MetadataId.SchemaVersion, SCHEMA_VERSION); } private addSource(source: ContractSourceConfig) { @@ -249,7 +250,7 @@ export default class Checkpoint { this.activeTemplates.push({ template: name, contractAddress: contract, startBlock: start }); if (persist) { - await this.store.insertTemplateSource(contract, start, name); + await this.store.insertTemplateSource(INDEXER_NAME, contract, start, name); } this.addSource({ @@ -287,21 +288,21 @@ export default class Checkpoint { }); }); - await this.store.insertCheckpoints(checkpoints); + await this.store.insertCheckpoints(INDEXER_NAME, checkpoints); } public async setBlockHash(blockNum: number, hash: string) { this.blockHashCache = { blockNumber: blockNum, hash }; - return this.store.setBlockHash(blockNum, hash); + return this.store.setBlockHash(INDEXER_NAME, blockNum, hash); } public async setLastIndexedBlock(block: number) { - await this.store.setMetadata(MetadataId.LastIndexedBlock, block); + await this.store.setMetadata(INDEXER_NAME, MetadataId.LastIndexedBlock, block); } public async insertCheckpoints(checkpoints: CheckpointRecord[]) { - await this.store.insertCheckpoints(checkpoints); + await this.store.insertCheckpoints(INDEXER_NAME, checkpoints); } public async getWriterParams(): Promise<{ @@ -322,7 +323,8 @@ export default class Checkpoint { private async getStartBlockNum() { const start = this.getConfigStartBlock(); - const lastBlock = (await this.store.getMetadataNumber(MetadataId.LastIndexedBlock)) ?? 0; + const lastBlock = + (await this.store.getMetadataNumber(INDEXER_NAME, MetadataId.LastIndexedBlock)) ?? 0; const nextBlock = lastBlock + 1; @@ -426,7 +428,7 @@ export default class Checkpoint { let current = blockNumber - 1; let lastGoodBlock: null | number = null; while (lastGoodBlock === null) { - const storedBlockHash = await this.store.getBlockHash(current); + const storedBlockHash = await this.store.getBlockHash(INDEXER_NAME, current); const currentBlockHash = await this.indexer.getProvider().getBlockHash(current); if (storedBlockHash === null || storedBlockHash === currentBlockHash) { @@ -453,7 +455,7 @@ export default class Checkpoint { }); // TODO: when we have full transaction support, we should include this in the transaction - await this.store.removeFutureData(lastGoodBlock); + await this.store.removeFutureData(INDEXER_NAME, lastGoodBlock); this.cpBlocksCache = null; this.blockHashCache = null; @@ -469,6 +471,7 @@ export default class Checkpoint { } const checkpointBlocks = await this.store.getNextCheckpointBlocks( + INDEXER_NAME, blockNum, this.sourceContracts, 15 @@ -485,7 +488,7 @@ export default class Checkpoint { return this.blockHashCache.hash; } - return this.store.getBlockHash(blockNumber); + return this.store.getBlockHash(INDEXER_NAME, blockNumber); } private get store(): CheckpointsStore { @@ -542,10 +545,22 @@ export default class Checkpoint { const networkIdentifier = await this.indexer.getProvider().getNetworkIdentifier(); const configChecksum = getConfigChecksum(this.config); - const storedNetworkIdentifier = await this.store.getMetadata(MetadataId.NetworkIdentifier); - const storedStartBlock = await this.store.getMetadataNumber(MetadataId.StartBlock); - const storedConfigChecksum = await this.store.getMetadata(MetadataId.ConfigChecksum); - const storedSchemaVersion = await this.store.getMetadataNumber(MetadataId.SchemaVersion); + const storedNetworkIdentifier = await this.store.getMetadata( + INDEXER_NAME, + MetadataId.NetworkIdentifier + ); + const storedStartBlock = await this.store.getMetadataNumber( + INDEXER_NAME, + MetadataId.StartBlock + ); + const storedConfigChecksum = await this.store.getMetadata( + INDEXER_NAME, + MetadataId.ConfigChecksum + ); + const storedSchemaVersion = await this.store.getMetadataNumber( + INDEXER_NAME, + MetadataId.SchemaVersion + ); const hasNetworkChanged = storedNetworkIdentifier && storedNetworkIdentifier !== networkIdentifier; const hasStartBlockChanged = @@ -560,9 +575,9 @@ export default class Checkpoint { await this.resetMetadata(); await this.reset(); - await this.store.setMetadata(MetadataId.NetworkIdentifier, networkIdentifier); - await this.store.setMetadata(MetadataId.StartBlock, this.getConfigStartBlock()); - await this.store.setMetadata(MetadataId.ConfigChecksum, configChecksum); + await this.store.setMetadata(INDEXER_NAME, MetadataId.NetworkIdentifier, networkIdentifier); + await this.store.setMetadata(INDEXER_NAME, MetadataId.StartBlock, this.getConfigStartBlock()); + await this.store.setMetadata(INDEXER_NAME, MetadataId.ConfigChecksum, configChecksum); } else if (hasNetworkChanged) { this.log.error( `network identifier changed from ${storedNetworkIdentifier} to ${networkIdentifier}. @@ -597,15 +612,19 @@ export default class Checkpoint { throw new Error('schema changed'); } else { if (!storedNetworkIdentifier) { - await this.store.setMetadata(MetadataId.NetworkIdentifier, networkIdentifier); + await this.store.setMetadata(INDEXER_NAME, MetadataId.NetworkIdentifier, networkIdentifier); } if (!storedStartBlock) { - await this.store.setMetadata(MetadataId.StartBlock, this.getConfigStartBlock()); + await this.store.setMetadata( + INDEXER_NAME, + MetadataId.StartBlock, + this.getConfigStartBlock() + ); } if (!storedConfigChecksum) { - await this.store.setMetadata(MetadataId.ConfigChecksum, configChecksum); + await this.store.setMetadata(INDEXER_NAME, MetadataId.ConfigChecksum, configChecksum); } } } diff --git a/src/stores/checkpoints.ts b/src/stores/checkpoints.ts index 38b8af2..b8dff69 100644 --- a/src/stores/checkpoints.ts +++ b/src/stores/checkpoints.ts @@ -4,29 +4,32 @@ import { Logger } from '../utils/logger'; import { chunk } from '../utils/helpers'; import { TemplateSource } from '../types'; -const Table = { +export const Table = { Blocks: '_blocks', Checkpoints: '_checkpoints', Metadata: '_metadatas', // using plural names to conform with standards entities, TemplateSources: '_template_sources' }; -const Fields = { +export const Fields = { Blocks: { + Indexer: 'indexer', Number: 'block_number', Hash: 'hash' }, Checkpoints: { Id: 'id', + Indexer: 'indexer', BlockNumber: 'block_number', ContractAddress: 'contract_address' }, Metadata: { Id: 'id', + Indexer: 'indexer', Value: 'value' }, TemplateSources: { - Id: 'id', + Indexer: 'indexer', ContractAddress: 'contract_address', StartBlock: 'start_block', Template: 'template' @@ -98,29 +101,35 @@ export class CheckpointsStore { if (!hasBlocksTable) { builder = builder.createTable(Table.Blocks, t => { - t.bigint(Fields.Blocks.Number).primary(); - t.string(Fields.Blocks.Hash).notNullable().unique(); + t.string(Fields.Blocks.Indexer).notNullable(); + t.bigint(Fields.Blocks.Number); + t.string(Fields.Blocks.Hash).notNullable(); + t.primary([Fields.Blocks.Indexer, Fields.Blocks.Number]); }); } if (!hasCheckpointsTable) { builder = builder.createTable(Table.Checkpoints, t => { - t.string(Fields.Checkpoints.Id, CheckpointIdSize).primary(); + t.string(Fields.Checkpoints.Id, CheckpointIdSize); + t.string(Fields.Checkpoints.Indexer).notNullable(); t.bigint(Fields.Checkpoints.BlockNumber).notNullable().index(); t.string(Fields.Checkpoints.ContractAddress, 66).notNullable().index(); + t.primary([Fields.Checkpoints.Id, Fields.Checkpoints.Indexer]); }); } if (!hasMetadataTable) { builder = builder.createTable(Table.Metadata, t => { - t.string(Fields.Metadata.Id, 20).primary(); + t.string(Fields.Metadata.Id, 20); + t.string(Fields.Metadata.Indexer).notNullable(); t.string(Fields.Metadata.Value, 128).notNullable(); + t.primary([Fields.Metadata.Id, Fields.Metadata.Indexer]); }); } if (!hasTemplateSourcesTable) { builder = builder.createTable(Table.TemplateSources, t => { - t.increments(Fields.TemplateSources.Id); + t.string(Fields.TemplateSources.Indexer).notNullable(); t.string(Fields.TemplateSources.ContractAddress, 66); t.bigint(Fields.TemplateSources.StartBlock).notNullable(); t.string(Fields.TemplateSources.Template, 128).notNullable(); @@ -170,14 +179,41 @@ export class CheckpointsStore { await this.createStore(); } - public async removeBlocks(): Promise { - return this.knex(Table.Blocks).del(); + public async removeFutureData(indexer: string, blockNumber: number): Promise { + return this.knex.transaction(async trx => { + await trx + .table(Table.Metadata) + .insert({ + [Fields.Metadata.Id]: MetadataId.LastIndexedBlock, + [Fields.Metadata.Indexer]: indexer, + [Fields.Metadata.Value]: blockNumber + }) + .onConflict([Fields.Metadata.Id, Fields.Metadata.Indexer]) + .merge(); + + await trx + .table(Table.Checkpoints) + .where(Fields.Checkpoints.Indexer, indexer) + .where(Fields.Checkpoints.BlockNumber, '>', blockNumber) + .del(); + + await trx.table(Table.Blocks).where(Fields.Blocks.Number, '>', blockNumber).del(); + }); } - public async getBlockHash(blockNumber: number): Promise { + public async setBlockHash(indexer: string, blockNumber: number, hash: string): Promise { + await this.knex.table(Table.Blocks).insert({ + [Fields.Blocks.Indexer]: indexer, + [Fields.Blocks.Number]: blockNumber, + [Fields.Blocks.Hash]: hash + }); + } + + public async getBlockHash(indxer: string, blockNumber: number): Promise { const blocks = await this.knex .select(Fields.Blocks.Hash) .from(Table.Blocks) + .where(Fields.Blocks.Indexer, indxer) .where(Fields.Blocks.Number, blockNumber) .limit(1); @@ -188,18 +224,28 @@ export class CheckpointsStore { return blocks[0][Fields.Blocks.Hash]; } - public async setBlockHash(blockNumber: number, hash: string): Promise { - await this.knex.table(Table.Blocks).insert({ - [Fields.Blocks.Number]: blockNumber, - [Fields.Blocks.Hash]: hash - }); + public async removeBlocks(indexer: string): Promise { + return this.knex(Table.Blocks).where(Fields.Blocks.Indexer, indexer).del(); + } + + public async setMetadata(indexer: string, id: string, value: ToString): Promise { + await this.knex + .table(Table.Metadata) + .insert({ + [Fields.Metadata.Id]: id, + [Fields.Metadata.Indexer]: indexer, + [Fields.Metadata.Value]: value + }) + .onConflict([Fields.Metadata.Id, Fields.Metadata.Indexer]) + .merge(); } - public async getMetadata(id: string): Promise { + public async getMetadata(indexer: string, id: string): Promise { const value = await this.knex .select(Fields.Metadata.Value) .from(Table.Metadata) .where(Fields.Metadata.Id, id) + .where(Fields.Metadata.Indexer, indexer) .limit(1); if (value.length == 0) { @@ -209,33 +255,16 @@ export class CheckpointsStore { return value[0][Fields.Metadata.Value]; } - public async getMetadataNumber(id: string, base = 10): Promise { - const strValue = await this.getMetadata(id); - if (!strValue) { - return undefined; - } + public async getMetadataNumber(indexer: string, id: string, base = 10): Promise { + const strValue = await this.getMetadata(indexer, id); + if (strValue === null) return null; return parseInt(strValue, base); } - public async setMetadata(id: string, value: ToString): Promise { - await this.knex - .table(Table.Metadata) - .insert({ - [Fields.Metadata.Id]: id, - [Fields.Metadata.Value]: value - }) - .onConflict(Fields.Metadata.Id) - .merge(); - } - - public async insertCheckpoints(checkpoints: CheckpointRecord[]): Promise { + public async insertCheckpoints(indexer: string, checkpoints: CheckpointRecord[]): Promise { const insert = async (items: CheckpointRecord[]) => { try { - if (items.length === 0) { - return; - } - await this.knex .table(Table.Checkpoints) .insert( @@ -244,17 +273,18 @@ export class CheckpointsStore { return { [Fields.Checkpoints.Id]: id, + [Fields.Checkpoints.Indexer]: indexer, [Fields.Checkpoints.BlockNumber]: checkpoint.blockNumber, [Fields.Checkpoints.ContractAddress]: checkpoint.contractAddress }; }) ) - .onConflict(Fields.Checkpoints.Id) + .onConflict([Fields.Checkpoints.Id, Fields.Checkpoints.Indexer]) .ignore(); } catch (err: any) { if (['ER_LOCK_DEADLOCK', '40P01'].includes(err.code)) { this.log.debug('deadlock detected, retrying...'); - return this.insertCheckpoints(items); + return this.insertCheckpoints(indexer, items); } throw err; @@ -264,26 +294,6 @@ export class CheckpointsStore { await Promise.all(chunk(checkpoints, 1000).map(chunk => insert(chunk))); } - public async removeFutureData(blockNumber: number): Promise { - return this.knex.transaction(async trx => { - await trx - .table(Table.Metadata) - .insert({ - [Fields.Metadata.Id]: MetadataId.LastIndexedBlock, - [Fields.Metadata.Value]: blockNumber - }) - .onConflict(Fields.Metadata.Id) - .merge(); - - await trx - .table(Table.Checkpoints) - .where(Fields.Checkpoints.BlockNumber, '>', blockNumber) - .del(); - - await trx.table(Table.Blocks).where(Fields.Blocks.Number, '>', blockNumber).del(); - }); - } - /** * Fetch list of checkpoint blocks greater than or equal to the * block number arguments, that have some events related to the @@ -293,6 +303,7 @@ export class CheckpointsStore { * can be modified by the limit command. */ public async getNextCheckpointBlocks( + indexer: string, block: number, contracts: string[], limit = 15 @@ -300,6 +311,7 @@ export class CheckpointsStore { const result = await this.knex .distinct(Fields.Checkpoints.BlockNumber) .from(Table.Checkpoints) + .where(Fields.Checkpoints.Indexer, indexer) .where(Fields.Checkpoints.BlockNumber, '>=', block) .whereIn(Fields.Checkpoints.ContractAddress, contracts) .orderBy(Fields.Checkpoints.BlockNumber, 'asc') @@ -311,25 +323,28 @@ export class CheckpointsStore { } public async insertTemplateSource( + indexer: string, contractAddress: string, startBlock: number, template: string ): Promise { return this.knex.table(Table.TemplateSources).insert({ + [Fields.TemplateSources.Indexer]: indexer, [Fields.TemplateSources.ContractAddress]: contractAddress, [Fields.TemplateSources.StartBlock]: startBlock, [Fields.TemplateSources.Template]: template }); } - public async getTemplateSources(): Promise { + public async getTemplateSources(indexer: string): Promise { const data = await this.knex .select( Fields.TemplateSources.ContractAddress, Fields.TemplateSources.StartBlock, Fields.TemplateSources.Template ) - .from(Table.TemplateSources); + .from(Table.TemplateSources) + .where(Fields.TemplateSources.Indexer, indexer); return data.map(row => ({ contractAddress: row[Fields.TemplateSources.ContractAddress], diff --git a/test/unit/stores/__snapshots__/checkpoints.test.ts.snap b/test/unit/stores/__snapshots__/checkpoints.test.ts.snap index c248dd3..fda4af0 100644 --- a/test/unit/stores/__snapshots__/checkpoints.test.ts.snap +++ b/test/unit/stores/__snapshots__/checkpoints.test.ts.snap @@ -1,26 +1,160 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`CheckpointsStore blocks should remove blocks 1`] = ` +[ + { + "block_number": 5000, + "hash": "0x0", + "indexer": "default", + }, + { + "block_number": 5001, + "hash": "0x1", + "indexer": "default", + }, +] +`; + +exports[`CheckpointsStore blocks should set block hash 1`] = ` +[ + { + "block_number": 5000, + "hash": "0x0", + "indexer": "default", + }, + { + "block_number": 5001, + "hash": "0x1", + "indexer": "default", + }, + { + "block_number": 5000, + "hash": "0xa", + "indexer": "OTHER", + }, +] +`; + +exports[`CheckpointsStore checkpoints should insert checkpoints 1`] = ` +[ + { + "block_number": 5000, + "contract_address": "0x01", + "id": "6f1246bdea", + "indexer": "default", + }, + { + "block_number": 9000, + "contract_address": "0x02", + "id": "92df4d22e3", + "indexer": "default", + }, + { + "block_number": 11000, + "contract_address": "0x01", + "id": "24a44a0b0b", + "indexer": "default", + }, +] +`; + exports[`CheckpointsStore createStore should execute correct query 1`] = ` -"create table \`_blocks\` (\`block_number\` bigint, \`hash\` varchar(255) not null, primary key (\`block_number\`)); -create unique index \`_blocks_hash_unique\` on \`_blocks\` (\`hash\`); -create table \`_checkpoints\` (\`id\` varchar(10), \`block_number\` bigint not null, \`contract_address\` varchar(66) not null, primary key (\`id\`)); +"create table \`_blocks\` (\`indexer\` varchar(255) not null, \`block_number\` bigint, \`hash\` varchar(255) not null, primary key (\`indexer\`, \`block_number\`)); +create table \`_checkpoints\` (\`id\` varchar(10), \`indexer\` varchar(255) not null, \`block_number\` bigint not null, \`contract_address\` varchar(66) not null, primary key (\`id\`, \`indexer\`)); create index \`_checkpoints_block_number_index\` on \`_checkpoints\` (\`block_number\`); create index \`_checkpoints_contract_address_index\` on \`_checkpoints\` (\`contract_address\`); -create table \`_metadatas\` (\`id\` varchar(20), \`value\` varchar(128) not null, primary key (\`id\`)); -create table \`_template_sources\` (\`id\` integer not null primary key autoincrement, \`contract_address\` varchar(66), \`start_block\` bigint not null, \`template\` varchar(128) not null)" +create table \`_metadatas\` (\`id\` varchar(20), \`indexer\` varchar(255) not null, \`value\` varchar(128) not null, primary key (\`id\`, \`indexer\`)); +create table \`_template_sources\` (\`indexer\` varchar(255) not null, \`contract_address\` varchar(66), \`start_block\` bigint not null, \`template\` varchar(128) not null)" +`; + +exports[`CheckpointsStore metadata should set metadata 1`] = ` +[ + { + "id": "key", + "indexer": "default", + "value": "default_value", + }, + { + "id": "number_key", + "indexer": "default", + "value": "1111", + }, + { + "id": "key", + "indexer": "OTHER", + "value": "other_value", + }, +] `; -exports[`CheckpointsStore insertCheckpoints should insert checkpoints 1`] = ` +exports[`CheckpointsStore removeFutureData should remove future data 1`] = ` [ { "block_number": 5000, - "contract_address": "0x0625dc1290b6e936be5f1a3e963cf629326b1f4dfd5a56738dea98e1ad31b7f3", - "id": "a739beda26", + "contract_address": "0x01", + "id": "6f1246bdea", + "indexer": "default", + }, + { + "block_number": 9000, + "contract_address": "0x02", + "id": "92df4d22e3", + "indexer": "default", + }, + { + "block_number": 11000, + "contract_address": "0x01", + "id": "24a44a0b0b", + "indexer": "OTHER", + }, +] +`; + +exports[`CheckpointsStore template sources should insert template sources 1`] = ` +[ + { + "contract_address": "0x01", + "indexer": "default", + "start_block": 1000, + "template": "Template1", + }, + { + "contract_address": "0x01", + "indexer": "default", + "start_block": 2000, + "template": "Template1", + }, + { + "contract_address": "0x02", + "indexer": "default", + "start_block": 2100, + "template": "Template3", + }, + { + "contract_address": "0x01", + "indexer": "OTHER", + "start_block": 50, + "template": "Template1", + }, +] +`; + +exports[`CheckpointsStore template sources should retrieve template sources 1`] = ` +[ + { + "contractAddress": "0x01", + "startBlock": 1000, + "template": "Template1", + }, + { + "contractAddress": "0x01", + "startBlock": 2000, + "template": "Template1", }, { - "block_number": 123222, - "contract_address": "0x0625dc1290b6e936be5f1a3e963cf629326b1f4dfd5a56738dea98e1ad31b7f3", - "id": "d2b8dcc2b5", + "contractAddress": "0x02", + "startBlock": 2100, + "template": "Template3", }, ] `; diff --git a/test/unit/stores/checkpoints.test.ts b/test/unit/stores/checkpoints.test.ts index cbef5f2..23f0c05 100644 --- a/test/unit/stores/checkpoints.test.ts +++ b/test/unit/stores/checkpoints.test.ts @@ -1,6 +1,6 @@ import knex from 'knex'; import { mockDeep } from 'jest-mock-extended'; -import { CheckpointsStore } from '../../../src/stores/checkpoints'; +import { CheckpointsStore, MetadataId, Table } from '../../../src/stores/checkpoints'; import { Logger } from '../../../src/utils/logger'; function createMockLogger() { @@ -10,6 +10,8 @@ function createMockLogger() { } describe('CheckpointsStore', () => { + const INDEXER = 'default'; + const mockKnex = knex({ client: 'sqlite3', connection: { @@ -33,22 +35,190 @@ describe('CheckpointsStore', () => { }); }); - describe('insertCheckpoints', () => { + describe('removeFutureData', () => { + afterAll(async () => { + await store.resetStore(); + }); + + it('should remove future data', async () => { + await store.setMetadata(INDEXER, MetadataId.LastIndexedBlock, 11001); + await store.setMetadata('OTHER', MetadataId.LastIndexedBlock, 11001); + + await store.insertCheckpoints(INDEXER, [ + { + contractAddress: '0x01', + blockNumber: 5000 + }, + { + contractAddress: '0x02', + blockNumber: 9000 + }, + { + contractAddress: '0x01', + blockNumber: 11000 + } + ]); + await store.insertCheckpoints('OTHER', [ + { + contractAddress: '0x01', + blockNumber: 11000 + } + ]); + + await store.removeFutureData(INDEXER, 10000); + + const defaultLastIndexedBlock = await store.getMetadataNumber( + INDEXER, + MetadataId.LastIndexedBlock + ); + expect(defaultLastIndexedBlock).toEqual(10000); + + const otherLastIndexedBlock = await store.getMetadataNumber( + 'OTHER', + MetadataId.LastIndexedBlock + ); + expect(otherLastIndexedBlock).toEqual(11001); + + const result = await mockKnex.select('*').from(Table.Checkpoints); + expect(result).toMatchSnapshot(); + }); + }); + + describe('blocks', () => { + afterAll(async () => { + await store.resetStore(); + }); + + it('should set block hash', async () => { + await store.setBlockHash(INDEXER, 5000, '0x0'); + await store.setBlockHash(INDEXER, 5001, '0x1'); + await store.setBlockHash('OTHER', 5000, '0xa'); + + const result = await mockKnex.select('*').from(Table.Blocks); + expect(result).toMatchSnapshot(); + }); + + it('should retrieve block hash', async () => { + const result = await store.getBlockHash(INDEXER, 5000); + expect(result).toEqual('0x0'); + }); + + it('should return null if retrieving non-existent block hash', async () => { + const result = await store.getBlockHash(INDEXER, 6000); + expect(result).toBeNull(); + }); + + it('should remove blocks', async () => { + await store.removeBlocks('OTHER'); + + const result = await mockKnex.select('*').from(Table.Blocks); + expect(result).toMatchSnapshot(); + }); + }); + + describe('metadata', () => { + afterAll(async () => { + await store.resetStore(); + }); + + it('should set metadata', async () => { + await store.setMetadata(INDEXER, 'key', 'default_value'); + await store.setMetadata(INDEXER, 'number_key', 1111); + await store.setMetadata('OTHER', 'key', 'other_value'); + + const result = await mockKnex.select('*').from(Table.Metadata); + expect(result).toMatchSnapshot(); + }); + + it('should retrieve metadata', async () => { + const result = await store.getMetadata(INDEXER, 'key'); + expect(result).toEqual('default_value'); + }); + + it('should retrieve metadata as number', async () => { + const result = await store.getMetadataNumber(INDEXER, 'number_key'); + expect(result).toEqual(1111); + }); + + it('should return null if retrieving non-existent metadata value', async () => { + const result = await store.getMetadata(INDEXER, 'non_existent_key'); + expect(result).toBeNull(); + }); + + it('should return null if retrieving non-existent metadata value as number', async () => { + const result = await store.getMetadataNumber(INDEXER, 'non_existent_key'); + expect(result).toBeNull(); + }); + + it('should update metadata', async () => { + await store.setMetadata(INDEXER, 'key', 'new_value'); + const result = await store.getMetadata(INDEXER, 'key'); + expect(result).toEqual('new_value'); + }); + }); + + describe('checkpoints', () => { + const CONTRACT_A = '0x01'; + const CONTRACT_B = '0x02'; + + afterAll(async () => { + await store.resetStore(); + }); + it('should insert checkpoints', async () => { const checkpoints = [ { - contractAddress: '0x0625dc1290b6e936be5f1a3e963cf629326b1f4dfd5a56738dea98e1ad31b7f3', + contractAddress: CONTRACT_A, blockNumber: 5000 }, { - contractAddress: '0x0625dc1290b6e936be5f1a3e963cf629326b1f4dfd5a56738dea98e1ad31b7f3', - blockNumber: 123222 + contractAddress: CONTRACT_B, + blockNumber: 9000 + }, + { + contractAddress: CONTRACT_A, + blockNumber: 11000 } ]; - await store.insertCheckpoints(checkpoints); + await store.insertCheckpoints(INDEXER, checkpoints); + + const result = await mockKnex.select('*').from(Table.Checkpoints); + expect(result).toMatchSnapshot(); + }); + + it('should return next checkpoint blocks', async () => { + let result = await store.getNextCheckpointBlocks(INDEXER, 4000, [CONTRACT_A, CONTRACT_B]); + expect(result).toEqual([5000, 9000, 11000]); + + result = await store.getNextCheckpointBlocks(INDEXER, 7000, [CONTRACT_A, CONTRACT_B]); + expect(result).toEqual([9000, 11000]); + + result = await store.getNextCheckpointBlocks(INDEXER, 4000, [CONTRACT_B]); + expect(result).toEqual([9000]); + }); + }); + + describe('template sources', () => { + const CONTRACT_A = '0x01'; + const CONTRACT_B = '0x02'; + + afterAll(async () => { + await store.resetStore(); + }); + + it('should insert template sources', async () => { + await store.insertTemplateSource(INDEXER, CONTRACT_A, 1000, 'Template1'); + await store.insertTemplateSource(INDEXER, CONTRACT_A, 2000, 'Template1'); + await store.insertTemplateSource(INDEXER, CONTRACT_B, 2100, 'Template3'); + await store.insertTemplateSource('OTHER', CONTRACT_A, 50, 'Template1'); + + const result = await mockKnex.select('*').from(Table.TemplateSources); + expect(result).toMatchSnapshot(); + }); - const result = await mockKnex.select('*').from('_checkpoints'); + it('should retrieve template sources', async () => { + const result = await store.getTemplateSources(INDEXER); expect(result).toMatchSnapshot(); }); });