Skip to content

Commit 0fe952a

Browse files
authored
Merge pull request #838 from starius/loopout-retarget-feerate-ever-block
loopout: re-target sweep's feerate every block
2 parents 9c347b4 + 2f22f96 commit 0fe952a

File tree

10 files changed

+600
-40
lines changed

10 files changed

+600
-40
lines changed

client.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ var (
6262
// probeTimeout is the maximum time until a probe is allowed to take.
6363
probeTimeout = 3 * time.Minute
6464

65+
// repushDelay is the delay of (re)adding a sweep to sweepbatcher after
66+
// a block is mined.
6567
repushDelay = 1 * time.Second
6668

69+
// additionalDelay is the delay added on top of repushDelay inside the
70+
// sweepbatcher to publish a sweep transaction.
71+
additionalDelay = 1 * time.Second
72+
6773
// MinerFeeEstimationFailed is a magic number that is returned in a
6874
// quote call as the miner fee if the fee estimation in lnd's wallet
6975
// failed because of insufficient funds.
@@ -185,13 +191,52 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
185191
"NewSweepFetcherFromSwapStore failed: %w", err)
186192
}
187193

194+
// There is circular dependency between executor and sweepbatcher, as
195+
// executor stores sweepbatcher and sweepbatcher depends on
196+
// executor.height() though loopOutSweepFeerateProvider.
197+
var executor *executor
198+
199+
// getHeight returns current height, according to executor.
200+
getHeight := func() int32 {
201+
if executor == nil {
202+
// This must not happen, because executor is set in this
203+
// function, before sweepbatcher.Run is called.
204+
log.Errorf("getHeight called when executor is nil")
205+
206+
return 0
207+
}
208+
209+
return executor.height()
210+
}
211+
212+
loopOutSweepFeerateProvider := newLoopOutSweepFeerateProvider(
213+
sweeper, loopDB, cfg.Lnd.ChainParams, getHeight,
214+
)
215+
188216
batcher := sweepbatcher.NewBatcher(
189217
cfg.Lnd.WalletKit, cfg.Lnd.ChainNotifier, cfg.Lnd.Signer,
190218
swapServerClient.MultiMuSig2SignSweep, verifySchnorrSig,
191219
cfg.Lnd.ChainParams, sweeperDb, sweepStore,
220+
221+
// Disable 100 sats/kw fee bump every block and retarget feerate
222+
// every block according to the current mempool condition.
223+
sweepbatcher.WithCustomFeeRate(
224+
loopOutSweepFeerateProvider.GetMinFeeRate,
225+
),
226+
227+
// Upon new block arrival, republishing is triggered in both
228+
// loopout.go code (waitForHtlcSpendConfirmedV2/ <-timerChan)
229+
// and in sweepbatcher code (batch.Run/case <-timerChan). The
230+
// former updates the fee rate which is used by the later by
231+
// calling AddSweep. Make sure they are ordered, add additional
232+
// delay time to sweepbatcher's handling. The delay used in
233+
// loopout.go is repushDelay.
234+
sweepbatcher.WithPublishDelay(
235+
repushDelay+additionalDelay,
236+
),
192237
)
193238

194-
executor := newExecutor(&executorConfig{
239+
executor = newExecutor(&executorConfig{
195240
lnd: cfg.Lnd,
196241
store: loopDB,
197242
sweeper: sweeper,
@@ -570,8 +615,11 @@ func (s *Client) getLoopOutSweepFee(ctx context.Context, confTarget int32) (
570615
htlc = swap.QuoteHtlcP2WSH
571616
}
572617

618+
label := "loopout-quote"
619+
573620
return s.sweeper.GetSweepFee(
574621
ctx, htlc.AddSuccessToEstimator, p2wshAddress, confTarget,
622+
label,
575623
)
576624
}
577625

liquidity/liquidity.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,13 @@ const (
106106
// time we reach timeout. We set this to a high estimate so that we can
107107
// account for worst-case fees, (1250 * 4 / 1000) = 50 sat/byte.
108108
defaultLoopInSweepFee = chainfee.SatPerKWeight(1250)
109-
)
110109

111-
var (
112110
// defaultHtlcConfTarget is the default confirmation target we use for
113111
// loop in swap htlcs, set to the same default at the client.
114112
defaultHtlcConfTarget = loop.DefaultHtlcConfTarget
113+
)
115114

115+
var (
116116
// defaultBudget is the default autoloop budget we set. This budget will
117117
// only be used for automatically dispatched swaps if autoloop is
118118
// explicitly enabled, so we are happy to set a non-zero value here. The

loopd/log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/lightninglabs/loop/liquidity"
1212
"github.com/lightninglabs/loop/loopdb"
1313
"github.com/lightninglabs/loop/notifications"
14+
"github.com/lightninglabs/loop/sweep"
1415
"github.com/lightninglabs/loop/sweepbatcher"
1516
"github.com/lightningnetwork/lnd"
1617
"github.com/lightningnetwork/lnd/build"
@@ -52,6 +53,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
5253
lnd.AddSubLogger(
5354
root, notifications.Subsystem, intercept, notifications.UseLogger,
5455
)
56+
lnd.AddSubLogger(
57+
root, sweep.Subsystem, intercept, sweep.UseLogger,
58+
)
5559
}
5660

5761
// genSubLogger creates a logger for a subsystem. We provide an instance of

loopin.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,10 +1077,12 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
10771077
}
10781078
}
10791079

1080+
label := fmt.Sprintf("loopin-timeout-%x", s.hash[:6])
1081+
10801082
// Calculate sweep tx fee.
10811083
fee, err := s.sweeper.GetSweepFee(
10821084
ctx, s.htlc.AddTimeoutToEstimator, s.timeoutAddr,
1083-
TimeoutTxConfTarget,
1085+
TimeoutTxConfTarget, label,
10841086
)
10851087
if err != nil {
10861088
return 0, err

loopin_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,13 @@ func handleHtlcExpiry(t *testing.T, ctx *loopInTestContext, inSwap *loopInSwap,
312312
// Expect timeout tx to be published.
313313
timeoutTx := <-ctx.lnd.TxPublishChannel
314314

315+
label := fmt.Sprintf("loopin-timeout-%x", inSwap.hash[:6])
316+
315317
// We can just get our sweep fee as we would in the swap code because
316318
// our estimate is static.
317319
fee, err := inSwap.sweeper.GetSweepFee(
318320
context.Background(), inSwap.htlc.AddTimeoutToEstimator,
319-
inSwap.timeoutAddr, TimeoutTxConfTarget,
321+
inSwap.timeoutAddr, TimeoutTxConfTarget, label,
320322
)
321323
require.NoError(t, err)
322324
cost.Onchain += fee

loopout.go

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,33 @@ const (
3838
// We'll try to sweep with MuSig2 at most 10 times. If that fails we'll
3939
// fail back to using standard scriptspend sweep.
4040
maxMusigSweepRetries = 10
41-
)
4241

43-
var (
4442
// MinLoopOutPreimageRevealDelta configures the minimum number of
4543
// remaining blocks before htlc expiry required to reveal preimage.
46-
MinLoopOutPreimageRevealDelta int32 = 20
44+
MinLoopOutPreimageRevealDelta = 20
4745

4846
// DefaultSweepConfTarget is the default confirmation target we'll use
4947
// when sweeping on-chain HTLCs.
50-
DefaultSweepConfTarget int32 = 9
48+
DefaultSweepConfTarget = 9
5149

5250
// DefaultHtlcConfTarget is the default confirmation target we'll use
5351
// for on-chain htlcs published by the swap client for Loop In.
54-
DefaultHtlcConfTarget int32 = 6
52+
DefaultHtlcConfTarget = 6
5553

5654
// DefaultSweepConfTargetDelta is the delta of blocks from a Loop Out
57-
// swap's expiration height at which we begin to use the default sweep
58-
// confirmation target.
59-
//
60-
// TODO(wilmer): tune?
61-
DefaultSweepConfTargetDelta = DefaultSweepConfTarget * 2
55+
// swap's expiration height at which we begin to cap the sweep
56+
// confirmation target with urgentSweepConfTarget and multiply feerate
57+
// by factor urgentSweepConfTargetFactor.
58+
DefaultSweepConfTargetDelta = 10
59+
60+
// urgentSweepConfTarget is the confirmation target we'll use when the
61+
// loop-out swap is about to expire (<= DefaultSweepConfTargetDelta
62+
// blocks to expire).
63+
urgentSweepConfTarget = 3
64+
65+
// urgentSweepConfTargetFactor is the factor we apply to feerate of
66+
// loop-out sweep if it is about to expire.
67+
urgentSweepConfTargetFactor = 1.1
6268
)
6369

6470
// loopOutSwap contains all the in-memory state related to a pending loop out
@@ -1169,12 +1175,12 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
11691175
timerChan = s.timerFactory(repushDelay)
11701176

11711177
case <-timerChan:
1172-
// sweepConfTarget will return false if the preimage is
1178+
// canSweep will return false if the preimage is
11731179
// not revealed yet but the conf target is closer than
11741180
// 20 blocks. In this case to be sure we won't attempt
11751181
// to sweep at all and we won't reveal the preimage
11761182
// either.
1177-
_, canSweep := s.sweepConfTarget()
1183+
canSweep := s.canSweep()
11781184
if !canSweep {
11791185
s.log.Infof("Aborting swap, timed " +
11801186
"out on-chain")
@@ -1375,9 +1381,9 @@ func validateLoopOutContract(lnd *lndclient.LndServices, request *OutRequest,
13751381
return nil
13761382
}
13771383

1378-
// sweepConfTarget returns the confirmation target for the htlc sweep or false
1379-
// if we're too late.
1380-
func (s *loopOutSwap) sweepConfTarget() (int32, bool) {
1384+
// canSweep will return false if the preimage is not revealed yet but the conf
1385+
// target is closer than 20 blocks (i.e. it is too late to reveal the preimage).
1386+
func (s *loopOutSwap) canSweep() bool {
13811387
remainingBlocks := s.CltvExpiry - s.height
13821388
blocksToLastReveal := remainingBlocks - MinLoopOutPreimageRevealDelta
13831389
preimageRevealed := s.state == loopdb.StatePreimageRevealed
@@ -1393,20 +1399,8 @@ func (s *loopOutSwap) sweepConfTarget() (int32, bool) {
13931399
s.height)
13941400

13951401
s.state = loopdb.StateFailTimeout
1396-
return 0, false
1397-
}
1398-
1399-
// Calculate the transaction fee based on the confirmation target
1400-
// required to sweep the HTLC before the timeout. We'll use the
1401-
// confirmation target provided by the client unless we've come too
1402-
// close to the expiration height, in which case we'll use the default
1403-
// if it is better than what the client provided.
1404-
confTarget := s.SweepConfTarget
1405-
if remainingBlocks <= DefaultSweepConfTargetDelta &&
1406-
confTarget > DefaultSweepConfTarget {
1407-
1408-
confTarget = DefaultSweepConfTarget
1402+
return false
14091403
}
14101404

1411-
return confTarget, true
1405+
return true
14121406
}

0 commit comments

Comments
 (0)