gitlab.com/jokerrs1/Sia@v1.3.2/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, _, _ := mt.wallet.ConfirmedBalance()
   108  	if siacoins.IsZero() {
   109  		t.Error("expecting mining full balance to not be zero")
   110  	}
   111  
   112  	// Mine a bunch of blocks.
   113  	for i := 0; i < 50; i++ {
   114  		b, _ := mt.miner.FindBlock()
   115  		err = mt.cs.AcceptBlock(b)
   116  		if err != nil {
   117  			t.Fatal(err)
   118  		}
   119  	}
   120  	morecoins, _, _ := mt.wallet.ConfirmedBalance()
   121  	if siacoins.Cmp(morecoins) >= 0 {
   122  		t.Error("wallet is not gaining balance while mining")
   123  	}
   124  }
   125  
   126  // TestIntegrationNilMinerDependencies tests that the miner properly handles
   127  // nil inputs for its dependencies.
   128  func TestIntegrationNilMinerDependencies(t *testing.T) {
   129  	if testing.Short() {
   130  		t.SkipNow()
   131  	}
   132  	mt, err := createMinerTester(t.Name())
   133  	if err != nil {
   134  		t.Fatal(err)
   135  	}
   136  	_, err = New(mt.cs, mt.tpool, nil, "")
   137  	if err != errNilWallet {
   138  		t.Fatal(err)
   139  	}
   140  	_, err = New(mt.cs, nil, mt.wallet, "")
   141  	if err != errNilTpool {
   142  		t.Fatal(err)
   143  	}
   144  	_, err = New(nil, mt.tpool, mt.wallet, "")
   145  	if err != errNilCS {
   146  		t.Fatal(err)
   147  	}
   148  	_, err = New(nil, nil, nil, "")
   149  	if err == nil {
   150  		t.Fatal(err)
   151  	}
   152  }
   153  
   154  // TestIntegrationBlocksMined checks that the BlocksMined function correctly
   155  // indicates the number of real blocks and stale blocks that have been mined.
   156  func TestIntegrationBlocksMined(t *testing.T) {
   157  	if testing.Short() {
   158  		t.SkipNow()
   159  	}
   160  	mt, err := createMinerTester(t.Name())
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	// Get an unsolved header.
   166  	unsolvedHeader, target, err := mt.miner.HeaderForWork()
   167  	if err != nil {
   168  		t.Fatal(err)
   169  	}
   170  	// Unsolve the header - necessary because the target is very low when
   171  	// mining.
   172  	for {
   173  		unsolvedHeader.Nonce[0]++
   174  		id := crypto.HashObject(unsolvedHeader)
   175  		if bytes.Compare(target[:], id[:]) < 0 {
   176  			break
   177  		}
   178  	}
   179  
   180  	// Get two solved headers.
   181  	header1, target, err := mt.miner.HeaderForWork()
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	header1 = solveHeader(header1, target)
   186  	header2, target, err := mt.miner.HeaderForWork()
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  	header2 = solveHeader(header2, target)
   191  
   192  	// Submit the unsolved header followed by the two solved headers, this
   193  	// should result in 1 real block mined and 1 stale block mined.
   194  	err = mt.miner.SubmitHeader(unsolvedHeader)
   195  	if err != modules.ErrBlockUnsolved {
   196  		t.Fatal(err)
   197  	}
   198  	err = mt.miner.SubmitHeader(header1)
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	err = mt.miner.SubmitHeader(header2)
   203  	if err != modules.ErrNonExtendingBlock {
   204  		t.Fatal(err)
   205  	}
   206  	goodBlocks, staleBlocks := mt.miner.BlocksMined()
   207  	if goodBlocks != 1 {
   208  		t.Error("expecting 1 good block")
   209  	}
   210  	if staleBlocks != 1 {
   211  		t.Error("expecting 1 stale block, got", staleBlocks)
   212  	}
   213  
   214  	// Reboot the miner and verify that the block record has persisted.
   215  	err = mt.miner.Close()
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	rebootMiner, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir))
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	goodBlocks, staleBlocks = rebootMiner.BlocksMined()
   224  	if goodBlocks != 1 {
   225  		t.Error("expecting 1 good block")
   226  	}
   227  	if staleBlocks != 1 {
   228  		t.Error("expecting 1 stale block, got", staleBlocks)
   229  	}
   230  }
   231  
   232  // TestIntegrationAutoRescan triggers a rescan during a call to New and
   233  // verifies that the rescanning happens correctly. The rescan is triggered by
   234  // a call to New, instead of getting called directly.
   235  func TestIntegrationAutoRescan(t *testing.T) {
   236  	if testing.Short() {
   237  		t.SkipNow()
   238  	}
   239  	mt, err := createMinerTester(t.Name())
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	_, err = mt.miner.AddBlock()
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  
   248  	// Get the persist data of the current miner.
   249  	oldChange := mt.miner.persist.RecentChange
   250  	oldHeight := mt.miner.persist.Height
   251  	oldTarget := mt.miner.persist.Target
   252  
   253  	// Corrupt the miner, close the miner, and make a new one from the same
   254  	// directory.
   255  	mt.miner.persist.RecentChange[0]++
   256  	mt.miner.persist.Height += 1e5
   257  	mt.miner.persist.Target[0]++
   258  	err = mt.miner.Close() // miner saves when it closes.
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	// Verify that rescanning resolved the corruption in the miner.
   264  	m, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir))
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	// Check that after rescanning, the values have returned to the usual values.
   269  	if m.persist.RecentChange != oldChange {
   270  		t.Error("rescan failed, ended up on the wrong change")
   271  	}
   272  	if m.persist.Height != oldHeight {
   273  		t.Error("rescan failed, ended up at the wrong height")
   274  	}
   275  	if m.persist.Target != oldTarget {
   276  		t.Error("rescan failed, ended up at the wrong target")
   277  	}
   278  }
   279  
   280  // TestIntegrationStartupRescan probes the startupRescan function, checking
   281  // that it works in the naive case. Rescan is called directly.
   282  func TestIntegrationStartupRescan(t *testing.T) {
   283  	if testing.Short() {
   284  		t.SkipNow()
   285  	}
   286  	mt, err := createMinerTester(t.Name())
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  
   291  	// Check that the miner's persist variables have been initialized to the
   292  	// first few blocks.
   293  	if mt.miner.persist.RecentChange == (modules.ConsensusChangeID{}) || mt.miner.persist.Height == 0 || mt.miner.persist.Target == (types.Target{}) {
   294  		t.Fatal("miner persist variables not initialized")
   295  	}
   296  	oldChange := mt.miner.persist.RecentChange
   297  	oldHeight := mt.miner.persist.Height
   298  	oldTarget := mt.miner.persist.Target
   299  
   300  	// Corrupt the miner and verify that a rescan repairs the corruption.
   301  	mt.miner.persist.RecentChange[0]++
   302  	mt.miner.persist.Height += 500
   303  	mt.miner.persist.Target[0]++
   304  	mt.cs.Unsubscribe(mt.miner)
   305  	err = mt.miner.startupRescan()
   306  	if err != nil {
   307  		t.Fatal(err)
   308  	}
   309  	if mt.miner.persist.RecentChange != oldChange {
   310  		t.Error("rescan failed, ended up on the wrong change")
   311  	}
   312  	if mt.miner.persist.Height != oldHeight {
   313  		t.Error("rescan failed, ended up at the wrong height")
   314  	}
   315  	if mt.miner.persist.Target != oldTarget {
   316  		t.Error("rescan failed, ended up at the wrong target")
   317  	}
   318  }
   319  
   320  // TestMinerCloseDeadlock checks that the miner can cleanly close even if the
   321  // CPU miner is running.
   322  func TestMinerCloseDeadlock(t *testing.T) {
   323  	mt, err := createMinerTester(t.Name())
   324  	if err != nil {
   325  		t.Fatal(err)
   326  	}
   327  	// StartCPUMining calls `go threadedMine()`, which needs to access the miner
   328  	// before Close() does in the next goroutine, otherwise m.tg.Add() fails
   329  	// at the top of threadedMine() and threadedMine() exits (silently!).
   330  	// I haven't seen this behavior since sticking Close() inside a goroutine,
   331  	// but I'm not sure that's comfort enough.
   332  	mt.miner.StartCPUMining()
   333  	time.Sleep(time.Millisecond * 250)
   334  
   335  	closed := make(chan struct{})
   336  	go func() {
   337  		if err := mt.miner.Close(); err != nil {
   338  			t.Fatal(err)
   339  		}
   340  		closed <- struct{}{}
   341  	}()
   342  	select {
   343  	case <-closed:
   344  	case <-time.After(5 * time.Second):
   345  		t.Fatal("mt.miner.Close never completed")
   346  	}
   347  }