Skip to content

Commit 095c4fc

Browse files
authored
fix: stop resolving revoked BNS names (#1519)
* fix: get name * fix: zonefile queries * fix: subdomains list in name * fix: zonefile by hash * fix: last filters * test: revoke namespace list * fix: revoke integration test * fix: datastore tests
1 parent 91d79bc commit 095c4fc

File tree

11 files changed

+322
-44
lines changed

11 files changed

+322
-44
lines changed

src/api/routes/bns/names.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function createBnsNamesRouter(db: PgStore, chainId: ChainID): express.Rou
3333
name: name,
3434
zoneFileHash: zoneFileHash,
3535
includeUnanchored,
36+
chainId,
3637
});
3738
if (zonefile.found) {
3839
setETagCacheHeaders(res);
@@ -49,7 +50,7 @@ export function createBnsNamesRouter(db: PgStore, chainId: ChainID): express.Rou
4950
asyncHandler(async (req, res, next) => {
5051
const { name } = req.params;
5152
const includeUnanchored = isUnanchoredRequest(req, res, next);
52-
const subdomainsList = await db.getSubdomainsListInName({ name, includeUnanchored });
53+
const subdomainsList = await db.getSubdomainsListInName({ name, includeUnanchored, chainId });
5354
setETagCacheHeaders(res);
5455
res.json(subdomainsList.results);
5556
})
@@ -61,7 +62,7 @@ export function createBnsNamesRouter(db: PgStore, chainId: ChainID): express.Rou
6162
asyncHandler(async (req, res, next) => {
6263
const { name } = req.params;
6364
const includeUnanchored = isUnanchoredRequest(req, res, next);
64-
const zonefile = await db.getLatestZoneFile({ name: name, includeUnanchored });
65+
const zonefile = await db.getLatestZoneFile({ name: name, includeUnanchored, chainId });
6566
if (zonefile.found) {
6667
setETagCacheHeaders(res);
6768
res.json(zonefile.result);
@@ -102,6 +103,7 @@ export function createBnsNamesRouter(db: PgStore, chainId: ChainID): express.Rou
102103
const subdomainQuery = await db.getSubdomain({
103104
subdomain: name,
104105
includeUnanchored,
106+
chainId,
105107
});
106108
if (!subdomainQuery.found) {
107109
const namePart = name.split('.').slice(1).join('.');

src/datastore/pg-store.ts

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
bnsHexValueToName,
1313
bnsNameCV,
1414
getBnsSmartContractId,
15+
bnsNameFromSubdomain,
1516
} from '../helpers';
1617
import { PgStoreEventEmitter } from './pg-store-event-emitter';
1718
import {
@@ -3219,14 +3220,17 @@ export class PgStore {
32193220
const queryResult = await this.sqlTransaction(async sql => {
32203221
const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored });
32213222
return await sql<{ name: string }[]>`
3222-
SELECT DISTINCT ON (name) name
3223-
FROM names
3224-
WHERE namespace_id = ${namespace}
3225-
AND registered_at <= ${maxBlockHeight}
3226-
AND canonical = true AND microblock_canonical = true
3227-
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
3228-
LIMIT 100
3229-
OFFSET ${offset}
3223+
SELECT name FROM (
3224+
SELECT DISTINCT ON (name) name, status
3225+
FROM names
3226+
WHERE namespace_id = ${namespace}
3227+
AND registered_at <= ${maxBlockHeight}
3228+
AND canonical = true AND microblock_canonical = true
3229+
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
3230+
LIMIT 100
3231+
OFFSET ${offset}
3232+
) AS name_status
3233+
WHERE status <> 'name-revoke'
32303234
`;
32313235
});
32323236
const results = queryResult.map(r => r.name);
@@ -3290,7 +3294,7 @@ export class PgStore {
32903294
n.event_index DESC
32913295
LIMIT 1
32923296
`;
3293-
if (nameZonefile.length === 0) {
3297+
if (nameZonefile.length === 0 || nameZonefile[0].status === 'name-revoke') {
32943298
return;
32953299
}
32963300
return nameZonefile[0];
@@ -3312,12 +3316,21 @@ export class PgStore {
33123316
name: string;
33133317
zoneFileHash: string;
33143318
includeUnanchored: boolean;
3319+
chainId: ChainID;
33153320
}): Promise<FoundOrNot<DbBnsZoneFile>> {
33163321
const queryResult = await this.sqlTransaction(async sql => {
33173322
const maxBlockHeight = await this.getMaxBlockHeight(sql, {
33183323
includeUnanchored: args.includeUnanchored,
33193324
});
33203325
const validZonefileHash = validateZonefileHash(args.zoneFileHash);
3326+
const parentNameStatus = await this.getName({
3327+
name: bnsNameFromSubdomain(args.name),
3328+
includeUnanchored: args.includeUnanchored,
3329+
chainId: args.chainId,
3330+
});
3331+
if (!parentNameStatus.found) {
3332+
return [] as { zonefile: string }[];
3333+
}
33213334
// Depending on the kind of name we got, use the correct table to pivot on canonical chain
33223335
// state to get the zonefile. We can't pivot on the `txs` table because some names/subdomains
33233336
// were imported from Stacks v1 and they don't have an associated tx.
@@ -3365,12 +3378,22 @@ export class PgStore {
33653378
async getLatestZoneFile({
33663379
name,
33673380
includeUnanchored,
3381+
chainId,
33683382
}: {
33693383
name: string;
33703384
includeUnanchored: boolean;
3385+
chainId: ChainID;
33713386
}): Promise<FoundOrNot<DbBnsZoneFile>> {
33723387
const queryResult = await this.sqlTransaction(async sql => {
33733388
const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored });
3389+
const parentNameStatus = await this.getName({
3390+
name: bnsNameFromSubdomain(name),
3391+
includeUnanchored,
3392+
chainId,
3393+
});
3394+
if (!parentNameStatus.found) {
3395+
return [] as { zonefile: string }[];
3396+
}
33743397
// Depending on the kind of name we got, use the correct table to pivot on canonical chain
33753398
// state to get the zonefile. We can't pivot on the `txs` table because some names/subdomains
33763399
// were imported from Stacks v1 and they don't have an associated tx.
@@ -3424,8 +3447,8 @@ export class PgStore {
34243447
}): Promise<FoundOrNot<string[]>> {
34253448
const queryResult = await this.sqlTransaction(async sql => {
34263449
const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored });
3427-
// 1. Get subdomains owned by this address.
3428-
// These don't produce NFT events so we have to look directly at the `subdomains` table.
3450+
// 1. Get subdomains owned by this address. These don't produce NFT events so we have to look
3451+
// directly at the `subdomains` table.
34293452
const subdomainsQuery = await sql<{ fully_qualified_subdomain: string }[]>`
34303453
WITH addr_subdomains AS (
34313454
SELECT DISTINCT ON (fully_qualified_subdomain)
@@ -3449,9 +3472,9 @@ export class PgStore {
34493472
ORDER BY
34503473
fully_qualified_subdomain
34513474
`;
3452-
// 2. Get names owned by this address which were imported from Blockstack v1.
3453-
// These also don't have an associated NFT event so we have to look directly at the `names` table,
3454-
// however, we'll also check if any of these names are still owned by the same user.
3475+
// 2. Get names owned by this address which were imported from Blockstack v1. These also don't
3476+
// have an associated NFT event so we have to look directly at the `names` table, however,
3477+
// we'll also check if any of these names are still owned by the same user.
34553478
const importedNamesQuery = await sql<{ name: string }[]>`
34563479
SELECT
34573480
name
@@ -3484,7 +3507,20 @@ export class PgStore {
34843507
...importedNamesQuery.map(i => i.name).filter(i => !oldImportedNames.includes(i)),
34853508
...nftNamesQuery.map(i => bnsHexValueToName(i.value)),
34863509
]);
3487-
return Array.from(results.values()).sort();
3510+
// 4. Now that we've acquired all names owned by this address, filter out the ones that are
3511+
// revoked.
3512+
const validatedResults: string[] = [];
3513+
for (const result of results) {
3514+
const valid = await this.getName({
3515+
name: bnsNameFromSubdomain(result),
3516+
includeUnanchored,
3517+
chainId,
3518+
});
3519+
if (valid.found) {
3520+
validatedResults.push(result);
3521+
}
3522+
}
3523+
return validatedResults.sort();
34883524
});
34893525
if (queryResult.length > 0) {
34903526
return {
@@ -3502,12 +3538,18 @@ export class PgStore {
35023538
async getSubdomainsListInName({
35033539
name,
35043540
includeUnanchored,
3541+
chainId,
35053542
}: {
35063543
name: string;
35073544
includeUnanchored: boolean;
3545+
chainId: ChainID;
35083546
}): Promise<{ results: string[] }> {
35093547
const queryResult = await this.sqlTransaction(async sql => {
35103548
const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored });
3549+
const status = await this.getName({ name, includeUnanchored, chainId });
3550+
if (!status.found) {
3551+
return [] as { fully_qualified_subdomain: string }[];
3552+
}
35113553
return await sql<{ fully_qualified_subdomain: string }[]>`
35123554
SELECT DISTINCT ON (fully_qualified_subdomain) fully_qualified_subdomain
35133555
FROM subdomains
@@ -3522,6 +3564,9 @@ export class PgStore {
35223564
return { results };
35233565
}
35243566

3567+
/**
3568+
* @deprecated This function is only used for testing.
3569+
*/
35253570
async getSubdomainsList({
35263571
page,
35273572
includeUnanchored,
@@ -3551,11 +3596,15 @@ export class PgStore {
35513596
const queryResult = await this.sqlTransaction(async sql => {
35523597
const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored });
35533598
return await sql<{ name: string }[]>`
3554-
SELECT DISTINCT ON (name) name
3555-
FROM names
3556-
WHERE canonical = true AND microblock_canonical = true
3557-
AND registered_at <= ${maxBlockHeight}
3558-
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
3599+
WITH name_results AS (
3600+
SELECT DISTINCT ON (name) name, status
3601+
FROM names
3602+
WHERE canonical = true AND microblock_canonical = true
3603+
AND registered_at <= ${maxBlockHeight}
3604+
ORDER BY name, registered_at DESC, microblock_sequence DESC, tx_index DESC, event_index DESC
3605+
)
3606+
SELECT name FROM name_results
3607+
WHERE status <> 'name-revoke'
35593608
LIMIT 100
35603609
OFFSET ${offset}
35613610
`;
@@ -3567,12 +3616,22 @@ export class PgStore {
35673616
async getSubdomain({
35683617
subdomain,
35693618
includeUnanchored,
3619+
chainId,
35703620
}: {
35713621
subdomain: string;
35723622
includeUnanchored: boolean;
3623+
chainId: ChainID;
35733624
}): Promise<FoundOrNot<DbBnsSubdomain & { index_block_hash: string }>> {
35743625
const queryResult = await this.sqlTransaction(async sql => {
35753626
const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored });
3627+
const status = await this.getName({
3628+
name: bnsNameFromSubdomain(subdomain),
3629+
includeUnanchored,
3630+
chainId,
3631+
});
3632+
if (!status.found) {
3633+
return [] as (DbBnsSubdomain & { tx_id: string; index_block_hash: string })[];
3634+
}
35763635
return await sql<(DbBnsSubdomain & { tx_id: string; index_block_hash: string })[]>`
35773636
SELECT s.*, z.zonefile
35783637
FROM subdomains AS s

src/helpers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,15 @@ export function bnsHexValueToName(hex: string): string {
957957
return `${name.buffer.toString('utf8')}.${namespace.buffer.toString('utf8')}`;
958958
}
959959

960+
/**
961+
* Returns the parent BNS name from a subdomain.
962+
* @param subdomain - Fully qualified subdomain
963+
* @returns BNS name
964+
*/
965+
export function bnsNameFromSubdomain(subdomain: string): string {
966+
return subdomain.split('.').slice(-2).join('.');
967+
}
968+
960969
export function getBnsSmartContractId(chainId: ChainID): string {
961970
return chainId === ChainID.Mainnet
962971
? 'SP000000000000000000002Q6VF78.bns::names'

0 commit comments

Comments
 (0)