github.com/bigzoro/my_simplechain@v0.0.0-20240315012955-8ad0a2a29bb9/consensus/scrypt/sealer.go (about)

     1  // Copyright (c) 2019 Simplechain
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Lesser General Public License as published by
     5  // the Free Software Foundation, either version 3 of the License, or
     6  // (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    11  // GNU Lesser General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Lesser General Public License
    14  // along with this program. If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package scrypt
    17  
    18  import (
    19  	"bytes"
    20  	crand "crypto/rand"
    21  	"encoding/json"
    22  	"errors"
    23  	"math"
    24  	"math/big"
    25  	"math/rand"
    26  	"net/http"
    27  	"runtime"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/bigzoro/my_simplechain/common"
    32  	"github.com/bigzoro/my_simplechain/common/hexutil"
    33  	"github.com/bigzoro/my_simplechain/consensus"
    34  	"github.com/bigzoro/my_simplechain/core/types"
    35  	"github.com/bigzoro/my_simplechain/log"
    36  )
    37  
    38  const (
    39  	// staleThreshold is the maximum depth of the acceptable stale but valid scrypt solution.
    40  	staleThreshold = 7
    41  )
    42  
    43  var (
    44  	errNoMiningWork      = errors.New("no mining work available yet")
    45  	errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
    46  )
    47  
    48  // Seal implements consensus.Engine, attempting to find a nonce that satisfies
    49  // the block's difficulty requirements.
    50  func (powScrypt *PowScrypt) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
    51  	// If we're running a fake PoW, simply return a 0 nonce immediately
    52  	if powScrypt.config.PowMode == ModeFake || powScrypt.config.PowMode == ModeFullFake {
    53  		header := block.Header()
    54  		header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
    55  		select {
    56  		case results <- block.WithSeal(header):
    57  		default:
    58  			log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", powScrypt.SealHash(block.Header()))
    59  		}
    60  		return nil
    61  	}
    62  
    63  	// Create a runner and the multiple search threads it directs
    64  	abort := make(chan struct{})
    65  
    66  	powScrypt.lock.Lock()
    67  	threads := powScrypt.threads
    68  	if powScrypt.rand == nil {
    69  		seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
    70  		if err != nil {
    71  			powScrypt.lock.Unlock()
    72  			return err
    73  		}
    74  		powScrypt.rand = rand.New(rand.NewSource(seed.Int64()))
    75  	}
    76  	powScrypt.lock.Unlock()
    77  	if threads == 0 {
    78  		threads = runtime.NumCPU()
    79  	}
    80  	if threads < 0 {
    81  		threads = 0 // Allows disabling local mining without extra logic around local/remote
    82  	}
    83  
    84  	// Push new work to remote sealer
    85  	if powScrypt.workCh != nil {
    86  		powScrypt.workCh <- &sealTask{block: block, results: results}
    87  	}
    88  	var (
    89  		pend   sync.WaitGroup
    90  		locals = make(chan *types.Block)
    91  	)
    92  	for i := 0; i < threads; i++ {
    93  		pend.Add(1)
    94  		go func(id int, nonce uint64) {
    95  			defer pend.Done()
    96  			powScrypt.mine(block, id, nonce, abort, locals)
    97  
    98  		}(i, uint64(powScrypt.rand.Int63()))
    99  	}
   100  	// Wait until sealing is terminated or a nonce is found
   101  	go func() {
   102  		var result *types.Block
   103  		select {
   104  		case <-stop:
   105  			// Outside abort, stop all miner threads
   106  			close(abort)
   107  		case result = <-locals:
   108  			// One of the threads found a block, abort all others
   109  			select {
   110  			case results <- result:
   111  			default:
   112  				log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", powScrypt.SealHash(block.Header()))
   113  			}
   114  			close(abort)
   115  		case <-powScrypt.update:
   116  			// Thread count was changed on user request, restart
   117  			close(abort)
   118  			if err := powScrypt.Seal(chain, block, results, stop); err != nil {
   119  				log.Error("Failed to restart sealing after update", "err", err)
   120  			}
   121  		}
   122  		// Wait for all miners to terminate and return the block
   123  		pend.Wait()
   124  	}()
   125  	return nil
   126  }
   127  
   128  // mine is the actual proof-of-work miner that searches for a nonce starting from
   129  // seed that results in correct final block difficulty.
   130  func (powScrypt *PowScrypt) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {
   131  	// Extract some data from the header
   132  	var (
   133  		header = block.Header()
   134  		hash   = powScrypt.SealHash(header).Bytes()
   135  		target = new(big.Int).Div(two256, header.Difficulty)
   136  	)
   137  	// Start generating random nonces until we abort or find a good one
   138  	var (
   139  		attempts = int64(0)
   140  		nonce    = seed
   141  	)
   142  	logger := log.New("miner", id)
   143  	logger.Trace("Started scrypt search for new nonces", "seed", seed)
   144  search:
   145  	for {
   146  		select {
   147  		case <-abort:
   148  			// Mining terminated, update stats and abort
   149  			logger.Trace("scrypt nonce search aborted", "attempts", nonce-seed)
   150  			powScrypt.hashrate.Mark(attempts)
   151  			break search
   152  
   153  		default:
   154  			// We don't have to update hash rate on every nonce, so update after after 2^X nonces
   155  			attempts++
   156  			if (attempts % (1 << 15)) == 0 {
   157  				powScrypt.hashrate.Mark(attempts)
   158  				attempts = 0
   159  			}
   160  			// Compute the PoW value of this nonce
   161  			digest, result := ScryptHash(hash, nonce)
   162  			if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
   163  				// Correct nonce found, create a new header with it
   164  				header = types.CopyHeader(header)
   165  				header.Nonce = types.EncodeNonce(nonce)
   166  				header.MixDigest = common.BytesToHash(digest)
   167  
   168  				// Seal and return a block (if still needed)
   169  				select {
   170  				case found <- block.WithSeal(header):
   171  					logger.Trace("scrypt nonce found and reported", "attempts", nonce-seed, "nonce", nonce)
   172  				case <-abort:
   173  					logger.Trace("scrypt nonce found but discarded", "attempts", nonce-seed, "nonce", nonce)
   174  				}
   175  				break search
   176  			}
   177  			nonce++
   178  		}
   179  	}
   180  }
   181  
   182  // remote is a standalone goroutine to handle remote mining related stuff.
   183  func (powScrypt *PowScrypt) remote(notify []string, noverify bool) {
   184  	var (
   185  		works = make(map[common.Hash]*types.Block)
   186  		rates = make(map[common.Hash]hashrate)
   187  
   188  		results      chan<- *types.Block
   189  		currentBlock *types.Block
   190  		currentWork  [3]string
   191  
   192  		notifyTransport = &http.Transport{}
   193  		notifyClient    = &http.Client{
   194  			Transport: notifyTransport,
   195  			Timeout:   time.Second,
   196  		}
   197  		notifyReqs = make([]*http.Request, len(notify))
   198  	)
   199  	// notifyWork notifies all the specified mining endpoints of the availability of
   200  	// new work to be processed.
   201  	notifyWork := func() {
   202  		work := currentWork
   203  		blob, _ := json.Marshal(work)
   204  
   205  		for i, url := range notify {
   206  			// Terminate any previously pending request and create the new work
   207  			if notifyReqs[i] != nil {
   208  				notifyTransport.CancelRequest(notifyReqs[i])
   209  			}
   210  			notifyReqs[i], _ = http.NewRequest("POST", url, bytes.NewReader(blob))
   211  			notifyReqs[i].Header.Set("Content-Type", "application/json")
   212  
   213  			// Push the new work concurrently to all the remote nodes
   214  			go func(req *http.Request, url string) {
   215  				res, err := notifyClient.Do(req)
   216  				if err != nil {
   217  					log.Warn("Failed to notify remote miner", "err", err)
   218  				} else {
   219  					log.Trace("Notified remote miner", "miner", url, "hash", log.Lazy{Fn: func() common.Hash { return common.HexToHash(work[0]) }}, "target", work[2])
   220  					res.Body.Close()
   221  				}
   222  			}(notifyReqs[i], url)
   223  		}
   224  	}
   225  	// makeWork creates a work package for external miner.
   226  	//
   227  	// The work package consists of 3 strings:
   228  	//   result[0], 32 bytes hex encoded current block header pow-hash
   229  	//   result[1], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
   230  	//   result[2], hex encoded block number
   231  	makeWork := func(block *types.Block) {
   232  		hash := powScrypt.SealHash(block.Header())
   233  
   234  		currentWork[0] = hash.Hex()
   235  		currentWork[1] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex()
   236  		currentWork[2] = hexutil.EncodeBig(block.Number())
   237  
   238  		// Trace the seal work fetched by remote sealer.
   239  		currentBlock = block
   240  		works[hash] = block
   241  	}
   242  	// submitWork verifies the submitted pow solution, returning
   243  	// whether the solution was accepted or not (not can be both a bad pow as well as
   244  	// any other error, like no pending work or stale mining result).
   245  	submitWork := func(nonce types.BlockNonce, mixDigest common.Hash, sealhash common.Hash) bool {
   246  		if currentBlock == nil {
   247  			log.Error("Pending work without block", "sealhash", sealhash)
   248  			return false
   249  		}
   250  		// Make sure the work submitted is present
   251  		block := works[sealhash]
   252  		if block == nil {
   253  			log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64())
   254  			return false
   255  		}
   256  		// Verify the correctness of submitted result.
   257  		header := block.Header()
   258  		header.Nonce = nonce
   259  		header.MixDigest = mixDigest
   260  
   261  		start := time.Now()
   262  		if !noverify {
   263  			if err := powScrypt.verifySeal(nil, header); err != nil {
   264  				log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", time.Since(start), "err", err)
   265  				return false
   266  			}
   267  		}
   268  		// Make sure the result channel is assigned.
   269  		if results == nil {
   270  			log.Warn("Scrypt result channel is empty, submitted mining result is rejected")
   271  			return false
   272  		}
   273  		log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", time.Since(start))
   274  
   275  		// Solutions seems to be valid, return to the miner and notify acceptance.
   276  		solution := block.WithSeal(header)
   277  
   278  		// The submitted solution is within the scope of acceptance.
   279  		if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() {
   280  			select {
   281  			case results <- solution:
   282  				log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
   283  				return true
   284  			default:
   285  				log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash)
   286  				return false
   287  			}
   288  		}
   289  		// The submitted block is too old to accept, drop it.
   290  		log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
   291  		return false
   292  	}
   293  
   294  	ticker := time.NewTicker(5 * time.Second)
   295  	defer ticker.Stop()
   296  
   297  	for {
   298  		select {
   299  		case work := <-powScrypt.workCh:
   300  			// Update current work with new received block.
   301  			// Note same work can be past twice, happens when changing CPU threads.
   302  			results = work.results
   303  
   304  			makeWork(work.block)
   305  
   306  			// Notify and requested URLs of the new work availability
   307  			notifyWork()
   308  
   309  		case work := <-powScrypt.fetchWorkCh:
   310  			// Return current mining work to remote miner.
   311  			if currentBlock == nil {
   312  				work.errc <- errNoMiningWork
   313  			} else {
   314  				work.res <- currentWork
   315  			}
   316  
   317  		case result := <-powScrypt.submitWorkCh:
   318  			// Verify submitted PoW solution based on maintained mining blocks.
   319  			if submitWork(result.nonce, result.mixDigest, result.hash) {
   320  				result.errc <- nil
   321  			} else {
   322  				result.errc <- errInvalidSealResult
   323  			}
   324  
   325  		case result := <-powScrypt.submitRateCh:
   326  			// Trace remote sealer's hash rate by submitted value.
   327  			rates[result.id] = hashrate{rate: result.rate, ping: time.Now()}
   328  			close(result.done)
   329  
   330  		case req := <-powScrypt.fetchRateCh:
   331  			// Gather all hash rate submitted by remote sealer.
   332  			var total uint64
   333  			for _, rate := range rates {
   334  				// this could overflow
   335  				total += rate.rate
   336  			}
   337  			req <- total
   338  
   339  		case <-ticker.C:
   340  			// Clear stale submitted hash rate.
   341  			for id, rate := range rates {
   342  				if time.Since(rate.ping) > 10*time.Second {
   343  					delete(rates, id)
   344  				}
   345  			}
   346  			// Clear stale pending blocks
   347  			if currentBlock != nil {
   348  				for hash, block := range works {
   349  					if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() {
   350  						delete(works, hash)
   351  					}
   352  				}
   353  			}
   354  
   355  		case errc := <-powScrypt.exitCh:
   356  			// Exit remote loop if scrypt is closed and return relevant error.
   357  			errc <- nil
   358  			log.Trace("Scrypt remote sealer is exiting")
   359  			return
   360  		}
   361  	}
   362  }