mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-01-12 22:43:34 +08:00
feat: add /extended/v2/blocks/:height_or_hash (#1774)
* docs: openapi * feat: get single block
This commit is contained in:
@@ -720,6 +720,35 @@ paths:
|
||||
$ref: ./api/blocks/get-nakamoto-blocks.schema.json
|
||||
example:
|
||||
$ref: ./api/blocks/get-nakamoto-blocks.example.json
|
||||
|
||||
/extended/v2/blocks/{height_or_hash}:
|
||||
get:
|
||||
summary: Get block
|
||||
description: |
|
||||
Retrieves a single block
|
||||
tags:
|
||||
- Blocks
|
||||
operationId: get_block
|
||||
parameters:
|
||||
- name: height_or_hash
|
||||
in: path
|
||||
description: filter by block height, hash, index block hash or the constant `latest` to filter for the most recent block
|
||||
required: true
|
||||
schema:
|
||||
oneOf:
|
||||
- type: integer
|
||||
example: 42000
|
||||
- type: string
|
||||
example: "0x4839a8b01cfb39ffcc0d07d3db31e848d5adf5279d529ed5062300b9f353ff79"
|
||||
responses:
|
||||
200:
|
||||
description: Block
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ./entities/blocks/nakamoto-block.schema.json
|
||||
example:
|
||||
$ref: ./entities/blocks/nakamoto-block.example.json
|
||||
|
||||
/extended/v1/block:
|
||||
get:
|
||||
@@ -769,8 +798,12 @@ paths:
|
||||
type: string
|
||||
example: "0x4839a8b01cfb39ffcc0d07d3db31e848d5adf5279d529ed5062300b9f353ff79"
|
||||
get:
|
||||
deprecated: true
|
||||
summary: Get block by hash
|
||||
description: Retrieves block details of a specific block for a given chain height. You can use the hash from your latest block ('get_block_list' API) to get your block details.
|
||||
description: |
|
||||
**NOTE:** This endpoint is deprecated in favor of [Get block](#operation/get_block).
|
||||
|
||||
Retrieves block details of a specific block for a given chain height. You can use the hash from your latest block ('get_block_list' API) to get your block details.
|
||||
tags:
|
||||
- Blocks
|
||||
operationId: get_block_by_hash
|
||||
@@ -799,8 +832,12 @@ paths:
|
||||
type: number
|
||||
example: 10000
|
||||
get:
|
||||
deprecated: true
|
||||
summary: Get block by height
|
||||
description: Retrieves block details of a specific block at a given block height
|
||||
description: |
|
||||
**NOTE:** This endpoint is deprecated in favor of [Get block](#operation/get_block).
|
||||
|
||||
Retrieves block details of a specific block at a given block height
|
||||
tags:
|
||||
- Blocks
|
||||
operationId: get_block_by_height
|
||||
|
||||
@@ -6,8 +6,13 @@ import {
|
||||
} from '../../../api/controllers/cache-controller';
|
||||
import { asyncHandler } from '../../async-handler';
|
||||
import { NakamotoBlockListResponse } from 'docs/generated';
|
||||
import { BlockLimitParamSchema, BlocksQueryParams, CompiledBlocksQueryParams } from './schemas';
|
||||
import { parseDbNakamotoBlock, validRequestQuery } from './helpers';
|
||||
import {
|
||||
BlocksQueryParams,
|
||||
BurnBlockParams,
|
||||
CompiledBlocksQueryParams,
|
||||
CompiledBurnBlockParams,
|
||||
} from './schemas';
|
||||
import { parseDbNakamotoBlock, validRequestParams, validRequestQuery } from './helpers';
|
||||
|
||||
export function createV2BlocksRouter(db: PgStore): express.Router {
|
||||
const router = express.Router();
|
||||
@@ -31,5 +36,23 @@ export function createV2BlocksRouter(db: PgStore): express.Router {
|
||||
res.json(response);
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:height_or_hash',
|
||||
cacheHandler,
|
||||
asyncHandler(async (req, res) => {
|
||||
if (!validRequestParams(req, res, CompiledBurnBlockParams)) return;
|
||||
const params = req.params as BurnBlockParams;
|
||||
|
||||
const block = await db.getV2Block(params);
|
||||
if (!block) {
|
||||
res.status(404).json({ errors: 'Not found' });
|
||||
return;
|
||||
}
|
||||
setETagCacheHeaders(res);
|
||||
res.json(parseDbNakamotoBlock(block));
|
||||
})
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -465,6 +465,7 @@ export class PgStore extends BasePgStore {
|
||||
/**
|
||||
* Returns Block information with metadata, including accepted and streamed microblocks hash
|
||||
* @returns `BlocksWithMetadata` object including list of Blocks with metadata and total count.
|
||||
* @deprecated use `getV2Blocks`
|
||||
*/
|
||||
async getBlocksWithMetadata({
|
||||
limit,
|
||||
@@ -596,14 +597,7 @@ export class PgStore extends BasePgStore {
|
||||
: undefined;
|
||||
|
||||
// Obtain blocks and transaction counts in the same query.
|
||||
const blocksQuery = await sql<
|
||||
(BlockQueryResult & {
|
||||
tx_ids: string;
|
||||
microblocks_accepted: string;
|
||||
microblocks_streamed: string;
|
||||
total: number;
|
||||
})[]
|
||||
>`
|
||||
const blocksQuery = await sql<(BlockQueryResult & { tx_ids: string; total: number })[]>`
|
||||
WITH block_count AS (
|
||||
${
|
||||
'burn_block_hash' in args
|
||||
@@ -656,6 +650,39 @@ export class PgStore extends BasePgStore {
|
||||
});
|
||||
}
|
||||
|
||||
async getV2Block(args: BurnBlockParams): Promise<BlockWithTransactionIds | undefined> {
|
||||
return await this.sqlTransaction(async sql => {
|
||||
const filter =
|
||||
args.height_or_hash === 'latest'
|
||||
? sql`index_block_hash = (SELECT index_block_hash FROM blocks WHERE canonical = TRUE ORDER BY block_height DESC LIMIT 1)`
|
||||
: CompiledBurnBlockHashParam.Check(args.height_or_hash)
|
||||
? sql`(
|
||||
block_hash = ${normalizeHashString(args.height_or_hash)}
|
||||
OR index_block_hash = ${normalizeHashString(args.height_or_hash)}
|
||||
)`
|
||||
: sql`block_height = ${args.height_or_hash}`;
|
||||
const blockQuery = await sql<(BlockQueryResult & { tx_ids: string })[]>`
|
||||
SELECT
|
||||
${sql(BLOCK_COLUMNS)},
|
||||
(
|
||||
SELECT STRING_AGG(tx_id,',')
|
||||
FROM txs
|
||||
WHERE index_block_hash = blocks.index_block_hash
|
||||
AND canonical = true
|
||||
AND microblock_canonical = true
|
||||
) AS tx_ids
|
||||
FROM blocks
|
||||
WHERE canonical = true AND ${filter}
|
||||
LIMIT 1
|
||||
`;
|
||||
if (blockQuery.count > 0)
|
||||
return {
|
||||
...parseBlockQueryResult(blockQuery[0]),
|
||||
tx_ids: blockQuery[0].tx_ids ? blockQuery[0].tx_ids.split(',') : [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getBlockTxs(indexBlockHash: string) {
|
||||
const result = await this.sql<{ tx_id: string; tx_index: number }[]>`
|
||||
SELECT tx_id, tx_index
|
||||
|
||||
@@ -731,4 +731,72 @@ describe('block tests', () => {
|
||||
fetch = await supertest(api.server).get(`/extended/v2/blocks?burn_block_hash=testvalue`);
|
||||
expect(fetch.status).toBe(400);
|
||||
});
|
||||
|
||||
test('blocks v2 retrieved by hash or height', async () => {
|
||||
for (let i = 1; i < 6; i++) {
|
||||
const block = new TestBlockBuilder({
|
||||
block_height: i,
|
||||
block_hash: `0x000000000000000000000000000000000000000000000000000000000000000${i}`,
|
||||
index_block_hash: `0x000000000000000000000000000000000000000000000000000000000000011${i}`,
|
||||
parent_index_block_hash: `0x000000000000000000000000000000000000000000000000000000000000011${
|
||||
i - 1
|
||||
}`,
|
||||
parent_block_hash: `0x000000000000000000000000000000000000000000000000000000000000000${
|
||||
i - 1
|
||||
}`,
|
||||
burn_block_height: 700000,
|
||||
burn_block_hash: '0x00000000000000000001e2ee7f0c6bd5361b5e7afd76156ca7d6f524ee5ca3d8',
|
||||
})
|
||||
.addTx({ tx_id: `0x000${i}` })
|
||||
.build();
|
||||
await db.update(block);
|
||||
}
|
||||
|
||||
// Get latest
|
||||
const block5 = {
|
||||
burn_block_hash: '0x00000000000000000001e2ee7f0c6bd5361b5e7afd76156ca7d6f524ee5ca3d8',
|
||||
burn_block_height: 700000,
|
||||
burn_block_time: 94869286,
|
||||
burn_block_time_iso: '1973-01-03T00:34:46.000Z',
|
||||
canonical: true,
|
||||
execution_cost_read_count: 0,
|
||||
execution_cost_read_length: 0,
|
||||
execution_cost_runtime: 0,
|
||||
execution_cost_write_count: 0,
|
||||
execution_cost_write_length: 0,
|
||||
hash: '0x0000000000000000000000000000000000000000000000000000000000000005',
|
||||
height: 5,
|
||||
index_block_hash: '0x0000000000000000000000000000000000000000000000000000000000000115',
|
||||
miner_txid: '0x4321',
|
||||
parent_block_hash: '0x0000000000000000000000000000000000000000000000000000000000000004',
|
||||
parent_index_block_hash: '0x0000000000000000000000000000000000000000000000000000000000000114',
|
||||
txs: ['0x0005'],
|
||||
};
|
||||
let fetch = await supertest(api.server).get(`/extended/v2/blocks/latest`);
|
||||
let json = JSON.parse(fetch.text);
|
||||
expect(fetch.status).toBe(200);
|
||||
expect(json).toStrictEqual(block5);
|
||||
|
||||
// Get by height
|
||||
fetch = await supertest(api.server).get(`/extended/v2/blocks/5`);
|
||||
json = JSON.parse(fetch.text);
|
||||
expect(fetch.status).toBe(200);
|
||||
expect(json).toStrictEqual(block5);
|
||||
|
||||
// Get by hash
|
||||
fetch = await supertest(api.server).get(
|
||||
`/extended/v2/blocks/0x0000000000000000000000000000000000000000000000000000000000000005`
|
||||
);
|
||||
json = JSON.parse(fetch.text);
|
||||
expect(fetch.status).toBe(200);
|
||||
expect(json).toStrictEqual(block5);
|
||||
|
||||
// Get by index block hash
|
||||
fetch = await supertest(api.server).get(
|
||||
`/extended/v2/blocks/0x0000000000000000000000000000000000000000000000000000000000000115`
|
||||
);
|
||||
json = JSON.parse(fetch.text);
|
||||
expect(fetch.status).toBe(200);
|
||||
expect(json).toStrictEqual(block5);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user