github.com/dominant-strategies/go-quai@v0.28.2/consensus/progpow/progpow.go (about)

     1  package progpow
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"math/rand"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strconv"
    13  	"sync"
    14  	"time"
    15  	"unsafe"
    16  
    17  	"github.com/dominant-strategies/go-quai/common"
    18  	"github.com/dominant-strategies/go-quai/common/hexutil"
    19  	"github.com/dominant-strategies/go-quai/consensus"
    20  	"github.com/dominant-strategies/go-quai/log"
    21  	"github.com/dominant-strategies/go-quai/metrics"
    22  	"github.com/dominant-strategies/go-quai/rpc"
    23  	mmap "github.com/edsrzf/mmap-go"
    24  	"github.com/hashicorp/golang-lru/simplelru"
    25  )
    26  
    27  var (
    28  	// sharedProgpow is a full instance that can be shared between multiple users.
    29  	sharedProgpow *Progpow
    30  	// algorithmRevision is the data structure version used for file naming.
    31  	algorithmRevision = 1
    32  	// dumpMagic is a dataset dump header to sanity check a data dump.
    33  	dumpMagic = []uint32{0xbaddcafe, 0xfee1dead}
    34  )
    35  
    36  var ErrInvalidDumpMagic = errors.New("invalid dump magic")
    37  
    38  func init() {
    39  	sharedConfig := Config{
    40  		PowMode: ModeNormal,
    41  	}
    42  	sharedProgpow = New(sharedConfig, nil, false)
    43  }
    44  
    45  // isLittleEndian returns whether the local system is running in little or big
    46  // endian byte order.
    47  func isLittleEndian() bool {
    48  	n := uint32(0x01020304)
    49  	return *(*byte)(unsafe.Pointer(&n)) == 0x04
    50  }
    51  
    52  // memoryMap tries to memory map a file of uint32s for read only access.
    53  func memoryMap(path string, lock bool) (*os.File, mmap.MMap, []uint32, error) {
    54  	file, err := os.OpenFile(path, os.O_RDONLY, 0644)
    55  	if err != nil {
    56  		return nil, nil, nil, err
    57  	}
    58  	mem, buffer, err := memoryMapFile(file, false)
    59  	if err != nil {
    60  		file.Close()
    61  		return nil, nil, nil, err
    62  	}
    63  	for i, magic := range dumpMagic {
    64  		if buffer[i] != magic {
    65  			mem.Unmap()
    66  			file.Close()
    67  			return nil, nil, nil, ErrInvalidDumpMagic
    68  		}
    69  	}
    70  	if lock {
    71  		if err := mem.Lock(); err != nil {
    72  			mem.Unmap()
    73  			file.Close()
    74  			return nil, nil, nil, err
    75  		}
    76  	}
    77  	return file, mem, buffer[len(dumpMagic):], err
    78  }
    79  
    80  // memoryMapFile tries to memory map an already opened file descriptor.
    81  func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) {
    82  	// Try to memory map the file
    83  	flag := mmap.RDONLY
    84  	if write {
    85  		flag = mmap.RDWR
    86  	}
    87  	mem, err := mmap.Map(file, flag, 0)
    88  	if err != nil {
    89  		return nil, nil, err
    90  	}
    91  	// Yay, we managed to memory map the file, here be dragons
    92  	header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem))
    93  	header.Len /= 4
    94  	header.Cap /= 4
    95  
    96  	return mem, *(*[]uint32)(unsafe.Pointer(&header)), nil
    97  }
    98  
    99  // memoryMapAndGenerate tries to memory map a temporary file of uint32s for write
   100  // access, fill it with the data from a generator and then move it into the final
   101  // path requested.
   102  func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(buffer []uint32)) (*os.File, mmap.MMap, []uint32, error) {
   103  	// Ensure the data folder exists
   104  	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
   105  		return nil, nil, nil, err
   106  	}
   107  	// Create a huge temporary empty file to fill with data
   108  	temp := path + "." + strconv.Itoa(rand.Int())
   109  
   110  	dump, err := os.Create(temp)
   111  	if err != nil {
   112  		return nil, nil, nil, err
   113  	}
   114  	if err = dump.Truncate(int64(len(dumpMagic))*4 + int64(size)); err != nil {
   115  		return nil, nil, nil, err
   116  	}
   117  	// Memory map the file for writing and fill it with the generator
   118  	mem, buffer, err := memoryMapFile(dump, true)
   119  	if err != nil {
   120  		dump.Close()
   121  		return nil, nil, nil, err
   122  	}
   123  	copy(buffer, dumpMagic)
   124  
   125  	data := buffer[len(dumpMagic):]
   126  	generator(data)
   127  
   128  	if err := mem.Unmap(); err != nil {
   129  		return nil, nil, nil, err
   130  	}
   131  	if err := dump.Close(); err != nil {
   132  		return nil, nil, nil, err
   133  	}
   134  	if err := os.Rename(temp, path); err != nil {
   135  		return nil, nil, nil, err
   136  	}
   137  	return memoryMap(path, lock)
   138  }
   139  
   140  // Mode defines the type and amount of PoW verification a progpow engine makes.
   141  type Mode uint
   142  
   143  const (
   144  	ModeNormal Mode = iota
   145  	ModeShared
   146  	ModeTest
   147  	ModeFake
   148  	ModeFullFake
   149  )
   150  
   151  // Config are the configuration parameters of the progpow.
   152  type Config struct {
   153  	PowMode Mode
   154  
   155  	CacheDir       string
   156  	CachesInMem    int
   157  	CachesOnDisk   int
   158  	CachesLockMmap bool
   159  	DurationLimit  *big.Int
   160  	GasCeil        uint64
   161  	MinDifficulty  *big.Int
   162  
   163  	// When set, notifications sent by the remote sealer will
   164  	// be block header JSON objects instead of work package arrays.
   165  	NotifyFull bool
   166  
   167  	Log *log.Logger `toml:"-"`
   168  }
   169  
   170  // Progpow is a proof-of-work consensus engine using the blake3 hash algorithm
   171  type Progpow struct {
   172  	config Config
   173  
   174  	caches *lru // In memory caches to avoid regenerating too often
   175  
   176  	// Mining related fields
   177  	rand     *rand.Rand    // Properly seeded random source for nonces
   178  	threads  int           // Number of threads to mine on if mining
   179  	update   chan struct{} // Notification channel to update mining parameters
   180  	hashrate metrics.Meter // Meter tracking the average hashrate
   181  	remote   *remoteSealer
   182  
   183  	// The fields below are hooks for testing
   184  	shared    *Progpow      // Shared PoW verifier to avoid cache regeneration
   185  	fakeFail  uint64        // Block number which fails PoW check even in fake mode
   186  	fakeDelay time.Duration // Time delay to sleep for before returning from verify
   187  
   188  	lock      sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
   189  	closeOnce sync.Once  // Ensures exit channel will not be closed twice.
   190  }
   191  
   192  // New creates a full sized progpow PoW scheme and starts a background thread for
   193  // remote mining, also optionally notifying a batch of remote services of new work
   194  // packages.
   195  func New(config Config, notify []string, noverify bool) *Progpow {
   196  	if config.Log == nil {
   197  		config.Log = &log.Log
   198  	}
   199  	if config.CachesInMem <= 0 {
   200  		config.Log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem)
   201  		config.CachesInMem = 1
   202  	}
   203  	if config.CacheDir != "" && config.CachesOnDisk > 0 {
   204  		config.Log.Info("Disk storage enabled for ethash caches", "dir", config.CacheDir, "count", config.CachesOnDisk)
   205  	}
   206  	progpow := &Progpow{
   207  		config:   config,
   208  		caches:   newlru("cache", config.CachesInMem, newCache),
   209  		update:   make(chan struct{}),
   210  		hashrate: metrics.NewMeterForced(),
   211  	}
   212  	if config.PowMode == ModeShared {
   213  		progpow.shared = sharedProgpow
   214  	}
   215  	progpow.remote = startRemoteSealer(progpow, notify, noverify)
   216  	return progpow
   217  }
   218  
   219  // NewTester creates a small sized progpow PoW scheme useful only for testing
   220  // purposes.
   221  func NewTester(notify []string, noverify bool) *Progpow {
   222  	return New(Config{PowMode: ModeTest}, notify, noverify)
   223  }
   224  
   225  // NewFaker creates a progpow consensus engine with a fake PoW scheme that accepts
   226  // all blocks' seal as valid, though they still have to conform to the Quai
   227  // consensus rules.
   228  func NewFaker() *Progpow {
   229  	return &Progpow{
   230  		config: Config{
   231  			PowMode: ModeFake,
   232  			Log:     &log.Log,
   233  		},
   234  	}
   235  }
   236  
   237  // NewFakeFailer creates a progpow consensus engine with a fake PoW scheme that
   238  // accepts all blocks as valid apart from the single one specified, though they
   239  // still have to conform to the Quai consensus rules.
   240  func NewFakeFailer(fail uint64) *Progpow {
   241  	return &Progpow{
   242  		config: Config{
   243  			PowMode: ModeFake,
   244  			Log:     &log.Log,
   245  		},
   246  		fakeFail: fail,
   247  	}
   248  }
   249  
   250  // NewFakeDelayer creates a progpow consensus engine with a fake PoW scheme that
   251  // accepts all blocks as valid, but delays verifications by some time, though
   252  // they still have to conform to the Quai consensus rules.
   253  func NewFakeDelayer(delay time.Duration) *Progpow {
   254  	return &Progpow{
   255  		config: Config{
   256  			PowMode: ModeFake,
   257  			Log:     &log.Log,
   258  		},
   259  		fakeDelay: delay,
   260  	}
   261  }
   262  
   263  // NewFullFaker creates an progpow consensus engine with a full fake scheme that
   264  // accepts all blocks as valid, without checking any consensus rules whatsoever.
   265  func NewFullFaker() *Progpow {
   266  	return &Progpow{
   267  		config: Config{
   268  			PowMode: ModeFullFake,
   269  			Log:     &log.Log,
   270  		},
   271  	}
   272  }
   273  
   274  // NewShared creates a full sized progpow PoW shared between all requesters running
   275  // in the same process.
   276  func NewShared() *Progpow {
   277  	return &Progpow{shared: sharedProgpow}
   278  }
   279  
   280  // Close closes the exit channel to notify all backend threads exiting.
   281  func (progpow *Progpow) Close() error {
   282  	progpow.closeOnce.Do(func() {
   283  		// Short circuit if the exit channel is not allocated.
   284  		if progpow.remote == nil {
   285  			return
   286  		}
   287  		close(progpow.remote.requestExit)
   288  		<-progpow.remote.exitCh
   289  	})
   290  	return nil
   291  }
   292  
   293  // lru tracks caches or datasets by their last use time, keeping at most N of them.
   294  type lru struct {
   295  	what string
   296  	new  func(epoch uint64) interface{}
   297  	mu   sync.Mutex
   298  	// Items are kept in a LRU cache, but there is a special case:
   299  	// We always keep an item for (highest seen epoch) + 1 as the 'future item'.
   300  	cache      *simplelru.LRU
   301  	future     uint64
   302  	futureItem interface{}
   303  }
   304  
   305  // newlru create a new least-recently-used cache for either the verification caches
   306  // or the mining datasets.
   307  func newlru(what string, maxItems int, new func(epoch uint64) interface{}) *lru {
   308  	if maxItems <= 0 {
   309  		maxItems = 1
   310  	}
   311  	cache, _ := simplelru.NewLRU(maxItems, func(key, value interface{}) {
   312  		log.Trace("Evicted ethash "+what, "epoch", key)
   313  	})
   314  	return &lru{what: what, new: new, cache: cache}
   315  }
   316  
   317  // get retrieves or creates an item for the given epoch. The first return value is always
   318  // non-nil. The second return value is non-nil if lru thinks that an item will be useful in
   319  // the near future.
   320  func (lru *lru) get(epoch uint64) (item, future interface{}) {
   321  	lru.mu.Lock()
   322  	defer lru.mu.Unlock()
   323  
   324  	// Get or create the item for the requested epoch.
   325  	item, ok := lru.cache.Get(epoch)
   326  	if !ok {
   327  		if lru.future > 0 && lru.future == epoch {
   328  			item = lru.futureItem
   329  		} else {
   330  			log.Trace("Requiring new ethash "+lru.what, "epoch", epoch)
   331  			item = lru.new(epoch)
   332  		}
   333  		lru.cache.Add(epoch, item)
   334  	}
   335  	// Update the 'future item' if epoch is larger than previously seen.
   336  	if epoch < maxEpoch-1 && lru.future < epoch+1 {
   337  		log.Trace("Requiring new future ethash "+lru.what, "epoch", epoch+1)
   338  		future = lru.new(epoch + 1)
   339  		lru.future = epoch + 1
   340  		lru.futureItem = future
   341  	}
   342  	return item, future
   343  }
   344  
   345  // cache wraps an ethash cache with some metadata to allow easier concurrent use.
   346  type cache struct {
   347  	epoch uint64    // Epoch for which this cache is relevant
   348  	dump  *os.File  // File descriptor of the memory mapped cache
   349  	mmap  mmap.MMap // Memory map itself to unmap before releasing
   350  	cache []uint32  // The actual cache data content (may be memory mapped)
   351  	cDag  []uint32  // The cDag used by progpow. May be nil
   352  	once  sync.Once // Ensures the cache is generated only once
   353  }
   354  
   355  // newCache creates a new ethash verification cache and returns it as a plain Go
   356  // interface to be usable in an LRU cache.
   357  func newCache(epoch uint64) interface{} {
   358  	return &cache{epoch: epoch}
   359  }
   360  
   361  // generate ensures that the cache content is generated before use.
   362  func (c *cache) generate(dir string, limit int, lock bool, test bool) {
   363  	c.once.Do(func() {
   364  		size := cacheSize(c.epoch*epochLength + 1)
   365  		seed := seedHash(c.epoch*epochLength + 1)
   366  		if test {
   367  			size = 1024
   368  		}
   369  		// If we don't store anything on disk, generate and return.
   370  		if dir == "" {
   371  			c.cache = make([]uint32, size/4)
   372  			generateCache(c.cache, c.epoch, seed)
   373  			c.cDag = make([]uint32, progpowCacheWords)
   374  			generateCDag(c.cDag, c.cache, c.epoch)
   375  			return
   376  		}
   377  		// Disk storage is needed, this will get fancy
   378  		var endian string
   379  		if !isLittleEndian() {
   380  			endian = ".be"
   381  		}
   382  		path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
   383  		logger := log.New("epoch")
   384  
   385  		// We're about to mmap the file, ensure that the mapping is cleaned up when the
   386  		// cache becomes unused.
   387  		runtime.SetFinalizer(c, (*cache).finalizer)
   388  
   389  		// Try to load the file from disk and memory map it
   390  		var err error
   391  		c.dump, c.mmap, c.cache, err = memoryMap(path, lock)
   392  		if err == nil {
   393  			logger.Debug("Loaded old ethash cache from disk")
   394  			c.cDag = make([]uint32, progpowCacheWords)
   395  			generateCDag(c.cDag, c.cache, c.epoch)
   396  			return
   397  		}
   398  		logger.Debug("Failed to load old ethash cache", "err", err)
   399  
   400  		// No previous cache available, create a new cache file to fill
   401  		c.dump, c.mmap, c.cache, err = memoryMapAndGenerate(path, size, lock, func(buffer []uint32) { generateCache(buffer, c.epoch, seed) })
   402  		if err != nil {
   403  			logger.Error("Failed to generate mapped ethash cache", "err", err)
   404  
   405  			c.cache = make([]uint32, size/4)
   406  			generateCache(c.cache, c.epoch, seed)
   407  		}
   408  		c.cDag = make([]uint32, progpowCacheWords)
   409  		generateCDag(c.cDag, c.cache, c.epoch)
   410  		// Iterate over all previous instances and delete old ones
   411  		for ep := int(c.epoch) - limit; ep >= 0; ep-- {
   412  			seed := seedHash(uint64(ep)*epochLength + 1)
   413  			path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
   414  			os.Remove(path)
   415  		}
   416  	})
   417  }
   418  
   419  // finalizer unmaps the memory and closes the file.
   420  func (c *cache) finalizer() {
   421  	if c.mmap != nil {
   422  		c.mmap.Unmap()
   423  		c.dump.Close()
   424  		c.mmap, c.dump = nil, nil
   425  	}
   426  }
   427  
   428  // cache tries to retrieve a verification cache for the specified block number
   429  // by first checking against a list of in-memory caches, then against caches
   430  // stored on disk, and finally generating one if none can be found.
   431  func (progpow *Progpow) cache(block uint64) *cache {
   432  	epoch := block / epochLength
   433  	currentI, futureI := progpow.caches.get(epoch)
   434  	current := currentI.(*cache)
   435  
   436  	// Wait for generation finish.
   437  	current.generate(progpow.config.CacheDir, progpow.config.CachesOnDisk, progpow.config.CachesLockMmap, progpow.config.PowMode == ModeTest)
   438  
   439  	// If we need a new future cache, now's a good time to regenerate it.
   440  	if futureI != nil {
   441  		future := futureI.(*cache)
   442  		go future.generate(progpow.config.CacheDir, progpow.config.CachesOnDisk, progpow.config.CachesLockMmap, progpow.config.PowMode == ModeTest)
   443  	}
   444  	return current
   445  }
   446  
   447  // Threads returns the number of mining threads currently enabled. This doesn't
   448  // necessarily mean that mining is running!
   449  func (progpow *Progpow) Threads() int {
   450  	progpow.lock.Lock()
   451  	defer progpow.lock.Unlock()
   452  
   453  	return progpow.threads
   454  }
   455  
   456  // SetThreads updates the number of mining threads currently enabled. Calling
   457  // this method does not start mining, only sets the thread count. If zero is
   458  // specified, the miner will use all cores of the machine. Setting a thread
   459  // count below zero is allowed and will cause the miner to idle, without any
   460  // work being done.
   461  func (progpow *Progpow) SetThreads(threads int) {
   462  	progpow.lock.Lock()
   463  	defer progpow.lock.Unlock()
   464  
   465  	if progpow.shared != nil {
   466  		// If we're running a shared PoW, set the thread count on that instead
   467  		progpow.shared.SetThreads(threads)
   468  	} else {
   469  		// Update the threads and ping any running seal to pull in any changes
   470  		progpow.threads = threads
   471  		select {
   472  		case progpow.update <- struct{}{}:
   473  		default:
   474  		}
   475  	}
   476  }
   477  
   478  // Hashrate implements PoW, returning the measured rate of the search invocations
   479  // per second over the last minute.
   480  // Note the returned hashrate includes local hashrate, but also includes the total
   481  // hashrate of all remote miner.
   482  func (progpow *Progpow) Hashrate() float64 {
   483  	// Short circuit if we are run the progpow in normal/test mode.
   484  	if progpow.config.PowMode != ModeNormal && progpow.config.PowMode != ModeTest {
   485  		return progpow.hashrate.Rate1()
   486  	}
   487  	var res = make(chan uint64, 1)
   488  
   489  	select {
   490  	case progpow.remote.fetchRateCh <- res:
   491  	case <-progpow.remote.exitCh:
   492  		// Return local hashrate only if progpow is stopped.
   493  		return progpow.hashrate.Rate1()
   494  	}
   495  
   496  	// Gather total submitted hash rate of remote sealers.
   497  	return progpow.hashrate.Rate1() + float64(<-res)
   498  }
   499  
   500  // SubmitHashrate can be used for remote miners to submit their hash rate.
   501  // This enables the node to report the combined hash rate of all miners
   502  // which submit work through this node.
   503  //
   504  // It accepts the miner hash rate and an identifier which must be unique
   505  // between nodes.
   506  func (progpow *Progpow) SubmitHashrate(rate hexutil.Uint64, id common.Hash) bool {
   507  	if progpow.remote == nil {
   508  		return false
   509  	}
   510  
   511  	var done = make(chan struct{}, 1)
   512  	select {
   513  	case progpow.remote.submitRateCh <- &hashrate{done: done, rate: uint64(rate), id: id}:
   514  	case <-progpow.remote.exitCh:
   515  		return false
   516  	}
   517  
   518  	// Block until hash rate submitted successfully.
   519  	<-done
   520  	return true
   521  }
   522  
   523  // APIs implements consensus.Engine, returning the user facing RPC APIs.
   524  func (progpow *Progpow) APIs(chain consensus.ChainHeaderReader) []rpc.API {
   525  	// In order to ensure backward compatibility, we exposes progpow RPC APIs
   526  	// to both eth and progpow namespaces.
   527  	return []rpc.API{
   528  		{
   529  			Namespace: "eth",
   530  			Version:   "1.0",
   531  			Service:   &API{progpow},
   532  			Public:    true,
   533  		},
   534  		{
   535  			Namespace: "progpow",
   536  			Version:   "1.0",
   537  			Service:   &API{progpow},
   538  			Public:    true,
   539  		},
   540  	}
   541  }