From 63f079d1a43f9ae13858175eee4a9fe2e97c71ae Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 19 Apr 2018 10:27:37 -0400 Subject: [PATCH] rpcserver: add fee limit support for routing related RPCs --- rpcserver.go | 132 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 51 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 549a91b2..591d08e6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1765,6 +1765,27 @@ type rpcPaymentRequest struct { routes []*routing.Route } +// calculateFeeLimit returns the fee limit in millisatoshis. If a percentage +// based fee limit has been requested, we'll factor in the ratio provided with +// the amount of the payment. +func calculateFeeLimit(feeLimit *lnrpc.FeeLimit, + amount lnwire.MilliSatoshi) lnwire.MilliSatoshi { + + switch feeLimit.GetLimit().(type) { + case *lnrpc.FeeLimit_Fixed: + return lnwire.NewMSatFromSatoshis( + btcutil.Amount(feeLimit.GetFixed()), + ) + case *lnrpc.FeeLimit_Percent: + return amount * lnwire.MilliSatoshi(feeLimit.GetPercent()) / 100 + default: + // If a fee limit was not specified, we'll use the payment's + // amount as an upper bound in order to avoid payment attempts + // from incurring fees higher than the payment amount itself. + return amount + } +} + // 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 @@ -1831,6 +1852,7 @@ func (r *rpcServer) SendToRoute(stream lnrpc.Lightning_SendToRouteServer) error // directly to the channel router for dispatching. type rpcPaymentIntent struct { msat lnwire.MilliSatoshi + feeLimit lnwire.MilliSatoshi dest *btcec.PublicKey rHash [32]byte cltvDelta uint16 @@ -1897,57 +1919,68 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error payIntent.msat = *payReq.MilliSat } + // Calculate the fee limit that should be used for this payment. + payIntent.feeLimit = calculateFeeLimit( + rpcPayReq.FeeLimit, payIntent.msat, + ) + copy(payIntent.rHash[:], payReq.PaymentHash[:]) payIntent.dest = payReq.Destination payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry()) payIntent.routeHints = payReq.RouteHints return payIntent, nil - } else { - // 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. - if len(rpcPayReq.Dest) != 0 { - payIntent.dest, err = btcec.ParsePubKey( - rpcPayReq.Dest, btcec.S256(), - ) - if err != nil { - return payIntent, err - } + } - } else { - pubBytes, err := hex.DecodeString(rpcPayReq.DestString) - if err != nil { - return payIntent, err - } - payIntent.dest, err = btcec.ParsePubKey(pubBytes, btcec.S256()) - if err != nil { - return payIntent, err - } - } - - // 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 = lnwire.NewMSatFromSatoshis( - btcutil.Amount(rpcPayReq.Amt), + // 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. + if len(rpcPayReq.Dest) != 0 { + payIntent.dest, err = btcec.ParsePubKey( + rpcPayReq.Dest, btcec.S256(), ) - payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta) - - // If the user is manually specifying payment details, then the - // payment hash may be encoded as a 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) + if err != nil { + return payIntent, err } + + } else { + pubBytes, err := hex.DecodeString(rpcPayReq.DestString) + if err != nil { + return payIntent, err + } + payIntent.dest, err = btcec.ParsePubKey(pubBytes, btcec.S256()) + if err != nil { + return payIntent, err + } + } + + // 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 = lnwire.NewMSatFromSatoshis( + btcutil.Amount(rpcPayReq.Amt), + ) + + // Calculate the fee limit that should be used for this payment. + payIntent.feeLimit = calculateFeeLimit( + rpcPayReq.FeeLimit, payIntent.msat, + ) + + payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta) + + // If the user is manually specifying payment details, then the + // payment hash may be encoded as a 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) } // If we're in debug HTLC mode, then all outgoing HTLCs will pay to the @@ -1993,6 +2026,7 @@ func (r *rpcServer) dispatchPaymentIntent(payIntent *rpcPaymentIntent) (*routing payment := &routing.LightningPayment{ Target: payIntent.dest, Amount: payIntent.msat, + FeeLimit: payIntent.feeLimit, PaymentHash: payIntent.rHash, RouteHints: payIntent.routeHints, } @@ -2052,9 +2086,6 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error { payChan := make(chan *rpcPaymentIntent) errChan := make(chan error, 1) - // TODO(roasbeef): enforce fee limits, pass into router, ditch if exceed limit - // * limit either a %, or absolute, or iff more than sending - // 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. @@ -2241,9 +2272,6 @@ func (r *rpcServer) SendToRouteSync(ctx context.Context, func (r *rpcServer) sendPaymentSync(ctx context.Context, nextPayment *rpcPaymentRequest) (*lnrpc.SendResponse, error) { - // TODO(roasbeef): enforce fee limits, pass into router, ditch if exceed limit - // * limit either a %, or absolute, or iff more than sending - // 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. @@ -3047,13 +3075,14 @@ func (r *rpcServer) QueryRoutes(ctx context.Context, // largest payment size allotted to (2^32) - 1 mSAT or 4.29 million // satoshis. amt := btcutil.Amount(in.Amt) - feeLimit := btcutil.Amount(in.FeeLimit) amtMSat := lnwire.NewMSatFromSatoshis(amt) if amtMSat > maxPaymentMSat { return nil, fmt.Errorf("payment of %v is too large, max payment "+ "allowed is %v", amt, maxPaymentMSat.ToSatoshis()) } + feeLimit := calculateFeeLimit(in.FeeLimit, amtMSat) + // Query the channel router for a possible path to the destination that // can carry `in.Amt` satoshis _including_ the total fee required on // the route. @@ -3063,11 +3092,12 @@ func (r *rpcServer) QueryRoutes(ctx context.Context, ) if in.FinalCltvDelta == 0 { routes, findErr = r.server.chanRouter.FindRoutes( - pubKey, amtMSat, uint32(in.NumRoutes), + pubKey, amtMSat, feeLimit, uint32(in.NumRoutes), ) } else { routes, findErr = r.server.chanRouter.FindRoutes( - pubKey, amtMSat, uint32(in.NumRoutes), uint16(in.FinalCltvDelta), + pubKey, amtMSat, feeLimit, uint32(in.NumRoutes), + uint16(in.FinalCltvDelta), ) } if findErr != nil {