github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/miner/remote_agent.go (about)

     1  // Copyright 2015 The Spectrum Authors
     2  // This file is part of the Spectrum library.
     3  //
     4  // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package miner
    18  
    19  import (
    20  	"errors"
    21  	"math/big"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/SmartMeshFoundation/Spectrum/common"
    27  	"github.com/SmartMeshFoundation/Spectrum/consensus"
    28  	"github.com/SmartMeshFoundation/Spectrum/consensus/ethash"
    29  	"github.com/SmartMeshFoundation/Spectrum/core/types"
    30  	"github.com/SmartMeshFoundation/Spectrum/log"
    31  )
    32  
    33  type hashrate struct {
    34  	ping time.Time
    35  	rate uint64
    36  }
    37  
    38  type RemoteAgent struct {
    39  	mu sync.Mutex
    40  
    41  	quitCh   chan struct{}
    42  	workCh   chan *Work
    43  	returnCh chan<- *Result
    44  
    45  	chain       consensus.ChainReader
    46  	engine      consensus.Engine
    47  	currentWork *Work
    48  	work        map[common.Hash]*Work
    49  
    50  	hashrateMu sync.RWMutex
    51  	hashrate   map[common.Hash]hashrate
    52  
    53  	running int32 // running indicates whether the agent is active. Call atomically
    54  }
    55  
    56  func NewRemoteAgent(chain consensus.ChainReader, engine consensus.Engine) *RemoteAgent {
    57  	return &RemoteAgent{
    58  		chain:    chain,
    59  		engine:   engine,
    60  		work:     make(map[common.Hash]*Work),
    61  		hashrate: make(map[common.Hash]hashrate),
    62  	}
    63  }
    64  
    65  func (a *RemoteAgent) SubmitHashrate(id common.Hash, rate uint64) {
    66  	a.hashrateMu.Lock()
    67  	defer a.hashrateMu.Unlock()
    68  
    69  	a.hashrate[id] = hashrate{time.Now(), rate}
    70  }
    71  
    72  func (a *RemoteAgent) Work() chan<- *Work {
    73  	return a.workCh
    74  }
    75  
    76  func (a *RemoteAgent) SetReturnCh(returnCh chan<- *Result) {
    77  	a.returnCh = returnCh
    78  }
    79  
    80  func (a *RemoteAgent) Start() {
    81  	if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
    82  		return
    83  	}
    84  	a.quitCh = make(chan struct{})
    85  	a.workCh = make(chan *Work, 1)
    86  	go a.loop(a.workCh, a.quitCh)
    87  }
    88  
    89  func (a *RemoteAgent) Stop() {
    90  	if !atomic.CompareAndSwapInt32(&a.running, 1, 0) {
    91  		return
    92  	}
    93  	close(a.quitCh)
    94  	close(a.workCh)
    95  }
    96  
    97  // GetHashRate returns the accumulated hashrate of all identifier combined
    98  func (a *RemoteAgent) GetHashRate() (tot int64) {
    99  	a.hashrateMu.RLock()
   100  	defer a.hashrateMu.RUnlock()
   101  
   102  	// this could overflow
   103  	for _, hashrate := range a.hashrate {
   104  		tot += int64(hashrate.rate)
   105  	}
   106  	return
   107  }
   108  
   109  func (a *RemoteAgent) GetWork() ([3]string, error) {
   110  	a.mu.Lock()
   111  	defer a.mu.Unlock()
   112  
   113  	var res [3]string
   114  
   115  	if a.currentWork != nil {
   116  		block := a.currentWork.Block
   117  
   118  		res[0] = block.HashNoNonce().Hex()
   119  		seedHash := ethash.SeedHash(block.NumberU64())
   120  		res[1] = common.BytesToHash(seedHash).Hex()
   121  		// Calculate the "target" to be returned to the external miner
   122  		n := big.NewInt(1)
   123  		n.Lsh(n, 255)
   124  		n.Div(n, block.Difficulty())
   125  		n.Lsh(n, 1)
   126  		res[2] = common.BytesToHash(n.Bytes()).Hex()
   127  
   128  		a.work[block.HashNoNonce()] = a.currentWork
   129  		return res, nil
   130  	}
   131  	return res, errors.New("No work available yet, don't panic.")
   132  }
   133  
   134  // SubmitWork tries to inject a pow solution into the remote agent, returning
   135  // whether the solution was accepted or not (not can be both a bad pow as well as
   136  // any other error, like no work pending).
   137  func (a *RemoteAgent) SubmitWork(nonce types.BlockNonce, mixDigest, hash common.Hash) bool {
   138  	a.mu.Lock()
   139  	defer a.mu.Unlock()
   140  
   141  	// Make sure the work submitted is present
   142  	work := a.work[hash]
   143  	if work == nil {
   144  		log.Info("Work submitted but none pending", "hash", hash)
   145  		return false
   146  	}
   147  	// Make sure the Engine solutions is indeed valid
   148  	result := work.Block.Header()
   149  	result.Nonce = nonce
   150  	result.MixDigest = mixDigest
   151  
   152  	if err := a.engine.VerifySeal(a.chain, result); err != nil {
   153  		log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
   154  		return false
   155  	}
   156  	block := work.Block.WithSeal(result)
   157  
   158  	// Solutions seems to be valid, return to the miner and notify acceptance
   159  	a.returnCh <- &Result{work, block}
   160  	delete(a.work, hash)
   161  
   162  	return true
   163  }
   164  
   165  // loop monitors mining events on the work and quit channels, updating the internal
   166  // state of the remote miner until a termination is requested.
   167  //
   168  // Note, the reason the work and quit channels are passed as parameters is because
   169  // RemoteAgent.Start() constantly recreates these channels, so the loop code cannot
   170  // assume data stability in these member fields.
   171  func (a *RemoteAgent) loop(workCh chan *Work, quitCh chan struct{}) {
   172  	ticker := time.NewTicker(5 * time.Second)
   173  	defer ticker.Stop()
   174  
   175  	for {
   176  		select {
   177  		case <-quitCh:
   178  			return
   179  		case work := <-workCh:
   180  			a.mu.Lock()
   181  			a.currentWork = work
   182  			a.mu.Unlock()
   183  		case <-ticker.C:
   184  			// cleanup
   185  			a.mu.Lock()
   186  			for hash, work := range a.work {
   187  				if time.Since(work.createdAt) > 7*(12*time.Second) {
   188  					delete(a.work, hash)
   189  				}
   190  			}
   191  			a.mu.Unlock()
   192  
   193  			a.hashrateMu.Lock()
   194  			for id, hashrate := range a.hashrate {
   195  				if time.Since(hashrate.ping) > 10*time.Second {
   196  					delete(a.hashrate, id)
   197  				}
   198  			}
   199  			a.hashrateMu.Unlock()
   200  		}
   201  	}
   202  }