Skip to content

Commit

Permalink
refactor: add range filters
Browse files Browse the repository at this point in the history
  • Loading branch information
janniks committed May 16, 2023
1 parent 5191fd4 commit 21febe7
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 71 deletions.
11 changes: 1 addition & 10 deletions src/api/routes/inscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,14 @@ import {
import { handleInscriptionCache, handleInscriptionTransfersCache } from '../util/cache';
import {
DEFAULT_API_LIMIT,
blockParam,
hexToBuffer,
parseBlockTransfers,
parseDbInscription,
parseDbInscriptions,
parseInscriptionLocations,
} from '../util/helpers';

function blockParam(param: string | undefined, name: string) {
const out: Record<string, string> = {};
if (BlockHashParamCType.Check(param)) {
out[`${name}_hash`] = param;
} else if (BlockHeightParamCType.Check(param)) {
out[`${name}_height`] = param;
}
return out;
}

function inscriptionIdArrayParam(param: string | number) {
return InscriptionIdParamCType.Check(param) ? { genesis_id: [param] } : { number: [param] };
}
Expand Down
33 changes: 27 additions & 6 deletions src/api/routes/stats.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { FastifyPluginAsync, FastifyPluginCallback } from 'fastify';
import { Server } from 'http';
import { blockParam } from '../util/helpers';
import { Type } from '@sinclair/typebox';
import { BlockHeightParam } from '../schemas';

const IndexRoutes: FastifyPluginCallback<Record<never, never>, Server, TypeBoxTypeProvider> = (
fastify,
options,
done
) => {
fastify.get('/stats/inscriptions', async (request, reply) => {
const inscriptions = await fastify.db.getInscriptionCountPerBlock();
await reply.send({
results: inscriptions,
});
});
fastify.get(
'/stats/inscriptions',
{
schema: {
operationId: 'getInscriptionCount',
summary: 'Inscription Count',
description: 'Retrieves count of inscriptions revealed per block',
tags: ['Inscriptions'],
querystring: Type.Object({
from_block_height: Type.Optional(BlockHeightParam),
to_block_height: Type.Optional(BlockHeightParam),
}),
},
},
async (request, reply) => {
const inscriptions = await fastify.db.getInscriptionCountPerBlock({
...blockParam(request.query.from_block_height, 'from_block'),
...blockParam(request.query.to_block_height, 'to_block'),
});
await reply.send({
results: inscriptions,
});
}
);
done();
};

Expand Down
12 changes: 12 additions & 0 deletions src/api/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
DbLocation,
} from '../../pg/types';
import {
BlockHashParamCType,
BlockHeightParamCType,
BlockInscriptionTransfer,
InscriptionLocationResponse,
InscriptionResponseType,
Expand Down Expand Up @@ -109,3 +111,13 @@ export const has0xPrefix = (id: string) => id.substr(0, 2).toLowerCase() === '0x
export function normalizedHexString(hex: string): string {
return has0xPrefix(hex) ? hex.substring(2) : hex;
}

export function blockParam(param: string | undefined, name: string) {
const out: Record<string, string> = {};
if (BlockHashParamCType.Check(param)) {
out[`${name}_hash`] = param;
} else if (BlockHeightParamCType.Check(param)) {
out[`${name}_height`] = param;
}
return out;
}
16 changes: 13 additions & 3 deletions src/pg/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
DbFullyLocatedInscriptionResult,
DbInscriptionContent,
DbInscriptionCountPerBlock,
DbInscriptionCountPerBlockFilters,
DbInscriptionIndexFilters,
DbInscriptionIndexOrder,
DbInscriptionIndexPaging,
Expand Down Expand Up @@ -477,9 +478,18 @@ export class PgStore extends BasePgStore {
}
}

// todo: add range filter
async getInscriptionCountPerBlock(): Promise<DbInscriptionCountPerBlock[]> {
return this.sql<DbInscriptionCountPerBlock[]>`SELECT * FROM inscriptions_per_block`;
async getInscriptionCountPerBlock(
filters?: DbInscriptionCountPerBlockFilters
): Promise<DbInscriptionCountPerBlock[]> {
// 9223372036854775807 is the max value for a bigint
return await this.sql<DbInscriptionCountPerBlock[]>`
SELECT *
FROM inscriptions_per_block
WHERE block_height >= ${filters?.from_block_height ?? '0'}
AND block_height <= ${filters?.to_block_height ?? '9223372036854775807'}
ORDER BY block_height DESC
LIMIT 10000
`; // roughly 70 days of blocks, assuming 10 minute block times on a full database
}

async refreshMaterializedView(viewName: string) {
Expand Down
5 changes: 5 additions & 0 deletions src/pg/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ export enum DbInscriptionIndexResultCountType {
custom,
}

export type DbInscriptionCountPerBlockFilters = {
from_block_height?: number;
to_block_height?: number;
};

export type DbInscriptionCountPerBlock = {
block_height: string;
inscription_count: string;
Expand Down
158 changes: 106 additions & 52 deletions tests/stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,76 +22,130 @@ describe('/stats', () => {
});

describe('/stats/inscriptions', () => {
const EXPECTED = {
results: [
{ block_height: '778000', inscription_count: '2', inscription_count_total: '2' },
{ block_height: '778001', inscription_count: '1', inscription_count_total: '3' },
{ block_height: '778002', inscription_count: '1', inscription_count_total: '4' },
{ block_height: '778005', inscription_count: '2', inscription_count_total: '6' },
{ block_height: '778010', inscription_count: '3', inscription_count_total: '9' },
],
};

test('returns stats when processing blocks in order', async () => {
await db.updateInscriptions(testRevealBuilder(778_000).build());
describe('event processing', () => {
const EXPECTED = {
results: [
{ block_height: '778010', inscription_count: '3', inscription_count_total: '9' },
{ block_height: '778005', inscription_count: '2', inscription_count_total: '6' },
{ block_height: '778002', inscription_count: '1', inscription_count_total: '4' },
{ block_height: '778001', inscription_count: '1', inscription_count_total: '3' },
{ block_height: '778000', inscription_count: '2', inscription_count_total: '2' },
],
};

test('returns stats when processing blocks in order', async () => {
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_001).build());
await db.updateInscriptions(testRevealBuilder(778_002).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());

const response = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/stats/inscriptions',
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual(EXPECTED);
});

test('returns stats when processing blocks out-of-order', async () => {
await db.updateInscriptions(testRevealBuilder(778_001).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_002).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());

const response = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/stats/inscriptions',
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual(EXPECTED);
});

test('returns stats when processing rollbacks', async () => {
const payloadApply = testRevealBuilder(778_004).build();
const payloadRollback = { ...payloadApply, apply: [], rollback: payloadApply.apply };

await db.updateInscriptions(testRevealBuilder(778_001).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(payloadApply);
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(payloadRollback);
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_002).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());

const response = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/stats/inscriptions',
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual(EXPECTED);
});
});

test('range filters', async () => {
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_001).build());
await db.updateInscriptions(testRevealBuilder(778_002).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());

const response = await fastify.inject({
const responseFrom = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/stats/inscriptions',
query: { from_block_height: '778004' },
});
expect(responseFrom.statusCode).toBe(200);
expect(responseFrom.json()).toStrictEqual({
results: [
{ block_height: '778010', inscription_count: '1', inscription_count_total: '6' },
{ block_height: '778005', inscription_count: '2', inscription_count_total: '5' },
],
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual(EXPECTED);
});

test('returns stats when processing blocks out-of-order', async () => {
await db.updateInscriptions(testRevealBuilder(778_001).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_002).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());

const response = await fastify.inject({
const responseTo = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/stats/inscriptions',
query: { to_block_height: '778004' },
});
expect(responseTo.statusCode).toBe(200);
expect(responseTo.json()).toStrictEqual({
results: [
{ block_height: '778002', inscription_count: '1', inscription_count_total: '3' },
{ block_height: '778001', inscription_count: '1', inscription_count_total: '2' },
{ block_height: '778000', inscription_count: '1', inscription_count_total: '1' },
],
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual(EXPECTED);
});

test('returns stats when processing rollbacks', async () => {
const payloadApply = testRevealBuilder(778_004).build();
const payloadRollback = { ...payloadApply, apply: [], rollback: payloadApply.apply };

await db.updateInscriptions(testRevealBuilder(778_001).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(payloadApply);
await db.updateInscriptions(testRevealBuilder(778_005).build());
await db.updateInscriptions(testRevealBuilder(778_000).build());
await db.updateInscriptions(payloadRollback);
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_002).build());
await db.updateInscriptions(testRevealBuilder(778_010).build());
await db.updateInscriptions(testRevealBuilder(778_005).build());

const response = await fastify.inject({
const responseFromTo = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/stats/inscriptions',
query: {
from_block_height: '778002',
to_block_height: '778005',
},
});
expect(responseFromTo.statusCode).toBe(200);
expect(responseFromTo.json()).toStrictEqual({
results: [
{ block_height: '778005', inscription_count: '2', inscription_count_total: '5' },
{ block_height: '778002', inscription_count: '1', inscription_count_total: '3' },
],
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual(EXPECTED);
});
});
});
Expand Down

0 comments on commit 21febe7

Please sign in to comment.