From e70c2229b03a563f0218c5fcfabb2875451bb77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Tue, 16 Jan 2024 16:38:29 -0600 Subject: [PATCH 1/8] fix: offload ticker lookup from BRC-20 activity query (#293) --- ...63472553_locations-block-height-indexes.ts | 22 +++ src/pg/brc20/brc20-pg-store.ts | 153 +++++++++--------- 2 files changed, 101 insertions(+), 74 deletions(-) create mode 100644 migrations/1705363472553_locations-block-height-indexes.ts diff --git a/migrations/1705363472553_locations-block-height-indexes.ts b/migrations/1705363472553_locations-block-height-indexes.ts new file mode 100644 index 00000000..304f3cac --- /dev/null +++ b/migrations/1705363472553_locations-block-height-indexes.ts @@ -0,0 +1,22 @@ +/* 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.dropIndex('locations', ['block_hash']); + pgm.dropIndex('locations', ['block_height']); + pgm.createIndex('locations', [ + { name: 'block_height', sort: 'DESC' }, + { name: 'tx_index', sort: 'DESC' }, + ]); +} + +export function down(pgm: MigrationBuilder): void { + pgm.dropIndex('locations', [ + { name: 'block_height', sort: 'DESC' }, + { name: 'tx_index', sort: 'DESC' }, + ]); + pgm.createIndex('locations', ['block_hash']); + pgm.createIndex('locations', ['block_height']); +} diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index 706b19e0..03325956 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -761,9 +761,9 @@ export class Brc20PgStore extends BasePgStoreModule { address?: string; } ): Promise> { + // Do we need a specific result count such as total activity or activity per address? objRemoveUndefinedValues(filters); const filterLength = Object.keys(filters).length; - // Do we need a specific result count such as total activity or activity per address? const needsGlobalEventCount = filterLength === 0 || (filterLength === 1 && filters.operation && filters.operation.length > 0); @@ -775,86 +775,91 @@ export class Brc20PgStore extends BasePgStoreModule { filters.address != undefined && filters.address != ''); const needsTickerCount = filterLength === 1 && filters.ticker && filters.ticker.length > 0; + // Which operations do we need if we're filtering by address? const sanitizedOperations: DbBrc20EventOperation[] = []; for (const i of filters.operation ?? BRC20_OPERATIONS) if (BRC20_OPERATIONS.includes(i)) sanitizedOperations?.push(i as DbBrc20EventOperation); + // Which tickers are we filtering for? const tickerConditions = this.sqlOr( - filters.ticker?.map(t => this.sql`d.ticker_lower = LOWER(${t})`) + filters.ticker?.map(t => this.sql`ticker_lower = LOWER(${t})`) ); - const results = await this.sql<(DbBrc20Activity & { total: number })[]>` - WITH event_count AS (${ - // Select count from the correct count cache table. - needsGlobalEventCount - ? this.sql` - SELECT COALESCE(SUM(count), 0) AS count - FROM brc20_counts_by_event_type - ${ - filters.operation - ? this.sql`WHERE event_type IN ${this.sql(filters.operation)}` - : this.sql`` - } - ` - : needsAddressEventCount - ? this.sql` - SELECT COALESCE(${this.sql.unsafe(sanitizedOperations.join('+'))}, 0) AS count - FROM brc20_counts_by_address_event_type - WHERE address = ${filters.address} - ` - : needsTickerCount - ? this.sql` - SELECT COALESCE(SUM(tx_count), 0) AS count - FROM brc20_deploys AS d - WHERE (${tickerConditions}) - ` - : this.sql`SELECT NULL AS count` - }) - SELECT - e.operation, - d.ticker, - l.genesis_id AS inscription_id, - l.block_height, - l.block_hash, - l.tx_id, - l.address, - l.timestamp, - l.output, - l.offset, - d.max AS deploy_max, - d.limit AS deploy_limit, - d.decimals AS deploy_decimals, - (SELECT amount FROM brc20_mints WHERE id = e.mint_id) AS mint_amount, - (SELECT amount || ';' || from_address || ';' || COALESCE(to_address, '') FROM brc20_transfers WHERE id = e.transfer_id) AS transfer_data, - ${ - needsGlobalEventCount || needsAddressEventCount || needsTickerCount - ? this.sql`(SELECT count FROM event_count)` - : this.sql`COUNT(*) OVER()` - } AS total - FROM brc20_events AS e - INNER JOIN brc20_deploys AS d ON e.brc20_deploy_id = d.id - INNER JOIN locations AS l ON e.genesis_location_id = l.id - WHERE TRUE - ${ - filters.operation ? this.sql`AND operation IN ${this.sql(filters.operation)}` : this.sql`` - } - ${tickerConditions ? this.sql`AND (${tickerConditions})` : this.sql``} - ${ - filters.block_height ? this.sql`AND l.block_height = ${filters.block_height}` : this.sql`` - } - ${ - filters.address - ? this.sql`AND (e.address = ${filters.address} OR e.from_address = ${filters.address})` - : this.sql`` - } - ORDER BY l.block_height DESC, l.tx_index DESC - LIMIT ${page.limit} - OFFSET ${page.offset} - `; - return { - total: results[0]?.total ?? 0, - results: results ?? [], - }; + return this.sqlTransaction(async sql => { + // The postgres query planner has trouble selecting an optimal plan when the WHERE condition + // checks any column from the `brc20_deploys` table. If the user is filtering by ticker, we + // should get the token IDs first and use those to filter directly in the `brc20_events` + // table. + const tickerIds = tickerConditions + ? (await sql<{ id: string }[]>`SELECT id FROM brc20_deploys WHERE ${tickerConditions}`).map( + i => i.id + ) + : undefined; + const results = await sql<(DbBrc20Activity & { total: number })[]>` + WITH event_count AS (${ + // Select count from the correct count cache table. + needsGlobalEventCount + ? sql` + SELECT COALESCE(SUM(count), 0) AS count + FROM brc20_counts_by_event_type + ${filters.operation ? sql`WHERE event_type IN ${sql(filters.operation)}` : sql``} + ` + : needsAddressEventCount + ? sql` + SELECT COALESCE(${sql.unsafe(sanitizedOperations.join('+'))}, 0) AS count + FROM brc20_counts_by_address_event_type + WHERE address = ${filters.address} + ` + : needsTickerCount && tickerIds !== undefined + ? sql` + SELECT COALESCE(SUM(tx_count), 0) AS count + FROM brc20_deploys AS d + WHERE id IN ${sql(tickerIds)} + ` + : sql`SELECT NULL AS count` + }) + SELECT + e.operation, + d.ticker, + l.genesis_id AS inscription_id, + l.block_height, + l.block_hash, + l.tx_id, + l.address, + l.timestamp, + l.output, + l.offset, + d.max AS deploy_max, + d.limit AS deploy_limit, + d.decimals AS deploy_decimals, + (SELECT amount FROM brc20_mints WHERE id = e.mint_id) AS mint_amount, + (SELECT amount || ';' || from_address || ';' || COALESCE(to_address, '') FROM brc20_transfers WHERE id = e.transfer_id) AS transfer_data, + ${ + needsGlobalEventCount || needsAddressEventCount || needsTickerCount + ? sql`(SELECT count FROM event_count)` + : sql`COUNT(*) OVER()` + } AS total + FROM brc20_events AS e + INNER JOIN brc20_deploys AS d ON e.brc20_deploy_id = d.id + INNER JOIN locations AS l ON e.genesis_location_id = l.id + WHERE TRUE + ${filters.operation ? sql`AND e.operation IN ${sql(filters.operation)}` : sql``} + ${tickerIds ? sql`AND e.brc20_deploy_id IN ${sql(tickerIds)}` : sql``} + ${filters.block_height ? sql`AND l.block_height = ${filters.block_height}` : sql``} + ${ + filters.address + ? sql`AND (e.address = ${filters.address} OR e.from_address = ${filters.address})` + : sql`` + } + ORDER BY l.block_height DESC, l.tx_index DESC + LIMIT ${page.limit} + OFFSET ${page.offset} + `; + return { + total: results[0]?.total ?? 0, + results: results ?? [], + }; + }); } } From 06f1af9409d8fdf059664542f7573105cab72d90 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 16 Jan 2024 22:41:15 +0000 Subject: [PATCH 2/8] chore(release): 2.1.2-beta.1 [skip ci] ## [2.1.2-beta.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.1...v2.1.2-beta.1) (2024-01-16) ### Bug Fixes * offload ticker lookup from BRC-20 activity query ([#293](https://github.com/hirosystems/ordinals-api/issues/293)) ([e70c222](https://github.com/hirosystems/ordinals-api/commit/e70c2229b03a563f0218c5fcfabb2875451bb77c)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fc764c8..6c3708b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.1.2-beta.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.1...v2.1.2-beta.1) (2024-01-16) + + +### Bug Fixes + +* offload ticker lookup from BRC-20 activity query ([#293](https://github.com/hirosystems/ordinals-api/issues/293)) ([e70c222](https://github.com/hirosystems/ordinals-api/commit/e70c2229b03a563f0218c5fcfabb2875451bb77c)) + ## [2.1.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.0...v2.1.1) (2024-01-09) From b0c0ffd198a9a98a88b5123927a8fdc5b68ef22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Fri, 2 Feb 2024 10:29:43 -0600 Subject: [PATCH 3/8] feat: support transfers by ordinal number instead of inscription id (#296) * feat: transfers by ordinal number * fix: upgrade client --- package-lock.json | 14 +-- package.json | 2 +- src/pg/brc20/brc20-pg-store.ts | 23 ++--- src/pg/brc20/helpers.ts | 4 +- src/pg/counts/counts-pg-store.ts | 10 +- src/pg/helpers.ts | 25 +++-- src/pg/pg-store.ts | 103 +++++++++++++------- src/pg/types.ts | 109 +++++++++++++-------- tests/brc20.test.ts | 158 +++++++++++++++++++++++-------- tests/cache.test.ts | 6 +- tests/helpers.ts | 3 +- tests/inscriptions.test.ts | 28 +++--- tests/server.test.ts | 4 +- 13 files changed, 323 insertions(+), 166 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c147abe..5681507e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", "@hirosystems/api-toolkit": "^1.3.1", - "@hirosystems/chainhook-client": "^1.5.0", + "@hirosystems/chainhook-client": "^1.6.0", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", @@ -1299,9 +1299,9 @@ } }, "node_modules/@hirosystems/chainhook-client": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.5.0.tgz", - "integrity": "sha512-iAhY3848Y64wiNhNx9PpntlxXGjdVPaxX1JfoivRh5S73Vo2BBEWrwYrcpyJrlYM0PnRNOry7/OUHGN33xwO3Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.6.0.tgz", + "integrity": "sha512-8skXF0Hk5XL5LH6enYySyuwEy3Kzn8AAb5+ERG0w9WSfehBNjLkL3zKJFuwg8Ov926YzE7a2ZnzlUpVmvvlulw==", "dependencies": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", @@ -19743,9 +19743,9 @@ } }, "@hirosystems/chainhook-client": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.5.0.tgz", - "integrity": "sha512-iAhY3848Y64wiNhNx9PpntlxXGjdVPaxX1JfoivRh5S73Vo2BBEWrwYrcpyJrlYM0PnRNOry7/OUHGN33xwO3Q==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.6.0.tgz", + "integrity": "sha512-8skXF0Hk5XL5LH6enYySyuwEy3Kzn8AAb5+ERG0w9WSfehBNjLkL3zKJFuwg8Ov926YzE7a2ZnzlUpVmvvlulw==", "requires": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", diff --git a/package.json b/package.json index 67adef79..17dad3d2 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", "@hirosystems/api-toolkit": "^1.3.1", - "@hirosystems/chainhook-client": "^1.5.0", + "@hirosystems/chainhook-client": "^1.6.0", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index 03325956..357795a4 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -3,12 +3,13 @@ import * as postgres from 'postgres'; import { hexToBuffer } from '../../api/util/helpers'; import { DbInscriptionIndexPaging, - DbInscriptionInsert, - DbLocationInsert, + InscriptionData, DbLocationPointerInsert, DbLocationTransferType, DbPaginatedResult, - DbRevealInsert, + InscriptionEventData, + LocationData, + InscriptionRevealData, } from '../types'; import { BRC20_DEPLOYS_COLUMNS, @@ -39,13 +40,13 @@ export class Brc20PgStore extends BasePgStoreModule { } async insertOperations(args: { - reveals: DbRevealInsert[]; + reveals: InscriptionEventData[]; pointers: DbLocationPointerInsert[]; }): Promise { for (const [i, reveal] of args.reveals.entries()) { const pointer = args.pointers[i]; if (parseInt(pointer.block_height) < BRC20_GENESIS_BLOCK) continue; - if (reveal.inscription) { + if ('inscription' in reveal) { if ( reveal.inscription.classic_number < 0 || reveal.inscription.number < 0 || @@ -75,7 +76,7 @@ export class Brc20PgStore extends BasePgStoreModule { } async applyTransfer(args: { - reveal: DbRevealInsert; + reveal: InscriptionEventData; pointer: DbLocationPointerInsert; }): Promise { await this.sqlWriteTransaction(async sql => { @@ -207,7 +208,7 @@ export class Brc20PgStore extends BasePgStoreModule { private async insertDeploy(deploy: { brc20: Brc20Deploy; - reveal: DbRevealInsert; + reveal: InscriptionEventData; pointer: DbLocationPointerInsert; }): Promise { if (deploy.reveal.location.transfer_type != DbLocationTransferType.transferred) return; @@ -257,7 +258,7 @@ export class Brc20PgStore extends BasePgStoreModule { private async insertMint(mint: { brc20: Brc20Mint; - reveal: DbRevealInsert; + reveal: InscriptionEventData; pointer: DbLocationPointerInsert; }): Promise { if (mint.reveal.location.transfer_type != DbLocationTransferType.transferred) return; @@ -335,7 +336,7 @@ export class Brc20PgStore extends BasePgStoreModule { private async insertTransfer(transfer: { brc20: Brc20Transfer; - reveal: DbRevealInsert; + reveal: InscriptionEventData; pointer: DbLocationPointerInsert; }): Promise { if (transfer.reveal.location.transfer_type != DbLocationTransferType.transferred) return; @@ -406,7 +407,7 @@ export class Brc20PgStore extends BasePgStoreModule { ); } - async rollBackInscription(args: { inscription: DbInscriptionInsert }): Promise { + async rollBackInscription(args: { inscription: InscriptionData }): Promise { const events = await this.sql` SELECT e.* FROM brc20_events AS e INNER JOIN inscriptions AS i ON i.id = e.inscription_id @@ -430,7 +431,7 @@ export class Brc20PgStore extends BasePgStoreModule { } } - async rollBackLocation(args: { location: DbLocationInsert }): Promise { + async rollBackLocation(args: { location: LocationData }): Promise { const events = await this.sql` SELECT e.* FROM brc20_events AS e INNER JOIN locations AS l ON l.id = e.genesis_location_id diff --git a/src/pg/brc20/helpers.ts b/src/pg/brc20/helpers.ts index de9d50ab..3b1f1958 100644 --- a/src/pg/brc20/helpers.ts +++ b/src/pg/brc20/helpers.ts @@ -2,7 +2,7 @@ import { Static, Type } from '@fastify/type-provider-typebox'; import { TypeCompiler } from '@sinclair/typebox/compiler'; import BigNumber from 'bignumber.js'; import { hexToBuffer } from '../../api/util/helpers'; -import { DbInscriptionInsert } from '../types'; +import { InscriptionData } from '../types'; const Brc20TickerSchema = Type.String({ minLength: 1 }); const Brc20NumberSchema = Type.RegEx(/^((\d+)|(\d*\.?\d+))$/); @@ -51,7 +51,7 @@ const UINT64_MAX = BigNumber('18446744073709551615'); // 20 digits const numExceedsMax = (num: string) => num.length >= 20 && UINT64_MAX.isLessThan(num); // For testing only -export function brc20FromInscription(inscription: DbInscriptionInsert): Brc20 | undefined { +export function brc20FromInscription(inscription: InscriptionData): Brc20 | undefined { if (inscription.number < 0) return; if (inscription.mime_type !== 'text/plain' && inscription.mime_type !== 'application/json') return; diff --git a/src/pg/counts/counts-pg-store.ts b/src/pg/counts/counts-pg-store.ts index 719b1b58..6994959c 100644 --- a/src/pg/counts/counts-pg-store.ts +++ b/src/pg/counts/counts-pg-store.ts @@ -3,9 +3,9 @@ import { SatoshiRarity } from '../../api/util/ordinal-satoshi'; import { DbInscription, DbInscriptionIndexFilters, - DbInscriptionInsert, + InscriptionData, DbInscriptionType, - DbLocationInsert, + RevealLocationData, DbLocationPointer, } from '../types'; import { DbInscriptionIndexResultCountType } from './types'; @@ -55,7 +55,7 @@ export class CountsPgStore extends BasePgStoreModule { } } - async applyInscriptions(writes: DbInscriptionInsert[]): Promise { + async applyInscriptions(writes: InscriptionData[]): Promise { if (writes.length === 0) return; const mimeType = new Map(); const rarity = new Map(); @@ -105,8 +105,8 @@ export class CountsPgStore extends BasePgStoreModule { } async rollBackInscription(args: { - inscription: DbInscriptionInsert; - location: DbLocationInsert; + inscription: InscriptionData; + location: RevealLocationData; }): Promise { await this.sql` WITH decrease_mime_type AS ( diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index 8704135b..4cef0600 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -7,7 +7,12 @@ import { BitcoinInscriptionTransferred, } from '@hirosystems/chainhook-client'; import { ENV } from '../env'; -import { DbLocationTransferType, DbRevealInsert } from './types'; +import { + DbLocationTransferType, + InscriptionEventData, + InscriptionTransferData, + InscriptionRevealData, +} from './types'; import { OrdinalSatoshi } from '../api/util/ordinal-satoshi'; /** @@ -83,13 +88,13 @@ export function removeNullBytes(input: string): string { return input.replace(/\x00/g, ''); } -function revealInsertFromOrdhookInscriptionRevealed(args: { +function updateFromOrdhookInscriptionRevealed(args: { block_height: number; block_hash: string; tx_id: string; timestamp: number; reveal: BitcoinInscriptionRevealed; -}): DbRevealInsert { +}): InscriptionRevealData { const satoshi = new OrdinalSatoshi(args.reveal.ordinal_number); const satpoint = parseSatPoint(args.reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(args.reveal.content_bytes); @@ -130,14 +135,14 @@ function revealInsertFromOrdhookInscriptionRevealed(args: { }; } -function revealInsertFromOrdhookInscriptionTransferred(args: { +function updateFromOrdhookInscriptionTransferred(args: { block_height: number; block_hash: string; tx_id: string; timestamp: number; blockTransferIndex: number; transfer: BitcoinInscriptionTransferred; -}): DbRevealInsert { +}): InscriptionTransferData { const satpoint = parseSatPoint(args.transfer.satpoint_post_transfer); const prevSatpoint = parseSatPoint(args.transfer.satpoint_pre_transfer); return { @@ -147,7 +152,7 @@ function revealInsertFromOrdhookInscriptionTransferred(args: { tx_id: args.tx_id, tx_index: args.transfer.tx_index, block_transfer_index: args.blockTransferIndex, - genesis_id: args.transfer.inscription_id, + ordinal_number: args.transfer.ordinal_number.toString(), address: args.transfer.destination.value ?? null, output: `${satpoint.tx_id}:${satpoint.vout}`, offset: satpoint.offset ?? null, @@ -164,18 +169,18 @@ function revealInsertFromOrdhookInscriptionTransferred(args: { }; } -export function revealInsertsFromOrdhookEvent(event: BitcoinEvent): DbRevealInsert[] { +export function revealInsertsFromOrdhookEvent(event: BitcoinEvent): InscriptionEventData[] { // Keep the relative ordering of a transfer within a block for faster future reads. let blockTransferIndex = 0; const block_height = event.block_identifier.index; const block_hash = normalizedHexString(event.block_identifier.hash); - const writes: DbRevealInsert[] = []; + const writes: InscriptionEventData[] = []; for (const tx of event.transactions) { const tx_id = normalizedHexString(tx.transaction_identifier.hash); for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) writes.push( - revealInsertFromOrdhookInscriptionRevealed({ + updateFromOrdhookInscriptionRevealed({ block_hash, block_height, tx_id, @@ -185,7 +190,7 @@ export function revealInsertsFromOrdhookEvent(event: BitcoinEvent): DbRevealInse ); if (operation.inscription_transferred) writes.push( - revealInsertFromOrdhookInscriptionTransferred({ + updateFromOrdhookInscriptionTransferred({ block_hash, block_height, tx_id, diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index ab8a6b6b..ce4032be 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -26,15 +26,19 @@ import { DbInscriptionIndexFilters, DbInscriptionIndexOrder, DbInscriptionIndexPaging, - DbInscriptionInsert, + InscriptionData, DbInscriptionLocationChange, DbLocation, - DbLocationInsert, + RevealLocationData, DbLocationPointer, DbLocationPointerInsert, DbPaginatedResult, - DbRevealInsert, + InscriptionEventData, LOCATIONS_COLUMNS, + InscriptionRevealData, + InscriptionInsert, + LocationInsert, + LocationData, } from './types'; export const MIGRATIONS_DIR = path.join(__dirname, '../../migrations'); @@ -123,8 +127,8 @@ export class PgStore extends BasePgStore { const time = stopwatch(); const writes = revealInsertsFromOrdhookEvent(event); const newBlessedNumbers = writes - .filter(w => w.inscription !== undefined && w.inscription.number >= 0) - .map(w => w.inscription?.number ?? 0); + .filter(w => 'inscription' in w && w.inscription.number >= 0) + .map(w => (w as InscriptionRevealData).inscription.number ?? 0); assertNoBlockInscriptionGap({ currentNumber: currentBlessedNumber, newNumbers: newBlessedNumbers, @@ -484,17 +488,55 @@ export class PgStore extends BasePgStore { `; // roughly 35 days of blocks, assuming 10 minute block times on a full database } - private async insertInscriptions(reveals: DbRevealInsert[]): Promise { + private async insertInscriptions(reveals: InscriptionEventData[]): Promise { if (reveals.length === 0) return; await this.sqlWriteTransaction(async sql => { - const inscriptions: DbInscriptionInsert[] = []; - const transferGenesisIds = new Set(); + const inscriptionInserts: InscriptionInsert[] = []; + const locationInserts: LocationInsert[] = []; + const revealOutputs: InscriptionEventData[] = []; + const transferredOrdinalNumbersSet = new Set(); for (const r of reveals) - if (r.inscription) inscriptions.push(r.inscription); - else transferGenesisIds.add(r.location.genesis_id); - if (inscriptions.length) + if ('inscription' in r) { + revealOutputs.push(r); + inscriptionInserts.push(r.inscription); + locationInserts.push({ + ...r.location, + inscription_id: sql`(SELECT id FROM inscriptions WHERE genesis_id = ${r.location.genesis_id})`, + timestamp: sql`TO_TIMESTAMP(${r.location.timestamp})`, + }); + } else { + transferredOrdinalNumbersSet.add(r.location.ordinal_number); + // Transfers can move multiple inscriptions in the same sat, we must expand all of them so + // we can update their respective locations. + // TODO: This could probably be optimized to use fewer queries. + const inscriptionIds = await sql<{ id: string; genesis_id: string }[]>` + SELECT id, genesis_id FROM inscriptions WHERE sat_ordinal = ${r.location.ordinal_number} + `; + for (const row of inscriptionIds) { + revealOutputs.push(r); + locationInserts.push({ + genesis_id: row.genesis_id, + inscription_id: row.id, + block_height: r.location.block_height, + block_hash: r.location.block_hash, + tx_id: r.location.tx_id, + tx_index: r.location.tx_index, + address: r.location.address, + output: r.location.output, + offset: r.location.offset, + prev_output: r.location.prev_output, + prev_offset: r.location.prev_offset, + value: r.location.value, + transfer_type: r.location.transfer_type, + block_transfer_index: r.location.block_transfer_index, + timestamp: sql`TO_TIMESTAMP(${r.location.timestamp})`, + }); + } + } + const transferredOrdinalNumbers = [...transferredOrdinalNumbersSet]; + if (inscriptionInserts.length) await sql` - INSERT INTO inscriptions ${sql(inscriptions)} + INSERT INTO inscriptions ${sql(inscriptionInserts)} ON CONFLICT ON CONSTRAINT inscriptions_number_unique DO UPDATE SET genesis_id = EXCLUDED.genesis_id, mime_type = EXCLUDED.mime_type, @@ -507,13 +549,8 @@ export class PgStore extends BasePgStore { sat_coinbase_height = EXCLUDED.sat_coinbase_height, updated_at = NOW() `; - const locationData = reveals.map(i => ({ - ...i.location, - inscription_id: sql`(SELECT id FROM inscriptions WHERE genesis_id = ${i.location.genesis_id} LIMIT 1)`, - timestamp: sql`TO_TIMESTAMP(${i.location.timestamp})`, - })); const pointers = await sql` - INSERT INTO locations ${sql(locationData)} + INSERT INTO locations ${sql(locationInserts)} ON CONFLICT ON CONSTRAINT locations_inscription_id_block_height_tx_index_unique DO UPDATE SET genesis_id = EXCLUDED.genesis_id, block_hash = EXCLUDED.block_hash, @@ -526,21 +563,23 @@ export class PgStore extends BasePgStore { RETURNING inscription_id, id AS location_id, block_height, tx_index, address `; await this.updateInscriptionRecursions(reveals); - if (transferGenesisIds.size) + if (transferredOrdinalNumbers.length) await sql` UPDATE inscriptions SET updated_at = NOW() - WHERE genesis_id IN ${sql([...transferGenesisIds])} + WHERE sat_ordinal IN ${sql(transferredOrdinalNumbers)} `; await this.updateInscriptionLocationPointers(pointers); for (const reveal of reveals) { - const action = reveal.inscription ? `reveal #${reveal.inscription.number}` : `transfer`; - logger.info( - `PgStore ${action} (${reveal.location.genesis_id}) at block ${reveal.location.block_height}` - ); + const action = + 'inscription' in reveal + ? `reveal #${reveal.inscription.number} (${reveal.location.genesis_id})` + : `transfer sat ${reveal.location.ordinal_number}`; + logger.info(`PgStore ${action} at block ${reveal.location.block_height}`); } - await this.counts.applyInscriptions(inscriptions); - if (ENV.BRC20_BLOCK_SCAN_ENABLED) await this.brc20.insertOperations({ reveals, pointers }); + await this.counts.applyInscriptions(inscriptionInserts); + if (ENV.BRC20_BLOCK_SCAN_ENABLED) + await this.brc20.insertOperations({ reveals: revealOutputs, pointers }); }); } @@ -584,12 +623,12 @@ export class PgStore extends BasePgStore { }); } - private async rollBackInscriptions(rollbacks: DbRevealInsert[]): Promise { + private async rollBackInscriptions(rollbacks: InscriptionEventData[]): Promise { if (rollbacks.length === 0) return; await this.sqlWriteTransaction(async sql => { // Roll back events in reverse so BRC-20 keeps a sane order. for (const rollback of rollbacks.reverse()) { - if (rollback.inscription) { + if ('inscription' in rollback) { await this.brc20.rollBackInscription({ inscription: rollback.inscription }); await this.counts.rollBackInscription({ inscription: rollback.inscription, @@ -609,7 +648,7 @@ export class PgStore extends BasePgStore { WHERE output = ${rollback.location.output} AND "offset" = ${rollback.location.offset} `; logger.info( - `PgStore rollback transfer for ${rollback.location.genesis_id} at block ${rollback.location.block_height}` + `PgStore rollback transfer for sat ${rollback.location.ordinal_number} at block ${rollback.location.block_height}` ); } } @@ -707,7 +746,7 @@ export class PgStore extends BasePgStore { } private async recalculateCurrentLocationPointerFromLocationRollBack(args: { - location: DbLocationInsert; + location: LocationData; }): Promise { await this.sqlWriteTransaction(async sql => { // Is the location we're rolling back *the* current location? @@ -740,7 +779,7 @@ export class PgStore extends BasePgStore { }); } - private async updateInscriptionRecursions(reveals: DbRevealInsert[]): Promise { + private async updateInscriptionRecursions(reveals: InscriptionEventData[]): Promise { if (reveals.length === 0) return; const inserts: { inscription_id: PgSqlQuery; @@ -748,7 +787,7 @@ export class PgStore extends BasePgStore { ref_inscription_genesis_id: string; }[] = []; for (const i of reveals) - if (i.inscription && i.recursive_refs?.length) { + if ('inscription' in i && i.recursive_refs?.length) { const refSet = new Set(i.recursive_refs); for (const ref of refSet) inserts.push({ diff --git a/src/pg/types.ts b/src/pg/types.ts index a418cfa3..d070c91d 100644 --- a/src/pg/types.ts +++ b/src/pg/types.ts @@ -1,7 +1,75 @@ -import { PgNumeric, PgBytea } from '@hirosystems/api-toolkit'; +import { PgNumeric, PgBytea, PgSqlQuery } from '@hirosystems/api-toolkit'; import { Order, OrderBy } from '../api/schemas'; import { SatoshiRarity } from '../api/util/ordinal-satoshi'; +/** + * Updates and inserts + */ + +export type InscriptionData = { + genesis_id: string; + number: number; + classic_number: number; + mime_type: string; + content_type: string; + content_length: number; + content: PgBytea; + fee: PgNumeric; + curse_type: string | null; + sat_ordinal: PgNumeric; + sat_rarity: string; + sat_coinbase_height: number; + recursive: boolean; +}; + +export type InscriptionInsert = InscriptionData; + +type AbstractLocationData = { + block_height: number; + block_hash: string; + tx_id: string; + tx_index: number; + address: string | null; + output: string; + offset: PgNumeric | null; + prev_output: string | null; + prev_offset: PgNumeric | null; + value: PgNumeric | null; + transfer_type: DbLocationTransferType; + block_transfer_index: number | null; +}; + +export type RevealLocationData = AbstractLocationData & { genesis_id: string; timestamp: number }; + +export type TransferLocationData = AbstractLocationData & { + ordinal_number: PgNumeric; + timestamp: number; +}; + +export type LocationData = RevealLocationData | TransferLocationData; + +export type LocationInsert = AbstractLocationData & { + timestamp: PgSqlQuery; + genesis_id: string; + inscription_id: PgSqlQuery | string; +}; + +export type InscriptionRevealData = { + inscription: InscriptionData; + recursive_refs: string[]; + location: RevealLocationData; +}; + +export type InscriptionTransferData = { + location: TransferLocationData; +}; + +export type InscriptionEventData = InscriptionRevealData | InscriptionTransferData; + +/** + * Selects + */ + export type DbPaginatedResult = { total: number; results: T[]; @@ -40,23 +108,6 @@ export enum DbLocationTransferType { burnt = 'burnt', } -export type DbLocationInsert = { - genesis_id: string; - block_height: number; - block_hash: string; - tx_id: string; - tx_index: number; - address: string | null; - output: string; - offset: PgNumeric | null; - prev_output: string | null; - prev_offset: PgNumeric | null; - value: PgNumeric | null; - timestamp: number; - transfer_type: DbLocationTransferType; - block_transfer_index: number | null; -}; - export type DbLocation = { id: string; inscription_id: string | null; @@ -134,28 +185,6 @@ export const LOCATIONS_COLUMNS = [ 'timestamp', ]; -export type DbInscriptionInsert = { - genesis_id: string; - number: number; - classic_number: number; - mime_type: string; - content_type: string; - content_length: number; - content: PgBytea; - fee: PgNumeric; - curse_type: string | null; - sat_ordinal: PgNumeric; - sat_rarity: string; - sat_coinbase_height: number; - recursive: boolean; -}; - -export type DbRevealInsert = { - inscription?: DbInscriptionInsert; - recursive_refs?: string[]; - location: DbLocationInsert; -}; - export type DbInscription = { id: string; genesis_id: string; diff --git a/tests/brc20.test.ts b/tests/brc20.test.ts index 2b3302fe..bea3bad8 100644 --- a/tests/brc20.test.ts +++ b/tests/brc20.test.ts @@ -3,7 +3,7 @@ import { buildApiServer } from '../src/api/init'; import { Brc20ActivityResponse, Brc20TokenResponse } from '../src/api/schemas'; import { brc20FromInscription } from '../src/pg/brc20/helpers'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; -import { DbInscriptionInsert } from '../src/pg/types'; +import { InscriptionData } from '../src/pg/types'; import { TestChainhookPayloadBuilder, TestFastifyServer, @@ -38,6 +38,7 @@ describe('BRC-20', () => { max: '250000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -63,6 +64,7 @@ describe('BRC-20', () => { amt: '10000', }, number: 1, + ordinal_number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -84,9 +86,9 @@ describe('BRC-20', () => { }); describe('token standard validation', () => { - const testInsert = (json: any): DbInscriptionInsert => { + const testInsert = (json: any): InscriptionData => { const content = Buffer.from(JSON.stringify(json), 'utf-8'); - const insert: DbInscriptionInsert = { + const insert: InscriptionData = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', number: 0, classic_number: 0, @@ -114,7 +116,7 @@ describe('BRC-20', () => { }), 'utf-8' ); - const insert: DbInscriptionInsert = { + const insert: InscriptionData = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', number: 0, classic_number: 0, @@ -143,7 +145,7 @@ describe('BRC-20', () => { '{"p": "brc-20", "op": "deploy", "tick": "PEPE", "max": "21000000"', 'utf-8' ); - const insert: DbInscriptionInsert = { + const insert: InscriptionData = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', number: 0, classic_number: 0, @@ -493,6 +495,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -544,6 +547,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -569,6 +573,7 @@ describe('BRC-20', () => { max: '19000000', }, number: 1, + ordinal_number: 1, tx_id: '3f8067a6e9b45308b5a090c2987feeb2d08cbaf814ef2ffabad7c381b62f5f7e', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -620,6 +625,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -645,6 +651,7 @@ describe('BRC-20', () => { max: '19000000', }, number: 1, + ordinal_number: 1, tx_id: '3f8067a6e9b45308b5a090c2987feeb2d08cbaf814ef2ffabad7c381b62f5f7e', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -719,6 +726,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, classic_number: -1, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', @@ -759,6 +767,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -784,6 +793,7 @@ describe('BRC-20', () => { amt: '250000', }, number: 1, + ordinal_number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -827,6 +837,7 @@ describe('BRC-20', () => { amt: '100000', }, number: 2, + ordinal_number: 2, tx_id: '7a1adbc3e93ddf8d7c4e0ba75aa11c98c431521dd850be8b955feedb716d8bec', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -885,6 +896,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -910,6 +922,7 @@ describe('BRC-20', () => { amt: '250000', }, number: 1, + ordinal_number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -936,6 +949,7 @@ describe('BRC-20', () => { amt: '250000', }, number: 1, + ordinal_number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -981,6 +995,7 @@ describe('BRC-20', () => { dec: '1', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1006,6 +1021,7 @@ describe('BRC-20', () => { amt: '250000.000', // Invalid decimal count }, number: 1, + ordinal_number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -1045,6 +1061,7 @@ describe('BRC-20', () => { dec: '1', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1070,6 +1087,7 @@ describe('BRC-20', () => { amt: '1000', }, number: 1, + ordinal_number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1086,6 +1104,7 @@ describe('BRC-20', () => { amt: '1000', }, number: 2, + ordinal_number: 2, tx_id: '7e09bda2cba34bca648cca6d79a074940d39b6137150d3a3edcf80c0e01419a5', address: address, }) @@ -1102,6 +1121,7 @@ describe('BRC-20', () => { amt: '5000000000', // Exceeds supply }, number: 3, + ordinal_number: 3, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -1145,6 +1165,7 @@ describe('BRC-20', () => { amt: '1000', }, number: 4, + ordinal_number: 4, tx_id: 'bf7a3e1a0647ca88f6539119b2defaec302683704ea270b3302e709597643548', address: address, }) @@ -1182,6 +1203,7 @@ describe('BRC-20', () => { amt: '1000', }, number: 0, + ordinal_number: 0, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1222,6 +1244,7 @@ describe('BRC-20', () => { lim: '100', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1247,6 +1270,7 @@ describe('BRC-20', () => { amt: '1000', // Greater than limit }, number: 1, + ordinal_number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1288,6 +1312,7 @@ describe('BRC-20', () => { amt: '2000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1342,6 +1367,7 @@ describe('BRC-20', () => { amt: '2000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1388,6 +1414,7 @@ describe('BRC-20', () => { amt: '5000000000', // More than was minted }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1434,6 +1461,7 @@ describe('BRC-20', () => { amt: '9000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1450,6 +1478,7 @@ describe('BRC-20', () => { amt: '2000', // Will exceed available balance }, number: 3, + ordinal_number: 3, tx_id: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', address: address, }) @@ -1497,6 +1526,7 @@ describe('BRC-20', () => { amt: '9000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1514,7 +1544,7 @@ describe('BRC-20', () => { hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + ordinal_number: 2, destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', @@ -1595,6 +1625,7 @@ describe('BRC-20', () => { amt: '9000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1612,7 +1643,7 @@ describe('BRC-20', () => { hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + ordinal_number: 2, destination: { type: 'spent_in_fees', value: '' }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', @@ -1663,6 +1694,7 @@ describe('BRC-20', () => { amt: '9000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1680,7 +1712,7 @@ describe('BRC-20', () => { hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + ordinal_number: 2, destination: { type: 'burnt' }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', @@ -1732,6 +1764,7 @@ describe('BRC-20', () => { amt: '9000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1749,7 +1782,7 @@ describe('BRC-20', () => { hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + ordinal_number: 2, destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', @@ -1773,7 +1806,7 @@ describe('BRC-20', () => { hash: '55bec906eadc9f5c120cc39555ba46e85e562eacd6217e4dd0b8552783286d0e', }) .inscriptionTransferred({ - inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + ordinal_number: 2, destination: { type: 'transferred', value: address }, satpoint_pre_transfer: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', @@ -1842,6 +1875,7 @@ describe('BRC-20', () => { amt: '20', }, number: 2, + ordinal_number: 2, tx_id: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205', address: address, }) @@ -1859,7 +1893,7 @@ describe('BRC-20', () => { hash: '486815e61723d03af344e1256d7e0c028a8e9e71eb38157f4bf069eb94292ee1', }) .inscriptionTransferred({ - inscription_id: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205i0', + ordinal_number: 2, destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205:0:0', @@ -1901,6 +1935,7 @@ describe('BRC-20', () => { amt: '20', }, number: 3, + ordinal_number: 3, tx_id: '09a812f72275892b4858880cf3821004a6e8885817159b340639afe9952ac053', address: address2, }) @@ -1930,7 +1965,7 @@ describe('BRC-20', () => { hash: '26c0c3acbb1c87e682ade86220ba06e649d7599ecfc49a71495f1bdd04efbbb4', }) .inscriptionTransferred({ - inscription_id: '09a812f72275892b4858880cf3821004a6e8885817159b340639afe9952ac053i0', + ordinal_number: 3, destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '486815e61723d03af344e1256d7e0c028a8e9e71eb38157f4bf069eb94292ee1:0:0', @@ -1975,6 +2010,7 @@ describe('BRC-20', () => { max: '21000000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -2014,6 +2050,7 @@ describe('BRC-20', () => { const blockHeights = incrementing(BRC20_GENESIS_BLOCK); let transferHash = randomHash(); + let number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2027,7 +2064,8 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: transferHash, address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -2036,6 +2074,7 @@ describe('BRC-20', () => { ); transferHash = randomHash(); + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2049,7 +2088,8 @@ describe('BRC-20', () => { tick: 'PEER', max: '21000000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: transferHash, address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -2058,6 +2098,7 @@ describe('BRC-20', () => { ); transferHash = randomHash(); + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2071,7 +2112,8 @@ describe('BRC-20', () => { tick: 'ABCD', max: '21000000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: transferHash, address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -2080,6 +2122,7 @@ describe('BRC-20', () => { ); transferHash = randomHash(); + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2093,7 +2136,8 @@ describe('BRC-20', () => { tick: 'DCBA', max: '21000000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: transferHash, address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -2124,6 +2168,7 @@ describe('BRC-20', () => { const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; // A deploys PEPE + let number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2137,7 +2182,8 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -2149,6 +2195,7 @@ describe('BRC-20', () => { const pepeMints = []; for (let i = 0; i < 10; i++) { const txHash = randomHash(); + number = inscriptionNumbers.next().value; const payload = new TestChainhookPayloadBuilder() .apply() .block({ height: blockHeights.next().value }) @@ -2161,7 +2208,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '10000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: txHash, address: addressA, }) @@ -2172,6 +2220,7 @@ describe('BRC-20', () => { } // B deploys ABCD + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2185,7 +2234,8 @@ describe('BRC-20', () => { tick: 'ABCD', max: '21000000', }, - number: inscriptionNumbers.next().value, + number: number, + ordinal_number: number, tx_id: randomHash(), address: addressB, }) @@ -2194,6 +2244,7 @@ describe('BRC-20', () => { ); // B mints 10000 ABCD + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2207,7 +2258,8 @@ describe('BRC-20', () => { tick: 'ABCD', amt: '10000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressB, }) @@ -2218,6 +2270,7 @@ describe('BRC-20', () => { // B send 1000 ABCD to A // (create inscription, transfer) const txHashTransfer = randomHash(); + number = inscriptionNumbers.next().value; const payloadTransfer = new TestChainhookPayloadBuilder() .apply() .block({ height: blockHeights.next().value }) @@ -2230,7 +2283,8 @@ describe('BRC-20', () => { tick: 'ABCD', amt: '1000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: txHashTransfer, address: addressB, }) @@ -2244,7 +2298,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: txHashTransferSend }) .inscriptionTransferred({ - inscription_id: `${txHashTransfer}i0`, + ordinal_number: number, destination: { type: 'transferred', value: addressA }, satpoint_pre_transfer: `${txHashTransfer}:0:0`, satpoint_post_transfer: `${txHashTransferSend}:0:0`, @@ -2365,6 +2419,7 @@ describe('BRC-20', () => { const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; // A deploys PEPE + let number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2378,7 +2433,8 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -2408,6 +2464,7 @@ describe('BRC-20', () => { ); // A mints 10000 PEPE + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2421,7 +2478,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '10000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -2454,6 +2512,7 @@ describe('BRC-20', () => { ); // B mints 10000 PEPE + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2467,7 +2526,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '10000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressB, }) @@ -2497,6 +2557,7 @@ describe('BRC-20', () => { // A creates transfer of 9000 PEPE const transferHash = randomHash(); + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2510,7 +2571,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: transferHash, address: addressA, }) @@ -2549,7 +2611,7 @@ describe('BRC-20', () => { .inscriptionTransferred({ destination: { type: 'transferred', value: addressB }, tx_index: 0, - inscription_id: `${transferHash}i0`, + ordinal_number: number, post_transfer_output_value: null, satpoint_pre_transfer: `${transferHash}:0:0`, satpoint_post_transfer: @@ -2622,6 +2684,7 @@ describe('BRC-20', () => { const addressC = 'bc1q9d80h0q5d3f54w7w8c3l2sguf9uset4ydw9xj2'; // Step 1: A deploys a token + let number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2635,7 +2698,8 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -2665,6 +2729,7 @@ describe('BRC-20', () => { ); // Step 2: A mints 1000 of the token + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2678,7 +2743,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -2735,6 +2801,7 @@ describe('BRC-20', () => { ); // Step 3: B mints 2000 of the token + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2748,7 +2815,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressB, }) @@ -2779,6 +2847,7 @@ describe('BRC-20', () => { // Step 4: A creates a transfer to B const transferHashAB = randomHash(); + const numberAB = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2792,7 +2861,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: inscriptionNumbers.next().value, + number: numberAB, + ordinal_number: numberAB, tx_id: transferHashAB, address: addressA, }) @@ -2846,6 +2916,7 @@ describe('BRC-20', () => { // Step 5: B creates a transfer to C const transferHashBC = randomHash(); + const numberBC = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -2859,7 +2930,8 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', }, - number: inscriptionNumbers.next().value, + number: numberBC, + ordinal_number: numberBC, tx_id: transferHashBC, address: addressB, }) @@ -2900,7 +2972,7 @@ describe('BRC-20', () => { .inscriptionTransferred({ destination: { type: 'transferred', value: addressB }, tx_index: 0, - inscription_id: `${transferHashAB}i0`, + ordinal_number: numberAB, post_transfer_output_value: null, satpoint_pre_transfer: `${transferHashAB}:0:0`, satpoint_post_transfer: `${transferHashABSend}:0:0`, @@ -2988,7 +3060,7 @@ describe('BRC-20', () => { .inscriptionTransferred({ destination: { type: 'transferred', value: addressC }, tx_index: 0, - inscription_id: `${transferHashBC}i0`, + ordinal_number: numberBC, post_transfer_output_value: null, satpoint_pre_transfer: `${transferHashBC}:0:0`, satpoint_post_transfer: `${transferHashBCSend}:0:0`, @@ -3073,6 +3145,7 @@ describe('BRC-20', () => { const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; // Step 1: Create a token PEPE + let number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -3086,7 +3159,8 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -3116,6 +3190,7 @@ describe('BRC-20', () => { ); // Step 2: Create a token PEER + number = inscriptionNumbers.next().value; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() @@ -3129,7 +3204,8 @@ describe('BRC-20', () => { tick: 'PEER', max: '21000000', }, - number: inscriptionNumbers.next().value, + number, + ordinal_number: number, tx_id: randomHash(), address: addressA, }) @@ -3222,6 +3298,7 @@ describe('BRC-20', () => { amt: '2000', }, number: 2, + ordinal_number: 2, tx_id: '633648e0e1ddcab8dea0496a561f2b08c486ae619b5634d7bb55d7f0cd32ef16', address: 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl', }) @@ -3268,6 +3345,7 @@ describe('BRC-20', () => { max: '250000', }, number: 0, + ordinal_number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl', }) @@ -3319,6 +3397,7 @@ describe('BRC-20', () => { amt: '9000', }, number: 2, + ordinal_number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -3335,7 +3414,7 @@ describe('BRC-20', () => { hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + ordinal_number: 2, destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', @@ -3365,6 +3444,7 @@ describe('BRC-20', () => { max: '1000', }, number: 3, + ordinal_number: 3, tx_id: '8354e85e87fa2df8b3a06ec0b9d395559b95174530cb19447fc4df5f6d4ca84d', address: address, }) @@ -3389,6 +3469,7 @@ describe('BRC-20', () => { amt: '500', }, number: 4, + ordinal_number: 4, tx_id: '81f4ee2c247c5f5c0d3a6753fef706df410ea61c2aa6d370003b98beb041b887', address: address, }) @@ -3414,6 +3495,7 @@ describe('BRC-20', () => { amt: '100', }, number: 5, + ordinal_number: 5, tx_id: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966f', address: address, }) @@ -3430,7 +3512,7 @@ describe('BRC-20', () => { hash: 'a00d01a3e772ce2219ddf3fe2fe4053be071262d9594f11f018fdada7179ae2d', }) .inscriptionTransferred({ - inscription_id: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966fi0', + ordinal_number: 5, destination: { type: 'transferred', value: address }, // To self satpoint_pre_transfer: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966f:0:0', diff --git a/tests/cache.test.ts b/tests/cache.test.ts index a8a78039..fa5a9f98 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -89,7 +89,7 @@ describe('ETag cache', () => { hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -126,7 +126,7 @@ describe('ETag cache', () => { hash: 'bebb1357c97d2348eb8ef24e1d8639ff79c8847bf12999ca7fef463489b40f0f', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -235,7 +235,7 @@ describe('ETag cache', () => { .block({ height: 778577 }) .transaction({ hash: 'ae9d273a10e899f0d2cad47ee2b0e77ab8a9addd9dd5bb5e4b03d6971c060d52' }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', diff --git a/tests/helpers.ts b/tests/helpers.ts index 569b6ed6..e1f0087c 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -114,6 +114,7 @@ export function brc20Reveal(args: { classic_number?: number; address: string; tx_id: string; + ordinal_number: number; }): BitcoinInscriptionRevealed { const content = Buffer.from(JSON.stringify(args.json), 'utf-8'); const reveal: BitcoinInscriptionRevealed = { @@ -128,7 +129,7 @@ export function brc20Reveal(args: { inscription_id: `${args.tx_id}i0`, inscription_output_value: 10000, inscriber_address: args.address, - ordinal_number: 0, + ordinal_number: args.ordinal_number, ordinal_block_height: 0, ordinal_offset: 0, satpoint_post_inscription: `${args.tx_id}:0:0`, diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index e89a8b36..5b6d6e1a 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -470,7 +470,7 @@ describe('/inscriptions', () => { hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -525,7 +525,7 @@ describe('/inscriptions', () => { hash: '0xe3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', @@ -616,7 +616,7 @@ describe('/inscriptions', () => { }) // Transfer 1 .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1qv7d2dgyvtctv7ya4t3ysy4c2s8qz4nm8t6dvm3', @@ -630,7 +630,7 @@ describe('/inscriptions', () => { }) // Transfer 2 .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -720,7 +720,7 @@ describe('/inscriptions', () => { hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -775,7 +775,7 @@ describe('/inscriptions', () => { hash: '0xe3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', @@ -871,7 +871,7 @@ describe('/inscriptions', () => { hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -930,7 +930,7 @@ describe('/inscriptions', () => { hash: '0xe3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', @@ -1031,7 +1031,7 @@ describe('/inscriptions', () => { inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', inscription_output_value: 10000, inscriber_address: 'bc1ptrehxtus25xx8jp5pchljxg2aps7mdemc4264zzzsdcvs6q25hhsf3rrph', - ordinal_number: 257418248345364, + ordinal_number: 257418248340000, ordinal_block_height: 650000, ordinal_offset: 0, satpoint_post_inscription: @@ -1067,7 +1067,7 @@ describe('/inscriptions', () => { hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -1083,7 +1083,7 @@ describe('/inscriptions', () => { hash: 'abe7deebd0c6bacc9b1ddd234f9442db0530180448e934f34b9cbf3d7e6d91cb', }) .inscriptionTransferred({ - inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', + ordinal_number: 257418248340000, destination: { type: 'transferred', value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', @@ -1172,7 +1172,7 @@ describe('/inscriptions', () => { hash: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', @@ -1186,7 +1186,7 @@ describe('/inscriptions', () => { }) // Transfer for same inscription in same block .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, destination: { type: 'transferred', value: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', @@ -2634,7 +2634,7 @@ describe('/inscriptions', () => { // Transfers affect result totals correctly const transfer2: BitcoinInscriptionTransferred = { - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 1000000000000, destination: { type: 'transferred', value: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', diff --git a/tests/server.test.ts b/tests/server.test.ts index 5f00ffda..878c5e94 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -175,7 +175,7 @@ describe('EventServer', () => { await expect(db.getChainTipBlockHeight()).resolves.toBe(775617); const transfer: BitcoinInscriptionTransferred = { - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 5, destination: { type: 'transferred', value: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf00000', @@ -311,7 +311,7 @@ describe('EventServer', () => { hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + ordinal_number: 5, destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', From 96a4ec792b12fe6c344bd3b510c0508b4c07ce53 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Feb 2024 16:32:16 +0000 Subject: [PATCH 4/8] chore(release): 2.2.0-beta.1 [skip ci] ## [2.2.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.2-beta.1...v2.2.0-beta.1) (2024-02-02) ### Features * support transfers by ordinal number instead of inscription id ([#296](https://github.com/hirosystems/ordinals-api/issues/296)) ([b0c0ffd](https://github.com/hirosystems/ordinals-api/commit/b0c0ffd198a9a98a88b5123927a8fdc5b68ef22d)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c3708b5..293105e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.2.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.2-beta.1...v2.2.0-beta.1) (2024-02-02) + + +### Features + +* support transfers by ordinal number instead of inscription id ([#296](https://github.com/hirosystems/ordinals-api/issues/296)) ([b0c0ffd](https://github.com/hirosystems/ordinals-api/commit/b0c0ffd198a9a98a88b5123927a8fdc5b68ef22d)) + ## [2.1.2-beta.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.1...v2.1.2-beta.1) (2024-01-16) From d0f7dccc49351df93c9ea4aadecc5f45392b259f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Fri, 2 Feb 2024 11:05:06 -0600 Subject: [PATCH 5/8] test: parallelize test suites (#298) * test: parallelize test suites * chore: change ordhook test suite name * ci: ordhook name --- .github/workflows/ci.yml | 6 ++- .vscode/launch.json | 63 +++++++++++++++++++++++++ package.json | 3 ++ tests/{ => api}/cache.test.ts | 6 +-- tests/{ => api}/inscriptions.test.ts | 6 +-- tests/{ => api}/ordinal-satoshi.test.ts | 2 +- tests/{ => api}/sats.test.ts | 6 +-- tests/{ => api}/stats.test.ts | 6 +-- tests/{ => api}/status.test.ts | 6 +-- tests/{ => brc-20}/brc20.test.ts | 14 +++--- tests/{ => ordhook}/server.test.ts | 10 ++-- 11 files changed, 99 insertions(+), 29 deletions(-) rename tests/{ => api}/cache.test.ts (99%) rename tests/{ => api}/inscriptions.test.ts (99%) rename tests/{ => api}/ordinal-satoshi.test.ts (97%) rename tests/{ => api}/sats.test.ts (98%) rename tests/{ => api}/stats.test.ts (98%) rename tests/{ => api}/status.test.ts (96%) rename tests/{ => brc-20}/brc20.test.ts (99%) rename tests/{ => ordhook}/server.test.ts (99%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caf7ac78..35853d17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,10 @@ jobs: run: npm run lint:prettier test: + strategy: + fail-fast: false + matrix: + suite: [api, brc-20, ordhook] runs-on: ubuntu-latest env: API_HOST: 127.0.0.1 @@ -92,7 +96,7 @@ jobs: npm run testenv:logs -- --no-color &> docker-compose-logs.txt & - name: Run tests - run: npm run test -- --coverage + run: npm run test:${{ matrix.suite }} -- --coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/.vscode/launch.json b/.vscode/launch.json index 2dd9c265..0ad80e0d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -80,5 +80,68 @@ "PGPASSWORD": "postgres", }, }, + { + "type": "node", + "request": "launch", + "name": "Jest: API", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": [ + "--testTimeout=3600000", + "--runInBand", + "--no-cache", + "${workspaceFolder}/tests/api/" + ], + "outputCapture": "std", + "console": "integratedTerminal", + "preLaunchTask": "npm: testenv:run", + "postDebugTask": "npm: testenv:stop", + "env": { + "PGHOST": "localhost", + "PGUSER": "postgres", + "PGPASSWORD": "postgres", + }, + }, + { + "type": "node", + "request": "launch", + "name": "Jest: BRC-20", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": [ + "--testTimeout=3600000", + "--runInBand", + "--no-cache", + "${workspaceFolder}/tests/brc-20/" + ], + "outputCapture": "std", + "console": "integratedTerminal", + "preLaunchTask": "npm: testenv:run", + "postDebugTask": "npm: testenv:stop", + "env": { + "PGHOST": "localhost", + "PGUSER": "postgres", + "PGPASSWORD": "postgres", + }, + }, + { + "type": "node", + "request": "launch", + "name": "Jest: Ordhook", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": [ + "--testTimeout=3600000", + "--runInBand", + "--no-cache", + "${workspaceFolder}/tests/ordhook/" + ], + "outputCapture": "std", + "console": "integratedTerminal", + "preLaunchTask": "npm: testenv:run", + "postDebugTask": "npm: testenv:stop", + "env": { + "PGHOST": "localhost", + "PGUSER": "postgres", + "PGPASSWORD": "postgres", + }, + }, ] } diff --git a/package.json b/package.json index 17dad3d2..61b642a5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "start": "node dist/src/index.js", "start-ts": "ts-node ./src/index.ts", "test": "jest --runInBand", + "test:brc-20": "npm run test -- ./tests/brc-20/", + "test:api": "npm run test -- ./tests/api/", + "test:ordhook": "npm run test -- ./tests/ordhook/", "migrate": "ts-node node_modules/.bin/node-pg-migrate -j ts", "lint:eslint": "eslint . --ext .js,.jsx,.ts,.tsx -f unix", "lint:prettier": "prettier --check src/**/*.ts tests/**/*.ts migrations/**/*.ts", diff --git a/tests/cache.test.ts b/tests/api/cache.test.ts similarity index 99% rename from tests/cache.test.ts rename to tests/api/cache.test.ts index fa5a9f98..da974352 100644 --- a/tests/cache.test.ts +++ b/tests/api/cache.test.ts @@ -1,7 +1,7 @@ import { runMigrations } from '@hirosystems/api-toolkit'; -import { buildApiServer } from '../src/api/init'; -import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; -import { TestChainhookPayloadBuilder, TestFastifyServer, randomHash } from './helpers'; +import { buildApiServer } from '../../src/api/init'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { TestChainhookPayloadBuilder, TestFastifyServer, randomHash } from '../helpers'; describe('ETag cache', () => { let db: PgStore; diff --git a/tests/inscriptions.test.ts b/tests/api/inscriptions.test.ts similarity index 99% rename from tests/inscriptions.test.ts rename to tests/api/inscriptions.test.ts index 5b6d6e1a..0dbe1158 100644 --- a/tests/inscriptions.test.ts +++ b/tests/api/inscriptions.test.ts @@ -1,7 +1,7 @@ import { runMigrations } from '@hirosystems/api-toolkit'; -import { buildApiServer } from '../src/api/init'; -import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; -import { TestChainhookPayloadBuilder, TestFastifyServer, rollBack } from './helpers'; +import { buildApiServer } from '../../src/api/init'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { TestChainhookPayloadBuilder, TestFastifyServer, rollBack } from '../helpers'; import { BitcoinInscriptionRevealed, BitcoinInscriptionTransferred, diff --git a/tests/ordinal-satoshi.test.ts b/tests/api/ordinal-satoshi.test.ts similarity index 97% rename from tests/ordinal-satoshi.test.ts rename to tests/api/ordinal-satoshi.test.ts index 28a14ef9..6f31c187 100644 --- a/tests/ordinal-satoshi.test.ts +++ b/tests/api/ordinal-satoshi.test.ts @@ -1,4 +1,4 @@ -import { OrdinalSatoshi, SatoshiRarity } from '../src/api/util/ordinal-satoshi'; +import { OrdinalSatoshi, SatoshiRarity } from '../../src/api/util/ordinal-satoshi'; describe('OrdinalSatoshi', () => { test('mythic sat', () => { diff --git a/tests/sats.test.ts b/tests/api/sats.test.ts similarity index 98% rename from tests/sats.test.ts rename to tests/api/sats.test.ts index 81c95d58..91b36b20 100644 --- a/tests/sats.test.ts +++ b/tests/api/sats.test.ts @@ -1,7 +1,7 @@ import { runMigrations } 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 { buildApiServer } from '../../src/api/init'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from '../helpers'; describe('/sats', () => { let db: PgStore; diff --git a/tests/stats.test.ts b/tests/api/stats.test.ts similarity index 98% rename from tests/stats.test.ts rename to tests/api/stats.test.ts index c1716a3b..9215ee74 100644 --- a/tests/stats.test.ts +++ b/tests/api/stats.test.ts @@ -1,7 +1,7 @@ import { runMigrations } from '@hirosystems/api-toolkit'; -import { buildApiServer } from '../src/api/init'; -import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; -import { TestChainhookPayloadBuilder, TestFastifyServer, randomHash } from './helpers'; +import { buildApiServer } from '../../src/api/init'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { TestChainhookPayloadBuilder, TestFastifyServer, randomHash } from '../helpers'; describe('/stats', () => { let db: PgStore; diff --git a/tests/status.test.ts b/tests/api/status.test.ts similarity index 96% rename from tests/status.test.ts rename to tests/api/status.test.ts index 52f61237..2c79dc72 100644 --- a/tests/status.test.ts +++ b/tests/api/status.test.ts @@ -1,7 +1,7 @@ import { runMigrations } 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 { buildApiServer } from '../../src/api/init'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from '../helpers'; describe('Status', () => { let db: PgStore; diff --git a/tests/brc20.test.ts b/tests/brc-20/brc20.test.ts similarity index 99% rename from tests/brc20.test.ts rename to tests/brc-20/brc20.test.ts index bea3bad8..3752d4d7 100644 --- a/tests/brc20.test.ts +++ b/tests/brc-20/brc20.test.ts @@ -1,9 +1,9 @@ import { runMigrations } from '@hirosystems/api-toolkit'; -import { buildApiServer } from '../src/api/init'; -import { Brc20ActivityResponse, Brc20TokenResponse } from '../src/api/schemas'; -import { brc20FromInscription } from '../src/pg/brc20/helpers'; -import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; -import { InscriptionData } from '../src/pg/types'; +import { buildApiServer } from '../../src/api/init'; +import { Brc20ActivityResponse, Brc20TokenResponse } from '../../src/api/schemas'; +import { brc20FromInscription } from '../../src/pg/brc20/helpers'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { InscriptionData } from '../../src/pg/types'; import { TestChainhookPayloadBuilder, TestFastifyServer, @@ -11,8 +11,8 @@ import { incrementing, randomHash, rollBack, -} from './helpers'; -import { BRC20_GENESIS_BLOCK } from '../src/pg/brc20/brc20-pg-store'; +} from '../helpers'; +import { BRC20_GENESIS_BLOCK } from '../../src/pg/brc20/brc20-pg-store'; describe('BRC-20', () => { let db: PgStore; diff --git a/tests/server.test.ts b/tests/ordhook/server.test.ts similarity index 99% rename from tests/server.test.ts rename to tests/ordhook/server.test.ts index 878c5e94..101384f0 100644 --- a/tests/server.test.ts +++ b/tests/ordhook/server.test.ts @@ -1,14 +1,14 @@ -import { PREDICATE_UUID, startOrdhookServer } from '../src/ordhook/server'; -import { ENV } from '../src/env'; -import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; -import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; +import { PREDICATE_UUID, startOrdhookServer } from '../../src/ordhook/server'; +import { ENV } from '../../src/env'; +import { MIGRATIONS_DIR, PgStore } from '../../src/pg/pg-store'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from '../helpers'; import { BadPayloadRequestError, BitcoinInscriptionRevealed, BitcoinInscriptionTransferred, ChainhookEventObserver, } from '@hirosystems/chainhook-client'; -import { buildApiServer } from '../src/api/init'; +import { buildApiServer } from '../../src/api/init'; import { runMigrations } from '@hirosystems/api-toolkit'; describe('EventServer', () => { From d8271eb9fc23d6262fab68c59a0710420ebcb8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Fri, 2 Feb 2024 11:41:07 -0600 Subject: [PATCH 6/8] fix: include token in address index on brc20_total_balances (#299) --- ...74_brc20-total-balances-address-deploy-index.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 migrations/1706894983174_brc20-total-balances-address-deploy-index.ts diff --git a/migrations/1706894983174_brc20-total-balances-address-deploy-index.ts b/migrations/1706894983174_brc20-total-balances-address-deploy-index.ts new file mode 100644 index 00000000..25e79706 --- /dev/null +++ b/migrations/1706894983174_brc20-total-balances-address-deploy-index.ts @@ -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.dropIndex('brc20_total_balances', ['address']); + pgm.createIndex('brc20_total_balances', ['address', 'brc20_deploy_id']); +} + +export function down(pgm: MigrationBuilder): void { + pgm.dropIndex('brc20_total_balances', ['address', 'brc20_deploy_id']); + pgm.createIndex('brc20_total_balances', ['address']); +} From 1d9edacfd046f8878891cea41dcee8b369b2ea53 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Feb 2024 17:43:12 +0000 Subject: [PATCH 7/8] chore(release): 2.2.0-beta.2 [skip ci] ## [2.2.0-beta.2](https://github.com/hirosystems/ordinals-api/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2024-02-02) ### Bug Fixes * include token in address index on brc20_total_balances ([#299](https://github.com/hirosystems/ordinals-api/issues/299)) ([d8271eb](https://github.com/hirosystems/ordinals-api/commit/d8271eb9fc23d6262fab68c59a0710420ebcb8bf)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 293105e2..2e5b3ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.2.0-beta.2](https://github.com/hirosystems/ordinals-api/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2024-02-02) + + +### Bug Fixes + +* include token in address index on brc20_total_balances ([#299](https://github.com/hirosystems/ordinals-api/issues/299)) ([d8271eb](https://github.com/hirosystems/ordinals-api/commit/d8271eb9fc23d6262fab68c59a0710420ebcb8bf)) + ## [2.2.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v2.1.2-beta.1...v2.2.0-beta.1) (2024-02-02) From da64e46f0f9e025462abb56f1c6680b2c1b3db03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Fri, 2 Feb 2024 15:27:26 -0600 Subject: [PATCH 8/8] test: multiple transfers in 1 sat (#300) --- tests/ordhook/server.test.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/ordhook/server.test.ts b/tests/ordhook/server.test.ts index 101384f0..efcacb15 100644 --- a/tests/ordhook/server.test.ts +++ b/tests/ordhook/server.test.ts @@ -690,6 +690,34 @@ describe('EventServer', () => { .build() ) ).resolves.not.toThrow(); + await expect( + db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '0x00000000000000000002173ce6af911021497679237eb4527757f90bd8b8c645', + timestamp: 1676913207, + }) + .transaction({ + hash: 'ccff45c1f320d75228527ed92c27e5c20f973b73bc9641226009fc8156302051', + }) + .inscriptionTransferred({ + ordinal_number: 257418248345364, + tx_index: 0, + destination: { + value: '3DPjniGQeJwm8dm76F8oRD1EYvc93KfVKf', + type: 'transferred', + }, + satpoint_pre_transfer: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + satpoint_post_transfer: + 'ccff45c1f320d75228527ed92c27e5c20f973b73bc9641226009fc8156302051:0:0', + post_transfer_output_value: 9000, + }) + .build() + ) + ).resolves.not.toThrow(); }); }); });