Skip to content

Commit e1d4c61

Browse files
authored
fix: use burn_block_time in 2.x blocks and block_time after 3.x (#2314)
* fix: pre nakamoto block time * fix: always use block time * test: behavior * test: new suite * fix: remove old tests * fix: throw when no time is present * fix: block time falsy check
1 parent 9421f75 commit e1d4c61

File tree

4 files changed

+118
-16
lines changed

4 files changed

+118
-16
lines changed

src/event-stream/core-node-message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export interface CoreNodeBlockMessage {
326326
missed_reward_slots: [];
327327
};
328328
};
329-
block_time: number;
329+
block_time: number | null;
330330
signer_bitvec?: string | null;
331331
signer_signature?: string[];
332332
}

src/event-stream/event-server.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -997,20 +997,20 @@ export function parseNewBlockMessage(
997997
) {
998998
const counts = newCoreNoreBlockEventCounts();
999999

1000+
// Nakamoto blocks now include their own `block_time`, but this will be empty for pre-Nakamoto
1001+
// blocks. We'll use the parent burn block timestamp as the receipt date for those. If both are
1002+
// blank, there's something wrong with Stacks core.
1003+
const block_time = msg.block_time ?? msg.burn_block_time;
1004+
if (block_time === undefined || block_time === null) {
1005+
throw new Error('Block message has no block_time or burn_block_time');
1006+
}
1007+
10001008
const parsedTxs: CoreNodeParsedTxMessage[] = [];
10011009
const blockData: CoreNodeMsgBlockData = {
10021010
...msg,
1011+
block_time,
10031012
};
10041013

1005-
if (!blockData.block_time) {
1006-
// If running in IBD mode, we use the parent burn block timestamp as the receipt date,
1007-
// otherwise, use the current timestamp.
1008-
const stacksBlockReceiptDate = isEventReplay
1009-
? msg.burn_block_time
1010-
: Math.round(Date.now() / 1000);
1011-
blockData.block_time = stacksBlockReceiptDate;
1012-
}
1013-
10141014
msg.transactions.forEach(item => {
10151015
const parsedTx = parseMessageTransaction(chainId, item, blockData, msg.events);
10161016
if (parsedTx) {

tests/api/block-time.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { ChainID } from '@stacks/transactions';
2+
import { CoreNodeBlockMessage } from '../../src/event-stream/core-node-message';
3+
import { parseNewBlockMessage } from '../../src/event-stream/event-server';
4+
5+
describe('block time tests', () => {
6+
test('takes block_time from block header', () => {
7+
const block: CoreNodeBlockMessage = {
8+
block_time: 1716238792,
9+
block_height: 1,
10+
block_hash: '0x1234',
11+
index_block_hash: '0x5678',
12+
parent_index_block_hash: '0x9abc',
13+
parent_block_hash: '0x1234',
14+
parent_microblock: '0x1234',
15+
parent_microblock_sequence: 0,
16+
parent_burn_block_hash: '0x1234',
17+
parent_burn_block_height: 0,
18+
parent_burn_block_timestamp: 0,
19+
burn_block_time: 1234567890,
20+
burn_block_hash: '0x1234',
21+
burn_block_height: 1,
22+
miner_txid: '0x1234',
23+
events: [],
24+
transactions: [],
25+
matured_miner_rewards: [],
26+
};
27+
const { dbData: parsed } = parseNewBlockMessage(ChainID.Mainnet, block, false);
28+
expect(parsed.block.block_time).toEqual(1716238792); // Takes block_time from block header
29+
});
30+
31+
test('takes burn_block_time from block header when block_time is not present', () => {
32+
const block: CoreNodeBlockMessage = {
33+
block_time: null,
34+
block_height: 1,
35+
block_hash: '0x1234',
36+
index_block_hash: '0x5678',
37+
parent_index_block_hash: '0x9abc',
38+
parent_block_hash: '0x1234',
39+
parent_microblock: '0x1234',
40+
parent_microblock_sequence: 0,
41+
parent_burn_block_hash: '0x1234',
42+
parent_burn_block_height: 0,
43+
parent_burn_block_timestamp: 0,
44+
burn_block_time: 1234567890,
45+
burn_block_hash: '0x1234',
46+
burn_block_height: 1,
47+
miner_txid: '0x1234',
48+
events: [],
49+
transactions: [],
50+
matured_miner_rewards: [],
51+
};
52+
const { dbData: parsed } = parseNewBlockMessage(ChainID.Mainnet, block, false);
53+
expect(parsed.block.block_time).toEqual(1234567890); // Takes burn_block_time from block header
54+
});
55+
56+
test('throws error if block_time and burn_block_time are not present', () => {
57+
// Use `any` to avoid type errors when setting `block_time` and `burn_block_time` to `null`.
58+
const block: any = {
59+
block_time: null,
60+
burn_block_time: null,
61+
block_height: 1,
62+
block_hash: '0x1234',
63+
index_block_hash: '0x5678',
64+
parent_index_block_hash: '0x9abc',
65+
parent_block_hash: '0x1234',
66+
parent_microblock: '0x1234',
67+
parent_microblock_sequence: 0,
68+
parent_burn_block_hash: '0x1234',
69+
parent_burn_block_height: 0,
70+
parent_burn_block_timestamp: 0,
71+
burn_block_hash: '0x1234',
72+
burn_block_height: 1,
73+
miner_txid: '0x1234',
74+
events: [],
75+
transactions: [],
76+
matured_miner_rewards: [],
77+
};
78+
expect(() => parseNewBlockMessage(ChainID.Mainnet, block, false)).toThrow(
79+
'Block message has no block_time or burn_block_time'
80+
);
81+
});
82+
});

tests/api/synthetic-stx-txs.test.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as fs from 'fs';
33
import * as path from 'path';
44
import { DecodedTxResult, TxPayloadTypeID } from 'stacks-encoding-native-js';
55
import { CoreNodeBlockMessage } from '../../src/event-stream/core-node-message';
6-
import { parseMessageTransaction } from '../../src/event-stream/reader';
6+
import { CoreNodeMsgBlockData, parseMessageTransaction } from '../../src/event-stream/reader';
77
import { parseNewBlockMessage } from '../../src/event-stream/event-server';
88

99
// Test processing of the psuedo-Stacks transactions, i.e. the ones that
@@ -20,7 +20,12 @@ describe('synthetic stx txs', () => {
2020
if (!txMsg) {
2121
throw new Error(`Cound not find tx ${txid}`);
2222
}
23-
const parsed = parseMessageTransaction(ChainID.Mainnet, txMsg, blockMsg, blockMsg.events);
23+
const parsed = parseMessageTransaction(
24+
ChainID.Mainnet,
25+
txMsg,
26+
blockMsg as unknown as CoreNodeMsgBlockData,
27+
blockMsg.events
28+
);
2429
if (!parsed) {
2530
throw new Error(`Failed to parse ${txid}`);
2631
}
@@ -75,7 +80,12 @@ describe('synthetic stx txs', () => {
7580
if (!txMsg) {
7681
throw new Error(`Cound not find tx ${txid}`);
7782
}
78-
const parsed = parseMessageTransaction(ChainID.Mainnet, txMsg, blockMsg, blockMsg.events);
83+
const parsed = parseMessageTransaction(
84+
ChainID.Mainnet,
85+
txMsg,
86+
blockMsg as unknown as CoreNodeMsgBlockData,
87+
blockMsg.events
88+
);
7989
if (!parsed) {
8090
throw new Error(`Failed to parse ${txid}`);
8191
}
@@ -130,7 +140,12 @@ describe('synthetic stx txs', () => {
130140
if (!txMsg) {
131141
throw new Error(`Cound not find tx ${txid}`);
132142
}
133-
const parsed = parseMessageTransaction(ChainID.Mainnet, txMsg, blockMsg, blockMsg.events);
143+
const parsed = parseMessageTransaction(
144+
ChainID.Mainnet,
145+
txMsg,
146+
blockMsg as unknown as CoreNodeMsgBlockData,
147+
blockMsg.events
148+
);
134149
if (!parsed) {
135150
throw new Error(`Failed to parse ${txid}`);
136151
}
@@ -234,7 +249,12 @@ describe('synthetic stx txs', () => {
234249
if (!txMsg) {
235250
throw new Error(`Cound not find tx ${txid}`);
236251
}
237-
const parsed = parseMessageTransaction(ChainID.Mainnet, txMsg, blockMsg, blockMsg.events);
252+
const parsed = parseMessageTransaction(
253+
ChainID.Mainnet,
254+
txMsg,
255+
blockMsg as unknown as CoreNodeMsgBlockData,
256+
blockMsg.events
257+
);
238258
if (!parsed) {
239259
throw new Error(`Failed to parse ${txid}`);
240260
}
@@ -333,7 +353,7 @@ describe('synthetic stx txs', () => {
333353
const parsed = parseMessageTransaction(
334354
ChainID.Mainnet,
335355
payload.txMsg,
336-
payload.blockMsg,
356+
payload.blockMsg as unknown as CoreNodeMsgBlockData,
337357
payload.blockMsg.events
338358
);
339359
let txType: 'contract_call' | 'token_transfer' | null;

0 commit comments

Comments
 (0)