d6f88cbe6f
This permits an AMP invoice to be "pseudo-reusable", where the invoice paramters can be used multiple times so long as a new payment address is supplied. This prevents additional round trips between payer and payee to obtain a new invoice, even though the payments/invoices won't be logically associated via the RPC interface like they would when the full reusable invoices are deployed.
606 lines
19 KiB
Go
606 lines
19 KiB
Go
package itest
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/amp"
|
|
"github.com/lightningnetwork/lnd/chainreg"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// testSendPaymentAMPInvoice tests that we can send an AMP payment to a
|
|
// specified AMP invoice using SendPaymentV2.
|
|
func testSendPaymentAMPInvoice(net *lntest.NetworkHarness, t *harnessTest) {
|
|
t.t.Run("native payaddr", func(t *testing.T) {
|
|
tt := newHarnessTest(t, net)
|
|
testSendPaymentAMPInvoiceCase(net, tt, false)
|
|
})
|
|
t.t.Run("external payaddr", func(t *testing.T) {
|
|
tt := newHarnessTest(t, net)
|
|
testSendPaymentAMPInvoiceCase(net, tt, true)
|
|
})
|
|
}
|
|
|
|
func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest,
|
|
useExternalPayAddr bool) {
|
|
|
|
ctxb := context.Background()
|
|
|
|
ctx := newMppTestContext(t, net)
|
|
defer ctx.shutdownNodes()
|
|
|
|
const paymentAmt = btcutil.Amount(300000)
|
|
|
|
// Set up a network with three different paths Alice <-> Bob. Channel
|
|
// capacities are set such that the payment can only succeed if (at
|
|
// least) three paths are used.
|
|
//
|
|
// _ Eve _
|
|
// / \
|
|
// Alice -- Carol ---- Bob
|
|
// \ /
|
|
// \__ Dave ____/
|
|
//
|
|
ctx.openChannel(ctx.carol, ctx.bob, 135000)
|
|
ctx.openChannel(ctx.alice, ctx.carol, 235000)
|
|
ctx.openChannel(ctx.dave, ctx.bob, 135000)
|
|
ctx.openChannel(ctx.alice, ctx.dave, 135000)
|
|
ctx.openChannel(ctx.eve, ctx.bob, 135000)
|
|
ctx.openChannel(ctx.carol, ctx.eve, 135000)
|
|
|
|
defer ctx.closeChannels()
|
|
|
|
ctx.waitForChannels()
|
|
|
|
// Subscribe to bob's invoices.
|
|
req := &lnrpc.InvoiceSubscription{}
|
|
ctxc, cancelSubscription := context.WithCancel(ctxb)
|
|
bobInvoiceSubscription, err := ctx.bob.SubscribeInvoices(ctxc, req)
|
|
require.NoError(t.t, err)
|
|
defer cancelSubscription()
|
|
|
|
addInvoiceResp, err := ctx.bob.AddInvoice(context.Background(), &lnrpc.Invoice{
|
|
Value: int64(paymentAmt),
|
|
IsAmp: true,
|
|
})
|
|
require.NoError(t.t, err)
|
|
|
|
// Ensure we get a notification of the invoice being added by Bob.
|
|
rpcInvoice, err := bobInvoiceSubscription.Recv()
|
|
require.NoError(t.t, err)
|
|
|
|
require.False(t.t, rpcInvoice.Settled) // nolint:staticcheck
|
|
require.Equal(t.t, lnrpc.Invoice_OPEN, rpcInvoice.State)
|
|
require.Equal(t.t, int64(0), rpcInvoice.AmtPaidSat)
|
|
require.Equal(t.t, int64(0), rpcInvoice.AmtPaidMsat)
|
|
|
|
require.Equal(t.t, 0, len(rpcInvoice.Htlcs))
|
|
|
|
// Increase Dave's fee to make the test deterministic. Otherwise it
|
|
// would be unpredictable whether pathfinding would go through Charlie
|
|
// or Dave for the first shard.
|
|
_, err = ctx.dave.UpdateChannelPolicy(
|
|
context.Background(),
|
|
&lnrpc.PolicyUpdateRequest{
|
|
Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true},
|
|
BaseFeeMsat: 500000,
|
|
FeeRate: 0.001,
|
|
TimeLockDelta: 40,
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("dave policy update: %v", err)
|
|
}
|
|
|
|
// Generate an external payment address when attempting to pseudo-reuse
|
|
// an AMP invoice. When using an external payment address, we'll also
|
|
// expect an extra invoice to appear in the ListInvoices response, since
|
|
// a new invoice will be JIT inserted under a different payment address
|
|
// than the one in the invoice.
|
|
var (
|
|
expNumInvoices = 1
|
|
externalPayAddr []byte
|
|
)
|
|
if useExternalPayAddr {
|
|
expNumInvoices = 2
|
|
externalPayAddr = make([]byte, 32)
|
|
_, err = rand.Read(externalPayAddr)
|
|
require.NoError(t.t, err)
|
|
}
|
|
|
|
ctxt, _ := context.WithTimeout(context.Background(), 4*defaultTimeout)
|
|
payment := sendAndAssertSuccess(
|
|
ctxt, t, ctx.alice,
|
|
&routerrpc.SendPaymentRequest{
|
|
PaymentRequest: addInvoiceResp.PaymentRequest,
|
|
PaymentAddr: externalPayAddr,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
},
|
|
)
|
|
|
|
// Check that Alice split the payment in at least three shards. Because
|
|
// the hand-off of the htlc to the link is asynchronous (via a mailbox),
|
|
// there is some non-determinism in the process. Depending on whether
|
|
// the new pathfinding round is started before or after the htlc is
|
|
// locked into the channel, different sharding may occur. Therefore we
|
|
// can only check if the number of shards isn't below the theoretical
|
|
// minimum.
|
|
succeeded := 0
|
|
for _, htlc := range payment.Htlcs {
|
|
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
|
|
succeeded++
|
|
}
|
|
}
|
|
|
|
const minExpectedShards = 3
|
|
if succeeded < minExpectedShards {
|
|
t.Fatalf("expected at least %v shards, but got %v",
|
|
minExpectedShards, succeeded)
|
|
}
|
|
|
|
// When an external payment address is supplied, we'll get an extra
|
|
// notification for the JIT inserted invoice, since it differs from the
|
|
// original.
|
|
if useExternalPayAddr {
|
|
_, err = bobInvoiceSubscription.Recv()
|
|
require.NoError(t.t, err)
|
|
}
|
|
|
|
// There should now be a settle event for the invoice.
|
|
rpcInvoice, err = bobInvoiceSubscription.Recv()
|
|
require.NoError(t.t, err)
|
|
|
|
// Also fetch Bob's invoice from ListInvoices and assert it is equal to
|
|
// the one recevied via the subscription.
|
|
invoiceResp, err := ctx.bob.ListInvoices(
|
|
ctxb, &lnrpc.ListInvoiceRequest{},
|
|
)
|
|
require.NoError(t.t, err)
|
|
require.Equal(t.t, expNumInvoices, len(invoiceResp.Invoices))
|
|
assertInvoiceEqual(t.t, rpcInvoice, invoiceResp.Invoices[expNumInvoices-1])
|
|
|
|
// Assert that the invoice is settled for the total payment amount and
|
|
// has the correct payment address.
|
|
require.True(t.t, rpcInvoice.Settled) // nolint:staticcheck
|
|
require.Equal(t.t, lnrpc.Invoice_SETTLED, rpcInvoice.State)
|
|
require.Equal(t.t, int64(paymentAmt), rpcInvoice.AmtPaidSat)
|
|
require.Equal(t.t, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
|
|
|
|
// Finally, assert that the same set id is recorded for each htlc, and
|
|
// that the preimage hash pair is valid.
|
|
var setID []byte
|
|
require.Equal(t.t, succeeded, len(rpcInvoice.Htlcs))
|
|
for _, htlc := range rpcInvoice.Htlcs {
|
|
require.NotNil(t.t, htlc.Amp)
|
|
if setID == nil {
|
|
setID = make([]byte, 32)
|
|
copy(setID, htlc.Amp.SetId)
|
|
}
|
|
require.Equal(t.t, setID, htlc.Amp.SetId)
|
|
|
|
// Parse the child hash and child preimage, and assert they are
|
|
// well-formed.
|
|
childHash, err := lntypes.MakeHash(htlc.Amp.Hash)
|
|
require.NoError(t.t, err)
|
|
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
|
|
require.NoError(t.t, err)
|
|
|
|
// Assert that the preimage actually matches the hashes.
|
|
validPreimage := childPreimage.Matches(childHash)
|
|
require.True(t.t, validPreimage)
|
|
}
|
|
|
|
}
|
|
|
|
// testSendPaymentAMP tests that we can send an AMP payment to a specified
|
|
// destination using SendPaymentV2.
|
|
func testSendPaymentAMP(net *lntest.NetworkHarness, t *harnessTest) {
|
|
ctxb := context.Background()
|
|
|
|
ctx := newMppTestContext(t, net)
|
|
defer ctx.shutdownNodes()
|
|
|
|
const paymentAmt = btcutil.Amount(300000)
|
|
|
|
// Set up a network with three different paths Alice <-> Bob. Channel
|
|
// capacities are set such that the payment can only succeed if (at
|
|
// least) three paths are used.
|
|
//
|
|
// _ Eve _
|
|
// / \
|
|
// Alice -- Carol ---- Bob
|
|
// \ /
|
|
// \__ Dave ____/
|
|
//
|
|
ctx.openChannel(ctx.carol, ctx.bob, 135000)
|
|
ctx.openChannel(ctx.alice, ctx.carol, 235000)
|
|
ctx.openChannel(ctx.dave, ctx.bob, 135000)
|
|
ctx.openChannel(ctx.alice, ctx.dave, 135000)
|
|
ctx.openChannel(ctx.eve, ctx.bob, 135000)
|
|
ctx.openChannel(ctx.carol, ctx.eve, 135000)
|
|
|
|
defer ctx.closeChannels()
|
|
|
|
ctx.waitForChannels()
|
|
|
|
// Increase Dave's fee to make the test deterministic. Otherwise it
|
|
// would be unpredictable whether pathfinding would go through Charlie
|
|
// or Dave for the first shard.
|
|
_, err := ctx.dave.UpdateChannelPolicy(
|
|
context.Background(),
|
|
&lnrpc.PolicyUpdateRequest{
|
|
Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true},
|
|
BaseFeeMsat: 500000,
|
|
FeeRate: 0.001,
|
|
TimeLockDelta: 40,
|
|
},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("dave policy update: %v", err)
|
|
}
|
|
|
|
ctxt, _ := context.WithTimeout(context.Background(), 4*defaultTimeout)
|
|
payment := sendAndAssertSuccess(
|
|
ctxt, t, ctx.alice,
|
|
&routerrpc.SendPaymentRequest{
|
|
Dest: ctx.bob.PubKey[:],
|
|
Amt: int64(paymentAmt),
|
|
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
Amp: true,
|
|
},
|
|
)
|
|
|
|
// Check that Alice split the payment in at least three shards. Because
|
|
// the hand-off of the htlc to the link is asynchronous (via a mailbox),
|
|
// there is some non-determinism in the process. Depending on whether
|
|
// the new pathfinding round is started before or after the htlc is
|
|
// locked into the channel, different sharding may occur. Therefore we
|
|
// can only check if the number of shards isn't below the theoretical
|
|
// minimum.
|
|
succeeded := 0
|
|
for _, htlc := range payment.Htlcs {
|
|
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
|
|
succeeded++
|
|
}
|
|
}
|
|
|
|
const minExpectedShards = 3
|
|
if succeeded < minExpectedShards {
|
|
t.Fatalf("expected at least %v shards, but got %v",
|
|
minExpectedShards, succeeded)
|
|
}
|
|
|
|
// Fetch Bob's invoices.
|
|
invoiceResp, err := ctx.bob.ListInvoices(
|
|
ctxb, &lnrpc.ListInvoiceRequest{},
|
|
)
|
|
require.NoError(t.t, err)
|
|
|
|
// There should only be one invoice.
|
|
require.Equal(t.t, 1, len(invoiceResp.Invoices))
|
|
rpcInvoice := invoiceResp.Invoices[0]
|
|
|
|
// Assert that the invoice is settled for the total payment amount and
|
|
// has the correct payment address.
|
|
require.True(t.t, rpcInvoice.Settled) // nolint:staticcheck
|
|
require.Equal(t.t, lnrpc.Invoice_SETTLED, rpcInvoice.State)
|
|
require.Equal(t.t, int64(paymentAmt), rpcInvoice.AmtPaidSat)
|
|
require.Equal(t.t, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
|
|
|
|
// Finally, assert that the same set id is recorded for each htlc, and
|
|
// that the preimage hash pair is valid.
|
|
var setID []byte
|
|
require.Equal(t.t, succeeded, len(rpcInvoice.Htlcs))
|
|
for _, htlc := range rpcInvoice.Htlcs {
|
|
require.NotNil(t.t, htlc.Amp)
|
|
if setID == nil {
|
|
setID = make([]byte, 32)
|
|
copy(setID, htlc.Amp.SetId)
|
|
}
|
|
require.Equal(t.t, setID, htlc.Amp.SetId)
|
|
|
|
// Parse the child hash and child preimage, and assert they are
|
|
// well-formed.
|
|
childHash, err := lntypes.MakeHash(htlc.Amp.Hash)
|
|
require.NoError(t.t, err)
|
|
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
|
|
require.NoError(t.t, err)
|
|
|
|
// Assert that the preimage actually matches the hashes.
|
|
validPreimage := childPreimage.Matches(childHash)
|
|
require.True(t.t, validPreimage)
|
|
}
|
|
}
|
|
|
|
func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) {
|
|
ctxb := context.Background()
|
|
|
|
ctx := newMppTestContext(t, net)
|
|
defer ctx.shutdownNodes()
|
|
|
|
const (
|
|
paymentAmt = btcutil.Amount(300000)
|
|
numShards = 3
|
|
shardAmt = paymentAmt / numShards
|
|
chanAmt = shardAmt * 3 / 2
|
|
)
|
|
|
|
// Set up a network with three different paths Alice <-> Bob.
|
|
// _ Eve _
|
|
// / \
|
|
// Alice -- Carol ---- Bob
|
|
// \ /
|
|
// \__ Dave ____/
|
|
//
|
|
ctx.openChannel(ctx.carol, ctx.bob, chanAmt)
|
|
ctx.openChannel(ctx.dave, ctx.bob, chanAmt)
|
|
ctx.openChannel(ctx.alice, ctx.dave, chanAmt)
|
|
ctx.openChannel(ctx.eve, ctx.bob, chanAmt)
|
|
ctx.openChannel(ctx.carol, ctx.eve, chanAmt)
|
|
|
|
// Since the channel Alice-> Carol will have to carry two
|
|
// shards, we make it larger.
|
|
ctx.openChannel(ctx.alice, ctx.carol, chanAmt+shardAmt)
|
|
|
|
defer ctx.closeChannels()
|
|
|
|
ctx.waitForChannels()
|
|
|
|
// Subscribe to bob's invoices.
|
|
req := &lnrpc.InvoiceSubscription{}
|
|
ctxc, cancelSubscription := context.WithCancel(ctxb)
|
|
bobInvoiceSubscription, err := ctx.bob.SubscribeInvoices(ctxc, req)
|
|
require.NoError(t.t, err)
|
|
defer cancelSubscription()
|
|
|
|
// We'll send shards along three routes from Alice.
|
|
sendRoutes := [numShards][]*lntest.HarnessNode{
|
|
{ctx.carol, ctx.bob},
|
|
{ctx.dave, ctx.bob},
|
|
{ctx.carol, ctx.eve, ctx.bob},
|
|
}
|
|
|
|
payAddr := make([]byte, 32)
|
|
_, err = rand.Read(payAddr)
|
|
require.NoError(t.t, err)
|
|
|
|
setID := make([]byte, 32)
|
|
_, err = rand.Read(setID)
|
|
require.NoError(t.t, err)
|
|
|
|
var sharer amp.Sharer
|
|
sharer, err = amp.NewSeedSharer()
|
|
require.NoError(t.t, err)
|
|
|
|
childPreimages := make(map[lntypes.Preimage]uint32)
|
|
responses := make(chan *lnrpc.HTLCAttempt, len(sendRoutes))
|
|
|
|
// Define a closure for sending each of the three shards.
|
|
sendShard := func(i int, hops []*lntest.HarnessNode) {
|
|
// Build a route for the specified hops.
|
|
r, err := ctx.buildRoute(ctxb, shardAmt, ctx.alice, hops)
|
|
if err != nil {
|
|
t.Fatalf("unable to build route: %v", err)
|
|
}
|
|
|
|
// Set the MPP records to indicate this is a payment shard.
|
|
hop := r.Hops[len(r.Hops)-1]
|
|
hop.TlvPayload = true
|
|
hop.MppRecord = &lnrpc.MPPRecord{
|
|
PaymentAddr: payAddr,
|
|
TotalAmtMsat: int64(paymentAmt * 1000),
|
|
}
|
|
|
|
var child *amp.Child
|
|
if i < len(sendRoutes)-1 {
|
|
var left amp.Sharer
|
|
left, sharer, err = sharer.Split()
|
|
require.NoError(t.t, err)
|
|
|
|
child = left.Child(uint32(i))
|
|
} else {
|
|
child = sharer.Child(uint32(i))
|
|
}
|
|
childPreimages[child.Preimage] = child.Index
|
|
|
|
hop.AmpRecord = &lnrpc.AMPRecord{
|
|
RootShare: child.Share[:],
|
|
SetId: setID,
|
|
ChildIndex: child.Index,
|
|
}
|
|
|
|
// Send the shard.
|
|
sendReq := &routerrpc.SendToRouteRequest{
|
|
PaymentHash: child.Hash[:],
|
|
Route: r,
|
|
}
|
|
|
|
// We'll send all shards in their own goroutine, since SendToRoute will
|
|
// block as long as the payment is in flight.
|
|
go func() {
|
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
resp, err := ctx.alice.RouterClient.SendToRouteV2(ctxt, sendReq)
|
|
if err != nil {
|
|
t.Fatalf("unable to send payment: %v", err)
|
|
}
|
|
|
|
responses <- resp
|
|
}()
|
|
}
|
|
|
|
// Send the first shard, this cause Bob to JIT add an invoice.
|
|
sendShard(0, sendRoutes[0])
|
|
|
|
// Ensure we get a notification of the invoice being added by Bob.
|
|
rpcInvoice, err := bobInvoiceSubscription.Recv()
|
|
require.NoError(t.t, err)
|
|
|
|
require.False(t.t, rpcInvoice.Settled) // nolint:staticcheck
|
|
require.Equal(t.t, lnrpc.Invoice_OPEN, rpcInvoice.State)
|
|
require.Equal(t.t, int64(0), rpcInvoice.AmtPaidSat)
|
|
require.Equal(t.t, int64(0), rpcInvoice.AmtPaidMsat)
|
|
require.Equal(t.t, payAddr, rpcInvoice.PaymentAddr)
|
|
|
|
require.Equal(t.t, 0, len(rpcInvoice.Htlcs))
|
|
|
|
sendShard(1, sendRoutes[1])
|
|
sendShard(2, sendRoutes[2])
|
|
|
|
// Assert that all of the child preimages are unique.
|
|
require.Equal(t.t, len(sendRoutes), len(childPreimages))
|
|
|
|
// Make a copy of the childPreimages map for validating the resulting
|
|
// invoice.
|
|
childPreimagesCopy := make(map[lntypes.Preimage]uint32)
|
|
for preimage, childIndex := range childPreimages {
|
|
childPreimagesCopy[preimage] = childIndex
|
|
}
|
|
|
|
// Wait for all responses to be back, and check that they all
|
|
// succeeded.
|
|
for range sendRoutes {
|
|
var resp *lnrpc.HTLCAttempt
|
|
select {
|
|
case resp = <-responses:
|
|
case <-time.After(defaultTimeout):
|
|
t.Fatalf("response not received")
|
|
}
|
|
|
|
if resp.Failure != nil {
|
|
t.Fatalf("received payment failure : %v", resp.Failure)
|
|
}
|
|
|
|
preimage, err := lntypes.MakePreimage(resp.Preimage)
|
|
require.NoError(t.t, err)
|
|
|
|
// Assert that the response includes one of our child preimages.
|
|
_, ok := childPreimages[preimage]
|
|
require.True(t.t, ok)
|
|
|
|
// Remove this preimage from out set so that we ensure all
|
|
// responses have a unique child preimage.
|
|
delete(childPreimages, preimage)
|
|
}
|
|
childPreimages = childPreimagesCopy
|
|
|
|
// There should now be a settle event for the invoice.
|
|
rpcInvoice, err = bobInvoiceSubscription.Recv()
|
|
require.NoError(t.t, err)
|
|
|
|
// Also fetch Bob's invoice from ListInvoices and assert it is equal to
|
|
// the one recevied via the subscription.
|
|
invoiceResp, err := ctx.bob.ListInvoices(
|
|
ctxb, &lnrpc.ListInvoiceRequest{},
|
|
)
|
|
require.NoError(t.t, err)
|
|
require.Equal(t.t, 1, len(invoiceResp.Invoices))
|
|
assertInvoiceEqual(t.t, rpcInvoice, invoiceResp.Invoices[0])
|
|
|
|
// Assert that the invoice is settled for the total payment amount and
|
|
// has the correct payment address.
|
|
require.True(t.t, rpcInvoice.Settled) // nolint:staticcheck
|
|
require.Equal(t.t, lnrpc.Invoice_SETTLED, rpcInvoice.State)
|
|
require.Equal(t.t, int64(paymentAmt), rpcInvoice.AmtPaidSat)
|
|
require.Equal(t.t, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
|
|
require.Equal(t.t, payAddr, rpcInvoice.PaymentAddr)
|
|
|
|
// Finally, assert that the proper set id is recorded for each htlc, and
|
|
// that the preimage hash pair is valid.
|
|
require.Equal(t.t, numShards, len(rpcInvoice.Htlcs))
|
|
for _, htlc := range rpcInvoice.Htlcs {
|
|
require.NotNil(t.t, htlc.Amp)
|
|
require.Equal(t.t, setID, htlc.Amp.SetId)
|
|
|
|
// Parse the child hash and child preimage, and assert they are
|
|
// well-formed.
|
|
childHash, err := lntypes.MakeHash(htlc.Amp.Hash)
|
|
require.NoError(t.t, err)
|
|
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
|
|
require.NoError(t.t, err)
|
|
|
|
// Assert that the preimage actually matches the hashes.
|
|
validPreimage := childPreimage.Matches(childHash)
|
|
require.True(t.t, validPreimage)
|
|
|
|
// Assert that the HTLC includes one of our child preimages.
|
|
childIndex, ok := childPreimages[childPreimage]
|
|
require.True(t.t, ok)
|
|
|
|
// Assert that the correct child index is reflected.
|
|
require.Equal(t.t, childIndex, htlc.Amp.ChildIndex)
|
|
|
|
// Remove this preimage from our set so that we ensure all HTLCs
|
|
// have a unique child preimage.
|
|
delete(childPreimages, childPreimage)
|
|
}
|
|
}
|
|
|
|
// assertInvoiceEqual asserts that two lnrpc.Invoices are equivalent. A custom
|
|
// comparison function is defined for these tests, since proto message returned
|
|
// from unary and streaming RPCs (as of protobuf 1.23.0 and grpc 1.29.1) aren't
|
|
// consistent with the private fields set on the messages. As a result, we avoid
|
|
// using require.Equal and test only the actual data members.
|
|
func assertInvoiceEqual(t *testing.T, a, b *lnrpc.Invoice) {
|
|
t.Helper()
|
|
|
|
// Ensure the HTLCs are sorted properly before attempting to compare.
|
|
sort.Slice(a.Htlcs, func(i, j int) bool {
|
|
return a.Htlcs[i].ChanId < a.Htlcs[j].ChanId
|
|
})
|
|
sort.Slice(b.Htlcs, func(i, j int) bool {
|
|
return b.Htlcs[i].ChanId < b.Htlcs[j].ChanId
|
|
})
|
|
|
|
require.Equal(t, a.Memo, b.Memo)
|
|
require.Equal(t, a.RPreimage, b.RPreimage)
|
|
require.Equal(t, a.RHash, b.RHash)
|
|
require.Equal(t, a.Value, b.Value)
|
|
require.Equal(t, a.ValueMsat, b.ValueMsat)
|
|
require.Equal(t, a.CreationDate, b.CreationDate)
|
|
require.Equal(t, a.SettleDate, b.SettleDate)
|
|
require.Equal(t, a.PaymentRequest, b.PaymentRequest)
|
|
require.Equal(t, a.DescriptionHash, b.DescriptionHash)
|
|
require.Equal(t, a.Expiry, b.Expiry)
|
|
require.Equal(t, a.FallbackAddr, b.FallbackAddr)
|
|
require.Equal(t, a.CltvExpiry, b.CltvExpiry)
|
|
require.Equal(t, a.RouteHints, b.RouteHints)
|
|
require.Equal(t, a.Private, b.Private)
|
|
require.Equal(t, a.AddIndex, b.AddIndex)
|
|
require.Equal(t, a.SettleIndex, b.SettleIndex)
|
|
require.Equal(t, a.AmtPaidSat, b.AmtPaidSat)
|
|
require.Equal(t, a.AmtPaidMsat, b.AmtPaidMsat)
|
|
require.Equal(t, a.State, b.State)
|
|
require.Equal(t, a.Features, b.Features)
|
|
require.Equal(t, a.IsKeysend, b.IsKeysend)
|
|
require.Equal(t, a.PaymentAddr, b.PaymentAddr)
|
|
require.Equal(t, a.IsAmp, b.IsAmp)
|
|
|
|
require.Equal(t, len(a.Htlcs), len(b.Htlcs))
|
|
for i := range a.Htlcs {
|
|
htlcA, htlcB := a.Htlcs[i], b.Htlcs[i]
|
|
require.Equal(t, htlcA.ChanId, htlcB.ChanId)
|
|
require.Equal(t, htlcA.HtlcIndex, htlcB.HtlcIndex)
|
|
require.Equal(t, htlcA.AmtMsat, htlcB.AmtMsat)
|
|
require.Equal(t, htlcA.AcceptHeight, htlcB.AcceptHeight)
|
|
require.Equal(t, htlcA.AcceptTime, htlcB.AcceptTime)
|
|
require.Equal(t, htlcA.ResolveTime, htlcB.ResolveTime)
|
|
require.Equal(t, htlcA.ExpiryHeight, htlcB.ExpiryHeight)
|
|
require.Equal(t, htlcA.State, htlcB.State)
|
|
require.Equal(t, htlcA.CustomRecords, htlcB.CustomRecords)
|
|
require.Equal(t, htlcA.MppTotalAmtMsat, htlcB.MppTotalAmtMsat)
|
|
require.Equal(t, htlcA.Amp, htlcB.Amp)
|
|
}
|
|
}
|