github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/producer/worker.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package producer 18 19 import ( 20 "encoding/json" 21 "math/big" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/vntchain/go-vnt/common" 27 "github.com/vntchain/go-vnt/consensus" 28 "github.com/vntchain/go-vnt/consensus/dpos" 29 "github.com/vntchain/go-vnt/core" 30 "github.com/vntchain/go-vnt/core/state" 31 "github.com/vntchain/go-vnt/core/types" 32 "github.com/vntchain/go-vnt/core/vm" 33 "github.com/vntchain/go-vnt/event" 34 "github.com/vntchain/go-vnt/log" 35 "github.com/vntchain/go-vnt/params" 36 "github.com/vntchain/go-vnt/vntdb" 37 ) 38 39 const ( 40 resultQueueSize = 10 41 producingLogAtDepth = 5 42 43 // txChanSize is the size of channel listening to NewTxsEvent. 44 // The number is referenced from the size of tx pool. 45 txChanSize = 4096 46 // chainHeadChanSize is the size of channel listening to ChainHeadEvent. 47 chainHeadChanSize = 10 48 // chainSideChanSize is the size of channel listening to ChainSideEvent. 49 chainSideChanSize = 10 50 ) 51 52 // Work is the workers current environment and holds 53 // all of the current state information 54 type Work struct { 55 config *params.ChainConfig 56 signer types.Signer 57 58 state *state.StateDB // apply state changes here 59 tcount int // tx count in cycle 60 gasPool *core.GasPool // available gas used to pack transactions 61 62 Block *types.Block // the new block 63 64 header *types.Header 65 txs []*types.Transaction 66 receipts []*types.Receipt 67 68 createdAt time.Time 69 } 70 71 type Result struct { 72 Work *Work 73 Block *types.Block 74 } 75 76 // worker is the main object which takes care of applying messages to the new state 77 type worker struct { 78 config *params.ChainConfig 79 engine consensus.Engine 80 81 mu sync.Mutex 82 83 // update loop 84 mux *event.TypeMux 85 txsCh chan core.NewTxsEvent 86 txsSub event.Subscription 87 chainHeadCh chan core.ChainHeadEvent 88 chainHeadSub event.Subscription 89 chainSideCh chan core.ChainSideEvent 90 chainSideSub event.Subscription 91 recBftMsgSub *event.TypeMuxSubscription 92 wg sync.WaitGroup 93 94 vnt Backend 95 chain *core.BlockChain 96 proc core.Validator 97 chainDb vntdb.Database 98 99 coinbase common.Address 100 extra []byte 101 102 currentMu sync.Mutex 103 current *Work 104 105 snapshotMu sync.RWMutex 106 snapshotBlock *types.Block 107 snapshotState *state.StateDB 108 109 unconfirmed *unconfirmedBlocks // set of locally produced blocks pending canonicalness confirmations 110 111 // atomic status counters 112 producing int32 113 atWork int32 114 115 roundTimer *time.Timer // Timer to trigger each round of producing block 116 resetTimerEvent chan *big.Int 117 producerStop chan struct{} 118 } 119 120 func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, vnt Backend, mux *event.TypeMux) *worker { 121 worker := &worker{ 122 config: config, 123 engine: engine, 124 vnt: vnt, 125 mux: mux, 126 txsCh: make(chan core.NewTxsEvent, txChanSize), 127 chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), 128 chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), 129 chainDb: vnt.ChainDb(), 130 chain: vnt.BlockChain(), 131 proc: vnt.BlockChain().Validator(), 132 coinbase: coinbase, 133 unconfirmed: newUnconfirmedBlocks(vnt.BlockChain(), producingLogAtDepth), 134 roundTimer: time.NewTimer(time.Second), 135 resetTimerEvent: make(chan *big.Int, 1), 136 producerStop: make(chan struct{}, 1), 137 } 138 worker.stopRoundTimer() 139 140 // Subscribe NewTxsEvent for tx pool 141 worker.txsSub = vnt.TxPool().SubscribeNewTxsEvent(worker.txsCh) 142 // Subscribe events for blockchain 143 worker.chainHeadSub = vnt.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) 144 worker.chainSideSub = vnt.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) 145 worker.recBftMsgSub = worker.mux.Subscribe(core.RecBftMsgEvent{}) 146 147 go worker.recBftMsg() 148 go worker.update() 149 150 worker.commitNewWork() 151 152 return worker 153 } 154 155 func (self *worker) setCoinbase(addr common.Address) { 156 self.mu.Lock() 157 defer self.mu.Unlock() 158 self.coinbase = addr 159 } 160 161 func (self *worker) setExtra(extra []byte) { 162 self.mu.Lock() 163 defer self.mu.Unlock() 164 self.extra = extra 165 } 166 167 func (self *worker) pending() (*types.Block, *state.StateDB) { 168 if atomic.LoadInt32(&self.producing) == 0 { 169 // return a snapshot to avoid contention on currentMu mutex 170 self.snapshotMu.RLock() 171 defer self.snapshotMu.RUnlock() 172 return self.snapshotBlock, self.snapshotState.Copy() 173 } 174 175 self.currentMu.Lock() 176 defer self.currentMu.Unlock() 177 return self.current.Block, self.current.state.Copy() 178 } 179 180 func (self *worker) pendingBlock() *types.Block { 181 if atomic.LoadInt32(&self.producing) == 0 { 182 // return a snapshot to avoid contention on currentMu mutex 183 self.snapshotMu.RLock() 184 defer self.snapshotMu.RUnlock() 185 return self.snapshotBlock 186 } 187 188 self.currentMu.Lock() 189 defer self.currentMu.Unlock() 190 return self.current.Block 191 } 192 193 func (self *worker) start() { 194 self.mu.Lock() 195 defer self.mu.Unlock() 196 197 atomic.StoreInt32(&self.producing, 1) 198 199 // Init bft 200 if dp, ok := self.engine.(*dpos.Dpos); ok { 201 dp.InitBft(self.SendBftMsg, self.SendBftPeerChangeMsg, self.chain.VerifyBlockForBft, self.writeBlock) 202 // 刚启动节点的bft节点设置 203 currentRoot := self.chain.CurrentHeader().Root 204 witnessesUrl := self.chain.Config().Dpos.WitnessesUrl 205 if db, err := self.chain.StateAt(currentRoot); err != nil { 206 log.Error("get current db error", "err", err) 207 } else { 208 _, urls := dp.GetWitnessesFromStateDB(db) 209 if len(urls) > 0 { 210 witnessesUrl = urls 211 } 212 } 213 self.SendBftPeerChangeMsg(witnessesUrl) 214 } 215 } 216 217 func (self *worker) stop() { 218 self.wg.Wait() 219 220 self.producerStop <- struct{}{} 221 222 self.mu.Lock() 223 defer self.mu.Unlock() 224 atomic.StoreInt32(&self.producing, 0) 225 atomic.StoreInt32(&self.atWork, 0) 226 227 if dp, ok := self.engine.(*dpos.Dpos); ok { 228 dp.ProducingStop() 229 } 230 } 231 232 func (self *worker) update() { 233 defer self.txsSub.Unsubscribe() 234 defer self.chainHeadSub.Unsubscribe() 235 defer self.chainSideSub.Unsubscribe() 236 defer self.recBftMsgSub.Unsubscribe() 237 238 for { 239 // A real event arrived, process interesting content 240 select { 241 // Handle ChainHeadEvent 242 case headEvent := <-self.chainHeadCh: 243 log.Debug("Worker: new block write finished", "block hash", headEvent.Block.Hash().String()) 244 if self.config.Dpos != nil { 245 if dp, ok := self.engine.(*dpos.Dpos); ok { 246 dp.CleanOldMsg(headEvent.Block.Number()) 247 } 248 } 249 250 // Handle ChainSideEvent 251 case ev := <-self.chainSideCh: 252 log.Info("Block fail to on chain", "block hash", ev.Block.Hash()) 253 254 // Handle NewTxsEvent 255 case ev := <-self.txsCh: 256 // Apply transactions to the pending state if we're not producing block. 257 // 258 // Note all transactions received may not be continuous with transactions 259 // already included in the current producing block. These transactions will 260 // be automatically eliminated. 261 if self.config.Dpos == nil && atomic.LoadInt32(&self.producing) == 0 { 262 self.currentMu.Lock() 263 txs := make(map[common.Address]types.Transactions) 264 for _, tx := range ev.Txs { 265 acc, _ := types.Sender(self.current.signer, tx) 266 txs[acc] = append(txs[acc], tx) 267 } 268 txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs) 269 self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase) 270 self.updateSnapshot() 271 self.currentMu.Unlock() 272 } 273 274 // Only Dpos using 275 case <-self.roundTimer.C: 276 if self.config.Dpos != nil { 277 self.commitNewWork() 278 } 279 280 case nextRoundTime := <-self.resetTimerEvent: 281 log.Trace("Receive reset round timer") 282 self.resetRoundTimer(nextRoundTime) 283 284 case <-self.producerStop: 285 self.stopRoundTimer() 286 287 // System stopped 288 case <-self.txsSub.Err(): 289 self.stopRoundTimer() 290 return 291 case <-self.chainHeadSub.Err(): 292 self.stopRoundTimer() 293 return 294 case <-self.chainSideSub.Err(): 295 self.stopRoundTimer() 296 return 297 } 298 } 299 } 300 301 func (self *worker) recBftMsg() { 302 for obj := range self.recBftMsgSub.Chan() { 303 switch ev := obj.Data.(type) { 304 case core.RecBftMsgEvent: 305 self.engine.HandleBftMsg(self.chain, ev.BftMsg.Msg) 306 default: 307 log.Warn("Receive bft msg, but type unknown") 308 } 309 } 310 } 311 312 // push sends a new work task to currently live producer agents. 313 func (self *worker) push(work *Work) { 314 if atomic.LoadInt32(&self.producing) != 1 { 315 return 316 } 317 318 if _, err := self.engine.Seal(self.chain, work.Block, nil); err != nil { 319 log.Warn("Block sealing failed", "err", err) 320 } 321 } 322 323 func (self *worker) SendBftMsg(msg types.ConsensusMsg) { 324 self.mux.Post(core.SendBftMsgEvent{ 325 BftMsg: types.BftMsg{ 326 BftType: msg.Type(), 327 Msg: msg, 328 }}) 329 } 330 331 func (self *worker) SendBftPeerChangeMsg(urls []string) { 332 self.mux.Post(core.BftPeerChangeEvent{ 333 Urls: urls, 334 }) 335 } 336 337 // writeBlock write block to block chain, and post NewProducedBlockEvent 338 func (self *worker) writeBlock(block *types.Block) error { 339 if err := self.chain.WriteBlock(block); err != nil { 340 log.Error("Failed writing block to chain", "err", err) 341 return err 342 } 343 344 // Broadcast the block and announce chain insertion event 345 self.mux.Post(core.NewProducedBlockEvent{Block: block}) 346 return nil 347 } 348 349 // makeCurrent creates a new environment for the current cycle. 350 func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error { 351 log.Trace("Enter make current") 352 353 state, err := self.chain.StateAt(parent.Root()) 354 if err != nil { 355 return err 356 } 357 work := &Work{ 358 config: self.config, 359 signer: types.NewHubbleSigner(self.config.ChainID), 360 state: state, 361 header: header, 362 createdAt: time.Now(), 363 } 364 365 // Keep track of transactions which return errors so they can be removed 366 work.tcount = 0 367 self.current = work 368 369 return nil 370 } 371 372 func (self *worker) commitNewWork() { 373 log.Trace("commitNewWork start") 374 375 self.mu.Lock() 376 defer self.mu.Unlock() 377 self.currentMu.Lock() 378 defer self.currentMu.Unlock() 379 380 tstart := time.Now() 381 tstamp := tstart.Unix() 382 parent := self.chain.CurrentBlock() 383 384 // Do not work too try before parent block 385 wait := time.Unix(parent.Time().Int64(), 0).Sub(tstart) 386 if wait > 0 { 387 log.Info("CommitNewWork start before parent's block time, wait", "wait", wait) 388 time.Sleep(wait) 389 } 390 391 num := parent.Number() 392 header := &types.Header{ 393 ParentHash: parent.Hash(), 394 Number: num.Add(num, common.Big1), 395 GasLimit: core.CalcGasLimit(parent), 396 Extra: self.extra, 397 Time: big.NewInt(tstamp), 398 } 399 // Only set the coinbase if we are producing (avoid spurious block rewards) 400 if atomic.LoadInt32(&self.producing) == 1 { 401 header.Coinbase = self.coinbase 402 } 403 404 preErr := self.engine.Prepare(self.chain, header) 405 if atomic.LoadInt32(&self.producing) == 1 { 406 log.Trace("Send reset round timer") 407 self.resetTimerEvent <- header.Time 408 } 409 if time.Unix(header.Time.Int64(), 0).Sub(time.Now()) <= 0 { 410 log.Warn("Prepare use too much time, missing out your turn") 411 return 412 } 413 414 // Could potentially happen if starting to produce in an odd state. 415 err := self.makeCurrent(parent, header) 416 if err != nil { 417 log.Error("Failed to create producing context", "err", err) 418 return 419 } 420 // Create the current work task and check any fork transitions needed 421 work := self.current 422 pending, err := self.vnt.TxPool().Pending() 423 if err != nil { 424 log.Error("Failed to fetch pending transactions", "err", err) 425 return 426 } 427 txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending) 428 work.commitTransactions(self.mux, txs, self.chain, self.coinbase) 429 430 // Create the new block to seal with the consensus engine 431 if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, work.receipts); err != nil { 432 log.Error("Failed to finalize block for sealing", "err", err) 433 return 434 } 435 blockheaderjson, _ := json.Marshal(work.Block.Header()) 436 blocktxjson, _ := json.Marshal(work.Block.Transactions()) 437 log.Debug("worker", "func", "commitNewWork", "block header", string(blockheaderjson), "block tx", string(blocktxjson)) 438 // We only care about logging if we're actually producing. 439 if atomic.LoadInt32(&self.producing) == 1 { 440 log.Info("Commit new producing work", "number", work.Block.Number(), "txs", work.tcount, "elapsed", common.PrettyDuration(time.Since(tstart))) 441 self.unconfirmed.Shift(work.Block.NumberU64() - 1) 442 } 443 444 // This is time consuming. The max time has been used is 4.3ms with 220txs in a block. 445 self.updateSnapshot() 446 447 if preErr != nil { 448 log.Debug("Failed to prepare header for producing", "preErr", preErr) 449 return 450 } 451 452 // updateSnapshot() is time consuming. If push() before updateSnapshot(), Gvnt may be 453 // stop for concurrent map iteration and map write. After push(), a block generated 454 // will be write to statedb, and may be updateSnapshot() still read statedb. Then 455 // an error occurs. 456 self.push(work) 457 } 458 459 // Reset the clock for the next period 460 func (self *worker) resetRoundTimer(nextRoundTime *big.Int) { 461 dur := time.Unix(nextRoundTime.Int64(), 0).Sub(time.Now()) 462 // Always make sure timer stoped and cleaned before Reset() 463 self.stopRoundTimer() 464 self.roundTimer.Reset(dur) 465 log.Debug("Reset round timer", "header.time", nextRoundTime, "time", time.Now().Unix(), "dur", dur) 466 } 467 468 func (self *worker) stopRoundTimer() { 469 // The stop command may be happen when the round timer is timeout but not deal with 470 // the timeout event, so cleaning the channel of roundTimer is needed. 471 if false == self.roundTimer.Stop() && len(self.roundTimer.C) > 0 { 472 <-self.roundTimer.C 473 log.Warn("worker.roundTimer.C still has a expired event, now has been cleaned") 474 } 475 } 476 477 func (self *worker) updateSnapshot() { 478 self.snapshotMu.Lock() 479 defer self.snapshotMu.Unlock() 480 481 self.snapshotBlock = types.NewBlock( 482 self.current.header, 483 self.current.txs, 484 self.current.receipts, 485 ) 486 self.snapshotState = self.current.state.Copy() 487 } 488 489 func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) { 490 if env.gasPool == nil { 491 env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit) 492 } 493 494 var coalescedLogs []*types.Log 495 496 for { 497 // If we don't have enough gas for any further transactions then we're done 498 if env.gasPool.Gas() < params.TxGas { 499 log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas) 500 break 501 } 502 // Retrieve the next transaction and abort if all done 503 tx := txs.Peek() 504 if tx == nil { 505 break 506 } 507 // Error may be ignored here. The error has already been checked 508 // during transaction acceptance is the transaction pool. 509 // 510 // We use the eip155 signer regardless of the current hf. 511 from, _ := types.Sender(env.signer, tx) 512 513 // Start executing the transaction 514 env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount) 515 516 err, logs := env.commitTransaction(tx, bc, coinbase, env.gasPool) 517 switch err { 518 case core.ErrGasLimitReached: 519 // Pop the current out-of-gas transaction without shifting in the next from the account 520 log.Trace("Gas limit exceeded for current block", "sender", from) 521 txs.Pop() 522 523 case core.ErrNonceTooLow: 524 // New head notification data race between the transaction pool and producer, shift 525 log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) 526 txs.Shift() 527 528 case core.ErrNonceTooHigh: 529 // Reorg notification data race between the transaction pool and producer, skip account = 530 log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) 531 txs.Pop() 532 533 case nil: 534 // Everything ok, collect the logs and shift in the next transaction from the same account 535 coalescedLogs = append(coalescedLogs, logs...) 536 env.tcount++ 537 txs.Shift() 538 539 default: 540 // Strange error, discard the transaction and get the next in line (note, the 541 // nonce-too-high clause will prevent us from executing in vain). 542 log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) 543 txs.Shift() 544 } 545 } 546 547 if len(coalescedLogs) > 0 || env.tcount > 0 { 548 // make a copy, the state caches the logs and these logs get "upgraded" from pending to produced 549 // logs by filling in the block hash when the block was produced by the local producer. This can 550 // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. 551 cpy := make([]*types.Log, len(coalescedLogs)) 552 for i, l := range coalescedLogs { 553 cpy[i] = new(types.Log) 554 *cpy[i] = *l 555 } 556 go func(logs []*types.Log, tcount int) { 557 if len(logs) > 0 { 558 mux.Post(core.PendingLogsEvent{Logs: logs}) 559 } 560 if tcount > 0 { 561 mux.Post(core.PendingStateEvent{}) 562 } 563 }(cpy, env.tcount) 564 } 565 } 566 567 func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) { 568 snap := env.state.Snapshot() 569 570 receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, &env.header.GasUsed, vm.Config{}) 571 if err != nil { 572 env.state.RevertToSnapshot(snap) 573 return err, nil 574 } 575 env.txs = append(env.txs, tx) 576 env.receipts = append(env.receipts, receipt) 577 578 return nil, receipt.Logs 579 }