Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use pre-calculated inscription count for unfiltered results #48

Merged
merged 2 commits into from
Apr 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions migrations/1682654536767_chain-tip-inscription-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate';

export const shorthands: ColumnDefinitions | undefined = undefined;

export function up(pgm: MigrationBuilder): void {
pgm.addColumn('chain_tip', {
inscription_count: {
type: 'int',
notNull: true,
default: 0,
},
});
}
58 changes: 30 additions & 28 deletions src/api/routes/inscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,29 +108,32 @@ const IndexRoutes: FastifyPluginCallback<Record<never, never>, Server, TypeBoxTy
async (request, reply) => {
const limit = request.query.limit ?? DEFAULT_API_LIMIT;
const offset = request.query.offset ?? 0;
const inscriptions = await fastify.db.getInscriptions({
...blockParam(request.query.genesis_block, 'genesis_block'),
...blockParam(request.query.from_genesis_block_height, 'from_genesis_block'),
...blockParam(request.query.to_genesis_block_height, 'to_genesis_block'),
...blockParam(request.query.from_sat_coinbase_height, 'from_sat_coinbase'),
...blockParam(request.query.to_sat_coinbase_height, 'to_sat_coinbase'),
from_genesis_timestamp: request.query.from_genesis_timestamp,
to_genesis_timestamp: request.query.to_genesis_timestamp,
from_sat_ordinal: bigIntParam(request.query.from_sat_ordinal),
to_sat_ordinal: bigIntParam(request.query.to_sat_ordinal),
from_number: request.query.from_number,
to_number: request.query.to_number,
genesis_id: request.query.id,
number: request.query.number,
output: request.query.output,
address: request.query.address,
mime_type: request.query.mime_type,
sat_rarity: request.query.rarity,
order_by: request.query.order_by ?? OrderBy.genesis_block_height,
order: request.query.order ?? Order.desc,
limit,
offset,
});
const inscriptions = await fastify.db.getInscriptions(
{ limit, offset },
{
...blockParam(request.query.genesis_block, 'genesis_block'),
...blockParam(request.query.from_genesis_block_height, 'from_genesis_block'),
...blockParam(request.query.to_genesis_block_height, 'to_genesis_block'),
...blockParam(request.query.from_sat_coinbase_height, 'from_sat_coinbase'),
...blockParam(request.query.to_sat_coinbase_height, 'to_sat_coinbase'),
from_genesis_timestamp: request.query.from_genesis_timestamp,
to_genesis_timestamp: request.query.to_genesis_timestamp,
from_sat_ordinal: bigIntParam(request.query.from_sat_ordinal),
to_sat_ordinal: bigIntParam(request.query.to_sat_ordinal),
from_number: request.query.from_number,
to_number: request.query.to_number,
genesis_id: request.query.id,
number: request.query.number,
output: request.query.output,
address: request.query.address,
mime_type: request.query.mime_type,
sat_rarity: request.query.rarity,
},
{
order_by: request.query.order_by ?? OrderBy.genesis_block_height,
order: request.query.order ?? Order.desc,
}
);
await reply.send({
limit,
offset,
Expand Down Expand Up @@ -166,11 +169,10 @@ const ShowRoutes: FastifyPluginCallback<Record<never, never>, Server, TypeBoxTyp
},
},
async (request, reply) => {
const inscription = await fastify.db.getInscriptions({
...inscriptionIdArrayParam(request.params.id),
limit: 1,
offset: 0,
});
const inscription = await fastify.db.getInscriptions(
{ limit: 1, offset: 0 },
{ ...inscriptionIdArrayParam(request.params.id) }
);
if (inscription.total > 0) {
await reply.send(parseDbInscription(inscription.results[0]));
} else {
Expand Down
9 changes: 4 additions & 5 deletions src/api/routes/sats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@ export const SatRoutes: FastifyPluginCallback<Record<never, never>, Server, Type
},
async (request, reply) => {
const sat = new OrdinalSatoshi(request.params.ordinal);
const inscriptions = await fastify.db.getInscriptions({
sat_ordinal: BigInt(request.params.ordinal),
limit: 1,
offset: 0,
});
const inscriptions = await fastify.db.getInscriptions(
{ limit: 1, offset: 0 },
{ sat_ordinal: BigInt(request.params.ordinal) }
);
await reply.send({
coinbase_height: sat.blockHeight,
cycle: sat.cycle,
Expand Down
1 change: 1 addition & 0 deletions src/chainhook/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,5 @@ export async function processInscriptionFeed(payload: unknown, db: PgStore): Pro
}
}
}
await db.updateChainTipInscriptionCount();
}
127 changes: 77 additions & 50 deletions src/pg/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,24 @@ export class PgStore extends BasePgStore {
`;
}

async updateChainTipInscriptionCount(): Promise<void> {
await this.sql`
UPDATE chain_tip SET inscription_count = (SELECT COUNT(*) FROM inscriptions)
`;
}

async getChainTipBlockHeight(): Promise<number> {
const result = await this.sql<{ block_height: number }[]>`SELECT block_height FROM chain_tip`;
return result[0].block_height;
}

async getChainTipInscriptionCount(): Promise<number> {
const result = await this.sql<{ inscription_count: number }[]>`
SELECT inscription_count FROM chain_tip
`;
return result[0].inscription_count;
}

async getMaxInscriptionNumber(): Promise<number | undefined> {
const result = await this.sql<{ max: number }[]>`SELECT MAX(number) FROM inscriptions`;
if (result[0].max) {
Expand Down Expand Up @@ -297,34 +310,40 @@ export class PgStore extends BasePgStore {
}
}

async getInscriptions(args: {
genesis_id?: string[];
genesis_block_height?: number;
genesis_block_hash?: string;
from_genesis_block_height?: number;
to_genesis_block_height?: number;
from_genesis_timestamp?: number;
to_genesis_timestamp?: number;
from_sat_coinbase_height?: number;
to_sat_coinbase_height?: number;
number?: number[];
from_number?: number;
to_number?: number;
address?: string[];
mime_type?: string[];
output?: string;
sat_rarity?: SatoshiRarity[];
sat_ordinal?: bigint;
from_sat_ordinal?: bigint;
to_sat_ordinal?: bigint;
order_by?: OrderBy;
order?: Order;
limit: number;
offset: number;
}): Promise<DbPaginatedResult<DbFullyLocatedInscriptionResult>> {
async getInscriptions(
page: {
limit: number;
offset: number;
},
args?: {
genesis_id?: string[];
genesis_block_height?: number;
genesis_block_hash?: string;
from_genesis_block_height?: number;
to_genesis_block_height?: number;
from_genesis_timestamp?: number;
to_genesis_timestamp?: number;
from_sat_coinbase_height?: number;
to_sat_coinbase_height?: number;
number?: number[];
from_number?: number;
to_number?: number;
address?: string[];
mime_type?: string[];
output?: string;
sat_rarity?: SatoshiRarity[];
sat_ordinal?: bigint;
from_sat_ordinal?: bigint;
to_sat_ordinal?: bigint;
},
sort?: {
order_by?: OrderBy;
order?: Order;
}
): Promise<DbPaginatedResult<DbFullyLocatedInscriptionResult>> {
// Sanitize ordering args because we'll use `unsafe` to concatenate them into the query.
let orderBy = 'gen.block_height';
switch (args.order_by) {
switch (sort?.order_by) {
case OrderBy.ordinal:
orderBy = 'loc.sat_ordinal';
break;
Expand All @@ -333,7 +352,10 @@ export class PgStore extends BasePgStore {
"ARRAY_POSITION(ARRAY['common','uncommon','rare','epic','legendary','mythic'], loc.sat_rarity)";
break;
}
const order = args.order === Order.asc ? 'ASC' : 'DESC';
const order = sort?.order === Order.asc ? 'ASC' : 'DESC';
// Do we need a filtered `COUNT(*)`? If not, use the global inscription count from `chain_tip`.
const unfiltered =
args === undefined || Object.values(args).find(k => k !== undefined) === undefined;

const results = await this.sql<({ total: number } & DbFullyLocatedInscriptionResult)[]>`
SELECT
Expand All @@ -357,88 +379,93 @@ export class PgStore extends BasePgStore {
loc.timestamp,
loc.value,
loc.sat_coinbase_height,
COUNT(*) OVER() as total
${unfiltered ? '0 as total' : this.sql`COUNT(*) OVER() as total`}
FROM inscriptions AS i
INNER JOIN locations AS loc ON loc.inscription_id = i.id
INNER JOIN locations AS gen ON gen.inscription_id = i.id
WHERE loc.current = TRUE AND gen.genesis = TRUE
${
args.genesis_id?.length
args?.genesis_id?.length
? this.sql`AND i.genesis_id IN ${this.sql(args.genesis_id)}`
: this.sql``
}
${
args.genesis_block_height
args?.genesis_block_height
? this.sql`AND gen.block_height = ${args.genesis_block_height}`
: this.sql``
}
${
args.genesis_block_hash
args?.genesis_block_hash
? this.sql`AND gen.block_hash = ${args.genesis_block_hash}`
: this.sql``
}
${
args.from_genesis_block_height
args?.from_genesis_block_height
? this.sql`AND gen.block_height >= ${args.from_genesis_block_height}`
: this.sql``
}
${
args.to_genesis_block_height
args?.to_genesis_block_height
? this.sql`AND gen.block_height <= ${args.to_genesis_block_height}`
: this.sql``
}
${
args.from_sat_coinbase_height
args?.from_sat_coinbase_height
? this.sql`AND loc.sat_coinbase_height >= ${args.from_sat_coinbase_height}`
: this.sql``
}
${
args.to_sat_coinbase_height
args?.to_sat_coinbase_height
? this.sql`AND loc.sat_coinbase_height <= ${args.to_sat_coinbase_height}`
: this.sql``
}
${
args.from_genesis_timestamp
args?.from_genesis_timestamp
? this.sql`AND gen.timestamp >= to_timestamp(${args.from_genesis_timestamp})`
: this.sql``
}
${
args.to_genesis_timestamp
args?.to_genesis_timestamp
? this.sql`AND gen.timestamp <= to_timestamp(${args.to_genesis_timestamp})`
: this.sql``
}
${
args.from_sat_ordinal
args?.from_sat_ordinal
? this.sql`AND loc.sat_ordinal >= ${args.from_sat_ordinal}`
: this.sql``
}
${
args.to_sat_ordinal ? this.sql`AND loc.sat_ordinal <= ${args.to_sat_ordinal}` : this.sql``
args?.to_sat_ordinal
? this.sql`AND loc.sat_ordinal <= ${args.to_sat_ordinal}`
: this.sql``
}
${args.number?.length ? this.sql`AND i.number IN ${this.sql(args.number)}` : this.sql``}
${args.from_number ? this.sql`AND i.number >= ${args.from_number}` : this.sql``}
${args.to_number ? this.sql`AND i.number <= ${args.to_number}` : this.sql``}
${args?.number?.length ? this.sql`AND i.number IN ${this.sql(args.number)}` : this.sql``}
${args?.from_number ? this.sql`AND i.number >= ${args.from_number}` : this.sql``}
${args?.to_number ? this.sql`AND i.number <= ${args.to_number}` : this.sql``}
${
args.address?.length ? this.sql`AND loc.address IN ${this.sql(args.address)}` : this.sql``
args?.address?.length
? this.sql`AND loc.address IN ${this.sql(args.address)}`
: this.sql``
}
${
args.mime_type?.length
args?.mime_type?.length
? this.sql`AND i.mime_type IN ${this.sql(args.mime_type)}`
: this.sql``
}
${args.output ? this.sql`AND loc.output = ${args.output}` : this.sql``}
${args?.output ? this.sql`AND loc.output = ${args.output}` : this.sql``}
${
args.sat_rarity?.length
args?.sat_rarity?.length
? this.sql`AND loc.sat_rarity IN ${this.sql(args.sat_rarity)}`
: this.sql``
}
${args.sat_ordinal ? this.sql`AND loc.sat_ordinal = ${args.sat_ordinal}` : this.sql``}
${args?.sat_ordinal ? this.sql`AND loc.sat_ordinal = ${args.sat_ordinal}` : this.sql``}
ORDER BY ${this.sql.unsafe(orderBy)} ${this.sql.unsafe(order)}
LIMIT ${args.limit}
OFFSET ${args.offset}
LIMIT ${page.limit}
OFFSET ${page.offset}
`;
const total = unfiltered ? await this.getChainTipInscriptionCount() : results[0]?.total ?? 0;
return {
total: results[0]?.total ?? 0,
total,
results: results ?? [],
};
}
Expand Down
3 changes: 3 additions & 0 deletions tests/inscriptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,7 @@ describe('/inscriptions', () => {
sat_coinbase_height: 650000,
},
});
await db.updateChainTipInscriptionCount();

const response1 = await fastify.inject({
method: 'GET',
Expand Down Expand Up @@ -1483,6 +1484,7 @@ describe('/inscriptions', () => {
sat_coinbase_height: 650000,
},
});
await db.updateChainTipInscriptionCount();

const response1 = await fastify.inject({
method: 'GET',
Expand Down Expand Up @@ -1583,6 +1585,7 @@ describe('/inscriptions', () => {
sat_coinbase_height: 650000,
},
});
await db.updateChainTipInscriptionCount();

const response1 = await fastify.inject({
method: 'GET',
Expand Down
24 changes: 14 additions & 10 deletions tests/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,13 @@ describe('EventServer', () => {
});
expect(response.statusCode).toBe(200);

const query = await db.getInscriptions({
genesis_id: ['0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8i0'],
limit: 1,
offset: 0,
});
const query = await db.getInscriptions(
{
limit: 1,
offset: 0,
},
{ genesis_id: ['0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8i0'] }
);
const inscr = query.results[0];
expect(inscr).not.toBeUndefined();
expect(inscr.address).toBe('bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td');
Expand Down Expand Up @@ -317,11 +319,13 @@ describe('EventServer', () => {
});
expect(response.statusCode).toBe(200);

const query = await db.getInscriptions({
genesis_id: ['38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0'],
limit: 1,
offset: 0,
});
const query = await db.getInscriptions(
{
limit: 1,
offset: 0,
},
{ genesis_id: ['38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0'] }
);
const inscr = query.results[0];
expect(inscr).not.toBeUndefined();
expect(inscr.address).toBe('bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf00000');
Expand Down