Skip to content

Commit 31c2eed

Browse files
authored
feat: add average stacks block time to burn block endpoints (#1963)
* feat: add average stacks block time to burn block endpoints * docs: schema example update
1 parent 2c3dbb0 commit 31c2eed

File tree

8 files changed

+102
-28
lines changed

8 files changed

+102
-28
lines changed

docs/api/blocks/get-burn-blocks.example.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"0xdaf61d2b355f35c94cf019af99aeb73d8e7db7301c7cd693a464ebd1cfc2228c",
1414
"0xb9e9b308cf9621ecbf66ca7b4689fe384b9b67c4588ec827d8163ab602fb935e",
1515
"0x754562cba6ec243f90485e97778ab472f462fd123ef5b83cc79d8759ca8875f5"
16-
]
16+
],
17+
"avg_block_time": 15.3
1718
}
1819
]
1920
}

docs/entities/blocks/burn-block.example.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"0xdaf61d2b355f35c94cf019af99aeb73d8e7db7301c7cd693a464ebd1cfc2228c",
99
"0xb9e9b308cf9621ecbf66ca7b4689fe384b9b67c4588ec827d8163ab602fb935e",
1010
"0x754562cba6ec243f90485e97778ab472f462fd123ef5b83cc79d8759ca8875f5"
11-
]
11+
],
12+
"avg_block_time": 15.3
1213
}

docs/entities/blocks/burn-block.schema.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"burn_block_time_iso",
99
"burn_block_hash",
1010
"burn_block_height",
11-
"stacks_blocks"
11+
"stacks_blocks",
12+
"avg_block_time"
1213
],
1314
"properties": {
1415
"burn_block_time": {
@@ -33,6 +34,10 @@
3334
"type": "string"
3435
},
3536
"description": "Hashes of the Stacks blocks included in the burn block"
37+
},
38+
"avg_block_time": {
39+
"type": "number",
40+
"description": "Average time between blocks in seconds. Returns 0 if there is only one block in the burn block."
3641
}
3742
}
3843
}

docs/generated.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,6 +1466,10 @@ export interface BurnBlock {
14661466
* Hashes of the Stacks blocks included in the burn block
14671467
*/
14681468
stacks_blocks: string[];
1469+
/**
1470+
* Average time between blocks in seconds. Returns 0 if there is only one block in the burn block.
1471+
*/
1472+
avg_block_time: number;
14691473
}
14701474
/**
14711475
* GET request that returns blocks

src/api/routes/v2/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function parseDbBurnBlock(block: DbBurnBlock): BurnBlock {
6060
burn_block_hash: block.burn_block_hash,
6161
burn_block_height: block.burn_block_height,
6262
stacks_blocks: block.stacks_blocks,
63+
avg_block_time: parseFloat(parseFloat(block.avg_block_time ?? '0').toFixed(2)),
6364
};
6465
return burnBlock;
6566
}

src/datastore/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface DbBurnBlock {
5555
burn_block_hash: string;
5656
burn_block_height: number;
5757
stacks_blocks: string[];
58+
avg_block_time: string | null;
5859
}
5960

6061
export interface DbBurnchainReward {

src/datastore/pg-store-v2.ts

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -269,24 +269,53 @@ export class PgStoreV2 extends BasePgStoreModule {
269269
const limit = args.limit ?? BlockLimitParamSchema.default;
270270
const offset = args.offset ?? 0;
271271
const blocksQuery = await sql<(DbBurnBlock & { total: number })[]>`
272-
WITH block_count AS (
273-
SELECT burn_block_height, block_count AS count FROM chain_tip
272+
WITH RelevantBlocks AS (
273+
SELECT DISTINCT ON (burn_block_height)
274+
burn_block_time,
275+
burn_block_hash,
276+
burn_block_height
277+
FROM blocks
278+
WHERE canonical = true
279+
ORDER BY burn_block_height DESC, block_height DESC
280+
LIMIT ${limit}
281+
OFFSET ${offset}
282+
),
283+
BlocksWithPrevTime AS (
284+
SELECT
285+
b.burn_block_time,
286+
b.burn_block_hash,
287+
b.burn_block_height,
288+
b.block_hash,
289+
b.block_time,
290+
b.block_height,
291+
LAG(b.block_time) OVER (PARTITION BY b.burn_block_height ORDER BY b.block_height) AS previous_block_time
292+
FROM blocks b
293+
WHERE
294+
canonical = true AND
295+
b.burn_block_height IN (SELECT burn_block_height FROM RelevantBlocks)
296+
),
297+
AverageTimes AS (
298+
SELECT
299+
burn_block_height,
300+
AVG(block_time - previous_block_time) FILTER (WHERE previous_block_time IS NOT NULL) AS avg_block_time
301+
FROM BlocksWithPrevTime
302+
GROUP BY burn_block_height
274303
)
275-
SELECT DISTINCT ON (burn_block_height)
276-
burn_block_time,
277-
burn_block_hash,
278-
burn_block_height,
279-
ARRAY_AGG(block_hash) OVER (
280-
PARTITION BY burn_block_height
281-
ORDER BY block_height DESC
304+
SELECT DISTINCT ON (r.burn_block_height)
305+
r.burn_block_time,
306+
r.burn_block_hash,
307+
r.burn_block_height,
308+
ARRAY_AGG(b.block_hash) OVER (
309+
PARTITION BY r.burn_block_height
310+
ORDER BY b.block_height DESC
282311
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
283312
) AS stacks_blocks,
284-
(SELECT count FROM block_count)::int AS total
285-
FROM blocks
286-
WHERE canonical = true
287-
ORDER BY burn_block_height DESC, block_height DESC
288-
LIMIT ${limit}
289-
OFFSET ${offset}
313+
(SELECT block_count FROM chain_tip)::int AS total,
314+
a.avg_block_time
315+
FROM RelevantBlocks r
316+
JOIN BlocksWithPrevTime b ON b.burn_block_height = r.burn_block_height
317+
JOIN AverageTimes a ON a.burn_block_height = r.burn_block_height
318+
ORDER BY r.burn_block_height DESC, b.block_height DESC;
290319
`;
291320
return {
292321
limit,
@@ -306,17 +335,37 @@ export class PgStoreV2 extends BasePgStoreModule {
306335
? sql`burn_block_hash = ${args.height_or_hash}`
307336
: sql`burn_block_height = ${args.height_or_hash}`;
308337
const blockQuery = await sql<DbBurnBlock[]>`
309-
SELECT DISTINCT ON (burn_block_height)
310-
burn_block_time,
311-
burn_block_hash,
312-
burn_block_height,
313-
ARRAY_AGG(block_hash) OVER (
314-
PARTITION BY burn_block_height
315-
ORDER BY block_height DESC
338+
WITH BlocksWithPrevTime AS (
339+
SELECT
340+
burn_block_time,
341+
burn_block_hash,
342+
burn_block_height,
343+
block_hash,
344+
block_time,
345+
block_height,
346+
LAG(block_time) OVER (PARTITION BY burn_block_height ORDER BY block_height) AS previous_block_time
347+
FROM blocks
348+
WHERE canonical = true AND ${filter}
349+
),
350+
AverageTimes AS (
351+
SELECT
352+
burn_block_height,
353+
AVG(block_time - previous_block_time) FILTER (WHERE previous_block_time IS NOT NULL) AS avg_block_time
354+
FROM BlocksWithPrevTime
355+
GROUP BY burn_block_height
356+
)
357+
SELECT DISTINCT ON (b.burn_block_height)
358+
b.burn_block_time,
359+
b.burn_block_hash,
360+
b.burn_block_height,
361+
ARRAY_AGG(b.block_hash) OVER (
362+
PARTITION BY b.burn_block_height
363+
ORDER BY b.block_height DESC
316364
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
317-
) AS stacks_blocks
318-
FROM blocks
319-
WHERE canonical = true AND ${filter}
365+
) AS stacks_blocks,
366+
a.avg_block_time
367+
FROM BlocksWithPrevTime b
368+
JOIN AverageTimes a ON a.burn_block_height = b.burn_block_height
320369
LIMIT 1
321370
`;
322371
if (blockQuery.count > 0) return blockQuery[0];

src/tests/block-tests.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,11 @@ describe('block tests', () => {
297297
burn_block_time: 1702386678,
298298
};
299299

300+
const tenMinutes = 10 * 60;
301+
let blockStartTime = 1714139800;
300302
const stacksBlock1 = {
301303
block_height: 1,
304+
block_time: (blockStartTime += tenMinutes),
302305
block_hash: '0x1234111111111111111111111111111111111111111111111111111111111111',
303306
index_block_hash: '0xabcd111111111111111111111111111111111111111111111111111111111111',
304307
parent_index_block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
@@ -308,6 +311,7 @@ describe('block tests', () => {
308311
};
309312
const stacksBlock2 = {
310313
block_height: 2,
314+
block_time: (blockStartTime += tenMinutes),
311315
block_hash: '0x1234211111111111111111111111111111111111111111111111111111111111',
312316
index_block_hash: '0xabcd211111111111111111111111111111111111111111111111111111111111',
313317
parent_index_block_hash: stacksBlock1.index_block_hash,
@@ -317,6 +321,7 @@ describe('block tests', () => {
317321
};
318322
const stacksBlock3 = {
319323
block_height: 3,
324+
block_time: (blockStartTime += tenMinutes),
320325
block_hash: '0x1234311111111111111111111111111111111111111111111111111111111111',
321326
index_block_hash: '0xabcd311111111111111111111111111111111111111111111111111111111111',
322327
parent_index_block_hash: stacksBlock2.index_block_hash,
@@ -326,6 +331,7 @@ describe('block tests', () => {
326331
};
327332
const stacksBlock4 = {
328333
block_height: 4,
334+
block_time: (blockStartTime += tenMinutes),
329335
block_hash: '0x1234411111111111111111111111111111111111111111111111111111111111',
330336
index_block_hash: '0xabcd411111111111111111111111111111111111111111111111111111111111',
331337
parent_index_block_hash: stacksBlock3.index_block_hash,
@@ -339,6 +345,7 @@ describe('block tests', () => {
339345
for (const block of stacksBlocks) {
340346
const dbBlock = new TestBlockBuilder({
341347
block_hash: block.block_hash,
348+
block_time: block.block_time,
342349
index_block_hash: block.index_block_hash,
343350
parent_index_block_hash: block.parent_index_block_hash,
344351
block_height: block.block_height,
@@ -352,13 +359,15 @@ describe('block tests', () => {
352359
const result = await supertest(api.server).get(`/extended/v2/burn-blocks`);
353360
expect(result.body.results).toEqual([
354361
{
362+
avg_block_time: tenMinutes,
355363
burn_block_hash: burnBlock2.burn_block_hash,
356364
burn_block_height: burnBlock2.burn_block_height,
357365
burn_block_time: burnBlock2.burn_block_time,
358366
burn_block_time_iso: unixEpochToIso(burnBlock2.burn_block_time),
359367
stacks_blocks: [stacksBlock4.block_hash, stacksBlock3.block_hash, stacksBlock2.block_hash],
360368
},
361369
{
370+
avg_block_time: 0,
362371
burn_block_hash: burnBlock1.burn_block_hash,
363372
burn_block_height: burnBlock1.burn_block_height,
364373
burn_block_time: burnBlock1.burn_block_time,
@@ -370,6 +379,7 @@ describe('block tests', () => {
370379
// test 'latest' filter
371380
const result2 = await supertest(api.server).get(`/extended/v2/burn-blocks/latest`);
372381
expect(result2.body).toEqual({
382+
avg_block_time: tenMinutes,
373383
burn_block_hash: stacksBlocks.at(-1)?.burn_block_hash,
374384
burn_block_height: stacksBlocks.at(-1)?.burn_block_height,
375385
burn_block_time: stacksBlocks.at(-1)?.burn_block_time,
@@ -382,6 +392,7 @@ describe('block tests', () => {
382392
`/extended/v2/burn-blocks/${stacksBlock1.burn_block_hash}`
383393
);
384394
expect(result3.body).toEqual({
395+
avg_block_time: 0,
385396
burn_block_hash: stacksBlock1.burn_block_hash,
386397
burn_block_height: stacksBlock1.burn_block_height,
387398
burn_block_time: stacksBlock1.burn_block_time,
@@ -394,6 +405,7 @@ describe('block tests', () => {
394405
`/extended/v2/burn-blocks/${stacksBlock1.burn_block_height}`
395406
);
396407
expect(result4.body).toEqual({
408+
avg_block_time: 0,
397409
burn_block_hash: stacksBlock1.burn_block_hash,
398410
burn_block_height: stacksBlock1.burn_block_height,
399411
burn_block_time: stacksBlock1.burn_block_time,

0 commit comments

Comments
 (0)