github.com/klaytn/klaytn@v1.10.2/work/worker.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/worker.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package work 22 23 import ( 24 "math/big" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "github.com/klaytn/klaytn/blockchain" 30 "github.com/klaytn/klaytn/blockchain/state" 31 "github.com/klaytn/klaytn/blockchain/types" 32 "github.com/klaytn/klaytn/blockchain/vm" 33 "github.com/klaytn/klaytn/common" 34 "github.com/klaytn/klaytn/consensus" 35 "github.com/klaytn/klaytn/consensus/misc" 36 "github.com/klaytn/klaytn/event" 37 klaytnmetrics "github.com/klaytn/klaytn/metrics" 38 "github.com/klaytn/klaytn/params" 39 "github.com/klaytn/klaytn/reward" 40 "github.com/klaytn/klaytn/storage/database" 41 "github.com/rcrowley/go-metrics" 42 ) 43 44 const ( 45 resultQueueSize = 10 46 miningLogAtDepth = 5 47 48 // txChanSize is the size of channel listening to NewTxsEvent. 49 // The number is referenced from the size of tx pool. 50 txChanSize = 4096 51 // chainHeadChanSize is the size of channel listening to ChainHeadEvent. 52 chainHeadChanSize = 10 53 // chainSideChanSize is the size of channel listening to ChainSideEvent. 54 chainSideChanSize = 10 55 // maxResendSize is the size of resending transactions to peer in order to prevent the txs from missing. 56 maxResendTxSize = 1000 57 ) 58 59 var ( 60 // Metrics for miner 61 timeLimitReachedCounter = metrics.NewRegisteredCounter("miner/timelimitreached", nil) 62 tooLongTxCounter = metrics.NewRegisteredCounter("miner/toolongtx", nil) 63 ResultChGauge = metrics.NewRegisteredGauge("miner/resultch", nil) 64 resentTxGauge = metrics.NewRegisteredGauge("miner/tx/resend/gauge", nil) 65 usedAllTxsCounter = metrics.NewRegisteredCounter("miner/usedalltxs", nil) 66 checkedTxsGauge = metrics.NewRegisteredGauge("miner/checkedtxs", nil) 67 tCountGauge = metrics.NewRegisteredGauge("miner/tcount", nil) 68 nonceTooLowTxsGauge = metrics.NewRegisteredGauge("miner/nonce/low/txs", nil) 69 nonceTooHighTxsGauge = metrics.NewRegisteredGauge("miner/nonce/high/txs", nil) 70 gasLimitReachedTxsGauge = metrics.NewRegisteredGauge("miner/limitreached/gas/txs", nil) 71 strangeErrorTxsCounter = metrics.NewRegisteredCounter("miner/strangeerror/txs", nil) 72 73 blockBaseFee = metrics.NewRegisteredGauge("miner/block/mining/basefee", nil) 74 blockMiningTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/mining/time", nil) 75 blockMiningExecuteTxTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/execute/time", nil) 76 blockMiningCommitTxTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/commit/time", nil) 77 blockMiningFinalizeTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/finalize/time", nil) 78 79 accountReadTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/account/reads", nil) 80 accountHashTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/account/hashes", nil) 81 accountUpdateTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/account/updates", nil) 82 accountCommitTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/account/commits", nil) 83 84 storageReadTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/storage/reads", nil) 85 storageHashTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/storage/hashes", nil) 86 storageUpdateTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/storage/updates", nil) 87 storageCommitTimer = klaytnmetrics.NewRegisteredHybridTimer("miner/block/storage/commits", nil) 88 89 snapshotAccountReadTimer = metrics.NewRegisteredTimer("miner/snapshot/account/reads", nil) 90 snapshotStorageReadTimer = metrics.NewRegisteredTimer("miner/snapshot/storage/reads", nil) 91 snapshotCommitTimer = metrics.NewRegisteredTimer("miner/snapshot/commits", nil) 92 calcDeferredRewardTimer = metrics.NewRegisteredTimer("reward/distribute/calcdeferredreward", nil) 93 ) 94 95 // Agent can register themself with the worker 96 type Agent interface { 97 Work() chan<- *Task 98 SetReturnCh(chan<- *Result) 99 Stop() 100 Start() 101 GetHashRate() int64 102 } 103 104 // Task is the workers current environment and holds 105 // all of the current state information 106 type Task struct { 107 config *params.ChainConfig 108 signer types.Signer 109 110 stateMu sync.RWMutex // protects state 111 state *state.StateDB // apply state changes here 112 tcount int // tx count in cycle 113 114 Block *types.Block // the new block 115 116 header *types.Header 117 txs []*types.Transaction 118 receipts []*types.Receipt 119 120 createdAt time.Time 121 } 122 123 type Result struct { 124 Task *Task 125 Block *types.Block 126 } 127 128 // worker is the main object which takes care of applying messages to the new state 129 type worker struct { 130 config *params.ChainConfig 131 engine consensus.Engine 132 133 mu sync.Mutex 134 135 // update loop 136 mux *event.TypeMux 137 txsCh chan blockchain.NewTxsEvent 138 txsSub event.Subscription 139 chainHeadCh chan blockchain.ChainHeadEvent 140 chainHeadSub event.Subscription 141 chainSideCh chan blockchain.ChainSideEvent 142 chainSideSub event.Subscription 143 wg sync.WaitGroup 144 145 agents map[Agent]struct{} 146 recv chan *Result 147 148 backend Backend 149 chain BlockChain 150 proc blockchain.Validator 151 chainDB database.DBManager 152 153 extra []byte 154 155 currentMu sync.Mutex 156 current *Task 157 rewardbase common.Address 158 159 snapshotMu sync.RWMutex 160 snapshotBlock *types.Block 161 snapshotState *state.StateDB 162 163 // atomic status counters 164 mining int32 165 atWork int32 166 167 nodetype common.ConnType 168 } 169 170 func newWorker(config *params.ChainConfig, engine consensus.Engine, rewardbase common.Address, backend Backend, mux *event.TypeMux, nodetype common.ConnType, TxResendUseLegacy bool) *worker { 171 worker := &worker{ 172 config: config, 173 engine: engine, 174 backend: backend, 175 mux: mux, 176 txsCh: make(chan blockchain.NewTxsEvent, txChanSize), 177 chainHeadCh: make(chan blockchain.ChainHeadEvent, chainHeadChanSize), 178 chainSideCh: make(chan blockchain.ChainSideEvent, chainSideChanSize), 179 chainDB: backend.ChainDB(), 180 recv: make(chan *Result, resultQueueSize), 181 chain: backend.BlockChain(), 182 proc: backend.BlockChain().Validator(), 183 agents: make(map[Agent]struct{}), 184 nodetype: nodetype, 185 rewardbase: rewardbase, 186 } 187 188 // Subscribe NewTxsEvent for tx pool 189 worker.txsSub = backend.TxPool().SubscribeNewTxsEvent(worker.txsCh) 190 // Subscribe events for blockchain 191 worker.chainHeadSub = backend.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) 192 worker.chainSideSub = backend.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) 193 go worker.update() 194 195 go worker.wait(TxResendUseLegacy) 196 return worker 197 } 198 199 func (self *worker) setExtra(extra []byte) { 200 self.mu.Lock() 201 defer self.mu.Unlock() 202 self.extra = extra 203 } 204 205 func (self *worker) pending() (*types.Block, *state.StateDB) { 206 if atomic.LoadInt32(&self.mining) == 0 { 207 // return a snapshot to avoid contention on currentMu mutex 208 self.snapshotMu.RLock() 209 defer self.snapshotMu.RUnlock() 210 return self.snapshotBlock, self.snapshotState.Copy() 211 } 212 213 self.currentMu.Lock() 214 defer self.currentMu.Unlock() 215 self.current.stateMu.Lock() 216 defer self.current.stateMu.Unlock() 217 return self.current.Block, self.current.state.Copy() 218 } 219 220 func (self *worker) pendingBlock() *types.Block { 221 if atomic.LoadInt32(&self.mining) == 0 { 222 // return a snapshot to avoid contention on currentMu mutex 223 self.snapshotMu.RLock() 224 defer self.snapshotMu.RUnlock() 225 return self.snapshotBlock 226 } 227 228 self.currentMu.Lock() 229 defer self.currentMu.Unlock() 230 return self.current.Block 231 } 232 233 func (self *worker) start() { 234 self.mu.Lock() 235 defer self.mu.Unlock() 236 237 atomic.StoreInt32(&self.mining, 1) 238 239 // istanbul BFT 240 if istanbul, ok := self.engine.(consensus.Istanbul); ok { 241 istanbul.Start(self.chain, self.chain.CurrentBlock, self.chain.HasBadBlock) 242 } 243 244 // spin up agents 245 for agent := range self.agents { 246 agent.Start() 247 } 248 } 249 250 func (self *worker) stop() { 251 self.wg.Wait() 252 253 self.mu.Lock() 254 defer self.mu.Unlock() 255 if atomic.LoadInt32(&self.mining) == 1 { 256 for agent := range self.agents { 257 agent.Stop() 258 } 259 } 260 261 // istanbul BFT 262 if istanbul, ok := self.engine.(consensus.Istanbul); ok { 263 istanbul.Stop() 264 } 265 266 atomic.StoreInt32(&self.mining, 0) 267 atomic.StoreInt32(&self.atWork, 0) 268 } 269 270 func (self *worker) register(agent Agent) { 271 self.mu.Lock() 272 defer self.mu.Unlock() 273 self.agents[agent] = struct{}{} 274 agent.SetReturnCh(self.recv) 275 } 276 277 func (self *worker) unregister(agent Agent) { 278 self.mu.Lock() 279 defer self.mu.Unlock() 280 delete(self.agents, agent) 281 agent.Stop() 282 } 283 284 func (self *worker) handleTxsCh(quitByErr chan bool) { 285 defer self.txsSub.Unsubscribe() 286 287 for { 288 select { 289 // Handle NewTxsEvent 290 case <-self.txsCh: 291 if atomic.LoadInt32(&self.mining) != 0 { 292 // If we're mining, but nothing is being processed, wake on new transactions 293 if self.config.Clique != nil && self.config.Clique.Period == 0 { 294 self.commitNewWork() 295 } 296 } 297 298 case <-quitByErr: 299 return 300 } 301 } 302 } 303 304 func (self *worker) update() { 305 defer self.chainHeadSub.Unsubscribe() 306 defer self.chainSideSub.Unsubscribe() 307 308 quitByErr := make(chan bool, 1) 309 go self.handleTxsCh(quitByErr) 310 311 for { 312 // A real event arrived, process interesting content 313 select { 314 // Handle ChainHeadEvent 315 case <-self.chainHeadCh: 316 // istanbul BFT 317 if h, ok := self.engine.(consensus.Handler); ok { 318 h.NewChainHead() 319 } 320 self.commitNewWork() 321 322 // TODO-Klaytn-Issue264 If we are using istanbul BFT, then we always have a canonical chain. 323 // Later we may be able to refine below code. 324 // Handle ChainSideEvent 325 case <-self.chainSideCh: 326 327 // System stopped 328 case <-self.txsSub.Err(): 329 quitByErr <- true 330 return 331 case <-self.chainHeadSub.Err(): 332 quitByErr <- true 333 return 334 case <-self.chainSideSub.Err(): 335 quitByErr <- true 336 return 337 } 338 } 339 } 340 341 func (self *worker) wait(TxResendUseLegacy bool) { 342 for { 343 mustCommitNewWork := true 344 for result := range self.recv { 345 atomic.AddInt32(&self.atWork, -1) 346 ResultChGauge.Update(ResultChGauge.Value() - 1) 347 if result == nil { 348 continue 349 } 350 351 // TODO-Klaytn drop or missing tx 352 if self.nodetype != common.CONSENSUSNODE { 353 if !TxResendUseLegacy { 354 continue 355 } 356 pending, err := self.backend.TxPool().Pending() 357 if err != nil { 358 logger.Error("Failed to fetch pending transactions", "err", err) 359 continue 360 } 361 362 if len(pending) > 0 { 363 accounts := len(pending) 364 resendTxSize := maxResendTxSize / accounts 365 if resendTxSize == 0 { 366 resendTxSize = 1 367 } 368 var resendTxs []*types.Transaction 369 for _, sortedTxs := range pending { 370 if len(sortedTxs) >= resendTxSize { 371 resendTxs = append(resendTxs, sortedTxs[:resendTxSize]...) 372 } else { 373 resendTxs = append(resendTxs, sortedTxs...) 374 } 375 } 376 if len(resendTxs) > 0 { 377 resentTxGauge.Update(int64(len(resendTxs))) 378 self.backend.ReBroadcastTxs(resendTxs) 379 } 380 } 381 continue 382 } 383 384 block := result.Block 385 work := result.Task 386 387 // Update the block hash in all logs since it is now available and not when the 388 // receipt/log of individual transactions were created. 389 for _, r := range work.receipts { 390 for _, l := range r.Logs { 391 l.BlockHash = block.Hash() 392 } 393 } 394 work.stateMu.Lock() 395 for _, log := range work.state.Logs() { 396 log.BlockHash = block.Hash() 397 } 398 399 start := time.Now() 400 result, err := self.chain.WriteBlockWithState(block, work.receipts, work.state) 401 work.stateMu.Unlock() 402 if err != nil { 403 if err == blockchain.ErrKnownBlock { 404 logger.Debug("Tried to insert already known block", "num", block.NumberU64(), "hash", block.Hash().String()) 405 } else { 406 logger.Error("Failed writing block to chain", "err", err) 407 } 408 continue 409 } 410 blockWriteTime := time.Since(start) 411 412 // TODO-Klaytn-Issue264 If we are using istanbul BFT, then we always have a canonical chain. 413 // Later we may be able to refine below code. 414 415 // check if canon block and write transactions 416 if result.Status == blockchain.CanonStatTy { 417 // implicit by posting ChainHeadEvent 418 mustCommitNewWork = false 419 } 420 421 // Broadcast the block and announce chain insertion event 422 self.mux.Post(blockchain.NewMinedBlockEvent{Block: block}) 423 424 var events []interface{} 425 426 work.stateMu.RLock() 427 logs := work.state.Logs() 428 work.stateMu.RUnlock() 429 430 events = append(events, blockchain.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) 431 if result.Status == blockchain.CanonStatTy { 432 events = append(events, blockchain.ChainHeadEvent{Block: block}) 433 } 434 435 // update governance CurrentSet if it is at an epoch block 436 if err := self.engine.CreateSnapshot(self.chain, block.NumberU64(), block.Hash(), nil); err != nil { 437 logger.Error("Failed to call snapshot", "err", err) 438 } 439 440 logger.Info("Successfully wrote mined block", "num", block.NumberU64(), 441 "hash", block.Hash(), "txs", len(block.Transactions()), "elapsed", blockWriteTime) 442 self.chain.PostChainEvents(events, logs) 443 444 // TODO-Klaytn-Issue264 If we are using istanbul BFT, then we always have a canonical chain. 445 // Later we may be able to refine below code. 446 if mustCommitNewWork { 447 self.commitNewWork() 448 } 449 } 450 } 451 } 452 453 // push sends a new work task to currently live work agents. 454 func (self *worker) push(work *Task) { 455 if atomic.LoadInt32(&self.mining) != 1 { 456 return 457 } 458 for agent := range self.agents { 459 atomic.AddInt32(&self.atWork, 1) 460 if ch := agent.Work(); ch != nil { 461 ch <- work 462 } 463 } 464 } 465 466 // makeCurrent creates a new environment for the current cycle. 467 func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error { 468 stateDB, err := self.chain.StateAt(parent.Root()) 469 if err != nil { 470 return err 471 } 472 work := NewTask(self.config, types.MakeSigner(self.config, header.Number), stateDB, header) 473 if self.nodetype != common.CONSENSUSNODE { 474 work.Block = parent 475 } 476 477 // Keep track of transactions which return errors so they can be removed 478 work.tcount = 0 479 self.current = work 480 return nil 481 } 482 483 func (self *worker) commitNewWork() { 484 var pending map[common.Address]types.Transactions 485 var err error 486 if self.nodetype == common.CONSENSUSNODE { 487 // Check any fork transitions needed 488 pending, err = self.backend.TxPool().Pending() 489 if err != nil { 490 logger.Error("Failed to fetch pending transactions", "err", err) 491 return 492 } 493 } 494 495 self.mu.Lock() 496 defer self.mu.Unlock() 497 self.currentMu.Lock() 498 defer self.currentMu.Unlock() 499 500 parent := self.chain.CurrentBlock() 501 nextBlockNum := new(big.Int).Add(parent.Number(), common.Big1) 502 var nextBaseFee *big.Int 503 if self.nodetype == common.CONSENSUSNODE { 504 if self.config.IsMagmaForkEnabled(nextBlockNum) { 505 // NOTE-klaytn NextBlockBaseFee needs the header of parent, self.chain.CurrentBlock 506 // So above code, TxPool().Pending(), is separated with this and can be refactored later. 507 nextBaseFee = misc.NextMagmaBlockBaseFee(parent.Header(), self.config.Governance.KIP71) 508 pending = types.FilterTransactionWithBaseFee(pending, nextBaseFee) 509 } 510 } 511 512 // TODO-Klaytn drop or missing tx 513 tstart := time.Now() 514 tstamp := tstart.Unix() 515 if self.nodetype == common.CONSENSUSNODE { 516 ideal := time.Unix(parent.Time().Int64()+params.BlockGenerationInterval, 0) 517 // If a timestamp of this block is faster than the ideal timestamp, 518 // wait for a while and get a new timestamp 519 if tstart.Before(ideal) { 520 wait := ideal.Sub(tstart) 521 logger.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) 522 time.Sleep(wait) 523 524 tstart = time.Now() // refresh for metrics 525 tstamp = tstart.Unix() // refresh for block timestamp 526 } 527 } 528 529 header := &types.Header{ 530 ParentHash: parent.Hash(), 531 Number: nextBlockNum, 532 Extra: self.extra, 533 Time: big.NewInt(tstamp), 534 } 535 if self.config.IsMagmaForkEnabled(nextBlockNum) { 536 header.BaseFee = nextBaseFee 537 } 538 if err := self.engine.Prepare(self.chain, header); err != nil { 539 logger.Error("Failed to prepare header for mining", "err", err) 540 return 541 } 542 // Could potentially happen if starting to mine in an odd state. 543 err = self.makeCurrent(parent, header) 544 if err != nil { 545 logger.Error("Failed to create mining context", "err", err) 546 return 547 } 548 549 // Obtain current work's state lock after we receive new work assignment. 550 self.current.stateMu.Lock() 551 defer self.current.stateMu.Unlock() 552 553 // Create the current work task 554 work := self.current 555 if self.nodetype == common.CONSENSUSNODE { 556 txs := types.NewTransactionsByTimeAndNonce(self.current.signer, pending) 557 work.commitTransactions(self.mux, txs, self.chain, self.rewardbase) 558 finishedCommitTx := time.Now() 559 560 // Create the new block to seal with the consensus engine 561 if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, work.receipts); err != nil { 562 logger.Error("Failed to finalize block for sealing", "err", err) 563 return 564 } 565 finishedFinalize := time.Now() 566 567 // We only care about logging if we're actually mining. 568 if atomic.LoadInt32(&self.mining) == 1 { 569 // Update the metrics subsystem with all the measurements 570 accountReadTimer.Update(work.state.AccountReads) 571 accountHashTimer.Update(work.state.AccountHashes) 572 accountUpdateTimer.Update(work.state.AccountUpdates) 573 accountCommitTimer.Update(work.state.AccountCommits) 574 575 storageReadTimer.Update(work.state.StorageReads) 576 storageHashTimer.Update(work.state.StorageHashes) 577 storageUpdateTimer.Update(work.state.StorageUpdates) 578 storageCommitTimer.Update(work.state.StorageCommits) 579 580 snapshotAccountReadTimer.Update(work.state.SnapshotAccountReads) 581 snapshotStorageReadTimer.Update(work.state.SnapshotStorageReads) 582 snapshotCommitTimer.Update(work.state.SnapshotCommits) 583 584 calcDeferredRewardTimer.Update(reward.CalcDeferredRewardTimer) 585 586 trieAccess := work.state.AccountReads + work.state.AccountHashes + work.state.AccountUpdates + work.state.AccountCommits 587 trieAccess += work.state.StorageReads + work.state.StorageHashes + work.state.StorageUpdates + work.state.StorageCommits 588 589 tCountGauge.Update(int64(work.tcount)) 590 blockMiningTime := time.Since(tstart) 591 commitTxTime := finishedCommitTx.Sub(tstart) 592 finalizeTime := finishedFinalize.Sub(finishedCommitTx) 593 594 if header.BaseFee != nil { 595 blockBaseFee.Update(header.BaseFee.Int64() / int64(params.Ston)) 596 } 597 blockMiningTimer.Update(blockMiningTime) 598 blockMiningCommitTxTimer.Update(commitTxTime) 599 blockMiningExecuteTxTimer.Update(commitTxTime - trieAccess) 600 blockMiningFinalizeTimer.Update(finalizeTime) 601 logger.Info("Commit new mining work", 602 "number", work.Block.Number(), "hash", work.Block.Hash(), 603 "txs", work.tcount, "elapsed", common.PrettyDuration(blockMiningTime), 604 "commitTime", common.PrettyDuration(commitTxTime), "finalizeTime", common.PrettyDuration(finalizeTime)) 605 } 606 } 607 608 self.push(work) 609 self.updateSnapshot() 610 } 611 612 func (self *worker) updateSnapshot() { 613 self.snapshotMu.Lock() 614 defer self.snapshotMu.Unlock() 615 616 self.snapshotBlock = types.NewBlock( 617 self.current.header, 618 self.current.txs, 619 self.current.receipts, 620 ) 621 self.snapshotState = self.current.state.Copy() 622 } 623 624 func (env *Task) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByTimeAndNonce, bc BlockChain, rewardbase common.Address) { 625 coalescedLogs := env.ApplyTransactions(txs, bc, rewardbase) 626 627 if len(coalescedLogs) > 0 || env.tcount > 0 { 628 // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined 629 // logs by filling in the block hash when the block was mined by the local miner. This can 630 // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. 631 cpy := make([]*types.Log, len(coalescedLogs)) 632 for i, l := range coalescedLogs { 633 cpy[i] = new(types.Log) 634 *cpy[i] = *l 635 } 636 go func(logs []*types.Log, tcount int) { 637 if len(logs) > 0 { 638 mux.Post(blockchain.PendingLogsEvent{Logs: logs}) 639 } 640 if tcount > 0 { 641 mux.Post(blockchain.PendingStateEvent{}) 642 } 643 }(cpy, env.tcount) 644 } 645 } 646 647 func (env *Task) ApplyTransactions(txs *types.TransactionsByTimeAndNonce, bc BlockChain, rewardbase common.Address) []*types.Log { 648 var coalescedLogs []*types.Log 649 650 // Limit the execution time of all transactions in a block 651 var abort int32 = 0 // To break the below commitTransaction for loop when timed out 652 chDone := make(chan bool) // To stop the goroutine below when processing txs is completed 653 654 // chEVM is used to notify the below goroutine of the running EVM so it can call evm.Cancel 655 // when timed out. We use a buffered channel to prevent the main EVM execution routine 656 // from being blocked due to the channel communication. 657 chEVM := make(chan *vm.EVM, 1) 658 659 go func() { 660 blockTimer := time.NewTimer(params.BlockGenerationTimeLimit) 661 timeout := false 662 var evm *vm.EVM 663 664 for { 665 select { 666 case <-blockTimer.C: 667 timeout = true 668 atomic.StoreInt32(&abort, 1) 669 670 case <-chDone: 671 // Everything is done. Stop this goroutine. 672 return 673 674 case evm = <-chEVM: 675 } 676 677 if timeout && evm != nil { 678 // Allow the first transaction to complete although it exceeds the time limit. 679 if env.tcount > 0 { 680 // The total time limit reached, thus we stop the currently running EVM. 681 evm.Cancel(vm.CancelByTotalTimeLimit) 682 } 683 evm = nil 684 } 685 } 686 }() 687 688 vmConfig := &vm.Config{ 689 RunningEVM: chEVM, 690 UseOpcodeComputationCost: true, 691 } 692 693 var numTxsChecked int64 = 0 694 var numTxsNonceTooLow int64 = 0 695 var numTxsNonceTooHigh int64 = 0 696 var numTxsGasLimitReached int64 = 0 697 CommitTransactionLoop: 698 for atomic.LoadInt32(&abort) == 0 { 699 // Retrieve the next transaction and abort if all done 700 tx := txs.Peek() 701 if tx == nil { 702 // To indicate that it does not have enough transactions for params.BlockGenerationTimeLimit. 703 if numTxsChecked > 0 { 704 usedAllTxsCounter.Inc(1) 705 } 706 break 707 } 708 numTxsChecked++ 709 // Error may be ignored here. The error has already been checked 710 // during transaction acceptance is the transaction pool. 711 // 712 // We use the eip155 signer regardless of the current hf. 713 from, _ := types.Sender(env.signer, tx) 714 715 // NOTE-Klaytn Since Klaytn is always in EIP155, the below replay protection code is not needed. 716 // TODO-Klaytn-RemoveLater Remove the code commented below. 717 // Check whether the tx is replay protected. If we're not in the EIP155 hf 718 // phase, start ignoring the sender until we do. 719 //if tx.Protected() && !env.config.IsEIP155(env.header.Number) { 720 // logger.Trace("Ignoring reply protected transaction", "hash", tx.Hash()) 721 // //logger.Error("#### worker.commitTransaction","tx.protected",tx.Protected(),"tx.hash",tx.Hash(),"nonce",tx.Nonce(),"to",tx.To()) 722 // txs.Pop() 723 // continue 724 //} 725 // Start executing the transaction 726 env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount) 727 728 err, logs := env.commitTransaction(tx, bc, rewardbase, vmConfig) 729 switch err { 730 case blockchain.ErrGasLimitReached: 731 // Pop the current out-of-gas transaction without shifting in the next from the account 732 logger.Trace("Gas limit exceeded for current block", "sender", from) 733 numTxsGasLimitReached++ 734 txs.Pop() 735 736 case blockchain.ErrNonceTooLow: 737 // New head notification data race between the transaction pool and miner, shift 738 logger.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) 739 numTxsNonceTooLow++ 740 txs.Shift() 741 742 case blockchain.ErrNonceTooHigh: 743 // Reorg notification data race between the transaction pool and miner, skip account = 744 logger.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce()) 745 numTxsNonceTooHigh++ 746 txs.Pop() 747 748 case vm.ErrTotalTimeLimitReached: 749 logger.Warn("Transaction aborted due to time limit", "hash", tx.Hash().String()) 750 timeLimitReachedCounter.Inc(1) 751 if env.tcount == 0 { 752 logger.Error("A single transaction exceeds total time limit", "hash", tx.Hash().String()) 753 tooLongTxCounter.Inc(1) 754 } 755 // NOTE-Klaytn Exit for loop immediately without checking abort variable again. 756 break CommitTransactionLoop 757 758 case blockchain.ErrTxTypeNotSupported: 759 // Pop the unsupported transaction without shifting in the next from the account 760 logger.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type()) 761 txs.Pop() 762 763 case nil: 764 // Everything ok, collect the logs and shift in the next transaction from the same account 765 coalescedLogs = append(coalescedLogs, logs...) 766 env.tcount++ 767 txs.Shift() 768 769 default: 770 // Strange error, discard the transaction and get the next in line (note, the 771 // nonce-too-high clause will prevent us from executing in vain). 772 logger.Warn("Transaction failed, account skipped", "sender", from, "hash", tx.Hash().String(), "err", err) 773 strangeErrorTxsCounter.Inc(1) 774 txs.Shift() 775 } 776 } 777 778 // Update the number of transactions checked and dropped during ApplyTransactions. 779 checkedTxsGauge.Update(numTxsChecked) 780 nonceTooLowTxsGauge.Update(numTxsNonceTooLow) 781 nonceTooHighTxsGauge.Update(numTxsNonceTooHigh) 782 gasLimitReachedTxsGauge.Update(numTxsGasLimitReached) 783 784 // Stop the goroutine that has been handling the timer. 785 chDone <- true 786 787 return coalescedLogs 788 } 789 790 func (env *Task) commitTransaction(tx *types.Transaction, bc BlockChain, rewardbase common.Address, vmConfig *vm.Config) (error, []*types.Log) { 791 snap := env.state.Snapshot() 792 793 receipt, _, err := bc.ApplyTransaction(env.config, &rewardbase, env.state, env.header, tx, &env.header.GasUsed, vmConfig) 794 if err != nil { 795 if err != vm.ErrInsufficientBalance && err != vm.ErrTotalTimeLimitReached { 796 tx.MarkUnexecutable(true) 797 } 798 env.state.RevertToSnapshot(snap) 799 return err, nil 800 } 801 env.txs = append(env.txs, tx) 802 env.receipts = append(env.receipts, receipt) 803 804 return nil, receipt.Logs 805 } 806 807 func NewTask(config *params.ChainConfig, signer types.Signer, statedb *state.StateDB, header *types.Header) *Task { 808 return &Task{ 809 config: config, 810 signer: signer, 811 state: statedb, 812 header: header, 813 createdAt: time.Now(), 814 } 815 } 816 817 func (env *Task) Transactions() []*types.Transaction { return env.txs } 818 func (env *Task) Receipts() []*types.Receipt { return env.receipts }