# ETH源码学习(5)处理tx **Published by:** [point](https://paragraph.com/@point/) **Published on:** 2022-05-13 **URL:** https://paragraph.com/@point/eth-5-tx ## Content 处理tx要先开启挖矿总结设置好新区块总gas检查总gas,重放将gas转化成msg创建evm检查我们的余额是否足够接下来的操作调用evm执行合约/eth转账转账的话addr放入内存,然后执行transfer,修改stateobject的值给矿工钱返回receipt源码miner.start() //入口 func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { // Short circuit if current is nil if w.current == nil { return true } //先设置好总的gas,因为eth是按gas上限来规定一个区块的大小,btc是按区块容量 gasLimit := w.current.header.GasLimit if w.current.gasPool == nil { w.current.gasPool = new(core.GasPool).AddGas(gasLimit) } var coalescedLogs []*types.Log for { // In the following three cases, we will interrupt the execution of the transaction. 在以下三种情况下,我们将中断事务的执行。 // (1) new head block event arrival, the interrupt signal is 1 新磁头块事件到达时,中断信号为1 // (2) worker start or restart, the interrupt signal is 1 工人启动或重启,中断信号为1 // (3) worker recreate the mining block with any newly arrived transactions, the interrupt signal is 2. 工人用任何新到达的事务重新创建采矿区块,中断信号为2。 // For the first two cases, the semi-finished work will be discarded. 对于前两种情况,半成品将被丢弃。 // For the third case, the semi-finished work will be submitted to the consensus engine. 对于第三种情况,半成品将提交给共识引擎。 if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { // Notify resubmit loop to increase resubmitting interval due to too frequent commits. 由于提交过于频繁,通知重新提交循环以增加重新提交间隔。 if atomic.LoadInt32(interrupt) == commitInterruptResubmit { ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) if ratio < 0.1 { ratio = 0.1 } w.resubmitAdjustCh <- &intervalAdjust{ ratio: ratio, inc: true, } } return atomic.LoadInt32(interrupt) == commitInterruptNewHead } // If we don't have enough gas for any further transactions then we're done 如果我们没有足够的gas来做进一步的交易,那我们结束 if w.current.gasPool.Gas() < params.TxGas { log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas) break } // Retrieve the next transaction and abort if all done //检索下一笔交易,如果交易集合为空则退出 commit tx := txs.Peek() if tx == nil { break } // Error may be ignored here. The error has already been checked //这里可以忽略错误。已检查错误 // during transaction acceptance is the transaction pool. //在事务接受期间,是事务池。 // We use the eip155 signer regardless of the current hf. //我们使用eip155签名者,而不考虑当前的hf。eth硬分叉事件 from, _ := types.Sender(w.current.signer, tx) // Check whether the tx is replay protected. If we're not in the EIP155 hf // phase, start ignoring the sender until we do. if tx.Protected() && !w.chainConfig.IsEIP155(w.current.header.Number) { log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block) txs.Pop() continue } // Start executing the transaction w.current.state.Prepare(tx.Hash(), w.current.tcount) // 执行交易 logs, err := w.commitTransaction(tx, coinbase) switch { case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account 在不从账户转入下一笔交易的情况下,弹出当前的out of gas交易 log.Trace("Gas limit exceeded for current block", "sender", from) txs.Pop() case errors.Is(err, core.ErrNonceTooLow): // New head notification data race between the transaction pool and miner, shift log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) // 移动到用户的下一个交易 txs.Shift() case errors.Is(err, core.ErrNonceTooHigh): // Reorg notification data race between the transaction pool and miner, skip account = log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) w.current.tcount++ txs.Shift() case errors.Is(err, core.ErrTxTypeNotSupported): // Pop the unsupported transaction without shifting in the next from the account log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type()) txs.Pop() default: // Strange error, discard the transaction and get the next in line (note, the // nonce-too-high clause will prevent us from executing in vain). log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) txs.Shift() } } if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are mining. The reason is that // when we are mining, the worker will regenerate a mining block every 3 seconds. // In order to avoid pushing the repeated pendingLog, we disable the pending log pushing. // make a copy, the state caches the logs and these logs get "upgraded" from pending to mined // logs by filling in the block hash when the block was mined by the local miner. This can // cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed. // 因为需要把log发送出去,而这边在挖矿完成后需要对log进行修改,所以拷贝一份发送出去,避免争用。 cpy := make([]*types.Log, len(coalescedLogs)) for i, l := range coalescedLogs { cpy[i] = new(types.Log) *cpy[i] = *l } w.pendingLogsFeed.Send(cpy) } // Notify resubmit loop to decrease resubmitting interval if current interval is larger // than the user-specified one. if interrupt != nil { w.resubmitAdjustCh <- &intervalAdjust{inc: false} } return false } func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { //将types.Transaction结构变量转为core.Message对象;这过程中会对发送者做签名验证,并获得发送者的地址缓存起来 msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee) if err != nil { return nil, err } // Create a new context to be used in the EVM environment //创建新的上下文(Context),此上下文将在EVM 环境(EVM environment)中使用;上下文中包含msg,区块头、区块指针、作者(挖矿者、获益者) blockContext := NewEVMBlockContext(header, bc, author) //创建新的EVM environment,其中包括了交易相关的所有信息以及调用机制; vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) //处理交易,将交易应用于当前的状态中,也就是执行状态转换,新的状态包含在环境对象中;得到执行结果以及花费的gas; return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } 跳过一段调用ApplyMessage,并将返回的内容整理成receipt代码func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // First check this message satisfies all consensus rules before //首先检查此消息是否满足所有共识规则 // applying the message. The rules include these clauses //应用消息。规则包括这些条款 // // 1. the nonce of the message caller is correct //调用方的nonce是正确的 // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) //调用者有足够的余额支付交易费用(gaslimit*gasprice) // 3. the amount of gas required is available in the block 区块内有可用的所需gas // 4. the purchased gas is enough to cover intrinsic usage // 5. there is no overflow when calculating intrinsic gas 没有gas溢出 // 6. caller has enough balance to cover asset transfer for **topmost** call //调用者有足够的gas to transfer // Check clauses 1-3, buy gas if everything is correct // b. buyGas:根据发送者定的gaslimit和GasPrice,从发送者余额中扣除以太币;从区块gas池中减掉本次gas;并对运行环境做好更新; if err := st.preCheck(); err != nil { return nil, err } msg := st.msg sender := vm.AccountRef(msg.From()) homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) contractCreation := msg.To() == nil // Check clauses 4-5, subtract intrinsic gas if everything is correct //支付固定费用 intrinsic gas。contractCreation为true即为合约创建,固定费用为53000;为false,则为普通交易固定费用为21000 gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul) if err != nil { return nil, err } if st.gas < gas { return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } st.gas -= gas // Check clause 6 if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) } // Set up the initial access list. if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsBerlin { st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) } var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) //如果是合约创建, 那么调用evm的Create方法创建新的合约,使用交易的data作为新合约的部署代码 if contractCreation { ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value) } else { // Increment the nonce for the next transaction //否则不是合约创建,增加发送者的Nonce值,然后调用evm.Call执行交易 st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) //真正转账的地方 ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { // Before EIP-3529: refunds were capped to gasUsed / 2 st.refundGas(params.RefundQuotient) } else { // After EIP-3529: refunds are capped to gasUsed / 5 st.refundGas(params.RefundQuotientEIP3529) } effectiveTip := st.gasPrice if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee)) } st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) return &ExecutionResult{ UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, }, nil } //转账 func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { //检查是否允许递归执行以及执行深度,若深度超过params.CallCreateDepth(即1024)就出错返回; if evm.Config.NoRecursion && evm.depth > 0 { return nil, gas, nil } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } // Fail if we're trying to transfer more than the available balance if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) //这个地址如果不在statedb中,则创建,就是不在内存中,就放到内存里 if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.Config.Debug && evm.depth == 0 { evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil) } return nil, gas, nil } evm.StateDB.CreateAccount(addr) } //转账 evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.Config.Debug && evm.depth == 0 { evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) }(gas, time.Now()) } if isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. code := evm.StateDB.GetCode(addr) if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { addrCopy := addr // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } } // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { gas = 0 } // TODO: consider clearing up unused snapshots: //} else { // evm.StateDB.DiscardSnapshot(snapshot) } return ret, gas, err } //转账操作,实际上就是再stateobject中加减,然后写入journal func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { db.SubBalance(sender, amount) db.AddBalance(recipient, amount) } ## Publication Information - [point](https://paragraph.com/@point/): Publication homepage - [All Posts](https://paragraph.com/@point/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@point): Subscribe to updates