lncli: use routerrpc for payments

This commit is contained in:
Joost Jager 2020-03-30 12:45:14 +02:00
parent f060da3971
commit dead6a772a
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
2 changed files with 116 additions and 60 deletions

@ -20,6 +20,12 @@ import (
"github.com/urfave/cli"
)
const (
// paymentTimeoutSeconds is the default timeout for the payment loop in
// lnd. No new attempts will be started after the timeout.
paymentTimeoutSeconds = 60
)
var (
cltvLimitFlag = cli.UintFlag{
Name: "cltv_limit",
@ -127,34 +133,36 @@ var sendPaymentCommand = cli.Command{
}
// retrieveFeeLimit retrieves the fee limit based on the different fee limit
// flags passed.
func retrieveFeeLimit(ctx *cli.Context) (*lnrpc.FeeLimit, error) {
// flags passed. It always returns a value and doesn't rely on lnd applying a
// default.
func retrieveFeeLimit(ctx *cli.Context, amt int64) (int64, error) {
switch {
case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"):
return nil, fmt.Errorf("either fee_limit or fee_limit_percent " +
return 0, fmt.Errorf("either fee_limit or fee_limit_percent " +
"can be set, but not both")
case ctx.IsSet("fee_limit"):
return &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: ctx.Int64("fee_limit"),
},
}, nil
return ctx.Int64("fee_limit"), nil
case ctx.IsSet("fee_limit_percent"):
return &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Percent{
Percent: ctx.Int64("fee_limit_percent"),
},
}, nil
// Round up the fee limit to prevent hitting zero on small
// amounts.
feeLimitRoundedUp :=
(amt*ctx.Int64("fee_limit_percent") + 99) / 100
return feeLimitRoundedUp, nil
}
// Since the fee limit flags aren't required, we don't return an error
// if they're not set.
return nil, nil
// If no fee limit is set, use the payment amount as a limit (100%).
return amt, nil
}
func confirmPayReq(resp *lnrpc.PayReq, amt int64) error {
func confirmPayReq(resp *lnrpc.PayReq, amt, feeLimit int64) error {
fmt.Printf("Payment hash: %v\n", resp.GetPaymentHash())
fmt.Printf("Description: %v\n", resp.GetDescription())
fmt.Printf("Amount (in satoshis): %v\n", amt)
fmt.Printf("Fee limit (in satoshis): %v\n", feeLimit)
fmt.Printf("Destination: %v\n", resp.GetDestination())
confirm := promptForConfirmation("Confirm payment (yes/no): ")
@ -175,7 +183,7 @@ func sendPayment(ctx *cli.Context) error {
// If a payment request was provided, we can exit early since all of the
// details of the payment are encoded within the request.
if ctx.IsSet("pay_req") {
req := &lnrpc.SendRequest{
req := &routerrpc.SendPaymentRequest{
PaymentRequest: ctx.String("pay_req"),
Amt: ctx.Int64("amt"),
}
@ -219,7 +227,7 @@ func sendPayment(ctx *cli.Context) error {
}
}
req := &lnrpc.SendRequest{
req := &routerrpc.SendPaymentRequest{
Dest: destNode,
Amt: amount,
DestCustomRecords: make(map[uint64][]byte),
@ -279,18 +287,14 @@ func sendPayment(ctx *cli.Context) error {
return sendPaymentRequest(ctx, req)
}
func sendPaymentRequest(ctx *cli.Context, req *lnrpc.SendRequest) error {
client, cleanUp := getClient(ctx)
defer cleanUp()
func sendPaymentRequest(ctx *cli.Context,
req *routerrpc.SendPaymentRequest) error {
// First, we'll retrieve the fee limit value passed since it can apply
// to both ways of sending payments (with the payment request or
// providing the details manually).
feeLimit, err := retrieveFeeLimit(ctx)
if err != nil {
return err
}
req.FeeLimit = feeLimit
conn := getClientConn(ctx, false)
defer conn.Close()
client := lnrpc.NewLightningClient(conn)
routerClient := routerrpc.NewRouterClient(conn)
req.OutgoingChanId = ctx.Uint64("outgoing_chan_id")
if ctx.IsSet(lastHopFlag.Name) {
@ -303,7 +307,8 @@ func sendPaymentRequest(ctx *cli.Context, req *lnrpc.SendRequest) error {
req.LastHopPubkey = lastHop[:]
}
req.CltvLimit = uint32(ctx.Int(cltvLimitFlag.Name))
req.CltvLimit = int32(ctx.Int(cltvLimitFlag.Name))
req.TimeoutSeconds = paymentTimeoutSeconds
req.AllowSelfPayment = ctx.Bool("allow_self_payment")
@ -334,54 +339,73 @@ func sendPaymentRequest(ctx *cli.Context, req *lnrpc.SendRequest) error {
}
}
amt := req.Amt
var feeLimit int64
if req.PaymentRequest != "" {
req := &lnrpc.PayReqString{PayReq: req.PaymentRequest}
resp, err := client.DecodePayReq(context.Background(), req)
// Decode payment request to find out the amount.
decodeReq := &lnrpc.PayReqString{PayReq: req.PaymentRequest}
decodeResp, err := client.DecodePayReq(
context.Background(), decodeReq,
)
if err != nil {
return err
}
invoiceAmt := resp.GetNumSatoshis()
// If amount is present in the request, override the request
// amount.
amt := req.Amt
invoiceAmt := decodeResp.GetNumSatoshis()
if invoiceAmt != 0 {
amt = invoiceAmt
}
// Calculate fee limit based on the determined amount.
feeLimit, err = retrieveFeeLimit(ctx, amt)
if err != nil {
return err
}
// Ask for confirmation of amount and fee limit if payment is
// forced.
if !ctx.Bool("force") {
err := confirmPayReq(resp, amt)
err := confirmPayReq(decodeResp, amt, feeLimit)
if err != nil {
return err
}
}
} else {
var err error
feeLimit, err = retrieveFeeLimit(ctx, req.Amt)
if err != nil {
return err
}
}
paymentStream, err := client.SendPayment(context.Background())
req.FeeLimitSat = feeLimit
stream, err := routerClient.SendPayment(context.Background(), req)
if err != nil {
return err
}
if err := paymentStream.Send(req); err != nil {
return err
for {
status, err := stream.Recv()
if err != nil {
return err
}
if status.State != routerrpc.PaymentState_IN_FLIGHT {
printRespJSON(status)
// If we get a payment error back, we pass an error up
// to main which eventually calls fatal() and returns
// with a non-zero exit code.
if status.State != routerrpc.PaymentState_SUCCEEDED {
return errors.New(status.State.String())
}
return nil
}
}
resp, err := paymentStream.Recv()
if err != nil {
return err
}
paymentStream.CloseSend()
printRespJSON(resp)
// If we get a payment error back, we pass an error
// up to main which eventually calls fatal() and returns
// with a non-zero exit code.
if resp.PaymentError != "" {
return errors.New(resp.PaymentError)
}
return nil
}
var payInvoiceCommand = cli.Command{
@ -412,7 +436,7 @@ func payInvoice(ctx *cli.Context) error {
return fmt.Errorf("pay_req argument missing")
}
req := &lnrpc.SendRequest{
req := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
Amt: ctx.Int64("amt"),
DestCustomRecords: make(map[uint64][]byte),

@ -2380,7 +2380,7 @@ func queryRoutes(ctx *cli.Context) error {
return fmt.Errorf("amt argument missing")
}
feeLimit, err := retrieveFeeLimit(ctx)
feeLimit, err := retrieveFeeLimitLegacy(ctx)
if err != nil {
return err
}
@ -2403,6 +2403,38 @@ func queryRoutes(ctx *cli.Context) error {
return nil
}
// retrieveFeeLimitLegacy retrieves the fee limit based on the different fee
// limit flags passed. This function will eventually disappear in favor of
// retrieveFeeLimit and the new payment rpc.
func retrieveFeeLimitLegacy(ctx *cli.Context) (*lnrpc.FeeLimit, error) {
switch {
case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"):
return nil, fmt.Errorf("either fee_limit or fee_limit_percent " +
"can be set, but not both")
case ctx.IsSet("fee_limit"):
return &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: ctx.Int64("fee_limit"),
},
}, nil
case ctx.IsSet("fee_limit_percent"):
feeLimitPercent := ctx.Int64("fee_limit_percent")
if feeLimitPercent < 0 {
return nil, errors.New("negative fee limit percentage " +
"provided")
}
return &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Percent{
Percent: feeLimitPercent,
},
}, nil
}
// Since the fee limit flags aren't required, we don't return an error
// if they're not set.
return nil, nil
}
var getNetworkInfoCommand = cli.Command{
Name: "getnetworkinfo",
Category: "Channels",