gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/miner/miner.go (about)

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