代码位置如下:
参考link
以太坊中有一个很重要的用途是智能合约,而其中evm模块是实现了执行智能合约的虚拟机。evm可以逐条解析执行智能合约的指令。
evm中的核心对象是EVM,代表一个以太坊虚拟机。其内部主要依赖:解释器Interoreter(循环解释执行给定的合约指令,直接遇到退出指令)、配置和状态数据库StateDB(用来提供数据的永久存储和查询)
1、创建EVM
前提是在core/state_processor.go中使用ApplyTransaction函数来处理交易。在这个函数里面,需要创建一个EVM来执行交易中的数据。
使用NewEVM函数来实现创建。
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM
主要包括:
- ctx: 提供访问当前区块链数据和挖矿环境的函数和数据
- statedb: 以太坊状态数据库对象
- chainConfig: 当前节点的区块链配置信息
- vmConfig: 虚拟机配置信息
- 解释器
而解释器的创建是通过函数NewEVMInterpreter
实现的。该函数首先要根据以太坊的版本不同,为jumpTable字段进行不同的填充。
2、创建合约
使用EVM.Create函数来创建合约。通过当前合约的创建者地址和其账户中的 Nonce 值,计算出来一个地址值,作为合约的地址。然后将这个地址和其它参数传给 EVM.create
方法。同一份合约代码,每次创建合约时得到的合约地址都是不一样的,因为合约是通过发送交易创建,而每发送一次交易 Nonce 值都会改变。
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
}
而EVM.create函数才是主要内容。
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error)
在函数的第一部分,需要进行判断,包括合约的递归调用次数depth是否在限制之内、以及创建者是否有足够的以太币。每次创建完成就需要将创建者的nonce值加一,这样每一次创建合约的地址才会不一样。
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, ErrDepth
}
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
nonce := evm.StateDB.GetNonce(caller.Address())
if nonce+1 < nonce {
return nil, common.Address{}, gas, ErrNonceUintOverflow
}
evm.StateDB.SetNonce(caller.Address(), nonce+1)
在完成一系列的判断之后,需要创建合约账户,将交易中规定的金额转到账户中,如果账户已经存在就返回错误。
if evm.chainRules.IsBerlin {
evm.StateDB.AddAddressToAccessList(address)
}
// 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 != types.EmptyCodeHash) {
return nil, common.Address{}, 0, ErrContractAddressCollision
}
// Create a new account on the state
snapshot := evm.StateDB.Snapshot()
evm.StateDB.CreateAccount(address)
if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1)
}
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
在第三部分中需要创建一个contract对象,其中包括合约在执行过程中的必要信息,比如合约的创建者、合约的地址等。具体函数在contract.go文件里面。在合约创建之后,需要调用run函数来运行合约。如果合约运行成功,并且代码长度没有超过限制MaxCodeSize
,就会将合约代码存储到stateDB中的合约账户中并且消耗一定的gas。因为在编译器阶段,会向源代码中插入其它代码,所以存储的是运行后的返回码。
//判断长度
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
err = ErrMaxCodeSizeExceeded
}
// Reject code starting with 0xEF if EIP-3541 is enabled.
if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
err = ErrInvalidCode
}
//没有错误就写入数据库
if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(address, ret)
} else {
err = ErrCodeStoreOutOfGas
}
}
2.1执行合约创建
主要实现在interpreter中的Run函数。首先迭代次数depth的增加和减少是在解释器中实现的,使用到了defer延迟函数。
之后我们判断readOnly和in.readOnly
,只有当readOnly为真(即我们需要设置为只读)并且in.readOnly为假(即当前不处于只读状态)时,才会执行大括号内的代码。
if readOnly && !in.readOnly {
in.readOnly = true
defer func() { in.readOnly = false }()
}
以太坊虚拟机的工作流程:
由solidity语言编写的智能合约,通过编译器编译成bytecode,之后发到以太坊上,以太坊底层通过evm模块支持合约的执行和调用,调用时根据合约获取代码,即合约的字节码,生成环境后载入到 EVM 执行。
所以需要opcode操作码和mem存储以及为了应对高并发情况下的栈资源问题,代码中创建了stack来保存一些被创造但未使用的栈空间。
主要函数内容以及解释如下:
for {
//如果处于调试模式,那么会记录执行前的状态信息,包括程序计数器(pc)、剩余的gas和操作的成本。
if debug {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
//从合约中获取当前程序计数器指向的操作
op = contract.GetOp(pc)
//从操作表中获取对应的操作
operation := in.table[op]
//获取操作的固定gas成本
cost = operation.constantGas // For tracing
// Validate stack 对stack进行验证
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
//如果剩余的gas不足以执行这个操作,那么返回错误
if !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
//如果操作有动态的gas成本,那么计算新的内存大小并扩展内存以适应操作
if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
var dynamicCost uint64
//计算动态的gas成本
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
//如果计算过程中出现错误或者剩余的gas不足以支付动态的gas成本,那么返回错误
if err != nil || !contract.UseGas(dynamicCost) {
return nil, ErrOutOfGas
}
//如果处于调试模式,那么记录执行后的状态信息
if debug {
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}
if memorySize > 0 {
mem.Resize(memorySize)
}
} else if debug {
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}
// 执行操作
res, err = operation.execute(&pc, in, callContext)
if err != nil {
break
}
//将程序计数器加一,准备执行下一个操作
pc++
}