forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
walletsweep_test.go
392 lines (323 loc) · 9.93 KB
/
walletsweep_test.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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
package sweep
import (
"bytes"
"fmt"
"testing"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// TestDetermineFeePerKw tests that given a fee preference, the
// DetermineFeePerKw will properly map it to a concrete fee in sat/kw.
func TestDetermineFeePerKw(t *testing.T) {
t.Parallel()
defaultFee := chainfee.SatPerKWeight(999)
relayFee := chainfee.SatPerKWeight(300)
feeEstimator := newMockFeeEstimator(defaultFee, relayFee)
// We'll populate two items in the internal map which is used to query
// a fee based on a confirmation target: the default conf target, and
// an arbitrary conf target. We'll ensure below that both of these are
// properly
feeEstimator.blocksToFee[50] = 300
feeEstimator.blocksToFee[defaultNumBlocksEstimate] = 1000
testCases := []struct {
// feePref is the target fee preference for this case.
feePref FeePreference
// fee is the value the DetermineFeePerKw should return given
// the FeePreference above
fee chainfee.SatPerKWeight
// fail determines if this test case should fail or not.
fail bool
}{
// A fee rate below the fee rate floor should output the floor.
{
feePref: FeePreference{
FeeRate: chainfee.SatPerKWeight(99),
},
fee: chainfee.FeePerKwFloor,
},
// A fee rate above the floor, should pass through and return
// the target fee rate.
{
feePref: FeePreference{
FeeRate: 900,
},
fee: 900,
},
// A specified confirmation target should cause the function to
// query the estimator which will return our value specified
// above.
{
feePref: FeePreference{
ConfTarget: 50,
},
fee: 300,
},
// If the caller doesn't specify any values at all, then we
// should query for the default conf target.
{
feePref: FeePreference{},
fee: 1000,
},
// Both conf target and fee rate are set, we should return with
// an error.
{
feePref: FeePreference{
ConfTarget: 50,
FeeRate: 90000,
},
fee: 300,
fail: true,
},
}
for i, testCase := range testCases {
targetFee, err := DetermineFeePerKw(
feeEstimator, testCase.feePref,
)
switch {
case testCase.fail && err != nil:
continue
case testCase.fail && err == nil:
t.Fatalf("expected failure for #%v", i)
case !testCase.fail && err != nil:
t.Fatalf("unable to estimate fee; %v", err)
}
if targetFee != testCase.fee {
t.Fatalf("#%v: wrong fee: expected %v got %v", i,
testCase.fee, targetFee)
}
}
}
type mockUtxoSource struct {
outputs []*lnwallet.Utxo
}
func newMockUtxoSource(utxos []*lnwallet.Utxo) *mockUtxoSource {
return &mockUtxoSource{
outputs: utxos,
}
}
func (m *mockUtxoSource) ListUnspentWitness(minConfs int32,
maxConfs int32) ([]*lnwallet.Utxo, error) {
return m.outputs, nil
}
type mockCoinSelectionLocker struct {
fail bool
}
func (m *mockCoinSelectionLocker) WithCoinSelectLock(f func() error) error {
if err := f(); err != nil {
return err
}
if m.fail {
return fmt.Errorf("kek")
}
return nil
}
type mockOutpointLocker struct {
lockedOutpoints map[wire.OutPoint]struct{}
unlockedOutpoints map[wire.OutPoint]struct{}
}
func newMockOutpointLocker() *mockOutpointLocker {
return &mockOutpointLocker{
lockedOutpoints: make(map[wire.OutPoint]struct{}),
unlockedOutpoints: make(map[wire.OutPoint]struct{}),
}
}
func (m *mockOutpointLocker) LockOutpoint(o wire.OutPoint) {
m.lockedOutpoints[o] = struct{}{}
}
func (m *mockOutpointLocker) UnlockOutpoint(o wire.OutPoint) {
m.unlockedOutpoints[o] = struct{}{}
}
var sweepScript = []byte{
0x0, 0x14, 0x64, 0x3d, 0x8b, 0x15, 0x69, 0x4a, 0x54,
0x7d, 0x57, 0x33, 0x6e, 0x51, 0xdf, 0xfd, 0x38, 0xe3,
0xe, 0x6e, 0xf8, 0xef,
}
var deliveryAddr = func() btcutil.Address {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
sweepScript, &chaincfg.TestNet3Params,
)
if err != nil {
panic(err)
}
return addrs[0]
}()
var testUtxos = []*lnwallet.Utxo{
{
// A p2wkh output.
AddressType: lnwallet.WitnessPubKey,
PkScript: []byte{
0x0, 0x14, 0x64, 0x3d, 0x8b, 0x15, 0x69, 0x4a, 0x54,
0x7d, 0x57, 0x33, 0x6e, 0x51, 0xdf, 0xfd, 0x38, 0xe3,
0xe, 0x6e, 0xf7, 0xef,
},
Value: 1000,
OutPoint: wire.OutPoint{
Index: 1,
},
},
{
// A np2wkh output.
AddressType: lnwallet.NestedWitnessPubKey,
PkScript: []byte{
0xa9, 0x14, 0x97, 0x17, 0xf7, 0xd1, 0x5f, 0x6f, 0x8b,
0x7, 0xe3, 0x58, 0x43, 0x19, 0xb9, 0x7e, 0xa9, 0x20,
0x18, 0xc3, 0x17, 0xd7, 0x87,
},
Value: 2000,
OutPoint: wire.OutPoint{
Index: 2,
},
},
// A p2wsh output.
{
AddressType: lnwallet.UnknownAddressType,
PkScript: []byte{
0x0, 0x20, 0x70, 0x1a, 0x8d, 0x40, 0x1c, 0x84, 0xfb, 0x13,
0xe6, 0xba, 0xf1, 0x69, 0xd5, 0x96, 0x84, 0xe2, 0x7a, 0xbd,
0x9f, 0xa2, 0x16, 0xc8, 0xbc, 0x5b, 0x9f, 0xc6, 0x3d, 0x62,
0x2f, 0xf8, 0xc5, 0x8c,
},
Value: 3000,
OutPoint: wire.OutPoint{
Index: 3,
},
},
}
func assertUtxosLocked(t *testing.T, utxoLocker *mockOutpointLocker,
utxos []*lnwallet.Utxo) {
t.Helper()
for _, utxo := range utxos {
if _, ok := utxoLocker.lockedOutpoints[utxo.OutPoint]; !ok {
t.Fatalf("utxo %v was never locked", utxo.OutPoint)
}
}
}
func assertNoUtxosUnlocked(t *testing.T, utxoLocker *mockOutpointLocker,
utxos []*lnwallet.Utxo) {
t.Helper()
if len(utxoLocker.unlockedOutpoints) != 0 {
t.Fatalf("outputs have been locked, but shouldn't have been")
}
}
func assertUtxosUnlocked(t *testing.T, utxoLocker *mockOutpointLocker,
utxos []*lnwallet.Utxo) {
t.Helper()
for _, utxo := range utxos {
if _, ok := utxoLocker.unlockedOutpoints[utxo.OutPoint]; !ok {
t.Fatalf("utxo %v was never unlocked", utxo.OutPoint)
}
}
}
func assertUtxosLockedAndUnlocked(t *testing.T, utxoLocker *mockOutpointLocker,
utxos []*lnwallet.Utxo) {
t.Helper()
for _, utxo := range utxos {
if _, ok := utxoLocker.lockedOutpoints[utxo.OutPoint]; !ok {
t.Fatalf("utxo %v was never locked", utxo.OutPoint)
}
if _, ok := utxoLocker.unlockedOutpoints[utxo.OutPoint]; !ok {
t.Fatalf("utxo %v was never unlocked", utxo.OutPoint)
}
}
}
// TestCraftSweepAllTxCoinSelectFail tests that if coin selection fails, then
// we unlock any outputs we may have locked in the passed closure.
func TestCraftSweepAllTxCoinSelectFail(t *testing.T) {
t.Parallel()
utxoSource := newMockUtxoSource(testUtxos)
coinSelectLocker := &mockCoinSelectionLocker{
fail: true,
}
utxoLocker := newMockOutpointLocker()
_, err := CraftSweepAllTx(
0, 100, nil, coinSelectLocker, utxoSource, utxoLocker, nil, nil,
)
// Since we instructed the coin select locker to fail above, we should
// get an error.
if err == nil {
t.Fatalf("sweep tx should have failed: %v", err)
}
// At this point, we'll now verify that all outputs were initially
// locked, and then also unlocked due to the failure.
assertUtxosLockedAndUnlocked(t, utxoLocker, testUtxos)
}
// TestCraftSweepAllTxUnknownWitnessType tests that if one of the inputs we
// encounter is of an unknown witness type, then we fail and unlock any prior
// locked outputs.
func TestCraftSweepAllTxUnknownWitnessType(t *testing.T) {
t.Parallel()
utxoSource := newMockUtxoSource(testUtxos)
coinSelectLocker := &mockCoinSelectionLocker{}
utxoLocker := newMockOutpointLocker()
_, err := CraftSweepAllTx(
0, 100, nil, coinSelectLocker, utxoSource, utxoLocker, nil, nil,
)
// Since passed in a p2wsh output, which is unknown, we should fail to
// map the output to a witness type.
if err == nil {
t.Fatalf("sweep tx should have failed: %v", err)
}
// At this point, we'll now verify that all outputs were initially
// locked, and then also unlocked since we weren't able to find a
// witness type for the last output.
assertUtxosLockedAndUnlocked(t, utxoLocker, testUtxos)
}
// TestCraftSweepAllTx tests that we'll properly lock all available outputs
// within the wallet, and craft a single sweep transaction that pays to the
// target output.
func TestCraftSweepAllTx(t *testing.T) {
t.Parallel()
// First, we'll make a mock signer along with a fee estimator, We'll
// use zero fees to we can assert a precise output value.
signer := &mockSigner{}
feeEstimator := newMockFeeEstimator(0, 0)
// For our UTXO source, we'll pass in all the UTXOs that we know of,
// other than the final one which is of an unknown witness type.
targetUTXOs := testUtxos[:2]
utxoSource := newMockUtxoSource(targetUTXOs)
coinSelectLocker := &mockCoinSelectionLocker{}
utxoLocker := newMockOutpointLocker()
sweepPkg, err := CraftSweepAllTx(
0, 100, deliveryAddr, coinSelectLocker, utxoSource, utxoLocker,
feeEstimator, signer,
)
if err != nil {
t.Fatalf("unable to make sweep tx: %v", err)
}
// At this point, all of the UTXOs that we made above should be locked
// and none of them unlocked.
assertUtxosLocked(t, utxoLocker, testUtxos[:2])
assertNoUtxosUnlocked(t, utxoLocker, testUtxos[:2])
// Now that we have our sweep transaction, we should find that we have
// a UTXO for each input, and also that our final output value is the
// sum of all our inputs.
sweepTx := sweepPkg.SweepTx
if len(sweepTx.TxIn) != len(targetUTXOs) {
t.Fatalf("expected %v utxo, got %v", len(targetUTXOs),
len(sweepTx.TxIn))
}
// We should have a single output that pays to our sweep script
// generated above.
expectedSweepValue := int64(3000)
if len(sweepTx.TxOut) != 1 {
t.Fatalf("should have %v outputs, instead have %v", 1,
len(sweepTx.TxOut))
}
output := sweepTx.TxOut[0]
switch {
case output.Value != expectedSweepValue:
t.Fatalf("expected %v sweep value, instead got %v",
expectedSweepValue, output.Value)
case !bytes.Equal(sweepScript, output.PkScript):
t.Fatalf("expected %x sweep script, instead got %x", sweepScript,
output.PkScript)
}
// If we cancel the sweep attempt, then we should find that all the
// UTXOs within the sweep transaction are now unlocked.
sweepPkg.CancelSweepAttempt()
assertUtxosUnlocked(t, utxoLocker, testUtxos[:2])
}