From 7b83761818ab4a178796a1867d143ff75ef338b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Wed, 1 Nov 2023 16:42:24 -0600 Subject: [PATCH 01/42] feat!: handle transfer types and consider them for BRC-20 indexing (#258) * fix: latest api toolkit * feat: transfer type handling * fix: latest chainhook client --- .../1698856424356_locations-transfer-type.ts | 14 +++ package-lock.json | 104 +++++------------- package.json | 4 +- src/pg/brc20/brc20-pg-store.ts | 51 ++++++--- src/pg/brc20/helpers.ts | 4 - src/pg/brc20/types.ts | 3 + src/pg/pg-store.ts | 9 +- src/pg/types.ts | 7 ++ tests/brc20.test.ts | 92 ++++++++++++++-- tests/cache.test.ts | 15 ++- tests/inscriptions.test.ts | 70 +++++++++--- tests/server.test.ts | 17 ++- 12 files changed, 256 insertions(+), 134 deletions(-) create mode 100644 migrations/1698856424356_locations-transfer-type.ts diff --git a/migrations/1698856424356_locations-transfer-type.ts b/migrations/1698856424356_locations-transfer-type.ts new file mode 100644 index 00000000..c3ba335f --- /dev/null +++ b/migrations/1698856424356_locations-transfer-type.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.createType('transfer_type', ['transferred', 'spent_in_fees', 'burnt']); + pgm.addColumn('locations', { + transfer_type: { + type: 'transfer_type', + notNull: true, + }, + }); +} diff --git a/package-lock.json b/package-lock.json index e7e849ad..6675bd43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,8 @@ "@fastify/multipart": "^7.1.0", "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", - "@hirosystems/api-toolkit": "^1.1.0", - "@hirosystems/chainhook-client": "^1.3.1", + "@hirosystems/api-toolkit": "^1.3.0", + "@hirosystems/chainhook-client": "^1.4.0", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", @@ -53,69 +53,6 @@ "typescript": "^4.7.4" } }, - "../api-toolkit": { - "name": "@hirosystems/api-toolkit", - "version": "1.1.0", - "extraneous": true, - "license": "Apache 2.0", - "dependencies": { - "node-pg-migrate": "^6.2.2", - "pino": "^8.11.0", - "postgres": "^3.3.4" - }, - "bin": { - "api-toolkit-git-info": "bin/api-toolkit-git-info.js" - }, - "devDependencies": { - "@commitlint/cli": "^17.5.0", - "@commitlint/config-conventional": "^17.4.4", - "@stacks/eslint-config": "^1.2.0", - "@types/jest": "^29.5.0", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "@typescript-eslint/parser": "^5.56.0", - "babel-jest": "^29.5.0", - "copyfiles": "^2.4.1", - "eslint": "^8.36.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-tsdoc": "^0.2.17", - "husky": "^8.0.3", - "jest": "^29.5.0", - "prettier": "^2.8.6", - "rimraf": "^4.4.1", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "typescript": "^5.0.2" - } - }, - "../chainhook/components/client/typescript": { - "name": "@hirosystems/chainhook-client", - "version": "1.3.0", - "extraneous": true, - "license": "Apache 2.0", - "dependencies": { - "@fastify/type-provider-typebox": "^3.2.0", - "fastify": "^4.15.0", - "pino": "^8.11.0", - "undici": "^5.21.2" - }, - "devDependencies": { - "@stacks/eslint-config": "^1.2.0", - "@types/jest": "^29.5.0", - "@types/node": "^18.15.7", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "@typescript-eslint/parser": "^5.56.0", - "babel-jest": "^29.5.0", - "eslint": "^8.36.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-tsdoc": "^0.2.17", - "jest": "^29.5.0", - "prettier": "^2.8.7", - "rimraf": "^4.4.1", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "typescript": "^5.0.2" - } - }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1270,22 +1207,30 @@ } }, "node_modules/@hirosystems/api-toolkit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.1.0.tgz", - "integrity": "sha512-yjlNNRJ4LP+oGrK1ECmZDLutfOwLPu58wbF4eet1Dr9kr36vUGuDEuT6VZKKet7zj7RsUdWW8/SRfUDp+0bppQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.0.tgz", + "integrity": "sha512-orZSVqaZV3LJb886W90DNLPkZQ3XvMhJqKUBaIYIzBdQIoPEf5B8dp/JK3bII/WAry940ofphklL2+YVxJQIIA==", "dependencies": { + "@fastify/cors": "^8.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/type-provider-typebox": "^3.2.0", + "fastify": "^4.3.0", + "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.11.0", "postgres": "^3.3.4" }, "bin": { "api-toolkit-git-info": "bin/api-toolkit-git-info.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/@hirosystems/chainhook-client": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.3.4.tgz", - "integrity": "sha512-oXJxzpzDqbcXIR+vwnCHC62P0AN8AyfrydOO4oOaiqBkt448GB5+Rs9iKUKJt/sVOTBaIlbJvr6uchwYi3PA+w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.0.tgz", + "integrity": "sha512-WmUP4iAz/0+TPLEfzk8TMKoEqPRociSbzQJXVxA8kYrhTwu5/Bqzw7zZoVSvDY5zh6p/vaMGNH6UC4IoTAzIwA==", "dependencies": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", @@ -19712,19 +19657,24 @@ "requires": {} }, "@hirosystems/api-toolkit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.1.0.tgz", - "integrity": "sha512-yjlNNRJ4LP+oGrK1ECmZDLutfOwLPu58wbF4eet1Dr9kr36vUGuDEuT6VZKKet7zj7RsUdWW8/SRfUDp+0bppQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.0.tgz", + "integrity": "sha512-orZSVqaZV3LJb886W90DNLPkZQ3XvMhJqKUBaIYIzBdQIoPEf5B8dp/JK3bII/WAry940ofphklL2+YVxJQIIA==", "requires": { + "@fastify/cors": "^8.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/type-provider-typebox": "^3.2.0", + "fastify": "^4.3.0", + "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.11.0", "postgres": "^3.3.4" } }, "@hirosystems/chainhook-client": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.3.4.tgz", - "integrity": "sha512-oXJxzpzDqbcXIR+vwnCHC62P0AN8AyfrydOO4oOaiqBkt448GB5+Rs9iKUKJt/sVOTBaIlbJvr6uchwYi3PA+w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.0.tgz", + "integrity": "sha512-WmUP4iAz/0+TPLEfzk8TMKoEqPRociSbzQJXVxA8kYrhTwu5/Bqzw7zZoVSvDY5zh6p/vaMGNH6UC4IoTAzIwA==", "requires": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", diff --git a/package.json b/package.json index 46c97509..5bbaec54 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "@fastify/multipart": "^7.1.0", "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", - "@hirosystems/api-toolkit": "^1.1.0", - "@hirosystems/chainhook-client": "^1.3.1", + "@hirosystems/api-toolkit": "^1.3.0", + "@hirosystems/chainhook-client": "^1.4.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 60300211..1e32edad 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -1,7 +1,13 @@ import { BasePgStoreModule, logger } from '@hirosystems/api-toolkit'; import * as postgres from 'postgres'; import { hexToBuffer } from '../../api/util/helpers'; -import { DbInscription, DbInscriptionIndexPaging, DbLocation, DbPaginatedResult } from '../types'; +import { + DbInscription, + DbInscriptionIndexPaging, + DbLocation, + DbLocationTransferType, + DbPaginatedResult, +} from '../types'; import { BRC20_DEPLOYS_COLUMNS, BRC20_OPERATIONS, @@ -20,15 +26,7 @@ import { DbBrc20TokenWithSupply, DbBrc20TransferEvent, } from './types'; - -import { - Brc20Deploy, - Brc20Mint, - Brc20Transfer, - brc20FromInscriptionContent, - isAddressSentAsFee, -} from './helpers'; - +import { Brc20Deploy, Brc20Mint, Brc20Transfer, brc20FromInscriptionContent } from './helpers'; import { Brc20TokenOrderBy } from '../../api/schemas'; import { objRemoveUndefinedValues } from '../helpers'; @@ -53,7 +51,8 @@ export class Brc20PgStore extends BasePgStoreModule { const block = await sql` SELECT EXISTS(SELECT location_id FROM genesis_locations WHERE location_id = l.id) AS genesis, - l.id, l.inscription_id, l.block_height, l.tx_id, l.tx_index, l.address + l.id, l.inscription_id, l.block_height, l.tx_id, l.tx_index, l.address, + l.transfer_type FROM locations AS l INNER JOIN inscriptions AS i ON l.inscription_id = i.id WHERE l.block_height = ${blockHeight} @@ -76,7 +75,7 @@ export class Brc20PgStore extends BasePgStoreModule { if (writes.length === 0) return; for (const write of writes) { if (write.genesis) { - if (isAddressSentAsFee(write.address)) continue; + if (write.transfer_type != DbLocationTransferType.transferred) continue; const content = await this.sql<{ content: string }[]>` SELECT content FROM inscriptions WHERE id = ${write.inscription_id} `; @@ -113,9 +112,15 @@ export class Brc20PgStore extends BasePgStoreModule { if (fromAddressRes.count === 0) return; const fromAddress = fromAddressRes[0].from_address; // Is this transfer sent as fee or from the same sender? If so, we'll return the balance. + // Is it burnt? Mark as empty owner. const returnToSender = - isAddressSentAsFee(location.address) || fromAddress == location.address; - const toAddress = returnToSender ? fromAddress : location.address; + location.transfer_type == DbLocationTransferType.spentInFees || + fromAddress == location.address; + const toAddress = returnToSender + ? fromAddress + : location.transfer_type == DbLocationTransferType.burnt + ? '' + : location.address; // Check if we have a valid transfer inscription emitted by this address that hasn't been sent // to another address before. Use `LIMIT 3` as a quick way of checking if we have just inserted // the first transfer for this inscription (genesis + transfer). @@ -226,7 +231,11 @@ export class Brc20PgStore extends BasePgStoreModule { op: Brc20Deploy; location: DbBrc20Location; }): Promise { - if (!deploy.location.inscription_id || isAddressSentAsFee(deploy.location.address)) return; + if ( + deploy.location.transfer_type != DbLocationTransferType.transferred || + !deploy.location.inscription_id + ) + return; const insert: DbBrc20DeployInsert = { inscription_id: deploy.location.inscription_id, block_height: deploy.location.block_height, @@ -272,7 +281,11 @@ export class Brc20PgStore extends BasePgStoreModule { } private async insertMint(mint: { op: Brc20Mint; location: DbBrc20Location }): Promise { - if (!mint.location.inscription_id || isAddressSentAsFee(mint.location.address)) return; + if ( + mint.location.transfer_type != DbLocationTransferType.transferred || + !mint.location.inscription_id + ) + return; // Check the following conditions: // * Is the mint amount within the allowed token limits? // * Is the number of decimals correct? @@ -349,7 +362,11 @@ export class Brc20PgStore extends BasePgStoreModule { op: Brc20Transfer; location: DbBrc20Location; }): Promise { - if (!transfer.location.inscription_id || isAddressSentAsFee(transfer.location.address)) return; + if ( + transfer.location.transfer_type != DbLocationTransferType.transferred || + !transfer.location.inscription_id + ) + return; // Check the following conditions: // * Do we have enough available balance to do this transfer? const transferRes = await this.sql` diff --git a/src/pg/brc20/helpers.ts b/src/pg/brc20/helpers.ts index a80f0f52..de9d50ab 100644 --- a/src/pg/brc20/helpers.ts +++ b/src/pg/brc20/helpers.ts @@ -79,7 +79,3 @@ export function brc20FromInscriptionContent(content: string): Brc20 | undefined // Not a BRC-20 inscription. } } - -export function isAddressSentAsFee(address: string | null): boolean { - return address === null || address.length === 0; -} diff --git a/src/pg/brc20/types.ts b/src/pg/brc20/types.ts index 03d77e9c..0bb72f33 100644 --- a/src/pg/brc20/types.ts +++ b/src/pg/brc20/types.ts @@ -1,3 +1,5 @@ +import { DbLocationTransferType } from '../types'; + export type DbBrc20Location = { id: string; inscription_id: string | null; @@ -5,6 +7,7 @@ export type DbBrc20Location = { tx_id: string; tx_index: number; address: string | null; + transfer_type: DbLocationTransferType; }; export type DbBrc20ScannedInscription = DbBrc20Location & { diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 288f8352..5fd601aa 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -32,11 +32,13 @@ import { DbLocation, DbLocationPointer, DbLocationPointerInsert, + DbLocationTransferType, DbPaginatedResult, DbRevealInsert, INSCRIPTIONS_COLUMNS, LOCATIONS_COLUMNS, } from './types'; +import { toEnumValue } from '@hirosystems/api-toolkit'; export const MIGRATIONS_DIR = path.join(__dirname, '../../migrations'); @@ -154,6 +156,7 @@ export class PgStore extends BasePgStore { prev_offset: null, value: reveal.inscription_output_value.toString(), timestamp: event.timestamp, + transfer_type: DbLocationTransferType.transferred, }, recursive_refs, }); @@ -191,6 +194,7 @@ export class PgStore extends BasePgStore { prev_offset: null, value: reveal.inscription_output_value.toString(), timestamp: event.timestamp, + transfer_type: DbLocationTransferType.transferred, }, recursive_refs, }); @@ -206,7 +210,7 @@ export class PgStore extends BasePgStore { tx_id, tx_index: transfer.tx_index, genesis_id: transfer.inscription_id, - address: transfer.updated_address, + address: transfer.destination.value ?? null, output: `${satpoint.tx_id}:${satpoint.vout}`, offset: satpoint.offset ?? null, prev_output: `${prevSatpoint.tx_id}:${prevSatpoint.vout}`, @@ -215,6 +219,9 @@ export class PgStore extends BasePgStore { ? transfer.post_transfer_output_value.toString() : null, timestamp: event.timestamp, + transfer_type: + toEnumValue(DbLocationTransferType, transfer.destination.type) ?? + DbLocationTransferType.transferred, }, }); } diff --git a/src/pg/types.ts b/src/pg/types.ts index 73c3e693..83538a2f 100644 --- a/src/pg/types.ts +++ b/src/pg/types.ts @@ -34,6 +34,12 @@ export type DbFullyLocatedInscriptionResult = { recursion_refs: string | null; }; +export enum DbLocationTransferType { + transferred = 'transferred', + spentInFees = 'spent_in_fees', + burnt = 'burnt', +} + export type DbLocationInsert = { genesis_id: string; block_height: number; @@ -47,6 +53,7 @@ export type DbLocationInsert = { prev_offset: PgNumeric | null; value: PgNumeric | null; timestamp: number; + transfer_type: DbLocationTransferType; }; export type DbLocation = { diff --git a/tests/brc20.test.ts b/tests/brc20.test.ts index 042fa51f..fa206798 100644 --- a/tests/brc20.test.ts +++ b/tests/brc20.test.ts @@ -1473,7 +1473,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -1571,7 +1571,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: '', // Sent as fee + destination: { type: 'spent_in_fees', value: '' }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -1599,6 +1599,74 @@ describe('BRC-20', () => { ]); }); + test('sending transfer to unspendable output does not return to sender', async () => { + const address = 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td'; + await deployAndMintPEPE(address); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775619, + hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', + }) + .transaction({ + hash: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', + }) + .inscriptionRevealed( + brc20Reveal({ + json: { + p: 'brc-20', + op: 'transfer', + tick: 'PEPE', + amt: '9000', + }, + number: 7, + tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', + address: address, + }) + ) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775620, + hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', + }) + .transaction({ + hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', + }) + .inscriptionTransferred({ + inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + destination: { type: 'burnt' }, + satpoint_pre_transfer: + 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', + satpoint_post_transfer: + '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', + post_transfer_output_value: null, + tx_index: 0, + }) + .build() + ); + + const response1 = await fastify.inject({ + method: 'GET', + url: `/ordinals/brc-20/balances/${address}`, + }); + expect(response1.statusCode).toBe(200); + const json1 = response1.json(); + expect(json1.total).toBe(1); + expect(json1.results).toStrictEqual([ + { + available_balance: '1000.000000000000000000', + overall_balance: '1000.000000000000000000', + ticker: 'PEPE', + transferrable_balance: '0.000000000000000000', + }, + ]); + }); + test('cannot spend valid transfer twice', async () => { const address = 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td'; const address2 = '3QNjwPDRafjBm9XxJpshgk3ksMJh3TFxTU'; @@ -1640,7 +1708,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -1664,7 +1732,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address, + destination: { type: 'transferred', value: address }, satpoint_pre_transfer: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', satpoint_post_transfer: @@ -1750,7 +1818,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205i0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205:0:0', satpoint_post_transfer: @@ -1821,7 +1889,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: '09a812f72275892b4858880cf3821004a6e8885817159b340639afe9952ac053i0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '486815e61723d03af344e1256d7e0c028a8e9e71eb38157f4bf069eb94292ee1:0:0', satpoint_post_transfer: @@ -2135,7 +2203,7 @@ describe('BRC-20', () => { .transaction({ hash: txHashTransferSend }) .inscriptionTransferred({ inscription_id: `${txHashTransfer}i0`, - updated_address: addressA, + destination: { type: 'transferred', value: addressA }, satpoint_pre_transfer: `${txHashTransfer}:0:0`, satpoint_post_transfer: `${txHashTransferSend}:0:0`, post_transfer_output_value: null, @@ -2437,7 +2505,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: randomHash() }) .inscriptionTransferred({ - updated_address: addressB, + destination: { type: 'transferred', value: addressB }, tx_index: 0, inscription_id: `${transferHash}i0`, post_transfer_output_value: null, @@ -2788,7 +2856,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: transferHashABSend }) .inscriptionTransferred({ - updated_address: addressB, + destination: { type: 'transferred', value: addressB }, tx_index: 0, inscription_id: `${transferHashAB}i0`, post_transfer_output_value: null, @@ -2876,7 +2944,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: transferHashBCSend }) .inscriptionTransferred({ - updated_address: addressC, + destination: { type: 'transferred', value: addressC }, tx_index: 0, inscription_id: `${transferHashBC}i0`, post_transfer_output_value: null, @@ -3226,7 +3294,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -3321,7 +3389,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966fi0', - updated_address: address, // To self + destination: { type: 'transferred', value: address }, // To self satpoint_pre_transfer: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966f:0:0', satpoint_post_transfer: diff --git a/tests/cache.test.ts b/tests/cache.test.ts index e789418a..6d7de35e 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -85,7 +85,10 @@ describe('ETag cache', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: 'da2da520f055e9fadaf1a78b3e01bc53596dcbb88e9c9f53bcb61b98310b1006:0:0', satpoint_post_transfer: @@ -119,7 +122,10 @@ describe('ETag cache', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -217,7 +223,10 @@ describe('ETag cache', () => { .transaction({ hash: 'ae9d273a10e899f0d2cad47ee2b0e77ab8a9addd9dd5bb5e4b03d6971c060d52' }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + destination: { + type: 'transferred', + value: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index bb42410d..6767fc0b 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -506,7 +506,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -558,7 +561,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + destination: { + type: 'transferred', + value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -676,7 +682,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -763,7 +772,10 @@ describe('/inscriptions', () => { // Transfer 1 .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1qv7d2dgyvtctv7ya4t3ysy4c2s8qz4nm8t6dvm3', + destination: { + type: 'transferred', + value: 'bc1qv7d2dgyvtctv7ya4t3ysy4c2s8qz4nm8t6dvm3', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -774,7 +786,10 @@ describe('/inscriptions', () => { // Transfer 2 .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -861,7 +876,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -913,7 +931,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + destination: { + type: 'transferred', + value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -1005,7 +1026,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -1061,7 +1085,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + destination: { + type: 'transferred', + value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -1193,7 +1220,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -1206,7 +1236,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cd:0:0', satpoint_post_transfer: @@ -1292,7 +1325,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + destination: { + type: 'transferred', + value: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -1303,7 +1339,10 @@ describe('/inscriptions', () => { // Transfer for same inscription in same block .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + destination: { + type: 'transferred', + value: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + }, satpoint_pre_transfer: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0:0', satpoint_post_transfer: @@ -2646,7 +2685,10 @@ describe('/inscriptions', () => { // Transfers affect result totals correctly const transfer2: BitcoinInscriptionTransferred = { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + destination: { + type: 'transferred', + value: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: diff --git a/tests/server.test.ts b/tests/server.test.ts index 49b8055b..f0785521 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -171,7 +171,10 @@ describe('EventServer', () => { const transfer: BitcoinInscriptionTransferred = { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf00000', + destination: { + type: 'transferred', + value: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf00000', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -281,7 +284,10 @@ describe('EventServer', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', + destination: { + type: 'transferred', + value: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -304,7 +310,10 @@ describe('EventServer', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', + destination: { + type: 'transferred', + value: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', + }, satpoint_pre_transfer: '9e2414153b1893f799477f7e1a00a52fafc235de72fd215cb3321f253c4464ac:0:0', satpoint_post_transfer: @@ -423,7 +432,7 @@ describe('EventServer', () => { }) .inscriptionTransferred({ inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', satpoint_post_transfer: From b1dc1335b7e1a8ae16e0835be45f7fb045799363 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 1 Nov 2023 22:45:49 +0000 Subject: [PATCH 02/42] chore(release): 2.0.0-beta.1 [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [2.0.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v1.2.6...v2.0.0-beta.1) (2023-11-01) ### ⚠ BREAKING CHANGES * handle transfer types and consider them for BRC-20 indexing (#258) ### Features * handle transfer types and consider them for BRC-20 indexing ([#258](https://github.com/hirosystems/ordinals-api/issues/258)) ([7b83761](https://github.com/hirosystems/ordinals-api/commit/7b83761818ab4a178796a1867d143ff75ef338b0)) --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d452b3d1..7d1d2c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [2.0.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v1.2.6...v2.0.0-beta.1) (2023-11-01) + + +### ⚠ BREAKING CHANGES + +* handle transfer types and consider them for BRC-20 indexing (#258) + +### Features + +* handle transfer types and consider them for BRC-20 indexing ([#258](https://github.com/hirosystems/ordinals-api/issues/258)) ([7b83761](https://github.com/hirosystems/ordinals-api/commit/7b83761818ab4a178796a1867d143ff75ef338b0)) + ## [1.2.6](https://github.com/hirosystems/ordinals-api/compare/v1.2.5...v1.2.6) (2023-10-11) From 18cd028b636184a96597fa0b0978ba9e4d23f55c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Wed, 1 Nov 2023 20:29:29 -0600 Subject: [PATCH 03/42] fix: reject blocks that would create blessed inscription gaps (#259) * fix: prevent blessed gaps * fix: inscription tests * fix: cache tests * fix: sats test * fix: server tests * fix: stats tests * fix: status tests * fix: brc-20 tests * test: gap detection * test: past block ignore * fix: recursion backfill test --- src/pg/pg-store.ts | 50 ++-- tests/brc20.test.ts | 108 ++++---- tests/cache.test.ts | 67 ++--- tests/inscriptions.test.ts | 498 +++++++++++-------------------------- tests/sats.test.ts | 6 +- tests/server.test.ts | 317 ++++++++++++++--------- tests/stats.test.ts | 121 ++++----- tests/status.test.ts | 4 +- 8 files changed, 483 insertions(+), 688 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 5fd601aa..ff663ec1 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -87,6 +87,12 @@ export class PgStore extends BasePgStore { async updateInscriptions(payload: Payload): Promise { let updatedBlockHeightMin = Infinity; await this.sqlWriteTransaction(async sql => { + // Check where we're at in terms of ingestion, e.g. block height and max blessed inscription + // number. This will let us determine if we should skip ingesting this block or throw an error + // if a gap is detected. + let blessedNumber = (await this.getMaxInscriptionNumber()) ?? -1; + const currentBlockHeight = await this.getChainTipBlockHeight(); + for (const rollbackEvent of payload.rollback) { // TODO: Optimize rollbacks just as we optimized applys. const event = rollbackEvent as BitcoinEvent; @@ -118,6 +124,12 @@ export class PgStore extends BasePgStore { for (const applyEvent of payload.apply) { const event = applyEvent as BitcoinEvent; const block_height = event.block_identifier.index; + if (block_height <= currentBlockHeight && block_height !== 767430) { + logger.info( + `PgStore skipping ingestion for previously seen block ${block_height}, current chain tip is at ${currentBlockHeight}` + ); + return; + } const block_hash = normalizedHexString(event.block_identifier.hash); const writes: DbRevealInsert[] = []; for (const tx of event.transactions) { @@ -125,6 +137,11 @@ export class PgStore extends BasePgStore { for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) { const reveal = operation.inscription_revealed; + if (blessedNumber + 1 !== reveal.inscription_number) + throw Error( + `PgStore inscription gap detected: Attempting to insert #${reveal.inscription_number} (${block_height}) but current max is #${blessedNumber}` + ); + blessedNumber = reveal.inscription_number; const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); @@ -237,15 +254,8 @@ export class PgStore extends BasePgStore { } }); await this.refreshMaterializedView('chain_tip'); - // Skip expensive view refreshes if we're not streaming live blocks. - if (payload.chainhook.is_streaming_blocks) { - // We'll issue materialized view refreshes in parallel. We will not wait for them to finish so - // we can respond to the chainhook node with a `200` HTTP code as soon as possible. - const views = [this.normalizeInscriptionCount({ min_block_height: updatedBlockHeightMin })]; - const viewRefresh = Promise.allSettled(views); - // Only wait for these on tests. - if (isTestEnv) await viewRefresh; - } + if (updatedBlockHeightMin !== Infinity) + await this.normalizeInscriptionCount({ min_block_height: updatedBlockHeightMin }); } async getChainTipBlockHeight(): Promise { @@ -403,8 +413,10 @@ export class PgStore extends BasePgStore { } ${filters?.to_sat_ordinal ? sql`AND i.sat_ordinal <= ${filters.to_sat_ordinal}` : sql``} ${filters?.number?.length ? sql`AND i.number IN ${sql(filters.number)}` : sql``} - ${filters?.from_number ? sql`AND i.number >= ${filters.from_number}` : sql``} - ${filters?.to_number ? sql`AND i.number <= ${filters.to_number}` : sql``} + ${ + filters?.from_number !== undefined ? sql`AND i.number >= ${filters.from_number}` : sql`` + } + ${filters?.to_number !== undefined ? sql`AND i.number <= ${filters.to_number}` : sql``} ${filters?.address?.length ? sql`AND cur.address IN ${sql(filters.address)}` : sql``} ${filters?.mime_type?.length ? sql`AND i.mime_type IN ${sql(filters.mime_type)}` : sql``} ${filters?.output ? sql`AND cur_l.output = ${filters.output}` : sql``} @@ -648,7 +660,6 @@ export class PgStore extends BasePgStore { SET updated_at = NOW() WHERE genesis_id IN ${sql([...transferGenesisIds])} `; - await this.backfillOrphanLocations(); await this.updateInscriptionLocationPointers(locations); await this.counts.applyInscriptions(inscriptions); } @@ -825,21 +836,6 @@ export class PgStore extends BasePgStore { }); } - private async backfillOrphanLocations(): Promise { - await this.sqlWriteTransaction(async sql => { - await sql` - UPDATE locations AS l - SET inscription_id = (SELECT id FROM inscriptions WHERE genesis_id = l.genesis_id) - WHERE l.inscription_id IS NULL - `; - await sql` - UPDATE inscription_recursions AS l - SET ref_inscription_id = (SELECT id FROM inscriptions WHERE genesis_id = l.ref_inscription_genesis_id) - WHERE l.ref_inscription_id IS NULL - `; - }); - } - private async recalculateCurrentLocationPointerFromLocationRollBack(args: { location: DbLocation; }): Promise { diff --git a/tests/brc20.test.ts b/tests/brc20.test.ts index fa206798..12c8cfb2 100644 --- a/tests/brc20.test.ts +++ b/tests/brc20.test.ts @@ -36,7 +36,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '250000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -61,7 +61,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '10000', }, - number: 6, + number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -86,7 +86,7 @@ describe('BRC-20', () => { const content = Buffer.from(JSON.stringify(json), 'utf-8'); const insert: DbInscriptionInsert = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 1, + number: 0, mime_type: 'application/json', content_type: 'application/json', content_length: content.length, @@ -113,7 +113,7 @@ describe('BRC-20', () => { ); const insert: DbInscriptionInsert = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 1, + number: 0, mime_type: 'foo/bar', content_type: 'foo/bar;x=1', content_length: content.length, @@ -141,7 +141,7 @@ describe('BRC-20', () => { ); const insert: DbInscriptionInsert = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 1, + number: 0, mime_type: 'application/json', content_type: 'application/json', content_length: content.length, @@ -487,7 +487,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -507,7 +507,7 @@ describe('BRC-20', () => { block_height: 775617, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 5, + number: 0, mint_limit: null, max_supply: '21000000.000000000000000000', ticker: 'PEPE', @@ -538,7 +538,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -563,7 +563,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '19000000', }, - number: 6, + number: 1, tx_id: '3f8067a6e9b45308b5a090c2987feeb2d08cbaf814ef2ffabad7c381b62f5f7e', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -585,7 +585,7 @@ describe('BRC-20', () => { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', mint_limit: null, - number: 5, + number: 0, ticker: 'PEPE', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', deploy_timestamp: 1677803510000, @@ -614,7 +614,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -639,7 +639,7 @@ describe('BRC-20', () => { tick: 'pepe', max: '19000000', }, - number: 6, + number: 1, tx_id: '3f8067a6e9b45308b5a090c2987feeb2d08cbaf814ef2ffabad7c381b62f5f7e', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -661,7 +661,7 @@ describe('BRC-20', () => { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', mint_limit: null, - number: 5, + number: 0, ticker: 'PEPE', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', deploy_timestamp: 1677803510000, @@ -684,7 +684,7 @@ describe('BRC-20', () => { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', mint_limit: null, - number: 5, + number: 0, ticker: 'PEPE', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', deploy_timestamp: 1677803510000, @@ -716,7 +716,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -741,7 +741,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000', }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -784,7 +784,7 @@ describe('BRC-20', () => { tick: 'pepe', amt: '100000', }, - number: 7, + number: 2, tx_id: '7a1adbc3e93ddf8d7c4e0ba75aa11c98c431521dd850be8b955feedb716d8bec', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -842,7 +842,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -867,7 +867,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000', }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -893,7 +893,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000', }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -938,7 +938,7 @@ describe('BRC-20', () => { max: '21000000', dec: '1', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -963,7 +963,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000.000', // Invalid decimal count }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -1002,7 +1002,7 @@ describe('BRC-20', () => { max: '2500', dec: '1', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1027,7 +1027,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 6, + number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1043,7 +1043,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 7, + number: 2, tx_id: '7e09bda2cba34bca648cca6d79a074940d39b6137150d3a3edcf80c0e01419a5', address: address, }) @@ -1059,7 +1059,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '5000000000', // Exceeds supply }, - number: 8, + number: 3, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -1102,7 +1102,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 9, + number: 4, tx_id: 'bf7a3e1a0647ca88f6539119b2defaec302683704ea270b3302e709597643548', address: address, }) @@ -1139,7 +1139,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 6, + number: 0, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1179,7 +1179,7 @@ describe('BRC-20', () => { dec: '1', lim: '100', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1204,7 +1204,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', // Greater than limit }, - number: 6, + number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1245,7 +1245,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1299,7 +1299,7 @@ describe('BRC-20', () => { tick: 'TEST', // Not found amt: '2000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1345,7 +1345,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '5000000000', // More than was minted }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1391,7 +1391,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1407,7 +1407,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', // Will exceed available balance }, - number: 8, + number: 3, tx_id: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', address: address, }) @@ -1454,7 +1454,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1552,7 +1552,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1620,7 +1620,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1689,7 +1689,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1799,7 +1799,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '20', }, - number: 10, + number: 2, tx_id: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205', address: address, }) @@ -1858,7 +1858,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '20', }, - number: 11, + number: 3, tx_id: '09a812f72275892b4858880cf3821004a6e8885817159b340639afe9952ac053', address: address2, }) @@ -1932,7 +1932,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -1947,7 +1947,7 @@ describe('BRC-20', () => { expect(response.json()).toStrictEqual({ token: { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 5, + number: 0, block_height: 775617, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', @@ -1968,7 +1968,7 @@ describe('BRC-20', () => { }); test('tokens filter by ticker prefix', async () => { - const inscriptionNumbers = incrementing(1); + const inscriptionNumbers = incrementing(0); const blockHeights = incrementing(775600); let transferHash = randomHash(); @@ -2076,7 +2076,7 @@ describe('BRC-20', () => { test('tokens using order_by tx_count', async () => { // Setup - const inscriptionNumbers = incrementing(1); + const inscriptionNumbers = incrementing(0); const blockHeights = incrementing(775600); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -2317,7 +2317,7 @@ describe('BRC-20', () => { describe('/brc-20/activity', () => { test('activity for token transfers', async () => { // Setup - const inscriptionNumbers = incrementing(1); + const inscriptionNumbers = incrementing(0); const blockHeights = incrementing(775600); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -2573,7 +2573,7 @@ describe('BRC-20', () => { // Step 7: B transfer_send the transfer to C // Setup - const inscriptionNumbers = incrementing(1); + const inscriptionNumbers = incrementing(0); const blockHeights = incrementing(775600); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -3026,7 +3026,7 @@ describe('BRC-20', () => { }); test('activity for multiple token creation', async () => { - const inscriptionNumbers = incrementing(1); + const inscriptionNumbers = incrementing(0); const blockHeights = incrementing(775600); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; @@ -3179,7 +3179,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', }, - number: 999, + number: 2, tx_id: '633648e0e1ddcab8dea0496a561f2b08c486ae619b5634d7bb55d7f0cd32ef16', address: 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl', }) @@ -3225,7 +3225,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '250000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl', }) @@ -3276,7 +3276,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -3322,7 +3322,7 @@ describe('BRC-20', () => { tick: '🔥', max: '1000', }, - number: 50, + number: 3, tx_id: '8354e85e87fa2df8b3a06ec0b9d395559b95174530cb19447fc4df5f6d4ca84d', address: address, }) @@ -3346,7 +3346,7 @@ describe('BRC-20', () => { tick: '🔥', amt: '500', }, - number: 60, + number: 4, tx_id: '81f4ee2c247c5f5c0d3a6753fef706df410ea61c2aa6d370003b98beb041b887', address: address, }) @@ -3371,7 +3371,7 @@ describe('BRC-20', () => { tick: '🔥', amt: '100', }, - number: 90, + number: 5, tx_id: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966f', address: address, }) diff --git a/tests/cache.test.ts b/tests/cache.test.ts index 6d7de35e..eda268f3 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -27,7 +27,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -54,7 +54,7 @@ describe('ETag cache', () => { // Check on numbered id too const nResponse = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', }); expect(nResponse.statusCode).toBe(200); expect(nResponse.headers.etag).not.toBeUndefined(); @@ -70,7 +70,7 @@ describe('ETag cache', () => { expect(cached.statusCode).toBe(304); const nCached = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', headers: { 'if-none-match': etag1 }, }); expect(nCached.statusCode).toBe(304); @@ -79,7 +79,7 @@ describe('ETag cache', () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() - .block({ height: 775700, timestamp: 1678122360 }) + .block({ height: 775618, timestamp: 1678122360 }) .transaction({ hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) @@ -106,7 +106,7 @@ describe('ETag cache', () => { expect(cached2.statusCode).toBe(200); const nCached2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', headers: { 'if-none-match': etag1 }, }); expect(nCached2.statusCode).toBe(200); @@ -116,7 +116,7 @@ describe('ETag cache', () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() - .block({ height: 775690, timestamp: 1678122360 }) + .block({ height: 775619, timestamp: 1678122360 }) .transaction({ hash: 'bebb1357c97d2348eb8ef24e1d8639ff79c8847bf12999ca7fef463489b40f0f', }) @@ -143,7 +143,7 @@ describe('ETag cache', () => { expect(cached3.statusCode).toBe(200); const nCached3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', headers: { 'if-none-match': etag2 }, }); expect(nCached3.statusCode).toBe(200); @@ -158,7 +158,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -176,13 +176,13 @@ describe('ETag cache', () => { await db.updateInscriptions(block1); const block2 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 775617 }) + .block({ height: 778576 }) .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 2, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -219,7 +219,7 @@ describe('ETag cache', () => { // New location const block3 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 775618 }) + .block({ height: 778577 }) .transaction({ hash: 'ae9d273a10e899f0d2cad47ee2b0e77ab8a9addd9dd5bb5e4b03d6971c060d52' }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -242,39 +242,6 @@ describe('ETag cache', () => { headers: { 'if-none-match': etag }, }); expect(cached2.statusCode).toBe(200); - const etag2 = cached2.headers.etag; - - // Upsert genesis location - const block4 = new TestChainhookPayloadBuilder() - .apply() - .block({ height: 778575 }) - .transaction({ hash: '0x9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201' }) - .inscriptionRevealed({ - content_bytes: '0x48656C6C6F', - content_type: 'text/plain', - content_length: 5, - inscription_number: 7, - inscription_fee: 705, - inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - inscription_output_value: 10000, - inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - ordinal_number: 257418248345364, - ordinal_block_height: 650000, - ordinal_offset: 0, - satpoint_post_inscription: - '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build(); - await db.updateInscriptions(block4); - const cached3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions', - headers: { 'if-none-match': etag2 }, - }); - expect(cached3.statusCode).toBe(200); }); test('inscriptions stats per block cache control', async () => { @@ -286,7 +253,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -323,13 +290,13 @@ describe('ETag cache', () => { // New block const block2 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 778577, hash: randomHash() }) + .block({ height: 778576, hash: randomHash() }) .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 2, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -364,7 +331,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -401,13 +368,13 @@ describe('ETag cache', () => { // New block const block2 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 778577, hash: randomHash() }) + .block({ height: 778576, hash: randomHash() }) .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 2, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index 6767fc0b..e101ac17 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -39,7 +39,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 188, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -59,7 +59,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -70,7 +70,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -90,14 +90,14 @@ describe('/inscriptions', () => { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778576, content_length: 5, mime_type: 'image/png', content_type: 'image/png', genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 1, value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', @@ -124,7 +124,7 @@ describe('/inscriptions', () => { // By inscription number const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/1', }); expect(response2.statusCode).toBe(200); expect(response2.json()).toStrictEqual(expected); @@ -146,7 +146,7 @@ describe('/inscriptions', () => { content_bytes: `0x${Buffer.from('World').toString('hex')}`, content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 188, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -167,7 +167,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 189, + inscription_number: 1, inscription_fee: 2805, inscription_id: 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', inscription_output_value: 10000, @@ -187,7 +187,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -204,7 +204,7 @@ describe('/inscriptions', () => { ).toString('hex')}`, content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 2, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -224,14 +224,14 @@ describe('/inscriptions', () => { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778576, content_length: 5, mime_type: 'image/png', content_type: 'image/png', genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 2, value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', @@ -261,53 +261,10 @@ describe('/inscriptions', () => { // By inscription number const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/2', }); expect(response2.statusCode).toBe(200); expect(response2.json()).toStrictEqual(expected); - - // Backfill new inscription - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ - height: 778600, - hash: '000000000000000000043b10697970720b44c79f6ca2dd604cc83cc015e0c459', - timestamp: 1676913207, - }) - .transaction({ - hash: 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5a', - }) - .inscriptionRevealed({ - content_bytes: `0x${Buffer.from('World').toString('hex')}`, - content_type: 'text/plain;charset=utf-8', - content_length: 5, - inscription_number: 200, - inscription_fee: 705, - inscription_id: 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5ai0', - inscription_output_value: 10000, - inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - ordinal_number: 257418248345364, - ordinal_block_height: 650000, - ordinal_offset: 0, - satpoint_post_inscription: - 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5a:0:0', - tx_index: 0, - inscription_input_index: 0, - transfers_pre_inscription: 0, - }) - .build() - ); - const response3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(response3.statusCode).toBe(200); - expect(response3.json().recursion_refs).toStrictEqual([ - '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', - 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5ai0', - ]); }); test('shows inscription with null genesis address', async () => { @@ -326,7 +283,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -353,7 +310,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', @@ -380,7 +337,7 @@ describe('/inscriptions', () => { // By inscription number const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', }); expect(response2.statusCode).toBe(200); expect(response2.json()).toStrictEqual(expected); @@ -479,7 +436,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -535,7 +492,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, value: '9000', tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', sat_ordinal: '257418248345364', @@ -590,7 +547,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, tx_id: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', value: '8000', sat_ordinal: '257418248345364', @@ -607,127 +564,6 @@ 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', - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 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, - recursive: false, - recursion_refs: null, - }); - - // Insert real genesis - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ height: 775610, timestamp: 1678122360 }) - .transaction({ - hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', - }) - .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - destination: { - type: 'transferred', - value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', - }, - satpoint_pre_transfer: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', - satpoint_post_transfer: - 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', - post_transfer_output_value: 9000, - tx_index: 0, - }) - .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, - recursive: false, - recursion_refs: null, - }); - }); - test('shows correct data when multiple transfers happen in the same block', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() @@ -744,7 +580,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 9000, @@ -815,7 +651,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, value: '8000', tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', sat_ordinal: '257418248345364', @@ -995,7 +831,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -1158,7 +994,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -1179,7 +1015,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 2805, inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', inscription_output_value: 10000, @@ -1259,7 +1095,7 @@ describe('/inscriptions', () => { expect(json2.results).toStrictEqual([ { id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', - number: 8, + number: 1, from: { address: 'bc1ptrehxtus25xx8jp5pchljxg2aps7mdemc4264zzzsdcvs6q25hhsf3rrph', block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', @@ -1285,7 +1121,7 @@ describe('/inscriptions', () => { }, { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 7, + number: 0, from: { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', @@ -1362,7 +1198,7 @@ describe('/inscriptions', () => { expect(json3.results).toStrictEqual([ { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 7, + number: 0, from: { address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', @@ -1388,7 +1224,7 @@ describe('/inscriptions', () => { }, { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 7, + number: 0, from: { address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', @@ -1433,7 +1269,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1453,7 +1289,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1490,53 +1326,53 @@ describe('/inscriptions', () => { expect(responseJson1.total).toBe(2); expect(responseJson1.results).toStrictEqual([ { - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - genesis_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 778575, + genesis_block_height: 778576, content_length: 5, - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - genesis_fee: '705', - id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + mime_type: 'image/png', + content_type: 'image/png', + genesis_fee: '2805', + id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 1, value: '10000', sat_ordinal: '257418248345364', + tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_coinbase_height: 51483, - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - location: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', + location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', sat_rarity: 'common', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', timestamp: 1676913207000, genesis_timestamp: 1676913207000, - genesis_tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + genesis_tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', curse_type: null, recursive: false, recursion_refs: null, }, { - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + genesis_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778575, content_length: 5, - mime_type: 'image/png', - content_type: 'image/png', - genesis_fee: '2805', - id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + mime_type: 'text/plain', + content_type: 'text/plain;charset=utf-8', + genesis_fee: '705', + id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', offset: '0', - number: 1, + number: 0, value: '10000', sat_ordinal: '257418248345364', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_coinbase_height: 51483, - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', + location: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', sat_rarity: 'common', + tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', timestamp: 1676913207000, genesis_timestamp: 1676913207000, - genesis_tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + genesis_tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', curse_type: null, recursive: false, recursion_refs: null, @@ -1561,7 +1397,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1581,7 +1417,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1627,7 +1463,7 @@ describe('/inscriptions', () => { genesis_fee: '705', id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', offset: '0', - number: 7, + number: 0, value: '10000', sat_ordinal: '257418248345364', sat_coinbase_height: 51483, @@ -1655,7 +1491,7 @@ describe('/inscriptions', () => { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778576, content_length: 5, mime_type: 'image/png', content_type: 'image/png', @@ -1686,8 +1522,8 @@ describe('/inscriptions', () => { expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(2); - expect(responseJson3.results[0]).toStrictEqual(result1); - expect(responseJson3.results[1]).toStrictEqual(result2); + expect(responseJson3.results[0]).toStrictEqual(result2); + expect(responseJson3.results[1]).toStrictEqual(result1); }); test('index filtered by sat rarity', async () => { @@ -1706,7 +1542,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1726,7 +1562,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1797,7 +1633,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1817,7 +1653,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1892,7 +1728,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1912,7 +1748,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1923,7 +1759,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 50, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -1942,25 +1778,25 @@ describe('/inscriptions', () => { const response1 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?number=7', + url: '/ordinals/v1/inscriptions?number=0', }); expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(1); - expect(responseJson1.results[0].number).toBe(7); + expect(responseJson1.results[0].number).toBe(0); const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?number=50', + url: '/ordinals/v1/inscriptions?number=1', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(1); - expect(responseJson2.results[0].number).toBe(50); + expect(responseJson2.results[0].number).toBe(1); const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?number=7&number=50', + url: '/ordinals/v1/inscriptions?number=0&number=1', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); @@ -1983,7 +1819,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2003,7 +1839,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -2033,17 +1869,17 @@ describe('/inscriptions', () => { const response1 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?genesis_block=775617', + url: '/ordinals/v1/inscriptions?genesis_block=778576', }); expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(1); expect(responseJson1.results.length).toBe(1); - expect(responseJson1.results[0].genesis_block_height).toBe(775617); + expect(responseJson1.results[0].genesis_block_height).toBe(778576); const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_genesis_block_height=778000', + url: '/ordinals/v1/inscriptions?to_genesis_block_height=778575', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); @@ -2053,19 +1889,19 @@ describe('/inscriptions', () => { const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?to_genesis_block_height=778000', + url: '/ordinals/v1/inscriptions?from_genesis_block_height=778576', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(1); expect(responseJson3.results.length).toBe(1); - expect(responseJson3.results[0].genesis_block_height).toBe(775617); + expect(responseJson3.results[0].genesis_block_height).toBe(778576); await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 778580, + height: 778577, hash: '000000000000000000003ac2d5b588bc97a5479d25e403cffd90bd60c9680cfc', timestamp: 1676913207, }) @@ -2076,7 +1912,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 70, + inscription_number: 2, inscription_fee: 705, inscription_id: '25b372de3de0cb6fcc52c89a8bc3fb78eec596521ba20de16e53c1585be7c3fci0', inscription_output_value: 10000, @@ -2094,13 +1930,13 @@ describe('/inscriptions', () => { ); const response4 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_genesis_block_height=778000&to_genesis_block_height=779000', + url: '/ordinals/v1/inscriptions?from_genesis_block_height=778575&to_genesis_block_height=778576', }); expect(response4.statusCode).toBe(200); const responseJson4 = response4.json(); expect(responseJson4.total).toBe(2); expect(responseJson4.results.length).toBe(2); - expect(responseJson4.results[0].genesis_block_height).toBe(778580); + expect(responseJson4.results[0].genesis_block_height).toBe(778576); expect(responseJson4.results[1].genesis_block_height).toBe(778575); }); @@ -2120,7 +1956,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2140,7 +1976,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -2197,7 +2033,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2217,7 +2053,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2282,7 +2118,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2302,7 +2138,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2367,7 +2203,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2387,7 +2223,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2452,7 +2288,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2472,7 +2308,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2483,7 +2319,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 50, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -2502,22 +2338,22 @@ describe('/inscriptions', () => { const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_number=10', + url: '/ordinals/v1/inscriptions?from_number=1', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(1); - expect(responseJson2.results[0].number).toBe(50); + expect(responseJson2.results[0].number).toBe(1); const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?to_number=10', + url: '/ordinals/v1/inscriptions?to_number=0', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(1); expect(responseJson3.results.length).toBe(1); - expect(responseJson3.results[0].number).toBe(7); + expect(responseJson3.results[0].number).toBe(0); }); test('index filtered by output', async () => { @@ -2536,7 +2372,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2556,7 +2392,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2601,7 +2437,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2633,7 +2469,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2700,7 +2536,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: 778577, hash: '000000000000000000032ef6c45a69c0496456b3cae84ee9f2899f636d03c5ac', timestamp: 1675312161, }) @@ -2757,7 +2593,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .rollback() .block({ - height: 775618, + height: 778577, hash: '000000000000000000032ef6c45a69c0496456b3cae84ee9f2899f636d03c5ac', timestamp: 1675312161, }) @@ -2802,7 +2638,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2822,7 +2658,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2893,7 +2729,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2913,14 +2749,14 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) .transaction({ hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', }) - .inscriptionRevealed({ + .cursedInscriptionRevealed({ content_bytes: `0x${Buffer.from( 'Hello /content/9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0' ).toString('hex')}`, @@ -2939,6 +2775,7 @@ describe('/inscriptions', () => { inscription_input_index: 0, transfers_pre_inscription: 0, tx_index: 0, + curse_type: 'test', }) .build() ); @@ -2984,7 +2821,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3003,7 +2840,7 @@ describe('/inscriptions', () => { const genesis2 = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -3082,7 +2919,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3102,7 +2939,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3113,7 +2950,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3133,7 +2970,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3144,7 +2981,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3168,9 +3005,9 @@ describe('/inscriptions', () => { expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(3); - expect(responseJson1.results[0].number).toStrictEqual(7); - expect(responseJson1.results[1].number).toStrictEqual(8); - expect(responseJson1.results[2].number).toStrictEqual(9); + expect(responseJson1.results[0].number).toStrictEqual(0); + expect(responseJson1.results[1].number).toStrictEqual(1); + expect(responseJson1.results[2].number).toStrictEqual(2); const response2 = await fastify.inject({ method: 'GET', @@ -3179,9 +3016,9 @@ describe('/inscriptions', () => { expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(3); - expect(responseJson2.results[0].number).toStrictEqual(9); - expect(responseJson2.results[1].number).toStrictEqual(8); - expect(responseJson2.results[2].number).toStrictEqual(7); + expect(responseJson2.results[0].number).toStrictEqual(2); + expect(responseJson2.results[1].number).toStrictEqual(1); + expect(responseJson2.results[2].number).toStrictEqual(0); }); test('index ordered by sat rarity', async () => { @@ -3200,7 +3037,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3220,7 +3057,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3231,7 +3068,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3251,7 +3088,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3262,7 +3099,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3318,7 +3155,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3338,7 +3175,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3349,7 +3186,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3369,7 +3206,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3380,7 +3217,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3436,7 +3273,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3456,7 +3293,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3467,7 +3304,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3487,7 +3324,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3498,7 +3335,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3522,9 +3359,9 @@ describe('/inscriptions', () => { expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(3); - expect(responseJson1.results[0].genesis_block_height).toStrictEqual(775617); - expect(responseJson1.results[1].genesis_block_height).toStrictEqual(778575); - expect(responseJson1.results[2].genesis_block_height).toStrictEqual(778583); + expect(responseJson1.results[0].genesis_block_height).toStrictEqual(778575); + expect(responseJson1.results[1].genesis_block_height).toStrictEqual(778576); + expect(responseJson1.results[2].genesis_block_height).toStrictEqual(778577); const response2 = await fastify.inject({ method: 'GET', @@ -3533,56 +3370,9 @@ describe('/inscriptions', () => { expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(3); - expect(responseJson2.results[0].genesis_block_height).toStrictEqual(778583); - expect(responseJson2.results[1].genesis_block_height).toStrictEqual(778575); - expect(responseJson2.results[2].genesis_block_height).toStrictEqual(775617); - - // Same genesis block items should be sorted by number - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ - height: 778583, - hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - timestamp: 1676913207, - }) - .transaction({ - hash: '907f66261909f40e6b892b7ac574fd9e8661262c83b48be7488194b66523abbd', - }) - .inscriptionRevealed({ - content_bytes: '0x48656C6C6F', - content_type: 'image/png', - content_length: 5, - inscription_number: 43, - inscription_fee: 2805, - inscription_id: '907f66261909f40e6b892b7ac574fd9e8661262c83b48be7488194b66523abbdi0', - inscription_output_value: 10000, - inscriber_address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', - ordinal_number: 0, - ordinal_block_height: 0, - ordinal_offset: 0, - satpoint_post_inscription: - '907f66261909f40e6b892b7ac574fd9e8661262c83b48be7488194b66523abbd:0:0', - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build() - ); - - const response3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions?order_by=genesis_block_height&order=desc', - }); - expect(response3.statusCode).toBe(200); - const responseJson3 = response3.json(); - expect(responseJson3.total).toBe(4); - expect(responseJson3.results[0].genesis_block_height).toStrictEqual(778583); - expect(responseJson3.results[0].number).toStrictEqual(43); - expect(responseJson3.results[1].genesis_block_height).toStrictEqual(778583); - expect(responseJson3.results[1].number).toStrictEqual(9); - expect(responseJson3.results[2].genesis_block_height).toStrictEqual(778575); - expect(responseJson3.results[3].genesis_block_height).toStrictEqual(775617); + expect(responseJson2.results[0].genesis_block_height).toStrictEqual(778577); + expect(responseJson2.results[1].genesis_block_height).toStrictEqual(778576); + expect(responseJson2.results[2].genesis_block_height).toStrictEqual(778575); }); }); }); diff --git a/tests/sats.test.ts b/tests/sats.test.ts index 479fee4c..af39a376 100644 --- a/tests/sats.test.ts +++ b/tests/sats.test.ts @@ -48,7 +48,7 @@ describe('/sats', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -105,7 +105,7 @@ describe('/sats', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 775618, hash: '000000000000000000002a244dc7dfcf8ab85e42d182531c27197fc125086f19', timestamp: 1676913207, }) @@ -147,7 +147,7 @@ describe('/sats', () => { content_type: 'image/png', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '000000000000000000002a244dc7dfcf8ab85e42d182531c27197fc125086f19', - genesis_block_height: 775617, + genesis_block_height: 775618, genesis_fee: '2805', genesis_timestamp: 1676913207000, genesis_tx_id: 'b9cd9489fe30b81d007f753663d12766f1368721a87f4c69056c8215caa57993', diff --git a/tests/server.test.ts b/tests/server.test.ts index f0785521..5a334af9 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -35,7 +35,7 @@ describe('EventServer', () => { content_bytes: '0x303030303030303030303030', content_type: 'text/plain;charset=utf-8', content_length: 12, - inscription_number: 100, + inscription_number: 0, inscription_fee: 3425, inscription_output_value: 10000, inscription_id: '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8i0', @@ -54,7 +54,7 @@ describe('EventServer', () => { const payload1 = new TestChainhookPayloadBuilder() .apply() .block({ - height: 107, + height: 767430, hash: '0x163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88', timestamp: 1676913207, }) @@ -89,7 +89,7 @@ describe('EventServer', () => { expect(inscr.genesis_block_hash).toBe( '163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88' ); - expect(inscr.genesis_block_height).toBe('107'); + expect(inscr.genesis_block_height).toBe('767430'); expect(inscr.genesis_fee).toBe('3425'); expect(inscr.genesis_id).toBe( '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8i0' @@ -99,7 +99,7 @@ describe('EventServer', () => { '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8' ); expect(inscr.mime_type).toBe('text/plain'); - expect(inscr.number).toBe('100'); + expect(inscr.number).toBe('0'); expect(inscr.offset).toBe('0'); expect(inscr.output).toBe( '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8:0' @@ -152,7 +152,7 @@ describe('EventServer', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -232,7 +232,7 @@ describe('EventServer', () => { '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc' ); expect(inscr.mime_type).toBe('image/png'); - expect(inscr.number).toBe('7'); + expect(inscr.number).toBe('0'); expect(inscr.offset).toBe('5000'); expect(inscr.output).toBe( '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8:0' @@ -269,186 +269,255 @@ describe('EventServer', () => { expect(c2[0].count).toBe(1); }); - test('saves transfer without genesis and fills the gap later', async () => { - // Insert transfers with no genesis + test('multiple inscription pointers on the same block are compared correctly', async () => { + const address = 'bc1q92zytmqgczsrg4xuhpc2asz6h4h7ke5hagw8k6'; + const address2 = 'bc1qtpm0fsaawxjsthfdrxhmrzunnpjx0g9hncgvp7'; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, - hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - timestamp: 1676913207, + height: 808382, + hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ - hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - }) - .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - destination: { - type: 'transferred', - value: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', - }, - satpoint_pre_transfer: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', - satpoint_post_transfer: - '9e2414153b1893f799477f7e1a00a52fafc235de72fd215cb3321f253c4464ac:0:0', - post_transfer_output_value: 9000, - tx_index: 0, + hash: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02', }) - .build() - ); - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ - height: 775621, - hash: '00000000000000000003dd4738355bedb73796de9b1099e59ff7adc235e967a6', - timestamp: 1676913207, + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 0, + inscription_fee: 2805, + inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + inscription_output_value: 10000, + inscriber_address: address, + ordinal_number: 5, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 995, }) .transaction({ - hash: '2fa1640d61f04a699833f0f6a884f543c835fc60f0fd4da8627ebb857acdce84', + hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - destination: { - type: 'transferred', - value: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', - }, + inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: - '9e2414153b1893f799477f7e1a00a52fafc235de72fd215cb3321f253c4464ac:0:0', + '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', satpoint_post_transfer: - '2fa1640d61f04a699833f0f6a884f543c835fc60f0fd4da8627ebb857acdce84:0:0', - post_transfer_output_value: 8000, - tx_index: 0, + '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', + post_transfer_output_value: null, + tx_index: 1019, // '1019' is less than '995' when compared as a string }) .build() ); - // Locations should exist with null FKs - const results1 = await db.sql` - SELECT * FROM locations - WHERE genesis_id = '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0' - `; - expect(results1.count).toBe(2); - expect(results1[0].inscription_id).toBeNull(); - expect(results1[1].inscription_id).toBeNull(); - const api1 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(api1.statusCode).toBe(404); - const api2 = await fastify.inject({ + const response = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0/transfers', + url: `/ordinals/inscriptions/6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0`, }); - expect(api2.json().total).toBe(0); + expect(response.statusCode).toBe(200); + const json = response.json(); + expect(json.genesis_address).toBe(address); + }); + }); - // Insert genesis and make sure locations are normalized. + describe('gap detection', () => { + test('server rejects payload with first inscription gap', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: 778575, hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) .transaction({ - hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', - content_type: 'image/png', + content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, - inscription_fee: 2805, - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_number: 0, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, - inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - ordinal_number: 5, - ordinal_block_height: 0, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, ordinal_offset: 0, satpoint_post_inscription: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', inscription_input_index: 0, transfers_pre_inscription: 0, tx_index: 0, }) .build() ); - // Locations should exist with all FKs filled in - const results2 = await db.sql` - SELECT * FROM locations - WHERE genesis_id = '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0' - `; - expect(results2.count).toBe(3); - expect(results2[0].inscription_id).not.toBeNull(); - expect(results2[1].inscription_id).not.toBeNull(); - expect(results2[2].inscription_id).not.toBeNull(); - const api3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(api3.statusCode).toBe(200); - expect(api3.json().genesis_block_height).toBe(775618); - const api4 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0/transfers', - }); - expect(api4.json().total).toBe(3); + const errorPayload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 5, // Gap at 5 + inscription_fee: 705, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await expect(db.updateInscriptions(errorPayload)).rejects.toThrowError( + /inscription gap detected/ + ); }); - test('multiple inscription pointers on the same block are compared correctly', async () => { - const address = 'bc1q92zytmqgczsrg4xuhpc2asz6h4h7ke5hagw8k6'; - const address2 = 'bc1qtpm0fsaawxjsthfdrxhmrzunnpjx0g9hncgvp7'; + test('server rejects payload with intermediate inscription gap', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 808382, - hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, }) .transaction({ - hash: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02', + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', - content_type: 'image/png', + content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 33224825, - inscription_fee: 2805, - inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + inscription_number: 0, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, - inscriber_address: address, - ordinal_number: 5, - ordinal_block_height: 0, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, ordinal_offset: 0, satpoint_post_inscription: - '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', inscription_input_index: 0, transfers_pre_inscription: 0, - tx_index: 995, - }) - .transaction({ - hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', - }) - .inscriptionTransferred({ - inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', - destination: { type: 'transferred', value: address2 }, - satpoint_pre_transfer: - '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', - satpoint_post_transfer: - '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', - post_transfer_output_value: null, - tx_index: 1019, // '1019' is less than '995' when compared as a string + tx_index: 0, }) .build() ); - const response = await fastify.inject({ - method: 'GET', - url: `/ordinals/inscriptions/6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0`, + const errorPayload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 1, + inscription_fee: 705, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .transaction({ + hash: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 4, // Gap + inscription_fee: 705, + inscription_id: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5o0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await expect(db.updateInscriptions(errorPayload)).rejects.toThrowError( + /inscription gap detected/ + ); + }); + + test('server ignores past blocks', async () => { + const payload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 0, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await db.updateInscriptions(payload); + + const response = await server['fastify'].inject({ + method: 'POST', + url: `/chainhook/${PREDICATE_UUID}`, + headers: { authorization: `Bearer ${ENV.CHAINHOOK_NODE_AUTH_TOKEN}` }, + payload: payload, }); expect(response.statusCode).toBe(200); - const json = response.json(); - expect(json.genesis_address).toBe(address); }); }); }); diff --git a/tests/stats.test.ts b/tests/stats.test.ts index 90097703..34b83c91 100644 --- a/tests/stats.test.ts +++ b/tests/stats.test.ts @@ -64,34 +64,11 @@ describe('/stats', () => { }; test('returns stats when processing blocks in order', async () => { - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_010)); - - const response = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/stats/inscriptions', - }); - expect(response.statusCode).toBe(200); - expect(response.json()).toStrictEqual(EXPECTED); - }); - - test('returns stats when processing blocks out-of-order', async () => { - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_005)); + await db.updateInscriptions(testRevealApply(778_000, [0, 1])); + await db.updateInscriptions(testRevealApply(778_001, [2])); + await db.updateInscriptions(testRevealApply(778_002, [3])); + await db.updateInscriptions(testRevealApply(778_005, [4, 5])); + await db.updateInscriptions(testRevealApply(778_010, [6, 7, 8])); const response = await fastify.inject({ method: 'GET', @@ -102,20 +79,16 @@ describe('/stats', () => { }); test('returns stats when processing rollbacks', async () => { - const payloadApply = testRevealApply(778_004); + const payloadApply = testRevealApply(778_004, [4]); const payloadRollback = { ...payloadApply, apply: [], rollback: payloadApply.apply }; - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_000)); + await db.updateInscriptions(testRevealApply(778_000, [0, 1])); + await db.updateInscriptions(testRevealApply(778_001, [2])); + await db.updateInscriptions(testRevealApply(778_002, [3])); await db.updateInscriptions(payloadApply); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_000)); await db.updateInscriptions(payloadRollback); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_005)); + await db.updateInscriptions(testRevealApply(778_005, [4, 5])); + await db.updateInscriptions(testRevealApply(778_010, [6, 7, 8])); const response = await fastify.inject({ method: 'GET', @@ -127,12 +100,11 @@ describe('/stats', () => { }); test('range filters', async () => { - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_010)); + await db.updateInscriptions(testRevealApply(778_000, [0])); + await db.updateInscriptions(testRevealApply(778_001, [1])); + await db.updateInscriptions(testRevealApply(778_002, [2])); + await db.updateInscriptions(testRevealApply(778_005, [3, 4])); + await db.updateInscriptions(testRevealApply(778_010, [5])); const responseFrom = await fastify.inject({ method: 'GET', @@ -222,34 +194,35 @@ describe('/stats', () => { }); }); -function testRevealApply(blockHeight: number) { - const randomHex = randomHash(); - return new TestChainhookPayloadBuilder() - .apply() - .block({ - height: blockHeight, - hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - timestamp: 1676913207, - }) - .transaction({ - hash: `0x${randomHex}`, - }) - .inscriptionRevealed({ - content_bytes: '0x48656C6C6F', - content_type: 'image/png', - content_length: 5, - inscription_number: Math.floor(Math.random() * 100000), - inscription_fee: 2805, - inscription_id: `${randomHex}i0`, - inscription_output_value: 10000, - inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - ordinal_number: Math.floor(Math.random() * 1000000), - ordinal_block_height: Math.floor(Math.random() * 777000), - ordinal_offset: 0, - satpoint_post_inscription: `${randomHex}:0:0`, - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build(); +function testRevealApply(blockHeight: number, numbers: number[]) { + const block = new TestChainhookPayloadBuilder().apply().block({ + height: blockHeight, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }); + for (const number of numbers) { + const randomHex = randomHash(); + block + .transaction({ + hash: `0x${randomHex}`, + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: number, + inscription_fee: 2805, + inscription_id: `${randomHex}i0`, + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: Math.floor(Math.random() * 1000000), + ordinal_block_height: Math.floor(Math.random() * 777000), + ordinal_offset: 0, + satpoint_post_inscription: `${randomHex}:0:0`, + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }); + } + return block.build(); } diff --git a/tests/status.test.ts b/tests/status.test.ts index 87b845be..bf043697 100644 --- a/tests/status.test.ts +++ b/tests/status.test.ts @@ -41,7 +41,7 @@ describe('Status', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -90,7 +90,7 @@ describe('Status', () => { server_version: 'ordinals-api v0.0.1 (test:123456)', status: 'ready', block_height: 791975, - max_inscription_number: 7, + max_inscription_number: 0, max_cursed_inscription_number: -2, }); }); From 900aad20368f662db07780ebe68e78caeeb9e854 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 03:53:21 +0000 Subject: [PATCH 04/42] chore(release): 2.0.0-beta.2 [skip ci] ## [2.0.0-beta.2](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2023-11-02) ### Bug Fixes * reject blocks that would create blessed inscription gaps ([#259](https://github.com/hirosystems/ordinals-api/issues/259)) ([18cd028](https://github.com/hirosystems/ordinals-api/commit/18cd028b636184a96597fa0b0978ba9e4d23f55c)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1d2c0c..98d2017e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.2](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2023-11-02) + + +### Bug Fixes + +* reject blocks that would create blessed inscription gaps ([#259](https://github.com/hirosystems/ordinals-api/issues/259)) ([18cd028](https://github.com/hirosystems/ordinals-api/commit/18cd028b636184a96597fa0b0978ba9e4d23f55c)) + ## [2.0.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v1.2.6...v2.0.0-beta.1) (2023-11-01) From 0e33a644773990531fa3f68e93ca97f83105a621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Wed, 1 Nov 2023 23:02:16 -0600 Subject: [PATCH 05/42] fix: keep block transfer index (#260) --- .../1698897577725_locations-location-index.ts | 14 ++++ src/pg/pg-store.ts | 36 +++++---- src/pg/types.ts | 1 + tests/inscriptions.test.ts | 75 +++++++++++++++++++ 4 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 migrations/1698897577725_locations-location-index.ts diff --git a/migrations/1698897577725_locations-location-index.ts b/migrations/1698897577725_locations-location-index.ts new file mode 100644 index 00000000..bb7461db --- /dev/null +++ b/migrations/1698897577725_locations-location-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.addColumn('locations', { + block_transfer_index: { + type: 'int', + }, + }); + pgm.addIndex('locations', ['block_height', { name: 'block_transfer_index', sort: 'DESC' }]); + pgm.addIndex('locations', ['block_hash', { name: 'block_transfer_index', sort: 'DESC' }]); +} diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index ff663ec1..ee7eab08 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -2,8 +2,8 @@ import { BasePgStore, PgConnectionVars, PgSqlClient, + PgSqlQuery, connectPostgres, - isTestEnv, logger, runMigrations, } from '@hirosystems/api-toolkit'; @@ -43,7 +43,6 @@ import { toEnumValue } from '@hirosystems/api-toolkit'; export const MIGRATIONS_DIR = path.join(__dirname, '../../migrations'); type InscriptionIdentifier = { genesis_id: string } | { number: number }; -type PgQueryFragment = postgres.PendingQuery; // TODO: Move to api-toolkit export class PgStore extends BasePgStore { readonly brc20: Brc20PgStore; @@ -121,6 +120,8 @@ export class PgStore extends BasePgStore { } updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); } + + let blockTransferIndex = 0; for (const applyEvent of payload.apply) { const event = applyEvent as BitcoinEvent; const block_height = event.block_identifier.index; @@ -165,6 +166,7 @@ export class PgStore extends BasePgStore { block_height, tx_id, tx_index: reveal.tx_index, + block_transfer_index: null, genesis_id: reveal.inscription_id, address: reveal.inscriber_address, output: `${satpoint.tx_id}:${satpoint.vout}`, @@ -203,6 +205,7 @@ export class PgStore extends BasePgStore { block_height, tx_id, tx_index: reveal.tx_index, + block_transfer_index: null, genesis_id: reveal.inscription_id, address: reveal.inscriber_address, output: `${satpoint.tx_id}:${satpoint.vout}`, @@ -226,6 +229,7 @@ export class PgStore extends BasePgStore { block_height, tx_id, tx_index: transfer.tx_index, + block_transfer_index: blockTransferIndex++, genesis_id: transfer.inscription_id, address: transfer.destination.value ?? null, output: `${satpoint.tx_id}:${satpoint.vout}`, @@ -514,7 +518,14 @@ export class PgStore extends BasePgStore { args: { block_height?: number; block_hash?: string } & DbInscriptionIndexPaging ): Promise> { const results = await this.sql<({ total: number } & DbInscriptionLocationChange)[]>` - WITH transfers AS ( + WITH max_transfer_index AS ( + SELECT MAX(block_transfer_index) FROM locations WHERE ${ + 'block_height' in args + ? this.sql`block_height = ${args.block_height}` + : this.sql`block_hash = ${args.block_hash}` + } AND block_transfer_index IS NOT NULL + ), + transfers AS ( SELECT i.id AS inscription_id, i.genesis_id, @@ -531,31 +542,30 @@ export class PgStore extends BasePgStore { ) ORDER BY ll.block_height DESC LIMIT 1 - ) AS from_id, - COUNT(*) OVER() as total + ) AS from_id FROM locations AS l INNER JOIN inscriptions AS i ON l.inscription_id = i.id WHERE - NOT EXISTS (SELECT location_id FROM genesis_locations WHERE location_id = l.id) - AND ${ 'block_height' in args ? this.sql`l.block_height = ${args.block_height}` : this.sql`l.block_hash = ${args.block_hash}` } - LIMIT ${args.limit} - OFFSET ${args.offset} + AND l.block_transfer_index IS NOT NULL + AND l.block_transfer_index <= ((SELECT max FROM max_transfer_index) - ${args.offset}::int) + AND l.block_transfer_index > + ((SELECT max FROM max_transfer_index) - (${args.offset}::int + ${args.limit}::int)) ) SELECT t.genesis_id, t.number, - t.total, + (SELECT max FROM max_transfer_index) + 1 AS total, ${this.sql.unsafe(LOCATIONS_COLUMNS.map(c => `lf.${c} AS from_${c}`).join(','))}, ${this.sql.unsafe(LOCATIONS_COLUMNS.map(c => `lt.${c} AS to_${c}`).join(','))} FROM transfers AS t INNER JOIN locations AS lf ON t.from_id = lf.id INNER JOIN locations AS lt ON t.to_id = lt.id - ORDER BY to_tx_index DESC + ORDER BY lt.block_transfer_index DESC `; return { total: results[0]?.total ?? 0, @@ -870,8 +880,8 @@ export class PgStore extends BasePgStore { private async updateInscriptionRecursions(reveals: DbRevealInsert[]): Promise { if (reveals.length === 0) return; const inserts: { - inscription_id: PgQueryFragment; - ref_inscription_id: PgQueryFragment; + inscription_id: PgSqlQuery; + ref_inscription_id: PgSqlQuery; ref_inscription_genesis_id: string; }[] = []; for (const i of reveals) diff --git a/src/pg/types.ts b/src/pg/types.ts index 83538a2f..c0e11ff4 100644 --- a/src/pg/types.ts +++ b/src/pg/types.ts @@ -54,6 +54,7 @@ export type DbLocationInsert = { value: PgNumeric | null; timestamp: number; transfer_type: DbLocationTransferType; + block_transfer_index: number | null; }; export type DbLocation = { diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index e101ac17..5625c893 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -1195,6 +1195,7 @@ describe('/inscriptions', () => { expect(response3.statusCode).toBe(200); const json3 = response3.json(); expect(json3.total).toBe(2); + expect(json3.results).toHaveLength(2); expect(json3.results).toStrictEqual([ { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -1249,6 +1250,80 @@ describe('/inscriptions', () => { }, }, ]); + + // Test pagination + const response4 = await fastify.inject({ + method: 'GET', + url: '/ordinals/v1/inscriptions/transfers?block=775701&limit=1&offset=0', + }); + expect(response4.statusCode).toBe(200); + const json4 = response4.json(); + expect(json4.total).toBe(2); + expect(json4.results).toHaveLength(1); + expect(json4.results).toStrictEqual([ + { + id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + number: 0, + from: { + address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', + block_height: 775701, + location: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0:0', + offset: '0', + output: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0', + timestamp: 1676913208000, + tx_id: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', + value: '8000', + }, + to: { + address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', + block_height: 775701, + location: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:1:0', + offset: '0', + output: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:1', + timestamp: 1676913208000, + tx_id: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', + value: '7500', + }, + }, + ]); + const response5 = await fastify.inject({ + method: 'GET', + url: '/ordinals/v1/inscriptions/transfers?block=775701&limit=1&offset=1', + }); + expect(response5.statusCode).toBe(200); + const json5 = response5.json(); + expect(json5.total).toBe(2); + expect(json5.results).toHaveLength(1); + expect(json5.results).toStrictEqual([ + { + id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + number: 0, + from: { + address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', + block_height: 775700, + location: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', + offset: '0', + output: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0', + timestamp: 1678122360000, + tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', + value: '9000', + }, + to: { + address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', + block_height: 775701, + location: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0:0', + offset: '0', + output: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0', + timestamp: 1676913208000, + tx_id: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', + value: '8000', + }, + }, + ]); }); }); From c64c2f3a48a840a13c7f0837e39282ff016fecf6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 05:04:57 +0000 Subject: [PATCH 06/42] chore(release): 2.0.0-beta.3 [skip ci] ## [2.0.0-beta.3](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2023-11-02) ### Bug Fixes * keep block transfer index ([#260](https://github.com/hirosystems/ordinals-api/issues/260)) ([0e33a64](https://github.com/hirosystems/ordinals-api/commit/0e33a644773990531fa3f68e93ca97f83105a621)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d2017e..5fcfd120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.3](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2023-11-02) + + +### Bug Fixes + +* keep block transfer index ([#260](https://github.com/hirosystems/ordinals-api/issues/260)) ([0e33a64](https://github.com/hirosystems/ordinals-api/commit/0e33a644773990531fa3f68e93ca97f83105a621)) + ## [2.0.0-beta.2](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2023-11-02) From 29aaeda39527c88b63b7c82739d8ed858c48a74b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Thu, 2 Nov 2023 11:55:23 -0600 Subject: [PATCH 07/42] fix: only validate gaps for blessed numbers (#262) --- src/pg/pg-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index ee7eab08..e3d67e11 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -138,7 +138,7 @@ export class PgStore extends BasePgStore { for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) { const reveal = operation.inscription_revealed; - if (blessedNumber + 1 !== reveal.inscription_number) + if (reveal.inscription_number >= 0 && blessedNumber + 1 !== reveal.inscription_number) throw Error( `PgStore inscription gap detected: Attempting to insert #${reveal.inscription_number} (${block_height}) but current max is #${blessedNumber}` ); From 8109ce5d406be43aefaa13ab148bf3d6395ff3c3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 17:58:17 +0000 Subject: [PATCH 08/42] chore(release): 2.0.0-beta.4 [skip ci] ## [2.0.0-beta.4](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2023-11-02) ### Bug Fixes * only validate gaps for blessed numbers ([#262](https://github.com/hirosystems/ordinals-api/issues/262)) ([29aaeda](https://github.com/hirosystems/ordinals-api/commit/29aaeda39527c88b63b7c82739d8ed858c48a74b)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcfd120..4f945eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.4](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2023-11-02) + + +### Bug Fixes + +* only validate gaps for blessed numbers ([#262](https://github.com/hirosystems/ordinals-api/issues/262)) ([29aaeda](https://github.com/hirosystems/ordinals-api/commit/29aaeda39527c88b63b7c82739d8ed858c48a74b)) + ## [2.0.0-beta.3](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2023-11-02) From c8f3c81a657cd28283bdd89e0a17af06cad93d63 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Thu, 2 Nov 2023 12:09:18 -0600 Subject: [PATCH 09/42] fix: only advance blessed number after gap detection --- src/pg/pg-store.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index e3d67e11..d1731266 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -138,11 +138,13 @@ export class PgStore extends BasePgStore { for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) { const reveal = operation.inscription_revealed; - if (reveal.inscription_number >= 0 && blessedNumber + 1 !== reveal.inscription_number) - throw Error( - `PgStore inscription gap detected: Attempting to insert #${reveal.inscription_number} (${block_height}) but current max is #${blessedNumber}` - ); - blessedNumber = reveal.inscription_number; + if (reveal.inscription_number >= 0) { + if (blessedNumber + 1 !== reveal.inscription_number) + throw Error( + `PgStore inscription gap detected: Attempting to insert #${reveal.inscription_number} (${block_height}) but current max is #${blessedNumber}` + ); + blessedNumber = reveal.inscription_number; + } const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); From 1ff72c0da17b9b0f9b57d18462e8180edb2da078 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 18:12:34 +0000 Subject: [PATCH 10/42] chore(release): 2.0.0-beta.5 [skip ci] ## [2.0.0-beta.5](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2023-11-02) ### Bug Fixes * only advance blessed number after gap detection ([c8f3c81](https://github.com/hirosystems/ordinals-api/commit/c8f3c81a657cd28283bdd89e0a17af06cad93d63)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f945eba..33d3b860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.5](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2023-11-02) + + +### Bug Fixes + +* only advance blessed number after gap detection ([c8f3c81](https://github.com/hirosystems/ordinals-api/commit/c8f3c81a657cd28283bdd89e0a17af06cad93d63)) + ## [2.0.0-beta.4](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2023-11-02) From 6381760f3860a96c3abfd485b612cd212927dca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Thu, 2 Nov 2023 14:15:48 -0600 Subject: [PATCH 11/42] fix: only scan BRC-20 after its genesis height (#263) --- src/pg/brc20/brc20-pg-store.ts | 4 ++ src/pg/pg-store.ts | 3 +- tests/brc20.test.ts | 121 +++++++++++++++++---------------- 3 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index 1e32edad..dd0ee33a 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -30,6 +30,9 @@ import { Brc20Deploy, Brc20Mint, Brc20Transfer, brc20FromInscriptionContent } fr import { Brc20TokenOrderBy } from '../../api/schemas'; import { objRemoveUndefinedValues } from '../helpers'; +/** The block at which BRC-20 activity began */ +export const BRC20_GENESIS_BLOCK = 779832; + export class Brc20PgStore extends BasePgStoreModule { sqlOr(partials: postgres.PendingQuery[] | undefined) { return partials?.reduce((acc, curr) => this.sql`${acc} OR ${curr}`); @@ -43,6 +46,7 @@ export class Brc20PgStore extends BasePgStoreModule { */ async scanBlocks(startBlock: number, endBlock: number): Promise { for (let blockHeight = startBlock; blockHeight <= endBlock; blockHeight++) { + if (blockHeight < BRC20_GENESIS_BLOCK) continue; logger.info(`Brc20PgStore scanning block ${blockHeight}`); await this.sqlWriteTransaction(async sql => { const limit = 100_000; diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index d1731266..70365cc1 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -41,6 +41,7 @@ import { import { toEnumValue } from '@hirosystems/api-toolkit'; export const MIGRATIONS_DIR = path.join(__dirname, '../../migrations'); +export const ORDINALS_GENESIS_BLOCK = 767430; type InscriptionIdentifier = { genesis_id: string } | { number: number }; @@ -125,7 +126,7 @@ export class PgStore extends BasePgStore { for (const applyEvent of payload.apply) { const event = applyEvent as BitcoinEvent; const block_height = event.block_identifier.index; - if (block_height <= currentBlockHeight && block_height !== 767430) { + if (block_height <= currentBlockHeight && block_height !== ORDINALS_GENESIS_BLOCK) { logger.info( `PgStore skipping ingestion for previously seen block ${block_height}, current chain tip is at ${currentBlockHeight}` ); diff --git a/tests/brc20.test.ts b/tests/brc20.test.ts index 12c8cfb2..21d43467 100644 --- a/tests/brc20.test.ts +++ b/tests/brc20.test.ts @@ -12,6 +12,7 @@ import { randomHash, rollBack, } from './helpers'; +import { BRC20_GENESIS_BLOCK } from '../src/pg/brc20/brc20-pg-store'; describe('BRC-20', () => { let db: PgStore; @@ -22,7 +23,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -47,7 +48,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -472,7 +473,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1677811111, }) @@ -504,7 +505,7 @@ describe('BRC-20', () => { expect(responseJson1.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', number: 0, @@ -524,7 +525,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -549,7 +550,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '000000000000000000021a0207fa97024506baaa74396822fb0a07ac20e70148', }) .transaction({ @@ -580,7 +581,7 @@ describe('BRC-20', () => { expect(responseJson1.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', @@ -600,7 +601,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -625,7 +626,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '000000000000000000021a0207fa97024506baaa74396822fb0a07ac20e70148', }) .transaction({ @@ -656,7 +657,7 @@ describe('BRC-20', () => { expect(responseJson1.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', @@ -679,7 +680,7 @@ describe('BRC-20', () => { expect(responseJson2.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', @@ -702,7 +703,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -727,7 +728,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -770,7 +771,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '0000000000000000000077163227125e51d838787d6af031bc9b55a3a1cc1b2c', }) .transaction({ @@ -828,7 +829,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -853,7 +854,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -879,7 +880,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .rollback() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '0000000000000000000077163227125e51d838787d6af031bc9b55a3a1cc1b2c', }) .transaction({ @@ -923,7 +924,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -949,7 +950,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -987,7 +988,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -1013,7 +1014,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -1088,7 +1089,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '000000000000000000001f14513d722146fddab04a1855665a5eca22df288c3c', }) .transaction({ @@ -1125,7 +1126,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -1163,7 +1164,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -1190,7 +1191,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -1231,7 +1232,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1272,7 +1273,7 @@ describe('BRC-20', () => { // Balance at previous block const response2 = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/balances/${address}?block_height=775618`, + url: `/ordinals/brc-20/balances/${address}?block_height=779833`, }); const json2 = response2.json(); expect(json2.results[0].available_balance).toBe('10000.000000000000000000'); @@ -1285,7 +1286,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1331,7 +1332,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1377,7 +1378,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1440,7 +1441,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1465,7 +1466,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1519,13 +1520,13 @@ describe('BRC-20', () => { // Balance at previous block const prevBlock1 = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/balances/${address}?block_height=775618`, + url: `/ordinals/brc-20/balances/${address}?block_height=779833`, }); const prevBlockJson1 = prevBlock1.json(); expect(prevBlockJson1.results[0].available_balance).toBe('10000.000000000000000000'); const prevBlock2 = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/balances/${address2}?block_height=775618`, + url: `/ordinals/brc-20/balances/${address2}?block_height=779833`, }); const prevBlockJson2 = prevBlock2.json(); expect(prevBlockJson2.results[0]).toBeUndefined(); @@ -1538,7 +1539,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1563,7 +1564,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1606,7 +1607,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1631,7 +1632,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1675,7 +1676,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1700,7 +1701,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '000000000000000000016ddf56d0fe72476165acee9500d48d3e2aaf8412f489', }) .transaction({ @@ -1724,7 +1725,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775621, + height: BRC20_GENESIS_BLOCK + 4, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1920,7 +1921,7 @@ describe('BRC-20', () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() - .block({ height: 775617 }) + .block({ height: BRC20_GENESIS_BLOCK }) .transaction({ hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', }) @@ -1948,7 +1949,7 @@ describe('BRC-20', () => { token: { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', number: 0, - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', ticker: 'PEPE', @@ -1969,7 +1970,7 @@ describe('BRC-20', () => { test('tokens filter by ticker prefix', async () => { const inscriptionNumbers = incrementing(0); - const blockHeights = incrementing(775600); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); let transferHash = randomHash(); await db.updateInscriptions( @@ -2077,7 +2078,7 @@ describe('BRC-20', () => { test('tokens using order_by tx_count', async () => { // Setup const inscriptionNumbers = incrementing(0); - const blockHeights = incrementing(775600); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -2318,7 +2319,7 @@ describe('BRC-20', () => { test('activity for token transfers', async () => { // Setup const inscriptionNumbers = incrementing(0); - const blockHeights = incrementing(775600); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -2574,7 +2575,7 @@ describe('BRC-20', () => { // Setup const inscriptionNumbers = incrementing(0); - const blockHeights = incrementing(775600); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; const addressC = 'bc1q9d80h0q5d3f54w7w8c3l2sguf9uset4ydw9xj2'; @@ -3027,7 +3028,7 @@ describe('BRC-20', () => { test('activity for multiple token creation', async () => { const inscriptionNumbers = incrementing(0); - const blockHeights = incrementing(775600); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; // Step 1: Create a token PEPE @@ -3119,7 +3120,7 @@ describe('BRC-20', () => { // Verify that no events are available before the first block height response = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?ticker=PEER&block_height=${775600}`, + url: `/ordinals/brc-20/activity?ticker=PEER&block_height=${BRC20_GENESIS_BLOCK}`, }); expect(response.statusCode).toBe(200); json = response.json(); @@ -3129,7 +3130,7 @@ describe('BRC-20', () => { // Verify that the PEER deploy is not in the activity feed when using block_height parameter response = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?block_height=${775600}`, + url: `/ordinals/brc-20/activity?block_height=${BRC20_GENESIS_BLOCK}`, }); expect(response.statusCode).toBe(200); json = response.json(); @@ -3165,7 +3166,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '0000000000000000000034dd2daec375371800da441b17651459b2220cbc1a6e', }) .transaction({ @@ -3211,7 +3212,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -3262,7 +3263,7 @@ describe('BRC-20', () => { const transferPEPE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -3286,7 +3287,7 @@ describe('BRC-20', () => { const sendPEPE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '000000000000000000016ddf56d0fe72476165acee9500d48d3e2aaf8412f489', }) .transaction({ @@ -3308,7 +3309,7 @@ describe('BRC-20', () => { const deployFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775621, + height: BRC20_GENESIS_BLOCK + 4, hash: '000000000000000000033b0b78ff68c5767109f45ee42696bd4db9b2845a7ea8', }) .transaction({ @@ -3332,7 +3333,7 @@ describe('BRC-20', () => { const mintFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775622, + height: BRC20_GENESIS_BLOCK + 5, hash: '00000000000000000001f022fadbd930ccf6acbe00a07626e3a0898fb5799bc9', }) .transaction({ @@ -3357,7 +3358,7 @@ describe('BRC-20', () => { const transferFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775623, + height: BRC20_GENESIS_BLOCK + 6, hash: '00000000000000000002bfcb8860d4730fcd3986b026b9629ea6106fe2cb9197', }) .transaction({ @@ -3381,7 +3382,7 @@ describe('BRC-20', () => { const sendFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775624, + height: BRC20_GENESIS_BLOCK + 7, hash: '00000000000000000003cbbe6d21f03f531cee6e96f33f4a8277a3d8bce5c759', }) .transaction({ @@ -3463,7 +3464,7 @@ describe('BRC-20', () => { expect(json.results[0].operation).toBe('transfer_send'); request = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?block_height=775622`, + url: `/ordinals/brc-20/activity?block_height=${BRC20_GENESIS_BLOCK + 5}`, }); json = request.json(); expect(json.total).toBe(1); @@ -3558,7 +3559,7 @@ describe('BRC-20', () => { expect(json.results[0].operation).toBe('deploy'); request = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?block_height=775622`, + url: `/ordinals/brc-20/activity?block_height=${BRC20_GENESIS_BLOCK + 5}`, }); json = request.json(); expect(json.total).toBe(0); From 8fd770bc4e83be6ddaa4fdead1d1be55c42f870b Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Thu, 2 Nov 2023 15:19:47 -0600 Subject: [PATCH 12/42] fix: reduce batching to 4500 --- src/pg/pg-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 70365cc1..59b08b5b 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -251,9 +251,9 @@ export class PgStore extends BasePgStore { } } } - // Divide insertion array into chunks of 5000 in order to avoid the postgres limit of 65534 + // Divide insertion array into chunks of 4500 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of chunkArray(writes, 5000)) + for (const writeChunk of chunkArray(writes, 4500)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) From 851c566767c68b6b2a14e2fad22cc7371b4906e9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 21:26:29 +0000 Subject: [PATCH 13/42] chore(release): 2.0.0-beta.6 [skip ci] ## [2.0.0-beta.6](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2023-11-02) ### Bug Fixes * only scan BRC-20 after its genesis height ([#263](https://github.com/hirosystems/ordinals-api/issues/263)) ([6381760](https://github.com/hirosystems/ordinals-api/commit/6381760f3860a96c3abfd485b612cd212927dca4)) * reduce batching to 4500 ([8fd770b](https://github.com/hirosystems/ordinals-api/commit/8fd770bc4e83be6ddaa4fdead1d1be55c42f870b)) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d3b860..dba7a0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [2.0.0-beta.6](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2023-11-02) + + +### Bug Fixes + +* only scan BRC-20 after its genesis height ([#263](https://github.com/hirosystems/ordinals-api/issues/263)) ([6381760](https://github.com/hirosystems/ordinals-api/commit/6381760f3860a96c3abfd485b612cd212927dca4)) +* reduce batching to 4500 ([8fd770b](https://github.com/hirosystems/ordinals-api/commit/8fd770bc4e83be6ddaa4fdead1d1be55c42f870b)) + ## [2.0.0-beta.5](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2023-11-02) From 337342251987d546600fe331154b3e8cd38dcd18 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Thu, 2 Nov 2023 15:57:03 -0600 Subject: [PATCH 14/42] fix: reduce to 4000 chunk size --- src/pg/pg-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 59b08b5b..4873d893 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -251,9 +251,9 @@ export class PgStore extends BasePgStore { } } } - // Divide insertion array into chunks of 4500 in order to avoid the postgres limit of 65534 + // Divide insertion array into chunks of 4000 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of chunkArray(writes, 4500)) + for (const writeChunk of chunkArray(writes, 4000)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) From bdcd89ab67f92fc35199b3251f60c641ea1cd855 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 2 Nov 2023 22:00:53 +0000 Subject: [PATCH 15/42] chore(release): 2.0.0-beta.7 [skip ci] ## [2.0.0-beta.7](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2023-11-02) ### Bug Fixes * reduce to 4000 chunk size ([3373422](https://github.com/hirosystems/ordinals-api/commit/337342251987d546600fe331154b3e8cd38dcd18)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dba7a0c4..f01ea2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.7](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2023-11-02) + + +### Bug Fixes + +* reduce to 4000 chunk size ([3373422](https://github.com/hirosystems/ordinals-api/commit/337342251987d546600fe331154b3e8cd38dcd18)) + ## [2.0.0-beta.6](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2023-11-02) From 56e2235a3823a94493bbe978c2f8e5be48c6cf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Fri, 3 Nov 2023 11:46:52 -0600 Subject: [PATCH 16/42] fix: do not reject unbounded inscriptions that come in disorder (#264) --- package-lock.json | 14 +++--- package.json | 2 +- src/pg/helpers.ts | 23 +++++++++ src/pg/pg-store.ts | 19 ++++---- tests/server.test.ts | 108 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 144 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6675bd43..09f58367 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.0", - "@hirosystems/chainhook-client": "^1.4.0", + "@hirosystems/chainhook-client": "^1.4.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", @@ -1228,9 +1228,9 @@ } }, "node_modules/@hirosystems/chainhook-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.0.tgz", - "integrity": "sha512-WmUP4iAz/0+TPLEfzk8TMKoEqPRociSbzQJXVxA8kYrhTwu5/Bqzw7zZoVSvDY5zh6p/vaMGNH6UC4IoTAzIwA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.1.tgz", + "integrity": "sha512-ZiOk8RlLgtyDc9oRDkWHXGbzhRIMyxRIAi0OHZG/mt5yu3qNzYZ7OmK4txShxAytwDb3okMZEwPK/CrsoLPLXA==", "dependencies": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", @@ -19672,9 +19672,9 @@ } }, "@hirosystems/chainhook-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.0.tgz", - "integrity": "sha512-WmUP4iAz/0+TPLEfzk8TMKoEqPRociSbzQJXVxA8kYrhTwu5/Bqzw7zZoVSvDY5zh6p/vaMGNH6UC4IoTAzIwA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.1.tgz", + "integrity": "sha512-ZiOk8RlLgtyDc9oRDkWHXGbzhRIMyxRIAi0OHZG/mt5yu3qNzYZ7OmK4txShxAytwDb3okMZEwPK/CrsoLPLXA==", "requires": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", diff --git a/package.json b/package.json index 5bbaec54..2e0fb6ac 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.0", - "@hirosystems/chainhook-client": "^1.4.0", + "@hirosystems/chainhook-client": "^1.4.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index d9cf0f3c..5b4af1c1 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -1,5 +1,28 @@ import { PgBytea } from '@hirosystems/api-toolkit'; import { hexToBuffer } from '../api/util/helpers'; +import { BadPayloadRequestError } from '@hirosystems/chainhook-client'; + +/** + * Check if writing a block would create an inscription number gap + * @param currentNumber - Current max blessed number + * @param newNumbers - New blessed numbers to be inserted + */ +export function assertNoBlockInscriptionGap(args: { + currentNumber: number; + newNumbers: number[]; + currentBlockHeight: number; + newBlockHeight: number; +}) { + args.newNumbers.sort(); + for (let n = 0; n < args.newNumbers.length; n++) { + const curr = args.currentNumber + n; + const next = args.newNumbers[n]; + if (next !== curr + 1) + throw new BadPayloadRequestError( + `Block inscription gap detected: Attempting to insert #${next} (${args.newBlockHeight}) but current max is #${curr}. Chain tip is at ${args.currentBlockHeight}.` + ); + } +} /** * Returns a list of referenced inscription ids from inscription content. diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 4873d893..06815416 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -17,7 +17,7 @@ import { ENV } from '../env'; import { Brc20PgStore } from './brc20/brc20-pg-store'; import { CountsPgStore } from './counts/counts-pg-store'; import { getIndexResultCountType } from './counts/helpers'; -import { chunkArray, getInscriptionRecursion } from './helpers'; +import { assertNoBlockInscriptionGap, chunkArray, getInscriptionRecursion } from './helpers'; import { DbFullyLocatedInscriptionResult, DbInscription, @@ -90,8 +90,9 @@ export class PgStore extends BasePgStore { // Check where we're at in terms of ingestion, e.g. block height and max blessed inscription // number. This will let us determine if we should skip ingesting this block or throw an error // if a gap is detected. - let blessedNumber = (await this.getMaxInscriptionNumber()) ?? -1; + const currentBlessedNumber = (await this.getMaxInscriptionNumber()) ?? -1; const currentBlockHeight = await this.getChainTipBlockHeight(); + const newBlessedNumbers: number[] = []; for (const rollbackEvent of payload.rollback) { // TODO: Optimize rollbacks just as we optimized applys. @@ -139,13 +140,7 @@ export class PgStore extends BasePgStore { for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) { const reveal = operation.inscription_revealed; - if (reveal.inscription_number >= 0) { - if (blessedNumber + 1 !== reveal.inscription_number) - throw Error( - `PgStore inscription gap detected: Attempting to insert #${reveal.inscription_number} (${block_height}) but current max is #${blessedNumber}` - ); - blessedNumber = reveal.inscription_number; - } + if (reveal.inscription_number >= 0) newBlessedNumbers.push(reveal.inscription_number); const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); @@ -251,6 +246,12 @@ export class PgStore extends BasePgStore { } } } + assertNoBlockInscriptionGap({ + currentNumber: currentBlessedNumber, + newNumbers: newBlessedNumbers, + currentBlockHeight: currentBlockHeight, + newBlockHeight: block_height, + }); // Divide insertion array into chunks of 4000 in order to avoid the postgres limit of 65534 // query params. for (const writeChunk of chunkArray(writes, 4000)) diff --git a/tests/server.test.ts b/tests/server.test.ts index 5a334af9..eaf2f91d 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -3,6 +3,7 @@ import { ENV } from '../src/env'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; import { + BadPayloadRequestError, BitcoinInscriptionRevealed, BitcoinInscriptionTransferred, ChainhookEventObserver, @@ -387,9 +388,14 @@ describe('EventServer', () => { tx_index: 0, }) .build(); - await expect(db.updateInscriptions(errorPayload)).rejects.toThrowError( - /inscription gap detected/ - ); + await expect(db.updateInscriptions(errorPayload)).rejects.toThrow(BadPayloadRequestError); + const response = await server['fastify'].inject({ + method: 'POST', + url: `/chainhook/${PREDICATE_UUID}`, + headers: { authorization: `Bearer ${ENV.CHAINHOOK_NODE_AUTH_TOKEN}` }, + payload: errorPayload, + }); + expect(response.statusCode).toBe(400); }); test('server rejects payload with intermediate inscription gap', async () => { @@ -474,8 +480,100 @@ describe('EventServer', () => { tx_index: 0, }) .build(); - await expect(db.updateInscriptions(errorPayload)).rejects.toThrowError( - /inscription gap detected/ + await expect(db.updateInscriptions(errorPayload)).rejects.toThrow(BadPayloadRequestError); + const response = await server['fastify'].inject({ + method: 'POST', + url: `/chainhook/${PREDICATE_UUID}`, + headers: { authorization: `Bearer ${ENV.CHAINHOOK_NODE_AUTH_TOKEN}` }, + payload: errorPayload, + }); + expect(response.statusCode).toBe(400); + }); + + test('server accepts payload with unordered unbound inscriptions', async () => { + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 0, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build() + ); + const unboundPayload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 2, + inscription_fee: 705, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .transaction({ + hash: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 1, + inscription_fee: 705, + inscription_id: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5o0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 0, // Unbounded + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await expect(db.updateInscriptions(unboundPayload)).resolves.not.toThrow( + BadPayloadRequestError ); }); From b173a33be6a96cc4c01bc901d2d62a5c0a9aff2d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 3 Nov 2023 17:50:19 +0000 Subject: [PATCH 17/42] chore(release): 2.0.0-beta.8 [skip ci] ## [2.0.0-beta.8](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2023-11-03) ### Bug Fixes * do not reject unbounded inscriptions that come in disorder ([#264](https://github.com/hirosystems/ordinals-api/issues/264)) ([56e2235](https://github.com/hirosystems/ordinals-api/commit/56e2235a3823a94493bbe978c2f8e5be48c6cf46)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f01ea2e5..7ae029a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.8](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2023-11-03) + + +### Bug Fixes + +* do not reject unbounded inscriptions that come in disorder ([#264](https://github.com/hirosystems/ordinals-api/issues/264)) ([56e2235](https://github.com/hirosystems/ordinals-api/commit/56e2235a3823a94493bbe978c2f8e5be48c6cf46)) + ## [2.0.0-beta.7](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2023-11-02) From 0f3f51a0186dd2f677e21669d2359cba02122acf Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 4 Nov 2023 05:52:59 -0600 Subject: [PATCH 18/42] fix: guarantee gap detection is comparing numbers --- src/pg/helpers.ts | 2 +- src/pg/pg-store.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index 5b4af1c1..c9f0f3be 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -13,7 +13,7 @@ export function assertNoBlockInscriptionGap(args: { currentBlockHeight: number; newBlockHeight: number; }) { - args.newNumbers.sort(); + args.newNumbers.sort((a, b) => a - b); for (let n = 0; n < args.newNumbers.length; n++) { const curr = args.currentNumber + n; const next = args.newNumbers[n]; diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 06815416..0f095a30 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -140,7 +140,8 @@ export class PgStore extends BasePgStore { for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) { const reveal = operation.inscription_revealed; - if (reveal.inscription_number >= 0) newBlessedNumbers.push(reveal.inscription_number); + if (reveal.inscription_number >= 0) + newBlessedNumbers.push(parseInt(`${reveal.inscription_number}`)); const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); From ba1b4f203114ce402cea999f087990d16e57de41 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 4 Nov 2023 11:59:48 +0000 Subject: [PATCH 19/42] chore(release): 2.0.0-beta.9 [skip ci] ## [2.0.0-beta.9](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2023-11-04) ### Bug Fixes * guarantee gap detection is comparing numbers ([0f3f51a](https://github.com/hirosystems/ordinals-api/commit/0f3f51a0186dd2f677e21669d2359cba02122acf)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae029a1..3e8805d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.9](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2023-11-04) + + +### Bug Fixes + +* guarantee gap detection is comparing numbers ([0f3f51a](https://github.com/hirosystems/ordinals-api/commit/0f3f51a0186dd2f677e21669d2359cba02122acf)) + ## [2.0.0-beta.8](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2023-11-03) From 7079dc079bfec5fbde7018dacbf20c1701d7ccb9 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 10:24:59 -0600 Subject: [PATCH 20/42] fix: reduce brc20 processing batch size to half --- src/pg/brc20/brc20-pg-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index dd0ee33a..91ad1eea 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -49,7 +49,7 @@ export class Brc20PgStore extends BasePgStoreModule { if (blockHeight < BRC20_GENESIS_BLOCK) continue; logger.info(`Brc20PgStore scanning block ${blockHeight}`); await this.sqlWriteTransaction(async sql => { - const limit = 100_000; + const limit = 50_000; let offset = 0; do { const block = await sql` From e2483bbb901ceb3efe4498b31cf234777fb26735 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 11 Nov 2023 16:31:27 +0000 Subject: [PATCH 21/42] chore(release): 2.0.0-beta.10 [skip ci] ## [2.0.0-beta.10](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.9...v2.0.0-beta.10) (2023-11-11) ### Bug Fixes * reduce brc20 processing batch size to half ([7079dc0](https://github.com/hirosystems/ordinals-api/commit/7079dc079bfec5fbde7018dacbf20c1701d7ccb9)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e8805d3..95ffc085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.10](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.9...v2.0.0-beta.10) (2023-11-11) + + +### Bug Fixes + +* reduce brc20 processing batch size to half ([7079dc0](https://github.com/hirosystems/ordinals-api/commit/7079dc079bfec5fbde7018dacbf20c1701d7ccb9)) + ## [2.0.0-beta.9](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2023-11-04) From 08688da84e7695156aa0e822c6f264e9085c5f1a Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 18:33:06 -0600 Subject: [PATCH 22/42] fix: bump docker image From df000b55fb2dd7bb32c058a257568394613612d6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Nov 2023 00:38:54 +0000 Subject: [PATCH 23/42] chore(release): 2.0.0-beta.11 [skip ci] ## [2.0.0-beta.11](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.10...v2.0.0-beta.11) (2023-11-12) ### Bug Fixes * bump docker image ([08688da](https://github.com/hirosystems/ordinals-api/commit/08688da84e7695156aa0e822c6f264e9085c5f1a)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ffc085..4c255346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.11](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.10...v2.0.0-beta.11) (2023-11-12) + + +### Bug Fixes + +* bump docker image ([08688da](https://github.com/hirosystems/ordinals-api/commit/08688da84e7695156aa0e822c6f264e9085c5f1a)) + ## [2.0.0-beta.10](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.9...v2.0.0-beta.10) (2023-11-11) From 95561481a140685d27e68c0c4e03d879882354f4 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 19:02:48 -0600 Subject: [PATCH 24/42] fix: reduce chunk size to 3000 --- src/pg/pg-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 0f095a30..fc9a2821 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -253,9 +253,9 @@ export class PgStore extends BasePgStore { currentBlockHeight: currentBlockHeight, newBlockHeight: block_height, }); - // Divide insertion array into chunks of 4000 in order to avoid the postgres limit of 65534 + // Divide insertion array into chunks of 3000 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of chunkArray(writes, 4000)) + for (const writeChunk of chunkArray(writes, 3000)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) From e7e17ed345f42a233b390a58ba0feae7ec37e39c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Nov 2023 01:08:03 +0000 Subject: [PATCH 25/42] chore(release): 2.0.0-beta.12 [skip ci] ## [2.0.0-beta.12](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.11...v2.0.0-beta.12) (2023-11-12) ### Bug Fixes * reduce chunk size to 3000 ([9556148](https://github.com/hirosystems/ordinals-api/commit/95561481a140685d27e68c0c4e03d879882354f4)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c255346..f90df75d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.12](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.11...v2.0.0-beta.12) (2023-11-12) + + +### Bug Fixes + +* reduce chunk size to 3000 ([9556148](https://github.com/hirosystems/ordinals-api/commit/95561481a140685d27e68c0c4e03d879882354f4)) + ## [2.0.0-beta.11](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.10...v2.0.0-beta.11) (2023-11-12) From 20376470455a0887d81f3128047d330e7d97e438 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 21:02:24 -0600 Subject: [PATCH 26/42] fix: reduce brc-20 batch to 5k --- src/pg/brc20/brc20-pg-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index 91ad1eea..b7eba051 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -49,7 +49,7 @@ export class Brc20PgStore extends BasePgStoreModule { if (blockHeight < BRC20_GENESIS_BLOCK) continue; logger.info(`Brc20PgStore scanning block ${blockHeight}`); await this.sqlWriteTransaction(async sql => { - const limit = 50_000; + const limit = 5000; let offset = 0; do { const block = await sql` From f031ec76bcca31ea9f7252107927ca7ea802ddac Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Nov 2023 03:05:54 +0000 Subject: [PATCH 27/42] chore(release): 2.0.0-beta.13 [skip ci] ## [2.0.0-beta.13](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.12...v2.0.0-beta.13) (2023-11-12) ### Bug Fixes * reduce brc-20 batch to 5k ([2037647](https://github.com/hirosystems/ordinals-api/commit/20376470455a0887d81f3128047d330e7d97e438)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f90df75d..db330c99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.13](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.12...v2.0.0-beta.13) (2023-11-12) + + +### Bug Fixes + +* reduce brc-20 batch to 5k ([2037647](https://github.com/hirosystems/ordinals-api/commit/20376470455a0887d81f3128047d330e7d97e438)) + ## [2.0.0-beta.12](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.11...v2.0.0-beta.12) (2023-11-12) From e2ed039120958fde07297c6760022770dab710a8 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 21:05:59 -0600 Subject: [PATCH 28/42] fix: try batch iterator generator --- src/pg/pg-store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index fc9a2821..66fc76f7 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -3,6 +3,7 @@ import { PgConnectionVars, PgSqlClient, PgSqlQuery, + batchIterate, connectPostgres, logger, runMigrations, @@ -255,7 +256,7 @@ export class PgStore extends BasePgStore { }); // Divide insertion array into chunks of 3000 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of chunkArray(writes, 3000)) + for (const writeChunk of batchIterate(writes, 3000)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) From 3bc1f53efa9d4db49d414052f3dbfc3058abf649 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Nov 2023 03:11:27 +0000 Subject: [PATCH 29/42] chore(release): 2.0.0-beta.14 [skip ci] ## [2.0.0-beta.14](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.13...v2.0.0-beta.14) (2023-11-12) ### Bug Fixes * try batch iterator generator ([e2ed039](https://github.com/hirosystems/ordinals-api/commit/e2ed039120958fde07297c6760022770dab710a8)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db330c99..1b4e6647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.14](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.13...v2.0.0-beta.14) (2023-11-12) + + +### Bug Fixes + +* try batch iterator generator ([e2ed039](https://github.com/hirosystems/ordinals-api/commit/e2ed039120958fde07297c6760022770dab710a8)) + ## [2.0.0-beta.13](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.12...v2.0.0-beta.13) (2023-11-12) From 9cf9230fe193a2075a0cd2e45a5fa316ecc225c5 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 21:22:02 -0600 Subject: [PATCH 30/42] fix: batch size to 2000 --- src/pg/pg-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 66fc76f7..d2efad4a 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -254,9 +254,9 @@ export class PgStore extends BasePgStore { currentBlockHeight: currentBlockHeight, newBlockHeight: block_height, }); - // Divide insertion array into chunks of 3000 in order to avoid the postgres limit of 65534 + // Divide insertion array into chunks of 2000 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of batchIterate(writes, 3000)) + for (const writeChunk of batchIterate(writes, 2000)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) From 91efda8632967f1ec0d98fd9d32cb904a18b5ce9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Nov 2023 03:34:25 +0000 Subject: [PATCH 31/42] chore(release): 2.0.0-beta.15 [skip ci] ## [2.0.0-beta.15](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.14...v2.0.0-beta.15) (2023-11-12) ### Bug Fixes * batch size to 2000 ([9cf9230](https://github.com/hirosystems/ordinals-api/commit/9cf9230fe193a2075a0cd2e45a5fa316ecc225c5)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b4e6647..c846fc6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.15](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.14...v2.0.0-beta.15) (2023-11-12) + + +### Bug Fixes + +* batch size to 2000 ([9cf9230](https://github.com/hirosystems/ordinals-api/commit/9cf9230fe193a2075a0cd2e45a5fa316ecc225c5)) + ## [2.0.0-beta.14](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.13...v2.0.0-beta.14) (2023-11-12) From 1d3a24b8bb1d852eb1ae198d84a5940eec997ded Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sat, 11 Nov 2023 21:51:44 -0600 Subject: [PATCH 32/42] fix: try returning brc20 batch to normal, use batchIterate everywhere --- src/pg/brc20/brc20-pg-store.ts | 2 +- src/pg/helpers.ts | 12 ------------ src/pg/pg-store.ts | 8 ++++---- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index b7eba051..dd0ee33a 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -49,7 +49,7 @@ export class Brc20PgStore extends BasePgStoreModule { if (blockHeight < BRC20_GENESIS_BLOCK) continue; logger.info(`Brc20PgStore scanning block ${blockHeight}`); await this.sqlWriteTransaction(async sql => { - const limit = 5000; + const limit = 100_000; let offset = 0; do { const block = await sql` diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index c9f0f3be..5506bedc 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -61,18 +61,6 @@ export function throwOnFirstRejected(settles: { return values; } -/** - * Divides array into equal chunks - * @param arr - Array - * @param chunkSize - Chunk size - * @returns Array of arrays - */ -export function chunkArray(arr: T[], chunkSize: number): T[][] { - const result: T[][] = []; - for (let i = 0; i < arr.length; i += chunkSize) result.push(arr.slice(i, i + chunkSize)); - return result; -} - export function objRemoveUndefinedValues(obj: object) { Object.keys(obj).forEach(key => (obj as any)[key] === undefined && delete (obj as any)[key]); } diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index d2efad4a..484cbb90 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -18,7 +18,7 @@ import { ENV } from '../env'; import { Brc20PgStore } from './brc20/brc20-pg-store'; import { CountsPgStore } from './counts/counts-pg-store'; import { getIndexResultCountType } from './counts/helpers'; -import { assertNoBlockInscriptionGap, chunkArray, getInscriptionRecursion } from './helpers'; +import { assertNoBlockInscriptionGap, getInscriptionRecursion } from './helpers'; import { DbFullyLocatedInscriptionResult, DbInscription, @@ -254,9 +254,9 @@ export class PgStore extends BasePgStore { currentBlockHeight: currentBlockHeight, newBlockHeight: block_height, }); - // Divide insertion array into chunks of 2000 in order to avoid the postgres limit of 65534 + // Divide insertion array into chunks of 4000 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of batchIterate(writes, 2000)) + for (const writeChunk of batchIterate(writes, 4000)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) @@ -903,7 +903,7 @@ export class PgStore extends BasePgStore { } if (inserts.length === 0) return; await this.sqlWriteTransaction(async sql => { - for (const chunk of chunkArray(inserts, 500)) + for (const chunk of batchIterate(inserts, 500)) await sql` INSERT INTO inscription_recursions ${sql(chunk)} ON CONFLICT ON CONSTRAINT inscription_recursions_unique DO NOTHING From 0e68c726d88114112e6281b6b8854792a28bc9cd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Nov 2023 03:55:06 +0000 Subject: [PATCH 33/42] chore(release): 2.0.0-beta.16 [skip ci] ## [2.0.0-beta.16](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.15...v2.0.0-beta.16) (2023-11-12) ### Bug Fixes * try returning brc20 batch to normal, use batchIterate everywhere ([1d3a24b](https://github.com/hirosystems/ordinals-api/commit/1d3a24b8bb1d852eb1ae198d84a5940eec997ded)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c846fc6c..76881e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.16](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.15...v2.0.0-beta.16) (2023-11-12) + + +### Bug Fixes + +* try returning brc20 batch to normal, use batchIterate everywhere ([1d3a24b](https://github.com/hirosystems/ordinals-api/commit/1d3a24b8bb1d852eb1ae198d84a5940eec997ded)) + ## [2.0.0-beta.15](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.14...v2.0.0-beta.15) (2023-11-12) From 13e1f16f6980b6743abe8d95644703329f07fe3f Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Thu, 16 Nov 2023 16:17:09 -0600 Subject: [PATCH 34/42] fix: inscription count generation --- src/env.ts | 5 ---- src/pg/counts/counts-pg-store.ts | 51 ++++++++++++++++++-------------- src/pg/pg-store.ts | 18 +++++------ 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/env.ts b/src/env.ts index 72106e43..9f83eed3 100644 --- a/src/env.ts +++ b/src/env.ts @@ -55,11 +55,6 @@ const schema = Type.Object({ /** Enables BRC-20 processing in write mode APIs */ BRC20_BLOCK_SCAN_ENABLED: Type.Boolean({ default: true }), - /** - * Disables inscription genesis/current location calculation, count aggregation, etc. so blocks - * can be ingested faster during a full replay. - */ - FAST_INGESTION_MODE: Type.Boolean({ default: false }), }); type Env = Static; diff --git a/src/pg/counts/counts-pg-store.ts b/src/pg/counts/counts-pg-store.ts index d63251fa..61a15ac4 100644 --- a/src/pg/counts/counts-pg-store.ts +++ b/src/pg/counts/counts-pg-store.ts @@ -56,42 +56,49 @@ export class CountsPgStore extends BasePgStoreModule { async applyInscriptions(writes: DbInscriptionInsert[]): Promise { if (writes.length === 0) return; - const mimeType = new Map(); - const rarity = new Map(); - const recursion = new Map(); - const type = new Map(); + const mimeType = new Map(); + const rarity = new Map(); + const recursion = new Map(); + const typeMap = new Map(); for (const i of writes) { - const t = i.number < 0 ? 'cursed' : 'blessed'; - mimeType.set(i.mime_type, { - mime_type: i.mime_type, - count: mimeType.get(i.mime_type)?.count ?? 0 + 1, - }); - rarity.set(i.sat_rarity, { - sat_rarity: i.sat_rarity, - count: rarity.get(i.sat_rarity)?.count ?? 0 + 1, - }); - recursion.set(i.recursive, { - recursive: i.recursive, - count: recursion.get(i.recursive)?.count ?? 0 + 1, - }); - type.set(t, { type: t, count: type.get(t)?.count ?? 0 + 1 }); + mimeType.set(i.mime_type, (mimeType.get(i.mime_type) ?? 0) + 1); + rarity.set(i.sat_rarity, (rarity.get(i.sat_rarity) ?? 0) + 1); + recursion.set(i.recursive, (recursion.get(i.recursive) ?? 0) + 1); + const inscrType = i.number < 0 ? 'cursed' : 'blessed'; + typeMap.set(inscrType, (typeMap.get(inscrType) ?? 0) + 1); } + const mimeTypeInsert = Array.from(mimeType.entries()).map(k => ({ + mime_type: k[0], + count: k[1], + })); + const rarityInsert = Array.from(rarity.entries()).map(k => ({ + sat_rarity: k[0], + count: k[1], + })); + const recursionInsert = Array.from(recursion.entries()).map(k => ({ + recursive: k[0], + count: k[1], + })); + const typeInsert = Array.from(typeMap.entries()).map(k => ({ + type: k[0], + count: k[1], + })); // `counts_by_address` and `counts_by_genesis_address` count increases are handled in // `applyLocations`. await this.sql` WITH increase_mime_type AS ( - INSERT INTO counts_by_mime_type ${this.sql([...mimeType.values()])} + INSERT INTO counts_by_mime_type ${this.sql(mimeTypeInsert)} ON CONFLICT (mime_type) DO UPDATE SET count = counts_by_mime_type.count + EXCLUDED.count ), increase_rarity AS ( - INSERT INTO counts_by_sat_rarity ${this.sql([...rarity.values()])} + INSERT INTO counts_by_sat_rarity ${this.sql(rarityInsert)} ON CONFLICT (sat_rarity) DO UPDATE SET count = counts_by_sat_rarity.count + EXCLUDED.count ), increase_recursive AS ( - INSERT INTO counts_by_recursive ${this.sql([...recursion.values()])} + INSERT INTO counts_by_recursive ${this.sql(recursionInsert)} ON CONFLICT (recursive) DO UPDATE SET count = counts_by_recursive.count + EXCLUDED.count ) - INSERT INTO counts_by_type ${this.sql([...type.values()])} + INSERT INTO counts_by_type ${this.sql(typeInsert)} ON CONFLICT (type) DO UPDATE SET count = counts_by_type.count + EXCLUDED.count `; } diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 484cbb90..93ae558b 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -669,16 +669,14 @@ export class PgStore extends BasePgStore { RETURNING inscription_id, id AS location_id, block_height, tx_index, address `; await this.updateInscriptionRecursions(writes); - if (!ENV.FAST_INGESTION_MODE) { - if (transferGenesisIds.size) - await sql` - UPDATE inscriptions - SET updated_at = NOW() - WHERE genesis_id IN ${sql([...transferGenesisIds])} - `; - await this.updateInscriptionLocationPointers(locations); - await this.counts.applyInscriptions(inscriptions); - } + if (transferGenesisIds.size) + await sql` + UPDATE inscriptions + SET updated_at = NOW() + WHERE genesis_id IN ${sql([...transferGenesisIds])} + `; + await this.updateInscriptionLocationPointers(locations); + await this.counts.applyInscriptions(inscriptions); for (const reveal of writes) { const action = reveal.inscription ? `reveal #${reveal.inscription.number}` : `transfer`; logger.info( From 5b1db36b02b39818f4b6ca61beb23a94ac9174bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Thu, 16 Nov 2023 20:04:41 -0600 Subject: [PATCH 35/42] build: run tests in band (#272) * build: attempt to fix migration * fix: test again * fix: log migrations * fix: try removing migrations * test: migrations on the outside * fix: return migs * test: delete some migrations * test: return some migs * test: remove more * test: more * fix: try without logs and without mv data * test: revert migrations * fix: run tests in band --- package-lock.json | 56 +++++++++++++++++++++++++++++++++----- package.json | 4 +-- tests/brc20.test.ts | 5 ++-- tests/cache.test.ts | 5 ++-- tests/inscriptions.test.ts | 5 ++-- tests/sats.test.ts | 5 ++-- tests/server.test.ts | 7 +++-- tests/stats.test.ts | 5 ++-- tests/status.test.ts | 5 ++-- 9 files changed, 73 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09f58367..479f269c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fastify/multipart": "^7.1.0", "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", - "@hirosystems/api-toolkit": "^1.3.0", + "@hirosystems/api-toolkit": "^1.3.1", "@hirosystems/chainhook-client": "^1.4.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", @@ -53,6 +53,48 @@ "typescript": "^4.7.4" } }, + "../api-toolkit": { + "name": "@hirosystems/api-toolkit", + "version": "1.3.0", + "extraneous": true, + "license": "Apache 2.0", + "dependencies": { + "@fastify/cors": "^8.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/type-provider-typebox": "^3.2.0", + "fastify": "^4.3.0", + "fastify-metrics": "^10.2.0", + "node-pg-migrate": "^6.2.2", + "pino": "^8.11.0", + "postgres": "^3.3.4" + }, + "bin": { + "api-toolkit-git-info": "bin/api-toolkit-git-info.js" + }, + "devDependencies": { + "@commitlint/cli": "^17.5.0", + "@commitlint/config-conventional": "^17.4.4", + "@stacks/eslint-config": "^1.2.0", + "@types/jest": "^29.5.0", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", + "babel-jest": "^29.5.0", + "copyfiles": "^2.4.1", + "eslint": "^8.36.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-tsdoc": "^0.2.17", + "husky": "^8.0.3", + "jest": "^29.5.0", + "prettier": "^2.8.6", + "rimraf": "^4.4.1", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1207,9 +1249,9 @@ } }, "node_modules/@hirosystems/api-toolkit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.0.tgz", - "integrity": "sha512-orZSVqaZV3LJb886W90DNLPkZQ3XvMhJqKUBaIYIzBdQIoPEf5B8dp/JK3bII/WAry940ofphklL2+YVxJQIIA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.1.tgz", + "integrity": "sha512-uUOqWcJlaxnlW30RyZ1UdidzFy29esd4bG0UxwnsJH+M+qtvV4V/NaHLRUFbnJkoF5b6vckZh1mldyBNY7aL1Q==", "dependencies": { "@fastify/cors": "^8.0.0", "@fastify/swagger": "^8.3.1", @@ -19657,9 +19699,9 @@ "requires": {} }, "@hirosystems/api-toolkit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.0.tgz", - "integrity": "sha512-orZSVqaZV3LJb886W90DNLPkZQ3XvMhJqKUBaIYIzBdQIoPEf5B8dp/JK3bII/WAry940ofphklL2+YVxJQIIA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.1.tgz", + "integrity": "sha512-uUOqWcJlaxnlW30RyZ1UdidzFy29esd4bG0UxwnsJH+M+qtvV4V/NaHLRUFbnJkoF5b6vckZh1mldyBNY7aL1Q==", "requires": { "@fastify/cors": "^8.0.0", "@fastify/swagger": "^8.3.1", diff --git a/package.json b/package.json index 2e0fb6ac..723f2e3d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "rimraf ./dist && tsc --project tsconfig.build.json", "start": "node dist/src/index.js", "start-ts": "ts-node ./src/index.ts", - "test": "jest", + "test": "jest --runInBand", "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", @@ -50,7 +50,7 @@ "@fastify/multipart": "^7.1.0", "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", - "@hirosystems/api-toolkit": "^1.3.0", + "@hirosystems/api-toolkit": "^1.3.1", "@hirosystems/chainhook-client": "^1.4.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", diff --git a/tests/brc20.test.ts b/tests/brc20.test.ts index 21d43467..8f51852d 100644 --- a/tests/brc20.test.ts +++ b/tests/brc20.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +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'; @@ -72,14 +72,15 @@ describe('BRC-20', () => { }; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('token standard validation', () => { diff --git a/tests/cache.test.ts b/tests/cache.test.ts index eda268f3..29c890eb 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +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'; @@ -8,14 +8,15 @@ describe('ETag cache', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); test('inscription cache control', async () => { diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index 5625c893..4fbf4255 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +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'; @@ -12,14 +12,15 @@ describe('/inscriptions', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('show', () => { diff --git a/tests/sats.test.ts b/tests/sats.test.ts index af39a376..723d23c7 100644 --- a/tests/sats.test.ts +++ b/tests/sats.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +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'; @@ -8,14 +8,15 @@ describe('/sats', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); test('returns valid sat', async () => { diff --git a/tests/server.test.ts b/tests/server.test.ts index eaf2f91d..b375d741 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -9,7 +9,7 @@ import { ChainhookEventObserver, } from '@hirosystems/chainhook-client'; import { buildApiServer } from '../src/api/init'; -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; describe('EventServer', () => { let db: PgStore; @@ -17,9 +17,9 @@ describe('EventServer', () => { let fastify: TestFastifyServer; beforeEach(async () => { - db = await PgStore.connect({ skipMigrations: true }); - await cycleMigrations(MIGRATIONS_DIR); + await runMigrations(MIGRATIONS_DIR, 'up'); ENV.CHAINHOOK_AUTO_PREDICATE_REGISTRATION = false; + db = await PgStore.connect({ skipMigrations: true }); server = await startChainhookServer({ db }); fastify = await buildApiServer({ db }); }); @@ -28,6 +28,7 @@ describe('EventServer', () => { await server.close(); await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('parser', () => { diff --git a/tests/stats.test.ts b/tests/stats.test.ts index 34b83c91..bc7119b1 100644 --- a/tests/stats.test.ts +++ b/tests/stats.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +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'; @@ -8,14 +8,15 @@ describe('/stats', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('/stats/inscriptions', () => { diff --git a/tests/status.test.ts b/tests/status.test.ts index bf043697..50596a48 100644 --- a/tests/status.test.ts +++ b/tests/status.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +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'; @@ -8,14 +8,15 @@ describe('Status', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); test('returns status when db is empty', async () => { From 69c597fa43fc2e8d933d3474c77ab0529ffdc0a3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 17 Nov 2023 02:07:09 +0000 Subject: [PATCH 36/42] chore(release): 2.0.0-beta.17 [skip ci] ## [2.0.0-beta.17](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.16...v2.0.0-beta.17) (2023-11-17) ### Bug Fixes * inscription count generation ([13e1f16](https://github.com/hirosystems/ordinals-api/commit/13e1f16f6980b6743abe8d95644703329f07fe3f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76881e81..9ae8419b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.17](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.16...v2.0.0-beta.17) (2023-11-17) + + +### Bug Fixes + +* inscription count generation ([13e1f16](https://github.com/hirosystems/ordinals-api/commit/13e1f16f6980b6743abe8d95644703329f07fe3f)) + ## [2.0.0-beta.16](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.15...v2.0.0-beta.16) (2023-11-12) From 86efc3acf8d743b22edc11e137458a51c892c1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Thu, 16 Nov 2023 20:56:31 -0600 Subject: [PATCH 37/42] fix: address location counts (#273) --- src/pg/counts/counts-pg-store.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pg/counts/counts-pg-store.ts b/src/pg/counts/counts-pg-store.ts index 61a15ac4..8d983719 100644 --- a/src/pg/counts/counts-pg-store.ts +++ b/src/pg/counts/counts-pg-store.ts @@ -142,28 +142,28 @@ export class CountsPgStore extends BasePgStoreModule { if (writes.length === 0) return; await this.sqlWriteTransaction(async sql => { const table = genesis ? sql`counts_by_genesis_address` : sql`counts_by_address`; - const oldAddr = new Map(); - const newAddr = new Map(); + const oldAddr = new Map(); + const newAddr = new Map(); for (const i of writes) { - if (i.old_address) - oldAddr.set(i.old_address, { - address: i.old_address, - count: oldAddr.get(i.old_address)?.count ?? 0 + 1, - }); - if (i.new_address) - newAddr.set(i.new_address, { - address: i.new_address, - count: newAddr.get(i.new_address)?.count ?? 0 + 1, - }); + if (i.old_address) oldAddr.set(i.old_address, (oldAddr.get(i.old_address) ?? 0) + 1); + if (i.new_address) newAddr.set(i.new_address, (newAddr.get(i.new_address) ?? 0) + 1); } - if (oldAddr.size) + const oldAddrInsert = Array.from(oldAddr.entries()).map(k => ({ + address: k[0], + count: k[1], + })); + const newAddrInsert = Array.from(newAddr.entries()).map(k => ({ + address: k[0], + count: k[1], + })); + if (oldAddrInsert.length > 0) await sql` - INSERT INTO ${table} ${sql([...oldAddr.values()])} + INSERT INTO ${table} ${sql(oldAddrInsert)} ON CONFLICT (address) DO UPDATE SET count = ${table}.count - EXCLUDED.count `; - if (newAddr.size) + if (newAddrInsert.length > 0) await sql` - INSERT INTO ${table} ${sql([...newAddr.values()])} + INSERT INTO ${table} ${sql(newAddrInsert)} ON CONFLICT (address) DO UPDATE SET count = ${table}.count + EXCLUDED.count `; }); From 2dde6e09b6c79bcca44bedbc201df564b185e6f9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 17 Nov 2023 02:58:49 +0000 Subject: [PATCH 38/42] chore(release): 2.0.0-beta.18 [skip ci] ## [2.0.0-beta.18](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.17...v2.0.0-beta.18) (2023-11-17) ### Bug Fixes * address location counts ([#273](https://github.com/hirosystems/ordinals-api/issues/273)) ([86efc3a](https://github.com/hirosystems/ordinals-api/commit/86efc3acf8d743b22edc11e137458a51c892c1c7)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae8419b..c918e7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.18](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.17...v2.0.0-beta.18) (2023-11-17) + + +### Bug Fixes + +* address location counts ([#273](https://github.com/hirosystems/ordinals-api/issues/273)) ([86efc3a](https://github.com/hirosystems/ordinals-api/commit/86efc3acf8d743b22edc11e137458a51c892c1c7)) + ## [2.0.0-beta.17](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.16...v2.0.0-beta.17) (2023-11-17) From cec3de061a5b475161b2d11369fc13b63ad8bc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Sun, 19 Nov 2023 18:16:02 -0600 Subject: [PATCH 39/42] fix: remove null bytes before inserting inscriptions (#274) * fix: remove null bytes before inserting inscriptions * fix: also on content type --- src/pg/helpers.ts | 5 +++++ src/pg/pg-store.ts | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index 5506bedc..443b45d2 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -64,3 +64,8 @@ export function throwOnFirstRejected(settles: { export function objRemoveUndefinedValues(obj: object) { Object.keys(obj).forEach(key => (obj as any)[key] === undefined && delete (obj as any)[key]); } + +export function removeNullBytes(input: string): string { + // Replace null byte with an empty string + return input.replace(/\x00/g, ''); +} diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 93ae558b..6616c33e 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -18,7 +18,7 @@ import { ENV } from '../env'; import { Brc20PgStore } from './brc20/brc20-pg-store'; import { CountsPgStore } from './counts/counts-pg-store'; import { getIndexResultCountType } from './counts/helpers'; -import { assertNoBlockInscriptionGap, getInscriptionRecursion } from './helpers'; +import { assertNoBlockInscriptionGap, getInscriptionRecursion, removeNullBytes } from './helpers'; import { DbFullyLocatedInscriptionResult, DbInscription, @@ -185,14 +185,15 @@ export class PgStore extends BasePgStore { const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); + const contentType = removeNullBytes(reveal.content_type); writes.push({ inscription: { genesis_id: reveal.inscription_id, - mime_type: reveal.content_type.split(';')[0], - content_type: reveal.content_type, + mime_type: contentType.split(';')[0], + content_type: contentType, content_length: reveal.content_length, number: reveal.inscription_number, - content: reveal.content_bytes, + content: removeNullBytes(reveal.content_bytes), fee: reveal.inscription_fee.toString(), curse_type: JSON.stringify(reveal.curse_type), sat_ordinal: reveal.ordinal_number.toString(), From d79d6e20392f034eeb5f1188cf6712cfa1b45841 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 20 Nov 2023 00:18:19 +0000 Subject: [PATCH 40/42] chore(release): 2.0.0-beta.19 [skip ci] ## [2.0.0-beta.19](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.18...v2.0.0-beta.19) (2023-11-20) ### Bug Fixes * remove null bytes before inserting inscriptions ([#274](https://github.com/hirosystems/ordinals-api/issues/274)) ([cec3de0](https://github.com/hirosystems/ordinals-api/commit/cec3de061a5b475161b2d11369fc13b63ad8bc27)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c918e7bd..83c42dd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.19](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.18...v2.0.0-beta.19) (2023-11-20) + + +### Bug Fixes + +* remove null bytes before inserting inscriptions ([#274](https://github.com/hirosystems/ordinals-api/issues/274)) ([cec3de0](https://github.com/hirosystems/ordinals-api/commit/cec3de061a5b475161b2d11369fc13b63ad8bc27)) + ## [2.0.0-beta.18](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.17...v2.0.0-beta.18) (2023-11-17) From 7e8d4127bc4b27917c59349abe9f5fbf98da98ab Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Sun, 19 Nov 2023 18:23:25 -0600 Subject: [PATCH 41/42] fix: also remove null bytes in blessed inscriptions --- src/pg/pg-store.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 6616c33e..25fdbc52 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -146,14 +146,15 @@ export class PgStore extends BasePgStore { const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); + const contentType = removeNullBytes(reveal.content_type); writes.push({ inscription: { genesis_id: reveal.inscription_id, - mime_type: reveal.content_type.split(';')[0], - content_type: reveal.content_type, + mime_type: contentType.split(';')[0], + content_type: contentType, content_length: reveal.content_length, number: reveal.inscription_number, - content: reveal.content_bytes, + content: removeNullBytes(reveal.content_bytes), fee: reveal.inscription_fee.toString(), curse_type: null, sat_ordinal: reveal.ordinal_number.toString(), From 0667a342606d99d6490fa88e310b76c3f7aa6f98 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 20 Nov 2023 00:25:53 +0000 Subject: [PATCH 42/42] chore(release): 2.0.0-beta.20 [skip ci] ## [2.0.0-beta.20](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.19...v2.0.0-beta.20) (2023-11-20) ### Bug Fixes * also remove null bytes in blessed inscriptions ([7e8d412](https://github.com/hirosystems/ordinals-api/commit/7e8d4127bc4b27917c59349abe9f5fbf98da98ab)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c42dd6..4e9edf40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.20](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.19...v2.0.0-beta.20) (2023-11-20) + + +### Bug Fixes + +* also remove null bytes in blessed inscriptions ([7e8d412](https://github.com/hirosystems/ordinals-api/commit/7e8d4127bc4b27917c59349abe9f5fbf98da98ab)) + ## [2.0.0-beta.19](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.18...v2.0.0-beta.19) (2023-11-20)