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