Skip to content

Commit 24fdae7

Browse files
authored
Merge pull request lightningnetwork#9693 from yyforyongyu/debug-listunspent
Fix inaccurate `locked_balance`
2 parents 7381f4b + 3d69d70 commit 24fdae7

File tree

8 files changed

+159
-33
lines changed

8 files changed

+159
-33
lines changed

docs/release-notes/release-notes-0.19.0.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@
101101
than once.
102102

103103
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9609) a bug that may
104-
cause `listunspent` to give inaccurate wallet UTXOs.
104+
cause `listunspent` to give inaccurate wallet UTXOs and
105+
[`locked_balance`](https://github.com/lightningnetwork/lnd/pull/9693).
105106

106107
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9626) a bug where a
107108
keysend payment would not fail properly and only resolve after restart. Now

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ require (
1111
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
1212
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
1313
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
14-
github.com/btcsuite/btcwallet v0.16.13-0.20250409141346-8e790bfb5832
14+
github.com/btcsuite/btcwallet v0.16.13
1515
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
1616
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
1717
github.com/btcsuite/btcwallet/walletdb v1.5.1
18-
github.com/btcsuite/btcwallet/wtxmgr v1.5.5
18+
github.com/btcsuite/btcwallet/wtxmgr v1.5.6
1919
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
2020
github.com/davecgh/go-spew v1.1.1
2121
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
6262
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
6363
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
6464
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
65-
github.com/btcsuite/btcwallet v0.16.13-0.20250409141346-8e790bfb5832 h1:Nd5r1YDNN47hn2UVyOcIJjNRvA90bpUrSVp8wnPMa18=
66-
github.com/btcsuite/btcwallet v0.16.13-0.20250409141346-8e790bfb5832/go.mod h1:Lpr6jNoTiWlgaXqFP+ar2YWSiD6Fl8xlh99WPvuErS4=
65+
github.com/btcsuite/btcwallet v0.16.13 h1:JGu+wrihQ0I00ODb3w92JtBPbrHxZhbcvU01O+e+lKw=
66+
github.com/btcsuite/btcwallet v0.16.13/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
6767
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
6868
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
6969
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
@@ -72,8 +72,8 @@ github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFX
7272
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw=
7373
github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI=
7474
github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs=
75-
github.com/btcsuite/btcwallet/wtxmgr v1.5.5 h1:VA/rHzAjiNuySPcKgdX3uAywbVczZlG5OZeSU7jYoZo=
76-
github.com/btcsuite/btcwallet/wtxmgr v1.5.5/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA=
75+
github.com/btcsuite/btcwallet/wtxmgr v1.5.6 h1:Zwvr/rrJYdOLqdBCSr4eICEstnEA+NBUvjIWLkrXaYI=
76+
github.com/btcsuite/btcwallet/wtxmgr v1.5.6/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA=
7777
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
7878
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
7979
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=

itest/list_on_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,9 @@ func init() {
755755
allTestCases = appendPrefixed(
756756
"channel force close", allTestCases, channelForceCloseTestCases,
757757
)
758+
allTestCases = appendPrefixed(
759+
"wallet", allTestCases, walletTestCases,
760+
)
758761

759762
// Prepare the test cases for windows to exclude some of the flaky
760763
// ones.

itest/lnd_wallet.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package itest
2+
3+
import (
4+
"github.com/btcsuite/btcd/btcutil"
5+
"github.com/btcsuite/btcd/wire"
6+
"github.com/lightningnetwork/lnd/lntest"
7+
"github.com/lightningnetwork/lnd/lntest/node"
8+
"github.com/lightningnetwork/lnd/lnwallet"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
// walletTestCases defines a set of tests aiming at asserting functionalities
13+
// provided by the wallerpc.
14+
var walletTestCases = []*lntest.TestCase{
15+
{
16+
Name: "listunspent P2WPKH",
17+
TestFunc: func(ht *lntest.HarnessTest) {
18+
runTestListUnspent(ht, ht.FundCoins)
19+
},
20+
},
21+
{
22+
Name: "listunspent NP2WPKH",
23+
TestFunc: func(ht *lntest.HarnessTest) {
24+
runTestListUnspent(ht, ht.FundCoinsNP2WKH)
25+
},
26+
},
27+
{
28+
Name: "listunspent P2TR",
29+
TestFunc: func(ht *lntest.HarnessTest) {
30+
runTestListUnspent(ht, ht.FundCoinsP2TR)
31+
},
32+
},
33+
{
34+
Name: "listunspent P2WPKH restart",
35+
TestFunc: func(ht *lntest.HarnessTest) {
36+
runTestListUnspentRestart(ht, ht.FundCoins)
37+
},
38+
},
39+
{
40+
Name: "listunspent NP2WPKH restart",
41+
TestFunc: func(ht *lntest.HarnessTest) {
42+
runTestListUnspentRestart(ht, ht.FundCoinsNP2WKH)
43+
},
44+
},
45+
{
46+
Name: "listunspent P2TR restart",
47+
TestFunc: func(ht *lntest.HarnessTest) {
48+
runTestListUnspentRestart(ht, ht.FundCoinsP2TR)
49+
},
50+
},
51+
}
52+
53+
type fundMethod func(amt btcutil.Amount, hn *node.HarnessNode) *wire.MsgTx
54+
55+
// testListUnspent checks that once Alice sends all coins to Bob, her wallet
56+
// balances are updated and the ListUnspent returns no UTXO.
57+
func runTestListUnspent(ht *lntest.HarnessTest, fundCoins fundMethod) {
58+
// Create two test nodes.
59+
alice := ht.NewNode("Alice", nil)
60+
bob := ht.NewNode("Bob", nil)
61+
62+
// Fund Alice one UTXO.
63+
coin := btcutil.Amount(100_000)
64+
fundCoins(coin, alice)
65+
66+
// Log Alice's wallet balance for debug.
67+
balance := alice.RPC.WalletBalance()
68+
ht.Logf("Alice has balance: %v", balance)
69+
70+
// Send all Alice's balance to Bob.
71+
ht.SendAllCoins(alice, bob)
72+
73+
// Mine Alice's send coin tx.
74+
ht.MineBlocksAndAssertNumTxes(1, 1)
75+
76+
// Alice wallet should be empty now, assert that all her balance fields
77+
// are zero.
78+
ht.AssertWalletAccountBalance(alice, lnwallet.DefaultAccountName, 0, 0)
79+
ht.AssertWalletLockedBalance(alice, 0)
80+
81+
// Alice should have no UTXO.
82+
ht.AssertNumUTXOs(alice, 0)
83+
}
84+
85+
// testListUnspentRestart checks that once Alice sends all coins to Bob, then
86+
// restarts, her wallet balances are updated and the ListUnspent returns no
87+
// UTXO.
88+
func runTestListUnspentRestart(ht *lntest.HarnessTest, fundCoins fundMethod) {
89+
// Create two test nodes.
90+
alice := ht.NewNode("Alice", nil)
91+
bob := ht.NewNode("Bob", nil)
92+
93+
// Fund Alice one UTXO.
94+
coin := btcutil.Amount(100_000)
95+
fundCoins(coin, alice)
96+
97+
// Log Alice's wallet balance for debug.
98+
balance := alice.RPC.WalletBalance()
99+
ht.Logf("Alice has balance: %v", balance)
100+
101+
// Send all Alice's balance to Bob.
102+
ht.SendAllCoins(alice, bob)
103+
104+
// Shutdown Alice.
105+
restart := ht.SuspendNode(alice)
106+
107+
// Mine Alice's send coin tx.
108+
ht.MineBlocksAndAssertNumTxes(1, 1)
109+
110+
// Restart Alice.
111+
require.NoError(ht, restart())
112+
113+
// Alice wallet should be empty now, assert that all her balance fields
114+
// are zero.
115+
ht.AssertWalletAccountBalance(alice, lnwallet.DefaultAccountName, 0, 0)
116+
ht.AssertWalletLockedBalance(alice, 0)
117+
118+
// Alice should have no UTXO.
119+
ht.AssertNumUTXOs(alice, 0)
120+
}

lntest/harness.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2282,6 +2282,28 @@ func (h *HarnessTest) SendCoins(a, b *node.HarnessNode,
22822282
return tx
22832283
}
22842284

2285+
// SendCoins sends all coins from node A to node B, returns the sending tx.
2286+
func (h *HarnessTest) SendAllCoins(a, b *node.HarnessNode) *wire.MsgTx {
2287+
// Create an address for Bob receive the coins.
2288+
req := &lnrpc.NewAddressRequest{
2289+
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
2290+
}
2291+
resp := b.RPC.NewAddress(req)
2292+
2293+
// Send the coins from Alice to Bob. We should expect a tx to be
2294+
// broadcast and seen in the mempool.
2295+
sendReq := &lnrpc.SendCoinsRequest{
2296+
Addr: resp.Address,
2297+
TargetConf: 6,
2298+
SendAll: true,
2299+
SpendUnconfirmed: true,
2300+
}
2301+
a.RPC.SendCoins(sendReq)
2302+
tx := h.GetNumTxsFromMempool(1)[0]
2303+
2304+
return tx
2305+
}
2306+
22852307
// CreateSimpleNetwork creates the number of nodes specified by the number of
22862308
// configs and makes a topology of `node1 -> node2 -> node3...`. Each node is
22872309
// created using the specified config, the neighbors are connected, and the

lntest/harness_assertion.go

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -748,20 +748,12 @@ func (h *HarnessTest) WaitForGraphSync(hn *node.HarnessNode) {
748748
// AssertNumUTXOsWithConf waits for the given number of UTXOs with the
749749
// specified confirmations range to be available or fails if that isn't the
750750
// case before the default timeout.
751-
//
752-
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
753-
// previous state of the node's UTXOs. The previous state is snapshotted when
754-
// finishing a previous test case via the cleanup function in `Subtest`. In
755-
// other words, this assertion only checks the new changes made in the current
756-
// test.
757751
func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
758752
expectedUtxos int, max, min int32) []*lnrpc.Utxo {
759753

760754
var unconfirmed bool
761755

762-
old := hn.State.UTXO.Confirmed
763756
if max == 0 {
764-
old = hn.State.UTXO.Unconfirmed
765757
unconfirmed = true
766758
}
767759

@@ -776,8 +768,8 @@ func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
776768
resp := hn.RPC.ListUnspent(req)
777769
total := len(resp.Utxos)
778770

779-
if total-old == expectedUtxos {
780-
utxos = resp.Utxos[old:]
771+
if total == expectedUtxos {
772+
utxos = resp.Utxos
781773

782774
return nil
783775
}
@@ -787,8 +779,8 @@ func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
787779
desc += fmt.Sprintf("%v\n", utxo)
788780
}
789781

790-
return errNumNotMatched(hn.Name(), "num of UTXOs",
791-
expectedUtxos, total-old, total, old, desc)
782+
return fmt.Errorf("%s: assert num of UTXOs failed: want %d, "+
783+
"got: %d, %s", hn.Name(), expectedUtxos, total, desc)
792784
}, DefaultTimeout)
793785
require.NoError(h, err, "timeout waiting for UTXOs")
794786

@@ -797,10 +789,6 @@ func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
797789

798790
// AssertNumUTXOsUnconfirmed asserts the expected num of unconfirmed utxos are
799791
// seen.
800-
//
801-
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
802-
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
803-
// details.
804792
func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
805793
num int) []*lnrpc.Utxo {
806794

@@ -809,10 +797,6 @@ func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
809797

810798
// AssertNumUTXOsConfirmed asserts the expected num of confirmed utxos are
811799
// seen, which means the returned utxos have at least one confirmation.
812-
//
813-
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
814-
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
815-
// details.
816800
func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
817801
num int) []*lnrpc.Utxo {
818802

@@ -821,10 +805,6 @@ func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
821805

822806
// AssertNumUTXOs asserts the expected num of utxos are seen, including
823807
// confirmed and unconfirmed outputs.
824-
//
825-
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
826-
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
827-
// details.
828808
func (h *HarnessTest) AssertNumUTXOs(hn *node.HarnessNode,
829809
num int) []*lnrpc.Utxo {
830810

rpcserver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3716,8 +3716,8 @@ func (r *rpcServer) WalletBalance(ctx context.Context,
37163716
)
37173717

37183718
rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+
3719-
"unconfirmed=%v)", totalBalance, confirmedBalance,
3720-
unconfirmedBalance)
3719+
"unconfirmed=%v, locked=%v)", totalBalance, confirmedBalance,
3720+
unconfirmedBalance, lockedBalance)
37213721

37223722
return &lnrpc.WalletBalanceResponse{
37233723
TotalBalance: int64(totalBalance),

0 commit comments

Comments
 (0)