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

lightningnetwork / lnd / 17132206455

21 Aug 2025 03:56PM UTC coverage: 54.685% (-2.6%) from 57.321%
17132206455

Pull #10167

github

web-flow
Merge 5dd2ed093 into 0c2f045f5
Pull Request #10167: multi: bump Go to 1.24.6

4 of 31 new or added lines in 10 files covered. (12.9%)

23854 existing lines in 284 files now uncovered.

108937 of 199210 relevant lines covered (54.68%)

22026.48 hits per line

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

77.08
/contractcourt/anchor_resolver.go
1
package contractcourt
2

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

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

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

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

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

32
        // chanPoint is the channel point of the original contract.
33
        chanPoint wire.OutPoint
34

35
        // chanType denotes the type of channel the contract belongs to.
36
        chanType channeldb.ChannelType
37

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

42
        // reportLock prevents concurrent access to the resolver report.
43
        reportLock sync.Mutex
44

45
        contractResolverKit
46
}
47

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

2✔
53
        amt := btcutil.Amount(anchorSignDescriptor.Output.Value)
2✔
54

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

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

2✔
72
        r.initLogger(fmt.Sprintf("%T(%v)", r, r.anchor))
2✔
73

2✔
74
        return r
2✔
75
}
2✔
76

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

84
// Resolve waits for the output to be swept.
85
//
86
// NOTE: Part of the ContractResolver interface.
87
func (c *anchorResolver) Resolve() (ContractResolver, error) {
2✔
88
        // If we're already resolved, then we can exit early.
2✔
89
        if c.IsResolved() {
2✔
90
                c.log.Errorf("already resolved")
×
91
                return nil, nil
×
92
        }
×
93

94
        var (
2✔
95
                outcome channeldb.ResolverOutcome
2✔
96
                spendTx *chainhash.Hash
2✔
97
        )
2✔
98

2✔
99
        select {
2✔
100
        case sweepRes := <-c.sweepResultChan:
2✔
101
                err := sweepRes.Err
2✔
102

2✔
103
                switch {
2✔
104
                // Anchor was swept successfully.
105
                case err == nil:
2✔
106
                        sweepTxID := sweepRes.Tx.TxHash()
2✔
107

2✔
108
                        spendTx = &sweepTxID
2✔
109
                        outcome = channeldb.ResolverOutcomeClaimed
2✔
110

111
                // Anchor was swept by someone else. This is possible after the
112
                // 16 block csv lock.
113
                case errors.Is(err, sweep.ErrRemoteSpend),
UNCOV
114
                        errors.Is(err, sweep.ErrInputMissing):
×
UNCOV
115

×
UNCOV
116
                        c.log.Warnf("our anchor spent by someone else")
×
UNCOV
117
                        outcome = channeldb.ResolverOutcomeUnclaimed
×
118

119
                // An unexpected error occurred.
120
                default:
×
121
                        c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err)
×
122

×
123
                        return nil, sweepRes.Err
×
124
                }
125

UNCOV
126
        case <-c.quit:
×
UNCOV
127
                return nil, errResolverShuttingDown
×
128
        }
129

130
        c.log.Infof("resolved in tx %v", spendTx)
2✔
131

2✔
132
        // Update report to reflect that funds are no longer in limbo.
2✔
133
        c.reportLock.Lock()
2✔
134
        if outcome == channeldb.ResolverOutcomeClaimed {
4✔
135
                c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
2✔
136
        }
2✔
137
        c.currentReport.LimboBalance = 0
2✔
138
        report := c.currentReport.resolverReport(
2✔
139
                spendTx, channeldb.ResolverTypeAnchor, outcome,
2✔
140
        )
2✔
141
        c.reportLock.Unlock()
2✔
142

2✔
143
        c.markResolved()
2✔
144
        return nil, c.PutResolverReport(nil, report)
2✔
145
}
146

147
// Stop signals the resolver to cancel any current resolution processes, and
148
// suspend.
149
//
150
// NOTE: Part of the ContractResolver interface.
151
func (c *anchorResolver) Stop() {
2✔
152
        c.log.Debugf("stopping...")
2✔
153
        defer c.log.Debugf("stopped")
2✔
154

2✔
155
        close(c.quit)
2✔
156
}
2✔
157

158
// SupplementState allows the user of a ContractResolver to supplement it with
159
// state required for the proper resolution of a contract.
160
//
161
// NOTE: Part of the ContractResolver interface.
162
func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) {
2✔
163
        c.chanType = state.ChanType
2✔
164
}
2✔
165

166
// report returns a report on the resolution state of the contract.
UNCOV
167
func (c *anchorResolver) report() *ContractReport {
×
UNCOV
168
        c.reportLock.Lock()
×
UNCOV
169
        defer c.reportLock.Unlock()
×
UNCOV
170

×
UNCOV
171
        reportCopy := c.currentReport
×
UNCOV
172
        return &reportCopy
×
UNCOV
173
}
×
174

175
func (c *anchorResolver) Encode(w io.Writer) error {
×
176
        return errors.New("serialization not supported")
×
177
}
×
178

179
// A compile time assertion to ensure anchorResolver meets the
180
// ContractResolver interface.
181
var _ ContractResolver = (*anchorResolver)(nil)
182

183
// Launch offers the anchor output to the sweeper.
184
func (c *anchorResolver) Launch() error {
2✔
185
        if c.isLaunched() {
2✔
UNCOV
186
                c.log.Tracef("already launched")
×
UNCOV
187
                return nil
×
UNCOV
188
        }
×
189

190
        c.log.Debugf("launching resolver...")
2✔
191
        c.markLaunched()
2✔
192

2✔
193
        // If we're already resolved, then we can exit early.
2✔
194
        if c.IsResolved() {
2✔
195
                c.log.Errorf("already resolved")
×
196
                return nil
×
197
        }
×
198

199
        // Attempt to update the sweep parameters to the post-confirmation
200
        // situation. We don't want to force sweep anymore, because the anchor
201
        // lost its special purpose to get the commitment confirmed. It is just
202
        // an output that we want to sweep only if it is economical to do so.
203
        //
204
        // An exclusive group is not necessary anymore, because we know that
205
        // this is the only anchor that can be swept. However, to avoid this
206
        // anchor input being group with other inputs, we still keep the
207
        // exclusive group here such that the anchor will be swept
208
        // independently.
209
        //
210
        // We also clear the parent tx information for cpfp, because the
211
        // commitment tx is confirmed.
212
        //
213
        // After a restart or when the remote force closes, the sweeper is not
214
        // yet aware of the anchor. In that case, it will be added as new input
215
        // to the sweeper.
216
        witnessType := input.CommitmentAnchor
2✔
217

2✔
218
        // For taproot channels, we need to use the proper witness type.
2✔
219
        if c.chanType.IsTaproot() {
2✔
UNCOV
220
                witnessType = input.TaprootAnchorSweepSpend
×
UNCOV
221
        }
×
222

223
        anchorInput := input.MakeBaseInput(
2✔
224
                &c.anchor, witnessType, &c.anchorSignDescriptor,
2✔
225
                c.broadcastHeight, nil,
2✔
226
        )
2✔
227

2✔
228
        exclusiveGroup := c.ShortChanID.ToUint64()
2✔
229

2✔
230
        resultChan, err := c.Sweeper.SweepInput(
2✔
231
                &anchorInput,
2✔
232
                sweep.Params{
2✔
233
                        // For normal anchor sweeping, the budget is 330 sats.
2✔
234
                        Budget: btcutil.Amount(
2✔
235
                                anchorInput.SignDesc().Output.Value,
2✔
236
                        ),
2✔
237

2✔
238
                        // There's no rush to sweep the anchor, so we use a nil
2✔
239
                        // deadline here.
2✔
240
                        DeadlineHeight: fn.None[int32](),
2✔
241

2✔
242
                        // Use the chan id as the exclusive group. This prevents
2✔
243
                        // any of the anchors from being batched together.
2✔
244
                        ExclusiveGroup: &exclusiveGroup,
2✔
245
                },
2✔
246
        )
2✔
247

2✔
248
        if err != nil {
2✔
249
                return err
×
250
        }
×
251

252
        c.sweepResultChan = resultChan
2✔
253

2✔
254
        return nil
2✔
255
}
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