diff --git a/migrations/1692980393413_locations-unique.ts b/migrations/1692980393413_locations-unique.ts index 704d87b5..56491527 100644 --- a/migrations/1692980393413_locations-unique.ts +++ b/migrations/1692980393413_locations-unique.ts @@ -16,5 +16,19 @@ export function up(pgm: MigrationBuilder): void { export function down(pgm: MigrationBuilder): void { pgm.dropConstraint('locations', 'locations_inscription_id_block_height_tx_index_unique'); pgm.dropIndex('locations', ['output', 'offset']); + // Modify any repeated offsets slightly so we can re-add the unique constraint. This is mostly for + // unit testing purposes. + pgm.sql(` + WITH duplicates AS ( + SELECT + id, output, "offset", ROW_NUMBER() OVER (PARTITION BY output, "offset" ORDER BY id) as rn + FROM locations + ) + UPDATE locations + SET "offset" = duplicates."offset" + rn - 1 + FROM duplicates + WHERE locations.id = duplicates.id + AND rn > 1 + `); pgm.createConstraint('locations', 'locations_output_offset_unique', 'UNIQUE(output, "offset")'); } diff --git a/tests/server.test.ts b/tests/server.test.ts index a8554cb0..5f00ffda 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -633,4 +633,63 @@ describe('EventServer', () => { expect(response.statusCode).toBe(200); }); }); + + describe('jubilee', () => { + test('supports multiple inscriptions on the same sat', async () => { + await expect( + 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: { classic: 0, jubilee: 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, + curse_type: null, + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: { classic: -1, jubilee: 1 }, // Would have been cursed + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i1', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, // Same sat + ordinal_block_height: 650000, + ordinal_offset: 0, + // Same satpoint + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + curse_type: null, + }) + .build() + ) + ).resolves.not.toThrow(); + }); + }); });