Skip to content

Commit

Permalink
fix: create table for recursive inscriptions count (#237)
Browse files Browse the repository at this point in the history
* fix: use table to count recursive inscriptions

* fix: genesis addr count rollback

* style: tweak table name

* style: tweak type name
  • Loading branch information
rafaelcr committed Sep 25, 2023
1 parent 5bc7abb commit e27fa41
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 72 deletions.
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>();
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;
}

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);

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

0 comments on commit e27fa41

Please sign in to comment.