github.com/isti4github/eth-ecc@v0.0.0-20201227085832-c337f2d99319/consensus/eccpow/sealer.go (about)

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