github.com/MetalBlockchain/subnet-evm@v0.6.3/miner/worker.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2015 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 // 27 // NOTE: this piece of code is modified by Ted Yin. 28 // The modification is also licensed under the same LGPL. 29 30 package miner 31 32 import ( 33 "errors" 34 "fmt" 35 "math/big" 36 "sync" 37 "time" 38 39 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 40 "github.com/MetalBlockchain/metalgo/utils/units" 41 "github.com/MetalBlockchain/subnet-evm/consensus" 42 "github.com/MetalBlockchain/subnet-evm/consensus/dummy" 43 "github.com/MetalBlockchain/subnet-evm/core" 44 "github.com/MetalBlockchain/subnet-evm/core/state" 45 "github.com/MetalBlockchain/subnet-evm/core/txpool" 46 "github.com/MetalBlockchain/subnet-evm/core/types" 47 "github.com/MetalBlockchain/subnet-evm/core/vm" 48 "github.com/MetalBlockchain/subnet-evm/params" 49 "github.com/MetalBlockchain/subnet-evm/precompile/precompileconfig" 50 "github.com/MetalBlockchain/subnet-evm/predicate" 51 "github.com/ethereum/go-ethereum/common" 52 "github.com/ethereum/go-ethereum/event" 53 "github.com/ethereum/go-ethereum/log" 54 ) 55 56 const ( 57 targetTxsSize = 1800 * units.KiB 58 ) 59 60 // environment is the worker's current environment and holds all of the current state information. 61 type environment struct { 62 signer types.Signer 63 state *state.StateDB // apply state changes here 64 tcount int // tx count in cycle 65 gasPool *core.GasPool // available gas used to pack transactions 66 67 parent *types.Header 68 header *types.Header 69 txs []*types.Transaction 70 receipts []*types.Receipt 71 size uint64 72 73 rules params.Rules 74 predicateContext *precompileconfig.PredicateContext 75 // predicateResults contains the results of checking the predicates for each transaction in the miner. 76 // The results are accumulated as transactions are executed by the miner and set on the BlockContext. 77 // If a transaction is dropped, its results must explicitly be removed from predicateResults in the same 78 // way that the gas pool and state is reset. 79 predicateResults *predicate.Results 80 81 start time.Time // Time that block building began 82 } 83 84 // worker is the main object which takes care of submitting new work to consensus engine 85 // and gathering the sealing result. 86 type worker struct { 87 config *Config 88 chainConfig *params.ChainConfig 89 engine consensus.Engine 90 eth Backend 91 chain *core.BlockChain 92 93 // Feeds 94 // TODO remove since this will never be written to 95 pendingLogsFeed event.Feed 96 97 // Subscriptions 98 mux *event.TypeMux // TODO replace 99 mu sync.RWMutex // The lock used to protect the coinbase and extra fields 100 coinbase common.Address 101 clock *mockable.Clock // Allows us mock the clock for testing 102 } 103 104 func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, clock *mockable.Clock) *worker { 105 worker := &worker{ 106 config: config, 107 chainConfig: chainConfig, 108 engine: engine, 109 eth: eth, 110 chain: eth.BlockChain(), 111 mux: mux, 112 coinbase: config.Etherbase, 113 clock: clock, 114 } 115 116 return worker 117 } 118 119 // setEtherbase sets the etherbase used to initialize the block coinbase field. 120 func (w *worker) setEtherbase(addr common.Address) { 121 w.mu.Lock() 122 defer w.mu.Unlock() 123 w.coinbase = addr 124 } 125 126 // commitNewWork generates several new sealing tasks based on the parent block. 127 func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateContext) (*types.Block, error) { 128 w.mu.RLock() 129 defer w.mu.RUnlock() 130 131 tstart := w.clock.Time() 132 timestamp := uint64(tstart.Unix()) 133 parent := w.chain.CurrentBlock() 134 // Note: in order to support asynchronous block production, blocks are allowed to have 135 // the same timestamp as their parent. This allows more than one block to be produced 136 // per second. 137 if parent.Time >= timestamp { 138 timestamp = parent.Time 139 } 140 141 var gasLimit uint64 142 // The fee manager relies on the state of the parent block to set the fee config 143 // because the fee config may be changed by the current block. 144 feeConfig, _, err := w.chain.GetFeeConfigAt(parent) 145 if err != nil { 146 return nil, err 147 } 148 configuredGasLimit := feeConfig.GasLimit.Uint64() 149 if w.chainConfig.IsSubnetEVM(timestamp) { 150 gasLimit = configuredGasLimit 151 } else { 152 // The gas limit is set in SubnetEVMGasLimit because the ceiling and floor were set to the same value 153 // such that the gas limit converged to it. Since this is hardbaked now, we remove the ability to configure it. 154 gasLimit = core.CalcGasLimit(parent.GasUsed, parent.GasLimit, configuredGasLimit, configuredGasLimit) 155 } 156 header := &types.Header{ 157 ParentHash: parent.Hash(), 158 Number: new(big.Int).Add(parent.Number, common.Big1), 159 GasLimit: gasLimit, 160 Extra: nil, 161 Time: timestamp, 162 } 163 164 if w.chainConfig.IsSubnetEVM(timestamp) { 165 var err error 166 header.Extra, header.BaseFee, err = dummy.CalcBaseFee(w.chainConfig, feeConfig, parent, timestamp) 167 if err != nil { 168 return nil, fmt.Errorf("failed to calculate new base fee: %w", err) 169 } 170 } 171 172 if w.coinbase == (common.Address{}) { 173 return nil, errors.New("cannot mine without etherbase") 174 } 175 header.Coinbase = w.coinbase 176 177 configuredCoinbase, isAllowFeeRecipient, err := w.chain.GetCoinbaseAt(parent) 178 if err != nil { 179 return nil, fmt.Errorf("failed to get configured coinbase: %w", err) 180 } 181 182 // if fee recipients are not allowed, then the coinbase is the configured coinbase 183 // don't set w.coinbase directly to the configured coinbase because that would override the 184 // coinbase set by the user 185 if !isAllowFeeRecipient && w.coinbase != configuredCoinbase { 186 log.Info("fee recipients are not allowed, using required coinbase for the mining", "currentminer", w.coinbase, "required", configuredCoinbase) 187 header.Coinbase = configuredCoinbase 188 } 189 190 if err := w.engine.Prepare(w.chain, header); err != nil { 191 return nil, fmt.Errorf("failed to prepare header for mining: %w", err) 192 } 193 194 env, err := w.createCurrentEnvironment(predicateContext, parent, header, tstart) 195 if err != nil { 196 return nil, fmt.Errorf("failed to create new current environment: %w", err) 197 } 198 // Ensure we always stop prefetcher after block building is complete. 199 defer func() { 200 if env.state == nil { 201 return 202 } 203 env.state.StopPrefetcher() 204 }() 205 // Configure any upgrades that should go into effect during this block. 206 err = core.ApplyUpgrades(w.chainConfig, &parent.Time, types.NewBlockWithHeader(header), env.state) 207 if err != nil { 208 log.Error("failed to configure precompiles mining new block", "parent", parent.Hash(), "number", header.Number, "timestamp", header.Time, "err", err) 209 return nil, err 210 } 211 212 // Fill the block with all available pending transactions. 213 pending := w.eth.TxPool().PendingWithBaseFee(true, header.BaseFee) 214 215 // Split the pending transactions into locals and remotes 216 localTxs := make(map[common.Address][]*txpool.LazyTransaction) 217 remoteTxs := pending 218 for _, account := range w.eth.TxPool().Locals() { 219 if txs := remoteTxs[account]; len(txs) > 0 { 220 delete(remoteTxs, account) 221 localTxs[account] = txs 222 } 223 } 224 if len(localTxs) > 0 { 225 txs := newTransactionsByPriceAndNonce(env.signer, localTxs, header.BaseFee) 226 w.commitTransactions(env, txs, header.Coinbase) 227 } 228 if len(remoteTxs) > 0 { 229 txs := newTransactionsByPriceAndNonce(env.signer, remoteTxs, header.BaseFee) 230 w.commitTransactions(env, txs, header.Coinbase) 231 } 232 233 return w.commit(env) 234 } 235 236 func (w *worker) createCurrentEnvironment(predicateContext *precompileconfig.PredicateContext, parent *types.Header, header *types.Header, tstart time.Time) (*environment, error) { 237 state, err := w.chain.StateAt(parent.Root) 238 if err != nil { 239 return nil, err 240 } 241 state.StartPrefetcher("miner", w.eth.BlockChain().CacheConfig().TriePrefetcherParallelism) 242 return &environment{ 243 signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), 244 state: state, 245 parent: parent, 246 header: header, 247 tcount: 0, 248 gasPool: new(core.GasPool).AddGas(header.GasLimit), 249 rules: w.chainConfig.Rules(header.Number, header.Time), 250 predicateContext: predicateContext, 251 predicateResults: predicate.NewResults(), 252 start: tstart, 253 }, nil 254 } 255 256 func (w *worker) commitTransaction(env *environment, tx *txpool.Transaction, coinbase common.Address) ([]*types.Log, error) { 257 var ( 258 snap = env.state.Snapshot() 259 gp = env.gasPool.Gas() 260 blockContext vm.BlockContext 261 ) 262 263 if env.rules.IsDurango { 264 results, err := core.CheckPredicates(env.rules, env.predicateContext, tx.Tx) 265 if err != nil { 266 log.Debug("Transaction predicate failed verification in miner", "tx", tx.Tx.Hash(), "err", err) 267 return nil, err 268 } 269 env.predicateResults.SetTxResults(tx.Tx.Hash(), results) 270 271 blockContext = core.NewEVMBlockContextWithPredicateResults(env.header, w.chain, &coinbase, env.predicateResults) 272 } else { 273 blockContext = core.NewEVMBlockContext(env.header, w.chain, &coinbase) 274 } 275 276 receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, blockContext, env.gasPool, env.state, env.header, tx.Tx, &env.header.GasUsed, *w.chain.GetVMConfig()) 277 if err != nil { 278 env.state.RevertToSnapshot(snap) 279 env.gasPool.SetGas(gp) 280 env.predicateResults.DeleteTxResults(tx.Tx.Hash()) 281 return nil, err 282 } 283 env.txs = append(env.txs, tx.Tx) 284 env.receipts = append(env.receipts, receipt) 285 env.size += tx.Tx.Size() 286 287 return receipt.Logs, nil 288 } 289 290 func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAndNonce, coinbase common.Address) { 291 for { 292 // If we don't have enough gas for any further transactions then we're done. 293 if env.gasPool.Gas() < params.TxGas { 294 log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas) 295 break 296 } 297 // Retrieve the next transaction and abort if all done. 298 ltx := txs.Peek() 299 if ltx == nil { 300 break 301 } 302 tx := ltx.Resolve() 303 if tx == nil { 304 log.Warn("Ignoring evicted transaction") 305 306 txs.Pop() 307 continue 308 } 309 // Abort transaction if it won't fit in the block and continue to search for a smaller 310 // transction that will fit. 311 if totalTxsSize := env.size + tx.Tx.Size(); totalTxsSize > targetTxsSize { 312 log.Trace("Skipping transaction that would exceed target size", "hash", tx.Tx.Hash(), "totalTxsSize", totalTxsSize, "txSize", tx.Tx.Size()) 313 314 txs.Pop() 315 continue 316 } 317 // Error may be ignored here. The error has already been checked 318 // during transaction acceptance is the transaction pool. 319 from, _ := types.Sender(env.signer, tx.Tx) 320 321 // Check whether the tx is replay protected. If we're not in the EIP155 hf 322 // phase, start ignoring the sender until we do. 323 if tx.Tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) { 324 log.Trace("Ignoring reply protected transaction", "hash", tx.Tx.Hash(), "eip155", w.chainConfig.EIP155Block) 325 326 txs.Pop() 327 continue 328 } 329 // Start executing the transaction 330 env.state.SetTxContext(tx.Tx.Hash(), env.tcount) 331 332 _, err := w.commitTransaction(env, tx, coinbase) 333 switch { 334 case errors.Is(err, core.ErrNonceTooLow): 335 // New head notification data race between the transaction pool and miner, shift 336 log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Tx.Nonce()) 337 txs.Shift() 338 339 case errors.Is(err, nil): 340 env.tcount++ 341 txs.Shift() 342 343 default: 344 // Transaction is regarded as invalid, drop all consecutive transactions from 345 // the same sender because of `nonce-too-high` clause. 346 log.Debug("Transaction failed, account skipped", "hash", tx.Tx.Hash(), "err", err) 347 txs.Pop() 348 } 349 } 350 } 351 352 // commit runs any post-transaction state modifications, assembles the final block 353 // and commits new work if consensus engine is running. 354 func (w *worker) commit(env *environment) (*types.Block, error) { 355 if env.rules.IsDurango { 356 predicateResultsBytes, err := env.predicateResults.Bytes() 357 if err != nil { 358 return nil, fmt.Errorf("failed to marshal predicate results: %w", err) 359 } 360 env.header.Extra = append(env.header.Extra, predicateResultsBytes...) 361 } 362 // Deep copy receipts here to avoid interaction between different tasks. 363 receipts := copyReceipts(env.receipts) 364 block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.parent, env.state, env.txs, nil, receipts) 365 if err != nil { 366 return nil, err 367 } 368 369 return w.handleResult(env, block, time.Now(), receipts) 370 } 371 372 func (w *worker) handleResult(env *environment, block *types.Block, createdAt time.Time, unfinishedReceipts []*types.Receipt) (*types.Block, error) { 373 // Short circuit when receiving duplicate result caused by resubmitting. 374 if w.chain.HasBlock(block.Hash(), block.NumberU64()) { 375 return nil, fmt.Errorf("produced duplicate block (Hash: %s, Number %d)", block.Hash(), block.NumberU64()) 376 } 377 // Different block could share same sealhash, deep copy here to prevent write-write conflict. 378 var ( 379 hash = block.Hash() 380 receipts = make([]*types.Receipt, len(unfinishedReceipts)) 381 logs []*types.Log 382 ) 383 for i, unfinishedReceipt := range unfinishedReceipts { 384 receipt := new(types.Receipt) 385 receipts[i] = receipt 386 *receipt = *unfinishedReceipt 387 388 // add block location fields 389 receipt.BlockHash = hash 390 receipt.BlockNumber = block.Number() 391 receipt.TransactionIndex = uint(i) 392 393 // Update the block hash in all logs since it is now available and not when the 394 // receipt/log of individual transactions were created. 395 receipt.Logs = make([]*types.Log, len(unfinishedReceipt.Logs)) 396 for j, unfinishedLog := range unfinishedReceipt.Logs { 397 log := new(types.Log) 398 receipt.Logs[j] = log 399 *log = *unfinishedLog 400 log.BlockHash = hash 401 } 402 logs = append(logs, receipt.Logs...) 403 } 404 405 feesInEther, err := core.TotalFeesFloat(block, receipts) 406 if err != nil { 407 log.Error("TotalFeesFloat error: %s", err) 408 } 409 log.Info("Commit new mining work", "number", block.Number(), "hash", hash, 410 "uncles", 0, "txs", env.tcount, 411 "gas", block.GasUsed(), "fees", feesInEther, 412 "elapsed", common.PrettyDuration(time.Since(env.start))) 413 414 // Note: the miner no longer emits a NewMinedBlock event. Instead the caller 415 // is responsible for running any additional verification and then inserting 416 // the block with InsertChain, which will also emit a new head event. 417 return block, nil 418 } 419 420 // copyReceipts makes a deep copy of the given receipts. 421 func copyReceipts(receipts []*types.Receipt) []*types.Receipt { 422 result := make([]*types.Receipt, len(receipts)) 423 for i, l := range receipts { 424 cpy := *l 425 result[i] = &cpy 426 } 427 return result 428 }