Skip to content

Commit 8723113

Browse files
committed
itest: RegisterSpendNtfn detects reorgs
Added itest "reorg_notifications" which tests that RegisterSpendNtfn behaves as expected during a reorg. A reorg notification is produced after a reorg affects the block which has produced a spending notification for this registration.
1 parent 2e36f9b commit 8723113

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ var allTestCases = []*lntest.TestCase{
4646
Name: "send selected coins channel reserve",
4747
TestFunc: testSendSelectedCoinsChannelReserve,
4848
},
49+
{
50+
Name: "reorg notifications",
51+
TestFunc: testReorgNotifications,
52+
},
4953
{
5054
Name: "disconnecting target peer",
5155
TestFunc: testDisconnectingTargetPeer,

itest/lnd_misc_test.go

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package itest
22

33
import (
4+
"context"
45
"encoding/hex"
56
"fmt"
67
"os"
78

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

0 commit comments

Comments
 (0)