Created
December 7, 2021 15:17
-
-
Save motorina0/9dd8309e5aa2e2da1969db4a423e0352 to your computer and use it in GitHub Desktop.
Update integration test for a multi-hop payment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package itest | |
import ( | |
"bytes" | |
"context" | |
"encoding/hex" | |
"time" | |
"github.com/btcsuite/btcd/wire" | |
"github.com/btcsuite/btcutil" | |
"github.com/davecgh/go-spew/spew" | |
"github.com/lightningnetwork/lnd/lnrpc" | |
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" | |
"github.com/lightningnetwork/lnd/lntest" | |
"github.com/lightningnetwork/lnd/lntest/wait" | |
"github.com/lightningnetwork/lnd/lntypes" | |
"github.com/lightningnetwork/lnd/lnwire" | |
"github.com/lightningnetwork/lnd/record" | |
"github.com/stretchr/testify/require" | |
) | |
func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) { | |
ctxb := context.Background() | |
// Open a channel with 100k satoshis between Alice and Bob with Alice being | |
// the sole funder of the channel. | |
chanAmt := btcutil.Amount(100000) | |
chanPoint := openChannelAndAssert( | |
t, net, net.Alice, net.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 := bytes.Repeat([]byte("A"), 32) | |
invoice := &lnrpc.Invoice{ | |
Memo: "testing", | |
RPreimage: preimage, | |
Value: paymentAmt, | |
} | |
invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice) | |
if err != nil { | |
t.Fatalf("unable to add invoice: %v", err) | |
} | |
// Wait for Alice to recognize and advertise the new channel generated | |
// above. | |
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) | |
err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) | |
if err != nil { | |
t.Fatalf("alice didn't advertise channel before "+ | |
"timeout: %v", err) | |
} | |
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) | |
if err != nil { | |
t.Fatalf("bob didn't advertise channel before "+ | |
"timeout: %v", err) | |
} | |
// With the invoice for Bob added, send a payment towards Alice paying | |
// to the above generated invoice. | |
resp := sendAndAssertSuccess( | |
t, net.Alice, &routerrpc.SendPaymentRequest{ | |
PaymentRequest: invoiceResp.PaymentRequest, | |
TimeoutSeconds: 60, | |
FeeLimitMsat: noFeeLimitMsat, | |
}, | |
) | |
if hex.EncodeToString(preimage) != resp.PaymentPreimage { | |
t.Fatalf("preimage mismatch: expected %v, got %v", preimage, | |
resp.PaymentPreimage) | |
} | |
// Bob's invoice should now be found and marked as settled. | |
payHash := &lnrpc.PaymentHash{ | |
RHash: invoiceResp.RHash, | |
} | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
dbInvoice, err := net.Bob.LookupInvoice(ctxt, payHash) | |
if err != nil { | |
t.Fatalf("unable to lookup invoice: %v", err) | |
} | |
if !dbInvoice.Settled { // nolint:staticcheck | |
t.Fatalf("bob's invoice should be marked as settled: %v", | |
spew.Sdump(dbInvoice)) | |
} | |
// With the payment completed all balance related stats should be | |
// properly updated. | |
err = wait.NoError( | |
assertAmountSent(paymentAmt, net.Alice, net.Bob), | |
3*time.Second, | |
) | |
if err != nil { | |
t.Fatalf(err.Error()) | |
} | |
// Create another invoice for Bob, this time leaving off the preimage | |
// to one will be randomly generated. We'll test the proper | |
// encoding/decoding of the zpay32 payment requests. | |
invoice = &lnrpc.Invoice{ | |
Memo: "test3", | |
Value: paymentAmt, | |
} | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice) | |
if err != nil { | |
t.Fatalf("unable to add invoice: %v", err) | |
} | |
// Next send another payment, but this time using a zpay32 encoded | |
// invoice rather than manually specifying the payment details. | |
sendAndAssertSuccess( | |
t, net.Alice, &routerrpc.SendPaymentRequest{ | |
PaymentRequest: invoiceResp.PaymentRequest, | |
TimeoutSeconds: 60, | |
FeeLimitMsat: noFeeLimitMsat, | |
}, | |
) | |
// The second payment should also have succeeded, with the balances | |
// being update accordingly. | |
err = wait.NoError( | |
assertAmountSent(2*paymentAmt, net.Alice, net.Bob), | |
3*time.Second, | |
) | |
if err != nil { | |
t.Fatalf(err.Error()) | |
} | |
// Next send a keysend payment. | |
keySendPreimage := lntypes.Preimage{3, 4, 5, 11} | |
keySendHash := keySendPreimage.Hash() | |
sendAndAssertSuccess( | |
t, net.Alice, &routerrpc.SendPaymentRequest{ | |
Dest: net.Bob.PubKey[:], | |
Amt: paymentAmt, | |
FinalCltvDelta: 40, | |
PaymentHash: keySendHash[:], | |
DestCustomRecords: map[uint64][]byte{ | |
record.KeySendType: keySendPreimage[:], | |
}, | |
TimeoutSeconds: 60, | |
FeeLimitMsat: noFeeLimitMsat, | |
}, | |
) | |
// The keysend payment should also have succeeded, with the balances | |
// being update accordingly. | |
err = wait.NoError( | |
assertAmountSent(3*paymentAmt, net.Alice, net.Bob), | |
3*time.Second, | |
) | |
if err != nil { | |
t.Fatalf(err.Error()) | |
} | |
// Assert that the invoice has the proper AMP fields set, since the | |
// legacy keysend payment should have been promoted into an AMP payment | |
// internally. | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
keysendInvoice, err := net.Bob.LookupInvoice( | |
ctxt, &lnrpc.PaymentHash{ | |
RHash: keySendHash[:], | |
}, | |
) | |
require.NoError(t.t, err) | |
require.Equal(t.t, 1, len(keysendInvoice.Htlcs)) | |
htlc := keysendInvoice.Htlcs[0] | |
require.Equal(t.t, uint64(0), htlc.MppTotalAmtMsat) | |
require.Nil(t.t, htlc.Amp) | |
// Now create an invoice and specify routing hints. | |
// We will test that the routing hints are encoded properly. | |
hintChannel := lnwire.ShortChannelID{BlockHeight: 10} | |
bobPubKey := hex.EncodeToString(net.Bob.PubKey[:]) | |
hints := []*lnrpc.RouteHint{ | |
{ | |
HopHints: []*lnrpc.HopHint{ | |
{ | |
NodeId: bobPubKey, | |
ChanId: hintChannel.ToUint64(), | |
FeeBaseMsat: 1, | |
FeeProportionalMillionths: 1000000, | |
CltvExpiryDelta: 20, | |
}, | |
}, | |
}, | |
} | |
invoice = &lnrpc.Invoice{ | |
Memo: "hints", | |
Value: paymentAmt, | |
RouteHints: hints, | |
} | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice) | |
if err != nil { | |
t.Fatalf("unable to add invoice: %v", err) | |
} | |
payreq, err := net.Bob.DecodePayReq(ctxt, &lnrpc.PayReqString{PayReq: invoiceResp.PaymentRequest}) | |
if err != nil { | |
t.Fatalf("failed to decode payment request %v", err) | |
} | |
if len(payreq.RouteHints) != 1 { | |
t.Fatalf("expected one routing hint") | |
} | |
routingHint := payreq.RouteHints[0] | |
if len(routingHint.HopHints) != 1 { | |
t.Fatalf("expected one hop hint") | |
} | |
hopHint := routingHint.HopHints[0] | |
if hopHint.FeeProportionalMillionths != 1000000 { | |
t.Fatalf("wrong FeeProportionalMillionths %v", | |
hopHint.FeeProportionalMillionths) | |
} | |
if hopHint.NodeId != bobPubKey { | |
t.Fatalf("wrong NodeId %v", | |
hopHint.NodeId) | |
} | |
if hopHint.ChanId != hintChannel.ToUint64() { | |
t.Fatalf("wrong ChanId %v", | |
hopHint.ChanId) | |
} | |
if hopHint.FeeBaseMsat != 1 { | |
t.Fatalf("wrong FeeBaseMsat %v", | |
hopHint.FeeBaseMsat) | |
} | |
if hopHint.CltvExpiryDelta != 20 { | |
t.Fatalf("wrong CltvExpiryDelta %v", | |
hopHint.CltvExpiryDelta) | |
} | |
////////////////\\\\\\\\\\\\\\\\\\\\\ | |
defer closeChannelAndAssert(t, net, net.Alice, chanPoint, false) | |
// Create a new node (Carol) and connect Bob to it | |
carol := net.NewNode(t.t, "Carol", nil) | |
defer shutdownAndAssert(net, t, carol) | |
net.ConnectNodes(t.t, net.Bob, carol) | |
// Open a channel with 200k satoshis between Bob and Carol with Bob being | |
// the sole funder of the channel. | |
secondChanAmt := btcutil.Amount(200000) | |
secondChanPoint := openChannelAndAssert( | |
t, net, net.Bob, carol, | |
lntest.OpenChannelParams{ | |
Amt: secondChanAmt, | |
}, | |
) | |
defer closeChannelAndAssert(t, net, net.Bob, secondChanPoint, false) | |
// Set deterministic BaseFee and FeeRate for the channel. | |
// The test must not be affected if the default values change. | |
const baseFeeMsat = 5000 | |
const feeRate = 0.01 | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
if _, err = net.Bob.UpdateChannelPolicy( | |
ctxt, | |
&lnrpc.PolicyUpdateRequest{ | |
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ | |
ChanPoint: secondChanPoint, | |
}, | |
BaseFeeMsat: baseFeeMsat, | |
FeeRate: feeRate, | |
TimeLockDelta: 40, | |
}, | |
); err != nil { | |
t.Fatalf("unable to update chan policy: %v", err) | |
} | |
// Wait for Carol and Bob to recognize and advertise the new | |
// channel generated above. | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
err = carol.WaitForNetworkChannelOpen(ctxt, secondChanPoint) | |
if err != nil { | |
t.Fatalf("carol didn't advertise channel before "+ | |
"timeout: %v", err) | |
} | |
err = net.Bob.WaitForNetworkChannelOpen(ctxt, secondChanPoint) | |
if err != nil { | |
t.Fatalf("bob didn't advertise channel with carol before "+ | |
"timeout: %v", err) | |
} | |
// Now that the channel is open, create an invoice for Carol which | |
// expects a payment of 1700 satoshis from Alice paid via a particular | |
// preimage. | |
const secondPaymentAmt = 1700 | |
secondPreimage := bytes.Repeat([]byte("C"), 32) | |
secondInvoice := &lnrpc.Invoice{ | |
Memo: "testing multi-hop payment", | |
RPreimage: secondPreimage, | |
Value: secondPaymentAmt, | |
} | |
secondInvoiceResp, err := carol.AddInvoice(ctxb, secondInvoice) | |
if err != nil { | |
t.Fatalf("unable to add invoice for carol: %v", err) | |
} | |
// With the invoice for Carol added, now Alice can make the payment. | |
resp = sendAndAssertSuccess( | |
t, net.Alice, &routerrpc.SendPaymentRequest{ | |
PaymentRequest: secondInvoiceResp.PaymentRequest, | |
TimeoutSeconds: 60, | |
FeeLimitMsat: noFeeLimitMsat, | |
}, | |
) | |
if hex.EncodeToString(secondPreimage) != resp.PaymentPreimage { | |
t.Fatalf("preimage mismatch: expected %v, got %v", secondPreimage, | |
resp.PaymentPreimage) | |
} | |
const secondPaymentFee = secondPaymentAmt*feeRate + baseFeeMsat/1000 | |
if resp.FeeSat != secondPaymentFee { | |
t.Fatalf("fee mismatch: expected %v, got %v", secondPaymentFee, | |
resp.FeeSat) | |
} | |
// Carol's invoice should now be found and marked as settled. | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
dbSecondInvoice, err := carol.LookupInvoice(ctxt, &lnrpc.PaymentHash{ | |
RHash: secondInvoiceResp.RHash, | |
}) | |
if err != nil { | |
t.Fatalf("unable to lookup invoice for carol: %v", err) | |
} | |
if dbSecondInvoice.State != lnrpc.Invoice_SETTLED { | |
t.Fatalf("carol's invoice should be marked as settled: %v", | |
spew.Sdump(dbSecondInvoice)) | |
} | |
// With the payment completed all balance related stats should be | |
// properly updated. | |
aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPoint) | |
if err != nil { | |
t.Fatalf("unable to get funding tx for alice: %v", err) | |
} | |
bobChanTXID, err := lnrpc.GetChanPointFundingTxid(secondChanPoint) | |
if err != nil { | |
t.Fatalf("unable to get funding tx for bob: %v", err) | |
} | |
aliceFundPoint := wire.OutPoint{ | |
Hash: *aliceChanTXID, | |
Index: chanPoint.OutputIndex, | |
} | |
bobFundPoint := wire.OutPoint{ | |
Hash: *bobChanTXID, | |
Index: secondChanPoint.OutputIndex, | |
} | |
// Note: `assertAmountSent()` - assumes that each node only has one channel. | |
// Using `assertAmountPaid()` instead. | |
const alicePrevPaymentsAmt = 3 * paymentAmt | |
const aliceTotalPaymentsAmt = alicePrevPaymentsAmt + secondPaymentAmt + secondPaymentFee | |
assertAmountPaid(t, "Alice sent to Bob", net.Alice, | |
aliceFundPoint, aliceTotalPaymentsAmt, int64(0)) | |
assertAmountPaid(t, "Bob received from Alice", net.Bob, | |
aliceFundPoint, int64(0), aliceTotalPaymentsAmt) | |
assertAmountPaid(t, "Bob sent to Carol", net.Bob, | |
bobFundPoint, secondPaymentAmt, int64(0)) | |
assertAmountPaid(t, "Carol received from Bob", carol, | |
bobFundPoint, int64(0), secondPaymentAmt) | |
// Make sure that Carol's channel balance is updated | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
carolChanBalResp, err := carol.ChannelBalance(ctxt, &lnrpc.ChannelBalanceRequest{}) | |
if err != nil { | |
t.Fatalf("unable to get carol's balance: %v", err) | |
} | |
if carolChanBalResp.LocalBalance.Sat != secondPaymentAmt { | |
t.Fatalf("wrong channel balance. Expected %v but got %v", carolChanBalResp.LocalBalance.Sat, secondPaymentAmt) | |
} | |
// Check that Bob has correctly recorded the forwarded payment | |
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) | |
bobForwardingHistory, err := net.Bob.ForwardingHistory( | |
ctxt, &lnrpc.ForwardingHistoryRequest{}, | |
) | |
if err != nil { | |
t.Fatalf("unable to query bob's forwarding history: %v", err) | |
} | |
if len(bobForwardingHistory.ForwardingEvents) != 1 { | |
t.Fatalf("expected 1 forwarded payment but got: %v", len(bobForwardingHistory.ForwardingEvents)) | |
} | |
forwardEvt := bobForwardingHistory.ForwardingEvents[0] | |
if forwardEvt.Fee != secondPaymentFee { | |
t.Fatalf("wrong forward fee: %v", forwardEvt.Fee) | |
} | |
if forwardEvt.AmtIn != secondPaymentAmt+secondPaymentFee { | |
t.Fatalf("wrong forward amount in: %v", forwardEvt.AmtIn) | |
} | |
if forwardEvt.AmtOut != secondPaymentAmt { | |
t.Fatalf("wrong forward amount out: %v", forwardEvt.AmtOut) | |
} | |
} |
Note:
- the updated code starts at line
240
- the full file content has been provided because a new import has been added
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The test logs no data, except the execution state (
PASS
):