Skip to content

Commit

Permalink
feat: add average stacks block time to burn block endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed Apr 26, 2024
1 parent cd151aa commit 97700a8
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 26 deletions.
7 changes: 6 additions & 1 deletion docs/entities/blocks/burn-block.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"burn_block_time_iso",
"burn_block_hash",
"burn_block_height",
"stacks_blocks"
"stacks_blocks",
"avg_block_time"
],
"properties": {
"burn_block_time": {
Expand All @@ -33,6 +34,10 @@
"type": "string"
},
"description": "Hashes of the Stacks blocks included in the burn block"
},
"avg_block_time": {
"type": "number",
"description": "Average time between blocks in seconds. Returns 0 if there is only one block in the burn block."
}
}
}
4 changes: 4 additions & 0 deletions docs/generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,10 @@ export interface BurnBlock {
* Hashes of the Stacks blocks included in the burn block
*/
stacks_blocks: string[];
/**
* Average time between blocks in seconds. Returns 0 if there is only one block in the burn block.
*/
avg_block_time: number;
}
/**
* GET request that returns blocks
Expand Down
1 change: 1 addition & 0 deletions src/api/routes/v2/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function parseDbBurnBlock(block: DbBurnBlock): BurnBlock {
burn_block_hash: block.burn_block_hash,
burn_block_height: block.burn_block_height,
stacks_blocks: block.stacks_blocks,
avg_block_time: parseFloat(parseFloat(block.avg_block_time ?? '0').toFixed(2)),
};
return burnBlock;
}
Expand Down
1 change: 1 addition & 0 deletions src/datastore/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface DbBurnBlock {
burn_block_hash: string;
burn_block_height: number;
stacks_blocks: string[];
avg_block_time: string | null;
}

export interface DbBurnchainReward {
Expand Down
99 changes: 74 additions & 25 deletions src/datastore/pg-store-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,24 +269,53 @@ export class PgStoreV2 extends BasePgStoreModule {
const limit = args.limit ?? BlockLimitParamSchema.default;
const offset = args.offset ?? 0;
const blocksQuery = await sql<(DbBurnBlock & { total: number })[]>`
WITH block_count AS (
SELECT burn_block_height, block_count AS count FROM chain_tip
WITH RelevantBlocks AS (
SELECT DISTINCT ON (burn_block_height)
burn_block_time,
burn_block_hash,
burn_block_height
FROM blocks
WHERE canonical = true
ORDER BY burn_block_height DESC, block_height DESC
LIMIT ${limit}
OFFSET ${offset}
),
BlocksWithPrevTime AS (
SELECT
b.burn_block_time,
b.burn_block_hash,
b.burn_block_height,
b.block_hash,
b.block_time,
b.block_height,
LAG(b.block_time) OVER (PARTITION BY b.burn_block_height ORDER BY b.block_height) AS previous_block_time
FROM blocks b
WHERE
canonical = true AND
b.burn_block_height IN (SELECT burn_block_height FROM RelevantBlocks)
),
AverageTimes AS (
SELECT
burn_block_height,
AVG(block_time - previous_block_time) FILTER (WHERE previous_block_time IS NOT NULL) AS avg_block_time
FROM BlocksWithPrevTime
GROUP BY burn_block_height
)
SELECT DISTINCT ON (burn_block_height)
burn_block_time,
burn_block_hash,
burn_block_height,
ARRAY_AGG(block_hash) OVER (
PARTITION BY burn_block_height
ORDER BY block_height DESC
SELECT DISTINCT ON (r.burn_block_height)
r.burn_block_time,
r.burn_block_hash,
r.burn_block_height,
ARRAY_AGG(b.block_hash) OVER (
PARTITION BY r.burn_block_height
ORDER BY b.block_height DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS stacks_blocks,
(SELECT count FROM block_count)::int AS total
FROM blocks
WHERE canonical = true
ORDER BY burn_block_height DESC, block_height DESC
LIMIT ${limit}
OFFSET ${offset}
(SELECT block_count FROM chain_tip)::int AS total,
a.avg_block_time
FROM RelevantBlocks r
JOIN BlocksWithPrevTime b ON b.burn_block_height = r.burn_block_height
JOIN AverageTimes a ON a.burn_block_height = r.burn_block_height
ORDER BY r.burn_block_height DESC, b.block_height DESC;
`;
return {
limit,
Expand All @@ -306,17 +335,37 @@ export class PgStoreV2 extends BasePgStoreModule {
? sql`burn_block_hash = ${args.height_or_hash}`
: sql`burn_block_height = ${args.height_or_hash}`;
const blockQuery = await sql<DbBurnBlock[]>`
SELECT DISTINCT ON (burn_block_height)
burn_block_time,
burn_block_hash,
burn_block_height,
ARRAY_AGG(block_hash) OVER (
PARTITION BY burn_block_height
ORDER BY block_height DESC
WITH BlocksWithPrevTime AS (
SELECT
burn_block_time,
burn_block_hash,
burn_block_height,
block_hash,
block_time,
block_height,
LAG(block_time) OVER (PARTITION BY burn_block_height ORDER BY block_height) AS previous_block_time
FROM blocks
WHERE canonical = true AND ${filter}
),
AverageTimes AS (
SELECT
burn_block_height,
AVG(block_time - previous_block_time) FILTER (WHERE previous_block_time IS NOT NULL) AS avg_block_time
FROM BlocksWithPrevTime
GROUP BY burn_block_height
)
SELECT DISTINCT ON (b.burn_block_height)
b.burn_block_time,
b.burn_block_hash,
b.burn_block_height,
ARRAY_AGG(b.block_hash) OVER (
PARTITION BY b.burn_block_height
ORDER BY b.block_height DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS stacks_blocks
FROM blocks
WHERE canonical = true AND ${filter}
) AS stacks_blocks,
a.avg_block_time
FROM BlocksWithPrevTime b
JOIN AverageTimes a ON a.burn_block_height = b.burn_block_height
LIMIT 1
`;
if (blockQuery.count > 0) return blockQuery[0];
Expand Down
12 changes: 12 additions & 0 deletions src/tests/block-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,11 @@ describe('block tests', () => {
burn_block_time: 1702386678,
};

const tenMinutes = 10 * 60;
let blockStartTime = 1714139800;
const stacksBlock1 = {
block_height: 1,
block_time: (blockStartTime += tenMinutes),
block_hash: '0x1234111111111111111111111111111111111111111111111111111111111111',
index_block_hash: '0xabcd111111111111111111111111111111111111111111111111111111111111',
parent_index_block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
Expand All @@ -308,6 +311,7 @@ describe('block tests', () => {
};
const stacksBlock2 = {
block_height: 2,
block_time: (blockStartTime += tenMinutes),
block_hash: '0x1234211111111111111111111111111111111111111111111111111111111111',
index_block_hash: '0xabcd211111111111111111111111111111111111111111111111111111111111',
parent_index_block_hash: stacksBlock1.index_block_hash,
Expand All @@ -317,6 +321,7 @@ describe('block tests', () => {
};
const stacksBlock3 = {
block_height: 3,
block_time: (blockStartTime += tenMinutes),
block_hash: '0x1234311111111111111111111111111111111111111111111111111111111111',
index_block_hash: '0xabcd311111111111111111111111111111111111111111111111111111111111',
parent_index_block_hash: stacksBlock2.index_block_hash,
Expand All @@ -326,6 +331,7 @@ describe('block tests', () => {
};
const stacksBlock4 = {
block_height: 4,
block_time: (blockStartTime += tenMinutes),
block_hash: '0x1234411111111111111111111111111111111111111111111111111111111111',
index_block_hash: '0xabcd411111111111111111111111111111111111111111111111111111111111',
parent_index_block_hash: stacksBlock3.index_block_hash,
Expand All @@ -339,6 +345,7 @@ describe('block tests', () => {
for (const block of stacksBlocks) {
const dbBlock = new TestBlockBuilder({
block_hash: block.block_hash,
block_time: block.block_time,
index_block_hash: block.index_block_hash,
parent_index_block_hash: block.parent_index_block_hash,
block_height: block.block_height,
Expand All @@ -352,13 +359,15 @@ describe('block tests', () => {
const result = await supertest(api.server).get(`/extended/v2/burn-blocks`);
expect(result.body.results).toEqual([
{
avg_block_time: tenMinutes,
burn_block_hash: burnBlock2.burn_block_hash,
burn_block_height: burnBlock2.burn_block_height,
burn_block_time: burnBlock2.burn_block_time,
burn_block_time_iso: unixEpochToIso(burnBlock2.burn_block_time),
stacks_blocks: [stacksBlock4.block_hash, stacksBlock3.block_hash, stacksBlock2.block_hash],
},
{
avg_block_time: 0,
burn_block_hash: burnBlock1.burn_block_hash,
burn_block_height: burnBlock1.burn_block_height,
burn_block_time: burnBlock1.burn_block_time,
Expand All @@ -370,6 +379,7 @@ describe('block tests', () => {
// test 'latest' filter
const result2 = await supertest(api.server).get(`/extended/v2/burn-blocks/latest`);
expect(result2.body).toEqual({
avg_block_time: tenMinutes,
burn_block_hash: stacksBlocks.at(-1)?.burn_block_hash,
burn_block_height: stacksBlocks.at(-1)?.burn_block_height,
burn_block_time: stacksBlocks.at(-1)?.burn_block_time,
Expand All @@ -382,6 +392,7 @@ describe('block tests', () => {
`/extended/v2/burn-blocks/${stacksBlock1.burn_block_hash}`
);
expect(result3.body).toEqual({
avg_block_time: 0,
burn_block_hash: stacksBlock1.burn_block_hash,
burn_block_height: stacksBlock1.burn_block_height,
burn_block_time: stacksBlock1.burn_block_time,
Expand All @@ -394,6 +405,7 @@ describe('block tests', () => {
`/extended/v2/burn-blocks/${stacksBlock1.burn_block_height}`
);
expect(result4.body).toEqual({
avg_block_time: 0,
burn_block_hash: stacksBlock1.burn_block_hash,
burn_block_height: stacksBlock1.burn_block_height,
burn_block_time: stacksBlock1.burn_block_time,
Expand Down

0 comments on commit 97700a8

Please sign in to comment.