github.com/lbryio/lbcd@v0.22.119/mining/cpuminer/cpuminer.go (about)

     1  // Copyright (c) 2014-2016 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package cpuminer
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"math/rand"
    11  	"runtime"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/lbryio/lbcd/blockchain"
    16  	"github.com/lbryio/lbcd/chaincfg"
    17  	"github.com/lbryio/lbcd/chaincfg/chainhash"
    18  	"github.com/lbryio/lbcd/mining"
    19  	"github.com/lbryio/lbcd/wire"
    20  	btcutil "github.com/lbryio/lbcutil"
    21  )
    22  
    23  const (
    24  	// maxNonce is the maximum value a nonce can be in a block header.
    25  	maxNonce = ^uint32(0) // 2^32 - 1
    26  
    27  	// maxExtraNonce is the maximum value an extra nonce used in a coinbase
    28  	// transaction can be.
    29  	maxExtraNonce = ^uint64(0) // 2^64 - 1
    30  
    31  	// hpsUpdateSecs is the number of seconds to wait in between each
    32  	// update to the hashes per second monitor.
    33  	hpsUpdateSecs = 10
    34  
    35  	// hashUpdateSec is the number of seconds each worker waits in between
    36  	// notifying the speed monitor with how many hashes have been completed
    37  	// while they are actively searching for a solution.  This is done to
    38  	// reduce the amount of syncs between the workers that must be done to
    39  	// keep track of the hashes per second.
    40  	hashUpdateSecs = 15
    41  )
    42  
    43  var (
    44  	// defaultNumWorkers is the default number of workers to use for mining
    45  	// and is based on the number of processor cores.  This helps ensure the
    46  	// system stays reasonably responsive under heavy load.
    47  	defaultNumWorkers = uint32(runtime.NumCPU())
    48  )
    49  
    50  // Config is a descriptor containing the cpu miner configuration.
    51  type Config struct {
    52  	// ChainParams identifies which chain parameters the cpu miner is
    53  	// associated with.
    54  	ChainParams *chaincfg.Params
    55  
    56  	// BlockTemplateGenerator identifies the instance to use in order to
    57  	// generate block templates that the miner will attempt to solve.
    58  	BlockTemplateGenerator *mining.BlkTmplGenerator
    59  
    60  	// MiningAddrs is a list of payment addresses to use for the generated
    61  	// blocks.  Each generated block will randomly choose one of them.
    62  	MiningAddrs []btcutil.Address
    63  
    64  	// ProcessBlock defines the function to call with any solved blocks.
    65  	// It typically must run the provided block through the same set of
    66  	// rules and handling as any other block coming from the network.
    67  	ProcessBlock func(*btcutil.Block, blockchain.BehaviorFlags) (bool, error)
    68  
    69  	// ConnectedCount defines the function to use to obtain how many other
    70  	// peers the server is connected to.  This is used by the automatic
    71  	// persistent mining routine to determine whether or it should attempt
    72  	// mining.  This is useful because there is no point in mining when not
    73  	// connected to any peers since there would no be anyone to send any
    74  	// found blocks to.
    75  	ConnectedCount func() int32
    76  
    77  	// IsCurrent defines the function to use to obtain whether or not the
    78  	// block chain is current.  This is used by the automatic persistent
    79  	// mining routine to determine whether or it should attempt mining.
    80  	// This is useful because there is no point in mining if the chain is
    81  	// not current since any solved blocks would be on a side chain and and
    82  	// up orphaned anyways.
    83  	IsCurrent func() bool
    84  }
    85  
    86  // CPUMiner provides facilities for solving blocks (mining) using the CPU in
    87  // a concurrency-safe manner.  It consists of two main goroutines -- a speed
    88  // monitor and a controller for worker goroutines which generate and solve
    89  // blocks.  The number of goroutines can be set via the SetMaxGoRoutines
    90  // function, but the default is based on the number of processor cores in the
    91  // system which is typically sufficient.
    92  type CPUMiner struct {
    93  	sync.Mutex
    94  	g                 *mining.BlkTmplGenerator
    95  	cfg               Config
    96  	numWorkers        uint32
    97  	started           bool
    98  	discreteMining    bool
    99  	submitBlockLock   sync.Mutex
   100  	wg                sync.WaitGroup
   101  	workerWg          sync.WaitGroup
   102  	updateNumWorkers  chan struct{}
   103  	queryHashesPerSec chan float64
   104  	updateHashes      chan uint64
   105  	speedMonitorQuit  chan struct{}
   106  	quit              chan struct{}
   107  }
   108  
   109  // speedMonitor handles tracking the number of hashes per second the mining
   110  // process is performing.  It must be run as a goroutine.
   111  func (m *CPUMiner) speedMonitor() {
   112  	log.Tracef("CPU miner speed monitor started")
   113  
   114  	var hashesPerSec float64
   115  	var totalHashes uint64
   116  	ticker := time.NewTicker(time.Second * hpsUpdateSecs)
   117  	defer ticker.Stop()
   118  
   119  out:
   120  	for {
   121  		select {
   122  		// Periodic updates from the workers with how many hashes they
   123  		// have performed.
   124  		case numHashes := <-m.updateHashes:
   125  			totalHashes += numHashes
   126  
   127  		// Time to update the hashes per second.
   128  		case <-ticker.C:
   129  			curHashesPerSec := float64(totalHashes) / hpsUpdateSecs
   130  			if hashesPerSec == 0 {
   131  				hashesPerSec = curHashesPerSec
   132  			}
   133  			hashesPerSec = (hashesPerSec + curHashesPerSec) / 2
   134  			totalHashes = 0
   135  			if hashesPerSec != 0 {
   136  				log.Debugf("Hash speed: %6.0f kilohashes/s",
   137  					hashesPerSec/1000)
   138  			}
   139  
   140  		// Request for the number of hashes per second.
   141  		case m.queryHashesPerSec <- hashesPerSec:
   142  			// Nothing to do.
   143  
   144  		case <-m.speedMonitorQuit:
   145  			break out
   146  		}
   147  	}
   148  
   149  	m.wg.Done()
   150  	log.Tracef("CPU miner speed monitor done")
   151  }
   152  
   153  // submitBlock submits the passed block to network after ensuring it passes all
   154  // of the consensus validation rules.
   155  func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
   156  	m.submitBlockLock.Lock()
   157  	defer m.submitBlockLock.Unlock()
   158  
   159  	// Ensure the block is not stale since a new block could have shown up
   160  	// while the solution was being found.  Typically that condition is
   161  	// detected and all work on the stale block is halted to start work on
   162  	// a new block, but the check only happens periodically, so it is
   163  	// possible a block was found and submitted in between.
   164  	msgBlock := block.MsgBlock()
   165  	if !msgBlock.Header.PrevBlock.IsEqual(&m.g.BestSnapshot().Hash) {
   166  		log.Debugf("Block submitted via CPU miner with previous "+
   167  			"block %s is stale", msgBlock.Header.PrevBlock)
   168  		return false
   169  	}
   170  
   171  	// Process this block using the same rules as blocks coming from other
   172  	// nodes.  This will in turn relay it to the network like normal.
   173  	isOrphan, err := m.cfg.ProcessBlock(block, blockchain.BFNone)
   174  	if err != nil {
   175  		// Anything other than a rule violation is an unexpected error,
   176  		// so log that error as an internal error.
   177  		if _, ok := err.(blockchain.RuleError); !ok {
   178  			log.Errorf("Unexpected error while processing "+
   179  				"block submitted via CPU miner: %v", err)
   180  			return false
   181  		}
   182  
   183  		log.Debugf("Block submitted via CPU miner rejected: %v", err)
   184  		return false
   185  	}
   186  	if isOrphan {
   187  		log.Debugf("Block submitted via CPU miner is an orphan")
   188  		return false
   189  	}
   190  
   191  	// The block was accepted.
   192  	coinbaseTx := block.MsgBlock().Transactions[0].TxOut[0]
   193  	log.Infof("Block submitted via CPU miner accepted (hash %s, "+
   194  		"amount %v)", block.Hash(), btcutil.Amount(coinbaseTx.Value))
   195  	return true
   196  }
   197  
   198  // solveBlock attempts to find some combination of a nonce, extra nonce, and
   199  // current timestamp which makes the passed block hash to a value less than the
   200  // target difficulty.  The timestamp is updated periodically and the passed
   201  // block is modified with all tweaks during this process.  This means that
   202  // when the function returns true, the block is ready for submission.
   203  //
   204  // This function will return early with false when conditions that trigger a
   205  // stale block such as a new block showing up or periodically when there are
   206  // new transactions and enough time has elapsed without finding a solution.
   207  func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
   208  	ticker *time.Ticker, quit chan struct{}) bool {
   209  
   210  	// Choose a random extra nonce offset for this block template and
   211  	// worker.
   212  	enOffset, err := wire.RandomUint64()
   213  	if err != nil {
   214  		log.Errorf("Unexpected error while generating random "+
   215  			"extra nonce offset: %v", err)
   216  		enOffset = 0
   217  	}
   218  
   219  	// Create some convenience variables.
   220  	header := &msgBlock.Header
   221  	targetDifficulty := blockchain.CompactToBig(header.Bits)
   222  
   223  	// Initial state.
   224  	lastGenerated := time.Now()
   225  	lastTxUpdate := m.g.TxSource().LastUpdated()
   226  	hashesCompleted := uint64(0)
   227  
   228  	// Note that the entire extra nonce range is iterated and the offset is
   229  	// added relying on the fact that overflow will wrap around 0 as
   230  	// provided by the Go spec.
   231  	for extraNonce := uint64(0); extraNonce < maxExtraNonce; extraNonce++ {
   232  		// Update the extra nonce in the block template with the
   233  		// new value by regenerating the coinbase script and
   234  		// setting the merkle root to the new value.
   235  		m.g.UpdateExtraNonce(msgBlock, blockHeight, extraNonce+enOffset)
   236  
   237  		// Search through the entire nonce range for a solution while
   238  		// periodically checking for early quit and stale block
   239  		// conditions along with updates to the speed monitor.
   240  		for i := uint32(0); i <= maxNonce; i++ {
   241  			select {
   242  			case <-quit:
   243  				return false
   244  
   245  			case <-ticker.C:
   246  				m.updateHashes <- hashesCompleted
   247  				hashesCompleted = 0
   248  
   249  				// The current block is stale if the best block
   250  				// has changed.
   251  				best := m.g.BestSnapshot()
   252  				if !header.PrevBlock.IsEqual(&best.Hash) {
   253  					return false
   254  				}
   255  
   256  				// The current block is stale if the memory pool
   257  				// has been updated since the block template was
   258  				// generated and it has been at least one
   259  				// minute.
   260  				if lastTxUpdate != m.g.TxSource().LastUpdated() &&
   261  					time.Now().After(lastGenerated.Add(time.Minute)) {
   262  
   263  					return false
   264  				}
   265  
   266  				m.g.UpdateBlockTime(msgBlock)
   267  
   268  			default:
   269  				// Non-blocking select to fall through
   270  			}
   271  
   272  			// Update the nonce and hash the block header.  Each
   273  			// hash is actually a double sha256 (two hashes), so
   274  			// increment the number of hashes completed for each
   275  			// attempt accordingly.
   276  			header.Nonce = i
   277  			hash := header.BlockPoWHash()
   278  			hashesCompleted += 2
   279  
   280  			// The block is solved when the new block hash is less
   281  			// than the target difficulty.  Yay!
   282  			if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
   283  				m.updateHashes <- hashesCompleted
   284  				return true
   285  			}
   286  		}
   287  	}
   288  
   289  	return false
   290  }
   291  
   292  // generateBlocks is a worker that is controlled by the miningWorkerController.
   293  // It is self contained in that it creates block templates and attempts to solve
   294  // them while detecting when it is performing stale work and reacting
   295  // accordingly by generating a new block template.  When a block is solved, it
   296  // is submitted.
   297  //
   298  // It must be run as a goroutine.
   299  func (m *CPUMiner) generateBlocks(quit chan struct{}) {
   300  	log.Tracef("Starting generate blocks worker")
   301  
   302  	// Start a ticker which is used to signal checks for stale work and
   303  	// updates to the speed monitor.
   304  	ticker := time.NewTicker(time.Second * hashUpdateSecs)
   305  	defer ticker.Stop()
   306  out:
   307  	for {
   308  		// Quit when the miner is stopped.
   309  		select {
   310  		case <-quit:
   311  			break out
   312  		default:
   313  			// Non-blocking select to fall through
   314  		}
   315  
   316  		// Wait until there is a connection to at least one other peer
   317  		// since there is no way to relay a found block or receive
   318  		// transactions to work on when there are no connected peers.
   319  		if m.cfg.ConnectedCount() == 0 {
   320  			time.Sleep(time.Second)
   321  			continue
   322  		}
   323  
   324  		// No point in searching for a solution before the chain is
   325  		// synced.  Also, grab the same lock as used for block
   326  		// submission, since the current block will be changing and
   327  		// this would otherwise end up building a new block template on
   328  		// a block that is in the process of becoming stale.
   329  		m.submitBlockLock.Lock()
   330  		curHeight := m.g.BestSnapshot().Height
   331  		if curHeight != 0 && !m.cfg.IsCurrent() {
   332  			m.submitBlockLock.Unlock()
   333  			time.Sleep(time.Second)
   334  			continue
   335  		}
   336  
   337  		// Choose a payment address at random.
   338  		rand.Seed(time.Now().UnixNano())
   339  		payToAddr := m.cfg.MiningAddrs[rand.Intn(len(m.cfg.MiningAddrs))]
   340  
   341  		// Create a new block template using the available transactions
   342  		// in the memory pool as a source of transactions to potentially
   343  		// include in the block.
   344  		template, err := m.g.NewBlockTemplate(payToAddr)
   345  		m.submitBlockLock.Unlock()
   346  		if err != nil {
   347  			errStr := fmt.Sprintf("Failed to create new block "+
   348  				"template: %v", err)
   349  			log.Errorf(errStr)
   350  			continue
   351  		}
   352  
   353  		// Attempt to solve the block.  The function will exit early
   354  		// with false when conditions that trigger a stale block, so
   355  		// a new block template can be generated.  When the return is
   356  		// true a solution was found, so submit the solved block.
   357  		if m.solveBlock(template.Block, curHeight+1, ticker, quit) {
   358  			block := btcutil.NewBlock(template.Block)
   359  			m.submitBlock(block)
   360  		}
   361  	}
   362  
   363  	m.workerWg.Done()
   364  	log.Tracef("Generate blocks worker done")
   365  }
   366  
   367  // miningWorkerController launches the worker goroutines that are used to
   368  // generate block templates and solve them.  It also provides the ability to
   369  // dynamically adjust the number of running worker goroutines.
   370  //
   371  // It must be run as a goroutine.
   372  func (m *CPUMiner) miningWorkerController() {
   373  	// launchWorkers groups common code to launch a specified number of
   374  	// workers for generating blocks.
   375  	var runningWorkers []chan struct{}
   376  	launchWorkers := func(numWorkers uint32) {
   377  		for i := uint32(0); i < numWorkers; i++ {
   378  			quit := make(chan struct{})
   379  			runningWorkers = append(runningWorkers, quit)
   380  
   381  			m.workerWg.Add(1)
   382  			go m.generateBlocks(quit)
   383  		}
   384  	}
   385  
   386  	// Launch the current number of workers by default.
   387  	runningWorkers = make([]chan struct{}, 0, m.numWorkers)
   388  	launchWorkers(m.numWorkers)
   389  
   390  out:
   391  	for {
   392  		select {
   393  		// Update the number of running workers.
   394  		case <-m.updateNumWorkers:
   395  			// No change.
   396  			numRunning := uint32(len(runningWorkers))
   397  			if m.numWorkers == numRunning {
   398  				continue
   399  			}
   400  
   401  			// Add new workers.
   402  			if m.numWorkers > numRunning {
   403  				launchWorkers(m.numWorkers - numRunning)
   404  				continue
   405  			}
   406  
   407  			// Signal the most recently created goroutines to exit.
   408  			for i := numRunning - 1; i >= m.numWorkers; i-- {
   409  				close(runningWorkers[i])
   410  				runningWorkers[i] = nil
   411  				runningWorkers = runningWorkers[:i]
   412  			}
   413  
   414  		case <-m.quit:
   415  			for _, quit := range runningWorkers {
   416  				close(quit)
   417  			}
   418  			break out
   419  		}
   420  	}
   421  
   422  	// Wait until all workers shut down to stop the speed monitor since
   423  	// they rely on being able to send updates to it.
   424  	m.workerWg.Wait()
   425  	close(m.speedMonitorQuit)
   426  	m.wg.Done()
   427  }
   428  
   429  // Start begins the CPU mining process as well as the speed monitor used to
   430  // track hashing metrics.  Calling this function when the CPU miner has
   431  // already been started will have no effect.
   432  //
   433  // This function is safe for concurrent access.
   434  func (m *CPUMiner) Start() {
   435  	m.Lock()
   436  	defer m.Unlock()
   437  
   438  	// Nothing to do if the miner is already running or if running in
   439  	// discrete mode (using GenerateNBlocks).
   440  	if m.started || m.discreteMining {
   441  		return
   442  	}
   443  
   444  	m.quit = make(chan struct{})
   445  	m.speedMonitorQuit = make(chan struct{})
   446  	m.wg.Add(2)
   447  	go m.speedMonitor()
   448  	go m.miningWorkerController()
   449  
   450  	m.started = true
   451  	log.Infof("CPU miner started")
   452  }
   453  
   454  // Stop gracefully stops the mining process by signalling all workers, and the
   455  // speed monitor to quit.  Calling this function when the CPU miner has not
   456  // already been started will have no effect.
   457  //
   458  // This function is safe for concurrent access.
   459  func (m *CPUMiner) Stop() {
   460  	m.Lock()
   461  	defer m.Unlock()
   462  
   463  	// Nothing to do if the miner is not currently running or if running in
   464  	// discrete mode (using GenerateNBlocks).
   465  	if !m.started || m.discreteMining {
   466  		return
   467  	}
   468  
   469  	close(m.quit)
   470  	m.wg.Wait()
   471  	m.started = false
   472  	log.Infof("CPU miner stopped")
   473  }
   474  
   475  // IsMining returns whether or not the CPU miner has been started and is
   476  // therefore currenting mining.
   477  //
   478  // This function is safe for concurrent access.
   479  func (m *CPUMiner) IsMining() bool {
   480  	m.Lock()
   481  	defer m.Unlock()
   482  
   483  	return m.started
   484  }
   485  
   486  // HashesPerSecond returns the number of hashes per second the mining process
   487  // is performing.  0 is returned if the miner is not currently running.
   488  //
   489  // This function is safe for concurrent access.
   490  func (m *CPUMiner) HashesPerSecond() float64 {
   491  	m.Lock()
   492  	defer m.Unlock()
   493  
   494  	// Nothing to do if the miner is not currently running.
   495  	if !m.started {
   496  		return 0
   497  	}
   498  
   499  	return <-m.queryHashesPerSec
   500  }
   501  
   502  // SetNumWorkers sets the number of workers to create which solve blocks.  Any
   503  // negative values will cause a default number of workers to be used which is
   504  // based on the number of processor cores in the system.  A value of 0 will
   505  // cause all CPU mining to be stopped.
   506  //
   507  // This function is safe for concurrent access.
   508  func (m *CPUMiner) SetNumWorkers(numWorkers int32) {
   509  	if numWorkers == 0 {
   510  		m.Stop()
   511  	}
   512  
   513  	// Don't lock until after the first check since Stop does its own
   514  	// locking.
   515  	m.Lock()
   516  	defer m.Unlock()
   517  
   518  	// Use default if provided value is negative.
   519  	if numWorkers < 0 {
   520  		m.numWorkers = defaultNumWorkers
   521  	} else {
   522  		m.numWorkers = uint32(numWorkers)
   523  	}
   524  
   525  	// When the miner is already running, notify the controller about the
   526  	// the change.
   527  	if m.started {
   528  		m.updateNumWorkers <- struct{}{}
   529  	}
   530  }
   531  
   532  // NumWorkers returns the number of workers which are running to solve blocks.
   533  //
   534  // This function is safe for concurrent access.
   535  func (m *CPUMiner) NumWorkers() int32 {
   536  	m.Lock()
   537  	defer m.Unlock()
   538  
   539  	return int32(m.numWorkers)
   540  }
   541  
   542  // GenerateNBlocks generates the requested number of blocks. It is self
   543  // contained in that it creates block templates and attempts to solve them while
   544  // detecting when it is performing stale work and reacting accordingly by
   545  // generating a new block template.  When a block is solved, it is submitted.
   546  // The function returns a list of the hashes of generated blocks.
   547  func (m *CPUMiner) GenerateNBlocks(n uint32, payToAddr btcutil.Address) ([]*chainhash.Hash, error) {
   548  	m.Lock()
   549  
   550  	// Respond with an error if server is already mining.
   551  	if m.started || m.discreteMining {
   552  		m.Unlock()
   553  		return nil, errors.New("Server is already CPU mining. Please call " +
   554  			"`setgenerate 0` before calling discrete `generate` commands.")
   555  	}
   556  
   557  	m.started = true
   558  	m.discreteMining = true
   559  
   560  	m.speedMonitorQuit = make(chan struct{})
   561  	m.wg.Add(1)
   562  	go m.speedMonitor()
   563  
   564  	m.Unlock()
   565  
   566  	log.Tracef("Generating %d blocks", n)
   567  
   568  	i := uint32(0)
   569  	blockHashes := make([]*chainhash.Hash, n)
   570  
   571  	// Start a ticker which is used to signal checks for stale work and
   572  	// updates to the speed monitor.
   573  	ticker := time.NewTicker(time.Second * hashUpdateSecs)
   574  	defer ticker.Stop()
   575  
   576  	for {
   577  		// Read updateNumWorkers in case someone tries a `setgenerate` while
   578  		// we're generating. We can ignore it as the `generate` RPC call only
   579  		// uses 1 worker.
   580  		select {
   581  		case <-m.updateNumWorkers:
   582  		default:
   583  		}
   584  
   585  		// Grab the lock used for block submission, since the current block will
   586  		// be changing and this would otherwise end up building a new block
   587  		// template on a block that is in the process of becoming stale.
   588  		m.submitBlockLock.Lock()
   589  		curHeight := m.g.BestSnapshot().Height
   590  
   591  		// Choose a payment address at random.
   592  		rand.Seed(time.Now().UnixNano())
   593  		if payToAddr == nil {
   594  			payToAddr = m.cfg.MiningAddrs[rand.Intn(len(m.cfg.MiningAddrs))]
   595  		}
   596  		// Create a new block template using the available transactions
   597  		// in the memory pool as a source of transactions to potentially
   598  		// include in the block.
   599  		template, err := m.g.NewBlockTemplate(payToAddr)
   600  		m.submitBlockLock.Unlock()
   601  		if err != nil {
   602  			errStr := fmt.Sprintf("Failed to create new block "+
   603  				"template: %v", err)
   604  			log.Errorf(errStr)
   605  			continue
   606  		}
   607  
   608  		// Attempt to solve the block.  The function will exit early
   609  		// with false when conditions that trigger a stale block, so
   610  		// a new block template can be generated.  When the return is
   611  		// true a solution was found, so submit the solved block.
   612  		if m.solveBlock(template.Block, curHeight+1, ticker, nil) {
   613  			block := btcutil.NewBlock(template.Block)
   614  			m.submitBlock(block)
   615  			blockHashes[i] = block.Hash()
   616  			i++
   617  			if i == n {
   618  				log.Tracef("Generated %d blocks", i)
   619  				m.Lock()
   620  				close(m.speedMonitorQuit)
   621  				m.wg.Wait()
   622  				m.started = false
   623  				m.discreteMining = false
   624  				m.Unlock()
   625  				return blockHashes, nil
   626  			}
   627  		}
   628  	}
   629  }
   630  
   631  // New returns a new instance of a CPU miner for the provided configuration.
   632  // Use Start to begin the mining process.  See the documentation for CPUMiner
   633  // type for more details.
   634  func New(cfg *Config) *CPUMiner {
   635  	return &CPUMiner{
   636  		g:                 cfg.BlockTemplateGenerator,
   637  		cfg:               *cfg,
   638  		numWorkers:        defaultNumWorkers,
   639  		updateNumWorkers:  make(chan struct{}),
   640  		queryHashesPerSec: make(chan float64),
   641  		updateHashes:      make(chan uint64, 512),
   642  	}
   643  }