github.com/bigzoro/my_simplechain@v0.0.0-20240315012955-8ad0a2a29bb9/consensus/scrypt/sealer.go (about) 1 // Copyright (c) 2019 Simplechain 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Lesser General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Lesser General Public License for more details. 12 // 13 // You should have received a copy of the GNU Lesser General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package scrypt 17 18 import ( 19 "bytes" 20 crand "crypto/rand" 21 "encoding/json" 22 "errors" 23 "math" 24 "math/big" 25 "math/rand" 26 "net/http" 27 "runtime" 28 "sync" 29 "time" 30 31 "github.com/bigzoro/my_simplechain/common" 32 "github.com/bigzoro/my_simplechain/common/hexutil" 33 "github.com/bigzoro/my_simplechain/consensus" 34 "github.com/bigzoro/my_simplechain/core/types" 35 "github.com/bigzoro/my_simplechain/log" 36 ) 37 38 const ( 39 // staleThreshold is the maximum depth of the acceptable stale but valid scrypt solution. 40 staleThreshold = 7 41 ) 42 43 var ( 44 errNoMiningWork = errors.New("no mining work available yet") 45 errInvalidSealResult = errors.New("invalid or stale proof-of-work solution") 46 ) 47 48 // Seal implements consensus.Engine, attempting to find a nonce that satisfies 49 // the block's difficulty requirements. 50 func (powScrypt *PowScrypt) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { 51 // If we're running a fake PoW, simply return a 0 nonce immediately 52 if powScrypt.config.PowMode == ModeFake || powScrypt.config.PowMode == ModeFullFake { 53 header := block.Header() 54 header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} 55 select { 56 case results <- block.WithSeal(header): 57 default: 58 log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", powScrypt.SealHash(block.Header())) 59 } 60 return nil 61 } 62 63 // Create a runner and the multiple search threads it directs 64 abort := make(chan struct{}) 65 66 powScrypt.lock.Lock() 67 threads := powScrypt.threads 68 if powScrypt.rand == nil { 69 seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) 70 if err != nil { 71 powScrypt.lock.Unlock() 72 return err 73 } 74 powScrypt.rand = rand.New(rand.NewSource(seed.Int64())) 75 } 76 powScrypt.lock.Unlock() 77 if threads == 0 { 78 threads = runtime.NumCPU() 79 } 80 if threads < 0 { 81 threads = 0 // Allows disabling local mining without extra logic around local/remote 82 } 83 84 // Push new work to remote sealer 85 if powScrypt.workCh != nil { 86 powScrypt.workCh <- &sealTask{block: block, results: results} 87 } 88 var ( 89 pend sync.WaitGroup 90 locals = make(chan *types.Block) 91 ) 92 for i := 0; i < threads; i++ { 93 pend.Add(1) 94 go func(id int, nonce uint64) { 95 defer pend.Done() 96 powScrypt.mine(block, id, nonce, abort, locals) 97 98 }(i, uint64(powScrypt.rand.Int63())) 99 } 100 // Wait until sealing is terminated or a nonce is found 101 go func() { 102 var result *types.Block 103 select { 104 case <-stop: 105 // Outside abort, stop all miner threads 106 close(abort) 107 case result = <-locals: 108 // One of the threads found a block, abort all others 109 select { 110 case results <- result: 111 default: 112 log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", powScrypt.SealHash(block.Header())) 113 } 114 close(abort) 115 case <-powScrypt.update: 116 // Thread count was changed on user request, restart 117 close(abort) 118 if err := powScrypt.Seal(chain, block, results, stop); err != nil { 119 log.Error("Failed to restart sealing after update", "err", err) 120 } 121 } 122 // Wait for all miners to terminate and return the block 123 pend.Wait() 124 }() 125 return nil 126 } 127 128 // mine is the actual proof-of-work miner that searches for a nonce starting from 129 // seed that results in correct final block difficulty. 130 func (powScrypt *PowScrypt) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { 131 // Extract some data from the header 132 var ( 133 header = block.Header() 134 hash = powScrypt.SealHash(header).Bytes() 135 target = new(big.Int).Div(two256, header.Difficulty) 136 ) 137 // Start generating random nonces until we abort or find a good one 138 var ( 139 attempts = int64(0) 140 nonce = seed 141 ) 142 logger := log.New("miner", id) 143 logger.Trace("Started scrypt search for new nonces", "seed", seed) 144 search: 145 for { 146 select { 147 case <-abort: 148 // Mining terminated, update stats and abort 149 logger.Trace("scrypt nonce search aborted", "attempts", nonce-seed) 150 powScrypt.hashrate.Mark(attempts) 151 break search 152 153 default: 154 // We don't have to update hash rate on every nonce, so update after after 2^X nonces 155 attempts++ 156 if (attempts % (1 << 15)) == 0 { 157 powScrypt.hashrate.Mark(attempts) 158 attempts = 0 159 } 160 // Compute the PoW value of this nonce 161 digest, result := ScryptHash(hash, nonce) 162 if new(big.Int).SetBytes(result).Cmp(target) <= 0 { 163 // Correct nonce found, create a new header with it 164 header = types.CopyHeader(header) 165 header.Nonce = types.EncodeNonce(nonce) 166 header.MixDigest = common.BytesToHash(digest) 167 168 // Seal and return a block (if still needed) 169 select { 170 case found <- block.WithSeal(header): 171 logger.Trace("scrypt nonce found and reported", "attempts", nonce-seed, "nonce", nonce) 172 case <-abort: 173 logger.Trace("scrypt nonce found but discarded", "attempts", nonce-seed, "nonce", nonce) 174 } 175 break search 176 } 177 nonce++ 178 } 179 } 180 } 181 182 // remote is a standalone goroutine to handle remote mining related stuff. 183 func (powScrypt *PowScrypt) remote(notify []string, noverify bool) { 184 var ( 185 works = make(map[common.Hash]*types.Block) 186 rates = make(map[common.Hash]hashrate) 187 188 results chan<- *types.Block 189 currentBlock *types.Block 190 currentWork [3]string 191 192 notifyTransport = &http.Transport{} 193 notifyClient = &http.Client{ 194 Transport: notifyTransport, 195 Timeout: time.Second, 196 } 197 notifyReqs = make([]*http.Request, len(notify)) 198 ) 199 // notifyWork notifies all the specified mining endpoints of the availability of 200 // new work to be processed. 201 notifyWork := func() { 202 work := currentWork 203 blob, _ := json.Marshal(work) 204 205 for i, url := range notify { 206 // Terminate any previously pending request and create the new work 207 if notifyReqs[i] != nil { 208 notifyTransport.CancelRequest(notifyReqs[i]) 209 } 210 notifyReqs[i], _ = http.NewRequest("POST", url, bytes.NewReader(blob)) 211 notifyReqs[i].Header.Set("Content-Type", "application/json") 212 213 // Push the new work concurrently to all the remote nodes 214 go func(req *http.Request, url string) { 215 res, err := notifyClient.Do(req) 216 if err != nil { 217 log.Warn("Failed to notify remote miner", "err", err) 218 } else { 219 log.Trace("Notified remote miner", "miner", url, "hash", log.Lazy{Fn: func() common.Hash { return common.HexToHash(work[0]) }}, "target", work[2]) 220 res.Body.Close() 221 } 222 }(notifyReqs[i], url) 223 } 224 } 225 // makeWork creates a work package for external miner. 226 // 227 // The work package consists of 3 strings: 228 // result[0], 32 bytes hex encoded current block header pow-hash 229 // result[1], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty 230 // result[2], hex encoded block number 231 makeWork := func(block *types.Block) { 232 hash := powScrypt.SealHash(block.Header()) 233 234 currentWork[0] = hash.Hex() 235 currentWork[1] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex() 236 currentWork[2] = hexutil.EncodeBig(block.Number()) 237 238 // Trace the seal work fetched by remote sealer. 239 currentBlock = block 240 works[hash] = block 241 } 242 // submitWork verifies the submitted pow solution, returning 243 // whether the solution was accepted or not (not can be both a bad pow as well as 244 // any other error, like no pending work or stale mining result). 245 submitWork := func(nonce types.BlockNonce, mixDigest common.Hash, sealhash common.Hash) bool { 246 if currentBlock == nil { 247 log.Error("Pending work without block", "sealhash", sealhash) 248 return false 249 } 250 // Make sure the work submitted is present 251 block := works[sealhash] 252 if block == nil { 253 log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64()) 254 return false 255 } 256 // Verify the correctness of submitted result. 257 header := block.Header() 258 header.Nonce = nonce 259 header.MixDigest = mixDigest 260 261 start := time.Now() 262 if !noverify { 263 if err := powScrypt.verifySeal(nil, header); err != nil { 264 log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", time.Since(start), "err", err) 265 return false 266 } 267 } 268 // Make sure the result channel is assigned. 269 if results == nil { 270 log.Warn("Scrypt result channel is empty, submitted mining result is rejected") 271 return false 272 } 273 log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", time.Since(start)) 274 275 // Solutions seems to be valid, return to the miner and notify acceptance. 276 solution := block.WithSeal(header) 277 278 // The submitted solution is within the scope of acceptance. 279 if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() { 280 select { 281 case results <- solution: 282 log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) 283 return true 284 default: 285 log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash) 286 return false 287 } 288 } 289 // The submitted block is too old to accept, drop it. 290 log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) 291 return false 292 } 293 294 ticker := time.NewTicker(5 * time.Second) 295 defer ticker.Stop() 296 297 for { 298 select { 299 case work := <-powScrypt.workCh: 300 // Update current work with new received block. 301 // Note same work can be past twice, happens when changing CPU threads. 302 results = work.results 303 304 makeWork(work.block) 305 306 // Notify and requested URLs of the new work availability 307 notifyWork() 308 309 case work := <-powScrypt.fetchWorkCh: 310 // Return current mining work to remote miner. 311 if currentBlock == nil { 312 work.errc <- errNoMiningWork 313 } else { 314 work.res <- currentWork 315 } 316 317 case result := <-powScrypt.submitWorkCh: 318 // Verify submitted PoW solution based on maintained mining blocks. 319 if submitWork(result.nonce, result.mixDigest, result.hash) { 320 result.errc <- nil 321 } else { 322 result.errc <- errInvalidSealResult 323 } 324 325 case result := <-powScrypt.submitRateCh: 326 // Trace remote sealer's hash rate by submitted value. 327 rates[result.id] = hashrate{rate: result.rate, ping: time.Now()} 328 close(result.done) 329 330 case req := <-powScrypt.fetchRateCh: 331 // Gather all hash rate submitted by remote sealer. 332 var total uint64 333 for _, rate := range rates { 334 // this could overflow 335 total += rate.rate 336 } 337 req <- total 338 339 case <-ticker.C: 340 // Clear stale submitted hash rate. 341 for id, rate := range rates { 342 if time.Since(rate.ping) > 10*time.Second { 343 delete(rates, id) 344 } 345 } 346 // Clear stale pending blocks 347 if currentBlock != nil { 348 for hash, block := range works { 349 if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() { 350 delete(works, hash) 351 } 352 } 353 } 354 355 case errc := <-powScrypt.exitCh: 356 // Exit remote loop if scrypt is closed and return relevant error. 357 errc <- nil 358 log.Trace("Scrypt remote sealer is exiting") 359 return 360 } 361 } 362 }