diff --git a/client.go b/client.go index 9b8161f..fcf0d48 100644 --- a/client.go +++ b/client.go @@ -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 { diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index 5aa589a..787ce29 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -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()), }) diff --git a/go.mod b/go.mod index 6aad69a..ea5a915 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index f437f92..b648ec4 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/interface.go b/interface.go index f702a45..287832d 100644 --- a/interface.go +++ b/interface.go @@ -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. diff --git a/lndclient/lnd_services.go b/lndclient/lnd_services.go index bae1cf5..ddb6abd 100644 --- a/lndclient/lnd_services.go +++ b/lndclient/lnd_services.go @@ -31,7 +31,7 @@ var ( minimalCompatibleVersion = &verrpc.Version{ AppMajor: 0, AppMinor: 10, - AppPatch: 0, + AppPatch: 1, BuildTags: []string{ "signrpc", "walletrpc", "chainrpc", "invoicesrpc", }, diff --git a/lndclient/router_client.go b/lndclient/router_client.go index f72c3d1..80eaeac 100644 --- a/lndclient/router_client.go +++ b/lndclient/router_client.go @@ -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[:] } diff --git a/loopd/start.go b/loopd/start.go index 9b49b54..030f608 100644 --- a/loopd/start.go +++ b/loopd/start.go @@ -24,7 +24,7 @@ var ( LoopMinRequiredLndVersion = &verrpc.Version{ AppMajor: 0, AppMinor: 10, - AppPatch: 0, + AppPatch: 1, BuildTags: []string{ "signrpc", "walletrpc", "chainrpc", "invoicesrpc", }, diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 4f9d9a1..b2ada65 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -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) diff --git a/loopd/view.go b/loopd/view.go index 433158c..aa2d250 100644 --- a/loopd/view.go +++ b/loopd/view.go @@ -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, diff --git a/loopdb/loopout.go b/loopdb/loopout.go index 3df0654..d24a57e 100644 --- a/loopdb/loopout.go +++ b/loopdb/loopout.go @@ -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 } diff --git a/loopdb/raw_db_test.go b/loopdb/raw_db_test.go new file mode 100644 index 0000000..358cedb --- /dev/null +++ b/loopdb/raw_db_test.go @@ -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) +} diff --git a/loopdb/store.go b/loopdb/store.go index e075495..505809d 100644 --- a/loopdb/store.go +++ b/loopdb/store.go @@ -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 diff --git a/loopdb/store_test.go b/loopdb/store_test.go index 26da3a8..799a1d4 100644 --- a/loopdb/store_test.go +++ b/loopdb/store_test.go @@ -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") + } +} diff --git a/loopout.go b/loopout.go index 7204dc9..b233c0b 100644 --- a/loopout.go +++ b/loopout.go @@ -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, } diff --git a/loopout_test.go b/loopout_test.go index af56c52..d5dc9de 100644 --- a/loopout_test.go +++ b/loopout_test.go @@ -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) { diff --git a/looprpc/client.pb.go b/looprpc/client.pb.go index e40bae0..f1a0b68 100644 --- a/looprpc/client.pb.go +++ b/looprpc/client.pb.go @@ -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. diff --git a/looprpc/client.proto b/looprpc/client.proto index 8d3d38b..76c23d5 100644 --- a/looprpc/client.proto +++ b/looprpc/client.proto @@ -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 diff --git a/looprpc/client.swagger.json b/looprpc/client.swagger.json index 75875ff..4e3958a 100644 --- a/looprpc/client.swagger.json +++ b/looprpc/client.swagger.json @@ -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",