Skip to content

Commit 1edb256

Browse files
authored
fix: support multiple BNS name events in the same transaction (#1337)
* fix: multiple name transfers in same block * fix: support multiple name events in same tx
1 parent 75e5187 commit 1edb256

File tree

6 files changed

+486
-30
lines changed

6 files changed

+486
-30
lines changed

src/datastore/common.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,12 @@ export interface DbBnsName {
517517
expire_block: number;
518518
grace_period?: number;
519519
renewal_deadline?: number;
520-
resolver?: string | undefined;
520+
resolver?: string;
521521
zonefile: string;
522522
zonefile_hash: string;
523523
tx_id: string;
524524
tx_index: number;
525+
event_index?: number;
525526
status?: string;
526527
canonical: boolean;
527528
}
@@ -1106,6 +1107,7 @@ export interface BnsNameInsertValues {
11061107
namespace_id: string;
11071108
tx_index: number;
11081109
tx_id: PgBytea;
1110+
event_index: number | null;
11091111
status: string | null;
11101112
canonical: boolean;
11111113
index_block_hash: PgBytea;

src/datastore/pg-store.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3127,7 +3127,7 @@ export class PgStore {
31273127
WHERE namespace_id = ${namespace}
31283128
AND registered_at <= ${maxBlockHeight}
31293129
AND canonical = true AND microblock_canonical = true
3130-
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC
3130+
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
31313131
LIMIT 100
31323132
OFFSET ${offset}
31333133
`;
@@ -3187,7 +3187,10 @@ export class PgStore {
31873187
AND n.registered_at <= ${maxBlockHeight}
31883188
AND n.canonical = true
31893189
AND n.microblock_canonical = true
3190-
ORDER BY n.registered_at DESC, n.microblock_sequence DESC, n.tx_index DESC
3190+
ORDER BY n.registered_at DESC,
3191+
n.microblock_sequence DESC,
3192+
n.tx_index DESC,
3193+
n.event_index DESC
31913194
LIMIT 1
31923195
`;
31933196
if (nameZonefile.length === 0) {
@@ -3455,7 +3458,7 @@ export class PgStore {
34553458
FROM names
34563459
WHERE canonical = true AND microblock_canonical = true
34573460
AND registered_at <= ${maxBlockHeight}
3458-
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC
3461+
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
34593462
LIMIT 100
34603463
OFFSET ${offset}
34613464
`;

src/datastore/pg-write-store.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,12 +1404,25 @@ export class PgWriteStore extends PgStore {
14041404
namespace_id,
14051405
tx_id,
14061406
tx_index,
1407+
event_index,
14071408
status,
14081409
canonical,
14091410
} = bnsName;
1410-
// Try to figure out the name's expiration block based on its namespace's lifetime. However, if
1411-
// the name was only transferred, keep the expiration from the last register/renewal we had.
1411+
// Try to figure out the name's expiration block based on its namespace's lifetime.
14121412
let expireBlock = expire_block;
1413+
const namespaceLifetime = await sql<{ lifetime: number }[]>`
1414+
SELECT lifetime
1415+
FROM namespaces
1416+
WHERE namespace_id = ${namespace_id}
1417+
AND canonical = true AND microblock_canonical = true
1418+
ORDER BY namespace_id, ready_block DESC, microblock_sequence DESC, tx_index DESC
1419+
LIMIT 1
1420+
`;
1421+
if (namespaceLifetime.length > 0) {
1422+
expireBlock = registered_at + namespaceLifetime[0].lifetime;
1423+
}
1424+
// If the name was transferred, keep the expiration from the last register/renewal we had (if
1425+
// any).
14131426
if (status === 'name-transfer') {
14141427
const prevExpiration = await sql<{ expire_block: number }[]>`
14151428
SELECT expire_block
@@ -1422,18 +1435,6 @@ export class PgWriteStore extends PgStore {
14221435
if (prevExpiration.length > 0) {
14231436
expireBlock = prevExpiration[0].expire_block;
14241437
}
1425-
} else {
1426-
const namespaceLifetime = await sql<{ lifetime: number }[]>`
1427-
SELECT lifetime
1428-
FROM namespaces
1429-
WHERE namespace_id = ${namespace_id}
1430-
AND canonical = true AND microblock_canonical = true
1431-
ORDER BY namespace_id, ready_block DESC, microblock_sequence DESC, tx_index DESC
1432-
LIMIT 1
1433-
`;
1434-
if (namespaceLifetime.length > 0) {
1435-
expireBlock = registered_at + namespaceLifetime[0].lifetime;
1436-
}
14371438
}
14381439
// If we didn't receive a zonefile, keep the last valid one.
14391440
let finalZonefile = zonefile;
@@ -1476,6 +1477,7 @@ export class PgWriteStore extends PgStore {
14761477
namespace_id: namespace_id,
14771478
tx_index: tx_index,
14781479
tx_id: tx_id,
1480+
event_index: event_index ?? null,
14791481
status: status ?? null,
14801482
canonical: canonical,
14811483
index_block_hash: blockData.index_block_hash,
@@ -1486,14 +1488,15 @@ export class PgWriteStore extends PgStore {
14861488
};
14871489
await sql`
14881490
INSERT INTO names ${sql(nameValues)}
1489-
ON CONFLICT ON CONSTRAINT unique_name_tx_id_index_block_hash_microblock_hash DO
1491+
ON CONFLICT ON CONSTRAINT unique_name_tx_id_index_block_hash_microblock_hash_event_index DO
14901492
UPDATE SET
14911493
address = EXCLUDED.address,
14921494
registered_at = EXCLUDED.registered_at,
14931495
expire_block = EXCLUDED.expire_block,
14941496
zonefile_hash = EXCLUDED.zonefile_hash,
14951497
namespace_id = EXCLUDED.namespace_id,
14961498
tx_index = EXCLUDED.tx_index,
1499+
event_index = EXCLUDED.event_index,
14971500
status = EXCLUDED.status,
14981501
canonical = EXCLUDED.canonical,
14991502
parent_index_block_hash = EXCLUDED.parent_index_block_hash,

src/event-stream/bns/bns-helpers.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BufferCV, ChainID, ClarityType, hexToCV, StringAsciiCV } from '@stacks/transactions';
2-
import { hexToBuffer, hexToUtf8String } from '../../helpers';
2+
import { bnsNameCV, hexToBuffer, hexToUtf8String } from '../../helpers';
33
import {
44
CoreNodeEvent,
55
CoreNodeEventType,
@@ -281,6 +281,7 @@ export function parseNameRenewalWithNoZonefileHashFromContractCall(
281281
zonefile: '',
282282
tx_id: tx.parsed_tx.tx_id,
283283
tx_index: tx.core_tx.tx_index,
284+
event_index: undefined,
284285
status: 'name-renewal',
285286
canonical: true,
286287
};
@@ -290,7 +291,7 @@ export function parseNameRenewalWithNoZonefileHashFromContractCall(
290291
export function parseNameFromContractEvent(
291292
event: SmartContractEvent,
292293
tx: CoreNodeParsedTxMessage,
293-
txEvents: CoreNodeEvent[],
294+
allEvents: CoreNodeEvent[],
294295
blockHeight: number,
295296
chainId: ChainID
296297
): DbBnsName | undefined {
@@ -303,24 +304,27 @@ export function parseNameFromContractEvent(
303304
} catch (error) {
304305
return;
305306
}
306-
let name_address = attachment.attachment.metadata.tx_sender.address;
307+
const fullName = `${attachment.attachment.metadata.name}.${attachment.attachment.metadata.namespace}`;
308+
let ownerAddress = attachment.attachment.metadata.tx_sender.address;
307309
// Is this a `name-transfer`? If so, look for the new owner in an `nft_transfer` event bundled in
308310
// the same transaction.
309311
if (attachment.attachment.metadata.op === 'name-transfer') {
310-
for (const txEvent of txEvents) {
312+
for (const eventItem of allEvents) {
311313
if (
312-
txEvent.type === CoreNodeEventType.NftTransferEvent &&
313-
txEvent.nft_transfer_event.asset_identifier === `${getBnsContractID(chainId)}::names`
314+
eventItem.txid === event.txid &&
315+
eventItem.type === CoreNodeEventType.NftTransferEvent &&
316+
eventItem.nft_transfer_event.asset_identifier === `${getBnsContractID(chainId)}::names` &&
317+
eventItem.nft_transfer_event.raw_value === bnsNameCV(fullName)
314318
) {
315-
name_address = txEvent.nft_transfer_event.recipient;
319+
ownerAddress = eventItem.nft_transfer_event.recipient;
316320
break;
317321
}
318322
}
319323
}
320324
const name: DbBnsName = {
321-
name: `${attachment.attachment.metadata.name}.${attachment.attachment.metadata.namespace}`,
325+
name: fullName,
322326
namespace_id: attachment.attachment.metadata.namespace,
323-
address: name_address,
327+
address: ownerAddress,
324328
// expire_block will be calculated upon DB insert based on the namespace's lifetime.
325329
expire_block: 0,
326330
registered_at: blockHeight,
@@ -329,6 +333,7 @@ export function parseNameFromContractEvent(
329333
zonefile: '',
330334
tx_id: event.txid,
331335
tx_index: tx.core_tx.tx_index,
336+
event_index: event.event_index,
332337
status: attachment.attachment.metadata.op,
333338
canonical: true,
334339
};

src/migrations/1608030374842_names.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export async function up(pgm: MigrationBuilder): Promise<void> {
5252
type: 'smallint',
5353
notNull: true,
5454
},
55+
event_index: 'integer',
5556
status: {
5657
type: 'string',
5758
notNull: false
@@ -89,10 +90,11 @@ export async function up(pgm: MigrationBuilder): Promise<void> {
8990
{ name: 'registered_at', sort: 'DESC' },
9091
{ name: 'microblock_sequence', sort: 'DESC' },
9192
{ name: 'tx_index', sort: 'DESC' },
93+
{ name: 'event_index', sort: 'DESC' }
9294
]);
9395
pgm.addConstraint(
9496
'names',
95-
'unique_name_tx_id_index_block_hash_microblock_hash',
96-
'UNIQUE(name, tx_id, index_block_hash, microblock_hash)'
97+
'unique_name_tx_id_index_block_hash_microblock_hash_event_index',
98+
'UNIQUE(name, tx_id, index_block_hash, microblock_hash, event_index)'
9799
);
98100
}

0 commit comments

Comments
 (0)