以太坊源码分析-交易执行及合约创建具体流程

1. StateProcessor的处理器会遍历块中的每一条交易,通过ApplyTransaction函数来执行交易

Process函数会返回receipts和logs以及交易所花费的gas,如果因为交易因insufficient gas而失败,那么将会返回错误

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())
	)
	// Mutate the block and state according to any hard-fork specs
	if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
		misc.ApplyDAOHardFork(statedb)
	}
	// Iterate over and process the individual transactions
	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...)
	}
	// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
	p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())

	return receipts, allLogs, *usedGas, nil
}

ApplyTransaction 创建EVM执行环境,在state database中应用该交易,接收返回的receipt以及创建bloom过滤器

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
	}
	// Create a new context to be used in the EVM environment
	context := NewEVMContext(msg, header, bc, author)
	// Create a new environment which holds all relevant information
	// about the transaction and calling mechanisms.
	vmenv := vm.NewEVM(context, statedb, config, cfg)
	// Apply the transaction to the current state (included in the env)
	_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
	if err != nil {
		return nil, 0, err
	}
	// Update the state with pending changes
	var root []byte
	if config.IsByzantium(header.Number) {
		statedb.Finalise(true)
	} else {
		root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
	}
	*usedGas += gas

	// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
	// based on the eip phase, we're passing whether the root touch-delete accounts.
	receipt := types.NewReceipt(root, failed, *usedGas)
	receipt.TxHash = tx.Hash()
	receipt.GasUsed = gas
	// if the transaction created a contract, store the creation address in the receipt.
	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})
	receipt.BlockHash = statedb.BlockHash()
	receipt.BlockNumber = header.Number
	receipt.TransactionIndex = uint(statedb.TxIndex())

	return receipt, gas, err
}

ApplyMessage会创建NewStateTransition,并调用StateTransition的TransitionDb()

func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
	return NewStateTransition(evm, msg, gp).TransitionDb()
}

在TransitionDb会判读交易是否是一个创建合约的交易,如果是则调用evm.Create,如果不是则调用evm.Call

EVM创建合约的过程

EVM的create函数需要的参数

  • ContractRef ContractRef is a reference to the contract’s backing object
  • codeAndHash

    type codeAndHash struct {

    code []byte

    hash common.Hash

    }

  • gas
  • value 部分在创建合约时希望往合约地址转账的值
  • address 合约地址 记得再看看合约地址是怎么生成的

主要流程如下

首先会根据调用者的地址以及该地址的nonce值计算出合约的地址

func CreateAddress(b common.Address, nonce uint64) common.Address {
	data, _ := rlp.EncodeToBytes([]interface{}{b, nonce})
	return common.BytesToAddress(Keccak256(data)[12:])
}

然后调用create函数来创建合约

  1. 检查合约执行的depth是否已经超过CallCreateDepth(1024)的深度,超过则返回空;
  2. 检查发送者的余额是否足够转账,若不够,返回错误;
  3. 将发送者的nonce值加1;
  4. 检查当前合约地址之前是不存在的;
  5. 快照当前的stateDB,如果发生错误,那么可以根据快照退回;
  6. 根据合约地址,在state里创建账户(即合约地址),并设置该地址的nonce值为1;
  7. 转账给该地址(当然有些创建合约的交易value值为0);
  8. 创建一个新的合约来执行当前的操作(该合约只在当前上下文起作用);
  9. 为合约地址绑定code,具体绑定code的代码下面解释;
  10. 在EVM中执行,返回结果
  11. 检查code大小是否超过系统设定值(24576,byte为单位);
  12. 如果没有错误返回,那么计算存储code需要的gas,并减去花费的gas;
  13. 如果出现任何错误,那么将根据快照返回到之前的世界状态(当然gas仍然被花费,初始错误是errExecutionReverted);

存储code的gas是如何计算的:

createDataGas := uint64(合约代码byte长度) * params.CreateDataGas
CreateDataGas=200

具体代码如下

func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
	// Depth check execution. Fail if we're trying to execute above the
	// limit.
	if evm.depth > int(params.CallCreateDepth) {
		return nil, common.Address{}, gas, ErrDepth
	}
	if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
		return nil, common.Address{}, gas, ErrInsufficientBalance
	}

	//将调用者的nonce值加1
	nonce := evm.StateDB.GetNonce(caller.Address())
	evm.StateDB.SetNonce(caller.Address(), nonce+1)

	// Ensure there's no existing contract already at the designated address
	contractHash := evm.StateDB.GetCodeHash(address)
	if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
		return nil, common.Address{}, 0, ErrContractAddressCollision
	}
	// 创建快照,如何后面交易失败,可以根据该快照来恢复状态
	snapshot := evm.StateDB.Snapshot()
	evm.StateDB.CreateAccount(address)
	if evm.chainRules.IsEIP158 {
		evm.StateDB.SetNonce(address, 1)
	}
	evm.Transfer(evm.StateDB, caller.Address(), address, value)

	//创建一个临时合约来执行内容,该合约只在当前上下文环境中起作用
	contract := NewContract(caller, AccountRef(address), value, gas)
	contract.SetCodeOptionalHash(&address, codeAndHash)

	if evm.vmConfig.NoRecursion && evm.depth > 0 {
		return nil, address, gas, nil
	}

	if evm.vmConfig.Debug && evm.depth == 0 {
		evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value)
	}
	start := time.Now()

	//run里面才是真正地在evm中执行这次合约创建
	ret, err := run(evm, contract, nil, false)

	// check whether the max code size has been exceeded
	maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize
	//计算用于存储合约的花费,每字节200gas
	if err == nil && !maxCodeSizeExceeded {
		createDataGas := uint64(len(ret)) * params.CreateDataGas
		if contract.UseGas(createDataGas) {
			evm.StateDB.SetCode(address, ret)
		} else {
			err = ErrCodeStoreOutOfGas
		}
	}

	//出现任何错误都将根据snapshot来恢复之前的状态,但是gas费仍要花费(除了errExecutionReverted)
	if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
		evm.StateDB.RevertToSnapshot(snapshot)
		if err != errExecutionReverted {
			contract.UseGas(contract.Gas)
		}
	}
	// Assign err if contract code size exceeds the max while the err is still empty.
	if maxCodeSizeExceeded && err == nil {
		err = errMaxCodeSizeExceeded
	}
	if evm.vmConfig.Debug && evm.depth == 0 {
		evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
	}
	return ret, address, contract.Gas, err

}

执行合约调用
core/vm/interpreter.go fun(int *EVMInterpreter) Run(…) 133行
获取操作码,再根据操作码从JumpTable获得执行指令,如何操作不合法,则返回错误

op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op]
if !operation.valid {
	return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
}

具体GetOp的操作如下

// GetOp returns the n'th element in the contract's byte array
func (c *Contract) GetOp(n uint64) OpCode {
	return OpCode(c.GetByte(n))
}

// GetByte returns the n'th byte in the contract's byte array
func (c *Contract) GetByte(n uint64) byte {
	if n < uint64(len(c.Code)) {
		return c.Code[n]
	}

	return 0
}

operation的定义如下:

type operation struct {
	execute     executionFunc  //该操作具体执行的函数
	constantGas uint64         //该操作的constantGas花费
	dynamicGas  gasFunc			//该操作计算动态dynamicGas函数
	// minStack tells how many stack items are required
	minStack int
	// maxStack specifies the max length the stack can have for this operation
	// to not overflow the stack.
	maxStack int

	// memorySize returns the memory size required for the operation
	memorySize memorySizeFunc

	halts   bool // indicates whether the operation should halt further execution
	jumps   bool // indicates whether the program counter should not increment
	writes  bool // determines whether this a state modifying operation
	valid   bool // indication whether the retrieved operation is valid and known
	reverts bool // determines whether the operation reverts state (implicitly halts)
	returns bool // determines whether the operations sets the return data content
}

计算新的内存并且扩展内存使得适用于这个operation

		var memorySize uint64
		if operation.memorySize != nil {
			memSize, overflow := operation.memorySize(stack)
			if overflow {
				return nil, errGasUintOverflow
			}
			//内存扩展为以32byte为一个字,如何内存溢出则返回错误(gas的计算也会以扩展后来计算,即以word的倍数来计算
			if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
				return nil, errGasUintOverflow
			}
		}

每个operation都会通过execute来执行

res, err = operation.execute(&pc, in, contract, mem, stack)

各个函数的execute可见core/vm/instructions.go的实现

关于stack的设计 core/vm/stack.go 主要就是push,pop,pek,dup,swap等一般的栈的操作
memory的设计 core/vm/memory.go

你可能感兴趣的:(区块链)