github.com/hhwill/poc-eth@v0.0.0-20240218063348-3bb107c90dbf/consensus/ethpoc/sealer.go (about) 1 package ethpoc 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "net/http" 8 "time" 9 10 "math/big" 11 "strconv" 12 13 "encoding/binary" 14 "fmt" 15 16 "github.com/ethereum/go-ethereum/accounts" 17 "github.com/ethereum/go-ethereum/common" 18 "github.com/ethereum/go-ethereum/common/math" 19 "github.com/ethereum/go-ethereum/consensus" 20 "github.com/ethereum/go-ethereum/core" 21 "github.com/ethereum/go-ethereum/core/types" 22 "github.com/ethereum/go-ethereum/core/vm" 23 "github.com/ethereum/go-ethereum/log" 24 "github.com/ethereum/go-ethereum/params" 25 "golang.org/x/net/context" 26 ) 27 28 const ( 29 // staleThreshold is the maximum depth of the acceptable stale but valid ethPoc solution. 30 staleThreshold = 7 31 ) 32 33 var ( 34 errNoMiningWork = errors.New("no mining work available yet") 35 errInvalidSealResult = errors.New("invalid or stale proof-of-work solution") 36 ) 37 38 // Seal implements consensus.Engine, attempting to find a nonce that satisfies 39 // the block's difficulty requirements. 40 func (ethPoc *EthPoc) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { 41 // If we're running a fake PoW, simply return a 0 nonce immediately 42 if ethPoc.config.PocMode == ModeFake || ethPoc.config.PocMode == ModeFullFake { 43 header := block.Header() 44 header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} 45 select { 46 case results <- block.WithSeal(header): 47 default: 48 log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethPoc.SealHash(block.Header())) 49 } 50 return nil 51 } 52 53 // Push new work to remote sealer 54 if ethPoc.workCh != nil { 55 ethPoc.workCh <- &sealTask{block: block, results: results} 56 } 57 58 ethPoc.cancelFunc() 59 ethPoc.ctx, ethPoc.cancelFunc = context.WithCancel(context.Background()) 60 log.Warn("Start cancel uncomplete work", "number", block.NumberU64()-1) 61 return nil 62 63 } 64 65 // remote is a standalone goroutine to handle remote mining related stuff. 66 67 // remote is a standalone goroutine to handle remote mining related stuff. 68 func (ethPoc *EthPoc) remote(notify []string, noverify bool) { 69 var ( 70 works = make(map[common.Hash]*types.Block) 71 rates = make(map[common.Hash]hashrate) 72 73 results chan<- *types.Block 74 currentBlock *types.Block 75 currentWork [5]string 76 77 notifyTransport = &http.Transport{} 78 notifyClient = &http.Client{ 79 Transport: notifyTransport, 80 Timeout: time.Second, 81 } 82 notifyReqs = make([]*http.Request, len(notify)) 83 ) 84 // notifyWork notifies all the specified mining endpoints of the availability of 85 // new work to be processed. 86 notifyWork := func() { 87 work := currentWork 88 blob, _ := json.Marshal(work) 89 90 for i, url := range notify { 91 // Terminate any previously pending request and create the new work 92 if notifyReqs[i] != nil { 93 notifyTransport.CancelRequest(notifyReqs[i]) 94 } 95 notifyReqs[i], _ = http.NewRequest("POST", url, bytes.NewReader(blob)) 96 notifyReqs[i].Header.Set("Content-Type", "application/json") 97 98 // Push the new work concurrently to all the remote nodes 99 go func(req *http.Request, url string) { 100 res, err := notifyClient.Do(req) 101 if err != nil { 102 log.Warn("Failed to notify remote miner", "err", err) 103 } else { 104 log.Trace("Notified remote miner", "miner", url, "hash", log.Lazy{Fn: func() common.Hash { return common.HexToHash(work[0]) }}, "target", work[2]) 105 res.Body.Close() 106 } 107 }(notifyReqs[i], url) 108 } 109 } 110 // makeWork creates a work package for external miner. 111 // 112 // The work package consists of 3 strings: 113 // result[0], 32 bytes hex encoded current block header pow-hash 114 // result[1], 32 bytes hex encoded seed hash used for DAG 115 // result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty 116 // result[3], hex encoded block number 117 makeWork := func(block *types.Block) { 118 hash := ethPoc.SealHash(block.Header()) 119 header := block.Header() 120 121 currentWork[0] = hash.Hex() 122 currentWork[1] = strconv.FormatUint(header.Number.Uint64(), 10) 123 currentWork[2] = header.MixDigest.Hex()[2:] 124 currentWork[3] = strconv.FormatUint(header.Difficulty.Uint64(), 10) 125 currentWork[4] = "" 126 127 // Trace the seal work fetched by remote sealer. 128 currentBlock = block 129 works[hash] = block 130 } 131 // submitWork verifies the submitted pow solution, returning 132 // whether the solution was accepted or not (not can be both a bad pow as well as 133 // any other error, like no pending work or stale mining result). 134 submitWork := func(block *types.Block, sealhash common.Hash, nonce, plotter, number uint64, deadLine *big.Int) bool { 135 // Verify the correctness of submitted result. 136 header := block.Header() 137 header.Nonce = types.EncodeNonce(nonce) 138 header.PlotterID = plotter 139 pTime := header.Time 140 141 nowTime := uint64(time.Now().Unix()) 142 deadLineU64 := deadLine.Uint64() 143 144 if pTime+deadLineU64 > nowTime { 145 interval := pTime + deadLineU64 - nowTime + 1 146 t := time.NewTimer(15 * time.Second) 147 log.Info("Start sleep", "ptime", pTime, "deadline", deadLineU64, "nowTime", nowTime, "time", interval) 148 select { 149 case <-t.C: 150 break 151 case <-ethPoc.ctx.Done(): 152 log.Info("Calcel sleep", "deadline", deadLineU64, "number", header.Number.Uint64(), "hash", sealhash) 153 return false 154 } 155 } 156 157 start := time.Now() 158 header.Time = uint64(start.Unix()) 159 160 if !noverify { 161 if err := ethPoc.verifySeal(nil, pTime, deadLine, header, true); err != nil { 162 log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", time.Since(start), "err", err) 163 return false 164 } 165 } 166 167 account := accounts.Account{Address: header.Coinbase} 168 wallet, err := ethPoc.am.Find(account) 169 if err != nil { 170 log.Warn("Can not find wallet for coin base address, submitted mining result is rejected", "error", err) 171 return false 172 } 173 sigHash, err := wallet.SignText(account, header.Hash().Bytes()) 174 if err != nil { 175 log.Warn("Sign Block hash error, submitted mining result is rejected", "error", err) 176 return false 177 } 178 header.BlockSign = sigHash 179 180 // Make sure the result channel is assigned. 181 if results == nil { 182 log.Warn("Ethash result channel is empty, submitted mining result is rejected") 183 return false 184 } 185 log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", time.Since(start)) 186 187 // Solutions seems to be valid, return to the miner and notify acceptance. 188 solution := block.WithSeal(header) 189 190 // The submitted solution is within the scope of acceptance. 191 if solution.NumberU64()+staleThreshold > currentBlock.NumberU64() { 192 select { 193 case results <- solution: 194 log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) 195 return true 196 default: 197 log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash) 198 return false 199 } 200 } 201 // The submitted block is too old to accept, drop it. 202 log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) 203 return false 204 } 205 206 verifyWork := func(sealhash common.Hash, nonce, plotter, number uint64) (*big.Int, bool) { 207 if currentBlock == nil { 208 log.Error("Pending work without block", "sealhash", sealhash) 209 return nil, false 210 } 211 // Make sure the work submitted is present 212 block := works[sealhash] 213 if block == nil { 214 log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", currentBlock.NumberU64()) 215 return nil, false 216 } 217 // Verify the correctness of submitted result. 218 header := block.Header() 219 header.Nonce = types.EncodeNonce(nonce) 220 header.PlotterID = plotter 221 222 genSign := header.MixDigest.Bytes() 223 scoop := int(calculateScoop(genSign, number)) 224 deadLine := calculateDeadline(header.PlotterID, header.Nonce, scoop, genSign, header.Difficulty) 225 log.Info("Compute deadline", "deadLine", deadLine.Uint64()) 226 227 benefiter, _, err := callContract(ethPoc.chainConfig, ethPoc.ctx, ethPoc.blockChain, header, plotter) 228 if err != nil { 229 log.Info("Get benefiter error", "error", err) 230 return nil, false 231 } 232 if common.BytesToAddress(benefiter) != header.Coinbase { 233 log.Warn(fmt.Sprintf("Invalid coinbase,benefiter [%x],coin base [%x]", benefiter, header.Coinbase)) 234 return nil, false 235 } 236 237 //fmt.Printf("benefiter %x coinbase %x err %v\n", benefiter, header.Coinbase, err) 238 go submitWork(block, sealhash, nonce, plotter, number, deadLine) 239 return deadLine, true 240 } 241 242 ticker := time.NewTicker(5 * time.Second) 243 defer ticker.Stop() 244 245 for { 246 select { 247 case work := <-ethPoc.workCh: 248 // Update current work with new received block. 249 // Note same work can be past twice, happens when changing CPU threads. 250 results = work.results 251 252 makeWork(work.block) 253 254 // Notify and requested URLs of the new work availability 255 notifyWork() 256 257 case work := <-ethPoc.fetchWorkCh: 258 // Return current mining work to remote miner. 259 if currentBlock == nil { 260 work.errc <- errNoMiningWork 261 } else { 262 work.res <- currentWork 263 } 264 265 case result := <-ethPoc.submitWorkCh: 266 // Verify submitted PoW solution based on maintained mining blocks. 267 268 var res [2]string 269 deadLine, rev := verifyWork(result.hash, result.nonce, result.plotter, result.number) 270 if deadLine != nil { 271 res[0] = strconv.FormatUint(deadLine.Uint64(), 10) 272 } 273 274 res[1] = strconv.FormatBool(rev) 275 276 result.errc <- res 277 278 //case result := <-ethPoc.submitRateCh: 279 // // Trace remote sealer's hash rate by submitted value. 280 // rates[result.id] = hashrate{rate: result.rate, ping: time.Now()} 281 // close(result.done) 282 283 case req := <-ethPoc.fetchRateCh: 284 // Gather all hash rate submitted by remote sealer. 285 var total uint64 286 for _, rate := range rates { 287 // this could overflow 288 total += rate.rate 289 } 290 req <- total 291 292 case <-ticker.C: 293 // Clear stale submitted hash rate. 294 for id, rate := range rates { 295 if time.Since(rate.ping) > 10*time.Second { 296 delete(rates, id) 297 } 298 } 299 // Clear stale pending blocks 300 if currentBlock != nil { 301 for hash, block := range works { 302 if block.NumberU64()+staleThreshold <= currentBlock.NumberU64() { 303 delete(works, hash) 304 } 305 } 306 } 307 308 case errc := <-ethPoc.exitCh: 309 // Exit remote loop if ethPoc is closed and return relevant error. 310 errc <- nil 311 log.Trace("Ethash remote sealer is exiting") 312 return 313 } 314 } 315 } 316 317 func callContract(chainConfig *params.ChainConfig, ctx context.Context, b *core.BlockChain, header *types.Header, plotter uint64) ([]byte, bool, error) { 318 319 pHeader := b.GetHeader(header.ParentHash, header.Number.Uint64()-1) 320 stateDb, err := b.StateAt(pHeader.Root) 321 if stateDb == nil || err != nil { 322 fmt.Printf("get state error %v\n", err) 323 return nil, false, err 324 } 325 // Set sender address or use a default if none specified 326 addr := common.BytesToAddress([]byte{8}) 327 328 data := make([]byte, 36) 329 copy(data, common.Hex2Bytes("57edda67")) 330 binary.BigEndian.PutUint64(data[28:36], plotter) 331 332 // Create new call message 333 msg := types.NewMessage(header.Coinbase, &addr, 0, big.NewInt(0), math.MaxUint64/2, big.NewInt(0), data, false) 334 335 // Get a new instance of the EVM. 336 evmContext := core.NewEVMContext(msg, pHeader, b, nil) 337 // Create a new environment which holds all relevant information 338 // about the transaction and calling mechanisms. 339 vmenv := vm.NewEVM(evmContext, stateDb, chainConfig, *b.GetVMConfig()) 340 341 // Wait for the context to be done and cancel the evm. Even if the 342 // EVM has finished, cancelling may be done (repeatedly) 343 go func() { 344 <-ctx.Done() 345 vmenv.Cancel() 346 }() 347 348 // Setup the gas pool (also for unmetered requests) 349 // and apply the message. 350 gp := new(core.GasPool).AddGas(math.MaxUint64) 351 res, _, failed, err := core.ApplyMessage(vmenv, msg, gp) 352 if err != nil { 353 fmt.Printf("ApplyMessage error %v\n", err) 354 return nil, false, err 355 } 356 357 fmt.Printf("contract address %x res %v err %v\n", res, failed, err) 358 359 return res, failed, err 360 }