github.com/Evanesco-Labs/go-evanesco@v1.0.1/zkpminer/miner.go (about) 1 package zkpminer 2 3 import ( 4 "context" 5 "errors" 6 "github.com/Evanesco-Labs/go-evanesco/accounts/abi/bind" 7 "github.com/Evanesco-Labs/go-evanesco/common" 8 "github.com/Evanesco-Labs/go-evanesco/core" 9 "github.com/Evanesco-Labs/go-evanesco/core/types" 10 "github.com/Evanesco-Labs/go-evanesco/evaclient" 11 "github.com/Evanesco-Labs/go-evanesco/event" 12 "github.com/Evanesco-Labs/go-evanesco/log" 13 "github.com/Evanesco-Labs/go-evanesco/rpc" 14 "github.com/Evanesco-Labs/go-evanesco/zkpminer/keypair" 15 "github.com/Evanesco-Labs/go-evanesco/zkpminer/problem" 16 "math/rand" 17 "os" 18 "runtime" 19 "runtime/debug" 20 "sync" 21 "sync/atomic" 22 "time" 23 ) 24 25 var ( 26 ErrorMinerWorkerOutOfRange = errors.New("miner's workers reach MaxWorkerCnt, can not add more workers") 27 ErrorLocalMinerWithoutBackend = errors.New("new local miner with nil backend") 28 ErrorBlockHeaderSubscribe = errors.New("block header subscribe error") 29 ) 30 31 type TaskStep int 32 33 const ( 34 TASKSTART TaskStep = iota 35 TASKWAITCHALLENGEBLOCK 36 TASKGETCHALLENGEBLOCK 37 TASKPROBLEMSOLVED 38 TASKSUBMITTED 39 ) 40 41 const ( 42 COINBASEINTERVAL = types.CoinBaseInterval 43 SUBMITADVANCE = types.SubmitAdvance 44 RPCTIMEOUT = time.Minute 45 ) 46 47 var WSUrlTryRound = 5 48 var RetryWSRPCWaitDuration = time.Second * 5 49 var RetryJitterMaxDuration = 1000 //millisecond 50 51 type Backend interface { 52 BlockChain() *core.BlockChain 53 EventMux() *event.TypeMux 54 } 55 56 type Task struct { 57 CoinbaseAddr common.Address 58 minerAddr common.Address 59 Step TaskStep 60 lastCoinBaseHash [32]byte 61 challengeHeader types.HeaderShort 62 challengeIndex Height 63 lottery *types.Lottery 64 signature [65]byte 65 } 66 67 func (t *Task) SetHeader(h types.HeaderShort) { 68 t.challengeHeader = h 69 t.lottery.ChallengeHeaderHash = h.Hash() 70 t.Step = TASKGETCHALLENGEBLOCK 71 } 72 73 func (t *Task) SetCoinbaseAddr(coinbaseAddr common.Address) { 74 t.CoinbaseAddr = coinbaseAddr 75 } 76 77 //SetTaskMinerAddr only use in TASKSTART step 78 func SetTaskMinerAddr(template *Task, minerAddr common.Address) Task { 79 if template.Step != TASKSTART { 80 panic("only use it to update task in step TASKSTART") 81 } 82 //Deep Copy task 83 return Task{ 84 minerAddr: minerAddr, 85 CoinbaseAddr: template.CoinbaseAddr, 86 Step: TASKSTART, 87 lastCoinBaseHash: template.lastCoinBaseHash, 88 challengeIndex: Height(uint64(0)), 89 lottery: &types.Lottery{ 90 MinerAddr: minerAddr, 91 CoinbaseAddr: template.CoinbaseAddr, 92 }, 93 } 94 } 95 96 type Config struct { 97 MinerList []keypair.Key 98 MaxWorkerCnt int32 99 MaxTaskCnt int32 100 CoinbaseInterval uint64 101 SubmitAdvance uint64 102 CoinbaseAddr common.Address 103 WsUrl []string 104 RpcTimeout time.Duration 105 PkPath string 106 } 107 108 func DefaultConfig() Config { 109 return Config{ 110 MinerList: make([]keypair.Key, 0), 111 MaxWorkerCnt: 1, 112 MaxTaskCnt: 1, 113 CoinbaseInterval: COINBASEINTERVAL, 114 SubmitAdvance: SUBMITADVANCE, 115 CoinbaseAddr: common.Address{}, 116 WsUrl: []string{}, 117 RpcTimeout: RPCTIMEOUT, 118 PkPath: "./QmQL4k1hKYiW3SDtMREjnrah1PBsak1VE3VgEqTyoDckz9", 119 } 120 } 121 122 func (config *Config) Customize(minerList []keypair.Key, coinbase common.Address, url []string, pkPath string) { 123 config.MinerList = minerList 124 125 config.CoinbaseAddr = coinbase 126 127 if len(url) != 0 { 128 config.WsUrl = append(config.WsUrl, url...) 129 } 130 131 if pkPath != "" { 132 config.PkPath = pkPath 133 } 134 } 135 136 type Miner struct { 137 mu sync.RWMutex 138 isEffective sync.Once 139 config Config 140 zkpProver *problem.Prover 141 MaxWorkerCnt int32 142 MaxTaskCnt int32 143 CoinbaseAddr common.Address 144 Workers map[common.Address]*Worker 145 scanner *Scanner 146 coinbaseInterval Height 147 submitAdvance Height 148 urlList []string 149 exitCh chan struct{} 150 } 151 152 func NewLocalMiner(config Config, backend Backend) (*Miner, error) { 153 runtime.GOMAXPROCS(1) 154 if backend == nil { 155 return nil, ErrorLocalMinerWithoutBackend 156 } 157 zkpProver, err := problem.NewProblemProver(config.PkPath) 158 if err != nil { 159 log.Error(err.Error()) 160 return nil, err 161 } 162 log.Info("Init ZKP Problem worker success!") 163 164 miner := Miner{ 165 mu: sync.RWMutex{}, 166 config: config, 167 zkpProver: zkpProver, 168 MaxWorkerCnt: config.MaxWorkerCnt, 169 MaxTaskCnt: config.MaxTaskCnt, 170 CoinbaseAddr: config.CoinbaseAddr, 171 Workers: make(map[common.Address]*Worker), 172 coinbaseInterval: Height(config.CoinbaseInterval), 173 submitAdvance: Height(config.SubmitAdvance), 174 exitCh: make(chan struct{}), 175 urlList: config.WsUrl, 176 isEffective: sync.Once{}, 177 } 178 179 checkEffective := func() { 180 //check effective 181 minerAddress := config.MinerList[0].Address 182 ok, coinbasePledge := Iseffective(minerAddress, backend.BlockChain().InprocHandler) 183 if !ok { 184 log.Error("Miner address not staked", "address", minerAddress.String()) 185 } 186 emptyAddr := common.Address{} 187 //coinbase address is not set on pledge, use miner address by default 188 if coinbasePledge == emptyAddr { 189 if miner.CoinbaseAddr == emptyAddr { 190 miner.CoinbaseAddr = minerAddress 191 return 192 } 193 return 194 } 195 //coinbase address is set, use pledge coinbase address by default 196 if coinbasePledge != emptyAddr { 197 if miner.CoinbaseAddr == emptyAddr { 198 miner.CoinbaseAddr = coinbasePledge 199 return 200 } 201 if miner.CoinbaseAddr != coinbasePledge { 202 log.Error(NotPledgeCoinbaseError.Error()) 203 log.Info("miner coinbase address:" + miner.CoinbaseAddr.String() + ", fortress coinbase address:" + coinbasePledge.String()) 204 return 205 } 206 return 207 } 208 } 209 210 miner.isEffective.Do(checkEffective) 211 212 explorer := LocalExplorer{ 213 Backend: backend, 214 headerCh: make(chan types.HeaderShort), 215 } 216 blockEventCh := make(chan core.ChainHeadEvent) 217 sub := backend.BlockChain().SubscribeChainHeadEvent(blockEventCh) 218 go func() { 219 for { 220 select { 221 case blockEvent := <-blockEventCh: 222 short := blockEvent.Block.Header().Short() 223 explorer.headerCh <- short 224 case <-sub.Err(): 225 log.Error(ErrorBlockHeaderSubscribe.Error()) 226 miner.Close() 227 } 228 } 229 }() 230 miner.NewScanner(&explorer) 231 miner.StartScanner() 232 233 go miner.Loop() 234 //add new workers 235 for _, key := range config.MinerList { 236 miner.NewWorker(key) 237 } 238 log.Info("miner start") 239 log.Info("waiting for next mining epoch") 240 return &miner, nil 241 } 242 243 func NewMiner(config Config) (*Miner, error) { 244 runtime.GOMAXPROCS(1) 245 zkpProver, err := problem.NewProblemProver(config.PkPath) 246 debug.FreeOSMemory() 247 if err != nil { 248 log.Error(err.Error()) 249 return nil, err 250 } 251 log.Info("Init ZKP Problem worker success!") 252 if len(config.WsUrl) == 0 { 253 Fatalf("Evanesco websocket url unset") 254 } 255 miner := Miner{ 256 mu: sync.RWMutex{}, 257 config: config, 258 zkpProver: zkpProver, 259 MaxWorkerCnt: config.MaxWorkerCnt, 260 MaxTaskCnt: config.MaxTaskCnt, 261 CoinbaseAddr: config.CoinbaseAddr, 262 Workers: make(map[common.Address]*Worker), 263 coinbaseInterval: Height(config.CoinbaseInterval), 264 submitAdvance: Height(config.SubmitAdvance), 265 exitCh: make(chan struct{}), 266 urlList: config.WsUrl, 267 isEffective: sync.Once{}, 268 } 269 270 explorer := RpcExplorer{ 271 Client: new(rpc.Client), 272 Sub: new(rpc.ClientSubscription), 273 HeaderCh: make(chan types.HeaderShort), 274 rpcTimeOut: config.RpcTimeout, 275 WsUrl: "", 276 } 277 278 miner.NewScanner(&explorer) 279 miner.updateWS() 280 281 go func() { 282 for { 283 err := <-explorer.Sub.Err() 284 log.Warn(ErrorBlockHeaderSubscribe.Error(), "err", err) 285 log.Info("try to connect another node") 286 miner.updateWS() 287 } 288 }() 289 290 go miner.Loop() 291 //add new workers 292 for _, key := range config.MinerList { 293 miner.NewWorker(key) 294 } 295 log.Info("miner start") 296 log.Info("waiting for next mining epoch") 297 return &miner, nil 298 } 299 300 func (m *Miner) updateWS() { 301 if m.scanner.IsUpdating() { 302 return 303 } 304 m.scanner.close() 305 exp, ok := m.scanner.explorer.(*RpcExplorer) 306 if !ok { 307 Fatalf("Full node miner disconnected from Avis Network more than %v", NewHeaderTimeoutDuration.String()) 308 return 309 } 310 //clean old rpc explorer and new 311 oldURL := exp.WsUrl 312 go func() { 313 if exp.Sub == nil || exp.Client == nil { 314 return 315 } 316 exp.Sub.Unsubscribe() 317 exp.Client.Close() 318 }() 319 remoteExp := RpcExplorer{ 320 Client: new(rpc.Client), 321 Sub: new(rpc.ClientSubscription), 322 HeaderCh: make(chan types.HeaderShort), 323 rpcTimeOut: RPCTIMEOUT, 324 WsUrl: oldURL, 325 } 326 m.scanner.explorer = &remoteExp 327 328 //set scanner status updating 329 atomic.StoreInt32(&m.scanner.updating, int32(1)) 330 defer func() { 331 atomic.StoreInt32(&m.scanner.updating, int32(0)) 332 }() 333 334 res := false 335 var err error 336 for i := 0; i < WSUrlTryRound; i++ { 337 for _, url := range m.urlList { 338 jitter := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(RetryJitterMaxDuration) 339 time.Sleep(RetryWSRPCWaitDuration + time.Millisecond*time.Duration(jitter)) 340 if url == remoteExp.WsUrl { 341 continue 342 } 343 remoteExp.Client, err = rpc.Dial(url) 344 if err != nil { 345 log.Warn("Websocket dial Evanesco node err", "err", err) 346 continue 347 } 348 349 remoteExp.Sub, err = remoteExp.Client.EthSubscribe(context.Background(), remoteExp.HeaderCh, "newHeadShort") 350 if err != nil { 351 log.Warn("Subscribe block err", "err", err) 352 continue 353 } 354 355 res = true 356 remoteExp.WsUrl = url 357 log.Info("Connected node WebSocket URL", "url", url) 358 break 359 } 360 if res == true { 361 //check miner address effective 362 checkEffective := func() { 363 defer func() { 364 m.scanner.CoinbaseAddr = m.CoinbaseAddr 365 }() 366 evaClient := evaclient.NewClient(remoteExp.Client) 367 caller, err := NewPledgeCaller(PledgeContract, evaClient) 368 if err != nil { 369 Fatalf("New Pledge caller error %v", err) 370 } 371 minerKey := m.config.MinerList[0] 372 ok, coinbasePledge, err := caller.IseffectiveNew(&bind.CallOpts{Pending: false}, minerKey.Address) 373 if err != nil { 374 Fatalf("call pledge contract abi IseffectiveNew error %v", err) 375 } 376 if !ok { 377 log.Error(NotEffectiveAddrError.Error(), "address", minerKey.Address.String()) 378 } 379 emptyAddr := common.Address{} 380 //coinbase address is not set on pledge, use miner address by default 381 if coinbasePledge == emptyAddr { 382 if m.CoinbaseAddr == emptyAddr { 383 m.CoinbaseAddr = minerKey.Address 384 return 385 } 386 return 387 } 388 //coinbase address is set, use pledge coinbase address by default 389 if coinbasePledge != emptyAddr { 390 if m.CoinbaseAddr == emptyAddr { 391 m.CoinbaseAddr = coinbasePledge 392 return 393 } 394 if m.CoinbaseAddr != coinbasePledge { 395 log.Error(NotPledgeCoinbaseError.Error()) 396 log.Info("miner coinbase address:" + m.CoinbaseAddr.String() + ", fortress coinbase address:" + coinbasePledge.String()) 397 return 398 } 399 return 400 } 401 } 402 403 m.isEffective.Do(checkEffective) 404 break 405 } else { 406 //reset url to try this url again 407 remoteExp.WsUrl = "" 408 } 409 } 410 411 if res == false { 412 log.Error("Dial all websocket urls failed") 413 os.Exit(1) 414 } 415 416 m.StartScanner() 417 return 418 } 419 420 func (m *Miner) Close() { 421 defer func() { 422 if recover() != nil { 423 } 424 }() 425 //close workers 426 for _, worker := range m.Workers { 427 worker.close() 428 } 429 //close scanner 430 m.scanner.close() 431 close(m.exitCh) 432 os.Exit(1) 433 } 434 435 func (m *Miner) NewWorker(minerKey keypair.Key) { 436 m.mu.Lock() 437 defer m.mu.Unlock() 438 if len(m.Workers) == int(m.MaxWorkerCnt) { 439 log.Error(ErrorMinerWorkerOutOfRange.Error()) 440 return 441 } 442 worker := Worker{ 443 mu: sync.RWMutex{}, 444 running: 0, 445 MaxTaskCnt: m.MaxTaskCnt, 446 CoinbaseAddr: m.CoinbaseAddr, 447 minerAddr: minerKey.Address, 448 pk: minerKey.PrivateKey.Public(), 449 sk: &minerKey.PrivateKey, 450 workingTaskCnt: 0, 451 coinbaseInterval: m.coinbaseInterval, 452 inboundTaskCh: make(chan *Task), 453 submitAdvance: m.submitAdvance, 454 scanner: m.scanner, 455 zkpProver: m.zkpProver, 456 exitCh: make(chan struct{}), 457 } 458 459 m.Workers[minerKey.Address] = &worker 460 go worker.Loop() 461 worker.start() 462 log.Debug("worker start") 463 } 464 465 func (m *Miner) CloseWorker(addr common.Address) { 466 if worker, ok := m.Workers[addr]; ok { 467 worker.close() 468 delete(m.Workers, addr) 469 } 470 } 471 472 func (m *Miner) StopWorker(addr common.Address) { 473 if worker, ok := m.Workers[addr]; ok { 474 worker.stop() 475 } 476 } 477 478 func (m *Miner) StartWorker(addr common.Address) { 479 if worker, ok := m.Workers[addr]; ok { 480 worker.start() 481 } 482 } 483 484 func (m *Miner) Loop() { 485 for { 486 select { 487 case <-m.exitCh: 488 return 489 case taskTem := <-m.scanner.outboundTaskCh: 490 if taskTem.Step == TASKSTART { 491 for _, worker := range m.Workers { 492 task := SetTaskMinerAddr(taskTem, worker.minerAddr) 493 worker.inboundTaskCh <- &task 494 } 495 continue 496 } 497 if taskTem.Step == TASKGETCHALLENGEBLOCK { 498 if worker, ok := m.Workers[taskTem.minerAddr]; ok { 499 worker.inboundTaskCh <- taskTem 500 continue 501 } 502 log.Warn("worker for this task not exist") 503 } 504 if taskTem.Step == TASKSUBMITTED { 505 //todo: store submitted lotteries for later queries 506 } 507 } 508 } 509 } 510 511 func (m *Miner) NewScanner(explorer Explorer) { 512 m.scanner = &Scanner{ 513 miner: m, 514 mu: sync.RWMutex{}, 515 CoinbaseAddr: m.CoinbaseAddr, 516 BestScore: zero, 517 LastBlockHeight: 0, 518 CoinbaseInterval: m.coinbaseInterval, 519 LastCoinbaseHeight: 0, 520 taskWait: make(map[Height][]*Task), 521 inboundTaskCh: make(chan *Task), 522 outboundTaskCh: make(chan *Task), 523 explorer: explorer, 524 exitCh: make(chan struct{}), 525 running: int32(0), 526 updating: int32(0), 527 } 528 } 529 530 func (m *Miner) StartScanner() { 531 i := 0 532 for { 533 i++ 534 if m.scanner.IsClosed() { 535 break 536 } 537 time.Sleep(time.Millisecond * 100) 538 m.scanner.close() 539 if i == 10 { 540 Fatalf("start carrier scanner failed") 541 } 542 } 543 m.scanner.exitCh = make(chan struct{}) 544 go m.scanner.Loop() 545 }