Skip to content

Instantly share code, notes, and snippets.

@motorina0
Created December 7, 2021 15:17
Show Gist options
  • Save motorina0/9dd8309e5aa2e2da1969db4a423e0352 to your computer and use it in GitHub Desktop.
Save motorina0/9dd8309e5aa2e2da1969db4a423e0352 to your computer and use it in GitHub Desktop.
Update integration test for a multi-hop payment
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)
}
}
@motorina0
Copy link
Author

The test logs no data, except the execution state (PASS):

=== RUN   TestLightningNetworkDaemon
=== RUN   TestLightningNetworkDaemon/tranche00/16-of-90/btcd/single_hop_invoice
--- PASS: TestLightningNetworkDaemon (39.38s)
    --- PASS: TestLightningNetworkDaemon/tranche00/16-of-90/btcd/single_hop_invoice (38.03s)
PASS

@motorina0
Copy link
Author

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