Skip to content

Commit

Permalink
lntest: add AMP itest
Browse files Browse the repository at this point in the history
  • Loading branch information
cfromknecht committed Apr 7, 2021
1 parent 888af51 commit 730b718
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 4 deletions.
12 changes: 8 additions & 4 deletions lnrpc/invoicesrpc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package invoicesrpc

import (
"encoding/hex"
"errors"
"fmt"

"github.com/btcsuite/btcd/btcec"
Expand All @@ -24,7 +23,7 @@ func decodePayReq(invoice *channeldb.Invoice,
if paymentRequest == "" {
preimage := invoice.Terms.PaymentPreimage
if preimage == nil {
return nil, errors.New("cannot reconstruct pay req")
return &zpay32.Invoice{}, nil
}
hash := [32]byte(preimage.Hash())
return &zpay32.Invoice{
Expand All @@ -51,6 +50,11 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
return nil, err
}

var rHash []byte
if decoded.PaymentHash != nil {
rHash = decoded.PaymentHash[:]
}

var descHash []byte
if decoded.DescriptionHash != nil {
descHash = decoded.DescriptionHash[:]
Expand Down Expand Up @@ -144,8 +148,8 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
}

rpcInvoice := &lnrpc.Invoice{
Memo: string(invoice.Memo[:]),
RHash: decoded.PaymentHash[:],
Memo: string(invoice.Memo),
RHash: rHash,
Value: int64(satAmt),
ValueMsat: int64(invoice.Terms.Value),
CreationDate: invoice.CreationDate.Unix(),
Expand Down
208 changes: 208 additions & 0 deletions lntest/itest/lnd_amp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package itest

import (
"context"
"crypto/rand"
"time"

"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/amp"
"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"
)

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()

// 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))
for i, hops := range sendRoutes {
// Build a route for the specified hops.
r, err := ctx.buildRoute(ctxb, shardAmt, net.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 := net.Alice.RouterClient.SendToRouteV2(ctxt, sendReq)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}

responses <- resp
}()
}

// 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

// Fetch Bob's invoices.
invoiceResp, err := net.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)
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)
}
}
4 changes: 4 additions & 0 deletions lntest/itest/lnd_test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ var allTestCases = []*testCase{
name: "sendtoroute multi path payment",
test: testSendToRouteMultiPath,
},
{
name: "sendtoroute amp",
test: testSendToRouteAMP,
},
{
name: "send multi path payment",
test: testSendMultiPathPayment,
Expand Down

0 comments on commit 730b718

Please sign in to comment.