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