之前分析挖矿模块,miner从TxPool拿来的交易,交给worker对象。后者要调用commitTransaction在本地执行交易,生成receipt,更改世界状态,打包成挖矿的block最后递交给engine进行挖矿。而这一节我们关注的是在commitTransaction中,如何在本地执行交易。
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
snap := w.current.state.Snapshot()
receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config{})
if err != nil {
w.current.state.RevertToSnapshot(snap)
return nil, err
}
w.current.txs = append(w.current.txs, tx)
w.current.receipts = append(w.current.receipts, receipt)
return receipt.Logs, nil
}
core.ApplyTransaction就是执行交易的入口,而交易的执行就离不开大名鼎鼎的以太坊虚拟机。
该函数的调用有两种情况:
1、是在将区块插入区块链前需要验证区块合法性
bc.insertChain ——> bc.processor.Process ——> stateProcessor.Process ——> ApplyTransaction
2、是worker挖矿过程中执行交易时
Worker.commitTransaction ——> ApplyTransaction
主要功能是:将交易转化成Message,创建EVM对象,调用ApplyMessage执行交易,生成日志对象;
1、将交易转换成Message;
2、初始化一个EVM的执行环境;
3、执行交易,改变stateDB世界状态,然后生成收据
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, uint64, error) {
// 转换一个Message对象
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, 0, err
}
// 使用message初始化一个执行上下文
context := NewEVMContext(msg, header, bc, author)
// 使用上下文初始化一个EVM执行环境
vmenv := vm.NewEVM(context, statedb, config, cfg)
// 执行交易,返回使用的gas数量
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, 0, err
}
// 更改stateDB世界状态
var root []byte
// 如果是拜占庭硬分叉,清理世界状态
if config.IsByzantium(header.Number) {
statedb.Finalise(true)
} else {
// 否则计算状态树根,用于产生收据
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
// 增加header中的usedGas
*usedGas += gas
// 用root和usedGas产生收据,即收据里包含了该交易执行时的世界状态,这样方便进行校验
receipt := types.NewReceipt(root, failed, *usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = gas
// 如果交易创建了一个合约,要把合约地址放在收据里
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
return receipt, gas, err
}
交易工作环境(StateTransition)的数据结构如下:
type StateTransition struct {
gp *GasPool // 区块工作环境中的gas剩余额度,就是header中的gasLimit
msg Message // 交易转化的message
gas uint64 // 交易的gas余额,最开始等于initialGas,随着交易执行会递减
gasPrice *big.Int
initialGas uint64 // 初始gas,等于交易的gasLimit
value *big.Int // 交易转账额度
data []byte // 交易的input,如果是合约创建,data就是合约代码
state vm.StateDB // 状态树
evm *vm.EVM // evm对象
}
这里gp是整个区块所有交易可用的gas,其实就是来自于header的gaslimit,而header的gasLimit是通过父区块的gasUsed推算出来的。initialGas是交易的gasLimit,gas是余额(等于initialGas减去交易usedGas)。
在ApplyMessage中,首先新建一个交易工作环境,然后紧接着调用TransitionDb方法:
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
}
TransitionDb()的主要功能是初始化交易工作环境,执行交易,然后处理交易执行前后的gas增减。
1、预先检查nonce和gas值,初始化交易工作环境的gas初始值;
2、计算并扣除固定gas消耗;
3、调用evm创建或执行交易;
4、奖励旷工;
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
// 先检查交易的nonce和当前状态nonce是否一致,然后调用buyGas;
// buyGas是要从区块的gasPool中取出一定的gas用于执行交易
if err = st.preCheck(); err != nil {
return
}
msg := st.msg
sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
contractCreation := msg.To() == nil
// 计算并扣除固定的gas消耗
// 固定的gas消耗(21000) + 非0值gas + 0值gas
gas, err := IntrinsicGas(st.data, contractCreation, homestead)
if err != nil {
return nil, 0, false, err
}
// 更新剩余的gas值
if err = st.useGas(gas); err != nil {
return nil, 0, false, err
}
var (
evm = st.evm
vmerr error
)
if contractCreation {
// 如果是合约创建建议,调用evm.Create
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
// 如果是交易或合约调用,则先设置交易发送方的nonce值+1
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
// 调用evm.Call执行交易
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
if vmerr != nil {
log.Debug("VM returned with error", "err", vmerr)
// evm的错误不影响共识,当且仅当vmerr是ErrInsufficientBalance时,return的gasUsed等于0,此时不影响账户状态;
// 否则,即使交易执行失败,gas还是会被扣除
if vmerr == vm.ErrInsufficientBalance {
return nil, 0, false, vmerr
}
}
// 返回余额给发起方,它同时调整了StateTransition中gasPool的余额
st.refundGas()
// 奖励矿工,gas消耗
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err
}
交易执行之前,要调用bugGas()将initialGas和gas都设置为交易的gasLimit,并从总的gaspool里减去预支的gas(即gasLimit),交易发起者的账户也要减去相应的价值。
func (st *StateTransition) buyGas() error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err
}
st.gas += st.msg.Gas()
st.initialGas = st.msg.Gas()
st.state.SubBalance(st.msg.From(), mgval)
return nil
}
这里实际上是preCheck中的内容,后面计算交易的具体gas后,还要调整gas的值:
func (st *StateTransition) useGas(amount uint64) error {
if st.gas < amount {
return vm.ErrOutOfGas
}
st.gas -= amount
return nil
}
最后,如果实际使用的gas超过了gasLimit,即initialGas,就返回错误;如果顺利执行,则将剩余的gas返回原来的账户,同时可能还有一笔奖励给原账户(st.gas + refund)。最后,将st.gas即交易执行后的gas余额返回给交易执行环境(st.gp)。
func (st *StateTransition) refundGas() {
// Apply refund counter, capped to half of the used gas.
refund := st.gasUsed() / 2
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
st.gas += refund
// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
st.state.AddBalance(st.msg.From(), remaining)
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gas)
}
执行过程中如果是合约创建交易,则调用evm.Create();如果是普通交易或调用智能合约,则调用evm.Call()方法。
if contractCreation {
// 如果是合约创建建议,调用evm.Create
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
// 如果是交易或合约调用,则先设置交易发送方的nonce值+1
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
// 调用evm.Call执行交易
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)