From 0daadb05bf70b93b5ba2fd86055ddfc6bf8d5eba Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 2 Apr 2025 00:47:02 +0800 Subject: [PATCH 1/5] lntest: add `SendAllCoins` and remove standby nodes context Standby nodes are no longer used so we update `AssertNumUTXOs` to remove the confusing check. --- lntest/harness.go | 22 ++++++++++++++++++++++ lntest/harness_assertion.go | 28 ++++------------------------ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lntest/harness.go b/lntest/harness.go index d0a36f6dd..4b12c8cf6 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -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 diff --git a/lntest/harness_assertion.go b/lntest/harness_assertion.go index 1b195a0a9..8cdbb4ad8 100644 --- a/lntest/harness_assertion.go +++ b/lntest/harness_assertion.go @@ -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 { From 9d7b55ed4ff8d89cc507931dcbf668e47a06f51d Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 9 Apr 2025 00:37:26 +0800 Subject: [PATCH 2/5] itest: add wallet-related itest --- itest/list_on_test.go | 3 ++ itest/lnd_wallet.go | 120 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 itest/lnd_wallet.go diff --git a/itest/list_on_test.go b/itest/list_on_test.go index e4dbc32ca..b8d2b09f5 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -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. diff --git a/itest/lnd_wallet.go b/itest/lnd_wallet.go new file mode 100644 index 000000000..1c07b087b --- /dev/null +++ b/itest/lnd_wallet.go @@ -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) +} From ab2361b8fba0cae6bf8599247d13d5282da0c6e4 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 9 Apr 2025 00:38:12 +0800 Subject: [PATCH 3/5] lnd: log `lockedBalance` in `WalletBalance` --- rpcserver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 491a88210..6e288604f 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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), From 07583f94ffa3ccea9d13cd5ebd095f890412ee72 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 9 Apr 2025 17:37:18 +0800 Subject: [PATCH 4/5] gomod: update `btcwallet` --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index cb032f756..0e981bc5b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 5bbd5a544..d0dd66643 100644 --- a/go.sum +++ b/go.sum @@ -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= From 3d69d70ebaafcffd94db9e6acdadb04f0ed24e50 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 9 Apr 2025 20:13:00 +0800 Subject: [PATCH 5/5] docs: update release notes --- docs/release-notes/release-notes-0.19.0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md index 5f02bbbd8..ae020a82c 100644 --- a/docs/release-notes/release-notes-0.19.0.md +++ b/docs/release-notes/release-notes-0.19.0.md @@ -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