github.com/decred/dcrlnd@v0.7.6/sweep/walletsweep_test.go (about) 1 package sweep 2 3 import ( 4 "bytes" 5 "fmt" 6 "testing" 7 8 "github.com/decred/dcrd/chaincfg/v3" 9 "github.com/decred/dcrd/txscript/v4/stdaddr" 10 "github.com/decred/dcrd/txscript/v4/stdscript" 11 "github.com/decred/dcrd/wire" 12 "github.com/decred/dcrlnd/lntest/mock" 13 "github.com/decred/dcrlnd/lnwallet" 14 "github.com/decred/dcrlnd/lnwallet/chainfee" 15 ) 16 17 // TestDetermineFeePerKB tests that given a fee preference, the 18 // DetermineFeePerKB will properly map it to a concrete fee in atom/KB. 19 func TestDetermineFeePerKB(t *testing.T) { 20 t.Parallel() 21 22 defaultFee := chainfee.AtomPerKByte(99999) 23 relayFee := chainfee.AtomPerKByte(30000) 24 25 feeEstimator := newMockFeeEstimator(defaultFee, relayFee) 26 27 // We'll populate two items in the internal map which is used to query 28 // a fee based on a confirmation target: the default conf target, and 29 // an arbitrary conf target. We'll ensure below that both of these are 30 // properly 31 feeEstimator.blocksToFee[50] = 30000 32 feeEstimator.blocksToFee[defaultNumBlocksEstimate] = 20000 33 34 testCases := []struct { 35 // feePref is the target fee preference for this case. 36 feePref FeePreference 37 38 // fee is the value the DetermineFeePerKB should return given 39 // the FeePreference above 40 fee chainfee.AtomPerKByte 41 42 // fail determines if this test case should fail or not. 43 fail bool 44 }{ 45 // A fee rate below the fee rate floor should output the floor. 46 { 47 feePref: FeePreference{ 48 FeeRate: chainfee.AtomPerKByte(99), 49 }, 50 fee: chainfee.FeePerKBFloor, 51 }, 52 53 // A fee rate above the floor, should pass through and return 54 // the target fee rate. 55 { 56 feePref: FeePreference{ 57 FeeRate: 90000, 58 }, 59 fee: 90000, 60 }, 61 62 // A specified confirmation target should cause the function to 63 // query the estimator which will return our value specified 64 // above. 65 { 66 feePref: FeePreference{ 67 ConfTarget: 50, 68 }, 69 fee: 30000, 70 }, 71 72 // If the caller doesn't specify any values at all, then we 73 // should query for the default conf target. 74 { 75 feePref: FeePreference{}, 76 fee: 20000, 77 }, 78 79 // Both conf target and fee rate are set, we should return with 80 // an error. 81 { 82 feePref: FeePreference{ 83 ConfTarget: 50, 84 FeeRate: 90000, 85 }, 86 fee: 30000, 87 fail: true, 88 }, 89 } 90 for i, testCase := range testCases { 91 targetFee, err := DetermineFeePerKB( 92 feeEstimator, testCase.feePref, 93 ) 94 switch { 95 case testCase.fail && err != nil: 96 continue 97 98 case testCase.fail && err == nil: 99 t.Fatalf("expected failure for #%v", i) 100 101 case !testCase.fail && err != nil: 102 t.Fatalf("unable to estimate fee; %v", err) 103 } 104 105 if targetFee != testCase.fee { 106 t.Fatalf("#%v: wrong fee: expected %v got %v", i, 107 testCase.fee, targetFee) 108 } 109 } 110 } 111 112 type mockUtxoSource struct { 113 outputs []*lnwallet.Utxo 114 } 115 116 func newMockUtxoSource(utxos []*lnwallet.Utxo) *mockUtxoSource { 117 return &mockUtxoSource{ 118 outputs: utxos, 119 } 120 } 121 122 func (m *mockUtxoSource) ListUnspentWitnessFromDefaultAccount(minConfs int32, 123 maxConfs int32) ([]*lnwallet.Utxo, error) { 124 125 return m.outputs, nil 126 } 127 128 type mockCoinSelectionLocker struct { 129 fail bool 130 } 131 132 func (m *mockCoinSelectionLocker) WithCoinSelectLock(f func() error) error { 133 if err := f(); err != nil { 134 return err 135 } 136 137 if m.fail { 138 return fmt.Errorf("kek") 139 } 140 141 return nil 142 143 } 144 145 type mockOutpointLocker struct { 146 lockedOutpoints map[wire.OutPoint]struct{} 147 148 unlockedOutpoints map[wire.OutPoint]struct{} 149 } 150 151 func newMockOutpointLocker() *mockOutpointLocker { 152 return &mockOutpointLocker{ 153 lockedOutpoints: make(map[wire.OutPoint]struct{}), 154 155 unlockedOutpoints: make(map[wire.OutPoint]struct{}), 156 } 157 } 158 159 func (m *mockOutpointLocker) LockOutpoint(o wire.OutPoint) { 160 m.lockedOutpoints[o] = struct{}{} 161 } 162 func (m *mockOutpointLocker) UnlockOutpoint(o wire.OutPoint) { 163 m.unlockedOutpoints[o] = struct{}{} 164 } 165 166 var sweepScript = []byte{ 167 0x76, 0xa9, 0x14, 0x3d, 0x8b, 0x15, 0x69, 0x4a, 0x54, 168 0x7d, 0x57, 0x33, 0x6e, 0x51, 0xdf, 0xfd, 0x38, 0xe3, 169 0x0e, 0x6e, 0xf8, 0xef, 0x20, 0x88, 0xac, 170 } 171 172 var deliveryAddr = func() stdaddr.Address { 173 _, addrs := stdscript.ExtractAddrs( 174 0, sweepScript, chaincfg.TestNet3Params(), 175 ) 176 return addrs[0] 177 }() 178 179 var testUtxos = []*lnwallet.Utxo{ 180 { 181 // A p2pkh output. 182 PkScript: []byte{ 183 0x76, 0xa9, 0x14, 0x3d, 0x8b, 0x15, 0x69, 0x4a, 0x54, 184 0x7d, 0x57, 0x33, 0x6e, 0x51, 0xdf, 0xfd, 0x38, 0xe3, 185 0x0e, 0x6e, 0xf7, 0xef, 0x20, 0x88, 0xac, 186 }, 187 Value: 10000, 188 OutPoint: wire.OutPoint{ 189 Index: 1, 190 }, 191 }, 192 193 { 194 // Another p2pkh output. 195 PkScript: []byte{ 196 0x76, 0xa9, 0x14, 0x17, 0xf7, 0xd1, 0x5f, 0x6f, 0x8b, 197 0x07, 0xe3, 0x58, 0x43, 0x19, 0xb9, 0x7e, 0xa9, 0x20, 198 0x18, 0xc3, 0x17, 0xd7, 0x87, 0x88, 0xac, 199 }, 200 Value: 20000, 201 OutPoint: wire.OutPoint{ 202 Index: 2, 203 }, 204 }, 205 206 // A p2wsh output. The sweeper shouldn't (currently) know how to spend 207 // this, thus will fail whenever a utxo of this type is included in the 208 // list of outputs to sweep. 209 { 210 AddressType: lnwallet.UnknownAddressType, 211 PkScript: []byte{ 212 0x0, 0x20, 0x70, 0x1a, 0x8d, 0x40, 0x1c, 0x84, 0xfb, 0x13, 213 0xe6, 0xba, 0xf1, 0x69, 0xd5, 0x96, 0x84, 0xe2, 0x7a, 0xbd, 214 0x9f, 0xa2, 0x16, 0xc8, 0xbc, 0x5b, 0x9f, 0xc6, 0x3d, 0x62, 215 0x2f, 0xf8, 0xc5, 0x8c, 216 }, 217 Value: 30000, 218 OutPoint: wire.OutPoint{ 219 Index: 3, 220 }, 221 }, 222 } 223 224 func assertUtxosLocked(t *testing.T, utxoLocker *mockOutpointLocker, 225 utxos []*lnwallet.Utxo) { 226 227 t.Helper() 228 229 for _, utxo := range utxos { 230 if _, ok := utxoLocker.lockedOutpoints[utxo.OutPoint]; !ok { 231 t.Fatalf("utxo %v was never locked", utxo.OutPoint) 232 } 233 } 234 235 } 236 237 func assertNoUtxosUnlocked(t *testing.T, utxoLocker *mockOutpointLocker, 238 utxos []*lnwallet.Utxo) { 239 240 t.Helper() 241 242 if len(utxoLocker.unlockedOutpoints) != 0 { 243 t.Fatalf("outputs have been locked, but shouldn't have been") 244 } 245 } 246 247 func assertUtxosUnlocked(t *testing.T, utxoLocker *mockOutpointLocker, 248 utxos []*lnwallet.Utxo) { 249 250 t.Helper() 251 252 for _, utxo := range utxos { 253 if _, ok := utxoLocker.unlockedOutpoints[utxo.OutPoint]; !ok { 254 t.Fatalf("utxo %v was never unlocked", utxo.OutPoint) 255 } 256 } 257 } 258 259 func assertUtxosLockedAndUnlocked(t *testing.T, utxoLocker *mockOutpointLocker, 260 utxos []*lnwallet.Utxo) { 261 262 t.Helper() 263 264 for _, utxo := range utxos { 265 if _, ok := utxoLocker.lockedOutpoints[utxo.OutPoint]; !ok { 266 t.Fatalf("utxo %v was never locked", utxo.OutPoint) 267 } 268 269 if _, ok := utxoLocker.unlockedOutpoints[utxo.OutPoint]; !ok { 270 t.Fatalf("utxo %v was never unlocked", utxo.OutPoint) 271 } 272 } 273 } 274 275 // TestCraftSweepAllTxCoinSelectFail tests that if coin selection fails, then 276 // we unlock any outputs we may have locked in the passed closure. 277 func TestCraftSweepAllTxCoinSelectFail(t *testing.T) { 278 t.Parallel() 279 280 utxoSource := newMockUtxoSource(testUtxos) 281 coinSelectLocker := &mockCoinSelectionLocker{ 282 fail: true, 283 } 284 utxoLocker := newMockOutpointLocker() 285 286 _, err := CraftSweepAllTx( 287 0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil, 288 nil, chaincfg.TestNet3Params(), 0, 289 ) 290 291 // Since we instructed the coin select locker to fail above, we should 292 // get an error. 293 if err == nil { 294 t.Fatalf("sweep tx should have failed: %v", err) 295 } 296 297 // At this point, we'll now verify that all outputs were initially 298 // locked, and then also unlocked due to the failure. 299 assertUtxosLockedAndUnlocked(t, utxoLocker, testUtxos) 300 } 301 302 // TestCraftSweepAllTxUnknownWitnessType tests that if one of the inputs we 303 // encounter is of an unknown witness type, then we fail and unlock any prior 304 // locked outputs. 305 func TestCraftSweepAllTxUnknownWitnessType(t *testing.T) { 306 t.Parallel() 307 308 utxoSource := newMockUtxoSource(testUtxos) 309 coinSelectLocker := &mockCoinSelectionLocker{} 310 utxoLocker := newMockOutpointLocker() 311 312 _, err := CraftSweepAllTx( 313 0, 10, nil, nil, coinSelectLocker, utxoSource, utxoLocker, nil, 314 nil, chaincfg.TestNet3Params(), 0, 315 ) 316 317 // Since passed in a p2wsh output, which is unknown, we should fail to 318 // map the output to a witness type. 319 if err == nil { 320 t.Fatalf("sweep tx should have failed: %v", err) 321 } 322 323 // At this point, we'll now verify that all outputs were initially 324 // locked, and then also unlocked since we weren't able to find a 325 // witness type for the last output. 326 assertUtxosLockedAndUnlocked(t, utxoLocker, testUtxos) 327 } 328 329 // TestCraftSweepAllTx tests that we'll properly lock all available outputs 330 // within the wallet, and craft a single sweep transaction that pays to the 331 // target output. 332 func TestCraftSweepAllTx(t *testing.T) { 333 t.Parallel() 334 335 // First, we'll make a mock signer along with a fee estimator, We'll 336 // use zero fees to we can assert a precise output value. 337 signer := &mock.DummySigner{} 338 feeEstimator := newMockFeeEstimator(0, 0) 339 340 // For our UTXO source, we'll pass in all the UTXOs that we know of, 341 // other than the final one which is of an unknown witness type. 342 targetUTXOs := testUtxos[:2] 343 utxoSource := newMockUtxoSource(targetUTXOs) 344 coinSelectLocker := &mockCoinSelectionLocker{} 345 utxoLocker := newMockOutpointLocker() 346 347 sweepPkg, err := CraftSweepAllTx( 348 0, 10, nil, deliveryAddr, coinSelectLocker, utxoSource, 349 utxoLocker, feeEstimator, signer, chaincfg.TestNet3Params(), 0, 350 ) 351 if err != nil { 352 t.Fatalf("unable to make sweep tx: %v", err) 353 } 354 355 // At this point, all of the UTXOs that we made above should be locked 356 // and none of them unlocked. 357 assertUtxosLocked(t, utxoLocker, testUtxos[:2]) 358 assertNoUtxosUnlocked(t, utxoLocker, testUtxos[:2]) 359 360 // Now that we have our sweep transaction, we should find that we have 361 // a UTXO for each input, and also that our final output value is the 362 // sum of all our inputs. 363 sweepTx := sweepPkg.SweepTx 364 if len(sweepTx.TxIn) != len(targetUTXOs) { 365 t.Fatalf("expected %v utxo, got %v", len(targetUTXOs), 366 len(sweepTx.TxIn)) 367 } 368 369 // We should have a single output that pays to our sweep script 370 // generated above. 371 expectedSweepValue := int64(30000) 372 if len(sweepTx.TxOut) != 1 { 373 t.Fatalf("should have %v outputs, instead have %v", 1, 374 len(sweepTx.TxOut)) 375 } 376 output := sweepTx.TxOut[0] 377 switch { 378 case output.Value != expectedSweepValue: 379 t.Fatalf("expected %v sweep value, instead got %v", 380 expectedSweepValue, output.Value) 381 382 case !bytes.Equal(sweepScript, output.PkScript): 383 t.Fatalf("expected %x sweep script, instead got %x", sweepScript, 384 output.PkScript) 385 } 386 387 // If we cancel the sweep attempt, then we should find that all the 388 // UTXOs within the sweep transaction are now unlocked. 389 sweepPkg.CancelSweepAttempt() 390 assertUtxosUnlocked(t, utxoLocker, testUtxos[:2]) 391 }