@@ -4,15 +4,18 @@ import (
4
4
"encoding/hex"
5
5
"fmt"
6
6
"os"
7
+ "time"
7
8
8
9
"github.com/btcsuite/btcd/btcutil"
10
+ "github.com/btcsuite/btcd/chaincfg/chainhash"
9
11
"github.com/btcsuite/btcd/txscript"
10
12
"github.com/btcsuite/btcd/wire"
11
13
"github.com/btcsuite/btcwallet/wallet"
12
14
"github.com/lightningnetwork/lnd/funding"
13
15
"github.com/lightningnetwork/lnd/input"
14
16
"github.com/lightningnetwork/lnd/lncfg"
15
17
"github.com/lightningnetwork/lnd/lnrpc"
18
+ "github.com/lightningnetwork/lnd/lnrpc/chainrpc"
16
19
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
17
20
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
18
21
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
@@ -1391,3 +1394,195 @@ func testGRPCNotFound(ht *lntest.HarnessTest) {
1391
1394
RHash : rHash ,
1392
1395
}, notFoundErr )
1393
1396
}
1397
+
1398
+ // testReorgNotifications tests that RegisterSpendNtfn behaves as expected
1399
+ // during a reorg. A reorg notification is produced after a reorg affects the
1400
+ // block which has produced a spending notification for this registration.
1401
+ func testReorgNotifications (ht * lntest.HarnessTest ) {
1402
+ alice := ht .NewNodeWithCoins ("Alice" , nil )
1403
+ bob := ht .NewNode ("Bob" , nil )
1404
+
1405
+ const tx1Amount = 1_000_000
1406
+
1407
+ // Alice will send coins to herself, Bob will watch spending and
1408
+ // confirmation of the transaction. We make sure that a node can watch
1409
+ // transactions which are not a part of its wallet.
1410
+ respAddr := alice .RPC .NewAddress (& lnrpc.NewAddressRequest {
1411
+ Type : lnrpc .AddressType_TAPROOT_PUBKEY ,
1412
+ })
1413
+ txid1Str := alice .RPC .SendCoins (& lnrpc.SendCoinsRequest {
1414
+ Addr : respAddr .Address ,
1415
+ Amount : tx1Amount ,
1416
+ SatPerVbyte : 2 ,
1417
+ }).Txid
1418
+ txid1 , err := chainhash .NewHashFromStr (txid1Str )
1419
+ require .NoError (ht , err )
1420
+ tx1 := ht .AssertTxInMempool (* txid1 )
1421
+
1422
+ // Find the output of tx1.
1423
+ tx1OutIndex := - 1
1424
+ for i , txOut := range tx1 .TxOut {
1425
+ if txOut .Value == tx1Amount {
1426
+ tx1OutIndex = i
1427
+ }
1428
+ }
1429
+ require .NotEqual (ht , - 1 , tx1OutIndex )
1430
+ tx1op := wire.OutPoint {
1431
+ Hash : * txid1 ,
1432
+ Index : uint32 (tx1OutIndex ),
1433
+ }
1434
+ tx1opLnrpc := & lnrpc.OutPoint {
1435
+ TxidStr : txid1Str ,
1436
+ OutputIndex : uint32 (tx1OutIndex ),
1437
+ }
1438
+ tx1opChainrpc := & chainrpc.Outpoint {
1439
+ Hash : txid1 [:],
1440
+ Index : uint32 (tx1OutIndex ),
1441
+ }
1442
+ pkscript := tx1 .TxOut [tx1OutIndex ].PkScript
1443
+
1444
+ // Now fee bump the output of the first transaction.
1445
+ alice .RPC .BumpFee (& walletrpc.BumpFeeRequest {
1446
+ Outpoint : tx1opLnrpc ,
1447
+ Immediate : true ,
1448
+ SatPerVbyte : 20 ,
1449
+ })
1450
+
1451
+ // Now find the fee bump tx.
1452
+ listSweepsReq := & walletrpc.ListSweepsRequest {
1453
+ Verbose : true ,
1454
+
1455
+ // startHeight -1 means include only unconfirmed.
1456
+ StartHeight : - 1 ,
1457
+ }
1458
+ var tx2aLnrpc * lnrpc.Transaction
1459
+ require .NoError (ht , wait .NoError (func () error {
1460
+ sweepsResp := alice .RPC .ListSweeps (listSweepsReq )
1461
+ sweepsDetails := sweepsResp .GetTransactionDetails ()
1462
+ if sweepsDetails == nil {
1463
+ return fmt .Errorf ("no sweep details" )
1464
+ }
1465
+ if len (sweepsDetails .Transactions ) != 1 {
1466
+ return fmt .Errorf ("got %d sweeps, want %d" ,
1467
+ len (sweepsDetails .Transactions ), 1 )
1468
+ }
1469
+ tx2aLnrpc = sweepsDetails .Transactions [0 ]
1470
+
1471
+ return nil
1472
+ }, defaultTimeout ))
1473
+ require .Len (ht , tx2aLnrpc .PreviousOutpoints , 1 )
1474
+ require .Equal (
1475
+ ht , tx1op .String (), tx2aLnrpc .PreviousOutpoints [0 ].Outpoint ,
1476
+ )
1477
+ txid2a , err := chainhash .NewHashFromStr (tx2aLnrpc .TxHash )
1478
+ require .NoError (ht , err )
1479
+ tx2a := ht .AssertTxInMempool (* txid2a )
1480
+
1481
+ // Fee bump the output of the first transaction agin with a higher fee
1482
+ // rate to get RBF transaction tx2b.
1483
+ alice .RPC .BumpFee (& walletrpc.BumpFeeRequest {
1484
+ Outpoint : tx1opLnrpc ,
1485
+ Immediate : true ,
1486
+ SatPerVbyte : 200 ,
1487
+ })
1488
+ var tx2bLnrpc * lnrpc.Transaction
1489
+ require .NoError (ht , wait .NoError (func () error {
1490
+ sweepsResp := alice .RPC .ListSweeps (listSweepsReq )
1491
+ sweepsDetails := sweepsResp .GetTransactionDetails ()
1492
+ if sweepsDetails == nil {
1493
+ return fmt .Errorf ("no sweep details" )
1494
+ }
1495
+ for _ , tx := range sweepsDetails .Transactions {
1496
+ if tx .TxHash != tx2aLnrpc .TxHash {
1497
+ tx2bLnrpc = tx
1498
+ break
1499
+ }
1500
+ }
1501
+ if tx2bLnrpc == nil {
1502
+ return fmt .Errorf ("tx2aLnrpc hasn't been replaced yet" )
1503
+ }
1504
+
1505
+ return nil
1506
+ }, defaultTimeout ))
1507
+ require .Len (ht , tx2bLnrpc .PreviousOutpoints , 1 )
1508
+ require .Equal (
1509
+ ht , tx1op .String (), tx2bLnrpc .PreviousOutpoints [0 ].Outpoint ,
1510
+ )
1511
+ txid2b , err := chainhash .NewHashFromStr (tx2bLnrpc .TxHash )
1512
+ require .NoError (ht , err )
1513
+ tx2b := ht .AssertTxInMempool (* txid2b )
1514
+
1515
+ // Mine tx1 only.
1516
+ ht .Miner ().MineBlockWithTxes ([]* btcutil.Tx {btcutil .NewTx (tx1 )})
1517
+
1518
+ // Bob starts watching spending of tx1op.
1519
+ spendClient := bob .RPC .RegisterSpendNtfn (& chainrpc.SpendRequest {
1520
+ Outpoint : tx1opChainrpc ,
1521
+ Script : pkscript ,
1522
+ HeightHint : ht .CurrentHeight (),
1523
+ })
1524
+
1525
+ // Mine tx2b.
1526
+ block1 := ht .Miner ().MineBlockWithTxes (
1527
+ []* btcutil.Tx {btcutil .NewTx (tx2b )},
1528
+ )
1529
+
1530
+ // Make sure RegisterSpendNtfn noticed the spending. Give 10s to receive
1531
+ // the notification.
1532
+ spendReceived := make (chan struct {})
1533
+ go func () {
1534
+ spendMsg , err := spendClient .Recv ()
1535
+ require .NoError (ht , err )
1536
+ spendDetails := spendMsg .GetSpend ()
1537
+ require .NotNil (ht , spendDetails )
1538
+ require .Equal (ht , txid2b [:], spendDetails .SpendingTxHash )
1539
+ close (spendReceived )
1540
+ }()
1541
+ select {
1542
+ case <- time .After (10 * time .Second ):
1543
+ ht .Fatal ("Failed to receive spending notification" )
1544
+ case <- spendReceived :
1545
+ }
1546
+
1547
+ // Reorg block1.
1548
+ blockHash1 := block1 .Header .BlockHash ()
1549
+ require .NoError (ht , ht .Miner ().Client .InvalidateBlock (& blockHash1 ))
1550
+
1551
+ // Mine empty blocks to evict block1 in bitcoin backend (e.g. bitcoind).
1552
+ ht .Miner ().MineEmptyBlocks (2 )
1553
+
1554
+ // Make sure RegisterSpendNtfn noticed the reorg. Transaction tx2b was
1555
+ // just unconfirmed. Give 10s to receive the notification.
1556
+ reorgReceived := make (chan struct {})
1557
+ go func () {
1558
+ spendMsg , err := spendClient .Recv ()
1559
+ require .NoError (ht , err )
1560
+ require .NotNil (ht , spendMsg .GetReorg ())
1561
+ close (reorgReceived )
1562
+ }()
1563
+ select {
1564
+ case <- time .After (10 * time .Second ):
1565
+ ht .Fatal ("Failed to receive reorg notification" )
1566
+ case <- reorgReceived :
1567
+ }
1568
+
1569
+ // Mine tx2a to confirm a different version of spending.
1570
+ ht .Miner ().MineBlockWithTxes ([]* btcutil.Tx {btcutil .NewTx (tx2a )})
1571
+
1572
+ // Make sure RegisterSpendNtfn noticed the spending. Give 10s to receive
1573
+ // the notification.
1574
+ spendReceived = make (chan struct {})
1575
+ go func () {
1576
+ spendMsg , err := spendClient .Recv ()
1577
+ require .NoError (ht , err )
1578
+ spendDetails := spendMsg .GetSpend ()
1579
+ require .NotNil (ht , spendDetails )
1580
+ require .Equal (ht , txid2a [:], spendDetails .SpendingTxHash )
1581
+ close (spendReceived )
1582
+ }()
1583
+ select {
1584
+ case <- time .After (10 * time .Second ):
1585
+ ht .Fatal ("Failed to receive spending notification" )
1586
+ case <- spendReceived :
1587
+ }
1588
+ }
0 commit comments