Merge pull request #2973 from joostjager/newer-payment-rpc
routerrpc: async payment rpc
This commit is contained in:
commit
880279b266
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,48 +34,20 @@ var (
|
||||
// ErrUnknownPaymentStatus is returned when we do not recognize the
|
||||
// existing state of a payment.
|
||||
ErrUnknownPaymentStatus = errors.New("unknown payment status")
|
||||
|
||||
// errNoAttemptInfo is returned when no attempt info is stored yet.
|
||||
errNoAttemptInfo = errors.New("unable to find attempt info for " +
|
||||
"inflight payment")
|
||||
)
|
||||
|
||||
// ControlTower tracks all outgoing payments made, whose primary purpose is to
|
||||
// prevent duplicate payments to the same payment hash. In production, a
|
||||
// persistent implementation is preferred so that tracking can survive across
|
||||
// restarts. Payments are transitioned through various payment states, and the
|
||||
// ControlTower interface provides access to driving the state transitions.
|
||||
type ControlTower interface {
|
||||
// InitPayment atomically moves the payment into the InFlight state.
|
||||
// This method checks that no suceeded payment exist for this payment
|
||||
// hash.
|
||||
InitPayment(lntypes.Hash, *PaymentCreationInfo) error
|
||||
|
||||
// RegisterAttempt atomically records the provided PaymentAttemptInfo.
|
||||
RegisterAttempt(lntypes.Hash, *PaymentAttemptInfo) error
|
||||
|
||||
// Success transitions a payment into the Succeeded state. After
|
||||
// invoking this method, InitPayment should always return an error to
|
||||
// prevent us from making duplicate payments to the same payment hash.
|
||||
// The provided preimage is atomically saved to the DB for record
|
||||
// keeping.
|
||||
Success(lntypes.Hash, lntypes.Preimage) error
|
||||
|
||||
// Fail transitions a payment into the Failed state, and records the
|
||||
// reason the payment failed. After invoking this method, InitPayment
|
||||
// should return nil on its next call for this payment hash, allowing
|
||||
// the switch to make a subsequent payment.
|
||||
Fail(lntypes.Hash, FailureReason) error
|
||||
|
||||
// FetchInFlightPayments returns all payments with status InFlight.
|
||||
FetchInFlightPayments() ([]*InFlightPayment, error)
|
||||
}
|
||||
|
||||
// paymentControl is persistent implementation of ControlTower to restrict
|
||||
// double payment sending.
|
||||
type paymentControl struct {
|
||||
// PaymentControl implements persistence for payments and payment attempts.
|
||||
type PaymentControl struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
// NewPaymentControl creates a new instance of the paymentControl.
|
||||
func NewPaymentControl(db *DB) ControlTower {
|
||||
return &paymentControl{
|
||||
// NewPaymentControl creates a new instance of the PaymentControl.
|
||||
func NewPaymentControl(db *DB) *PaymentControl {
|
||||
return &PaymentControl{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
@ -83,7 +56,7 @@ func NewPaymentControl(db *DB) ControlTower {
|
||||
// making sure it does not already exist as an in-flight payment. Then this
|
||||
// method returns successfully, the payment is guranteeed to be in the InFlight
|
||||
// state.
|
||||
func (p *paymentControl) InitPayment(paymentHash lntypes.Hash,
|
||||
func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash,
|
||||
info *PaymentCreationInfo) error {
|
||||
|
||||
var b bytes.Buffer
|
||||
@ -173,7 +146,7 @@ func (p *paymentControl) InitPayment(paymentHash lntypes.Hash,
|
||||
|
||||
// RegisterAttempt atomically records the provided PaymentAttemptInfo to the
|
||||
// DB.
|
||||
func (p *paymentControl) RegisterAttempt(paymentHash lntypes.Hash,
|
||||
func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
|
||||
attempt *PaymentAttemptInfo) error {
|
||||
|
||||
// Serialize the information before opening the db transaction.
|
||||
@ -218,10 +191,13 @@ func (p *paymentControl) RegisterAttempt(paymentHash lntypes.Hash,
|
||||
// method, InitPayment should always return an error to prevent us from making
|
||||
// duplicate payments to the same payment hash. The provided preimage is
|
||||
// atomically saved to the DB for record keeping.
|
||||
func (p *paymentControl) Success(paymentHash lntypes.Hash,
|
||||
preimage lntypes.Preimage) error {
|
||||
func (p *PaymentControl) Success(paymentHash lntypes.Hash,
|
||||
preimage lntypes.Preimage) (*route.Route, error) {
|
||||
|
||||
var updateErr error
|
||||
var (
|
||||
updateErr error
|
||||
route *route.Route
|
||||
)
|
||||
err := p.db.Batch(func(tx *bbolt.Tx) error {
|
||||
// Reset the update error, to avoid carrying over an error
|
||||
// from a previous execution of the batched db transaction.
|
||||
@ -243,21 +219,33 @@ func (p *paymentControl) Success(paymentHash lntypes.Hash,
|
||||
|
||||
// Record the successful payment info atomically to the
|
||||
// payments record.
|
||||
return bucket.Put(paymentSettleInfoKey, preimage[:])
|
||||
err = bucket.Put(paymentSettleInfoKey, preimage[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve attempt info for the notification.
|
||||
attempt, err := fetchPaymentAttempt(bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route = &attempt.Route
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updateErr
|
||||
|
||||
return route, updateErr
|
||||
}
|
||||
|
||||
// Fail transitions a payment into the Failed state, and records the reason the
|
||||
// payment failed. After invoking this method, InitPayment should return nil on
|
||||
// its next call for this payment hash, allowing the switch to make a
|
||||
// subsequent payment.
|
||||
func (p *paymentControl) Fail(paymentHash lntypes.Hash,
|
||||
func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
|
||||
reason FailureReason) error {
|
||||
|
||||
var updateErr error
|
||||
@ -291,6 +279,28 @@ func (p *paymentControl) Fail(paymentHash lntypes.Hash,
|
||||
return updateErr
|
||||
}
|
||||
|
||||
// FetchPayment returns information about a payment from the database.
|
||||
func (p *PaymentControl) FetchPayment(paymentHash lntypes.Hash) (
|
||||
*Payment, error) {
|
||||
|
||||
var payment *Payment
|
||||
err := p.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket, err := fetchPaymentBucket(tx, paymentHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payment, err = fetchPayment(bucket)
|
||||
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payment, nil
|
||||
}
|
||||
|
||||
// createPaymentBucket creates or fetches the sub-bucket assigned to this
|
||||
// payment hash.
|
||||
func createPaymentBucket(tx *bbolt.Tx, paymentHash lntypes.Hash) (
|
||||
@ -389,6 +399,17 @@ func ensureInFlight(bucket *bbolt.Bucket) error {
|
||||
}
|
||||
}
|
||||
|
||||
// fetchPaymentAttempt fetches the payment attempt from the bucket.
|
||||
func fetchPaymentAttempt(bucket *bbolt.Bucket) (*PaymentAttemptInfo, error) {
|
||||
attemptData := bucket.Get(paymentAttemptInfoKey)
|
||||
if attemptData == nil {
|
||||
return nil, errNoAttemptInfo
|
||||
}
|
||||
|
||||
r := bytes.NewReader(attemptData)
|
||||
return deserializePaymentAttemptInfo(r)
|
||||
}
|
||||
|
||||
// InFlightPayment is a wrapper around a payment that has status InFlight.
|
||||
type InFlightPayment struct {
|
||||
// Info is the PaymentCreationInfo of the in-flight payment.
|
||||
@ -402,7 +423,7 @@ type InFlightPayment struct {
|
||||
}
|
||||
|
||||
// FetchInFlightPayments returns all payments with status InFlight.
|
||||
func (p *paymentControl) FetchInFlightPayments() ([]*InFlightPayment, error) {
|
||||
func (p *PaymentControl) FetchInFlightPayments() ([]*InFlightPayment, error) {
|
||||
var inFlights []*InFlightPayment
|
||||
err := p.db.View(func(tx *bbolt.Tx) error {
|
||||
payments := tx.Bucket(paymentsRootBucket)
|
||||
@ -440,15 +461,11 @@ func (p *paymentControl) FetchInFlightPayments() ([]*InFlightPayment, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now get the attempt info, which may or may not be
|
||||
// available.
|
||||
attempt := bucket.Get(paymentAttemptInfoKey)
|
||||
if attempt != nil {
|
||||
r = bytes.NewReader(attempt)
|
||||
inFlight.Attempt, err = deserializePaymentAttemptInfo(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Now get the attempt info. It could be that there is
|
||||
// no attempt info yet.
|
||||
inFlight.Attempt, err = fetchPaymentAttempt(bucket)
|
||||
if err != nil && err != errNoAttemptInfo {
|
||||
return err
|
||||
}
|
||||
|
||||
inFlights = append(inFlights, inFlight)
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
func initDB() (*DB, error) {
|
||||
@ -131,9 +132,14 @@ func TestPaymentControlSwitchFail(t *testing.T) {
|
||||
)
|
||||
|
||||
// Verifies that status was changed to StatusSucceeded.
|
||||
if err := pControl.Success(info.PaymentHash, preimg); err != nil {
|
||||
var route *route.Route
|
||||
route, err = pControl.Success(info.PaymentHash, preimg)
|
||||
if err != nil {
|
||||
t.Fatalf("error shouldn't have been received, got: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(*route, attempt.Route) {
|
||||
t.Fatalf("unexpected route returned")
|
||||
}
|
||||
|
||||
assertPaymentStatus(t, db, info.PaymentHash, StatusSucceeded)
|
||||
assertPaymentInfo(t, db, info.PaymentHash, info, attempt, preimg, nil)
|
||||
@ -204,7 +210,7 @@ func TestPaymentControlSwitchDoubleSend(t *testing.T) {
|
||||
}
|
||||
|
||||
// After settling, the error should be ErrAlreadyPaid.
|
||||
if err := pControl.Success(info.PaymentHash, preimg); err != nil {
|
||||
if _, err := pControl.Success(info.PaymentHash, preimg); err != nil {
|
||||
t.Fatalf("error shouldn't have been received, got: %v", err)
|
||||
}
|
||||
assertPaymentStatus(t, db, info.PaymentHash, StatusSucceeded)
|
||||
@ -234,7 +240,7 @@ func TestPaymentControlSuccessesWithoutInFlight(t *testing.T) {
|
||||
}
|
||||
|
||||
// Attempt to complete the payment should fail.
|
||||
err = pControl.Success(info.PaymentHash, preimg)
|
||||
_, err = pControl.Success(info.PaymentHash, preimg)
|
||||
if err != ErrPaymentNotInitiated {
|
||||
t.Fatalf("expected ErrPaymentNotInitiated, got %v", err)
|
||||
}
|
||||
@ -337,7 +343,7 @@ func TestPaymentControlDeleteNonInFligt(t *testing.T) {
|
||||
)
|
||||
} else if p.success {
|
||||
// Verifies that status was changed to StatusSucceeded.
|
||||
err := pControl.Success(info.PaymentHash, preimg)
|
||||
_, err := pControl.Success(info.PaymentHash, preimg)
|
||||
if err != nil {
|
||||
t.Fatalf("error shouldn't have been received, got: %v", err)
|
||||
}
|
@ -95,8 +95,24 @@ const (
|
||||
FailureReasonNoRoute FailureReason = 1
|
||||
|
||||
// TODO(halseth): cancel state.
|
||||
|
||||
// TODO(joostjager): Add failure reasons for:
|
||||
// UnknownPaymentHash, FinalInvalidAmt, FinalInvalidCltv
|
||||
// LocalLiquidityInsufficient, RemoteCapacityInsufficient.
|
||||
)
|
||||
|
||||
// String returns a human readable FailureReason
|
||||
func (r FailureReason) String() string {
|
||||
switch r {
|
||||
case FailureReasonTimeout:
|
||||
return "timeout"
|
||||
case FailureReasonNoRoute:
|
||||
return "no_route"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// PaymentStatus represent current status of payment
|
||||
type PaymentStatus byte
|
||||
|
||||
|
@ -2031,6 +2031,38 @@ var cltvLimitFlag = cli.UintFlag{
|
||||
"this payment",
|
||||
}
|
||||
|
||||
// paymentFlags returns common flags for sendpayment and payinvoice.
|
||||
func paymentFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "pay_req",
|
||||
Usage: "a zpay32 encoded payment request to fulfill",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "fee_limit",
|
||||
Usage: "maximum fee allowed in satoshis when " +
|
||||
"sending the payment",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "fee_limit_percent",
|
||||
Usage: "percentage of the payment's amount used as " +
|
||||
"the maximum fee allowed when sending the " +
|
||||
"payment",
|
||||
},
|
||||
cltvLimitFlag,
|
||||
cli.Uint64Flag{
|
||||
Name: "outgoing_chan_id",
|
||||
Usage: "short channel id of the outgoing channel to " +
|
||||
"use for the first hop of the payment",
|
||||
Value: 0,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "will skip payment request confirmation",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var sendPaymentCommand = cli.Command{
|
||||
Name: "sendpayment",
|
||||
Category: "Payments",
|
||||
@ -2057,7 +2089,7 @@ var sendPaymentCommand = cli.Command{
|
||||
destination.
|
||||
`,
|
||||
ArgsUsage: "dest amt payment_hash final_cltv_delta | --pay_req=[payment request]",
|
||||
Flags: []cli.Flag{
|
||||
Flags: append(paymentFlags(),
|
||||
cli.StringFlag{
|
||||
Name: "dest, d",
|
||||
Usage: "the compressed identity pubkey of the " +
|
||||
@ -2067,17 +2099,6 @@ var sendPaymentCommand = cli.Command{
|
||||
Name: "amt, a",
|
||||
Usage: "number of satoshis to send",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "fee_limit",
|
||||
Usage: "maximum fee allowed in satoshis when sending" +
|
||||
"the payment",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "fee_limit_percent",
|
||||
Usage: "percentage of the payment's amount used as the" +
|
||||
"maximum fee allowed when sending the payment",
|
||||
},
|
||||
cltvLimitFlag,
|
||||
cli.StringFlag{
|
||||
Name: "payment_hash, r",
|
||||
Usage: "the hash to use within the payment's HTLC",
|
||||
@ -2086,25 +2107,11 @@ var sendPaymentCommand = cli.Command{
|
||||
Name: "debug_send",
|
||||
Usage: "use the debug rHash when sending the HTLC",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pay_req",
|
||||
Usage: "a zpay32 encoded payment request to fulfill",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "final_cltv_delta",
|
||||
Usage: "the number of blocks the last hop has to reveal the preimage",
|
||||
},
|
||||
cli.Uint64Flag{
|
||||
Name: "outgoing_chan_id",
|
||||
Usage: "short channel id of the outgoing channel to " +
|
||||
"use for the first hop of the payment",
|
||||
Value: 0,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "will skip payment request confirmation",
|
||||
},
|
||||
},
|
||||
),
|
||||
Action: sendPayment,
|
||||
}
|
||||
|
||||
@ -2134,26 +2141,7 @@ func retrieveFeeLimit(ctx *cli.Context) (*lnrpc.FeeLimit, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func confirmPayReq(ctx *cli.Context, client lnrpc.LightningClient, payReq string) error {
|
||||
ctxb := context.Background()
|
||||
|
||||
req := &lnrpc.PayReqString{PayReq: payReq}
|
||||
resp, err := client.DecodePayReq(ctxb, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the amount was not included in the invoice, then we let
|
||||
// the payee specify the amount of satoshis they wish to send.
|
||||
amt := resp.GetNumSatoshis()
|
||||
if amt == 0 {
|
||||
amt = ctx.Int64("amt")
|
||||
if amt == 0 {
|
||||
return fmt.Errorf("amount must be specified when " +
|
||||
"paying a zero amount invoice")
|
||||
}
|
||||
}
|
||||
|
||||
func confirmPayReq(resp *lnrpc.PayReq, amt int64) error {
|
||||
fmt.Printf("Description: %v\n", resp.GetDescription())
|
||||
fmt.Printf("Amount (in satoshis): %v\n", amt)
|
||||
fmt.Printf("Destination: %v\n", resp.GetDestination())
|
||||
@ -2167,45 +2155,27 @@ func confirmPayReq(ctx *cli.Context, client lnrpc.LightningClient, payReq string
|
||||
}
|
||||
|
||||
func sendPayment(ctx *cli.Context) error {
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
// Show command help if no arguments provided
|
||||
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
|
||||
cli.ShowCommandHelp(ctx, "sendpayment")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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") {
|
||||
if !ctx.Bool("force") {
|
||||
err = confirmPayReq(ctx, client, ctx.String("pay_req"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
req := &lnrpc.SendRequest{
|
||||
PaymentRequest: ctx.String("pay_req"),
|
||||
Amt: ctx.Int64("amt"),
|
||||
FeeLimit: feeLimit,
|
||||
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
|
||||
CltvLimit: uint32(ctx.Int(cltvLimitFlag.Name)),
|
||||
}
|
||||
|
||||
return sendPaymentRequest(client, req)
|
||||
return sendPaymentRequest(ctx, req)
|
||||
}
|
||||
|
||||
var (
|
||||
destNode []byte
|
||||
amount int64
|
||||
err error
|
||||
)
|
||||
|
||||
args := ctx.Args()
|
||||
@ -2239,9 +2209,8 @@ func sendPayment(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
req := &lnrpc.SendRequest{
|
||||
Dest: destNode,
|
||||
Amt: amount,
|
||||
FeeLimit: feeLimit,
|
||||
Dest: destNode,
|
||||
Amt: amount,
|
||||
}
|
||||
|
||||
if ctx.Bool("debug_send") && (ctx.IsSet("payment_hash") || args.Present()) {
|
||||
@ -2280,10 +2249,47 @@ func sendPayment(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
return sendPaymentRequest(client, req)
|
||||
return sendPaymentRequest(ctx, req)
|
||||
}
|
||||
|
||||
func sendPaymentRequest(client lnrpc.LightningClient, req *lnrpc.SendRequest) error {
|
||||
func sendPaymentRequest(ctx *cli.Context, req *lnrpc.SendRequest) error {
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
// 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
|
||||
|
||||
req.OutgoingChanId = ctx.Uint64("outgoing_chan_id")
|
||||
req.CltvLimit = uint32(ctx.Int(cltvLimitFlag.Name))
|
||||
|
||||
amt := req.Amt
|
||||
|
||||
if req.PaymentRequest != "" {
|
||||
req := &lnrpc.PayReqString{PayReq: req.PaymentRequest}
|
||||
resp, err := client.DecodePayReq(context.Background(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
invoiceAmt := resp.GetNumSatoshis()
|
||||
if invoiceAmt != 0 {
|
||||
amt = invoiceAmt
|
||||
}
|
||||
|
||||
if !ctx.Bool("force") {
|
||||
err := confirmPayReq(resp, amt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paymentStream, err := client.SendPayment(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -2325,45 +2331,18 @@ var payInvoiceCommand = cli.Command{
|
||||
Category: "Payments",
|
||||
Usage: "Pay an invoice over lightning.",
|
||||
ArgsUsage: "pay_req",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "pay_req",
|
||||
Usage: "a zpay32 encoded payment request to fulfill",
|
||||
},
|
||||
Flags: append(paymentFlags(),
|
||||
cli.Int64Flag{
|
||||
Name: "amt",
|
||||
Usage: "(optional) number of satoshis to fulfill the " +
|
||||
"invoice",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "fee_limit",
|
||||
Usage: "maximum fee allowed in satoshis when sending " +
|
||||
"the payment",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "fee_limit_percent",
|
||||
Usage: "percentage of the payment's amount used as the" +
|
||||
"maximum fee allowed when sending the payment",
|
||||
},
|
||||
cltvLimitFlag,
|
||||
cli.Uint64Flag{
|
||||
Name: "outgoing_chan_id",
|
||||
Usage: "short channel id of the outgoing channel to " +
|
||||
"use for the first hop of the payment",
|
||||
Value: 0,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "will skip payment request confirmation",
|
||||
},
|
||||
},
|
||||
),
|
||||
Action: actionDecorator(payInvoice),
|
||||
}
|
||||
|
||||
func payInvoice(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
client, cleanUp := getClient(ctx)
|
||||
defer cleanUp()
|
||||
|
||||
var payReq string
|
||||
switch {
|
||||
@ -2375,27 +2354,12 @@ func payInvoice(ctx *cli.Context) error {
|
||||
return fmt.Errorf("pay_req argument missing")
|
||||
}
|
||||
|
||||
feeLimit, err := retrieveFeeLimit(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ctx.Bool("force") {
|
||||
err = confirmPayReq(ctx, client, payReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
req := &lnrpc.SendRequest{
|
||||
PaymentRequest: payReq,
|
||||
Amt: ctx.Int64("amt"),
|
||||
FeeLimit: feeLimit,
|
||||
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
|
||||
CltvLimit: uint32(ctx.Int(cltvLimitFlag.Name)),
|
||||
}
|
||||
|
||||
return sendPaymentRequest(client, req)
|
||||
return sendPaymentRequest(ctx, req)
|
||||
}
|
||||
|
||||
var sendToRouteCommand = cli.Command{
|
||||
|
@ -5,10 +5,8 @@ package routerrpc
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
)
|
||||
@ -45,12 +43,6 @@ type Config struct {
|
||||
// server will find the macaroon named DefaultRouterMacFilename.
|
||||
NetworkDir string
|
||||
|
||||
// ActiveNetParams are the network parameters of the primary network
|
||||
// that the route is operating on. This is necessary so we can ensure
|
||||
// that we receive payment requests that send to destinations on our
|
||||
// network.
|
||||
ActiveNetParams *chaincfg.Params
|
||||
|
||||
// MacService is the main macaroon service that we'll use to handle
|
||||
// authentication for the Router rpc server.
|
||||
MacService *macaroons.Service
|
||||
|
@ -23,6 +23,46 @@ var _ = math.Inf
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type PaymentState int32
|
||||
|
||||
const (
|
||||
//*
|
||||
//Payment is still in flight.
|
||||
PaymentState_IN_FLIGHT PaymentState = 0
|
||||
//*
|
||||
//Payment completed successfully.
|
||||
PaymentState_SUCCEEDED PaymentState = 1
|
||||
//*
|
||||
//There are more routes to try, but the payment timeout was exceeded.
|
||||
PaymentState_FAILED_TIMEOUT PaymentState = 2
|
||||
//*
|
||||
//All possible routes were tried and failed permanently. Or were no
|
||||
//routes to the destination at all.
|
||||
PaymentState_FAILED_NO_ROUTE PaymentState = 3
|
||||
)
|
||||
|
||||
var PaymentState_name = map[int32]string{
|
||||
0: "IN_FLIGHT",
|
||||
1: "SUCCEEDED",
|
||||
2: "FAILED_TIMEOUT",
|
||||
3: "FAILED_NO_ROUTE",
|
||||
}
|
||||
|
||||
var PaymentState_value = map[string]int32{
|
||||
"IN_FLIGHT": 0,
|
||||
"SUCCEEDED": 1,
|
||||
"FAILED_TIMEOUT": 2,
|
||||
"FAILED_NO_ROUTE": 3,
|
||||
}
|
||||
|
||||
func (x PaymentState) String() string {
|
||||
return proto.EnumName(PaymentState_name, int32(x))
|
||||
}
|
||||
|
||||
func (PaymentState) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{0}
|
||||
}
|
||||
|
||||
type Failure_FailureCode int32
|
||||
|
||||
const (
|
||||
@ -109,161 +149,238 @@ func (x Failure_FailureCode) String() string {
|
||||
}
|
||||
|
||||
func (Failure_FailureCode) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{6, 0}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{7, 0}
|
||||
}
|
||||
|
||||
type PaymentRequest struct {
|
||||
type SendPaymentRequest struct {
|
||||
/// The identity pubkey of the payment recipient
|
||||
Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"`
|
||||
/// Number of satoshis to send.
|
||||
Amt int64 `protobuf:"varint,2,opt,name=amt,proto3" json:"amt,omitempty"`
|
||||
/// The hash to use within the payment's HTLC
|
||||
PaymentHash []byte `protobuf:"bytes,3,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
|
||||
//*
|
||||
//A serialized BOLT-11 payment request that contains all information
|
||||
//required to dispatch the payment. If the pay req is invalid, or expired,
|
||||
//an error will be returned.
|
||||
PayReq string `protobuf:"bytes,1,opt,name=pay_req,json=payReq,proto3" json:"pay_req,omitempty"`
|
||||
//The CLTV delta from the current height that should be used to set the
|
||||
//timelock for the final hop.
|
||||
FinalCltvDelta int32 `protobuf:"varint,4,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"`
|
||||
//*
|
||||
//An absolute limit on the highest fee we should pay when looking for a route
|
||||
//to the destination. Routes with fees higher than this will be ignored, if
|
||||
//there are no routes with a fee below this amount, an error will be
|
||||
//returned.
|
||||
FeeLimitSat int64 `protobuf:"varint,2,opt,name=fee_limit_sat,json=feeLimitSat,proto3" json:"fee_limit_sat,omitempty"`
|
||||
//*
|
||||
//An absolute limit on the cumulative CLTV value along the route for this
|
||||
//payment. Routes with total CLTV values higher than this will be ignored,
|
||||
//if there are no routes with a CLTV value below this amount, an error will
|
||||
//be returned.
|
||||
CltvLimit int32 `protobuf:"varint,3,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
|
||||
//A bare-bones invoice for a payment within the Lightning Network. With the
|
||||
//details of the invoice, the sender has all the data necessary to send a
|
||||
//payment to the recipient. The amount in the payment request may be zero. In
|
||||
//that case it is required to set the amt field as well. If no payment request
|
||||
//is specified, the following fields are required: dest, amt and payment_hash.
|
||||
PaymentRequest string `protobuf:"bytes,5,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
|
||||
//*
|
||||
//An upper limit on the amount of time we should spend when attempting to
|
||||
//fulfill the payment. This is expressed in seconds. If we cannot make a
|
||||
//successful payment within this time frame, an error will be returned.
|
||||
TimeoutSeconds int32 `protobuf:"varint,4,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"`
|
||||
//This field must be non-zero.
|
||||
TimeoutSeconds int32 `protobuf:"varint,6,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"`
|
||||
//*
|
||||
//The maximum number of satoshis 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.
|
||||
FeeLimitSat int64 `protobuf:"varint,7,opt,name=fee_limit_sat,json=feeLimitSat,proto3" json:"fee_limit_sat,omitempty"`
|
||||
//*
|
||||
//The channel id of the channel that must be taken to the first hop. If zero,
|
||||
//any channel may be used.
|
||||
OutgoingChannelId int64 `protobuf:"varint,5,opt,name=outgoing_channel_id,json=outgoingChannelId,proto3" json:"outgoing_channel_id,omitempty"`
|
||||
OutgoingChanId uint64 `protobuf:"varint,8,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"`
|
||||
//*
|
||||
//An optional maximum total time lock for the route. If zero, there is no
|
||||
//maximum enforced.
|
||||
CltvLimit int32 `protobuf:"varint,9,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PaymentRequest) Reset() { *m = PaymentRequest{} }
|
||||
func (m *PaymentRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PaymentRequest) ProtoMessage() {}
|
||||
func (*PaymentRequest) Descriptor() ([]byte, []int) {
|
||||
func (m *SendPaymentRequest) Reset() { *m = SendPaymentRequest{} }
|
||||
func (m *SendPaymentRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*SendPaymentRequest) ProtoMessage() {}
|
||||
func (*SendPaymentRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{0}
|
||||
}
|
||||
|
||||
func (m *PaymentRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PaymentRequest.Unmarshal(m, b)
|
||||
func (m *SendPaymentRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_SendPaymentRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PaymentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PaymentRequest.Marshal(b, m, deterministic)
|
||||
func (m *SendPaymentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_SendPaymentRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PaymentRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PaymentRequest.Merge(m, src)
|
||||
func (m *SendPaymentRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_SendPaymentRequest.Merge(m, src)
|
||||
}
|
||||
func (m *PaymentRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_PaymentRequest.Size(m)
|
||||
func (m *SendPaymentRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_SendPaymentRequest.Size(m)
|
||||
}
|
||||
func (m *PaymentRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PaymentRequest.DiscardUnknown(m)
|
||||
func (m *SendPaymentRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_SendPaymentRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PaymentRequest proto.InternalMessageInfo
|
||||
var xxx_messageInfo_SendPaymentRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *PaymentRequest) GetPayReq() string {
|
||||
func (m *SendPaymentRequest) GetDest() []byte {
|
||||
if m != nil {
|
||||
return m.PayReq
|
||||
return m.Dest
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) GetAmt() int64 {
|
||||
if m != nil {
|
||||
return m.Amt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) GetPaymentHash() []byte {
|
||||
if m != nil {
|
||||
return m.PaymentHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) GetFinalCltvDelta() int32 {
|
||||
if m != nil {
|
||||
return m.FinalCltvDelta
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) GetPaymentRequest() string {
|
||||
if m != nil {
|
||||
return m.PaymentRequest
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PaymentRequest) GetFeeLimitSat() int64 {
|
||||
if m != nil {
|
||||
return m.FeeLimitSat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *PaymentRequest) GetCltvLimit() int32 {
|
||||
if m != nil {
|
||||
return m.CltvLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *PaymentRequest) GetTimeoutSeconds() int32 {
|
||||
func (m *SendPaymentRequest) GetTimeoutSeconds() int32 {
|
||||
if m != nil {
|
||||
return m.TimeoutSeconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *PaymentRequest) GetOutgoingChannelId() int64 {
|
||||
func (m *SendPaymentRequest) GetFeeLimitSat() int64 {
|
||||
if m != nil {
|
||||
return m.OutgoingChannelId
|
||||
return m.FeeLimitSat
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type PaymentResponse struct {
|
||||
//*
|
||||
//The payment hash that we paid to. Provided so callers are able to map
|
||||
//responses (which may be streaming) back to their original requests.
|
||||
PayHash []byte `protobuf:"bytes,1,opt,name=pay_hash,json=payHash,proto3" json:"pay_hash,omitempty"`
|
||||
//*
|
||||
//The pre-image of the payment successfully completed.
|
||||
PreImage []byte `protobuf:"bytes,2,opt,name=pre_image,json=preImage,proto3" json:"pre_image,omitempty"`
|
||||
//*
|
||||
//If not an empty string, then a string representation of the payment error.
|
||||
PaymentErr string `protobuf:"bytes,3,opt,name=payment_err,json=paymentErr,proto3" json:"payment_err,omitempty"`
|
||||
func (m *SendPaymentRequest) GetOutgoingChanId() uint64 {
|
||||
if m != nil {
|
||||
return m.OutgoingChanId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *SendPaymentRequest) GetCltvLimit() int32 {
|
||||
if m != nil {
|
||||
return m.CltvLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type TrackPaymentRequest struct {
|
||||
/// The hash of the payment to look up.
|
||||
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PaymentResponse) Reset() { *m = PaymentResponse{} }
|
||||
func (m *PaymentResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*PaymentResponse) ProtoMessage() {}
|
||||
func (*PaymentResponse) Descriptor() ([]byte, []int) {
|
||||
func (m *TrackPaymentRequest) Reset() { *m = TrackPaymentRequest{} }
|
||||
func (m *TrackPaymentRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*TrackPaymentRequest) ProtoMessage() {}
|
||||
func (*TrackPaymentRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{1}
|
||||
}
|
||||
|
||||
func (m *PaymentResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PaymentResponse.Unmarshal(m, b)
|
||||
func (m *TrackPaymentRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_TrackPaymentRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PaymentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PaymentResponse.Marshal(b, m, deterministic)
|
||||
func (m *TrackPaymentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_TrackPaymentRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PaymentResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PaymentResponse.Merge(m, src)
|
||||
func (m *TrackPaymentRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_TrackPaymentRequest.Merge(m, src)
|
||||
}
|
||||
func (m *PaymentResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_PaymentResponse.Size(m)
|
||||
func (m *TrackPaymentRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_TrackPaymentRequest.Size(m)
|
||||
}
|
||||
func (m *PaymentResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PaymentResponse.DiscardUnknown(m)
|
||||
func (m *TrackPaymentRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_TrackPaymentRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PaymentResponse proto.InternalMessageInfo
|
||||
var xxx_messageInfo_TrackPaymentRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *PaymentResponse) GetPayHash() []byte {
|
||||
func (m *TrackPaymentRequest) GetPaymentHash() []byte {
|
||||
if m != nil {
|
||||
return m.PayHash
|
||||
return m.PaymentHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PaymentResponse) GetPreImage() []byte {
|
||||
type PaymentStatus struct {
|
||||
/// Current state the payment is in.
|
||||
State PaymentState `protobuf:"varint,1,opt,name=state,proto3,enum=routerrpc.PaymentState" json:"state,omitempty"`
|
||||
//*
|
||||
//The pre-image of the payment when state is SUCCEEDED.
|
||||
Preimage []byte `protobuf:"bytes,2,opt,name=preimage,proto3" json:"preimage,omitempty"`
|
||||
//*
|
||||
//The taken route when state is SUCCEEDED.
|
||||
Route *lnrpc.Route `protobuf:"bytes,3,opt,name=route,proto3" json:"route,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PaymentStatus) Reset() { *m = PaymentStatus{} }
|
||||
func (m *PaymentStatus) String() string { return proto.CompactTextString(m) }
|
||||
func (*PaymentStatus) ProtoMessage() {}
|
||||
func (*PaymentStatus) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{2}
|
||||
}
|
||||
|
||||
func (m *PaymentStatus) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PaymentStatus.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PaymentStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PaymentStatus.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PaymentStatus) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PaymentStatus.Merge(m, src)
|
||||
}
|
||||
func (m *PaymentStatus) XXX_Size() int {
|
||||
return xxx_messageInfo_PaymentStatus.Size(m)
|
||||
}
|
||||
func (m *PaymentStatus) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PaymentStatus.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PaymentStatus proto.InternalMessageInfo
|
||||
|
||||
func (m *PaymentStatus) GetState() PaymentState {
|
||||
if m != nil {
|
||||
return m.PreImage
|
||||
return m.State
|
||||
}
|
||||
return PaymentState_IN_FLIGHT
|
||||
}
|
||||
|
||||
func (m *PaymentStatus) GetPreimage() []byte {
|
||||
if m != nil {
|
||||
return m.Preimage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PaymentResponse) GetPaymentErr() string {
|
||||
func (m *PaymentStatus) GetRoute() *lnrpc.Route {
|
||||
if m != nil {
|
||||
return m.PaymentErr
|
||||
return m.Route
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
type RouteFeeRequest struct {
|
||||
@ -282,7 +399,7 @@ func (m *RouteFeeRequest) Reset() { *m = RouteFeeRequest{} }
|
||||
func (m *RouteFeeRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*RouteFeeRequest) ProtoMessage() {}
|
||||
func (*RouteFeeRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{2}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{3}
|
||||
}
|
||||
|
||||
func (m *RouteFeeRequest) XXX_Unmarshal(b []byte) error {
|
||||
@ -336,7 +453,7 @@ func (m *RouteFeeResponse) Reset() { *m = RouteFeeResponse{} }
|
||||
func (m *RouteFeeResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*RouteFeeResponse) ProtoMessage() {}
|
||||
func (*RouteFeeResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{3}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{4}
|
||||
}
|
||||
|
||||
func (m *RouteFeeResponse) XXX_Unmarshal(b []byte) error {
|
||||
@ -385,7 +502,7 @@ func (m *SendToRouteRequest) Reset() { *m = SendToRouteRequest{} }
|
||||
func (m *SendToRouteRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*SendToRouteRequest) ProtoMessage() {}
|
||||
func (*SendToRouteRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{4}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{5}
|
||||
}
|
||||
|
||||
func (m *SendToRouteRequest) XXX_Unmarshal(b []byte) error {
|
||||
@ -434,7 +551,7 @@ func (m *SendToRouteResponse) Reset() { *m = SendToRouteResponse{} }
|
||||
func (m *SendToRouteResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*SendToRouteResponse) ProtoMessage() {}
|
||||
func (*SendToRouteResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{5}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{6}
|
||||
}
|
||||
|
||||
func (m *SendToRouteResponse) XXX_Unmarshal(b []byte) error {
|
||||
@ -495,7 +612,7 @@ func (m *Failure) Reset() { *m = Failure{} }
|
||||
func (m *Failure) String() string { return proto.CompactTextString(m) }
|
||||
func (*Failure) ProtoMessage() {}
|
||||
func (*Failure) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{6}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{7}
|
||||
}
|
||||
|
||||
func (m *Failure) XXX_Unmarshal(b []byte) error {
|
||||
@ -632,7 +749,7 @@ func (m *ChannelUpdate) Reset() { *m = ChannelUpdate{} }
|
||||
func (m *ChannelUpdate) String() string { return proto.CompactTextString(m) }
|
||||
func (*ChannelUpdate) ProtoMessage() {}
|
||||
func (*ChannelUpdate) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{7}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{8}
|
||||
}
|
||||
|
||||
func (m *ChannelUpdate) XXX_Unmarshal(b []byte) error {
|
||||
@ -747,7 +864,7 @@ func (m *ResetMissionControlRequest) Reset() { *m = ResetMissionControlR
|
||||
func (m *ResetMissionControlRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ResetMissionControlRequest) ProtoMessage() {}
|
||||
func (*ResetMissionControlRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{8}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{9}
|
||||
}
|
||||
|
||||
func (m *ResetMissionControlRequest) XXX_Unmarshal(b []byte) error {
|
||||
@ -778,7 +895,7 @@ func (m *ResetMissionControlResponse) Reset() { *m = ResetMissionControl
|
||||
func (m *ResetMissionControlResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ResetMissionControlResponse) ProtoMessage() {}
|
||||
func (*ResetMissionControlResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{9}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{10}
|
||||
}
|
||||
|
||||
func (m *ResetMissionControlResponse) XXX_Unmarshal(b []byte) error {
|
||||
@ -809,7 +926,7 @@ func (m *QueryMissionControlRequest) Reset() { *m = QueryMissionControlR
|
||||
func (m *QueryMissionControlRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*QueryMissionControlRequest) ProtoMessage() {}
|
||||
func (*QueryMissionControlRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{10}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{11}
|
||||
}
|
||||
|
||||
func (m *QueryMissionControlRequest) XXX_Unmarshal(b []byte) error {
|
||||
@ -842,7 +959,7 @@ func (m *QueryMissionControlResponse) Reset() { *m = QueryMissionControl
|
||||
func (m *QueryMissionControlResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*QueryMissionControlResponse) ProtoMessage() {}
|
||||
func (*QueryMissionControlResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{11}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{12}
|
||||
}
|
||||
|
||||
func (m *QueryMissionControlResponse) XXX_Unmarshal(b []byte) error {
|
||||
@ -889,7 +1006,7 @@ func (m *NodeHistory) Reset() { *m = NodeHistory{} }
|
||||
func (m *NodeHistory) String() string { return proto.CompactTextString(m) }
|
||||
func (*NodeHistory) ProtoMessage() {}
|
||||
func (*NodeHistory) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{12}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{13}
|
||||
}
|
||||
|
||||
func (m *NodeHistory) XXX_Unmarshal(b []byte) error {
|
||||
@ -957,7 +1074,7 @@ func (m *ChannelHistory) Reset() { *m = ChannelHistory{} }
|
||||
func (m *ChannelHistory) String() string { return proto.CompactTextString(m) }
|
||||
func (*ChannelHistory) ProtoMessage() {}
|
||||
func (*ChannelHistory) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{13}
|
||||
return fileDescriptor_7a0613f69d37b0a5, []int{14}
|
||||
}
|
||||
|
||||
func (m *ChannelHistory) XXX_Unmarshal(b []byte) error {
|
||||
@ -1007,9 +1124,11 @@ func (m *ChannelHistory) GetSuccessProb() float32 {
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("routerrpc.PaymentState", PaymentState_name, PaymentState_value)
|
||||
proto.RegisterEnum("routerrpc.Failure_FailureCode", Failure_FailureCode_name, Failure_FailureCode_value)
|
||||
proto.RegisterType((*PaymentRequest)(nil), "routerrpc.PaymentRequest")
|
||||
proto.RegisterType((*PaymentResponse)(nil), "routerrpc.PaymentResponse")
|
||||
proto.RegisterType((*SendPaymentRequest)(nil), "routerrpc.SendPaymentRequest")
|
||||
proto.RegisterType((*TrackPaymentRequest)(nil), "routerrpc.TrackPaymentRequest")
|
||||
proto.RegisterType((*PaymentStatus)(nil), "routerrpc.PaymentStatus")
|
||||
proto.RegisterType((*RouteFeeRequest)(nil), "routerrpc.RouteFeeRequest")
|
||||
proto.RegisterType((*RouteFeeResponse)(nil), "routerrpc.RouteFeeResponse")
|
||||
proto.RegisterType((*SendToRouteRequest)(nil), "routerrpc.SendToRouteRequest")
|
||||
@ -1027,98 +1146,106 @@ func init() {
|
||||
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
|
||||
|
||||
var fileDescriptor_7a0613f69d37b0a5 = []byte{
|
||||
// 1456 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xdb, 0x72, 0x1a, 0x47,
|
||||
0x13, 0xfe, 0x31, 0x12, 0x87, 0xe6, 0xa0, 0xd5, 0xe8, 0x60, 0x84, 0x2c, 0x5b, 0xde, 0xff, 0xff,
|
||||
0x1d, 0x95, 0xcb, 0x25, 0x55, 0x48, 0xd9, 0x95, 0xab, 0xa4, 0x30, 0x2c, 0xd1, 0x96, 0x60, 0xc1,
|
||||
0x03, 0xc8, 0x56, 0x72, 0x31, 0x35, 0x62, 0x47, 0xb0, 0x11, 0xec, 0xae, 0x76, 0x87, 0xc4, 0xe4,
|
||||
0x01, 0xf2, 0x3a, 0xc9, 0x4d, 0x6e, 0x73, 0x97, 0x87, 0xc8, 0xdb, 0xa4, 0x66, 0x66, 0x97, 0x83,
|
||||
0x8c, 0x92, 0x5c, 0xc1, 0x7e, 0xdf, 0x37, 0xdd, 0xd3, 0x3d, 0xdd, 0x3d, 0x03, 0xfb, 0x81, 0x37,
|
||||
0xe5, 0x2c, 0x08, 0xfc, 0xc1, 0x99, 0xfa, 0x77, 0xea, 0x07, 0x1e, 0xf7, 0x50, 0x76, 0x8e, 0x97,
|
||||
0xb3, 0x81, 0x3f, 0x50, 0xa8, 0xfe, 0x47, 0x02, 0x8a, 0x1d, 0x3a, 0x9b, 0x30, 0x97, 0x63, 0x76,
|
||||
0x37, 0x65, 0x21, 0x47, 0x8f, 0x21, 0xed, 0xd3, 0x19, 0x09, 0xd8, 0x5d, 0x29, 0x71, 0x9c, 0x38,
|
||||
0xc9, 0xe2, 0x94, 0x4f, 0x67, 0x98, 0xdd, 0x21, 0x1d, 0x0a, 0x37, 0x8c, 0x91, 0xb1, 0x33, 0x71,
|
||||
0x38, 0x09, 0x29, 0x2f, 0x3d, 0x3a, 0x4e, 0x9c, 0x24, 0x71, 0xee, 0x86, 0xb1, 0xa6, 0xc0, 0xba,
|
||||
0x94, 0xa3, 0x23, 0x80, 0xc1, 0x98, 0xff, 0xa0, 0x44, 0xa5, 0xe4, 0x71, 0xe2, 0x64, 0x13, 0x67,
|
||||
0x05, 0x22, 0x15, 0xe8, 0x33, 0xd8, 0xe2, 0xce, 0x84, 0x79, 0x53, 0x4e, 0x42, 0x36, 0xf0, 0x5c,
|
||||
0x3b, 0x2c, 0x6d, 0x48, 0x4d, 0x31, 0x82, 0xbb, 0x0a, 0x45, 0xa7, 0xb0, 0xe3, 0x4d, 0xf9, 0xd0,
|
||||
0x73, 0xdc, 0x21, 0x19, 0x8c, 0xa8, 0xeb, 0xb2, 0x31, 0x71, 0xec, 0xd2, 0xa6, 0xf4, 0xb8, 0x1d,
|
||||
0x53, 0x35, 0xc5, 0x98, 0xb6, 0xfe, 0x3d, 0x6c, 0xcd, 0xc3, 0x08, 0x7d, 0xcf, 0x0d, 0x19, 0x3a,
|
||||
0x80, 0x8c, 0x88, 0x63, 0x44, 0xc3, 0x91, 0x0c, 0x24, 0x8f, 0x45, 0x5c, 0xe7, 0x34, 0x1c, 0xa1,
|
||||
0x43, 0xc8, 0xfa, 0x01, 0x23, 0xce, 0x84, 0x0e, 0x99, 0x8c, 0x22, 0x8f, 0x33, 0x7e, 0xc0, 0x4c,
|
||||
0xf1, 0x8d, 0x9e, 0x41, 0xce, 0x57, 0xa6, 0x08, 0x0b, 0x02, 0x19, 0x43, 0x16, 0x43, 0x04, 0x19,
|
||||
0x41, 0xa0, 0x7f, 0x05, 0x5b, 0x58, 0xe4, 0xb2, 0xc1, 0x58, 0x9c, 0x33, 0x04, 0x1b, 0x36, 0x0b,
|
||||
0x79, 0xe4, 0x47, 0xfe, 0x17, 0x79, 0xa4, 0x93, 0xe5, 0x44, 0xa5, 0xe8, 0x44, 0xe4, 0x48, 0xb7,
|
||||
0x41, 0x5b, 0xac, 0x8f, 0x36, 0x7b, 0x02, 0x9a, 0x38, 0x1f, 0x11, 0xae, 0xc8, 0xf1, 0x44, 0xac,
|
||||
0x4a, 0xc8, 0x55, 0xc5, 0x08, 0x6f, 0x30, 0xd6, 0x0a, 0x29, 0x47, 0x2f, 0x54, 0x0a, 0xc9, 0xd8,
|
||||
0x1b, 0xdc, 0x12, 0x9b, 0x8d, 0xe9, 0x2c, 0x32, 0x5f, 0x10, 0x70, 0xd3, 0x1b, 0xdc, 0xd6, 0x05,
|
||||
0xa8, 0x7f, 0x07, 0xa8, 0xcb, 0x5c, 0xbb, 0xe7, 0x49, 0x5f, 0xf1, 0x46, 0x9f, 0x43, 0x3e, 0x0e,
|
||||
0x6e, 0x29, 0x31, 0x71, 0xc0, 0x32, 0x39, 0x3a, 0x6c, 0xca, 0x52, 0x91, 0x66, 0x73, 0x95, 0xfc,
|
||||
0xe9, 0xd8, 0x15, 0xf5, 0xa2, 0xcc, 0x28, 0x4a, 0x27, 0xb0, 0xb3, 0x62, 0x3c, 0x8a, 0xa2, 0x0c,
|
||||
0x22, 0x8d, 0x2a, 0xad, 0x89, 0x79, 0x5a, 0xe5, 0x37, 0x7a, 0x05, 0xe9, 0x1b, 0xea, 0x8c, 0xa7,
|
||||
0x41, 0x6c, 0x18, 0x9d, 0xce, 0x2b, 0xf2, 0xb4, 0xa1, 0x18, 0x1c, 0x4b, 0xf4, 0x9f, 0xd3, 0x90,
|
||||
0x8e, 0x40, 0x54, 0x81, 0x8d, 0x81, 0x67, 0x2b, 0x8b, 0xc5, 0xca, 0xd3, 0x4f, 0x97, 0xc5, 0xbf,
|
||||
0x35, 0xcf, 0x66, 0x58, 0x6a, 0x51, 0x05, 0xf6, 0x22, 0x53, 0x24, 0xf4, 0xa6, 0xc1, 0x80, 0x11,
|
||||
0x7f, 0x7a, 0x7d, 0xcb, 0x66, 0xd1, 0x69, 0xef, 0x44, 0x64, 0x57, 0x72, 0x1d, 0x49, 0xa1, 0xaf,
|
||||
0xa1, 0x18, 0x97, 0xda, 0xd4, 0xb7, 0x29, 0x67, 0xf2, 0xec, 0x73, 0x95, 0xd2, 0x92, 0xc7, 0xa8,
|
||||
0xe2, 0xfa, 0x92, 0xc7, 0x85, 0xc1, 0xf2, 0xa7, 0x28, 0xab, 0x11, 0x1f, 0x0f, 0xd4, 0xe9, 0x89,
|
||||
0xba, 0xde, 0xc0, 0x19, 0x01, 0xc8, 0x73, 0xd3, 0xa1, 0xe0, 0xb9, 0x8e, 0xe7, 0x92, 0x70, 0x44,
|
||||
0x49, 0xe5, 0xf5, 0x1b, 0x59, 0xcb, 0x79, 0x9c, 0x93, 0x60, 0x77, 0x44, 0x2b, 0xaf, 0xdf, 0x88,
|
||||
0xd2, 0x93, 0xdd, 0xc3, 0x3e, 0xfa, 0x4e, 0x30, 0x2b, 0xa5, 0x8e, 0x13, 0x27, 0x05, 0x2c, 0x1b,
|
||||
0xca, 0x90, 0x08, 0xda, 0x85, 0xcd, 0x9b, 0x31, 0x1d, 0x86, 0xa5, 0xb4, 0xa4, 0xd4, 0x87, 0xfe,
|
||||
0xe7, 0x06, 0xe4, 0x96, 0x52, 0x80, 0xf2, 0x90, 0xc1, 0x46, 0xd7, 0xc0, 0x97, 0x46, 0x5d, 0xfb,
|
||||
0x0f, 0x2a, 0xc1, 0x6e, 0xdf, 0xba, 0xb0, 0xda, 0xef, 0x2d, 0xd2, 0xa9, 0x5e, 0xb5, 0x0c, 0xab,
|
||||
0x47, 0xce, 0xab, 0xdd, 0x73, 0x2d, 0x81, 0x9e, 0x40, 0xc9, 0xb4, 0x6a, 0x6d, 0x8c, 0x8d, 0x5a,
|
||||
0x6f, 0xce, 0x55, 0x5b, 0xed, 0xbe, 0xd5, 0xd3, 0x1e, 0xa1, 0x67, 0x70, 0xd8, 0x30, 0xad, 0x6a,
|
||||
0x93, 0x2c, 0x34, 0xb5, 0x66, 0xef, 0x92, 0x18, 0x1f, 0x3a, 0x26, 0xbe, 0xd2, 0x92, 0xeb, 0x04,
|
||||
0xe7, 0xbd, 0x66, 0x2d, 0xb6, 0xb0, 0x81, 0x0e, 0x60, 0x4f, 0x09, 0xd4, 0x12, 0xd2, 0x6b, 0xb7,
|
||||
0x49, 0xb7, 0xdd, 0xb6, 0xb4, 0x4d, 0xb4, 0x0d, 0x05, 0xd3, 0xba, 0xac, 0x36, 0xcd, 0x3a, 0xc1,
|
||||
0x46, 0xb5, 0xd9, 0xd2, 0x52, 0x68, 0x07, 0xb6, 0xee, 0xeb, 0xd2, 0xc2, 0x44, 0xac, 0x6b, 0x5b,
|
||||
0x66, 0xdb, 0x22, 0x97, 0x06, 0xee, 0x9a, 0x6d, 0x4b, 0xcb, 0xa0, 0x7d, 0x40, 0xab, 0xd4, 0x79,
|
||||
0xab, 0x5a, 0xd3, 0xb2, 0x68, 0x0f, 0xb6, 0x57, 0xf1, 0x0b, 0xe3, 0x4a, 0x03, 0x91, 0x06, 0xb5,
|
||||
0x31, 0xf2, 0xd6, 0x68, 0xb6, 0xdf, 0x93, 0x96, 0x69, 0x99, 0xad, 0x7e, 0x4b, 0xcb, 0xa1, 0x5d,
|
||||
0xd0, 0x1a, 0x86, 0x41, 0x4c, 0xab, 0xdb, 0x6f, 0x34, 0xcc, 0x9a, 0x69, 0x58, 0x3d, 0x2d, 0xaf,
|
||||
0x3c, 0xaf, 0x0b, 0xbc, 0x20, 0x16, 0xd4, 0xce, 0xab, 0x96, 0x65, 0x34, 0x49, 0xdd, 0xec, 0x56,
|
||||
0xdf, 0x36, 0x8d, 0xba, 0x56, 0x44, 0x47, 0x70, 0xd0, 0x33, 0x5a, 0x9d, 0x36, 0xae, 0xe2, 0x2b,
|
||||
0x12, 0xf3, 0x8d, 0xaa, 0xd9, 0xec, 0x63, 0x43, 0xdb, 0x42, 0xcf, 0xe1, 0x08, 0x1b, 0xef, 0xfa,
|
||||
0x26, 0x36, 0xea, 0xc4, 0x6a, 0xd7, 0x0d, 0xd2, 0x30, 0xaa, 0xbd, 0x3e, 0x36, 0x48, 0xcb, 0xec,
|
||||
0x76, 0x4d, 0xeb, 0x1b, 0x4d, 0x43, 0xff, 0x83, 0xe3, 0xb9, 0x64, 0x6e, 0xe0, 0x9e, 0x6a, 0x5b,
|
||||
0xc4, 0x17, 0x9f, 0xa7, 0x65, 0x7c, 0xe8, 0x91, 0x8e, 0x61, 0x60, 0x0d, 0xa1, 0x32, 0xec, 0x2f,
|
||||
0xdc, 0x2b, 0x07, 0x91, 0xef, 0x1d, 0xc1, 0x75, 0x0c, 0xdc, 0xaa, 0x5a, 0xe2, 0x80, 0x57, 0xb8,
|
||||
0x5d, 0xb1, 0xed, 0x05, 0x77, 0x7f, 0xdb, 0x7b, 0xfa, 0x2f, 0x49, 0x28, 0xac, 0x14, 0x3d, 0x7a,
|
||||
0x02, 0xd9, 0xd0, 0x19, 0xba, 0x94, 0x8b, 0x56, 0x56, 0x5d, 0xbe, 0x00, 0xe4, 0x05, 0x30, 0xa2,
|
||||
0x8e, 0xab, 0xc6, 0x8b, 0xea, 0xb6, 0xac, 0x44, 0xe4, 0x70, 0x79, 0x0c, 0x69, 0xd1, 0x33, 0x62,
|
||||
0x96, 0x27, 0x65, 0x83, 0xa4, 0xc4, 0xa7, 0x69, 0x0b, 0xab, 0x62, 0x7e, 0x85, 0x9c, 0x4e, 0x7c,
|
||||
0xd9, 0x3b, 0x05, 0xbc, 0x00, 0xd0, 0x7f, 0xa1, 0x30, 0x61, 0x61, 0x48, 0x87, 0x8c, 0xa8, 0xfa,
|
||||
0x07, 0xa9, 0xc8, 0x47, 0x60, 0x43, 0x60, 0x42, 0x14, 0xf7, 0xaf, 0x12, 0x6d, 0x2a, 0x51, 0x04,
|
||||
0x2a, 0xd1, 0xfd, 0xf1, 0xc9, 0x69, 0xd4, 0x66, 0xcb, 0xe3, 0x93, 0x53, 0xf4, 0x12, 0xb6, 0x55,
|
||||
0x2f, 0x3b, 0xae, 0x33, 0x99, 0x4e, 0x54, 0x4f, 0xa7, 0xe5, 0x96, 0xb7, 0x64, 0x4f, 0x2b, 0x5c,
|
||||
0xb6, 0xf6, 0x01, 0x64, 0xae, 0x69, 0xc8, 0xc4, 0xe4, 0x2e, 0x65, 0xa4, 0xb1, 0xb4, 0xf8, 0x6e,
|
||||
0x30, 0x79, 0x09, 0x89, 0x79, 0x1e, 0x88, 0x69, 0x92, 0x55, 0xd4, 0x0d, 0x63, 0x58, 0xe4, 0x71,
|
||||
0xee, 0x81, 0x7e, 0x5c, 0x78, 0xc8, 0x2d, 0x79, 0x50, 0xb8, 0xf4, 0xf0, 0x12, 0xb6, 0xd9, 0x47,
|
||||
0x1e, 0x50, 0xe2, 0xf9, 0xf4, 0x6e, 0xca, 0x88, 0x4d, 0x39, 0x2d, 0xe5, 0x65, 0x72, 0xb7, 0x24,
|
||||
0xd1, 0x96, 0x78, 0x9d, 0x72, 0xaa, 0x3f, 0x81, 0x32, 0x66, 0x21, 0xe3, 0x2d, 0x27, 0x0c, 0x1d,
|
||||
0xcf, 0xad, 0x79, 0x2e, 0x0f, 0xbc, 0x71, 0x74, 0x01, 0xe8, 0x47, 0x70, 0xb8, 0x96, 0x55, 0x13,
|
||||
0x5c, 0x2c, 0x7e, 0x37, 0x65, 0xc1, 0x6c, 0xfd, 0xe2, 0x0b, 0x38, 0x5c, 0xcb, 0x46, 0xe3, 0xff,
|
||||
0x15, 0x6c, 0xba, 0x9e, 0xcd, 0xc2, 0x52, 0xe2, 0x38, 0x79, 0x92, 0xab, 0xec, 0x2f, 0xcd, 0x4d,
|
||||
0xcb, 0xb3, 0xd9, 0xb9, 0x13, 0x72, 0x2f, 0x98, 0x61, 0x25, 0xd2, 0x7f, 0x4f, 0x40, 0x6e, 0x09,
|
||||
0x46, 0xfb, 0x90, 0x8a, 0x66, 0xb4, 0x2a, 0xaa, 0xe8, 0x0b, 0xbd, 0x80, 0xe2, 0x98, 0x86, 0x9c,
|
||||
0x88, 0x91, 0x4d, 0xc4, 0x21, 0x45, 0xf7, 0xdd, 0x3d, 0x14, 0x7d, 0x09, 0x8f, 0x3d, 0x3e, 0x62,
|
||||
0x81, 0x7c, 0x2f, 0x90, 0x70, 0x3a, 0x18, 0xb0, 0x30, 0x24, 0x7e, 0xe0, 0x5d, 0xcb, 0x52, 0x7b,
|
||||
0x84, 0x1f, 0xa2, 0xd1, 0x6b, 0xc8, 0x44, 0x35, 0x22, 0x9e, 0x23, 0x62, 0xeb, 0x07, 0x9f, 0x8e,
|
||||
0xfc, 0x78, 0xf7, 0x73, 0xa9, 0xfe, 0x6b, 0x02, 0x8a, 0xab, 0x24, 0x7a, 0x2a, 0xab, 0x3f, 0x7e,
|
||||
0xad, 0x24, 0xe4, 0x61, 0x2e, 0x21, 0xff, 0x3a, 0x96, 0x0a, 0xec, 0x4e, 0x1c, 0x97, 0xf8, 0xcc,
|
||||
0xa5, 0x63, 0xe7, 0x27, 0x46, 0xe2, 0x87, 0x44, 0x52, 0xaa, 0xd7, 0x72, 0x48, 0x87, 0xfc, 0x4a,
|
||||
0xd0, 0x1b, 0x32, 0xe8, 0x15, 0xac, 0xf2, 0x5b, 0x12, 0x52, 0xf2, 0xca, 0x0e, 0x50, 0x1d, 0x72,
|
||||
0xe2, 0x0a, 0x8f, 0x5e, 0x4d, 0x68, 0x39, 0xe2, 0xd5, 0x07, 0x61, 0xb9, 0xbc, 0x8e, 0x8a, 0x8e,
|
||||
0xfc, 0x02, 0x34, 0x23, 0xe4, 0xce, 0x44, 0xdc, 0x86, 0xd1, 0x9b, 0x06, 0x2d, 0xeb, 0xef, 0x3d,
|
||||
0x94, 0xca, 0x87, 0x6b, 0xb9, 0xc8, 0x58, 0x53, 0x6d, 0x29, 0x7a, 0x55, 0xa0, 0xa3, 0x25, 0xed,
|
||||
0xa7, 0x4f, 0x99, 0xf2, 0xd3, 0x87, 0xe8, 0xc8, 0x9a, 0x0d, 0x3b, 0x6b, 0x2a, 0x1d, 0xfd, 0x7f,
|
||||
0x79, 0x07, 0x0f, 0xf6, 0x49, 0xf9, 0xc5, 0x3f, 0xc9, 0x16, 0x5e, 0xd6, 0xb4, 0xc4, 0x8a, 0x97,
|
||||
0x87, 0x1b, 0x6a, 0xc5, 0xcb, 0xdf, 0x74, 0xd6, 0xdb, 0xcf, 0xbf, 0x3d, 0x1b, 0x3a, 0x7c, 0x34,
|
||||
0xbd, 0x3e, 0x1d, 0x78, 0x93, 0xb3, 0xb1, 0x33, 0x1c, 0x71, 0xd7, 0x71, 0x87, 0x2e, 0xe3, 0x3f,
|
||||
0x7a, 0xc1, 0xed, 0xd9, 0xd8, 0xb5, 0xcf, 0xe4, 0x2b, 0xed, 0x6c, 0x6e, 0xee, 0x3a, 0x25, 0x1f,
|
||||
0xf8, 0x5f, 0xfc, 0x15, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x3e, 0x2a, 0xde, 0x10, 0x0c, 0x00, 0x00,
|
||||
// 1575 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0xdd, 0x72, 0x22, 0xc7,
|
||||
0x15, 0x36, 0x02, 0x09, 0x71, 0xf8, 0xd1, 0xa8, 0xa5, 0xd5, 0xb2, 0x68, 0xb5, 0x96, 0x27, 0xc9,
|
||||
0x5a, 0xb5, 0xe5, 0x48, 0x09, 0xa9, 0x75, 0xf9, 0x2a, 0x29, 0x16, 0x1a, 0x33, 0x59, 0x98, 0x91,
|
||||
0x1b, 0x58, 0x5b, 0xc9, 0x45, 0x57, 0x0b, 0x5a, 0x30, 0x25, 0x98, 0xc1, 0xd3, 0x8d, 0xb3, 0xca,
|
||||
0x45, 0xee, 0x92, 0xd7, 0x49, 0x9e, 0x20, 0x97, 0x79, 0x87, 0xbc, 0x4d, 0xaa, 0xbb, 0x07, 0x18,
|
||||
0x10, 0xda, 0xf8, 0x4a, 0xcc, 0x77, 0xbe, 0x3e, 0xe7, 0xf4, 0xf9, 0xeb, 0x23, 0x38, 0x89, 0xc2,
|
||||
0xb9, 0xe4, 0x51, 0x34, 0x1b, 0x5c, 0x99, 0x5f, 0x97, 0xb3, 0x28, 0x94, 0x21, 0xca, 0x2d, 0xf1,
|
||||
0x4a, 0x2e, 0x9a, 0x0d, 0x0c, 0x6a, 0xff, 0x67, 0x07, 0x50, 0x97, 0x07, 0xc3, 0x6b, 0xf6, 0x30,
|
||||
0xe5, 0x81, 0x24, 0xfc, 0xc7, 0x39, 0x17, 0x12, 0x21, 0xc8, 0x0c, 0xb9, 0x90, 0xe5, 0xd4, 0x79,
|
||||
0xea, 0xa2, 0x40, 0xf4, 0x6f, 0x64, 0x41, 0x9a, 0x4d, 0x65, 0x79, 0xe7, 0x3c, 0x75, 0x91, 0x26,
|
||||
0xea, 0x27, 0xfa, 0x02, 0x0a, 0x33, 0x73, 0x8e, 0x8e, 0x99, 0x18, 0x97, 0xd3, 0x9a, 0x9d, 0x8f,
|
||||
0xb1, 0x16, 0x13, 0x63, 0x74, 0x01, 0xd6, 0x9d, 0x1f, 0xb0, 0x09, 0x1d, 0x4c, 0xe4, 0x4f, 0x74,
|
||||
0xc8, 0x27, 0x92, 0x95, 0x33, 0xe7, 0xa9, 0x8b, 0x5d, 0x52, 0xd2, 0x78, 0x7d, 0x22, 0x7f, 0x6a,
|
||||
0x28, 0x14, 0x7d, 0x09, 0x07, 0x0b, 0x65, 0x91, 0xf1, 0xa2, 0xbc, 0x7b, 0x9e, 0xba, 0xc8, 0x91,
|
||||
0xd2, 0x6c, 0xdd, 0xb7, 0x2f, 0xe1, 0x40, 0xfa, 0x53, 0x1e, 0xce, 0x25, 0x15, 0x7c, 0x10, 0x06,
|
||||
0x43, 0x51, 0xde, 0x33, 0x1a, 0x63, 0xb8, 0x6b, 0x50, 0x64, 0x43, 0xf1, 0x8e, 0x73, 0x3a, 0xf1,
|
||||
0xa7, 0xbe, 0xa4, 0x82, 0xc9, 0x72, 0x56, 0xbb, 0x9e, 0xbf, 0xe3, 0xbc, 0xad, 0xb0, 0x2e, 0x93,
|
||||
0xca, 0xbf, 0x70, 0x2e, 0x47, 0xa1, 0x1f, 0x8c, 0xe8, 0x60, 0xcc, 0x02, 0xea, 0x0f, 0xcb, 0xfb,
|
||||
0xe7, 0xa9, 0x8b, 0x0c, 0x29, 0x2d, 0xf0, 0xfa, 0x98, 0x05, 0xce, 0x10, 0x9d, 0x01, 0xe8, 0x3b,
|
||||
0x68, 0x75, 0xe5, 0x9c, 0xb6, 0x98, 0x53, 0x88, 0xd6, 0x65, 0x7f, 0x03, 0x47, 0xbd, 0x88, 0x0d,
|
||||
0xee, 0x37, 0x02, 0xb9, 0x19, 0xa2, 0xd4, 0xa3, 0x10, 0xd9, 0x7f, 0x83, 0x62, 0x7c, 0xa8, 0x2b,
|
||||
0x99, 0x9c, 0x0b, 0xf4, 0x6b, 0xd8, 0x15, 0x92, 0x49, 0xae, 0xc9, 0xa5, 0xea, 0xf3, 0xcb, 0x65,
|
||||
0xe6, 0x2e, 0x13, 0x44, 0x4e, 0x0c, 0x0b, 0x55, 0x60, 0x7f, 0x16, 0x71, 0x7f, 0xca, 0x46, 0x5c,
|
||||
0x27, 0xa7, 0x40, 0x96, 0xdf, 0xc8, 0x86, 0x5d, 0x7d, 0x58, 0xa7, 0x26, 0x5f, 0x2d, 0x5c, 0x4e,
|
||||
0x02, 0xa5, 0x86, 0x28, 0x8c, 0x18, 0x91, 0xfd, 0x7b, 0x38, 0xd0, 0xdf, 0x4d, 0xce, 0x3f, 0x95,
|
||||
0xfe, 0xe7, 0x90, 0x65, 0x53, 0x13, 0x47, 0x53, 0x02, 0x7b, 0x6c, 0xaa, 0x42, 0x68, 0x0f, 0xc1,
|
||||
0x5a, 0x9d, 0x17, 0xb3, 0x30, 0x10, 0x5c, 0x85, 0x55, 0x29, 0x57, 0x51, 0x55, 0x29, 0x98, 0xaa,
|
||||
0x53, 0x29, 0x7d, 0xaa, 0x14, 0xe3, 0x4d, 0xce, 0x3b, 0x82, 0x49, 0xf4, 0xda, 0x64, 0x93, 0x4e,
|
||||
0xc2, 0xc1, 0xbd, 0xaa, 0x0f, 0xf6, 0x10, 0xab, 0x2f, 0x2a, 0xb8, 0x1d, 0x0e, 0xee, 0x1b, 0x0a,
|
||||
0xb4, 0xff, 0x6c, 0xea, 0xb4, 0x17, 0x1a, 0xdf, 0x7f, 0x76, 0x78, 0x57, 0x21, 0xd8, 0x79, 0x3a,
|
||||
0x04, 0x14, 0x8e, 0xd6, 0x94, 0xc7, 0xb7, 0x48, 0x46, 0x36, 0xb5, 0x11, 0xd9, 0xaf, 0x20, 0x7b,
|
||||
0xc7, 0xfc, 0xc9, 0x3c, 0x5a, 0x28, 0x46, 0x89, 0x34, 0x35, 0x8d, 0x84, 0x2c, 0x28, 0xf6, 0x3f,
|
||||
0xb2, 0x90, 0x8d, 0x41, 0x54, 0x85, 0xcc, 0x20, 0x1c, 0x2e, 0xb2, 0xfb, 0xea, 0xf1, 0xb1, 0xc5,
|
||||
0xdf, 0x7a, 0x38, 0xe4, 0x44, 0x73, 0x51, 0x15, 0x9e, 0xc5, 0xaa, 0xa8, 0x08, 0xe7, 0xd1, 0x80,
|
||||
0xd3, 0xd9, 0xfc, 0xf6, 0x9e, 0x3f, 0xc4, 0x09, 0x3f, 0x8a, 0x85, 0x5d, 0x2d, 0xbb, 0xd6, 0x22,
|
||||
0xf4, 0x07, 0x28, 0xa9, 0x8a, 0x0e, 0xf8, 0x84, 0xce, 0x67, 0x43, 0xb6, 0x2c, 0x82, 0x72, 0xc2,
|
||||
0x62, 0xdd, 0x10, 0xfa, 0x5a, 0x4e, 0x8a, 0x83, 0xe4, 0x27, 0x3a, 0x85, 0xdc, 0x58, 0x4e, 0x06,
|
||||
0x26, 0x7b, 0x19, 0xdd, 0x14, 0xfb, 0x0a, 0xd0, 0x79, 0xb3, 0xa1, 0x18, 0x06, 0x7e, 0x18, 0x50,
|
||||
0x31, 0x66, 0xb4, 0xfa, 0xf6, 0x6b, 0xdd, 0xac, 0x05, 0x92, 0xd7, 0x60, 0x77, 0xcc, 0xaa, 0x6f,
|
||||
0xbf, 0x46, 0x9f, 0x43, 0x5e, 0xb7, 0x0c, 0xff, 0x38, 0xf3, 0xa3, 0x07, 0xdd, 0xa5, 0x45, 0xa2,
|
||||
0xbb, 0x08, 0x6b, 0x04, 0x1d, 0xc3, 0xee, 0xdd, 0x84, 0x8d, 0x84, 0xee, 0xcc, 0x22, 0x31, 0x1f,
|
||||
0xf6, 0x7f, 0x33, 0x90, 0x4f, 0x84, 0x00, 0x15, 0x60, 0x9f, 0xe0, 0x2e, 0x26, 0x1f, 0x70, 0xc3,
|
||||
0xfa, 0x0c, 0x95, 0xe1, 0xb8, 0xef, 0xbe, 0x77, 0xbd, 0xef, 0x5d, 0x7a, 0x5d, 0xbb, 0xe9, 0x60,
|
||||
0xb7, 0x47, 0x5b, 0xb5, 0x6e, 0xcb, 0x4a, 0xa1, 0x97, 0x50, 0x76, 0xdc, 0xba, 0x47, 0x08, 0xae,
|
||||
0xf7, 0x96, 0xb2, 0x5a, 0xc7, 0xeb, 0xbb, 0x3d, 0x6b, 0x07, 0x7d, 0x0e, 0xa7, 0x4d, 0xc7, 0xad,
|
||||
0xb5, 0xe9, 0x8a, 0x53, 0x6f, 0xf7, 0x3e, 0x50, 0xfc, 0xc3, 0xb5, 0x43, 0x6e, 0xac, 0xf4, 0x36,
|
||||
0x42, 0xab, 0xd7, 0xae, 0x2f, 0x34, 0x64, 0xd0, 0x0b, 0x78, 0x66, 0x08, 0xe6, 0x08, 0xed, 0x79,
|
||||
0x1e, 0xed, 0x7a, 0x9e, 0x6b, 0xed, 0xa2, 0x43, 0x28, 0x3a, 0xee, 0x87, 0x5a, 0xdb, 0x69, 0x50,
|
||||
0x82, 0x6b, 0xed, 0x8e, 0xb5, 0x87, 0x8e, 0xe0, 0x60, 0x93, 0x97, 0x55, 0x2a, 0x16, 0x3c, 0xcf,
|
||||
0x75, 0x3c, 0x97, 0x7e, 0xc0, 0xa4, 0xeb, 0x78, 0xae, 0xb5, 0x8f, 0x4e, 0x00, 0xad, 0x8b, 0x5a,
|
||||
0x9d, 0x5a, 0xdd, 0xca, 0xa1, 0x67, 0x70, 0xb8, 0x8e, 0xbf, 0xc7, 0x37, 0x16, 0xa8, 0x30, 0x18,
|
||||
0xc7, 0xe8, 0x3b, 0xdc, 0xf6, 0xbe, 0xa7, 0x1d, 0xc7, 0x75, 0x3a, 0xfd, 0x8e, 0x95, 0x47, 0xc7,
|
||||
0x60, 0x35, 0x31, 0xa6, 0x8e, 0xdb, 0xed, 0x37, 0x9b, 0x4e, 0xdd, 0xc1, 0x6e, 0xcf, 0x2a, 0x18,
|
||||
0xcb, 0xdb, 0x2e, 0x5e, 0x54, 0x07, 0xea, 0xad, 0x9a, 0xeb, 0xe2, 0x36, 0x6d, 0x38, 0xdd, 0xda,
|
||||
0xbb, 0x36, 0x6e, 0x58, 0x25, 0x74, 0x06, 0x2f, 0x7a, 0xb8, 0x73, 0xed, 0x91, 0x1a, 0xb9, 0xa1,
|
||||
0x0b, 0x79, 0xb3, 0xe6, 0xb4, 0xfb, 0x04, 0x5b, 0x07, 0xe8, 0x0b, 0x38, 0x23, 0xf8, 0xbb, 0xbe,
|
||||
0x43, 0x70, 0x83, 0xba, 0x5e, 0x03, 0xd3, 0x26, 0xae, 0xf5, 0xfa, 0x04, 0xd3, 0x8e, 0xd3, 0xed,
|
||||
0x3a, 0xee, 0xb7, 0x96, 0x85, 0x7e, 0x09, 0xe7, 0x4b, 0xca, 0x52, 0xc1, 0x06, 0xeb, 0x50, 0xdd,
|
||||
0x6f, 0x91, 0x4f, 0x17, 0xff, 0xd0, 0xa3, 0xd7, 0x18, 0x13, 0x0b, 0xa1, 0x0a, 0x9c, 0xac, 0xcc,
|
||||
0x1b, 0x03, 0xb1, 0xed, 0x23, 0x25, 0xbb, 0xc6, 0xa4, 0x53, 0x73, 0x55, 0x82, 0xd7, 0x64, 0xc7,
|
||||
0xca, 0xed, 0x95, 0x6c, 0xd3, 0xed, 0x67, 0xf6, 0x3f, 0xd3, 0x50, 0x5c, 0x2b, 0x7a, 0xf4, 0x12,
|
||||
0x72, 0xc2, 0x1f, 0x05, 0x4c, 0xaa, 0x56, 0x36, 0x5d, 0xbe, 0x02, 0xf4, 0xd4, 0x1f, 0x33, 0x3f,
|
||||
0x30, 0xe3, 0xc5, 0x74, 0x5b, 0x4e, 0x23, 0x7a, 0xb8, 0x3c, 0x87, 0xec, 0xe2, 0xd5, 0x48, 0xeb,
|
||||
0x06, 0xd9, 0x1b, 0x98, 0xd7, 0xe2, 0x25, 0xe4, 0xd4, 0xfc, 0x12, 0x92, 0x4d, 0x67, 0xba, 0x77,
|
||||
0x8a, 0x64, 0x05, 0xa0, 0x5f, 0x40, 0x71, 0xca, 0x85, 0x60, 0x23, 0x4e, 0x4d, 0xfd, 0x83, 0x66,
|
||||
0x14, 0x62, 0xb0, 0xa9, 0x30, 0x45, 0x5a, 0xf4, 0xaf, 0x21, 0xed, 0x1a, 0x52, 0x0c, 0x1a, 0xd2,
|
||||
0xe6, 0xf8, 0x94, 0x2c, 0x6e, 0xb3, 0xe4, 0xf8, 0x94, 0x0c, 0xbd, 0x81, 0x43, 0xd3, 0xcb, 0x7e,
|
||||
0xe0, 0x4f, 0xe7, 0x53, 0xd3, 0xd3, 0x59, 0xed, 0xf2, 0x81, 0xee, 0x69, 0x83, 0xeb, 0xd6, 0x7e,
|
||||
0x01, 0xfb, 0xb7, 0x4c, 0x70, 0x35, 0xb9, 0xf5, 0x5b, 0x58, 0x24, 0x59, 0xf5, 0xdd, 0xe4, 0x5c,
|
||||
0x89, 0xd4, 0x3c, 0x8f, 0xd4, 0x34, 0xc9, 0x19, 0xd1, 0x1d, 0xe7, 0x44, 0xc5, 0x71, 0x69, 0x81,
|
||||
0x7d, 0x5c, 0x59, 0xc8, 0x27, 0x2c, 0x18, 0x5c, 0x5b, 0x78, 0x03, 0x87, 0xfc, 0xa3, 0x8c, 0x18,
|
||||
0x0d, 0x67, 0xec, 0xc7, 0x39, 0xa7, 0x43, 0x26, 0x59, 0xb9, 0xa0, 0x83, 0x7b, 0xa0, 0x05, 0x9e,
|
||||
0xc6, 0x1b, 0x4c, 0x32, 0xfb, 0x25, 0x54, 0x08, 0x17, 0x5c, 0x76, 0x7c, 0x21, 0xfc, 0x30, 0xa8,
|
||||
0x87, 0x81, 0x8c, 0xc2, 0x49, 0xfc, 0x00, 0xd8, 0x67, 0x70, 0xba, 0x55, 0x6a, 0x26, 0xb8, 0x3a,
|
||||
0xfc, 0xdd, 0x9c, 0x47, 0x0f, 0xdb, 0x0f, 0xbf, 0x87, 0xd3, 0xad, 0xd2, 0x78, 0xfc, 0x7f, 0x05,
|
||||
0xbb, 0x41, 0x38, 0xe4, 0xa2, 0x9c, 0x3a, 0x4f, 0x5f, 0xe4, 0xab, 0x27, 0x89, 0xb9, 0xe9, 0x86,
|
||||
0x43, 0xde, 0xf2, 0x85, 0x0c, 0xa3, 0x07, 0x62, 0x48, 0xf6, 0xbf, 0x53, 0x90, 0x4f, 0xc0, 0xe8,
|
||||
0x04, 0xf6, 0xe2, 0x19, 0x6d, 0x8a, 0x2a, 0xfe, 0x42, 0xaf, 0xa1, 0x34, 0x61, 0x42, 0x52, 0x35,
|
||||
0xb2, 0xa9, 0x4a, 0x52, 0xfc, 0xde, 0x6d, 0xa0, 0xe8, 0x1b, 0x78, 0x1e, 0xca, 0x31, 0x8f, 0xcc,
|
||||
0x5a, 0x22, 0xe6, 0x83, 0x01, 0x17, 0x82, 0xce, 0xa2, 0xf0, 0x56, 0x97, 0xda, 0x0e, 0x79, 0x4a,
|
||||
0x8c, 0xde, 0xc2, 0x7e, 0x5c, 0x23, 0xa2, 0x9c, 0xd1, 0xae, 0xbf, 0x78, 0x3c, 0xf2, 0x17, 0xde,
|
||||
0x2f, 0xa9, 0xf6, 0xbf, 0x52, 0x50, 0x5a, 0x17, 0xa2, 0x57, 0xba, 0xfa, 0x75, 0x09, 0xfa, 0x43,
|
||||
0x7d, 0x8f, 0x0c, 0x49, 0x20, 0x3f, 0xfb, 0x2e, 0x55, 0x38, 0x9e, 0xfa, 0x01, 0x9d, 0xf1, 0x80,
|
||||
0x4d, 0xfc, 0xbf, 0x72, 0xba, 0x58, 0x24, 0xd2, 0x9a, 0xbd, 0x55, 0x86, 0x6c, 0x28, 0xac, 0x5d,
|
||||
0x3a, 0xa3, 0x2f, 0xbd, 0x86, 0xbd, 0xe9, 0x43, 0x21, 0xb9, 0x11, 0xa1, 0x22, 0xe4, 0x1c, 0x97,
|
||||
0x36, 0xdb, 0xce, 0xb7, 0xad, 0x9e, 0xf5, 0x99, 0xfa, 0xec, 0xf6, 0xeb, 0x75, 0x8c, 0x1b, 0xb8,
|
||||
0x61, 0xa5, 0x10, 0x82, 0x92, 0x1a, 0x04, 0xb8, 0x41, 0x7b, 0x4e, 0x07, 0x7b, 0x7d, 0xf5, 0x2a,
|
||||
0x1c, 0xc1, 0x41, 0x8c, 0xb9, 0x1e, 0x25, 0x5e, 0xbf, 0x87, 0xad, 0x74, 0xf5, 0xef, 0x19, 0xd8,
|
||||
0xd3, 0x9b, 0x40, 0x84, 0x5a, 0x90, 0x4f, 0xac, 0xc7, 0xe8, 0x2c, 0x11, 0xc8, 0xc7, 0x6b, 0x73,
|
||||
0xa5, 0xbc, 0x7d, 0x55, 0x9b, 0x8b, 0xdf, 0xa4, 0xd0, 0x1f, 0xa1, 0x90, 0x5c, 0x10, 0x51, 0xf2,
|
||||
0xe1, 0xdf, 0xb2, 0x39, 0x7e, 0x52, 0xd7, 0x7b, 0xb0, 0xb0, 0x90, 0xfe, 0x54, 0x3d, 0xda, 0xf1,
|
||||
0xea, 0x85, 0x2a, 0x09, 0xfe, 0xc6, 0x3e, 0x57, 0x39, 0xdd, 0x2a, 0x8b, 0xcb, 0xbc, 0x6d, 0xae,
|
||||
0x18, 0x2f, 0x3f, 0x8f, 0xae, 0xb8, 0xbe, 0x71, 0x55, 0x5e, 0x3d, 0x25, 0x8e, 0xb5, 0x0d, 0xe1,
|
||||
0x68, 0x4b, 0x43, 0xa2, 0x5f, 0x25, 0x3d, 0x78, 0xb2, 0x9d, 0x2b, 0xaf, 0xff, 0x1f, 0x6d, 0x65,
|
||||
0x65, 0x4b, 0xe7, 0xae, 0x59, 0x79, 0xba, 0xef, 0xd7, 0xac, 0x7c, 0x62, 0x00, 0xbc, 0xfb, 0xed,
|
||||
0x9f, 0xae, 0x46, 0xbe, 0x1c, 0xcf, 0x6f, 0x2f, 0x07, 0xe1, 0xf4, 0x6a, 0xe2, 0x8f, 0xc6, 0x32,
|
||||
0xf0, 0x83, 0x51, 0xc0, 0xe5, 0x5f, 0xc2, 0xe8, 0xfe, 0x6a, 0x12, 0x0c, 0xaf, 0xf4, 0x32, 0x79,
|
||||
0xb5, 0x54, 0x77, 0xbb, 0xa7, 0xff, 0xad, 0xfa, 0xdd, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9a,
|
||||
0xf3, 0x15, 0x70, 0x86, 0x0d, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@ -1135,11 +1262,13 @@ const _ = grpc.SupportPackageIsVersion4
|
||||
type RouterClient interface {
|
||||
//*
|
||||
//SendPayment attempts to route a payment described by the passed
|
||||
//PaymentRequest to the final destination. If we are unable to route the
|
||||
//payment, or cannot find a route that satisfies the constraints in the
|
||||
//PaymentRequest, then an error will be returned. Otherwise, the payment
|
||||
//pre-image, along with the final route will be returned.
|
||||
SendPayment(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error)
|
||||
//PaymentRequest to the final destination. The call returns a stream of
|
||||
//payment status updates.
|
||||
SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentClient, error)
|
||||
//*
|
||||
//TrackPayment returns an update stream for the payment identified by the
|
||||
//payment hash.
|
||||
TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error)
|
||||
//*
|
||||
//EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
//may cost to send an HTLC to the target end destination.
|
||||
@ -1167,13 +1296,68 @@ func NewRouterClient(cc *grpc.ClientConn) RouterClient {
|
||||
return &routerClient{cc}
|
||||
}
|
||||
|
||||
func (c *routerClient) SendPayment(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentResponse, error) {
|
||||
out := new(PaymentResponse)
|
||||
err := c.cc.Invoke(ctx, "/routerrpc.Router/SendPayment", in, out, opts...)
|
||||
func (c *routerClient) SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_Router_serviceDesc.Streams[0], "/routerrpc.Router/SendPayment", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
x := &routerSendPaymentClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Router_SendPaymentClient interface {
|
||||
Recv() (*PaymentStatus, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type routerSendPaymentClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *routerSendPaymentClient) Recv() (*PaymentStatus, error) {
|
||||
m := new(PaymentStatus)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *routerClient) TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_Router_serviceDesc.Streams[1], "/routerrpc.Router/TrackPayment", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &routerTrackPaymentClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Router_TrackPaymentClient interface {
|
||||
Recv() (*PaymentStatus, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type routerTrackPaymentClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *routerTrackPaymentClient) Recv() (*PaymentStatus, error) {
|
||||
m := new(PaymentStatus)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *routerClient) EstimateRouteFee(ctx context.Context, in *RouteFeeRequest, opts ...grpc.CallOption) (*RouteFeeResponse, error) {
|
||||
@ -1216,11 +1400,13 @@ func (c *routerClient) QueryMissionControl(ctx context.Context, in *QueryMission
|
||||
type RouterServer interface {
|
||||
//*
|
||||
//SendPayment attempts to route a payment described by the passed
|
||||
//PaymentRequest to the final destination. If we are unable to route the
|
||||
//payment, or cannot find a route that satisfies the constraints in the
|
||||
//PaymentRequest, then an error will be returned. Otherwise, the payment
|
||||
//pre-image, along with the final route will be returned.
|
||||
SendPayment(context.Context, *PaymentRequest) (*PaymentResponse, error)
|
||||
//PaymentRequest to the final destination. The call returns a stream of
|
||||
//payment status updates.
|
||||
SendPayment(*SendPaymentRequest, Router_SendPaymentServer) error
|
||||
//*
|
||||
//TrackPayment returns an update stream for the payment identified by the
|
||||
//payment hash.
|
||||
TrackPayment(*TrackPaymentRequest, Router_TrackPaymentServer) error
|
||||
//*
|
||||
//EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
//may cost to send an HTLC to the target end destination.
|
||||
@ -1244,22 +1430,46 @@ func RegisterRouterServer(s *grpc.Server, srv RouterServer) {
|
||||
s.RegisterService(&_Router_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Router_SendPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PaymentRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
func _Router_SendPayment_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SendPaymentRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(RouterServer).SendPayment(ctx, in)
|
||||
return srv.(RouterServer).SendPayment(m, &routerSendPaymentServer{stream})
|
||||
}
|
||||
|
||||
type Router_SendPaymentServer interface {
|
||||
Send(*PaymentStatus) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type routerSendPaymentServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *routerSendPaymentServer) Send(m *PaymentStatus) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _Router_TrackPayment_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(TrackPaymentRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/routerrpc.Router/SendPayment",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(RouterServer).SendPayment(ctx, req.(*PaymentRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
return srv.(RouterServer).TrackPayment(m, &routerTrackPaymentServer{stream})
|
||||
}
|
||||
|
||||
type Router_TrackPaymentServer interface {
|
||||
Send(*PaymentStatus) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type routerTrackPaymentServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *routerTrackPaymentServer) Send(m *PaymentStatus) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _Router_EstimateRouteFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
@ -1338,10 +1548,6 @@ var _Router_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "routerrpc.Router",
|
||||
HandlerType: (*RouterServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SendPayment",
|
||||
Handler: _Router_SendPayment_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "EstimateRouteFee",
|
||||
Handler: _Router_EstimateRouteFee_Handler,
|
||||
@ -1359,6 +1565,17 @@ var _Router_serviceDesc = grpc.ServiceDesc{
|
||||
Handler: _Router_QueryMissionControl_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "SendPayment",
|
||||
Handler: _Router_SendPayment_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "TrackPayment",
|
||||
Handler: _Router_TrackPayment_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "routerrpc/router.proto",
|
||||
}
|
||||
|
@ -6,62 +6,105 @@ package routerrpc;
|
||||
|
||||
option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc";
|
||||
|
||||
message PaymentRequest {
|
||||
/**
|
||||
A serialized BOLT-11 payment request that contains all information
|
||||
required to dispatch the payment. If the pay req is invalid, or expired,
|
||||
an error will be returned.
|
||||
*/
|
||||
string pay_req = 1;
|
||||
message SendPaymentRequest {
|
||||
/// The identity pubkey of the payment recipient
|
||||
bytes dest = 1;
|
||||
|
||||
/// Number of satoshis to send.
|
||||
int64 amt = 2;
|
||||
|
||||
/// The hash to use within the payment's HTLC
|
||||
bytes payment_hash = 3;
|
||||
|
||||
/**
|
||||
An absolute limit on the highest fee we should pay when looking for a route
|
||||
to the destination. Routes with fees higher than this will be ignored, if
|
||||
there are no routes with a fee below this amount, an error will be
|
||||
returned.
|
||||
The CLTV delta from the current height that should be used to set the
|
||||
timelock for the final hop.
|
||||
*/
|
||||
int64 fee_limit_sat = 2;
|
||||
int32 final_cltv_delta = 4;
|
||||
|
||||
/**
|
||||
An absolute limit on the cumulative CLTV value along the route for this
|
||||
payment. Routes with total CLTV values higher than this will be ignored,
|
||||
if there are no routes with a CLTV value below this amount, an error will
|
||||
be returned.
|
||||
A bare-bones invoice for a payment within the Lightning Network. With the
|
||||
details of the invoice, the sender has all the data necessary to send a
|
||||
payment to the recipient. The amount in the payment request may be zero. In
|
||||
that case it is required to set the amt field as well. If no payment request
|
||||
is specified, the following fields are required: dest, amt and payment_hash.
|
||||
*/
|
||||
int32 cltv_limit = 3;
|
||||
string payment_request = 5;
|
||||
|
||||
/**
|
||||
An upper limit on the amount of time we should spend when attempting to
|
||||
fulfill the payment. This is expressed in seconds. If we cannot make a
|
||||
successful payment within this time frame, an error will be returned.
|
||||
This field must be non-zero.
|
||||
*/
|
||||
int32 timeout_seconds = 4;
|
||||
int32 timeout_seconds = 6;
|
||||
|
||||
/**
|
||||
The maximum number of satoshis 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.
|
||||
*/
|
||||
int64 fee_limit_sat = 7;
|
||||
|
||||
/**
|
||||
The channel id of the channel that must be taken to the first hop. If zero,
|
||||
any channel may be used.
|
||||
*/
|
||||
int64 outgoing_channel_id = 5;
|
||||
uint64 outgoing_chan_id = 8;
|
||||
|
||||
/**
|
||||
An optional maximum total time lock for the route. If zero, there is no
|
||||
maximum enforced.
|
||||
*/
|
||||
int32 cltv_limit = 9;
|
||||
}
|
||||
|
||||
message PaymentResponse {
|
||||
/**
|
||||
The payment hash that we paid to. Provided so callers are able to map
|
||||
responses (which may be streaming) back to their original requests.
|
||||
*/
|
||||
bytes pay_hash = 1;
|
||||
|
||||
/**
|
||||
The pre-image of the payment successfully completed.
|
||||
*/
|
||||
bytes pre_image = 2;
|
||||
|
||||
/**
|
||||
If not an empty string, then a string representation of the payment error.
|
||||
*/
|
||||
string payment_err = 3;
|
||||
message TrackPaymentRequest {
|
||||
/// The hash of the payment to look up.
|
||||
bytes payment_hash = 1;
|
||||
}
|
||||
|
||||
enum PaymentState {
|
||||
/**
|
||||
Payment is still in flight.
|
||||
*/
|
||||
IN_FLIGHT = 0;
|
||||
|
||||
/**
|
||||
Payment completed successfully.
|
||||
*/
|
||||
SUCCEEDED = 1;
|
||||
|
||||
/**
|
||||
There are more routes to try, but the payment timeout was exceeded.
|
||||
*/
|
||||
FAILED_TIMEOUT = 2;
|
||||
|
||||
/**
|
||||
All possible routes were tried and failed permanently. Or were no
|
||||
routes to the destination at all.
|
||||
*/
|
||||
FAILED_NO_ROUTE = 3;
|
||||
}
|
||||
|
||||
|
||||
message PaymentStatus {
|
||||
/// Current state the payment is in.
|
||||
PaymentState state = 1;
|
||||
|
||||
/**
|
||||
The pre-image of the payment when state is SUCCEEDED.
|
||||
*/
|
||||
bytes preimage = 2;
|
||||
|
||||
/**
|
||||
The taken route when state is SUCCEEDED.
|
||||
*/
|
||||
lnrpc.Route route = 3;
|
||||
}
|
||||
|
||||
|
||||
message RouteFeeRequest {
|
||||
/**
|
||||
The destination once wishes to obtain a routing fee quote to.
|
||||
@ -289,12 +332,17 @@ message ChannelHistory {
|
||||
service Router {
|
||||
/**
|
||||
SendPayment attempts to route a payment described by the passed
|
||||
PaymentRequest to the final destination. If we are unable to route the
|
||||
payment, or cannot find a route that satisfies the constraints in the
|
||||
PaymentRequest, then an error will be returned. Otherwise, the payment
|
||||
pre-image, along with the final route will be returned.
|
||||
PaymentRequest to the final destination. The call returns a stream of
|
||||
payment status updates.
|
||||
*/
|
||||
rpc SendPayment(PaymentRequest) returns (PaymentResponse);
|
||||
rpc SendPayment(SendPaymentRequest) returns (stream PaymentStatus);
|
||||
|
||||
/**
|
||||
TrackPayment returns an update stream for the payment identified by the
|
||||
payment hash.
|
||||
*/
|
||||
rpc TrackPayment(TrackPaymentRequest) returns (stream PaymentStatus);
|
||||
|
||||
|
||||
/**
|
||||
EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
|
@ -4,12 +4,15 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
context "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -38,6 +41,16 @@ type RouterBackend struct {
|
||||
finalExpiry ...uint16) (*route.Route, error)
|
||||
|
||||
MissionControl *routing.MissionControl
|
||||
|
||||
// ActiveNetParams are the network parameters of the primary network
|
||||
// that the route is operating on. This is necessary so we can ensure
|
||||
// that we receive payment requests that send to destinations on our
|
||||
// network.
|
||||
ActiveNetParams *chaincfg.Params
|
||||
|
||||
// Tower is the ControlTower instance that is used to track pending
|
||||
// payments.
|
||||
Tower routing.ControlTower
|
||||
}
|
||||
|
||||
// QueryRoutes attempts to query the daemons' Channel Router for a possible
|
||||
@ -336,3 +349,158 @@ func (r *RouterBackend) UnmarshallRoute(rpcroute *lnrpc.Route) (
|
||||
|
||||
return route, nil
|
||||
}
|
||||
|
||||
// extractIntentFromSendRequest attempts to parse the SendRequest details
|
||||
// required to dispatch a client from the information presented by an RPC
|
||||
// client.
|
||||
func (r *RouterBackend) extractIntentFromSendRequest(
|
||||
rpcPayReq *SendPaymentRequest) (*routing.LightningPayment, error) {
|
||||
|
||||
payIntent := &routing.LightningPayment{}
|
||||
|
||||
// Pass along an outgoing channel restriction if specified.
|
||||
if rpcPayReq.OutgoingChanId != 0 {
|
||||
payIntent.OutgoingChannelID = &rpcPayReq.OutgoingChanId
|
||||
}
|
||||
|
||||
// Take cltv limit from request if set.
|
||||
if rpcPayReq.CltvLimit != 0 {
|
||||
cltvLimit := uint32(rpcPayReq.CltvLimit)
|
||||
payIntent.CltvLimit = &cltvLimit
|
||||
}
|
||||
|
||||
// Take fee limit from request.
|
||||
payIntent.FeeLimit = lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount(rpcPayReq.FeeLimitSat),
|
||||
)
|
||||
|
||||
// Set payment attempt timeout.
|
||||
if rpcPayReq.TimeoutSeconds == 0 {
|
||||
return nil, errors.New("timeout_seconds must be specified")
|
||||
}
|
||||
|
||||
payIntent.PayAttemptTimeout = time.Second *
|
||||
time.Duration(rpcPayReq.TimeoutSeconds)
|
||||
|
||||
// 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 != "" {
|
||||
switch {
|
||||
|
||||
case len(rpcPayReq.Dest) > 0:
|
||||
return nil, errors.New("dest and payment_request " +
|
||||
"cannot appear together")
|
||||
|
||||
case len(rpcPayReq.PaymentHash) > 0:
|
||||
return nil, errors.New("dest and payment_hash " +
|
||||
"cannot appear together")
|
||||
|
||||
case rpcPayReq.FinalCltvDelta != 0:
|
||||
return nil, errors.New("dest and final_cltv_delta " +
|
||||
"cannot appear together")
|
||||
}
|
||||
|
||||
payReq, err := zpay32.Decode(
|
||||
rpcPayReq.PaymentRequest, r.ActiveNetParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we'll ensure that this payreq hasn't already expired.
|
||||
err = ValidatePayReqExpiry(payReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the amount was not included in the invoice, then we let
|
||||
// the payee 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 {
|
||||
if rpcPayReq.Amt == 0 {
|
||||
return nil, errors.New("amount must be " +
|
||||
"specified when paying a zero amount " +
|
||||
"invoice")
|
||||
}
|
||||
|
||||
payIntent.Amount = lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount(rpcPayReq.Amt),
|
||||
)
|
||||
} else {
|
||||
if rpcPayReq.Amt != 0 {
|
||||
return nil, errors.New("amount must not be " +
|
||||
"specified when paying a non-zero " +
|
||||
" amount invoice")
|
||||
}
|
||||
|
||||
payIntent.Amount = *payReq.MilliSat
|
||||
}
|
||||
|
||||
copy(payIntent.PaymentHash[:], payReq.PaymentHash[:])
|
||||
destKey := payReq.Destination.SerializeCompressed()
|
||||
copy(payIntent.Target[:], destKey)
|
||||
|
||||
payIntent.FinalCLTVDelta = uint16(payReq.MinFinalCLTVExpiry())
|
||||
payIntent.RouteHints = payReq.RouteHints
|
||||
} else {
|
||||
// Otherwise, If the payment request field was not specified
|
||||
// (and a custom route wasn't specified), construct the payment
|
||||
// from the other fields.
|
||||
|
||||
// Payment destination.
|
||||
if len(rpcPayReq.Dest) != 33 {
|
||||
return nil, errors.New("invalid key length")
|
||||
|
||||
}
|
||||
pubBytes := rpcPayReq.Dest
|
||||
copy(payIntent.Target[:], pubBytes)
|
||||
|
||||
// Final payment CLTV delta.
|
||||
if rpcPayReq.FinalCltvDelta != 0 {
|
||||
payIntent.FinalCLTVDelta =
|
||||
uint16(rpcPayReq.FinalCltvDelta)
|
||||
} else {
|
||||
payIntent.FinalCLTVDelta = zpay32.DefaultFinalCLTVDelta
|
||||
}
|
||||
|
||||
// Amount.
|
||||
if rpcPayReq.Amt == 0 {
|
||||
return nil, errors.New("amount must be specified")
|
||||
}
|
||||
|
||||
payIntent.Amount = lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount(rpcPayReq.Amt),
|
||||
)
|
||||
|
||||
// Payment hash.
|
||||
copy(payIntent.PaymentHash[:], rpcPayReq.PaymentHash)
|
||||
}
|
||||
|
||||
// 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.
|
||||
if payIntent.Amount > r.MaxPaymentMSat {
|
||||
// In this case, we'll send an error to the caller, but
|
||||
// continue our loop for the next payment.
|
||||
return payIntent, fmt.Errorf("payment of %v is too large, "+
|
||||
"max payment allowed is %v", payIntent.Amount,
|
||||
r.MaxPaymentMSat)
|
||||
|
||||
}
|
||||
|
||||
return payIntent, nil
|
||||
}
|
||||
|
||||
// ValidatePayReqExpiry checks if the passed payment request has expired. In
|
||||
// the case it has expired, an error will be returned.
|
||||
func ValidatePayReqExpiry(payReq *zpay32.Invoice) error {
|
||||
expiry := payReq.Expiry()
|
||||
validUntil := payReq.Timestamp.Add(expiry)
|
||||
if time.Now().After(validUntil) {
|
||||
return fmt.Errorf("invoice expired. Valid until %v", validUntil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -9,18 +9,19 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
@ -55,6 +56,10 @@ var (
|
||||
Entity: "offchain",
|
||||
Action: "write",
|
||||
}},
|
||||
"/routerrpc.Router/TrackPayment": {{
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
}},
|
||||
"/routerrpc.Router/EstimateRouteFee": {{
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
@ -187,61 +192,35 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error {
|
||||
// payment, or cannot find a route that satisfies the constraints in the
|
||||
// PaymentRequest, then an error will be returned. Otherwise, the payment
|
||||
// pre-image, along with the final route will be returned.
|
||||
func (s *Server) SendPayment(ctx context.Context,
|
||||
req *PaymentRequest) (*PaymentResponse, error) {
|
||||
func (s *Server) SendPayment(req *SendPaymentRequest,
|
||||
stream Router_SendPaymentServer) error {
|
||||
|
||||
switch {
|
||||
// If the payment request isn't populated, then we won't be able to
|
||||
// even attempt a payment.
|
||||
case req.PayReq == "":
|
||||
return nil, fmt.Errorf("a valid payment request MUST be specified")
|
||||
}
|
||||
|
||||
// Now that we know the payment request is present, we'll attempt to
|
||||
// decode it in order to parse out all the parameters for the route.
|
||||
payReq, err := zpay32.Decode(req.PayReq, s.cfg.ActiveNetParams)
|
||||
payment, err := s.cfg.RouterBackend.extractIntentFromSendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Atm, this service does not support invoices that don't have their
|
||||
// value fully specified.
|
||||
if payReq.MilliSat == nil {
|
||||
return nil, fmt.Errorf("zero value invoices are not supported")
|
||||
}
|
||||
|
||||
var destination route.Vertex
|
||||
copy(destination[:], payReq.Destination.SerializeCompressed())
|
||||
|
||||
// Now that all the information we need has been parsed, we'll map this
|
||||
// proto request into a proper request that our backing router can
|
||||
// understand.
|
||||
finalDelta := uint16(payReq.MinFinalCLTVExpiry())
|
||||
payment := routing.LightningPayment{
|
||||
Target: destination,
|
||||
Amount: *payReq.MilliSat,
|
||||
FeeLimit: lnwire.MilliSatoshi(req.FeeLimitSat),
|
||||
PaymentHash: *payReq.PaymentHash,
|
||||
FinalCLTVDelta: &finalDelta,
|
||||
PayAttemptTimeout: time.Second * time.Duration(req.TimeoutSeconds),
|
||||
RouteHints: payReq.RouteHints,
|
||||
}
|
||||
|
||||
// Pin to an outgoing channel if specified.
|
||||
if req.OutgoingChannelId != 0 {
|
||||
chanID := uint64(req.OutgoingChannelId)
|
||||
payment.OutgoingChannelID = &chanID
|
||||
}
|
||||
|
||||
preImage, _, err := s.cfg.Router.SendPayment(&payment)
|
||||
err = s.cfg.Router.SendPaymentAsync(payment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Transform user errors to grpc code.
|
||||
if err == channeldb.ErrPaymentInFlight ||
|
||||
err == channeldb.ErrAlreadyPaid {
|
||||
|
||||
log.Debugf("SendPayment async result for hash %x: %v",
|
||||
payment.PaymentHash, err)
|
||||
|
||||
return status.Error(
|
||||
codes.AlreadyExists, err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
log.Errorf("SendPayment async error for hash %x: %v",
|
||||
payment.PaymentHash, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return &PaymentResponse{
|
||||
PayHash: (*payReq.PaymentHash)[:],
|
||||
PreImage: preImage[:],
|
||||
}, nil
|
||||
return s.trackPayment(payment.PaymentHash, stream)
|
||||
}
|
||||
|
||||
// EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
|
||||
@ -500,3 +479,89 @@ func (s *Server) QueryMissionControl(ctx context.Context,
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// TrackPayment returns a stream of payment state updates. The stream is
|
||||
// closed when the payment completes.
|
||||
func (s *Server) TrackPayment(request *TrackPaymentRequest,
|
||||
stream Router_TrackPaymentServer) error {
|
||||
|
||||
paymentHash, err := lntypes.MakeHash(request.PaymentHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("TrackPayment called for payment %v", paymentHash)
|
||||
|
||||
return s.trackPayment(paymentHash, stream)
|
||||
}
|
||||
|
||||
// trackPayment writes payment status updates to the provided stream.
|
||||
func (s *Server) trackPayment(paymentHash lntypes.Hash,
|
||||
stream Router_TrackPaymentServer) error {
|
||||
|
||||
// Subscribe to the outcome of this payment.
|
||||
inFlight, resultChan, err := s.cfg.RouterBackend.Tower.SubscribePayment(
|
||||
paymentHash,
|
||||
)
|
||||
switch {
|
||||
case err == channeldb.ErrPaymentNotInitiated:
|
||||
return status.Error(codes.NotFound, err.Error())
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
// If it is in flight, send a state update to the client. Payment status
|
||||
// update streams are expected to always send the current payment state
|
||||
// immediately.
|
||||
if inFlight {
|
||||
err = stream.Send(&PaymentStatus{
|
||||
State: PaymentState_IN_FLIGHT,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the outcome of the payment. For payments that have
|
||||
// completed, the result should already be waiting on the channel.
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
// Marshall result to rpc type.
|
||||
var status PaymentStatus
|
||||
|
||||
if result.Success {
|
||||
log.Debugf("Payment %v successfully completed",
|
||||
paymentHash)
|
||||
|
||||
status.State = PaymentState_SUCCEEDED
|
||||
status.Preimage = result.Preimage[:]
|
||||
status.Route = s.cfg.RouterBackend.MarshallRoute(
|
||||
result.Route,
|
||||
)
|
||||
} else {
|
||||
switch result.FailureReason {
|
||||
|
||||
case channeldb.FailureReasonTimeout:
|
||||
status.State = PaymentState_FAILED_TIMEOUT
|
||||
|
||||
case channeldb.FailureReasonNoRoute:
|
||||
status.State = PaymentState_FAILED_NO_ROUTE
|
||||
|
||||
default:
|
||||
return errors.New("unknown failure reason")
|
||||
}
|
||||
}
|
||||
|
||||
// Send event to the client.
|
||||
err = stream.Send(&status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case <-stream.Context().Done():
|
||||
log.Debugf("Payment status stream %v canceled", paymentHash)
|
||||
return stream.Context().Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ import (
|
||||
// HashSize of array used to store hashes.
|
||||
const HashSize = 32
|
||||
|
||||
// ZeroHash is a predefined hash containing all zeroes.
|
||||
var ZeroHash Hash
|
||||
|
||||
// Hash is used in several of the lightning messages and common structures. It
|
||||
// typically represents a payment hash.
|
||||
type Hash [HashSize]byte
|
||||
|
238
routing/control_tower.go
Normal file
238
routing/control_tower.go
Normal file
@ -0,0 +1,238 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
// ControlTower tracks all outgoing payments made, whose primary purpose is to
|
||||
// prevent duplicate payments to the same payment hash. In production, a
|
||||
// persistent implementation is preferred so that tracking can survive across
|
||||
// restarts. Payments are transitioned through various payment states, and the
|
||||
// ControlTower interface provides access to driving the state transitions.
|
||||
type ControlTower interface {
|
||||
// InitPayment atomically moves the payment into the InFlight state.
|
||||
// This method checks that no suceeded payment exist for this payment
|
||||
// hash.
|
||||
InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error
|
||||
|
||||
// RegisterAttempt atomically records the provided PaymentAttemptInfo.
|
||||
RegisterAttempt(lntypes.Hash, *channeldb.PaymentAttemptInfo) error
|
||||
|
||||
// Success transitions a payment into the Succeeded state. After
|
||||
// invoking this method, InitPayment should always return an error to
|
||||
// prevent us from making duplicate payments to the same payment hash.
|
||||
// The provided preimage is atomically saved to the DB for record
|
||||
// keeping.
|
||||
Success(lntypes.Hash, lntypes.Preimage) error
|
||||
|
||||
// Fail transitions a payment into the Failed state, and records the
|
||||
// reason the payment failed. After invoking this method, InitPayment
|
||||
// should return nil on its next call for this payment hash, allowing
|
||||
// the switch to make a subsequent payment.
|
||||
Fail(lntypes.Hash, channeldb.FailureReason) error
|
||||
|
||||
// FetchInFlightPayments returns all payments with status InFlight.
|
||||
FetchInFlightPayments() ([]*channeldb.InFlightPayment, error)
|
||||
|
||||
// SubscribePayment subscribes to updates for the payment with the given
|
||||
// hash. It returns a boolean indicating whether the payment is still in
|
||||
// flight and a channel that provides the final outcome of the payment.
|
||||
SubscribePayment(paymentHash lntypes.Hash) (bool, chan PaymentResult,
|
||||
error)
|
||||
}
|
||||
|
||||
// PaymentResult is the struct describing the events received by payment
|
||||
// subscribers.
|
||||
type PaymentResult struct {
|
||||
// Success indicates whether the payment was successful.
|
||||
Success bool
|
||||
|
||||
// Route is the (last) route attempted to send the HTLC. It is only set
|
||||
// for successful payments.
|
||||
Route *route.Route
|
||||
|
||||
// PaymentPreimage is the preimage of a successful payment. This serves
|
||||
// as a proof of payment. It is only set for successful payments.
|
||||
Preimage lntypes.Preimage
|
||||
|
||||
// Failure is a failure reason code indicating the reason the payment
|
||||
// failed. It is only set for failed payments.
|
||||
FailureReason channeldb.FailureReason
|
||||
}
|
||||
|
||||
// controlTower is persistent implementation of ControlTower to restrict
|
||||
// double payment sending.
|
||||
type controlTower struct {
|
||||
db *channeldb.PaymentControl
|
||||
|
||||
subscribers map[lntypes.Hash][]chan PaymentResult
|
||||
subscribersMtx sync.Mutex
|
||||
}
|
||||
|
||||
// NewControlTower creates a new instance of the controlTower.
|
||||
func NewControlTower(db *channeldb.PaymentControl) ControlTower {
|
||||
return &controlTower{
|
||||
db: db,
|
||||
subscribers: make(map[lntypes.Hash][]chan PaymentResult),
|
||||
}
|
||||
}
|
||||
|
||||
// InitPayment checks or records the given PaymentCreationInfo with the DB,
|
||||
// making sure it does not already exist as an in-flight payment. Then this
|
||||
// method returns successfully, the payment is guranteeed to be in the InFlight
|
||||
// state.
|
||||
func (p *controlTower) InitPayment(paymentHash lntypes.Hash,
|
||||
info *channeldb.PaymentCreationInfo) error {
|
||||
|
||||
return p.db.InitPayment(paymentHash, info)
|
||||
}
|
||||
|
||||
// RegisterAttempt atomically records the provided PaymentAttemptInfo to the
|
||||
// DB.
|
||||
func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash,
|
||||
attempt *channeldb.PaymentAttemptInfo) error {
|
||||
|
||||
return p.db.RegisterAttempt(paymentHash, attempt)
|
||||
}
|
||||
|
||||
// Success transitions a payment into the Succeeded state. After invoking this
|
||||
// method, InitPayment should always return an error to prevent us from making
|
||||
// duplicate payments to the same payment hash. The provided preimage is
|
||||
// atomically saved to the DB for record keeping.
|
||||
func (p *controlTower) Success(paymentHash lntypes.Hash,
|
||||
preimage lntypes.Preimage) error {
|
||||
|
||||
route, err := p.db.Success(paymentHash, preimage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Notify subscribers of success event.
|
||||
p.notifyFinalEvent(
|
||||
paymentHash, PaymentResult{
|
||||
Success: true,
|
||||
Preimage: preimage,
|
||||
Route: route,
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fail transitions a payment into the Failed state, and records the reason the
|
||||
// payment failed. After invoking this method, InitPayment should return nil on
|
||||
// its next call for this payment hash, allowing the switch to make a
|
||||
// subsequent payment.
|
||||
func (p *controlTower) Fail(paymentHash lntypes.Hash,
|
||||
reason channeldb.FailureReason) error {
|
||||
|
||||
err := p.db.Fail(paymentHash, reason)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Notify subscribers of fail event.
|
||||
p.notifyFinalEvent(
|
||||
paymentHash, PaymentResult{
|
||||
Success: false,
|
||||
FailureReason: reason,
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchInFlightPayments returns all payments with status InFlight.
|
||||
func (p *controlTower) FetchInFlightPayments() ([]*channeldb.InFlightPayment, error) {
|
||||
return p.db.FetchInFlightPayments()
|
||||
}
|
||||
|
||||
// SubscribePayment subscribes to updates for the payment with the given hash.
|
||||
// It returns a boolean indicating whether the payment is still in flight and a
|
||||
// channel that provides the final outcome of the payment.
|
||||
func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) (
|
||||
bool, chan PaymentResult, error) {
|
||||
|
||||
// Create a channel with buffer size 1. For every payment there will be
|
||||
// exactly one event sent.
|
||||
c := make(chan PaymentResult, 1)
|
||||
|
||||
// Take lock before querying the db to prevent this scenario:
|
||||
// FetchPayment returns us an in-flight state -> payment succeeds, but
|
||||
// there is no subscriber to notify yet -> we add ourselves as a
|
||||
// subscriber -> ... we will never receive a notification.
|
||||
p.subscribersMtx.Lock()
|
||||
defer p.subscribersMtx.Unlock()
|
||||
|
||||
payment, err := p.db.FetchPayment(paymentHash)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
var event PaymentResult
|
||||
|
||||
switch payment.Status {
|
||||
|
||||
// Payment is currently in flight. Register this subscriber and
|
||||
// return without writing a result to the channel yet.
|
||||
case channeldb.StatusInFlight:
|
||||
p.subscribers[paymentHash] = append(
|
||||
p.subscribers[paymentHash], c,
|
||||
)
|
||||
|
||||
return true, c, nil
|
||||
|
||||
// Payment already succeeded. It is not necessary to register as
|
||||
// a subscriber, because we can send the result on the channel
|
||||
// immediately.
|
||||
case channeldb.StatusSucceeded:
|
||||
event.Success = true
|
||||
event.Preimage = *payment.PaymentPreimage
|
||||
event.Route = &payment.Attempt.Route
|
||||
|
||||
// Payment already failed. It is not necessary to register as a
|
||||
// subscriber, because we can send the result on the channel
|
||||
// immediately.
|
||||
case channeldb.StatusFailed:
|
||||
event.Success = false
|
||||
event.FailureReason = *payment.Failure
|
||||
|
||||
default:
|
||||
return false, nil, errors.New("unknown payment status")
|
||||
}
|
||||
|
||||
// Write immediate result to the channel.
|
||||
c <- event
|
||||
close(c)
|
||||
|
||||
return false, c, nil
|
||||
}
|
||||
|
||||
// notifyFinalEvent sends a final payment event to all subscribers of this
|
||||
// payment. The channel will be closed after this.
|
||||
func (p *controlTower) notifyFinalEvent(paymentHash lntypes.Hash,
|
||||
event PaymentResult) {
|
||||
|
||||
// Get all subscribers for this hash. As there is only a single outcome,
|
||||
// the subscriber list can be cleared.
|
||||
p.subscribersMtx.Lock()
|
||||
list, ok := p.subscribers[paymentHash]
|
||||
if !ok {
|
||||
p.subscribersMtx.Unlock()
|
||||
return
|
||||
}
|
||||
delete(p.subscribers, paymentHash)
|
||||
p.subscribersMtx.Unlock()
|
||||
|
||||
// Notify all subscribers of the event. The subscriber channel is
|
||||
// buffered, so it cannot block here.
|
||||
for _, subscriber := range list {
|
||||
subscriber <- event
|
||||
close(subscriber)
|
||||
}
|
||||
}
|
283
routing/control_tower_test.go
Normal file
283
routing/control_tower_test.go
Normal file
@ -0,0 +1,283 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
var (
|
||||
priv, _ = btcec.NewPrivateKey(btcec.S256())
|
||||
pub = priv.PubKey()
|
||||
|
||||
testHop = &route.Hop{
|
||||
PubKeyBytes: route.NewVertex(pub),
|
||||
ChannelID: 12345,
|
||||
OutgoingTimeLock: 111,
|
||||
AmtToForward: 555,
|
||||
}
|
||||
|
||||
testRoute = route.Route{
|
||||
TotalTimeLock: 123,
|
||||
TotalAmount: 1234567,
|
||||
SourcePubKey: route.NewVertex(pub),
|
||||
Hops: []*route.Hop{
|
||||
testHop,
|
||||
testHop,
|
||||
},
|
||||
}
|
||||
|
||||
testTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// TestControlTowerSubscribeUnknown tests that subscribing to an unknown
|
||||
// payment fails.
|
||||
func TestControlTowerSubscribeUnknown(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := initDB()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to init db: %v", err)
|
||||
}
|
||||
|
||||
pControl := NewControlTower(channeldb.NewPaymentControl(db))
|
||||
|
||||
// Subscription should fail when the payment is not known.
|
||||
_, _, err = pControl.SubscribePayment(lntypes.Hash{1})
|
||||
if err != channeldb.ErrPaymentNotInitiated {
|
||||
t.Fatal("expected subscribe to fail for unknown payment")
|
||||
}
|
||||
}
|
||||
|
||||
// TestControlTowerSubscribeSuccess tests that payment updates for a
|
||||
// successful payment are properly sent to subscribers.
|
||||
func TestControlTowerSubscribeSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := initDB()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to init db: %v", err)
|
||||
}
|
||||
|
||||
pControl := NewControlTower(channeldb.NewPaymentControl(db))
|
||||
|
||||
// Initiate a payment.
|
||||
info, attempt, preimg, err := genInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = pControl.InitPayment(info.PaymentHash, info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Subscription should succeed and immediately report the InFlight
|
||||
// status.
|
||||
inFlight, subscriber1, err := pControl.SubscribePayment(info.PaymentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("expected subscribe to succeed, but got: %v", err)
|
||||
}
|
||||
if !inFlight {
|
||||
t.Fatalf("unexpected payment to be in flight")
|
||||
}
|
||||
|
||||
// Register an attempt.
|
||||
err = pControl.RegisterAttempt(info.PaymentHash, attempt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Register a second subscriber after the first attempt has started.
|
||||
inFlight, subscriber2, err := pControl.SubscribePayment(info.PaymentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("expected subscribe to succeed, but got: %v", err)
|
||||
}
|
||||
if !inFlight {
|
||||
t.Fatalf("unexpected payment to be in flight")
|
||||
}
|
||||
|
||||
// Mark the payment as successful.
|
||||
if err := pControl.Success(info.PaymentHash, preimg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Register a third subscriber after the payment succeeded.
|
||||
inFlight, subscriber3, err := pControl.SubscribePayment(info.PaymentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("expected subscribe to succeed, but got: %v", err)
|
||||
}
|
||||
if inFlight {
|
||||
t.Fatalf("expected payment to be finished")
|
||||
}
|
||||
|
||||
// We expect all subscribers to now report the final outcome followed by
|
||||
// no other events.
|
||||
subscribers := []chan PaymentResult{
|
||||
subscriber1, subscriber2, subscriber3,
|
||||
}
|
||||
|
||||
for _, s := range subscribers {
|
||||
var result PaymentResult
|
||||
select {
|
||||
case result = <-s:
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatal("timeout waiting for payment result")
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
t.Fatal("unexpected payment state")
|
||||
}
|
||||
if result.Preimage != preimg {
|
||||
t.Fatal("unexpected preimage")
|
||||
}
|
||||
if !reflect.DeepEqual(result.Route, &attempt.Route) {
|
||||
t.Fatal("unexpected route")
|
||||
}
|
||||
|
||||
// After the final event, we expect the channel to be closed.
|
||||
select {
|
||||
case _, ok := <-s:
|
||||
if ok {
|
||||
t.Fatal("expected channel to be closed")
|
||||
}
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatal("timeout waiting for result channel close")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPaymentControlSubscribeFail tests that payment updates for a
|
||||
// failed payment are properly sent to subscribers.
|
||||
func TestPaymentControlSubscribeFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := initDB()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to init db: %v", err)
|
||||
}
|
||||
|
||||
pControl := NewControlTower(channeldb.NewPaymentControl(db))
|
||||
|
||||
// Initiate a payment.
|
||||
info, _, _, err := genInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = pControl.InitPayment(info.PaymentHash, info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Subscription should succeed.
|
||||
_, subscriber1, err := pControl.SubscribePayment(info.PaymentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("expected subscribe to succeed, but got: %v", err)
|
||||
}
|
||||
|
||||
// Mark the payment as failed.
|
||||
if err := pControl.Fail(info.PaymentHash, channeldb.FailureReasonTimeout); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Register a second subscriber after the payment failed.
|
||||
inFlight, subscriber2, err := pControl.SubscribePayment(info.PaymentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("expected subscribe to succeed, but got: %v", err)
|
||||
}
|
||||
if inFlight {
|
||||
t.Fatalf("expected payment to be finished")
|
||||
}
|
||||
|
||||
// We expect all subscribers to now report the final outcome followed by
|
||||
// no other events.
|
||||
subscribers := []chan PaymentResult{
|
||||
subscriber1, subscriber2,
|
||||
}
|
||||
|
||||
for _, s := range subscribers {
|
||||
var result PaymentResult
|
||||
select {
|
||||
case result = <-s:
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatal("timeout waiting for payment result")
|
||||
}
|
||||
|
||||
if result.Success {
|
||||
t.Fatal("unexpected payment state")
|
||||
}
|
||||
if result.Route != nil {
|
||||
t.Fatal("expected no route")
|
||||
}
|
||||
if result.FailureReason != channeldb.FailureReasonTimeout {
|
||||
t.Fatal("unexpected failure reason")
|
||||
}
|
||||
|
||||
// After the final event, we expect the channel to be closed.
|
||||
select {
|
||||
case _, ok := <-s:
|
||||
if ok {
|
||||
t.Fatal("expected channel to be closed")
|
||||
}
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatal("timeout waiting for result channel close")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initDB() (*channeldb.DB, error) {
|
||||
tempPath, err := ioutil.TempDir("", "routingdb")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := channeldb.Open(tempPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, err
|
||||
}
|
||||
|
||||
func genInfo() (*channeldb.PaymentCreationInfo, *channeldb.PaymentAttemptInfo,
|
||||
lntypes.Preimage, error) {
|
||||
|
||||
preimage, err := genPreimage()
|
||||
if err != nil {
|
||||
return nil, nil, preimage, fmt.Errorf("unable to "+
|
||||
"generate preimage: %v", err)
|
||||
}
|
||||
|
||||
rhash := sha256.Sum256(preimage[:])
|
||||
return &channeldb.PaymentCreationInfo{
|
||||
PaymentHash: rhash,
|
||||
Value: 1,
|
||||
CreationDate: time.Unix(time.Now().Unix(), 0),
|
||||
PaymentRequest: []byte("hola"),
|
||||
},
|
||||
&channeldb.PaymentAttemptInfo{
|
||||
PaymentID: 1,
|
||||
SessionKey: priv,
|
||||
Route: testRoute,
|
||||
}, preimage, nil
|
||||
}
|
||||
|
||||
func genPreimage() ([32]byte, error) {
|
||||
var preimage [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, preimage[:]); err != nil {
|
||||
return preimage, err
|
||||
}
|
||||
return preimage, nil
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
@ -182,7 +183,7 @@ type mockControlTower struct {
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var _ channeldb.ControlTower = (*mockControlTower)(nil)
|
||||
var _ ControlTower = (*mockControlTower)(nil)
|
||||
|
||||
func makeMockControlTower() *mockControlTower {
|
||||
return &mockControlTower{
|
||||
@ -284,3 +285,9 @@ func (m *mockControlTower) FetchInFlightPayments() (
|
||||
|
||||
return fl, nil
|
||||
}
|
||||
|
||||
func (m *mockControlTower) SubscribePayment(paymentHash lntypes.Hash) (
|
||||
bool, chan PaymentResult, error) {
|
||||
|
||||
return false, nil, errors.New("not implemented")
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func TestRequestRoute(t *testing.T) {
|
||||
|
||||
payment := &LightningPayment{
|
||||
CltvLimit: &cltvLimit,
|
||||
FinalCLTVDelta: &finalCltvDelta,
|
||||
FinalCLTVDelta: finalCltvDelta,
|
||||
}
|
||||
|
||||
route, err := session.RequestRoute(payment, height, finalCltvDelta)
|
||||
|
@ -218,7 +218,7 @@ type Config struct {
|
||||
|
||||
// Control keeps track of the status of ongoing payments, ensuring we
|
||||
// can properly resume them across restarts.
|
||||
Control channeldb.ControlTower
|
||||
Control ControlTower
|
||||
|
||||
// MissionControl is a shared memory of sorts that executions of
|
||||
// payment path finding use in order to remember which vertexes/edges
|
||||
@ -1565,10 +1565,8 @@ type LightningPayment struct {
|
||||
|
||||
// FinalCLTVDelta is the CTLV expiry delta to use for the _final_ hop
|
||||
// in the route. This means that the final hop will have a CLTV delta
|
||||
// of at least: currentHeight + FinalCLTVDelta. If this value is
|
||||
// unspecified, then a default value of DefaultFinalCLTVDelta will be
|
||||
// used.
|
||||
FinalCLTVDelta *uint16
|
||||
// of at least: currentHeight + FinalCLTVDelta.
|
||||
FinalCLTVDelta uint16
|
||||
|
||||
// PayAttemptTimeout is a timeout value that we'll use to determine
|
||||
// when we should should abandon the payment attempt after consecutive
|
||||
@ -1608,6 +1606,45 @@ type LightningPayment struct {
|
||||
func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte,
|
||||
*route.Route, error) {
|
||||
|
||||
paySession, err := r.preparePayment(payment)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, err
|
||||
}
|
||||
|
||||
// Since this is the first time this payment is being made, we pass nil
|
||||
// for the existing attempt.
|
||||
return r.sendPayment(nil, payment, paySession)
|
||||
}
|
||||
|
||||
// SendPaymentAsync is the non-blocking version of SendPayment. The payment
|
||||
// result needs to be retrieved via the control tower.
|
||||
func (r *ChannelRouter) SendPaymentAsync(payment *LightningPayment) error {
|
||||
paySession, err := r.preparePayment(payment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Since this is the first time this payment is being made, we pass nil
|
||||
// for the existing attempt.
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
|
||||
_, _, err := r.sendPayment(nil, payment, paySession)
|
||||
if err != nil {
|
||||
log.Errorf("Payment with hash %x failed: %v",
|
||||
payment.PaymentHash, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// preparePayment creates the payment session and registers the payment with the
|
||||
// control tower.
|
||||
func (r *ChannelRouter) preparePayment(payment *LightningPayment) (
|
||||
PaymentSession, error) {
|
||||
|
||||
// Before starting the HTLC routing attempt, we'll create a fresh
|
||||
// payment session which will report our errors back to mission
|
||||
// control.
|
||||
@ -1615,7 +1652,7 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte,
|
||||
payment.RouteHints, payment.Target,
|
||||
)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Record this payment hash with the ControlTower, ensuring it is not
|
||||
@ -1629,12 +1666,10 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte,
|
||||
|
||||
err = r.cfg.Control.InitPayment(payment.PaymentHash, info)
|
||||
if err != nil {
|
||||
return [32]byte{}, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Since this is the first time this payment is being made, we pass nil
|
||||
// for the existing attempt.
|
||||
return r.sendPayment(nil, payment, paySession)
|
||||
return paySession, nil
|
||||
}
|
||||
|
||||
// SendToRoute attempts to send a payment with the given hash through the
|
||||
@ -1732,13 +1767,6 @@ func (r *ChannelRouter) sendPayment(
|
||||
return [32]byte{}, nil, err
|
||||
}
|
||||
|
||||
var finalCLTVDelta uint16
|
||||
if payment.FinalCLTVDelta == nil {
|
||||
finalCLTVDelta = zpay32.DefaultFinalCLTVDelta
|
||||
} else {
|
||||
finalCLTVDelta = *payment.FinalCLTVDelta
|
||||
}
|
||||
|
||||
var payAttemptTimeout time.Duration
|
||||
if payment.PayAttemptTimeout == time.Duration(0) {
|
||||
payAttemptTimeout = defaultPayAttemptTimeout
|
||||
@ -1756,7 +1784,7 @@ func (r *ChannelRouter) sendPayment(
|
||||
paySession: paySession,
|
||||
timeoutChan: timeoutChan,
|
||||
currentHeight: currentHeight,
|
||||
finalCLTVDelta: finalCLTVDelta,
|
||||
finalCLTVDelta: uint16(payment.FinalCLTVDelta),
|
||||
attempt: existingAttempt,
|
||||
circuit: nil,
|
||||
lastError: nil,
|
||||
|
39
rpcserver.go
39
rpcserver.go
@ -67,8 +67,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
zeroHash [32]byte
|
||||
|
||||
// MaxPaymentMSat is the maximum allowed payment currently permitted as
|
||||
// defined in BOLT-002. This value depends on which chain is active.
|
||||
// It is set to the value under the Bitcoin chain as default.
|
||||
@ -476,8 +474,10 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
||||
|
||||
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
|
||||
},
|
||||
FindRoute: s.chanRouter.FindRoute,
|
||||
MissionControl: s.missionControl,
|
||||
FindRoute: s.chanRouter.FindRoute,
|
||||
MissionControl: s.missionControl,
|
||||
ActiveNetParams: activeNetParams.Params,
|
||||
Tower: s.controlTower,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -2738,18 +2738,6 @@ func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription,
|
||||
}
|
||||
}
|
||||
|
||||
// validatePayReqExpiry checks if the passed payment request has expired. In
|
||||
// the case it has expired, an error will be returned.
|
||||
func validatePayReqExpiry(payReq *zpay32.Invoice) error {
|
||||
expiry := payReq.Expiry()
|
||||
validUntil := payReq.Timestamp.Add(expiry)
|
||||
if time.Now().After(validUntil) {
|
||||
return fmt.Errorf("invoice expired. Valid until %v", validUntil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -2932,7 +2920,7 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error
|
||||
}
|
||||
|
||||
// Next, we'll ensure that this payreq hasn't already expired.
|
||||
err = validatePayReqExpiry(payReq)
|
||||
err = routerrpc.ValidatePayReqExpiry(payReq)
|
||||
if err != nil {
|
||||
return payIntent, err
|
||||
}
|
||||
@ -3000,7 +2988,11 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error
|
||||
rpcPayReq.FeeLimit, payIntent.msat,
|
||||
)
|
||||
|
||||
payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta)
|
||||
if rpcPayReq.FinalCltvDelta != 0 {
|
||||
payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta)
|
||||
} else {
|
||||
payIntent.cltvDelta = zpay32.DefaultFinalCLTVDelta
|
||||
}
|
||||
|
||||
// If the user is manually specifying payment details, then the payment
|
||||
// hash may be encoded as a string.
|
||||
@ -3018,7 +3010,9 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error
|
||||
// If we're in debug HTLC mode, then all outgoing HTLCs will pay to the
|
||||
// same debug rHash. Otherwise, we pay to the rHash specified within
|
||||
// the RPC request.
|
||||
case cfg.DebugHTLC && bytes.Equal(payIntent.rHash[:], zeroHash[:]):
|
||||
case cfg.DebugHTLC &&
|
||||
bytes.Equal(payIntent.rHash[:], lntypes.ZeroHash[:]):
|
||||
|
||||
copy(payIntent.rHash[:], invoices.DebugHash[:])
|
||||
|
||||
default:
|
||||
@ -3069,6 +3063,7 @@ func (r *rpcServer) dispatchPaymentIntent(
|
||||
payment := &routing.LightningPayment{
|
||||
Target: payIntent.dest,
|
||||
Amount: payIntent.msat,
|
||||
FinalCLTVDelta: payIntent.cltvDelta,
|
||||
FeeLimit: payIntent.feeLimit,
|
||||
CltvLimit: payIntent.cltvLimit,
|
||||
PaymentHash: payIntent.rHash,
|
||||
@ -3077,12 +3072,6 @@ func (r *rpcServer) dispatchPaymentIntent(
|
||||
PaymentRequest: payIntent.payReq,
|
||||
}
|
||||
|
||||
// If the final CLTV value was specified, then we'll use that
|
||||
// rather than the default.
|
||||
if payIntent.cltvDelta != 0 {
|
||||
payment.FinalCLTVDelta = &payIntent.cltvDelta
|
||||
}
|
||||
|
||||
preImage, route, routerErr = r.server.chanRouter.SendPayment(
|
||||
payment,
|
||||
)
|
||||
|
@ -192,6 +192,8 @@ type server struct {
|
||||
|
||||
chanRouter *routing.ChannelRouter
|
||||
|
||||
controlTower routing.ControlTower
|
||||
|
||||
authGossiper *discovery.AuthenticatedGossiper
|
||||
|
||||
utxoNursery *utxoNursery
|
||||
@ -649,12 +651,16 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
||||
routerrpc.GetMissionControlConfig(cfg.SubRPCServers.RouterRPC),
|
||||
)
|
||||
|
||||
paymentControl := channeldb.NewPaymentControl(chanDB)
|
||||
|
||||
s.controlTower = routing.NewControlTower(paymentControl)
|
||||
|
||||
s.chanRouter, err = routing.New(routing.Config{
|
||||
Graph: chanGraph,
|
||||
Chain: cc.chainIO,
|
||||
ChainView: cc.chainView,
|
||||
Payer: s.htlcSwitch,
|
||||
Control: channeldb.NewPaymentControl(chanDB),
|
||||
Control: s.controlTower,
|
||||
MissionControl: s.missionControl,
|
||||
ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
|
||||
GraphPruneInterval: time.Duration(time.Hour),
|
||||
|
@ -191,9 +191,6 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
|
||||
subCfgValue.FieldByName("NetworkDir").Set(
|
||||
reflect.ValueOf(networkDir),
|
||||
)
|
||||
subCfgValue.FieldByName("ActiveNetParams").Set(
|
||||
reflect.ValueOf(activeNetParams),
|
||||
)
|
||||
subCfgValue.FieldByName("MacService").Set(
|
||||
reflect.ValueOf(macService),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user