gitlab.com/jokerrs1/Sia@v1.3.2/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.ProductionDependencies{})
    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.ProductionDependencies{})
    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 := wt.wallet.StartTransaction()
    75  	err = tbuilder.FundSiacoins(dustOutputValue.Mul64(uint64(noutputs)))
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	wt.wallet.mu.Lock()
    81  	var dest types.UnlockHash
    82  	for k := range wt.wallet.keys {
    83  		dest = k
    84  		break
    85  	}
    86  	wt.wallet.mu.Unlock()
    87  
    88  	for i := 0; i < noutputs; i++ {
    89  		tbuilder.AddSiacoinOutput(types.SiacoinOutput{
    90  			Value:      dustOutputValue,
    91  			UnlockHash: dest,
    92  		})
    93  	}
    94  
    95  	txns, err := tbuilder.Sign(true)
    96  	if err != nil {
    97  		t.Fatal(err)
    98  	}
    99  
   100  	err = wt.tpool.AcceptTransactionSet(txns)
   101  	if err != nil {
   102  		t.Fatal(err)
   103  	}
   104  
   105  	_, err = wt.miner.AddBlock()
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  
   110  	time.Sleep(time.Second)
   111  
   112  	wt.wallet.mu.Lock()
   113  	// force a sync because bucket stats may not be reliable until commit
   114  	wt.wallet.syncDB()
   115  	siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
   116  	wt.wallet.mu.Unlock()
   117  	if siacoinOutputs < defragThreshold {
   118  		t.Fatal("defrag consolidated dust outputs")
   119  	}
   120  }
   121  
   122  // TestDefragOutputExhaustion verifies that sending transactions still succeeds
   123  // even when the defragger is under heavy stress.
   124  func TestDefragOutputExhaustion(t *testing.T) {
   125  	if testing.Short() || !build.VLONG {
   126  		t.SkipNow()
   127  	}
   128  	t.Parallel()
   129  	wt, err := createWalletTester(t.Name(), &modules.ProductionDependencies{})
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	defer wt.closeWt()
   134  
   135  	wt.wallet.mu.Lock()
   136  	var dest types.UnlockHash
   137  	for k := range wt.wallet.keys {
   138  		dest = k
   139  		break
   140  	}
   141  	wt.wallet.mu.Unlock()
   142  
   143  	_, err = wt.miner.AddBlock()
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  
   148  	// concurrently make a bunch of transactions with lots of outputs to keep the
   149  	// defragger running
   150  	closechan := make(chan struct{})
   151  	donechan := make(chan struct{})
   152  	go func() {
   153  		defer close(donechan)
   154  		for {
   155  			select {
   156  			case <-closechan:
   157  				return
   158  			case <-time.After(time.Millisecond * 100):
   159  				_, err := wt.miner.AddBlock()
   160  				if err != nil {
   161  					t.Fatal(err)
   162  				}
   163  				txnValue := types.SiacoinPrecision.Mul64(3000)
   164  				fee := types.SiacoinPrecision.Mul64(10)
   165  				numOutputs := defragThreshold + 1
   166  
   167  				tbuilder := wt.wallet.StartTransaction()
   168  				tbuilder.FundSiacoins(txnValue.Mul64(uint64(numOutputs)).Add(fee))
   169  
   170  				for i := 0; i < numOutputs; i++ {
   171  					tbuilder.AddSiacoinOutput(types.SiacoinOutput{
   172  						Value:      txnValue,
   173  						UnlockHash: dest,
   174  					})
   175  				}
   176  
   177  				tbuilder.AddMinerFee(fee)
   178  
   179  				txns, err := tbuilder.Sign(true)
   180  				if err != nil {
   181  					t.Error("Error signing fragmenting transaction:", err)
   182  				}
   183  				err = wt.tpool.AcceptTransactionSet(txns)
   184  				if err != nil {
   185  					t.Error("Error accepting fragmenting transaction:", err)
   186  				}
   187  				_, err = wt.miner.AddBlock()
   188  				if err != nil {
   189  					t.Fatal(err)
   190  				}
   191  			}
   192  		}
   193  	}()
   194  
   195  	time.Sleep(time.Second * 1)
   196  
   197  	// ensure we can still send transactions while receiving aggressively
   198  	// fragmented outputs
   199  	for i := 0; i < 30; i++ {
   200  		sendAmount := types.SiacoinPrecision.Mul64(2000)
   201  		_, err = wt.wallet.SendSiacoins(sendAmount, types.UnlockHash{})
   202  		if err != nil {
   203  			t.Errorf("%v: %v", i, err)
   204  		}
   205  		time.Sleep(time.Millisecond * 50)
   206  	}
   207  
   208  	close(closechan)
   209  	<-donechan
   210  }
   211  
   212  // TestDefragInterrupted checks that a failing defrag unmarks spent outputs correctly
   213  func TestDefragInterrupted(t *testing.T) {
   214  	if testing.Short() {
   215  		t.SkipNow()
   216  	}
   217  	t.Parallel()
   218  	deps := dependencyDefragInterrupted{}
   219  	deps.fail()
   220  	wt, err := createWalletTester(t.Name(), &deps)
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  	defer wt.closeWt()
   225  
   226  	// mine defragThreshold blocks, resulting in defragThreshold outputs
   227  	for i := 0; i < defragThreshold; i++ {
   228  		_, err := wt.miner.AddBlock()
   229  		if err != nil {
   230  			t.Fatal(err)
   231  		}
   232  	}
   233  
   234  	// allow some time for the defrag transaction to occur, then mine another block
   235  	time.Sleep(time.Second * 5)
   236  
   237  	wt.wallet.mu.Lock()
   238  	// force a sync because bucket stats may not be reliable until commit
   239  	wt.wallet.syncDB()
   240  	spentOutputs := wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN
   241  	siacoinOutputs := wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
   242  	wt.wallet.mu.Unlock()
   243  	if siacoinOutputs <= defragThreshold {
   244  		t.Fatal("not enough outputs created - defrag wasn't triggered")
   245  	}
   246  	if spentOutputs > 0 {
   247  		t.Fatalf("There should be 0 outputs in the database since defrag failed but there were %v",
   248  			spentOutputs)
   249  	}
   250  
   251  	// Trigger defrag again
   252  	_, err = wt.miner.AddBlock()
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  
   257  	// allow some time for the defrag
   258  	time.Sleep(time.Second * 5)
   259  
   260  	// Mine another block to update the wallet
   261  	_, err = wt.miner.AddBlock()
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  
   266  	// Defrag should have worked this time
   267  	wt.wallet.mu.Lock()
   268  	// force a sync because bucket stats may not be reliable until commit
   269  	wt.wallet.syncDB()
   270  	spentOutputs = wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN
   271  	siacoinOutputs = wt.wallet.dbTx.Bucket(bucketSiacoinOutputs).Stats().KeyN
   272  	wt.wallet.mu.Unlock()
   273  	if siacoinOutputs > defragThreshold {
   274  		t.Fatalf("defrag should result in fewer than defragThreshold outputs, got %v wanted %v\n", siacoinOutputs, defragThreshold)
   275  	}
   276  	if spentOutputs == 0 {
   277  		t.Fatalf("There should be > 0 spentOutputs")
   278  	}
   279  
   280  }