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 }