详解以太坊的工作原理(五)

标签:
区块链以太坊 |
分类: IT技术类 |
日志
以太坊允许日志可以跟踪各种交易和信息。一个合约可以通过定义“事件”来显示的生成日志。
一个日志的实体包含:
- 记录器的账户地址
- 代表本次交易执行的各种事件的一系列主题以及与这些事件相关的任何数据
日志被保存在bloom过滤器
交易收据
自于被包含在交易收据中的日志信息存储在头中。就像你在商店买东西时收到的收据一样,以太坊为每笔交易都产生一个收据。像你期望的那样,每个收据包含关于交易的特定信息。这些收据包含着:
- 区块序号
- 区块Hash
- 交易Hash
- 当前交易使用了的gas
- 在当前交易执行完之后当前块使用的累计gas
- 执行当前交易时创建的日志
- 等等
区块难度
区块的难度是被用来在验证区块时加强一致性。创世纪区块的难度是131,072,有一个特殊的公式用来计算之后的每个块的难度。如果某个区块比前一个区块验证的更快,以太坊协议就会增加区块的难度。
区块的难度影响nonce,它是在挖矿时必须要使用proof-of-work算法来计算的一个hash值。
区块难度和nonce之间的关系用数学形式表达就是:
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607237530.png
Hd代表的是难度。
找到符合难度阈值的nonce唯一方法就是使用proof-of-work算法来列举所有的可能性。找到解决方案预期时间与难度成正比—难度越高,找到nonce就越困难,因此验证一个区块也就越难,这又相应地增加了验证新块所需的时间。所以,通过调整区块难度,协议可以调整验证区块所需的时间。
另一方面,如果验证时间变的越来越慢,协议就会降低难度。这样的话,验证时间自我调节以保持恒定的速率—平均每15s一个块。
交易执行
我们已经到了以太坊协议最复杂的部分:交易的执行。假设你发送了一笔交易给以太坊网络处理,将以太坊状态转换成包含你的交易这个过程到底发生了什么?
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607246652.png
首先,为了可以被执行所有的交易必须都要符合最基础的一系列要求,包括:
- 交易必须是正确格式化的RLP。”RLP”代表Recursive Length Prefix,它是一种数据格式,用来编码二进制数据嵌套数组。以太坊就是使用RLP格式序列化对象。
- 有效的交易签名。
- 有效的交易序号。回忆一下账户中的nonce就是从此账户发送出去交易的计数。如果有效,那么交易序号一定等于发送账户中的nonce。
- 交易的gas limit 一定要等于或者大于交易使用的intrinsic gas,intrinsic gas包括:
——-1.执行交易预订费用为21,000gas
——-2.随交易发送的数据的gas费用(每字节数据或代码为0的费用为4gas,每个非零字节的数据或代码费用为68gas)
——-3.如果交易是合约创建交易,还需要额外的32,000gas
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607274072.png - 发送账户余额必须有足够的Ether来支付”前期”gas费用。前期gas费用的计算比较简单:首先,交易的gas
limit乘以交易的gas价格得到最大的gas费用。然后,这个最大gas费用被加到从发送方传送给接收方的总值。
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607309558.png
如何交易符合上面所说的所有要求,那么我们进行下面步骤。
第一步,我们从发送者的余额中扣除执行的前期费用,并为当前交易将发送者账户中的nonce增加1。此时,我们可以计算剩余的gas,将交易的总gas减去使用的intrinsic
gas。
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607334405.png
第二步,开始执行交易。在交易执行的整个过程中,以太坊保持跟踪“子状态”。子状态是记录在交易中生成的信息的一种方式,当交易完成时会立即需要这些信息。具体来说,它包含:
- 自毁集:在交易完成之后会被丢弃的账户集(如果存在的话)
- 日志系列:虚拟机的代码执行的归档和可检索的检查点
- 退款余额:交易完成之后需要退还给发送账户的总额。回忆一下我们之前提到的以太坊中的存储需要付费,发送者要是清理了内存就会有退款。以太坊使用退款计数进行跟踪退款余额。退款计数从0开始并且每当合约删除了一些存储中的东西都会进行增加。
第三步,交易所需的各种计算开始被处理。
当交易所需的步骤全部处理完成,并假设没有无效状态,通过确定退还给发送者的未使用的gas量,最终的状态也被确定。除了未使用的gas,发送者还会得到上面所说的“退款余额”中退还的一些津贴。
一旦发送者得到退款之后:
- gas的Ether就会矿工
- 交易使用的gas会被添加到区块的gas计数中(计数一直记录当前区块中所有交易使用的gas总量,这对于验证区块时是非常有用的)
- 所有在自毁集中的账户(如果存在的话)都会被删除
最后,我们就有了一个新的状态以及交易创建的一系列日志。
现在我们已经介绍了交易执行的基本知识,让我们再看看合约创建交易和消息通信的一些区别。
合约创建(Contract creation)
回忆一下在以太坊中,有两种账户类型:合约账户和外部拥有账户。当我们说一个交易是“合约创建”,是指交易的目的是创建一个新的合约账户。
为了创建一个新的合约账户,我们使用一个特殊的公式来声明新账户的地址。然后我们使用下面的方法来初始化一个账户:
- 设置nonce为0
- 如果发送者通过交易发送了一定量的Ether作为value,那么设置账户的余额为value
- 将存储设置为0
- 设置合约的codeHash为一个空字符串的Hash值
一旦我们完成了账户的初始化,使用交易发送过来的init code(查看”交易和信息”章节来复习一下init code),实际上就创造了一个账户。init code的执行过程是各种各样的。取决于合约的构造器,可能是更新账户的存储,也可能是创建另一个合约账户,或者发起另一个消息通信等等。
当初始化合约的代码被执行之后,会使用gas。交易不允许使用的gas超过剩余gas。如果它使用的gas超过剩余gas,那么就会发生gas不足异(OOG)常并退出。如果一个交易由于gas不足异常而退出,那么状态会立刻恢复到交易前的一个点。发送者也不会获得在gas用完之前所花费的gas。
不过,如果发送者随着交易发送了Ether,即使合约创建失败Ether也会被退回来。
如果初始化代码成功的执行完成,最后的合约创建的花费会被支付。这些是存储成本,与创建的合约代码大小成正比(再一次,没有免费的午餐)。如果没有足够的剩余gas来支付最后的花费,那么交易就会再次宣布gas不足异常并中断退出。
如果所有的都正常进行没有任何异常出现,那么任何剩余的未使用gas都会被退回给原始的交易发送者,现在改变的状态才被允许永久保存。
消息通信(Message calls)
消息通信的执行与合约创建比较类似,只不过有一点点区别。
由于没有新账户被创建,所以消息通信的执行不包含任何的init code。不过,它可以包含输入数据,如果交易发送者提供了此数据的话。一旦执行,消息通信同样会有一个额外的组件来包含输出数据,如果后续执行需要此数据的话就组件就会被使用。
就像合约创建一样,如果消息通信执行退出是因为gas不足或交易无效(例如栈溢出,无效跳转目的地或无效指令),那么已使用的gas是不会被退回给原始触发者的。相反,所有剩余的未使用gas也会被消耗掉,并且状态会被立刻重置为余额转移之前的那个点。
没有任何方法停止或恢复交易的执行而不让系统消耗你提供的所有gas,直到最新的以太坊更新。例如,假设你编写了一个合约,当调用者没有授权来执行这些交易的时候抛出一个错误。在以太坊的前一个版本中,剩余的gas也会被消耗掉,并且没有任何gas退回给发送者。但是拜占庭更新包括了一个新的“恢复”代码,允许合约停止执行并且恢复状态改变而不消耗剩余的gas,此代码还拥有返回交易失败原因的能力。如果一个交易是由于恢复而退出,那么未使用的gas就会被返回给发送者。
执行模式
到目前为止,我们了解了从开始到结束执行的交易必须经历的一系列的步骤。现在,我们来看看交易究竟是如何在虚拟机(VM)中执行的。
协议实际操作交易处理的部分是以太坊自己的虚拟机,称之为以太坊虚拟机(EVM)。
像之前定义的那样,EVM是图灵完备虚拟机器。EVM存在而典型图灵完备机器不存在的唯一限制就是EVM本质上是被gas束缚。因此,可以完成的计算总量本质上是被提供的gas总量限制的。
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607369960.png
此外,EVM具有基于堆栈的架构。堆栈机器
EVM中每个堆栈项的大小为256位,堆栈有一个最大的大小,为1024位。
EVM有内存,项目按照可寻址字节数组来存储。内存是易失性的,也就是数据是不持久的。
EVM也有一个存储器。不像内存,存储器是非易失性的,并作为系统状态的一部分进行维护。EVM分开保存程序代码,在虚拟ROM
http://7fvhfe.com1.z0.glb.clouddn.com/wp-content/uploads/2017/10/201710110607384909.png
EVM同样有属于它自己的语言:“EVM字节码”,当一个程序员比如你或我写一个在以太坊上运行的智能合约时,我们通常都是用高级语言例如Solidity来编写代码。然后我们可以将它编译成EVM可以理解的EVM字节码。
好了,现在来说执行。
在执行特定的计算之前,处理器会确定下面所说的信息是有效和是否可获取:
- 系统状态
- 用于计算的剩余gas
- 拥有执行代码的账户地址
- 原始触发此次执行的交易发送者的地址
- 触发代码执行的账户地址(可能与原始发送者不同)
- 触发此次执行的交易gas价格
- 此次执行的输入数据
- Value(单位为Wei)作为当前执行的一部分传递给该账户
- 待执行的机器码
- 当前区块的区块头
- 当前消息通信或合约创建堆栈的深度
执行刚开始时,内存和堆栈都是空的,程序计数器为0。
1 | PC: 0 STACK: [] MEM: [], STORAGE: {} |
然后EVM开始递归的执行交易,为每个循环计算系统状态和机器状态。系统状态也就是以太坊的全局状态(global state)。机器状态包含:
- 可获取的gas
- 程序计数器
- 内存的内容
- 内存中字的活跃数
- 堆栈的内容
堆栈中的项从系列的最左边被删除或者添加。
每个循环,剩余的gas都会被减少相应的量,程序计数器也会增加。
在每个循环的结束,都有三种可能性:
- 机器到达异常状态(例如 gas不足,无效指令,堆栈项不足,堆栈项会溢出1024,无效的JUMP/JUMPI目的地等等)因此停止,并丢弃任何的更改
- 进入后续处理下一个循环
- 机器到达了受控停止(到达执行过程的终点)
假设执行没有遇到异常状态,达到一个“可控的”或正常的停止,机器就会产生一个合成状态,执行之后的剩余gas、产生的子状态、以及组合输出。
呼。我们终于过了一遍以太坊最难的部分了。如果你不能完全理解这个部分,也没关系。除非你在理解非常深层次的东西,否则你真的没有必要去理解执行的每个细节。
一个块是如何完成的?
最后,让我们看看一个包含许多交易的块是如何完成的。
当我们说“完成”,取决于此块是新的还是已存在的,可以指两个不同的事情。如果是个新块,就是指挖这个块所需的处理。如果是已存在的块,就是指验证此块的处理。不论哪种情况,一个块的“完成”都有4个要求:
1)验证(或者,如果是挖矿的话,就是确定)ommers
在区块头中的每个ommer都必须是有效的头并且必须在当前块的6代之内
2)验证(或者,如果是挖矿的话,就是确定)交易
区块中的gasUsed数量必须与区块中所列交易使用的累积gas量相等。(回忆一下,当执行一个交易的时候,我们会跟踪区块的gas计数器,也就跟踪了区块中所有交易使用的gas总数量)
3)申请奖励(只有挖矿时)
受益人的地址会因为挖矿而获得5Ether(在以太坊EIP-649
4)校验(或者,如果是挖矿的话,就是计算一个有效的)状态和nonce
确保所有的交易和改变的结果状态都被应用了,然后在区块奖励被应用于最终交易结果状态之后定义一个新块为状态。通过检查最终状态与存储在头中的状态树来进行验证。