gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/money_test.go (about)

     1  package wallet
     2  
     3  import (
     4  	"sort"
     5  	"testing"
     6  
     7  	"gitlab.com/SiaPrime/SiaPrime/modules"
     8  	"gitlab.com/SiaPrime/SiaPrime/types"
     9  )
    10  
    11  // TestSendSiacoins probes the SendSiacoins method of the wallet.
    12  func TestSendSiacoins(t *testing.T) {
    13  	if testing.Short() {
    14  		t.SkipNow()
    15  	}
    16  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
    17  	if err != nil {
    18  		t.Fatal(err)
    19  	}
    20  	defer wt.closeWt()
    21  
    22  	// Get the initial balance - should be 1 block. The unconfirmed balances
    23  	// should be 0.
    24  	confirmedBal, _, _, err := wt.wallet.ConfirmedBalance()
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	unconfirmedOut, unconfirmedIn, err := wt.wallet.UnconfirmedBalance()
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  
    33  	devSubsidy := types.CalculateDevSubsidy(1)
    34  	expectedBalance := types.CalculateCoinbase(1).Sub(devSubsidy)
    35  
    36  	if !confirmedBal.Equals(expectedBalance) {
    37  		t.Error("unexpected confirmed balance")
    38  	}
    39  	if !unconfirmedOut.Equals(types.ZeroCurrency) {
    40  		t.Error("unconfirmed balance should be 0")
    41  	}
    42  	if !unconfirmedIn.Equals(types.ZeroCurrency) {
    43  		t.Error("unconfirmed balance should be 0")
    44  	}
    45  
    46  	// Send 5000 hastings. The wallet will automatically add a fee. Outgoing
    47  	// unconfirmed siacoins - incoming unconfirmed siacoins should equal 5000 +
    48  	// fee.
    49  	sendValue := types.SiacoinPrecision.Mul64(3)
    50  	_, tpoolFee := wt.wallet.tpool.FeeEstimation()
    51  	tpoolFee = tpoolFee.Mul64(750)
    52  	_, err = wt.wallet.SendSiacoins(sendValue, types.UnlockHash{})
    53  	if err != nil {
    54  		t.Fatal(err)
    55  	}
    56  	confirmedBal2, _, _, err := wt.wallet.ConfirmedBalance()
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	unconfirmedOut2, unconfirmedIn2, err := wt.wallet.UnconfirmedBalance()
    61  	if err != nil {
    62  		t.Fatal(err)
    63  	}
    64  	if !confirmedBal2.Equals(confirmedBal) {
    65  		t.Error("confirmed balance changed without introduction of blocks")
    66  	}
    67  	if !unconfirmedOut2.Equals(unconfirmedIn2.Add(sendValue).Add(tpoolFee)) {
    68  		t.Error("sending siacoins appears to be ineffective")
    69  	}
    70  
    71  	// Move the balance into the confirmed set.
    72  	b, _ := wt.miner.FindBlock()
    73  	err = wt.cs.AcceptBlock(b)
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	confirmedBal3, _, _, err := wt.wallet.ConfirmedBalance()
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	unconfirmedOut3, unconfirmedIn3, err := wt.wallet.UnconfirmedBalance()
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  
    86  	devSubsidy3 := types.CalculateDevSubsidy(2)
    87  	expectedBalance3 := types.CalculateCoinbase(2).Sub(devSubsidy3)
    88  
    89  	if !confirmedBal3.Equals(confirmedBal2.Add(expectedBalance3).Sub(sendValue).Sub(tpoolFee)) {
    90  		t.Error("confirmed balance did not adjust to the expected value")
    91  	}
    92  	if !unconfirmedOut3.Equals(types.ZeroCurrency) {
    93  		t.Error("unconfirmed balance should be 0")
    94  	}
    95  	if !unconfirmedIn3.Equals(types.ZeroCurrency) {
    96  		t.Error("unconfirmed balance should be 0")
    97  	}
    98  }
    99  
   100  // TestIntegrationSendOverUnder sends too many siacoins, resulting in an error,
   101  // followed by sending few enough siacoins that the send should complete.
   102  //
   103  // This test is here because of a bug found in production where the wallet
   104  // would mark outputs as spent before it knew that there was enough money  to
   105  // complete the transaction. This meant that, after trying to send too many
   106  // coins, all outputs got marked 'sent'. This test reproduces those conditions
   107  // to ensure it does not happen again.
   108  func TestIntegrationSendOverUnder(t *testing.T) {
   109  	if testing.Short() {
   110  		t.SkipNow()
   111  	}
   112  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	defer wt.closeWt()
   117  
   118  	// Spend too many siacoins.
   119  	tooManyCoins := types.SiacoinPrecision.Mul64(1e12)
   120  	_, err = wt.wallet.SendSiacoins(tooManyCoins, types.UnlockHash{})
   121  	if err == nil {
   122  		t.Error("low balance err not returned after attempting to send too many coins:", err)
   123  	}
   124  
   125  	// Spend a reasonable amount of siacoins.
   126  	reasonableCoins := types.SiacoinPrecision.Mul64(100e3)
   127  	_, err = wt.wallet.SendSiacoins(reasonableCoins, types.UnlockHash{})
   128  	if err != nil {
   129  		t.Error("unexpected error: ", err)
   130  	}
   131  }
   132  
   133  // TestIntegrationSpendHalfHalf spends more than half of the coins, and then
   134  // more than half of the coins again, to make sure that the wallet is not
   135  // reusing outputs that it has already spent.
   136  func TestIntegrationSpendHalfHalf(t *testing.T) {
   137  	if testing.Short() {
   138  		t.SkipNow()
   139  	}
   140  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	defer wt.closeWt()
   145  
   146  	// Spend more than half of the coins twice.
   147  	halfPlus := types.SiacoinPrecision.Mul64(200e3)
   148  	_, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{})
   149  	if err != nil {
   150  		t.Error("unexpected error: ", err)
   151  	}
   152  	_, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{1})
   153  	if err == nil {
   154  		t.Error("wallet appears to be reusing outputs when building transactions: ", err)
   155  	}
   156  }
   157  
   158  // TestIntegrationSpendUnconfirmed spends an unconfirmed siacoin output.
   159  func TestIntegrationSpendUnconfirmed(t *testing.T) {
   160  	if testing.Short() {
   161  		t.SkipNow()
   162  	}
   163  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	defer wt.closeWt()
   168  
   169  	// Balance is 239999.2 coins.
   170  
   171  	// Spend the only output.
   172  	halfPlus := types.SiacoinPrecision.Mul64(200e3)
   173  	_, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{})
   174  	if err != nil {
   175  		t.Error("unexpected error: ", err)
   176  	}
   177  	someMore := types.SiacoinPrecision.Mul64(39e3)
   178  	_, err = wt.wallet.SendSiacoins(someMore, types.UnlockHash{1})
   179  	if err != nil {
   180  		t.Error("wallet appears to be struggling to spend unconfirmed outputs")
   181  	}
   182  }
   183  
   184  // TestIntegrationSortedOutputsSorting checks that the outputs are being correctly sorted
   185  // by the currency value.
   186  func TestIntegrationSortedOutputsSorting(t *testing.T) {
   187  	if testing.Short() {
   188  		t.SkipNow()
   189  	}
   190  	so := sortedOutputs{
   191  		ids: []types.SiacoinOutputID{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}},
   192  		outputs: []types.SiacoinOutput{
   193  			{Value: types.NewCurrency64(2)},
   194  			{Value: types.NewCurrency64(3)},
   195  			{Value: types.NewCurrency64(4)},
   196  			{Value: types.NewCurrency64(7)},
   197  			{Value: types.NewCurrency64(6)},
   198  			{Value: types.NewCurrency64(0)},
   199  			{Value: types.NewCurrency64(1)},
   200  			{Value: types.NewCurrency64(5)},
   201  		},
   202  	}
   203  	sort.Sort(so)
   204  
   205  	expectedIDSorting := []types.SiacoinOutputID{{5}, {6}, {0}, {1}, {2}, {7}, {4}, {3}}
   206  	for i := uint64(0); i < 8; i++ {
   207  		if so.ids[i] != expectedIDSorting[i] {
   208  			t.Error("an id is out of place: ", i)
   209  		}
   210  		if !so.outputs[i].Value.Equals64(i) {
   211  			t.Error("a value is out of place: ", i)
   212  		}
   213  	}
   214  }
   215  
   216  // TestSendSiacoinsFailed checks if SendSiacoins and SendSiacoinsMulti behave
   217  // correctly when funcing the Transaction succeeded but accepting it didn't.
   218  func TestSendSiacoinsAcceptTxnSetFailed(t *testing.T) {
   219  	if testing.Short() {
   220  		t.SkipNow()
   221  	}
   222  	deps := &dependencySendSiacoinsInterrupted{}
   223  	wt, err := createWalletTester(t.Name(), deps)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	defer wt.closeWt()
   228  
   229  	// There should be no spent transactions in the database at this point
   230  	wt.wallet.mu.Lock()
   231  	wt.wallet.syncDB()
   232  	if wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN != 0 {
   233  		wt.wallet.mu.Unlock()
   234  		t.Fatal("bucketSpentOutputs isn't empty")
   235  	}
   236  	wt.wallet.mu.Unlock()
   237  
   238  	// Try to send coins using SendSiacoinsMulti
   239  	numOutputs := 10
   240  	scos := make([]types.SiacoinOutput, numOutputs)
   241  	for i := 0; i < numOutputs; i++ {
   242  		uc, err := wt.wallet.NextAddress()
   243  		if err != nil {
   244  			t.Fatal(err)
   245  		}
   246  		scos[i].Value = types.SiacoinPrecision
   247  		scos[i].UnlockHash = uc.UnlockHash()
   248  	}
   249  	deps.fail()
   250  	_, err = wt.wallet.SendSiacoinsMulti(scos)
   251  	if err == nil {
   252  		t.Fatal("SendSiacoinsMulti should have failed but didn't")
   253  	}
   254  
   255  	// Send some coins using SendSiacoins
   256  	uc, err := wt.wallet.NextAddress()
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  	deps.fail()
   261  	_, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash())
   262  	if err == nil {
   263  		t.Fatal("SendSiacoins should have failed but didn't")
   264  	}
   265  
   266  	// There should still be no spent transactions in the database
   267  	wt.wallet.mu.Lock()
   268  	wt.wallet.syncDB()
   269  	bucket := wt.wallet.dbTx.Bucket(bucketSpentOutputs)
   270  	if bucket.Stats().KeyN != 0 {
   271  		wt.wallet.mu.Unlock()
   272  		t.Fatal("bucketSpentOutputs isn't empty")
   273  	}
   274  	wt.wallet.mu.Unlock()
   275  
   276  	// Send the money again without the failing dependency
   277  	_, err = wt.wallet.SendSiacoinsMulti(scos)
   278  	if err != nil {
   279  		t.Fatalf("SendSiacoinsMulti failed: %v", err)
   280  	}
   281  
   282  	// Send some coins using SendSiacoins
   283  	_, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash())
   284  	if err != nil {
   285  		t.Fatalf("SendSiacoins failed: %v", err)
   286  	}
   287  }