forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
walletsweep.go
286 lines (243 loc) · 9.64 KB
/
walletsweep.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package sweep
import (
"fmt"
"math"
"github.com/Actinium-project/acmd/txscript"
"github.com/Actinium-project/acmd/wire"
"github.com/Actinium-project/acmutil"
"github.com/Actinium-project/lnd/input"
"github.com/Actinium-project/lnd/lnwallet"
"github.com/Actinium-project/lnd/lnwallet/chainfee"
)
const (
// defaultNumBlocksEstimate is the number of blocks that we fall back
// to issuing an estimate for if a fee pre fence doesn't specify an
// explicit conf target or fee rate.
defaultNumBlocksEstimate = 6
)
// FeePreference allows callers to express their time value for inclusion of a
// transaction into a block via either a confirmation target, or a fee rate.
type FeePreference struct {
// ConfTarget if non-zero, signals a fee preference expressed in the
// number of desired blocks between first broadcast, and confirmation.
ConfTarget uint32
// FeeRate if non-zero, signals a fee pre fence expressed in the fee
// rate expressed in sat/kw for a particular transaction.
FeeRate chainfee.SatPerKWeight
}
// String returns a human-readable string of the fee preference.
func (p FeePreference) String() string {
if p.ConfTarget != 0 {
return fmt.Sprintf("%v blocks", p.ConfTarget)
}
return p.FeeRate.String()
}
// DetermineFeePerKw will determine the fee in sat/kw that should be paid given
// an estimator, a confirmation target, and a manual value for sat/byte. A
// value is chosen based on the two free parameters as one, or both of them can
// be zero.
func DetermineFeePerKw(feeEstimator chainfee.Estimator,
feePref FeePreference) (chainfee.SatPerKWeight, error) {
switch {
// If both values are set, then we'll return an error as we require a
// strict directive.
case feePref.FeeRate != 0 && feePref.ConfTarget != 0:
return 0, fmt.Errorf("only FeeRate or ConfTarget should " +
"be set for FeePreferences")
// If the target number of confirmations is set, then we'll use that to
// consult our fee estimator for an adequate fee.
case feePref.ConfTarget != 0:
feePerKw, err := feeEstimator.EstimateFeePerKW(
uint32(feePref.ConfTarget),
)
if err != nil {
return 0, fmt.Errorf("unable to query fee "+
"estimator: %v", err)
}
return feePerKw, nil
// If a manual sat/byte fee rate is set, then we'll use that directly.
// We'll need to convert it to sat/kw as this is what we use
// internally.
case feePref.FeeRate != 0:
feePerKW := feePref.FeeRate
if feePerKW < chainfee.FeePerKwFloor {
log.Infof("Manual fee rate input of %d sat/kw is "+
"too low, using %d sat/kw instead", feePerKW,
chainfee.FeePerKwFloor)
feePerKW = chainfee.FeePerKwFloor
}
return feePerKW, nil
// Otherwise, we'll attempt a relaxed confirmation target for the
// transaction
default:
feePerKw, err := feeEstimator.EstimateFeePerKW(
defaultNumBlocksEstimate,
)
if err != nil {
return 0, fmt.Errorf("unable to query fee estimator: "+
"%v", err)
}
return feePerKw, nil
}
}
// UtxoSource is an interface that allows a caller to access a source of UTXOs
// to use when crafting sweep transactions.
type UtxoSource interface {
// ListUnspentWitness returns all UTXOs from the source that have
// between minConfs and maxConfs number of confirmations.
ListUnspentWitness(minConfs, maxConfs int32) ([]*lnwallet.Utxo, error)
}
// CoinSelectionLocker is an interface that allows the caller to perform an
// operation, which is synchronized with all coin selection attempts. This can
// be used when an operation requires that all coin selection operations cease
// forward progress. Think of this as an exclusive lock on coin selection
// operations.
type CoinSelectionLocker interface {
// WithCoinSelectLock will execute the passed function closure in a
// synchronized manner preventing any coin selection operations from
// proceeding while the closure if executing. This can be seen as the
// ability to execute a function closure under an exclusive coin
// selection lock.
WithCoinSelectLock(func() error) error
}
// OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the
// outpoints shouldn't be used for any sort of channel funding of coin
// selection. Locked outpoints are not expect to be persisted between restarts.
type OutpointLocker interface {
// LockOutpoint locks a target outpoint, rendering it unusable for coin
// selection.
LockOutpoint(o wire.OutPoint)
// UnlockOutpoint unlocks a target outpoint, allowing it to be used for
// coin selection once again.
UnlockOutpoint(o wire.OutPoint)
}
// WalletSweepPackage is a package that gives the caller the ability to sweep
// ALL funds from a wallet in a single transaction. We also package a function
// closure that allows one to abort the operation.
type WalletSweepPackage struct {
// SweepTx is a fully signed, and valid transaction that is broadcast,
// will sweep ALL confirmed coins in the wallet with a single
// transaction.
SweepTx *wire.MsgTx
// CancelSweepAttempt allows the caller to cancel the sweep attempt.
//
// NOTE: If the sweeping transaction isn't or cannot be broadcast, then
// this closure MUST be called, otherwise all selected utxos will be
// unable to be used.
CancelSweepAttempt func()
}
// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
// caller to sweep ALL outputs within the wallet to a single UTXO, as specified
// by the delivery address. The sweep transaction will be crafted with the
// target fee rate, and will use the utxoSource and outpointLocker as sources
// for wallet funds.
func CraftSweepAllTx(feeRate chainfee.SatPerKWeight, blockHeight uint32,
deliveryAddr btcutil.Address, coinSelectLocker CoinSelectionLocker,
utxoSource UtxoSource, outpointLocker OutpointLocker,
feeEstimator chainfee.Estimator,
signer input.Signer) (*WalletSweepPackage, error) {
// TODO(roasbeef): turn off ATPL as well when available?
var allOutputs []*lnwallet.Utxo
// We'll make a function closure up front that allows us to unlock all
// selected outputs to ensure that they become available again in the
// case of an error after the outputs have been locked, but before we
// can actually craft a sweeping transaction.
unlockOutputs := func() {
for _, utxo := range allOutputs {
outpointLocker.UnlockOutpoint(utxo.OutPoint)
}
}
// Next, we'll use the coinSelectLocker to ensure that no coin
// selection takes place while we fetch and lock all outputs the wallet
// knows of. Otherwise, it may be possible for a new funding flow to
// lock an output while we fetch the set of unspent witnesses.
err := coinSelectLocker.WithCoinSelectLock(func() error {
// Now that we can be sure that no other coin selection
// operations are going on, we can grab a clean snapshot of the
// current UTXO state of the wallet.
utxos, err := utxoSource.ListUnspentWitness(
1, math.MaxInt32,
)
if err != nil {
return err
}
// We'll now lock each UTXO to ensure that other callers don't
// attempt to use these UTXOs in transactions while we're
// crafting out sweep all transaction.
for _, utxo := range utxos {
outpointLocker.LockOutpoint(utxo.OutPoint)
}
allOutputs = append(allOutputs, utxos...)
return nil
})
if err != nil {
// If we failed at all, we'll unlock any outputs selected just
// in case we had any lingering outputs.
unlockOutputs()
return nil, fmt.Errorf("unable to fetch+lock wallet "+
"utxos: %v", err)
}
// Now that we've locked all the potential outputs to sweep, we'll
// assemble an input for each of them, so we can hand it off to the
// sweeper to generate and sign a transaction for us.
var inputsToSweep []input.Input
for _, output := range allOutputs {
// As we'll be signing for outputs under control of the wallet,
// we only need to populate the output value and output script.
// The rest of the items will be populated internally within
// the sweeper via the witness generation function.
signDesc := &input.SignDescriptor{
Output: &wire.TxOut{
PkScript: output.PkScript,
Value: int64(output.Value),
},
HashType: txscript.SigHashAll,
}
pkScript := output.PkScript
// Based on the output type, we'll map it to the proper witness
// type so we can generate the set of input scripts needed to
// sweep the output.
var witnessType input.WitnessType
switch output.AddressType {
// If this is a p2wkh output, then we'll assume it's a witness
// key hash witness type.
case lnwallet.WitnessPubKey:
witnessType = input.WitnessKeyHash
// If this is a p2sh output, then as since it's under control
// of the wallet, we'll assume it's a nested p2sh output.
case lnwallet.NestedWitnessPubKey:
witnessType = input.NestedWitnessKeyHash
// All other output types we count as unknown and will fail to
// sweep.
default:
unlockOutputs()
return nil, fmt.Errorf("unable to sweep coins, "+
"unknown script: %x", pkScript[:])
}
// Now that we've constructed the items required, we'll make an
// input which can be passed to the sweeper for ultimate
// sweeping.
input := input.MakeBaseInput(&output.OutPoint, witnessType, signDesc, 0)
inputsToSweep = append(inputsToSweep, &input)
}
// Next, we'll convert the delivery addr to a pkScript that we can use
// to create the sweep transaction.
deliveryPkScript, err := txscript.PayToAddrScript(deliveryAddr)
if err != nil {
unlockOutputs()
return nil, err
}
// Finally, we'll ask the sweeper to craft a sweep transaction which
// respects our fee preference and targets all the UTXOs of the wallet.
sweepTx, err := createSweepTx(
inputsToSweep, deliveryPkScript, blockHeight, feeRate, signer,
)
if err != nil {
unlockOutputs()
return nil, err
}
return &WalletSweepPackage{
SweepTx: sweepTx,
CancelSweepAttempt: unlockOutputs,
}, nil
}