From 4cdb5e1c947cd5169d4a9b138fb065becc115fce Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 4 Sep 2023 10:13:46 +0200 Subject: [PATCH] make intercept method shareable with lsps2 --- cln/cln_interceptor.go | 38 +++-- .../{intercept.go => intercept_handler.go} | 156 +++++++----------- lnd/interceptor.go | 31 ++-- main.go | 5 +- shared/combined_handler.go | 27 +++ shared/intercept_handler.go | 65 ++++++++ 6 files changed, 202 insertions(+), 120 deletions(-) rename interceptor/{intercept.go => intercept_handler.go} (73%) create mode 100644 shared/combined_handler.go create mode 100644 shared/intercept_handler.go diff --git a/cln/cln_interceptor.go b/cln/cln_interceptor.go index f853b96..7514248 100644 --- a/cln/cln_interceptor.go +++ b/cln/cln_interceptor.go @@ -14,6 +14,7 @@ import ( "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" + "github.com/breez/lspd/shared" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -147,15 +148,23 @@ func (i *ClnHtlcInterceptor) intercept() error { return } - interceptResult := i.interceptor.Intercept(scid, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) + interceptResult := i.interceptor.Intercept(shared.InterceptRequest{ + Identifier: request.Onion.SharedSecret, + Scid: *scid, + PaymentHash: paymentHash, + IncomingAmountMsat: request.Htlc.AmountMsat, + OutgoingAmountMsat: request.Onion.ForwardMsat, + IncomingExpiry: request.Htlc.CltvExpiry, + OutgoingExpiry: request.Onion.OutgoingCltvValue, + }) switch interceptResult.Action { - case interceptor.INTERCEPT_RESUME_WITH_ONION: + case shared.INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) - case interceptor.INTERCEPT_FAIL_HTLC_WITH_CODE: + case shared.INTERCEPT_FAIL_HTLC_WITH_CODE: interceptorClient.Send( i.failWithCode(request, interceptResult.FailureCode), ) - case interceptor.INTERCEPT_RESUME: + case shared.INTERCEPT_RESUME: fallthrough default: interceptorClient.Send( @@ -187,17 +196,17 @@ func (i *ClnHtlcInterceptor) WaitStarted() { i.initWg.Wait() } -func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult interceptor.InterceptResult) *proto.HtlcResolution { +func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult shared.InterceptResult) *proto.HtlcResolution { //decoding and encoding onion with alias in type 6 record. payload, err := hex.DecodeString(request.Onion.Payload) if err != nil { log.Printf("resumeWithOnion: hex.DecodeString(%v) error: %v", request.Onion.Payload, err) - return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) + return i.failWithCode(request, shared.FAILURE_TEMPORARY_CHANNEL_FAILURE) } - newPayload, err := encodePayloadWithNextHop(payload, interceptResult.ChannelId, interceptResult.AmountMsat) + newPayload, err := encodePayloadWithNextHop(payload, interceptResult.Scid, interceptResult.AmountMsat) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) - return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) + return i.failWithCode(request, shared.FAILURE_TEMPORARY_CHANNEL_FAILURE) } newPayloadStr := hex.EncodeToString(newPayload) @@ -224,7 +233,7 @@ func (i *ClnHtlcInterceptor) defaultResolution(request *proto.HtlcAccepted) *pro } } -func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code interceptor.InterceptFailureCode) *proto.HtlcResolution { +func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code shared.InterceptFailureCode) *proto.HtlcResolution { return &proto.HtlcResolution{ Correlationid: request.Correlationid, Outcome: &proto.HtlcResolution_Fail{ @@ -237,7 +246,7 @@ func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code inte } } -func encodePayloadWithNextHop(payload []byte, channelId uint64, amountToForward uint64) ([]byte, error) { +func encodePayloadWithNextHop(payload []byte, scid lightning.ShortChannelID, amountToForward uint64) ([]byte, error) { bufReader := bytes.NewBuffer(payload) var b [8]byte varInt, err := sphinx.ReadVarInt(bufReader, &b) @@ -256,6 +265,7 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64, amountToForward return nil, fmt.Errorf("DecodeWithParsedTypes failed for %x: %v", innerPayload[:], err) } + channelId := uint64(scid) tt := record.NewNextHopIDRecord(&channelId) ttbuf := bytes.NewBuffer([]byte{}) if err := tt.Encode(ttbuf); err != nil { @@ -294,13 +304,13 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64, amountToForward return newPayloadBuf.Bytes(), nil } -func (i *ClnHtlcInterceptor) mapFailureCode(original interceptor.InterceptFailureCode) string { +func (i *ClnHtlcInterceptor) mapFailureCode(original shared.InterceptFailureCode) string { switch original { - case interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE: + case shared.FAILURE_TEMPORARY_CHANNEL_FAILURE: return "1007" - case interceptor.FAILURE_TEMPORARY_NODE_FAILURE: + case shared.FAILURE_TEMPORARY_NODE_FAILURE: return "2002" - case interceptor.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + case shared.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: return "400F" default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) diff --git a/interceptor/intercept.go b/interceptor/intercept_handler.go similarity index 73% rename from interceptor/intercept.go rename to interceptor/intercept_handler.go index 1ff0a7f..d9ef467 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept_handler.go @@ -20,34 +20,6 @@ import ( "golang.org/x/sync/singleflight" ) -type InterceptAction int - -const ( - INTERCEPT_RESUME InterceptAction = 0 - INTERCEPT_RESUME_WITH_ONION InterceptAction = 1 - INTERCEPT_FAIL_HTLC_WITH_CODE InterceptAction = 2 -) - -type InterceptFailureCode uint16 - -var ( - FAILURE_TEMPORARY_CHANNEL_FAILURE InterceptFailureCode = 0x1007 - FAILURE_TEMPORARY_NODE_FAILURE InterceptFailureCode = 0x2002 - FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS InterceptFailureCode = 0x400F -) - -type InterceptResult struct { - Action InterceptAction - FailureCode InterceptFailureCode - Destination []byte - AmountMsat uint64 - TotalAmountMsat uint64 - ChannelPoint *wire.OutPoint - ChannelId uint64 - PaymentSecret []byte - UseLegacyOnionBlob bool -} - type Interceptor struct { client lightning.Client config *config.NodeConfig @@ -59,7 +31,7 @@ type Interceptor struct { notificationService *notifications.NotificationService } -func NewInterceptor( +func NewInterceptHandler( client lightning.Client, config *config.NodeConfig, store InterceptStore, @@ -79,16 +51,16 @@ func NewInterceptor( } } -func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { - reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) - log.Printf("Intercept: scid: %s, paymentHash: %s, outgoindAmount: %v, outgoingExpiry: %v, incomingExpiry: %v", scid.ToString(), reqPaymentHashStr, reqOutgoingAmountMsat, reqOutgoingExpiry, reqIncomingExpiry) +func (i *Interceptor) Intercept(req shared.InterceptRequest) shared.InterceptResult { + reqPaymentHashStr := hex.EncodeToString(req.PaymentHash) + log.Printf("Intercept: scid: %s, paymentHash: %x, outgoindAmount: %v, outgoingExpiry: %v, incomingExpiry: %v", req.Scid.ToString(), reqPaymentHashStr, req.OutgoingAmountMsat, req.OutgoingExpiry, req.IncomingExpiry) resp, _, _ := i.payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { - token, params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, tag, err := i.store.PaymentInfo(reqPaymentHash) + token, params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, tag, err := i.store.PaymentInfo(req.PaymentHash) if err != nil { - log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_NODE_FAILURE, + log.Printf("paymentInfo(%x) error: %v", req.PaymentHash, err) + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_NODE_FAILURE, }, nil } @@ -98,12 +70,12 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ log.Printf("ERROR: Payment was registered without destination. paymentHash: %s", reqPaymentHashStr) } - isProbe := isRegistered && !bytes.Equal(paymentHash, reqPaymentHash) - nextHop, _ := i.client.GetPeerId(scid) + isProbe := isRegistered && !bytes.Equal(paymentHash, req.PaymentHash) + nextHop, _ := i.client.GetPeerId(&req.Scid) if err != nil { - log.Printf("GetPeerId(%s) error: %v", scid.ToString(), err) - return InterceptResult{ - Action: INTERCEPT_RESUME, + log.Printf("GetPeerId(%s) error: %v", req.Scid.ToString(), err) + return shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, }, nil } @@ -111,8 +83,8 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ // that means we are not the last hop of the payment, so we'll just forward. if isRegistered && nextHop != nil && !bytes.Equal(nextHop, destination) { log.Printf("paymentHash: %s, nextHop (%s) != destination (%s)", reqPaymentHashStr, hex.EncodeToString(nextHop), hex.EncodeToString(destination)) - return InterceptResult{ - Action: INTERCEPT_RESUME, + return shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, }, nil } @@ -125,8 +97,8 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ // is not registered, there's nothing left to be done. Just continue. if !isRegistered { log.Printf("paymentHash: %s, nextHop == nil and not registered", reqPaymentHashStr) - return InterceptResult{ - Action: INTERCEPT_RESUME, + return shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, }, nil } @@ -138,9 +110,9 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ isConnected, err := i.client.IsConnected(nextHop) if err != nil { log.Printf("IsConnected(%x) error: %v", nextHop, err) - return &InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return &shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -148,8 +120,8 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ // If this is a known probe, we'll quit early for non-connected clients. if !isConnected { log.Printf("paymentHash: %s, probe and not connected", reqPaymentHashStr) - return InterceptResult{ - Action: INTERCEPT_RESUME, + return shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, }, nil } @@ -160,9 +132,9 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ // know that the actual payment would probably succeed. if channelPoint == nil { log.Printf("paymentHash: %s, probe and channelPoint == nil", reqPaymentHashStr) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, }, nil } } @@ -178,8 +150,8 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ // The peer is online, we can resume the htlc if it's not a channel open. if !isRegistered { - return InterceptResult{ - Action: INTERCEPT_RESUME, + return shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, }, nil } @@ -198,20 +170,20 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ } // Make sure the cltv delta is enough. - if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { - log.Printf("paymentHash: %s, outgoingExpiry: %v, incomingExpiry: %v, i.config.TimeLockDelta: %v", reqPaymentHashStr, reqOutgoingExpiry, reqIncomingExpiry, i.config.TimeLockDelta) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + if int64(req.IncomingExpiry)-int64(req.OutgoingExpiry) < int64(i.config.TimeLockDelta) { + log.Printf("paymentHash: %s, outgoingExpiry: %v, incomingExpiry: %v, i.config.TimeLockDelta: %v", reqPaymentHashStr, req.OutgoingExpiry, req.IncomingExpiry, i.config.TimeLockDelta) + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } validUntil, err := time.Parse(lsps0.TIME_FORMAT, params.ValidUntil) if err != nil { log.Printf("time.Parse(%s, %s) failed. Failing channel open: %v", lsps0.TIME_FORMAT, params.ValidUntil, err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -220,27 +192,27 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ if time.Now().UTC().After(validUntil) { if !i.isCurrentChainFeeCheaper(token, params) { log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. paymenthash: %s, params: %+v", reqPaymentHashStr, params) } - channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat, tag) + channelPoint, err = i.openChannel(req.PaymentHash, destination, incomingAmountMsat, tag) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } } var bigProd, bigAmt big.Int - amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() + amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(req.OutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() deadline := time.Now().Add(60 * time.Second) @@ -259,23 +231,23 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ if err != nil { log.Printf("insertChannel error: %v", err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } - channelID := uint64(chanResult.ConfirmedChannelID) - if channelID == 0 { - channelID = uint64(chanResult.InitialChannelID) + channelID := chanResult.ConfirmedChannelID + if uint64(channelID) == 0 { + channelID = chanResult.InitialChannelID } useLegacyOnionBlob := slices.Contains(i.config.LegacyOnionTokens, token) - return InterceptResult{ - Action: INTERCEPT_RESUME_WITH_ONION, + return shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME_WITH_ONION, Destination: destination, ChannelPoint: channelPoint, - ChannelId: channelID, + Scid: channelID, PaymentSecret: paymentSecret, AmountMsat: uint64(amt), TotalAmountMsat: uint64(outgoingAmountMsat), @@ -292,16 +264,16 @@ func (i *Interceptor) Intercept(scid *lightning.ShortChannelID, reqPaymentHash [ } log.Printf("Error: Channel failed to open... timed out. ") - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return shared.InterceptResult{ + Action: shared.INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: shared.FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil }) - return resp.(InterceptResult) + return resp.(shared.InterceptResult) } -func (i *Interceptor) notify(reqPaymentHashStr string, nextHop []byte, isRegistered bool) *InterceptResult { +func (i *Interceptor) notify(reqPaymentHashStr string, nextHop []byte, isRegistered bool) *shared.InterceptResult { // If not connected, send a notification to the registered // notification service for this client if available. notified, err := i.notificationService.Notify( @@ -313,8 +285,8 @@ func (i *Interceptor) notify(reqPaymentHashStr string, nextHop []byte, isRegiste // is offline or unknown. We'll resume the HTLC (which will // result in UNKOWN_NEXT_PEER) if err != nil || !notified { - return &InterceptResult{ - Action: INTERCEPT_RESUME, + return &shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, } } @@ -337,8 +309,8 @@ func (i *Interceptor) notify(reqPaymentHashStr string, nextHop []byte, isRegiste nextHop, err, ) - return &InterceptResult{ - Action: INTERCEPT_RESUME, + return &shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, } } @@ -359,8 +331,8 @@ func (i *Interceptor) notify(reqPaymentHashStr string, nextHop []byte, isRegiste nextHop, err, ) - return &InterceptResult{ - Action: INTERCEPT_RESUME, + return &shared.InterceptResult{ + Action: shared.INTERCEPT_RESUME, } } } diff --git a/lnd/interceptor.go b/lnd/interceptor.go index 0f6c6ed..7c69aa4 100644 --- a/lnd/interceptor.go +++ b/lnd/interceptor.go @@ -10,6 +10,7 @@ import ( "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" + "github.com/breez/lspd/shared" "github.com/btcsuite/btcd/btcec/v2" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnrpc" @@ -136,17 +137,25 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { scid := lightning.ShortChannelID(request.OutgoingRequestedChanId) - interceptResult := i.interceptor.Intercept(&scid, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) + interceptResult := i.interceptor.Intercept(shared.InterceptRequest{ + Identifier: request.IncomingCircuitKey.String(), + Scid: scid, + PaymentHash: request.PaymentHash, + IncomingAmountMsat: request.IncomingAmountMsat, + OutgoingAmountMsat: request.OutgoingAmountMsat, + IncomingExpiry: request.IncomingExpiry, + OutgoingExpiry: request.OutgoingExpiry, + }) switch interceptResult.Action { - case interceptor.INTERCEPT_RESUME_WITH_ONION: + case shared.INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.createOnionResponse(interceptResult, request)) - case interceptor.INTERCEPT_FAIL_HTLC_WITH_CODE: + case shared.INTERCEPT_FAIL_HTLC_WITH_CODE: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, FailureCode: i.mapFailureCode(interceptResult.FailureCode), }) - case interceptor.INTERCEPT_RESUME: + case shared.INTERCEPT_RESUME: fallthrough default: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ @@ -166,13 +175,13 @@ func (i *LndHtlcInterceptor) intercept() error { } } -func (i *LndHtlcInterceptor) mapFailureCode(original interceptor.InterceptFailureCode) lnrpc.Failure_FailureCode { +func (i *LndHtlcInterceptor) mapFailureCode(original shared.InterceptFailureCode) lnrpc.Failure_FailureCode { switch original { - case interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE: + case shared.FAILURE_TEMPORARY_CHANNEL_FAILURE: return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE - case interceptor.FAILURE_TEMPORARY_NODE_FAILURE: + case shared.FAILURE_TEMPORARY_NODE_FAILURE: return lnrpc.Failure_TEMPORARY_NODE_FAILURE - case interceptor.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + case shared.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: return lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) @@ -181,7 +190,7 @@ func (i *LndHtlcInterceptor) mapFailureCode(original interceptor.InterceptFailur } func (i *LndHtlcInterceptor) constructOnion( - interceptResult interceptor.InterceptResult, + interceptResult shared.InterceptResult, reqOutgoingExpiry uint32, reqPaymentHash []byte, ) ([]byte, error) { @@ -242,7 +251,7 @@ func (i *LndHtlcInterceptor) constructOnion( return onionBlob.Bytes(), nil } -func (i *LndHtlcInterceptor) createOnionResponse(interceptResult interceptor.InterceptResult, request *routerrpc.ForwardHtlcInterceptRequest) *routerrpc.ForwardHtlcInterceptResponse { +func (i *LndHtlcInterceptor) createOnionResponse(interceptResult shared.InterceptResult, request *routerrpc.ForwardHtlcInterceptRequest) *routerrpc.ForwardHtlcInterceptResponse { onionBlob := request.OnionBlob if interceptResult.UseLegacyOnionBlob { @@ -261,7 +270,7 @@ func (i *LndHtlcInterceptor) createOnionResponse(interceptResult interceptor.Int IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_RESUME, OutgoingAmountMsat: interceptResult.AmountMsat, - OutgoingRequestedChanId: uint64(interceptResult.ChannelId), + OutgoingRequestedChanId: uint64(interceptResult.Scid), OnionBlob: onionBlob, } } diff --git a/main.go b/main.go index 130d861..d846751 100644 --- a/main.go +++ b/main.go @@ -110,7 +110,7 @@ func main() { client.StartListeners() fwsync := lnd.NewForwardingHistorySync(client, interceptStore, forwardingStore) - interceptor := interceptor.NewInterceptor(client, node.NodeConfig, interceptStore, openingStore, feeEstimator, feeStrategy, notificationService) + interceptor := interceptor.NewInterceptHandler(client, node.NodeConfig, interceptStore, openingStore, feeEstimator, feeStrategy, notificationService) htlcInterceptor, err = lnd.NewLndHtlcInterceptor(node.NodeConfig, client, fwsync, interceptor) if err != nil { log.Fatalf("failed to initialize LND interceptor: %v", err) @@ -123,8 +123,7 @@ func main() { log.Fatalf("failed to initialize CLN client: %v", err) } - interceptor := interceptor.NewInterceptor(client, node.NodeConfig, interceptStore, openingStore, feeEstimator, feeStrategy, notificationService) - htlcInterceptor, err = cln.NewClnHtlcInterceptor(node.NodeConfig, client, interceptor) + interceptor := interceptor.NewInterceptHandler(client, node.NodeConfig, interceptStore, openingStore, feeEstimator, feeStrategy, notificationService) if err != nil { log.Fatalf("failed to initialize CLN interceptor: %v", err) } diff --git a/shared/combined_handler.go b/shared/combined_handler.go new file mode 100644 index 0000000..b53f7c9 --- /dev/null +++ b/shared/combined_handler.go @@ -0,0 +1,27 @@ +package shared + +import "log" + +type CombinedHandler struct { + handlers []InterceptHandler +} + +func NewCombinedHandler(handlers ...InterceptHandler) *CombinedHandler { + return &CombinedHandler{ + handlers: handlers, + } +} + +func (c *CombinedHandler) Intercept(req InterceptRequest) InterceptResult { + for i, handler := range c.handlers { + res := handler.Intercept(req) + log.Printf("Intercept %+v. Interceptor %d returns %+v", req, i, res) + if res.Action != INTERCEPT_RESUME { + return res + } + } + + return InterceptResult{ + Action: INTERCEPT_RESUME, + } +} diff --git a/shared/intercept_handler.go b/shared/intercept_handler.go new file mode 100644 index 0000000..e7e350e --- /dev/null +++ b/shared/intercept_handler.go @@ -0,0 +1,65 @@ +package shared + +import ( + "fmt" + + "github.com/breez/lspd/lightning" + "github.com/btcsuite/btcd/wire" +) + +type InterceptAction int + +const ( + INTERCEPT_RESUME InterceptAction = 0 + INTERCEPT_RESUME_WITH_ONION InterceptAction = 1 + INTERCEPT_FAIL_HTLC_WITH_CODE InterceptAction = 2 + INTERCEPT_IGNORE InterceptAction = 3 +) + +type InterceptFailureCode uint16 + +var ( + FAILURE_TEMPORARY_CHANNEL_FAILURE InterceptFailureCode = 0x1007 + FAILURE_AMOUNT_BELOW_MINIMUM InterceptFailureCode = 0x100B + FAILURE_INCORRECT_CLTV_EXPIRY InterceptFailureCode = 0x100D + FAILURE_TEMPORARY_NODE_FAILURE InterceptFailureCode = 0x2002 + FAILURE_UNKNOWN_NEXT_PEER InterceptFailureCode = 0x400A + FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS InterceptFailureCode = 0x400F +) + +type InterceptRequest struct { + // Identifier that uniquely identifies this htlc. + // For cln, that's hash of the next onion or the shared secret. + Identifier string + Scid lightning.ShortChannelID + PaymentHash []byte + IncomingAmountMsat uint64 + OutgoingAmountMsat uint64 + IncomingExpiry uint32 + OutgoingExpiry uint32 +} + +func (r *InterceptRequest) PaymentId() string { + return fmt.Sprintf("%s|%x", r.Scid.ToString(), r.PaymentHash) +} + +func (r *InterceptRequest) HtlcId() string { + return r.Identifier +} + +type InterceptResult struct { + Action InterceptAction + FailureCode InterceptFailureCode + Destination []byte + AmountMsat uint64 + FeeMsat *uint64 + TotalAmountMsat uint64 + ChannelPoint *wire.OutPoint + Scid lightning.ShortChannelID + PaymentSecret []byte + UseLegacyOnionBlob bool +} + +type InterceptHandler interface { + Intercept(req InterceptRequest) InterceptResult +}