Skip to content

Commit

Permalink
fix: move to many-to-many genesis and current table (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelcr committed Jul 9, 2023
1 parent 4a25913 commit 2b6b6ec
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 39 deletions.
40 changes: 40 additions & 0 deletions migrations/1688925112931_genesis-locations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* 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('genesis', {
inscription_id: {
type: 'bigint',
notNull: true,
},
location_id: {
type: 'bigint',
notNull: true,
},
block_height: {
type: 'bigint',
notNull: true,
},
});
pgm.createConstraint('genesis', 'genesis_inscription_id_unique', 'UNIQUE(inscription_id)');
pgm.createIndex('genesis', ['location_id']);

pgm.createTable('current', {
inscription_id: {
type: 'bigint',
notNull: true,
},
location_id: {
type: 'bigint',
notNull: true,
},
block_height: {
type: 'bigint',
notNull: true,
},
});
pgm.createConstraint('current', 'current_inscription_id_unique', 'UNIQUE(inscription_id)');
pgm.createIndex('current', ['location_id']);
}
115 changes: 79 additions & 36 deletions src/pg/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,6 @@ export class PgStore extends BasePgStore {
await this.refreshMaterializedView('chain_tip');
// Skip expensive view refreshes if we're not streaming any live blocks yet.
if (payload.chainhook.is_streaming_blocks) {
await this.refreshMaterializedView('genesis_locations');
await this.refreshMaterializedView('current_locations');
await this.refreshMaterializedView('inscription_count');
await this.refreshMaterializedView('mime_type_counts');
await this.refreshMaterializedView('sat_rarity_counts');
Expand Down Expand Up @@ -346,6 +344,14 @@ export class PgStore extends BasePgStore {
// `ORDER` statement
const order = sort?.order === Order.asc ? sql`ASC` : sql`DESC`;
const results = await sql<({ total: number } & DbFullyLocatedInscriptionResult)[]>`
WITH gen_locations AS (
SELECT l.* FROM locations AS l
INNER JOIN genesis AS g ON l.id = g.location_id
),
cur_locations AS (
SELECT l.* FROM locations AS l
INNER JOIN current AS c ON l.id = c.location_id
)
SELECT
i.genesis_id,
i.number,
Expand Down Expand Up @@ -374,8 +380,8 @@ export class PgStore extends BasePgStore {
: sql`0 as total`
}
FROM inscriptions AS i
INNER JOIN current_locations AS loc ON loc.inscription_id = i.id
INNER JOIN genesis_locations AS gen ON gen.inscription_id = i.id
INNER JOIN cur_locations AS loc ON loc.inscription_id = i.id
INNER JOIN gen_locations AS gen ON gen.inscription_id = i.id
WHERE TRUE
${
filters?.genesis_id?.length
Expand Down Expand Up @@ -523,7 +529,7 @@ export class PgStore extends BasePgStore {
FROM locations AS l
INNER JOIN inscriptions AS i ON l.inscription_id = i.id
WHERE
NOT EXISTS (SELECT id FROM genesis_locations WHERE id = l.id)
NOT EXISTS (SELECT location_id FROM genesis WHERE location_id = l.id)
AND
${
'block_height' in args
Expand Down Expand Up @@ -660,7 +666,7 @@ export class PgStore extends BasePgStore {
sat_coinbase_height: args.location.sat_coinbase_height,
timestamp: sql`to_timestamp(${args.location.timestamp})`,
};
await sql<DbLocation[]>`
const locationRes = await sql<{ id: string }[]>`
INSERT INTO locations ${sql(location)}
ON CONFLICT ON CONSTRAINT locations_output_offset_unique DO UPDATE SET
inscription_id = EXCLUDED.inscription_id,
Expand All @@ -673,7 +679,13 @@ export class PgStore extends BasePgStore {
sat_rarity = EXCLUDED.sat_rarity,
sat_coinbase_height = EXCLUDED.sat_coinbase_height,
timestamp = EXCLUDED.timestamp
RETURNING id
`;
await this.updateInscriptionLocationPointers({
inscription_id,
location_id: locationRes[0].id,
block_height: args.location.block_height,
});
const json = inscriptionContentToJson(args.inscription);
if (json) {
const values = {
Expand All @@ -698,36 +710,44 @@ export class PgStore extends BasePgStore {
inscription_id: number;
location: DbLocationInsert;
}): Promise<void> {
const location = {
inscription_id: args.inscription_id,
block_height: args.location.block_height,
block_hash: args.location.block_hash,
tx_id: args.location.tx_id,
address: args.location.address,
output: args.location.output,
offset: args.location.offset,
prev_output: args.location.prev_output,
prev_offset: args.location.prev_offset,
value: args.location.value,
sat_ordinal: args.location.sat_ordinal,
sat_rarity: args.location.sat_rarity,
sat_coinbase_height: args.location.sat_coinbase_height,
timestamp: this.sql`to_timestamp(${args.location.timestamp})`,
};
await this.sql`
INSERT INTO locations ${this.sql(location)}
ON CONFLICT ON CONSTRAINT locations_output_offset_unique DO UPDATE SET
inscription_id = EXCLUDED.inscription_id,
block_height = EXCLUDED.block_height,
block_hash = EXCLUDED.block_hash,
tx_id = EXCLUDED.tx_id,
address = EXCLUDED.address,
value = EXCLUDED.value,
sat_ordinal = EXCLUDED.sat_ordinal,
sat_rarity = EXCLUDED.sat_rarity,
sat_coinbase_height = EXCLUDED.sat_coinbase_height,
timestamp = EXCLUDED.timestamp
`;
await this.sqlWriteTransaction(async sql => {
const location = {
inscription_id: args.inscription_id,
block_height: args.location.block_height,
block_hash: args.location.block_hash,
tx_id: args.location.tx_id,
address: args.location.address,
output: args.location.output,
offset: args.location.offset,
prev_output: args.location.prev_output,
prev_offset: args.location.prev_offset,
value: args.location.value,
sat_ordinal: args.location.sat_ordinal,
sat_rarity: args.location.sat_rarity,
sat_coinbase_height: args.location.sat_coinbase_height,
timestamp: this.sql`to_timestamp(${args.location.timestamp})`,
};
const locationRes = await sql<{ id: string }[]>`
INSERT INTO locations ${this.sql(location)}
ON CONFLICT ON CONSTRAINT locations_output_offset_unique DO UPDATE SET
inscription_id = EXCLUDED.inscription_id,
block_height = EXCLUDED.block_height,
block_hash = EXCLUDED.block_hash,
tx_id = EXCLUDED.tx_id,
address = EXCLUDED.address,
value = EXCLUDED.value,
sat_ordinal = EXCLUDED.sat_ordinal,
sat_rarity = EXCLUDED.sat_rarity,
sat_coinbase_height = EXCLUDED.sat_coinbase_height,
timestamp = EXCLUDED.timestamp
RETURNING id
`;
await this.updateInscriptionLocationPointers({
inscription_id: args.inscription_id,
location_id: locationRes[0].id,
block_height: args.location.block_height,
});
});
}

private async rollBackInscriptionGenesis(args: { genesis_id: string }): Promise<void> {
Expand Down Expand Up @@ -759,4 +779,27 @@ export class PgStore extends BasePgStore {
});
return inscription_id;
}

private async updateInscriptionLocationPointers(args: {
inscription_id: number;
location_id: string;
block_height: number;
}): Promise<void> {
await this.sqlWriteTransaction(async sql => {
await sql`
INSERT INTO genesis ${sql(args)}
ON CONFLICT ON CONSTRAINT genesis_inscription_id_unique DO UPDATE SET
location_id = EXCLUDED.location_id,
block_height = EXCLUDED.block_height
WHERE EXCLUDED.block_height < genesis.block_height
`;
await sql`
INSERT INTO current ${sql(args)}
ON CONFLICT ON CONSTRAINT current_inscription_id_unique DO UPDATE SET
location_id = EXCLUDED.location_id,
block_height = EXCLUDED.block_height
WHERE EXCLUDED.block_height > current.block_height
`;
});
}
}
117 changes: 114 additions & 3 deletions tests/inscriptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,117 @@ describe('/inscriptions', () => {
});
});

test('shows correct inscription data after an unordered transfer', async () => {
await db.updateInscriptions(
new TestChainhookPayloadBuilder()
.apply()
.block({
height: 775617,
hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d',
timestamp: 1676913207,
})
.transaction({
hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
})
.inscriptionRevealed({
content_bytes: '0x48656C6C6F',
content_type: 'image/png',
content_length: 5,
inscription_number: 7,
inscription_fee: 2805,
inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
inscription_output_value: 9000,
inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
ordinal_number: 257418248345364,
ordinal_block_height: 51483,
ordinal_offset: 0,
satpoint_post_inscription:
'38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0',
})
.build()
);

const response1 = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
});
expect(response1.statusCode).toBe(200);
expect(response1.json()).toStrictEqual({
address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d',
genesis_block_height: 775617,
content_length: 5,
mime_type: 'image/png',
content_type: 'image/png',
genesis_fee: '2805',
id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
offset: '0',
number: 7,
value: '9000',
tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
sat_ordinal: '257418248345364',
sat_coinbase_height: 51483,
output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0',
location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0',
sat_rarity: 'common',
timestamp: 1676913207000,
genesis_timestamp: 1676913207000,
genesis_tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
curse_type: null,
});

// Insert real genesis
await db.updateInscriptions(
new TestChainhookPayloadBuilder()
.apply()
.block({ height: 775610, timestamp: 1678122360 })
.transaction({
hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444',
})
.inscriptionTransferred({
inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd',
satpoint_pre_transfer:
'38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0',
satpoint_post_transfer:
'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0',
post_transfer_output_value: 9000,
ordinal_number: null,
})
.build()
);
const response = await fastify.inject({
method: 'GET',
url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
});
expect(response.statusCode).toBe(200);
expect(response.json()).toStrictEqual({
address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
genesis_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd',
genesis_block_hash: '163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88',
genesis_block_height: 775610,
content_length: 5,
mime_type: 'image/png',
content_type: 'image/png',
genesis_fee: '2805',
id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
offset: '0',
number: 7,
value: '9000',
tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
sat_ordinal: '257418248345364',
sat_coinbase_height: 51483,
output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0',
location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0',
sat_rarity: 'common',
timestamp: 1676913207000,
genesis_timestamp: 1678122360000,
genesis_tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444',
curse_type: null,
});
});

test('shows correct cursed inscription data after a transfer', async () => {
await db.updateInscriptions(
new TestChainhookPayloadBuilder()
Expand Down Expand Up @@ -2422,7 +2533,7 @@ describe('/inscriptions', () => {
expect(response1.statusCode).toBe(200);
const responseJson1 = response1.json();
expect(responseJson1.total).toBe(0);
expect(responseJson1.results.length).toBe(0);
expect(responseJson1.results.length).toBeGreaterThan(0);

const response2 = await fastify.inject({
method: 'GET',
Expand All @@ -2431,7 +2542,7 @@ describe('/inscriptions', () => {
expect(response2.statusCode).toBe(200);
const responseJson2 = response2.json();
expect(responseJson2.total).toBe(0);
expect(responseJson2.results.length).toBe(0);
expect(responseJson2.results.length).toBeGreaterThan(0);

const response3 = await fastify.inject({
method: 'GET',
Expand All @@ -2440,7 +2551,7 @@ describe('/inscriptions', () => {
expect(response3.statusCode).toBe(200);
const responseJson3 = response3.json();
expect(responseJson3.total).toBe(0);
expect(responseJson3.results.length).toBe(0);
expect(responseJson3.results.length).toBeGreaterThan(0);
});
});
});
Expand Down

0 comments on commit 2b6b6ec

Please sign in to comment.