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" "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 ( var (
cltvLimitFlag = cli.UintFlag{ cltvLimitFlag = cli.UintFlag{
Name: "cltv_limit", Name: "cltv_limit",
@ -127,34 +133,36 @@ var sendPaymentCommand = cli.Command{
} }
// retrieveFeeLimit retrieves the fee limit based on the different fee limit // retrieveFeeLimit retrieves the fee limit based on the different fee limit
// flags passed. // flags passed. It always returns a value and doesn't rely on lnd applying a
func retrieveFeeLimit(ctx *cli.Context) (*lnrpc.FeeLimit, error) { // default.
func retrieveFeeLimit(ctx *cli.Context, amt int64) (int64, error) {
switch { switch {
case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"): 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") "can be set, but not both")
case ctx.IsSet("fee_limit"): case ctx.IsSet("fee_limit"):
return &lnrpc.FeeLimit{ return ctx.Int64("fee_limit"), nil
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: ctx.Int64("fee_limit"),
},
}, nil
case ctx.IsSet("fee_limit_percent"): case ctx.IsSet("fee_limit_percent"):
return &lnrpc.FeeLimit{ // Round up the fee limit to prevent hitting zero on small
Limit: &lnrpc.FeeLimit_Percent{ // amounts.
Percent: ctx.Int64("fee_limit_percent"), feeLimitRoundedUp :=
}, (amt*ctx.Int64("fee_limit_percent") + 99) / 100
}, nil
return feeLimitRoundedUp, nil
} }
// Since the fee limit flags aren't required, we don't return an error // If no fee limit is set, use the payment amount as a limit (100%).
// if they're not set. return amt, nil
return nil, 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("Description: %v\n", resp.GetDescription())
fmt.Printf("Amount (in satoshis): %v\n", amt) fmt.Printf("Amount (in satoshis): %v\n", amt)
fmt.Printf("Fee limit (in satoshis): %v\n", feeLimit)
fmt.Printf("Destination: %v\n", resp.GetDestination()) fmt.Printf("Destination: %v\n", resp.GetDestination())
confirm := promptForConfirmation("Confirm payment (yes/no): ") 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 // If a payment request was provided, we can exit early since all of the
// details of the payment are encoded within the request. // details of the payment are encoded within the request.
if ctx.IsSet("pay_req") { if ctx.IsSet("pay_req") {
req := &lnrpc.SendRequest{ req := &routerrpc.SendPaymentRequest{
PaymentRequest: ctx.String("pay_req"), PaymentRequest: ctx.String("pay_req"),
Amt: ctx.Int64("amt"), Amt: ctx.Int64("amt"),
} }
@ -219,7 +227,7 @@ func sendPayment(ctx *cli.Context) error {
} }
} }
req := &lnrpc.SendRequest{ req := &routerrpc.SendPaymentRequest{
Dest: destNode, Dest: destNode,
Amt: amount, Amt: amount,
DestCustomRecords: make(map[uint64][]byte), DestCustomRecords: make(map[uint64][]byte),
@ -279,18 +287,14 @@ func sendPayment(ctx *cli.Context) error {
return sendPaymentRequest(ctx, req) return sendPaymentRequest(ctx, req)
} }
func sendPaymentRequest(ctx *cli.Context, req *lnrpc.SendRequest) error { func sendPaymentRequest(ctx *cli.Context,
client, cleanUp := getClient(ctx) req *routerrpc.SendPaymentRequest) error {
defer cleanUp()
// First, we'll retrieve the fee limit value passed since it can apply conn := getClientConn(ctx, false)
// to both ways of sending payments (with the payment request or defer conn.Close()
// providing the details manually).
feeLimit, err := retrieveFeeLimit(ctx) client := lnrpc.NewLightningClient(conn)
if err != nil { routerClient := routerrpc.NewRouterClient(conn)
return err
}
req.FeeLimit = feeLimit
req.OutgoingChanId = ctx.Uint64("outgoing_chan_id") req.OutgoingChanId = ctx.Uint64("outgoing_chan_id")
if ctx.IsSet(lastHopFlag.Name) { if ctx.IsSet(lastHopFlag.Name) {
@ -303,7 +307,8 @@ func sendPaymentRequest(ctx *cli.Context, req *lnrpc.SendRequest) error {
req.LastHopPubkey = lastHop[:] 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") 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 != "" { if req.PaymentRequest != "" {
req := &lnrpc.PayReqString{PayReq: req.PaymentRequest} // Decode payment request to find out the amount.
resp, err := client.DecodePayReq(context.Background(), req) decodeReq := &lnrpc.PayReqString{PayReq: req.PaymentRequest}
decodeResp, err := client.DecodePayReq(
context.Background(), decodeReq,
)
if err != nil { if err != nil {
return err 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 { if invoiceAmt != 0 {
amt = invoiceAmt 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") { if !ctx.Bool("force") {
err := confirmPayReq(resp, amt) err := confirmPayReq(decodeResp, amt, feeLimit)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
if err := paymentStream.Send(req); err != nil { for {
return err 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{ var payInvoiceCommand = cli.Command{
@ -412,7 +436,7 @@ func payInvoice(ctx *cli.Context) error {
return fmt.Errorf("pay_req argument missing") return fmt.Errorf("pay_req argument missing")
} }
req := &lnrpc.SendRequest{ req := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq, PaymentRequest: payReq,
Amt: ctx.Int64("amt"), Amt: ctx.Int64("amt"),
DestCustomRecords: make(map[uint64][]byte), DestCustomRecords: make(map[uint64][]byte),

@ -2380,7 +2380,7 @@ func queryRoutes(ctx *cli.Context) error {
return fmt.Errorf("amt argument missing") return fmt.Errorf("amt argument missing")
} }
feeLimit, err := retrieveFeeLimit(ctx) feeLimit, err := retrieveFeeLimitLegacy(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -2403,6 +2403,38 @@ func queryRoutes(ctx *cli.Context) error {
return nil 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{ var getNetworkInfoCommand = cli.Command{
Name: "getnetworkinfo", Name: "getnetworkinfo",
Category: "Channels", Category: "Channels",