Merge pull request #2973 from joostjager/newer-payment-rpc

routerrpc: async payment rpc
This commit is contained in:
Olaoluwa Osuntokun 2019-06-05 15:03:38 +02:00 committed by GitHub
commit 880279b266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1599 additions and 555 deletions

@ -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 {
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;
/**
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.
The CLTV delta from the current height that should be used to set the
timelock for the final hop.
*/
string pay_req = 1;
int32 final_cltv_delta = 4;
/**
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.
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.
*/
int64 fee_limit_sat = 2;
/**
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.
*/
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

@ -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)
}
}

@ -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,

@ -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),
)