github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/miner/miner_test.go (about)

     1  package miner
     2  
     3  import (
     4  	"bytes"
     5  	"path/filepath"
     6  	"testing"
     7  	"time"
     8  	"unsafe"
     9  
    10  	"SiaPrime/build"
    11  	"SiaPrime/crypto"
    12  	"SiaPrime/modules"
    13  	"SiaPrime/modules/consensus"
    14  	"SiaPrime/modules/gateway"
    15  	"SiaPrime/modules/transactionpool"
    16  	"SiaPrime/modules/wallet"
    17  	"SiaPrime/types"
    18  	"gitlab.com/NebulousLabs/fastrand"
    19  )
    20  
    21  // A minerTester is the helper object for miner testing.
    22  type minerTester struct {
    23  	gateway   modules.Gateway
    24  	cs        modules.ConsensusSet
    25  	tpool     modules.TransactionPool
    26  	wallet    modules.Wallet
    27  	walletKey crypto.TwofishKey
    28  
    29  	miner *Miner
    30  
    31  	minedBlocks []types.Block
    32  	persistDir  string
    33  }
    34  
    35  // createMinerTester creates a minerTester that's ready for use.
    36  func createMinerTester(name string) (*minerTester, error) {
    37  	testdir := build.TempDir(modules.MinerDir, name)
    38  
    39  	// Create the modules.
    40  	g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir))
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir))
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir))
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	var key crypto.TwofishKey
    57  	fastrand.Read(key[:])
    58  	_, err = w.Encrypt(key)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	err = w.Unlock(key)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	m, err := New(cs, tp, w, filepath.Join(testdir, modules.MinerDir))
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	// Assemble the minerTester.
    72  	mt := &minerTester{
    73  		gateway:   g,
    74  		cs:        cs,
    75  		tpool:     tp,
    76  		wallet:    w,
    77  		walletKey: key,
    78  
    79  		miner: m,
    80  
    81  		persistDir: testdir,
    82  	}
    83  
    84  	// Mine until the wallet has money.
    85  	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
    86  		b, err := m.AddBlock()
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		mt.minedBlocks = append(mt.minedBlocks, b)
    91  	}
    92  
    93  	return mt, nil
    94  }
    95  
    96  // TestIntegrationMiner creates a miner, mines a few blocks, and checks that
    97  // the wallet balance is updating as the blocks get mined.
    98  func TestIntegrationMiner(t *testing.T) {
    99  	if testing.Short() {
   100  		t.SkipNow()
   101  	}
   102  	mt, err := createMinerTester(t.Name())
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  
   107  	// Check that the wallet has money.
   108  	siacoins, _, _, err := mt.wallet.ConfirmedBalance()
   109  	if err != nil {
   110  		t.Error(err)
   111  	}
   112  	if siacoins.IsZero() {
   113  		t.Error("expecting mining full balance to not be zero")
   114  	}
   115  
   116  	// Mine a bunch of blocks.
   117  	for i := 0; i < 50; i++ {
   118  		b, _ := mt.miner.FindBlock()
   119  		err = mt.cs.AcceptBlock(b)
   120  		if err != nil {
   121  			t.Fatal(err)
   122  		}
   123  	}
   124  	morecoins, _, _, err := mt.wallet.ConfirmedBalance()
   125  	if err != nil {
   126  		t.Error(err)
   127  	}
   128  	if siacoins.Cmp(morecoins) >= 0 {
   129  		t.Error("wallet is not gaining balance while mining")
   130  	}
   131  }
   132  
   133  // TestIntegrationNilMinerDependencies tests that the miner properly handles
   134  // nil inputs for its dependencies.
   135  func TestIntegrationNilMinerDependencies(t *testing.T) {
   136  	if testing.Short() {
   137  		t.SkipNow()
   138  	}
   139  	mt, err := createMinerTester(t.Name())
   140  	if err != nil {
   141  		t.Fatal(err)
   142  	}
   143  	_, err = New(mt.cs, mt.tpool, nil, "")
   144  	if err != errNilWallet {
   145  		t.Fatal(err)
   146  	}
   147  	_, err = New(mt.cs, nil, mt.wallet, "")
   148  	if err != errNilTpool {
   149  		t.Fatal(err)
   150  	}
   151  	_, err = New(nil, mt.tpool, mt.wallet, "")
   152  	if err != errNilCS {
   153  		t.Fatal(err)
   154  	}
   155  	_, err = New(nil, nil, nil, "")
   156  	if err == nil {
   157  		t.Fatal(err)
   158  	}
   159  }
   160  
   161  // TestIntegrationBlocksMined checks that the BlocksMined function correctly
   162  // indicates the number of real blocks and stale blocks that have been mined.
   163  func TestIntegrationBlocksMined(t *testing.T) {
   164  	if testing.Short() {
   165  		t.SkipNow()
   166  	}
   167  	mt, err := createMinerTester(t.Name())
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  
   172  	// Get an unsolved header.
   173  	unsolvedHeader, target, err := mt.miner.HeaderForWork()
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	// Solve the header - necessary because the target is very low when
   178  	// mining.
   179  	for {
   180  		*(*uint64)(unsafe.Pointer(&unsolvedHeader.Nonce)) += types.ASICHardforkFactor
   181  		id := crypto.HashObject(unsolvedHeader)
   182  		if bytes.Compare(target[:], id[:]) < 0 {
   183  			break
   184  		}
   185  	}
   186  
   187  	// Get two solved headers.
   188  	header1, target, err := mt.miner.HeaderForWork()
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  	header1 = solveHeader(header1, target)
   193  	header2, target, err := mt.miner.HeaderForWork()
   194  	if err != nil {
   195  		t.Fatal(err)
   196  	}
   197  	header2 = solveHeader(header2, target)
   198  
   199  	// Submit the unsolved header followed by the two solved headers, this
   200  	// should result in 1 real block mined and 1 stale block mined.
   201  	err = mt.miner.SubmitHeader(unsolvedHeader)
   202  	if err != modules.ErrBlockUnsolved {
   203  		t.Fatal(err)
   204  	}
   205  	err = mt.miner.SubmitHeader(header1)
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	err = mt.miner.SubmitHeader(header2)
   210  	if err != modules.ErrNonExtendingBlock {
   211  		t.Fatal(err)
   212  	}
   213  	goodBlocks, staleBlocks := mt.miner.BlocksMined()
   214  	if goodBlocks != 1 {
   215  		t.Error("expecting 1 good block")
   216  	}
   217  	if staleBlocks != 1 {
   218  		t.Error("expecting 1 stale block, got", staleBlocks)
   219  	}
   220  
   221  	// Reboot the miner and verify that the block record has persisted.
   222  	err = mt.miner.Close()
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	rebootMiner, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir))
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	goodBlocks, staleBlocks = rebootMiner.BlocksMined()
   231  	if goodBlocks != 1 {
   232  		t.Error("expecting 1 good block")
   233  	}
   234  	if staleBlocks != 1 {
   235  		t.Error("expecting 1 stale block, got", staleBlocks)
   236  	}
   237  }
   238  
   239  // TestIntegrationAutoRescan triggers a rescan during a call to New and
   240  // verifies that the rescanning happens correctly. The rescan is triggered by
   241  // a call to New, instead of getting called directly.
   242  func TestIntegrationAutoRescan(t *testing.T) {
   243  	if testing.Short() {
   244  		t.SkipNow()
   245  	}
   246  	mt, err := createMinerTester(t.Name())
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	_, err = mt.miner.AddBlock()
   251  	if err != nil {
   252  		t.Fatal(err)
   253  	}
   254  
   255  	// Get the persist data of the current miner.
   256  	oldChange := mt.miner.persist.RecentChange
   257  	oldHeight := mt.miner.persist.Height
   258  	oldTarget := mt.miner.persist.Target
   259  
   260  	// Corrupt the miner, close the miner, and make a new one from the same
   261  	// directory.
   262  	mt.miner.persist.RecentChange[0]++
   263  	mt.miner.persist.Height += 1e5
   264  	mt.miner.persist.Target[0]++
   265  	err = mt.miner.Close() // miner saves when it closes.
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  
   270  	// Verify that rescanning resolved the corruption in the miner.
   271  	m, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir))
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  	// Check that after rescanning, the values have returned to the usual values.
   276  	if m.persist.RecentChange != oldChange {
   277  		t.Error("rescan failed, ended up on the wrong change")
   278  	}
   279  	if m.persist.Height != oldHeight {
   280  		t.Error("rescan failed, ended up at the wrong height")
   281  	}
   282  	if m.persist.Target != oldTarget {
   283  		t.Error("rescan failed, ended up at the wrong target")
   284  	}
   285  }
   286  
   287  // TestIntegrationStartupRescan probes the startupRescan function, checking
   288  // that it works in the naive case. Rescan is called directly.
   289  func TestIntegrationStartupRescan(t *testing.T) {
   290  	if testing.Short() {
   291  		t.SkipNow()
   292  	}
   293  	mt, err := createMinerTester(t.Name())
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  
   298  	// Check that the miner's persist variables have been initialized to the
   299  	// first few blocks.
   300  	if mt.miner.persist.RecentChange == (modules.ConsensusChangeID{}) || mt.miner.persist.Height == 0 || mt.miner.persist.Target == (types.Target{}) {
   301  		t.Fatal("miner persist variables not initialized")
   302  	}
   303  	oldChange := mt.miner.persist.RecentChange
   304  	oldHeight := mt.miner.persist.Height
   305  	oldTarget := mt.miner.persist.Target
   306  
   307  	// Corrupt the miner and verify that a rescan repairs the corruption.
   308  	mt.miner.persist.RecentChange[0]++
   309  	mt.miner.persist.Height += 500
   310  	mt.miner.persist.Target[0]++
   311  	mt.cs.Unsubscribe(mt.miner)
   312  	err = mt.miner.startupRescan()
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  	if mt.miner.persist.RecentChange != oldChange {
   317  		t.Error("rescan failed, ended up on the wrong change")
   318  	}
   319  	if mt.miner.persist.Height != oldHeight {
   320  		t.Error("rescan failed, ended up at the wrong height")
   321  	}
   322  	if mt.miner.persist.Target != oldTarget {
   323  		t.Error("rescan failed, ended up at the wrong target")
   324  	}
   325  }
   326  
   327  // TestMinerCloseDeadlock checks that the miner can cleanly close even if the
   328  // CPU miner is running.
   329  func TestMinerCloseDeadlock(t *testing.T) {
   330  	if testing.Short() {
   331  		t.SkipNow()
   332  	}
   333  	mt, err := createMinerTester(t.Name())
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	// StartCPUMining calls `go threadedMine()`, which needs to access the miner
   338  	// before Close() does in the next goroutine, otherwise m.tg.Add() fails
   339  	// at the top of threadedMine() and threadedMine() exits (silently!).
   340  	// I haven't seen this behavior since sticking Close() inside a goroutine,
   341  	// but I'm not sure that's comfort enough.
   342  	mt.miner.StartCPUMining()
   343  	time.Sleep(time.Millisecond * 250)
   344  
   345  	closed := make(chan struct{})
   346  	go func() {
   347  		if err := mt.miner.Close(); err != nil {
   348  			t.Fatal(err)
   349  		}
   350  		closed <- struct{}{}
   351  	}()
   352  	select {
   353  	case <-closed:
   354  	case <-time.After(5 * time.Second):
   355  		t.Fatal("mt.miner.Close never completed")
   356  	}
   357  }