diff --git a/lntemp/harness.go b/lntemp/harness.go index 072dc85bf..ed9865f28 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -971,6 +971,22 @@ func (h *HarnessTest) FundCoinsUnconfirmed(amt btcutil.Amount, h.fundCoins(amt, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false) } +// FundCoinsNP2WKH attempts to send amt satoshis from the internal mining node +// to the targeted lightning node using a NP2WKH address. +func (h *HarnessTest) FundCoinsNP2WKH(amt btcutil.Amount, + target *node.HarnessNode) { + + h.fundCoins(amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true) +} + +// FundCoinsP2TR attempts to send amt satoshis from the internal mining node to +// the targeted lightning node using a P2TR address. +func (h *HarnessTest) FundCoinsP2TR(amt btcutil.Amount, + target *node.HarnessNode) { + + h.fundCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true) +} + // CompletePaymentRequests sends payments from a node to complete all payment // requests. This function does not return until all payments successfully // complete without errors. diff --git a/lntemp/harness_miner.go b/lntemp/harness_miner.go index 1c37b33f6..bcccb8656 100644 --- a/lntemp/harness_miner.go +++ b/lntemp/harness_miner.go @@ -376,3 +376,10 @@ func (h *HarnessMiner) GetNumTxsFromMempool(n int) []*wire.MsgTx { return txes } + +// NewMinerAddress creates a new address for the miner and asserts. +func (h *HarnessMiner) NewMinerAddress() btcutil.Address { + addr, err := h.NewAddress() + require.NoError(h, err, "failed to create new miner address") + return addr +} diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 01022f501..fd0c8721b 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -319,3 +319,39 @@ func (h *HarnessRPC) ChannelAcceptor() (AcceptorClient, context.CancelFunc) { return resp, cancel } + +// SendCoins sends a given amount of money to the specified address from the +// passed node. +func (h *HarnessRPC) SendCoins( + req *lnrpc.SendCoinsRequest) *lnrpc.SendCoinsResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.SendCoins(ctxt, req) + require.NoErrorf(h, err, "node %s failed to send coins to address %s", + h.Name, req.Addr) + + return resp +} + +// SendCoinsAssertErr sends a given amount of money to the specified address +// from the passed node and asserts an error has returned. +func (h *HarnessRPC) SendCoinsAssertErr(req *lnrpc.SendCoinsRequest) { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.LN.SendCoins(ctxt, req) + require.Error(h, err, "node %s didn't not return an error", h.Name) +} + +// GetTransactions makes a RPC call to GetTransactions and asserts. +func (h *HarnessRPC) GetTransactions() *lnrpc.TransactionDetails { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.GetTransactions(ctxt, &lnrpc.GetTransactionsRequest{}) + require.NoErrorf(h, err, "failed to GetTransactions for %s", h.Name) + + return resp +} diff --git a/lntemp/rpc/wallet_kit.go b/lntemp/rpc/wallet_kit.go index ed0f86960..2feddf473 100644 --- a/lntemp/rpc/wallet_kit.go +++ b/lntemp/rpc/wallet_kit.go @@ -5,6 +5,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/stretchr/testify/require" ) // ===================== @@ -73,3 +74,27 @@ func (h *HarnessRPC) FinalizePsbt( return resp } + +// LabelTransactionAssertErr makes a RPC call to the node's LabelTransaction +// and asserts an error is returned. It then returns the error. +func (h *HarnessRPC) LabelTransactionAssertErr( + req *walletrpc.LabelTransactionRequest) error { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.WalletKit.LabelTransaction(ctxt, req) + require.Error(h, err, "expected error returned") + + return err +} + +// LabelTransaction makes a RPC call to the node's LabelTransaction +// and asserts no error is returned. +func (h *HarnessRPC) LabelTransaction(req *walletrpc.LabelTransactionRequest) { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.WalletKit.LabelTransaction(ctxt, req) + h.NoError(err, "LabelTransaction") +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 302d17890..8c55c9317 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -71,4 +71,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "data loss protection", TestFunc: testDataLossProtection, }, + { + Name: "sweep coins", + TestFunc: testSweepAllCoins, + }, } diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go index 99472f148..a548a77c6 100644 --- a/lntest/itest/lnd_misc_test.go +++ b/lntest/itest/lnd_misc_test.go @@ -22,6 +22,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntemp" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet" @@ -1180,26 +1181,23 @@ func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) { // testSweepAllCoins tests that we're able to properly sweep all coins from the // wallet into a single target address at the specified fee rate. -func testSweepAllCoins(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - +// +// TODO(yy): expand this test to also use P2TR. +func testSweepAllCoins(ht *lntemp.HarnessTest) { // First, we'll make a new node, ainz who'll we'll use to test wallet // sweeping. - ainz := net.NewNode(t.t, "Ainz", nil) - defer shutdownAndAssert(net, t, ainz) + // + // NOTE: we won't use standby nodes here since the test will change + // each of the node's wallet state. + ainz := ht.NewNode("Ainz", nil) // Next, we'll give Ainz exactly 2 utxos of 1 BTC each, with one of // them being p2wkh and the other being a n2wpkh address. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, ainz) - - net.SendCoinsNP2WKH(t.t, btcutil.SatoshiPerBitcoin, ainz) + ht.FundCoins(btcutil.SatoshiPerBitcoin, ainz) + ht.FundCoinsNP2WKH(btcutil.SatoshiPerBitcoin, ainz) // Ensure that we can't send coins to our own Pubkey. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - info, err := ainz.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - if err != nil { - t.Fatalf("unable to get node info: %v", err) - } + info := ainz.RPC.GetInfo() // Create a label that we will used to label the transaction with. sendCoinsLabel := "send all coins" @@ -1209,171 +1207,132 @@ func testSweepAllCoins(net *lntest.NetworkHarness, t *harnessTest) { SendAll: true, Label: sendCoinsLabel, } - _, err = ainz.SendCoins(ctxt, sweepReq) - if err == nil { - t.Fatalf("expected SendCoins to users own pubkey to fail") - } + ainz.RPC.SendCoinsAssertErr(sweepReq) - // Ensure that we can't send coins to another users Pubkey. - info, err = net.Alice.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - if err != nil { - t.Fatalf("unable to get node info: %v", err) - } + // Ensure that we can't send coins to another user's Pubkey. + info = ht.Alice.RPC.GetInfo() sweepReq = &lnrpc.SendCoinsRequest{ Addr: info.IdentityPubkey, SendAll: true, Label: sendCoinsLabel, } - _, err = ainz.SendCoins(ctxt, sweepReq) - if err == nil { - t.Fatalf("expected SendCoins to Alices pubkey to fail") - } + ainz.RPC.SendCoinsAssertErr(sweepReq) // With the two coins above mined, we'll now instruct ainz to sweep all - // the coins to an external address not under its control. - // We will first attempt to send the coins to addresses that are not - // compatible with the current network. This is to test that the wallet - // will prevent any onchain transactions to addresses that are not on the + // the coins to an external address not under its control. We will + // first attempt to send the coins to addresses that are not compatible + // with the current network. This is to test that the wallet will + // prevent any onchain transactions to addresses that are not on the // same network as the user. // Send coins to a testnet3 address. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) sweepReq = &lnrpc.SendCoinsRequest{ Addr: "tb1qfc8fusa98jx8uvnhzavxccqlzvg749tvjw82tg", SendAll: true, Label: sendCoinsLabel, } - _, err = ainz.SendCoins(ctxt, sweepReq) - if err == nil { - t.Fatalf("expected SendCoins to different network to fail") - } + ainz.RPC.SendCoinsAssertErr(sweepReq) // Send coins to a mainnet address. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) sweepReq = &lnrpc.SendCoinsRequest{ Addr: "1MPaXKp5HhsLNjVSqaL7fChE3TVyrTMRT3", SendAll: true, Label: sendCoinsLabel, } - _, err = ainz.SendCoins(ctxt, sweepReq) - if err == nil { - t.Fatalf("expected SendCoins to different network to fail") - } + ainz.RPC.SendCoinsAssertErr(sweepReq) // Send coins to a compatible address. - minerAddr, err := net.Miner.NewAddress() - if err != nil { - t.Fatalf("unable to create new miner addr: %v", err) - } - + minerAddr := ht.Miner.NewMinerAddress() sweepReq = &lnrpc.SendCoinsRequest{ Addr: minerAddr.String(), SendAll: true, Label: sendCoinsLabel, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = ainz.SendCoins(ctxt, sweepReq) - if err != nil { - t.Fatalf("unable to sweep coins: %v", err) - } + ainz.RPC.SendCoins(sweepReq) // We'll mine a block which should include the sweep transaction we // generated above. - block := mineBlocks(t, net, 1, 1)[0] + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] // The sweep transaction should have exactly two inputs as we only had // two UTXOs in the wallet. sweepTx := block.Transactions[1] - if len(sweepTx.TxIn) != 2 { - t.Fatalf("expected 2 inputs instead have %v", len(sweepTx.TxIn)) + require.Len(ht, sweepTx.TxIn, 2, "expected 2 inputs") + + // assertTxLabel is a helper function which finds a target tx in our + // set of transactions and checks that it has the desired label. + assertTxLabel := func(targetTx, label string) { + // List all transactions relevant to our wallet, and find the + // tx so that we can check the correct label has been set. + txResp := ainz.RPC.GetTransactions() + + // Find our transaction in the set of transactions returned and + // check its label. + for _, txn := range txResp.Transactions { + if txn.TxHash == targetTx { + require.Equal(ht, label, txn.Label, + "labels not match") + } + } } sweepTxStr := sweepTx.TxHash().String() - assertTxLabel(t, ainz, sweepTxStr, sendCoinsLabel) + assertTxLabel(sweepTxStr, sendCoinsLabel) - // While we are looking at labels, we test our label transaction command - // to make sure it is behaving as expected. First, we try to label our - // transaction with an empty label, and check that we fail as expected. + // While we are looking at labels, we test our label transaction + // command to make sure it is behaving as expected. First, we try to + // label our transaction with an empty label, and check that we fail as + // expected. sweepHash := sweepTx.TxHash() - _, err = ainz.WalletKitClient.LabelTransaction( - ctxt, &walletrpc.LabelTransactionRequest{ - Txid: sweepHash[:], - Label: "", - Overwrite: false, - }, - ) - if err == nil { - t.Fatalf("expected error for zero transaction label") + req := &walletrpc.LabelTransactionRequest{ + Txid: sweepHash[:], + Label: "", + Overwrite: false, } + err := ainz.RPC.LabelTransactionAssertErr(req) // Our error will be wrapped in a rpc error, so we check that it // contains the error we expect. errZeroLabel := "cannot label transaction with empty label" - if !strings.Contains(err.Error(), errZeroLabel) { - t.Fatalf("expected: zero label error, got: %v", err) - } + require.Contains(ht, err.Error(), errZeroLabel, + "expected: zero label errorv") // Next, we try to relabel our transaction without setting the overwrite // boolean. We expect this to fail, because the wallet requires setting // of this param to prevent accidental overwrite of labels. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = ainz.WalletKitClient.LabelTransaction( - ctxt, &walletrpc.LabelTransactionRequest{ - Txid: sweepHash[:], - Label: "label that will not work", - Overwrite: false, - }, - ) - if err == nil { - t.Fatalf("expected error for tx already labelled") + req = &walletrpc.LabelTransactionRequest{ + Txid: sweepHash[:], + Label: "label that will not work", + Overwrite: false, } + err = ainz.RPC.LabelTransactionAssertErr(req) // Our error will be wrapped in a rpc error, so we check that it // contains the error we expect. - if !strings.Contains(err.Error(), wallet.ErrTxLabelExists.Error()) { - t.Fatalf("expected: label exists, got: %v", err) - } + require.Contains(ht, err.Error(), wallet.ErrTxLabelExists.Error()) // Finally, we overwrite our label with a new label, which should not // fail. newLabel := "new sweep tx label" - _, err = ainz.WalletKitClient.LabelTransaction( - ctxt, &walletrpc.LabelTransactionRequest{ - Txid: sweepHash[:], - Label: newLabel, - Overwrite: true, - }, - ) - if err != nil { - t.Fatalf("could not label tx: %v", err) + req = &walletrpc.LabelTransactionRequest{ + Txid: sweepHash[:], + Label: newLabel, + Overwrite: true, } + ainz.RPC.LabelTransaction(req) - assertTxLabel(t, ainz, sweepTxStr, newLabel) + assertTxLabel(sweepTxStr, newLabel) // Finally, Ainz should now have no coins at all within his wallet. - balReq := &lnrpc.WalletBalanceRequest{} - resp, err := ainz.WalletBalance(ctxt, balReq) - if err != nil { - t.Fatalf("unable to get ainz's balance: %v", err) - } - switch { - case resp.ConfirmedBalance != 0: - t.Fatalf("expected no confirmed balance, instead have %v", - resp.ConfirmedBalance) - - case resp.UnconfirmedBalance != 0: - t.Fatalf("expected no unconfirmed balance, instead have %v", - resp.UnconfirmedBalance) - } + resp := ainz.RPC.WalletBalance() + require.Zero(ht, resp.ConfirmedBalance, "wrong confirmed balance") + require.Zero(ht, resp.UnconfirmedBalance, "wrong unconfirmed balance") // If we try again, but this time specifying an amount, then the call // should fail. sweepReq.Amount = 10000 - _, err = ainz.SendCoins(ctxt, sweepReq) - if err == nil { - t.Fatalf("sweep attempt should fail") - } + ainz.RPC.SendCoinsAssertErr(sweepReq) } // testListAddresses tests that we get all the addresses and their diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index fe5ac6378..bfaf68e38 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -4,10 +4,6 @@ package itest var allTestCases = []*testCase{ - { - name: "sweep coins", - test: testSweepAllCoins, - }, { name: "list addresses", test: testListAddresses,