Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lnrpc+routerrpc+lncli: add msat fields #3706

Merged
merged 2 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions lnrpc/marshall_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package lnrpc

import (
"errors"

"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
)

var (
// ErrSatMsatMutualExclusive is returned when both a sat and an msat
// amount are set.
ErrSatMsatMutualExclusive = errors.New(
"sat and msat arguments are mutually exclusive",
)
)

// 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 *FeeLimit,
joostjager marked this conversation as resolved.
Show resolved Hide resolved
amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {

switch feeLimit.GetLimit().(type) {

case *FeeLimit_Fixed:
return lnwire.NewMSatFromSatoshis(
btcutil.Amount(feeLimit.GetFixed()),
)
joostjager marked this conversation as resolved.
Show resolved Hide resolved

case *FeeLimit_FixedMsat:
return lnwire.MilliSatoshi(feeLimit.GetFixedMsat())

case *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
}
}

// UnmarshallAmt returns a strong msat type for a sat/msat pair of rpc fields.
func UnmarshallAmt(amtSat, amtMsat int64) (lnwire.MilliSatoshi, error) {
if amtSat != 0 && amtMsat != 0 {
return 0, ErrSatMsatMutualExclusive
}

if amtSat != 0 {
return lnwire.NewMSatFromSatoshis(btcutil.Amount(amtSat)), nil
}

return lnwire.MilliSatoshi(amtMsat), nil
}
281 changes: 158 additions & 123 deletions lnrpc/routerrpc/router.pb.go

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion lnrpc/routerrpc/router.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,20 @@ message SendPaymentRequest {
/// The identity pubkey of the payment recipient
bytes dest = 1;

/// Number of satoshis to send.
/**
Number of satoshis to send.

The fields amt and amt_msat are mutually exclusive.
*/
int64 amt = 2;

/**
Number of millisatoshis to send.

The fields amt and amt_msat are mutually exclusive.
*/
int64 amt_msat = 12;

/// The hash to use within the payment's HTLC
bytes payment_hash = 3;

Expand Down Expand Up @@ -44,9 +55,22 @@ message SendPaymentRequest {
If this field is left to the default value of 0, only zero-fee routes will
be considered. This usually means single hop routes connecting directly to
the destination. To send the payment without a fee limit, use max int here.

The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
*/
int64 fee_limit_sat = 7;

/**
The maximum number of millisatoshis that will be paid as a fee of the
payment. If this field is left to the default value of 0, only zero-fee
routes will be considered. This usually means single hop routes connecting
directly to the destination. To send the payment without a fee limit, use
max int here.

The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
*/
int64 fee_limit_msat = 13;

/**
The channel id of the channel that must be taken to the first hop. If zero,
any channel may be used.
Expand Down
62 changes: 25 additions & 37 deletions lnrpc/routerrpc/router_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,17 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
// Currently, within the bootstrap phase of the network, we limit the
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
// satoshis.
amt := btcutil.Amount(in.Amt)
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > r.MaxPaymentMSat {
amt, err := lnrpc.UnmarshallAmt(in.Amt, in.AmtMsat)
if err != nil {
return nil, err
}
if amt > r.MaxPaymentMSat {
return nil, fmt.Errorf("payment of %v is too large, max payment "+
"allowed is %v", amt, r.MaxPaymentMSat.ToSatoshis())
}

// Unmarshall restrictions from request.
feeLimit := calculateFeeLimit(in.FeeLimit, amtMSat)
feeLimit := lnrpc.CalculateFeeLimit(in.FeeLimit, amt)

ignoredNodes := make(map[route.Vertex]struct{})
for _, ignorePubKey := range in.IgnoredNodes {
Expand Down Expand Up @@ -239,7 +241,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
// can carry `in.Amt` satoshis _including_ the total fee required on
// the route.
route, err := r.FindRoute(
sourcePubKey, targetPubKey, amtMSat, restrictions,
sourcePubKey, targetPubKey, amt, restrictions,
destTlvRecords, finalCLTVDelta,
)
if err != nil {
Expand Down Expand Up @@ -308,27 +310,6 @@ func (r *RouterBackend) rpcEdgeToPair(e *lnrpc.EdgeLocator) (
return pair, nil
}

// 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
}
}

// MarshallRoute marshalls an internal route to an rpc route struct.
func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) {
resp := &lnrpc.Route{
Expand Down Expand Up @@ -526,9 +507,12 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.CltvLimit = cltvLimit

// Take fee limit from request.
payIntent.FeeLimit = lnwire.NewMSatFromSatoshis(
btcutil.Amount(rpcPayReq.FeeLimitSat),
payIntent.FeeLimit, err = lnrpc.UnmarshallAmt(
rpcPayReq.FeeLimitSat, rpcPayReq.FeeLimitMsat,
)
if err != nil {
return nil, err
}

// Set payment attempt timeout.
if rpcPayReq.TimeoutSeconds == 0 {
Expand Down Expand Up @@ -556,6 +540,14 @@ func (r *RouterBackend) extractIntentFromSendRequest(
}
payIntent.RouteHints = routeHints

// Unmarshall either sat or msat amount from request.
reqAmt, err := lnrpc.UnmarshallAmt(
rpcPayReq.Amt, rpcPayReq.AmtMsat,
)
if err != nil {
return nil, err
}

// 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.
Expand Down Expand Up @@ -593,17 +585,15 @@ func (r *RouterBackend) extractIntentFromSendRequest(
// We override the amount to pay with the amount provided from
// the payment request.
if payReq.MilliSat == nil {
if rpcPayReq.Amt == 0 {
if reqAmt == 0 {
return nil, errors.New("amount must be " +
"specified when paying a zero amount " +
"invoice")
}

payIntent.Amount = lnwire.NewMSatFromSatoshis(
btcutil.Amount(rpcPayReq.Amt),
)
payIntent.Amount = reqAmt
} else {
if rpcPayReq.Amt != 0 {
if reqAmt != 0 {
return nil, errors.New("amount must not be " +
"specified when paying a non-zero " +
" amount invoice")
Expand Down Expand Up @@ -641,13 +631,11 @@ func (r *RouterBackend) extractIntentFromSendRequest(
}

// Amount.
if rpcPayReq.Amt == 0 {
if reqAmt == 0 {
return nil, errors.New("amount must be specified")
}

payIntent.Amount = lnwire.NewMSatFromSatoshis(
btcutil.Amount(rpcPayReq.Amt),
)
payIntent.Amount = reqAmt

// Payment hash.
copy(payIntent.PaymentHash[:], rpcPayReq.PaymentHash)
Expand Down
36 changes: 25 additions & 11 deletions lnrpc/routerrpc/router_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ var (
// and passed onto path finding.
func TestQueryRoutes(t *testing.T) {
t.Run("no mission control", func(t *testing.T) {
testQueryRoutes(t, false)
testQueryRoutes(t, false, false)
})
t.Run("no mission control and msat", func(t *testing.T) {
testQueryRoutes(t, false, true)
})
t.Run("with mission control", func(t *testing.T) {
testQueryRoutes(t, true)
testQueryRoutes(t, true, false)
})
}

func testQueryRoutes(t *testing.T, useMissionControl bool) {
func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool) {
ignoreNodeBytes, err := hex.DecodeString(ignoreNodeKey)
if err != nil {
t.Fatal(err)
Expand All @@ -57,14 +60,8 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) {

request := &lnrpc.QueryRoutesRequest{
PubKey: destKey,
Amt: 100000,
FinalCltvDelta: 100,
FeeLimit: &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: 250,
},
},
IgnoredNodes: [][]byte{ignoreNodeBytes},
IgnoredNodes: [][]byte{ignoreNodeBytes},
IgnoredEdges: []*lnrpc.EdgeLocator{{
ChannelId: 555,
DirectionReverse: true,
Expand All @@ -76,12 +73,29 @@ func testQueryRoutes(t *testing.T, useMissionControl bool) {
UseMissionControl: useMissionControl,
}

amtSat := int64(100000)
if useMsat {
request.AmtMsat = amtSat * 1000
request.FeeLimit = &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_FixedMsat{
FixedMsat: 250000,
},
}
} else {
request.Amt = amtSat
request.FeeLimit = &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: 250,
},
}
}

findRoute := func(source, target route.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
_ []tlv.Record,
finalExpiry ...uint16) (*route.Route, error) {

if int64(amt) != request.Amt*1000 {
if int64(amt) != amtSat*1000 {
t.Fatal("unexpected amount")
}

Expand Down
Loading