Merge pull request #9693 from yyforyongyu/debug-listunspent

Fix inaccurate `locked_balance`
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-04-17 08:46:36 +02:00
commit 24fdae7dff
8 files changed
+159 -33

No files matched your search

+2 -1
View File
@@ -101,7 +101,8 @@
than once.
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9609) a bug that may
cause `listunspent` to give inaccurate wallet UTXOs.
cause `listunspent` to give inaccurate wallet UTXOs and
[`locked_balance`](https://github.com/lightningnetwork/lnd/pull/9693).
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9626) a bug where a
keysend payment would not fail properly and only resolve after restart. Now
+2 -2
View File
@@ -11,11 +11,11 @@ require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
github.com/btcsuite/btcwallet v0.16.13-0.20250409141346-8e790bfb5832
github.com/btcsuite/btcwallet v0.16.13
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
github.com/btcsuite/btcwallet/walletdb v1.5.1
github.com/btcsuite/btcwallet/wtxmgr v1.5.5
github.com/btcsuite/btcwallet/wtxmgr v1.5.6
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
+4 -4
View File
@@ -62,8 +62,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.16.13-0.20250409141346-8e790bfb5832 h1:Nd5r1YDNN47hn2UVyOcIJjNRvA90bpUrSVp8wnPMa18=
github.com/btcsuite/btcwallet v0.16.13-0.20250409141346-8e790bfb5832/go.mod h1:Lpr6jNoTiWlgaXqFP+ar2YWSiD6Fl8xlh99WPvuErS4=
github.com/btcsuite/btcwallet v0.16.13 h1:JGu+wrihQ0I00ODb3w92JtBPbrHxZhbcvU01O+e+lKw=
github.com/btcsuite/btcwallet v0.16.13/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
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
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw=
github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI=
github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs=
github.com/btcsuite/btcwallet/wtxmgr v1.5.5 h1:VA/rHzAjiNuySPcKgdX3uAywbVczZlG5OZeSU7jYoZo=
github.com/btcsuite/btcwallet/wtxmgr v1.5.5/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA=
github.com/btcsuite/btcwallet/wtxmgr v1.5.6 h1:Zwvr/rrJYdOLqdBCSr4eICEstnEA+NBUvjIWLkrXaYI=
github.com/btcsuite/btcwallet/wtxmgr v1.5.6/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
+3
View File
@@ -755,6 +755,9 @@ func init() {
allTestCases = appendPrefixed(
"channel force close", allTestCases, channelForceCloseTestCases,
)
allTestCases = appendPrefixed(
"wallet", allTestCases, walletTestCases,
)
// Prepare the test cases for windows to exclude some of the flaky
// ones.
+120
View File
@@ -0,0 +1,120 @@
package itest
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
)
// walletTestCases defines a set of tests aiming at asserting functionalities
// provided by the wallerpc.
var walletTestCases = []*lntest.TestCase{
{
Name: "listunspent P2WPKH",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspent(ht, ht.FundCoins)
},
},
{
Name: "listunspent NP2WPKH",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspent(ht, ht.FundCoinsNP2WKH)
},
},
{
Name: "listunspent P2TR",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspent(ht, ht.FundCoinsP2TR)
},
},
{
Name: "listunspent P2WPKH restart",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspentRestart(ht, ht.FundCoins)
},
},
{
Name: "listunspent NP2WPKH restart",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspentRestart(ht, ht.FundCoinsNP2WKH)
},
},
{
Name: "listunspent P2TR restart",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspentRestart(ht, ht.FundCoinsP2TR)
},
},
}
type fundMethod func(amt btcutil.Amount, hn *node.HarnessNode) *wire.MsgTx
// testListUnspent checks that once Alice sends all coins to Bob, her wallet
// balances are updated and the ListUnspent returns no UTXO.
func runTestListUnspent(ht *lntest.HarnessTest, fundCoins fundMethod) {
// Create two test nodes.
alice := ht.NewNode("Alice", nil)
bob := ht.NewNode("Bob", nil)
// Fund Alice one UTXO.
coin := btcutil.Amount(100_000)
fundCoins(coin, alice)
// Log Alice's wallet balance for debug.
balance := alice.RPC.WalletBalance()
ht.Logf("Alice has balance: %v", balance)
// Send all Alice's balance to Bob.
ht.SendAllCoins(alice, bob)
// Mine Alice's send coin tx.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Alice wallet should be empty now, assert that all her balance fields
// are zero.
ht.AssertWalletAccountBalance(alice, lnwallet.DefaultAccountName, 0, 0)
ht.AssertWalletLockedBalance(alice, 0)
// Alice should have no UTXO.
ht.AssertNumUTXOs(alice, 0)
}
// testListUnspentRestart checks that once Alice sends all coins to Bob, then
// restarts, her wallet balances are updated and the ListUnspent returns no
// UTXO.
func runTestListUnspentRestart(ht *lntest.HarnessTest, fundCoins fundMethod) {
// Create two test nodes.
alice := ht.NewNode("Alice", nil)
bob := ht.NewNode("Bob", nil)
// Fund Alice one UTXO.
coin := btcutil.Amount(100_000)
fundCoins(coin, alice)
// Log Alice's wallet balance for debug.
balance := alice.RPC.WalletBalance()
ht.Logf("Alice has balance: %v", balance)
// Send all Alice's balance to Bob.
ht.SendAllCoins(alice, bob)
// Shutdown Alice.
restart := ht.SuspendNode(alice)
// Mine Alice's send coin tx.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Restart Alice.
require.NoError(ht, restart())
// Alice wallet should be empty now, assert that all her balance fields
// are zero.
ht.AssertWalletAccountBalance(alice, lnwallet.DefaultAccountName, 0, 0)
ht.AssertWalletLockedBalance(alice, 0)
// Alice should have no UTXO.
ht.AssertNumUTXOs(alice, 0)
}
+22
View File
@@ -2282,6 +2282,28 @@ func (h *HarnessTest) SendCoins(a, b *node.HarnessNode,
return tx
}
// SendCoins sends all coins from node A to node B, returns the sending tx.
func (h *HarnessTest) SendAllCoins(a, b *node.HarnessNode) *wire.MsgTx {
// Create an address for Bob receive the coins.
req := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
}
resp := b.RPC.NewAddress(req)
// Send the coins from Alice to Bob. We should expect a tx to be
// broadcast and seen in the mempool.
sendReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
TargetConf: 6,
SendAll: true,
SpendUnconfirmed: true,
}
a.RPC.SendCoins(sendReq)
tx := h.GetNumTxsFromMempool(1)[0]
return tx
}
// CreateSimpleNetwork creates the number of nodes specified by the number of
// configs and makes a topology of `node1 -> node2 -> node3...`. Each node is
// created using the specified config, the neighbors are connected, and the
+4 -24
View File
@@ -748,20 +748,12 @@ func (h *HarnessTest) WaitForGraphSync(hn *node.HarnessNode) {
// AssertNumUTXOsWithConf waits for the given number of UTXOs with the
// specified confirmations range to be available or fails if that isn't the
// case before the default timeout.
//
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
// previous state of the node's UTXOs. The previous state is snapshotted when
// finishing a previous test case via the cleanup function in `Subtest`. In
// other words, this assertion only checks the new changes made in the current
// test.
func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
expectedUtxos int, max, min int32) []*lnrpc.Utxo {
var unconfirmed bool
old := hn.State.UTXO.Confirmed
if max == 0 {
old = hn.State.UTXO.Unconfirmed
unconfirmed = true
}
@@ -776,8 +768,8 @@ func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
resp := hn.RPC.ListUnspent(req)
total := len(resp.Utxos)
if total-old == expectedUtxos {
utxos = resp.Utxos[old:]
if total == expectedUtxos {
utxos = resp.Utxos
return nil
}
@@ -787,8 +779,8 @@ func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
desc += fmt.Sprintf("%v\n", utxo)
}
return errNumNotMatched(hn.Name(), "num of UTXOs",
expectedUtxos, total-old, total, old, desc)
return fmt.Errorf("%s: assert num of UTXOs failed: want %d, "+
"got: %d, %s", hn.Name(), expectedUtxos, total, desc)
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for UTXOs")
@@ -797,10 +789,6 @@ func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
// AssertNumUTXOsUnconfirmed asserts the expected num of unconfirmed utxos are
// seen.
//
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
// details.
func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
num int) []*lnrpc.Utxo {
@@ -809,10 +797,6 @@ func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
// AssertNumUTXOsConfirmed asserts the expected num of confirmed utxos are
// seen, which means the returned utxos have at least one confirmation.
//
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
// details.
func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
num int) []*lnrpc.Utxo {
@@ -821,10 +805,6 @@ func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
// AssertNumUTXOs asserts the expected num of utxos are seen, including
// confirmed and unconfirmed outputs.
//
// NOTE: for standby nodes(Alice and Bob), this method takes account of the
// previous state of the node's UTXOs. Check `AssertNumUTXOsWithConf` for
// details.
func (h *HarnessTest) AssertNumUTXOs(hn *node.HarnessNode,
num int) []*lnrpc.Utxo {
+2 -2
View File
@@ -3716,8 +3716,8 @@ func (r *rpcServer) WalletBalance(ctx context.Context,
)
rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+
"unconfirmed=%v)", totalBalance, confirmedBalance,
unconfirmedBalance)
"unconfirmed=%v, locked=%v)", totalBalance, confirmedBalance,
unconfirmedBalance, lockedBalance)
return &lnrpc.WalletBalanceResponse{
TotalBalance: int64(totalBalance),