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: create table for recursive inscriptions count #237

Merged
merged 4 commits into from
Sep 25, 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
27 changes: 27 additions & 0 deletions migrations/1695655140203_counts-by-recursive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* 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.createTable('counts_by_recursive', {
recursive: {
type: 'boolean',
notNull: true,
primaryKey: true,
},
count: {
type: 'bigint',
notNull: true,
default: 1,
},
});
pgm.sql(`
INSERT INTO counts_by_recursive (recursive, count)
(SELECT recursive, COUNT(*) AS count FROM inscriptions GROUP BY recursive)
`);
}

export function down(pgm: MigrationBuilder): void {
pgm.dropTable('counts_by_recursive');
}
110 changes: 70 additions & 40 deletions src/pg/counts/counts-pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export class CountsPgStore extends BasePgStoreModule {
return await this.getSatRarityCount(filters?.sat_rarity);
case DbInscriptionIndexResultCountType.address:
return await this.getAddressCount(filters?.address);
case DbInscriptionIndexResultCountType.recursive:
return await this.getRecursiveCount(filters?.recursive);
case DbInscriptionIndexResultCountType.genesisAddress:
return await this.getGenesisAddressCount(filters?.genesis_address);
case DbInscriptionIndexResultCountType.blockHeight:
Expand All @@ -54,58 +56,76 @@ export class CountsPgStore extends BasePgStoreModule {

async applyInscriptions(writes: DbInscriptionInsert[]): Promise<void> {
if (writes.length === 0) return;
await this.sqlWriteTransaction(async sql => {
const mimeType = new Map<string, any>();
const rarity = new Map<string, any>();
const type = new Map<string, any>();
for (const i of writes) {
const t = i.number < 0 ? 'cursed' : 'blessed';
mimeType.set(i.mime_type, {
mime_type: i.mime_type,
count: mimeType.get(i.mime_type)?.count ?? 0 + 1,
});
rarity.set(i.sat_rarity, {
sat_rarity: i.sat_rarity,
count: rarity.get(i.sat_rarity)?.count ?? 0 + 1,
});
type.set(t, { type: t, count: type.get(t)?.count ?? 0 + 1 });
}
await sql`
INSERT INTO counts_by_mime_type ${sql([...mimeType.values()])}
const mimeType = new Map<string, any>();
rafaelcr marked this conversation as resolved.
Show resolved Hide resolved
const rarity = new Map<string, any>();
const recursion = new Map<boolean, any>();
const type = new Map<string, any>();
for (const i of writes) {
const t = i.number < 0 ? 'cursed' : 'blessed';
mimeType.set(i.mime_type, {
mime_type: i.mime_type,
count: mimeType.get(i.mime_type)?.count ?? 0 + 1,
});
rarity.set(i.sat_rarity, {
sat_rarity: i.sat_rarity,
count: rarity.get(i.sat_rarity)?.count ?? 0 + 1,
});
recursion.set(i.recursive, {
recursive: i.recursive,
count: recursion.get(i.recursive)?.count ?? 0 + 1,
});
type.set(t, { type: t, count: type.get(t)?.count ?? 0 + 1 });
}
// `counts_by_address` and `counts_by_genesis_address` count increases are handled in
// `applyLocations`.
await this.sql`
WITH increase_mime_type AS (
INSERT INTO counts_by_mime_type ${this.sql([...mimeType.values()])}
ON CONFLICT (mime_type) DO UPDATE SET count = counts_by_mime_type.count + EXCLUDED.count
`;
await sql`
INSERT INTO counts_by_sat_rarity ${sql([...rarity.values()])}
),
increase_rarity AS (
INSERT INTO counts_by_sat_rarity ${this.sql([...rarity.values()])}
ON CONFLICT (sat_rarity) DO UPDATE SET count = counts_by_sat_rarity.count + EXCLUDED.count
`;
await sql`
INSERT INTO counts_by_type ${sql([...type.values()])}
ON CONFLICT (type) DO UPDATE SET count = counts_by_type.count + EXCLUDED.count
`;
});
),
increase_recursive AS (
INSERT INTO counts_by_recursive ${this.sql([...recursion.values()])}
ON CONFLICT (recursive) DO UPDATE SET count = counts_by_recursive.count + EXCLUDED.count
)
INSERT INTO counts_by_type ${this.sql([...type.values()])}
ON CONFLICT (type) DO UPDATE SET count = counts_by_type.count + EXCLUDED.count
`;
}

async rollBackInscription(args: { inscription: DbInscription }): Promise<void> {
await this.sqlWriteTransaction(async sql => {
await sql`
UPDATE counts_by_mime_type SET count = count - 1 WHERE mime_type = ${args.inscription.mime_type}
`;
await sql`
UPDATE counts_by_sat_rarity SET count = count - 1 WHERE sat_rarity = ${args.inscription.sat_rarity}
`;
await sql`
await this.sql`
WITH decrease_mime_type AS (
UPDATE counts_by_mime_type SET count = count - 1
WHERE mime_type = ${args.inscription.mime_type}
),
decrease_rarity AS (
UPDATE counts_by_sat_rarity SET count = count - 1
WHERE sat_rarity = ${args.inscription.sat_rarity}
),
decrease_recursive AS (
UPDATE counts_by_recursive SET count = count - 1
WHERE recursive = ${args.inscription.recursive}
),
decrease_type AS (
UPDATE counts_by_type SET count = count - 1 WHERE type = ${
parseInt(args.inscription.number) < 0
? DbInscriptionType.cursed
: DbInscriptionType.blessed
}
`;
await sql`
UPDATE counts_by_address SET count = count - 1 WHERE address = (
),
decrease_genesis AS (
UPDATE counts_by_genesis_address SET count = count - 1 WHERE address = (
SELECT address FROM current_locations WHERE inscription_id = ${args.inscription.id}
)
`;
});
)
UPDATE counts_by_address SET count = count - 1 WHERE address = (
SELECT address FROM current_locations WHERE inscription_id = ${args.inscription.id}
)
`;
}

async applyLocations(
Expand Down Expand Up @@ -213,6 +233,16 @@ export class CountsPgStore extends BasePgStoreModule {
return result[0].count;
}

private async getRecursiveCount(recursive?: boolean): Promise<number> {
const rec = recursive !== undefined ? [recursive] : [true, false];
const result = await this.sql<{ count: number }[]>`
SELECT COALESCE(SUM(count), 0) AS count
FROM counts_by_recursive
WHERE recursive IN ${this.sql(rec)}
`;
return result[0].count;
rafaelcr marked this conversation as resolved.
Show resolved Hide resolved
}

private async getAddressCount(address?: string[]): Promise<number> {
if (!address) return 0;
const result = await this.sql<{ count: number }[]>`
Expand Down
1 change: 1 addition & 0 deletions src/pg/counts/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function getIndexResultCountType(
if (filters.to_genesis_block_height) return DbInscriptionIndexResultCountType.toblockHeight;
if (filters.genesis_block_hash) return DbInscriptionIndexResultCountType.blockHash;
if (filters.cursed !== undefined) return DbInscriptionIndexResultCountType.cursed;
if (filters.recursive !== undefined) return DbInscriptionIndexResultCountType.recursive;
if (filters.number || filters.genesis_id || filters.output || filters.sat_ordinal)
return DbInscriptionIndexResultCountType.singleResult;
case 2:
Expand Down
2 changes: 2 additions & 0 deletions src/pg/counts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export enum DbInscriptionIndexResultCountType {
blockHeightRange,
/** Filtered by block hash */
blockHash,
/** Filtered by recursive */
recursive,
/** Filtered by some other param that yields a single result (easy to count) */
singleResult,
/** Filtered by custom arguments (tough to count) */
Expand Down
2 changes: 2 additions & 0 deletions src/pg/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export type DbInscription = {
sat_ordinal: string;
sat_rarity: string;
sat_coinbase_height: string;
recursive: boolean;
};

export type DbInscriptionContent = {
Expand All @@ -178,6 +179,7 @@ export const INSCRIPTIONS_COLUMNS = [
'sat_ordinal',
'sat_rarity',
'sat_coinbase_height',
'recursive',
];

export type DbInscriptionIndexPaging = {
Expand Down
74 changes: 42 additions & 32 deletions tests/inscriptions.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cycleMigrations } from '@hirosystems/api-toolkit';
import { buildApiServer } from '../src/api/init';
import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store';
import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers';
import { TestChainhookPayloadBuilder, TestFastifyServer, rollBack } from './helpers';
import {
BitcoinInscriptionRevealed,
BitcoinInscriptionTransferred,
Expand Down Expand Up @@ -2958,37 +2958,36 @@ describe('/inscriptions', () => {
})
.build()
);
await db.updateInscriptions(
new TestChainhookPayloadBuilder()
.apply()
.block({
height: 775617,
hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d',
timestamp: 1675312161,
})
.transaction({
hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
})
.inscriptionRevealed({
content_bytes: '0x48656C6C6F',
content_type: 'image/png',
content_length: 5,
inscription_number: 1,
inscription_fee: 2805,
inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
inscription_output_value: 10000,
inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
ordinal_number: 1000000000000,
ordinal_block_height: 650000,
ordinal_offset: 0,
satpoint_post_inscription:
'38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0',
inscription_input_index: 0,
transfers_pre_inscription: 0,
tx_index: 0,
})
.build()
);
const genesis2 = new TestChainhookPayloadBuilder()
.apply()
.block({
height: 775617,
hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d',
timestamp: 1675312161,
})
.transaction({
hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
})
.inscriptionRevealed({
content_bytes: '0x48656C6C6F',
content_type: 'image/png',
content_length: 5,
inscription_number: 1,
inscription_fee: 2805,
inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
inscription_output_value: 10000,
inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
ordinal_number: 1000000000000,
ordinal_block_height: 650000,
ordinal_offset: 0,
satpoint_post_inscription:
'38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0',
inscription_input_index: 0,
transfers_pre_inscription: 0,
tx_index: 0,
})
.build();
await db.updateInscriptions(genesis2);
janniks marked this conversation as resolved.
Show resolved Hide resolved

const response1 = await fastify.inject({
method: 'GET',
Expand All @@ -3010,6 +3009,17 @@ describe('/inscriptions', () => {
const responseJson2 = response2.json();
expect(responseJson2.total).toBe(2);
expect(responseJson2.results.length).toBe(2);

// Roll back genesis and check if count is still correct
await db.updateInscriptions(rollBack(genesis2));
const response3 = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/inscriptions?genesis_address=bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
});
expect(response3.statusCode).toBe(200);
const responseJson3 = response3.json();
expect(responseJson3.total).toBe(0);
expect(responseJson3.results.length).toBe(0);
});
});

Expand Down
Loading