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

     1  package miner
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     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/persist"
    13  	"github.com/NebulousLabs/Sia/types"
    14  )
    15  
    16  var (
    17  	errNilCS     = errors.New("miner cannot use a nil consensus set")
    18  	errNilTpool  = errors.New("miner cannot use a nil transaction pool")
    19  	errNilWallet = errors.New("miner cannot use a nil wallet")
    20  
    21  	// HeaderMemory is the number of previous calls to 'header'
    22  	// that are remembered. Additionally, 'header' will only poll for a
    23  	// new block every 'headerMemory / blockMemory' times it is
    24  	// called. This reduces the amount of memory used, but comes at the cost of
    25  	// not always having the most recent transactions.
    26  	HeaderMemory = func() int {
    27  		if build.Release == "dev" {
    28  			return 500
    29  		}
    30  		if build.Release == "standard" {
    31  			return 10000
    32  		}
    33  		if build.Release == "testing" {
    34  			return 50
    35  		}
    36  		panic("unrecognized build.Release")
    37  	}()
    38  
    39  	// BlockMemory is the maximum number of blocks the miner will store
    40  	// Blocks take up to 2 megabytes of memory, which is why this number is
    41  	// limited.
    42  	BlockMemory = func() int {
    43  		if build.Release == "dev" {
    44  			return 10
    45  		}
    46  		if build.Release == "standard" {
    47  			return 50
    48  		}
    49  		if build.Release == "testing" {
    50  			return 5
    51  		}
    52  		panic("unrecognized build.Release")
    53  	}()
    54  
    55  	// MaxSourceBlockAge is the maximum amount of time that is allowed to
    56  	// elapse between generating source blocks.
    57  	MaxSourceBlockAge = func() time.Duration {
    58  		if build.Release == "dev" {
    59  			return 5 * time.Second
    60  		}
    61  		if build.Release == "standard" {
    62  			return 30 * time.Second
    63  		}
    64  		if build.Release == "testing" {
    65  			return 1 * time.Second
    66  		}
    67  		panic("unrecognized build.Release")
    68  	}()
    69  )
    70  
    71  // Miner struct contains all variables the miner needs
    72  // in order to create and submit blocks.
    73  type Miner struct {
    74  	// Module dependencies.
    75  	cs     modules.ConsensusSet
    76  	tpool  modules.TransactionPool
    77  	wallet modules.Wallet
    78  
    79  	// BlockManager variables. Becaues blocks are large, one block is used to
    80  	// make many headers which can be used by miners. Headers include an
    81  	// arbitrary data transaction (appended to the block) to make the merkle
    82  	// roots unique (preventing miners from doing redundant work). Every N
    83  	// requests or M seconds, a new block is used to create headers.
    84  	//
    85  	// Only 'blocksMemory' blocks are kept in memory at a time, which
    86  	// keeps ram usage reasonable. Miners may request many headers in parallel,
    87  	// and thus may be working on different blocks. When they submit the solved
    88  	// header to the block manager, the rest of the block needs to be found in
    89  	// a lookup.
    90  	blockMem        map[types.BlockHeader]*types.Block             // Mappings from headers to the blocks they are derived from.
    91  	arbDataMem      map[types.BlockHeader][crypto.EntropySize]byte // Mappings from the headers to their unique arb data.
    92  	headerMem       []types.BlockHeader                            // A circular list of headers that have been given out from the api recently.
    93  	sourceBlock     *types.Block                                   // The block from which new headers for mining are created.
    94  	sourceBlockTime time.Time                                      // How long headers have been using the same block (different from 'recent block').
    95  	memProgress     int                                            // The index of the most recent header used in headerMem.
    96  
    97  	// CPUMiner variables.
    98  	miningOn bool  // indicates if the miner is supposed to be running
    99  	mining   bool  // indicates if the miner is actually running
   100  	hashRate int64 // indicates hashes per second
   101  
   102  	// Utils
   103  	log        *persist.Logger
   104  	mu         sync.RWMutex
   105  	persist    persistence
   106  	persistDir string
   107  }
   108  
   109  // startupRescan will rescan the blockchain in the event that the miner
   110  // persistance layer has become desynchronized from the consensus persistance
   111  // layer. This might happen if a user replaces any of the folders with backups
   112  // or deletes any of the folders.
   113  func (m *Miner) startupRescan() error {
   114  	// Reset all of the variables that have relevance to the consensus set. The
   115  	// operations are wrapped by an anonymous function so that the locking can
   116  	// be handled using a defer statement.
   117  	err := func() error {
   118  		m.mu.Lock()
   119  		defer m.mu.Unlock()
   120  
   121  		m.log.Println("Performing a miner rescan.")
   122  		m.persist.RecentChange = modules.ConsensusChangeBeginning
   123  		m.persist.Height = 0
   124  		m.persist.Target = types.Target{}
   125  		return m.save()
   126  	}()
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// Subscribe to the consensus set. This is a blocking call that will not
   132  	// return until the miner has fully caught up to the current block.
   133  	return m.cs.ConsensusSetSubscribe(m, modules.ConsensusChangeBeginning)
   134  }
   135  
   136  // New returns a ready-to-go miner that is not mining.
   137  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, w modules.Wallet, persistDir string) (*Miner, error) {
   138  	// Create the miner and its dependencies.
   139  	if cs == nil {
   140  		return nil, errNilCS
   141  	}
   142  	if tpool == nil {
   143  		return nil, errNilTpool
   144  	}
   145  	if w == nil {
   146  		return nil, errNilWallet
   147  	}
   148  
   149  	// Assemble the miner. The miner is assembled without an address because
   150  	// the wallet is likely not unlocked yet. The miner will grab an address
   151  	// after the miner is unlocked (this must be coded manually for each
   152  	// function that potentially requires the miner to have an address.
   153  	m := &Miner{
   154  		cs:     cs,
   155  		tpool:  tpool,
   156  		wallet: w,
   157  
   158  		blockMem:   make(map[types.BlockHeader]*types.Block),
   159  		arbDataMem: make(map[types.BlockHeader][crypto.EntropySize]byte),
   160  		headerMem:  make([]types.BlockHeader, HeaderMemory),
   161  
   162  		persistDir: persistDir,
   163  	}
   164  
   165  	err := m.initPersist()
   166  	if err != nil {
   167  		return nil, errors.New("miner persistence startup failed: " + err.Error())
   168  	}
   169  
   170  	err = m.cs.ConsensusSetSubscribe(m, m.persist.RecentChange)
   171  	if err == modules.ErrInvalidConsensusChangeID {
   172  		// Perform a rescan of the consensus set if the change id is not found.
   173  		// The id will only be not found if there has been desynchronization
   174  		// between the miner and the consensus package.
   175  		err = m.startupRescan()
   176  		if err != nil {
   177  			return nil, errors.New("miner startup failed - rescanning failed: " + err.Error())
   178  		}
   179  	} else if err != nil {
   180  		return nil, errors.New("miner subscription failed: " + err.Error())
   181  	}
   182  
   183  	m.tpool.TransactionPoolSubscribe(m)
   184  
   185  	// Save after synchronizing with consensus
   186  	err = m.save()
   187  	if err != nil {
   188  		return nil, errors.New("miner could not save during startup: " + err.Error())
   189  	}
   190  
   191  	return m, nil
   192  }
   193  
   194  // Close terminates all ongoing processes involving the miner, enabling garbage
   195  // collection.
   196  func (m *Miner) Close() error {
   197  	m.mu.Lock()
   198  	defer m.mu.Unlock()
   199  
   200  	m.cs.Unsubscribe(m)
   201  
   202  	var errs []error
   203  	if err := m.saveSync(); err != nil {
   204  		errs = append(errs, fmt.Errorf("save failed: %v", err))
   205  	}
   206  	if err := m.log.Close(); err != nil {
   207  		errs = append(errs, fmt.Errorf("log.Close failed: %v", err))
   208  	}
   209  	return build.JoinErrors(errs, "; ")
   210  }
   211  
   212  // checkAddress checks that the miner has an address, fetching an address from
   213  // the wallet if not.
   214  func (m *Miner) checkAddress() error {
   215  	if m.persist.Address != (types.UnlockHash{}) {
   216  		return nil
   217  	}
   218  	uc, err := m.wallet.NextAddress()
   219  	if err != nil {
   220  		return err
   221  	}
   222  	m.persist.Address = uc.UnlockHash()
   223  	return nil
   224  }
   225  
   226  // BlocksMined returns the number of good blocks and stale blocks that have
   227  // been mined by the miner.
   228  func (m *Miner) BlocksMined() (goodBlocks, staleBlocks int) {
   229  	m.mu.Lock()
   230  	defer m.mu.Unlock()
   231  
   232  	for _, blockID := range m.persist.BlocksFound {
   233  		if m.cs.InCurrentPath(blockID) {
   234  			goodBlocks++
   235  		} else {
   236  			staleBlocks++
   237  		}
   238  	}
   239  	return
   240  }