diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 4a9b2baec..5fb0b6851 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -511,4 +511,8 @@ var allTestCases = []*lntest.TestCase{ Name: "async bidirectional payments", TestFunc: testBidirectionalAsyncPayments, }, + { + Name: "lookup htlc", + TestFunc: testLookupHTLC, + }, } diff --git a/itest/lnd_htlc_test.go b/itest/lnd_htlc_test.go new file mode 100644 index 000000000..393d75f5e --- /dev/null +++ b/itest/lnd_htlc_test.go @@ -0,0 +1,71 @@ +package itest + +import ( + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// testLookupHTLC checks that `LookupHtlc` returns the correct HTLC +// information. +func testLookupHTLC(ht *lntest.HarnessTest) { + const chanAmt = btcutil.Amount(1000000) + + alice := ht.Alice + carol := ht.NewNode("Carol", []string{ + "--store-final-htlc-resolutions", + }) + ht.EnsureConnected(alice, carol) + + // Open a channel between Alice and Carol. + cp := ht.OpenChannel( + alice, carol, lntest.OpenChannelParams{Amt: chanAmt}, + ) + + // Channel should be ready for payments. + const payAmt = 100 + + // Create an invoice. + invoice := &lnrpc.Invoice{ + Memo: "alice to carol htlc lookup", + RPreimage: ht.Random32Bytes(), + Value: payAmt, + } + + // Carol adds the invoice to her database. + resp := carol.RPC.AddInvoice(invoice) + + // Subscribe the invoice. + stream := carol.RPC.SubscribeSingleInvoice(resp.RHash) + + // Alice pays Carol's invoice. + ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest}) + + // Carol waits until the invoice is settled. + ht.AssertInvoiceState(stream, lnrpc.Invoice_SETTLED) + + // Get the channel using the assert function. + // + // TODO(yy): make `ht.OpenChannel` return lnrpc.Channel instead of + // lnrpc.ChannelPoint. + channel := ht.AssertChannelExists(carol, cp) + + // Lookup the HTLC and assert the htlc is settled offchain. + req := &lnrpc.LookupHtlcRequest{ + ChanId: channel.ChanId, + HtlcIndex: 0, + } + + // Check that Alice will get an error from LookupHtlc. + err := alice.RPC.LookupHtlcAssertErr(req) + gErr := status.Convert(err) + require.Equal(ht, codes.Unavailable, gErr.Code()) + + // Check that Carol can get the final htlc info. + finalHTLC := carol.RPC.LookupHtlc(req) + require.True(ht, finalHTLC.Settled, "htlc should be settled") + require.True(ht, finalHTLC.Offchain, "htlc should be Offchain") +} diff --git a/lntest/rpc/lnd.go b/lntest/rpc/lnd.go index 92cca37c1..87e745b98 100644 --- a/lntest/rpc/lnd.go +++ b/lntest/rpc/lnd.go @@ -696,3 +696,29 @@ func (h *HarnessRPC) GetChanInfo( return resp } + +// LookupHtlc makes a RPC call to the node's LookupHtlc and returns the +// response. +func (h *HarnessRPC) LookupHtlc( + req *lnrpc.LookupHtlcRequest) *lnrpc.LookupHtlcResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.LookupHtlc(ctxt, req) + h.NoError(err, "LookupHtlc") + + return resp +} + +// LookupHtlcAssertErr makes a RPC call to the node's LookupHtlc and asserts +// an RPC error is returned. +func (h *HarnessRPC) LookupHtlcAssertErr(req *lnrpc.LookupHtlcRequest) error { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.LN.LookupHtlc(ctxt, req) + require.Error(h, err, "expected an error") + + return err +}