github.com/ZuluSpl0it/Sia@v1.3.7/modules/wallet/defrag_test.go (about)

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