diff --git a/cln_interceptor.go b/cln_interceptor.go index b29fcbc..ffadb47 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -168,7 +168,7 @@ func (i *ClnHtlcInterceptor) intercept() error { interceptorClient.Send(i.defaultResolution(request)) i.doneWg.Done() } - interceptResult := intercept(i.client, i.config, nextHop, paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) + interceptResult := intercept(i.client, i.config, nextHop, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) diff --git a/intercept.go b/intercept.go index 14a9305..a8ecc75 100644 --- a/intercept.go +++ b/intercept.go @@ -45,7 +45,7 @@ type interceptResult struct { onionBlob []byte } -func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { +func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) @@ -66,6 +66,13 @@ func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPa if channelPoint == nil { if bytes.Equal(paymentHash, reqPaymentHash) { + if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(config.TimeLockDelta) { + return interceptResult{ + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + channelPoint, err = openChannel(client, config, reqPaymentHash, destination, incomingAmountMsat) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) diff --git a/itest/cltv_test.go b/itest/cltv_test.go new file mode 100644 index 0000000..7bc9eaf --- /dev/null +++ b/itest/cltv_test.go @@ -0,0 +1,58 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testInvalidCltv(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(lspAmountMsat), + }) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + + // Decrement the delay in the first hop, so the cltv delta will become 143 (too little) + route.Hops[0].Delay-- + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") +} diff --git a/itest/lspd_node.go b/itest/lspd_node.go index df1fc15..0b47bd0 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -30,7 +30,7 @@ var ( var ( lspBaseFeeMsat uint32 = 1000 lspFeeRatePpm uint32 = 1 - lspCltvDelta uint16 = 40 + lspCltvDelta uint16 = 144 ) type LspNode interface { diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 4c69ad4..b3220f9 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -97,4 +97,8 @@ var allTestCases = []*testCase{ name: "testProbing", test: testProbing, }, + { + name: "testInvalidCltv", + test: testInvalidCltv, + }, } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 5e0f812..c474e93 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -148,7 +148,7 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { - interceptResult := intercept(i.client, i.config, nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) + interceptResult := intercept(i.client, i.config, nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{