github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/miner/remote_agent.go (about)

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