lnrpc: remove deprecated Send* RPC server implementations
What changed, and why it matters
This commit removes old, unused code paths for sending Lightning payments through the main RPC server. These RPCs (SendPayment, SendPaymentSync, SendToRoute, SendToRouteSync) were already deleted from the service definition in a prior change, so this patch only cleans up the leftover server-side implementations and their macaroon permissions. There is no security vulnerability here; it is routine code deletion of dead functionality.
No action required. Treat as routine maintenance. Verify that replacement RPCs (routerrpc SendPaymentV2 / SendToRouteV2) are used by clients and that no operational tooling still depends on the removed methods.
Security signals we found
Removal of deprecated RPC handlers and macaroon permissions
Deletion of dead payment-dispatch helper code
No new input parsing, network exposure, or privilege changes introduced
Evidence from the diff
The diff deletes the legacy lnrpc payment handlers and their helper types from rpcserver.go. Removed items include: SendPayment, SendPaymentSync, SendToRoute, SendToRouteSync methods; paymentStream, rpcPaymentRequest, rpcPaymentIntent, extractPaymentIntent, dispatchPaymentIntent, sendPayment, sendPaymentSync; and the corresponding macaroon permission entries. The removed code called existing routing APIs (chanRouter.SendPayment / SendToRoute) and did not introduce new behavior. It is a follow-up cleanup after the RPC definitions themselves were removed.
Changed components
lnrpc RPC server (rpcserver.go)Macaroon permission map MainRPCServerPermissionsLegacy SendPayment / SendToRoute RPC implementationsInspect captured patch +0 / −789
diff --git a/rpcserver.go b/rpcserver.go
index d4a9ce5..4019499 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"image/color"
- "io"
"maps"
"math"
"net"
@@ -76,7 +75,6 @@ import (
paymentsdb "github.com/lightningnetwork/lnd/payments/db"
"github.com/lightningnetwork/lnd/peer"
"github.com/lightningnetwork/lnd/peernotifier"
- "github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/blindedpath"
"github.com/lightningnetwork/lnd/routing/route"
@@ -398,22 +396,6 @@ func MainRPCServerPermissions() map[string][]bakery.Op {
Entity: "offchain",
Action: "read",
}},
- "/lnrpc.Lightning/SendPayment": {{
- Entity: "offchain",
- Action: "write",
- }},
- "/lnrpc.Lightning/SendPaymentSync": {{
- Entity: "offchain",
- Action: "write",
- }},
- "/lnrpc.Lightning/SendToRoute": {{
- Entity: "offchain",
- Action: "write",
- }},
- "/lnrpc.Lightning/SendToRouteSync": {{
- Entity: "offchain",
- Action: "write",
- }},
"/lnrpc.Lightning/AddInvoice": {{
Entity: "invoices",
Action: "write",
@@ -5694,777 +5676,6 @@ func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription,
}
}
-// paymentStream enables different types of payment streams, such as:
-// lnrpc.Lightning_SendPaymentServer and lnrpc.Lightning_SendToRouteServer to
-// execute sendPayment. We use this struct as a sort of bridge to enable code
-// re-use between SendPayment and SendToRoute.
-type paymentStream struct {
- getCtx func() context.Context
- recv func() (*rpcPaymentRequest, error)
- send func(*lnrpc.SendResponse) error
-}
-
-// rpcPaymentRequest wraps lnrpc.SendRequest so that routes from
-// lnrpc.SendToRouteRequest can be passed to sendPayment.
-type rpcPaymentRequest struct {
- *lnrpc.SendRequest
- route *route.Route
-}
-
-// SendPayment dispatches a bi-directional streaming RPC for sending payments
-// through the Lightning Network. A single RPC invocation creates a persistent
-// bi-directional stream allowing clients to rapidly send payments through the
-// Lightning Network with a single persistent connection.
-func (r *rpcServer) SendPayment(
- stream lnrpc.Lightning_SendPaymentServer) error {
-
- var lock sync.Mutex
-
- return r.sendPayment(&paymentStream{
- getCtx: stream.Context,
- recv: func() (*rpcPaymentRequest, error) {
- req, err := stream.Recv()
- if err != nil {
- return nil, err
- }
-
- return &rpcPaymentRequest{
- SendRequest: req,
- }, nil
- },
- send: func(r *lnrpc.SendResponse) error {
- // Calling stream.Send concurrently is not safe.
- lock.Lock()
- defer lock.Unlock()
- return stream.Send(r)
- },
- })
-}
-
-// SendToRoute dispatches a bi-directional streaming RPC for sending payments
-// through the Lightning Network via predefined routes passed in. A single RPC
-// invocation creates a persistent bi-directional stream allowing clients to
-// rapidly send payments through the Lightning Network with a single persistent
-// connection.
-func (r *rpcServer) SendToRoute(
- stream lnrpc.Lightning_SendToRouteServer) error {
-
- var lock sync.Mutex
-
- return r.sendPayment(&paymentStream{
- getCtx: stream.Context,
- recv: func() (*rpcPaymentRequest, error) {
- req, err := stream.Recv()
- if err != nil {
- return nil, err
- }
-
- return r.unmarshallSendToRouteRequest(req)
- },
- send: func(r *lnrpc.SendResponse) error {
- // Calling stream.Send concurrently is not safe.
- lock.Lock()
- defer lock.Unlock()
- return stream.Send(r)
- },
- })
-}
-
-// unmarshallSendToRouteRequest unmarshalls an rpc sendtoroute request
-func (r *rpcServer) unmarshallSendToRouteRequest(
- req *lnrpc.SendToRouteRequest) (*rpcPaymentRequest, error) {
-
- if req.Route == nil {
- return nil, fmt.Errorf("unable to send, no route provided")
- }
-
- route, err := r.routerBackend.UnmarshallRoute(req.Route)
- if err != nil {
- return nil, err
- }
-
- return &rpcPaymentRequest{
- SendRequest: &lnrpc.SendRequest{
- PaymentHash: req.PaymentHash,
- PaymentHashString: req.PaymentHashString,
- },
- route: route,
- }, nil
-}
-
-// rpcPaymentIntent is a small wrapper struct around the of values we can
-// receive from a client over RPC if they wish to send a payment. We'll either
-// extract these fields from a payment request (which may include routing
-// hints), or we'll get a fully populated route from the user that we'll pass
-// directly to the channel router for dispatching.
-type rpcPaymentIntent struct {
- msat lnwire.MilliSatoshi
- feeLimit lnwire.MilliSatoshi
- cltvLimit uint32
- dest route.Vertex
- rHash [32]byte
- cltvDelta uint16
- routeHints [][]zpay32.HopHint
- outgoingChannelIDs []uint64
- lastHop *route.Vertex
- destFeatures *lnwire.FeatureVector
- paymentAddr fn.Option[[32]byte]
- payReq []byte
- metadata []byte
- blindedPathSet *routing.BlindedPaymentPathSet
-
- destCustomRecords record.CustomSet
-
- route *route.Route
-}
-
-// extractPaymentIntent attempts to parse the complete details required to
-// dispatch a client from the information presented by an RPC client. There are
-// three ways a client can specify their payment details: a payment request,
-// via manual details, or via a complete route.
-//
-//nolint:funlen
-func (r *rpcServer) extractPaymentIntent(
- rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error) {
-
- payIntent := rpcPaymentIntent{}
-
- // If a route was specified, then we can use that directly.
- if rpcPayReq.route != nil {
- // If the user is using the REST interface, then they'll be
- // passing the payment hash as a hex encoded string.
- if rpcPayReq.PaymentHashString != "" {
- paymentHash, err := hex.DecodeString(
- rpcPayReq.PaymentHashString,
- )
- if err != nil {
- return payIntent, err
- }
-
- copy(payIntent.rHash[:], paymentHash)
- } else {
- copy(payIntent.rHash[:], rpcPayReq.PaymentHash)
- }
-
- payIntent.route = rpcPayReq.route
- return payIntent, nil
- }
-
- // If there are no routes specified, pass along a outgoing channel
- // restriction if specified. The main server rpc does not support
- // multiple channel restrictions.
- if rpcPayReq.OutgoingChanId != 0 {
- payIntent.outgoingChannelIDs = []uint64{
- rpcPayReq.OutgoingChanId,
- }
- }
-
- // Pass along a last hop restriction if specified.
- if len(rpcPayReq.LastHopPubkey) > 0 {
- lastHop, err := route.NewVertexFromBytes(
- rpcPayReq.LastHopPubkey,
- )
- if err != nil {
- return payIntent, err
- }
- payIntent.lastHop = &lastHop
- }
-
- // Take the CLTV limit from the request if set, otherwise use the max.
- cltvLimit, err := routerrpc.ValidateCLTVLimit(
- rpcPayReq.CltvLimit, r.cfg.MaxOutgoingCltvExpiry,
- )
- if err != nil {
- return payIntent, err
- }
- payIntent.cltvLimit = cltvLimit
-
- customRecords := record.CustomSet(rpcPayReq.DestCustomRecords)
- if err := customRecords.Validate(); err != nil {
- return payIntent, err
- }
- payIntent.destCustomRecords = customRecords
-
- validateDest := func(dest route.Vertex) error {
- if rpcPayReq.AllowSelfPayment {
- return nil
- }
-
- if dest == r.selfNode {
- return errors.New("self-payments not allowed")
- }
-
- return nil
- }
-
- // If the payment request field isn't blank, then the details of the
- // invoice are encoded entirely within the encoded payReq. So we'll
- // attempt to decode it, populating the payment accordingly.
- if rpcPayReq.PaymentRequest != "" {
- payReq, err := zpay32.Decode(
- rpcPayReq.PaymentRequest, r.cfg.ActiveNetParams.Params,
- zpay32.WithErrorOnUnknownFeatureBit(),
- )
- if err != nil {
- return payIntent, err
- }
-
- // Next, we'll ensure that this payreq hasn't already expired.
- err = routerrpc.ValidatePayReqExpiry(
- r.routerBackend.Clock, payReq,
- )
- if err != nil {
- return payIntent, err
- }
-
- // If the amount was not included in the invoice, then we let
- // the payer specify the amount of satoshis they wish to send.
- // We override the amount to pay with the amount provided from
- // the payment request.
- if payReq.MilliSat == nil {
- amt, err := lnrpc.UnmarshallAmt(
- rpcPayReq.Amt, rpcPayReq.AmtMsat,
- )
- if err != nil {
- return payIntent, err
- }
- if amt == 0 {
- return payIntent, errors.New("amount must be " +
- "specified when paying a zero amount " +
- "invoice")
- }
-
- payIntent.msat = amt
- } else {
- payIntent.msat = *payReq.MilliSat
- }
-
- // Calculate the fee limit that should be used for this payment.
- payIntent.feeLimit = lnrpc.CalculateFeeLimit(
- rpcPayReq.FeeLimit, payIntent.msat,
- )
-
- copy(payIntent.rHash[:], payReq.PaymentHash[:])
- destKey := payReq.Destination.SerializeCompressed()
- copy(payIntent.dest[:], destKey)
- payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
- payIntent.routeHints = payReq.RouteHints
- payIntent.payReq = []byte(rpcPayReq.PaymentRequest)
- payIntent.destFeatures = payReq.Features
- payIntent.paymentAddr = payReq.PaymentAddr
- payIntent.metadata = payReq.Metadata
-
- if len(payReq.BlindedPaymentPaths) > 0 {
- pathSet, err := routerrpc.BuildBlindedPathSet(
- payReq.BlindedPaymentPaths,
- )
- if err != nil {
- return payIntent, err
- }
- payIntent.blindedPathSet = pathSet
-
- // Replace the destination node with the target public
- // key of the blinded path set.
- copy(
- payIntent.dest[:],
- pathSet.TargetPubKey().SerializeCompressed(),
- )
-
- pathFeatures := pathSet.Features()
- if !pathFeatures.IsEmpty() {
- payIntent.destFeatures = pathFeatures.Clone()
- }
- }
-
- if err := validateDest(payIntent.dest); err != nil {
- return payIntent, err
- }
-
- // Do bounds checking with the block padding.
- err = routing.ValidateCLTVLimit(
- payIntent.cltvLimit, payIntent.cltvDelta, true,
- )
- if err != nil {
- return payIntent, err
- }
-
- return payIntent, nil
- }
-
- // At this point, a destination MUST be specified, so we'll convert it
- // into the proper representation now. The destination will either be
- // encoded as raw bytes, or via a hex string.
- var pubBytes []byte
- if len(rpcPayReq.Dest) != 0 {
- pubBytes = rpcPayReq.Dest
- } else {
- var err error
- pubBytes, err = hex.DecodeString(rpcPayReq.DestString)
- if err != nil {
- return payIntent, err
- }
- }
- if len(pubBytes) != 33 {
- return payIntent, errors.New("invalid key length")
- }
- copy(payIntent.dest[:], pubBytes)
-
- if err := validateDest(payIntent.dest); err != nil {
- return payIntent, err
- }
-
- // Payment address may not be needed by legacy invoices.
- if len(rpcPayReq.PaymentAddr) != 0 && len(rpcPayReq.PaymentAddr) != 32 {
- return payIntent, errors.New("invalid payment address length")
- }
-
- // Set the payment address if it was explicitly defined with the
- // rpcPaymentRequest.
- // Note that the payment address for the payIntent should be nil if none
- // was provided with the rpcPaymentRequest.
- if len(rpcPayReq.PaymentAddr) != 0 {
- var addr [32]byte
- copy(addr[:], rpcPayReq.PaymentAddr)
- payIntent.paymentAddr = fn.Some(addr)
- }
-
- // Otherwise, If the payment request field was not specified
- // (and a custom route wasn't specified), construct the payment
- // from the other fields.
- payIntent.msat, err = lnrpc.UnmarshallAmt(
- rpcPayReq.Amt, rpcPayReq.AmtMsat,
- )
- if err != nil {
- return payIntent, err
- }
-
- // Calculate the fee limit that should be used for this payment.
- payIntent.feeLimit = lnrpc.CalculateFeeLimit(
- rpcPayReq.FeeLimit, payIntent.msat,
- )
-
- if rpcPayReq.FinalCltvDelta != 0 {
- payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta)
- } else {
- // If no final cltv delta is given, assume the default that we
- // use when creating an invoice. We do not assume the default of
- // 9 blocks that is defined in BOLT-11, because this is never
- // enough for other lnd nodes.
- payIntent.cltvDelta = uint16(r.cfg.Bitcoin.TimeLockDelta)
- }
-
- // Do bounds checking with the block padding so the router isn't left
- // with a zombie payment in case the user messes up.
- err = routing.ValidateCLTVLimit(
- payIntent.cltvLimit, payIntent.cltvDelta, true,
- )
- if err != nil {
- return payIntent, err
- }
-
- // If the user is manually specifying payment details, then the payment
- // hash may be encoded as a string.
- switch {
- case rpcPayReq.PaymentHashString != "":
- paymentHash, err := hex.DecodeString(
- rpcPayReq.PaymentHashString,
- )
- if err != nil {
- return payIntent, err
- }
-
- copy(payIntent.rHash[:], paymentHash)
-
- default:
- copy(payIntent.rHash[:], rpcPayReq.PaymentHash)
- }
-
- // Unmarshal any custom destination features.
- payIntent.destFeatures = routerrpc.UnmarshalFeatures(
- rpcPayReq.DestFeatures,
- )
-
- return payIntent, nil
-}
-
-type paymentIntentResponse struct {
- Route *route.Route
- Preimage [32]byte
- Err error
-}
-
-// dispatchPaymentIntent attempts to fully dispatch an RPC payment intent.
-// We'll either pass the payment as a whole to the channel router, or give it a
-// pre-built route. The first error this method returns denotes if we were
-// unable to save the payment. The second error returned denotes if the payment
-// didn't succeed.
-func (r *rpcServer) dispatchPaymentIntent(ctx context.Context,
- payIntent *rpcPaymentIntent) (*paymentIntentResponse, error) {
-
- // Construct a payment request to send to the channel router. If the
- // payment is successful, the route chosen will be returned. Otherwise,
- // we'll get a non-nil error.
- var (
- preImage [32]byte
- route *route.Route
- routerErr error
- )
-
- // If a route was specified, then we'll pass the route directly to the
- // router, otherwise we'll create a payment session to execute it.
- if payIntent.route == nil {
- payment := &routing.LightningPayment{
- Target: payIntent.dest,
- Amount: payIntent.msat,
- FinalCLTVDelta: payIntent.cltvDelta,
- FeeLimit: payIntent.feeLimit,
- CltvLimit: payIntent.cltvLimit,
- RouteHints: payIntent.routeHints,
- OutgoingChannelIDs: payIntent.outgoingChannelIDs,
- LastHop: payIntent.lastHop,
- PaymentRequest: payIntent.payReq,
- PayAttemptTimeout: routing.DefaultPayAttemptTimeout,
- DestCustomRecords: payIntent.destCustomRecords,
- DestFeatures: payIntent.destFeatures,
- PaymentAddr: payIntent.paymentAddr,
- Metadata: payIntent.metadata,
- BlindedPathSet: payIntent.blindedPathSet,
-
- // Don't enable multi-part payments on the main rpc.
- // Users need to use routerrpc for that.
- MaxParts: 1,
- }
- err := payment.SetPaymentHash(payIntent.rHash)
- if err != nil {
- return nil, err
- }
-
- preImage, route, routerErr = r.server.chanRouter.SendPayment(
- ctx, payment,
- )
- } else {
- var attempt *paymentsdb.HTLCAttempt
- attempt, routerErr = r.server.chanRouter.SendToRoute(
- ctx, payIntent.rHash, payIntent.route, nil,
- )
-
- if routerErr == nil {
- preImage = attempt.Settle.Preimage
- }
-
- route = payIntent.route
- }
-
- // If the route failed, then we'll return a nil save err, but a non-nil
- // routing err.
- if routerErr != nil {
- rpcsLog.Warnf("Unable to send payment: %v", routerErr)
-
- return &paymentIntentResponse{
- Err: routerErr,
- }, nil
- }
-
- return &paymentIntentResponse{
- Route: route,
- Preimage: preImage,
- }, nil
-}
-
-// sendPayment takes a paymentStream (a source of pre-built routes or payment
-// requests) and continually attempt to dispatch payment requests written to
-// the write end of the stream. Responses will also be streamed back to the
-// client via the write end of the stream. This method is by both SendToRoute
-// and SendPayment as the logic is virtually identical.
-func (r *rpcServer) sendPayment(stream *paymentStream) error {
- payChan := make(chan *rpcPaymentIntent)
- errChan := make(chan error, 1)
-
- // We don't allow payments to be sent while the daemon itself is still
- // syncing as we may be trying to sent a payment over a "stale"
- // channel.
- if !r.server.Started() {
- return ErrServerNotActive
- }
-
- // TODO(roasbeef): check payment filter to see if already used?
-
- // In order to limit the level of concurrency and prevent a client from
- // attempting to OOM the server, we'll set up a semaphore to create an
- // upper ceiling on the number of outstanding payments.
- const numOutstandingPayments = 2000
- htlcSema := make(chan struct{}, numOutstandingPayments)
- for i := 0; i < numOutstandingPayments; i++ {
- htlcSema <- struct{}{}
- }
-
- // We keep track of the running goroutines and set up a quit signal we
- // can use to request them to exit if the method returns because of an
- // encountered error.
- var wg sync.WaitGroup
- reqQuit := make(chan struct{})
- defer close(reqQuit)
-
- // Launch a new goroutine to handle reading new payment requests from
- // the client. This way we can handle errors independently of blocking
- // and waiting for the next payment request to come through.
- // TODO(joostjager): Callers expect result to come in in the same order
- // as the request were sent, but this is far from guarantueed in the
- // code below.
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- for {
- select {
- case <-reqQuit:
- return
-
- default:
- // Receive the next pending payment within the
- // stream sent by the client. If we read the
- // EOF sentinel, then the client has closed the
- // stream, and we can exit normally.
- nextPayment, err := stream.recv()
- if err == io.EOF {
- close(payChan)
- return
- } else if err != nil {
- rpcsLog.Errorf("Failed receiving from "+
- "stream: %v", err)
-
- select {
- case errChan <- err:
- default:
- }
- return
- }
-
- // Populate the next payment, either from the
- // payment request, or from the explicitly set
- // fields. If the payment proto wasn't well
- // formed, then we'll send an error reply and
- // wait for the next payment.
- payIntent, err := r.extractPaymentIntent(
- nextPayment,
- )
- if err != nil {
- if err := stream.send(&lnrpc.SendResponse{
- PaymentError: err.Error(),
- PaymentHash: payIntent.rHash[:],
- }); err != nil {
- rpcsLog.Errorf("Failed "+
- "sending on "+
- "stream: %v", err)
-
- select {
- case errChan <- err:
- default:
- }
- return
- }
- continue
- }
-
- // If the payment was well formed, then we'll
- // send to the dispatch goroutine, or exit,
- // which ever comes first.
- select {
- case payChan <- &payIntent:
- case <-reqQuit:
- return
- }
- }
- }
- }()
-
-sendLoop:
- for {
- select {
-
- // If we encounter and error either during sending or
- // receiving, we return directly, closing the stream.
- case err := <-errChan:
- return err
-
- case <-r.quit:
- return errors.New("rpc server shutting down")
-
- case payIntent, ok := <-payChan:
- // If the receive loop is done, we break the send loop
- // and wait for the ongoing payments to finish before
- // exiting.
- if !ok {
- break sendLoop
- }
-
- // We launch a new goroutine to execute the current
- // payment so we can continue to serve requests while
- // this payment is being dispatched.
- wg.Add(1)
- go func(payIntent *rpcPaymentIntent) {
- defer wg.Done()
-
- // Attempt to grab a free semaphore slot, using
- // a defer to eventually release the slot
- // regardless of payment success.
- select {
- case <-htlcSema:
- case <-reqQuit:
- return
- }
- defer func() {
- htlcSema <- struct{}{}
- }()
-
- resp, saveErr := r.dispatchPaymentIntent(
- stream.getCtx(), payIntent,
- )
-
- switch {
- // If we were unable to save the state of the
- // payment, then we'll return the error to the
- // user, and terminate.
- case saveErr != nil:
- rpcsLog.Errorf("Failed dispatching "+
- "payment intent: %v", saveErr)
-
- select {
- case errChan <- saveErr:
- default:
- }
- return
-
- // If we receive payment error than, instead of
- // terminating the stream, send error response
- // to the user.
- case resp.Err != nil:
- err := stream.send(&lnrpc.SendResponse{
- PaymentError: resp.Err.Error(),
- PaymentHash: payIntent.rHash[:],
- })
- if err != nil {
- rpcsLog.Errorf("Failed "+
- "sending error "+
- "response: %v", err)
-
- select {
- case errChan <- err:
- default:
- }
- }
- return
- }
-
- backend := r.routerBackend
- marshalledRouted, err := backend.MarshallRoute(
- resp.Route,
- )
- if err != nil {
- errChan <- err
- return
- }
-
- err = stream.send(&lnrpc.SendResponse{
- PaymentHash: payIntent.rHash[:],
- PaymentPreimage: resp.Preimage[:],
- PaymentRoute: marshalledRouted,
- })
- if err != nil {
- rpcsLog.Errorf("Failed sending "+
- "response: %v", err)
-
- select {
- case errChan <- err:
- default:
- }
- return
- }
- }(payIntent)
- }
- }
-
- // Wait for all goroutines to finish before closing the stream.
- wg.Wait()
- return nil
-}
-
-// SendPaymentSync is the synchronous non-streaming version of SendPayment.
-// This RPC is intended to be consumed by clients of the REST proxy.
-// Additionally, this RPC expects the destination's public key and the payment
-// hash (if any) to be encoded as hex strings.
-func (r *rpcServer) SendPaymentSync(ctx context.Context,
- nextPayment *lnrpc.SendRequest) (*lnrpc.SendResponse, error) {
-
- return r.sendPaymentSync(ctx, &rpcPaymentRequest{
- SendRequest: nextPayment,
- })
-}
-
-// SendToRouteSync is the synchronous non-streaming version of SendToRoute.
-// This RPC is intended to be consumed by clients of the REST proxy.
-// Additionally, this RPC expects the payment hash (if any) to be encoded as
-// hex strings.
-func (r *rpcServer) SendToRouteSync(ctx context.Context,
- req *lnrpc.SendToRouteRequest) (*lnrpc.SendResponse, error) {
-
- if req.Route == nil {
- return nil, fmt.Errorf("unable to send, no routes provided")
- }
-
- paymentRequest, err := r.unmarshallSendToRouteRequest(req)
- if err != nil {
- return nil, err
- }
-
- return r.sendPaymentSync(ctx, paymentRequest)
-}
-
-// sendPaymentSync is the synchronous variant of sendPayment. It will block and
-// wait until the payment has been fully completed.
-func (r *rpcServer) sendPaymentSync(ctx context.Context,
- nextPayment *rpcPaymentRequest) (*lnrpc.SendResponse, error) {
-
- // We don't allow payments to be sent while the daemon itself is still
- // syncing as we may be trying to sent a payment over a "stale"
- // channel.
- if !r.server.Started() {
- return nil, ErrServerNotActive
- }
-
- // First we'll attempt to map the proto describing the next payment to
- // an intent that we can pass to local sub-systems.
- payIntent, err := r.extractPaymentIntent(nextPayment)
- if err != nil {
- return nil, err
- }
-
- // With the payment validated, we'll now attempt to dispatch the
- // payment.
- resp, saveErr := r.dispatchPaymentIntent(ctx, &payIntent)
- switch {
- case saveErr != nil:
- return nil, saveErr
-
- case resp.Err != nil:
- return &lnrpc.SendResponse{
- PaymentError: resp.Err.Error(),
- PaymentHash: payIntent.rHash[:],
- }, nil
- }
-
- rpcRoute, err := r.routerBackend.MarshallRoute(resp.Route)
- if err != nil {
- return nil, err
- }
-
- return &lnrpc.SendResponse{
- PaymentHash: payIntent.rHash[:],
- PaymentPreimage: resp.Preimage[:],
- PaymentRoute: rpcRoute,
- }, nil
-}
-
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.