以太坊 EVM内交易执行分析(二)

接着上次的分析,分析一下run方法是如何执行智能合约的。至于以太币的交易,在上一篇中,已经由分析的那两个函数完成了;

     合约的运行是从run开始的,go-ethereum/core/vm/evm.go 。可以分为两部分一部分是预编译,另外一部分是解释器。

    预编译合约

// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
}
}
return evm.interpreter.Run(contract, input)
}
     在以太坊中,go-ethereum/core/vm/contracts.go,事先编译好了一组合约,具体如下:

var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256Add{},
common.BytesToAddress([]byte{7}): &bn256ScalarMul{},
common.BytesToAddress([]byte{8}): &bn256Pairing{},
}
    如果要执行的contract恰好属于预编译的合约集合,那么它就可以直接运行;由于预编译的合约内容是固化在以太坊中的,所以我们输入参数即可(input),不需要输入合约内容(code)。

type PrecompiledContract interface {
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
}
    每个预编译的合约都有两个方法,第一个是计算需要花费gas,第二个是合约的具体实现。如果是基于evm开发的公链,我们完全可以自己开发出更多的预编译合约,这样运行速度会增加很多。

解释器

    不属于预编译的contract,则由Interpreter解释执行。 这里 我们需要看一下上一篇文章中的EVM框架图。

    EVMInterpreter中存在一个config结构体,其中包含一个JumpTable数组,这个数组包含了256个operation对象;每个operation对象是一个指令,指令由四个函数组成——execute、gascost、validatestack、memory size;而operation执行时,会用到stack、memory,statedb。

    execute:指令执行函数

    gascost:gas计算

    validatestack:栈深度检测

    memorysize:内存消耗

    contract则是智能合约对象,其中code则是合约代码,不过是已经转化为指令的代码,

 

     从上图我可以看到,解释器的run()函数执行过程很简单;就是contract的code中的指令,依次通过jumptable找到对应operation;然后由opertation中四个函数实现指令的执行。

           

      这是run()函数的主流程;由栈(stack)负责保存操作数,所有的操作都是运行在栈上的;内存(memory)数据结构是一个byte数组,用于一些内存操作(MLOAD,MSTORE,MSTORE8)及合约调用的参数拷贝(CALL,CALLCODE)。

     每个节点都有一个EVM,为了严重交易,每一笔交易都会在每个节点的EVM都会运行一遍,很明显这样会造成计算资源的浪费。而从安全性的方面考虑,在一个区块链系统中,一但系统中节点的状态出现不同的状态,各个节点的EVM运行智能合约时,会出现错误,导致系统无法到达一致,达成共识;这就要求整个系统必须再次达成一致性。

原文地址:https://www.cnblogs.com/405845829qq/p/10071627.html