Skip to content

Commit 1f64818

Browse files
authored
feat!: optimize tables and improve canonical treatment of BNS data (#1287)
* refactor: re-order helper files * fix: new columns for zonefiles * feat: batch update subdomains * fix: optimize some queries * fix: api tests * fix: optimize more queries, add cache handlers * fix: some integration tests * fix: all integration tests * fix: v1 import test individually * fix: move v1 import tests to separate file * fix: start adding on conflict to subdomains * fix: add uniqueness to names, subdomains and namespaces * fix: remove extra index * refactor: remove unused tx function * fix: unused exports * feat: echo block height while doing replays * refactor: separate batch updates * fix: calculate expire_block for a name based on namespace lifetime * fix: test strict equal * fix: remove secondary temporary table for event import * fix: test sorting * fix: detect namespaces readyed by third party contracts * fix: bigint for namespaces * refactor: move bns helper tests to bns suite * chore: make event test less verbose * chore: temporarily disable migrations on boot * chore: add verbose logs to migration * chore: make migration verbosity depend on log level * fix: detect names from print event * test: re-orgd attachments * test: duplicate zonefile updates
1 parent a10ac03 commit 1f64818

25 files changed

+1400
-721
lines changed

src/api/routes/bns/addresses.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ import { asyncHandler } from '../../async-handler';
33
import { DataStore } from '../../../datastore/common';
44
import { isUnanchoredRequest } from '../../query-helpers';
55
import { ChainID } from '@stacks/transactions';
6+
import {
7+
getETagCacheHandler,
8+
setETagCacheHeaders,
9+
} from '../../../api/controllers/cache-controller';
610

711
const SUPPORTED_BLOCKCHAINS = ['stacks'];
812

913
export function createBnsAddressesRouter(db: DataStore, chainId: ChainID): express.Router {
1014
const router = express.Router();
15+
const cacheHandler = getETagCacheHandler(db);
16+
1117
router.get(
1218
'/:blockchain/:address',
19+
cacheHandler,
1320
asyncHandler(async (req, res, next) => {
1421
// Retrieves a list of names owned by the address provided.
1522
const { blockchain, address } = req.params;
@@ -23,6 +30,7 @@ export function createBnsAddressesRouter(db: DataStore, chainId: ChainID): expre
2330
includeUnanchored,
2431
chainId,
2532
});
33+
setETagCacheHeaders(res);
2634
if (namesByAddress.found) {
2735
res.json({ names: namesByAddress.result });
2836
} else {

src/api/routes/bns/names.ts

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,92 +3,85 @@ import { asyncHandler } from '../../async-handler';
33
import { DataStore } from '../../../datastore/common';
44
import { parsePagingQueryInput } from '../../../api/pagination';
55
import { isUnanchoredRequest } from '../../query-helpers';
6-
import { bnsBlockchain, BnsErrors } from '../../../bns-constants';
6+
import { bnsBlockchain, BnsErrors } from '../../../event-stream/bns/bns-constants';
77
import { BnsGetNameInfoResponse } from '@stacks/stacks-blockchain-api-types';
88
import { ChainID } from '@stacks/transactions';
9+
import {
10+
getETagCacheHandler,
11+
setETagCacheHeaders,
12+
} from '../../../api/controllers/cache-controller';
913

1014
export function createBnsNamesRouter(db: DataStore, chainId: ChainID): express.Router {
1115
const router = express.Router();
16+
const cacheHandler = getETagCacheHandler(db);
1217

1318
router.get(
1419
'/:name/zonefile/:zoneFileHash',
20+
cacheHandler,
1521
asyncHandler(async (req, res, next) => {
16-
// Fetches the historical zonefile specified by the username and zone hash.
1722
const { name, zoneFileHash } = req.params;
1823
const includeUnanchored = isUnanchoredRequest(req, res, next);
19-
let nameFound = false;
20-
const nameQuery = await db.getName({ name: name, includeUnanchored, chainId: chainId });
21-
nameFound = nameQuery.found;
22-
if (!nameFound) {
23-
const subdomainQuery = await db.getSubdomain({ subdomain: name, includeUnanchored });
24-
nameFound = subdomainQuery.found;
25-
}
26-
27-
if (nameFound) {
28-
const zonefile = await db.getHistoricalZoneFile({ name: name, zoneFileHash: zoneFileHash });
29-
if (zonefile.found) {
30-
res.json(zonefile.result);
31-
} else {
32-
res.status(404).json({ error: 'No such zonefile' });
33-
}
24+
const zonefile = await db.getHistoricalZoneFile({
25+
name: name,
26+
zoneFileHash: zoneFileHash,
27+
includeUnanchored,
28+
});
29+
if (zonefile.found) {
30+
setETagCacheHeaders(res);
31+
res.json(zonefile.result);
3432
} else {
35-
res.status(400).json({ error: 'Invalid name or subdomain' });
33+
res.status(404).json({ error: 'No such name or zonefile' });
3634
}
3735
})
3836
);
3937

4038
router.get(
4139
'/:name/subdomains',
40+
cacheHandler,
4241
asyncHandler(async (req, res, next) => {
4342
const { name } = req.params;
4443
const includeUnanchored = isUnanchoredRequest(req, res, next);
4544
const subdomainsList = await db.getSubdomainsListInName({ name, includeUnanchored });
45+
setETagCacheHeaders(res);
4646
res.json(subdomainsList.results);
4747
})
4848
);
4949

5050
router.get(
5151
'/:name/zonefile',
52+
cacheHandler,
5253
asyncHandler(async (req, res, next) => {
53-
// Fetch a user’s raw zone file. This only works for RFC-compliant zone files. This method returns an error for names that have non-standard zone files.
5454
const { name } = req.params;
5555
const includeUnanchored = isUnanchoredRequest(req, res, next);
56-
let nameFound = false;
57-
const nameQuery = await db.getName({ name: name, includeUnanchored, chainId: chainId });
58-
nameFound = nameQuery.found;
59-
if (!nameFound) {
60-
const subdomainQuery = await db.getSubdomain({ subdomain: name, includeUnanchored });
61-
nameFound = subdomainQuery.found;
62-
}
63-
64-
if (nameFound) {
65-
const zonefile = await db.getLatestZoneFile({ name: name, includeUnanchored });
66-
if (zonefile.found) {
67-
res.json(zonefile.result);
68-
} else {
69-
res.status(404).json({ error: 'No zone file for name' });
70-
}
56+
const zonefile = await db.getLatestZoneFile({ name: name, includeUnanchored });
57+
if (zonefile.found) {
58+
setETagCacheHeaders(res);
59+
res.json(zonefile.result);
7160
} else {
72-
res.status(400).json({ error: 'Invalid name or subdomain' });
61+
res.status(404).json({ error: 'No such name or zonefile does not exist' });
7362
}
7463
})
7564
);
7665

7766
router.get(
7867
'/',
68+
cacheHandler,
7969
asyncHandler(async (req, res, next) => {
8070
const page = parsePagingQueryInput(req.query.page ?? 0);
8171
const includeUnanchored = isUnanchoredRequest(req, res, next);
8272
const { results } = await db.getNamesList({ page, includeUnanchored });
8373
if (results.length === 0 && req.query.page) {
8474
res.status(400).json(BnsErrors.InvalidPageNumber);
75+
} else {
76+
setETagCacheHeaders(res);
77+
res.json(results);
8578
}
86-
res.json(results);
8779
})
8880
);
8981

9082
router.get(
9183
'/:name',
84+
cacheHandler,
9285
asyncHandler(async (req, res, next) => {
9386
const { name } = req.params;
9487
const includeUnanchored = isUnanchoredRequest(req, res, next);
@@ -149,6 +142,7 @@ export function createBnsNamesRouter(db: DataStore, chainId: ChainID): express.R
149142
const response = Object.fromEntries(
150143
Object.entries(nameInfoResponse).filter(([_, v]) => v != null)
151144
);
145+
setETagCacheHeaders(res);
152146
res.json(response);
153147
})
154148
);

src/api/routes/bns/namespaces.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,35 @@ import { asyncHandler } from '../../async-handler';
33
import { DataStore } from '../../../datastore/common';
44
import { parsePagingQueryInput } from '../../../api/pagination';
55
import { isUnanchoredRequest } from '../../query-helpers';
6-
import { BnsErrors } from '../../../bns-constants';
6+
import { BnsErrors } from '../../../event-stream/bns/bns-constants';
77
import { BnsGetAllNamespacesResponse } from '@stacks/stacks-blockchain-api-types';
8+
import {
9+
getETagCacheHandler,
10+
setETagCacheHeaders,
11+
} from '../../../api/controllers/cache-controller';
812

913
export function createBnsNamespacesRouter(db: DataStore): express.Router {
1014
const router = express.Router();
15+
const cacheHandler = getETagCacheHandler(db);
1116

1217
router.get(
1318
'/',
19+
cacheHandler,
1420
asyncHandler(async (req, res, next) => {
1521
const includeUnanchored = isUnanchoredRequest(req, res, next);
1622
const { results } = await db.getNamespaceList({ includeUnanchored });
1723
const response: BnsGetAllNamespacesResponse = {
1824
namespaces: results,
1925
};
26+
setETagCacheHeaders(res);
2027
res.json(response);
2128
return;
2229
})
2330
);
2431

2532
router.get(
2633
'/:tld/names',
34+
cacheHandler,
2735
asyncHandler(async (req, res, next) => {
2836
const { tld } = req.params;
2937
const page = parsePagingQueryInput(req.query.page ?? 0);
@@ -39,8 +47,10 @@ export function createBnsNamespacesRouter(db: DataStore): express.Router {
3947
});
4048
if (results.length === 0 && req.query.page) {
4149
res.status(400).json(BnsErrors.InvalidPageNumber);
50+
} else {
51+
setETagCacheHeaders(res);
52+
res.json(results);
4253
}
43-
res.json(results);
4454
}
4555
})
4656
);

src/api/routes/bns/pricing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import {
1414
listCV,
1515
ChainID,
1616
} from '@stacks/transactions';
17-
import { GetStacksNetwork, getBnsContractID } from './../../../bns-helpers';
1817
import {
1918
BnsGetNamePriceResponse,
2019
BnsGetNamespacePriceResponse,
2120
} from '@stacks/stacks-blockchain-api-types';
2221
import { isValidPrincipal, logger } from './../../../helpers';
22+
import { getBnsContractID, GetStacksNetwork } from '../../../event-stream/bns/bns-helpers';
2323

2424
export function createBnsPriceRouter(db: DataStore, chainId: ChainID): express.Router {
2525
const router = express.Router();

src/datastore/common.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,31 @@ export interface DataStoreTxEventData {
428428
namespaces: DbBnsNamespace[];
429429
}
430430

431+
export interface DataStoreAttachmentData {
432+
op: string;
433+
name: string;
434+
namespace: string;
435+
zonefile: string;
436+
zonefileHash: string;
437+
txId: string;
438+
indexBlockHash: string;
439+
blockHeight: number;
440+
}
441+
442+
export interface DataStoreSubdomainBlockData {
443+
index_block_hash: string;
444+
parent_index_block_hash: string;
445+
microblock_hash: string;
446+
microblock_sequence: number;
447+
microblock_canonical: boolean;
448+
}
449+
450+
export interface DataStoreAttachmentSubdomainData {
451+
attachment?: DataStoreAttachmentData;
452+
blockData?: DataStoreSubdomainBlockData;
453+
subdomains?: DbBnsSubdomain[];
454+
}
455+
431456
export interface DbSearchResult {
432457
entity_type: 'standard_address' | 'contract_address' | 'block_hash' | 'tx_id' | 'mempool_tx_id';
433458
entity_id: string;
@@ -472,6 +497,7 @@ export interface DbInboundStxTransfer {
472497
export interface DbBnsZoneFile {
473498
zonefile: string;
474499
}
500+
475501
export interface DbBnsNamespace {
476502
id?: number;
477503
namespace_id: string;
@@ -480,8 +506,8 @@ export interface DbBnsNamespace {
480506
reveal_block: number;
481507
ready_block: number;
482508
buckets: string;
483-
base: number;
484-
coeff: number;
509+
base: bigint;
510+
coeff: bigint;
485511
nonalpha_discount: number;
486512
no_vowel_discount: number;
487513
lifetime: number;
@@ -683,7 +709,6 @@ export interface DataStore extends DataStoreEventEmitter {
683709
limit: number;
684710
offset: number;
685711
}): Promise<{ results: DbMempoolTx[]; total: number }>;
686-
getTxStrict(args: { txId: string; indexBlockHash: string }): Promise<FoundOrNot<DbTx>>;
687712
getTx(args: { txId: string; includeUnanchored: boolean }): Promise<FoundOrNot<DbTx>>;
688713
getTxList(args: {
689714
limit: number;
@@ -744,7 +769,8 @@ export interface DataStore extends DataStoreEventEmitter {
744769

745770
updateMicroblocks(data: DataStoreMicroblockUpdateData): Promise<void>;
746771

747-
updateZoneContent(zonefile: string, zonefile_hash: string, tx_id: string): Promise<void>;
772+
updateAttachments(attachments: DataStoreAttachmentData[]): Promise<void>;
773+
748774
resolveBnsSubdomains(
749775
blockData: {
750776
index_block_hash: string;
@@ -948,6 +974,7 @@ export interface DataStore extends DataStoreEventEmitter {
948974
getHistoricalZoneFile(args: {
949975
name: string;
950976
zoneFileHash: string;
977+
includeUnanchored: boolean;
951978
}): Promise<FoundOrNot<DbBnsZoneFile>>;
952979
getLatestZoneFile(args: {
953980
name: string;

0 commit comments

Comments
 (0)