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

     1  package wallet
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/SiaPrime/SiaPrime/build"
    10  	"gitlab.com/SiaPrime/SiaPrime/modules"
    11  	"gitlab.com/SiaPrime/SiaPrime/types"
    12  )
    13  
    14  // TestDefragWallet mines many blocks and checks that the wallet's outputs are
    15  // consolidated once more than defragThreshold blocks are mined.
    16  func TestDefragWallet(t *testing.T) {
    17  	if testing.Short() {
    18  		t.SkipNow()
    19  	}
    20  	t.Parallel()
    21  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
    22  	if err != nil {
    23  		t.Fatal(err)
    24  	}
    25  	defer wt.closeWt()
    26  
    27  	// mine defragThreshold blocks, resulting in defragThreshold outputs
    28  	for i := 0; i < defragThreshold; i++ {
    29  		_, err := wt.miner.AddBlock()
    30  		if err != nil {
    31  			t.Fatal(err)
    32  		}
    33  	}
    34  
    35  	// add another block to push the number of outputs over the threshold
    36  	_, err = wt.miner.AddBlock()
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  
    41  	// allow some time for the defrag transaction to occur, then mine another block
    42  	time.Sleep(time.Second * 5)
    43  
    44  	_, err = wt.miner.AddBlock()
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  
    49  	// defrag should keep the outputs below the threshold
    50  	wt.wallet.mu.Lock()
    51  	// force a sync because bucket stats may not be reliable until commit
    52  	wt.wallet.syncDB()
    53  	siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
    54  	wt.wallet.mu.Unlock()
    55  	if siacoinOutputs > defragThreshold {
    56  		t.Fatalf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v\n", siacoinOutputs, defragThreshold)
    57  	}
    58  }
    59  
    60  // TestDefragWalletDust verifies that dust outputs do not trigger the defrag
    61  // operation.
    62  func TestDefragWalletDust(t *testing.T) {
    63  	if testing.Short() {
    64  		t.SkipNow()
    65  	}
    66  	t.Parallel()
    67  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  	defer wt.closeWt()
    72  
    73  	dustOutputValue := types.NewCurrency64(10000)
    74  	noutputs := defragThreshold + 1
    75  
    76  	tbuilder, err := wt.wallet.StartTransaction()
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	err = tbuilder.FundSiacoins(dustOutputValue.Mul64(uint64(noutputs)))
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	wt.wallet.mu.Lock()
    86  	var dest types.UnlockHash
    87  	for k := range wt.wallet.keys {
    88  		dest = k
    89  		break
    90  	}
    91  	wt.wallet.mu.Unlock()
    92  
    93  	for i := 0; i < noutputs; i++ {
    94  		tbuilder.AddSiacoinOutput(types.SiacoinOutput{
    95  			Value:      dustOutputValue,
    96  			UnlockHash: dest,
    97  		})
    98  	}
    99  
   100  	txns, err := tbuilder.Sign(true)
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  
   105  	err = wt.tpool.AcceptTransactionSet(txns)
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  
   110  	_, err = wt.miner.AddBlock()
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  
   115  	time.Sleep(time.Second)
   116  
   117  	wt.wallet.mu.Lock()
   118  	// force a sync because bucket stats may not be reliable until commit
   119  	wt.wallet.syncDB()
   120  	siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
   121  	wt.wallet.mu.Unlock()
   122  	if siacoinOutputs < defragThreshold {
   123  		t.Fatal("defrag consolidated dust outputs")
   124  	}
   125  }
   126  
   127  // TestDefragOutputExhaustion verifies that sending transactions still succeeds
   128  // even when the defragger is under heavy stress.
   129  func TestDefragOutputExhaustion(t *testing.T) {
   130  	if testing.Short() || !build.VLONG {
   131  		t.SkipNow()
   132  	}
   133  	t.Parallel()
   134  	wt, err := createWalletTester(t.Name(), modules.ProdDependencies)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	defer wt.closeWt()
   139  
   140  	wt.wallet.mu.Lock()
   141  	var dest types.UnlockHash
   142  	for k := range wt.wallet.keys {
   143  		dest = k
   144  		break
   145  	}
   146  	wt.wallet.mu.Unlock()
   147  
   148  	_, err = wt.miner.AddBlock()
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  
   153  	// concurrently make a bunch of transactions with lots of outputs to keep the
   154  	// defragger running
   155  	closechan := make(chan struct{})
   156  	donechan := make(chan struct{})
   157  	go func() {
   158  		defer close(donechan)
   159  		for {
   160  			select {
   161  			case <-closechan:
   162  				return
   163  			case <-time.After(time.Millisecond * 100):
   164  				_, err := wt.miner.AddBlock()
   165  				if err != nil {
   166  					t.Fatal(err)
   167  				}
   168  				txnValue := types.SiacoinPrecision.Mul64(3000)
   169  				fee := types.SiacoinPrecision.Mul64(10)
   170  				numOutputs := defragThreshold + 1
   171  
   172  				tbuilder, err := wt.wallet.StartTransaction()
   173  				if err != nil {
   174  					t.Fatal(err)
   175  				}
   176  
   177  				tbuilder.FundSiacoins(txnValue.Mul64(uint64(numOutputs)).Add(fee))
   178  
   179  				for i := 0; i < numOutputs; i++ {
   180  					tbuilder.AddSiacoinOutput(types.SiacoinOutput{
   181  						Value:      txnValue,
   182  						UnlockHash: dest,
   183  					})
   184  				}
   185  
   186  				tbuilder.AddMinerFee(fee)
   187  
   188  				txns, err := tbuilder.Sign(true)
   189  				if err != nil {
   190  					t.Error("Error signing fragmenting transaction:", err)
   191  				}
   192  				err = wt.tpool.AcceptTransactionSet(txns)
   193  				if err != nil {
   194  					t.Error("Error accepting fragmenting transaction:", err)
   195  				}
   196  				_, err = wt.miner.AddBlock()
   197  				if err != nil {
   198  					t.Fatal(err)
   199  				}
   200  			}
   201  		}
   202  	}()
   203  
   204  	time.Sleep(time.Second * 1)
   205  
   206  	// ensure we can still send transactions while receiving aggressively
   207  	// fragmented outputs
   208  	for i := 0; i < 30; i++ {
   209  		sendAmount := types.SiacoinPrecision.Mul64(2000)
   210  		_, err = wt.wallet.SendSiacoins(sendAmount, types.UnlockHash{})
   211  		if err != nil {
   212  			t.Errorf("%v: %v", i, err)
   213  		}
   214  		time.Sleep(time.Millisecond * 50)
   215  	}
   216  
   217  	close(closechan)
   218  	<-donechan
   219  }
   220  
   221  // TestDefragInterrupted checks that a failing defrag un-marks spent outputs correctly
   222  func TestDefragInterrupted(t *testing.T) {
   223  	if testing.Short() {
   224  		t.SkipNow()
   225  	}
   226  	t.Parallel()
   227  	deps := dependencyDefragInterrupted{}
   228  	deps.fail()
   229  	wt, err := createWalletTester(t.Name(), &deps)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	defer wt.closeWt()
   234  
   235  	// mine defragThreshold blocks, resulting in defragThreshold outputs
   236  	for i := 0; i < defragThreshold; i++ {
   237  		_, err := wt.miner.AddBlock()
   238  		if err != nil {
   239  			t.Fatal(err)
   240  		}
   241  	}
   242  
   243  	err = build.Retry(50, 100*time.Millisecond, func() error {
   244  		wt.wallet.mu.Lock()
   245  		// force a sync because bucket stats may not be reliable until commit
   246  		wt.wallet.syncDB()
   247  		spentOutputs := wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN
   248  		siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
   249  		wt.wallet.mu.Unlock()
   250  
   251  		if siacoinOutputs <= defragThreshold {
   252  			return errors.New("not enough outputs created - defrag wasn't triggered")
   253  		}
   254  		if spentOutputs > 0 {
   255  			err := fmt.Errorf("There should be 0 outputs in the database since defrag failed but there were %v",
   256  				spentOutputs)
   257  			return err
   258  		}
   259  		return nil
   260  	})
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  
   265  	// Trigger defrag again
   266  	_, err = wt.miner.AddBlock()
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  
   271  	// Check to make sure wallet is updated
   272  	loop := 0
   273  	err = build.Retry(50, 100*time.Millisecond, func() error {
   274  		// Mine another block every 10 iterations to make sure the wallet is
   275  		// updated
   276  		if loop%10 == 0 {
   277  			_, err = wt.miner.AddBlock()
   278  			if err != nil {
   279  				t.Fatal(err)
   280  			}
   281  		}
   282  		loop++
   283  
   284  		// force a sync because bucket stats may not be reliable until commit
   285  		wt.wallet.mu.Lock()
   286  		wt.wallet.syncDB()
   287  		spentOutputs := wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN
   288  		siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
   289  		wt.wallet.mu.Unlock()
   290  
   291  		if siacoinOutputs > defragThreshold {
   292  			err := fmt.Errorf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v", siacoinOutputs, defragThreshold)
   293  			return err
   294  		}
   295  		if spentOutputs == 0 {
   296  			return errors.New("There should be > 0 spentOutputs")
   297  		}
   298  		return nil
   299  	})
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  }