mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
35992e1503
So it can be used via `ht.CreateSimpleNetwork`.
1433 lines
49 KiB
Go
1433 lines
49 KiB
Go
package itest
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntest/node"
|
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// testPaymentSucceededHTLCRemoteSwept checks that when an outgoing HTLC is
|
|
// timed out and is swept by the remote via the direct preimage spend path, the
|
|
// payment will be marked as succeeded. This test creates a topology from Alice
|
|
// -> Bob, and let Alice send payments to Bob. Bob then goes offline, such that
|
|
// Alice's outgoing HTLC will time out. Once the force close transaction is
|
|
// broadcast by Alice, she then goes offline and Bob comes back online to take
|
|
// her outgoing HTLC. And Alice should mark this payment as succeeded after she
|
|
// comes back online again.
|
|
func testPaymentSucceededHTLCRemoteSwept(ht *lntest.HarnessTest) {
|
|
// Set the feerate to be 10 sat/vb.
|
|
ht.SetFeeEstimate(2500)
|
|
|
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
|
// being the sole funder of the channel.
|
|
chanAmt := btcutil.Amount(100_000)
|
|
openChannelParams := lntest.OpenChannelParams{
|
|
Amt: chanAmt,
|
|
}
|
|
cfgs := [][]string{nil, nil}
|
|
|
|
// Create a two hop network: Alice -> Bob.
|
|
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, openChannelParams)
|
|
chanPoint := chanPoints[0]
|
|
alice, bob := nodes[0], nodes[1]
|
|
|
|
// We now create two payments, one above dust and the other below dust,
|
|
// and we should see different behavior in terms of when the payment
|
|
// will be marked as failed due to the HTLC timeout.
|
|
//
|
|
// First, create random preimages.
|
|
preimage := ht.RandomPreimage()
|
|
dustPreimage := ht.RandomPreimage()
|
|
|
|
// Get the preimage hashes.
|
|
payHash := preimage.Hash()
|
|
dustPayHash := dustPreimage.Hash()
|
|
|
|
// Create an hold invoice for Bob which expects a payment of 10k
|
|
// satoshis from Alice.
|
|
const paymentAmt = 10_000
|
|
req := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: paymentAmt,
|
|
Hash: payHash[:],
|
|
// Use a small CLTV value so we can mine fewer blocks.
|
|
CltvExpiry: finalCltvDelta,
|
|
}
|
|
invoice := bob.RPC.AddHoldInvoice(req)
|
|
|
|
// Create another hold invoice for Bob which expects a payment of 1k
|
|
// satoshis from Alice.
|
|
const dustAmt = 1000
|
|
req = &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: dustAmt,
|
|
Hash: dustPayHash[:],
|
|
// Use a small CLTV value so we can mine fewer blocks.
|
|
CltvExpiry: finalCltvDelta,
|
|
}
|
|
dustInvoice := bob.RPC.AddHoldInvoice(req)
|
|
|
|
// Alice now sends both payments to Bob.
|
|
payReq := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: invoice.PaymentRequest,
|
|
TimeoutSeconds: 3600,
|
|
}
|
|
dustPayReq := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: dustInvoice.PaymentRequest,
|
|
TimeoutSeconds: 3600,
|
|
}
|
|
|
|
// We expect the payment to stay in-flight from both streams.
|
|
ht.SendPaymentAssertInflight(alice, payReq)
|
|
ht.SendPaymentAssertInflight(alice, dustPayReq)
|
|
|
|
// We also check the payments are marked as IN_FLIGHT in Alice's
|
|
// database.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
|
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// Bob should have two incoming HTLC.
|
|
ht.AssertIncomingHTLCActive(bob, chanPoint, payHash[:])
|
|
ht.AssertIncomingHTLCActive(bob, chanPoint, dustPayHash[:])
|
|
|
|
// Alice should have two outgoing HTLCs.
|
|
ht.AssertOutgoingHTLCActive(alice, chanPoint, payHash[:])
|
|
ht.AssertOutgoingHTLCActive(alice, chanPoint, dustPayHash[:])
|
|
|
|
// Let Bob go offline.
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
// Alice force closes the channel, which puts her commitment tx into
|
|
// the mempool.
|
|
ht.CloseChannelAssertPending(alice, chanPoint, true)
|
|
|
|
// We now let Alice go offline to avoid her sweeping her outgoing htlc.
|
|
restartAlice := ht.SuspendNode(alice)
|
|
|
|
// Mine one block to confirm Alice's force closing tx.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Restart Bob to settle the invoice and sweep the htlc output.
|
|
require.NoError(ht, restartBob())
|
|
|
|
// Bob now settles the invoices, since his link with Alice is broken,
|
|
// Alice won't know the preimages.
|
|
bob.RPC.SettleInvoice(preimage[:])
|
|
bob.RPC.SettleInvoice(dustPreimage[:])
|
|
|
|
// Once Bob comes back up, he should find the force closing transaction
|
|
// from Alice and try to sweep the non-dust outgoing htlc via the
|
|
// direct preimage spend.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block to trigger the sweep.
|
|
//
|
|
// TODO(yy): remove it once `blockbeat` is implemented.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Mine Bob's sweeping tx.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Let Alice come back up. Since the channel is now closed, we expect
|
|
// different behaviors based on whether the HTLC is a dust.
|
|
// - For dust payment, it should be failed now as the HTLC won't go
|
|
// onchain.
|
|
// - For non-dust payment, it should be marked as succeeded since her
|
|
// outgoing htlc is swept by Bob.
|
|
require.NoError(ht, restartAlice())
|
|
|
|
// Since Alice is restarted, we need to track the payments again.
|
|
payStream := alice.RPC.TrackPaymentV2(payHash[:])
|
|
dustPayStream := alice.RPC.TrackPaymentV2(dustPayHash[:])
|
|
|
|
// Check that the dust payment is failed in both the stream and DB.
|
|
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_FAILED)
|
|
ht.AssertPaymentStatusFromStream(dustPayStream, lnrpc.Payment_FAILED)
|
|
|
|
// We expect the non-dust payment to marked as succeeded in Alice's
|
|
// database and also from her stream.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_SUCCEEDED)
|
|
}
|
|
|
|
// testPaymentFailedHTLCLocalSwept checks that when an outgoing HTLC is timed
|
|
// out and claimed onchain via the timeout path, the payment will be marked as
|
|
// failed. This test creates a topology from Alice -> Bob, and let Alice send
|
|
// payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
|
|
// time out. Alice will also be restarted to make sure resumed payments are
|
|
// also marked as failed.
|
|
func testPaymentFailedHTLCLocalSwept(ht *lntest.HarnessTest) {
|
|
success := ht.Run("fail payment", func(t *testing.T) {
|
|
st := ht.Subtest(t)
|
|
runTestPaymentHTLCTimeout(st, false)
|
|
})
|
|
if !success {
|
|
return
|
|
}
|
|
|
|
ht.Run("fail resumed payment", func(t *testing.T) {
|
|
st := ht.Subtest(t)
|
|
runTestPaymentHTLCTimeout(st, true)
|
|
})
|
|
}
|
|
|
|
// runTestPaymentHTLCTimeout is the helper function that actually runs the
|
|
// testPaymentFailedHTLCLocalSwept.
|
|
func runTestPaymentHTLCTimeout(ht *lntest.HarnessTest, restartAlice bool) {
|
|
// Set the feerate to be 10 sat/vb.
|
|
ht.SetFeeEstimate(2500)
|
|
|
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
|
// being the sole funder of the channel.
|
|
chanAmt := btcutil.Amount(100_000)
|
|
openChannelParams := lntest.OpenChannelParams{
|
|
Amt: chanAmt,
|
|
}
|
|
cfgs := [][]string{nil, nil}
|
|
|
|
// Create a two hop network: Alice -> Bob.
|
|
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, openChannelParams)
|
|
chanPoint := chanPoints[0]
|
|
alice, bob := nodes[0], nodes[1]
|
|
|
|
// We now create two payments, one above dust and the other below dust,
|
|
// and we should see different behavior in terms of when the payment
|
|
// will be marked as failed due to the HTLC timeout.
|
|
//
|
|
// First, create random preimages.
|
|
preimage := ht.RandomPreimage()
|
|
dustPreimage := ht.RandomPreimage()
|
|
|
|
// Get the preimage hashes.
|
|
payHash := preimage.Hash()
|
|
dustPayHash := dustPreimage.Hash()
|
|
|
|
// Create an hold invoice for Bob which expects a payment of 10k
|
|
// satoshis from Alice.
|
|
const paymentAmt = 20_000
|
|
req := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: paymentAmt,
|
|
Hash: payHash[:],
|
|
// Use a small CLTV value so we can mine fewer blocks.
|
|
CltvExpiry: finalCltvDelta,
|
|
}
|
|
invoice := bob.RPC.AddHoldInvoice(req)
|
|
|
|
// Create another hold invoice for Bob which expects a payment of 1k
|
|
// satoshis from Alice.
|
|
const dustAmt = 1000
|
|
req = &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: dustAmt,
|
|
Hash: dustPayHash[:],
|
|
// Use a small CLTV value so we can mine fewer blocks.
|
|
CltvExpiry: finalCltvDelta,
|
|
}
|
|
dustInvoice := bob.RPC.AddHoldInvoice(req)
|
|
|
|
// Alice now sends both the payments to Bob.
|
|
payReq := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: invoice.PaymentRequest,
|
|
TimeoutSeconds: 3600,
|
|
}
|
|
dustPayReq := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: dustInvoice.PaymentRequest,
|
|
TimeoutSeconds: 3600,
|
|
}
|
|
|
|
// We expect the payment to stay in-flight from both streams.
|
|
ht.SendPaymentAssertInflight(alice, payReq)
|
|
ht.SendPaymentAssertInflight(alice, dustPayReq)
|
|
|
|
// We also check the payments are marked as IN_FLIGHT in Alice's
|
|
// database.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
|
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// Bob should have two incoming HTLC.
|
|
ht.AssertIncomingHTLCActive(bob, chanPoint, payHash[:])
|
|
ht.AssertIncomingHTLCActive(bob, chanPoint, dustPayHash[:])
|
|
|
|
// Alice should have two outgoing HTLCs.
|
|
ht.AssertOutgoingHTLCActive(alice, chanPoint, payHash[:])
|
|
ht.AssertOutgoingHTLCActive(alice, chanPoint, dustPayHash[:])
|
|
|
|
// Let Bob go offline.
|
|
ht.Shutdown(bob)
|
|
|
|
// We'll now mine enough blocks to trigger Alice to broadcast her
|
|
// commitment transaction due to the fact that the HTLC is about to
|
|
// timeout. With the default outgoing broadcast delta of zero, this
|
|
// will be the same height as the htlc expiry height.
|
|
numBlocks := padCLTV(
|
|
uint32(req.CltvExpiry - lncfg.DefaultOutgoingBroadcastDelta),
|
|
)
|
|
ht.MineBlocks(int(numBlocks))
|
|
|
|
// Restart Alice if requested.
|
|
if restartAlice {
|
|
// Restart Alice to test the resumed payment is canceled.
|
|
ht.RestartNode(alice)
|
|
}
|
|
|
|
// We now subscribe to the payment status.
|
|
payStream := alice.RPC.TrackPaymentV2(payHash[:])
|
|
dustPayStream := alice.RPC.TrackPaymentV2(dustPayHash[:])
|
|
|
|
// Mine a block to confirm Alice's closing transaction.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Now the channel is closed, we expect different behaviors based on
|
|
// whether the HTLC is a dust. For dust payment, it should be failed
|
|
// now as the HTLC won't go onchain. For non-dust payment, it should
|
|
// still be inflight. It won't be marked as failed unless the outgoing
|
|
// HTLC is resolved onchain.
|
|
//
|
|
// NOTE: it's possible for Bob to race against Alice using the
|
|
// preimage path. If Bob successfully claims the HTLC, Alice should
|
|
// mark the non-dust payment as succeeded.
|
|
//
|
|
// Check that the dust payment is failed in both the stream and DB.
|
|
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_FAILED)
|
|
ht.AssertPaymentStatusFromStream(dustPayStream, lnrpc.Payment_FAILED)
|
|
|
|
// Check that the non-dust payment is still in-flight.
|
|
//
|
|
// NOTE: we don't check the payment status from the stream here as
|
|
// there's no new status being sent.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// We now have two possible cases for the non-dust payment:
|
|
// - Bob stays offline, and Alice will sweep her outgoing HTLC, which
|
|
// makes the payment failed.
|
|
// - Bob comes back online, and claims the HTLC on Alice's commitment
|
|
// via direct preimage spend, hence racing against Alice onchain. If
|
|
// he succeeds, Alice should mark the payment as succeeded.
|
|
//
|
|
// TODO(yy): test the second case once we have the RPC to clean
|
|
// mempool.
|
|
|
|
// Since Alice's force close transaction has been confirmed, she should
|
|
// sweep her outgoing HTLC in next block.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Cleanup the channel.
|
|
ht.CleanupForceClose(alice)
|
|
|
|
// We expect the non-dust payment to marked as failed in Alice's
|
|
// database and also from her stream.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_FAILED)
|
|
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
|
|
}
|
|
|
|
// testSendDirectPayment creates a topology Alice->Bob and then tests that
|
|
// Alice can send a direct payment to Bob. This test modifies the fee estimator
|
|
// to return floor fee rate(1 sat/vb).
|
|
func testSendDirectPayment(ht *lntest.HarnessTest) {
|
|
// Grab Alice and Bob's nodes for convenience.
|
|
alice, bob := ht.Alice, ht.Bob
|
|
|
|
// Create a list of commitment types we want to test.
|
|
commitmentTypes := []lnrpc.CommitmentType{
|
|
lnrpc.CommitmentType_ANCHORS,
|
|
lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
|
}
|
|
|
|
// testSendPayment opens a channel between Alice and Bob using the
|
|
// specified params. It then sends a payment from Alice to Bob and
|
|
// asserts it being successful.
|
|
testSendPayment := func(ht *lntest.HarnessTest,
|
|
params lntest.OpenChannelParams) {
|
|
|
|
// Check that there are no payments before test.
|
|
chanPoint := ht.OpenChannel(alice, bob, params)
|
|
|
|
// Now that the channel is open, create an invoice for Bob
|
|
// which expects a payment of 1000 satoshis from Alice paid via
|
|
// a particular preimage.
|
|
const paymentAmt = 1000
|
|
preimage := ht.Random32Bytes()
|
|
invoice := &lnrpc.Invoice{
|
|
RPreimage: preimage,
|
|
Value: paymentAmt,
|
|
}
|
|
invoiceResp := bob.RPC.AddInvoice(invoice)
|
|
|
|
// With the invoice for Bob added, send a payment towards Alice
|
|
// paying to the above generated invoice.
|
|
payReqs := []string{invoiceResp.PaymentRequest}
|
|
ht.CompletePaymentRequests(alice, payReqs)
|
|
|
|
p := ht.AssertNumPayments(alice, 1)[0]
|
|
path := p.Htlcs[len(p.Htlcs)-1].Route.Hops
|
|
|
|
// Ensure that the stored path shows a direct payment to Bob
|
|
// with no other nodes in-between.
|
|
require.Len(ht, path, 1, "wrong number of routes in path")
|
|
require.Equal(ht, bob.PubKeyStr, path[0].PubKey, "wrong pubkey")
|
|
|
|
// The payment amount should also match our previous payment
|
|
// directly.
|
|
require.EqualValues(ht, paymentAmt, p.ValueSat,
|
|
"incorrect sat amount")
|
|
require.EqualValues(ht, paymentAmt*1000, p.ValueMsat,
|
|
"incorrect msat amount")
|
|
|
|
// The payment hash (or r-hash) should have been stored
|
|
// correctly.
|
|
correctRHash := hex.EncodeToString(invoiceResp.RHash)
|
|
require.Equal(ht, correctRHash, p.PaymentHash, "incorrect hash")
|
|
|
|
// As we made a single-hop direct payment, there should have
|
|
// been no fee applied.
|
|
require.Zero(ht, p.FeeSat, "fee should be 0")
|
|
require.Zero(ht, p.FeeMsat, "fee should be 0")
|
|
|
|
// Now verify that the payment request returned by the rpc
|
|
// matches the invoice that we paid.
|
|
require.Equal(ht, invoiceResp.PaymentRequest, p.PaymentRequest,
|
|
"incorrect payreq")
|
|
|
|
// Delete all payments from Alice. DB should have no payments.
|
|
alice.RPC.DeleteAllPayments()
|
|
ht.AssertNumPayments(alice, 0)
|
|
|
|
// TODO(yy): remove the sleep once the following bug is fixed.
|
|
// When the invoice is reported settled, the commitment dance
|
|
// is not yet finished, which can cause an error when closing
|
|
// the channel, saying there's active HTLCs. We need to
|
|
// investigate this issue and reverse the order to, first
|
|
// finish the commitment dance, then report the invoice as
|
|
// settled.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Close the channel.
|
|
//
|
|
// NOTE: This implicitly tests that the channel link is active
|
|
// before closing this channel. The above payment will trigger
|
|
// a commitment dance in both of the nodes. If the node fails
|
|
// to update the commitment state, we will fail to close the
|
|
// channel as the link won't be active.
|
|
ht.CloseChannel(alice, chanPoint)
|
|
}
|
|
|
|
// Run the test cases.
|
|
for _, ct := range commitmentTypes {
|
|
ht.Run(ct.String(), func(t *testing.T) {
|
|
st := ht.Subtest(t)
|
|
|
|
// Set the fee estimate to 1sat/vbyte.
|
|
st.SetFeeEstimate(250)
|
|
|
|
// Restart the nodes with the specified commitment type.
|
|
args := lntest.NodeArgsForCommitType(ct)
|
|
st.RestartNodeWithExtraArgs(alice, args)
|
|
st.RestartNodeWithExtraArgs(bob, args)
|
|
|
|
// Make sure they are connected.
|
|
st.EnsureConnected(alice, bob)
|
|
|
|
// Open a channel with 100k satoshis between Alice and
|
|
// Bob with Alice being the sole funder of the channel.
|
|
params := lntest.OpenChannelParams{
|
|
Amt: 100_000,
|
|
CommitmentType: ct,
|
|
}
|
|
|
|
// Open private channel for taproot channels.
|
|
if ct == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
params.Private = true
|
|
}
|
|
|
|
testSendPayment(st, params)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testListPayments(ht *lntest.HarnessTest) {
|
|
alice, bob := ht.Alice, ht.Bob
|
|
|
|
// Check that there are no payments before test.
|
|
ht.AssertNumPayments(alice, 0)
|
|
|
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
|
// being the sole funder of the channel.
|
|
chanAmt := btcutil.Amount(100000)
|
|
chanPoint := ht.OpenChannel(
|
|
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
|
|
)
|
|
|
|
// Now that the channel is open, create an invoice for Bob which
|
|
// expects a payment of 1000 satoshis from Alice paid via a particular
|
|
// preimage.
|
|
const paymentAmt = 1000
|
|
preimage := ht.Random32Bytes()
|
|
invoice := &lnrpc.Invoice{
|
|
Memo: "testing",
|
|
RPreimage: preimage,
|
|
Value: paymentAmt,
|
|
}
|
|
invoiceResp := bob.RPC.AddInvoice(invoice)
|
|
|
|
// Check that Bob has added the invoice.
|
|
invoice = ht.AssertNumInvoices(bob, 1)[0]
|
|
|
|
// With the invoice for Bob added, send a payment towards Alice paying
|
|
// to the above generated invoice.
|
|
payReqs := []string{invoiceResp.PaymentRequest}
|
|
ht.CompletePaymentRequests(alice, payReqs)
|
|
|
|
// Grab Alice's list of payments, she should show the existence of
|
|
// exactly one payment.
|
|
p := ht.AssertNumPayments(alice, 1)[0]
|
|
path := p.Htlcs[len(p.Htlcs)-1].Route.Hops
|
|
|
|
// Ensure that the stored path shows a direct payment to Bob with no
|
|
// other nodes in-between.
|
|
require.Len(ht, path, 1, "wrong number of routes in path")
|
|
require.Equal(ht, bob.PubKeyStr, path[0].PubKey, "wrong pub key")
|
|
|
|
// The payment amount should also match our previous payment directly.
|
|
require.EqualValues(ht, paymentAmt, p.ValueSat, "incorrect sat amount")
|
|
require.EqualValues(ht, paymentAmt*1000, p.ValueMsat,
|
|
"incorrect msat amount")
|
|
|
|
// The payment hash (or r-hash) should have been stored correctly.
|
|
correctRHash := hex.EncodeToString(invoiceResp.RHash)
|
|
require.Equal(ht, correctRHash, p.PaymentHash, "incorrect RHash")
|
|
|
|
// As we made a single-hop direct payment, there should have been no
|
|
// fee applied.
|
|
require.Zero(ht, p.FeeSat, "fee should be 0")
|
|
require.Zero(ht, p.FeeMsat, "fee should be 0")
|
|
|
|
// Now verify that the payment request returned by the rpc matches the
|
|
// invoice that we paid.
|
|
require.Equal(ht, invoiceResp.PaymentRequest, p.PaymentRequest,
|
|
"incorrect payreq")
|
|
|
|
// testCase holds a case to be used by both the payment and the invoice
|
|
// tests.
|
|
type testCase struct {
|
|
name string
|
|
startDate uint64
|
|
endDate uint64
|
|
expected bool
|
|
}
|
|
|
|
// Create test cases to check the timestamp filters.
|
|
createCases := func(createTimeSeconds uint64) []testCase {
|
|
return []testCase{
|
|
{
|
|
// Use a start date same as the creation date
|
|
// should return us the item.
|
|
name: "exact start date",
|
|
startDate: createTimeSeconds,
|
|
expected: true,
|
|
},
|
|
{
|
|
// Use an earlier start date should return us
|
|
// the item.
|
|
name: "earlier start date",
|
|
startDate: createTimeSeconds - 1,
|
|
expected: true,
|
|
},
|
|
{
|
|
// Use a future start date should return us
|
|
// nothing.
|
|
name: "future start date",
|
|
startDate: createTimeSeconds + 1,
|
|
expected: false,
|
|
},
|
|
{
|
|
// Use an end date same as the creation date
|
|
// should return us the item.
|
|
name: "exact end date",
|
|
endDate: createTimeSeconds,
|
|
expected: true,
|
|
},
|
|
{
|
|
// Use an end date in the future should return
|
|
// us the item.
|
|
name: "future end date",
|
|
endDate: createTimeSeconds + 1,
|
|
expected: true,
|
|
},
|
|
{
|
|
// Use an earlier end date should return us
|
|
// nothing.
|
|
name: "earlier end date",
|
|
endDate: createTimeSeconds - 1,
|
|
expected: false,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Get the payment creation time in seconds.
|
|
paymentCreateSeconds := uint64(
|
|
p.CreationTimeNs / time.Second.Nanoseconds(),
|
|
)
|
|
|
|
// Create test cases from the payment creation time.
|
|
testCases := createCases(paymentCreateSeconds)
|
|
|
|
// We now check the timestamp filters in `ListPayments`.
|
|
for _, tc := range testCases {
|
|
ht.Run("payment_"+tc.name, func(t *testing.T) {
|
|
req := &lnrpc.ListPaymentsRequest{
|
|
CreationDateStart: tc.startDate,
|
|
CreationDateEnd: tc.endDate,
|
|
}
|
|
resp := alice.RPC.ListPayments(req)
|
|
|
|
if tc.expected {
|
|
require.Lenf(t, resp.Payments, 1, "req=%v", req)
|
|
} else {
|
|
require.Emptyf(t, resp.Payments, "req=%v", req)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Create test cases from the invoice creation time.
|
|
testCases = createCases(uint64(invoice.CreationDate))
|
|
|
|
// We now do the same check for `ListInvoices`.
|
|
for _, tc := range testCases {
|
|
ht.Run("invoice_"+tc.name, func(t *testing.T) {
|
|
req := &lnrpc.ListInvoiceRequest{
|
|
CreationDateStart: tc.startDate,
|
|
CreationDateEnd: tc.endDate,
|
|
}
|
|
resp := bob.RPC.ListInvoices(req)
|
|
|
|
if tc.expected {
|
|
require.Lenf(t, resp.Invoices, 1, "req: %v",
|
|
req)
|
|
} else {
|
|
require.Emptyf(t, resp.Invoices, "req: %v", req)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Delete all payments from Alice. DB should have no payments.
|
|
alice.RPC.DeleteAllPayments()
|
|
|
|
// Check that there are no payments after test.
|
|
ht.AssertNumPayments(alice, 0)
|
|
|
|
// TODO(yy): remove the sleep once the following bug is fixed.
|
|
// When the invoice is reported settled, the commitment dance is not
|
|
// yet finished, which can cause an error when closing the channel,
|
|
// saying there's active HTLCs. We need to investigate this issue and
|
|
// reverse the order to, first finish the commitment dance, then report
|
|
// the invoice as settled.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// Close the channel.
|
|
ht.CloseChannel(alice, chanPoint)
|
|
}
|
|
|
|
// testPaymentFollowingChannelOpen tests that the channel transition from
|
|
// 'pending' to 'open' state does not cause any inconsistencies within other
|
|
// subsystems trying to update the channel state in the db. We follow this
|
|
// transition with a payment that updates the commitment state and verify that
|
|
// the pending state is up to date.
|
|
func testPaymentFollowingChannelOpen(ht *lntest.HarnessTest) {
|
|
const paymentAmt = btcutil.Amount(100)
|
|
channelCapacity := paymentAmt * 1000
|
|
|
|
// We first establish a channel between Alice and Bob.
|
|
alice, bob := ht.Alice, ht.Bob
|
|
p := lntest.OpenChannelParams{
|
|
Amt: channelCapacity,
|
|
}
|
|
pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p)
|
|
|
|
// 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.
|
|
ht.AssertNodesNumPendingOpenChannels(alice, bob, 1)
|
|
|
|
// We are restarting Bob's node to let the link be created for the
|
|
// pending channel.
|
|
ht.RestartNode(bob)
|
|
|
|
// We ensure that Bob reconnects to Alice.
|
|
ht.EnsureConnected(bob, alice)
|
|
|
|
// We mine six blocks for the channel to be confirmed.
|
|
ht.MineBlocksAndAssertNumTxes(6, 1)
|
|
|
|
// We verify that the channel is open from both nodes point of view.
|
|
chanPoint := lntest.ChanPointFromPendingUpdate(pendingUpdate)
|
|
ht.AssertNodesNumPendingOpenChannels(alice, bob, 0)
|
|
ht.AssertChannelExists(alice, chanPoint)
|
|
ht.AssertChannelExists(bob, chanPoint)
|
|
|
|
// With the channel open, we'll create invoices for Bob that Alice will
|
|
// pay to in order to advance the state of the channel.
|
|
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
|
|
|
|
// Send payment to Bob so that a channel update to disk will be
|
|
// executed.
|
|
ht.CompletePaymentRequests(alice, []string{bobPayReqs[0]})
|
|
|
|
// TODO(yy): remove the sleep once the following bug is fixed.
|
|
// When the invoice is reported settled, the commitment dance is not
|
|
// yet finished, which can cause an error when closing the channel,
|
|
// saying there's active HTLCs. We need to investigate this issue and
|
|
// reverse the order to, first finish the commitment dance, then report
|
|
// the invoice as settled.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
// 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.
|
|
ht.CloseChannel(alice, chanPoint)
|
|
}
|
|
|
|
// testAsyncPayments tests the performance of the async payments.
|
|
func testAsyncPayments(ht *lntest.HarnessTest) {
|
|
// We use new nodes here as the benchmark test creates lots of data
|
|
// which can be costly to be carried on.
|
|
alice := ht.NewNode("Alice", []string{"--pending-commit-interval=3m"})
|
|
bob := ht.NewNode("Bob", []string{"--pending-commit-interval=3m"})
|
|
|
|
ht.EnsureConnected(alice, bob)
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
|
|
runAsyncPayments(ht, alice, bob, nil)
|
|
}
|
|
|
|
// runAsyncPayments tests the performance of the async payments.
|
|
func runAsyncPayments(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
|
|
commitType *lnrpc.CommitmentType) {
|
|
|
|
const paymentAmt = 100
|
|
|
|
channelCapacity := btcutil.Amount(paymentAmt * 2000)
|
|
|
|
chanArgs := lntest.OpenChannelParams{
|
|
Amt: channelCapacity,
|
|
}
|
|
|
|
if commitType != nil {
|
|
chanArgs.CommitmentType = *commitType
|
|
|
|
if *commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
chanArgs.Private = true
|
|
}
|
|
}
|
|
|
|
// First establish a channel with a capacity equals to the overall
|
|
// amount of payments, between Alice and Bob, at the end of the test
|
|
// Alice should send all money from her side to Bob.
|
|
chanPoint := ht.OpenChannel(
|
|
alice, bob, chanArgs,
|
|
)
|
|
|
|
info := ht.QueryChannelByChanPoint(alice, chanPoint)
|
|
|
|
// We'll create a number of invoices equal the max number of HTLCs that
|
|
// can be carried in one direction. The number on the commitment will
|
|
// likely be lower, but we can't guarantee that any more HTLCs will
|
|
// succeed due to the limited path diversity and inability of the router
|
|
// to retry via another path.
|
|
numInvoices := input.MaxHTLCNumber / 2
|
|
|
|
bobAmt := int64(numInvoices * paymentAmt)
|
|
aliceAmt := info.LocalBalance - bobAmt
|
|
|
|
// With the channel open, we'll create invoices for Bob that Alice
|
|
// will pay to in order to advance the state of the channel.
|
|
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices)
|
|
|
|
// Simultaneously send payments from Alice to Bob using of Bob's
|
|
// payment hashes generated above.
|
|
now := time.Now()
|
|
|
|
settled := make(chan struct{})
|
|
defer close(settled)
|
|
|
|
timeout := wait.AsyncBenchmarkTimeout
|
|
for i := 0; i < numInvoices; i++ {
|
|
payReq := bobPayReqs[i]
|
|
go func() {
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: payReq,
|
|
TimeoutSeconds: int32(timeout.Seconds()),
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
// AssertPaymentStatusWithTimeout will assert that the
|
|
// payment is settled.
|
|
stream := alice.RPC.SendPayment(req)
|
|
ht.AssertPaymentSucceedWithTimeout(stream, timeout)
|
|
|
|
settled <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
// Wait until all the payments have settled.
|
|
timer := time.After(timeout)
|
|
for i := 0; i < numInvoices; i++ {
|
|
select {
|
|
case <-settled:
|
|
case <-timer:
|
|
require.Fail(ht, "timeout", "wait payment failed")
|
|
}
|
|
}
|
|
|
|
// All payments have been sent, mark the finish time.
|
|
timeTaken := time.Since(now)
|
|
|
|
// Wait for the revocation to be received so alice no longer has
|
|
// pending htlcs listed and has correct balances. This is needed due to
|
|
// the fact that we now pipeline the settles.
|
|
assertChannelState(ht, alice, chanPoint, aliceAmt, bobAmt)
|
|
|
|
// Wait for Bob to receive revocation from Alice.
|
|
assertChannelState(ht, bob, chanPoint, bobAmt, aliceAmt)
|
|
|
|
ht.Log("\tBenchmark info: Elapsed time: ", timeTaken)
|
|
ht.Log("\tBenchmark info: TPS: ",
|
|
float64(numInvoices)/timeTaken.Seconds())
|
|
|
|
// 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.
|
|
ht.CloseChannel(alice, chanPoint)
|
|
}
|
|
|
|
// testBidirectionalAsyncPayments tests that nodes are able to send the
|
|
// payments to each other in async manner without blocking.
|
|
func testBidirectionalAsyncPayments(ht *lntest.HarnessTest) {
|
|
const paymentAmt = 1000
|
|
|
|
// We use new nodes here as the benchmark test creates lots of data
|
|
// which can be costly to be carried on.
|
|
args := []string{
|
|
// Increase the dust threshold to avoid the payments fail due
|
|
// to threshold limit reached.
|
|
"--channel-max-fee-exposure=10000000",
|
|
|
|
// Increase the pending commit interval since there are lots of
|
|
// commitment dances.
|
|
"--pending-commit-interval=5m",
|
|
|
|
// Increase the mailbox delivery timeout as there are lots of
|
|
// ADDs going on.
|
|
"--htlcswitch.mailboxdeliverytimeout=2m",
|
|
}
|
|
alice := ht.NewNode("Alice", args)
|
|
bob := ht.NewNode("Bob", args)
|
|
|
|
ht.EnsureConnected(alice, bob)
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
|
|
// First establish a channel with a capacity equals to the overall
|
|
// amount of payments, between Alice and Bob, at the end of the test
|
|
// Alice should send all money from her side to Bob.
|
|
chanPoint := ht.OpenChannel(
|
|
alice, bob, lntest.OpenChannelParams{
|
|
Amt: paymentAmt * 2000,
|
|
PushAmt: paymentAmt * 1000,
|
|
},
|
|
)
|
|
|
|
info := ht.QueryChannelByChanPoint(alice, chanPoint)
|
|
|
|
// We'll create a number of invoices equal the max number of HTLCs that
|
|
// can be carried in one direction. The number on the commitment will
|
|
// likely be lower, but we can't guarantee that more HTLCs will succeed
|
|
// due to the limited path diversity and inability of the router to
|
|
// retry via another path.
|
|
numInvoices := input.MaxHTLCNumber / 2
|
|
|
|
// Nodes should exchange the same amount of money and because of this
|
|
// at the end balances should remain the same.
|
|
aliceAmt := info.LocalBalance
|
|
bobAmt := info.RemoteBalance
|
|
|
|
// With the channel open, we'll create invoices for Bob that Alice
|
|
// will pay to in order to advance the state of the channel.
|
|
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices)
|
|
|
|
// With the channel open, we'll create invoices for Alice that Bob
|
|
// will pay to in order to advance the state of the channel.
|
|
alicePayReqs, _, _ := ht.CreatePayReqs(alice, paymentAmt, numInvoices)
|
|
|
|
// Reset mission control to prevent previous payment results from
|
|
// interfering with this test. A new channel has been opened, but
|
|
// mission control operates on node pairs.
|
|
alice.RPC.ResetMissionControl()
|
|
|
|
// Send payments from Alice to Bob and from Bob to Alice in async
|
|
// manner.
|
|
settled := make(chan struct{})
|
|
defer close(settled)
|
|
|
|
timeout := wait.AsyncBenchmarkTimeout * 2
|
|
send := func(node *node.HarnessNode, payReq string) {
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: payReq,
|
|
TimeoutSeconds: int32(timeout.Seconds()),
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
// AssertPaymentStatusWithTimeout will assert that the
|
|
// payment is settled.
|
|
stream := node.RPC.SendPayment(req)
|
|
ht.AssertPaymentSucceedWithTimeout(stream, timeout)
|
|
|
|
settled <- struct{}{}
|
|
}
|
|
|
|
for i := 0; i < numInvoices; i++ {
|
|
go send(bob, alicePayReqs[i])
|
|
go send(alice, bobPayReqs[i])
|
|
}
|
|
|
|
// Expect all payments to succeed.
|
|
timer := time.After(timeout)
|
|
for i := 0; i < 2*numInvoices; i++ {
|
|
select {
|
|
case <-settled:
|
|
case <-timer:
|
|
require.Fail(ht, "timeout", "wait payment failed")
|
|
}
|
|
}
|
|
|
|
// Wait for Alice and Bob to receive revocations messages, and update
|
|
// states, i.e. balance info.
|
|
assertChannelState(ht, alice, chanPoint, aliceAmt, bobAmt)
|
|
|
|
// Next query for Bob's and Alice's channel states, in order to confirm
|
|
// that all payment have been successfully transmitted.
|
|
assertChannelState(ht, bob, chanPoint, bobAmt, aliceAmt)
|
|
|
|
// 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.
|
|
ht.CloseChannel(alice, chanPoint)
|
|
}
|
|
|
|
func testInvoiceSubscriptions(ht *lntest.HarnessTest) {
|
|
const chanAmt = btcutil.Amount(500000)
|
|
|
|
alice, bob := ht.Alice, ht.Bob
|
|
|
|
// Create a new invoice subscription client for Bob, the notification
|
|
// should be dispatched shortly below.
|
|
req := &lnrpc.InvoiceSubscription{}
|
|
bobInvoiceSubscription := bob.RPC.SubscribeInvoices(req)
|
|
|
|
// Open a channel with 500k satoshis between Alice and Bob with Alice
|
|
// being the sole funder of the channel.
|
|
chanPoint := ht.OpenChannel(
|
|
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
|
|
)
|
|
|
|
// Next create a new invoice for Bob requesting 1k satoshis.
|
|
const paymentAmt = 1000
|
|
invoice := &lnrpc.Invoice{
|
|
Memo: "testing",
|
|
RPreimage: ht.Random32Bytes(),
|
|
Value: paymentAmt,
|
|
}
|
|
invoiceResp := bob.RPC.AddInvoice(invoice)
|
|
lastAddIndex := invoiceResp.AddIndex
|
|
|
|
// With the above invoice added, we should receive an update event.
|
|
invoiceUpdate := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
|
|
require.NotEqual(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
|
|
"invoice should not be settled")
|
|
|
|
// With the assertion above set up, send a payment from Alice to Bob
|
|
// which should finalize and settle the invoice.
|
|
ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest})
|
|
|
|
// The invoice update should exactly match the invoice created
|
|
// above, but should now be settled and have SettleDate
|
|
invoiceUpdate = ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
|
|
require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
|
|
"invoice not settled but should be")
|
|
require.NotZero(ht, invoiceUpdate.SettleDate,
|
|
"invoice should have non zero settle date, but doesn't")
|
|
require.Equal(ht, invoice.RPreimage, invoiceUpdate.RPreimage,
|
|
"payment preimages don't match")
|
|
require.NotZero(ht, invoiceUpdate.SettleIndex,
|
|
"invoice should have settle index")
|
|
settleIndex := invoiceUpdate.SettleIndex
|
|
|
|
// We'll now add 3 more invoices to Bob's invoice registry.
|
|
const numInvoices = 3
|
|
payReqs, _, newInvoices := ht.CreatePayReqs(
|
|
bob, paymentAmt, numInvoices,
|
|
)
|
|
|
|
// Now that the set of invoices has been added, we'll re-register for
|
|
// streaming invoice notifications for Bob, this time specifying the
|
|
// add index of the last prior invoice.
|
|
req = &lnrpc.InvoiceSubscription{AddIndex: lastAddIndex}
|
|
bobInvoiceSubscription = bob.RPC.SubscribeInvoices(req)
|
|
|
|
// Since we specified a value of the prior add index above, we should
|
|
// now immediately get the invoices we just added as we should get the
|
|
// backlog of notifications.
|
|
for i := 0; i < numInvoices; i++ {
|
|
invoiceUpdate := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
|
|
|
|
// We should now get the ith invoice we added, as they should
|
|
// be returned in order.
|
|
require.NotEqual(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
|
|
"should have only received add events")
|
|
|
|
originalInvoice := newInvoices[i]
|
|
rHash := sha256.Sum256(originalInvoice.RPreimage)
|
|
require.Equal(ht, rHash[:], invoiceUpdate.RHash,
|
|
"invoices have mismatched payment hashes")
|
|
}
|
|
|
|
// We'll now have Bob settle out the remainder of these invoices so we
|
|
// can test that all settled invoices are properly notified.
|
|
ht.CompletePaymentRequests(alice, payReqs)
|
|
|
|
// With the set of invoices paid, we'll now cancel the old
|
|
// subscription, and create a new one for Bob, this time using the
|
|
// settle index to obtain the backlog of settled invoices.
|
|
req = &lnrpc.InvoiceSubscription{
|
|
SettleIndex: settleIndex,
|
|
}
|
|
bobInvoiceSubscription = bob.RPC.SubscribeInvoices(req)
|
|
|
|
// As we specified the index of the past settle index, we should now
|
|
// receive notifications for the three HTLCs that we just settled. As
|
|
// the order that the HTLCs will be settled in is partially randomized,
|
|
// we'll use a map to assert that the proper set has been settled.
|
|
settledInvoices := make(map[[32]byte]struct{})
|
|
for _, invoice := range newInvoices {
|
|
rHash := sha256.Sum256(invoice.RPreimage)
|
|
settledInvoices[rHash] = struct{}{}
|
|
}
|
|
for i := 0; i < numInvoices; i++ {
|
|
invoiceUpdate := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
|
|
|
|
// We should now get the ith invoice we added, as they should
|
|
// be returned in order.
|
|
require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
|
|
"should have only received settle events")
|
|
|
|
var rHash [32]byte
|
|
copy(rHash[:], invoiceUpdate.RHash)
|
|
require.Contains(ht, settledInvoices, rHash,
|
|
"unknown invoice settled")
|
|
|
|
delete(settledInvoices, rHash)
|
|
}
|
|
|
|
// At this point, all the invoices should be fully settled.
|
|
require.Empty(ht, settledInvoices, "not all invoices settled")
|
|
|
|
// TODO(yy): remove the sleep once the following bug is fixed.
|
|
// When the invoice is reported settled, the commitment dance is not
|
|
// yet finished, which can cause an error when closing the channel,
|
|
// saying there's active HTLCs. We need to investigate this issue and
|
|
// reverse the order to, first finish the commitment dance, then report
|
|
// the invoice as settled.
|
|
time.Sleep(2 * time.Second)
|
|
|
|
ht.CloseChannel(alice, chanPoint)
|
|
}
|
|
|
|
// assertChannelState asserts the channel state by checking the values in
|
|
// fields, LocalBalance, RemoteBalance and num of PendingHtlcs.
|
|
func assertChannelState(ht *lntest.HarnessTest, hn *node.HarnessNode,
|
|
cp *lnrpc.ChannelPoint, localBalance, remoteBalance int64) {
|
|
|
|
// Get the funding point.
|
|
err := wait.NoError(func() error {
|
|
// Find the target channel first.
|
|
target := ht.GetChannelByChanPoint(hn, cp)
|
|
|
|
if len(target.PendingHtlcs) != 0 {
|
|
return fmt.Errorf("pending htlcs is "+
|
|
"incorrect, got %v, expected %v",
|
|
len(target.PendingHtlcs), 0)
|
|
}
|
|
|
|
if target.LocalBalance != localBalance {
|
|
return fmt.Errorf("local balance is "+
|
|
"incorrect, got %v, expected %v",
|
|
target.LocalBalance, localBalance)
|
|
}
|
|
|
|
if target.RemoteBalance != remoteBalance {
|
|
return fmt.Errorf("remote balance is "+
|
|
"incorrect, got %v, expected %v",
|
|
target.RemoteBalance, remoteBalance)
|
|
}
|
|
|
|
return nil
|
|
}, lntest.DefaultTimeout)
|
|
require.NoError(ht, err, "timeout while chekcing for balance")
|
|
}
|
|
|
|
// testPaymentFailureReasonCanceled ensures that the cancellation of a
|
|
// SendPayment request results in the payment failure reason
|
|
// FAILURE_REASON_CANCELED. This failure reason indicates that the context was
|
|
// cancelled manually by the user. It does not interrupt the current payment
|
|
// attempt, but will prevent any further payment attempts. The test steps are:
|
|
// 1.) Alice pays Carol's invoice through Bob.
|
|
// 2.) Bob intercepts the htlc, keeping the payment pending.
|
|
// 3.) Alice cancels the payment context, the payment is still pending.
|
|
// 4.) Bob fails OR resumes the intercepted HTLC.
|
|
// 5.) Alice observes a failed OR succeeded payment with failure reason
|
|
// FAILURE_REASON_CANCELED which suppresses further payment attempts.
|
|
func testPaymentFailureReasonCanceled(ht *lntest.HarnessTest) {
|
|
// Initialize the test context with 3 connected nodes.
|
|
ts := newInterceptorTestScenario(ht)
|
|
|
|
alice, bob, carol := ts.alice, ts.bob, ts.carol
|
|
|
|
// Open and wait for channels.
|
|
const chanAmt = btcutil.Amount(300000)
|
|
p := lntest.OpenChannelParams{Amt: chanAmt}
|
|
reqs := []*lntest.OpenChannelRequest{
|
|
{Local: alice, Remote: bob, Param: p},
|
|
{Local: bob, Remote: carol, Param: p},
|
|
}
|
|
resp := ht.OpenMultiChannelsAsync(reqs)
|
|
cpAB, cpBC := resp[0], resp[1]
|
|
|
|
// Make sure Alice is aware of channel Bob=>Carol.
|
|
ht.AssertTopologyChannelOpen(alice, cpBC)
|
|
|
|
// Connect the interceptor.
|
|
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
|
|
defer cancelInterceptor()
|
|
|
|
// First we check that the payment is successful when bob resumes the
|
|
// htlc even though the payment context was canceled before invoice
|
|
// settlement.
|
|
sendPaymentInterceptAndCancel(
|
|
ht, ts, cpAB, routerrpc.ResolveHoldForwardAction_RESUME,
|
|
lnrpc.Payment_SUCCEEDED, interceptor,
|
|
)
|
|
|
|
// Next we check that the context cancellation results in the expected
|
|
// failure reason while the htlc is being held and failed after
|
|
// cancellation.
|
|
// Note that we'd have to reset Alice's mission control if we tested the
|
|
// htlc fail case before the htlc resume case.
|
|
sendPaymentInterceptAndCancel(
|
|
ht, ts, cpAB, routerrpc.ResolveHoldForwardAction_FAIL,
|
|
lnrpc.Payment_FAILED, interceptor,
|
|
)
|
|
|
|
// Finally, close channels.
|
|
ht.CloseChannel(alice, cpAB)
|
|
ht.CloseChannel(bob, cpBC)
|
|
}
|
|
|
|
func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
|
|
ts *interceptorTestScenario, cpAB *lnrpc.ChannelPoint,
|
|
interceptorAction routerrpc.ResolveHoldForwardAction,
|
|
expectedPaymentStatus lnrpc.Payment_PaymentStatus,
|
|
interceptor rpc.InterceptorClient) {
|
|
|
|
// Prepare the test cases.
|
|
alice, bob, carol := ts.alice, ts.bob, ts.carol
|
|
|
|
// Prepare the test cases.
|
|
addResponse := carol.RPC.AddInvoice(&lnrpc.Invoice{
|
|
ValueMsat: 1000,
|
|
})
|
|
invoice := carol.RPC.LookupInvoice(addResponse.RHash)
|
|
|
|
// We initiate a payment from Alice and define the payment context
|
|
// cancellable.
|
|
ctx, cancelPaymentContext := context.WithCancel(context.Background())
|
|
var paymentStream rpc.PaymentClient
|
|
go func() {
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: invoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitSat: 100000,
|
|
Cancelable: true,
|
|
}
|
|
|
|
paymentStream = alice.RPC.SendPaymentWithContext(ctx, req)
|
|
}()
|
|
|
|
// We start the htlc interceptor with a simple implementation that
|
|
// saves all intercepted packets. These packets are held to simulate a
|
|
// pending payment.
|
|
packet := ht.ReceiveHtlcInterceptor(interceptor)
|
|
|
|
// Here we should wait for the channel to contain a pending htlc, and
|
|
// also be shown as being active.
|
|
ht.AssertIncomingHTLCActive(bob, cpAB, invoice.RHash)
|
|
|
|
// Ensure that Alice's payment is in-flight because Bob is holding the
|
|
// htlc.
|
|
ht.AssertPaymentStatusFromStream(paymentStream, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// Cancel the payment context. This should end the payment stream
|
|
// context, but the payment should still be in state in-flight without a
|
|
// failure reason.
|
|
cancelPaymentContext()
|
|
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], invoice.RPreimage)
|
|
payment := ht.AssertPaymentStatus(
|
|
alice, preimage, lnrpc.Payment_IN_FLIGHT,
|
|
)
|
|
reasonNone := lnrpc.PaymentFailureReason_FAILURE_REASON_NONE
|
|
require.Equal(ht, reasonNone, payment.FailureReason)
|
|
|
|
// Bob sends the interceptor action to the intercepted htlc.
|
|
err := interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
|
IncomingCircuitKey: packet.IncomingCircuitKey,
|
|
Action: interceptorAction,
|
|
})
|
|
require.NoError(ht, err, "failed to send request")
|
|
|
|
// Assert that the payment status is as expected.
|
|
ht.AssertPaymentStatus(alice, preimage, expectedPaymentStatus)
|
|
|
|
// Since the payment context was cancelled, no further payment attempts
|
|
// should've been made, and we observe FAILURE_REASON_CANCELED.
|
|
expectedReason := lnrpc.PaymentFailureReason_FAILURE_REASON_CANCELED
|
|
ht.AssertPaymentFailureReason(alice, preimage, expectedReason)
|
|
}
|
|
|
|
// testSendToRouteFailHTLCTimeout is similar to
|
|
// testPaymentFailedHTLCLocalSwept. The only difference is the `SendPayment` is
|
|
// replaced with `SendToRouteV2`. It checks that when an outgoing HTLC is timed
|
|
// out and claimed onchain via the timeout path, the payment will be marked as
|
|
// failed. This test creates a topology from Alice -> Bob, and let Alice send
|
|
// payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
|
|
// time out. Alice will also be restarted to make sure resumed payments are
|
|
// also marked as failed.
|
|
func testSendToRouteFailHTLCTimeout(ht *lntest.HarnessTest) {
|
|
success := ht.Run("fail payment", func(t *testing.T) {
|
|
st := ht.Subtest(t)
|
|
runSendToRouteFailHTLCTimeout(st, false)
|
|
})
|
|
if !success {
|
|
return
|
|
}
|
|
|
|
ht.Run("fail resumed payment", func(t *testing.T) {
|
|
st := ht.Subtest(t)
|
|
runTestPaymentHTLCTimeout(st, true)
|
|
})
|
|
}
|
|
|
|
// runSendToRouteFailHTLCTimeout is the helper function that actually runs the
|
|
// testSendToRouteFailHTLCTimeout.
|
|
func runSendToRouteFailHTLCTimeout(ht *lntest.HarnessTest, restartAlice bool) {
|
|
// Set the feerate to be 10 sat/vb.
|
|
ht.SetFeeEstimate(2500)
|
|
|
|
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
|
// being the sole funder of the channel.
|
|
chanAmt := btcutil.Amount(100_000)
|
|
openChannelParams := lntest.OpenChannelParams{
|
|
Amt: chanAmt,
|
|
}
|
|
cfgs := [][]string{nil, nil}
|
|
|
|
// Create a two hop network: Alice -> Bob.
|
|
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, openChannelParams)
|
|
chanPoint := chanPoints[0]
|
|
alice, bob := nodes[0], nodes[1]
|
|
|
|
// We now create two payments, one above dust and the other below dust,
|
|
// and we should see different behavior in terms of when the payment
|
|
// will be marked as failed due to the HTLC timeout.
|
|
//
|
|
// First, create random preimages.
|
|
preimage := ht.RandomPreimage()
|
|
dustPreimage := ht.RandomPreimage()
|
|
|
|
// Get the preimage hashes.
|
|
payHash := preimage.Hash()
|
|
dustPayHash := dustPreimage.Hash()
|
|
|
|
// Create an hold invoice for Bob which expects a payment of 10k
|
|
// satoshis from Alice.
|
|
const paymentAmt = 20_000
|
|
req := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: paymentAmt,
|
|
Hash: payHash[:],
|
|
// Use a small CLTV value so we can mine fewer blocks.
|
|
CltvExpiry: finalCltvDelta,
|
|
}
|
|
invoice := bob.RPC.AddHoldInvoice(req)
|
|
|
|
// Create another hold invoice for Bob which expects a payment of 1k
|
|
// satoshis from Alice.
|
|
const dustAmt = 1000
|
|
req = &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: dustAmt,
|
|
Hash: dustPayHash[:],
|
|
// Use a small CLTV value so we can mine fewer blocks.
|
|
CltvExpiry: finalCltvDelta,
|
|
}
|
|
dustInvoice := bob.RPC.AddHoldInvoice(req)
|
|
|
|
// Construct a route to send the non-dust payment.
|
|
go func() {
|
|
// Query the route to send the payment.
|
|
routesReq := &lnrpc.QueryRoutesRequest{
|
|
PubKey: bob.PubKeyStr,
|
|
Amt: paymentAmt,
|
|
FinalCltvDelta: finalCltvDelta,
|
|
}
|
|
routes := alice.RPC.QueryRoutes(routesReq)
|
|
require.Len(ht, routes.Routes, 1)
|
|
|
|
route := routes.Routes[0]
|
|
require.Len(ht, route.Hops, 1)
|
|
|
|
// Modify the hop to include MPP info.
|
|
route.Hops[0].MppRecord = &lnrpc.MPPRecord{
|
|
PaymentAddr: invoice.PaymentAddr,
|
|
TotalAmtMsat: int64(
|
|
lnwire.NewMSatFromSatoshis(paymentAmt),
|
|
),
|
|
}
|
|
|
|
// Send the payment with the modified value.
|
|
req := &routerrpc.SendToRouteRequest{
|
|
PaymentHash: payHash[:],
|
|
Route: route,
|
|
}
|
|
|
|
// Send the payment and expect no error.
|
|
attempt := alice.RPC.SendToRouteV2(req)
|
|
require.Equal(ht, lnrpc.HTLCAttempt_FAILED, attempt.Status)
|
|
}()
|
|
|
|
// Check that the payment is in-flight.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// Construct a route to send the dust payment.
|
|
go func() {
|
|
// Query the route to send the payment.
|
|
routesReq := &lnrpc.QueryRoutesRequest{
|
|
PubKey: bob.PubKeyStr,
|
|
Amt: dustAmt,
|
|
FinalCltvDelta: finalCltvDelta,
|
|
}
|
|
routes := alice.RPC.QueryRoutes(routesReq)
|
|
require.Len(ht, routes.Routes, 1)
|
|
|
|
route := routes.Routes[0]
|
|
require.Len(ht, route.Hops, 1)
|
|
|
|
// Modify the hop to include MPP info.
|
|
route.Hops[0].MppRecord = &lnrpc.MPPRecord{
|
|
PaymentAddr: dustInvoice.PaymentAddr,
|
|
TotalAmtMsat: int64(
|
|
lnwire.NewMSatFromSatoshis(dustAmt),
|
|
),
|
|
}
|
|
|
|
// Send the payment with the modified value.
|
|
req := &routerrpc.SendToRouteRequest{
|
|
PaymentHash: dustPayHash[:],
|
|
Route: route,
|
|
}
|
|
|
|
// Send the payment and expect no error.
|
|
attempt := alice.RPC.SendToRouteV2(req)
|
|
require.Equal(ht, lnrpc.HTLCAttempt_FAILED, attempt.Status)
|
|
}()
|
|
|
|
// Check that the dust payment is in-flight.
|
|
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// Bob should have two incoming HTLC.
|
|
ht.AssertIncomingHTLCActive(bob, chanPoint, payHash[:])
|
|
ht.AssertIncomingHTLCActive(bob, chanPoint, dustPayHash[:])
|
|
|
|
// Alice should have two outgoing HTLCs.
|
|
ht.AssertOutgoingHTLCActive(alice, chanPoint, payHash[:])
|
|
ht.AssertOutgoingHTLCActive(alice, chanPoint, dustPayHash[:])
|
|
|
|
// Let Bob go offline.
|
|
ht.Shutdown(bob)
|
|
|
|
// We'll now mine enough blocks to trigger Alice to broadcast her
|
|
// commitment transaction due to the fact that the HTLC is about to
|
|
// timeout. With the default outgoing broadcast delta of zero, this
|
|
// will be the same height as the htlc expiry height.
|
|
numBlocks := padCLTV(
|
|
uint32(req.CltvExpiry - lncfg.DefaultOutgoingBroadcastDelta),
|
|
)
|
|
ht.MineEmptyBlocks(int(numBlocks - 1))
|
|
|
|
// Restart Alice if requested.
|
|
if restartAlice {
|
|
// Restart Alice to test the resumed payment is canceled.
|
|
ht.RestartNode(alice)
|
|
}
|
|
|
|
// We now subscribe to the payment status.
|
|
payStream := alice.RPC.TrackPaymentV2(payHash[:])
|
|
dustPayStream := alice.RPC.TrackPaymentV2(dustPayHash[:])
|
|
|
|
// Mine a block to confirm Alice's closing transaction.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Now the channel is closed, we expect different behaviors based on
|
|
// whether the HTLC is a dust. For dust payment, it should be failed
|
|
// now as the HTLC won't go onchain. For non-dust payment, it should
|
|
// still be inflight. It won't be marked as failed unless the outgoing
|
|
// HTLC is resolved onchain.
|
|
//
|
|
// Check that the dust payment is failed in both the stream and DB.
|
|
ht.AssertPaymentStatus(alice, dustPreimage, lnrpc.Payment_FAILED)
|
|
ht.AssertPaymentStatusFromStream(dustPayStream, lnrpc.Payment_FAILED)
|
|
|
|
// Check that the non-dust payment is still in-flight.
|
|
//
|
|
// NOTE: we don't check the payment status from the stream here as
|
|
// there's no new status being sent.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
|
|
|
|
// We now have two possible cases for the non-dust payment:
|
|
// - Bob stays offline, and Alice will sweep her outgoing HTLC, which
|
|
// makes the payment failed.
|
|
// - Bob comes back online, and claims the HTLC on Alice's commitment
|
|
// via direct preimage spend, hence racing against Alice onchain. If
|
|
// he succeeds, Alice should mark the payment as succeeded.
|
|
//
|
|
// TODO(yy): test the second case once we have the RPC to clean
|
|
// mempool.
|
|
|
|
// Since Alice's force close transaction has been confirmed, she should
|
|
// sweep her outgoing HTLC in next block.
|
|
ht.MineBlocksAndAssertNumTxes(2, 1)
|
|
|
|
// We expect the non-dust payment to marked as failed in Alice's
|
|
// database and also from her stream.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_FAILED)
|
|
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
|
|
}
|