Skip to content

Commit 5082566

Browse files
authored
Merge pull request #9677 from NishantBansal2003/conf-count
Expose confirmation count for pending 'channel open' transactions
2 parents d1d3a82 + 64a841b commit 5082566

File tree

14 files changed

+2928
-2151
lines changed

14 files changed

+2928
-2151
lines changed

chainntnfs/interface.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ type TxConfirmation struct {
203203
Block *wire.MsgBlock
204204
}
205205

206+
// TxUpdateInfo contains information about a transaction before it has reached
207+
// its required number of confirmations. Transactions are registered for
208+
// notification for a specific number of "required" confirmations, this struct
209+
// will update the caller incrementally after each new block is found as long as
210+
// the transaction is not yet fully regarded as confirmed.
211+
type TxUpdateInfo struct {
212+
// BlockHeight is the height of the block that contains the transaction.
213+
BlockHeight uint32
214+
215+
// NumConfsLeft is the number of confirmations left for the transaction
216+
// to be regarded as fully confirmed.
217+
NumConfsLeft uint32
218+
}
219+
206220
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
207221
// callers can be notified of: the instance the target txid reaches the targeted
208222
// number of confirmations, how many confirmations are left for the target txid
@@ -229,11 +243,12 @@ type ConfirmationEvent struct {
229243

230244
// Updates is a channel that will sent upon, at every incremental
231245
// confirmation, how many confirmations are left to declare the
232-
// transaction as fully confirmed.
246+
// transaction as fully confirmed, along with the height of the block
247+
// that contains the transaction.
233248
//
234249
// NOTE: This channel must be buffered with the number of required
235250
// confirmations.
236-
Updates chan uint32
251+
Updates chan TxUpdateInfo
237252

238253
// NegativeConf is a channel that will be sent upon if the transaction
239254
// confirms, but is later reorged out of the chain. The integer sent
@@ -262,7 +277,7 @@ func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
262277
// the channel so we need to create a larger buffer to avoid
263278
// blocking the notifier.
264279
Confirmed: make(chan *TxConfirmation, 1),
265-
Updates: make(chan uint32, numConfs),
280+
Updates: make(chan TxUpdateInfo, numConfs),
266281
NegativeConf: make(chan int32, 1),
267282
Done: make(chan struct{}, 1),
268283
Cancel: cancel,

chainntnfs/txnotifier.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -947,8 +947,12 @@ func (n *TxNotifier) dispatchConfDetails(
947947

948948
// We'll send a 0 value to the Updates channel,
949949
// indicating that the transaction/output script has already
950-
// been confirmed.
951-
err := n.notifyNumConfsLeft(ntfn, 0)
950+
// been confirmed, and include the block height at which the
951+
// transaction was included.
952+
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
953+
NumConfsLeft: 0,
954+
BlockHeight: details.BlockHeight,
955+
})
952956
if err != nil {
953957
return err
954958
}
@@ -977,7 +981,10 @@ func (n *TxNotifier) dispatchConfDetails(
977981
// confirmations are left for the transaction/output script to
978982
// be confirmed.
979983
numConfsLeft := confHeight - n.currentHeight
980-
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
984+
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
985+
NumConfsLeft: numConfsLeft,
986+
BlockHeight: details.BlockHeight,
987+
})
981988
if err != nil {
982989
return err
983990
}
@@ -1731,7 +1738,10 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
17311738
for confRequest := range confRequests {
17321739
confSet := n.confNotifications[confRequest]
17331740
for _, ntfn := range confSet.ntfns {
1734-
txConfHeight := confSet.details.BlockHeight +
1741+
// blockHeight is the height of the block which
1742+
// contains the transaction.
1743+
blockHeight := confSet.details.BlockHeight
1744+
txConfHeight := blockHeight +
17351745
ntfn.NumConfirmations - 1
17361746
numConfsLeft := txConfHeight - height
17371747

@@ -1744,7 +1754,10 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
17441754
continue
17451755
}
17461756

1747-
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
1757+
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
1758+
NumConfsLeft: numConfsLeft,
1759+
BlockHeight: blockHeight,
1760+
})
17481761
if err != nil {
17491762
return err
17501763
}
@@ -2011,6 +2024,20 @@ func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
20112024
if !ntfn.dispatched {
20122025
confHeight := heightDisconnected + ntfn.NumConfirmations - 1
20132026
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
2027+
2028+
// We also signal the reorg to the notifier in case the
2029+
// subscriber is also interested in the reorgs before the
2030+
// transaction received its required confirmation.
2031+
//
2032+
// Because as soon as a new block is connected which has the
2033+
// transaction included again we preemptively read the buffered
2034+
// channel.
2035+
select {
2036+
case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
2037+
case <-n.quit:
2038+
return ErrTxNotifierExiting
2039+
}
2040+
20142041
if exists {
20152042
delete(ntfnSet, ntfn)
20162043
}
@@ -2099,25 +2126,28 @@ func (n *TxNotifier) TearDown() {
20992126
}
21002127

21012128
// notifyNumConfsLeft sends the number of confirmations left to the
2102-
// notification subscriber through the Event.Updates channel.
2129+
// notification subscriber through the Event.Updates channel, along with the
2130+
// block height in which the transaction was included.
21032131
//
21042132
// NOTE: must be used with the TxNotifier's lock held.
2105-
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn, num uint32) error {
2133+
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn,
2134+
info TxUpdateInfo) error {
2135+
21062136
// If the number left is no less than the recorded value, we can skip
21072137
// sending it as it means this same value has already been sent before.
2108-
if num >= ntfn.numConfsLeft {
2138+
if info.NumConfsLeft >= ntfn.numConfsLeft {
21092139
Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
2110-
"request %v conf_id=%v", num, ntfn.ConfRequest,
2111-
ntfn.ConfID)
2140+
"request %v conf_id=%v", info.NumConfsLeft,
2141+
ntfn.ConfRequest, ntfn.ConfID)
21122142

21132143
return nil
21142144
}
21152145

21162146
// Update the number of confirmations left to the notification.
2117-
ntfn.numConfsLeft = num
2147+
ntfn.numConfsLeft = info.NumConfsLeft
21182148

21192149
select {
2120-
case ntfn.Event.Updates <- num:
2150+
case ntfn.Event.Updates <- info:
21212151
case <-n.quit:
21222152
return ErrTxNotifierExiting
21232153
}

chainntnfs/txnotifier_test.go

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
271271
// We should only receive one update for tx1 since it only requires
272272
// one confirmation and it already met it.
273273
select {
274-
case numConfsLeft := <-ntfn1.Event.Updates:
275-
const expected = 0
276-
if numConfsLeft != expected {
277-
t.Fatalf("Received incorrect confirmation update: tx1 "+
278-
"expected %d confirmations left, got %d",
279-
expected, numConfsLeft)
274+
case updDetails := <-ntfn1.Event.Updates:
275+
expected := chainntnfs.TxUpdateInfo{
276+
NumConfsLeft: 0,
277+
BlockHeight: 11,
280278
}
279+
require.Equal(t, expected, updDetails)
281280
default:
282281
t.Fatal("Expected confirmation update for tx1")
283282
}
@@ -300,13 +299,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
300299
// We should only receive one update for tx2 since it only has one
301300
// confirmation so far and it requires two.
302301
select {
303-
case numConfsLeft := <-ntfn2.Event.Updates:
304-
const expected = 1
305-
if numConfsLeft != expected {
306-
t.Fatalf("Received incorrect confirmation update: tx2 "+
307-
"expected %d confirmations left, got %d",
308-
expected, numConfsLeft)
302+
case updDetails := <-ntfn2.Event.Updates:
303+
expected := chainntnfs.TxUpdateInfo{
304+
NumConfsLeft: 1,
305+
BlockHeight: 11,
309306
}
307+
require.Equal(t, expected, updDetails)
310308
default:
311309
t.Fatal("Expected confirmation update for tx2")
312310
}
@@ -341,13 +339,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
341339
// We should only receive one update since the last at the new height,
342340
// indicating how many confirmations are still left.
343341
select {
344-
case numConfsLeft := <-ntfn2.Event.Updates:
345-
const expected = 0
346-
if numConfsLeft != expected {
347-
t.Fatalf("Received incorrect confirmation update: tx2 "+
348-
"expected %d confirmations left, got %d",
349-
expected, numConfsLeft)
342+
case updDetails := <-ntfn2.Event.Updates:
343+
expected := chainntnfs.TxUpdateInfo{
344+
NumConfsLeft: 0,
345+
BlockHeight: 11,
350346
}
347+
require.Equal(t, expected, updDetails)
351348
default:
352349
t.Fatal("Expected confirmation update for tx2")
353350
}
@@ -411,13 +408,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
411408
err = n.UpdateConfDetails(ntfn1.HistoricalDispatch.ConfRequest, &txConf1)
412409
require.NoError(t, err, "unable to update conf details")
413410
select {
414-
case numConfsLeft := <-ntfn1.Event.Updates:
415-
const expected = 0
416-
if numConfsLeft != expected {
417-
t.Fatalf("Received incorrect confirmation update: tx1 "+
418-
"expected %d confirmations left, got %d",
419-
expected, numConfsLeft)
411+
case updDetails := <-ntfn1.Event.Updates:
412+
expected := chainntnfs.TxUpdateInfo{
413+
NumConfsLeft: 0,
414+
BlockHeight: 9,
420415
}
416+
require.Equal(t, expected, updDetails)
421417
default:
422418
t.Fatal("Expected confirmation update for tx1")
423419
}
@@ -443,13 +439,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
443439
err = n.UpdateConfDetails(ntfn2.HistoricalDispatch.ConfRequest, &txConf2)
444440
require.NoError(t, err, "unable to update conf details")
445441
select {
446-
case numConfsLeft := <-ntfn2.Event.Updates:
447-
const expected = 1
448-
if numConfsLeft != expected {
449-
t.Fatalf("Received incorrect confirmation update: tx2 "+
450-
"expected %d confirmations left, got %d",
451-
expected, numConfsLeft)
442+
case updDetails := <-ntfn2.Event.Updates:
443+
expected := chainntnfs.TxUpdateInfo{
444+
NumConfsLeft: 1,
445+
BlockHeight: 9,
452446
}
447+
require.Equal(t, expected, updDetails)
453448
default:
454449
t.Fatal("Expected confirmation update for tx2")
455450
}
@@ -485,13 +480,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
485480
// We should only receive one update for tx2 since the last one,
486481
// indicating how many confirmations are still left.
487482
select {
488-
case numConfsLeft := <-ntfn2.Event.Updates:
489-
const expected = 0
490-
if numConfsLeft != expected {
491-
t.Fatalf("Received incorrect confirmation update: tx2 "+
492-
"expected %d confirmations left, got %d",
493-
expected, numConfsLeft)
483+
case updDetails := <-ntfn2.Event.Updates:
484+
expected := chainntnfs.TxUpdateInfo{
485+
NumConfsLeft: 0,
486+
BlockHeight: 9,
494487
}
488+
require.Equal(t, expected, updDetails)
495489
default:
496490
t.Fatal("Expected confirmation update for tx2")
497491
}
@@ -1490,13 +1484,12 @@ func TestTxNotifierConfReorg(t *testing.T) {
14901484
// We should only receive one update for tx2 since it only requires
14911485
// one confirmation and it already met it.
14921486
select {
1493-
case numConfsLeft := <-ntfn2.Event.Updates:
1494-
const expected = 0
1495-
if numConfsLeft != expected {
1496-
t.Fatalf("Received incorrect confirmation update: tx2 "+
1497-
"expected %d confirmations left, got %d",
1498-
expected, numConfsLeft)
1487+
case updDetails := <-ntfn2.Event.Updates:
1488+
expected := chainntnfs.TxUpdateInfo{
1489+
NumConfsLeft: 0,
1490+
BlockHeight: 12,
14991491
}
1492+
require.Equal(t, expected, updDetails)
15001493
default:
15011494
t.Fatal("Expected confirmation update for tx2")
15021495
}
@@ -1520,15 +1513,14 @@ func TestTxNotifierConfReorg(t *testing.T) {
15201513
// confirmations and it has already met them.
15211514
for i := uint32(1); i <= 2; i++ {
15221515
select {
1523-
case numConfsLeft := <-ntfn3.Event.Updates:
1524-
expected := tx3NumConfs - i
1525-
if numConfsLeft != expected {
1526-
t.Fatalf("Received incorrect confirmation update: tx3 "+
1527-
"expected %d confirmations left, got %d",
1528-
expected, numConfsLeft)
1516+
case updDetails := <-ntfn3.Event.Updates:
1517+
expected := chainntnfs.TxUpdateInfo{
1518+
NumConfsLeft: tx3NumConfs - i,
1519+
BlockHeight: 12,
15291520
}
1521+
require.Equal(t, expected, updDetails)
15301522
default:
1531-
t.Fatal("Expected confirmation update for tx2")
1523+
t.Fatal("Expected confirmation update for tx3")
15321524
}
15331525
}
15341526

@@ -1548,6 +1540,56 @@ func TestTxNotifierConfReorg(t *testing.T) {
15481540
}
15491541
}
15501542

1543+
// TestTxNotifierReorgPartialConfirmation ensures that a tx with intermediate
1544+
// confirmations handles a reorg correctly and emits the appropriate reorg ntfn.
1545+
func TestTxNotifierReorgPartialConfirmation(t *testing.T) {
1546+
t.Parallel()
1547+
1548+
const txNumConfs uint32 = 2
1549+
hintCache := newMockHintCache()
1550+
n := chainntnfs.NewTxNotifier(
1551+
7, chainntnfs.ReorgSafetyLimit, hintCache, hintCache,
1552+
)
1553+
1554+
// Tx will be confirmed in block 9 and requires 2 confs.
1555+
tx := wire.MsgTx{Version: 1}
1556+
tx.AddTxOut(&wire.TxOut{PkScript: testRawScript})
1557+
txHash := tx.TxHash()
1558+
ntfn, err := n.RegisterConf(&txHash, testRawScript, txNumConfs, 1)
1559+
require.NoError(t, err, "unable to register ntfn")
1560+
1561+
err = n.UpdateConfDetails(ntfn.HistoricalDispatch.ConfRequest, nil)
1562+
require.NoError(t, err, "unable to deliver conf details")
1563+
1564+
// Mine 1 block to satisfy the requirement for a partially confirmed tx.
1565+
block := btcutil.NewBlock(&wire.MsgBlock{
1566+
Transactions: []*wire.MsgTx{&tx},
1567+
})
1568+
err = n.ConnectTip(block, 8)
1569+
require.NoError(t, err, "failed to connect block")
1570+
err = n.NotifyHeight(8)
1571+
require.NoError(t, err, "unable to dispatch notifications")
1572+
1573+
// Now that the transaction is partially confirmed, reorg out those
1574+
// blocks.
1575+
err = n.DisconnectTip(8)
1576+
require.NoError(t, err, "unable to disconnect block")
1577+
1578+
// After the intermediate confirmation is reorged out, the tx should not
1579+
// trigger a confirmation ntfn, but should trigger a reorg ntfn.
1580+
select {
1581+
case <-ntfn.Event.Confirmed:
1582+
t.Fatal("unexpected confirmation after reorg")
1583+
default:
1584+
}
1585+
1586+
select {
1587+
case <-ntfn.Event.NegativeConf:
1588+
default:
1589+
t.Fatal("expected to receive reorg notification")
1590+
}
1591+
}
1592+
15511593
// TestTxNotifierSpendReorg ensures that clients are notified of a reorg when
15521594
// the spending transaction of an outpoint for which they registered a spend
15531595
// notification for has been reorged out of the chain.

0 commit comments

Comments
 (0)