diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go new file mode 100644 index 00000000..23386d99 --- /dev/null +++ b/lntest/itest/lnd_misc_test.go @@ -0,0 +1,1965 @@ +package itest + +import ( + "bytes" + "context" + "crypto/rand" + "fmt" + "io/ioutil" + "strings" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/wallet" + "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/funding" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +// testDisconnectingTargetPeer performs a test which disconnects Alice-peer from +// Bob-peer and then re-connects them again. We expect Alice to be able to +// disconnect at any point. +func testDisconnectingTargetPeer(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // We'll start both nodes with a high backoff so that they don't + // reconnect automatically during our test. + args := []string{ + "--minbackoff=1m", + "--maxbackoff=1m", + } + + alice := net.NewNode(t.t, "Alice", args) + defer shutdownAndAssert(net, t, alice) + + bob := net.NewNode(t.t, "Bob", args) + defer shutdownAndAssert(net, t, bob) + + // Start by connecting Alice and Bob with no channels. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, alice, bob) + + // Check existing connection. + assertNumConnections(t, alice, bob, 1) + + // Give Alice some coins so she can fund a channel. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, alice) + + chanAmt := funding.MaxBtcFundingAmount + pushAmt := btcutil.Amount(0) + + // Create a new channel that requires 1 confs before it's considered + // open, then broadcast the funding transaction + const numConfs = 1 + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + pendingUpdate, err := net.OpenPendingChannel( + ctxt, alice, bob, chanAmt, pushAmt, + ) + if err != nil { + t.Fatalf("unable to open channel: %v", err) + } + + // At this point, the channel's funding transaction will have been + // broadcast, but not confirmed. Alice and Bob's nodes should reflect + // this when queried via RPC. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + assertNumOpenChannelsPending(ctxt, t, alice, bob, 1) + + // Disconnect Alice-peer from Bob-peer and get error causes by one + // pending channel with detach node is existing. + if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { + t.Fatalf("Bob's peer was disconnected from Alice's"+ + " while one pending channel is existing: err %v", err) + } + + time.Sleep(time.Millisecond * 300) + + // Assert that the connection was torn down. + assertNumConnections(t, alice, bob, 0) + + fundingTxID, err := chainhash.NewHash(pendingUpdate.Txid) + if err != nil { + t.Fatalf("unable to convert funding txid into chainhash.Hash:"+ + " %v", err) + } + + // Mine a block, then wait for Alice's node to notify us that the + // channel has been opened. The funding transaction should be found + // within the newly mined block. + block := mineBlocks(t, net, numConfs, 1)[0] + assertTxInBlock(t, block, fundingTxID) + + // At this point, the channel should be fully opened and there should be + // no pending channels remaining for either node. + time.Sleep(time.Millisecond * 300) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + + assertNumOpenChannelsPending(ctxt, t, alice, bob, 0) + + // Reconnect the nodes so that the channel can become active. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, alice, bob) + + // The channel should be listed in the peer information returned by both + // peers. + outPoint := wire.OutPoint{ + Hash: *fundingTxID, + Index: pendingUpdate.OutputIndex, + } + + // Check both nodes to ensure that the channel is ready for operation. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.AssertChannelExists(ctxt, alice, &outPoint); err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.AssertChannelExists(ctxt, bob, &outPoint); err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + + // Disconnect Alice-peer from Bob-peer and get error causes by one + // active channel with detach node is existing. + if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { + t.Fatalf("Bob's peer was disconnected from Alice's"+ + " while one active channel is existing: err %v", err) + } + + // Check existing connection. + assertNumConnections(t, alice, bob, 0) + + // Reconnect both nodes before force closing the channel. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, alice, bob) + + // Finally, immediately close the channel. This function will also block + // until the channel is closed and will additionally assert the relevant + // channel closing post conditions. + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ + FundingTxidBytes: pendingUpdate.Txid, + }, + OutputIndex: pendingUpdate.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, alice, chanPoint, true) + + // Disconnect Alice-peer from Bob-peer without getting error about + // existing channels. + if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { + t.Fatalf("unable to disconnect Bob's peer from Alice's: err %v", + err) + } + + // Check zero peer connections. + assertNumConnections(t, alice, bob, 0) + + // Finally, re-connect both nodes. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, alice, bob) + + // Check existing connection. + assertNumConnections(t, alice, net.Bob, 1) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, alice, chanPoint) +} + +// testSphinxReplayPersistence verifies that replayed onion packets are rejected +// by a remote peer after a restart. We use a combination of unsafe +// configuration arguments to force Carol to replay the same sphinx packet after +// reconnecting to Dave, and compare the returned failure message with what we +// expect for replayed onion packets. +func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // Open a channel with 100k satoshis between Carol and Dave with Carol being + // the sole funder of the channel. + chanAmt := btcutil.Amount(100000) + + // First, we'll create Dave, the receiver, and start him in hodl mode. + dave := net.NewNode(t.t, "Dave", []string{"--hodl.exit-settle"}) + + // We must remember to shutdown the nodes we created for the duration + // of the tests, only leaving the two seed nodes (Alice and Bob) within + // our test network. + defer shutdownAndAssert(net, t, dave) + + // Next, we'll create Carol and establish a channel to from her to + // Dave. Carol is started in both unsafe-replay which will cause her to + // replay any pending Adds held in memory upon reconnection. + carol := net.NewNode(t.t, "Carol", []string{"--unsafe-replay"}) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, carol, dave) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPoint := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Next, we'll create Fred who is going to initiate the payment and + // establish a channel to from him to Carol. We can't perform this test + // by paying from Carol directly to Dave, because the '--unsafe-replay' + // setup doesn't apply to locally added htlcs. In that case, the + // mailbox, that is responsible for generating the replay, is bypassed. + fred := net.NewNode(t.t, "Fred", nil) + defer shutdownAndAssert(net, t, fred) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, fred, carol) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, fred) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointFC := openChannelAndAssert( + ctxt, t, net, fred, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Now that the channel is open, create an invoice for Dave which + // expects a payment of 1000 satoshis from Carol paid via a particular + // preimage. + const paymentAmt = 1000 + preimage := bytes.Repeat([]byte("A"), 32) + invoice := &lnrpc.Invoice{ + Memo: "testing", + RPreimage: preimage, + Value: paymentAmt, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + invoiceResp, err := dave.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Wait for all channels to be recognized and advertized. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("alice didn't advertise channel before "+ + "timeout: %v", err) + } + err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("bob didn't advertise channel before "+ + "timeout: %v", err) + } + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointFC) + if err != nil { + t.Fatalf("alice didn't advertise channel before "+ + "timeout: %v", err) + } + err = fred.WaitForNetworkChannelOpen(ctxt, chanPointFC) + if err != nil { + t.Fatalf("bob didn't advertise channel before "+ + "timeout: %v", err) + } + + // With the invoice for Dave added, send a payment from Fred paying + // to the above generated invoice. + ctx, cancel := context.WithCancel(ctxb) + defer cancel() + + payStream, err := fred.RouterClient.SendPaymentV2( + ctx, + &routerrpc.SendPaymentRequest{ + PaymentRequest: invoiceResp.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + }, + ) + if err != nil { + t.Fatalf("unable to open payment stream: %v", err) + } + + time.Sleep(200 * time.Millisecond) + + // Dave's invoice should not be marked as settled. + payHash := &lnrpc.PaymentHash{ + RHash: invoiceResp.RHash, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + dbInvoice, err := dave.LookupInvoice(ctxt, payHash) + if err != nil { + t.Fatalf("unable to lookup invoice: %v", err) + } + if dbInvoice.Settled { + t.Fatalf("dave's invoice should not be marked as settled: %v", + spew.Sdump(dbInvoice)) + } + + // With the payment sent but hedl, all balance related stats should not + // have changed. + err = wait.InvariantNoError( + assertAmountSent(0, carol, dave), 3*time.Second, + ) + if err != nil { + t.Fatalf(err.Error()) + } + + // With the first payment sent, restart dave to make sure he is + // persisting the information required to detect replayed sphinx + // packets. + if err := net.RestartNode(dave, nil); err != nil { + t.Fatalf("unable to restart dave: %v", err) + } + + // Carol should retransmit the Add hedl in her mailbox on startup. Dave + // should not accept the replayed Add, and actually fail back the + // pending payment. Even though he still holds the original settle, if + // he does fail, it is almost certainly caused by the sphinx replay + // protection, as it is the only validation we do in hodl mode. + result, err := getPaymentResult(payStream) + if err != nil { + t.Fatalf("unable to receive payment response: %v", err) + } + + // Assert that Fred receives the expected failure after Carol sent a + // duplicate packet that fails due to sphinx replay detection. + if result.Status == lnrpc.Payment_SUCCEEDED { + t.Fatalf("expected payment error") + } + assertLastHTLCError(t, fred, lnrpc.Failure_INVALID_ONION_KEY) + + // Since the payment failed, the balance should still be left + // unaltered. + err = wait.InvariantNoError( + assertAmountSent(0, carol, dave), 3*time.Second, + ) + if err != nil { + t.Fatalf(err.Error()) + } + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPoint, true) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, carol, chanPoint) +} + +// testListChannels checks that the response from ListChannels is correct. It +// tests the values in all ChannelConstraints are returned as expected. Once +// ListChannels becomes mature, a test against all fields in ListChannels should +// be performed. +func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const aliceRemoteMaxHtlcs = 50 + const bobRemoteMaxHtlcs = 100 + + // Create two fresh nodes and open a channel between them. + alice := net.NewNode(t.t, "Alice", nil) + defer shutdownAndAssert(net, t, alice) + + bob := net.NewNode( + t.t, "Bob", []string{ + fmt.Sprintf( + "--default-remote-max-htlcs=%v", + bobRemoteMaxHtlcs, + ), + }, + ) + defer shutdownAndAssert(net, t, bob) + + // Connect Alice to Bob. + net.ConnectNodes(ctxb, t.t, alice, bob) + + // Give Alice some coins so she can fund a channel. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, alice) + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. The minial HTLC amount is set to + // 4200 msats. + const customizedMinHtlc = 4200 + + chanAmt := btcutil.Amount(100000) + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPoint := openChannelAndAssert( + ctxt, t, net, alice, bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + MinHtlc: customizedMinHtlc, + RemoteMaxHtlcs: aliceRemoteMaxHtlcs, + }, + ) + + // Wait for Alice and Bob to receive the channel edge from the + // funding manager. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("alice didn't see the alice->bob channel before "+ + "timeout: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("bob didn't see the bob->alice channel before "+ + "timeout: %v", err) + } + + // Alice should have one channel opened with Bob. + assertNodeNumChannels(t, alice, 1) + // Bob should have one channel opened with Alice. + assertNodeNumChannels(t, bob, 1) + + // Get the ListChannel response from Alice. + listReq := &lnrpc.ListChannelsRequest{} + ctxb = context.Background() + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := alice.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to query for %s's channel list: %v", + alice.Name(), err) + } + + // Check the returned response is correct. + aliceChannel := resp.Channels[0] + + // defaultConstraints is a ChannelConstraints with default values. It is + // used to test against Alice's local channel constraints. + defaultConstraints := &lnrpc.ChannelConstraints{ + CsvDelay: 4, + ChanReserveSat: 1000, + DustLimitSat: uint64(lnwallet.DefaultDustLimit()), + MaxPendingAmtMsat: 99000000, + MinHtlcMsat: 1, + MaxAcceptedHtlcs: bobRemoteMaxHtlcs, + } + assertChannelConstraintsEqual( + t, defaultConstraints, aliceChannel.LocalConstraints, + ) + + // customizedConstraints is a ChannelConstraints with customized values. + // Ideally, all these values can be passed in when creating the channel. + // Currently, only the MinHtlcMsat is customized. It is used to check + // against Alice's remote channel constratins. + customizedConstraints := &lnrpc.ChannelConstraints{ + CsvDelay: 4, + ChanReserveSat: 1000, + DustLimitSat: uint64(lnwallet.DefaultDustLimit()), + MaxPendingAmtMsat: 99000000, + MinHtlcMsat: customizedMinHtlc, + MaxAcceptedHtlcs: aliceRemoteMaxHtlcs, + } + assertChannelConstraintsEqual( + t, customizedConstraints, aliceChannel.RemoteConstraints, + ) + + // Get the ListChannel response for Bob. + listReq = &lnrpc.ListChannelsRequest{} + ctxb = context.Background() + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err = bob.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to query for %s's channel "+ + "list: %v", bob.Name(), err) + } + + bobChannel := resp.Channels[0] + if bobChannel.ChannelPoint != aliceChannel.ChannelPoint { + t.Fatalf("Bob's channel point mismatched, want: %s, got: %s", + chanPoint.String(), bobChannel.ChannelPoint, + ) + } + + // Check channel constraints match. Alice's local channel constraint should + // be equal to Bob's remote channel constraint, and her remote one should + // be equal to Bob's local one. + assertChannelConstraintsEqual( + t, aliceChannel.LocalConstraints, bobChannel.RemoteConstraints, + ) + assertChannelConstraintsEqual( + t, aliceChannel.RemoteConstraints, bobChannel.LocalConstraints, + ) + +} + +// testMaxPendingChannels checks that error is returned from remote peer if +// max pending channel number was exceeded and that '--maxpendingchannels' flag +// exists and works properly. +func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + maxPendingChannels := lncfg.DefaultMaxPendingChannels + 1 + amount := funding.MaxBtcFundingAmount + + // Create a new node (Carol) with greater number of max pending + // channels. + args := []string{ + fmt.Sprintf("--maxpendingchannels=%v", maxPendingChannels), + } + carol := net.NewNode(t.t, "Carol", args) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, net.Alice, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + carolBalance := btcutil.Amount(maxPendingChannels) * amount + net.SendCoins(ctxt, t.t, carolBalance, carol) + + // Send open channel requests without generating new blocks thereby + // increasing pool of pending channels. Then check that we can't open + // the channel if the number of pending channels exceed max value. + openStreams := make([]lnrpc.Lightning_OpenChannelClient, maxPendingChannels) + for i := 0; i < maxPendingChannels; i++ { + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + stream := openChannelStream( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: amount, + }, + ) + openStreams[i] = stream + } + + // Carol exhausted available amount of pending channels, next open + // channel request should cause ErrorGeneric to be sent back to Alice. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + _, err := net.OpenChannel( + ctxt, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: amount, + }, + ) + + if err == nil { + t.Fatalf("error wasn't received") + } else if !strings.Contains( + err.Error(), lnwire.ErrMaxPendingChannels.Error(), + ) { + t.Fatalf("not expected error was received: %v", err) + } + + // For now our channels are in pending state, in order to not interfere + // with other tests we should clean up - complete opening of the + // channel and then close it. + + // Mine 6 blocks, then wait for node's to notify us that the channel has + // been opened. The funding transactions should be found within the + // first newly mined block. 6 blocks make sure the funding transaction + // has enough confirmations to be announced publicly. + block := mineBlocks(t, net, 6, maxPendingChannels)[0] + + chanPoints := make([]*lnrpc.ChannelPoint, maxPendingChannels) + for i, stream := range openStreams { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + fundingChanPoint, err := net.WaitForChannelOpen(ctxt, stream) + if err != nil { + t.Fatalf("error while waiting for channel open: %v", err) + } + + fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + + // Ensure that the funding transaction enters a block, and is + // properly advertised by Alice. + assertTxInBlock(t, block, fundingTxID) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, fundingChanPoint) + if err != nil { + t.Fatalf("channel not seen on network before "+ + "timeout: %v", err) + } + + // The channel should be listed in the peer information + // returned by both peers. + chanPoint := wire.OutPoint{ + Hash: *fundingTxID, + Index: fundingChanPoint.OutputIndex, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.AssertChannelExists(ctxt, net.Alice, &chanPoint); err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + + chanPoints[i] = fundingChanPoint + } + + // Next, close the channel between Alice and Carol, asserting that the + // channel has been properly closed on-chain. + for _, chanPoint := range chanPoints { + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) + } +} + +// testGarbageCollectLinkNodes tests that we properly garbase collect link nodes +// from the database and the set of persistent connections within the server. +func testGarbageCollectLinkNodes(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const ( + chanAmt = 1000000 + ) + + // Open a channel between Alice and Bob which will later be + // cooperatively closed. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + coopChanPoint := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Create Carol's node and connect Alice to her. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, net.Alice, carol) + + // Open a channel between Alice and Carol which will later be force + // closed. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + forceCloseChanPoint := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Now, create Dave's a node and also open a channel between Alice and + // him. This link will serve as the only persistent link throughout + // restarts in this test. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + net.ConnectNodes(ctxt, t.t, net.Alice, dave) + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + persistentChanPoint := openChannelAndAssert( + ctxt, t, net, net.Alice, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // isConnected is a helper closure that checks if a peer is connected to + // Alice. + isConnected := func(pubKey string) bool { + req := &lnrpc.ListPeersRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := net.Alice.ListPeers(ctxt, req) + if err != nil { + t.Fatalf("unable to retrieve alice's peers: %v", err) + } + + for _, peer := range resp.Peers { + if peer.PubKey == pubKey { + return true + } + } + + return false + } + + // Restart both Bob and Carol to ensure Alice is able to reconnect to + // them. + if err := net.RestartNode(net.Bob, nil); err != nil { + t.Fatalf("unable to restart bob's node: %v", err) + } + if err := net.RestartNode(carol, nil); err != nil { + t.Fatalf("unable to restart carol's node: %v", err) + } + + require.Eventually(t.t, func() bool { + return isConnected(net.Bob.PubKeyStr) + }, defaultTimeout, 20*time.Millisecond) + require.Eventually(t.t, func() bool { + return isConnected(carol.PubKeyStr) + }, defaultTimeout, 20*time.Millisecond) + + // We'll also restart Alice to ensure she can reconnect to her peers + // with open channels. + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart alice's node: %v", err) + } + + require.Eventually(t.t, func() bool { + return isConnected(net.Bob.PubKeyStr) + }, defaultTimeout, 20*time.Millisecond) + require.Eventually(t.t, func() bool { + return isConnected(carol.PubKeyStr) + }, defaultTimeout, 20*time.Millisecond) + require.Eventually(t.t, func() bool { + return isConnected(dave.PubKeyStr) + }, defaultTimeout, 20*time.Millisecond) + err := wait.Predicate(func() bool { + return isConnected(dave.PubKeyStr) + }, defaultTimeout) + + // testReconnection is a helper closure that restarts the nodes at both + // ends of a channel to ensure they do not reconnect after restarting. + // When restarting Alice, we'll first need to ensure she has + // reestablished her connection with Dave, as they still have an open + // channel together. + testReconnection := func(node *lntest.HarnessNode) { + // Restart both nodes, to trigger the pruning logic. + if err := net.RestartNode(node, nil); err != nil { + t.Fatalf("unable to restart %v's node: %v", + node.Name(), err) + } + + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart alice's node: %v", err) + } + + // Now restart both nodes and make sure they don't reconnect. + if err := net.RestartNode(node, nil); err != nil { + t.Fatalf("unable to restart %v's node: %v", node.Name(), + err) + } + err = wait.Invariant(func() bool { + return !isConnected(node.PubKeyStr) + }, 5*time.Second) + if err != nil { + t.Fatalf("alice reconnected to %v", node.Name()) + } + + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart alice's node: %v", err) + } + err = wait.Predicate(func() bool { + return isConnected(dave.PubKeyStr) + }, defaultTimeout) + if err != nil { + t.Fatalf("alice didn't reconnect to Dave") + } + + err = wait.Invariant(func() bool { + return !isConnected(node.PubKeyStr) + }, 5*time.Second) + if err != nil { + t.Fatalf("alice reconnected to %v", node.Name()) + } + } + + // Now, we'll close the channel between Alice and Bob and ensure there + // is no reconnection logic between the both once the channel is fully + // closed. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, coopChanPoint, false) + + testReconnection(net.Bob) + + // We'll do the same with Alice and Carol, but this time we'll force + // close the channel instead. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, forceCloseChanPoint, true) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, net.Alice, forceCloseChanPoint) + + // We'll need to mine some blocks in order to mark the channel fully + // closed. + _, err = net.Miner.Client.Generate(chainreg.DefaultBitcoinTimeLockDelta - defaultCSV) + if err != nil { + t.Fatalf("unable to generate blocks: %v", err) + } + + // Before we test reconnection, we'll ensure that the channel has been + // fully cleaned up for both Carol and Alice. + var predErr error + pendingChansRequest := &lnrpc.PendingChannelsRequest{} + err = wait.Predicate(func() bool { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + pendingChanResp, err := net.Alice.PendingChannels( + ctxt, pendingChansRequest, + ) + if err != nil { + predErr = fmt.Errorf("unable to query for pending "+ + "channels: %v", err) + return false + } + + predErr = checkNumForceClosedChannels(pendingChanResp, 0) + if predErr != nil { + return false + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + pendingChanResp, err = carol.PendingChannels( + ctxt, pendingChansRequest, + ) + if err != nil { + predErr = fmt.Errorf("unable to query for pending "+ + "channels: %v", err) + return false + } + + predErr = checkNumForceClosedChannels(pendingChanResp, 0) + + return predErr == nil + + }, defaultTimeout) + if err != nil { + t.Fatalf("channels not marked as fully resolved: %v", predErr) + } + + testReconnection(carol) + + // Finally, we'll ensure that Bob and Carol no longer show in Alice's + // channel graph. + describeGraphReq := &lnrpc.ChannelGraphRequest{ + IncludeUnannounced: true, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + channelGraph, err := net.Alice.DescribeGraph(ctxt, describeGraphReq) + if err != nil { + t.Fatalf("unable to query for alice's channel graph: %v", err) + } + for _, node := range channelGraph.Nodes { + if node.PubKey == net.Bob.PubKeyStr { + t.Fatalf("did not expect to find bob in the channel " + + "graph, but did") + } + if node.PubKey == carol.PubKeyStr { + t.Fatalf("did not expect to find carol in the channel " + + "graph, but did") + } + } + + // Now that the test is done, we can also close the persistent link. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, persistentChanPoint, false) +} + +// testDataLossProtection tests that if one of the nodes in a channel +// relationship lost state, they will detect this during channel sync, and the +// up-to-date party will force close the channel, giving the outdated party the +// opportunity to sweep its output. +func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + const ( + chanAmt = funding.MaxBtcFundingAmount + paymentAmt = 10000 + numInvoices = 6 + ) + + // Carol will be the up-to-date party. We set --nolisten to ensure Dave + // won't be able to connect to her and trigger the channel data + // protection logic automatically. We also can't have Carol + // automatically re-connect too early, otherwise DLP would be initiated + // at the wrong moment. + carol := net.NewNode( + t.t, "Carol", []string{"--nolisten", "--minbackoff=1h"}, + ) + defer shutdownAndAssert(net, t, carol) + + // Dave will be the party losing his state. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + // Before we make a channel, we'll load up Carol with some coins sent + // directly from the miner. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + // timeTravel is a method that will make Carol open a channel to the + // passed node, settle a series of payments, then reset the node back + // to the state before the payments happened. When this method returns + // the node will be unaware of the new state updates. The returned + // function can be used to restart the node in this state. + timeTravel := func(node *lntest.HarnessNode) (func() error, + *lnrpc.ChannelPoint, int64, error) { + + // We must let the node communicate with Carol before they are + // able to open channel, so we connect them. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.EnsureConnected(ctxt, t.t, carol, node) + + // We'll first open up a channel between them with a 0.5 BTC + // value. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPoint := openChannelAndAssert( + ctxt, t, net, carol, node, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // With the channel open, we'll create a few invoices for the + // node that Carol will pay to in order to advance the state of + // the channel. + // TODO(halseth): have dangling HTLCs on the commitment, able to + // retrieve funds? + payReqs, _, _, err := createPayReqs( + node, paymentAmt, numInvoices, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + // Wait for Carol to receive the channel edge from the funding + // manager. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("carol didn't see the carol->%s channel "+ + "before timeout: %v", node.Name(), err) + } + + // Send payments from Carol using 3 of the payment hashes + // generated above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, carol, carol.RouterClient, + payReqs[:numInvoices/2], true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) + } + + // Next query for the node's channel state, as we sent 3 + // payments of 10k satoshis each, it should now see his balance + // as being 30k satoshis. + var nodeChan *lnrpc.Channel + var predErr error + err = wait.Predicate(func() bool { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + bChan, err := getChanInfo(ctxt, node) + if err != nil { + t.Fatalf("unable to get channel info: %v", err) + } + if bChan.LocalBalance != 30000 { + predErr = fmt.Errorf("balance is incorrect, "+ + "got %v, expected %v", + bChan.LocalBalance, 30000) + return false + } + + nodeChan = bChan + return true + }, defaultTimeout) + if err != nil { + t.Fatalf("%v", predErr) + } + + // Grab the current commitment height (update number), we'll + // later revert him to this state after additional updates to + // revoke this state. + stateNumPreCopy := nodeChan.NumUpdates + + // With the temporary file created, copy the current state into + // the temporary file we created above. Later after more + // updates, we'll restore this state. + if err := net.BackupDb(node); err != nil { + t.Fatalf("unable to copy database files: %v", err) + } + + // Finally, send more payments from , using the remaining + // payment hashes. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, carol, carol.RouterClient, + payReqs[numInvoices/2:], true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + nodeChan, err = getChanInfo(ctxt, node) + if err != nil { + t.Fatalf("unable to get dave chan info: %v", err) + } + + // Now we shutdown the node, copying over the its temporary + // database state which has the *prior* channel state over his + // current most up to date state. With this, we essentially + // force the node to travel back in time within the channel's + // history. + if err = net.RestartNode(node, func() error { + return net.RestoreDb(node) + }); err != nil { + t.Fatalf("unable to restart node: %v", err) + } + + // Make sure the channel is still there from the PoV of the + // node. + assertNodeNumChannels(t, node, 1) + + // Now query for the channel state, it should show that it's at + // a state number in the past, not the *latest* state. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + nodeChan, err = getChanInfo(ctxt, node) + if err != nil { + t.Fatalf("unable to get dave chan info: %v", err) + } + if nodeChan.NumUpdates != stateNumPreCopy { + t.Fatalf("db copy failed: %v", nodeChan.NumUpdates) + } + + balReq := &lnrpc.WalletBalanceRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + balResp, err := node.WalletBalance(ctxt, balReq) + if err != nil { + t.Fatalf("unable to get dave's balance: %v", err) + } + + restart, err := net.SuspendNode(node) + if err != nil { + t.Fatalf("unable to suspend node: %v", err) + } + + return restart, chanPoint, balResp.ConfirmedBalance, nil + } + + // Reset Dave to a state where he has an outdated channel state. + restartDave, _, daveStartingBalance, err := timeTravel(dave) + if err != nil { + t.Fatalf("unable to time travel dave: %v", err) + } + + // We make a note of the nodes' current on-chain balances, to make sure + // they are able to retrieve the channel funds eventually, + balReq := &lnrpc.WalletBalanceRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + carolBalResp, err := carol.WalletBalance(ctxt, balReq) + if err != nil { + t.Fatalf("unable to get carol's balance: %v", err) + } + carolStartingBalance := carolBalResp.ConfirmedBalance + + // Restart Dave to trigger a channel resync. + if err := restartDave(); err != nil { + t.Fatalf("unable to restart dave: %v", err) + } + + // Assert that once Dave comes up, they reconnect, Carol force closes + // on chain, and both of them properly carry out the DLP protocol. + assertDLPExecuted( + net, t, carol, carolStartingBalance, dave, daveStartingBalance, + false, + ) + + // As a second part of this test, we will test the scenario where a + // channel is closed while Dave is offline, loses his state and comes + // back online. In this case the node should attempt to resync the + // channel, and the peer should resend a channel sync message for the + // closed channel, such that Dave can retrieve his funds. + // + // We start by letting Dave time travel back to an outdated state. + restartDave, chanPoint2, daveStartingBalance, err := timeTravel(dave) + if err != nil { + t.Fatalf("unable to time travel eve: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + carolBalResp, err = carol.WalletBalance(ctxt, balReq) + if err != nil { + t.Fatalf("unable to get carol's balance: %v", err) + } + carolStartingBalance = carolBalResp.ConfirmedBalance + + // Now let Carol force close the channel while Dave is offline. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPoint2, true) + + // Wait for the channel to be marked pending force close. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = waitForChannelPendingForceClose(ctxt, carol, chanPoint2) + if err != nil { + t.Fatalf("channel not pending force close: %v", err) + } + + // Mine enough blocks for Carol to sweep her funds. + mineBlocks(t, net, defaultCSV-1, 0) + + carolSweep, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + if err != nil { + t.Fatalf("unable to find Carol's sweep tx in mempool: %v", err) + } + block := mineBlocks(t, net, 1, 1)[0] + assertTxInBlock(t, block, carolSweep) + + // Now the channel should be fully closed also from Carol's POV. + assertNumPendingChannels(t, carol, 0, 0) + + // Make sure Carol got her balance back. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + carolBalResp, err = carol.WalletBalance(ctxt, balReq) + if err != nil { + t.Fatalf("unable to get carol's balance: %v", err) + } + carolBalance := carolBalResp.ConfirmedBalance + if carolBalance <= carolStartingBalance { + t.Fatalf("expected carol to have balance above %d, "+ + "instead had %v", carolStartingBalance, + carolBalance) + } + + assertNodeNumChannels(t, carol, 0) + + // When Dave comes online, he will reconnect to Carol, try to resync + // the channel, but it will already be closed. Carol should resend the + // information Dave needs to sweep his funds. + if err := restartDave(); err != nil { + t.Fatalf("unable to restart Eve: %v", err) + } + + // Dave should sweep his funds. + _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + if err != nil { + t.Fatalf("unable to find Dave's sweep tx in mempool: %v", err) + } + + // Mine a block to confirm the sweep, and make sure Dave got his + // balance back. + mineBlocks(t, net, 1, 1) + assertNodeNumChannels(t, dave, 0) + + err = wait.NoError(func() error { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + daveBalResp, err := dave.WalletBalance(ctxt, balReq) + if err != nil { + return fmt.Errorf("unable to get dave's balance: %v", + err) + } + + daveBalance := daveBalResp.ConfirmedBalance + if daveBalance <= daveStartingBalance { + return fmt.Errorf("expected dave to have balance "+ + "above %d, intead had %v", daveStartingBalance, + daveBalance) + } + + return nil + }, defaultTimeout) + if err != nil { + t.Fatalf("%v", err) + } +} + +// testRejectHTLC tests that a node can be created with the flag --rejecthtlc. +// This means that the node will reject all forwarded HTLCs but can still +// accept direct HTLCs as well as send HTLCs. +func testRejectHTLC(net *lntest.NetworkHarness, t *harnessTest) { + // RejectHTLC + // Alice ------> Carol ------> Bob + // + const chanAmt = btcutil.Amount(1000000) + ctxb := context.Background() + + // Create Carol with reject htlc flag. + carol := net.NewNode(t.t, "Carol", []string{"--rejecthtlc"}) + defer shutdownAndAssert(net, t, carol) + + // Connect Alice to Carol. + net.ConnectNodes(ctxb, t.t, net.Alice, carol) + + // Connect Carol to Bob. + net.ConnectNodes(ctxb, t.t, carol, net.Bob) + + // Send coins to Carol. + net.SendCoins(ctxb, t.t, btcutil.SatoshiPerBitcoin, carol) + + // Send coins to Alice. + net.SendCoins(ctxb, t.t, btcutil.SatoshiPerBitcent, net.Alice) + + // Open a channel between Alice and Carol. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Open a channel between Carol and Bob. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Channel should be ready for payments. + const payAmt = 100 + + // Helper closure to generate a random pre image. + genPreImage := func() []byte { + preimage := make([]byte, 32) + + _, err := rand.Read(preimage) + if err != nil { + t.Fatalf("unable to generate preimage: %v", err) + } + + return preimage + } + + // Create an invoice from Carol of 100 satoshis. + // We expect Alice to be able to pay this invoice. + preimage := genPreImage() + + carolInvoice := &lnrpc.Invoice{ + Memo: "testing - alice should pay carol", + RPreimage: preimage, + Value: payAmt, + } + + // Carol adds the invoice to her database. + resp, err := carol.AddInvoice(ctxb, carolInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Alice pays Carols invoice. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, + []string{resp.PaymentRequest}, true, + ) + if err != nil { + t.Fatalf("unable to send payments from alice to carol: %v", err) + } + + // Create an invoice from Bob of 100 satoshis. + // We expect Carol to be able to pay this invoice. + preimage = genPreImage() + + bobInvoice := &lnrpc.Invoice{ + Memo: "testing - carol should pay bob", + RPreimage: preimage, + Value: payAmt, + } + + // Bob adds the invoice to his database. + resp, err = net.Bob.AddInvoice(ctxb, bobInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Carol pays Bobs invoice. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, carol, carol.RouterClient, + []string{resp.PaymentRequest}, true, + ) + if err != nil { + t.Fatalf("unable to send payments from carol to bob: %v", err) + } + + // Create an invoice from Bob of 100 satoshis. + // Alice attempts to pay Bob but this should fail, since we are + // using Carol as a hop and her node will reject onward HTLCs. + preimage = genPreImage() + + bobInvoice = &lnrpc.Invoice{ + Memo: "testing - alice tries to pay bob", + RPreimage: preimage, + Value: payAmt, + } + + // Bob adds the invoice to his database. + resp, err = net.Bob.AddInvoice(ctxb, bobInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + // Alice attempts to pay Bobs invoice. This payment should be rejected since + // we are using Carol as an intermediary hop, Carol is running lnd with + // --rejecthtlc. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, + []string{resp.PaymentRequest}, true, + ) + if err == nil { + t.Fatalf( + "should have been rejected, carol will not accept forwarded htlcs", + ) + } + + assertLastHTLCError(t, net.Alice, lnrpc.Failure_CHANNEL_DISABLED) + + // Close all channels. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +func testNodeSignVerify(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + chanAmt := funding.MaxBtcFundingAmount + pushAmt := btcutil.Amount(100000) + + // Create a channel between alice and bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + aliceBobCh := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: pushAmt, + }, + ) + + aliceMsg := []byte("alice msg") + + // alice signs "alice msg" and sends her signature to bob. + sigReq := &lnrpc.SignMessageRequest{Msg: aliceMsg} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + sigResp, err := net.Alice.SignMessage(ctxt, sigReq) + if err != nil { + t.Fatalf("SignMessage rpc call failed: %v", err) + } + aliceSig := sigResp.Signature + + // bob verifying alice's signature should succeed since alice and bob are + // connected. + verifyReq := &lnrpc.VerifyMessageRequest{Msg: aliceMsg, Signature: aliceSig} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + verifyResp, err := net.Bob.VerifyMessage(ctxt, verifyReq) + if err != nil { + t.Fatalf("VerifyMessage failed: %v", err) + } + if !verifyResp.Valid { + t.Fatalf("alice's signature didn't validate") + } + if verifyResp.Pubkey != net.Alice.PubKeyStr { + t.Fatalf("alice's signature doesn't contain alice's pubkey.") + } + + // carol is a new node that is unconnected to alice or bob. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + carolMsg := []byte("carol msg") + + // carol signs "carol msg" and sends her signature to bob. + sigReq = &lnrpc.SignMessageRequest{Msg: carolMsg} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + sigResp, err = carol.SignMessage(ctxt, sigReq) + if err != nil { + t.Fatalf("SignMessage rpc call failed: %v", err) + } + carolSig := sigResp.Signature + + // bob verifying carol's signature should fail since they are not connected. + verifyReq = &lnrpc.VerifyMessageRequest{Msg: carolMsg, Signature: carolSig} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + verifyResp, err = net.Bob.VerifyMessage(ctxt, verifyReq) + if err != nil { + t.Fatalf("VerifyMessage failed: %v", err) + } + if verifyResp.Valid { + t.Fatalf("carol's signature should not be valid") + } + if verifyResp.Pubkey != carol.PubKeyStr { + t.Fatalf("carol's signature doesn't contain her pubkey") + } + + // Close the channel between alice and bob. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, aliceBobCh, false) +} + +// testSendUpdateDisableChannel ensures that a channel update with the disable +// flag set is sent once a channel has been either unilaterally or cooperatively +// closed. +func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const ( + chanAmt = 100000 + ) + + // Open a channel between Alice and Bob and Alice and Carol. These will + // be closed later on in order to trigger channel update messages + // marking the channels as disabled. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + carol := net.NewNode( + t.t, "Carol", []string{ + "--minbackoff=10s", + "--chan-enable-timeout=1.5s", + "--chan-disable-timeout=3s", + "--chan-status-sample-interval=.5s", + }) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, net.Alice, carol) + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceCarol := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // We create a new node Eve that has an inactive channel timeout of + // just 2 seconds (down from the default 20m). It will be used to test + // channel updates for channels going inactive. + eve := net.NewNode( + t.t, "Eve", []string{ + "--minbackoff=10s", + "--chan-enable-timeout=1.5s", + "--chan-disable-timeout=3s", + "--chan-status-sample-interval=.5s", + }) + defer shutdownAndAssert(net, t, eve) + + // Give Eve some coins. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, eve) + + // Connect Eve to Carol and Bob, and open a channel to carol. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, eve, carol) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, eve, net.Bob) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointEveCarol := openChannelAndAssert( + ctxt, t, net, eve, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Launch a node for Dave which will connect to Bob in order to receive + // graph updates from. This will ensure that the channel updates are + // propagated throughout the network. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxt, t.t, net.Bob, dave) + + daveSub := subscribeGraphNotifications(ctxb, t, dave) + defer close(daveSub.quit) + + // We should expect to see a channel update with the default routing + // policy, except that it should indicate the channel is disabled. + expectedPolicy := &lnrpc.RoutingPolicy{ + FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat), + FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate), + TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta, + MinHtlc: 1000, // default value + MaxHtlcMsat: calculateMaxHtlc(chanAmt), + Disabled: true, + } + + // Let Carol go offline. Since Eve has an inactive timeout of 2s, we + // expect her to send an update disabling the channel. + restartCarol, err := net.SuspendNode(carol) + if err != nil { + t.Fatalf("unable to suspend carol: %v", err) + } + waitForChannelUpdate( + t, daveSub, + []expectedChanUpdate{ + {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, + }, + ) + + // We restart Carol. Since the channel now becomes active again, Eve + // should send a ChannelUpdate setting the channel no longer disabled. + if err := restartCarol(); err != nil { + t.Fatalf("unable to restart carol: %v", err) + } + + expectedPolicy.Disabled = false + waitForChannelUpdate( + t, daveSub, + []expectedChanUpdate{ + {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, + }, + ) + + // Now we'll test a long disconnection. Disconnect Carol and Eve and + // ensure they both detect each other as disabled. Their min backoffs + // are high enough to not interfere with disabling logic. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, carol, eve); err != nil { + t.Fatalf("unable to disconnect Carol from Eve: %v", err) + } + + // Wait for a disable from both Carol and Eve to come through. + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, daveSub, + []expectedChanUpdate{ + {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, + {carol.PubKeyStr, expectedPolicy, chanPointEveCarol}, + }, + ) + + // Reconnect Carol and Eve, this should cause them to reenable the + // channel from both ends after a short delay. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.EnsureConnected(ctxt, t.t, carol, eve) + + expectedPolicy.Disabled = false + waitForChannelUpdate( + t, daveSub, + []expectedChanUpdate{ + {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, + {carol.PubKeyStr, expectedPolicy, chanPointEveCarol}, + }, + ) + + // Now we'll test a short disconnection. Disconnect Carol and Eve, then + // reconnect them after one second so that their scheduled disables are + // aborted. One second is twice the status sample interval, so this + // should allow for the disconnect to be detected, but still leave time + // to cancel the announcement before the 3 second inactive timeout is + // hit. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, carol, eve); err != nil { + t.Fatalf("unable to disconnect Carol from Eve: %v", err) + } + time.Sleep(time.Second) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.EnsureConnected(ctxt, t.t, eve, carol) + + // Since the disable should have been canceled by both Carol and Eve, we + // expect no channel updates to appear on the network. + assertNoChannelUpdates(t, daveSub, 4*time.Second) + + // Close Alice's channels with Bob and Carol cooperatively and + // unilaterally respectively. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + _, _, err = net.CloseChannel(ctxt, net.Alice, chanPointAliceBob, false) + if err != nil { + t.Fatalf("unable to close channel: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + _, _, err = net.CloseChannel(ctxt, net.Alice, chanPointAliceCarol, true) + if err != nil { + t.Fatalf("unable to close channel: %v", err) + } + + // Now that the channel close processes have been started, we should + // receive an update marking each as disabled. + expectedPolicy.Disabled = true + waitForChannelUpdate( + t, daveSub, + []expectedChanUpdate{ + {net.Alice.PubKeyStr, expectedPolicy, chanPointAliceBob}, + {net.Alice.PubKeyStr, expectedPolicy, chanPointAliceCarol}, + }, + ) + + // Finally, close the channels by mining the closing transactions. + mineBlocks(t, net, 1, 2) + + // Also do this check for Eve's channel with Carol. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + _, _, err = net.CloseChannel(ctxt, eve, chanPointEveCarol, false) + if err != nil { + t.Fatalf("unable to close channel: %v", err) + } + + waitForChannelUpdate( + t, daveSub, + []expectedChanUpdate{ + {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, + }, + ) + mineBlocks(t, net, 1, 1) + + // And finally, clean up the force closed channel by mining the + // sweeping transaction. + cleanupForceClose(t, net, net.Alice, chanPointAliceCarol) +} + +// testAbandonChannel abandones a channel and asserts that it is no +// longer open and not in one of the pending closure states. It also +// verifies that the abandoned channel is reported as closed with close +// type 'abandoned'. +func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // First establish a channel between Alice and Bob. + channelParam := lntest.OpenChannelParams{ + Amt: funding.MaxBtcFundingAmount, + PushAmt: btcutil.Amount(100000), + } + + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPoint := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, channelParam, + ) + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + chanPointStr := fmt.Sprintf("%v:%v", txid, chanPoint.OutputIndex) + + // Wait for channel to be confirmed open. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("alice didn't report channel: %v", err) + } + err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("bob didn't report channel: %v", err) + } + + // Now that the channel is open, we'll obtain its channel ID real quick + // so we can use it to query the graph below. + listReq := &lnrpc.ListChannelsRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceChannelList, err := net.Alice.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to fetch alice's channels: %v", err) + } + var chanID uint64 + for _, channel := range aliceChannelList.Channels { + if channel.ChannelPoint == chanPointStr { + chanID = channel.ChanId + } + } + + if chanID == 0 { + t.Fatalf("unable to find channel") + } + + // To make sure the channel is removed from the backup file as well when + // being abandoned, grab a backup snapshot so we can compare it with the + // later state. + bkupBefore, err := ioutil.ReadFile(net.Alice.ChanBackupPath()) + if err != nil { + t.Fatalf("could not get channel backup before abandoning "+ + "channel: %v", err) + } + + // Send request to abandon channel. + abandonChannelRequest := &lnrpc.AbandonChannelRequest{ + ChannelPoint: chanPoint, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Alice.AbandonChannel(ctxt, abandonChannelRequest) + if err != nil { + t.Fatalf("unable to abandon channel: %v", err) + } + + // Assert that channel in no longer open. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceChannelList, err = net.Alice.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to list channels: %v", err) + } + if len(aliceChannelList.Channels) != 0 { + t.Fatalf("alice should only have no channels open, "+ + "instead she has %v", + len(aliceChannelList.Channels)) + } + + // Assert that channel is not pending closure. + pendingReq := &lnrpc.PendingChannelsRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + alicePendingList, err := net.Alice.PendingChannels(ctxt, pendingReq) + if err != nil { + t.Fatalf("unable to list pending channels: %v", err) + } + if len(alicePendingList.PendingClosingChannels) != 0 { //nolint:staticcheck + t.Fatalf("alice should only have no pending closing channels, "+ + "instead she has %v", + len(alicePendingList.PendingClosingChannels)) //nolint:staticcheck + } + if len(alicePendingList.PendingForceClosingChannels) != 0 { + t.Fatalf("alice should only have no pending force closing "+ + "channels instead she has %v", + len(alicePendingList.PendingForceClosingChannels)) + } + if len(alicePendingList.WaitingCloseChannels) != 0 { + t.Fatalf("alice should only have no waiting close "+ + "channels instead she has %v", + len(alicePendingList.WaitingCloseChannels)) + } + + // Assert that channel is listed as abandoned. + closedReq := &lnrpc.ClosedChannelsRequest{ + Abandoned: true, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceClosedList, err := net.Alice.ClosedChannels(ctxt, closedReq) + if err != nil { + t.Fatalf("unable to list closed channels: %v", err) + } + if len(aliceClosedList.Channels) != 1 { + t.Fatalf("alice should only have a single abandoned channel, "+ + "instead she has %v", + len(aliceClosedList.Channels)) + } + + // Ensure that the channel can no longer be found in the channel graph. + _, err = net.Alice.GetChanInfo(ctxb, &lnrpc.ChanInfoRequest{ + ChanId: chanID, + }) + if !strings.Contains(err.Error(), "marked as zombie") { + t.Fatalf("channel shouldn't be found in the channel " + + "graph!") + } + + // Make sure the channel is no longer in the channel backup list. + err = wait.Predicate(func() bool { + bkupAfter, err := ioutil.ReadFile(net.Alice.ChanBackupPath()) + if err != nil { + t.Fatalf("could not get channel backup before "+ + "abandoning channel: %v", err) + } + + return len(bkupAfter) < len(bkupBefore) + }, defaultTimeout) + if err != nil { + t.Fatalf("channel wasn't removed from channel backup file") + } + + // Calling AbandonChannel again, should result in no new errors, as the + // channel has already been removed. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Alice.AbandonChannel(ctxt, abandonChannelRequest) + if err != nil { + t.Fatalf("unable to abandon channel a second time: %v", err) + } + + // Now that we're done with the test, the channel can be closed. This + // is necessary to avoid unexpected outcomes of other tests that use + // Bob's lnd instance. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPoint, true) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, net.Bob, chanPoint) +} + +// 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() + + // 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) + + // 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. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, ainz) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoinsNP2WKH(ctxt, t.t, btcutil.SatoshiPerBitcoin, ainz) + + // Ensure that we can't send coins to our own Pubkey. + info, err := ainz.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) + if err != nil { + t.Fatalf("unable to get node info: %v", err) + } + + // Create a label that we will used to label the transaction with. + sendCoinsLabel := "send all coins" + + sweepReq := &lnrpc.SendCoinsRequest{ + Addr: info.IdentityPubkey, + SendAll: true, + Label: sendCoinsLabel, + } + _, err = ainz.SendCoins(ctxt, sweepReq) + if err == nil { + t.Fatalf("expected SendCoins to users own pubkey to fail") + } + + // 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) + } + + 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") + } + + // 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 + // 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") + } + + // 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") + } + + // Send coins to a compatible address. + minerAddr, err := net.Miner.NewAddress() + if err != nil { + t.Fatalf("unable to create new miner addr: %v", err) + } + + 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) + } + + // We'll mine a block which should include the sweep transaction we + // generated above. + block := mineBlocks(t, net, 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)) + } + + sweepTxStr := sweepTx.TxHash().String() + assertTxLabel(ctxb, t, ainz, 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. + 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") + } + + // 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) + } + + // 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. + _, 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") + } + + // 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) + } + + // 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) + } + + assertTxLabel(ctxb, t, ainz, 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) + } + + // 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") + } +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index c99a1d24..2ec06254 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -1,34 +1,17 @@ package itest import ( - "bytes" "context" - "crypto/rand" "flag" "fmt" - "io/ioutil" "os" "strings" "testing" "time" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/wallet" - "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/chainreg" - "github.com/lightningnetwork/lnd/funding" - "github.com/lightningnetwork/lnd/lncfg" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" - "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" - "github.com/lightningnetwork/lnd/lntest/wait" - "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -98,1943 +81,6 @@ func getTestCaseSplitTranche() ([]*testCase, uint, uint) { return allTestCases[trancheOffset:trancheEnd], threadID, trancheOffset } -// testDisconnectingTargetPeer performs a test which disconnects Alice-peer from -// Bob-peer and then re-connects them again. We expect Alice to be able to -// disconnect at any point. -func testDisconnectingTargetPeer(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // We'll start both nodes with a high backoff so that they don't - // reconnect automatically during our test. - args := []string{ - "--minbackoff=1m", - "--maxbackoff=1m", - } - - alice := net.NewNode(t.t, "Alice", args) - defer shutdownAndAssert(net, t, alice) - - bob := net.NewNode(t.t, "Bob", args) - defer shutdownAndAssert(net, t, bob) - - // Start by connecting Alice and Bob with no channels. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, alice, bob) - - // Check existing connection. - assertNumConnections(t, alice, bob, 1) - - // Give Alice some coins so she can fund a channel. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, alice) - - chanAmt := funding.MaxBtcFundingAmount - pushAmt := btcutil.Amount(0) - - // Create a new channel that requires 1 confs before it's considered - // open, then broadcast the funding transaction - const numConfs = 1 - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - pendingUpdate, err := net.OpenPendingChannel( - ctxt, alice, bob, chanAmt, pushAmt, - ) - if err != nil { - t.Fatalf("unable to open channel: %v", err) - } - - // At this point, the channel's funding transaction will have been - // broadcast, but not confirmed. Alice and Bob's nodes should reflect - // this when queried via RPC. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - assertNumOpenChannelsPending(ctxt, t, alice, bob, 1) - - // Disconnect Alice-peer from Bob-peer and get error causes by one - // pending channel with detach node is existing. - if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { - t.Fatalf("Bob's peer was disconnected from Alice's"+ - " while one pending channel is existing: err %v", err) - } - - time.Sleep(time.Millisecond * 300) - - // Assert that the connection was torn down. - assertNumConnections(t, alice, bob, 0) - - fundingTxID, err := chainhash.NewHash(pendingUpdate.Txid) - if err != nil { - t.Fatalf("unable to convert funding txid into chainhash.Hash:"+ - " %v", err) - } - - // Mine a block, then wait for Alice's node to notify us that the - // channel has been opened. The funding transaction should be found - // within the newly mined block. - block := mineBlocks(t, net, numConfs, 1)[0] - assertTxInBlock(t, block, fundingTxID) - - // At this point, the channel should be fully opened and there should be - // no pending channels remaining for either node. - time.Sleep(time.Millisecond * 300) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - - assertNumOpenChannelsPending(ctxt, t, alice, bob, 0) - - // Reconnect the nodes so that the channel can become active. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, alice, bob) - - // The channel should be listed in the peer information returned by both - // peers. - outPoint := wire.OutPoint{ - Hash: *fundingTxID, - Index: pendingUpdate.OutputIndex, - } - - // Check both nodes to ensure that the channel is ready for operation. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.AssertChannelExists(ctxt, alice, &outPoint); err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.AssertChannelExists(ctxt, bob, &outPoint); err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - - // Disconnect Alice-peer from Bob-peer and get error causes by one - // active channel with detach node is existing. - if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { - t.Fatalf("Bob's peer was disconnected from Alice's"+ - " while one active channel is existing: err %v", err) - } - - // Check existing connection. - assertNumConnections(t, alice, bob, 0) - - // Reconnect both nodes before force closing the channel. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, alice, bob) - - // Finally, immediately close the channel. This function will also block - // until the channel is closed and will additionally assert the relevant - // channel closing post conditions. - chanPoint := &lnrpc.ChannelPoint{ - FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ - FundingTxidBytes: pendingUpdate.Txid, - }, - OutputIndex: pendingUpdate.OutputIndex, - } - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, alice, chanPoint, true) - - // Disconnect Alice-peer from Bob-peer without getting error about - // existing channels. - if err := net.DisconnectNodes(ctxt, alice, bob); err != nil { - t.Fatalf("unable to disconnect Bob's peer from Alice's: err %v", - err) - } - - // Check zero peer connections. - assertNumConnections(t, alice, bob, 0) - - // Finally, re-connect both nodes. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, alice, bob) - - // Check existing connection. - assertNumConnections(t, alice, net.Bob, 1) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, alice, chanPoint) -} - -// testSphinxReplayPersistence verifies that replayed onion packets are rejected -// by a remote peer after a restart. We use a combination of unsafe -// configuration arguments to force Carol to replay the same sphinx packet after -// reconnecting to Dave, and compare the returned failure message with what we -// expect for replayed onion packets. -func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // Open a channel with 100k satoshis between Carol and Dave with Carol being - // the sole funder of the channel. - chanAmt := btcutil.Amount(100000) - - // First, we'll create Dave, the receiver, and start him in hodl mode. - dave := net.NewNode(t.t, "Dave", []string{"--hodl.exit-settle"}) - - // We must remember to shutdown the nodes we created for the duration - // of the tests, only leaving the two seed nodes (Alice and Bob) within - // our test network. - defer shutdownAndAssert(net, t, dave) - - // Next, we'll create Carol and establish a channel to from her to - // Dave. Carol is started in both unsafe-replay which will cause her to - // replay any pending Adds held in memory upon reconnection. - carol := net.NewNode(t.t, "Carol", []string{"--unsafe-replay"}) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, carol, dave) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Next, we'll create Fred who is going to initiate the payment and - // establish a channel to from him to Carol. We can't perform this test - // by paying from Carol directly to Dave, because the '--unsafe-replay' - // setup doesn't apply to locally added htlcs. In that case, the - // mailbox, that is responsible for generating the replay, is bypassed. - fred := net.NewNode(t.t, "Fred", nil) - defer shutdownAndAssert(net, t, fred) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, fred, carol) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, fred) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointFC := openChannelAndAssert( - ctxt, t, net, fred, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Now that the channel is open, create an invoice for Dave which - // expects a payment of 1000 satoshis from Carol paid via a particular - // preimage. - const paymentAmt = 1000 - preimage := bytes.Repeat([]byte("A"), 32) - invoice := &lnrpc.Invoice{ - Memo: "testing", - RPreimage: preimage, - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err := dave.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - // Wait for all channels to be recognized and advertized. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("alice didn't advertise channel before "+ - "timeout: %v", err) - } - err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("bob didn't advertise channel before "+ - "timeout: %v", err) - } - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointFC) - if err != nil { - t.Fatalf("alice didn't advertise channel before "+ - "timeout: %v", err) - } - err = fred.WaitForNetworkChannelOpen(ctxt, chanPointFC) - if err != nil { - t.Fatalf("bob didn't advertise channel before "+ - "timeout: %v", err) - } - - // With the invoice for Dave added, send a payment from Fred paying - // to the above generated invoice. - ctx, cancel := context.WithCancel(ctxb) - defer cancel() - - payStream, err := fred.RouterClient.SendPaymentV2( - ctx, - &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - if err != nil { - t.Fatalf("unable to open payment stream: %v", err) - } - - time.Sleep(200 * time.Millisecond) - - // Dave's invoice should not be marked as settled. - payHash := &lnrpc.PaymentHash{ - RHash: invoiceResp.RHash, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - dbInvoice, err := dave.LookupInvoice(ctxt, payHash) - if err != nil { - t.Fatalf("unable to lookup invoice: %v", err) - } - if dbInvoice.Settled { - t.Fatalf("dave's invoice should not be marked as settled: %v", - spew.Sdump(dbInvoice)) - } - - // With the payment sent but hedl, all balance related stats should not - // have changed. - err = wait.InvariantNoError( - assertAmountSent(0, carol, dave), 3*time.Second, - ) - if err != nil { - t.Fatalf(err.Error()) - } - - // With the first payment sent, restart dave to make sure he is - // persisting the information required to detect replayed sphinx - // packets. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - - // Carol should retransmit the Add hedl in her mailbox on startup. Dave - // should not accept the replayed Add, and actually fail back the - // pending payment. Even though he still holds the original settle, if - // he does fail, it is almost certainly caused by the sphinx replay - // protection, as it is the only validation we do in hodl mode. - result, err := getPaymentResult(payStream) - if err != nil { - t.Fatalf("unable to receive payment response: %v", err) - } - - // Assert that Fred receives the expected failure after Carol sent a - // duplicate packet that fails due to sphinx replay detection. - if result.Status == lnrpc.Payment_SUCCEEDED { - t.Fatalf("expected payment error") - } - assertLastHTLCError(t, fred, lnrpc.Failure_INVALID_ONION_KEY) - - // Since the payment failed, the balance should still be left - // unaltered. - err = wait.InvariantNoError( - assertAmountSent(0, carol, dave), 3*time.Second, - ) - if err != nil { - t.Fatalf(err.Error()) - } - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPoint, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, carol, chanPoint) -} - -// testListChannels checks that the response from ListChannels is correct. It -// tests the values in all ChannelConstraints are returned as expected. Once -// ListChannels becomes mature, a test against all fields in ListChannels should -// be performed. -func testListChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const aliceRemoteMaxHtlcs = 50 - const bobRemoteMaxHtlcs = 100 - - // Create two fresh nodes and open a channel between them. - alice := net.NewNode(t.t, "Alice", nil) - defer shutdownAndAssert(net, t, alice) - - bob := net.NewNode( - t.t, "Bob", []string{ - fmt.Sprintf( - "--default-remote-max-htlcs=%v", - bobRemoteMaxHtlcs, - ), - }, - ) - defer shutdownAndAssert(net, t, bob) - - // Connect Alice to Bob. - net.ConnectNodes(ctxb, t.t, alice, bob) - - // Give Alice some coins so she can fund a channel. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, alice) - - // Open a channel with 100k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. The minial HTLC amount is set to - // 4200 msats. - const customizedMinHtlc = 4200 - - chanAmt := btcutil.Amount(100000) - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, alice, bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - MinHtlc: customizedMinHtlc, - RemoteMaxHtlcs: aliceRemoteMaxHtlcs, - }, - ) - - // Wait for Alice and Bob to receive the channel edge from the - // funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("alice didn't see the alice->bob channel before "+ - "timeout: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("bob didn't see the bob->alice channel before "+ - "timeout: %v", err) - } - - // Alice should have one channel opened with Bob. - assertNodeNumChannels(t, alice, 1) - // Bob should have one channel opened with Alice. - assertNodeNumChannels(t, bob, 1) - - // Get the ListChannel response from Alice. - listReq := &lnrpc.ListChannelsRequest{} - ctxb = context.Background() - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := alice.ListChannels(ctxt, listReq) - if err != nil { - t.Fatalf("unable to query for %s's channel list: %v", - alice.Name(), err) - } - - // Check the returned response is correct. - aliceChannel := resp.Channels[0] - - // defaultConstraints is a ChannelConstraints with default values. It is - // used to test against Alice's local channel constraints. - defaultConstraints := &lnrpc.ChannelConstraints{ - CsvDelay: 4, - ChanReserveSat: 1000, - DustLimitSat: uint64(lnwallet.DefaultDustLimit()), - MaxPendingAmtMsat: 99000000, - MinHtlcMsat: 1, - MaxAcceptedHtlcs: bobRemoteMaxHtlcs, - } - assertChannelConstraintsEqual( - t, defaultConstraints, aliceChannel.LocalConstraints, - ) - - // customizedConstraints is a ChannelConstraints with customized values. - // Ideally, all these values can be passed in when creating the channel. - // Currently, only the MinHtlcMsat is customized. It is used to check - // against Alice's remote channel constratins. - customizedConstraints := &lnrpc.ChannelConstraints{ - CsvDelay: 4, - ChanReserveSat: 1000, - DustLimitSat: uint64(lnwallet.DefaultDustLimit()), - MaxPendingAmtMsat: 99000000, - MinHtlcMsat: customizedMinHtlc, - MaxAcceptedHtlcs: aliceRemoteMaxHtlcs, - } - assertChannelConstraintsEqual( - t, customizedConstraints, aliceChannel.RemoteConstraints, - ) - - // Get the ListChannel response for Bob. - listReq = &lnrpc.ListChannelsRequest{} - ctxb = context.Background() - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err = bob.ListChannels(ctxt, listReq) - if err != nil { - t.Fatalf("unable to query for %s's channel "+ - "list: %v", bob.Name(), err) - } - - bobChannel := resp.Channels[0] - if bobChannel.ChannelPoint != aliceChannel.ChannelPoint { - t.Fatalf("Bob's channel point mismatched, want: %s, got: %s", - chanPoint.String(), bobChannel.ChannelPoint, - ) - } - - // Check channel constraints match. Alice's local channel constraint should - // be equal to Bob's remote channel constraint, and her remote one should - // be equal to Bob's local one. - assertChannelConstraintsEqual( - t, aliceChannel.LocalConstraints, bobChannel.RemoteConstraints, - ) - assertChannelConstraintsEqual( - t, aliceChannel.RemoteConstraints, bobChannel.LocalConstraints, - ) - -} - -// testMaxPendingChannels checks that error is returned from remote peer if -// max pending channel number was exceeded and that '--maxpendingchannels' flag -// exists and works properly. -func testMaxPendingChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - maxPendingChannels := lncfg.DefaultMaxPendingChannels + 1 - amount := funding.MaxBtcFundingAmount - - // Create a new node (Carol) with greater number of max pending - // channels. - args := []string{ - fmt.Sprintf("--maxpendingchannels=%v", maxPendingChannels), - } - carol := net.NewNode(t.t, "Carol", args) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, net.Alice, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolBalance := btcutil.Amount(maxPendingChannels) * amount - net.SendCoins(ctxt, t.t, carolBalance, carol) - - // Send open channel requests without generating new blocks thereby - // increasing pool of pending channels. Then check that we can't open - // the channel if the number of pending channels exceed max value. - openStreams := make([]lnrpc.Lightning_OpenChannelClient, maxPendingChannels) - for i := 0; i < maxPendingChannels; i++ { - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - stream := openChannelStream( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: amount, - }, - ) - openStreams[i] = stream - } - - // Carol exhausted available amount of pending channels, next open - // channel request should cause ErrorGeneric to be sent back to Alice. - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - _, err := net.OpenChannel( - ctxt, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: amount, - }, - ) - - if err == nil { - t.Fatalf("error wasn't received") - } else if !strings.Contains( - err.Error(), lnwire.ErrMaxPendingChannels.Error(), - ) { - t.Fatalf("not expected error was received: %v", err) - } - - // For now our channels are in pending state, in order to not interfere - // with other tests we should clean up - complete opening of the - // channel and then close it. - - // Mine 6 blocks, then wait for node's to notify us that the channel has - // been opened. The funding transactions should be found within the - // first newly mined block. 6 blocks make sure the funding transaction - // has enough confirmations to be announced publicly. - block := mineBlocks(t, net, 6, maxPendingChannels)[0] - - chanPoints := make([]*lnrpc.ChannelPoint, maxPendingChannels) - for i, stream := range openStreams { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fundingChanPoint, err := net.WaitForChannelOpen(ctxt, stream) - if err != nil { - t.Fatalf("error while waiting for channel open: %v", err) - } - - fundingTxID, err := lnrpc.GetChanPointFundingTxid(fundingChanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - - // Ensure that the funding transaction enters a block, and is - // properly advertised by Alice. - assertTxInBlock(t, block, fundingTxID) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, fundingChanPoint) - if err != nil { - t.Fatalf("channel not seen on network before "+ - "timeout: %v", err) - } - - // The channel should be listed in the peer information - // returned by both peers. - chanPoint := wire.OutPoint{ - Hash: *fundingTxID, - Index: fundingChanPoint.OutputIndex, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.AssertChannelExists(ctxt, net.Alice, &chanPoint); err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - - chanPoints[i] = fundingChanPoint - } - - // Next, close the channel between Alice and Carol, asserting that the - // channel has been properly closed on-chain. - for _, chanPoint := range chanPoints { - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) - } -} - -// testGarbageCollectLinkNodes tests that we properly garbase collect link nodes -// from the database and the set of persistent connections within the server. -func testGarbageCollectLinkNodes(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const ( - chanAmt = 1000000 - ) - - // Open a channel between Alice and Bob which will later be - // cooperatively closed. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - coopChanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Create Carol's node and connect Alice to her. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, net.Alice, carol) - - // Open a channel between Alice and Carol which will later be force - // closed. - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - forceCloseChanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Now, create Dave's a node and also open a channel between Alice and - // him. This link will serve as the only persistent link throughout - // restarts in this test. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - net.ConnectNodes(ctxt, t.t, net.Alice, dave) - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - persistentChanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // isConnected is a helper closure that checks if a peer is connected to - // Alice. - isConnected := func(pubKey string) bool { - req := &lnrpc.ListPeersRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Alice.ListPeers(ctxt, req) - if err != nil { - t.Fatalf("unable to retrieve alice's peers: %v", err) - } - - for _, peer := range resp.Peers { - if peer.PubKey == pubKey { - return true - } - } - - return false - } - - // Restart both Bob and Carol to ensure Alice is able to reconnect to - // them. - if err := net.RestartNode(net.Bob, nil); err != nil { - t.Fatalf("unable to restart bob's node: %v", err) - } - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("unable to restart carol's node: %v", err) - } - - require.Eventually(t.t, func() bool { - return isConnected(net.Bob.PubKeyStr) - }, defaultTimeout, 20*time.Millisecond) - require.Eventually(t.t, func() bool { - return isConnected(carol.PubKeyStr) - }, defaultTimeout, 20*time.Millisecond) - - // We'll also restart Alice to ensure she can reconnect to her peers - // with open channels. - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice's node: %v", err) - } - - require.Eventually(t.t, func() bool { - return isConnected(net.Bob.PubKeyStr) - }, defaultTimeout, 20*time.Millisecond) - require.Eventually(t.t, func() bool { - return isConnected(carol.PubKeyStr) - }, defaultTimeout, 20*time.Millisecond) - require.Eventually(t.t, func() bool { - return isConnected(dave.PubKeyStr) - }, defaultTimeout, 20*time.Millisecond) - err := wait.Predicate(func() bool { - return isConnected(dave.PubKeyStr) - }, defaultTimeout) - - // testReconnection is a helper closure that restarts the nodes at both - // ends of a channel to ensure they do not reconnect after restarting. - // When restarting Alice, we'll first need to ensure she has - // reestablished her connection with Dave, as they still have an open - // channel together. - testReconnection := func(node *lntest.HarnessNode) { - // Restart both nodes, to trigger the pruning logic. - if err := net.RestartNode(node, nil); err != nil { - t.Fatalf("unable to restart %v's node: %v", - node.Name(), err) - } - - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice's node: %v", err) - } - - // Now restart both nodes and make sure they don't reconnect. - if err := net.RestartNode(node, nil); err != nil { - t.Fatalf("unable to restart %v's node: %v", node.Name(), - err) - } - err = wait.Invariant(func() bool { - return !isConnected(node.PubKeyStr) - }, 5*time.Second) - if err != nil { - t.Fatalf("alice reconnected to %v", node.Name()) - } - - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice's node: %v", err) - } - err = wait.Predicate(func() bool { - return isConnected(dave.PubKeyStr) - }, defaultTimeout) - if err != nil { - t.Fatalf("alice didn't reconnect to Dave") - } - - err = wait.Invariant(func() bool { - return !isConnected(node.PubKeyStr) - }, 5*time.Second) - if err != nil { - t.Fatalf("alice reconnected to %v", node.Name()) - } - } - - // Now, we'll close the channel between Alice and Bob and ensure there - // is no reconnection logic between the both once the channel is fully - // closed. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, coopChanPoint, false) - - testReconnection(net.Bob) - - // We'll do the same with Alice and Carol, but this time we'll force - // close the channel instead. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, forceCloseChanPoint, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Alice, forceCloseChanPoint) - - // We'll need to mine some blocks in order to mark the channel fully - // closed. - _, err = net.Miner.Client.Generate(chainreg.DefaultBitcoinTimeLockDelta - defaultCSV) - if err != nil { - t.Fatalf("unable to generate blocks: %v", err) - } - - // Before we test reconnection, we'll ensure that the channel has been - // fully cleaned up for both Carol and Alice. - var predErr error - pendingChansRequest := &lnrpc.PendingChannelsRequest{} - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - pendingChanResp, err := net.Alice.PendingChannels( - ctxt, pendingChansRequest, - ) - if err != nil { - predErr = fmt.Errorf("unable to query for pending "+ - "channels: %v", err) - return false - } - - predErr = checkNumForceClosedChannels(pendingChanResp, 0) - if predErr != nil { - return false - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - pendingChanResp, err = carol.PendingChannels( - ctxt, pendingChansRequest, - ) - if err != nil { - predErr = fmt.Errorf("unable to query for pending "+ - "channels: %v", err) - return false - } - - predErr = checkNumForceClosedChannels(pendingChanResp, 0) - - return predErr == nil - - }, defaultTimeout) - if err != nil { - t.Fatalf("channels not marked as fully resolved: %v", predErr) - } - - testReconnection(carol) - - // Finally, we'll ensure that Bob and Carol no longer show in Alice's - // channel graph. - describeGraphReq := &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: true, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - channelGraph, err := net.Alice.DescribeGraph(ctxt, describeGraphReq) - if err != nil { - t.Fatalf("unable to query for alice's channel graph: %v", err) - } - for _, node := range channelGraph.Nodes { - if node.PubKey == net.Bob.PubKeyStr { - t.Fatalf("did not expect to find bob in the channel " + - "graph, but did") - } - if node.PubKey == carol.PubKeyStr { - t.Fatalf("did not expect to find carol in the channel " + - "graph, but did") - } - } - - // Now that the test is done, we can also close the persistent link. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, persistentChanPoint, false) -} - -// testDataLossProtection tests that if one of the nodes in a channel -// relationship lost state, they will detect this during channel sync, and the -// up-to-date party will force close the channel, giving the outdated party the -// opportunity to sweep its output. -func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - const ( - chanAmt = funding.MaxBtcFundingAmount - paymentAmt = 10000 - numInvoices = 6 - ) - - // Carol will be the up-to-date party. We set --nolisten to ensure Dave - // won't be able to connect to her and trigger the channel data - // protection logic automatically. We also can't have Carol - // automatically re-connect too early, otherwise DLP would be initiated - // at the wrong moment. - carol := net.NewNode( - t.t, "Carol", []string{"--nolisten", "--minbackoff=1h"}, - ) - defer shutdownAndAssert(net, t, carol) - - // Dave will be the party losing his state. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - // Before we make a channel, we'll load up Carol with some coins sent - // directly from the miner. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - // timeTravel is a method that will make Carol open a channel to the - // passed node, settle a series of payments, then reset the node back - // to the state before the payments happened. When this method returns - // the node will be unaware of the new state updates. The returned - // function can be used to restart the node in this state. - timeTravel := func(node *lntest.HarnessNode) (func() error, - *lnrpc.ChannelPoint, int64, error) { - - // We must let the node communicate with Carol before they are - // able to open channel, so we connect them. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.EnsureConnected(ctxt, t.t, carol, node) - - // We'll first open up a channel between them with a 0.5 BTC - // value. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, carol, node, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // With the channel open, we'll create a few invoices for the - // node that Carol will pay to in order to advance the state of - // the channel. - // TODO(halseth): have dangling HTLCs on the commitment, able to - // retrieve funds? - payReqs, _, _, err := createPayReqs( - node, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Wait for Carol to receive the channel edge from the funding - // manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("carol didn't see the carol->%s channel "+ - "before timeout: %v", node.Name(), err) - } - - // Send payments from Carol using 3 of the payment hashes - // generated above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, carol, carol.RouterClient, - payReqs[:numInvoices/2], true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // Next query for the node's channel state, as we sent 3 - // payments of 10k satoshis each, it should now see his balance - // as being 30k satoshis. - var nodeChan *lnrpc.Channel - var predErr error - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - bChan, err := getChanInfo(ctxt, node) - if err != nil { - t.Fatalf("unable to get channel info: %v", err) - } - if bChan.LocalBalance != 30000 { - predErr = fmt.Errorf("balance is incorrect, "+ - "got %v, expected %v", - bChan.LocalBalance, 30000) - return false - } - - nodeChan = bChan - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } - - // Grab the current commitment height (update number), we'll - // later revert him to this state after additional updates to - // revoke this state. - stateNumPreCopy := nodeChan.NumUpdates - - // With the temporary file created, copy the current state into - // the temporary file we created above. Later after more - // updates, we'll restore this state. - if err := net.BackupDb(node); err != nil { - t.Fatalf("unable to copy database files: %v", err) - } - - // Finally, send more payments from , using the remaining - // payment hashes. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, carol, carol.RouterClient, - payReqs[numInvoices/2:], true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - nodeChan, err = getChanInfo(ctxt, node) - if err != nil { - t.Fatalf("unable to get dave chan info: %v", err) - } - - // Now we shutdown the node, copying over the its temporary - // database state which has the *prior* channel state over his - // current most up to date state. With this, we essentially - // force the node to travel back in time within the channel's - // history. - if err = net.RestartNode(node, func() error { - return net.RestoreDb(node) - }); err != nil { - t.Fatalf("unable to restart node: %v", err) - } - - // Make sure the channel is still there from the PoV of the - // node. - assertNodeNumChannels(t, node, 1) - - // Now query for the channel state, it should show that it's at - // a state number in the past, not the *latest* state. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - nodeChan, err = getChanInfo(ctxt, node) - if err != nil { - t.Fatalf("unable to get dave chan info: %v", err) - } - if nodeChan.NumUpdates != stateNumPreCopy { - t.Fatalf("db copy failed: %v", nodeChan.NumUpdates) - } - - balReq := &lnrpc.WalletBalanceRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - balResp, err := node.WalletBalance(ctxt, balReq) - if err != nil { - t.Fatalf("unable to get dave's balance: %v", err) - } - - restart, err := net.SuspendNode(node) - if err != nil { - t.Fatalf("unable to suspend node: %v", err) - } - - return restart, chanPoint, balResp.ConfirmedBalance, nil - } - - // Reset Dave to a state where he has an outdated channel state. - restartDave, _, daveStartingBalance, err := timeTravel(dave) - if err != nil { - t.Fatalf("unable to time travel dave: %v", err) - } - - // We make a note of the nodes' current on-chain balances, to make sure - // they are able to retrieve the channel funds eventually, - balReq := &lnrpc.WalletBalanceRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolBalResp, err := carol.WalletBalance(ctxt, balReq) - if err != nil { - t.Fatalf("unable to get carol's balance: %v", err) - } - carolStartingBalance := carolBalResp.ConfirmedBalance - - // Restart Dave to trigger a channel resync. - if err := restartDave(); err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - - // Assert that once Dave comes up, they reconnect, Carol force closes - // on chain, and both of them properly carry out the DLP protocol. - assertDLPExecuted( - net, t, carol, carolStartingBalance, dave, daveStartingBalance, - false, - ) - - // As a second part of this test, we will test the scenario where a - // channel is closed while Dave is offline, loses his state and comes - // back online. In this case the node should attempt to resync the - // channel, and the peer should resend a channel sync message for the - // closed channel, such that Dave can retrieve his funds. - // - // We start by letting Dave time travel back to an outdated state. - restartDave, chanPoint2, daveStartingBalance, err := timeTravel(dave) - if err != nil { - t.Fatalf("unable to time travel eve: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolBalResp, err = carol.WalletBalance(ctxt, balReq) - if err != nil { - t.Fatalf("unable to get carol's balance: %v", err) - } - carolStartingBalance = carolBalResp.ConfirmedBalance - - // Now let Carol force close the channel while Dave is offline. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPoint2, true) - - // Wait for the channel to be marked pending force close. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = waitForChannelPendingForceClose(ctxt, carol, chanPoint2) - if err != nil { - t.Fatalf("channel not pending force close: %v", err) - } - - // Mine enough blocks for Carol to sweep her funds. - mineBlocks(t, net, defaultCSV-1, 0) - - carolSweep, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Carol's sweep tx in mempool: %v", err) - } - block := mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, carolSweep) - - // Now the channel should be fully closed also from Carol's POV. - assertNumPendingChannels(t, carol, 0, 0) - - // Make sure Carol got her balance back. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolBalResp, err = carol.WalletBalance(ctxt, balReq) - if err != nil { - t.Fatalf("unable to get carol's balance: %v", err) - } - carolBalance := carolBalResp.ConfirmedBalance - if carolBalance <= carolStartingBalance { - t.Fatalf("expected carol to have balance above %d, "+ - "instead had %v", carolStartingBalance, - carolBalance) - } - - assertNodeNumChannels(t, carol, 0) - - // When Dave comes online, he will reconnect to Carol, try to resync - // the channel, but it will already be closed. Carol should resend the - // information Dave needs to sweep his funds. - if err := restartDave(); err != nil { - t.Fatalf("unable to restart Eve: %v", err) - } - - // Dave should sweep his funds. - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - if err != nil { - t.Fatalf("unable to find Dave's sweep tx in mempool: %v", err) - } - - // Mine a block to confirm the sweep, and make sure Dave got his - // balance back. - mineBlocks(t, net, 1, 1) - assertNodeNumChannels(t, dave, 0) - - err = wait.NoError(func() error { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - daveBalResp, err := dave.WalletBalance(ctxt, balReq) - if err != nil { - return fmt.Errorf("unable to get dave's balance: %v", - err) - } - - daveBalance := daveBalResp.ConfirmedBalance - if daveBalance <= daveStartingBalance { - return fmt.Errorf("expected dave to have balance "+ - "above %d, intead had %v", daveStartingBalance, - daveBalance) - } - - return nil - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", err) - } -} - -// testRejectHTLC tests that a node can be created with the flag --rejecthtlc. -// This means that the node will reject all forwarded HTLCs but can still -// accept direct HTLCs as well as send HTLCs. -func testRejectHTLC(net *lntest.NetworkHarness, t *harnessTest) { - // RejectHTLC - // Alice ------> Carol ------> Bob - // - const chanAmt = btcutil.Amount(1000000) - ctxb := context.Background() - - // Create Carol with reject htlc flag. - carol := net.NewNode(t.t, "Carol", []string{"--rejecthtlc"}) - defer shutdownAndAssert(net, t, carol) - - // Connect Alice to Carol. - net.ConnectNodes(ctxb, t.t, net.Alice, carol) - - // Connect Carol to Bob. - net.ConnectNodes(ctxb, t.t, carol, net.Bob) - - // Send coins to Carol. - net.SendCoins(ctxb, t.t, btcutil.SatoshiPerBitcoin, carol) - - // Send coins to Alice. - net.SendCoins(ctxb, t.t, btcutil.SatoshiPerBitcent, net.Alice) - - // Open a channel between Alice and Carol. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Open a channel between Carol and Bob. - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Channel should be ready for payments. - const payAmt = 100 - - // Helper closure to generate a random pre image. - genPreImage := func() []byte { - preimage := make([]byte, 32) - - _, err := rand.Read(preimage) - if err != nil { - t.Fatalf("unable to generate preimage: %v", err) - } - - return preimage - } - - // Create an invoice from Carol of 100 satoshis. - // We expect Alice to be able to pay this invoice. - preimage := genPreImage() - - carolInvoice := &lnrpc.Invoice{ - Memo: "testing - alice should pay carol", - RPreimage: preimage, - Value: payAmt, - } - - // Carol adds the invoice to her database. - resp, err := carol.AddInvoice(ctxb, carolInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - // Alice pays Carols invoice. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, - []string{resp.PaymentRequest}, true, - ) - if err != nil { - t.Fatalf("unable to send payments from alice to carol: %v", err) - } - - // Create an invoice from Bob of 100 satoshis. - // We expect Carol to be able to pay this invoice. - preimage = genPreImage() - - bobInvoice := &lnrpc.Invoice{ - Memo: "testing - carol should pay bob", - RPreimage: preimage, - Value: payAmt, - } - - // Bob adds the invoice to his database. - resp, err = net.Bob.AddInvoice(ctxb, bobInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - // Carol pays Bobs invoice. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, carol, carol.RouterClient, - []string{resp.PaymentRequest}, true, - ) - if err != nil { - t.Fatalf("unable to send payments from carol to bob: %v", err) - } - - // Create an invoice from Bob of 100 satoshis. - // Alice attempts to pay Bob but this should fail, since we are - // using Carol as a hop and her node will reject onward HTLCs. - preimage = genPreImage() - - bobInvoice = &lnrpc.Invoice{ - Memo: "testing - alice tries to pay bob", - RPreimage: preimage, - Value: payAmt, - } - - // Bob adds the invoice to his database. - resp, err = net.Bob.AddInvoice(ctxb, bobInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - - // Alice attempts to pay Bobs invoice. This payment should be rejected since - // we are using Carol as an intermediary hop, Carol is running lnd with - // --rejecthtlc. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, - []string{resp.PaymentRequest}, true, - ) - if err == nil { - t.Fatalf( - "should have been rejected, carol will not accept forwarded htlcs", - ) - } - - assertLastHTLCError(t, net.Alice, lnrpc.Failure_CHANNEL_DISABLED) - - // Close all channels. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) -} - -func testNodeSignVerify(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - chanAmt := funding.MaxBtcFundingAmount - pushAmt := btcutil.Amount(100000) - - // Create a channel between alice and bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - aliceBobCh := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - }, - ) - - aliceMsg := []byte("alice msg") - - // alice signs "alice msg" and sends her signature to bob. - sigReq := &lnrpc.SignMessageRequest{Msg: aliceMsg} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - sigResp, err := net.Alice.SignMessage(ctxt, sigReq) - if err != nil { - t.Fatalf("SignMessage rpc call failed: %v", err) - } - aliceSig := sigResp.Signature - - // bob verifying alice's signature should succeed since alice and bob are - // connected. - verifyReq := &lnrpc.VerifyMessageRequest{Msg: aliceMsg, Signature: aliceSig} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - verifyResp, err := net.Bob.VerifyMessage(ctxt, verifyReq) - if err != nil { - t.Fatalf("VerifyMessage failed: %v", err) - } - if !verifyResp.Valid { - t.Fatalf("alice's signature didn't validate") - } - if verifyResp.Pubkey != net.Alice.PubKeyStr { - t.Fatalf("alice's signature doesn't contain alice's pubkey.") - } - - // carol is a new node that is unconnected to alice or bob. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - carolMsg := []byte("carol msg") - - // carol signs "carol msg" and sends her signature to bob. - sigReq = &lnrpc.SignMessageRequest{Msg: carolMsg} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - sigResp, err = carol.SignMessage(ctxt, sigReq) - if err != nil { - t.Fatalf("SignMessage rpc call failed: %v", err) - } - carolSig := sigResp.Signature - - // bob verifying carol's signature should fail since they are not connected. - verifyReq = &lnrpc.VerifyMessageRequest{Msg: carolMsg, Signature: carolSig} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - verifyResp, err = net.Bob.VerifyMessage(ctxt, verifyReq) - if err != nil { - t.Fatalf("VerifyMessage failed: %v", err) - } - if verifyResp.Valid { - t.Fatalf("carol's signature should not be valid") - } - if verifyResp.Pubkey != carol.PubKeyStr { - t.Fatalf("carol's signature doesn't contain her pubkey") - } - - // Close the channel between alice and bob. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, aliceBobCh, false) -} - -// testSendUpdateDisableChannel ensures that a channel update with the disable -// flag set is sent once a channel has been either unilaterally or cooperatively -// closed. -func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const ( - chanAmt = 100000 - ) - - // Open a channel between Alice and Bob and Alice and Carol. These will - // be closed later on in order to trigger channel update messages - // marking the channels as disabled. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAliceBob := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - carol := net.NewNode( - t.t, "Carol", []string{ - "--minbackoff=10s", - "--chan-enable-timeout=1.5s", - "--chan-disable-timeout=3s", - "--chan-status-sample-interval=.5s", - }) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, net.Alice, carol) - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAliceCarol := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // We create a new node Eve that has an inactive channel timeout of - // just 2 seconds (down from the default 20m). It will be used to test - // channel updates for channels going inactive. - eve := net.NewNode( - t.t, "Eve", []string{ - "--minbackoff=10s", - "--chan-enable-timeout=1.5s", - "--chan-disable-timeout=3s", - "--chan-status-sample-interval=.5s", - }) - defer shutdownAndAssert(net, t, eve) - - // Give Eve some coins. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, eve) - - // Connect Eve to Carol and Bob, and open a channel to carol. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, eve, carol) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, eve, net.Bob) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointEveCarol := openChannelAndAssert( - ctxt, t, net, eve, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Launch a node for Dave which will connect to Bob in order to receive - // graph updates from. This will ensure that the channel updates are - // propagated throughout the network. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxt, t.t, net.Bob, dave) - - daveSub := subscribeGraphNotifications(ctxb, t, dave) - defer close(daveSub.quit) - - // We should expect to see a channel update with the default routing - // policy, except that it should indicate the channel is disabled. - expectedPolicy := &lnrpc.RoutingPolicy{ - FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat), - FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate), - TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta, - MinHtlc: 1000, // default value - MaxHtlcMsat: calculateMaxHtlc(chanAmt), - Disabled: true, - } - - // Let Carol go offline. Since Eve has an inactive timeout of 2s, we - // expect her to send an update disabling the channel. - restartCarol, err := net.SuspendNode(carol) - if err != nil { - t.Fatalf("unable to suspend carol: %v", err) - } - waitForChannelUpdate( - t, daveSub, - []expectedChanUpdate{ - {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, - }, - ) - - // We restart Carol. Since the channel now becomes active again, Eve - // should send a ChannelUpdate setting the channel no longer disabled. - if err := restartCarol(); err != nil { - t.Fatalf("unable to restart carol: %v", err) - } - - expectedPolicy.Disabled = false - waitForChannelUpdate( - t, daveSub, - []expectedChanUpdate{ - {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, - }, - ) - - // Now we'll test a long disconnection. Disconnect Carol and Eve and - // ensure they both detect each other as disabled. Their min backoffs - // are high enough to not interfere with disabling logic. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.DisconnectNodes(ctxt, carol, eve); err != nil { - t.Fatalf("unable to disconnect Carol from Eve: %v", err) - } - - // Wait for a disable from both Carol and Eve to come through. - expectedPolicy.Disabled = true - waitForChannelUpdate( - t, daveSub, - []expectedChanUpdate{ - {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, - {carol.PubKeyStr, expectedPolicy, chanPointEveCarol}, - }, - ) - - // Reconnect Carol and Eve, this should cause them to reenable the - // channel from both ends after a short delay. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.EnsureConnected(ctxt, t.t, carol, eve) - - expectedPolicy.Disabled = false - waitForChannelUpdate( - t, daveSub, - []expectedChanUpdate{ - {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, - {carol.PubKeyStr, expectedPolicy, chanPointEveCarol}, - }, - ) - - // Now we'll test a short disconnection. Disconnect Carol and Eve, then - // reconnect them after one second so that their scheduled disables are - // aborted. One second is twice the status sample interval, so this - // should allow for the disconnect to be detected, but still leave time - // to cancel the announcement before the 3 second inactive timeout is - // hit. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.DisconnectNodes(ctxt, carol, eve); err != nil { - t.Fatalf("unable to disconnect Carol from Eve: %v", err) - } - time.Sleep(time.Second) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.EnsureConnected(ctxt, t.t, eve, carol) - - // Since the disable should have been canceled by both Carol and Eve, we - // expect no channel updates to appear on the network. - assertNoChannelUpdates(t, daveSub, 4*time.Second) - - // Close Alice's channels with Bob and Carol cooperatively and - // unilaterally respectively. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - _, _, err = net.CloseChannel(ctxt, net.Alice, chanPointAliceBob, false) - if err != nil { - t.Fatalf("unable to close channel: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - _, _, err = net.CloseChannel(ctxt, net.Alice, chanPointAliceCarol, true) - if err != nil { - t.Fatalf("unable to close channel: %v", err) - } - - // Now that the channel close processes have been started, we should - // receive an update marking each as disabled. - expectedPolicy.Disabled = true - waitForChannelUpdate( - t, daveSub, - []expectedChanUpdate{ - {net.Alice.PubKeyStr, expectedPolicy, chanPointAliceBob}, - {net.Alice.PubKeyStr, expectedPolicy, chanPointAliceCarol}, - }, - ) - - // Finally, close the channels by mining the closing transactions. - mineBlocks(t, net, 1, 2) - - // Also do this check for Eve's channel with Carol. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - _, _, err = net.CloseChannel(ctxt, eve, chanPointEveCarol, false) - if err != nil { - t.Fatalf("unable to close channel: %v", err) - } - - waitForChannelUpdate( - t, daveSub, - []expectedChanUpdate{ - {eve.PubKeyStr, expectedPolicy, chanPointEveCarol}, - }, - ) - mineBlocks(t, net, 1, 1) - - // And finally, clean up the force closed channel by mining the - // sweeping transaction. - cleanupForceClose(t, net, net.Alice, chanPointAliceCarol) -} - -// testAbandonChannel abandones a channel and asserts that it is no -// longer open and not in one of the pending closure states. It also -// verifies that the abandoned channel is reported as closed with close -// type 'abandoned'. -func testAbandonChannel(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // First establish a channel between Alice and Bob. - channelParam := lntest.OpenChannelParams{ - Amt: funding.MaxBtcFundingAmount, - PushAmt: btcutil.Amount(100000), - } - - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, channelParam, - ) - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - chanPointStr := fmt.Sprintf("%v:%v", txid, chanPoint.OutputIndex) - - // Wait for channel to be confirmed open. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("alice didn't report channel: %v", err) - } - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("bob didn't report channel: %v", err) - } - - // Now that the channel is open, we'll obtain its channel ID real quick - // so we can use it to query the graph below. - listReq := &lnrpc.ListChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceChannelList, err := net.Alice.ListChannels(ctxt, listReq) - if err != nil { - t.Fatalf("unable to fetch alice's channels: %v", err) - } - var chanID uint64 - for _, channel := range aliceChannelList.Channels { - if channel.ChannelPoint == chanPointStr { - chanID = channel.ChanId - } - } - - if chanID == 0 { - t.Fatalf("unable to find channel") - } - - // To make sure the channel is removed from the backup file as well when - // being abandoned, grab a backup snapshot so we can compare it with the - // later state. - bkupBefore, err := ioutil.ReadFile(net.Alice.ChanBackupPath()) - if err != nil { - t.Fatalf("could not get channel backup before abandoning "+ - "channel: %v", err) - } - - // Send request to abandon channel. - abandonChannelRequest := &lnrpc.AbandonChannelRequest{ - ChannelPoint: chanPoint, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Alice.AbandonChannel(ctxt, abandonChannelRequest) - if err != nil { - t.Fatalf("unable to abandon channel: %v", err) - } - - // Assert that channel in no longer open. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceChannelList, err = net.Alice.ListChannels(ctxt, listReq) - if err != nil { - t.Fatalf("unable to list channels: %v", err) - } - if len(aliceChannelList.Channels) != 0 { - t.Fatalf("alice should only have no channels open, "+ - "instead she has %v", - len(aliceChannelList.Channels)) - } - - // Assert that channel is not pending closure. - pendingReq := &lnrpc.PendingChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - alicePendingList, err := net.Alice.PendingChannels(ctxt, pendingReq) - if err != nil { - t.Fatalf("unable to list pending channels: %v", err) - } - if len(alicePendingList.PendingClosingChannels) != 0 { //nolint:staticcheck - t.Fatalf("alice should only have no pending closing channels, "+ - "instead she has %v", - len(alicePendingList.PendingClosingChannels)) //nolint:staticcheck - } - if len(alicePendingList.PendingForceClosingChannels) != 0 { - t.Fatalf("alice should only have no pending force closing "+ - "channels instead she has %v", - len(alicePendingList.PendingForceClosingChannels)) - } - if len(alicePendingList.WaitingCloseChannels) != 0 { - t.Fatalf("alice should only have no waiting close "+ - "channels instead she has %v", - len(alicePendingList.WaitingCloseChannels)) - } - - // Assert that channel is listed as abandoned. - closedReq := &lnrpc.ClosedChannelsRequest{ - Abandoned: true, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceClosedList, err := net.Alice.ClosedChannels(ctxt, closedReq) - if err != nil { - t.Fatalf("unable to list closed channels: %v", err) - } - if len(aliceClosedList.Channels) != 1 { - t.Fatalf("alice should only have a single abandoned channel, "+ - "instead she has %v", - len(aliceClosedList.Channels)) - } - - // Ensure that the channel can no longer be found in the channel graph. - _, err = net.Alice.GetChanInfo(ctxb, &lnrpc.ChanInfoRequest{ - ChanId: chanID, - }) - if !strings.Contains(err.Error(), "marked as zombie") { - t.Fatalf("channel shouldn't be found in the channel " + - "graph!") - } - - // Make sure the channel is no longer in the channel backup list. - err = wait.Predicate(func() bool { - bkupAfter, err := ioutil.ReadFile(net.Alice.ChanBackupPath()) - if err != nil { - t.Fatalf("could not get channel backup before "+ - "abandoning channel: %v", err) - } - - return len(bkupAfter) < len(bkupBefore) - }, defaultTimeout) - if err != nil { - t.Fatalf("channel wasn't removed from channel backup file") - } - - // Calling AbandonChannel again, should result in no new errors, as the - // channel has already been removed. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Alice.AbandonChannel(ctxt, abandonChannelRequest) - if err != nil { - t.Fatalf("unable to abandon channel a second time: %v", err) - } - - // Now that we're done with the test, the channel can be closed. This - // is necessary to avoid unexpected outcomes of other tests that use - // Bob's lnd instance. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPoint, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Bob, chanPoint) -} - -// 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() - - // 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) - - // 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. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, ainz) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoinsNP2WKH(ctxt, t.t, btcutil.SatoshiPerBitcoin, ainz) - - // Ensure that we can't send coins to our own Pubkey. - info, err := ainz.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) - if err != nil { - t.Fatalf("unable to get node info: %v", err) - } - - // Create a label that we will used to label the transaction with. - sendCoinsLabel := "send all coins" - - sweepReq := &lnrpc.SendCoinsRequest{ - Addr: info.IdentityPubkey, - SendAll: true, - Label: sendCoinsLabel, - } - _, err = ainz.SendCoins(ctxt, sweepReq) - if err == nil { - t.Fatalf("expected SendCoins to users own pubkey to fail") - } - - // 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) - } - - 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") - } - - // 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 - // 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") - } - - // 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") - } - - // Send coins to a compatible address. - minerAddr, err := net.Miner.NewAddress() - if err != nil { - t.Fatalf("unable to create new miner addr: %v", err) - } - - 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) - } - - // We'll mine a block which should include the sweep transaction we - // generated above. - block := mineBlocks(t, net, 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)) - } - - sweepTxStr := sweepTx.TxHash().String() - assertTxLabel(ctxb, t, ainz, 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. - 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") - } - - // 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) - } - - // 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. - _, 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") - } - - // 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) - } - - // 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) - } - - assertTxLabel(ctxb, t, ainz, 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) - } - - // 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") - } -} - // TestLightningNetworkDaemon performs a series of integration tests amongst a // programmatically driven network of lnd nodes. func TestLightningNetworkDaemon(t *testing.T) {