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  }