From 6abf4a66de892b1e88780952c04263b4fbb36ba1 Mon Sep 17 00:00:00 2001 From: ErikEk Date: Sun, 17 Jul 2022 12:32:14 +0200 Subject: [PATCH] route: add custom onion blob to sendtoroute --- cmd/lncli/cmd_payments.go | 11 +++++++ docs/release-notes/release-notes-0.16.0.md | 3 ++ lnrpc/routerrpc/router.pb.go | 30 +++++++++++++------ lnrpc/routerrpc/router.proto | 6 ++++ lnrpc/routerrpc/router.swagger.json | 5 ++++ lnrpc/routerrpc/router_server.go | 12 ++++++-- routing/payment_lifecycle.go | 35 ++++++++++++++-------- routing/router.go | 16 +++++----- routing/router_test.go | 16 +++++----- rpcserver.go | 2 +- 10 files changed, 97 insertions(+), 39 deletions(-) diff --git a/cmd/lncli/cmd_payments.go b/cmd/lncli/cmd_payments.go index 0cf5e9e967..19941d6ccb 100644 --- a/cmd/lncli/cmd_payments.go +++ b/cmd/lncli/cmd_payments.go @@ -975,10 +975,21 @@ func sendToRoute(ctx *cli.Context) error { route = routes.Route } + var onionBlob []byte + + if ctx.IsSet("onion_blob") { + onionBlob, err = hex.DecodeString(ctx.String("onion_blob")) + + if err != nil { + return err + } + } + req := &routerrpc.SendToRouteRequest{ PaymentHash: rHash, Route: route, SkipTempErr: ctx.Bool("skip_temp_err"), + OnionBlob: onionBlob, } return sendToRouteRequest(ctx, req) diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index f6703f8c51..3d0c91788c 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -21,6 +21,9 @@ that were signed by our wallet. Prior to this change `SignPsbt` didn't indicate whether the Psbt held any inputs for our wallet to sign. +* Allowing users to parse a [custom onion blob though the + sendtoroute interface](https://github.com/lightningnetwork/lnd/pull/6750). + * [Add list addresses RPC](https://github.com/lightningnetwork/lnd/pull/6596). * Add [TrackPayments](https://github.com/lightningnetwork/lnd/pull/6335) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index a6f43fe8b5..dc675c9df0 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -879,6 +879,9 @@ type SendToRouteRequest struct { // failed unless a terminal error is occurred, such as payment timeout, no // routes, incorrect payment details, or insufficient funds. SkipTempErr bool `protobuf:"varint,3,opt,name=skip_temp_err,json=skipTempErr,proto3" json:"skip_temp_err,omitempty"` + // Custom onion blob. Limited to 1366 bytes. This will be used instead of the + // standard generated sphinx package. + OnionBlob []byte `protobuf:"bytes,4,opt,name=onion_blob,json=onionBlob,proto3" json:"onion_blob,omitempty"` } func (x *SendToRouteRequest) Reset() { @@ -934,6 +937,13 @@ func (x *SendToRouteRequest) GetSkipTempErr() bool { return false } +func (x *SendToRouteRequest) GetOnionBlob() []byte { + if x != nil { + return x.OnionBlob + } + return nil +} + type SendToRouteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3103,15 +3113,17 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, - 0x6c, 0x61, 0x79, 0x22, 0x7f, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x65, 0x72, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x65, 0x6d, - 0x70, 0x45, 0x72, 0x72, 0x22, 0x5b, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x6c, 0x61, 0x79, 0x22, 0x9e, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, + 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x65, + 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x65, + 0x6d, 0x70, 0x45, 0x72, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, + 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, + 0x42, 0x6c, 0x6f, 0x62, 0x22, 0x5b, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 4cfd4f37ff..cd942d80a9 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -362,6 +362,12 @@ message SendToRouteRequest { routes, incorrect payment details, or insufficient funds. */ bool skip_temp_err = 3; + + /* + Custom onion blob. Limited to 1366 bytes. This will be used instead of the + standard generated sphinx package. + */ + bytes onion_blob = 4; } message SendToRouteResponse { diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index 7cd634a874..b3cfa58703 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -1726,6 +1726,11 @@ "skip_temp_err": { "type": "boolean", "description": "Whether the payment should be marked as failed when a temporary error is\nreturned from the given route. Set it to true so the payment won't be\nfailed unless a terminal error is occurred, such as payment timeout, no\nroutes, incorrect payment details, or insufficient funds." + }, + "onion_blob": { + "type": "string", + "format": "byte", + "description": "Custom onion blob. Limited to 1366 bytes. This will be used instead of the\nstandard generated sphinx package." } } }, diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 1967a3dd60..41a8f1fbb6 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -392,6 +392,10 @@ func (s *Server) SendToRouteV2(ctx context.Context, if req.Route == nil { return nil, fmt.Errorf("unable to send, no routes provided") } + if len(req.OnionBlob) > lnwire.OnionPacketSize { + return nil, fmt.Errorf("the provided onion blob is to large, "+ + "size is limited to %d bytes", lnwire.OnionPacketSize) + } route, err := s.cfg.RouterBackend.UnmarshallRoute(req.Route) if err != nil { @@ -412,9 +416,13 @@ func (s *Server) SendToRouteV2(ctx context.Context, // case, we give precedence to the attempt information as stored in the // db. if req.SkipTempErr { - attempt, err = s.cfg.Router.SendToRouteSkipTempErr(hash, route) + attempt, err = s.cfg.Router.SendToRouteSkipTempErr( + hash, route, req.OnionBlob, + ) } else { - attempt, err = s.cfg.Router.SendToRoute(hash, route) + attempt, err = s.cfg.Router.SendToRoute( + hash, route, req.OnionBlob, + ) } if attempt != nil { rpcAttempt, err := s.cfg.RouterBackend.MarshalHTLCAttempt( diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index b87dddef13..718f103e79 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -296,7 +296,9 @@ lifecycle: lastShard := rt.ReceiverAmt() == currentState.remainingAmt // We found a route to try, launch a new shard. - attempt, outcome, err := shardHandler.launchShard(rt, lastShard) + attempt, outcome, err := shardHandler.launchShard( + rt, lastShard, nil, + ) switch { // We may get a terminal error if we've processed a shard with // a terminal state (settled or permanent failure), while we @@ -426,12 +428,13 @@ type launchOutcome struct { // non-nil error, it means that the attempt was not sent onto the network, so // no result will be available in the future for it. func (p *shardHandler) launchShard(rt *route.Route, - lastShard bool) (*channeldb.HTLCAttemptInfo, *launchOutcome, error) { + lastShard bool, customOnionBlob []byte) (*channeldb.HTLCAttemptInfo, + *launchOutcome, error) { // Using the route received from the payment session, create a new // shard to send. firstHop, htlcAdd, attempt, err := p.createNewPaymentAttempt( - rt, lastShard, + rt, lastShard, customOnionBlob, ) if err != nil { return nil, nil, err @@ -658,7 +661,8 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( } // createNewPaymentAttempt creates a new payment attempt from the given route. -func (p *shardHandler) createNewPaymentAttempt(rt *route.Route, lastShard bool) ( +func (p *shardHandler) createNewPaymentAttempt(rt *route.Route, lastShard bool, + customOninonBlob []byte) ( lnwire.ShortChannelID, *lnwire.UpdateAddHTLC, *channeldb.HTLCAttemptInfo, error) { @@ -696,14 +700,7 @@ func (p *shardHandler) createNewPaymentAttempt(rt *route.Route, lastShard bool) hop.AMP = shard.AMP() } - // Generate the raw encoded sphinx packet to be included along - // with the htlcAdd message that we send directly to the - // switch. hash := shard.Hash() - onionBlob, _, err := generateSphinxPacket(rt, hash[:], sessionKey) - if err != nil { - return lnwire.ShortChannelID{}, nil, nil, err - } // Craft an HTLC packet to send to the layer 2 switch. The // metadata within this packet will be used to route the @@ -713,7 +710,21 @@ func (p *shardHandler) createNewPaymentAttempt(rt *route.Route, lastShard bool) Expiry: rt.TotalTimeLock, PaymentHash: hash, } - copy(htlcAdd.OnionBlob[:], onionBlob) + + // Use the custom onion blob if it is provided. Otherwise, we will + // generate the raw encoded sphinx packet to be included along + // with the htlcAdd message that we send directly to the + // switch. + if customOninonBlob != nil { + copy(htlcAdd.OnionBlob[:], customOninonBlob) + } else { + onionBlob, _, err := generateSphinxPacket(rt, hash[:], + sessionKey) + if err != nil { + return lnwire.ShortChannelID{}, nil, nil, err + } + copy(htlcAdd.OnionBlob[:], onionBlob) + } // Attempt to send this payment through the network to complete // the payment. If this attempt fails, then we'll continue on diff --git a/routing/router.go b/routing/router.go index cf6357c686..a250b7694c 100644 --- a/routing/router.go +++ b/routing/router.go @@ -2157,18 +2157,19 @@ func (r *ChannelRouter) preparePayment(payment *LightningPayment) ( // SendToRoute sends a payment using the provided route and fails the payment // when an error is returned from the attempt. -func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, - rt *route.Route) (*channeldb.HTLCAttempt, error) { +func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route, + customOnionBlob []byte) (*channeldb.HTLCAttempt, error) { - return r.sendToRoute(htlcHash, rt, false) + return r.sendToRoute(htlcHash, rt, false, customOnionBlob) } // SendToRouteSkipTempErr sends a payment using the provided route and fails // the payment ONLY when a terminal error is returned from the attempt. func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash, - rt *route.Route) (*channeldb.HTLCAttempt, error) { + rt *route.Route, customOnionBlob []byte) (*channeldb.HTLCAttempt, + error) { - return r.sendToRoute(htlcHash, rt, true) + return r.sendToRoute(htlcHash, rt, true, customOnionBlob) } // sendToRoute attempts to send a payment with the given hash through the @@ -2178,7 +2179,8 @@ func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash, // was initiated, both return values will be non-nil. If skipTempErr is true, // the payment won't be failed unless a terminal error has occurred. func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, - skipTempErr bool) (*channeldb.HTLCAttempt, error) { + skipTempErr bool, customOnionBlob []byte) (*channeldb.HTLCAttempt, + error) { // Calculate amount paid to receiver. amt := rt.ReceiverAmt() @@ -2243,7 +2245,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, } var shardError error - attempt, outcome, err := sh.launchShard(rt, false) + attempt, outcome, err := sh.launchShard(rt, false, customOnionBlob) // With SendToRoute, it can happen that the route exceeds protocol // constraints. Mark the payment as failed with an internal error. diff --git a/routing/router_test.go b/routing/router_test.go index 8c1edd2533..a84c26a154 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -461,7 +461,7 @@ func TestChannelUpdateValidation(t *testing.T) { // Send off the payment request to the router. The specified route // should be attempted and the channel update should be received by // router and ignored because it is missing a valid signature. - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) require.Error(t, err, "expected route to fail with channel update") _, e1, e2, err = ctx.router.GetChannelByID( @@ -478,7 +478,7 @@ func TestChannelUpdateValidation(t *testing.T) { signErrChanUpdate(t, testGraph.privKeyMap["b"], &errChanUpdate) // Retry the payment using the same route as before. - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) require.Error(t, err, "expected route to fail with channel update") // This time a valid signature was supplied and the policy change should @@ -2870,7 +2870,7 @@ func TestSendToRouteStructuredError(t *testing.T) { // update should be received by router and ignored // because it is missing a valid // signature. - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) fErr, ok := err.(*htlcswitch.ForwardingError) require.True( @@ -2951,7 +2951,7 @@ func TestSendToRouteMaxHops(t *testing.T) { // Send off the payment request to the router. We expect an error back // indicating that the route is too long. var payment lntypes.Hash - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) if err != route.ErrMaxRouteHopsExceeded { t.Fatalf("expected ErrMaxRouteHopsExceeded, but got %v", err) } @@ -4197,7 +4197,7 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) { ).Return(nil) // Expect a successful send to route. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.NoError(t, err) require.Equal(t, testAttempt, attempt) @@ -4283,7 +4283,7 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) { ).Return(nil, nil) // Expect a failed send to route. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.Equal(t, tempErr, err) require.Equal(t, testAttempt, attempt) @@ -4372,7 +4372,7 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) { ).Return(&failureReason, nil) // Expect a failed send to route. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.Equal(t, permErr, err) require.Equal(t, testAttempt, attempt) @@ -4461,7 +4461,7 @@ func TestSendToRouteTempFailure(t *testing.T) { ).Return(nil, nil) // Expect a failed send to route. - attempt, err := router.SendToRoute(payHash, rt) + attempt, err := router.SendToRoute(payHash, rt, nil) require.Equal(t, tempErr, err) require.Equal(t, testAttempt, attempt) diff --git a/rpcserver.go b/rpcserver.go index b75295612b..f5b6f30678 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -5057,7 +5057,7 @@ func (r *rpcServer) dispatchPaymentIntent( } else { var attempt *channeldb.HTLCAttempt attempt, routerErr = r.server.chanRouter.SendToRoute( - payIntent.rHash, payIntent.route, + payIntent.rHash, payIntent.route, nil, ) if routerErr == nil {