Skip to content

Commit bf2ef0a

Browse files
authored
fix: move /extended/v1/burn_block to /extended/v2/burn-blocks (#1772)
* fix: move burn block to v2 * fix: new file * fix: unused exports
1 parent 5015977 commit bf2ef0a

File tree

12 files changed

+242
-222
lines changed

12 files changed

+242
-222
lines changed

docs/openapi.yaml

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -612,14 +612,14 @@ paths:
612612
schema:
613613
$ref: ./api/microblocks/get-unanchored-txs.schema.json
614614

615-
/extended/v1/burn_block:
615+
/extended/v2/burn-blocks:
616616
get:
617-
summary: Get recent burn blocks
617+
summary: Get burn blocks
618618
description: |
619619
Retrieves a list of recent burn blocks
620620
tags:
621621
- Blocks
622-
operationId: get_burn_block_list
622+
operationId: get_burn_blocks
623623
parameters:
624624
- name: limit
625625
in: query
@@ -636,20 +636,6 @@ paths:
636636
schema:
637637
type: integer
638638
example: 42000
639-
- name: height
640-
in: query
641-
description: filter by burn block height
642-
required: false
643-
schema:
644-
type: integer
645-
example: 42000
646-
- name: hash
647-
in: query
648-
description: filter by burn block hash or the constant 'latest' to filter for the most recent burn block
649-
required: false
650-
schema:
651-
type: string
652-
example: "0x4839a8b01cfb39ffcc0d07d3db31e848d5adf5279d529ed5062300b9f353ff79"
653639
responses:
654640
200:
655641
description: List of burn blocks
@@ -660,6 +646,34 @@ paths:
660646
example:
661647
$ref: ./api/blocks/get-burn-blocks.example.json
662648

649+
/extended/v2/burn-blocks/{height_or_hash}:
650+
get:
651+
summary: Get burn block
652+
description: Retrieves a single burn block
653+
tags:
654+
- Blocks
655+
operationId: get_burn_block
656+
parameters:
657+
- name: height_or_hash
658+
in: path
659+
description: filter by burn block height, hash, or the constant `latest` to filter for the most recent burn block
660+
required: true
661+
schema:
662+
oneOf:
663+
- type: integer
664+
example: 42000
665+
- type: string
666+
example: "0x4839a8b01cfb39ffcc0d07d3db31e848d5adf5279d529ed5062300b9f353ff79"
667+
responses:
668+
200:
669+
description: Burn block
670+
content:
671+
application/json:
672+
schema:
673+
$ref: ./entities/blocks/burn-block.schema.json
674+
example:
675+
$ref: ./entities/blocks/burn-block.example.json
676+
663677
/extended/v2/blocks:
664678
get:
665679
summary: Get blocks

src/api/controllers/db-controller.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -538,35 +538,6 @@ export async function getMicroblockFromDataStore({
538538
};
539539
}
540540

541-
export async function getBurnBlocksFromDataStore(args: {
542-
db: PgStore;
543-
limit: number;
544-
offset: number;
545-
height: number | null;
546-
hash: 'latest' | string | null;
547-
}): Promise<{ total: number; results: BurnBlock[] }> {
548-
const query = await args.db.getBurnBlocks({
549-
limit: args.limit,
550-
offset: args.offset,
551-
height: args.height,
552-
hash: args.hash,
553-
});
554-
const results = query.results.map(r => {
555-
const burnBlock: BurnBlock = {
556-
burn_block_time: r.burn_block_time,
557-
burn_block_time_iso: unixEpochToIso(r.burn_block_time),
558-
burn_block_hash: r.burn_block_hash,
559-
burn_block_height: r.burn_block_height,
560-
stacks_blocks: r.stacks_blocks,
561-
};
562-
return burnBlock;
563-
});
564-
return {
565-
total: query.total,
566-
results,
567-
};
568-
}
569-
570541
export async function getMicroblocksFromDataStore(args: {
571542
db: PgStore;
572543
limit: number;

src/api/init.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { logger, loggerMiddleware } from '../logger';
4545
import { SERVER_VERSION, isPgConnectionError, isProdEnv, waiter } from '@hirosystems/api-toolkit';
4646
import { createV2BlocksRouter } from './routes/v2/blocks';
4747
import { getReqQuery } from './query-helpers';
48-
import { createBurnBlockRouter } from './routes/burn-block';
48+
import { createV2BurnBlocksRouter } from './routes/v2/burn-blocks';
4949

5050
export interface ApiServer {
5151
expressApp: express.Express;
@@ -199,7 +199,6 @@ export async function startApiServer(opts: {
199199
v1.use('/status', createStatusRouter(datastore));
200200
v1.use('/fee_rate', createFeeRateRouter(datastore));
201201
v1.use('/tokens', createTokenRouter(datastore));
202-
v1.use('/burn_block', createBurnBlockRouter(datastore));
203202

204203
// These could be defined in one route but a url reporting library breaks with regex in middleware paths
205204
v1.use('/pox2', createPoxEventsRouter(datastore, 'pox2'));
@@ -227,6 +226,7 @@ export async function startApiServer(opts: {
227226
(() => {
228227
const v2 = express.Router();
229228
v2.use('/blocks', createV2BlocksRouter(datastore));
229+
v2.use('/burn-blocks', createV2BurnBlocksRouter(datastore));
230230
return v2;
231231
})()
232232
);

src/api/query-helpers.ts

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -111,53 +111,6 @@ export function getBlockParams(
111111
}
112112
}
113113

114-
/**
115-
* Parses a block hash value from a given request query param.
116-
* If an error is encountered while parsing the param then a 400 response with an error message is sent and the function throws.
117-
* @param queryParamName - name of the query param
118-
* @param paramRequired - if true then the function will throw and return a 400 if the param is missing, if false then the function will return null if the param is missing
119-
*/
120-
export function getBlockHashQueryParam<TRequired extends boolean>(
121-
queryParamName: string,
122-
paramRequired: TRequired,
123-
req: Request,
124-
res: Response,
125-
next: NextFunction
126-
): TRequired extends true ? string | never : string | null {
127-
if (!(queryParamName in req.query)) {
128-
if (paramRequired) {
129-
handleBadRequest(
130-
res,
131-
next,
132-
`Request is missing required "${queryParamName}" query parameter`
133-
);
134-
} else {
135-
return null as TRequired extends true ? string : string | null;
136-
}
137-
}
138-
const hashParamVal = req.query[queryParamName];
139-
if (typeof hashParamVal !== 'string') {
140-
handleBadRequest(
141-
res,
142-
next,
143-
`Unexpected type for block hash query parameter: ${JSON.stringify(hashParamVal)}`
144-
);
145-
}
146-
147-
// Extract the hash part, ignoring '0x' if present
148-
const match = hashParamVal.match(/^(0x)?([a-fA-F0-9]{64})$/i);
149-
if (!match) {
150-
handleBadRequest(
151-
res,
152-
next,
153-
"Invalid hash string. Ensure it is 64 hexadecimal characters long, with an optional '0x' prefix"
154-
);
155-
}
156-
157-
// Normalize the string
158-
return '0x' + match[2].toLowerCase();
159-
}
160-
161114
/**
162115
* Parses a block height value from a given request query param.
163116
* If an error is encountered while parsing the param then a 400 response with an error message is sent and the function throws.

src/api/routes/burn-block.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/api/routes/v2/blocks.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '../../../api/controllers/cache-controller';
77
import { asyncHandler } from '../../async-handler';
88
import { NakamotoBlockListResponse } from 'docs/generated';
9-
import { BlockLimitParam, BlocksQueryParams, CompiledBlocksQueryParams } from './schemas';
9+
import { BlockLimitParamSchema, BlocksQueryParams, CompiledBlocksQueryParams } from './schemas';
1010
import { parseDbNakamotoBlock, validRequestQuery } from './helpers';
1111

1212
export function createV2BlocksRouter(db: PgStore): express.Router {
@@ -20,10 +20,10 @@ export function createV2BlocksRouter(db: PgStore): express.Router {
2020
if (!validRequestQuery(req, res, CompiledBlocksQueryParams)) return;
2121
const query = req.query as BlocksQueryParams;
2222

23-
const { results, total } = await db.getV2Blocks(query);
23+
const { limit, offset, results, total } = await db.getV2Blocks(query);
2424
const response: NakamotoBlockListResponse = {
25-
limit: query.limit ?? BlockLimitParam.default,
26-
offset: query.offset ?? 0,
25+
limit,
26+
offset,
2727
total,
2828
results: results.map(r => parseDbNakamotoBlock(r)),
2929
};

src/api/routes/v2/burn-blocks.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as express from 'express';
2+
import { BurnBlockListResponse } from '@stacks/stacks-blockchain-api-types';
3+
import { getETagCacheHandler, setETagCacheHeaders } from '../../controllers/cache-controller';
4+
import { asyncHandler } from '../../async-handler';
5+
import { PgStore } from '../../../datastore/pg-store';
6+
import { parseDbBurnBlock, validRequestParams, validRequestQuery } from './helpers';
7+
import {
8+
BlockPaginationQueryParams,
9+
BurnBlockParams,
10+
CompiledBlockPaginationParams,
11+
CompiledBurnBlockParams,
12+
} from './schemas';
13+
14+
export function createV2BurnBlocksRouter(db: PgStore): express.Router {
15+
const router = express.Router();
16+
const cacheHandler = getETagCacheHandler(db);
17+
18+
router.get(
19+
'/',
20+
cacheHandler,
21+
asyncHandler(async (req, res) => {
22+
if (!validRequestQuery(req, res, CompiledBlockPaginationParams)) return;
23+
const query = req.query as BlockPaginationQueryParams;
24+
25+
const { limit, offset, results, total } = await db.getBurnBlocks(query);
26+
const response: BurnBlockListResponse = {
27+
limit,
28+
offset,
29+
total,
30+
results: results.map(r => parseDbBurnBlock(r)),
31+
};
32+
setETagCacheHeaders(res);
33+
res.json(response);
34+
})
35+
);
36+
37+
router.get(
38+
'/:height_or_hash',
39+
cacheHandler,
40+
asyncHandler(async (req, res) => {
41+
if (!validRequestParams(req, res, CompiledBurnBlockParams)) return;
42+
const params = req.params as BurnBlockParams;
43+
44+
const block = await db.getBurnBlock(params);
45+
if (!block) {
46+
res.status(404).json({ errors: 'Not found' });
47+
return;
48+
}
49+
setETagCacheHeaders(res);
50+
res.json(parseDbBurnBlock(block));
51+
})
52+
);
53+
54+
return router;
55+
}

src/api/routes/v2/helpers.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { NakamotoBlock } from 'docs/generated';
2-
import { BlockWithTransactionIds } from '../../../datastore/common';
1+
import { BurnBlock, NakamotoBlock } from 'docs/generated';
2+
import { BlockWithTransactionIds, DbBurnBlock } from '../../../datastore/common';
33
import { unixEpochToIso } from '../../../helpers';
44
import { TypeCheck } from '@sinclair/typebox/compiler';
55
import { Request, Response } from 'express';
@@ -25,6 +25,26 @@ export function validRequestQuery(
2525
return true;
2626
}
2727

28+
/**
29+
* Validate request path parameters with a TypeBox compiled schema
30+
* @param req - Request
31+
* @param res - Response
32+
* @param compiledType - TypeBox compiled schema
33+
* @returns boolean
34+
*/
35+
export function validRequestParams(
36+
req: Request,
37+
res: Response,
38+
compiledType: TypeCheck<TSchema>
39+
): boolean {
40+
if (!compiledType.Check(req.params)) {
41+
// TODO: Return a more user-friendly error
42+
res.status(400).json({ errors: [...compiledType.Errors(req.params)] });
43+
return false;
44+
}
45+
return true;
46+
}
47+
2848
export function parseDbNakamotoBlock(block: BlockWithTransactionIds): NakamotoBlock {
2949
const apiBlock: NakamotoBlock = {
3050
canonical: block.canonical,
@@ -47,3 +67,14 @@ export function parseDbNakamotoBlock(block: BlockWithTransactionIds): NakamotoBl
4767
};
4868
return apiBlock;
4969
}
70+
71+
export function parseDbBurnBlock(block: DbBurnBlock): BurnBlock {
72+
const burnBlock: BurnBlock = {
73+
burn_block_time: block.burn_block_time,
74+
burn_block_time_iso: unixEpochToIso(block.burn_block_time),
75+
burn_block_hash: block.burn_block_hash,
76+
burn_block_height: block.burn_block_height,
77+
stacks_blocks: block.stacks_blocks,
78+
};
79+
return burnBlock;
80+
}

0 commit comments

Comments
 (0)