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

lightningnetwork / lnd / 12343072627

15 Dec 2024 11:09PM UTC coverage: 57.504% (-1.1%) from 58.636%
12343072627

Pull #9315

github

yyforyongyu
contractcourt: offer outgoing htlc one block earlier before its expiry

We need to offer the outgoing htlc one block earlier to make sure when
the expiry height hits, the sweeper will not miss sweeping it in the
same block. This also means the outgoing contest resolver now only does
one thing - watch for preimage spend till height expiry-1, which can
easily be moved into the timeout resolver instead in the future.
Pull Request #9315: Implement `blockbeat`

1445 of 2007 new or added lines in 26 files covered. (72.0%)

19246 existing lines in 249 files now uncovered.

102342 of 177975 relevant lines covered (57.5%)

24772.24 hits per line

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

76.3
/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✔
NEW
90
                c.log.Errorf("already resolved")
×
NEW
91
                return nil, nil
×
UNCOV
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
                switch sweepRes.Err {
2✔
102
                // Anchor was swept successfully.
103
                case nil:
2✔
104
                        sweepTxID := sweepRes.Tx.TxHash()
2✔
105

2✔
106
                        spendTx = &sweepTxID
2✔
107
                        outcome = channeldb.ResolverOutcomeClaimed
2✔
108

109
                // Anchor was swept by someone else. This is possible after the
110
                // 16 block csv lock.
UNCOV
111
                case sweep.ErrRemoteSpend:
×
UNCOV
112
                        c.log.Warnf("our anchor spent by someone else")
×
UNCOV
113
                        outcome = channeldb.ResolverOutcomeUnclaimed
×
114

115
                // An unexpected error occurred.
116
                default:
×
117
                        c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err)
×
118

×
119
                        return nil, sweepRes.Err
×
120
                }
121

UNCOV
122
        case <-c.quit:
×
UNCOV
123
                return nil, errResolverShuttingDown
×
124
        }
125

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

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

2✔
139
        c.markResolved()
2✔
140
        return nil, c.PutResolverReport(nil, report)
2✔
141
}
142

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

2✔
151
        close(c.quit)
2✔
152
}
2✔
153

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

162
// report returns a report on the resolution state of the contract.
UNCOV
163
func (c *anchorResolver) report() *ContractReport {
×
UNCOV
164
        c.reportLock.Lock()
×
UNCOV
165
        defer c.reportLock.Unlock()
×
UNCOV
166

×
UNCOV
167
        reportCopy := c.currentReport
×
UNCOV
168
        return &reportCopy
×
UNCOV
169
}
×
170

171
func (c *anchorResolver) Encode(w io.Writer) error {
×
172
        return errors.New("serialization not supported")
×
173
}
×
174

175
// A compile time assertion to ensure anchorResolver meets the
176
// ContractResolver interface.
177
var _ ContractResolver = (*anchorResolver)(nil)
178

179
// Launch offers the anchor output to the sweeper.
180
func (c *anchorResolver) Launch() error {
2✔
181
        if c.isLaunched() {
2✔
NEW
182
                c.log.Tracef("already launched")
×
NEW
183
                return nil
×
NEW
184
        }
×
185

186
        c.log.Debugf("launching resolver...")
2✔
187
        c.markLaunched()
2✔
188

2✔
189
        // If we're already resolved, then we can exit early.
2✔
190
        if c.IsResolved() {
2✔
NEW
191
                c.log.Errorf("already resolved")
×
NEW
192
                return nil
×
NEW
193
        }
×
194

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

2✔
211
        // For taproot channels, we need to use the proper witness type.
2✔
212
        if c.chanType.IsTaproot() {
2✔
NEW
213
                witnessType = input.TaprootAnchorSweepSpend
×
NEW
214
        }
×
215

216
        anchorInput := input.MakeBaseInput(
2✔
217
                &c.anchor, witnessType, &c.anchorSignDescriptor,
2✔
218
                c.broadcastHeight, nil,
2✔
219
        )
2✔
220

2✔
221
        resultChan, err := c.Sweeper.SweepInput(
2✔
222
                &anchorInput,
2✔
223
                sweep.Params{
2✔
224
                        // For normal anchor sweeping, the budget is 330 sats.
2✔
225
                        Budget: btcutil.Amount(
2✔
226
                                anchorInput.SignDesc().Output.Value,
2✔
227
                        ),
2✔
228

2✔
229
                        // There's no rush to sweep the anchor, so we use a nil
2✔
230
                        // deadline here.
2✔
231
                        DeadlineHeight: fn.None[int32](),
2✔
232
                },
2✔
233
        )
2✔
234

2✔
235
        if err != nil {
2✔
NEW
236
                return err
×
NEW
237
        }
×
238

239
        c.sweepResultChan = resultChan
2✔
240

2✔
241
        return nil
2✔
242
}
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