gitlab.com/jokerrs1/Sia@v1.3.2/modules/wallet/money_test.go (about)

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