Skip to content

Commit

Permalink
test: polish and sync first block number scenario
Browse files Browse the repository at this point in the history
  • Loading branch information
sdlyy committed Sep 14, 2023
1 parent 8c4eefc commit 9a5aac6
Showing 1 changed file with 135 additions and 22 deletions.
157 changes: 135 additions & 22 deletions packages/backend/src/indexers/BlockNumberIndexer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,67 @@ import {
BlockNumberRecord,
BlockNumberRepository,
} from '../peripherals/database/BlockNumberRepository'
import { IndexerStateRepository } from '../peripherals/database/IndexerStateRepository'
import {
IndexerStateRecord,
IndexerStateRepository,
} from '../peripherals/database/IndexerStateRepository'
import { BlockNumberIndexer } from './BlockNumberIndexer'
import { ClockIndexer } from './ClockIndexer'

describe(BlockNumberIndexer.name, () => {
describe(BlockNumberIndexer.prototype.update.name, () => {
it('downloads a new block and returns its timestamp without reorg', async () => {
const [genesisBlock] = BLOCKS
const fakeBlockchainClient = mockBlockchainClient(BLOCKS)
const fakeBlockNumberRepository = mockBlockNumberRepository([
blockToRecord(genesisBlock!),
])
const fakeIndexerStateRepository = mockIndexerStateRepository()

const blockNumberIndexer = new BlockNumberIndexer(
mockObject<BlockchainClient>({
getBlockNumberAtOrBefore,
getBlock: async (number) => mockBlock(number),
}),
mockObject<BlockNumberRepository>({
findByNumber: async (number) => mockBlockRecord(number),
addMany: async () => [0],
add: async () => 0,
}),
mockObject<IndexerStateRepository>(),
fakeBlockchainClient,
fakeBlockNumberRepository,
fakeIndexerStateRepository,
0,
mockObject<ClockIndexer>({
subscribe: () => {},
}),
Logger.SILENT,
)

// from stays at 0 as we do not check against `from` value
expect(await blockNumberIndexer.update(0, 1)).toEqual(0)
/**
* First run with genesis block already in the database
* won't fetch a new block
*/
expect(await blockNumberIndexer.update(0, 1)).toEqual(1)
expect(
fakeBlockchainClient.getBlockNumberAtOrBefore,
).toHaveBeenCalledWith(new UnixTime(1))
expect(fakeBlockchainClient.getBlock).not.toHaveBeenCalled()

/**
* Second run with missing blocks in the database
* will attempt to fetch next single block from BLOCKS - no. 1, timestamp: 1000
* @see BLOCKS
*/
expect(await blockNumberIndexer.update(0, 2000)).toEqual(1000)
expect(
fakeBlockchainClient.getBlockNumberAtOrBefore,
).toHaveBeenCalledWith(new UnixTime(2000))
expect(fakeBlockchainClient.getBlock).toHaveBeenCalledWith(1)
expect(fakeBlockNumberRepository.findByNumber).toHaveBeenCalledWith(0)

/**
* Third run with missing blocks in the database
* will attempt to fetch next single block from BLOCKS - no. 2, timestamp: 2000
* @see BLOCKS
*/
expect(await blockNumberIndexer.update(1000, 2000)).toEqual(2000)
expect(
fakeBlockchainClient.getBlockNumberAtOrBefore,
).toHaveBeenCalledWith(new UnixTime(2000))
expect(fakeBlockchainClient.getBlock).toHaveBeenCalledWith(2)
expect(fakeBlockNumberRepository.findByNumber).toHaveBeenCalledWith(1)
})

it('downloads a new block and returns its timestamp with reorg', async () => {
Expand Down Expand Up @@ -86,9 +118,16 @@ describe(BlockNumberIndexer.name, () => {
expect(await blockNumberIndexer.update(0, 3000)).toEqual(1000)
expect(await blockNumberIndexer.update(1000, 3000)).toEqual(3000)
expect(memoryBlockNumberRepository.addMany).toHaveBeenCalledTimes(1)

const block2 = BLOCKS[2]
const block3 = BLOCKS[3]

assert(block2)
assert(block3)

expect(memoryBlockNumberRepository.addMany).toHaveBeenNthCalledWith(
1,
[BLOCKS[2], BLOCKS[3]].map(blockToRecord),
[block2, block3].map(blockToRecord),
)
})
})
Expand All @@ -100,7 +139,7 @@ const HASH2 = Hash256.random()
const HASH3 = Hash256.random()
const HASH4 = Hash256.random()

const BLOCKS = [
const BLOCKS: BlockFromClient[] = [
{
number: 0,
hash: HASH0.toString(),
Expand Down Expand Up @@ -131,7 +170,7 @@ const BLOCKS = [
parentHash: HASH3.toString(),
timestamp: 4000,
},
] as const
]

function mockBlockRecord(blockNumber: number): BlockNumberRecord | undefined {
if (blockNumber === 0) {
Expand Down Expand Up @@ -168,20 +207,94 @@ function getBlockNumberAtOrBefore(timestamp: UnixTime): Promise<number> {
return Promise.resolve(block.number)
}

function mockBlockNumberRepository(initialStorage: BlockNumberRecord[]) {
function mockBlockNumberRepository(initialStorage: BlockNumberRecord[] = []) {
const blockNumberStorage: BlockNumberRecord[] = [...initialStorage]

return mockObject<BlockNumberRepository>({
findByNumber: async (number) =>
blockNumberStorage.find((bnr) => bnr.blockNumber === number),
findLast: async () => blockNumberStorage.at(-1),
findByNumber: async (number) => {
const result = blockNumberStorage.find(
(bnr) => bnr.blockNumber === number,
)

console.dir({ method: 'findByNumber', number, result })
return result
},
findLast: async () => {
const result = blockNumberStorage.at(-1)
console.dir({ method: 'findLast', result })
return result
},
addMany: async (blocks: BlockNumberRecord[]) => {
blockNumberStorage.push(...blocks)
return blocks.map((b) => b.blockNumber)
const result = blocks.map((b) => b.blockNumber)
console.dir({ method: 'addMany', blocks, result })
return result
},
add: async (block: BlockNumberRecord) => {
blockNumberStorage.push(block)
return block.blockNumber
const result = block.blockNumber
console.dir({ method: 'add', block })
return result
},
})
}

function mockBlockchainClient(blocks: BlockFromClient[]) {
const blockchainBlocks = [...blocks]

return mockObject<BlockchainClient>({
getBlockNumberAtOrBefore: async (timestamp) => {
const block = blockchainBlocks
.filter((b) => b.timestamp <= timestamp.toNumber())
.sort((a, b) => b.timestamp - a.timestamp)
.shift()
assert(
block,
`Block not found for given timestamp: ${timestamp.toString()}`,
)

console.dir({ method: 'getBlockNumberAtOrBefore', timestamp, block })

return Promise.resolve(block.number)
},
getBlock: async (blockId) => {
if (typeof blockId === 'number') {
const block = blockchainBlocks.find((b) => b.number === blockId)
assert(block, `Block not found for given number: ${blockId}`)

console.dir({ method: 'getBlock#number', blockId, block })

return block
}

const block = blockchainBlocks.find((b) => b.hash === blockId.toString())
console.dir({ method: 'getBlock#hash', blockId, block })
assert(block, `Block not found for given hash: ${blockId}`)
return block
},
})
}

function mockIndexerStateRepository(initialState: IndexerStateRecord[] = []) {
const states: IndexerStateRecord[] = [...initialState]

return mockObject<IndexerStateRepository>({
findById: async (id) => {
const result = states.find((s) => s.id === id)
console.dir({ method: 'findById', id, result })
return result
},
addOrUpdate: async (record) => {
const presentState = states.find((s) => s.id === record.id)

if (presentState) {
presentState.height = record.height

return record.id
} else {
states.push(record)
return record.id
}
},
})
}
Expand Down

0 comments on commit 9a5aac6

Please sign in to comment.