diff --git a/client.go b/client.go index dbcbec7..1b906cf 100644 --- a/client.go +++ b/client.go @@ -108,6 +108,14 @@ type ClientConfig struct { // for a loop out swap. When greater than one, a multi-part payment may // be attempted. LoopOutMaxParts uint32 + + // TotalPaymentTimeout is the total amount of time until we time out + // off-chain payments (used in loop out). + TotalPaymentTimeout time.Duration + + // MaxPaymentRetries is the maximum times we retry an off-chain payment + // (used in loop out). + MaxPaymentRetries int } // NewClient returns a new instance to initiate swaps with. @@ -142,12 +150,14 @@ func NewClient(dbDir string, cfg *ClientConfig) (*Client, func(), error) { } executor := newExecutor(&executorConfig{ - lnd: cfg.Lnd, - store: store, - sweeper: sweeper, - createExpiryTimer: config.CreateExpiryTimer, - loopOutMaxParts: cfg.LoopOutMaxParts, - cancelSwap: swapServerClient.CancelLoopOutSwap, + lnd: cfg.Lnd, + store: store, + sweeper: sweeper, + createExpiryTimer: config.CreateExpiryTimer, + loopOutMaxParts: cfg.LoopOutMaxParts, + totalPaymentTimeout: cfg.TotalPaymentTimeout, + maxPaymentRetries: cfg.MaxPaymentRetries, + cancelSwap: swapServerClient.CancelLoopOutSwap, }) client := &Client{ diff --git a/executor.go b/executor.go index f759e2c..2ac94e7 100644 --- a/executor.go +++ b/executor.go @@ -26,6 +26,10 @@ type executorConfig struct { loopOutMaxParts uint32 + totalPaymentTimeout time.Duration + + maxPaymentRetries int + cancelSwap func(ctx context.Context, details *outCancelDetails) error } @@ -141,12 +145,14 @@ func (s *executor) run(mainCtx context.Context, defer s.wg.Done() err := newSwap.execute(mainCtx, &executeConfig{ - statusChan: statusChan, - sweeper: s.sweeper, - blockEpochChan: queue.ChanOut(), - timerFactory: s.executorConfig.createExpiryTimer, - loopOutMaxParts: s.executorConfig.loopOutMaxParts, - cancelSwap: s.executorConfig.cancelSwap, + statusChan: statusChan, + sweeper: s.sweeper, + blockEpochChan: queue.ChanOut(), + timerFactory: s.executorConfig.createExpiryTimer, + loopOutMaxParts: s.executorConfig.loopOutMaxParts, + totalPaymentTimout: s.executorConfig.totalPaymentTimeout, + maxPaymentRetries: s.executorConfig.maxPaymentRetries, + cancelSwap: s.executorConfig.cancelSwap, }, height) if err != nil && err != context.Canceled { log.Errorf("Execute error: %v", err) diff --git a/loopd/config.go b/loopd/config.go index a528e30..619f21e 100644 --- a/loopd/config.go +++ b/loopd/config.go @@ -33,9 +33,11 @@ var ( LoopDirBase, DefaultNetwork, defaultConfigFilename, ) - defaultMaxLogFiles = 3 - defaultMaxLogFileSize = 10 - defaultLoopOutMaxParts = uint32(5) + defaultMaxLogFiles = 3 + defaultMaxLogFileSize = 10 + defaultLoopOutMaxParts = uint32(5) + defaultTotalPaymentTimeout = time.Minute * 60 + defaultMaxPaymentRetries = 3 // DefaultTLSCertFilename is the default file name for the autogenerated // TLS certificate. @@ -144,6 +146,9 @@ type Config struct { LoopOutMaxParts uint32 `long:"loopoutmaxparts" description:"The maximum number of payment parts that may be used for a loop out swap."` + TotalPaymentTimeout time.Duration `long:"totalpaymenttimeout" description:"The timeout to use for off-chain payments."` + MaxPaymentRetries int `long:"maxpaymentretries" description:"The maximum number of times an off-chain payment may be retried."` + Lnd *lndConfig `group:"lnd" namespace:"lnd"` Server *loopServerConfig `group:"server" namespace:"server"` @@ -165,19 +170,21 @@ func DefaultConfig() Config { Server: &loopServerConfig{ NoTLS: false, }, - LoopDir: LoopDirBase, - ConfigFile: defaultConfigFile, - DataDir: LoopDirBase, - LogDir: defaultLogDir, - MaxLogFiles: defaultMaxLogFiles, - MaxLogFileSize: defaultMaxLogFileSize, - DebugLevel: defaultLogLevel, - TLSCertPath: DefaultTLSCertPath, - TLSKeyPath: DefaultTLSKeyPath, - MacaroonPath: DefaultMacaroonPath, - MaxLSATCost: lsat.DefaultMaxCostSats, - MaxLSATFee: lsat.DefaultMaxRoutingFeeSats, - LoopOutMaxParts: defaultLoopOutMaxParts, + LoopDir: LoopDirBase, + ConfigFile: defaultConfigFile, + DataDir: LoopDirBase, + LogDir: defaultLogDir, + MaxLogFiles: defaultMaxLogFiles, + MaxLogFileSize: defaultMaxLogFileSize, + DebugLevel: defaultLogLevel, + TLSCertPath: DefaultTLSCertPath, + TLSKeyPath: DefaultTLSKeyPath, + MacaroonPath: DefaultMacaroonPath, + MaxLSATCost: lsat.DefaultMaxCostSats, + MaxLSATFee: lsat.DefaultMaxRoutingFeeSats, + LoopOutMaxParts: defaultLoopOutMaxParts, + TotalPaymentTimeout: defaultTotalPaymentTimeout, + MaxPaymentRetries: defaultMaxPaymentRetries, Lnd: &lndConfig{ Host: "localhost:10009", MacaroonPath: DefaultLndMacaroonPath, @@ -299,6 +306,17 @@ func Validate(cfg *Config) error { return fmt.Errorf("must specify --lnd.macaroonpath") } + // Allow at most 2x the default total payment timeout. + if cfg.TotalPaymentTimeout > 2*defaultTotalPaymentTimeout { + return fmt.Errorf("max total payment timeout allowed is at "+ + "most %v", 2*defaultTotalPaymentTimeout) + } + + // At least one retry. + if cfg.MaxPaymentRetries < 1 { + return fmt.Errorf("max payment retries must be positive") + } + return nil } diff --git a/loopd/utils.go b/loopd/utils.go index 753f6f5..2513526 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -17,14 +17,16 @@ func getClient(config *Config, lnd *lndclient.LndServices) (*loop.Client, func(), error) { clientConfig := &loop.ClientConfig{ - ServerAddress: config.Server.Host, - ProxyAddress: config.Server.Proxy, - SwapServerNoTLS: config.Server.NoTLS, - TLSPathServer: config.Server.TLSPath, - Lnd: lnd, - MaxLsatCost: btcutil.Amount(config.MaxLSATCost), - MaxLsatFee: btcutil.Amount(config.MaxLSATFee), - LoopOutMaxParts: config.LoopOutMaxParts, + ServerAddress: config.Server.Host, + ProxyAddress: config.Server.Proxy, + SwapServerNoTLS: config.Server.NoTLS, + TLSPathServer: config.Server.TLSPath, + Lnd: lnd, + MaxLsatCost: btcutil.Amount(config.MaxLSATCost), + MaxLsatFee: btcutil.Amount(config.MaxLSATFee), + LoopOutMaxParts: config.LoopOutMaxParts, + TotalPaymentTimeout: config.TotalPaymentTimeout, + MaxPaymentRetries: config.MaxPaymentRetries, } swapClient, cleanUp, err := loop.NewClient(config.DataDir, clientConfig) diff --git a/loopout.go b/loopout.go index 87fbf97..d046d4f 100644 --- a/loopout.go +++ b/loopout.go @@ -52,15 +52,6 @@ var ( // // TODO(wilmer): tune? DefaultSweepConfTargetDelta = DefaultSweepConfTarget * 2 - - // totalPaymentTimeout is the total timeout used for the loop out - // offchain payment. - totalPaymentTimeout = time.Minute * 60 - - // maxPaymentRetries is the maximum number of times the client will - // attempt to pay the invoice before failing the swap. This retry limit - // only applies to when the client uses a routing helper plugin. - maxPaymentRetries = 3 ) // loopOutSwap contains all the in-memory state related to a pending loop out @@ -87,12 +78,14 @@ type loopOutSwap struct { // executeConfig contains extra configuration to execute the swap. type executeConfig struct { - sweeper *sweep.Sweeper - statusChan chan<- SwapInfo - blockEpochChan <-chan interface{} - timerFactory func(d time.Duration) <-chan time.Time - loopOutMaxParts uint32 - cancelSwap func(context.Context, *outCancelDetails) error + sweeper *sweep.Sweeper + statusChan chan<- SwapInfo + blockEpochChan <-chan interface{} + timerFactory func(d time.Duration) <-chan time.Time + loopOutMaxParts uint32 + totalPaymentTimout time.Duration + maxPaymentRetries int + cancelSwap func(context.Context, *outCancelDetails) error } // loopOutInitResult contains information about a just-initiated loop out swap. @@ -683,6 +676,8 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context, } maxRetries := 1 + paymentTimeout := s.executeConfig.totalPaymentTimout + // Attempt to acquire and initialize the routing plugin. routingPlugin, err := AcquireRoutingPlugin( ctx, pluginType, *s.lnd, target, nil, amt, @@ -695,11 +690,11 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context, s.log.Infof("Acquired routing plugin %v for payment %v", pluginType, hash.String()) - maxRetries = maxPaymentRetries + maxRetries = s.executeConfig.maxPaymentRetries + paymentTimeout /= time.Duration(maxRetries) defer ReleaseRoutingPlugin(ctx) } - paymentTimeout := totalPaymentTimeout / time.Duration(maxRetries) req := lndclient.SendPaymentRequest{ MaxFee: maxFee, Invoice: invoice,