Merge pull request #205 from joostjager/channel-set

loop out: allow outbound channel set for multi-loops
pull/203/head
Joost Jager 4 years ago committed by GitHub
commit 3316c4beed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -359,9 +359,8 @@ func (s *Client) resumeSwaps(ctx context.Context,
func (s *Client) LoopOut(globalCtx context.Context,
request *OutRequest) (*lntypes.Hash, btcutil.Address, error) {
log.Infof("LoopOut %v to %v (channel: %v)",
request.Amount, request.DestAddr,
request.LoopOutChannel,
log.Infof("LoopOut %v to %v (channels: %v)",
request.Amount, request.DestAddr, request.OutgoingChanSet,
)
if err := s.waitForInitialized(globalCtx); err != nil {

@ -3,6 +3,8 @@ package main
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcutil"
@ -25,10 +27,10 @@ var loopOutCommand = cli.Command{
Optionally a BASE58/bech32 encoded bitcoin destination address may be
specified. If not specified, a new wallet address will be generated.`,
Flags: []cli.Flag{
cli.Uint64Flag{
cli.StringFlag{
Name: "channel",
Usage: "the 8-byte compact channel ID of the channel " +
"to loop out",
Usage: "the comma-separated list of short " +
"channel IDs of the channels to loop out",
},
cli.StringFlag{
Name: "addr",
@ -87,6 +89,17 @@ func loopOut(ctx *cli.Context) error {
return err
}
// Parse outgoing channel set.
chanStrings := strings.Split(ctx.String("channel"), ",")
var outgoingChanSet []uint64
for _, chanString := range chanStrings {
chanID, err := strconv.ParseUint(chanString, 10, 64)
if err != nil {
return err
}
outgoingChanSet = append(outgoingChanSet, chanID)
}
var destAddr string
switch {
case ctx.IsSet("addr"):
@ -145,11 +158,6 @@ func loopOut(ctx *cli.Context) error {
return err
}
var unchargeChannel uint64
if ctx.IsSet("channel") {
unchargeChannel = ctx.Uint64("channel")
}
resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{
Amt: int64(amt),
Dest: destAddr,
@ -158,7 +166,7 @@ func loopOut(ctx *cli.Context) error {
MaxSwapFee: int64(limits.maxSwapFee),
MaxPrepayRoutingFee: int64(*limits.maxPrepayRoutingFee),
MaxSwapRoutingFee: int64(*limits.maxSwapRoutingFee),
LoopOutChannel: unchargeChannel,
OutgoingChanSet: outgoingChanSet,
SweepConfTarget: sweepConfTarget,
SwapPublicationDeadline: uint64(swapDeadline.Unix()),
})

@ -1,7 +1,7 @@
module github.com/lightninglabs/loop
require (
github.com/btcsuite/btcd v0.20.1-beta
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v1.0.2
github.com/coreos/bbolt v1.3.3
@ -11,7 +11,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway v1.12.2
github.com/jessevdk/go-flags v1.4.0
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
github.com/lightningnetwork/lnd v0.10.0-beta.rc5
github.com/lightningnetwork/lnd v0.10.1-beta.rc1
github.com/lightningnetwork/lnd/queue v1.0.3
github.com/urfave/cli v1.20.0
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0

@ -24,6 +24,9 @@ github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcug
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 h1:QyTpiR5nQe94vza2qkvf7Ns8XX2Rjh/vdIhO3RzGj4o=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
@ -34,6 +37,8 @@ github.com/btcsuite/btcutil/psbt v1.0.2 h1:gCVY3KxdoEVU7Q6TjusPO+GANIwVgr9yTLqM+
github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcwallet v0.11.1-0.20200403222202-ada7ca077ebb h1:kkq2SSCy+OrC7GVZLIqutoHVR2yW4SJQdX70jtmuLDI=
github.com/btcsuite/btcwallet v0.11.1-0.20200403222202-ada7ca077ebb/go.mod h1:9fJNm1aXi4q9P5Nk23mmqppCy1Le3f2/JMWj9UXKkCc=
github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe h1:0m9uXDcnUc3Fv72635O/MfLbhbW+0hfSVgRiWezpkHU=
github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
@ -46,6 +51,8 @@ github.com/btcsuite/btcwallet/walletdb v1.3.1 h1:lW1Ac3F1jJY4K11P+YQtRNcP5jFk27A
github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe h1:yQbJVYfsKbdqDQNLxd4hhiLSiMkIygefW5mSHMsdKpc=
github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe/go.mod h1:OwC0W0HhUszbWdvJvH6xvgabKSJ0lXl11YbmmqF9YXQ=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
@ -157,6 +164,8 @@ github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.10.0-beta.rc5 h1:HcX35Djwk+xoNQe/LA7HnQ11jzbq68TAcpBluhNIKqc=
github.com/lightningnetwork/lnd v0.10.0-beta.rc5/go.mod h1:mEnmP+sSgiKUFBozT3I5xEOgRAREMEWd/3lcWDrB+5E=
github.com/lightningnetwork/lnd v0.10.1-beta.rc1 h1:4uBkLHrxeIf6ad5AHlFhGcOhtbvmYwK4iTcDMixmLpw=
github.com/lightningnetwork/lnd v0.10.1-beta.rc1/go.mod h1:mRd+8n/QOlAiolWVnt1RaTzxVvOyplT3J5uYwUb/EDw=
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
@ -222,6 +231,8 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

@ -64,9 +64,9 @@ type OutRequest struct {
// client sweep tx.
SweepConfTarget int32
// LoopOutChannel optionally specifies the short channel id of the
// channel to loop out.
LoopOutChannel *uint64
// OutgoingChanSet optionally specifies the short channel ids of the
// channels that may be used to loop out.
OutgoingChanSet loopdb.ChannelSet
// SwapPublicationDeadline can be set by the client to allow the server
// delaying publication of the swap HTLC to save on chain fees.

@ -31,7 +31,7 @@ var (
minimalCompatibleVersion = &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 0,
AppPatch: 1,
BuildTags: []string{
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
},

@ -64,10 +64,20 @@ type SendPaymentRequest struct {
// are only processed when the Invoice field is empty.
Invoice string
MaxFee btcutil.Amount
MaxCltv *int32
OutgoingChannel *uint64
Timeout time.Duration
// MaxFee is the fee limit for this payment.
MaxFee btcutil.Amount
// MaxCltv is the maximum timelock for this payment. If nil, there is no
// maximum.
MaxCltv *int32
// OutgoingChanIds is a restriction on the set of possible outgoing
// channels. If nil or empty, there is no restriction.
OutgoingChanIds []uint64
// Timeout is the payment loop timeout. After this time, no new payment
// attempts will be started.
Timeout time.Duration
// Target is the node in which the payment should be routed towards.
Target route.Vertex
@ -126,17 +136,16 @@ func (r *routerClient) SendPayment(ctx context.Context,
rpcCtx := r.routerKitMac.WithMacaroonAuth(ctx)
rpcReq := &routerrpc.SendPaymentRequest{
FeeLimitSat: int64(request.MaxFee),
PaymentRequest: request.Invoice,
TimeoutSeconds: int32(request.Timeout.Seconds()),
MaxParts: request.MaxParts,
FeeLimitSat: int64(request.MaxFee),
PaymentRequest: request.Invoice,
TimeoutSeconds: int32(request.Timeout.Seconds()),
MaxParts: request.MaxParts,
OutgoingChanIds: request.OutgoingChanIds,
}
if request.MaxCltv != nil {
rpcReq.CltvLimit = *request.MaxCltv
}
if request.OutgoingChannel != nil {
rpcReq.OutgoingChanId = *request.OutgoingChannel
}
if request.LastHopPubkey != nil {
rpcReq.LastHopPubkey = request.LastHopPubkey[:]
}

@ -24,7 +24,7 @@ var (
LoopMinRequiredLndVersion = &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 0,
AppPatch: 1,
BuildTags: []string{
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
},

@ -89,9 +89,19 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
int64(in.SwapPublicationDeadline), 0,
),
}
if in.LoopOutChannel != 0 {
req.LoopOutChannel = &in.LoopOutChannel
switch {
case in.LoopOutChannel != 0 && len(in.OutgoingChanSet) > 0:
return nil, errors.New("loop_out_channel and outgoing_" +
"chan_ids are mutually exclusive")
case in.LoopOutChannel != 0:
req.OutgoingChanSet = loopdb.ChannelSet{in.LoopOutChannel}
default:
req.OutgoingChanSet = in.OutgoingChanSet
}
hash, htlc, err := s.impl.LoopOut(ctx, req)
if err != nil {
log.Errorf("LoopOut: %v", err)

@ -2,7 +2,6 @@ package loopd
import (
"fmt"
"strconv"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/loop"
@ -64,13 +63,8 @@ func viewOut(swapClient *loop.Client, chainParams *chaincfg.Params) error {
fmt.Printf(" Preimage: %v\n", s.Contract.Preimage)
fmt.Printf(" Htlc address: %v\n", htlc.Address)
unchargeChannel := "any"
if s.Contract.UnchargeChannel != nil {
unchargeChannel = strconv.FormatUint(
*s.Contract.UnchargeChannel, 10,
)
}
fmt.Printf(" Uncharge channel: %v\n", unchargeChannel)
fmt.Printf(" Uncharge channels: %v\n",
s.Contract.OutgoingChanSet)
fmt.Printf(" Dest: %v\n", s.Contract.DestAddr)
fmt.Printf(" Amt: %v, Expiry: %v\n",
s.Contract.AmountRequested, s.Contract.CltvExpiry,

@ -4,6 +4,8 @@ import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/chaincfg"
@ -34,9 +36,9 @@ type LoopOutContract struct {
// client sweep tx.
SweepConfTarget int32
// TargetChannel is the channel to loop out. If zero, any channel may
// be used.
UnchargeChannel *uint64
// OutgoingChanSet is the set of short ids of channels that may be used.
// If empty, any channel may be used.
OutgoingChanSet ChannelSet
// PrepayInvoice is the invoice that the client should pay to the
// server that will be returned if the swap is complete.
@ -53,6 +55,34 @@ type LoopOutContract struct {
SwapPublicationDeadline time.Time
}
// ChannelSet stores a set of channels.
type ChannelSet []uint64
// String returns the human-readable representation of a channel set.
func (c ChannelSet) String() string {
channelStrings := make([]string, len(c))
for i, chanID := range c {
channelStrings[i] = strconv.FormatUint(chanID, 10)
}
return strings.Join(channelStrings, ",")
}
// NewChannelSet instantiates a new channel set and verifies that there are no
// duplicates present.
func NewChannelSet(set []uint64) (ChannelSet, error) {
// Check channel set for duplicates.
chanSet := make(map[uint64]struct{})
for _, chanID := range set {
if _, exists := chanSet[chanID]; exists {
return nil, fmt.Errorf("duplicate chan in set: id=%v",
chanID)
}
chanSet[chanID] = struct{}{}
}
return ChannelSet(set), nil
}
// LoopOut is a combination of the contract and the updates.
type LoopOut struct {
Loop
@ -161,7 +191,7 @@ func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) (
return nil, err
}
if unchargeChannel != 0 {
contract.UnchargeChannel = &unchargeChannel
contract.OutgoingChanSet = ChannelSet{unchargeChannel}
}
var deadlineNano int64
@ -248,10 +278,9 @@ func serializeLoopOutContract(swap *LoopOutContract) (
return nil, err
}
var unchargeChannel uint64
if swap.UnchargeChannel != nil {
unchargeChannel = *swap.UnchargeChannel
}
// Always write no outgoing channel. This field is replaced by an
// outgoing channel set.
unchargeChannel := uint64(0)
if err := binary.Write(&b, byteOrder, unchargeChannel); err != nil {
return nil, err
}

@ -0,0 +1,147 @@
package loopdb
import (
"encoding/hex"
"errors"
"fmt"
"strings"
"github.com/coreos/bbolt"
)
// DumpDB dumps go code describing the contents of the database to stdout. This
// function is only intended for use during development. Therefore also the
// linter unused warnings are suppressed.
//
// Example output:
//
// map[string]interface{}{
// Hex("1234"): map[string]interface{}{
// "human-readable": Hex("102030"),
// Hex("1111"): Hex("5783492373"),
// },
// }
func DumpDB(tx *bbolt.Tx) error { // nolint: unused
return tx.ForEach(func(k []byte, bucket *bbolt.Bucket) error {
key := toString(k)
fmt.Printf("%v: ", key)
err := dumpBucket(bucket)
if err != nil {
return err
}
fmt.Printf(",\n")
return nil
})
}
func dumpBucket(bucket *bbolt.Bucket) error { // nolint: unused
fmt.Printf("map[string]interface{} {\n")
err := bucket.ForEach(func(k, v []byte) error {
key := toString(k)
fmt.Printf("%v: ", key)
subBucket := bucket.Bucket(k)
if subBucket != nil {
err := dumpBucket(subBucket)
if err != nil {
return err
}
} else {
fmt.Print(toHex(v))
}
fmt.Printf(",\n")
return nil
})
if err != nil {
return err
}
fmt.Printf("}")
return nil
}
// RestoreDB primes the database with the given data set.
func RestoreDB(tx *bbolt.Tx, data map[string]interface{}) error {
for k, v := range data {
key := []byte(k)
value := v.(map[string]interface{})
subBucket, err := tx.CreateBucket(key)
if err != nil {
return fmt.Errorf("create bucket %v: %v",
string(key), err)
}
if err := restoreDB(subBucket, value); err != nil {
return err
}
}
return nil
}
func restoreDB(bucket *bbolt.Bucket, data map[string]interface{}) error {
for k, v := range data {
key := []byte(k)
switch value := v.(type) {
// Key contains value.
case string:
err := bucket.Put(key, []byte(value))
if err != nil {
return err
}
// Key contains a sub-bucket.
case map[string]interface{}:
subBucket, err := bucket.CreateBucket(key)
if err != nil {
return err
}
if err := restoreDB(subBucket, value); err != nil {
return err
}
default:
return errors.New("invalid type")
}
}
return nil
}
func toHex(v []byte) string { // nolint: unused
if len(v) == 0 {
return "nil"
}
return "Hex(\"" + hex.EncodeToString(v) + "\")"
}
func toString(v []byte) string { // nolint: unused
readableChars := "abcdefghijklmnopqrstuvwxyz0123456789-"
for _, c := range v {
if !strings.Contains(readableChars, string(c)) {
return toHex(v)
}
}
return "\"" + string(v) + "\""
}
// Hex is a test helper function to convert readable hex arrays to raw byte
// strings.
func Hex(value string) string {
b, err := hex.DecodeString(value)
if err != nil {
panic(err)
}
return string(b)
}

@ -1,9 +1,11 @@
package loopdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"time"
@ -51,6 +53,14 @@ var (
// value: time || rawSwapState
contractKey = []byte("contract")
// outgoingChanSetKey is the key that stores a list of channel ids that
// restrict the loop out swap payment.
//
// path: loopOutBucket -> swapBucket[hash] -> outgoingChanSetKey
//
// value: concatenation of uint64 channel ids
outgoingChanSetKey = []byte("outgoing-chan-set")
byteOrder = binary.BigEndian
keyLength = 33
@ -146,12 +156,15 @@ func NewBoltSwapStore(dbPath string, chainParams *chaincfg.Params) (
}, nil
}
func (s *boltSwapStore) fetchSwaps(bucketKey []byte,
callback func([]byte, Loop) error) error {
// FetchLoopOutSwaps returns all loop out swaps currently in the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
var swaps []*LoopOut
return s.db.View(func(tx *bbolt.Tx) error {
err := s.db.View(func(tx *bbolt.Tx) error {
// First, we'll grab our main loop in bucket key.
rootBucket := tx.Bucket(bucketKey)
rootBucket := tx.Bucket(loopOutBucketKey)
if rootBucket == nil {
return errors.New("bucket does not exist")
}
@ -180,65 +193,58 @@ func (s *boltSwapStore) fetchSwaps(bucketKey []byte,
return errors.New("contract not found")
}
// Once we have the raw swap, we'll also need to decode
// each of the past updates to the swap itself.
stateBucket := swapBucket.Bucket(updatesBucketKey)
if stateBucket == nil {
return errors.New("updates bucket not found")
contract, err := deserializeLoopOutContract(
contractBytes, s.chainParams,
)
if err != nil {
return err
}
// De serialize and collect each swap update into our
// slice of swap events.
var updates []*LoopEvent
err := stateBucket.ForEach(func(k, v []byte) error {
event, err := deserializeLoopEvent(v)
if err != nil {
return err
// Read the list of concatenated outgoing channel ids
// that form the outgoing set.
setBytes := swapBucket.Get(outgoingChanSetKey)
if outgoingChanSetKey != nil {
r := bytes.NewReader(setBytes)
readLoop:
for {
var chanID uint64
err := binary.Read(r, byteOrder, &chanID)
switch {
case err == io.EOF:
break readLoop
case err != nil:
return err
}
contract.OutgoingChanSet = append(
contract.OutgoingChanSet,
chanID,
)
}
}
updates = append(updates, event)
return nil
})
updates, err := deserializeUpdates(swapBucket)
if err != nil {
return err
}
var hash lntypes.Hash
copy(hash[:], swapHash)
loop := Loop{
Hash: hash,
Events: updates,
loop := LoopOut{
Loop: Loop{
Events: updates,
},
Contract: contract,
}
return callback(contractBytes, loop)
})
})
}
// FetchLoopOutSwaps returns all loop out swaps currently in the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
var swaps []*LoopOut
err := s.fetchSwaps(loopOutBucketKey,
func(contractBytes []byte, loop Loop) error {
contract, err := deserializeLoopOutContract(
contractBytes, s.chainParams,
)
loop.Hash, err = lntypes.MakeHash(swapHash)
if err != nil {
return err
}
swaps = append(swaps, &LoopOut{
Contract: contract,
Loop: loop,
})
swaps = append(swaps, &loop)
return nil
},
)
})
})
if err != nil {
return nil, err
}
@ -246,14 +252,72 @@ func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
return swaps, nil
}
// deserializeUpdates deserializes the list of swap updates that are stored as a
// key of the given bucket.
func deserializeUpdates(swapBucket *bbolt.Bucket) ([]*LoopEvent, error) {
// Once we have the raw swap, we'll also need to decode
// each of the past updates to the swap itself.
stateBucket := swapBucket.Bucket(updatesBucketKey)
if stateBucket == nil {
return nil, errors.New("updates bucket not found")
}
// Deserialize and collect each swap update into our slice of swap
// events.
var updates []*LoopEvent
err := stateBucket.ForEach(func(_, v []byte) error {
event, err := deserializeLoopEvent(v)
if err != nil {
return err
}
updates = append(updates, event)
return nil
})
if err != nil {
return nil, err
}
return updates, nil
}
// FetchLoopInSwaps returns all loop in swaps currently in the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) FetchLoopInSwaps() ([]*LoopIn, error) {
var swaps []*LoopIn
err := s.fetchSwaps(loopInBucketKey,
func(contractBytes []byte, loop Loop) error {
err := s.db.View(func(tx *bbolt.Tx) error {
// First, we'll grab our main loop in bucket key.
rootBucket := tx.Bucket(loopInBucketKey)
if rootBucket == nil {
return errors.New("bucket does not exist")
}
// We'll now traverse the root bucket for all active swaps. The
// primary key is the swap hash itself.
return rootBucket.ForEach(func(swapHash, v []byte) error {
// Only go into things that we know are sub-bucket
// keys.
if v != nil {
return nil
}
// From the root bucket, we'll grab the next swap
// bucket for this swap from its swaphash.
swapBucket := rootBucket.Bucket(swapHash)
if swapBucket == nil {
return fmt.Errorf("swap bucket %x not found",
swapHash)
}
// With the main swap bucket obtained, we'll grab the
// raw swap contract bytes and decode it.
contractBytes := swapBucket.Get(contractKey)
if contractBytes == nil {
return errors.New("contract not found")
}
contract, err := deserializeLoopInContract(
contractBytes,
)
@ -261,14 +325,28 @@ func (s *boltSwapStore) FetchLoopInSwaps() ([]*LoopIn, error) {
return err
}
swaps = append(swaps, &LoopIn{
updates, err := deserializeUpdates(swapBucket)
if err != nil {
return err
}
loop := LoopIn{
Loop: Loop{
Events: updates,
},
Contract: contract,
Loop: loop,
})
}
loop.Hash, err = lntypes.MakeHash(swapHash)
if err != nil {
return err
}
swaps = append(swaps, &loop)
return nil
},
)
})
})
if err != nil {
return nil, err
}
@ -276,37 +354,68 @@ func (s *boltSwapStore) FetchLoopInSwaps() ([]*LoopIn, error) {
return swaps, nil
}
// createLoop creates a swap in the store. It requires that the contract is
// already serialized to be able to use this function for both in and out swaps.
func (s *boltSwapStore) createLoop(bucketKey []byte, hash lntypes.Hash,
contractBytes []byte) error {
// createLoopBucket creates the bucket for a particular swap.
func createLoopBucket(tx *bbolt.Tx, swapTypeKey []byte, hash lntypes.Hash) (
*bbolt.Bucket, error) {
// First, we'll grab the root bucket that houses all of our
// swaps of this type.
swapTypeBucket, err := tx.CreateBucketIfNotExists(swapTypeKey)
if err != nil {
return nil, err
}
// If the swap already exists, then we'll exit as we don't want
// to override a swap.
if swapTypeBucket.Get(hash[:]) != nil {
return nil, fmt.Errorf("swap %v already exists", hash)
}
// From the swap type bucket, we'll make a new sub swap bucket using the
// swap hash to store the individual swap.
return swapTypeBucket.CreateBucket(hash[:])
}
// CreateLoopOut adds an initiated swap to the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
swap *LoopOutContract) error {
// If the hash doesn't match the pre-image, then this is an invalid
// swap so we'll bail out early.
if hash != swap.Preimage.Hash() {
return errors.New("hash and preimage do not match")
}
// Otherwise, we'll create a new swap within the database.
return s.db.Update(func(tx *bbolt.Tx) error {
// First, we'll grab the root bucket that houses all of our
// main swaps.
rootBucket, err := tx.CreateBucketIfNotExists(
bucketKey,
)
// Create the swap bucket.
swapBucket, err := createLoopBucket(tx, loopOutBucketKey, hash)
if err != nil {
return err
}
// If the swap already exists, then we'll exit as we don't want
// to override a swap.
if rootBucket.Get(hash[:]) != nil {
return fmt.Errorf("swap %v already exists", hash)
// With the swap bucket created, we'll store the swap itself.
contractBytes, err := serializeLoopOutContract(swap)
if err != nil {
return err
}
// From the root bucket, we'll make a new sub swap bucket using
// the swap hash.
swapBucket, err := rootBucket.CreateBucket(hash[:])
err = swapBucket.Put(contractKey, contractBytes)
if err != nil {
return err
}
// With the swap bucket created, we'll store the swap itself.
err = swapBucket.Put(contractKey, contractBytes)
// Write the outgoing channel set.
var b bytes.Buffer
for _, chanID := range swap.OutgoingChanSet {
err := binary.Write(&b, byteOrder, chanID)
if err != nil {
return err
}
}
err = swapBucket.Put(outgoingChanSetKey, b.Bytes())
if err != nil {
return err
}
@ -318,26 +427,6 @@ func (s *boltSwapStore) createLoop(bucketKey []byte, hash lntypes.Hash,
})
}
// CreateLoopOut adds an initiated swap to the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
swap *LoopOutContract) error {
// If the hash doesn't match the pre-image, then this is an invalid
// swap so we'll bail out early.
if hash != swap.Preimage.Hash() {
return errors.New("hash and preimage do not match")
}
contractBytes, err := serializeLoopOutContract(swap)
if err != nil {
return err
}
return s.createLoop(loopOutBucketKey, hash, contractBytes)
}
// CreateLoopIn adds an initiated swap to the store.
//
// NOTE: Part of the loopdb.SwapStore interface.
@ -350,12 +439,30 @@ func (s *boltSwapStore) CreateLoopIn(hash lntypes.Hash,
return errors.New("hash and preimage do not match")
}
contractBytes, err := serializeLoopInContract(swap)
if err != nil {
return err
}
// Otherwise, we'll create a new swap within the database.
return s.db.Update(func(tx *bbolt.Tx) error {
// Create the swap bucket.
swapBucket, err := createLoopBucket(tx, loopInBucketKey, hash)
if err != nil {
return err
}
// With the swap bucket created, we'll store the swap itself.
contractBytes, err := serializeLoopInContract(swap)
if err != nil {
return err
}
return s.createLoop(loopInBucketKey, hash, contractBytes)
err = swapBucket.Put(contractKey, contractBytes)
if err != nil {
return err
}
// Finally, we'll create an empty updates bucket for this swap
// to track any future updates to the swap itself.
_, err = swapBucket.CreateBucket(updatesBucketKey)
return err
})
}
// updateLoop saves a new swap state transition to the store. It takes in a

@ -40,33 +40,12 @@ var (
// TestLoopOutStore tests all the basic functionality of the current bbolt
// swap store.
func TestLoopOutStore(t *testing.T) {
tempDirName, err := ioutil.TempDir("", "clientstore")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDirName)
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
if err != nil {
t.Fatal(err)
}
// First, verify that an empty database has no active swaps.
swaps, err := store.FetchLoopOutSwaps()
if err != nil {
t.Fatal(err)
}
if len(swaps) != 0 {
t.Fatal("expected empty store")
}
destAddr := test.GetDestAddr(t, 0)
hash := sha256.Sum256(testPreimage[:])
initiationTime := time.Date(2018, 11, 1, 0, 0, 0, 0, time.UTC)
// Next, we'll make a new pending swap that we'll insert into the
// database shortly.
pendingSwap := LoopOutContract{
unrestrictedSwap := LoopOutContract{
SwapContract: SwapContract{
AmountRequested: 100,
Preimage: testPreimage,
@ -92,6 +71,41 @@ func TestLoopOutStore(t *testing.T) {
SwapPublicationDeadline: time.Unix(0, initiationTime.UnixNano()),
}
t.Run("no outgoing set", func(t *testing.T) {
testLoopOutStore(t, &unrestrictedSwap)
})
restrictedSwap := unrestrictedSwap
restrictedSwap.OutgoingChanSet = ChannelSet{1, 2}
t.Run("two channel outgoing set", func(t *testing.T) {
testLoopOutStore(t, &restrictedSwap)
})
}
// testLoopOutStore tests the basic functionality of the current bbolt
// swap store for specific swap parameters.
func testLoopOutStore(t *testing.T, pendingSwap *LoopOutContract) {
tempDirName, err := ioutil.TempDir("", "clientstore")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDirName)
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
if err != nil {
t.Fatal(err)
}
// First, verify that an empty database has no active swaps.
swaps, err := store.FetchLoopOutSwaps()
if err != nil {
t.Fatal(err)
}
if len(swaps) != 0 {
t.Fatal("expected empty store")
}
// checkSwap is a test helper function that'll assert the state of a
// swap.
checkSwap := func(expectedState SwapState) {
@ -107,7 +121,7 @@ func TestLoopOutStore(t *testing.T) {
}
swap := swaps[0].Contract
if !reflect.DeepEqual(swap, &pendingSwap) {
if !reflect.DeepEqual(swap, pendingSwap) {
t.Fatal("invalid pending swap data")
}
@ -118,15 +132,17 @@ func TestLoopOutStore(t *testing.T) {
}
}
hash := pendingSwap.Preimage.Hash()
// If we create a new swap, then it should show up as being initialized
// right after.
if err := store.CreateLoopOut(hash, &pendingSwap); err != nil {
if err := store.CreateLoopOut(hash, pendingSwap); err != nil {
t.Fatal(err)
}
checkSwap(StateInitiated)
// Trying to make the same swap again should result in an error.
if err := store.CreateLoopOut(hash, &pendingSwap); err == nil {
if err := store.CreateLoopOut(hash, pendingSwap); err == nil {
t.Fatal("expected error on storing duplicate")
}
checkSwap(StateInitiated)
@ -366,3 +382,65 @@ func createVersionZeroDb(t *testing.T, dbPath string) {
t.Fatal(err)
}
}
// TestLegacyOutgoingChannel asserts that a legacy channel restriction is
// properly mapped onto the newer channel set.
func TestLegacyOutgoingChannel(t *testing.T) {
var (
legacyDbVersion = Hex("00000003")
legacyOutgoingChannel = Hex("0000000000000005")
)
legacyDb := map[string]interface{}{
"loop-in": map[string]interface{}{},
"metadata": map[string]interface{}{
"dbp": legacyDbVersion,
},
"uncharge-swaps": map[string]interface{}{
Hex("2a595d79a55168970532805ae20c9b5fac98f04db79ba4c6ae9b9ac0f206359e"): map[string]interface{}{
"contract": Hex("1562d6fbec140000010101010202020203030303040404040101010102020202030303030404040400000000000000640d707265706179696e766f69636501010101010101010101010101010101010101010101010101010101010101010201010101010101010101010101010101010101010101010101010101010101010300000090000000000000000a0000000000000014000000000000002800000063223347454e556d6e4552745766516374344e65676f6d557171745a757a5947507742530b73776170696e766f69636500000002000000000000001e") + legacyOutgoingChannel + Hex("1562d6fbec140000"),
"updates": map[string]interface{}{
Hex("0000000000000001"): Hex("1508290a92d4c00001000000000000000000000000000000000000000000000000"),
Hex("0000000000000002"): Hex("1508290a92d4c00006000000000000000000000000000000000000000000000000"),
},
},
},
}
// Restore a legacy database.
tempDirName, err := ioutil.TempDir("", "clientstore")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDirName)
tempPath := filepath.Join(tempDirName, dbFileName)
db, err := bbolt.Open(tempPath, 0600, nil)
if err != nil {
t.Fatal(err)
}
err = db.Update(func(tx *bbolt.Tx) error {
return RestoreDB(tx, legacyDb)
})
if err != nil {
t.Fatal(err)
}
db.Close()
// Fetch the legacy swap.
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
if err != nil {
t.Fatal(err)
}
swaps, err := store.FetchLoopOutSwaps()
if err != nil {
t.Fatal(err)
}
// Assert that the outgoing channel is read properly.
expectedChannelSet := ChannelSet{5}
if !reflect.DeepEqual(swaps[0].Contract.OutgoingChanSet, expectedChannelSet) {
t.Fatal("invalid outgoing channel")
}
}

@ -112,6 +112,12 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
return nil, err
}
// Check channel set for duplicates.
chanSet, err := loopdb.NewChannelSet(request.OutgoingChanSet)
if err != nil {
return nil, err
}
// Instantiate a struct that contains all required data to start the
// swap.
initiationTime := time.Now()
@ -121,7 +127,6 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
DestAddr: request.DestAddr,
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
SweepConfTarget: request.SweepConfTarget,
UnchargeChannel: request.LoopOutChannel,
PrepayInvoice: swapResp.prepayInvoice,
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
SwapPublicationDeadline: request.SwapPublicationDeadline,
@ -136,6 +141,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
MaxMinerFee: request.MaxMinerFee,
MaxSwapFee: request.MaxSwapFee,
},
OutgoingChanSet: chanSet,
}
swapKit := newSwapKit(
@ -429,9 +435,10 @@ func (s *loopOutSwap) persistState(ctx context.Context) error {
func (s *loopOutSwap) payInvoices(ctx context.Context) {
// Pay the swap invoice.
s.log.Infof("Sending swap payment %v", s.SwapInvoice)
s.swapPaymentChan = s.payInvoice(
ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
s.LoopOutContract.UnchargeChannel,
s.LoopOutContract.OutgoingChanSet,
)
// Pay the prepay invoice.
@ -445,7 +452,7 @@ func (s *loopOutSwap) payInvoices(ctx context.Context) {
// payInvoice pays a single invoice.
func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
maxFee btcutil.Amount,
outgoingChannel *uint64) chan lndclient.PaymentResult {
outgoingChanIds loopdb.ChannelSet) chan lndclient.PaymentResult {
resultChan := make(chan lndclient.PaymentResult)
@ -453,7 +460,7 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
var result lndclient.PaymentResult
status, err := s.payInvoiceAsync(
ctx, invoice, maxFee, outgoingChannel,
ctx, invoice, maxFee, outgoingChanIds,
)
if err != nil {
result.Err = err
@ -474,8 +481,8 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
// payInvoiceAsync is the asynchronously executed part of paying an invoice.
func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
invoice string, maxFee btcutil.Amount, outgoingChannel *uint64) (
*lndclient.PaymentStatus, error) {
invoice string, maxFee btcutil.Amount,
outgoingChanIds loopdb.ChannelSet) (*lndclient.PaymentStatus, error) {
// Extract hash from payment request. Unfortunately the request
// components aren't available directly.
@ -488,7 +495,7 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
req := lndclient.SendPaymentRequest{
MaxFee: maxFee,
Invoice: invoice,
OutgoingChannel: outgoingChannel,
OutgoingChanIds: outgoingChanIds,
Timeout: paymentTimeout,
MaxParts: s.executeConfig.loopOutMaxParts,
}

@ -3,6 +3,7 @@ package loop
import (
"context"
"errors"
"reflect"
"testing"
"time"
@ -15,6 +16,115 @@ import (
"github.com/lightninglabs/loop/test"
)
// TestLoopOutPaymentParameters tests the first part of the loop out process up
// to the point where the off-chain payments are made.
func TestLoopOutPaymentParameters(t *testing.T) {
defer test.Guard(t)()
// Set up test context objects.
lnd := test.NewMockLnd()
ctx := test.NewContext(t, lnd)
server := newServerMock()
store := newStoreMock(t)
expiryChan := make(chan time.Time)
timerFactory := func(_ time.Duration) <-chan time.Time {
return expiryChan
}
height := int32(600)
cfg := &swapConfig{
lnd: &lnd.LndServices,
store: store,
server: server,
}
sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices}
blockEpochChan := make(chan interface{})
statusChan := make(chan SwapInfo)
const maxParts = 5
// Initiate the swap.
req := *testRequest
req.OutgoingChanSet = loopdb.ChannelSet{2, 3}
swap, err := newLoopOutSwap(
context.Background(), cfg, height, &req,
)
if err != nil {
t.Fatal(err)
}
// Execute the swap in its own goroutine.
errChan := make(chan error)
swapCtx, cancel := context.WithCancel(context.Background())
go func() {
err := swap.execute(swapCtx, &executeConfig{
statusChan: statusChan,
sweeper: sweeper,
blockEpochChan: blockEpochChan,
timerFactory: timerFactory,
loopOutMaxParts: maxParts,
}, height)
if err != nil {
log.Error(err)
}
errChan <- err
}()
store.assertLoopOutStored()
state := <-statusChan
if state.State != loopdb.StateInitiated {
t.Fatal("unexpected state")
}
// Intercept the swap and prepay payments. Order is undefined.
payments := []test.RouterPaymentChannelMessage{
<-ctx.Lnd.RouterSendPaymentChannel,
<-ctx.Lnd.RouterSendPaymentChannel,
}
// Find the swap payment.
var swapPayment test.RouterPaymentChannelMessage
for _, p := range payments {
if p.Invoice == swap.SwapInvoice {
swapPayment = p
}
}
// Assert that it is sent as a multi-part payment.
if swapPayment.MaxParts != maxParts {
t.Fatalf("Expected %v parts, but got %v",
maxParts, swapPayment.MaxParts)
}
// Verify the outgoing channel set restriction.
if !reflect.DeepEqual(
[]uint64(req.OutgoingChanSet), swapPayment.OutgoingChanIds,
) {
t.Fatalf("Unexpected outgoing channel set")
}
// Swap is expected to register for confirmation of the htlc. Assert
// this to prevent a blocked channel in the mock.
ctx.AssertRegisterConf()
// Cancel the swap. There is nothing else we need to assert. The payment
// parameters don't play a role in the remainder of the swap process.
cancel()
// Expect the swap to signal that it was cancelled.
err = <-errChan
if err != context.Canceled {
t.Fatal(err)
}
}
// TestLateHtlcPublish tests that the client is not revealing the preimage if
// there are not enough blocks left.
func TestLateHtlcPublish(t *testing.T) {

@ -152,9 +152,15 @@ type LoopOutRequest struct {
//max_miner_fee is typically taken from the response of the GetQuote call.
MaxMinerFee int64 `protobuf:"varint,7,opt,name=max_miner_fee,json=maxMinerFee,proto3" json:"max_miner_fee,omitempty"`
//*
//The channel to loop out, the channel to loop out is selected based on the
//lowest routing fee for the swap payment to the server.
LoopOutChannel uint64 `protobuf:"varint,8,opt,name=loop_out_channel,json=loopOutChannel,proto3" json:"loop_out_channel,omitempty"`
//Deprecated, use outgoing_chan_set. The channel to loop out, the channel
//to loop out is selected based on the lowest routing fee for the swap
//payment to the server.
LoopOutChannel uint64 `protobuf:"varint,8,opt,name=loop_out_channel,json=loopOutChannel,proto3" json:"loop_out_channel,omitempty"` // Deprecated: Do not use.
//*
//A restriction on the channel set that may be used to loop out. The actual
//channel(s) that will be used are selected based on the lowest routing fee
//for the swap payment to the server.
OutgoingChanSet []uint64 `protobuf:"varint,11,rep,packed,name=outgoing_chan_set,json=outgoingChanSet,proto3" json:"outgoing_chan_set,omitempty"`
//*
//The number of blocks from the on-chain HTLC's confirmation height that it
//should be swept within.
@ -245,6 +251,7 @@ func (m *LoopOutRequest) GetMaxMinerFee() int64 {
return 0
}
// Deprecated: Do not use.
func (m *LoopOutRequest) GetLoopOutChannel() uint64 {
if m != nil {
return m.LoopOutChannel
@ -252,6 +259,13 @@ func (m *LoopOutRequest) GetLoopOutChannel() uint64 {
return 0
}
func (m *LoopOutRequest) GetOutgoingChanSet() []uint64 {
if m != nil {
return m.OutgoingChanSet
}
return nil
}
func (m *LoopOutRequest) GetSweepConfTarget() int32 {
if m != nil {
return m.SweepConfTarget
@ -1235,101 +1249,103 @@ func init() {
func init() { proto.RegisterFile("client.proto", fileDescriptor_014de31d7ac8c57c) }
var fileDescriptor_014de31d7ac8c57c = []byte{
// 1504 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xcb, 0x72, 0xdb, 0x46,
0x16, 0x35, 0xf8, 0xe6, 0x25, 0x08, 0x82, 0x2d, 0x5b, 0xa2, 0x38, 0xe3, 0xb2, 0x8c, 0x19, 0xcf,
0xc8, 0xaa, 0x29, 0x71, 0x2c, 0xaf, 0xc6, 0x35, 0x95, 0x2a, 0x99, 0xa2, 0x2d, 0xaa, 0xf4, 0x60,
0x40, 0xca, 0x55, 0xf6, 0x06, 0x69, 0x11, 0x2d, 0x11, 0x15, 0xe2, 0x61, 0x74, 0xd3, 0x92, 0xca,
0xe5, 0x4d, 0x7e, 0x21, 0xbb, 0x2c, 0xf3, 0x07, 0x59, 0x67, 0x97, 0x3f, 0x48, 0xe5, 0x03, 0xb2,
0xc9, 0x27, 0xe4, 0x03, 0x52, 0xfd, 0x20, 0x08, 0x48, 0x94, 0x17, 0xde, 0x11, 0xa7, 0x4f, 0x9f,
0xbe, 0x7d, 0xef, 0xed, 0xd3, 0x4d, 0xd0, 0xc7, 0x53, 0x8f, 0x04, 0x6c, 0x3b, 0x8a, 0x43, 0x16,
0xa2, 0xf2, 0x34, 0x0c, 0xa3, 0x38, 0x1a, 0xb7, 0xff, 0x7e, 0x11, 0x86, 0x17, 0x53, 0xd2, 0xc1,
0x91, 0xd7, 0xc1, 0x41, 0x10, 0x32, 0xcc, 0xbc, 0x30, 0xa0, 0x92, 0x66, 0xfd, 0x90, 0x07, 0xe3,
0x30, 0x0c, 0xa3, 0x93, 0x19, 0xb3, 0xc9, 0xfb, 0x19, 0xa1, 0x0c, 0x99, 0x90, 0xc7, 0x3e, 0x6b,
0x69, 0x1b, 0xda, 0x66, 0xde, 0xe6, 0x3f, 0x11, 0x82, 0x82, 0x4b, 0x28, 0x6b, 0xe5, 0x36, 0xb4,
0xcd, 0xaa, 0x2d, 0x7e, 0xa3, 0x0e, 0xdc, 0xf7, 0xf1, 0x95, 0x43, 0x2f, 0x71, 0xe4, 0xc4, 0xe1,
0x8c, 0x79, 0xc1, 0x85, 0x73, 0x4e, 0x48, 0x2b, 0x2f, 0xa6, 0x35, 0x7d, 0x7c, 0x35, 0xbc, 0xc4,
0x91, 0x2d, 0x47, 0x5e, 0x11, 0x82, 0x9e, 0xc3, 0x2a, 0x9f, 0x10, 0xc5, 0x24, 0xc2, 0xd7, 0x99,
0x29, 0x05, 0x31, 0x65, 0xc5, 0xc7, 0x57, 0x03, 0x31, 0x98, 0x9a, 0xb4, 0x01, 0x7a, 0xb2, 0x0a,
0xa7, 0x16, 0x05, 0x15, 0x94, 0x3a, 0x67, 0xfc, 0x13, 0x8c, 0x94, 0x2c, 0x0f, 0xbc, 0x24, 0x38,
0x7a, 0x22, 0xb7, 0xeb, 0x33, 0x64, 0x41, 0x9d, 0xb3, 0x7c, 0x2f, 0x20, 0xb1, 0x10, 0x2a, 0x0b,
0x52, 0xcd, 0xc7, 0x57, 0x47, 0x1c, 0xe3, 0x4a, 0x9b, 0x60, 0xf2, 0x9c, 0x39, 0xe1, 0x8c, 0x39,
0xe3, 0x09, 0x0e, 0x02, 0x32, 0x6d, 0x55, 0x36, 0xb4, 0xcd, 0x82, 0x6d, 0x4c, 0x65, 0x86, 0xba,
0x12, 0x45, 0x5b, 0xd0, 0xa4, 0x97, 0x84, 0x44, 0xce, 0x38, 0x0c, 0xce, 0x1d, 0x86, 0xe3, 0x0b,
0xc2, 0x5a, 0xd5, 0x0d, 0x6d, 0xb3, 0x68, 0x37, 0xc4, 0x40, 0x37, 0x0c, 0xce, 0x47, 0x02, 0x46,
0x2f, 0x60, 0x5d, 0x44, 0x1f, 0xcd, 0xce, 0xa6, 0xde, 0x58, 0xe4, 0xde, 0x71, 0x09, 0x76, 0xa7,
0x5e, 0x40, 0x5a, 0x20, 0xe4, 0xd7, 0x38, 0x61, 0xb0, 0x18, 0xdf, 0x53, 0xc3, 0xd6, 0xaf, 0x1a,
0xd4, 0x79, 0x71, 0xfa, 0xc1, 0xdd, 0xb5, 0xb9, 0x99, 0xa1, 0xdc, 0xad, 0x0c, 0xdd, 0xda, 0x7b,
0xfe, 0xf6, 0xde, 0xd7, 0xa1, 0x32, 0xc5, 0x94, 0x39, 0x93, 0x30, 0x12, 0xe5, 0xd0, 0xed, 0x32,
0xff, 0xde, 0x0f, 0x23, 0xf4, 0x0f, 0xa8, 0x93, 0x2b, 0x46, 0xe2, 0x00, 0x4f, 0x9d, 0x09, 0x9b,
0x8e, 0x45, 0x0d, 0x2a, 0xb6, 0x3e, 0x07, 0xf7, 0xd9, 0x74, 0xcc, 0x73, 0xc7, 0xc7, 0x32, 0x09,
0x29, 0x89, 0x84, 0x18, 0x1c, 0x5f, 0xe4, 0xc3, 0xfa, 0x45, 0x03, 0x5d, 0x74, 0x06, 0xa1, 0x51,
0x18, 0x50, 0x82, 0x10, 0xe4, 0x3c, 0x57, 0xec, 0xa8, 0xfa, 0x32, 0xd7, 0xd2, 0xec, 0x9c, 0xe7,
0xf2, 0x70, 0x3c, 0xd7, 0x39, 0xbb, 0x66, 0x84, 0x8a, 0x68, 0x75, 0xbb, 0xec, 0xb9, 0x2f, 0xf9,
0x27, 0x7a, 0x02, 0xba, 0x58, 0x09, 0xbb, 0x6e, 0x4c, 0x28, 0x95, 0x3d, 0x29, 0x26, 0xd6, 0x38,
0xbe, 0x2b, 0x61, 0xb4, 0x0d, 0x2b, 0x69, 0x9a, 0x13, 0x44, 0x3b, 0x97, 0x74, 0x22, 0xf6, 0x56,
0xb5, 0x9b, 0x29, 0xe6, 0xb1, 0x18, 0x40, 0xff, 0x01, 0x94, 0xe1, 0x4b, 0x7a, 0x51, 0xd0, 0xcd,
0x14, 0x7d, 0xc0, 0x71, 0xcb, 0x04, 0xe3, 0x28, 0x0c, 0x3c, 0x16, 0xc6, 0xaa, 0x30, 0xd6, 0xef,
0x79, 0x00, 0xbe, 0xad, 0x21, 0xc3, 0x6c, 0x46, 0x97, 0x9e, 0x21, 0xbe, 0xcd, 0xdc, 0x9d, 0xdb,
0xac, 0xdd, 0xdc, 0x66, 0x81, 0x5d, 0x47, 0xb2, 0x56, 0xc6, 0x4e, 0x73, 0x5b, 0x9d, 0xe6, 0x6d,
0xbe, 0xc6, 0xe8, 0x3a, 0x22, 0xb6, 0x18, 0x46, 0x9b, 0x50, 0xa4, 0x0c, 0x33, 0x79, 0x86, 0x8c,
0x1d, 0x94, 0xe1, 0xf1, 0x58, 0x88, 0x2d, 0x09, 0xe8, 0xdf, 0xd0, 0xf0, 0x02, 0x8f, 0x79, 0xb2,
0x03, 0x99, 0xe7, 0xcf, 0x0f, 0x93, 0xb1, 0x80, 0x47, 0x9e, 0x2f, 0x8f, 0x01, 0x6f, 0x85, 0x59,
0xe4, 0x62, 0x46, 0x24, 0x53, 0x1e, 0x29, 0x83, 0xe3, 0xa7, 0x02, 0x16, 0xcc, 0x9b, 0xa5, 0x28,
0x2f, 0x2f, 0xc5, 0xf2, 0xd4, 0xea, 0xcb, 0x53, 0x7b, 0x57, 0xe1, 0xea, 0x77, 0x15, 0xee, 0x11,
0xd4, 0xc6, 0x21, 0x65, 0x0e, 0x25, 0xf1, 0x07, 0x12, 0x8b, 0x03, 0x9b, 0xb7, 0x81, 0x43, 0x43,
0x81, 0xa0, 0xc7, 0xa0, 0x0b, 0x42, 0x18, 0x8c, 0x27, 0xd8, 0x0b, 0xc4, 0x39, 0xcd, 0xdb, 0x62,
0xd2, 0x89, 0x84, 0x78, 0x8b, 0x4b, 0xca, 0xf9, 0xb9, 0xe4, 0x80, 0xb4, 0x10, 0xc1, 0x51, 0x98,
0x85, 0xc0, 0x3c, 0xf4, 0x28, 0xe3, 0x89, 0xa5, 0xf3, 0xaa, 0x7f, 0x05, 0xcd, 0x14, 0xa6, 0x1a,
0xfa, 0x29, 0x14, 0xf9, 0x69, 0xa4, 0x2d, 0x6d, 0x23, 0xbf, 0x59, 0xdb, 0x59, 0xb9, 0x55, 0x93,
0x19, 0xb5, 0x25, 0xc3, 0x7a, 0x0c, 0x0d, 0x0e, 0xf6, 0x83, 0xf3, 0x70, 0x7e, 0xc2, 0x8d, 0xe4,
0x38, 0xe8, 0xbc, 0x47, 0x2c, 0x03, 0xf4, 0x11, 0x89, 0xfd, 0x64, 0xc9, 0x4f, 0x50, 0x57, 0xdf,
0x6a, 0xb9, 0x7f, 0x41, 0xc3, 0xf7, 0x02, 0x69, 0x00, 0xd8, 0x0f, 0x67, 0x01, 0x53, 0x85, 0xad,
0xfb, 0x5e, 0xc0, 0xd5, 0x77, 0x05, 0x28, 0x78, 0x73, 0xa3, 0x50, 0xbc, 0x92, 0xe2, 0x49, 0xaf,
0x90, 0xbc, 0x83, 0x42, 0x45, 0x33, 0x73, 0x07, 0x85, 0x4a, 0xce, 0xcc, 0x1f, 0x14, 0x2a, 0x79,
0xb3, 0x70, 0x50, 0xa8, 0x14, 0xcc, 0xe2, 0x41, 0xa1, 0x52, 0x36, 0x2b, 0xd6, 0x8f, 0x1a, 0xe8,
0x5f, 0xcf, 0x42, 0x46, 0xee, 0x76, 0x24, 0x51, 0x91, 0x85, 0x0d, 0xe4, 0x84, 0x0d, 0xc0, 0x78,
0x61, 0x89, 0xb7, 0x1c, 0x25, 0xbf, 0xc4, 0x51, 0x3e, 0xeb, 0x9b, 0x85, 0xcf, 0xfb, 0xe6, 0x4f,
0x1a, 0xd4, 0x55, 0x90, 0x2a, 0x49, 0xeb, 0x50, 0x49, 0x1c, 0x52, 0x86, 0x5a, 0xa6, 0xca, 0x1e,
0x1f, 0x02, 0xa4, 0x2e, 0x0f, 0x69, 0x9f, 0xd5, 0x28, 0xb9, 0x39, 0xfe, 0x06, 0xd5, 0x9b, 0xce,
0x59, 0xf1, 0xe7, 0xb6, 0x29, 0x2e, 0x02, 0x1e, 0x24, 0xbe, 0xf6, 0x49, 0xc0, 0x1c, 0x71, 0x4b,
0x4a, 0xff, 0x6c, 0x88, 0xe0, 0x24, 0xbe, 0xc7, 0x13, 0xf5, 0x10, 0x60, 0x3c, 0x65, 0x1f, 0x1c,
0x97, 0x4c, 0x19, 0x16, 0x25, 0x2a, 0xda, 0x55, 0x8e, 0xec, 0x71, 0xc0, 0x6a, 0x40, 0x7d, 0x14,
0x7e, 0x4b, 0x82, 0xa4, 0xd0, 0xff, 0x07, 0x63, 0x0e, 0xa8, 0x4d, 0x6c, 0x41, 0x89, 0x09, 0x44,
0x75, 0xd6, 0xe2, 0xb4, 0x1f, 0x52, 0xcc, 0x04, 0xd9, 0x56, 0x0c, 0xeb, 0xe7, 0x1c, 0x54, 0x13,
0x94, 0x67, 0xfc, 0x0c, 0x53, 0xe2, 0xf8, 0x78, 0x8c, 0xe3, 0x30, 0x0c, 0x54, 0x7f, 0xe9, 0x1c,
0x3c, 0x52, 0x18, 0x3f, 0x28, 0xf3, 0x7d, 0x4c, 0x30, 0x9d, 0x88, 0x54, 0xe8, 0x76, 0x4d, 0x61,
0xfb, 0x98, 0x4e, 0xd0, 0x53, 0x30, 0xe7, 0x94, 0x28, 0x26, 0x9e, 0x8f, 0x2f, 0x88, 0xf2, 0xe7,
0x86, 0xc2, 0x07, 0x0a, 0xe6, 0x36, 0x22, 0xbb, 0xcc, 0x89, 0xb0, 0xe7, 0x3a, 0x3e, 0xc5, 0x4c,
0x5d, 0xf4, 0x86, 0xc4, 0x07, 0xd8, 0x73, 0x8f, 0x28, 0x66, 0xe8, 0x19, 0x3c, 0x48, 0xbd, 0x06,
0x52, 0x74, 0xd9, 0xc6, 0x28, 0x4e, 0x9e, 0x03, 0xc9, 0x94, 0xc7, 0xa0, 0x73, 0x5f, 0x72, 0xc6,
0x31, 0xc1, 0x8c, 0xb8, 0xaa, 0x91, 0x6b, 0x1c, 0xeb, 0x4a, 0x08, 0xb5, 0xa0, 0x4c, 0xae, 0x22,
0x2f, 0x26, 0xae, 0xf0, 0xa5, 0x8a, 0x3d, 0xff, 0xe4, 0x93, 0x29, 0x0b, 0x63, 0x7c, 0x41, 0x9c,
0x00, 0xfb, 0x44, 0x58, 0x46, 0xd5, 0xae, 0x29, 0xec, 0x18, 0xfb, 0x64, 0xeb, 0x09, 0x54, 0xe6,
0x46, 0x8b, 0x74, 0xa8, 0x1c, 0x9e, 0x9c, 0x0c, 0x9c, 0x93, 0xd3, 0x91, 0x79, 0x0f, 0xd5, 0xa0,
0x2c, 0xbe, 0xfa, 0xc7, 0xa6, 0xb6, 0x45, 0xa1, 0x9a, 0xf8, 0x2c, 0xaa, 0x43, 0xb5, 0x7f, 0xdc,
0x1f, 0xf5, 0x77, 0x47, 0xbd, 0x3d, 0xf3, 0x1e, 0x7a, 0x00, 0xcd, 0x81, 0xdd, 0xeb, 0x1f, 0xed,
0xbe, 0xee, 0x39, 0x76, 0xef, 0x4d, 0x6f, 0xf7, 0xb0, 0xb7, 0x67, 0x6a, 0x08, 0x81, 0xb1, 0x3f,
0x3a, 0xec, 0x3a, 0x83, 0xd3, 0x97, 0x87, 0xfd, 0xe1, 0x7e, 0x6f, 0xcf, 0xcc, 0x71, 0xcd, 0xe1,
0x69, 0xb7, 0xdb, 0x1b, 0x0e, 0xcd, 0x3c, 0x02, 0x28, 0xbd, 0xda, 0xed, 0x73, 0x72, 0x01, 0xad,
0x40, 0xa3, 0x7f, 0xfc, 0xe6, 0xa4, 0xdf, 0xed, 0x39, 0xc3, 0xde, 0x68, 0xc4, 0xc1, 0xe2, 0xce,
0x9f, 0x25, 0x79, 0xd3, 0x74, 0xc5, 0x6b, 0x0f, 0xd9, 0x50, 0x56, 0xef, 0x37, 0xb4, 0xb6, 0xe8,
0x87, 0xcc, 0x8b, 0xae, 0xfd, 0x20, 0x63, 0x41, 0xf3, 0x7e, 0xb2, 0xd6, 0xbe, 0xfb, 0xed, 0x8f,
0xef, 0x73, 0x4d, 0x4b, 0xef, 0x7c, 0x78, 0xd6, 0xe1, 0x8c, 0x4e, 0x38, 0x63, 0x2f, 0xb4, 0x2d,
0x74, 0x02, 0x25, 0xf9, 0xec, 0x40, 0xab, 0x19, 0xc9, 0xe4, 0x1d, 0x72, 0x97, 0xe2, 0xaa, 0x50,
0x34, 0xad, 0x5a, 0xa2, 0xe8, 0x05, 0x5c, 0xf0, 0x7f, 0x50, 0x56, 0xf7, 0x65, 0x2a, 0xc8, 0xec,
0x0d, 0xda, 0x5e, 0xe6, 0x93, 0xff, 0xd5, 0xd0, 0x3b, 0xa8, 0x26, 0x16, 0x8b, 0xd6, 0x17, 0xe1,
0xdc, 0xb0, 0xe2, 0x76, 0x7b, 0xd9, 0x50, 0x36, 0x2c, 0x64, 0x24, 0x61, 0x09, 0xfb, 0x45, 0xa7,
0xb2, 0xcc, 0xdc, 0x7e, 0x51, 0x2b, 0xb3, 0x7c, 0xca, 0x91, 0x97, 0x06, 0x66, 0xb5, 0x85, 0xe4,
0x7d, 0x84, 0x32, 0x92, 0x9d, 0x8f, 0x9e, 0xfb, 0x09, 0xbd, 0x05, 0x5d, 0x15, 0x40, 0x38, 0x35,
0x5a, 0x24, 0x2b, 0xed, 0xe4, 0xed, 0xd5, 0x9b, 0xb0, 0x8a, 0xf6, 0xb6, 0x74, 0x38, 0x63, 0x1d,
0x26, 0xa4, 0x9c, 0x44, 0x5a, 0xf8, 0x5b, 0x4a, 0x3a, 0x6d, 0xca, 0x29, 0xe9, 0x8c, 0x0d, 0x5a,
0x1b, 0x42, 0xba, 0x8d, 0x5a, 0x19, 0xe9, 0xf7, 0x9c, 0xd3, 0xf9, 0x88, 0x7d, 0xf6, 0x09, 0xbd,
0x03, 0xe3, 0x35, 0x61, 0xb2, 0xd8, 0x5f, 0x14, 0xfd, 0xba, 0x58, 0x62, 0x05, 0x35, 0x53, 0x2d,
0xa0, 0x82, 0xff, 0x26, 0xa5, 0xfd, 0x45, 0xe1, 0x3f, 0x12, 0xda, 0xeb, 0x68, 0x2d, 0xad, 0x9d,
0x8e, 0xfe, 0x2d, 0xd4, 0xf9, 0x0a, 0x73, 0xdf, 0xa3, 0xa9, 0xfe, 0xcd, 0x98, 0x6b, 0x7b, 0xed,
0x16, 0x9e, 0x3d, 0x13, 0xa8, 0x21, 0x96, 0xa0, 0x98, 0x75, 0xa4, 0xa1, 0x9e, 0x95, 0xc4, 0xff,
0xa5, 0xe7, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x46, 0x6c, 0x13, 0xa0, 0x66, 0x0d, 0x00, 0x00,
// 1533 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0x4d, 0x72, 0xdb, 0x38,
0x16, 0x0e, 0xf5, 0xaf, 0x27, 0x8a, 0xa2, 0xe0, 0xc4, 0x96, 0x35, 0x93, 0x8a, 0xc2, 0x99, 0xcc,
0x28, 0xae, 0x94, 0x35, 0x71, 0x56, 0x93, 0x9a, 0x9a, 0x2a, 0x47, 0x56, 0x62, 0xb9, 0xfc, 0xa3,
0xa1, 0xe4, 0x54, 0x25, 0x1b, 0x0e, 0x2c, 0xc2, 0x16, 0xab, 0x45, 0x82, 0x21, 0xa0, 0xd8, 0xae,
0x54, 0x36, 0x7d, 0x85, 0xbe, 0x41, 0xdf, 0xa0, 0xd7, 0xbd, 0xeb, 0x65, 0xef, 0xba, 0xfa, 0x00,
0xbd, 0xe9, 0x23, 0xf4, 0x01, 0xba, 0x00, 0x50, 0x14, 0x65, 0xcb, 0x59, 0x64, 0x27, 0x7e, 0xf8,
0xf0, 0xe1, 0xe1, 0xbd, 0x87, 0x0f, 0x10, 0xe8, 0xe3, 0xa9, 0x47, 0x02, 0xbe, 0x1d, 0x46, 0x94,
0x53, 0x54, 0x9c, 0x52, 0x1a, 0x46, 0xe1, 0xb8, 0xf9, 0xd7, 0x0b, 0x4a, 0x2f, 0xa6, 0xa4, 0x83,
0x43, 0xaf, 0x83, 0x83, 0x80, 0x72, 0xcc, 0x3d, 0x1a, 0x30, 0x45, 0xb3, 0x7e, 0xce, 0x82, 0x71,
0x48, 0x69, 0x78, 0x32, 0xe3, 0x36, 0xf9, 0x30, 0x23, 0x8c, 0x23, 0x13, 0xb2, 0xd8, 0xe7, 0x0d,
0xad, 0xa5, 0xb5, 0xb3, 0xb6, 0xf8, 0x89, 0x10, 0xe4, 0x5c, 0xc2, 0x78, 0x23, 0xd3, 0xd2, 0xda,
0x65, 0x5b, 0xfe, 0x46, 0x1d, 0xb8, 0xef, 0xe3, 0x2b, 0x87, 0x5d, 0xe2, 0xd0, 0x89, 0xe8, 0x8c,
0x7b, 0xc1, 0x85, 0x73, 0x4e, 0x48, 0x23, 0x2b, 0xa7, 0xd5, 0x7d, 0x7c, 0x35, 0xbc, 0xc4, 0xa1,
0xad, 0x46, 0x5e, 0x13, 0x82, 0x5e, 0xc0, 0xba, 0x98, 0x10, 0x46, 0x24, 0xc4, 0xd7, 0x4b, 0x53,
0x72, 0x72, 0xca, 0x9a, 0x8f, 0xaf, 0x06, 0x72, 0x30, 0x35, 0xa9, 0x05, 0x7a, 0xb2, 0x8a, 0xa0,
0xe6, 0x25, 0x15, 0x62, 0x75, 0xc1, 0xf8, 0x3b, 0x18, 0x29, 0x59, 0x11, 0x78, 0x41, 0x72, 0xf4,
0x44, 0x6e, 0xd7, 0xe7, 0xc8, 0x82, 0xaa, 0x60, 0xf9, 0x5e, 0x40, 0x22, 0x29, 0x54, 0x94, 0xa4,
0x8a, 0x8f, 0xaf, 0x8e, 0x04, 0x26, 0x94, 0x9e, 0x81, 0x29, 0x72, 0xe6, 0xd0, 0x19, 0x77, 0xc6,
0x13, 0x1c, 0x04, 0x64, 0xda, 0x28, 0xb5, 0xb4, 0x76, 0xee, 0x55, 0xa6, 0xa1, 0xd9, 0xc6, 0x54,
0x65, 0xa9, 0xab, 0x46, 0xd0, 0x16, 0xd4, 0xe9, 0x8c, 0x5f, 0x50, 0xb1, 0x09, 0xc1, 0x76, 0x18,
0xe1, 0x8d, 0x4a, 0x2b, 0xdb, 0xce, 0xd9, 0xb5, 0xf9, 0x80, 0xe0, 0x0e, 0x09, 0x17, 0x5c, 0x76,
0x49, 0x48, 0xe8, 0x8c, 0x69, 0x70, 0xee, 0x70, 0x1c, 0x5d, 0x10, 0xde, 0x28, 0xb7, 0xb4, 0x76,
0xde, 0xae, 0xc9, 0x81, 0x2e, 0x0d, 0xce, 0x47, 0x12, 0x46, 0x2f, 0x61, 0x53, 0xee, 0x36, 0x9c,
0x9d, 0x4d, 0xbd, 0xb1, 0xac, 0x95, 0xe3, 0x12, 0xec, 0x4e, 0xbd, 0x80, 0x34, 0x40, 0x84, 0x63,
0x6f, 0x08, 0xc2, 0x60, 0x31, 0xbe, 0x17, 0x0f, 0x5b, 0xbf, 0x68, 0x50, 0x15, 0xc5, 0xec, 0x07,
0x77, 0xd7, 0xf2, 0x66, 0x46, 0x33, 0xb7, 0x32, 0x7a, 0x2b, 0x57, 0xd9, 0xdb, 0xb9, 0xda, 0x84,
0xd2, 0x14, 0x33, 0xee, 0x4c, 0x68, 0x28, 0xcb, 0xa7, 0xdb, 0x45, 0xf1, 0xbd, 0x4f, 0x43, 0xf4,
0x37, 0xa8, 0x92, 0x2b, 0x4e, 0xa2, 0x00, 0x4f, 0x9d, 0x09, 0x9f, 0x8e, 0x65, 0xcd, 0x4a, 0xb6,
0x3e, 0x07, 0xf7, 0xf9, 0x74, 0x8c, 0xda, 0x60, 0x8a, 0xb1, 0xa5, 0x84, 0x14, 0x64, 0x42, 0x0c,
0x81, 0x2f, 0xf2, 0x61, 0xfd, 0xa4, 0x81, 0x2e, 0x3b, 0x89, 0xb0, 0x90, 0x06, 0x8c, 0x20, 0x04,
0x19, 0xcf, 0x95, 0x3b, 0x2a, 0xcb, 0xc2, 0x64, 0x3c, 0x57, 0x84, 0xe3, 0xb9, 0xce, 0xd9, 0x35,
0x27, 0x4c, 0x46, 0xab, 0xdb, 0x45, 0xcf, 0x7d, 0x25, 0x3e, 0xd1, 0x13, 0xd0, 0xe5, 0x4a, 0xd8,
0x75, 0x23, 0xc2, 0x98, 0xea, 0x61, 0x39, 0xb1, 0x22, 0xf0, 0x5d, 0x05, 0xa3, 0x6d, 0x58, 0x4b,
0xd3, 0x9c, 0x20, 0xdc, 0xb9, 0x64, 0x13, 0xb9, 0xb7, 0xb2, 0x5d, 0x4f, 0x31, 0x8f, 0xe5, 0x00,
0x7a, 0x06, 0x68, 0x89, 0xaf, 0xe8, 0x79, 0x49, 0x37, 0x53, 0xf4, 0x81, 0xc0, 0x2d, 0x13, 0x8c,
0x23, 0x1a, 0x78, 0x9c, 0x46, 0x71, 0x61, 0xac, 0xdf, 0xb2, 0x00, 0x62, 0x5b, 0x43, 0x8e, 0xf9,
0x8c, 0xad, 0x3c, 0x73, 0x62, 0x9b, 0x99, 0x3b, 0xb7, 0x59, 0xb9, 0xb9, 0xcd, 0x1c, 0xbf, 0x0e,
0x55, 0xad, 0x8c, 0x9d, 0xfa, 0x76, 0x7c, 0xfa, 0xb7, 0xc5, 0x1a, 0xa3, 0xeb, 0x90, 0xd8, 0x72,
0x18, 0xb5, 0x21, 0xcf, 0x38, 0xe6, 0xea, 0xcc, 0x19, 0x3b, 0x68, 0x89, 0x27, 0x62, 0x21, 0xb6,
0x22, 0xa0, 0x7f, 0x42, 0xcd, 0x0b, 0x3c, 0xee, 0xa9, 0x0e, 0xe4, 0x9e, 0x3f, 0x3f, 0x7c, 0xc6,
0x02, 0x1e, 0x79, 0xbe, 0x90, 0x34, 0x65, 0x2b, 0xcc, 0x42, 0x17, 0x73, 0xa2, 0x98, 0xea, 0x08,
0x1a, 0x02, 0x3f, 0x95, 0xb0, 0x64, 0xde, 0x2c, 0x45, 0x71, 0x75, 0x29, 0x56, 0xa7, 0x56, 0x5f,
0x9d, 0xda, 0xbb, 0x0a, 0x57, 0xbd, 0xab, 0x70, 0x8f, 0xa0, 0x32, 0xa6, 0x8c, 0x3b, 0x8c, 0x44,
0x1f, 0x49, 0x24, 0x0f, 0x78, 0xd6, 0x06, 0x01, 0x0d, 0x25, 0x82, 0x1e, 0x83, 0x2e, 0x09, 0x34,
0x18, 0x4f, 0xb0, 0x17, 0xc8, 0x73, 0x9a, 0xb5, 0xe5, 0xa4, 0x13, 0x05, 0x89, 0x16, 0x57, 0x94,
0xf3, 0x73, 0xc5, 0x01, 0x65, 0x39, 0x92, 0x13, 0x63, 0x16, 0x02, 0xf3, 0xd0, 0x63, 0x5c, 0x24,
0x96, 0xcd, 0xab, 0xfe, 0x5f, 0xa8, 0xa7, 0xb0, 0xb8, 0xa1, 0x9f, 0x42, 0x5e, 0x9c, 0x46, 0xd6,
0xd0, 0x5a, 0xd9, 0x76, 0x65, 0x67, 0xed, 0x56, 0x4d, 0x66, 0xcc, 0x56, 0x0c, 0xeb, 0x31, 0xd4,
0x04, 0xd8, 0x0f, 0xce, 0xe9, 0xfc, 0x84, 0x1b, 0xc9, 0x71, 0xd0, 0x45, 0x8f, 0x58, 0x06, 0xe8,
0x23, 0x12, 0xf9, 0xc9, 0x92, 0x9f, 0xa1, 0x1a, 0x7f, 0xc7, 0xcb, 0xfd, 0x03, 0x6a, 0xbe, 0x17,
0x28, 0x03, 0xc0, 0x3e, 0x9d, 0x05, 0x3c, 0x2e, 0x6c, 0xd5, 0xf7, 0x02, 0xa1, 0xbe, 0x2b, 0x41,
0xc9, 0x9b, 0x1b, 0x45, 0xcc, 0x2b, 0xc4, 0x3c, 0xe5, 0x15, 0x8a, 0x77, 0x90, 0x2b, 0x69, 0x66,
0xe6, 0x20, 0x57, 0xca, 0x98, 0xd9, 0x83, 0x5c, 0x29, 0x6b, 0xe6, 0x0e, 0x72, 0xa5, 0x9c, 0x99,
0x3f, 0xc8, 0x95, 0x8a, 0x66, 0xc9, 0xfa, 0x5e, 0x03, 0xfd, 0x7f, 0x33, 0xca, 0xc9, 0xdd, 0x8e,
0x24, 0x2b, 0xb2, 0xb0, 0x81, 0x8c, 0xb4, 0x01, 0x18, 0x2f, 0x2c, 0xf1, 0x96, 0xa3, 0x64, 0x57,
0x38, 0xca, 0x17, 0x7d, 0x33, 0xf7, 0x65, 0xdf, 0xfc, 0x41, 0x83, 0x6a, 0x1c, 0x64, 0x9c, 0xa4,
0x4d, 0x28, 0x25, 0x0e, 0xa9, 0x42, 0x2d, 0xb2, 0xd8, 0x1e, 0x1f, 0x02, 0xa4, 0x2e, 0x1b, 0x65,
0x9f, 0xe5, 0x30, 0xb9, 0x69, 0xfe, 0x02, 0xe5, 0x9b, 0xce, 0x59, 0xf2, 0xe7, 0xb6, 0x29, 0x2f,
0x02, 0x11, 0x24, 0xbe, 0xf6, 0x49, 0xc0, 0x1d, 0x79, 0xab, 0x2a, 0xff, 0xac, 0xc9, 0xe0, 0x14,
0xbe, 0x27, 0x12, 0xf5, 0x10, 0x60, 0x3c, 0xe5, 0x1f, 0x1d, 0x97, 0x4c, 0x39, 0x96, 0x25, 0xca,
0xdb, 0x65, 0x81, 0xec, 0x09, 0xc0, 0xaa, 0x41, 0x75, 0x44, 0xbf, 0x21, 0x41, 0x52, 0xe8, 0xff,
0x80, 0x31, 0x07, 0xe2, 0x4d, 0x6c, 0x41, 0x81, 0x4b, 0x24, 0xee, 0xac, 0xc5, 0x69, 0x3f, 0x64,
0x98, 0x4b, 0xb2, 0x1d, 0x33, 0xac, 0x1f, 0x33, 0x50, 0x4e, 0x50, 0x91, 0xf1, 0x33, 0xcc, 0x88,
0xe3, 0xe3, 0x31, 0x8e, 0x28, 0x0d, 0xe2, 0xfe, 0xd2, 0x05, 0x78, 0x14, 0x63, 0xe2, 0xa0, 0xcc,
0xf7, 0x31, 0xc1, 0x6c, 0x22, 0x53, 0xa1, 0xdb, 0x95, 0x18, 0xdb, 0xc7, 0x6c, 0x82, 0x9e, 0x82,
0x39, 0xa7, 0x84, 0x11, 0xf1, 0x7c, 0x7c, 0x41, 0x62, 0x7f, 0xae, 0xc5, 0xf8, 0x20, 0x86, 0x85,
0x8d, 0xa8, 0x2e, 0x73, 0x42, 0xec, 0xb9, 0x8e, 0xcf, 0x30, 0x8f, 0x1f, 0x06, 0x86, 0xc2, 0x07,
0xd8, 0x73, 0x8f, 0x18, 0xe6, 0xe8, 0x39, 0x3c, 0x48, 0xbd, 0x1e, 0x52, 0x74, 0xd5, 0xc6, 0x28,
0x4a, 0x9e, 0x0f, 0xc9, 0x94, 0xc7, 0xa0, 0x0b, 0x5f, 0x72, 0xc6, 0x11, 0xc1, 0x9c, 0xb8, 0x71,
0x23, 0x57, 0x04, 0xd6, 0x55, 0x10, 0x6a, 0x40, 0x91, 0x5c, 0x85, 0x5e, 0x44, 0x5c, 0xe9, 0x4b,
0x25, 0x7b, 0xfe, 0x29, 0x26, 0x33, 0x4e, 0x23, 0x7c, 0x41, 0x9c, 0x00, 0xfb, 0x44, 0x5a, 0x46,
0xd9, 0xae, 0xc4, 0xd8, 0x31, 0xf6, 0xc9, 0xd6, 0x13, 0x28, 0xcd, 0x8d, 0x16, 0xe9, 0x50, 0x3a,
0x3c, 0x39, 0x19, 0x38, 0x27, 0xa7, 0x23, 0xf3, 0x1e, 0xaa, 0x40, 0x51, 0x7e, 0xf5, 0x8f, 0x4d,
0x6d, 0x8b, 0x41, 0x39, 0xf1, 0x59, 0x54, 0x85, 0x72, 0xff, 0xb8, 0x3f, 0xea, 0xef, 0x8e, 0x7a,
0x7b, 0xe6, 0x3d, 0xf4, 0x00, 0xea, 0x03, 0xbb, 0xd7, 0x3f, 0xda, 0x7d, 0xd3, 0x73, 0xec, 0xde,
0xdb, 0xde, 0xee, 0x61, 0x6f, 0xcf, 0xd4, 0x10, 0x02, 0x63, 0x7f, 0x74, 0xd8, 0x75, 0x06, 0xa7,
0xaf, 0x0e, 0xfb, 0xc3, 0xfd, 0xde, 0x9e, 0x99, 0x11, 0x9a, 0xc3, 0xd3, 0x6e, 0xb7, 0x37, 0x1c,
0x9a, 0x59, 0x04, 0x50, 0x78, 0xbd, 0xdb, 0x17, 0xe4, 0x1c, 0x5a, 0x83, 0x5a, 0xff, 0xf8, 0xed,
0x49, 0xbf, 0xdb, 0x73, 0x86, 0xbd, 0xd1, 0x48, 0x80, 0xf9, 0x9d, 0x3f, 0x0a, 0xea, 0xa6, 0xe9,
0xca, 0xd7, 0x21, 0xb2, 0xa1, 0x18, 0xbf, 0xf7, 0xd0, 0xc6, 0xa2, 0x1f, 0x96, 0x5e, 0x80, 0xcd,
0x07, 0x4b, 0x16, 0x34, 0xef, 0x27, 0x6b, 0xe3, 0xdb, 0x5f, 0x7f, 0xff, 0x2e, 0x53, 0xb7, 0xf4,
0xce, 0xc7, 0xe7, 0x1d, 0xc1, 0xe8, 0xd0, 0x19, 0x7f, 0xa9, 0x6d, 0xa1, 0x13, 0x28, 0xa8, 0x67,
0x07, 0x5a, 0x5f, 0x92, 0x4c, 0xde, 0x21, 0x77, 0x29, 0xae, 0x4b, 0x45, 0xd3, 0xaa, 0x24, 0x8a,
0x5e, 0x20, 0x04, 0xff, 0x0d, 0xc5, 0xf8, 0xbe, 0x4c, 0x05, 0xb9, 0x7c, 0x83, 0x36, 0x57, 0xf9,
0xe4, 0xbf, 0x34, 0xf4, 0x1e, 0xca, 0x89, 0xc5, 0xa2, 0xcd, 0x45, 0x38, 0x37, 0xac, 0xb8, 0xd9,
0x5c, 0x35, 0xb4, 0x1c, 0x16, 0x32, 0x92, 0xb0, 0xa4, 0xfd, 0xa2, 0x53, 0x55, 0x66, 0x61, 0xbf,
0xa8, 0xb1, 0xb4, 0x7c, 0xca, 0x91, 0x57, 0x06, 0x66, 0x35, 0xa5, 0xe4, 0x7d, 0x84, 0x96, 0x24,
0x3b, 0x9f, 0x3c, 0xf7, 0x33, 0x7a, 0x07, 0x7a, 0x5c, 0x00, 0xe9, 0xd4, 0x68, 0x91, 0xac, 0xb4,
0x93, 0x37, 0xd7, 0x6f, 0xc2, 0x71, 0xb4, 0xb7, 0xa5, 0xe9, 0x8c, 0x77, 0xb8, 0x94, 0x72, 0x12,
0x69, 0xe9, 0x6f, 0x29, 0xe9, 0xb4, 0x29, 0xa7, 0xa4, 0x97, 0x6c, 0xd0, 0x6a, 0x49, 0xe9, 0x26,
0x6a, 0x2c, 0x49, 0x7f, 0x10, 0x9c, 0xce, 0x27, 0xec, 0xf3, 0xcf, 0xe8, 0x3d, 0x18, 0x6f, 0x08,
0x57, 0xc5, 0xfe, 0xaa, 0xe8, 0x37, 0xe5, 0x12, 0x6b, 0xa8, 0x9e, 0x6a, 0x81, 0x38, 0xf8, 0xff,
0xa7, 0xb4, 0xbf, 0x2a, 0xfc, 0x47, 0x52, 0x7b, 0x13, 0x6d, 0xa4, 0xb5, 0xd3, 0xd1, 0xbf, 0x83,
0xaa, 0x58, 0x61, 0xee, 0x7b, 0x2c, 0xd5, 0xbf, 0x4b, 0xe6, 0xda, 0xdc, 0xb8, 0x85, 0x2f, 0x9f,
0x09, 0x54, 0x93, 0x4b, 0x30, 0xcc, 0x3b, 0xca, 0x50, 0xcf, 0x0a, 0xf2, 0xff, 0xd5, 0x8b, 0x3f,
0x03, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x9f, 0xce, 0xa4, 0x96, 0x0d, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.

@ -163,10 +163,18 @@ message LoopOutRequest {
int64 max_miner_fee = 7;
/**
The channel to loop out, the channel to loop out is selected based on the
lowest routing fee for the swap payment to the server.
Deprecated, use outgoing_chan_set. The channel to loop out, the channel
to loop out is selected based on the lowest routing fee for the swap
payment to the server.
*/
uint64 loop_out_channel = 8;
uint64 loop_out_channel = 8 [deprecated = true];
/**
A restriction on the channel set that may be used to loop out. The actual
channel(s) that will be used are selected based on the lowest routing fee
for the swap payment to the server.
*/
repeated uint64 outgoing_chan_set = 11;
/**
The number of blocks from the on-chain HTLC's confirmation height that it

@ -355,7 +355,15 @@
"loop_out_channel": {
"type": "string",
"format": "uint64",
"description": "*\nThe channel to loop out, the channel to loop out is selected based on the\nlowest routing fee for the swap payment to the server."
"description": "*\nDeprecated, use outgoing_chan_set. The channel to loop out, the channel\nto loop out is selected based on the lowest routing fee for the swap\npayment to the server."
},
"outgoing_chan_set": {
"type": "array",
"items": {
"type": "string",
"format": "uint64"
},
"description": "*\nA restriction on the channel set that may be used to loop out. The actual\nchannel(s) that will be used are selected based on the lowest routing fee\nfor the swap payment to the server."
},
"sweep_conf_target": {
"type": "integer",

Loading…
Cancel
Save