Skip to content

Commit 1017efe

Browse files
authored
Merge pull request #6605 from Roasbeef/proper-sweep-lease-second-level-success
contractcourt: unify the lease specific HTLC sweeping logic
2 parents 199f9d1 + 9b0718a commit 1017efe

7 files changed

+127
-95
lines changed

contractcourt/channel_arbitrator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,6 +2146,9 @@ func (c *ChannelArbitrator) prepContractResolutions(
21462146
resolver := newSuccessResolver(
21472147
resolution, height, htlc, resolverCfg,
21482148
)
2149+
if chanState != nil {
2150+
resolver.SupplementState(chanState)
2151+
}
21492152
htlcResolvers = append(htlcResolvers, resolver)
21502153
}
21512154

@@ -2204,6 +2207,9 @@ func (c *ChannelArbitrator) prepContractResolutions(
22042207
resolution, height, htlc,
22052208
resolverCfg,
22062209
)
2210+
if chanState != nil {
2211+
resolver.SupplementState(chanState)
2212+
}
22072213
htlcResolvers = append(htlcResolvers, resolver)
22082214
}
22092215

contractcourt/htlc_incoming_contest_resolver.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -439,13 +439,6 @@ func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) {
439439
h.htlc = htlc
440440
}
441441

442-
// SupplementState allows the user of a ContractResolver to supplement it with
443-
// state required for the proper resolution of a contract.
444-
//
445-
// NOTE: Part of the ContractResolver interface.
446-
func (h *htlcIncomingContestResolver) SupplementState(_ *channeldb.OpenChannel) {
447-
}
448-
449442
// decodePayload (re)decodes the hop payload of a received htlc.
450443
func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload,
451444
[]byte, error) {

contractcourt/htlc_lease_resolver.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package contractcourt
2+
3+
import (
4+
"math"
5+
6+
"github.com/btcsuite/btcd/wire"
7+
"github.com/lightningnetwork/lnd/chainntnfs"
8+
"github.com/lightningnetwork/lnd/channeldb"
9+
"github.com/lightningnetwork/lnd/input"
10+
)
11+
12+
// htlcLeaseResolver is a struct that houses the lease specific HTLC resolution
13+
// logic. This includes deriving the _true_ waiting height, as well as the
14+
// input to offer to the sweeper.
15+
type htlcLeaseResolver struct {
16+
// channelInitiator denotes whether the party responsible for resolving
17+
// the contract initiated the channel.
18+
channelInitiator bool
19+
20+
// leaseExpiry denotes the additional waiting period the contract must
21+
// hold until it can be resolved. This waiting period is known as the
22+
// expiration of a script-enforced leased channel and only applies to
23+
// the channel initiator.
24+
//
25+
// NOTE: This value should only be set when the contract belongs to a
26+
// leased channel.
27+
leaseExpiry uint32
28+
}
29+
30+
// hasCLTV denotes whether the resolver must wait for an additional CLTV to
31+
// expire before resolving the contract.
32+
func (h *htlcLeaseResolver) hasCLTV() bool {
33+
return h.channelInitiator && h.leaseExpiry > 0
34+
}
35+
36+
// deriveWaitHeight computes the height the resolver needs to wait until it can
37+
// sweep the input.
38+
func (h *htlcLeaseResolver) deriveWaitHeight(csvDelay uint32,
39+
commitSpend *chainntnfs.SpendDetail) uint32 {
40+
41+
waitHeight := uint32(commitSpend.SpendingHeight) + csvDelay - 1
42+
if h.hasCLTV() {
43+
waitHeight = uint32(math.Max(
44+
float64(waitHeight), float64(h.leaseExpiry),
45+
))
46+
}
47+
48+
return waitHeight
49+
}
50+
51+
// makeSweepInput constructs the type of input (either just csv or csv+ctlv) to
52+
// send to the sweeper so the output can ultimately be swept.
53+
func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint,
54+
wType, cltvWtype input.StandardWitnessType,
55+
signDesc *input.SignDescriptor,
56+
csvDelay, broadcastHeight uint32, payHash [32]byte) *input.BaseInput {
57+
58+
if h.hasCLTV() {
59+
log.Infof("%T(%x): CSV and CLTV locks expired, offering "+
60+
"second-layer output to sweeper: %v", h, payHash, op)
61+
62+
return input.NewCsvInputWithCltv(
63+
op, cltvWtype, signDesc,
64+
broadcastHeight, csvDelay,
65+
h.leaseExpiry,
66+
)
67+
}
68+
69+
log.Infof("%T(%x): CSV lock expired, offering second-layer output to "+
70+
"sweeper: %v", h, payHash, op)
71+
72+
return input.NewCsvInput(op, wType, signDesc, broadcastHeight, csvDelay)
73+
}
74+
75+
// SupplementState allows the user of a ContractResolver to supplement it with
76+
// state required for the proper resolution of a contract.
77+
//
78+
// NOTE: Part of the ContractResolver interface.
79+
func (h *htlcLeaseResolver) SupplementState(state *channeldb.OpenChannel) {
80+
if state.ChanType.HasLeaseExpiration() {
81+
h.leaseExpiry = state.ThawHeight
82+
}
83+
h.channelInitiator = state.IsInitiator
84+
}

contractcourt/htlc_outgoing_contest_resolver.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,6 @@ func (h *htlcOutgoingContestResolver) IsResolved() bool {
190190
return h.resolved
191191
}
192192

193-
// SupplementState allows the user of a ContractResolver to supplement it with
194-
// state required for the proper resolution of a contract.
195-
//
196-
// NOTE: Part of the ContractResolver interface.
197-
func (h *htlcOutgoingContestResolver) SupplementState(state *channeldb.OpenChannel) {
198-
h.htlcTimeoutResolver.SupplementState(state)
199-
}
200-
201193
// Encode writes an encoded version of the ContractResolver into the passed
202194
// Writer.
203195
//

contractcourt/htlc_success_resolver.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ type htlcSuccessResolver struct {
6767
reportLock sync.Mutex
6868

6969
contractResolverKit
70+
71+
htlcLeaseResolver
7072
}
7173

7274
// newSuccessResolver instanties a new htlc success resolver.
@@ -292,9 +294,12 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
292294
}
293295
}
294296

295-
// The HTLC success tx has a CSV lock that we must wait for.
296-
waitHeight := uint32(commitSpend.SpendingHeight) +
297-
h.htlcResolution.CsvDelay - 1
297+
// The HTLC success tx has a CSV lock that we must wait for, and if
298+
// this is a lease enforced channel and we're the imitator, we may need
299+
// to wait for longer.
300+
waitHeight := h.deriveWaitHeight(
301+
h.htlcResolution.CsvDelay, commitSpend,
302+
)
298303

299304
// Now that the sweeper has broadcasted the second-level transaction,
300305
// it has confirmed, and we have checkpointed our state, we'll sweep
@@ -305,8 +310,14 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
305310
h.currentReport.MaturityHeight = waitHeight
306311
h.reportLock.Unlock()
307312

308-
log.Infof("%T(%x): waiting for CSV lock to expire at height %v",
309-
h, h.htlc.RHash[:], waitHeight)
313+
if h.hasCLTV() {
314+
log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
315+
"expire at height %v", h, h.htlc.RHash[:],
316+
waitHeight)
317+
} else {
318+
log.Infof("%T(%x): waiting for CSV lock to expire at "+
319+
"height %v", h, h.htlc.RHash[:], waitHeight)
320+
}
310321

311322
err := waitForHeight(waitHeight, h.Notifier, h.quit)
312323
if err != nil {
@@ -327,10 +338,14 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
327338
log.Infof("%T(%x): CSV lock expired, offering second-layer "+
328339
"output to sweeper: %v", h, h.htlc.RHash[:], op)
329340

330-
inp := input.NewCsvInput(
341+
// Let the sweeper sweep the second-level output now that the
342+
// CSV/CLTV locks have expired.
343+
inp := h.makeSweepInput(
331344
op, input.HtlcAcceptedSuccessSecondLevel,
332-
&h.htlcResolution.SweepSignDesc, h.broadcastHeight,
333-
h.htlcResolution.CsvDelay,
345+
input.LeaseHtlcAcceptedSuccessSecondLevel,
346+
&h.htlcResolution.SweepSignDesc,
347+
h.htlcResolution.CsvDelay, h.broadcastHeight,
348+
h.htlc.RHash,
334349
)
335350
_, err = h.Sweeper.SweepInput(
336351
inp,
@@ -626,13 +641,6 @@ func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
626641
h.htlc = htlc
627642
}
628643

629-
// SupplementState allows the user of a ContractResolver to supplement it with
630-
// state required for the proper resolution of a contract.
631-
//
632-
// NOTE: Part of the ContractResolver interface.
633-
func (h *htlcSuccessResolver) SupplementState(_ *channeldb.OpenChannel) {
634-
}
635-
636644
// HtlcPoint returns the htlc's outpoint on the commitment tx.
637645
//
638646
// NOTE: Part of the htlcContractResolver interface.

contractcourt/htlc_timeout_resolver.go

Lines changed: 12 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/binary"
55
"fmt"
66
"io"
7-
"math"
87
"sync"
98

109
"github.com/btcsuite/btcd/btcutil"
@@ -48,19 +47,6 @@ type htlcTimeoutResolver struct {
4847
// htlc contains information on the htlc that we are resolving on-chain.
4948
htlc channeldb.HTLC
5049

51-
// channelInitiator denotes whether the party responsible for resolving
52-
// the contract initiated the channel.
53-
channelInitiator bool
54-
55-
// leaseExpiry denotes the additional waiting period the contract must
56-
// hold until it can be resolved. This waiting period is known as the
57-
// expiration of a script-enforced leased channel and only applies to
58-
// the channel initiator.
59-
//
60-
// NOTE: This value should only be set when the contract belongs to a
61-
// leased channel.
62-
leaseExpiry uint32
63-
6450
// currentReport stores the current state of the resolver for reporting
6551
// over the rpc interface. This should only be reported in case we have
6652
// a non-nil SignDetails on the htlcResolution, otherwise the nursery
@@ -71,6 +57,8 @@ type htlcTimeoutResolver struct {
7157
reportLock sync.Mutex
7258

7359
contractResolverKit
60+
61+
htlcLeaseResolver
7462
}
7563

7664
// newTimeoutResolver instantiates a new timeout htlc resolver.
@@ -444,13 +432,9 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
444432
// the CSV and possible CLTV lock to expire, before sweeping the output
445433
// on the second-level.
446434
case h.htlcResolution.SignDetails != nil:
447-
waitHeight := uint32(commitSpend.SpendingHeight) +
448-
h.htlcResolution.CsvDelay - 1
449-
if h.hasCLTV() {
450-
waitHeight = uint32(math.Max(
451-
float64(waitHeight), float64(h.leaseExpiry),
452-
))
453-
}
435+
waitHeight := h.deriveWaitHeight(
436+
h.htlcResolution.CsvDelay, commitSpend,
437+
)
454438

455439
h.reportLock.Lock()
456440
h.currentReport.Stage = 2
@@ -483,27 +467,13 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
483467

484468
// Let the sweeper sweep the second-level output now that the
485469
// CSV/CLTV locks have expired.
486-
var inp *input.BaseInput
487-
if h.hasCLTV() {
488-
log.Infof("%T(%x): CSV and CLTV locks expired, offering "+
489-
"second-layer output to sweeper: %v", h,
490-
h.htlc.RHash[:], op)
491-
inp = input.NewCsvInputWithCltv(
492-
op, input.LeaseHtlcOfferedTimeoutSecondLevel,
493-
&h.htlcResolution.SweepSignDesc,
494-
h.broadcastHeight, h.htlcResolution.CsvDelay,
495-
h.leaseExpiry,
496-
)
497-
} else {
498-
log.Infof("%T(%x): CSV lock expired, offering "+
499-
"second-layer output to sweeper: %v", h,
500-
h.htlc.RHash[:], op)
501-
inp = input.NewCsvInput(
502-
op, input.HtlcOfferedTimeoutSecondLevel,
503-
&h.htlcResolution.SweepSignDesc,
504-
h.broadcastHeight, h.htlcResolution.CsvDelay,
505-
)
506-
}
470+
inp := h.makeSweepInput(
471+
op, input.HtlcOfferedTimeoutSecondLevel,
472+
input.LeaseHtlcOfferedTimeoutSecondLevel,
473+
&h.htlcResolution.SweepSignDesc,
474+
h.htlcResolution.CsvDelay, h.broadcastHeight,
475+
h.htlc.RHash,
476+
)
507477
_, err = h.Sweeper.SweepInput(
508478
inp,
509479
sweep.Params{
@@ -716,23 +686,6 @@ func (h *htlcTimeoutResolver) Supplement(htlc channeldb.HTLC) {
716686
h.htlc = htlc
717687
}
718688

719-
// SupplementState allows the user of a ContractResolver to supplement it with
720-
// state required for the proper resolution of a contract.
721-
//
722-
// NOTE: Part of the ContractResolver interface.
723-
func (h *htlcTimeoutResolver) SupplementState(state *channeldb.OpenChannel) {
724-
if state.ChanType.HasLeaseExpiration() {
725-
h.leaseExpiry = state.ThawHeight
726-
}
727-
h.channelInitiator = state.IsInitiator
728-
}
729-
730-
// hasCLTV denotes whether the resolver must wait for an additional CLTV to
731-
// expire before resolving the contract.
732-
func (h *htlcTimeoutResolver) hasCLTV() bool {
733-
return h.channelInitiator && h.leaseExpiry > 0
734-
}
735-
736689
// HtlcPoint returns the htlc's outpoint on the commitment tx.
737690
//
738691
// NOTE: Part of the htlcContractResolver interface.

docs/release-notes/release-notes-0.15.0.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,6 @@ compact filters and block/block headers.
125125
* [Fixed deadlock in the invoice registry](
126126
https://github.com/lightningnetwork/lnd/pull/6600)
127127

128-
## Neutrino
129-
130-
* [New neutrino sub-server](https://github.com/lightningnetwork/lnd/pull/5652)
131-
capable of status checks, adding, disconnecting and listing
132-
peers, fetching compact filters and block/block headers.
133-
134128
* [Added signature length
135129
validation](https://github.com/lightningnetwork/lnd/pull/6314) when calling
136130
`NewSigFromRawSignature`.
@@ -187,6 +181,8 @@ from occurring that would result in an erroneous force close.](https://github.co
187181
* [Fixed a wrong channel status inheritance used in `migration26` and
188182
`migration27`](https://github.com/lightningnetwork/lnd/pull/6563).
189183

184+
* [Fixes an issue related to HTLCs on lease enforced channels that can lead to itest flakes](https://github.com/lightningnetwork/lnd/pull/6605/files)
185+
190186
## Routing
191187

192188
* [Add a new `time_pref` parameter to the QueryRoutes and SendPayment APIs](https://github.com/lightningnetwork/lnd/pull/6024) that

0 commit comments

Comments
 (0)