lnd/itest/lnd_amp_test.go
Andras Banki-Horvath c8de7a1699
channeldb: filter AMP state to relevant set IDs
When fetching an AMP invoice we sometimes filter HTLCs to selected set
IDs, however we always kept the full AMP state which is irrelevant as it
contains state for all AMP payments. This was a side effect of
UpdateInvoice needing to serialize the whole invoice when storing after
an update but it is an unwanted "feature" as users will need to filter
to relevant set when listing an AMP payment or subsribing to an update.
2024-09-03 19:40:46 +02:00

696 lines
23 KiB
Go

package itest
import (
"encoding/hex"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/amp"
"github.com/lightningnetwork/lnd/chainreg"
"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/lntypes"
"github.com/stretchr/testify/require"
)
// testSendPaymentAMPInvoice tests that we can send an AMP payment to a
// specified AMP invoice using SendPaymentV2.
func testSendPaymentAMPInvoice(ht *lntest.HarnessTest) {
succeed := ht.Run("native payaddr", func(t *testing.T) {
tt := ht.Subtest(t)
testSendPaymentAMPInvoiceCase(tt, false)
})
// Abort the test if failed.
if !succeed {
return
}
ht.Run("external payaddr", func(t *testing.T) {
tt := ht.Subtest(t)
testSendPaymentAMPInvoiceCase(tt, true)
})
}
func testSendPaymentAMPInvoiceCase(ht *lntest.HarnessTest,
useExternalPayAddr bool) {
mts := newMppTestScenario(ht)
// Subscribe to bob's invoices. Do this early in the test to make sure
// that the subscription has actually been completed when we add an
// invoice. Otherwise the notification will be missed.
req := &lnrpc.InvoiceSubscription{}
bobInvoiceSubscription := mts.bob.RPC.SubscribeInvoices(req)
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 ____/
//
mppReq := &mppOpenChannelRequest{
amtAliceCarol: 285000,
amtAliceDave: 155000,
amtCarolBob: 200000,
amtCarolEve: 155000,
amtDaveBob: 155000,
amtEveBob: 155000,
}
mts.openChannels(mppReq)
chanPointAliceDave := mts.channelPoints[1]
chanPointDaveBob := mts.channelPoints[4]
invoice := &lnrpc.Invoice{
Value: int64(paymentAmt),
IsAmp: true,
}
addInvoiceResp := mts.bob.RPC.AddInvoice(invoice)
// Ensure we get a notification of the invoice being added by Bob.
rpcInvoice := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
require.False(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat)
require.Equal(ht, 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.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: 500_000,
FeeRateMilliMsat: int64(0.001 * 1_000_000),
TimeLockDelta: 40,
MinHtlc: 1000, // default value
MaxHtlcMsat: 133_650_000,
}
mts.dave.UpdateGlobalPolicy(expectedPolicy)
// Make sure Alice has heard it for both Dave's channels.
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false,
)
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointDaveBob, false,
)
// 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.
//
// NOTE: This will only work when the peer has spontaneous AMP payments
// enabled otherwise no invoice under a different payment_addr will be
// found.
var (
expNumInvoices = 1
externalPayAddr []byte
)
if useExternalPayAddr {
expNumInvoices = 2
externalPayAddr = ht.Random32Bytes()
}
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: addInvoiceResp.PaymentRequest,
PaymentAddr: externalPayAddr,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
Amp: true,
}
payment := ht.SendPaymentAssertSettled(mts.alice, sendReq)
// 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
require.GreaterOrEqual(ht, succeeded, minExpectedShards,
"expected num of shards not reached")
// 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 {
ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
}
// There should now be a settle event for the invoice.
rpcInvoice = ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
// Also fetch Bob's invoice from ListInvoices and assert it is equal to
// the one received via the subscription.
invoices := ht.AssertNumInvoices(mts.bob, expNumInvoices)
ht.AssertInvoiceEqual(rpcInvoice, invoices[expNumInvoices-1])
// Assert that the invoice is settled for the total payment amount and
// has the correct payment address.
require.True(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State)
require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat)
require.Equal(ht, 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(ht, succeeded, len(rpcInvoice.Htlcs))
for _, htlc := range rpcInvoice.Htlcs {
require.NotNil(ht, htlc.Amp)
if setID == nil {
setID = make([]byte, 32)
copy(setID, htlc.Amp.SetId)
}
require.Equal(ht, 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(ht, err)
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
require.NoError(ht, err)
// Assert that the preimage actually matches the hashes.
validPreimage := childPreimage.Matches(childHash)
require.True(ht, validPreimage)
}
// The set ID we extract above should be shown in the final settled
// state.
ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)]
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
// Finally, close all channels.
mts.closeChannels()
}
// testSendPaymentAMPInvoiceRepeat tests that it's possible to pay an AMP
// invoice multiple times by having the client generate a new setID each time.
func testSendPaymentAMPInvoiceRepeat(ht *lntest.HarnessTest) {
// In this basic test, we'll only need two nodes as we want to
// primarily test the recurring payment feature. So we'll re-use the
carol := ht.NewNode("Carol", nil)
// Send Carol enough coins to be able to open a channel to Dave.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
dave := ht.NewNode("Dave", nil)
// Set up an invoice subscription so we can be notified when Dave
// receives his repeated payments.
req := &lnrpc.InvoiceSubscription{}
invSubscription := dave.RPC.SubscribeInvoices(req)
// Before we start the test, we'll ensure both sides are connected to
// the funding flow can properly be executed.
ht.EnsureConnected(carol, dave)
// Establish a channel between Carol and Dave.
chanAmt := btcutil.Amount(100_000)
ht.OpenChannel(
carol, dave, lntest.OpenChannelParams{Amt: chanAmt},
)
// Create an AMP invoice of a trivial amount, that we'll pay repeatedly
// in this integration test.
paymentAmt := 10000
invoice := &lnrpc.Invoice{
Value: int64(paymentAmt),
IsAmp: true,
}
addInvoiceResp := dave.RPC.AddInvoice(invoice)
// We should get an initial notification that the HTLC has been added.
rpcInvoice := ht.ReceiveInvoiceUpdate(invSubscription)
require.False(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat)
require.Equal(ht, 0, len(rpcInvoice.Htlcs))
// Now we'll use Carol to pay the invoice that Dave created.
ht.CompletePaymentRequests(
carol, []string{addInvoiceResp.PaymentRequest},
lntest.WithAMP(),
)
// Dave should get a notification that the invoice has been settled.
invoiceNtfn := ht.ReceiveInvoiceUpdate(invSubscription)
// The notification should signal that the invoice is now settled, and
// should also include the set ID, show the proper amount paid, and have
// the correct settle index and time.
require.True(ht, invoiceNtfn.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceNtfn.State)
require.Equal(ht, paymentAmt, int(invoiceNtfn.AmtPaidSat))
require.Equal(ht, 1, len(invoiceNtfn.AmpInvoiceState))
var firstSetID []byte
for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState {
firstSetID, _ = hex.DecodeString(setIDStr)
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED,
ampState.State)
require.GreaterOrEqual(ht, ampState.SettleTime,
rpcInvoice.CreationDate)
require.Equal(ht, uint64(1), ampState.SettleIndex)
}
// Pay the invoice again, we should get another notification that Dave
// has received another payment.
ht.CompletePaymentRequests(
carol, []string{addInvoiceResp.PaymentRequest},
lntest.WithAMP(),
)
// Dave should get another notification.
invoiceNtfn = ht.ReceiveInvoiceUpdate(invSubscription)
// The invoice should still be shown as settled, and also include the
// information about this newly generated setID, showing 2x the amount
// paid.
require.True(ht, invoiceNtfn.Settled)
require.Equal(ht, paymentAmt*2, int(invoiceNtfn.AmtPaidSat))
var secondSetID []byte
for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState {
secondSetID, _ = hex.DecodeString(setIDStr)
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED,
ampState.State)
}
// The returned invoice should only include a single HTLC since we
// return the "projected" sub-invoice for a given setID.
require.Equal(ht, 1, len(invoiceNtfn.Htlcs))
// The AMP state should also be restricted to a single entry for the
// "projected" sub-invoice.
require.Equal(ht, 1, len(invoiceNtfn.AmpInvoiceState))
// Now we'll look up the invoice using the new LookupInvoice2 RPC call
// by the set ID of each of the invoices.
msg := &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{
SetId: firstSetID,
},
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY,
}
subInvoice1 := dave.RPC.LookupInvoiceV2(msg)
msg = &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{
SetId: secondSetID,
},
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY,
}
subInvoice2 := dave.RPC.LookupInvoiceV2(msg)
// Each invoice should only show a single HTLC present, as we passed
// the HTLC set only modifier.
require.Equal(ht, 1, len(subInvoice1.Htlcs))
require.Equal(ht, 1, len(subInvoice2.Htlcs))
// If we look up the same invoice, by its payment address, but now with
// the HTLC blank modifier, then none of them should be returned.
msg = &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
PaymentAddr: addInvoiceResp.PaymentAddr,
},
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_BLANK,
}
rootInvoice := dave.RPC.LookupInvoiceV2(msg)
require.Equal(ht, 0, len(rootInvoice.Htlcs))
// If we look up the same invoice, by its payment address, but without
// that modified, then we should get all the relevant HTLCs.
msg = &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
PaymentAddr: addInvoiceResp.PaymentAddr,
},
}
rootInvoice = dave.RPC.LookupInvoiceV2(msg)
require.Equal(ht, 2, len(rootInvoice.Htlcs))
// Finally, we'll test that if we subscribe for notifications of
// settled invoices, we get a backlog, which includes the invoice we
// settled last (since you can only fetch from index 1 onwards), and
// only the relevant set of HTLCs.
req = &lnrpc.InvoiceSubscription{
SettleIndex: 1,
}
invSub2 := dave.RPC.SubscribeInvoices(req)
// The first invoice we get back should match the state of the invoice
// after our second payment: amt updated, but only a single HTLC shown
// through.
backlogInv := ht.ReceiveInvoiceUpdate(invSub2)
require.Equal(ht, 1, len(backlogInv.Htlcs))
require.Equal(ht, 1, len(backlogInv.AmpInvoiceState))
require.True(ht, backlogInv.Settled)
require.Equal(ht, paymentAmt*2, int(backlogInv.AmtPaidSat))
}
// testSendPaymentAMP tests that we can send an AMP payment to a specified
// destination using SendPaymentV2.
func testSendPaymentAMP(ht *lntest.HarnessTest) {
mts := newMppTestScenario(ht)
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 ____/
//
mppReq := &mppOpenChannelRequest{
amtAliceCarol: 285000,
amtAliceDave: 155000,
amtCarolBob: 200000,
amtCarolEve: 155000,
amtDaveBob: 155000,
amtEveBob: 155000,
}
mts.openChannels(mppReq)
chanPointAliceDave := mts.channelPoints[1]
// 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.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: 500_000,
FeeRateMilliMsat: int64(0.001 * 1_000_000),
TimeLockDelta: 40,
MinHtlc: 1000, // default value
MaxHtlcMsat: 133_650_000,
}
mts.dave.UpdateGlobalPolicy(expectedPolicy)
// Make sure Alice has heard it.
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false,
)
sendReq := &routerrpc.SendPaymentRequest{
Dest: mts.bob.PubKey[:],
Amt: int64(paymentAmt),
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
Amp: true,
}
payment := ht.SendPaymentAssertSettled(mts.alice, sendReq)
// 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++
}
// When an AMP record is expected, it will only be seen on the
// last hop of a route. So we make sure it isn't set on any of
// the hops except the last one
for _, hop := range htlc.Route.Hops[:len(htlc.Route.Hops)-1] {
require.Nil(ht, hop.AmpRecord)
}
lastHop := htlc.Route.Hops[len(htlc.Route.Hops)-1]
require.NotNil(ht, lastHop.AmpRecord)
}
const minExpectedShards = 3
require.GreaterOrEqual(ht, succeeded, minExpectedShards,
"expected num of shards not reached")
// Fetch Bob's invoices. There should only be one invoice.
invoices := ht.AssertNumInvoices(mts.bob, 1)
rpcInvoice := invoices[0]
// Assert that the invoice is settled for the total payment amount and
// has the correct payment address.
require.True(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State)
require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat)
require.Equal(ht, 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(ht, succeeded, len(rpcInvoice.Htlcs))
for _, htlc := range rpcInvoice.Htlcs {
require.NotNil(ht, htlc.Amp)
if setID == nil {
setID = make([]byte, 32)
copy(setID, htlc.Amp.SetId)
}
require.Equal(ht, 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(ht, err)
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
require.NoError(ht, err)
// Assert that the preimage actually matches the hashes.
validPreimage := childPreimage.Matches(childHash)
require.True(ht, validPreimage)
}
// The set ID we extract above should be shown in the final settled
// state.
ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)]
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
// Finally, close all channels.
mts.closeChannels()
}
func testSendToRouteAMP(ht *lntest.HarnessTest) {
mts := newMppTestScenario(ht)
const (
paymentAmt = btcutil.Amount(300000)
numShards = 3
shardAmt = paymentAmt / numShards
chanAmt = shardAmt * 3 / 2
)
// Subscribe to bob's invoices.
req := &lnrpc.InvoiceSubscription{}
bobInvoiceSubscription := mts.bob.RPC.SubscribeInvoices(req)
// Set up a network with three different paths Alice <-> Bob.
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
//
mppReq := &mppOpenChannelRequest{
// Since the channel Alice-> Carol will have to carry two
// shards, we make it larger.
amtAliceCarol: chanAmt + shardAmt,
amtAliceDave: chanAmt,
amtCarolBob: chanAmt,
amtCarolEve: chanAmt,
amtDaveBob: chanAmt,
amtEveBob: chanAmt,
}
mts.openChannels(mppReq)
// We'll send shards along three routes from Alice.
sendRoutes := [numShards][]*node.HarnessNode{
{mts.carol, mts.bob},
{mts.dave, mts.bob},
{mts.carol, mts.eve, mts.bob},
}
payAddr := ht.Random32Bytes()
setID := ht.Random32Bytes()
var sharer amp.Sharer
sharer, err := amp.NewSeedSharer()
require.NoError(ht, 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 []*node.HarnessNode) {
// Build a route for the specified hops.
r := mts.buildRoute(shardAmt, mts.alice, hops)
// 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(ht, 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() {
resp := mts.alice.RPC.SendToRouteV2(sendReq)
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(ht, err)
require.False(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat)
require.Equal(ht, payAddr, rpcInvoice.PaymentAddr)
require.Equal(ht, 0, len(rpcInvoice.Htlcs))
sendShard(1, sendRoutes[1])
sendShard(2, sendRoutes[2])
// Assert that all of the child preimages are unique.
require.Equal(ht, 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.
timer := time.After(defaultTimeout)
for range sendRoutes {
var resp *lnrpc.HTLCAttempt
select {
case resp = <-responses:
case <-timer:
require.Fail(ht, "response not received")
}
require.Nil(ht, resp.Failure, "received payment failure")
preimage, err := lntypes.MakePreimage(resp.Preimage)
require.NoError(ht, err)
// Assert that the response includes one of our child preimages.
_, ok := childPreimages[preimage]
require.True(ht, 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(ht, err)
// Also fetch Bob's invoice from ListInvoices and assert it is equal to
// the one received via the subscription.
invoices := ht.AssertNumInvoices(mts.bob, 1)
ht.AssertInvoiceEqual(rpcInvoice, invoices[0])
// Assert that the invoice is settled for the total payment amount and
// has the correct payment address.
require.True(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State)
require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
require.Equal(ht, 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(ht, numShards, len(rpcInvoice.Htlcs))
for _, htlc := range rpcInvoice.Htlcs {
require.NotNil(ht, htlc.Amp)
require.Equal(ht, 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(ht, err)
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
require.NoError(ht, err)
// Assert that the preimage actually matches the hashes.
validPreimage := childPreimage.Matches(childHash)
require.True(ht, validPreimage)
// Assert that the HTLC includes one of our child preimages.
childIndex, ok := childPreimages[childPreimage]
require.True(ht, ok)
// Assert that the correct child index is reflected.
require.Equal(ht, childIndex, htlc.Amp.ChildIndex)
// Remove this preimage from our set so that we ensure all HTLCs
// have a unique child preimage.
delete(childPreimages, childPreimage)
}
// Finally, close all channels.
mts.closeChannels()
}