github.com/NebulousLabs/Sia@v1.3.7/modules/miner/miner_test.go (about)

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