go-ethereum中Processor源码学习
这是对交易进行处理的地方
NewStateProcessor
先从BlockChain的构造方法中开始看,在创建了验证器之后,就创建了Processor
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
return &StateProcessor{
config: config,
bc: bc,
engine: engine,
}
}
可见直接创建了一个StateProcessor并返回,StateProcessor实现了Processor接口,这个接口只有一个方法:Process
Process
在插入区块时,对每个要插入的区块都执行了该方法
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
header = block.Header()
allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit())
)
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(statedb)
}
for i, tx := range block.Transactions() {
statedb.Prepare(tx.Hash(), block.Hash(), i)
receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
if err != nil {
return nil, nil, 0, err
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), receipts)
return receipts, allLogs, *usedGas, nil
}
首先初始化了一系列的变量,其中比较特殊的是gp,它是一个GasPool对象,用于追踪交易执行过程中gas的使用,实际上是一个uint64类型,封装了一些加减方法,初始值时区块的最大gas量。
接着对DAO分叉进行了特殊处理,然后遍历区块中的每个交易,对于每个交易,先执行statedb的Prepare方法,表示准备处理交易,主要是存入交易hash和区块hash以及交易编号。接着调用ApplyTransaction处理交易,最后返回收据。并将每一个交易处理完之后的数据和日志收集起来在最后返回。处理完所有交易后调用engine的Finalize方法发放奖励。
ApplyTransaction
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) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, 0, err
}
context := NewEVMContext(msg, header, bc, author)
vmenv := vm.NewEVM(context, statedb, config, cfg)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, 0, err
}
var root []byte
if config.IsByzantium(header.Number) {
statedb.Finalise(true)
} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
*usedGas += gas
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())
}
receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
return receipt, gas, err
}
第一行调用了transaction的AsMessage方法。这是将一个交易以message的形式表示。接着构建了EVM上下文环境和EVM。然后调用了ApplyMessage方法,这是真正执行交易的地方,详见后文,执行完成后返回了gas的使用数量以及成功与否。如果没有错误,表示交易执行完毕。接着更新了状态树,然后创建了一个收据,存储着状态树中间状态的根值,使用的gas,交易hash,另外如果是创建合约的交易还存储合约地址等信息。
ApplyMessage
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
}
这里先利用NewStateTransition构造了StateTransition对象
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{
gp: gp,
evm: evm,
msg: msg,
gasPrice: msg.GasPrice(),
value: msg.Value(),
data: msg.Data(),
state: evm.StateDB,
}
}
然后调用了TransitionDb方法
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
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, err := IntrinsicGas(st.data, contractCreation, homestead)
if err != nil {
return nil, 0, false, err
}
if err = st.useGas(gas); err != nil {
return nil, 0, false, err
}
var (
evm = st.evm
vmerr error
)
if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
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)
if vmerr == vm.ErrInsufficientBalance {
return nil, 0, false, vmerr
}
}
st.refundGas()
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
}
第一步进行的预检查:
func (st *StateTransition) preCheck() error {
if st.msg.CheckNonce() {
nonce := st.state.GetNonce(st.msg.From())
if nonce < st.msg.Nonce() {
return ErrNonceTooHigh
} else if nonce > st.msg.Nonce() {
return ErrNonceTooLow
}
}
return st.buyGas()
}
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
}
这里主要是检查nonce。然后利用buyGas购买了gas,这里首先计算总共的金额,然后判断账户金额是否足够,然后在gp中记录所用的gas,并更新StateTransition的gas和initialGas字段,最后从账户中减去对应的金额。这里的gas是指gasLimit,也就是需要保证账户中至少有gasLimit*gasPrice余额,才能继续交易。之后在StateTransition的gas字段记录最大gas量
接下来记录了交易的发送者,并根据接受者是否为空判断是否是合约创建交易。接着计算需要消耗的gas
func IntrinsicGas(data []byte, contractCreation, homestead bool) (uint64, error) {
if contractCreation && homestead {
gas = params.TxGasContractCreation
} else {
gas = params.TxGas
}
if len(data) > 0 {
// Zero and non-zero bytes are priced differently
var nz uint64
for _, byt := range data {
if byt != 0 {
nz++
}
}
if (math.MaxUint64-gas)/params.TxDataNonZeroGas < nz {
return 0, vm.ErrOutOfGas
}
gas += nz * params.TxDataNonZeroGas
z := uint64(len(data)) - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, vm.ErrOutOfGas
}
gas += z * params.TxDataZeroGas
}
return gas, nil
}
接着,如果是创建合约的交易并且是家园版本,基础费用为53000gas,否则基础费用是21000。往下再计算交易中附带数据所消耗的gas,遍历所有字节数组,对于非0的数据,每字节支付68gas,为0的数据每字节支付4gas。最后计算出总gas。关于这个固有费用的计算是按照黄皮书6.2节的说明实现的。
回到TransitionDb,计算完需要的gas后,使用useGas方法减去对应的gas数,这里主要是用StateTransition的gas字段减去刚才的固有gas量。
接着,如果是创建合约的话调用evm的create方法创建合约。否则的话就是一般交易,这时将发送者的nonce递增一,然后调用call方法对账户转账或调用合约。接着,如果有错误的而且是金额不足的错误则返回错误。否则调用refundGas退回gas
func (st *StateTransition) refundGas() {
refund := st.gasUsed() / 2
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
st.gas += refund
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
st.state.AddBalance(st.msg.From(), remaining)
st.gp.AddGas(st.gas)
}
func (st *StateTransition) gasUsed() uint64 {
return st.initialGas - st.gas
}
首先计算已用的gas,gasUsed中initialGas是初始的gas,也就是交易允许的最大gas数,而gas是剩余的gas,在交易执行中会一直变化。二者相减就是已用的gas。先设refund的值为已用gas的一般,然后将其和statedb的refund相比,若比他大,则以statedb的refund为标准。然后将gas加上refund的值,这样在计算出实际需要退换的余额。注意这里的逻辑是花费的金额要小于实际金额。
回到TransitionDb最后,退款完毕后,将所用的gas费用发送给Coinbase,也就是矿工,当做奖励。这样一个交易执行完毕。
题图来自unsplash:https://unsplash.com/photos/3P3NHLZGCp8