Skip to content

Commit 07339c0

Browse files
committed
fix: sql transactional consistency bug with fetching chaintip in various areas (#1853)
1 parent 2dd1de8 commit 07339c0

File tree

11 files changed

+28
-28
lines changed

11 files changed

+28
-28
lines changed

src/api/controllers/cache-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ async function calculateETag(
252252
switch (etagType) {
253253
case ETagType.chainTip:
254254
try {
255-
const chainTip = await db.getChainTip();
255+
const chainTip = await db.getChainTip(db.sql);
256256
if (chainTip.block_height === 0) {
257257
// This should never happen unless the API is serving requests before it has synced any
258258
// blocks.

src/api/routes/status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function createStatusRouter(db: PgStore): express.Router {
1919
response.pox_v2_unlock_height = poxForceUnlockHeights.result.pox2UnlockHeight as number;
2020
response.pox_v3_unlock_height = poxForceUnlockHeights.result.pox3UnlockHeight as number;
2121
}
22-
const chainTip = await db.getChainTip();
22+
const chainTip = await db.getChainTip(db.sql);
2323
if (chainTip.block_height > 0) {
2424
response.chain_tip = {
2525
block_height: chainTip.block_height,

src/datastore/pg-store.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ export class PgStore extends BasePgStore {
204204
});
205205
}
206206

207-
async getChainTip(): Promise<DbChainTip> {
208-
const tipResult = await this.sql<DbChainTip[]>`SELECT * FROM chain_tip`;
207+
async getChainTip(sql: PgSqlClient): Promise<DbChainTip> {
208+
const tipResult = await sql<DbChainTip[]>`SELECT * FROM chain_tip`;
209209
const tip = tipResult[0];
210210
return {
211211
block_height: tip?.block_height ?? 0,
@@ -607,7 +607,7 @@ export class PgStore extends BasePgStore {
607607

608608
async getUnanchoredTxsInternal(sql: PgSqlClient): Promise<{ txs: DbTx[] }> {
609609
// Get transactions that have been streamed in microblocks but not yet accepted or rejected in an anchor block.
610-
const { block_height } = await this.getChainTip();
610+
const { block_height } = await this.getChainTip(sql);
611611
const unanchoredBlockHeight = block_height + 1;
612612
const query = await sql<ContractTxQueryResult[]>`
613613
SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])}
@@ -1402,7 +1402,7 @@ export class PgStore extends BasePgStore {
14021402
sql: PgSqlClient,
14031403
{ includeUnanchored }: { includeUnanchored: boolean }
14041404
): Promise<number> {
1405-
const chainTip = await this.getChainTip();
1405+
const chainTip = await this.getChainTip(sql);
14061406
if (includeUnanchored) {
14071407
return chainTip.block_height + 1;
14081408
} else {
@@ -2142,7 +2142,7 @@ export class PgStore extends BasePgStore {
21422142

21432143
async getStxBalanceAtBlock(stxAddress: string, blockHeight: number): Promise<DbStxBalance> {
21442144
return await this.sqlTransaction(async sql => {
2145-
const chainTip = await this.getChainTip();
2145+
const chainTip = await this.getChainTip(sql);
21462146
const blockHeightToQuery =
21472147
blockHeight > chainTip.block_height ? chainTip.block_height : blockHeight;
21482148
const blockQuery = await this.getBlockByHeightInternal(sql, blockHeightToQuery);

src/datastore/pg-write-store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export class PgWriteStore extends PgStore {
183183
let batchedTxData: DataStoreTxEventData[] = [];
184184

185185
await this.sqlWriteTransaction(async sql => {
186-
const chainTip = await this.getChainTip();
186+
const chainTip = await this.getChainTip(sql);
187187
await this.handleReorg(sql, data.block, chainTip.block_height);
188188
const isCanonical = data.block.block_height > chainTip.block_height;
189189
if (!isCanonical) {
@@ -555,7 +555,7 @@ export class PgWriteStore extends PgStore {
555555
// Sanity check: ensure incoming microblocks have a `parent_index_block_hash` that matches the
556556
// API's current known canonical chain tip. We assume this holds true so incoming microblock
557557
// data is always treated as being built off the current canonical anchor block.
558-
const chainTip = await this.getChainTip();
558+
const chainTip = await this.getChainTip(sql);
559559
const nonCanonicalMicroblock = data.microblocks.find(
560560
mb => mb.parent_index_block_hash !== chainTip.index_block_hash
561561
);
@@ -1797,7 +1797,7 @@ export class PgWriteStore extends PgStore {
17971797
async updateMempoolTxs({ mempoolTxs: txs }: { mempoolTxs: DbMempoolTxRaw[] }): Promise<void> {
17981798
const updatedTxIds: string[] = [];
17991799
await this.sqlWriteTransaction(async sql => {
1800-
const chainTip = await this.getChainTip();
1800+
const chainTip = await this.getChainTip(sql);
18011801
updatedTxIds.push(...(await this.insertDbMempoolTxs(txs, chainTip, sql)));
18021802
});
18031803
if (!this.isEventReplay) {

src/event-stream/event-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ export async function startEventServer(opts: {
888888
if (ibdHeight) {
889889
app.use(IBD_PRUNABLE_ROUTES, async (req, res, next) => {
890890
try {
891-
const chainTip = await db.getChainTip();
891+
const chainTip = await db.getChainTip(db.sql);
892892
if (chainTip.block_height > ibdHeight) {
893893
next();
894894
} else {

src/tests-event-replay/import-export-tests.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('import/export tests', () => {
2828
test('event import and export cycle', async () => {
2929
// Import from mocknet TSV
3030
await importEventsFromTsv('src/tests-event-replay/tsv/mocknet.tsv', 'archival', true, true);
31-
const chainTip = await db.getChainTip();
31+
const chainTip = await db.getChainTip(db.sql);
3232
expect(chainTip.block_height).toBe(28);
3333
expect(chainTip.index_block_hash).toBe(
3434
'0x76cd67a65c0dfd5ea450bb9efe30da89fa125bfc077c953802f718353283a533'
@@ -50,7 +50,7 @@ describe('import/export tests', () => {
5050
// Re-import with exported TSV and check that chain tip matches.
5151
try {
5252
await importEventsFromTsv(`${tmpDir}/export.tsv`, 'archival', true, true);
53-
const newChainTip = await db.getChainTip();
53+
const newChainTip = await db.getChainTip(db.sql);
5454
expect(newChainTip.block_height).toBe(28);
5555
expect(newChainTip.index_block_hash).toBe(
5656
'0x76cd67a65c0dfd5ea450bb9efe30da89fa125bfc077c953802f718353283a533'
@@ -196,14 +196,14 @@ describe('IBD', () => {
196196
process.env.IBD_MODE_UNTIL_BLOCK = '1000';
197197
// TSV has 1 microblock message.
198198
await expect(getIbdInterceptCountFromTsvEvents()).resolves.toBe(1);
199-
await expect(db.getChainTip()).resolves.toHaveProperty('block_height', 28);
199+
await expect(db.getChainTip(db.sql)).resolves.toHaveProperty('block_height', 28);
200200
});
201201

202202
test('IBD mode does NOT block certain API routes once the threshold number of blocks are ingested', async () => {
203203
process.env.IBD_MODE_UNTIL_BLOCK = '1';
204204
// Microblock processed normally.
205205
await expect(getIbdInterceptCountFromTsvEvents()).resolves.toBe(0);
206-
await expect(db.getChainTip()).resolves.toHaveProperty('block_height', 28);
206+
await expect(db.getChainTip(db.sql)).resolves.toHaveProperty('block_height', 28);
207207
});
208208

209209
test('IBD mode covers prune mode', async () => {

src/tests-event-replay/poison-microblock-tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('poison microblock for height 80743', () => {
2525
true
2626
);
2727
const poisonTxId = '0x58ffe62029f94f7101b959536ea4953b9bce0ec3f6e2a06254c511bdd5cfa9e7';
28-
const chainTip = await db.getChainTip();
28+
const chainTip = await db.getChainTip(db.sql);
2929
// query the txs table and check the transaction type
3030
const searchResult = await db.searchHash({ hash: poisonTxId });
3131
let entityData: any;

src/tests/cache-control-tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ describe('cache-control tests', () => {
321321
],
322322
});
323323

324-
const chainTip2 = await db.getChainTip();
324+
const chainTip2 = await db.getChainTip(db.sql);
325325
expect(chainTip2.block_hash).toBe(block1.block_hash);
326326
expect(chainTip2.block_height).toBe(block1.block_height);
327327
expect(chainTip2.index_block_hash).toBe(block1.index_block_hash);

src/tests/datastore-tests.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4101,7 +4101,7 @@ describe('postgres datastore', () => {
41014101

41024102
const blockQuery1 = await db.getBlock({ hash: block2b.block_hash });
41034103
expect(blockQuery1.result?.canonical).toBe(false);
4104-
const chainTip1 = await db.getChainTip();
4104+
const chainTip1 = await db.getChainTip(db.sql);
41054105
expect(chainTip1).toEqual({
41064106
block_hash: '0x33',
41074107
block_height: 3,
@@ -4169,7 +4169,7 @@ describe('postgres datastore', () => {
41694169
const blockQuery2 = await db.getBlock({ hash: block3b.block_hash });
41704170
expect(blockQuery2.result?.canonical).toBe(false);
41714171
// Chain tip doesn't change yet.
4172-
const chainTip2 = await db.getChainTip();
4172+
const chainTip2 = await db.getChainTip(db.sql);
41734173
expect(chainTip2).toEqual({
41744174
block_hash: '0x33',
41754175
block_height: 3,
@@ -4220,7 +4220,7 @@ describe('postgres datastore', () => {
42204220

42214221
const blockQuery3 = await db.getBlock({ hash: block3b.block_hash });
42224222
expect(blockQuery3.result?.canonical).toBe(true);
4223-
const chainTip3 = await db.getChainTip();
4223+
const chainTip3 = await db.getChainTip(db.sql);
42244224
expect(chainTip3).toEqual({
42254225
block_count: 4,
42264226
block_hash: '0x44bb',

src/tests/mempool-tests.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,7 +1666,7 @@ describe('mempool tests', () => {
16661666
// Simulate the bug with a txs being in the mempool at confirmed at the same time by
16671667
// directly inserting the mempool-tx and mined-tx, bypassing the normal update functions.
16681668
await db.updateBlock(db.sql, dbBlock1);
1669-
const chainTip = await db.getChainTip();
1669+
const chainTip = await db.getChainTip(db.sql);
16701670
await db.insertDbMempoolTxs([mempoolTx], chainTip, db.sql);
16711671
await db.updateTx(db.sql, dbTx1);
16721672

@@ -1828,7 +1828,7 @@ describe('mempool tests', () => {
18281828

18291829
await db.updateMempoolTxs({ mempoolTxs: [mempoolTx] });
18301830

1831-
let chainTip = await db.getChainTip();
1831+
let chainTip = await db.getChainTip(db.sql);
18321832
expect(chainTip.mempool_tx_count).toBe(1);
18331833

18341834
// Verify tx shows up in mempool (non-pruned)
@@ -1852,7 +1852,7 @@ describe('mempool tests', () => {
18521852
expect(mempoolResult2.body.results).toHaveLength(0);
18531853
const mempoolCount2 = await supertest(api.server).get(`/extended/v1/tx/mempool`);
18541854
expect(mempoolCount2.body.total).toBe(0);
1855-
chainTip = await db.getChainTip();
1855+
chainTip = await db.getChainTip(db.sql);
18561856
expect(chainTip.mempool_tx_count).toBe(0);
18571857

18581858
// Re-broadcast mempool tx
@@ -1865,7 +1865,7 @@ describe('mempool tests', () => {
18651865
expect(mempoolResult3.body.results[0].tx_id).toBe(txId);
18661866
const mempoolCount3 = await supertest(api.server).get(`/extended/v1/tx/mempool`);
18671867
expect(mempoolCount3.body.total).toBe(1);
1868-
chainTip = await db.getChainTip();
1868+
chainTip = await db.getChainTip(db.sql);
18691869
expect(chainTip.mempool_tx_count).toBe(1);
18701870

18711871
// Mine tx in block to prune from mempool
@@ -1898,7 +1898,7 @@ describe('mempool tests', () => {
18981898
expect(mempoolResult4.body.results).toHaveLength(0);
18991899
const mempoolCount4 = await supertest(api.server).get(`/extended/v1/tx/mempool`);
19001900
expect(mempoolCount4.body.total).toBe(0);
1901-
chainTip = await db.getChainTip();
1901+
chainTip = await db.getChainTip(db.sql);
19021902
expect(chainTip.mempool_tx_count).toBe(0);
19031903

19041904
// Verify tx is mined
@@ -1931,7 +1931,7 @@ describe('mempool tests', () => {
19311931
expect(mempoolResult5.body.results[0].tx_id).toBe(txId);
19321932
const mempoolCount5 = await supertest(api.server).get(`/extended/v1/tx/mempool`);
19331933
expect(mempoolCount5.body.total).toBe(1);
1934-
chainTip = await db.getChainTip();
1934+
chainTip = await db.getChainTip(db.sql);
19351935
expect(chainTip.mempool_tx_count).toBe(1);
19361936

19371937
// Re-broadcast mempool tx

0 commit comments

Comments
 (0)