一、合约的创建和赋值:

1. 合约

合约(Contract)是 EVM 用来执行(虚拟机)指令的结构体。

2. 合约的结构

Contract 的结构定义于:core/vm/contract.go 中,在这些成员变量里,

  • caller 是转帐转出方地址(账户),
  • self 是转入方地址,不过它们的类型都用接口 ContractRef 来表示;
  • Code 是指令数组,其中每一个 byte 都对应于一个预定义的虚拟机指令;
  • CodeHash 是 Code 的 RLP 哈希值;
  • Input 是数据数组,是指令所操作的数据集合;
  • Args 是参数。

3. self变量

​ 有意思的是 self 这个变量,为什么转入方地址要被命名成 self 呢?

Contract 实现了ContractRef 接口,返回的恰恰就是这个 self 地址。

func (c *Contract) Address() common.Address {return c.self.Address()}

​ 所以当 Contract 对象作为一个 ContractRef 接口出现时,它返回的地址就是它的 self地址。

那什么时候 Contract 会被类型转换成 ContractRef 呢?

当 Contract A 调用另一个Contract B 时,A 就会作为 B 的 caller 成员变量出现。

Contract 可以调用 Contract,这就为系统在业务上的潜在扩展,提供了空间。

创建一个 Contract 对象时,重点关注对 self 的初始化,以及对 Code, CodeAddr 和Input 的赋值。

另外,StateDB 提供

  • 方法 SetCode(),可以将指令数组 Code 存储在某个 stateObject 对象中;
  • 方法 GetCode(),可以从某个 stateObject 对象中读取已有的指令数组 Code。
func (self *StateDB) SetCode(addr common.Address, code []byte)
func (self *StateDB) GetCode(addr common.Address, code []byte)

4. stateObject

stateObject (core/state/state_object.go)是 Ethereum 里用来管理一个账户所有信息修改的结构体,它以一个 Address 类型变量为唯一标示符。

StateDB 在内部用一个巨大的map 结构来管理这些 stateObject 对象。

所有账户信息-包括 Ether 余额,指令数组 Code,该账户发起合约次数 nonce 等-它们发生的所有变化,会首先缓存到 StateDB 里的某个stateObject 里,然后在合适的时候,被 StateDB 一起提交到底层数据库。

5. 创建并执行 Contract

​ EVM(core/vm/evm.go)中 目前有五个函数可以创建并执行 Contract,按照作用和调用方式,可以分成两类:

  • ​ Create(), Call(): 二者均在 StateProcessor 的 ApplyTransaction()被调用以执行单个交易,并且都有调用转帐函数完成转帐。
  • ​ CallCode(), DelegateCall(), StaticCall():三者由于分别对应于不同的虚拟机指令(1 byte)操作,不会用以执行单个交易,也都不能处理转帐。

考虑到与执行交易的相关性,这里着重探讨 Create()和 Call()。

call()

先来看 Call(),它用来处理(转帐)转入方地址不为空的情况:

Call()函数的逻辑可以简单分为以上 6 步。

  • 步骤(3)调用了转帐函数 Transfer(),转入账户 caller, 转出账户 addr;
  • 步骤(4)创建一个 Contract 对象,并初始化其成员变量 caller, self(addr), value 和 gas;
  • 步骤(5)赋值 Contract 对象的 Code, CodeHash, CodeAddr 成员变量;
  • 步骤(6) 调用 run()函数执行该合约的指令,最后 Call()函数返回。

相关代码可见:

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error)
{if evm.vmConfig.NoRecursion && evm.depth > 0 {//如果设置了“禁用 call”,并且depth 正确,直接返回return nil, gas, nil}// Fail if we're trying to execute above the call depth limitif evm.depth > int(params.CallCreateDepth) {//如果 call 的栈深度超过了预设值, 报错return nil, gas, ErrDepth}// Fail if we're trying to transfer more than the available balanceif !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {//检查发出账户是否有足够的钱(实际实现的函数定义在     core/evm.go/CanTransfer()中)但目前还不知道是怎么调用的return nil, gas, ErrInsufficientBalance}var (to = AccountRef(addr)snapshot  = evm.StateDB.Snapshot())if !evm.StateDB.Exist(addr) {//建立账户precompiles := PrecompiledContractsHomesteadif evm.ChainConfig().IsByzantium(evm.BlockNumber) { precompiles = PrecompiledContractsByzantium}if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {return nil, gas, nil}evm.StateDB.CreateAccount(addr)}evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)//转移// initialise a new contract and set the code that is to be used by the// E The contract is a scoped environment for this execution context// only.contract := NewContract(caller, to, value, gas)//建立合约contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr),evm.StateDB.GetCode(addr)ret, err = run(evm, snapshot, contract, input)// When an error was returned by the EVM or when setting the creation code// above we revert to the snapshot and consume any gas remaining. Additionally// when we're in homestead this also counts for code storage gas errors. if err != nil {evm.StateDB.RevertToSnapshot(snapshot) if err != errExecutionReverted {contract.UseGas(contract.Gas)}return ret, contract.Gas, err}

因为此时(转帐)转入地址不为空,所以直接将入参 addr 初始化 Contract 对象的 self 地址,并可从 StateDB 中(其实是以 addr 标识的账户 stateObject 对象)读取出相关的 Code 和CodeHash 并赋值给 contract 的成员变量。

注意,此时转入方地址参数 addr 同时亦被赋值予 contract.CodeAddr。

create()

再来看看 EVM.Create(),它用来处理(转帐)转入方地址为空的情况。

与 Call()相比,Create()因为没有 Address 类型的入参 addr,其流程有几处明显不同:

  • ​ 步骤(3)中创建一个新地址 contractAddr,作为(转帐)转入方地址,亦作为Contract 的 self 地址;
  • ​ 步骤(6)由于 contracrAddr 刚刚新建,db 中尚无与该地址相关的 Code 信息, 所以会将类型为[]byte 的入参 code,赋值予 Contract 对象的 Code 成员;
  • ​ 步骤(8)将本次执行合约的返回结果,作为 contractAddr 所对应账户(stateObject 对象)的 Code 储存起来,以备下次调用。

​ 还有一点隐藏的比较深,Call()有一个入参 input 类型为[]byte,而 Create()有一个入参code 类型同样为[]byte,没有入参 input,它们之间有无关系?

其实,它们来源都是Transaction 对象 tx 的成员变量 Payload!调用 EVM.Create()或 Call()的入口在StateTransition.TransitionDb()中,

  • 当 tx.Recipent 为空时,tx.data.Payload 被当作所创建Contract 的 Code;
  • 当 tx.Recipient 不为空时,tx.data.Payload 被当作 Contract 的 Input。

二、预编译合约

EVM 中执行合约(指令)的函数是 run(),在 core/vm/evm.go 中其实现代码如下:

  • 可见如果待执行的 Contract 对象恰好属于一组预编译的合约集合-此时以指令地址CodeAddr 为匹配项-那么它可以直接运行;
  • 没有经过预编译的 Contract,才会由Interpreter 解释执行。这里的”预编译”,可理解为不需要编译(解释)指令(Code)。预编译的合约,其逻辑全部固定且已知,所以执行中不再需要 Code,仅需 Input 即可。

在代码实现中,预编译合约只需实现两个方法 Required()和 Run()即可,这两方法仅需一个入参 input。

/core/vm/contracts.gotype PrecompiledContract interface { RequiredGas(input []byte) uint64 Run(input []byte) ([]byte, error)
}func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contrat) (ret []byte, err error) {gas := p.RequiredGas(input) if contract.UseGas(gas) {return p.Run(input)}return nil, ErrOutOfGas}

目前,Ethereuem 代码中已经加入了多个预编译合约,功能覆盖了包括椭圆曲线密钥恢复,SHA-3(256bits)哈希算法,RIPEMD-160 加密算法等等。

相信基于自身业务的需求,二次开发者完全可以加入自己的预编译合约,大大加快合约的执行速度。

三、解释器执行合约的指令

解释器 Interpreter 用来执行(非预编译的)合约指令。

它的结构体 UML 关系图如下所示:

​ Interpreter 结构体通过一个 Config 类型的成员变量,间接持有一个包括 256 个operation 对象在内的数组 JumpTable。

operation 是做什么的呢?

每个 operation 对象正对 应 一 个 已 定 义 的 虚 拟 机 指 令 , 它 所 含 有 的 四 个 函 数 变 量 execute, gasCost, validateStack, memorySize 提供了这个虚拟机指令所代表的所有操作。

每个指令长度1byte,Contract 对象的成员变量 Code 类型为[]byte,就是这些虚拟机指令的任意集合,operation 对象的函数操作,主要会用到 Stack,Memory, IntPool 这几个自定义的数据结构。

这样一来,Interpreter 的 Run()函数就很好理解了,其核心流程就是逐个 byte 遍历入参 Contract 对象的 Code 变量,将其解释为一个已知的 operation,然后依次调用该operation 对象的四个函数,流程示意图如下:

operation 在操作过程中,会需要几个数据结构:

  • Stack,实现了标准容器 -栈的行为;
  • Memory,一个字节数组,可表示线性排列的任意数据;
  • intPool,提供对big.Int 数据的存储和读取。

已定义的 operation,种类很丰富,包括:

  • ​ 算术运算:ADD,MUL,SUB,DIV,SDIV,MOD,SMOD,EXP…;
  • ​ 逻辑运算:LT,GT,EQ,ISZERO,AND,XOR,OR,NOT…;
  • ​ 业务功能:SHA3,ADDRESS,BALANCE,ORIGIN,CALLER,GASPRICE,LOG1,LOG2…等等需要特别注意的是 LOGn 指令操作,它用来创建 n 个 Log 对象,这里 n 最大是 4。还记得 Log 在何时被用到么?每个交易(Transaction,tx)执行完成后,会创建一个 Receipt 对象用来记录这个交易的执行结果。Receipt 携带一个 Log 数组,用来记录 tx 操作过程中的所有变动细节,而这些 Log,正是通过合适的 LOGn 指令-即合约指令数组(Contract.Code) 中的单个 byte,在其对应的 operation 里被创建出来的。每个新创建的 Log 对象被缓存在StateDB 中的相对应的 stateObject 里,待需要时从 StateDB 中读取。

准确的来说是从链上取二进制的代码指令,这个指令就是合约编译后产生的binary,binary中包含了每个方法对应的ID及指令集,发送一笔交易会先对方法进行编码,编码时会产生方法ID,然后签名,再发送交易,交易进入EVM后根据Id查找对应的指令集,根据输入数据和指令集进行交易执行。

以太坊探究:ETH交易部分分析

https://m.8btc.com/article/265557

区块链 以太坊 合约 创建、执行 详解相关推荐

  1. 8支团队正在努力构建下一代区块链以太坊Ethereum 2.0

    2019独角兽企业重金招聘Python工程师标准>>> "我们不想在构建 Ethereum 2.0时重新造轮子." 谈到开发人员为 Ethereum 区块链进行两 ...

  2. 可编程区块链以太坊的未来

    区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>,感受一下先行者的思想. 到本文结束时,你将了解一般的区块链(特别是下一代区 ...

  3. 回看2015年是如何描述区块链以太坊的未来

    区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>,感受一下先行者的思想. 到本文结束时,你将了解一般的区块链(特别是下一代区 ...

  4. 回望Vinay2015年的文章:内容可编程的区块链——以太坊的未来

    2019独角兽企业重金招聘Python工程师标准>>> 区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>, ...

  5. 上下文可编程区块链——以太坊的未来

    区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>,感受一下先行者的思想. 到本文结束时,你将了解一般的区块链(特别是下一代区 ...

  6. 【区块链-以太坊】5 Ubuntu下truffle ganache安装及使用

    [区块链-以太坊]5 Ubuntu下truffle & ganache安装及使用 1 truffle安装 1)输入sudo npm install -g truffle 2)将truffle复 ...

  7. 区块链-以太坊学习资料汇总

    最近一段时间以来,对区块链的底层研究和基于区块链的应用开发已经越来越火热了.机缘巧合,目前我也在进行区块链方面的开发,在之后的博客中,我会和大家一起分享区块链开发中的酸甜苦辣.今天我先来对收藏区块链- ...

  8. 【区块链开发入门】(四) Truffle详解篇2

    由于本篇篇幅较长,因此转为两篇文章.Truffle详解篇篇1请见:link 目录导航页 [区块链开发入门](一) 以太坊的搭建与运行 [区块链开发入门](二) 以太坊的编程接口 [区块链开发入门](三 ...

  9. 区块链 以太坊 交易结构、执行、存储 解析 交易中为什么没有包含发送者地址这条数据

    一. 交易的结构 1. Transaction结构 交易结构定义在 core/types/transaction.go 中: type Transaction struct {//交易数据data t ...

  10. 区块链 以太坊 区块结构详解

    一.结构 区块由两部分组成,分别是 区块头(header) 区块体(body) 1. 结构图 2. 区块头(header) 区块头存储了区块的元信息,用来对区块内容进行一些标识,校验,说明等. 通用字 ...

最新文章

  1. android时间轴折线图,带时间轴的折线图
  2. python银行系统-Python实现银行账户资金交易管理系统
  3. fastreport(A)
  4. python标准库os的方法_Python中标准库OS的常用方法总结大全
  5. m3u8链接地址_FFmpeg下载m3u8视频
  6. asp.net ajax1.0基础回顾(五):调用ASMX(WebService)
  7. 有关为旧版代码创建存根的更多信息–测试技术7
  8. 怎样在全球的Azure上开Case
  9. linux第八周实验
  10. 【第115期】零基础学做游戏制作_搭环境Unity hub
  11. 蓝桥杯算法训练——调和数列问题
  12. 蓝桥杯计算机软件大赛什么时间,“蓝桥杯”全国软件设计大赛
  13. 给英语单词编故事的三个方法
  14. Windows系统资源监控工具perfmon-只能查看报告。
  15. svg html g标签id,svg是什么?svg常用的方法(附代码)
  16. 第十三届蓝桥杯大赛软件赛省赛真题
  17. vscode win10笔记本 蓝屏_遇到win10电脑蓝屏怎么办_简单解决win10蓝屏的方法
  18. 集赞神器!朋友圈集赞一键秒搞定!从此集赞随心所欲!
  19. html+js+canvas实现画板涂画功能和vue+canvas实现画板涂画功能
  20. Java程序中操作Word表格

热门文章

  1. CATransition 动画处理视图切换
  2. Linux命令之passwd
  3. Asp.net MVC 示例项目Suteki.Shop分析之---IOC(控制反转)
  4. Python 单例模式实现的五种方式
  5. ZJOI 2008 瞭望塔 三分法
  6. Kubernetes1.4即将发布
  7. 第三周的学习进度情况
  8. hdu 4599 Dice
  9. 程序化导入导出EXCEL数据,完全由模板输出
  10. java integer源码_Integer包装类源码分析