@@ -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