From aa9c3272e32bcc3ce76667ac4914426d2b5d5d81 Mon Sep 17 00:00:00 2001 From: Nishant Bansal Date: Thu, 11 Sep 2025 18:24:54 +0530 Subject: [PATCH] chainntnfs: signal reorg notification before required confirmations In this commit, we send the reorg notification even when the transaction has not yet reached the required confirmations, in case the caller is interested in knowing about it. Signed-off-by: Nishant Bansal --- chainntnfs/txnotifier.go | 14 ++++++++++ chainntnfs/txnotifier_test.go | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/chainntnfs/txnotifier.go b/chainntnfs/txnotifier.go index 8b5b8dc8b..9055d8567 100644 --- a/chainntnfs/txnotifier.go +++ b/chainntnfs/txnotifier.go @@ -2011,6 +2011,20 @@ func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn, if !ntfn.dispatched { confHeight := heightDisconnected + ntfn.NumConfirmations - 1 ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight] + + // We also signal the reorg to the notifier in case the + // subscriber is also interested in the reorgs before the + // transaction received its required confirmation. + // + // Because as soon as a new block is connected which has the + // transaction included again we preemptively read the buffered + // channel. + select { + case ntfn.Event.NegativeConf <- int32(n.reorgDepth): + case <-n.quit: + return ErrTxNotifierExiting + } + if exists { delete(ntfnSet, ntfn) } diff --git a/chainntnfs/txnotifier_test.go b/chainntnfs/txnotifier_test.go index c7bc8da50..1279330cf 100644 --- a/chainntnfs/txnotifier_test.go +++ b/chainntnfs/txnotifier_test.go @@ -1548,6 +1548,56 @@ func TestTxNotifierConfReorg(t *testing.T) { } } +// TestTxNotifierReorgPartialConfirmation ensures that a tx with intermediate +// confirmations handles a reorg correctly and emits the appropriate reorg ntfn. +func TestTxNotifierReorgPartialConfirmation(t *testing.T) { + t.Parallel() + + const txNumConfs uint32 = 2 + hintCache := newMockHintCache() + n := chainntnfs.NewTxNotifier( + 7, chainntnfs.ReorgSafetyLimit, hintCache, hintCache, + ) + + // Tx will be confirmed in block 9 and requires 2 confs. + tx := wire.MsgTx{Version: 1} + tx.AddTxOut(&wire.TxOut{PkScript: testRawScript}) + txHash := tx.TxHash() + ntfn, err := n.RegisterConf(&txHash, testRawScript, txNumConfs, 1) + require.NoError(t, err, "unable to register ntfn") + + err = n.UpdateConfDetails(ntfn.HistoricalDispatch.ConfRequest, nil) + require.NoError(t, err, "unable to deliver conf details") + + // Mine 1 block to satisfy the requirement for a partially confirmed tx. + block := btcutil.NewBlock(&wire.MsgBlock{ + Transactions: []*wire.MsgTx{&tx}, + }) + err = n.ConnectTip(block, 8) + require.NoError(t, err, "failed to connect block") + err = n.NotifyHeight(8) + require.NoError(t, err, "unable to dispatch notifications") + + // Now that the transaction is partially confirmed, reorg out those + // blocks. + err = n.DisconnectTip(8) + require.NoError(t, err, "unable to disconnect block") + + // After the intermediate confirmation is reorged out, the tx should not + // trigger a confirmation ntfn, but should trigger a reorg ntfn. + select { + case <-ntfn.Event.Confirmed: + t.Fatal("unexpected confirmation after reorg") + default: + } + + select { + case <-ntfn.Event.NegativeConf: + default: + t.Fatal("expected to receive reorg notification") + } +} + // TestTxNotifierSpendReorg ensures that clients are notified of a reorg when // the spending transaction of an outpoint for which they registered a spend // notification for has been reorged out of the chain.