Merge pull request #419 from carlaKC/autoloop-4-loopin

liquidity: add loopin to autoloop
pull/450/head
Harsha Goli 2 years ago committed by GitHub
commit be6eae3bda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -48,6 +48,14 @@ var setLiquidityRuleCommand = cli.Command{
Description: "Update or remove the liquidity rule for a channel/peer.", Description: "Update or remove the liquidity rule for a channel/peer.",
ArgsUsage: "{shortchanid | peerpubkey}", ArgsUsage: "{shortchanid | peerpubkey}",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{
Name: "type",
Usage: "the type of swap to perform, set to 'out' " +
"for acquiring inbound liquidity or 'in' for " +
"acquiring outbound liquidity.",
Value: "out",
},
cli.IntFlag{ cli.IntFlag{
Name: "incoming_threshold", Name: "incoming_threshold",
Usage: "the minimum percentage of incoming liquidity " + Usage: "the minimum percentage of incoming liquidity " +
@ -168,7 +176,18 @@ func setRule(ctx *cli.Context) error {
newRule := &looprpc.LiquidityRule{ newRule := &looprpc.LiquidityRule{
ChannelId: chanID, ChannelId: chanID,
Type: looprpc.LiquidityRuleType_THRESHOLD, Type: looprpc.LiquidityRuleType_THRESHOLD,
SwapType: looprpc.SwapType_LOOP_OUT, }
if ctx.IsSet("type") {
switch ctx.String("type") {
case "in":
newRule.SwapType = looprpc.SwapType_LOOP_IN
case "out":
newRule.SwapType = looprpc.SwapType_LOOP_OUT
default:
return errors.New("please set type to in or out")
}
} }
if pubkeyRule { if pubkeyRule {
@ -292,6 +311,11 @@ var setParamsCommand = cli.Command{
Usage: "the maximum amount in satoshis that the " + Usage: "the maximum amount in satoshis that the " +
"autoloop client will dispatch per-swap", "autoloop client will dispatch per-swap",
}, },
cli.IntFlag{
Name: "htlc_conf",
Usage: "the confirmation target for loop in on-chain " +
"htlcs",
},
}, },
Action: setParams, Action: setParams,
} }
@ -422,6 +446,11 @@ func setParams(ctx *cli.Context) error {
flagSet = true flagSet = true
} }
if ctx.IsSet("htlc_conf") {
params.HtlcConfTarget = int32(ctx.Int("htlc_conf"))
flagSet = true
}
if !flagSet { if !flagSet {
return fmt.Errorf("at least one flag required to set params") return fmt.Errorf("at least one flag required to set params")
} }

@ -9,6 +9,16 @@ following command:
loop setparams --autoloop=true loop setparams --autoloop=true
``` ```
At present, autoloop can be configured to either acquire incoming liquidity
using loop out, or acquire outgoing liquidity using loop in. It cannot support
automated swaps in both directions. To set the type of swaps you would like
to automatically dispatch, use:
```
loop setparams --type={in|out}
```
Autoloop will perform loop out swaps *by default*.
Swaps that are dispatched by the autolooper can be identified in the output of Swaps that are dispatched by the autolooper can be identified in the output of
`ListSwaps` by their label field, which will contain: `[reserved]: autoloop-out`. `ListSwaps` by their label field, which will contain: `[reserved]: autoloop-out`.
@ -286,7 +296,9 @@ following reasons will be displayed:
* Fee insufficient: if the fees that a swap will cost are more than the * Fee insufficient: if the fees that a swap will cost are more than the
percentage of total swap amount that we allow, this reason will be displayed. percentage of total swap amount that we allow, this reason will be displayed.
See [fees](#fees) to update this value. See [fees](#fees) to update this value.
* Loop in unreachable: if the client node is unreachable by the server
off-chain, this reason will be displayed. Try improving the connectivity of
your node so that it is reachable by the loop server.
Further details for all of these reasons can be found in loopd's debug level Further details for all of these reasons can be found in loopd's debug level
logs. logs.

@ -50,13 +50,22 @@ func TestAutoLoopDisabled(t *testing.T) {
// loop in/out swaps. We expect a swap for our channel to be suggested, // loop in/out swaps. We expect a swap for our channel to be suggested,
// but do not expect any swaps to be executed, since autoloop is // but do not expect any swaps to be executed, since autoloop is
// disabled by default. // disabled by default.
c.autoloop(1, chan1Rec.Amount+1, nil, quotes, nil) step := &autoloopStep{
minAmt: 1,
maxAmt: chan1Rec.Amount + 1,
quotesOut: quotes,
}
c.autoloop(step)
// Trigger another autoloop, this time setting our server restrictions // Trigger another autoloop, this time setting our server restrictions
// to have a minimum swap amount greater than the amount that we need // to have a minimum swap amount greater than the amount that we need
// to swap. In this case we don't even expect to get a quote, because // to swap. In this case we don't even expect to get a quote, because
// our suggested swap is beneath the minimum swap size. // our suggested swap is beneath the minimum swap size.
c.autoloop(chan1Rec.Amount+1, chan1Rec.Amount+2, nil, nil, nil) step = &autoloopStep{
minAmt: chan1Rec.Amount + 1,
maxAmt: chan1Rec.Amount + 2,
}
c.autoloop(step)
c.stop() c.stop()
} }
@ -99,6 +108,7 @@ func TestAutoLoopEnabled(t *testing.T) {
chanID1: chanRule, chanID1: chanRule,
chanID2: chanRule, chanID2: chanRule,
}, },
HtlcConfTarget: defaultHtlcConfTarget,
} }
) )
c := newAutoloopTestCtx(t, params, channels, testRestrictions) c := newAutoloopTestCtx(t, params, channels, testRestrictions)
@ -191,7 +201,13 @@ func TestAutoLoopEnabled(t *testing.T) {
// Tick our autolooper with no existing swaps, we expect a loop out // Tick our autolooper with no existing swaps, we expect a loop out
// swap to be dispatched for each channel. // swap to be dispatched for each channel.
c.autoloop(1, amt+1, nil, quotes, loopOuts) step := &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
quotesOut: quotes,
expectedOut: loopOuts,
}
c.autoloop(step)
// Tick again with both of our swaps in progress. We haven't shifted our // Tick again with both of our swaps in progress. We haven't shifted our
// channel balances at all, so swaps should still be suggested, but we // channel balances at all, so swaps should still be suggested, but we
@ -201,7 +217,12 @@ func TestAutoLoopEnabled(t *testing.T) {
existingSwapFromRequest(chan2Swap, testTime, nil), existingSwapFromRequest(chan2Swap, testTime, nil),
} }
c.autoloop(1, amt+1, existing, nil, nil) step = &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
existingOut: existing,
}
c.autoloop(step)
// Now, we update our channel 2 swap to have failed due to off chain // Now, we update our channel 2 swap to have failed due to off chain
// failure and our first swap to have succeeded. // failure and our first swap to have succeeded.
@ -254,7 +275,14 @@ func TestAutoLoopEnabled(t *testing.T) {
// We tick again, this time we expect another swap on channel 1 (which // We tick again, this time we expect another swap on channel 1 (which
// still has balances which reflect that we need to swap), but nothing // still has balances which reflect that we need to swap), but nothing
// for channel 2, since it has had a failure. // for channel 2, since it has had a failure.
c.autoloop(1, amt+1, existing, quotes, loopOuts) step = &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
existingOut: existing,
quotesOut: quotes,
expectedOut: loopOuts,
}
c.autoloop(step)
// Now, we progress our time so that we have sufficiently backed off // Now, we progress our time so that we have sufficiently backed off
// for channel 2, and could perform another swap. // for channel 2, and could perform another swap.
@ -268,7 +296,13 @@ func TestAutoLoopEnabled(t *testing.T) {
existingSwapFromRequest(chan2Swap, testTime, failedOffChain), existingSwapFromRequest(chan2Swap, testTime, failedOffChain),
} }
c.autoloop(1, amt+1, existing, quotes, nil) step = &autoloopStep{
minAmt: 1,
maxAmt: amt + 1,
existingOut: existing,
quotesOut: quotes,
}
c.autoloop(step)
c.stop() c.stop()
} }
@ -318,6 +352,7 @@ func TestCompositeRules(t *testing.T) {
PeerRules: map[route.Vertex]*SwapRule{ PeerRules: map[route.Vertex]*SwapRule{
peer2: chanRule, peer2: chanRule,
}, },
HtlcConfTarget: defaultHtlcConfTarget,
} }
) )
@ -425,8 +460,357 @@ func TestCompositeRules(t *testing.T) {
// swap to be dispatched for each of our rules. We set our server side // swap to be dispatched for each of our rules. We set our server side
// maximum to be greater than the swap amount for our peer swap (which // maximum to be greater than the swap amount for our peer swap (which
// is the larger of the two swaps). // is the larger of the two swaps).
c.autoloop(1, peerAmount+1, nil, quotes, loopOuts) step := &autoloopStep{
minAmt: 1,
maxAmt: peerAmount + 1,
quotesOut: quotes,
expectedOut: loopOuts,
}
c.autoloop(step)
c.stop()
}
// TestAutoLoopInEnabled tests dispatch of autoloop in swaps.
func TestAutoLoopInEnabled(t *testing.T) {
defer test.Guard(t)()
var (
chan1 = lndclient.ChannelInfo{
ChannelID: chanID1.ToUint64(),
PubKeyBytes: peer1,
Capacity: 100000,
RemoteBalance: 100000,
LocalBalance: 0,
}
chan2 = lndclient.ChannelInfo{
ChannelID: chanID2.ToUint64(),
PubKeyBytes: peer2,
Capacity: 200000,
RemoteBalance: 200000,
LocalBalance: 0,
}
channels = []lndclient.ChannelInfo{
chan1, chan2,
}
// Create a rule which will loop in, with no inbound liquidity
// reserve.
rule = &SwapRule{
ThresholdRule: NewThresholdRule(0, 60),
Type: swap.TypeIn,
}
// Under these rules, we'll have the following recommended
// swaps:
peer1ExpectedAmt btcutil.Amount = 80000
peer2ExpectedAmt btcutil.Amount = 160000
// Set our per-swap budget to 5% of swap amount.
swapFeePPM uint64 = 50000
htlcConfTarget int32 = 10
// Calculate the maximum amount we'll pay for each swap and
// set our budget to be able to accommodate both.
peer1MaxFee = ppmToSat(peer1ExpectedAmt, swapFeePPM)
peer2MaxFee = ppmToSat(peer2ExpectedAmt, swapFeePPM)
params = Parameters{
Autoloop: true,
AutoFeeBudget: peer1MaxFee + peer2MaxFee + 1,
AutoFeeStartDate: testTime,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
FeeLimit: NewFeePortion(swapFeePPM),
ChannelRules: make(map[lnwire.ShortChannelID]*SwapRule),
PeerRules: map[route.Vertex]*SwapRule{
peer1: rule,
peer2: rule,
},
HtlcConfTarget: htlcConfTarget,
SweepConfTarget: loop.DefaultSweepConfTarget,
}
)
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
c.start()
// Calculate our maximum allowed fees and create quotes that fall within
// our budget.
var (
quote1 = &loop.LoopInQuote{
SwapFee: peer1MaxFee / 4,
MinerFee: peer1MaxFee / 8,
}
quote2Unaffordable = &loop.LoopInQuote{
SwapFee: peer2MaxFee * 2,
MinerFee: peer2MaxFee * 2,
}
quoteRequest1 = &loop.LoopInQuoteRequest{
Amount: peer1ExpectedAmt,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer1,
}
quoteRequest2 = &loop.LoopInQuoteRequest{
Amount: peer2ExpectedAmt,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer2,
}
peer1Swap = &loop.LoopInRequest{
Amount: peer1ExpectedAmt,
MaxSwapFee: quote1.SwapFee,
MaxMinerFee: quote1.MinerFee,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer1,
ExternalHtlc: false,
Label: labels.AutoloopLabel(swap.TypeIn),
Initiator: autoloopSwapInitiator,
}
)
// Tick our autolooper with no existing swaps. Both of our peers
// require swaps, but one of our peer's quotes is too expensive.
step := &autoloopStep{
minAmt: 1,
maxAmt: peer2ExpectedAmt + 1,
quotesIn: []quoteInRequestResp{
{
request: quoteRequest1,
quote: quote1,
},
{
request: quoteRequest2,
quote: quote2Unaffordable,
},
},
expectedIn: []loopInRequestResp{
{
request: peer1Swap,
response: &loop.LoopInSwapInfo{
SwapHash: lntypes.Hash{1},
},
},
},
}
c.autoloop(step)
// Now, we tick again with our first swap in progress. This time, we
// provide a quote for our second swap which is more affordable, so we
// expect it to be dispatched.
var (
quote2Affordable = &loop.LoopInQuote{
SwapFee: peer2MaxFee / 8,
MinerFee: peer2MaxFee / 2,
}
peer2Swap = &loop.LoopInRequest{
Amount: peer2ExpectedAmt,
MaxSwapFee: quote2Affordable.SwapFee,
MaxMinerFee: quote2Affordable.MinerFee,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer2,
ExternalHtlc: false,
Label: labels.AutoloopLabel(swap.TypeIn),
Initiator: autoloopSwapInitiator,
}
existing = []*loopdb.LoopIn{
existingInFromRequest(peer1Swap, testTime, nil),
}
)
step = &autoloopStep{
minAmt: 1,
maxAmt: peer2ExpectedAmt + 1,
quotesIn: []quoteInRequestResp{
{
request: quoteRequest2,
quote: quote2Affordable,
},
},
existingIn: existing,
expectedIn: []loopInRequestResp{
{
request: peer2Swap,
response: &loop.LoopInSwapInfo{
SwapHash: lntypes.Hash{2},
},
},
},
}
c.autoloop(step)
c.stop()
}
// TestAutoloopBothTypes tests dispatching of a loop out and loop in swap at the
// same time.
func TestAutoloopBothTypes(t *testing.T) {
defer test.Guard(t)()
var (
chan1 = lndclient.ChannelInfo{
ChannelID: chanID1.ToUint64(),
PubKeyBytes: peer1,
Capacity: 1000000,
LocalBalance: 1000000,
}
chan2 = lndclient.ChannelInfo{
ChannelID: chanID2.ToUint64(),
PubKeyBytes: peer2,
Capacity: 200000,
RemoteBalance: 200000,
LocalBalance: 0,
}
channels = []lndclient.ChannelInfo{
chan1, chan2,
}
// Create a rule which will loop out, with no outbound liquidity
// reserve.
outRule = &SwapRule{
ThresholdRule: NewThresholdRule(40, 0),
Type: swap.TypeOut,
}
// Create a rule which will loop in, with no inbound liquidity
// reserve.
inRule = &SwapRule{
ThresholdRule: NewThresholdRule(0, 60),
Type: swap.TypeIn,
}
// Under this rule, we expect a loop in swap.
loopOutAmt btcutil.Amount = 700000
loopInAmount btcutil.Amount = 160000
// Set our per-swap budget to 5% of swap amount.
swapFeePPM uint64 = 50000
htlcConfTarget int32 = 10
// Calculate the maximum amount we'll pay for our loop in.
loopOutMaxFee = ppmToSat(loopOutAmt, swapFeePPM)
loopInMaxFee = ppmToSat(loopInAmount, swapFeePPM)
params = Parameters{
Autoloop: true,
AutoFeeBudget: loopOutMaxFee + loopInMaxFee + 1,
AutoFeeStartDate: testTime,
MaxAutoInFlight: 2,
FailureBackOff: time.Hour,
FeeLimit: NewFeePortion(swapFeePPM),
ChannelRules: map[lnwire.ShortChannelID]*SwapRule{
chanID1: outRule,
},
PeerRules: map[route.Vertex]*SwapRule{
peer2: inRule,
},
HtlcConfTarget: htlcConfTarget,
SweepConfTarget: loop.DefaultSweepConfTarget,
}
)
c := newAutoloopTestCtx(t, params, channels, testRestrictions)
c.start()
// Calculate our maximum allowed fees and create quotes that fall within
// our budget.
var (
loopOutQuote = &loop.LoopOutQuote{
SwapFee: loopOutMaxFee / 4,
PrepayAmount: loopOutMaxFee / 4,
}
loopOutQuoteReq = &loop.LoopOutQuoteRequest{
Amount: loopOutAmt,
SweepConfTarget: params.SweepConfTarget,
SwapPublicationDeadline: testTime,
}
prepayMaxFee, routeMaxFee,
minerFee = params.FeeLimit.loopOutFees(
loopOutAmt, loopOutQuote,
)
loopOutSwap = &loop.OutRequest{
Amount: loopOutAmt,
MaxSwapRoutingFee: routeMaxFee,
MaxPrepayRoutingFee: prepayMaxFee,
MaxSwapFee: loopOutQuote.SwapFee,
MaxPrepayAmount: loopOutQuote.PrepayAmount,
MaxMinerFee: minerFee,
SweepConfTarget: params.SweepConfTarget,
OutgoingChanSet: loopdb.ChannelSet{
chanID1.ToUint64(),
},
Label: labels.AutoloopLabel(swap.TypeOut),
Initiator: autoloopSwapInitiator,
}
loopinQuote = &loop.LoopInQuote{
SwapFee: loopInMaxFee / 4,
MinerFee: loopInMaxFee / 8,
}
loopInQuoteReq = &loop.LoopInQuoteRequest{
Amount: loopInAmount,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer2,
}
loopInSwap = &loop.LoopInRequest{
Amount: loopInAmount,
MaxSwapFee: loopinQuote.SwapFee,
MaxMinerFee: loopinQuote.MinerFee,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer2,
ExternalHtlc: false,
Label: labels.AutoloopLabel(swap.TypeIn),
Initiator: autoloopSwapInitiator,
}
)
step := &autoloopStep{
minAmt: 1,
maxAmt: loopOutAmt + 1,
quotesOut: []quoteRequestResp{
{
request: loopOutQuoteReq,
quote: loopOutQuote,
},
},
quotesIn: []quoteInRequestResp{
{
request: loopInQuoteReq,
quote: loopinQuote,
},
},
expectedOut: []loopOutRequestResp{
{
request: loopOutSwap,
response: &loop.LoopOutSwapInfo{
SwapHash: lntypes.Hash{1},
},
},
},
expectedIn: []loopInRequestResp{
{
request: loopInSwap,
response: &loop.LoopInSwapInfo{
SwapHash: lntypes.Hash{2},
},
},
},
}
c.autoloop(step)
c.stop() c.stop()
} }
@ -455,3 +839,24 @@ func existingSwapFromRequest(request *loop.OutRequest, initTime time.Time,
}, },
} }
} }
func existingInFromRequest(in *loop.LoopInRequest, initTime time.Time,
events []*loopdb.LoopEvent) *loopdb.LoopIn {
return &loopdb.LoopIn{
Loop: loopdb.Loop{
Events: events,
},
Contract: &loopdb.LoopInContract{
SwapContract: loopdb.SwapContract{
MaxSwapFee: in.MaxSwapFee,
MaxMinerFee: in.MaxMinerFee,
InitiationTime: initTime,
Label: in.Label,
},
HtlcConfTarget: in.HtlcConfTarget,
LastHop: in.LastHop,
ExternalHtlc: in.ExternalHtlc,
},
}
}

@ -27,10 +27,21 @@ type autoloopTestCtx struct {
// quotes is a channel that we get loop out quote requests on. // quotes is a channel that we get loop out quote requests on.
quotes chan *loop.LoopOutQuote quotes chan *loop.LoopOutQuote
// quoteRequestIn is a channel that requests for loop in quotes are
// pushed into.
quoteRequestIn chan *loop.LoopInQuoteRequest
// quotesIn is a channel that we get loop in quote responses on.
quotesIn chan *loop.LoopInQuote
// loopOutRestrictions is a channel that we get the server's // loopOutRestrictions is a channel that we get the server's
// restrictions on. // restrictions on.
loopOutRestrictions chan *Restrictions loopOutRestrictions chan *Restrictions
// loopInRestrictions is a channel that we get the server's
// loop in restrictions on.
loopInRestrictions chan *Restrictions
// loopOuts is a channel that we get existing loop out swaps on. // loopOuts is a channel that we get existing loop out swaps on.
loopOuts chan []*loopdb.LoopOut loopOuts chan []*loopdb.LoopOut
@ -47,6 +58,13 @@ type autoloopTestCtx struct {
// loopOut is a channel that we return loop out responses on. // loopOut is a channel that we return loop out responses on.
loopOut chan *loop.LoopOutSwapInfo loopOut chan *loop.LoopOutSwapInfo
// inRequest is a channel that requests to dispatch loop in swaps are
// pushed into.
inRequest chan *loop.LoopInRequest
// loopIn is a channel that we return loop in responses on.
loopIn chan *loop.LoopInSwapInfo
// errChan is a channel that we send run errors into. // errChan is a channel that we send run errors into.
errChan chan error errChan chan error
@ -80,14 +98,18 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
quoteRequest: make(chan *loop.LoopOutQuoteRequest), quoteRequest: make(chan *loop.LoopOutQuoteRequest),
quotes: make(chan *loop.LoopOutQuote), quotes: make(chan *loop.LoopOutQuote),
quoteRequestIn: make(chan *loop.LoopInQuoteRequest),
quotesIn: make(chan *loop.LoopInQuote),
loopOutRestrictions: make(chan *Restrictions), loopOutRestrictions: make(chan *Restrictions),
loopInRestrictions: make(chan *Restrictions),
loopOuts: make(chan []*loopdb.LoopOut), loopOuts: make(chan []*loopdb.LoopOut),
loopIns: make(chan []*loopdb.LoopIn), loopIns: make(chan []*loopdb.LoopIn),
restrictions: make(chan *Restrictions), restrictions: make(chan *Restrictions),
outRequest: make(chan *loop.OutRequest), outRequest: make(chan *loop.OutRequest),
loopOut: make(chan *loop.LoopOutSwapInfo), loopOut: make(chan *loop.LoopOutSwapInfo),
inRequest: make(chan *loop.LoopInRequest),
errChan: make(chan error, 1), loopIn: make(chan *loop.LoopInSwapInfo),
errChan: make(chan error, 1),
} }
// Set lnd's channels to equal the set of channels we want for our // Set lnd's channels to equal the set of channels we want for our
@ -96,10 +118,14 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
cfg := &Config{ cfg := &Config{
AutoloopTicker: ticker.NewForce(DefaultAutoloopTicker), AutoloopTicker: ticker.NewForce(DefaultAutoloopTicker),
Restrictions: func(context.Context, swap.Type) (*Restrictions, Restrictions: func(_ context.Context, swapType swap.Type) (*Restrictions,
error) { error) {
return <-testCtx.loopOutRestrictions, nil if swapType == swap.TypeOut {
return <-testCtx.loopOutRestrictions, nil
}
return <-testCtx.loopInRestrictions, nil
}, },
ListLoopOut: func() ([]*loopdb.LoopOut, error) { ListLoopOut: func() ([]*loopdb.LoopOut, error) {
return <-testCtx.loopOuts, nil return <-testCtx.loopOuts, nil
@ -123,6 +149,20 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
return <-testCtx.loopOut, nil return <-testCtx.loopOut, nil
}, },
LoopInQuote: func(_ context.Context,
req *loop.LoopInQuoteRequest) (*loop.LoopInQuote, error) {
testCtx.quoteRequestIn <- req
return <-testCtx.quotesIn, nil
},
LoopIn: func(_ context.Context,
req *loop.LoopInRequest) (*loop.LoopInSwapInfo, error) {
testCtx.inRequest <- req
return <-testCtx.loopIn, nil
},
MinimumConfirmations: loop.DefaultSweepConfTarget, MinimumConfirmations: loop.DefaultSweepConfTarget,
Lnd: &testCtx.lnd.LndServices, Lnd: &testCtx.lnd.LndServices,
Clock: testCtx.testClock, Clock: testCtx.testClock,
@ -177,31 +217,70 @@ type loopOutRequestResp struct {
response *loop.LoopOutSwapInfo response *loop.LoopOutSwapInfo
} }
// quoteInRequestResp pairs an expected loop in quote request with the response
// we would like to provide the manager with.
type quoteInRequestResp struct {
request *loop.LoopInQuoteRequest
quote *loop.LoopInQuote
}
// loopInRequestResp pairs and expected loop in request with the response we
// would like the mocked server to respond with.
type loopInRequestResp struct {
request *loop.LoopInRequest
response *loop.LoopInSwapInfo
}
// autoloopStep contains all of the information to required to step
// through an autoloop tick.
type autoloopStep struct {
minAmt btcutil.Amount
maxAmt btcutil.Amount
existingOut []*loopdb.LoopOut
existingIn []*loopdb.LoopIn
quotesOut []quoteRequestResp
quotesIn []quoteInRequestResp
expectedOut []loopOutRequestResp
expectedIn []loopInRequestResp
}
// autoloop walks our test context through the process of triggering our // autoloop walks our test context through the process of triggering our
// autoloop functionality, providing mocked values as required. The set of // autoloop functionality, providing mocked values as required. The set of
// quotes provided indicates that we expect swap suggestions to be made (since // quotes provided indicates that we expect swap suggestions to be made (since
// we will query for a quote for each suggested swap). The set of expected // we will query for a quote for each suggested swap). The set of expected
// swaps indicates whether we expect any of these swap suggestions to actually // swaps indicates whether we expect any of these swap suggestions to actually
// be dispatched by the autolooper. // be dispatched by the autolooper.
func (c *autoloopTestCtx) autoloop(minAmt, maxAmt btcutil.Amount, func (c *autoloopTestCtx) autoloop(step *autoloopStep) {
existingOut []*loopdb.LoopOut, quotes []quoteRequestResp,
expectedSwaps []loopOutRequestResp) {
// Tick our autoloop ticker to force assessing whether we want to loop. // Tick our autoloop ticker to force assessing whether we want to loop.
c.manager.cfg.AutoloopTicker.Force <- testTime c.manager.cfg.AutoloopTicker.Force <- testTime
// Send a mocked response from the server with the swap size limits. // Send a mocked response from the server with the swap size limits.
c.loopOutRestrictions <- NewRestrictions(minAmt, maxAmt) c.loopOutRestrictions <- NewRestrictions(step.minAmt, step.maxAmt)
c.loopInRestrictions <- NewRestrictions(step.minAmt, step.maxAmt)
// Provide the liquidity manager with our desired existing set of swaps. // Provide the liquidity manager with our desired existing set of swaps.
c.loopOuts <- existingOut c.loopOuts <- step.existingOut
c.loopIns <- nil c.loopIns <- step.existingIn
// Assert that we query the server for a quote for each of our // Assert that we query the server for a quote for each of our
// recommended swaps. Note that this differs from our set of expected // recommended swaps. Note that this differs from our set of expected
// swaps because we may get quotes for suggested swaps but then just // swaps because we may get quotes for suggested swaps but then just
// log them. // log them.
for _, expected := range quotes { for _, expected := range step.quotesIn {
request := <-c.quoteRequestIn
assert.Equal(
c.t, expected.request.Amount, request.Amount,
)
assert.Equal(
c.t, expected.request.HtlcConfTarget,
request.HtlcConfTarget,
)
c.quotesIn <- expected.quote
}
for _, expected := range step.quotesOut {
request := <-c.quoteRequest request := <-c.quoteRequest
assert.Equal( assert.Equal(
c.t, expected.request.Amount, request.Amount, c.t, expected.request.Amount, request.Amount,
@ -214,7 +293,7 @@ func (c *autoloopTestCtx) autoloop(minAmt, maxAmt btcutil.Amount,
} }
// Assert that we dispatch the expected set of swaps. // Assert that we dispatch the expected set of swaps.
for _, expected := range expectedSwaps { for _, expected := range step.expectedOut {
actual := <-c.outRequest actual := <-c.outRequest
// Set our destination address to nil so that we do not need to // Set our destination address to nil so that we do not need to
@ -224,4 +303,12 @@ func (c *autoloopTestCtx) autoloop(minAmt, maxAmt btcutil.Amount,
assert.Equal(c.t, expected.request, actual) assert.Equal(c.t, expected.request, actual)
c.loopOut <- expected.response c.loopOut <- expected.response
} }
for _, expected := range step.expectedIn {
actual := <-c.inRequest
assert.Equal(c.t, expected.request, actual)
c.loopIn <- expected.response
}
} }

@ -90,6 +90,10 @@ const (
) )
var ( var (
// defaultHtlcConfTarget is the default confirmation target we use for
// loop in swap htlcs, set to the same default at the client.
defaultHtlcConfTarget = loop.DefaultHtlcConfTarget
// defaultBudget is the default autoloop budget we set. This budget will // defaultBudget is the default autoloop budget we set. This budget will
// only be used for automatically dispatched swaps if autoloop is // only be used for automatically dispatched swaps if autoloop is
// explicitly enabled, so we are happy to set a non-zero value here. The // explicitly enabled, so we are happy to set a non-zero value here. The
@ -107,6 +111,7 @@ var (
PeerRules: make(map[route.Vertex]*SwapRule), PeerRules: make(map[route.Vertex]*SwapRule),
FailureBackOff: defaultFailureBackoff, FailureBackOff: defaultFailureBackoff,
SweepConfTarget: defaultConfTarget, SweepConfTarget: defaultConfTarget,
HtlcConfTarget: defaultHtlcConfTarget,
FeeLimit: defaultFeePortion(), FeeLimit: defaultFeePortion(),
} }
@ -170,10 +175,18 @@ type Config struct {
LoopOutQuote func(ctx context.Context, LoopOutQuote func(ctx context.Context,
request *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, error) request *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, error)
// LoopInQuote provides a quote for a loop in swap.
LoopInQuote func(ctx context.Context,
request *loop.LoopInQuoteRequest) (*loop.LoopInQuote, error)
// LoopOut dispatches a loop out. // LoopOut dispatches a loop out.
LoopOut func(ctx context.Context, request *loop.OutRequest) ( LoopOut func(ctx context.Context, request *loop.OutRequest) (
*loop.LoopOutSwapInfo, error) *loop.LoopOutSwapInfo, error)
// LoopIn dispatches a loop in swap.
LoopIn func(ctx context.Context,
request *loop.LoopInRequest) (*loop.LoopInSwapInfo, error)
// Clock allows easy mocking of time in unit tests. // Clock allows easy mocking of time in unit tests.
Clock clock.Clock Clock clock.Clock
@ -212,6 +225,10 @@ type Parameters struct {
// transaction in. This value affects the on chain fees we will pay. // transaction in. This value affects the on chain fees we will pay.
SweepConfTarget int32 SweepConfTarget int32
// HtlcConfTarget is the confirmation target that we use for publishing
// loop in swap htlcs on chain.
HtlcConfTarget int32
// FeeLimit controls the fee limit we place on swaps. // FeeLimit controls the fee limit we place on swaps.
FeeLimit FeeLimit FeeLimit FeeLimit
@ -249,10 +266,11 @@ func (p Parameters) String() string {
} }
return fmt.Sprintf("rules: %v, failure backoff: %v, sweep "+ return fmt.Sprintf("rules: %v, failure backoff: %v, sweep "+
"sweep conf target: %v, fees: %v, auto budget: %v, budget "+ "sweep conf target: %v, htlc conf target: %v,fees: %v, "+
"start: %v, max auto in flight: %v, minimum swap size=%v, "+ "auto budget: %v, budget start: %v, max auto in flight: %v, "+
"maximum swap size=%v", strings.Join(ruleList, ","), "minimum swap size=%v, maximum swap size=%v",
p.FailureBackOff, p.SweepConfTarget, p.FeeLimit, strings.Join(ruleList, ","), p.FailureBackOff,
p.SweepConfTarget, p.HtlcConfTarget, p.FeeLimit,
p.AutoFeeBudget, p.AutoFeeStartDate, p.MaxAutoInFlight, p.AutoFeeBudget, p.AutoFeeStartDate, p.MaxAutoInFlight,
p.ClientRestrictions.Minimum, p.ClientRestrictions.Maximum) p.ClientRestrictions.Minimum, p.ClientRestrictions.Maximum)
} }
@ -310,6 +328,11 @@ func (p Parameters) validate(minConfs int32, openChans []lndclient.ChannelInfo,
return ErrZeroChannelID return ErrZeroChannelID
} }
if rule.Type == swap.TypeIn {
return errors.New("channel level rules not supported for " +
"loop in swaps, only peer-level rules allowed")
}
if err := rule.validate(); err != nil { if err := rule.validate(); err != nil {
return fmt.Errorf("channel: %v has invalid rule: %v", return fmt.Errorf("channel: %v has invalid rule: %v",
channel.ToUint64(), err) channel.ToUint64(), err)
@ -329,6 +352,10 @@ func (p Parameters) validate(minConfs int32, openChans []lndclient.ChannelInfo,
minConfs) minConfs)
} }
if p.HtlcConfTarget < 1 {
return fmt.Errorf("htlc confirmation target must be > 0")
}
if err := p.FeeLimit.validate(); err != nil { if err := p.FeeLimit.validate(); err != nil {
return err return err
} }
@ -508,7 +535,7 @@ func (m *Manager) autoloop(ctx context.Context) error {
// If we don't actually have dispatch of swaps enabled, log // If we don't actually have dispatch of swaps enabled, log
// suggestions. // suggestions.
if !m.params.Autoloop { if !m.params.Autoloop {
log.Debugf("recommended autoloop: %v sats over "+ log.Debugf("recommended autoloop out: %v sats over "+
"%v", swap.Amount, swap.OutgoingChanSet) "%v", swap.Amount, swap.OutgoingChanSet)
continue continue
@ -526,6 +553,27 @@ func (m *Manager) autoloop(ctx context.Context) error {
loopOut.HtlcAddressP2WSH) loopOut.HtlcAddressP2WSH)
} }
for _, in := range suggestion.InSwaps {
// If we don't actually have dispatch of swaps enabled, log
// suggestions.
if !m.params.Autoloop {
log.Debugf("recommended autoloop in: %v sats over "+
"%v", in.Amount, in.LastHop)
continue
}
in := in
loopIn, err := m.cfg.LoopIn(ctx, &in)
if err != nil {
return err
}
log.Infof("loop in automatically dispatched: hash: %v, "+
"address: %v", loopIn.SwapHash,
loopIn.HtlcAddressNP2WSH)
}
return nil return nil
} }
@ -546,6 +594,9 @@ type Suggestions struct {
// OutSwaps is the set of loop out swaps that we suggest executing. // OutSwaps is the set of loop out swaps that we suggest executing.
OutSwaps []loop.OutRequest OutSwaps []loop.OutRequest
// InSwaps is the set of loop in swaps that we suggest executing.
InSwaps []loop.LoopInRequest
// DisqualifiedChans maps the set of channels that we do not recommend // DisqualifiedChans maps the set of channels that we do not recommend
// swaps on to the reason that we did not recommend a swap. // swaps on to the reason that we did not recommend a swap.
DisqualifiedChans map[lnwire.ShortChannelID]Reason DisqualifiedChans map[lnwire.ShortChannelID]Reason
@ -563,13 +614,17 @@ func newSuggestions() *Suggestions {
} }
func (s *Suggestions) addSwap(swap swapSuggestion) error { func (s *Suggestions) addSwap(swap swapSuggestion) error {
out, ok := swap.(*loopOutSwapSuggestion) switch t := swap.(type) {
if !ok { case *loopOutSwapSuggestion:
s.OutSwaps = append(s.OutSwaps, t.OutRequest)
case *loopInSwapSuggestion:
s.InSwaps = append(s.InSwaps, t.LoopInRequest)
default:
return fmt.Errorf("unexpected swap type: %T", swap) return fmt.Errorf("unexpected swap type: %T", swap)
} }
s.OutSwaps = append(s.OutSwaps, out.OutRequest)
return nil return nil
} }
@ -624,6 +679,11 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
return nil, err return nil, err
} }
inRestrictions, err := m.getSwapRestrictions(ctx, swap.TypeIn)
if err != nil {
return nil, err
}
// List our current set of swaps so that we can determine which channels // List our current set of swaps so that we can determine which channels
// are already being utilized by swaps. Note that these calls may race // are already being utilized by swaps. Note that these calls may race
// with manual initiation of swaps. // with manual initiation of swaps.
@ -708,7 +768,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
suggestion, err := m.suggestSwap( suggestion, err := m.suggestSwap(
ctx, traffic, balances, rule, outRestrictions, ctx, traffic, balances, rule, outRestrictions,
autoloop, inRestrictions, autoloop,
) )
var reasonErr *reasonError var reasonErr *reasonError
if errors.As(err, &reasonErr) { if errors.As(err, &reasonErr) {
@ -734,7 +794,7 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
suggestion, err := m.suggestSwap( suggestion, err := m.suggestSwap(
ctx, traffic, balance, rule, outRestrictions, ctx, traffic, balance, rule, outRestrictions,
autoloop, inRestrictions, autoloop,
) )
var reasonErr *reasonError var reasonErr *reasonError
@ -830,18 +890,24 @@ func (m *Manager) SuggestSwaps(ctx context.Context, autoloop bool) (
// swap request for the rule provided. // swap request for the rule provided.
func (m *Manager) suggestSwap(ctx context.Context, traffic *swapTraffic, func (m *Manager) suggestSwap(ctx context.Context, traffic *swapTraffic,
balance *balances, rule *SwapRule, outRestrictions *Restrictions, balance *balances, rule *SwapRule, outRestrictions *Restrictions,
autoloop bool) (swapSuggestion, error) { inRestrictions *Restrictions, autoloop bool) (swapSuggestion, error) {
var ( var (
builder swapBuilder builder swapBuilder
restrictions *Restrictions restrictions *Restrictions
) )
// Get an appropriate builder and set of restrictions based on our swap
// type.
switch rule.Type { switch rule.Type {
case swap.TypeOut: case swap.TypeOut:
builder = newLoopOutBuilder(m.cfg) builder = newLoopOutBuilder(m.cfg)
restrictions = outRestrictions restrictions = outRestrictions
case swap.TypeIn:
builder = newLoopInBuilder(m.cfg)
restrictions = inRestrictions
default: default:
return nil, fmt.Errorf("unsupported swap type: %v", rule.Type) return nil, fmt.Errorf("unsupported swap type: %v", rule.Type)
} }
@ -863,7 +929,7 @@ func (m *Manager) suggestSwap(ctx context.Context, traffic *swapTraffic,
// Next, get the amount that we need to swap for this entity, skipping // Next, get the amount that we need to swap for this entity, skipping
// over it if no change in liquidity is required. // over it if no change in liquidity is required.
amount := rule.swapAmount(balance, restrictions) amount := rule.swapAmount(balance, restrictions, rule.Type)
if amount == 0 { if amount == 0 {
return nil, newReasonError(ReasonLiquidityOk) return nil, newReasonError(ReasonLiquidityOk)
} }

@ -16,6 +16,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -1292,6 +1293,19 @@ func TestInFlightLimit(t *testing.T) {
} }
} }
type mockServer struct {
mock.Mock
}
// Restrictions mocks a call to the server to get swap size restrictions.
func (m *mockServer) Restrictions(ctx context.Context, swapType swap.Type) (
*Restrictions, error) {
args := m.Called(ctx, swapType)
return args.Get(0).(*Restrictions), args.Error(1)
}
// TestSizeRestrictions tests the use of client-set size restrictions on swaps. // TestSizeRestrictions tests the use of client-set size restrictions on swaps.
func TestSizeRestrictions(t *testing.T) { func TestSizeRestrictions(t *testing.T) {
var ( var (
@ -1321,9 +1335,7 @@ func TestSizeRestrictions(t *testing.T) {
// has configured. // has configured.
clientRestrictions Restrictions clientRestrictions Restrictions
// server holds the server's mocked responses to our terms prepareMock func(m *mockServer)
// endpoint.
serverRestrictions []Restrictions
// suggestions is the set of suggestions we expect. // suggestions is the set of suggestions we expect.
suggestions *Suggestions suggestions *Suggestions
@ -1336,9 +1348,6 @@ func TestSizeRestrictions(t *testing.T) {
clientRestrictions: Restrictions{ clientRestrictions: Restrictions{
Minimum: 7000, Minimum: 7000,
}, },
serverRestrictions: []Restrictions{
serverRestrictions, serverRestrictions,
},
suggestions: &Suggestions{ suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{ OutSwaps: []loop.OutRequest{
chan1Rec, chan1Rec,
@ -1352,9 +1361,6 @@ func TestSizeRestrictions(t *testing.T) {
clientRestrictions: Restrictions{ clientRestrictions: Restrictions{
Minimum: 8000, Minimum: 8000,
}, },
serverRestrictions: []Restrictions{
serverRestrictions, serverRestrictions,
},
suggestions: &Suggestions{ suggestions: &Suggestions{
DisqualifiedChans: map[lnwire.ShortChannelID]Reason{ DisqualifiedChans: map[lnwire.ShortChannelID]Reason{
chanID1: ReasonLiquidityOk, chanID1: ReasonLiquidityOk,
@ -1367,9 +1373,6 @@ func TestSizeRestrictions(t *testing.T) {
clientRestrictions: Restrictions{ clientRestrictions: Restrictions{
Maximum: 7000, Maximum: 7000,
}, },
serverRestrictions: []Restrictions{
serverRestrictions, serverRestrictions,
},
suggestions: &Suggestions{ suggestions: &Suggestions{
OutSwaps: []loop.OutRequest{ OutSwaps: []loop.OutRequest{
outSwap, outSwap,
@ -1387,12 +1390,26 @@ func TestSizeRestrictions(t *testing.T) {
Minimum: 6500, Minimum: 6500,
Maximum: 9000, Maximum: 9000,
}, },
serverRestrictions: []Restrictions{ prepareMock: func(m *mockServer) {
serverRestrictions, restrictions := serverRestrictions
{
Minimum: 5000, m.On(
Maximum: 6000, "Restrictions", mock.Anything,
}, swap.TypeOut,
).Return(
&restrictions, nil,
).Once()
m.On(
"Restrictions", mock.Anything,
swap.TypeOut,
).Return(
&Restrictions{
Minimum: 5000,
Maximum: 6000,
}, nil,
).Once()
}, },
suggestions: nil, suggestions: nil,
expectedError: ErrMaxExceedsServer, expectedError: ErrMaxExceedsServer,
@ -1415,27 +1432,32 @@ func TestSizeRestrictions(t *testing.T) {
chanID1: chanRule, chanID1: chanRule,
} }
// callCount tracks the number of calls we make to // Use a mock that has our expected calls for the test
// our restrictions endpoint. // case set to provide server restrictions.
var callCount int mockServer := &mockServer{}
cfg.Restrictions = func(_ context.Context, _ swap.Type) (
*Restrictions, error) {
restrictions := testCase.serverRestrictions[callCount] // If the test wants us to prime the mock, use its
callCount++ // function, otherwise just return our default
// restrictions.
if testCase.prepareMock != nil {
testCase.prepareMock(mockServer)
} else {
restrictions := serverRestrictions
return &restrictions, nil mockServer.On(
"Restrictions", mock.Anything,
mock.Anything,
).Return(&restrictions, nil)
} }
cfg.Restrictions = mockServer.Restrictions
testSuggestSwaps( testSuggestSwaps(
t, newSuggestSwapsSetup(cfg, lnd, params), t, newSuggestSwapsSetup(cfg, lnd, params),
testCase.suggestions, testCase.expectedError, testCase.suggestions, testCase.expectedError,
) )
require.Equal( mockServer.AssertExpectations(t)
t, callCount, len(testCase.serverRestrictions),
"too many restrictions provided by mock",
)
}) })
} }
} }

@ -0,0 +1,126 @@
package liquidity
import (
"context"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/labels"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Compile-time assertion that loopInBuilder satisfies the swapBuilder
// interface.
var _ swapBuilder = (*loopInBuilder)(nil)
func newLoopInBuilder(cfg *Config) *loopInBuilder {
return &loopInBuilder{
cfg: cfg,
}
}
type loopInBuilder struct {
// cfg contains all the external functionality we require to create
// swaps.
cfg *Config
}
// swapType returns the swap type that the builder is responsible for creating.
func (b *loopInBuilder) swapType() swap.Type {
return swap.TypeIn
}
// maySwap checks whether we can currently execute a swap, examining the
// current on-chain fee conditions against relevant to our swap type against
// our fee restrictions.
//
// For loop in, we cannot check any upfront costs because we do not know how
// many inputs will be used for our on-chain htlc before it is made, so we can't
// make nay estimations.
func (b *loopInBuilder) maySwap(_ context.Context, _ Parameters) error {
return nil
}
// inUse examines our current swap traffic to determine whether we should
// suggest the builder's type of swap for the peer and channels suggested.
func (b *loopInBuilder) inUse(traffic *swapTraffic, peer route.Vertex,
channels []lnwire.ShortChannelID) error {
for _, chanID := range channels {
if traffic.ongoingLoopOut[chanID] {
log.Debugf("Channel: %v not eligible for suggestions, "+
"ongoing loop out utilizing channel", chanID)
return newReasonError(ReasonLoopOut)
}
}
if traffic.ongoingLoopIn[peer] {
log.Debugf("Peer: %x not eligible for suggestions ongoing "+
"loop in utilizing peer", peer)
return newReasonError(ReasonLoopIn)
}
lastFail, recentFail := traffic.failedLoopIn[peer]
if recentFail {
log.Debugf("Peer: %v not eligible for suggestions, "+
"was part of a failed swap at: %v", peer,
lastFail)
return newReasonError(ReasonFailureBackoff)
}
return nil
}
// buildSwap creates a swap for the target peer/channels provided. The autoloop
// boolean indicates whether this swap will actually be executed.
//
// For loop in, we do not add the autoloop label for dry runs.
func (b *loopInBuilder) buildSwap(ctx context.Context, pubkey route.Vertex,
_ []lnwire.ShortChannelID, amount btcutil.Amount,
autoloop bool, params Parameters) (swapSuggestion, error) {
quote, err := b.cfg.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
Amount: amount,
LastHop: &pubkey,
HtlcConfTarget: params.HtlcConfTarget,
})
if err != nil {
// If the server fails our quote, we're not reachable right
// now, so we want to catch this error and fail with a
// structured error so that we know why we can't swap.
status, ok := status.FromError(err)
if ok && status.Code() == codes.FailedPrecondition {
return nil, newReasonError(ReasonLoopInUnreachable)
}
return nil, err
}
if err := params.FeeLimit.loopInLimits(amount, quote); err != nil {
return nil, err
}
request := loop.LoopInRequest{
Amount: amount,
MaxSwapFee: quote.SwapFee,
MaxMinerFee: quote.MinerFee,
HtlcConfTarget: params.HtlcConfTarget,
LastHop: &pubkey,
Initiator: autoloopSwapInitiator,
}
if autoloop {
request.Label = labels.AutoloopLabel(swap.TypeIn)
}
return &loopInSwapSuggestion{
LoopInRequest: request,
}, nil
}

@ -0,0 +1,193 @@
package liquidity
import (
"context"
"errors"
"testing"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// TestLoopinInUse tests that the loop in swap builder prevents dispatching
// swaps for peers when there is already a swap running for that peer.
func TestLoopinInUse(t *testing.T) {
var (
peer1 = route.Vertex{1}
chan1 = lnwire.NewShortChanIDFromInt(1)
peer2 = route.Vertex{2}
chan2 = lnwire.NewShortChanIDFromInt(2)
)
tests := []struct {
name string
ongoingLoopOut *lnwire.ShortChannelID
ongoingLoopIn *route.Vertex
failedLoopIn *route.Vertex
expectedErr error
}{
{
name: "swap allowed",
ongoingLoopIn: &peer2,
ongoingLoopOut: &chan2,
failedLoopIn: &peer2,
expectedErr: nil,
},
{
name: "conflicts with loop out",
ongoingLoopOut: &chan1,
expectedErr: newReasonError(ReasonLoopOut),
},
{
name: "conflicts with loop in",
ongoingLoopIn: &peer1,
expectedErr: newReasonError(ReasonLoopIn),
},
{
name: "previous failed loopin",
failedLoopIn: &peer1,
expectedErr: newReasonError(ReasonFailureBackoff),
},
}
for _, testCase := range tests {
traffic := newSwapTraffic()
if testCase.ongoingLoopOut != nil {
traffic.ongoingLoopOut[*testCase.ongoingLoopOut] = true
}
if testCase.ongoingLoopIn != nil {
traffic.ongoingLoopIn[*testCase.ongoingLoopIn] = true
}
if testCase.failedLoopIn != nil {
traffic.failedLoopIn[*testCase.failedLoopIn] = testTime
}
builder := newLoopInBuilder(nil)
err := builder.inUse(traffic, peer1, []lnwire.ShortChannelID{
chan1,
})
require.Equal(t, testCase.expectedErr, err)
}
}
// TestLoopinBuildSwap tests construction of loop in swaps for autoloop,
// including the case where the client cannot get a quote because it is not
// reachable from the server.
func TestLoopinBuildSwap(t *testing.T) {
var (
peer1 = route.Vertex{1}
chan1 = lnwire.NewShortChanIDFromInt(1)
htlcConfTarget int32 = 6
swapAmt btcutil.Amount = 100000
quote = &loop.LoopInQuote{
SwapFee: 1,
MinerFee: 2,
}
expectedSwap = &loopInSwapSuggestion{
loop.LoopInRequest{
Amount: swapAmt,
MaxSwapFee: quote.SwapFee,
MaxMinerFee: quote.MinerFee,
HtlcConfTarget: htlcConfTarget,
LastHop: &peer1,
Initiator: autoloopSwapInitiator,
},
}
quoteRequest = &loop.LoopInQuoteRequest{
Amount: swapAmt,
LastHop: &peer1,
HtlcConfTarget: htlcConfTarget,
}
errPrecondition = status.Error(codes.FailedPrecondition, "failed")
errOtherCode = status.Error(codes.DeadlineExceeded, "timeout")
errNoCode = errors.New("failure")
)
tests := []struct {
name string
prepareMock func(*mockCfg)
expectedSwap swapSuggestion
expectedErr error
}{
{
name: "quote successful",
prepareMock: func(m *mockCfg) {
m.On(
"LoopInQuote", mock.Anything,
quoteRequest,
).Return(quote, nil)
},
expectedSwap: expectedSwap,
},
{
name: "client unreachable",
prepareMock: func(m *mockCfg) {
m.On(
"LoopInQuote", mock.Anything,
quoteRequest,
).Return(quote, errPrecondition)
},
expectedSwap: nil,
expectedErr: newReasonError(ReasonLoopInUnreachable),
},
{
name: "other error code",
prepareMock: func(m *mockCfg) {
m.On(
"LoopInQuote", mock.Anything,
quoteRequest,
).Return(quote, errOtherCode)
},
expectedSwap: nil,
expectedErr: errOtherCode,
},
{
name: "no error code",
prepareMock: func(m *mockCfg) {
m.On(
"LoopInQuote", mock.Anything,
quoteRequest,
).Return(quote, errNoCode)
},
expectedSwap: nil,
expectedErr: errNoCode,
},
}
for _, testCase := range tests {
mock, cfg := newMockConfig()
params := defaultParameters
params.HtlcConfTarget = htlcConfTarget
params.AutoFeeBudget = 100000
testCase.prepareMock(mock)
builder := newLoopInBuilder(cfg)
swap, err := builder.buildSwap(
context.Background(), peer1, []lnwire.ShortChannelID{
chan1,
}, swapAmt, false, params,
)
assert.Equal(t, testCase.expectedSwap, swap)
assert.Equal(t, testCase.expectedErr, err)
mock.AssertExpectations(t)
}
}

@ -0,0 +1,33 @@
package liquidity
import (
"context"
"github.com/lightninglabs/loop"
"github.com/stretchr/testify/mock"
)
// newMockConfig returns a liquidity config with mocked calls. Note that
// functions that are not implemented by the mock will panic if called.
func newMockConfig() (*mockCfg, *Config) {
mockCfg := &mockCfg{}
// Create a liquidity config which calls our mock.
config := &Config{
LoopInQuote: mockCfg.LoopInQuote,
}
return mockCfg, config
}
type mockCfg struct {
mock.Mock
}
// LoopInQuote mocks a call to get a loop in quote from the server.
func (m *mockCfg) LoopInQuote(ctx context.Context,
request *loop.LoopInQuoteRequest) (*loop.LoopInQuote, error) {
args := m.Called(ctx, request)
return args.Get(0).(*loop.LoopInQuote), args.Error(1)
}

@ -65,6 +65,10 @@ const (
// ReasonFeePPMInsufficient indicates that the fees a swap would require // ReasonFeePPMInsufficient indicates that the fees a swap would require
// are greater than the portion of swap amount allocated to fees. // are greater than the portion of swap amount allocated to fees.
ReasonFeePPMInsufficient ReasonFeePPMInsufficient
// ReasonLoopInUnreachable indicates that the server does not have a
// path to the client, so cannot perform a loop in swap at this time.
ReasonLoopInUnreachable
) )
// String returns a string representation of a reason. // String returns a string representation of a reason.
@ -112,6 +116,9 @@ func (r Reason) String() string {
case ReasonFeePPMInsufficient: case ReasonFeePPMInsufficient:
return "fee portion insufficient" return "fee portion insufficient"
case ReasonLoopInUnreachable:
return "loop in unreachable"
default: default:
return "unknown" return "unknown"
} }

@ -72,7 +72,7 @@ func (r *ThresholdRule) validate() error {
// swapAmount suggests a swap based on the liquidity thresholds configured, // swapAmount suggests a swap based on the liquidity thresholds configured,
// returning zero if no swap is recommended. // returning zero if no swap is recommended.
func (r *ThresholdRule) swapAmount(channel *balances, func (r *ThresholdRule) swapAmount(channel *balances,
restrictions *Restrictions) btcutil.Amount { restrictions *Restrictions, swapType swap.Type) btcutil.Amount {
var ( var (
// For loop out swaps, we want to adjust our incoming liquidity // For loop out swaps, we want to adjust our incoming liquidity
@ -95,6 +95,14 @@ func (r *ThresholdRule) swapAmount(channel *balances,
reservePercentage = uint64(r.MinimumOutgoing) reservePercentage = uint64(r.MinimumOutgoing)
) )
// For loop in swaps, we reverse our target and reserve values.
if swapType == swap.TypeIn {
targetBalance = channel.outgoing
targetPercentage = uint64(r.MinimumOutgoing)
reserveBalance = channel.incoming
reservePercentage = uint64(r.MinimumIncoming)
}
// Examine our total balance and required ratios to decide whether we // Examine our total balance and required ratios to decide whether we
// need to swap. // need to swap.
amount := calculateSwapAmount( amount := calculateSwapAmount(

@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/swap"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -249,6 +250,7 @@ func TestSuggestSwap(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
swap := test.rule.swapAmount( swap := test.rule.swapAmount(
test.channel, test.outRestrictions, test.channel, test.outRestrictions,
swap.TypeOut,
) )
require.Equal(t, test.swap, swap) require.Equal(t, test.swap, swap)
}) })

@ -719,8 +719,9 @@ func (s *swapClientServer) GetLiquidityParams(_ context.Context,
Rules: make( Rules: make(
[]*clientrpc.LiquidityRule, 0, totalRules, []*clientrpc.LiquidityRule, 0, totalRules,
), ),
MinSwapAmount: uint64(cfg.ClientRestrictions.Minimum), MinSwapAmount: uint64(cfg.ClientRestrictions.Minimum),
MaxSwapAmount: uint64(cfg.ClientRestrictions.Maximum), MaxSwapAmount: uint64(cfg.ClientRestrictions.Maximum),
HtlcConfTarget: cfg.HtlcConfTarget,
} }
switch f := cfg.FeeLimit.(type) { switch f := cfg.FeeLimit.(type) {
@ -812,6 +813,7 @@ func (s *swapClientServer) SetLiquidityParams(ctx context.Context,
Minimum: btcutil.Amount(in.Parameters.MinSwapAmount), Minimum: btcutil.Amount(in.Parameters.MinSwapAmount),
Maximum: btcutil.Amount(in.Parameters.MaxSwapAmount), Maximum: btcutil.Amount(in.Parameters.MaxSwapAmount),
}, },
HtlcConfTarget: in.Parameters.HtlcConfTarget,
} }
// Zero unix time is different to zero golang time. // Zero unix time is different to zero golang time.
@ -953,13 +955,17 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context,
return nil, err return nil, err
} }
var ( resp := &clientrpc.SuggestSwapsResponse{
loopOut []*clientrpc.LoopOutRequest LoopOut: make(
disqualified []*clientrpc.Disqualified []*clientrpc.LoopOutRequest, len(suggestions.OutSwaps),
) ),
LoopIn: make(
[]*clientrpc.LoopInRequest, len(suggestions.InSwaps),
),
}
for _, swap := range suggestions.OutSwaps { for i, swap := range suggestions.OutSwaps {
loopOut = append(loopOut, &clientrpc.LoopOutRequest{ resp.LoopOut[i] = &clientrpc.LoopOutRequest{
Amt: int64(swap.Amount), Amt: int64(swap.Amount),
OutgoingChanSet: swap.OutgoingChanSet, OutgoingChanSet: swap.OutgoingChanSet,
MaxSwapFee: int64(swap.MaxSwapFee), MaxSwapFee: int64(swap.MaxSwapFee),
@ -968,7 +974,22 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context,
MaxSwapRoutingFee: int64(swap.MaxSwapRoutingFee), MaxSwapRoutingFee: int64(swap.MaxSwapRoutingFee),
MaxPrepayRoutingFee: int64(swap.MaxPrepayRoutingFee), MaxPrepayRoutingFee: int64(swap.MaxPrepayRoutingFee),
SweepConfTarget: swap.SweepConfTarget, SweepConfTarget: swap.SweepConfTarget,
}) }
}
for i, swap := range suggestions.InSwaps {
loopIn := &clientrpc.LoopInRequest{
Amt: int64(swap.Amount),
MaxSwapFee: int64(swap.MaxSwapFee),
MaxMinerFee: int64(swap.MaxMinerFee),
HtlcConfTarget: swap.HtlcConfTarget,
}
if swap.LastHop != nil {
loopIn.LastHop = swap.LastHop[:]
}
resp.LoopIn[i] = loopIn
} }
for id, reason := range suggestions.DisqualifiedChans { for id, reason := range suggestions.DisqualifiedChans {
@ -982,7 +1003,7 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context,
ChannelId: id.ToUint64(), ChannelId: id.ToUint64(),
} }
disqualified = append(disqualified, exclChan) resp.Disqualified = append(resp.Disqualified, exclChan)
} }
for pubkey, reason := range suggestions.DisqualifiedPeers { for pubkey, reason := range suggestions.DisqualifiedPeers {
@ -996,13 +1017,10 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context,
Pubkey: pubkey[:], Pubkey: pubkey[:],
} }
disqualified = append(disqualified, exclChan) resp.Disqualified = append(resp.Disqualified, exclChan)
} }
return &clientrpc.SuggestSwapsResponse{ return resp, nil
LoopOut: loopOut,
Disqualified: disqualified,
}, nil
} }
func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) { func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) {

@ -39,6 +39,7 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager {
mngrCfg := &liquidity.Config{ mngrCfg := &liquidity.Config{
AutoloopTicker: ticker.NewForce(liquidity.DefaultAutoloopTicker), AutoloopTicker: ticker.NewForce(liquidity.DefaultAutoloopTicker),
LoopOut: client.LoopOut, LoopOut: client.LoopOut,
LoopIn: client.LoopIn,
Restrictions: func(ctx context.Context, Restrictions: func(ctx context.Context,
swapType swap.Type) (*liquidity.Restrictions, error) { swapType swap.Type) (*liquidity.Restrictions, error) {
@ -65,6 +66,7 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager {
Lnd: client.LndServices, Lnd: client.LndServices,
Clock: clock.NewDefaultClock(), Clock: clock.NewDefaultClock(),
LoopOutQuote: client.LoopOutQuote, LoopOutQuote: client.LoopOutQuote,
LoopInQuote: client.LoopInQuote,
ListLoopOut: client.Store.FetchLoopOutSwaps, ListLoopOut: client.Store.FetchLoopOutSwaps,
ListLoopIn: client.Store.FetchLoopInSwaps, ListLoopIn: client.Store.FetchLoopInSwaps,
MinimumConfirmations: minConfTarget, MinimumConfirmations: minConfTarget,

@ -2179,6 +2179,9 @@ type LiquidityParameters struct {
//dispatch a swap for. This value is subject to the server-side limits //dispatch a swap for. This value is subject to the server-side limits
//specified by the LoopOutTerms endpoint. //specified by the LoopOutTerms endpoint.
MaxSwapAmount uint64 `protobuf:"varint,15,opt,name=max_swap_amount,json=maxSwapAmount,proto3" json:"max_swap_amount,omitempty"` MaxSwapAmount uint64 `protobuf:"varint,15,opt,name=max_swap_amount,json=maxSwapAmount,proto3" json:"max_swap_amount,omitempty"`
//
//The confirmation target for loop in on-chain htlcs.
HtlcConfTarget int32 `protobuf:"varint,17,opt,name=htlc_conf_target,json=htlcConfTarget,proto3" json:"htlc_conf_target,omitempty"`
} }
func (x *LiquidityParameters) Reset() { func (x *LiquidityParameters) Reset() {
@ -2325,6 +2328,13 @@ func (x *LiquidityParameters) GetMaxSwapAmount() uint64 {
return 0 return 0
} }
func (x *LiquidityParameters) GetHtlcConfTarget() int32 {
if x != nil {
return x.HtlcConfTarget
}
return 0
}
type LiquidityRule struct { type LiquidityRule struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -2636,6 +2646,9 @@ type SuggestSwapsResponse struct {
//The set of recommended loop outs. //The set of recommended loop outs.
LoopOut []*LoopOutRequest `protobuf:"bytes,1,rep,name=loop_out,json=loopOut,proto3" json:"loop_out,omitempty"` LoopOut []*LoopOutRequest `protobuf:"bytes,1,rep,name=loop_out,json=loopOut,proto3" json:"loop_out,omitempty"`
// //
//The set of recommended loop in swaps
LoopIn []*LoopInRequest `protobuf:"bytes,3,rep,name=loop_in,json=loopIn,proto3" json:"loop_in,omitempty"`
//
//Disqualified contains the set of channels that swaps are not recommended //Disqualified contains the set of channels that swaps are not recommended
//for. //for.
Disqualified []*Disqualified `protobuf:"bytes,2,rep,name=disqualified,proto3" json:"disqualified,omitempty"` Disqualified []*Disqualified `protobuf:"bytes,2,rep,name=disqualified,proto3" json:"disqualified,omitempty"`
@ -2680,6 +2693,13 @@ func (x *SuggestSwapsResponse) GetLoopOut() []*LoopOutRequest {
return nil return nil
} }
func (x *SuggestSwapsResponse) GetLoopIn() []*LoopInRequest {
if x != nil {
return x.LoopIn
}
return nil
}
func (x *SuggestSwapsResponse) GetDisqualified() []*Disqualified { func (x *SuggestSwapsResponse) GetDisqualified() []*Disqualified {
if x != nil { if x != nil {
return x.Disqualified return x.Disqualified
@ -2910,7 +2930,7 @@ var file_client_proto_rawDesc = []byte{
0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65,
0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe0, 0x05, 0x0a, 0x13, 0x4c, 0x69, 0x71, 0x75, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8a, 0x06, 0x0a, 0x13, 0x4c, 0x69, 0x71, 0x75,
0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12,
0x2c, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2c, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69,
@ -2956,166 +2976,172 @@ var file_client_proto_rawDesc = []byte{
0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x41, 0x6d, 0x6f, 0x75, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x41, 0x6d, 0x6f, 0x75,
0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x61, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78,
0x53, 0x77, 0x61, 0x70, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x02, 0x0a, 0x0d, 0x4c, 0x53, 0x77, 0x61, 0x70, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x68, 0x74,
0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x63, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x11,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x68, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61,
0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x09, 0x73, 0x72, 0x67, 0x65, 0x74, 0x22, 0x84, 0x02, 0x0a, 0x0d, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69,
0x77, 0x61, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x54, 0x79, 0x70, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e,
0x65, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x74, 0x79,
0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
0x6b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x77, 0x61,
0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18,
0x69, 0x64, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x2e, 0x0a,
0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6f,
0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x52,
0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a,
0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68,
0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d,
0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x12,
0x64, 0x22, 0x59, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f,
0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69,
0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x6e, 0x67, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x59, 0x0a, 0x19, 0x53,
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d,
0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61,
0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x22, 0x1c, 0x0a, 0x1a, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c,
0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79,
0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x75, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61,
0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71,
0x74, 0x22, 0x72, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70,
0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x72, 0x0a, 0x0c, 0x44,
0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x69, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63,
0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75,
0x70, 0x63, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x85, 0x01, 0x0a, 0x14, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x75, 0x74,
0x0a, 0x08, 0x6c, 0x6f, 0x6f, 0x70, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x6f, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22,
0x32, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0xb6, 0x01, 0x0a, 0x14, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73,
0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x4f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x6c, 0x6f, 0x6f, 0x70,
0x75, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6f, 0x6f,
0x65, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x65, 0x73, 0x74, 0x52, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x07,
0x0c, 0x64, 0x69, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2a, 0x25, 0x0a, 0x6c, 0x6f, 0x6f, 0x70, 0x5f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x08, 0x53, 0x77, 0x61, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x4f, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65,
0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x06, 0x6c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x12, 0x39, 0x0a,
0x49, 0x4e, 0x10, 0x01, 0x2a, 0x73, 0x0a, 0x09, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x0c, 0x64, 0x69, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x02, 0x20,
0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69,
0x12, 0x15, 0x0a, 0x11, 0x50, 0x52, 0x45, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x73, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x71,
0x45, 0x41, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2a, 0x25, 0x0a, 0x08, 0x53, 0x77, 0x61, 0x70,
0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54,
0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x01, 0x2a,
0x45, 0x44, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x73, 0x0a, 0x09, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09,
0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x2a, 0xed, 0x01, 0x0a, 0x0d, 0x46, 0x61, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x50,
0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x52, 0x45, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x41, 0x4c, 0x45, 0x44,
0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49,
0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x53, 0x48, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53,
0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4f, 0x46, 0x46, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x10, 0x53, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12,
0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x54, 0x4c,
0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x45, 0x44, 0x10, 0x05, 0x2a, 0xed, 0x01, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65,
0x1c, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52,
0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12,
0x25, 0x0a, 0x21, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x1b, 0x0a, 0x17, 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, 0x56, 0x4e, 0x5f, 0x4f, 0x46, 0x46, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16,
0x41, 0x4c, 0x55, 0x45, 0x10, 0x04, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54,
0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x46, 0x41, 0x49, 0x4c,
0x52, 0x59, 0x10, 0x05, 0x12, 0x23, 0x0a, 0x1f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50,
0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x46, 0x41,
0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x06, 0x2a, 0x2f, 0x0a, 0x11, 0x4c, 0x69, 0x71, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53,
0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10,
0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x04, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x10, 0x01, 0x2a, 0xa6, 0x03, 0x0a, 0x0a, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x10, 0x05, 0x12,
0x75, 0x74, 0x6f, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x55, 0x54, 0x23, 0x0a, 0x1f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f,
0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55,
0x10, 0x00, 0x12, 0x22, 0x0a, 0x1e, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x54, 0x10, 0x06, 0x2a, 0x2f, 0x0a, 0x11, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74,
0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x79, 0x52, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b,
0x52, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48,
0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x53, 0x4f, 0x4c, 0x44, 0x10, 0x01, 0x2a, 0xa6, 0x03, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x6f, 0x52, 0x65,
0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41,
0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x45, 0x4c, 0x41, 0x50, 0x53, 0x45, 0x44, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x22, 0x0a,
0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x1e, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44,
0x4e, 0x5f, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x47, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10,
0x14, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x41, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e,
0x50, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x53, 0x10, 0x02, 0x12, 0x1e, 0x0a,
0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x4e, 0x45, 0x52, 0x5f, 0x46, 0x45, 0x45, 0x1a, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44,
0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x47, 0x45, 0x54, 0x5f, 0x45, 0x4c, 0x41, 0x50, 0x53, 0x45, 0x44, 0x10, 0x03, 0x12, 0x19, 0x0a,
0x4e, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x41, 0x55, 0x15, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x5f,
0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, 0x54, 0x4f,
0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x4f, 0x46, 0x46, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x46, 0x45, 0x45,
0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f,
0x4f, 0x55, 0x54, 0x10, 0x09, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x4e, 0x5f, 0x4d, 0x49, 0x4e, 0x45, 0x52, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a,
0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x0a, 0x12, 0x1c, 0x12, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45,
0x0a, 0x18, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x49, 0x50, 0x41, 0x59, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45,
0x51, 0x55, 0x49, 0x44, 0x49, 0x54, 0x59, 0x5f, 0x4f, 0x4b, 0x10, 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x42, 0x41, 0x43,
0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x4b, 0x4f, 0x46, 0x46, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52,
0x45, 0x54, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x09,
0x0c, 0x12, 0x20, 0x0a, 0x1c, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f,
0x5f, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x0a, 0x12, 0x1c, 0x0a, 0x18, 0x41, 0x55, 0x54,
0x54, 0x10, 0x0d, 0x32, 0xc2, 0x07, 0x0a, 0x0a, 0x53, 0x77, 0x61, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x49, 0x51, 0x55, 0x49, 0x44, 0x49,
0x6e, 0x74, 0x12, 0x39, 0x0a, 0x07, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x17, 0x2e, 0x54, 0x59, 0x5f, 0x4f, 0x4b, 0x10, 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x41, 0x55, 0x54, 0x4f, 0x5f,
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x49, 0x4e,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x20, 0x0a, 0x1c,
0x2e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x5f,
0x06, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x12, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0d, 0x32, 0xc2,
0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x07, 0x0a, 0x0a, 0x53, 0x77, 0x61, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a,
0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x07, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x72, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x1a, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70,
0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x4c, 0x6f, 0x6f, 0x70,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x30, 0x49, 0x6e, 0x12, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f,
0x01, 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x19, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6c, 0x6f, 0x6f,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x6c,
0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x53, 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
0x6f, 0x12, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x09,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70,
0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71,
0x12, 0x40, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x12, 0x39, 0x0a, 0x08, 0x53, 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6c,
0x63, 0x2e, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x40, 0x0a, 0x0c, 0x4c,
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6c, 0x6f,
0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74,
0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a,
0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x15, 0x2e,
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f,
0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d,
0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x73, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d,
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x50, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
0x6f, 0x62, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x6f, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x51,
0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x73, 0x61, 0x74, 0x54, 0x6f, 0x6b, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6f,
0x65, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73,
0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6f, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x15,
0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a,
0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x73, 0x61, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x16,
0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52,
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x5d, 0x0a, 0x12, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x56, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50,
0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70,
0x2e, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72,
0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x5d, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x4c, 0x69,
0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x22, 0x2e,
0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69,
0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73,
0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -3182,40 +3208,41 @@ var file_client_proto_depIdxs = []int32{
25, // 11: looprpc.SetLiquidityParamsRequest.parameters:type_name -> looprpc.LiquidityParameters 25, // 11: looprpc.SetLiquidityParamsRequest.parameters:type_name -> looprpc.LiquidityParameters
4, // 12: looprpc.Disqualified.reason:type_name -> looprpc.AutoReason 4, // 12: looprpc.Disqualified.reason:type_name -> looprpc.AutoReason
5, // 13: looprpc.SuggestSwapsResponse.loop_out:type_name -> looprpc.LoopOutRequest 5, // 13: looprpc.SuggestSwapsResponse.loop_out:type_name -> looprpc.LoopOutRequest
30, // 14: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified 6, // 14: looprpc.SuggestSwapsResponse.loop_in:type_name -> looprpc.LoopInRequest
5, // 15: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest 30, // 15: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified
6, // 16: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest 5, // 16: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest
8, // 17: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest 6, // 17: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest
10, // 18: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest 8, // 18: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest
12, // 19: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest 10, // 19: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest
13, // 20: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest 12, // 20: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest
16, // 21: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest 13, // 21: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest
13, // 22: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest 16, // 22: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest
16, // 23: looprpc.SwapClient.GetLoopInQuote:input_type -> looprpc.QuoteRequest 13, // 23: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest
19, // 24: looprpc.SwapClient.Probe:input_type -> looprpc.ProbeRequest 16, // 24: looprpc.SwapClient.GetLoopInQuote:input_type -> looprpc.QuoteRequest
21, // 25: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest 19, // 25: looprpc.SwapClient.Probe:input_type -> looprpc.ProbeRequest
24, // 26: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest 21, // 26: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest
27, // 27: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest 24, // 27: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest
29, // 28: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest 27, // 28: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest
7, // 29: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse 29, // 29: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest
7, // 30: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse 7, // 30: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse
9, // 31: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus 7, // 31: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse
11, // 32: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse 9, // 32: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus
9, // 33: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus 11, // 33: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse
15, // 34: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse 9, // 34: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus
18, // 35: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse 15, // 35: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse
14, // 36: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse 18, // 36: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse
17, // 37: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse 14, // 37: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse
20, // 38: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse 17, // 38: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse
22, // 39: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse 20, // 39: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse
25, // 40: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters 22, // 40: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse
28, // 41: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse 25, // 41: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters
31, // 42: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse 28, // 42: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse
29, // [29:43] is the sub-list for method output_type 31, // 43: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse
15, // [15:29] is the sub-list for method input_type 30, // [30:44] is the sub-list for method output_type
15, // [15:15] is the sub-list for extension type_name 16, // [16:30] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension extendee 16, // [16:16] is the sub-list for extension type_name
0, // [0:15] is the sub-list for field type_name 16, // [16:16] is the sub-list for extension extendee
0, // [0:16] is the sub-list for field type_name
} }
func init() { file_client_proto_init() } func init() { file_client_proto_init() }

@ -848,6 +848,11 @@ message LiquidityParameters {
specified by the LoopOutTerms endpoint. specified by the LoopOutTerms endpoint.
*/ */
uint64 max_swap_amount = 15; uint64 max_swap_amount = 15;
/*
The confirmation target for loop in on-chain htlcs.
*/
int32 htlc_conf_target = 17;
} }
enum LiquidityRuleType { enum LiquidityRuleType {
@ -1012,6 +1017,11 @@ message SuggestSwapsResponse {
*/ */
repeated LoopOutRequest loop_out = 1; repeated LoopOutRequest loop_out = 1;
/*
The set of recommended loop in swaps
*/
repeated LoopInRequest loop_in = 3;
/* /*
Disqualified contains the set of channels that swaps are not recommended Disqualified contains the set of channels that swaps are not recommended
for. for.

@ -675,6 +675,11 @@
"type": "string", "type": "string",
"format": "uint64", "format": "uint64",
"description": "The maximum amount, expressed in satoshis, that the autoloop client will\ndispatch a swap for. This value is subject to the server-side limits\nspecified by the LoopOutTerms endpoint." "description": "The maximum amount, expressed in satoshis, that the autoloop client will\ndispatch a swap for. This value is subject to the server-side limits\nspecified by the LoopOutTerms endpoint."
},
"htlc_conf_target": {
"type": "integer",
"format": "int32",
"description": "The confirmation target for loop in on-chain htlcs."
} }
} }
}, },
@ -999,6 +1004,13 @@
}, },
"description": "The set of recommended loop outs." "description": "The set of recommended loop outs."
}, },
"loop_in": {
"type": "array",
"items": {
"$ref": "#/definitions/looprpcLoopInRequest"
},
"title": "The set of recommended loop in swaps"
},
"disqualified": { "disqualified": {
"type": "array", "type": "array",
"items": { "items": {

@ -15,6 +15,10 @@ This file tracks release notes for the loop client.
## Next release ## Next release
#### New Features #### New Features
* Loop in functionality has been added to AutoLoop. This feature can be enabled
to acquire outgoing capacity on your node automatically, using
`loop setrule --type=in`. At present, autoloop can only be set to loop out
*or* loop in, and cannot manage liquidity in both directions.
#### Breaking Changes #### Breaking Changes

Loading…
Cancel
Save