Skip to content

Commit 8dc3f97

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 6f09d96 commit 8dc3f97

File tree

2 files changed

+208
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)