Merge pull request #439 from bhandras/ignore_cheap_paths

loopout: using server recommended routing plugins + low/high router
pull/461/head
András Bánki-Horváth 2 years ago committed by GitHub
commit bf67b14595
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -108,6 +108,14 @@ type ClientConfig struct {
// for a loop out swap. When greater than one, a multi-part payment may
// be attempted.
LoopOutMaxParts uint32
// TotalPaymentTimeout is the total amount of time until we time out
// off-chain payments (used in loop out).
TotalPaymentTimeout time.Duration
// MaxPaymentRetries is the maximum times we retry an off-chain payment
// (used in loop out).
MaxPaymentRetries int
}
// NewClient returns a new instance to initiate swaps with.
@ -142,12 +150,14 @@ func NewClient(dbDir string, cfg *ClientConfig) (*Client, func(), error) {
}
executor := newExecutor(&executorConfig{
lnd: cfg.Lnd,
store: store,
sweeper: sweeper,
createExpiryTimer: config.CreateExpiryTimer,
loopOutMaxParts: cfg.LoopOutMaxParts,
cancelSwap: swapServerClient.CancelLoopOutSwap,
lnd: cfg.Lnd,
store: store,
sweeper: sweeper,
createExpiryTimer: config.CreateExpiryTimer,
loopOutMaxParts: cfg.LoopOutMaxParts,
totalPaymentTimeout: cfg.TotalPaymentTimeout,
maxPaymentRetries: cfg.MaxPaymentRetries,
cancelSwap: swapServerClient.CancelLoopOutSwap,
})
client := &Client{

@ -26,6 +26,10 @@ type executorConfig struct {
loopOutMaxParts uint32
totalPaymentTimeout time.Duration
maxPaymentRetries int
cancelSwap func(ctx context.Context, details *outCancelDetails) error
}
@ -141,12 +145,14 @@ func (s *executor) run(mainCtx context.Context,
defer s.wg.Done()
err := newSwap.execute(mainCtx, &executeConfig{
statusChan: statusChan,
sweeper: s.sweeper,
blockEpochChan: queue.ChanOut(),
timerFactory: s.executorConfig.createExpiryTimer,
loopOutMaxParts: s.executorConfig.loopOutMaxParts,
cancelSwap: s.executorConfig.cancelSwap,
statusChan: statusChan,
sweeper: s.sweeper,
blockEpochChan: queue.ChanOut(),
timerFactory: s.executorConfig.createExpiryTimer,
loopOutMaxParts: s.executorConfig.loopOutMaxParts,
totalPaymentTimout: s.executorConfig.totalPaymentTimeout,
maxPaymentRetries: s.executorConfig.maxPaymentRetries,
cancelSwap: s.executorConfig.cancelSwap,
}, height)
if err != nil && err != context.Canceled {
log.Errorf("Execute error: %v", err)

@ -6,6 +6,7 @@ require (
github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c
github.com/coreos/bbolt v1.3.3
github.com/davecgh/go-spew v1.1.1
github.com/fortytw2/leaktest v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
github.com/jessevdk/go-flags v1.4.0

@ -33,9 +33,11 @@ var (
LoopDirBase, DefaultNetwork, defaultConfigFilename,
)
defaultMaxLogFiles = 3
defaultMaxLogFileSize = 10
defaultLoopOutMaxParts = uint32(5)
defaultMaxLogFiles = 3
defaultMaxLogFileSize = 10
defaultLoopOutMaxParts = uint32(5)
defaultTotalPaymentTimeout = time.Minute * 60
defaultMaxPaymentRetries = 3
// DefaultTLSCertFilename is the default file name for the autogenerated
// TLS certificate.
@ -144,6 +146,9 @@ type Config struct {
LoopOutMaxParts uint32 `long:"loopoutmaxparts" description:"The maximum number of payment parts that may be used for a loop out swap."`
TotalPaymentTimeout time.Duration `long:"totalpaymenttimeout" description:"The timeout to use for off-chain payments."`
MaxPaymentRetries int `long:"maxpaymentretries" description:"The maximum number of times an off-chain payment may be retried."`
Lnd *lndConfig `group:"lnd" namespace:"lnd"`
Server *loopServerConfig `group:"server" namespace:"server"`
@ -165,19 +170,21 @@ func DefaultConfig() Config {
Server: &loopServerConfig{
NoTLS: false,
},
LoopDir: LoopDirBase,
ConfigFile: defaultConfigFile,
DataDir: LoopDirBase,
LogDir: defaultLogDir,
MaxLogFiles: defaultMaxLogFiles,
MaxLogFileSize: defaultMaxLogFileSize,
DebugLevel: defaultLogLevel,
TLSCertPath: DefaultTLSCertPath,
TLSKeyPath: DefaultTLSKeyPath,
MacaroonPath: DefaultMacaroonPath,
MaxLSATCost: lsat.DefaultMaxCostSats,
MaxLSATFee: lsat.DefaultMaxRoutingFeeSats,
LoopOutMaxParts: defaultLoopOutMaxParts,
LoopDir: LoopDirBase,
ConfigFile: defaultConfigFile,
DataDir: LoopDirBase,
LogDir: defaultLogDir,
MaxLogFiles: defaultMaxLogFiles,
MaxLogFileSize: defaultMaxLogFileSize,
DebugLevel: defaultLogLevel,
TLSCertPath: DefaultTLSCertPath,
TLSKeyPath: DefaultTLSKeyPath,
MacaroonPath: DefaultMacaroonPath,
MaxLSATCost: lsat.DefaultMaxCostSats,
MaxLSATFee: lsat.DefaultMaxRoutingFeeSats,
LoopOutMaxParts: defaultLoopOutMaxParts,
TotalPaymentTimeout: defaultTotalPaymentTimeout,
MaxPaymentRetries: defaultMaxPaymentRetries,
Lnd: &lndConfig{
Host: "localhost:10009",
MacaroonPath: DefaultLndMacaroonPath,
@ -299,6 +306,17 @@ func Validate(cfg *Config) error {
return fmt.Errorf("must specify --lnd.macaroonpath")
}
// Allow at most 2x the default total payment timeout.
if cfg.TotalPaymentTimeout > 2*defaultTotalPaymentTimeout {
return fmt.Errorf("max total payment timeout allowed is at "+
"most %v", 2*defaultTotalPaymentTimeout)
}
// At least one retry.
if cfg.MaxPaymentRetries < 1 {
return fmt.Errorf("max payment retries must be positive")
}
return nil
}

@ -17,14 +17,16 @@ func getClient(config *Config, lnd *lndclient.LndServices) (*loop.Client,
func(), error) {
clientConfig := &loop.ClientConfig{
ServerAddress: config.Server.Host,
ProxyAddress: config.Server.Proxy,
SwapServerNoTLS: config.Server.NoTLS,
TLSPathServer: config.Server.TLSPath,
Lnd: lnd,
MaxLsatCost: btcutil.Amount(config.MaxLSATCost),
MaxLsatFee: btcutil.Amount(config.MaxLSATFee),
LoopOutMaxParts: config.LoopOutMaxParts,
ServerAddress: config.Server.Host,
ProxyAddress: config.Server.Proxy,
SwapServerNoTLS: config.Server.NoTLS,
TLSPathServer: config.Server.TLSPath,
Lnd: lnd,
MaxLsatCost: btcutil.Amount(config.MaxLSATCost),
MaxLsatFee: btcutil.Amount(config.MaxLSATFee),
LoopOutMaxParts: config.LoopOutMaxParts,
TotalPaymentTimeout: config.TotalPaymentTimeout,
MaxPaymentRetries: config.MaxPaymentRetries,
}
swapClient, cleanUp, err := loop.NewClient(config.DataDir, clientConfig)

@ -47,13 +47,17 @@ const (
// the server to perform a probe to test inbound liquidty.
ProtocolVersionProbe ProtocolVersion = 8
// The client may ask the server to use a custom routing helper plugin
// in order to enhance off-chain payments corresponding to a swap.
ProtocolVersionRoutingPlugin = 9
// ProtocolVersionUnrecorded is set for swaps were created before we
// started saving protocol version with swaps.
ProtocolVersionUnrecorded ProtocolVersion = math.MaxUint32
// CurrentRPCProtocolVersion defines the version of the RPC protocol
// that is currently supported by the loop client.
CurrentRPCProtocolVersion = looprpc.ProtocolVersion_PROBE
CurrentRPCProtocolVersion = looprpc.ProtocolVersion_ROUTING_PLUGIN
// CurrentInternalProtocolVersion defines the RPC current protocol in
// the internal representation.
@ -95,6 +99,9 @@ func (p ProtocolVersion) String() string {
case ProtocolVersionProbe:
return "Probe"
case ProtocolVersionRoutingPlugin:
return "Routing Plugin"
default:
return "Unknown"
}

@ -23,6 +23,7 @@ func TestProtocolVersionSanity(t *testing.T) {
ProtocolVersionMultiLoopIn,
ProtocolVersionLoopOutCancel,
ProtocolVersionProbe,
ProtocolVersionRoutingPlugin,
}
rpcVersions := [...]looprpc.ProtocolVersion{
@ -35,6 +36,7 @@ func TestProtocolVersionSanity(t *testing.T) {
looprpc.ProtocolVersion_MULTI_LOOP_IN,
looprpc.ProtocolVersion_LOOP_OUT_CANCEL,
looprpc.ProtocolVersion_PROBE,
looprpc.ProtocolVersion_ROUTING_PLUGIN,
}
require.Equal(t, len(versions), len(rpcVersions))

@ -52,10 +52,6 @@ var (
//
// TODO(wilmer): tune?
DefaultSweepConfTargetDelta = DefaultSweepConfTarget * 2
// paymentTimeout is the timeout for the loop out payment loop as
// communicated to lnd.
paymentTimeout = time.Minute * 30
)
// loopOutSwap contains all the in-memory state related to a pending loop out
@ -72,6 +68,8 @@ type loopOutSwap struct {
// htlcTxHash is the confirmed htlc tx id.
htlcTxHash *chainhash.Hash
swapInvoicePaymentAddr [32]byte
swapPaymentChan chan paymentResult
prePaymentChan chan paymentResult
@ -80,12 +78,14 @@ type loopOutSwap struct {
// executeConfig contains extra configuration to execute the swap.
type executeConfig struct {
sweeper *sweep.Sweeper
statusChan chan<- SwapInfo
blockEpochChan <-chan interface{}
timerFactory func(d time.Duration) <-chan time.Time
loopOutMaxParts uint32
cancelSwap func(context.Context, *outCancelDetails) error
sweeper *sweep.Sweeper
statusChan chan<- SwapInfo
blockEpochChan <-chan interface{}
timerFactory func(d time.Duration) <-chan time.Time
loopOutMaxParts uint32
totalPaymentTimout time.Duration
maxPaymentRetries int
cancelSwap func(context.Context, *outCancelDetails) error
}
// loopOutInitResult contains information about a just-initiated loop out swap.
@ -198,10 +198,18 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
// Log htlc address for debugging.
swapKit.log.Infof("Htlc address: %v", htlc.Address)
// Obtain the payment addr since we'll need it later for routing plugin
// recommendation and possibly for cancel.
paymentAddr, err := obtainSwapPaymentAddr(contract.SwapInvoice, cfg)
if err != nil {
return nil, err
}
swap := &loopOutSwap{
LoopOutContract: contract,
swapKit: *swapKit,
htlc: htlc,
LoopOutContract: contract,
swapKit: *swapKit,
htlc: htlc,
swapInvoicePaymentAddr: *paymentAddr,
}
// Persist the data before exiting this function, so that the caller
@ -244,11 +252,21 @@ func resumeLoopOutSwap(reqContext context.Context, cfg *swapConfig,
// Log htlc address for debugging.
swapKit.log.Infof("Htlc address: %v", htlc.Address)
// Obtain the payment addr since we'll need it later for routing plugin
// recommendation and possibly for cancel.
paymentAddr, err := obtainSwapPaymentAddr(
pend.Contract.SwapInvoice, cfg,
)
if err != nil {
return nil, err
}
// Create the swap.
swap := &loopOutSwap{
LoopOutContract: *pend.Contract,
swapKit: *swapKit,
htlc: htlc,
LoopOutContract: *pend.Contract,
swapKit: *swapKit,
htlc: htlc,
swapInvoicePaymentAddr: *paymentAddr,
}
lastUpdate := pend.LastUpdate()
@ -263,6 +281,24 @@ func resumeLoopOutSwap(reqContext context.Context, cfg *swapConfig,
return swap, nil
}
// obtainSwapPaymentAddr will retrieve the payment addr from the passed invoice.
func obtainSwapPaymentAddr(swapInvoice string, cfg *swapConfig) (
*[32]byte, error) {
swapPayReq, err := zpay32.Decode(
swapInvoice, cfg.lnd.ChainParams,
)
if err != nil {
return nil, err
}
if swapPayReq.PaymentAddr == nil {
return nil, fmt.Errorf("expected payment address for invoice")
}
return swapPayReq.PaymentAddr, nil
}
// sendUpdate reports an update to the swap state.
func (s *loopOutSwap) sendUpdate(ctx context.Context) error {
info := s.swapInfo()
@ -537,16 +573,29 @@ func (s *loopOutSwap) payInvoices(ctx context.Context) {
// Pay the swap invoice.
s.log.Infof("Sending swap payment %v", s.SwapInvoice)
// Ask the server if it recommends using a routing plugin.
pluginType, err := s.swapKit.server.RecommendRoutingPlugin(
ctx, s.swapInfo().SwapHash, s.swapInvoicePaymentAddr,
)
if err != nil {
s.log.Warnf("Server couldn't recommend routing plugin: %v", err)
pluginType = RoutingPluginNone
} else {
s.log.Infof("Server recommended routing plugin: %v", pluginType)
}
// Use the recommended routing plugin.
s.swapPaymentChan = s.payInvoice(
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
s.LoopOutContract.OutgoingChanSet,
s.LoopOutContract.OutgoingChanSet, pluginType,
)
// Pay the prepay invoice.
// Pay the prepay invoice. Won't use the routing plugin here as the
// prepay is trivially small and shouldn't normally need any help.
s.log.Infof("Sending prepayment %v", s.PrepayInvoice)
s.prePaymentChan = s.payInvoice(
ctx, s.PrepayInvoice, s.MaxPrepayRoutingFee,
nil,
nil, RoutingPluginNone,
)
}
@ -573,8 +622,8 @@ func (p paymentResult) failure() error {
// payInvoice pays a single invoice.
func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
maxFee btcutil.Amount,
outgoingChanIds loopdb.ChannelSet) chan paymentResult {
maxFee btcutil.Amount, outgoingChanIds loopdb.ChannelSet,
pluginType RoutingPluginType) chan paymentResult {
resultChan := make(chan paymentResult)
sendResult := func(result paymentResult) {
@ -588,7 +637,7 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
var result paymentResult
status, err := s.payInvoiceAsync(
ctx, invoice, maxFee, outgoingChanIds,
ctx, invoice, maxFee, outgoingChanIds, pluginType,
)
if err != nil {
result.err = err
@ -616,16 +665,39 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
// payInvoiceAsync is the asynchronously executed part of paying an invoice.
func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
invoice string, maxFee btcutil.Amount,
outgoingChanIds loopdb.ChannelSet) (*lndclient.PaymentStatus, error) {
outgoingChanIds loopdb.ChannelSet, pluginType RoutingPluginType) (
*lndclient.PaymentStatus, error) {
// Extract hash from payment request. Unfortunately the request
// components aren't available directly.
chainParams := s.lnd.ChainParams
hash, _, err := swap.DecodeInvoice(chainParams, invoice)
target, routeHints, hash, amt, err := swap.DecodeInvoice(
chainParams, invoice,
)
if err != nil {
return nil, err
}
maxRetries := 1
paymentTimeout := s.executeConfig.totalPaymentTimout
// Attempt to acquire and initialize the routing plugin.
routingPlugin, err := AcquireRoutingPlugin(
ctx, pluginType, *s.lnd, target, routeHints, amt,
)
if err != nil {
return nil, err
}
if routingPlugin != nil {
s.log.Infof("Acquired routing plugin %v for payment %v",
pluginType, hash.String())
maxRetries = s.executeConfig.maxPaymentRetries
paymentTimeout /= time.Duration(maxRetries)
defer ReleaseRoutingPlugin(ctx)
}
req := lndclient.SendPaymentRequest{
MaxFee: maxFee,
Invoice: invoice,
@ -635,12 +707,75 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
}
// Lookup state of the swap payment.
paymentStateCtx, cancel := context.WithCancel(ctx)
payCtx, cancel := context.WithCancel(ctx)
defer cancel()
payStatusChan, payErrChan, err := s.lnd.Router.SendPayment(
paymentStateCtx, req,
start := time.Now()
paymentStatus, attempts, err := s.sendPaymentWithRetry(
payCtx, hash, &req, maxRetries, routingPlugin, pluginType,
)
dt := time.Since(start)
paymentSuccess := err == nil &&
paymentStatus.State == lnrpc.Payment_SUCCEEDED
if err := s.swapKit.server.ReportRoutingResult(
ctx, s.swapInfo().SwapHash, s.swapInvoicePaymentAddr, pluginType,
paymentSuccess, int32(attempts), dt.Milliseconds(),
); err != nil {
s.log.Warnf("Failed to report routing result: %v", err)
}
return paymentStatus, err
}
// sendPaymentWithRetry will send the payment, optionally with the passed
// routing plugin retrying at most maxRetries times.
func (s *loopOutSwap) sendPaymentWithRetry(ctx context.Context,
hash lntypes.Hash, req *lndclient.SendPaymentRequest, maxRetries int,
routingPlugin RoutingPlugin, pluginType RoutingPluginType) (
*lndclient.PaymentStatus, int, error) {
tryCount := 1
for {
s.log.Infof("Payment (%v) try count %v/%v (plugin=%v)",
hash.String(), tryCount, maxRetries,
pluginType.String())
if routingPlugin != nil {
if err := routingPlugin.BeforePayment(
ctx, tryCount, maxRetries,
); err != nil {
return nil, tryCount, err
}
}
var err error
paymentStatus, err := s.awaitSendPayment(ctx, hash, req)
if err != nil {
return nil, tryCount, err
}
// Payment has succeeded, we can return here.
if paymentStatus.State == lnrpc.Payment_SUCCEEDED {
return paymentStatus, tryCount, nil
}
// Retry if the payment has timed out, or return here.
if tryCount > maxRetries || paymentStatus.FailureReason !=
lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT {
return paymentStatus, tryCount, nil
}
tryCount++
}
}
func (s *loopOutSwap) awaitSendPayment(ctx context.Context, hash lntypes.Hash,
req *lndclient.SendPaymentRequest) (*lndclient.PaymentStatus, error) {
payStatusChan, payErrChan, err := s.lnd.Router.SendPayment(ctx, *req)
if err != nil {
return nil, err
}
@ -665,15 +800,16 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
return nil, errors.New("unknown payment state")
}
// Abort the swap in case of an error. An unknown payment error
// from TrackPayment is no longer expected here.
// Abort the swap in case of an error. An unknown
// payment error from TrackPayment is no longer expected
// here.
case err := <-payErrChan:
if err != channeldb.ErrAlreadyPaid {
return nil, err
}
payStatusChan, payErrChan, err =
s.lnd.Router.TrackPayment(paymentStateCtx, hash)
s.lnd.Router.TrackPayment(ctx, hash)
if err != nil {
return nil, err
}
@ -997,22 +1133,9 @@ func (s *loopOutSwap) failOffChain(ctx context.Context, paymentType paymentType,
// Set our state to failed off chain timeout.
s.state = loopdb.StateFailOffchainPayments
swapPayReq, err := zpay32.Decode(
s.LoopOutContract.SwapInvoice, s.swapConfig.lnd.ChainParams,
)
if err != nil {
s.log.Errorf("could not decode swap invoice: %v", err)
return
}
if swapPayReq.PaymentAddr == nil {
s.log.Errorf("expected payment address for invoice")
return
}
details := &outCancelDetails{
hash: s.hash,
paymentAddr: *swapPayReq.PaymentAddr,
paymentAddr: s.swapInvoicePaymentAddr,
metadata: routeCancelMetadata{
paymentType: paymentType,
failureReason: status.FailureReason,
@ -1194,7 +1317,7 @@ func validateLoopOutContract(lnd *lndclient.LndServices,
// Check invoice amounts.
chainParams := lnd.ChainParams
swapInvoiceHash, swapInvoiceAmt, err := swap.DecodeInvoice(
_, _, swapInvoiceHash, swapInvoiceAmt, err := swap.DecodeInvoice(
chainParams, response.swapInvoice,
)
if err != nil {
@ -1207,7 +1330,7 @@ func validateLoopOutContract(lnd *lndclient.LndServices,
swapInvoiceHash, swapHash)
}
_, prepayInvoiceAmt, err := swap.DecodeInvoice(
_, _, _, prepayInvoiceAmt, err := swap.DecodeInvoice(
chainParams, response.prepayInvoice,
)
if err != nil {

@ -16,6 +16,10 @@ This file tracks release notes for the loop client.
#### New Features
* Loop client now supports optional routing plugins to improve off-chain payment
reliability. One such plugin that the client implemenets will gradually prefer
increasingly more expensive routes in case payments using cheap routes time out.
#### Breaking Changes
#### Bug Fixes

@ -0,0 +1,659 @@
package loop
import (
"context"
"fmt"
"sort"
"sync"
"time"
"github.com/btcsuite/btclog"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
// ErrRoutingPluginNotApplicable means that the selected routing plugin
// is not able to enhance routing given the current conditions and
// therefore shouldn't be used.
ErrRoutingPluginNotApplicable = fmt.Errorf("routing plugin not " +
"applicable")
// ErrRoutingPluginNoMoreRetries means that the routing plugin can't
// effectively help the payment with more retries.
ErrRoutingPluginNoMoreRetries = fmt.Errorf("routing plugin can't " +
"retry more")
)
var (
routingPluginMx sync.Mutex
routingPluginInstance RoutingPlugin
)
// RoutingPlugin is a generic interface for off-chain payment helpers.
type RoutingPlugin interface {
// Init initializes the routing plugin.
Init(ctx context.Context, target route.Vertex,
routeHints [][]zpay32.HopHint, amt btcutil.Amount) error
// Done deinitializes the routing plugin (restoring any state the
// plugin might have changed).
Done(ctx context.Context) error
// BeforePayment is called before each payment. Attempt counter is
// passed, counting attempts from 1.
BeforePayment(ctx context.Context, attempt int, maxAttempts int) error
}
// makeRoutingPlugin is a helper to instantiate routing plugins.
func makeRoutingPlugin(pluginType RoutingPluginType,
lnd lndclient.LndServices, clock clock.Clock) RoutingPlugin {
if pluginType == RoutingPluginLowHigh {
return &lowToHighRoutingPlugin{
lnd: lnd,
clock: clock,
}
}
return nil
}
// AcquireRoutingPlugin will return a RoutingPlugin instance (or nil). As the
// LND instance used is a shared resource, currently only one requestor will be
// able to acquire a RoutingPlugin instance. If someone is already holding the
// instance a nil is returned.
func AcquireRoutingPlugin(ctx context.Context, pluginType RoutingPluginType,
lnd lndclient.LndServices, target route.Vertex,
routeHints [][]zpay32.HopHint, amt btcutil.Amount) (
RoutingPlugin, error) {
routingPluginMx.Lock()
defer routingPluginMx.Unlock()
// Another swap is already using the routing plugin.
if routingPluginInstance != nil {
return nil, nil
}
routingPluginInstance = makeRoutingPlugin(
pluginType, lnd, clock.NewDefaultClock(),
)
if routingPluginInstance == nil {
return nil, nil
}
// Initialize the plugin with the passed parameters.
err := routingPluginInstance.Init(ctx, target, routeHints, amt)
if err != nil {
if err == ErrRoutingPluginNotApplicable {
// Since the routing plugin is not applicable for this
// payment, we can immediately destruct it.
if err := routingPluginInstance.Done(ctx); err != nil {
log.Errorf("Error while releasing routing "+
"plugin: %v", err)
}
// ErrRoutingPluginNotApplicable is non critical, so
// we're masking this error as we can continue the swap
// flow without the routing plugin.
err = nil
}
routingPluginInstance = nil
return nil, err
}
return routingPluginInstance, nil
}
// ReleaseRoutingPlugin will release the RoutingPlugin, allowing other
// requestors to acquire the instance.
func ReleaseRoutingPlugin(ctx context.Context) {
routingPluginMx.Lock()
defer routingPluginMx.Unlock()
if routingPluginInstance == nil {
return
}
if err := routingPluginInstance.Done(ctx); err != nil {
log.Errorf("Error while releasing routing plugin: %v",
err)
}
routingPluginInstance = nil
}
// lowToHighRoutingPlugin is a RoutingPlugin that implements "low to high"
// routing. This means that when we're attempting to pay to a target we'll
// gradually (with a linear step function) discard inbound peers to that target
// given routing timeouts. The lowToHighRoutingPlugin itself is responsible for
// manipulating LND's Mission Control to make such routing attempts possible.
type lowToHighRoutingPlugin struct {
lnd lndclient.LndServices
clock clock.Clock
target route.Vertex
amount btcutil.Amount
mcState map[route.Vertex]lndclient.MissionControlEntry
// nodesByMaxFee holds nodes sorted by maximum fees that would be paid
// to the target node for the target amount.
nodesByMaxFee []nodeFeeInfo
// mcChanged flags that the MC settings for the tracked nodes were
// changed and should be reset to their original state once the plugin
// is done.
mcChanged bool
}
type nodeFeeInfo struct {
node route.Vertex
capacity btcutil.Amount
fee int64
}
// Enforce that lowToHighRoutingPlugin implements the RoutingPlugin interface.
var _ RoutingPlugin = (*lowToHighRoutingPlugin)(nil)
// buildPrivateChannels creates the private channel map from the passed route
// hints. The code is taken and adapted from LND. Original source:
// lnd/routing/payment_session_source.go.
func buildPrivateChannels(routeHints [][]zpay32.HopHint,
target route.Vertex) map[route.Vertex][]*lndclient.ChannelEdge {
edges := make(map[route.Vertex][]*lndclient.ChannelEdge)
// Traverse through all of the available hop hints and include them in
// our edges map.
for _, routeHint := range routeHints {
// If multiple hop hints are provided within a single route
// hint, we'll assume they must be chained together and sorted
// in forward order in order to reach the target successfully.
for i, hopHint := range routeHint {
// In order to determine the end node of this hint,
// we'll need to look at the next hint's start node. If
// we've reached the end of the hints list, we can
// assume we've reached the target.
var toNode route.Vertex
if i != len(routeHint)-1 {
toNode = route.NewVertex(routeHint[i+1].NodeID)
} else {
toNode = target
}
fromNode := route.NewVertex(hopHint.NodeID)
// Finally, create the channel edges from the hop hint
// and add them to list of edges.
edgeFrom := &lndclient.ChannelEdge{
Node1: fromNode,
Node2: toNode,
ChannelID: hopHint.ChannelID,
Node1Policy: &lndclient.RoutingPolicy{
TimeLockDelta: uint32(
hopHint.CLTVExpiryDelta,
),
FeeBaseMsat: int64(
hopHint.FeeBaseMSat,
),
FeeRateMilliMsat: int64(
hopHint.FeeProportionalMillionths,
),
},
}
edges[fromNode] = append(edges[fromNode], edgeFrom)
// Note that we're adding the edge here in both
// directions as we don't actually use it to find a path
// but just to walk from the target until the first
// node that peers with multiple nodes.
edgeTo := &lndclient.ChannelEdge{
Node1: toNode,
Node2: fromNode,
ChannelID: hopHint.ChannelID,
Node2Policy: &lndclient.RoutingPolicy{
TimeLockDelta: uint32(
hopHint.CLTVExpiryDelta,
),
FeeBaseMsat: int64(
hopHint.FeeBaseMSat,
),
FeeRateMilliMsat: int64(
hopHint.FeeProportionalMillionths,
),
},
}
edges[toNode] = append(edges[toNode], edgeTo)
}
}
return edges
}
func getNodeInfo(ctx context.Context, lnd lndclient.LightningClient,
nodeID route.Vertex,
privateEdges map[route.Vertex][]*lndclient.ChannelEdge) (
*lndclient.NodeInfo, error) {
nodeInfo, err := lnd.GetNodeInfo(ctx, nodeID, true)
if err != nil {
status, ok := status.FromError(err)
if ok && status.Code() == codes.NotFound {
// It's still possible that we should know this node
// from the provided route hints even though it is not
// part of the public graph. If we don't know any
// private channels then just return the error.
if _, ok := privateEdges[nodeID]; !ok {
return nil, err
}
nodeInfo = &lndclient.NodeInfo{
Node: &lndclient.Node{
PubKey: nodeID,
},
}
} else {
return nil, err
}
}
if len(privateEdges) > 0 {
for _, edge := range privateEdges[nodeID] {
nodeInfo.Channels = append(nodeInfo.Channels, *edge)
nodeInfo.TotalCapacity += edge.Capacity
}
}
return nodeInfo, nil
}
// saveMissionControlState will save the MC state for the node pairs formed by
// the passed nodes and target.
func (r *lowToHighRoutingPlugin) saveMissionControlState(ctx context.Context,
nodes map[route.Vertex]*lndclient.NodeInfo, target route.Vertex) error {
entries, err := r.lnd.Router.QueryMissionControl(ctx)
if err != nil {
return err
}
r.mcState = make(map[route.Vertex]lndclient.MissionControlEntry)
for _, entry := range entries {
// Skip pairs which we do not intend to change.
if _, ok := nodes[entry.NodeFrom]; !ok {
continue
}
if entry.NodeTo != target {
continue
}
r.mcState[entry.NodeFrom] = entry
}
log.Debugf("Saved MC state: %v", spew.Sdump(r.mcState))
return nil
}
// nodesByMaxFee is a helper function to order the passed nodes by overall max
// fee towards the target node if we'd want to fwd the passed amount.
func nodesByMaxFee(amt btcutil.Amount, target route.Vertex,
nodes map[route.Vertex]*lndclient.NodeInfo) []nodeFeeInfo {
// maxFeePerNode assigns the maximum fees that would be paid through
// selected nodes to the target node for the target amount.
maxFeePerNode := make(map[route.Vertex]int64)
totalCapacityPerNode := make(map[route.Vertex]btcutil.Amount)
amtMsat := int64(amt * 1000)
for nodeID, node := range nodes {
var totalCapacity btcutil.Amount
for _, ch := range node.Channels {
var policy *lndclient.RoutingPolicy
if ch.Node1 == nodeID && ch.Node2 == target {
policy = ch.Node1Policy
} else if ch.Node1 == target && ch.Node2 == nodeID {
policy = ch.Node2Policy
}
if policy == nil {
continue
}
totalCapacity += ch.Capacity
log.Debugf("'%v', policy=%v",
node.Alias, spew.Sdump(policy))
fee := policy.FeeBaseMsat +
policy.FeeRateMilliMsat*amtMsat
// For all peers we'll save the "maximum" routing fee
// for that peer.
if fee > maxFeePerNode[nodeID] {
maxFeePerNode[nodeID] = fee
}
}
totalCapacityPerNode[nodeID] = totalCapacity
}
nodesByMaxFee := make([]nodeFeeInfo, 0, len(maxFeePerNode))
// Sort peers by maximum fee, so that we can later on disable edges
// in a gradual way.
for nodeID, fee := range maxFeePerNode {
nodesByMaxFee = append(
nodesByMaxFee, nodeFeeInfo{
node: nodeID,
capacity: totalCapacityPerNode[nodeID],
fee: fee,
},
)
}
sort.Slice(nodesByMaxFee, func(i, j int) bool {
return nodesByMaxFee[i].fee < nodesByMaxFee[j].fee
})
for i, nodeFee := range nodesByMaxFee {
log.Tracef("nodesByMaxFee[%v] = %v (%v)", i,
nodeFee.node.String(), nodeFee.fee)
}
return nodesByMaxFee
}
// Init will initialize the "low to high" routing plugin. It'll save the MC
// state and also preinit the internal state of the routing plugin. When the
// instance is released, the saved MC state can be restored.
func (r *lowToHighRoutingPlugin) Init(ctx context.Context, target route.Vertex,
routeHints [][]zpay32.HopHint, amt btcutil.Amount) error {
// Prepare the private edges from the passed route hints.
privateEdges := buildPrivateChannels(routeHints, target)
// Save the original target as the current "exit" node: where
// our payments should flow forward.
exit := target
// Walk until the first fork (if there's any). This first
// fork will be where we're going to try to manipulate success
// probabilities to increasingly prefer more expensive edges.
// We track all visited peers, so we won't end up walking
// back and forth on graphs that have no forks.
visited := map[route.Vertex]struct{}{
target: {},
}
var (
targetNodeInfo *lndclient.NodeInfo
err error
)
for {
targetNodeInfo, err = getNodeInfo(
ctx, r.lnd.Client, target, privateEdges,
)
if err != nil {
return err
}
// If the target node has only one or more channels but all
// connects to the same peer, then we need to walk further.
var (
peer route.Vertex
peers []route.Vertex
)
for _, edge := range targetNodeInfo.Channels {
if target != edge.Node1 {
peer = edge.Node1
} else {
peer = edge.Node2
}
if _, ok := visited[peer]; !ok {
visited[peer] = struct{}{}
peers = append(peers, peer)
}
}
if len(peers) == 1 {
exit = target
target = peers[0]
continue
}
// If there are no more peers to visit then we can't use
// this routing plugin.
if len(peers) == 0 {
return ErrRoutingPluginNotApplicable
}
// Found the first fork to our target.
break
}
log.Debugf("Low/high plugin target: '%v' %v", targetNodeInfo.Alias,
targetNodeInfo.PubKey.String())
// Gather node info (including channels) for the nodes we're
// interested in.
targetChanged := exit != target
nodes := make(map[route.Vertex]*lndclient.NodeInfo)
for _, edge := range targetNodeInfo.Channels {
// Skip edges to the exit node since from there the route to
// the invoice target is always constructed from the same hops.
if targetChanged &&
(edge.Node1 == exit || edge.Node2 == exit) {
continue
}
var peer route.Vertex
if edge.Node1 == target {
peer = edge.Node2
} else {
peer = edge.Node1
}
nodeInfo, err := getNodeInfo(ctx, r.lnd.Client, peer, nil)
if err != nil {
return err
}
nodes[peer] = nodeInfo
}
// Get the nodes ordered by routing fee towards the target.
r.nodesByMaxFee = nodesByMaxFee(amt, target, nodes)
r.target = target
r.amount = amt
// Save MC state.
err = r.saveMissionControlState(ctx, nodes, target)
if err != nil {
return err
}
return nil
}
// BeforePayment will reconfigure the mission control on each payment attempt.
func (r *lowToHighRoutingPlugin) BeforePayment(ctx context.Context,
currAttempt int, maxAttempts int) error {
queryRoutesReq := lndclient.QueryRoutesRequest{
Source: &r.lnd.NodePubkey,
PubKey: r.target,
AmtMsat: lnwire.MilliSatoshi(r.amount * 1000),
FeeLimitMsat: lnwire.MilliSatoshi(r.amount * 1000),
UseMissionControl: true,
}
// If logging in trace level, query routes and log to see how what path
// we find before MC is manipulated.
if log.Level() == btclog.LevelTrace {
res, err := r.lnd.Client.QueryRoutes(ctx, queryRoutesReq)
log.Tracef("BeforePayment() QueryRoutes(1)=%v, err=%v",
spew.Sdump(res), err)
}
// Do not do anything unless we tried to route the payment at least
// once.
if currAttempt < 2 {
return nil
}
// Calculate the limit until we'll disable edges. The way we calculate
// this limit is that we take the minimum and maximum fee peers which
// define our fee range. Within this fee range we'll scale linearly
// where each step euqals to the range divided by maxAttempts.
minFee := r.nodesByMaxFee[0].fee
maxFee := r.nodesByMaxFee[len(r.nodesByMaxFee)-1].fee
limit := minFee +
((maxFee-minFee)/int64(maxAttempts))*int64(currAttempt)
// Create a timestamp just slightly in the future as Mission Control
// stores timestamps with sub second precision where as we send a unix
// timestamp it may occur that we can't override the last entries as
// they have the same unix timestamp.
// TODO(bhandras): not very reliable, ideally we'd need a force import
// for MC.
now := r.clock.Now().Add(time.Second)
allowed := 0
entries := make(
[]lndclient.MissionControlEntry, 0, len(r.nodesByMaxFee),
)
for _, nodeFeeInfo := range r.nodesByMaxFee {
if nodeFeeInfo.fee < limit {
log.Debugf("Discouraging payments from %v to %v",
nodeFeeInfo.node, r.target)
entries = append(
entries, lndclient.MissionControlEntry{
NodeFrom: nodeFeeInfo.node,
NodeTo: r.target,
FailTime: now,
FailAmt: 1,
})
} else {
log.Debugf("Encouraging payments from %v to %v",
nodeFeeInfo.node, r.target)
entries = append(
entries, lndclient.MissionControlEntry{
NodeFrom: nodeFeeInfo.node,
NodeTo: r.target,
SuccessTime: now,
SuccessAmt: lnwire.MilliSatoshi(
nodeFeeInfo.capacity * 1000,
),
})
allowed++
}
}
// There's no point retrying the payment since we discouraged using
// all inbound peers to the target.
if allowed == 0 {
return ErrRoutingPluginNoMoreRetries
}
err := r.lnd.Router.ImportMissionControl(ctx, entries)
if err != nil {
return err
}
// Flag that we have changed the MC state.
r.mcChanged = true
log.Tracef("Imported MC state: %v", spew.Sdump(entries))
// If logging in trace level, query routes and log to see how our
// changes affected path finding.
if log.Level() == btclog.LevelTrace {
res, err := r.lnd.Client.QueryRoutes(ctx, queryRoutesReq)
log.Tracef("BeforePayment() QueryRoutes(2)=%v, err=%v",
spew.Sdump(res), err)
}
return nil
}
// Done will attempt to reconstruct the MC state for the affected node pairs to
// the same state as it was before using the routing plugin. For those node
// pairs where the beginning state was empty, we set success for the maximum
// capacity for the sake of simplicity.
func (r *lowToHighRoutingPlugin) Done(ctx context.Context) error {
if r.mcState == nil {
return nil
}
defer func() {
r.mcState = nil
}()
// If none of the selected pairs were manipulated we can skip ahead.
if !r.mcChanged {
log.Debugf("MC state not changed, skipping restore")
return nil
}
// Roll the entry times forward (to be able to override recent updates).
// Use the "time travel" trick which is required to make overrides
// succeed.
now := r.clock.Now().Add(time.Second)
entries := make(
[]lndclient.MissionControlEntry, 0, len(r.nodesByMaxFee),
)
for _, nodeInfo := range r.nodesByMaxFee {
// We didn't have MC state for this node pair before, so just
// set it to succeed the max amount and fail anything more than
// that. This way we don't restrict forwarding for normal cases.
if _, ok := r.mcState[nodeInfo.node]; !ok {
capacity := lnwire.MilliSatoshi(
nodeInfo.capacity * 1000,
)
entries = append(
entries, lndclient.MissionControlEntry{
NodeFrom: nodeInfo.node,
NodeTo: r.target,
FailTime: now,
FailAmt: capacity + 1,
SuccessTime: now,
SuccessAmt: capacity,
})
} else {
// We did have a MC entry for this pair, so we just bump
// the time to now + 1 sec.
entry := r.mcState[nodeInfo.node]
if !entry.FailTime.IsZero() {
entry.FailTime = now
}
if !entry.SuccessTime.IsZero() {
entry.SuccessTime = now
}
entries = append(entries, entry)
}
}
err := r.lnd.Router.ImportMissionControl(ctx, entries)
if err != nil {
return err
}
log.Debugf("Restored partial MC state: %v",
spew.Sdump(entries))
return nil
}

@ -0,0 +1,698 @@
package loop
import (
"context"
"testing"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require"
)
var (
alice = route.Vertex{1}
bob = route.Vertex{2}
charlie = route.Vertex{3}
dave = route.Vertex{4}
eugene = route.Vertex{5}
loopNode = route.Vertex{99}
privFrank, _ = btcec.NewPrivateKey(btcec.S256())
frankPubKey = privFrank.PubKey()
frank = route.NewVertex(frankPubKey)
privGeorge, _ = btcec.NewPrivateKey(btcec.S256())
georgePubKey = privGeorge.PubKey()
george = route.NewVertex(georgePubKey)
)
// testChan holds simplified test data for channels.
type testChan struct {
nodeID1 route.Vertex
nodeID2 route.Vertex
chanID uint64
capacity int64
feeBase1 int64
feeRate1 int64
feeBase2 int64
feeRate2 int64
}
// makeTestNetwork is a helper creating mocked network data from test inputs.
func makeTestNetwork(channels []testChan) ([]lndclient.ChannelInfo,
map[uint64]*lndclient.ChannelEdge) {
chanInfos := make([]lndclient.ChannelInfo, len(channels))
edges := make(map[uint64]*lndclient.ChannelEdge, len(channels))
for i, ch := range channels {
chanInfos[i] = lndclient.ChannelInfo{
ChannelID: ch.chanID,
}
edges[ch.chanID] = &lndclient.ChannelEdge{
ChannelID: ch.chanID,
Capacity: btcutil.Amount(ch.capacity),
Node1: ch.nodeID1,
Node2: ch.nodeID2,
Node1Policy: &lndclient.RoutingPolicy{
FeeBaseMsat: ch.feeBase1,
FeeRateMilliMsat: ch.feeRate1,
},
Node2Policy: &lndclient.RoutingPolicy{
FeeBaseMsat: ch.feeBase2,
FeeRateMilliMsat: ch.feeRate2,
},
}
}
return chanInfos, edges
}
// TestLowHighRoutingPlugin tests that the low-high routing plugin does indeed
// gradually change MC settings in favour of more expensive inbound channels
// towards the Loop server.
func TestLowHighRoutingPlugin(t *testing.T) {
target := loopNode
amt := btcutil.Amount(50)
testTime := time.Now().UTC()
// We expect Mission Control entries to be set to now + 1 sec.
testTimeMc := testTime.Add(time.Second)
tests := []struct {
name string
channels []testChan
routeHints [][]zpay32.HopHint
initError error
missionControlState [][]lndclient.MissionControlEntry
restoredMissionControlState []lndclient.MissionControlEntry
}{
{
name: "degenerate network 1",
//
// Alice --- Loop
//
channels: []testChan{
{alice, loopNode, 1, 1000, 1000, 1, 1000, 1},
},
initError: ErrRoutingPluginNotApplicable,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{
NodeFrom: alice,
NodeTo: loopNode,
SuccessTime: testTime,
SuccessAmt: 10000,
},
},
},
},
{
name: "degenerate network 2",
//
// Alice --- Bob --- Loop
//
channels: []testChan{
// Alice - Bob
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
// Bob - Loop
{bob, loopNode, 2, 1000, 1000, 1, 1000, 1},
},
initError: ErrRoutingPluginNotApplicable,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{},
},
},
},
{
name: "degenrate network 3",
//
// _____Bob_____
// / \
// Alice Dave---Loop
// \___
// Charlie
//
channels: []testChan{
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
// Bob - Dave (cheap)
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
},
initError: ErrRoutingPluginNotApplicable,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{
NodeFrom: bob,
NodeTo: dave,
FailTime: time.Time{},
FailAmt: 0,
SuccessTime: testTime,
SuccessAmt: 10000,
},
},
},
restoredMissionControlState: []lndclient.MissionControlEntry{
{
NodeFrom: bob,
NodeTo: dave,
FailTime: time.Time{},
FailAmt: 0,
SuccessTime: testTimeMc,
SuccessAmt: 10000,
},
},
},
{ // nolint: dupl
name: "fork before loop node 1",
//
// _____Bob_____
// / \
// Alice Dave---Loop
// \___ ___/
// Charlie
//
channels: []testChan{
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
// Bob - Dave (cheap)
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
// Charlie - Dave (expensive)
{charlie, dave, 4, 1000, 1000, 100, 1000, 1},
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
},
initError: nil,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{
NodeFrom: bob,
NodeTo: dave,
FailTime: time.Time{},
FailAmt: 0,
SuccessTime: testTime,
SuccessAmt: 10000,
},
},
// MC state set on the second attempt.
{
// Discourage Bob - Dave
{
NodeFrom: bob,
NodeTo: dave,
FailTime: testTimeMc,
FailAmt: 1,
},
// Encourage Charlie - Dave
{
NodeFrom: charlie,
NodeTo: dave,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
},
restoredMissionControlState: []lndclient.MissionControlEntry{
{
NodeFrom: bob,
NodeTo: dave,
FailTime: time.Time{},
FailAmt: 0,
SuccessTime: testTimeMc,
SuccessAmt: 10000,
},
{
NodeFrom: charlie,
NodeTo: dave,
FailTime: testTimeMc,
FailAmt: 1000001,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
},
{ // nolint: dupl
name: "fork before loop node 1 with equal inbound fees",
//
// _____Bob_____
// / \
// Alice Dave---Loop
// \___ ___/
// Charlie
//
channels: []testChan{
{alice, bob, 1, 999, 1000, 1, 1000, 1},
{alice, charlie, 2, 9999, 1000, 1, 1000, 1},
// Bob - Dave (expensive)
{bob, dave, 3, 999, 1000, 100, 1000, 1},
// Charlie - Dave (expensive)
{charlie, dave, 4, 999, 1000, 100, 1000, 1},
{dave, loopNode, 5, 999, 1000, 1, 1000, 1},
},
initError: nil,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{
NodeFrom: dave,
NodeTo: loopNode,
SuccessTime: testTime,
SuccessAmt: 10000,
},
},
// MC state on the second attempt encourages
// both inbound peers to make sure we do try
// to route through both.
{
{
NodeFrom: dave,
NodeTo: loopNode,
SuccessTime: testTime,
SuccessAmt: 10000,
},
{
NodeFrom: bob,
NodeTo: dave,
SuccessTime: testTimeMc,
SuccessAmt: 999000,
},
{
NodeFrom: charlie,
NodeTo: dave,
SuccessTime: testTimeMc,
SuccessAmt: 999000,
},
},
},
restoredMissionControlState: []lndclient.MissionControlEntry{
{
NodeFrom: dave,
NodeTo: loopNode,
SuccessTime: testTime,
SuccessAmt: 10000,
},
{
NodeFrom: bob,
NodeTo: dave,
SuccessTime: testTimeMc,
SuccessAmt: 999000,
FailTime: testTimeMc,
FailAmt: 999001,
},
{
NodeFrom: charlie,
NodeTo: dave,
SuccessTime: testTimeMc,
SuccessAmt: 999000,
FailTime: testTimeMc,
FailAmt: 999001,
},
},
},
{
name: "fork before loop node 2",
//
// _____Bob_____
// / \
// Alice Eugene---Frank---George---Loop
// |\___ ___//
// | Charlie /
// \ /
// \___ ___/
// Dave
//
channels: []testChan{
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
{alice, dave, 3, 1000, 1000, 1, 1000, 1},
// Bob - Eugene (cheap)
{bob, eugene, 4, 1000, 1000, 1, 1000, 1},
// Charlie - Eugene (more expensive)
{charlie, eugene, 5, 1000, 1000, 2, 1000, 1},
// Dave - Eugene (most expensive)
{dave, eugene, 6, 1000, 1001, 2, 1000, 1},
{eugene, frank, 7, 1000, 1000, 1, 1000, 1},
},
// Private channels: Frank - George - Loop
routeHints: [][]zpay32.HopHint{{
{
NodeID: frankPubKey,
ChannelID: 8,
FeeBaseMSat: 1000,
FeeProportionalMillionths: 1,
},
{
NodeID: georgePubKey,
ChannelID: 9,
FeeBaseMSat: 1000,
FeeProportionalMillionths: 1,
},
}},
initError: nil,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{
NodeFrom: charlie,
NodeTo: eugene,
SuccessTime: testTime,
SuccessAmt: 10000,
},
},
// MC state set on the second attempt.
{
// Discourage Bob - Eugene
{
NodeFrom: bob,
NodeTo: eugene,
FailTime: testTimeMc,
FailAmt: 1,
},
// Encourage Charlie - Eugene
{
NodeFrom: charlie,
NodeTo: eugene,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
// Encourage Dave - Eugene
{
NodeFrom: dave,
NodeTo: eugene,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
// MC state set on the third attempt.
{
// Discourage Bob - Eugene
{
NodeFrom: bob,
NodeTo: eugene,
FailTime: testTimeMc,
FailAmt: 1,
},
// Discourage Charlie - Eugene
{
NodeFrom: charlie,
NodeTo: eugene,
FailTime: testTimeMc,
FailAmt: 1,
},
// Encourage Dave - Eugene
{
NodeFrom: dave,
NodeTo: eugene,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
},
restoredMissionControlState: []lndclient.MissionControlEntry{
{
NodeFrom: bob,
NodeTo: eugene,
FailTime: testTimeMc,
FailAmt: 1000001,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
{
NodeFrom: charlie,
NodeTo: eugene,
FailTime: time.Time{},
FailAmt: 0,
SuccessTime: testTimeMc,
SuccessAmt: 10000,
},
{
NodeFrom: dave,
NodeTo: eugene,
FailTime: testTimeMc,
FailAmt: 1000001,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
},
{
name: "fork before loop node 3",
//
// _____Bob_____
// / \
// Alice Eugene---Frank---George---Loop
// |\___ ___/ /
// | Charlie /
// \ /
// \___ ___________________/
// Dave
//
channels: []testChan{
// Alice - Bob
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
// Alice - Charlie
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
// Alice - Dave
{alice, dave, 3, 1000, 1000, 1, 1000, 1},
// Bob - Eugene
{bob, eugene, 4, 1000, 1000, 1, 1000, 1},
// Charlie - Eugene
{charlie, eugene, 5, 1000, 1000, 2, 1000, 1},
// Dave - George (expensive)
{dave, george, 6, 1000, 1001, 2, 1000, 1},
// Eugene - Frank
{eugene, frank, 7, 1000, 1000, 1, 1000, 1},
// Frank - George (cheap)
{frank, george, 8, 1000, 1000, 1, 1000, 1},
// George - Loop
{george, loopNode, 9, 1000, 1000, 1, 1000, 1},
},
initError: nil,
missionControlState: [][]lndclient.MissionControlEntry{
// The original MC state we start with.
{
{
NodeFrom: charlie,
NodeTo: eugene,
SuccessTime: testTime,
SuccessAmt: 10000,
},
},
// MC state set on the second attempt.
{
{
NodeFrom: charlie,
NodeTo: eugene,
SuccessTime: testTime,
SuccessAmt: 10000,
},
// Discourage Frank - George
{
NodeFrom: frank,
NodeTo: george,
FailTime: testTimeMc,
FailAmt: 1,
},
// Encourage Dave - George
{
NodeFrom: dave,
NodeTo: george,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
},
restoredMissionControlState: []lndclient.MissionControlEntry{
{
NodeFrom: charlie,
NodeTo: eugene,
SuccessTime: testTime,
SuccessAmt: 10000,
},
{
NodeFrom: frank,
NodeTo: george,
FailTime: testTimeMc,
FailAmt: 1000001,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
{
NodeFrom: dave,
NodeTo: george,
FailTime: testTimeMc,
FailAmt: 1000001,
SuccessTime: testTimeMc,
SuccessAmt: 1000000,
},
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
mockLnd := test.NewMockLnd()
mockLnd.Channels, mockLnd.ChannelEdges =
makeTestNetwork(tc.channels)
lnd := lndclient.LndServices{
Client: mockLnd.Client,
Router: mockLnd.Router,
}
testClock := clock.NewTestClock(testTime)
plugin := makeRoutingPlugin(
RoutingPluginLowHigh, lnd, testClock,
)
require.NotNil(t, plugin)
// Set start state for MC.
mockLnd.MissionControlState = tc.missionControlState[0]
// Initialize the routing plugin.
require.Equal(
t, tc.initError,
plugin.Init(
context.TODO(), target, tc.routeHints,
amt,
),
)
if tc.initError != nil {
// Make sure that MC state is untouched.
require.Equal(
t, tc.missionControlState[0],
mockLnd.MissionControlState,
)
return
}
maxAttempts := len(tc.missionControlState)
for i, expectedState := range tc.missionControlState {
// Check that after each step, MC state is what
// we expect it to be.
require.NoError(
t, plugin.BeforePayment(
context.TODO(),
i+1, maxAttempts,
),
)
require.ElementsMatch(
t, expectedState,
mockLnd.MissionControlState,
)
}
// Make sure we covered all inbound channels.
require.Error(
t, ErrRoutingPluginNoMoreRetries,
plugin.BeforePayment(
context.TODO(), maxAttempts, maxAttempts,
),
)
// Deinitialize the routing plugin.
require.NoError(t, plugin.Done(context.TODO()))
// Make sure that MC state is reset after Done() is
// called.
require.ElementsMatch(
t, tc.restoredMissionControlState,
mockLnd.MissionControlState,
)
})
}
}
func TestRoutingPluginAcquireRelease(t *testing.T) {
mockLnd := test.NewMockLnd()
// _____Bob_____
// / \
// Alice Dave---Loop
// \___ ___/
// Charlie
//
channels := []testChan{
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
{charlie, dave, 4, 1000, 1000, 100, 1000, 1},
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
}
mockLnd.Channels, mockLnd.ChannelEdges = makeTestNetwork(channels)
lnd := lndclient.LndServices{
Client: mockLnd.Client,
Router: mockLnd.Router,
}
target := loopNode
amt := btcutil.Amount(50)
ctx := context.TODO()
// RoutingPluginNone returns nil.
plugin, err := AcquireRoutingPlugin(
ctx, RoutingPluginNone, lnd, target, nil, amt,
)
require.Nil(t, plugin)
require.NoError(t, err)
// Attempting to acquire RoutingPluginNone again still returns nil.
plugin, err = AcquireRoutingPlugin(
ctx, RoutingPluginNone, lnd, target, nil, amt,
)
require.Nil(t, plugin)
require.NoError(t, err)
// Call ReleaseRoutingPlugin twice to ensure we can call it even when no
// plugin is acquired.
ReleaseRoutingPlugin(ctx)
ReleaseRoutingPlugin(ctx)
// RoutingPluginNone returns nil.
plugin2, err := AcquireRoutingPlugin(
ctx, RoutingPluginNone, lnd, target, nil, amt,
)
require.Nil(t, plugin2)
require.NoError(t, err)
// Acquire is successful.
plugin, err = AcquireRoutingPlugin(
ctx, RoutingPluginLowHigh, lnd, target, nil, amt,
)
require.NotNil(t, plugin)
require.NoError(t, err)
// Plugin already acquired, above.
plugin2, err = AcquireRoutingPlugin(
ctx, RoutingPluginLowHigh, lnd, target, nil, amt,
)
require.Nil(t, plugin2)
require.NoError(t, err)
// Release acruired plugin.
ReleaseRoutingPlugin(ctx)
// Acquire is successful.
plugin2, err = AcquireRoutingPlugin(
ctx, RoutingPluginLowHigh, lnd, target, nil, amt,
)
require.NotNil(t, plugin2)
require.NoError(t, err)
}

@ -242,3 +242,15 @@ func (s *serverMock) Probe(ctx context.Context, amt btcutil.Amount,
return nil
}
func (s *serverMock) RecommendRoutingPlugin(_ context.Context, _ lntypes.Hash,
_ [32]byte) (RoutingPluginType, error) {
return RoutingPluginNone, nil
}
func (s *serverMock) ReportRoutingResult(_ context.Context, _ lntypes.Hash,
_ [32]byte, _ RoutingPluginType, _ bool, _ int32, _ int64) error {
return nil
}

@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
)
@ -27,24 +28,30 @@ func FeeRateAsPercentage(feeRate int64) float64 {
return float64(feeRate) / (FeeRateTotalParts / 100)
}
// DecodeInvoice gets the hash and the amount of an invoice.
// DecodeInvoice gets the destination, hash and the amount of an invoice.
// It requires an amount to be specified.
func DecodeInvoice(params *chaincfg.Params,
payReq string) (lntypes.Hash, btcutil.Amount, error) {
func DecodeInvoice(params *chaincfg.Params, payReq string) (route.Vertex,
[][]zpay32.HopHint, lntypes.Hash, btcutil.Amount, error) {
swapPayReq, err := zpay32.Decode(
payReq, params,
)
if err != nil {
return lntypes.Hash{}, 0, err
return route.Vertex{}, nil, lntypes.Hash{}, 0, err
}
if swapPayReq.MilliSat == nil {
return lntypes.Hash{}, 0, errors.New("no amount in invoice")
return route.Vertex{}, nil, lntypes.Hash{}, 0,
errors.New("no amount in invoice")
}
var hash lntypes.Hash
copy(hash[:], swapPayReq.PaymentHash[:])
return hash, swapPayReq.MilliSat.ToSatoshis(), nil
var destination route.Vertex
destPubKey := swapPayReq.Destination.SerializeCompressed()
copy(destination[:], destPubKey)
return destination, swapPayReq.RouteHints, hash,
swapPayReq.MilliSat.ToSatoshis(), nil
}

@ -43,6 +43,30 @@ var (
"be provided")
)
// RoutingPluginType represents the routing plugin type directly.
type RoutingPluginType uint8
const (
// RoutingPluginNone is recommended when the client shouldn't use any
// routing plugin.
RoutingPluginNone RoutingPluginType = 0
// RoutingPluginLowHigh is recommended when the client should use
// low-high routing method.
RoutingPluginLowHigh RoutingPluginType = 1
)
// String pretty prints a RoutingPluginType.
func (r RoutingPluginType) String() string {
switch r {
case RoutingPluginLowHigh:
return "Low/High"
default:
return "None"
}
}
type swapServerClient interface {
GetLoopOutTerms(ctx context.Context) (
*LoopOutTerms, error)
@ -86,6 +110,17 @@ type swapServerClient interface {
// CancelLoopOutSwap cancels a loop out swap.
CancelLoopOutSwap(ctx context.Context,
details *outCancelDetails) error
// RecommendRoutingPlugin asks the server for routing plugin
// recommendation for off-chain payment(s) of a swap.
RecommendRoutingPlugin(ctx context.Context, swapHash lntypes.Hash,
paymentAddr [32]byte) (RoutingPluginType, error)
// ReportRoutingResult reports a routing result corresponding to a swap.
ReportRoutingResult(ctx context.Context,
swapHash lntypes.Hash, paymentAddr [32]byte,
plugin RoutingPluginType, success bool, attempts int32,
totalTime int64) error
}
type grpcSwapServerClient struct {
@ -619,6 +654,72 @@ func (s *grpcSwapServerClient) CancelLoopOutSwap(ctx context.Context,
return err
}
// RecommendRoutingPlugin asks the server for routing plugin recommendation for
// off-chain payment(s) of a swap.
func (s *grpcSwapServerClient) RecommendRoutingPlugin(ctx context.Context,
swapHash lntypes.Hash, paymentAddr [32]byte) (RoutingPluginType, error) {
req := &looprpc.RecommendRoutingPluginReq{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion,
SwapHash: swapHash[:],
PaymentAddress: paymentAddr[:],
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
res, err := s.server.RecommendRoutingPlugin(rpcCtx, req)
if err != nil {
return RoutingPluginNone, err
}
var plugin RoutingPluginType
switch res.Plugin {
case looprpc.RoutingPlugin_NONE:
plugin = RoutingPluginNone
case looprpc.RoutingPlugin_LOW_HIGH:
plugin = RoutingPluginLowHigh
default:
log.Warnf("Recommended routing plugin is unknown: %v", plugin)
plugin = RoutingPluginNone
}
return plugin, nil
}
// ReportRoutingResult reports a routing result corresponding to a swap.
func (s *grpcSwapServerClient) ReportRoutingResult(ctx context.Context,
swapHash lntypes.Hash, paymentAddr [32]byte, plugin RoutingPluginType,
success bool, attempts int32, totalTime int64) error {
var rpcRoutingPlugin looprpc.RoutingPlugin
switch plugin {
case RoutingPluginLowHigh:
rpcRoutingPlugin = looprpc.RoutingPlugin_LOW_HIGH
default:
rpcRoutingPlugin = looprpc.RoutingPlugin_NONE
}
req := &looprpc.ReportRoutingResultReq{
ProtocolVersion: loopdb.CurrentRPCProtocolVersion,
SwapHash: swapHash[:],
PaymentAddress: paymentAddr[:],
Plugin: rpcRoutingPlugin,
Success: success,
Attempts: attempts,
TotalTime: totalTime,
}
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
_, err := s.server.ReportRoutingResult(rpcCtx, req)
return err
}
func rpcRouteCancel(details *outCancelDetails) (
*looprpc.CancelLoopOutSwapRequest_RouteCancel, error) {

@ -59,6 +59,9 @@ const (
// The client is able to ask the server to probe to test inbound
// liquidity.
ProtocolVersion_PROBE ProtocolVersion = 8
// The client may ask the server to use a custom routing helper plugin in
// order to enhance off-chain payments corresponding to a swap.
ProtocolVersion_ROUTING_PLUGIN ProtocolVersion = 9
)
// Enum value maps for ProtocolVersion.
@ -73,6 +76,7 @@ var (
6: "MULTI_LOOP_IN",
7: "LOOP_OUT_CANCEL",
8: "PROBE",
9: "ROUTING_PLUGIN",
}
ProtocolVersion_value = map[string]int32{
"LEGACY": 0,
@ -84,6 +88,7 @@ var (
"MULTI_LOOP_IN": 6,
"LOOP_OUT_CANCEL": 7,
"PROBE": 8,
"ROUTING_PLUGIN": 9,
}
)
@ -357,6 +362,54 @@ func (PaymentFailureReason) EnumDescriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{3}
}
type RoutingPlugin int32
const (
// Client won't use any plugins to help with payment routing.
RoutingPlugin_NONE RoutingPlugin = 0
// Client will try more expensive routes for off-chain payments.
RoutingPlugin_LOW_HIGH RoutingPlugin = 1
)
// Enum value maps for RoutingPlugin.
var (
RoutingPlugin_name = map[int32]string{
0: "NONE",
1: "LOW_HIGH",
}
RoutingPlugin_value = map[string]int32{
"NONE": 0,
"LOW_HIGH": 1,
}
)
func (x RoutingPlugin) Enum() *RoutingPlugin {
p := new(RoutingPlugin)
*p = x
return p
}
func (x RoutingPlugin) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (RoutingPlugin) Descriptor() protoreflect.EnumDescriptor {
return file_server_proto_enumTypes[4].Descriptor()
}
func (RoutingPlugin) Type() protoreflect.EnumType {
return &file_server_proto_enumTypes[4]
}
func (x RoutingPlugin) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use RoutingPlugin.Descriptor instead.
func (RoutingPlugin) EnumDescriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{4}
}
type ServerLoopOutRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1947,6 +2000,260 @@ func (*ServerProbeResponse) Descriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{22}
}
type RecommendRoutingPluginReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The hash of the swap requesting a routing plugin.
SwapHash []byte `protobuf:"bytes,2,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"`
// The payment address for the swap invoice, used to ensure that only the
// swap owner can request routing plugin recommendation.
PaymentAddress []byte `protobuf:"bytes,3,opt,name=payment_address,json=paymentAddress,proto3" json:"payment_address,omitempty"`
}
func (x *RecommendRoutingPluginReq) Reset() {
*x = RecommendRoutingPluginReq{}
if protoimpl.UnsafeEnabled {
mi := &file_server_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RecommendRoutingPluginReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RecommendRoutingPluginReq) ProtoMessage() {}
func (x *RecommendRoutingPluginReq) ProtoReflect() protoreflect.Message {
mi := &file_server_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RecommendRoutingPluginReq.ProtoReflect.Descriptor instead.
func (*RecommendRoutingPluginReq) Descriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{23}
}
func (x *RecommendRoutingPluginReq) GetProtocolVersion() ProtocolVersion {
if x != nil {
return x.ProtocolVersion
}
return ProtocolVersion_LEGACY
}
func (x *RecommendRoutingPluginReq) GetSwapHash() []byte {
if x != nil {
return x.SwapHash
}
return nil
}
func (x *RecommendRoutingPluginReq) GetPaymentAddress() []byte {
if x != nil {
return x.PaymentAddress
}
return nil
}
type RecommendRoutingPluginRes struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The routing plugin to use for off-chain payments.
Plugin RoutingPlugin `protobuf:"varint,1,opt,name=plugin,proto3,enum=looprpc.RoutingPlugin" json:"plugin,omitempty"`
}
func (x *RecommendRoutingPluginRes) Reset() {
*x = RecommendRoutingPluginRes{}
if protoimpl.UnsafeEnabled {
mi := &file_server_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RecommendRoutingPluginRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RecommendRoutingPluginRes) ProtoMessage() {}
func (x *RecommendRoutingPluginRes) ProtoReflect() protoreflect.Message {
mi := &file_server_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RecommendRoutingPluginRes.ProtoReflect.Descriptor instead.
func (*RecommendRoutingPluginRes) Descriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{24}
}
func (x *RecommendRoutingPluginRes) GetPlugin() RoutingPlugin {
if x != nil {
return x.Plugin
}
return RoutingPlugin_NONE
}
type ReportRoutingResultReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The swap hash.
SwapHash []byte `protobuf:"bytes,2,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"`
// The payment address for the swap invoice, used to ensure that only the
// swap owner can report routing result.
PaymentAddress []byte `protobuf:"bytes,3,opt,name=payment_address,json=paymentAddress,proto3" json:"payment_address,omitempty"`
// The routing plugin that was used.
Plugin RoutingPlugin `protobuf:"varint,4,opt,name=plugin,proto3,enum=looprpc.RoutingPlugin" json:"plugin,omitempty"`
// Whether this payment succeeded.
Success bool `protobuf:"varint,5,opt,name=success,proto3" json:"success,omitempty"`
// The number of payment attempts using the plugin.
Attempts int32 `protobuf:"varint,6,opt,name=attempts,proto3" json:"attempts,omitempty"`
// Total time used in milliseconds.
TotalTime int64 `protobuf:"varint,7,opt,name=total_time,json=totalTime,proto3" json:"total_time,omitempty"`
}
func (x *ReportRoutingResultReq) Reset() {
*x = ReportRoutingResultReq{}
if protoimpl.UnsafeEnabled {
mi := &file_server_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReportRoutingResultReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReportRoutingResultReq) ProtoMessage() {}
func (x *ReportRoutingResultReq) ProtoReflect() protoreflect.Message {
mi := &file_server_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReportRoutingResultReq.ProtoReflect.Descriptor instead.
func (*ReportRoutingResultReq) Descriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{25}
}
func (x *ReportRoutingResultReq) GetProtocolVersion() ProtocolVersion {
if x != nil {
return x.ProtocolVersion
}
return ProtocolVersion_LEGACY
}
func (x *ReportRoutingResultReq) GetSwapHash() []byte {
if x != nil {
return x.SwapHash
}
return nil
}
func (x *ReportRoutingResultReq) GetPaymentAddress() []byte {
if x != nil {
return x.PaymentAddress
}
return nil
}
func (x *ReportRoutingResultReq) GetPlugin() RoutingPlugin {
if x != nil {
return x.Plugin
}
return RoutingPlugin_NONE
}
func (x *ReportRoutingResultReq) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
func (x *ReportRoutingResultReq) GetAttempts() int32 {
if x != nil {
return x.Attempts
}
return 0
}
func (x *ReportRoutingResultReq) GetTotalTime() int64 {
if x != nil {
return x.TotalTime
}
return 0
}
type ReportRoutingResultRes struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ReportRoutingResultRes) Reset() {
*x = ReportRoutingResultRes{}
if protoimpl.UnsafeEnabled {
mi := &file_server_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReportRoutingResultRes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReportRoutingResultRes) ProtoMessage() {}
func (x *ReportRoutingResultRes) ProtoReflect() protoreflect.Message {
mi := &file_server_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReportRoutingResultRes.ProtoReflect.Descriptor instead.
func (*ReportRoutingResultRes) Descriptor() ([]byte, []int) {
return file_server_proto_rawDescGZIP(), []int{26}
}
var File_server_proto protoreflect.FileDescriptor
var file_server_proto_rawDesc = []byte{
@ -2176,136 +2483,187 @@ var file_server_proto_rawDesc = []byte{
0x32, 0x12, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73,
0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xc2, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x4c,
0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x55, 0x4c, 0x54, 0x49,
0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x4e,
0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x45, 0x47, 0x57, 0x49, 0x54, 0x5f, 0x4c, 0x4f, 0x4f,
0x50, 0x5f, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x45, 0x49, 0x4d, 0x41,
0x47, 0x45, 0x5f, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54,
0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52,
0x59, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07,
0x48, 0x54, 0x4c, 0x43, 0x5f, 0x56, 0x32, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x55, 0x4c,
0x54, 0x49, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f,
0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10,
0x07, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x52, 0x4f, 0x42, 0x45, 0x10, 0x08, 0x2a, 0xfc, 0x03, 0x0a,
0x0f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65,
0x12, 0x14, 0x0a, 0x10, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49,
0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52,
0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10,
0x01, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x55, 0x43, 0x43,
0x45, 0x53, 0x53, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f,
0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03,
0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45,
0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53,
0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x56,
0x41, 0x4c, 0x49, 0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54,
0x10, 0x05, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49,
0x4c, 0x45, 0x44, 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49,
0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45,
0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54,
0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49,
0x4c, 0x45, 0x44, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e,
0x45, 0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41,
0x49, 0x4c, 0x45, 0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43,
0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x45, 0x52, 0x56, 0x45,
0x52, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53,
0x48, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f,
0x55, 0x4e, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55,
0x52, 0x45, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48,
0x54, 0x4c, 0x43, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x0c, 0x12,
0x1f, 0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54,
0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x0d,
0x12, 0x20, 0x0a, 0x1c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e,
0x54, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c,
0x10, 0x0e, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49,
0x4c, 0x45, 0x44, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x50, 0x4c, 0x45, 0x5f, 0x53, 0x57, 0x41,
0x50, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x53, 0x10, 0x0f, 0x2a, 0x4a, 0x0a, 0x10, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x52, 0x4f, 0x55,
0x54, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f,
0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x2a, 0xf1, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
0x12, 0x1b, 0x0a, 0x17, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f,
0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1e, 0x0a,
0x1a, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1f, 0x0a,
0x1b, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x1c,
0x0a, 0x18, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45,
0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x30, 0x0a, 0x2c,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f,
0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67,
0x69, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x43, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77,
0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73,
0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x22, 0x4b, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75,
0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x12, 0x2e, 0x0a,
0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e,
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50,
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0xa8, 0x02,
0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x12, 0x43, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a,
0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x06, 0x70, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x05,
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a,
0x08, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52,
0x08, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74,
0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f,
0x72, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52,
0x65, 0x73, 0x2a, 0xd6, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59,
0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x4c, 0x4f, 0x4f, 0x50,
0x5f, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x4e, 0x41, 0x54, 0x49, 0x56, 0x45,
0x5f, 0x53, 0x45, 0x47, 0x57, 0x49, 0x54, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10,
0x02, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x45, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x50, 0x55,
0x53, 0x48, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x18, 0x0a,
0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x4c, 0x4f, 0x4f,
0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x54, 0x4c, 0x43, 0x5f,
0x56, 0x32, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x4c, 0x4f,
0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f,
0x4f, 0x55, 0x54, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05,
0x50, 0x52, 0x4f, 0x42, 0x45, 0x10, 0x08, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x4f, 0x55, 0x54, 0x49,
0x4e, 0x47, 0x5f, 0x50, 0x4c, 0x55, 0x47, 0x49, 0x4e, 0x10, 0x09, 0x2a, 0xfc, 0x03, 0x0a, 0x0f,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
0x14, 0x0a, 0x10, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41,
0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f,
0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x01,
0x12, 0x12, 0x0a, 0x0e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45,
0x53, 0x53, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46,
0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x12,
0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
0x5f, 0x4e, 0x4f, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x45,
0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41,
0x4c, 0x49, 0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10,
0x05, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c,
0x45, 0x44, 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d,
0x45, 0x4f, 0x55, 0x54, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52,
0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10,
0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c,
0x45, 0x44, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45,
0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49,
0x4c, 0x45, 0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x41,
0x54, 0x49, 0x4f, 0x4e, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52,
0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48,
0x45, 0x44, 0x10, 0x0a, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x55,
0x4e, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52,
0x45, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48, 0x54,
0x4c, 0x43, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x0c, 0x12, 0x1f,
0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f,
0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x0d, 0x12,
0x20, 0x0a, 0x1c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54,
0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10,
0x0e, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c,
0x45, 0x44, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x50, 0x4c, 0x45, 0x5f, 0x53, 0x57, 0x41, 0x50,
0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x53, 0x10, 0x0f, 0x2a, 0x4a, 0x0a, 0x10, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11,
0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x52, 0x4f, 0x55, 0x54,
0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x52,
0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x2a, 0xf1, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
0x1b, 0x0a, 0x17, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52,
0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a,
0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53,
0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59,
0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x2b,
0x0a, 0x27, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45,
0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e,
0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x32, 0xcf, 0x07, 0x0a, 0x0a,
0x53, 0x77, 0x61, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f,
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f,
0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c,
0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x4e,
0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1d, 0x2e,
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f,
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c,
0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f,
0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13,
0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x12, 0x29, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x75, 0x73, 0x68, 0x50,
0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c,
0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f,
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f,
0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b,
0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53,
0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x1c, 0x0a,
0x18, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x30, 0x0a, 0x2c, 0x4c,
0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f,
0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d,
0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x2b, 0x0a,
0x27, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54,
0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0x27, 0x0a, 0x0d, 0x52, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e,
0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x57, 0x5f, 0x48, 0x49, 0x47,
0x48, 0x10, 0x01, 0x32, 0x8a, 0x09, 0x0a, 0x0a, 0x53, 0x77, 0x61, 0x70, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72,
0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65,
0x72, 0x6d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x4e, 0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75,
0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50,
0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x29, 0x2e, 0x6c, 0x6f,
0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70,
0x4f, 0x75, 0x74, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x75,
0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f,
0x74, 0x65, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75,
0x6f, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72,
0x6d, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d,
0x73, 0x12, 0x4c, 0x0a, 0x0d, 0x4e, 0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x53, 0x77,
0x61, 0x70, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65,
0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x54, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x21,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c,
0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c,
0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49,
0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e,
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f,
0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x4c, 0x0a, 0x0d, 0x4e, 0x65, 0x77,
0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49,
0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f,
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e,
0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a,
0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75,
0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f,
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x65, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73,
0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73,
0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63,
0x72, 0x69, 0x62, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x27, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62,
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5a, 0x0a,
0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77,
0x61, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e,
0x63, 0x65, 0x6c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61,
0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x72, 0x6f,
0x62, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a,
0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x73,
0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62,
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x65,
0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x49,
0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f,
0x6f, 0x70, 0x49, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5a, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c,
0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f,
0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e,
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f,
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70,
0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65,
0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12,
0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d,
0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c,
0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x12, 0x57, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72,
0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52,
0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x1a,
0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73,
0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f,
0x70, 0x2f, 0x73, 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -2320,44 +2678,49 @@ func file_server_proto_rawDescGZIP() []byte {
return file_server_proto_rawDescData
}
var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 27)
var file_server_proto_goTypes = []interface{}{
(ProtocolVersion)(0), // 0: looprpc.ProtocolVersion
(ServerSwapState)(0), // 1: looprpc.ServerSwapState
(RoutePaymentType)(0), // 2: looprpc.RoutePaymentType
(PaymentFailureReason)(0), // 3: looprpc.PaymentFailureReason
(*ServerLoopOutRequest)(nil), // 4: looprpc.ServerLoopOutRequest
(*ServerLoopOutResponse)(nil), // 5: looprpc.ServerLoopOutResponse
(*ServerLoopOutQuoteRequest)(nil), // 6: looprpc.ServerLoopOutQuoteRequest
(*ServerLoopOutQuote)(nil), // 7: looprpc.ServerLoopOutQuote
(*ServerLoopOutTermsRequest)(nil), // 8: looprpc.ServerLoopOutTermsRequest
(*ServerLoopOutTerms)(nil), // 9: looprpc.ServerLoopOutTerms
(*ServerLoopInRequest)(nil), // 10: looprpc.ServerLoopInRequest
(*ServerLoopInResponse)(nil), // 11: looprpc.ServerLoopInResponse
(*ServerLoopInQuoteRequest)(nil), // 12: looprpc.ServerLoopInQuoteRequest
(*ServerLoopInQuoteResponse)(nil), // 13: looprpc.ServerLoopInQuoteResponse
(*ServerLoopInTermsRequest)(nil), // 14: looprpc.ServerLoopInTermsRequest
(*ServerLoopInTerms)(nil), // 15: looprpc.ServerLoopInTerms
(*ServerLoopOutPushPreimageRequest)(nil), // 16: looprpc.ServerLoopOutPushPreimageRequest
(*ServerLoopOutPushPreimageResponse)(nil), // 17: looprpc.ServerLoopOutPushPreimageResponse
(*SubscribeUpdatesRequest)(nil), // 18: looprpc.SubscribeUpdatesRequest
(*SubscribeLoopOutUpdatesResponse)(nil), // 19: looprpc.SubscribeLoopOutUpdatesResponse
(*SubscribeLoopInUpdatesResponse)(nil), // 20: looprpc.SubscribeLoopInUpdatesResponse
(*RouteCancel)(nil), // 21: looprpc.RouteCancel
(*HtlcAttempt)(nil), // 22: looprpc.HtlcAttempt
(*CancelLoopOutSwapRequest)(nil), // 23: looprpc.CancelLoopOutSwapRequest
(*CancelLoopOutSwapResponse)(nil), // 24: looprpc.CancelLoopOutSwapResponse
(*ServerProbeRequest)(nil), // 25: looprpc.ServerProbeRequest
(*ServerProbeResponse)(nil), // 26: looprpc.ServerProbeResponse
(*RouteHint)(nil), // 27: looprpc.RouteHint
(RoutingPlugin)(0), // 4: looprpc.RoutingPlugin
(*ServerLoopOutRequest)(nil), // 5: looprpc.ServerLoopOutRequest
(*ServerLoopOutResponse)(nil), // 6: looprpc.ServerLoopOutResponse
(*ServerLoopOutQuoteRequest)(nil), // 7: looprpc.ServerLoopOutQuoteRequest
(*ServerLoopOutQuote)(nil), // 8: looprpc.ServerLoopOutQuote
(*ServerLoopOutTermsRequest)(nil), // 9: looprpc.ServerLoopOutTermsRequest
(*ServerLoopOutTerms)(nil), // 10: looprpc.ServerLoopOutTerms
(*ServerLoopInRequest)(nil), // 11: looprpc.ServerLoopInRequest
(*ServerLoopInResponse)(nil), // 12: looprpc.ServerLoopInResponse
(*ServerLoopInQuoteRequest)(nil), // 13: looprpc.ServerLoopInQuoteRequest
(*ServerLoopInQuoteResponse)(nil), // 14: looprpc.ServerLoopInQuoteResponse
(*ServerLoopInTermsRequest)(nil), // 15: looprpc.ServerLoopInTermsRequest
(*ServerLoopInTerms)(nil), // 16: looprpc.ServerLoopInTerms
(*ServerLoopOutPushPreimageRequest)(nil), // 17: looprpc.ServerLoopOutPushPreimageRequest
(*ServerLoopOutPushPreimageResponse)(nil), // 18: looprpc.ServerLoopOutPushPreimageResponse
(*SubscribeUpdatesRequest)(nil), // 19: looprpc.SubscribeUpdatesRequest
(*SubscribeLoopOutUpdatesResponse)(nil), // 20: looprpc.SubscribeLoopOutUpdatesResponse
(*SubscribeLoopInUpdatesResponse)(nil), // 21: looprpc.SubscribeLoopInUpdatesResponse
(*RouteCancel)(nil), // 22: looprpc.RouteCancel
(*HtlcAttempt)(nil), // 23: looprpc.HtlcAttempt
(*CancelLoopOutSwapRequest)(nil), // 24: looprpc.CancelLoopOutSwapRequest
(*CancelLoopOutSwapResponse)(nil), // 25: looprpc.CancelLoopOutSwapResponse
(*ServerProbeRequest)(nil), // 26: looprpc.ServerProbeRequest
(*ServerProbeResponse)(nil), // 27: looprpc.ServerProbeResponse
(*RecommendRoutingPluginReq)(nil), // 28: looprpc.RecommendRoutingPluginReq
(*RecommendRoutingPluginRes)(nil), // 29: looprpc.RecommendRoutingPluginRes
(*ReportRoutingResultReq)(nil), // 30: looprpc.ReportRoutingResultReq
(*ReportRoutingResultRes)(nil), // 31: looprpc.ReportRoutingResultRes
(*RouteHint)(nil), // 32: looprpc.RouteHint
}
var file_server_proto_depIdxs = []int32{
0, // 0: looprpc.ServerLoopOutRequest.protocol_version:type_name -> looprpc.ProtocolVersion
0, // 1: looprpc.ServerLoopOutQuoteRequest.protocol_version:type_name -> looprpc.ProtocolVersion
0, // 2: looprpc.ServerLoopOutTermsRequest.protocol_version:type_name -> looprpc.ProtocolVersion
0, // 3: looprpc.ServerLoopInRequest.protocol_version:type_name -> looprpc.ProtocolVersion
27, // 4: looprpc.ServerLoopInQuoteRequest.route_hints:type_name -> looprpc.RouteHint
32, // 4: looprpc.ServerLoopInQuoteRequest.route_hints:type_name -> looprpc.RouteHint
0, // 5: looprpc.ServerLoopInQuoteRequest.protocol_version:type_name -> looprpc.ProtocolVersion
0, // 6: looprpc.ServerLoopInTermsRequest.protocol_version:type_name -> looprpc.ProtocolVersion
0, // 7: looprpc.ServerLoopOutPushPreimageRequest.protocol_version:type_name -> looprpc.ProtocolVersion
@ -2365,39 +2728,47 @@ var file_server_proto_depIdxs = []int32{
1, // 9: looprpc.SubscribeLoopOutUpdatesResponse.state:type_name -> looprpc.ServerSwapState
1, // 10: looprpc.SubscribeLoopInUpdatesResponse.state:type_name -> looprpc.ServerSwapState
2, // 11: looprpc.RouteCancel.route_type:type_name -> looprpc.RoutePaymentType
22, // 12: looprpc.RouteCancel.attempts:type_name -> looprpc.HtlcAttempt
23, // 12: looprpc.RouteCancel.attempts:type_name -> looprpc.HtlcAttempt
3, // 13: looprpc.RouteCancel.failure:type_name -> looprpc.PaymentFailureReason
0, // 14: looprpc.CancelLoopOutSwapRequest.protocol_version:type_name -> looprpc.ProtocolVersion
21, // 15: looprpc.CancelLoopOutSwapRequest.route_cancel:type_name -> looprpc.RouteCancel
22, // 15: looprpc.CancelLoopOutSwapRequest.route_cancel:type_name -> looprpc.RouteCancel
0, // 16: looprpc.ServerProbeRequest.protocol_version:type_name -> looprpc.ProtocolVersion
27, // 17: looprpc.ServerProbeRequest.route_hints:type_name -> looprpc.RouteHint
8, // 18: looprpc.SwapServer.LoopOutTerms:input_type -> looprpc.ServerLoopOutTermsRequest
4, // 19: looprpc.SwapServer.NewLoopOutSwap:input_type -> looprpc.ServerLoopOutRequest
16, // 20: looprpc.SwapServer.LoopOutPushPreimage:input_type -> looprpc.ServerLoopOutPushPreimageRequest
6, // 21: looprpc.SwapServer.LoopOutQuote:input_type -> looprpc.ServerLoopOutQuoteRequest
14, // 22: looprpc.SwapServer.LoopInTerms:input_type -> looprpc.ServerLoopInTermsRequest
10, // 23: looprpc.SwapServer.NewLoopInSwap:input_type -> looprpc.ServerLoopInRequest
12, // 24: looprpc.SwapServer.LoopInQuote:input_type -> looprpc.ServerLoopInQuoteRequest
18, // 25: looprpc.SwapServer.SubscribeLoopOutUpdates:input_type -> looprpc.SubscribeUpdatesRequest
18, // 26: looprpc.SwapServer.SubscribeLoopInUpdates:input_type -> looprpc.SubscribeUpdatesRequest
23, // 27: looprpc.SwapServer.CancelLoopOutSwap:input_type -> looprpc.CancelLoopOutSwapRequest
25, // 28: looprpc.SwapServer.Probe:input_type -> looprpc.ServerProbeRequest
9, // 29: looprpc.SwapServer.LoopOutTerms:output_type -> looprpc.ServerLoopOutTerms
5, // 30: looprpc.SwapServer.NewLoopOutSwap:output_type -> looprpc.ServerLoopOutResponse
17, // 31: looprpc.SwapServer.LoopOutPushPreimage:output_type -> looprpc.ServerLoopOutPushPreimageResponse
7, // 32: looprpc.SwapServer.LoopOutQuote:output_type -> looprpc.ServerLoopOutQuote
15, // 33: looprpc.SwapServer.LoopInTerms:output_type -> looprpc.ServerLoopInTerms
11, // 34: looprpc.SwapServer.NewLoopInSwap:output_type -> looprpc.ServerLoopInResponse
13, // 35: looprpc.SwapServer.LoopInQuote:output_type -> looprpc.ServerLoopInQuoteResponse
19, // 36: looprpc.SwapServer.SubscribeLoopOutUpdates:output_type -> looprpc.SubscribeLoopOutUpdatesResponse
20, // 37: looprpc.SwapServer.SubscribeLoopInUpdates:output_type -> looprpc.SubscribeLoopInUpdatesResponse
24, // 38: looprpc.SwapServer.CancelLoopOutSwap:output_type -> looprpc.CancelLoopOutSwapResponse
26, // 39: looprpc.SwapServer.Probe:output_type -> looprpc.ServerProbeResponse
29, // [29:40] is the sub-list for method output_type
18, // [18:29] is the sub-list for method input_type
18, // [18:18] is the sub-list for extension type_name
18, // [18:18] is the sub-list for extension extendee
0, // [0:18] is the sub-list for field type_name
32, // 17: looprpc.ServerProbeRequest.route_hints:type_name -> looprpc.RouteHint
0, // 18: looprpc.RecommendRoutingPluginReq.protocol_version:type_name -> looprpc.ProtocolVersion
4, // 19: looprpc.RecommendRoutingPluginRes.plugin:type_name -> looprpc.RoutingPlugin
0, // 20: looprpc.ReportRoutingResultReq.protocol_version:type_name -> looprpc.ProtocolVersion
4, // 21: looprpc.ReportRoutingResultReq.plugin:type_name -> looprpc.RoutingPlugin
9, // 22: looprpc.SwapServer.LoopOutTerms:input_type -> looprpc.ServerLoopOutTermsRequest
5, // 23: looprpc.SwapServer.NewLoopOutSwap:input_type -> looprpc.ServerLoopOutRequest
17, // 24: looprpc.SwapServer.LoopOutPushPreimage:input_type -> looprpc.ServerLoopOutPushPreimageRequest
7, // 25: looprpc.SwapServer.LoopOutQuote:input_type -> looprpc.ServerLoopOutQuoteRequest
15, // 26: looprpc.SwapServer.LoopInTerms:input_type -> looprpc.ServerLoopInTermsRequest
11, // 27: looprpc.SwapServer.NewLoopInSwap:input_type -> looprpc.ServerLoopInRequest
13, // 28: looprpc.SwapServer.LoopInQuote:input_type -> looprpc.ServerLoopInQuoteRequest
19, // 29: looprpc.SwapServer.SubscribeLoopOutUpdates:input_type -> looprpc.SubscribeUpdatesRequest
19, // 30: looprpc.SwapServer.SubscribeLoopInUpdates:input_type -> looprpc.SubscribeUpdatesRequest
24, // 31: looprpc.SwapServer.CancelLoopOutSwap:input_type -> looprpc.CancelLoopOutSwapRequest
26, // 32: looprpc.SwapServer.Probe:input_type -> looprpc.ServerProbeRequest
28, // 33: looprpc.SwapServer.RecommendRoutingPlugin:input_type -> looprpc.RecommendRoutingPluginReq
30, // 34: looprpc.SwapServer.ReportRoutingResult:input_type -> looprpc.ReportRoutingResultReq
10, // 35: looprpc.SwapServer.LoopOutTerms:output_type -> looprpc.ServerLoopOutTerms
6, // 36: looprpc.SwapServer.NewLoopOutSwap:output_type -> looprpc.ServerLoopOutResponse
18, // 37: looprpc.SwapServer.LoopOutPushPreimage:output_type -> looprpc.ServerLoopOutPushPreimageResponse
8, // 38: looprpc.SwapServer.LoopOutQuote:output_type -> looprpc.ServerLoopOutQuote
16, // 39: looprpc.SwapServer.LoopInTerms:output_type -> looprpc.ServerLoopInTerms
12, // 40: looprpc.SwapServer.NewLoopInSwap:output_type -> looprpc.ServerLoopInResponse
14, // 41: looprpc.SwapServer.LoopInQuote:output_type -> looprpc.ServerLoopInQuoteResponse
20, // 42: looprpc.SwapServer.SubscribeLoopOutUpdates:output_type -> looprpc.SubscribeLoopOutUpdatesResponse
21, // 43: looprpc.SwapServer.SubscribeLoopInUpdates:output_type -> looprpc.SubscribeLoopInUpdatesResponse
25, // 44: looprpc.SwapServer.CancelLoopOutSwap:output_type -> looprpc.CancelLoopOutSwapResponse
27, // 45: looprpc.SwapServer.Probe:output_type -> looprpc.ServerProbeResponse
29, // 46: looprpc.SwapServer.RecommendRoutingPlugin:output_type -> looprpc.RecommendRoutingPluginRes
31, // 47: looprpc.SwapServer.ReportRoutingResult:output_type -> looprpc.ReportRoutingResultRes
35, // [35:48] is the sub-list for method output_type
22, // [22:35] is the sub-list for method input_type
22, // [22:22] is the sub-list for extension type_name
22, // [22:22] is the sub-list for extension extendee
0, // [0:22] is the sub-list for field type_name
}
func init() { file_server_proto_init() }
@ -2683,6 +3054,54 @@ func file_server_proto_init() {
return nil
}
}
file_server_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RecommendRoutingPluginReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_server_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RecommendRoutingPluginRes); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_server_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReportRoutingResultReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_server_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReportRoutingResultRes); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_server_proto_msgTypes[19].OneofWrappers = []interface{}{
(*CancelLoopOutSwapRequest_RouteCancel)(nil),
@ -2692,8 +3111,8 @@ func file_server_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_server_proto_rawDesc,
NumEnums: 4,
NumMessages: 23,
NumEnums: 5,
NumMessages: 27,
NumExtensions: 0,
NumServices: 1,
},

@ -37,6 +37,12 @@ service SwapServer {
returns (CancelLoopOutSwapResponse);
rpc Probe (ServerProbeRequest) returns (ServerProbeResponse);
rpc RecommendRoutingPlugin (RecommendRoutingPluginReq)
returns (RecommendRoutingPluginRes);
rpc ReportRoutingResult (ReportRoutingResultReq)
returns (ReportRoutingResultRes);
}
/**
@ -82,6 +88,10 @@ enum ProtocolVersion {
// The client is able to ask the server to probe to test inbound
// liquidity.
PROBE = 8;
// The client may ask the server to use a custom routing helper plugin in
// order to enhance off-chain payments corresponding to a swap.
ROUTING_PLUGIN = 9;
}
message ServerLoopOutRequest {
@ -458,3 +468,53 @@ message ServerProbeRequest {
message ServerProbeResponse {
}
message RecommendRoutingPluginReq {
ProtocolVersion protocol_version = 1;
// The hash of the swap requesting a routing plugin.
bytes swap_hash = 2;
// The payment address for the swap invoice, used to ensure that only the
// swap owner can request routing plugin recommendation.
bytes payment_address = 3;
}
enum RoutingPlugin {
// Client won't use any plugins to help with payment routing.
NONE = 0;
// Client will try more expensive routes for off-chain payments.
LOW_HIGH = 1;
}
message RecommendRoutingPluginRes {
// The routing plugin to use for off-chain payments.
RoutingPlugin plugin = 1;
}
message ReportRoutingResultReq {
ProtocolVersion protocol_version = 1;
// The swap hash.
bytes swap_hash = 2;
// The payment address for the swap invoice, used to ensure that only the
// swap owner can report routing result.
bytes payment_address = 3;
// The routing plugin that was used.
RoutingPlugin plugin = 4;
// Whether this payment succeeded.
bool success = 5;
// The number of payment attempts using the plugin.
int32 attempts = 6;
// Total time used in milliseconds.
int64 total_time = 7;
}
message ReportRoutingResultRes {
}

@ -29,6 +29,8 @@ type SwapServerClient interface {
SubscribeLoopInUpdates(ctx context.Context, in *SubscribeUpdatesRequest, opts ...grpc.CallOption) (SwapServer_SubscribeLoopInUpdatesClient, error)
CancelLoopOutSwap(ctx context.Context, in *CancelLoopOutSwapRequest, opts ...grpc.CallOption) (*CancelLoopOutSwapResponse, error)
Probe(ctx context.Context, in *ServerProbeRequest, opts ...grpc.CallOption) (*ServerProbeResponse, error)
RecommendRoutingPlugin(ctx context.Context, in *RecommendRoutingPluginReq, opts ...grpc.CallOption) (*RecommendRoutingPluginRes, error)
ReportRoutingResult(ctx context.Context, in *ReportRoutingResultReq, opts ...grpc.CallOption) (*ReportRoutingResultRes, error)
}
type swapServerClient struct {
@ -184,6 +186,24 @@ func (c *swapServerClient) Probe(ctx context.Context, in *ServerProbeRequest, op
return out, nil
}
func (c *swapServerClient) RecommendRoutingPlugin(ctx context.Context, in *RecommendRoutingPluginReq, opts ...grpc.CallOption) (*RecommendRoutingPluginRes, error) {
out := new(RecommendRoutingPluginRes)
err := c.cc.Invoke(ctx, "/looprpc.SwapServer/RecommendRoutingPlugin", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapServerClient) ReportRoutingResult(ctx context.Context, in *ReportRoutingResultReq, opts ...grpc.CallOption) (*ReportRoutingResultRes, error) {
out := new(ReportRoutingResultRes)
err := c.cc.Invoke(ctx, "/looprpc.SwapServer/ReportRoutingResult", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SwapServerServer is the server API for SwapServer service.
// All implementations must embed UnimplementedSwapServerServer
// for forward compatibility
@ -199,6 +219,8 @@ type SwapServerServer interface {
SubscribeLoopInUpdates(*SubscribeUpdatesRequest, SwapServer_SubscribeLoopInUpdatesServer) error
CancelLoopOutSwap(context.Context, *CancelLoopOutSwapRequest) (*CancelLoopOutSwapResponse, error)
Probe(context.Context, *ServerProbeRequest) (*ServerProbeResponse, error)
RecommendRoutingPlugin(context.Context, *RecommendRoutingPluginReq) (*RecommendRoutingPluginRes, error)
ReportRoutingResult(context.Context, *ReportRoutingResultReq) (*ReportRoutingResultRes, error)
mustEmbedUnimplementedSwapServerServer()
}
@ -239,6 +261,12 @@ func (UnimplementedSwapServerServer) CancelLoopOutSwap(context.Context, *CancelL
func (UnimplementedSwapServerServer) Probe(context.Context, *ServerProbeRequest) (*ServerProbeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Probe not implemented")
}
func (UnimplementedSwapServerServer) RecommendRoutingPlugin(context.Context, *RecommendRoutingPluginReq) (*RecommendRoutingPluginRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecommendRoutingPlugin not implemented")
}
func (UnimplementedSwapServerServer) ReportRoutingResult(context.Context, *ReportRoutingResultReq) (*ReportRoutingResultRes, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReportRoutingResult not implemented")
}
func (UnimplementedSwapServerServer) mustEmbedUnimplementedSwapServerServer() {}
// UnsafeSwapServerServer may be embedded to opt out of forward compatibility for this service.
@ -456,6 +484,42 @@ func _SwapServer_Probe_Handler(srv interface{}, ctx context.Context, dec func(in
return interceptor(ctx, in, info, handler)
}
func _SwapServer_RecommendRoutingPlugin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RecommendRoutingPluginReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapServerServer).RecommendRoutingPlugin(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapServer/RecommendRoutingPlugin",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapServerServer).RecommendRoutingPlugin(ctx, req.(*RecommendRoutingPluginReq))
}
return interceptor(ctx, in, info, handler)
}
func _SwapServer_ReportRoutingResult_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReportRoutingResultReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapServerServer).ReportRoutingResult(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapServer/ReportRoutingResult",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapServerServer).ReportRoutingResult(ctx, req.(*ReportRoutingResultReq))
}
return interceptor(ctx, in, info, handler)
}
// SwapServer_ServiceDesc is the grpc.ServiceDesc for SwapServer service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -499,6 +563,14 @@ var SwapServer_ServiceDesc = grpc.ServiceDesc{
MethodName: "Probe",
Handler: _SwapServer_Probe_Handler,
},
{
MethodName: "RecommendRoutingPlugin",
Handler: _SwapServer_RecommendRoutingPlugin_Handler,
},
{
MethodName: "ReportRoutingResult",
Handler: _SwapServer_ReportRoutingResult_Handler,
},
},
Streams: []grpc.StreamDesc{
{

@ -186,10 +186,14 @@ func (h *mockLightningClient) ListTransactions(
func (h *mockLightningClient) GetNodeInfo(ctx context.Context,
pubKeyBytes route.Vertex, includeChannels bool) (*lndclient.NodeInfo, error) {
nodeInfo := lndclient.NodeInfo{}
nodeInfo := &lndclient.NodeInfo{
Node: &lndclient.Node{
PubKey: pubKeyBytes,
},
}
if !includeChannels {
return nil, nil
return nodeInfo, nil
}
nodePubKey, err := route.NewVertexFromStr(h.lnd.NodePubkey)
@ -214,7 +218,7 @@ func (h *mockLightningClient) GetNodeInfo(ctx context.Context,
nodeInfo.ChannelCount = len(nodeInfo.Channels)
return &nodeInfo, nil
return nodeInfo, nil
}
// GetChanInfo retrieves all the info the node has on the given channel

@ -163,11 +163,12 @@ type LndMockServices struct {
// keyed by hash string.
Invoices map[lntypes.Hash]*lndclient.Invoice
Channels []lndclient.ChannelInfo
ChannelEdges map[uint64]*lndclient.ChannelEdge
ClosedChannels []lndclient.ClosedChannel
ForwardingEvents []lndclient.ForwardingEvent
Payments []lndclient.Payment
Channels []lndclient.ChannelInfo
ChannelEdges map[uint64]*lndclient.ChannelEdge
ClosedChannels []lndclient.ClosedChannel
ForwardingEvents []lndclient.ForwardingEvent
Payments []lndclient.Payment
MissionControlState []lndclient.MissionControlEntry
WaitForFinished func()

@ -42,3 +42,66 @@ func (r *mockRouter) TrackPayment(ctx context.Context,
return statusChan, errorChan, nil
}
func (r *mockRouter) QueryMissionControl(ctx context.Context) (
[]lndclient.MissionControlEntry, error) {
return r.lnd.MissionControlState, nil
}
// ImpotMissionControl is a mocked reimplementation of the pair import.
// Reference: lnd/router/missioncontrol_state.go:importSnapshot().
func (r *mockRouter) ImportMissionControl(ctx context.Context,
entries []lndclient.MissionControlEntry) error {
for _, entry := range entries {
found := false
for i := range r.lnd.MissionControlState {
current := &r.lnd.MissionControlState[i]
if entry.NodeFrom == current.NodeFrom &&
entry.NodeTo == current.NodeTo {
// Mark that the entry has been found and updated.
found = true
// Import failure result first. We ignore failure
// relax interval here for convenience.
current.FailTime = entry.FailTime
current.FailAmt = entry.FailAmt
switch {
case entry.FailAmt == 0:
current.SuccessAmt = 0
case entry.FailAmt <= current.SuccessAmt:
current.SuccessAmt = entry.FailAmt - 1
}
// Import success result second.
current.SuccessTime = entry.SuccessTime
if entry.SuccessAmt > current.SuccessAmt {
current.SuccessAmt = entry.SuccessAmt
}
if !current.FailTime.IsZero() &&
entry.SuccessAmt >= current.FailAmt {
current.FailAmt = entry.SuccessAmt + 1
}
}
}
if !found {
r.lnd.MissionControlState = append(
r.lnd.MissionControlState, entry,
)
}
}
return nil
}
func (r *mockRouter) ResetMissionControl(ctx context.Context) error {
r.lnd.MissionControlState = []lndclient.MissionControlEntry{}
return nil
}

Loading…
Cancel
Save