Skip to content

Commit bfd5c2b

Browse files
committed
test re-orgs
1 parent 3d83728 commit bfd5c2b

File tree

2 files changed

+127
-9
lines changed

2 files changed

+127
-9
lines changed

src/datastore/pg-write-store.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,10 +1824,12 @@ export class PgWriteStore extends PgStore {
18241824
`;
18251825
assert(res.count === batch.length, `Expecting ${batch.length} inserts, got ${res.count}`);
18261826
}
1827-
// Update contract_log_counts
1827+
// Update contract_log_counts (only for canonical events)
18281828
const countDeltas = new Map<string, number>();
18291829
for (const v of values) {
1830-
countDeltas.set(v.contract_identifier, (countDeltas.get(v.contract_identifier) ?? 0) + 1);
1830+
if (v.canonical) {
1831+
countDeltas.set(v.contract_identifier, (countDeltas.get(v.contract_identifier) ?? 0) + 1);
1832+
}
18311833
}
18321834
for (const [contractId, count] of countDeltas) {
18331835
await sql`
@@ -1858,13 +1860,15 @@ export class PgWriteStore extends PgStore {
18581860
await sql`
18591861
INSERT INTO contract_logs ${sql(values)}
18601862
`;
1861-
// Update contract_log_counts
1862-
await sql`
1863-
INSERT INTO contract_log_counts (contract_identifier, count)
1864-
VALUES (${event.contract_identifier}, 1)
1865-
ON CONFLICT (contract_identifier)
1866-
DO UPDATE SET count = contract_log_counts.count + 1
1867-
`;
1863+
// Update contract_log_counts (only for canonical events)
1864+
if (event.canonical) {
1865+
await sql`
1866+
INSERT INTO contract_log_counts (contract_identifier, count)
1867+
VALUES (${event.contract_identifier}, 1)
1868+
ON CONFLICT (contract_identifier)
1869+
DO UPDATE SET count = contract_log_counts.count + 1
1870+
`;
1871+
}
18681872
}
18691873

18701874
async updatePoxSetsBatch(sql: PgSqlClient, block: DbBlock, poxSet: DbPoxSetSigners) {

tests/api/smart-contracts/print-events.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,118 @@ describe('smart contract print events v2', () => {
286286
);
287287
assert.equal(body.total, 3);
288288
});
289+
290+
test('contract_log_counts stays consistent across re-orgs', async () => {
291+
const ibh = (i: number) => `0x${i.toString().padStart(64, '0')}`;
292+
293+
// Block 1: 2 print events on the canonical chain
294+
await db.update(
295+
new TestBlockBuilder({
296+
block_height: 1,
297+
index_block_hash: ibh(1),
298+
parent_index_block_hash: ibh(0),
299+
})
300+
.addTx({ tx_id: ibh(0x1001) })
301+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
302+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
303+
.build()
304+
);
305+
306+
// Block 2: 3 print events on the canonical chain
307+
await db.update(
308+
new TestBlockBuilder({
309+
block_height: 2,
310+
index_block_hash: ibh(2),
311+
parent_index_block_hash: ibh(1),
312+
})
313+
.addTx({ tx_id: ibh(0x2001) })
314+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
315+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
316+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
317+
.build()
318+
);
319+
320+
// Block 3: 1 print event on the canonical chain
321+
await db.update(
322+
new TestBlockBuilder({
323+
block_height: 3,
324+
index_block_hash: ibh(3),
325+
parent_index_block_hash: ibh(2),
326+
})
327+
.addTx({ tx_id: ibh(0x3001) })
328+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
329+
.build()
330+
);
331+
332+
// Verify: 2 + 3 + 1 = 6 events total
333+
let countResult = await client`
334+
SELECT count FROM contract_log_counts WHERE contract_identifier = ${CONTRACT_ID}
335+
`;
336+
assert.equal(countResult[0].count, 6);
337+
let { body } = await supertest(api.server).get(
338+
`/extended/v2/smart-contracts/${CONTRACT_ID}/print-events`
339+
);
340+
assert.equal(body.total, 6);
341+
assert.equal(body.results.length, 6);
342+
343+
// --- Trigger a re-org ---
344+
// Create an alternative block 2b (fork at height 2) that builds on block 1.
345+
// This goes in as non-canonical because it doesn't extend past the current tip (height 3).
346+
await db.update(
347+
new TestBlockBuilder({
348+
block_height: 2,
349+
index_block_hash: ibh(0xb2),
350+
parent_index_block_hash: ibh(1),
351+
})
352+
.addTx({ tx_id: ibh(0xb201) })
353+
.addTxContractLogEvent({ contract_identifier: CONTRACT_ID, topic: 'print' })
354+
.build()
355+
);
356+
357+
// Block 3b on the fork
358+
await db.update(
359+
new TestBlockBuilder({
360+
block_height: 3,
361+
index_block_hash: ibh(0xb3),
362+
parent_index_block_hash: ibh(0xb2),
363+
})
364+
.addTx({ tx_id: ibh(0xb301) })
365+
.build()
366+
);
367+
368+
// Block 4b extends the fork past the original tip (height 3) — triggers re-org.
369+
// Original blocks 2 and 3 become non-canonical (losing 3 + 1 = 4 events).
370+
// Fork blocks 2b and 3b become canonical (gaining 1 + 0 = 1 event).
371+
await db.update(
372+
new TestBlockBuilder({
373+
block_height: 4,
374+
index_block_hash: ibh(0xb4),
375+
parent_index_block_hash: ibh(0xb3),
376+
})
377+
.addTx({ tx_id: ibh(0xb401) })
378+
.build()
379+
);
380+
381+
// After re-org: block 1 (2 events) + block 2b (1 event) = 3 canonical events
382+
countResult = await client`
383+
SELECT count FROM contract_log_counts WHERE contract_identifier = ${CONTRACT_ID}
384+
`;
385+
assert.equal(countResult[0].count, 3);
386+
387+
// API should reflect the same total
388+
({ body } = await supertest(api.server).get(
389+
`/extended/v2/smart-contracts/${CONTRACT_ID}/print-events`
390+
));
391+
assert.equal(body.total, 3);
392+
assert.equal(body.results.length, 3);
393+
394+
// Verify the remaining events come from the correct blocks/txs
395+
const txIds = body.results.map((e: { tx_id: string }) => e.tx_id);
396+
// Block 2b event + block 1 events (newest first)
397+
assert.ok(txIds.includes(ibh(0xb201)), 'should include fork block 2b tx');
398+
assert.ok(txIds.includes(ibh(0x1001)), 'should include original block 1 tx');
399+
// Original block 2 and 3 txs should NOT be present
400+
assert.ok(!txIds.includes(ibh(0x2001)), 'should not include orphaned block 2 tx');
401+
assert.ok(!txIds.includes(ibh(0x3001)), 'should not include orphaned block 3 tx');
402+
});
289403
});

0 commit comments

Comments
 (0)