-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
htlc_outgoing_contest_resolver.go
218 lines (190 loc) · 6.91 KB
/
htlc_outgoing_contest_resolver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package contractcourt
import (
"fmt"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
)
// htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
// outgoing HTLC that is still contested. An HTLC is still contested, if at the
// time that we broadcast the commitment transaction, it isn't able to be fully
// resolved. In this case, we'll either wait for the HTLC to timeout, or for
// us to learn of the preimage.
type htlcOutgoingContestResolver struct {
// htlcTimeoutResolver is the inner solver that this resolver may turn
// into. This only happens if the HTLC expires on-chain.
*htlcTimeoutResolver
}
// newOutgoingContestResolver instantiates a new outgoing contested htlc
// resolver.
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcOutgoingContestResolver {
timeout := newTimeoutResolver(
res, broadcastHeight, htlc, resCfg,
)
return &htlcOutgoingContestResolver{
htlcTimeoutResolver: timeout,
}
}
// Resolve commences the resolution of this contract. As this contract hasn't
// yet timed out, we'll wait for one of two things to happen
//
// 1. The HTLC expires. In this case, we'll sweep the funds and send a clean
// up cancel message to outside sub-systems.
//
// 2. The remote party sweeps this HTLC on-chain, in which case we'll add the
// pre-image to our global cache, then send a clean up settle message
// backwards.
//
// When either of these two things happens, we'll create a new resolver which
// is able to handle the final resolution of the contract. We're only the pivot
// point.
func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
// If we're already full resolved, then we don't have anything further
// to do.
if h.resolved {
return nil, nil
}
// Otherwise, we'll watch for two external signals to decide if we'll
// morph into another resolver, or fully resolve the contract.
//
// The output we'll be watching for is the *direct* spend from the HTLC
// output. If this isn't our commitment transaction, it'll be right on
// the resolution. Otherwise, we fetch this pointer from the input of
// the time out transaction.
outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
if err != nil {
return nil, err
}
// First, we'll register for a spend notification for this output. If
// the remote party sweeps with the pre-image, we'll be notified.
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
outPointToWatch, scriptToWatch, h.broadcastHeight,
)
if err != nil {
return nil, err
}
// We'll quickly check to see if the output has already been spent.
select {
// If the output has already been spent, then we can stop early and
// sweep the pre-image from the output.
case commitSpend, ok := <-spendNtfn.Spend:
if !ok {
return nil, errResolverShuttingDown
}
// TODO(roasbeef): Checkpoint?
return h.claimCleanUp(commitSpend)
// If it hasn't, then we'll watch for both the expiration, and the
// sweeping out this output.
default:
}
// If we reach this point, then we can't fully act yet, so we'll await
// either of our signals triggering: the HTLC expires, or we learn of
// the preimage.
blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return nil, err
}
defer blockEpochs.Cancel()
for {
select {
// A new block has arrived, we'll check to see if this leads to
// HTLC expiration.
case newBlock, ok := <-blockEpochs.Epochs:
if !ok {
return nil, errResolverShuttingDown
}
// If the current height is >= expiry-1, then a timeout
// path spend will be valid to be included in the next
// block, and we can immediately return the resolver.
//
// TODO(joostjager): Statement above may not be valid.
// For CLTV locks, the expiry value is the last
// _invalid_ block. The likely reason that this does not
// create a problem, is that utxonursery is checking the
// expiry again (in the proper way).
//
// Source:
// https://github.com/btcsuite/btcd/blob/991d32e72fe84d5fbf9c47cd604d793a0cd3a072/blockchain/validate.go#L154
newHeight := uint32(newBlock.Height)
if newHeight >= h.htlcResolution.Expiry-1 {
log.Infof("%T(%v): HTLC has expired "+
"(height=%v, expiry=%v), transforming "+
"into timeout resolver", h,
h.htlcResolution.ClaimOutpoint,
newHeight, h.htlcResolution.Expiry)
return h.htlcTimeoutResolver, nil
}
// The output has been spent! This means the preimage has been
// revealed on-chain.
case commitSpend, ok := <-spendNtfn.Spend:
if !ok {
return nil, errResolverShuttingDown
}
// The only way this output can be spent by the remote
// party is by revealing the preimage. So we'll perform
// our duties to clean up the contract once it has been
// claimed.
return h.claimCleanUp(commitSpend)
case <-h.quit:
return nil, fmt.Errorf("resolver canceled")
}
}
}
// report returns a report on the resolution state of the contract.
func (h *htlcOutgoingContestResolver) report() *ContractReport {
// No locking needed as these values are read-only.
finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedTimeoutTx != nil {
finalAmt = btcutil.Amount(
h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
)
}
return &ContractReport{
Outpoint: h.htlcResolution.ClaimOutpoint,
Type: ReportOutputOutgoingHtlc,
Amount: finalAmt,
MaturityHeight: h.htlcResolution.Expiry,
LimboBalance: finalAmt,
Stage: 1,
}
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcOutgoingContestResolver) Stop() {
close(h.quit)
}
// IsResolved returns true if the stored state in the resolve is fully
// resolved. In this case the target output can be forgotten.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcOutgoingContestResolver) IsResolved() bool {
return h.resolved
}
// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
return h.htlcTimeoutResolver.Encode(w)
}
// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
// from the passed Reader instance, returning an active ContractResolver
// instance.
func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*htlcOutgoingContestResolver, error) {
h := &htlcOutgoingContestResolver{}
timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
if err != nil {
return nil, err
}
h.htlcTimeoutResolver = timeoutResolver
return h, nil
}
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)