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 }