• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.64
/contractcourt/htlc_outgoing_contest_resolver.go
1
package contractcourt
2

3
import (
4
        "fmt"
5
        "io"
6

7
        "github.com/btcsuite/btcd/btcutil"
8
        "github.com/lightningnetwork/lnd/channeldb"
9
        "github.com/lightningnetwork/lnd/fn/v2"
10
        "github.com/lightningnetwork/lnd/lnwallet"
11
)
12

13
// htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
14
// outgoing HTLC that is still contested. An HTLC is still contested, if at the
15
// time that we broadcast the commitment transaction, it isn't able to be fully
16
// resolved. In this case, we'll either wait for the HTLC to timeout, or for
17
// us to learn of the preimage.
18
type htlcOutgoingContestResolver struct {
19
        // htlcTimeoutResolver is the inner solver that this resolver may turn
20
        // into. This only happens if the HTLC expires on-chain.
21
        *htlcTimeoutResolver
22
}
23

24
// newOutgoingContestResolver instantiates a new outgoing contested htlc
25
// resolver.
26
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
27
        broadcastHeight uint32, htlc channeldb.HTLC,
28
        resCfg ResolverConfig) *htlcOutgoingContestResolver {
1✔
29

1✔
30
        timeout := newTimeoutResolver(
1✔
31
                res, broadcastHeight, htlc, resCfg,
1✔
32
        )
1✔
33

1✔
34
        return &htlcOutgoingContestResolver{
1✔
35
                htlcTimeoutResolver: timeout,
1✔
36
        }
1✔
37
}
1✔
38

39
// Resolve commences the resolution of this contract. As this contract hasn't
40
// yet timed out, we'll wait for one of two things to happen
41
//
42
//  1. The HTLC expires. In this case, we'll sweep the funds and send a clean
43
//     up cancel message to outside sub-systems.
44
//
45
//  2. The remote party sweeps this HTLC on-chain, in which case we'll add the
46
//     pre-image to our global cache, then send a clean up settle message
47
//     backwards.
48
//
49
// When either of these two things happens, we'll create a new resolver which
50
// is able to handle the final resolution of the contract. We're only the pivot
51
// point.
52
func (h *htlcOutgoingContestResolver) Resolve(
53
        _ bool) (ContractResolver, error) {
4✔
54

4✔
55
        // If we're already full resolved, then we don't have anything further
4✔
56
        // to do.
4✔
57
        if h.resolved {
4✔
58
                return nil, nil
×
59
        }
×
60

61
        // Otherwise, we'll watch for two external signals to decide if we'll
62
        // morph into another resolver, or fully resolve the contract.
63
        //
64
        // The output we'll be watching for is the *direct* spend from the HTLC
65
        // output. If this isn't our commitment transaction, it'll be right on
66
        // the resolution. Otherwise, we fetch this pointer from the input of
67
        // the time out transaction.
68
        outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
4✔
69
        if err != nil {
4✔
70
                return nil, err
×
71
        }
×
72

73
        // First, we'll register for a spend notification for this output. If
74
        // the remote party sweeps with the pre-image, we'll be notified.
75
        spendNtfn, err := h.Notifier.RegisterSpendNtfn(
4✔
76
                outPointToWatch, scriptToWatch, h.broadcastHeight,
4✔
77
        )
4✔
78
        if err != nil {
4✔
79
                return nil, err
×
80
        }
×
81

82
        // We'll quickly check to see if the output has already been spent.
83
        select {
4✔
84
        // If the output has already been spent, then we can stop early and
85
        // sweep the pre-image from the output.
86
        case commitSpend, ok := <-spendNtfn.Spend:
×
87
                if !ok {
×
88
                        return nil, errResolverShuttingDown
×
89
                }
×
90

91
                // TODO(roasbeef): Checkpoint?
92
                return h.claimCleanUp(commitSpend)
×
93

94
        // If it hasn't, then we'll watch for both the expiration, and the
95
        // sweeping out this output.
96
        default:
4✔
97
        }
98

99
        // If we reach this point, then we can't fully act yet, so we'll await
100
        // either of our signals triggering: the HTLC expires, or we learn of
101
        // the preimage.
102
        blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
4✔
103
        if err != nil {
4✔
104
                return nil, err
×
105
        }
×
106
        defer blockEpochs.Cancel()
4✔
107

4✔
108
        for {
10✔
109
                select {
6✔
110

111
                // A new block has arrived, we'll check to see if this leads to
112
                // HTLC expiration.
113
                case newBlock, ok := <-blockEpochs.Epochs:
4✔
114
                        if !ok {
4✔
115
                                return nil, errResolverShuttingDown
×
116
                        }
×
117

118
                        // If the current height is >= expiry, then a timeout
119
                        // path spend will be valid to be included in the next
120
                        // block, and we can immediately return the resolver.
121
                        //
122
                        // NOTE: when broadcasting this transaction, btcd will
123
                        // check the timelock in `CheckTransactionStandard`,
124
                        // which requires `expiry < currentHeight+1`. If the
125
                        // check doesn't pass, error `transaction is not
126
                        // finalized` will be returned and the broadcast will
127
                        // fail.
128
                        newHeight := uint32(newBlock.Height)
4✔
129
                        if newHeight >= h.htlcResolution.Expiry {
6✔
130
                                log.Infof("%T(%v): HTLC has expired "+
2✔
131
                                        "(height=%v, expiry=%v), transforming "+
2✔
132
                                        "into timeout resolver", h,
2✔
133
                                        h.htlcResolution.ClaimOutpoint,
2✔
134
                                        newHeight, h.htlcResolution.Expiry)
2✔
135
                                return h.htlcTimeoutResolver, nil
2✔
136
                        }
2✔
137

138
                // The output has been spent! This means the preimage has been
139
                // revealed on-chain.
140
                case commitSpend, ok := <-spendNtfn.Spend:
1✔
141
                        if !ok {
1✔
142
                                return nil, errResolverShuttingDown
×
143
                        }
×
144

145
                        // The only way this output can be spent by the remote
146
                        // party is by revealing the preimage. So we'll perform
147
                        // our duties to clean up the contract once it has been
148
                        // claimed.
149
                        return h.claimCleanUp(commitSpend)
1✔
150

151
                case <-h.quit:
1✔
152
                        return nil, fmt.Errorf("resolver canceled")
1✔
153
                }
154
        }
155
}
156

157
// report returns a report on the resolution state of the contract.
158
func (h *htlcOutgoingContestResolver) report() *ContractReport {
×
159
        // No locking needed as these values are read-only.
×
160

×
161
        finalAmt := h.htlc.Amt.ToSatoshis()
×
162
        if h.htlcResolution.SignedTimeoutTx != nil {
×
163
                finalAmt = btcutil.Amount(
×
164
                        h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
×
165
                )
×
166
        }
×
167

168
        return &ContractReport{
×
169
                Outpoint:       h.htlcResolution.ClaimOutpoint,
×
170
                Type:           ReportOutputOutgoingHtlc,
×
171
                Amount:         finalAmt,
×
172
                MaturityHeight: h.htlcResolution.Expiry,
×
173
                LimboBalance:   finalAmt,
×
174
                Stage:          1,
×
175
        }
×
176
}
177

178
// Stop signals the resolver to cancel any current resolution processes, and
179
// suspend.
180
//
181
// NOTE: Part of the ContractResolver interface.
182
func (h *htlcOutgoingContestResolver) Stop() {
1✔
183
        close(h.quit)
1✔
184
}
1✔
185

186
// IsResolved returns true if the stored state in the resolve is fully
187
// resolved. In this case the target output can be forgotten.
188
//
189
// NOTE: Part of the ContractResolver interface.
190
func (h *htlcOutgoingContestResolver) IsResolved() bool {
2✔
191
        return h.resolved
2✔
192
}
2✔
193

194
// Encode writes an encoded version of the ContractResolver into the passed
195
// Writer.
196
//
197
// NOTE: Part of the ContractResolver interface.
198
func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
3✔
199
        return h.htlcTimeoutResolver.Encode(w)
3✔
200
}
3✔
201

202
// SupplementDeadline does nothing for an incoming htlc resolver.
203
//
204
// NOTE: Part of the htlcContractResolver interface.
205
func (h *htlcOutgoingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
2✔
206
}
2✔
207

208
// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
209
// from the passed Reader instance, returning an active ContractResolver
210
// instance.
211
func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
212
        *htlcOutgoingContestResolver, error) {
4✔
213

4✔
214
        h := &htlcOutgoingContestResolver{}
4✔
215
        timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
4✔
216
        if err != nil {
4✔
217
                return nil, err
×
218
        }
×
219
        h.htlcTimeoutResolver = timeoutResolver
4✔
220
        return h, nil
4✔
221
}
222

223
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
224
// ContractResolver interface.
225
var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc