diff --git a/lntest/itest/lnd_channel_balance_test.go b/lntest/itest/lnd_channel_balance_test.go new file mode 100644 index 00000000..14552fd5 --- /dev/null +++ b/lntest/itest/lnd_channel_balance_test.go @@ -0,0 +1,281 @@ +package itest + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/funding" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +// testChannelBalance creates a new channel between Alice and Bob, then checks +// channel balance to be equal amount specified while creation of channel. +func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // Open a channel with 0.16 BTC between Alice and Bob, ensuring the + // channel has been opened properly. + amount := funding.MaxBtcFundingAmount + + // Creates a helper closure to be used below which asserts the proper + // response to a channel balance RPC. + checkChannelBalance := func(node *lntest.HarnessNode, + local, remote btcutil.Amount) { + + expectedResponse := &lnrpc.ChannelBalanceResponse{ + LocalBalance: &lnrpc.Amount{ + Sat: uint64(local), + Msat: uint64(lnwire.NewMSatFromSatoshis(local)), + }, + RemoteBalance: &lnrpc.Amount{ + Sat: uint64(remote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + remote, + )), + }, + UnsettledLocalBalance: &lnrpc.Amount{}, + UnsettledRemoteBalance: &lnrpc.Amount{}, + PendingOpenLocalBalance: &lnrpc.Amount{}, + PendingOpenRemoteBalance: &lnrpc.Amount{}, + // Deprecated fields. + Balance: int64(local), + } + assertChannelBalanceResp(t, node, expectedResponse) + } + + // Before beginning, make sure alice and bob are connected. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.EnsureConnected(ctxt, t.t, net.Alice, net.Bob) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPoint := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: amount, + }, + ) + + // Wait for both Alice and Bob to recognize this new channel. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("alice didn't advertise channel before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("bob didn't advertise channel before "+ + "timeout: %v", err) + } + + cType, err := channelCommitType(net.Alice, chanPoint) + if err != nil { + t.Fatalf("unable to get channel type: %v", err) + } + + // As this is a single funder channel, Alice's balance should be + // exactly 0.5 BTC since now state transitions have taken place yet. + checkChannelBalance(net.Alice, amount-cType.calcStaticFee(0), 0) + + // Ensure Bob currently has no available balance within the channel. + checkChannelBalance(net.Bob, 0, amount-cType.calcStaticFee(0)) + + // Finally close the channel between Alice and Bob, asserting that the + // channel has been properly closed on-chain. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) +} + +// testChannelUnsettledBalance will test that the UnsettledBalance field +// is updated according to the number of Pending Htlcs. +// Alice will send Htlcs to Carol while she is in hodl mode. This will result +// in a build of pending Htlcs. We expect the channels unsettled balance to +// equal the sum of all the Pending Htlcs. +func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) { + const chanAmt = btcutil.Amount(1000000) + ctxb := context.Background() + + // Creates a helper closure to be used below which asserts the proper + // response to a channel balance RPC. + checkChannelBalance := func(node *lntest.HarnessNode, + local, remote, unsettledLocal, unsettledRemote btcutil.Amount) { + + expectedResponse := &lnrpc.ChannelBalanceResponse{ + LocalBalance: &lnrpc.Amount{ + Sat: uint64(local), + Msat: uint64(lnwire.NewMSatFromSatoshis( + local, + )), + }, + RemoteBalance: &lnrpc.Amount{ + Sat: uint64(remote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + remote, + )), + }, + UnsettledLocalBalance: &lnrpc.Amount{ + Sat: uint64(unsettledLocal), + Msat: uint64(lnwire.NewMSatFromSatoshis( + unsettledLocal, + )), + }, + UnsettledRemoteBalance: &lnrpc.Amount{ + Sat: uint64(unsettledRemote), + Msat: uint64(lnwire.NewMSatFromSatoshis( + unsettledRemote, + )), + }, + PendingOpenLocalBalance: &lnrpc.Amount{}, + PendingOpenRemoteBalance: &lnrpc.Amount{}, + // Deprecated fields. + Balance: int64(local), + } + assertChannelBalanceResp(t, node, expectedResponse) + } + + // Create carol in hodl mode. + carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) + defer shutdownAndAssert(net, t, carol) + + // Connect Alice to Carol. + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + net.ConnectNodes(ctxb, t.t, net.Alice, carol) + + // Open a channel between Alice and Carol. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Wait for Alice and Carol to receive the channel edge from the + // funding manager. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("alice didn't see the alice->carol channel before "+ + "timeout: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("alice didn't see the alice->carol channel before "+ + "timeout: %v", err) + } + + cType, err := channelCommitType(net.Alice, chanPointAlice) + require.NoError(t.t, err, "unable to get channel type") + + // Check alice's channel balance, which should have zero remote and zero + // pending balance. + checkChannelBalance(net.Alice, chanAmt-cType.calcStaticFee(0), 0, 0, 0) + + // Check carol's channel balance, which should have zero local and zero + // pending balance. + checkChannelBalance(carol, 0, chanAmt-cType.calcStaticFee(0), 0, 0) + + // Channel should be ready for payments. + const ( + payAmt = 100 + numInvoices = 6 + ) + + // Simulateneously send numInvoices payments from Alice to Carol. + carolPubKey := carol.PubKey[:] + errChan := make(chan error) + for i := 0; i < numInvoices; i++ { + go func() { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err := net.Alice.RouterClient.SendPaymentV2(ctxt, + &routerrpc.SendPaymentRequest{ + Dest: carolPubKey, + Amt: int64(payAmt), + PaymentHash: makeFakePayHash(t), + FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + }) + + if err != nil { + errChan <- err + } + }() + } + + // Test that the UnsettledBalance for both Alice and Carol + // is equal to the amount of invoices * payAmt. + var unsettledErr error + nodes := []*lntest.HarnessNode{net.Alice, carol} + err = wait.Predicate(func() bool { + // There should be a number of PendingHtlcs equal + // to the amount of Invoices sent. + unsettledErr = assertNumActiveHtlcs(nodes, numInvoices) + if unsettledErr != nil { + return false + } + + // Set the amount expected for the Unsettled Balance for + // this channel. + expectedBalance := numInvoices * payAmt + + // Check each nodes UnsettledBalance field. + for _, node := range nodes { + // Get channel info for the node. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanInfo, err := getChanInfo(ctxt, node) + if err != nil { + unsettledErr = err + return false + } + + // Check that UnsettledBalance is what we expect. + if int(chanInfo.UnsettledBalance) != expectedBalance { + unsettledErr = fmt.Errorf("unsettled balance failed "+ + "expected: %v, received: %v", expectedBalance, + chanInfo.UnsettledBalance) + return false + } + } + + return true + }, defaultTimeout) + if err != nil { + t.Fatalf("unsettled balace error: %v", unsettledErr) + } + + // Check for payment errors. + select { + case err := <-errChan: + t.Fatalf("payment error: %v", err) + default: + } + + // Check alice's channel balance, which should have a remote unsettled + // balance that equals to the amount of invoices * payAmt. The remote + // balance remains zero. + aliceLocal := chanAmt - cType.calcStaticFee(0) - numInvoices*payAmt + checkChannelBalance(net.Alice, aliceLocal, 0, 0, numInvoices*payAmt) + + // Check carol's channel balance, which should have a local unsettled + // balance that equals to the amount of invoices * payAmt. The local + // balance remains zero. + checkChannelBalance(carol, 0, aliceLocal, numInvoices*payAmt, 0) + + // Force and assert the channel closure. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, true) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, net.Alice, chanPointAlice) +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 99c21c6d..a699f3a6 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -612,271 +612,6 @@ func findTxAtHeight(ctx context.Context, t *harnessTest, height int32, return nil } -// testChannelBalance creates a new channel between Alice and Bob, then checks -// channel balance to be equal amount specified while creation of channel. -func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // Open a channel with 0.16 BTC between Alice and Bob, ensuring the - // channel has been opened properly. - amount := funding.MaxBtcFundingAmount - - // Creates a helper closure to be used below which asserts the proper - // response to a channel balance RPC. - checkChannelBalance := func(node *lntest.HarnessNode, - local, remote btcutil.Amount) { - - expectedResponse := &lnrpc.ChannelBalanceResponse{ - LocalBalance: &lnrpc.Amount{ - Sat: uint64(local), - Msat: uint64(lnwire.NewMSatFromSatoshis(local)), - }, - RemoteBalance: &lnrpc.Amount{ - Sat: uint64(remote), - Msat: uint64(lnwire.NewMSatFromSatoshis( - remote, - )), - }, - UnsettledLocalBalance: &lnrpc.Amount{}, - UnsettledRemoteBalance: &lnrpc.Amount{}, - PendingOpenLocalBalance: &lnrpc.Amount{}, - PendingOpenRemoteBalance: &lnrpc.Amount{}, - // Deprecated fields. - Balance: int64(local), - } - assertChannelBalanceResp(t, node, expectedResponse) - } - - // Before beginning, make sure alice and bob are connected. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.EnsureConnected(ctxt, t.t, net.Alice, net.Bob) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: amount, - }, - ) - - // Wait for both Alice and Bob to recognize this new channel. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("alice didn't advertise channel before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("bob didn't advertise channel before "+ - "timeout: %v", err) - } - - cType, err := channelCommitType(net.Alice, chanPoint) - if err != nil { - t.Fatalf("unable to get channel type: %v", err) - } - - // As this is a single funder channel, Alice's balance should be - // exactly 0.5 BTC since now state transitions have taken place yet. - checkChannelBalance(net.Alice, amount-cType.calcStaticFee(0), 0) - - // Ensure Bob currently has no available balance within the channel. - checkChannelBalance(net.Bob, 0, amount-cType.calcStaticFee(0)) - - // Finally close the channel between Alice and Bob, asserting that the - // channel has been properly closed on-chain. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) -} - -// testChannelUnsettledBalance will test that the UnsettledBalance field -// is updated according to the number of Pending Htlcs. -// Alice will send Htlcs to Carol while she is in hodl mode. This will result -// in a build of pending Htlcs. We expect the channels unsettled balance to -// equal the sum of all the Pending Htlcs. -func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) { - const chanAmt = btcutil.Amount(1000000) - ctxb := context.Background() - - // Creates a helper closure to be used below which asserts the proper - // response to a channel balance RPC. - checkChannelBalance := func(node *lntest.HarnessNode, - local, remote, unsettledLocal, unsettledRemote btcutil.Amount) { - - expectedResponse := &lnrpc.ChannelBalanceResponse{ - LocalBalance: &lnrpc.Amount{ - Sat: uint64(local), - Msat: uint64(lnwire.NewMSatFromSatoshis( - local, - )), - }, - RemoteBalance: &lnrpc.Amount{ - Sat: uint64(remote), - Msat: uint64(lnwire.NewMSatFromSatoshis( - remote, - )), - }, - UnsettledLocalBalance: &lnrpc.Amount{ - Sat: uint64(unsettledLocal), - Msat: uint64(lnwire.NewMSatFromSatoshis( - unsettledLocal, - )), - }, - UnsettledRemoteBalance: &lnrpc.Amount{ - Sat: uint64(unsettledRemote), - Msat: uint64(lnwire.NewMSatFromSatoshis( - unsettledRemote, - )), - }, - PendingOpenLocalBalance: &lnrpc.Amount{}, - PendingOpenRemoteBalance: &lnrpc.Amount{}, - // Deprecated fields. - Balance: int64(local), - } - assertChannelBalanceResp(t, node, expectedResponse) - } - - // Create carol in hodl mode. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - defer shutdownAndAssert(net, t, carol) - - // Connect Alice to Carol. - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - net.ConnectNodes(ctxb, t.t, net.Alice, carol) - - // Open a channel between Alice and Carol. - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Wait for Alice and Carol to receive the channel edge from the - // funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) - if err != nil { - t.Fatalf("alice didn't see the alice->carol channel before "+ - "timeout: %v", err) - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointAlice) - if err != nil { - t.Fatalf("alice didn't see the alice->carol channel before "+ - "timeout: %v", err) - } - - cType, err := channelCommitType(net.Alice, chanPointAlice) - require.NoError(t.t, err, "unable to get channel type") - - // Check alice's channel balance, which should have zero remote and zero - // pending balance. - checkChannelBalance(net.Alice, chanAmt-cType.calcStaticFee(0), 0, 0, 0) - - // Check carol's channel balance, which should have zero local and zero - // pending balance. - checkChannelBalance(carol, 0, chanAmt-cType.calcStaticFee(0), 0, 0) - - // Channel should be ready for payments. - const ( - payAmt = 100 - numInvoices = 6 - ) - - // Simulateneously send numInvoices payments from Alice to Carol. - carolPubKey := carol.PubKey[:] - errChan := make(chan error) - for i := 0; i < numInvoices; i++ { - go func() { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err := net.Alice.RouterClient.SendPaymentV2(ctxt, - &routerrpc.SendPaymentRequest{ - Dest: carolPubKey, - Amt: int64(payAmt), - PaymentHash: makeFakePayHash(t), - FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }) - - if err != nil { - errChan <- err - } - }() - } - - // Test that the UnsettledBalance for both Alice and Carol - // is equal to the amount of invoices * payAmt. - var unsettledErr error - nodes := []*lntest.HarnessNode{net.Alice, carol} - err = wait.Predicate(func() bool { - // There should be a number of PendingHtlcs equal - // to the amount of Invoices sent. - unsettledErr = assertNumActiveHtlcs(nodes, numInvoices) - if unsettledErr != nil { - return false - } - - // Set the amount expected for the Unsettled Balance for - // this channel. - expectedBalance := numInvoices * payAmt - - // Check each nodes UnsettledBalance field. - for _, node := range nodes { - // Get channel info for the node. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanInfo, err := getChanInfo(ctxt, node) - if err != nil { - unsettledErr = err - return false - } - - // Check that UnsettledBalance is what we expect. - if int(chanInfo.UnsettledBalance) != expectedBalance { - unsettledErr = fmt.Errorf("unsettled balance failed "+ - "expected: %v, received: %v", expectedBalance, - chanInfo.UnsettledBalance) - return false - } - } - - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("unsettled balace error: %v", unsettledErr) - } - - // Check for payment errors. - select { - case err := <-errChan: - t.Fatalf("payment error: %v", err) - default: - } - - // Check alice's channel balance, which should have a remote unsettled - // balance that equals to the amount of invoices * payAmt. The remote - // balance remains zero. - aliceLocal := chanAmt - cType.calcStaticFee(0) - numInvoices*payAmt - checkChannelBalance(net.Alice, aliceLocal, 0, 0, numInvoices*payAmt) - - // Check carol's channel balance, which should have a local unsettled - // balance that equals to the amount of invoices * payAmt. The local - // balance remains zero. - checkChannelBalance(carol, 0, aliceLocal, numInvoices*payAmt, 0) - - // Force and assert the channel closure. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Alice, chanPointAlice) -} - // 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