Skip to content

Instantly share code, notes, and snippets.

@shivanshuraj1333
Last active July 2, 2021 19:15
Show Gist options
  • Save shivanshuraj1333/dcfd95ff7bd0385f02f765bba99d4e7a to your computer and use it in GitHub Desktop.
Save shivanshuraj1333/dcfd95ff7bd0385f02f765bba99d4e7a to your computer and use it in GitHub Desktop.
Solution for Chaincode Labs LN Seminar : 1) LND

This gist contains the solution for Chaincode Labs LN Seminar task 1. LND Note: The updated test file lnd_single_hop_invoice_test.go is added below.

Problem Statement:

In https://github.com/lightningnetwork/lnd/blob/6d661334599ffa2a409ad6b0942328f9fd213d09/lntest/itest/lnd_single_hop_invoice_test.go#L24-L33 Alice sends payment to Bob. Add a new node (Carol), open a channel from Bob to Carol and then send a payment from Alice to Bob to Carol. Create a gist and please include the modifications you made and an output of the test you created.

Steps to run the test:

  1. If LND repository is not present in your local system run git clone https://github.com/lightningnetwork/lnd
  2. Make sure you have go version = 1.6 on your machine
  3. Navigate to lnd and build a development version on your machine cd lnd && make install
  4. From root of lnd navigate to /lnd/lntest/itest
  5. update the file lnd_single_hop_invoice_test.go using the one which is present in the gist
  6. to test the updated test file run make itest icase=single_hop_invoice log=stdout from root directory of the project (lnd)

Important Note: Checkout gist commit history to track the new changes to lnd_single_hop_invoice_test.go file

Explanation of the approach used: All the modifications in the function testSingleHopInvoice in lnd_single_hop_invoice_test.go are explained below:

Initially a fresh new Node named Carol is created and channel between Bob and Carol is opened. Once the channel is opened, and a connection between Bob and Carol is established, a Invoice for Carol expecting a payment of 1000 Satoshis from Bob via a particular preimage is created. After waiting for Bob and Carol to recognise and advertise the new channel generated between them a payment request to Bob paying to the above generated invoice is sent and performed the assertion that the payment completes successfully. After that settled the Carol's invoice and checked the updated balance related stats using assertAmountSent(paymentAmt, net.Bob, Carol) to check the transfer from Bob -> Carol and 'assertAmountSent(paymentAmt, net.Alice, Carol)' to check the complete transfer for Alive -> Bob -> Carol. Continuing with the above approach another transaction of 'Alice -> Bob -> Carol' is made using zpay32 encoded invoice and keysend payment method. The following diagram shows this in more clear manner.

It is to be noted that the following cheks are used at the end of transation 1, 2 and 3 to verify the complete transaction from Alice to Carol. As first transaction contains over 1k satoshis transfer, 2nd contains overall 2k satoshis transfer (includeing transcation 1 & 2) and 3rd contrains overall 3k satoshis transfer (includeing transaction 1, 2 , and 3) from Alice to Carol through Bob.

For transaction 1:

	err = wait.NoError(
		assertAmountSent(paymentAmt, net.Alice, Carol),
		3*time.Second,
	)

For transaction 2:

	err = wait.NoError(
		assertAmountSent(2*paymentAmt, net.Alice, Carol),
		3*time.Second,
	)

For transaction 3:

	err = wait.NoError(
		assertAmountSent(3*paymentAmt, net.Alice, Carol),
		3*time.Second,
	)
package itest
import (
"bytes"
"context"
"encoding/hex"
"time"
"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.
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
chanAmt := btcutil.Amount(100000)
chanPoint := openChannelAndAssert(
ctxt, 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.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp := sendAndAssertSuccess(
ctxt, 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(ctxt, 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())
}
// Till now alice -> bob channel opening, sending payment
// and generating invoice is tested properly, now opening the channel from Bob to
// Carol and in the similar fashion sending payment and
// generating invoice.
// Creating Carol and opening a channel from
// Bob to Carol (bob -> carol)
Carol := net.NewNode(t.t, "Carol", nil)
defer shutdownAndAssert(net, t, Carol)
// establishing a connection
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
net.ConnectNodes(ctxt, t.t, net.Bob, Carol)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPointCarol := openChannelAndAssert(
ctxt, t, net, net.Bob, Carol,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// After opening a channel, creating an invoice for Carol which
// expects a payment of 1000 satoshis from Bob paid via a particular
// preimage, using the same paymentAmt which is used in transaction
// between Alice and Bob
invoiceCarol := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
invoiceRespCarol, err := Carol.AddInvoice(ctxb, invoiceCarol)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
// Wait for Bob and Carol to recognize and advertise the new channel generated
// between Bob and Carol
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointCarol)
if err != nil {
t.Fatalf("bob didn't advertise channel before "+
"timeout: %v", err)
}
err = Carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol)
if err != nil {
t.Fatalf("carol didn't advertise channel before "+
"timeout: %v", err)
}
// With the invoice for Carol added, send a payment request to Bob paying
// to the above generated invoice and assert that the
// payment completes successfully.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
respCarol := sendAndAssertSuccess(
ctxt, t, net.Bob,
&routerrpc.SendPaymentRequest{
PaymentRequest: invoiceRespCarol.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
if hex.EncodeToString(preimage) != respCarol.PaymentPreimage {
t.Fatalf("preimage mismatch: expected %v, got %v", preimage,
respCarol.PaymentPreimage)
}
// Carols's invoice should now be found and marked as settled.
payHash = &lnrpc.PaymentHash{
RHash: invoiceRespCarol.RHash,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
dbInvoice, err = Carol.LookupInvoice(ctxt, payHash)
if err != nil {
t.Fatalf("unable to lookup invoice: %v", err)
}
if !dbInvoice.Settled {
t.Fatalf("carol'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.Bob, Carol),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
// Checking the transfer of total 1000 Satoshis following the transaction history as
// Alice -> Bob -> Carol, i.e all the 1000 Satoshis are sent from Alice to Carol via Bob
err = wait.NoError(
assertAmountSent(paymentAmt, net.Alice, Carol),
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(ctxt, 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.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
sendAndAssertSuccess(
ctxt, t, net.Alice,
&routerrpc.SendPaymentRequest{
PaymentRequest: invoiceResp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
// As done in the above test for transaction between alice and bob,
// creating another invoice for Carol, this time leaving off the preimage
// to one will be randomly generated. Testing the proper
// encoding/decoding of the zpay32 payment requests.
invoiceCarol = &lnrpc.Invoice{
Memo: "test3",
Value: paymentAmt,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoiceRespCarol, err = Carol.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.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
sendAndAssertSuccess(
ctxt, t, net.Bob,
&routerrpc.SendPaymentRequest{
PaymentRequest: invoiceRespCarol.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.Bob, Carol),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
// Checking the transfer of total 2000 Satoshis following the transaction history as
// Alice -> Bob -> Carol, i.e all the 2000 Satoshis are being sent from Alice to Carol via Bob
err = wait.NoError(
assertAmountSent(2*paymentAmt, net.Alice, Carol),
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()
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
sendAndAssertSuccess(
ctxt, 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,
},
)
// Similarly, sending a keysend payment.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
sendAndAssertSuccess(
ctxt, t, net.Bob,
&routerrpc.SendPaymentRequest{
Dest: Carol.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.Bob, Carol),
3*time.Second,
)
if err != nil {
t.Fatalf(err.Error())
}
// Checking the transfer of total 3000 Satoshis following the transaction history as
// Alice -> Bob -> Carol, i.e all the 3000 Satoshis are being sent from Alice to Carol via Bob
err = wait.NoError(
assertAmountSent(3*paymentAmt, net.Alice, Carol),
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(ctxt, 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)
}
// In the same way, asserting 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 = Carol.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}
carolPubKey := hex.EncodeToString(Carol.PubKey[:])
hints = []*lnrpc.RouteHint{
{
HopHints: []*lnrpc.HopHint{
{
NodeId: carolPubKey,
ChanId: hintChannel.ToUint64(),
FeeBaseMsat: 1,
FeeProportionalMillionths: 1000000,
CltvExpiryDelta: 20,
},
},
},
}
invoice = &lnrpc.Invoice{
Memo: "hints",
Value: paymentAmt,
RouteHints: hints,
}
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
invoiceRespCarol, err = Carol.AddInvoice(ctxt, invoice)
if err != nil {
t.Fatalf("unable to add invoice: %v", err)
}
payreq, err = Carol.DecodePayReq(ctxt, &lnrpc.PayReqString{PayReq: invoiceRespCarol.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 != carolPubKey {
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)
}
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointCarol, false)
}
Building itest btcd and lnd.
CGO_ENABLED=0 GO111MODULE=on go build -v -tags="rpctest" -o lntest/itest/btcd-itest -ldflags " -X github.com/lightningnetwork/lnd/build.Commit=v0.13.0-beta-98-g93a2e267-dirty -X github.com/lightningnetwork/lnd/build.CommitHash=93a2e26763fc5eb8fd24758447f89a541672dcbe -X github.com/lightningnetwork/lnd/build.GoVersion=go1.16.5 -X github.com/lightningnetwork/lnd/build.RawTags=dev,autopilotrpc,chainrpc,invoicesrpc,routerrpc,signrpc,verrpc,walletrpc,watchtowerrpc,wtclientrpc,rpctest,btcd" github.com/btcsuite/btcd
CGO_ENABLED=0 GO111MODULE=on go build -v -tags="dev autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc rpctest btcd" -o lntest/itest/lnd-itest -ldflags " -X github.com/lightningnetwork/lnd/build.Commit=v0.13.0-beta-98-g93a2e267-dirty -X github.com/lightningnetwork/lnd/build.CommitHash=93a2e26763fc5eb8fd24758447f89a541672dcbe -X github.com/lightningnetwork/lnd/build.GoVersion=go1.16.5 -X github.com/lightningnetwork/lnd/build.RawTags=dev,autopilotrpc,chainrpc,invoicesrpc,routerrpc,signrpc,verrpc,walletrpc,watchtowerrpc,wtclientrpc,rpctest,btcd" github.com/lightningnetwork/lnd/cmd/lnd
Building itest binary for btcd backend.
CGO_ENABLED=0 GO111MODULE=on go test -v ./lntest/itest -tags="dev autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc rpctest btcd" -c -o lntest/itest/itest.test
Running integration tests with btcd backend.
rm -rf lntest/itest/*.log lntest/itest/.logs-*; date
Sat Jul 3 00:30:42 IST 2021
EXEC_SUFFIX= scripts/itest_part.sh 0 1 -test.run="TestLightningNetworkDaemon/.*-of-.*/.*/single_hop_invoice" -test.timeout=60m
/Users/shivashr/bitcoin/lnd/lntest/itest/itest.test -test.v -test.run=TestLightningNetworkDaemon/.*-of-.*/.*/single_hop_invoice -test.timeout=60m -logoutput -goroutinedump -logdir=.logs-tranche0 -lndexec=/Users/shivashr/bitcoin/lnd/lntest/itest/lnd-itest -btcdexec=/Users/shivashr/bitcoin/lnd/lntest/itest/btcd-itest -splittranches=1 -runtranche=0
=== RUN TestLightningNetworkDaemon
=== RUN TestLightningNetworkDaemon/15-of-82/btcd/single_hop_invoice
--- PASS: TestLightningNetworkDaemon (43.09s)
--- PASS: TestLightningNetworkDaemon/15-of-82/btcd/single_hop_invoice (41.29s)
PASS
lntest/itest/log_check_errors.sh
No itest errors detected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment