github.com/klaytn/klaytn@v1.12.1/work/remote_agent.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from miner/remote_agent.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package work
    22  
    23  import (
    24  	"errors"
    25  	"math/big"
    26  	"sync"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/consensus"
    32  	"github.com/klaytn/klaytn/consensus/gxhash"
    33  )
    34  
    35  type hashrate struct {
    36  	ping time.Time
    37  	rate uint64
    38  }
    39  
    40  type RemoteAgent struct {
    41  	mu sync.Mutex
    42  
    43  	quitCh   chan struct{}
    44  	workCh   chan *Task
    45  	returnCh chan<- *Result
    46  
    47  	chain       consensus.ChainReader
    48  	engine      consensus.Engine
    49  	currentWork *Task
    50  	work        map[common.Hash]*Task
    51  
    52  	hashrateMu sync.RWMutex
    53  	hashrate   map[common.Hash]hashrate
    54  
    55  	running int32 // running indicates whether the agent is active. Call atomically
    56  }
    57  
    58  func NewRemoteAgent(chain consensus.ChainReader, engine consensus.Engine) *RemoteAgent {
    59  	return &RemoteAgent{
    60  		chain:    chain,
    61  		engine:   engine,
    62  		work:     make(map[common.Hash]*Task),
    63  		hashrate: make(map[common.Hash]hashrate),
    64  	}
    65  }
    66  
    67  func (a *RemoteAgent) SubmitHashrate(id common.Hash, rate uint64) {
    68  	a.hashrateMu.Lock()
    69  	defer a.hashrateMu.Unlock()
    70  
    71  	a.hashrate[id] = hashrate{time.Now(), rate}
    72  }
    73  
    74  func (a *RemoteAgent) Work() chan<- *Task {
    75  	return a.workCh
    76  }
    77  
    78  func (a *RemoteAgent) SetReturnCh(returnCh chan<- *Result) {
    79  	a.returnCh = returnCh
    80  }
    81  
    82  func (a *RemoteAgent) Start() {
    83  	if !atomic.CompareAndSwapInt32(&a.running, 0, 1) {
    84  		return
    85  	}
    86  	a.quitCh = make(chan struct{})
    87  	a.workCh = make(chan *Task, 1)
    88  	go a.loop(a.workCh, a.quitCh)
    89  }
    90  
    91  func (a *RemoteAgent) Stop() {
    92  	if !atomic.CompareAndSwapInt32(&a.running, 1, 0) {
    93  		return
    94  	}
    95  	close(a.quitCh)
    96  	close(a.workCh)
    97  }
    98  
    99  // GetHashRate returns the accumulated hashrate of all identifier combined
   100  func (a *RemoteAgent) GetHashRate() (tot int64) {
   101  	a.hashrateMu.RLock()
   102  	defer a.hashrateMu.RUnlock()
   103  
   104  	// this could overflow
   105  	for _, hashrate := range a.hashrate {
   106  		tot += int64(hashrate.rate)
   107  	}
   108  	return
   109  }
   110  
   111  func (a *RemoteAgent) GetWork() ([3]string, error) {
   112  	a.mu.Lock()
   113  	defer a.mu.Unlock()
   114  
   115  	var res [3]string
   116  
   117  	if a.currentWork != nil {
   118  		block := a.currentWork.Block
   119  
   120  		res[0] = block.HashNoNonce().Hex()
   121  		seedHash := gxhash.SeedHash(block.NumberU64())
   122  		res[1] = common.BytesToHash(seedHash).Hex()
   123  		// Calculate the "target" to be returned to the external miner
   124  		n := big.NewInt(1)
   125  		n.Lsh(n, 255)
   126  		n.Div(n, block.BlockScore())
   127  		n.Lsh(n, 1)
   128  		res[2] = common.BytesToHash(n.Bytes()).Hex()
   129  
   130  		a.work[block.HashNoNonce()] = a.currentWork
   131  		return res, nil
   132  	}
   133  	return res, errors.New("No work available yet, don't panic.")
   134  }
   135  
   136  // SubmitWork tries to inject a pow solution into the remote agent, returning
   137  // whether the solution was accepted or not (not can be both a bad pow as well as
   138  // any other error, like no work pending).
   139  func (a *RemoteAgent) SubmitWork(hash common.Hash) bool {
   140  	a.mu.Lock()
   141  	defer a.mu.Unlock()
   142  
   143  	// Make sure the work submitted is present
   144  	work := a.work[hash]
   145  	if work == nil {
   146  		logger.Debug("Task submitted but none pending", "hash", hash)
   147  		return false
   148  	}
   149  	// Make sure the Engine solutions is indeed valid
   150  	result := work.Block.Header()
   151  
   152  	if err := a.engine.VerifySeal(a.chain, result); err != nil {
   153  		logger.Error("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 *Task, 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  }