gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/opt/miner/remote_agent.go (about)

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