diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index 9fdafcef..93300982 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -2,7 +2,6 @@ package invoicesrpc import ( "encoding/hex" - "errors" "fmt" "github.com/btcsuite/btcd/btcec" @@ -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{ @@ -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[:] @@ -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(), diff --git a/lntest/itest/lnd_amp_test.go b/lntest/itest/lnd_amp_test.go new file mode 100644 index 00000000..d6c3b89a --- /dev/null +++ b/lntest/itest/lnd_amp_test.go @@ -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) + } +} diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 216851e2..7f630c0d 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -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,