• 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

81.45
/contractcourt/anchor_resolver.go
1
package contractcourt
2

3
import (
4
        "errors"
5
        "io"
6
        "sync"
7

8
        "github.com/btcsuite/btcd/btcutil"
9
        "github.com/btcsuite/btcd/chaincfg/chainhash"
10
        "github.com/btcsuite/btcd/wire"
11
        "github.com/lightningnetwork/lnd/channeldb"
12
        "github.com/lightningnetwork/lnd/fn/v2"
13
        "github.com/lightningnetwork/lnd/input"
14
        "github.com/lightningnetwork/lnd/sweep"
15
)
16

17
// anchorResolver is a resolver that will attempt to sweep our anchor output.
18
type anchorResolver struct {
19
        // anchorSignDescriptor contains the information that is required to
20
        // sweep the anchor.
21
        anchorSignDescriptor input.SignDescriptor
22

23
        // anchor is the outpoint on the commitment transaction.
24
        anchor wire.OutPoint
25

26
        // resolved reflects if the contract has been fully resolved or not.
27
        resolved bool
28

29
        // broadcastHeight is the height that the original contract was
30
        // broadcast to the main-chain at. We'll use this value to bound any
31
        // historical queries to the chain for spends/confirmations.
32
        broadcastHeight uint32
33

34
        // chanPoint is the channel point of the original contract.
35
        chanPoint wire.OutPoint
36

37
        // chanType denotes the type of channel the contract belongs to.
38
        chanType channeldb.ChannelType
39

40
        // currentReport stores the current state of the resolver for reporting
41
        // over the rpc interface.
42
        currentReport ContractReport
43

44
        // reportLock prevents concurrent access to the resolver report.
45
        reportLock sync.Mutex
46

47
        contractResolverKit
48
}
49

50
// newAnchorResolver instantiates a new anchor resolver.
51
func newAnchorResolver(anchorSignDescriptor input.SignDescriptor,
52
        anchor wire.OutPoint, broadcastHeight uint32,
53
        chanPoint wire.OutPoint, resCfg ResolverConfig) *anchorResolver {
2✔
54

2✔
55
        amt := btcutil.Amount(anchorSignDescriptor.Output.Value)
2✔
56

2✔
57
        report := ContractReport{
2✔
58
                Outpoint:         anchor,
2✔
59
                Type:             ReportOutputAnchor,
2✔
60
                Amount:           amt,
2✔
61
                LimboBalance:     amt,
2✔
62
                RecoveredBalance: 0,
2✔
63
        }
2✔
64

2✔
65
        r := &anchorResolver{
2✔
66
                contractResolverKit:  *newContractResolverKit(resCfg),
2✔
67
                anchorSignDescriptor: anchorSignDescriptor,
2✔
68
                anchor:               anchor,
2✔
69
                broadcastHeight:      broadcastHeight,
2✔
70
                chanPoint:            chanPoint,
2✔
71
                currentReport:        report,
2✔
72
        }
2✔
73

2✔
74
        r.initLogger(r)
2✔
75

2✔
76
        return r
2✔
77
}
2✔
78

79
// ResolverKey returns an identifier which should be globally unique for this
80
// particular resolver within the chain the original contract resides within.
81
func (c *anchorResolver) ResolverKey() []byte {
2✔
82
        // The anchor resolver is stateless and doesn't need a database key.
2✔
83
        return nil
2✔
84
}
2✔
85

86
// Resolve offers the anchor output to the sweeper and waits for it to be swept.
87
func (c *anchorResolver) Resolve(_ bool) (ContractResolver, error) {
2✔
88
        // Attempt to update the sweep parameters to the post-confirmation
2✔
89
        // situation. We don't want to force sweep anymore, because the anchor
2✔
90
        // lost its special purpose to get the commitment confirmed. It is just
2✔
91
        // an output that we want to sweep only if it is economical to do so.
2✔
92
        //
2✔
93
        // An exclusive group is not necessary anymore, because we know that
2✔
94
        // this is the only anchor that can be swept.
2✔
95
        //
2✔
96
        // We also clear the parent tx information for cpfp, because the
2✔
97
        // commitment tx is confirmed.
2✔
98
        //
2✔
99
        // After a restart or when the remote force closes, the sweeper is not
2✔
100
        // yet aware of the anchor. In that case, it will be added as new input
2✔
101
        // to the sweeper.
2✔
102
        witnessType := input.CommitmentAnchor
2✔
103

2✔
104
        // For taproot channels, we need to use the proper witness type.
2✔
105
        if c.chanType.IsTaproot() {
2✔
106
                witnessType = input.TaprootAnchorSweepSpend
×
107
        }
×
108

109
        anchorInput := input.MakeBaseInput(
2✔
110
                &c.anchor, witnessType, &c.anchorSignDescriptor,
2✔
111
                c.broadcastHeight, nil,
2✔
112
        )
2✔
113

2✔
114
        resultChan, err := c.Sweeper.SweepInput(
2✔
115
                &anchorInput,
2✔
116
                sweep.Params{
2✔
117
                        // For normal anchor sweeping, the budget is 330 sats.
2✔
118
                        Budget: btcutil.Amount(
2✔
119
                                anchorInput.SignDesc().Output.Value,
2✔
120
                        ),
2✔
121

2✔
122
                        // There's no rush to sweep the anchor, so we use a nil
2✔
123
                        // deadline here.
2✔
124
                        DeadlineHeight: fn.None[int32](),
2✔
125
                },
2✔
126
        )
2✔
127
        if err != nil {
2✔
128
                return nil, err
×
129
        }
×
130

131
        var (
2✔
132
                outcome channeldb.ResolverOutcome
2✔
133
                spendTx *chainhash.Hash
2✔
134
        )
2✔
135

2✔
136
        select {
2✔
137
        case sweepRes := <-resultChan:
2✔
138
                switch sweepRes.Err {
2✔
139
                // Anchor was swept successfully.
140
                case nil:
2✔
141
                        sweepTxID := sweepRes.Tx.TxHash()
2✔
142

2✔
143
                        spendTx = &sweepTxID
2✔
144
                        outcome = channeldb.ResolverOutcomeClaimed
2✔
145

146
                // Anchor was swept by someone else. This is possible after the
147
                // 16 block csv lock.
148
                case sweep.ErrRemoteSpend:
×
149
                        c.log.Warnf("our anchor spent by someone else")
×
150
                        outcome = channeldb.ResolverOutcomeUnclaimed
×
151

152
                // An unexpected error occurred.
153
                default:
×
154
                        c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err)
×
155

×
156
                        return nil, sweepRes.Err
×
157
                }
158

159
        case <-c.quit:
×
160
                return nil, errResolverShuttingDown
×
161
        }
162

163
        // Update report to reflect that funds are no longer in limbo.
164
        c.reportLock.Lock()
2✔
165
        if outcome == channeldb.ResolverOutcomeClaimed {
4✔
166
                c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
2✔
167
        }
2✔
168
        c.currentReport.LimboBalance = 0
2✔
169
        report := c.currentReport.resolverReport(
2✔
170
                spendTx, channeldb.ResolverTypeAnchor, outcome,
2✔
171
        )
2✔
172
        c.reportLock.Unlock()
2✔
173

2✔
174
        c.resolved = true
2✔
175
        return nil, c.PutResolverReport(nil, report)
2✔
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 (c *anchorResolver) Stop() {
2✔
183
        close(c.quit)
2✔
184
}
2✔
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 (c *anchorResolver) IsResolved() bool {
5✔
191
        return c.resolved
5✔
192
}
5✔
193

194
// SupplementState allows the user of a ContractResolver to supplement it with
195
// state required for the proper resolution of a contract.
196
//
197
// NOTE: Part of the ContractResolver interface.
198
func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) {
2✔
199
        c.chanType = state.ChanType
2✔
200
}
2✔
201

202
// report returns a report on the resolution state of the contract.
203
func (c *anchorResolver) report() *ContractReport {
×
204
        c.reportLock.Lock()
×
205
        defer c.reportLock.Unlock()
×
206

×
207
        reportCopy := c.currentReport
×
208
        return &reportCopy
×
209
}
×
210

211
func (c *anchorResolver) Encode(w io.Writer) error {
×
212
        return errors.New("serialization not supported")
×
213
}
×
214

215
// A compile time assertion to ensure anchorResolver meets the
216
// ContractResolver interface.
217
var _ ContractResolver = (*anchorResolver)(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