Merge pull request #9693 from yyforyongyu/debug-listunspent
Fix inaccurate `locked_balance`
This commit is contained in:
8 files changed
+159
-33
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user