github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/miner/miner_test.go (about)

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