EVM是基于栈的,一个基于栈的机器是使用后进先出堆栈来保存临时值的计算机。
EVM 中每个栈项的大小为 256 位,栈最大的大小为 1024位。
EVM 是有内存的,其中的数据存储为字寻址字节数组。 内存中存储的数据不是永久性数据。
EVM 也有外存。与内存不同,外存作为系统状态的一部分进行维护,能够存储永久性数据。 EVM 将程序代码单独存储在一个只能通过特殊指令访问的虚拟 ROM 中。通过这种方式,EVM 与典型的冯诺依曼架构不同,后者将程序代码存储在内存或存储器中。
EVM 也有自己的语言:“EVM 字节码”。 当程序员编写在以太坊上运行的智能合约时,我们通常会使用更高级别的语言(例如 Solidity)编写代码,然后将其编译为 EVM 可以理解的 EVM 字节码。
介绍完EVM的基础概念,我们来看看具体的执行环节。
在执行一个计算前,处理器会去报下面的信息是可用和合法的:
- 系统状态
- 计算剩余的gas量
- 合约代码的创建账户地址
- 发起此次执行的交易的发送方地址
- 导致代码执行的帐户地址(可能与原始发送方不同)
- 交易执行的gas pirce
- 输入数据
- 传入的以太币数值(单位wei)
- 被执行的机器码
- 当前区块的区块头
- 当前消息调用或合约创建的栈的深度
当开始执行时,内存和栈都应该是空的,且程序计数器的值为0。
然后,EVM 递归地执行交易,计算每个循环的系统状态和机器状态。 系统状态就是以太坊的全局状态。 机器状态包括:
- 可用的gas
- 程序计数器
- 内存数据
- 内存中的活跃字数
- 栈内容
每次循环,相应的gas都会从剩余的总gas中被减去,程序计数器加1。
- 机器达到异常状态(例如gas 不足、指令无效、栈内存不足、无效的 JUMP/JUMPI 目标等)。此时计算必须停止,而且更改会被作废
- 继续处理进入下一个循环
- 机器达到受控停止(执行过程结束)
假设执行没有遇到异常状态并达到“受控”或正常停止,机器会生成结果状态、执行后的剩余气体、应计子状态和结果输出。
现在,我们一览了以太坊最复杂的部分之一。 即使你没有完全理解这部分,也没关系。 除非你想成为区块链架构师,需要了解很多底层,否则你实际上并不需要了解具体的执行细节。
区块是如何确定的?
最后,让我们来了解一下包含许多交易的区块是如何被确定的?
一个区块的确定可能意味着不同的事情,这取决于这个区块是新产生的还是已经存在的。如果这是一个新的区块,我们指的是产生区块的挖矿过程。如果这是已经存在区块,指的是区块验证过程。这两种情况的区块确定,需要4个要素:
- 验证叔块
块头中的每个叔块必须有着合法的区块头,并且在当前区块的第六代之内。
- 验证交易
区块上的gasUsed的数量,必须等于区块中交易使用的累积gas数量。
- 奖励(只有在挖矿时才有)
矿工地址因开采该区块而获得 5 Ether。(根据以太坊提案 EIP-649,这个 5 ETH 的奖励已减少到 3 ETH)。 此外,对于每个叔块,当前区块的矿工将额外获得当前区块奖励的 1/32。最后,叔块的引用区块也会获得一定的奖励(有一个特殊的计算公式)。
- 验证状态和随机数
在确保所有交易和结果的状态都发生了更改后,区块获得奖励,此时区块的新状态最终被确定并定义。
工作量证明
“区块”部分简要介绍了区块难度的概念。 赋予区块难度的算法称为工作证明 (PoW)。
以太坊的工作量证明算法称为“Ethash”(以前称为 Dagger-Hashimoto)。
该算法的正式定义如下:
m是mixHash,n是nonce。Hn是新产生区块的头部(不包括mixHash和nonce,因为这两部分是需要被计算得出的),Hn是区块头部的nonce,d是DAG,是一个很大的数据集。
在“区块”的章节中,我们有提到过区块头是由几个不同的字段组成的。上面的mixHash和nonce正是区块头中的字段:
- mixHash:一个hash,和nonce一起使用能够证明该区块提供了足够的计算量
- nonce:一个hash,和mixHash一起使用能够证明该区块提供了足够的计算量
工作量证明就是用来计算这两个hash的。
至于计算mixHash和nonce的具体细节是比较复杂的,我们未来可以单独写一篇比较深入的专栏去分析。工作量证明算法大致如下[1]:
- 为每个区块计算一个种子seed
- 根据种子可以计算一个初始大小为 16MB的伪随机缓存cache。轻客户端保存这个 cache,用于辅助校验区块和生成数据集
- 根据 cache, 可以生成一个初始大小为 1GB的DAG数据集。数据集中的每个条目(64字节)仅依赖于 cache 中的一小部分条目。数据集会随时间线性增长,每30000个区块间隔更新一次。数据集仅仅存储在完整客户端和矿工节点,但大多数时间矿工的工作是读取这个数据集,而不是改变它
- 挖矿则是在数据集中选取随机的部分并将他们一起哈希。可以根据 cache 仅生成验证所需的部分,这样就可以使用少量内存完整验证,所以对于验证来讲,仅需要保存 cache 即可。
挖矿的安全机制
总体而言,POW(工作量证明)的目的是以加密安全的方式证明花费了特定数量的计算来生成一些输出(例如nonce)。采用这个方式,是因为找不到比穷举法更好的方法来找到低于所需阈值的随机数。重复利用哈希函数的解在空间中具有均匀分布的特性。因此我们可以确信,平均而言,找到这样一个随机数所需的时间取决于难度阈值。 难度越高,解决随机数所需的时间就越长。 通过这种方式,PoW 算法能通过调整难度,来加强区块链的安全性。
区块链安全是什么意思? 很简单,我们想创建一个每个人都信任的区块链。 正如前文有提到的,如果存在多个链,用户将无法合理地确定哪个链是“有效”链,从而让该链失去用户的信任。 为了让用户接受存储在区块链上的底层状态,我们需要让人相信该区块链是一个遵循单一规范的区块链。
这正是 PoW 算法所做的:它确保特定区块链保持规范,使攻击者难以创建覆盖历史特定部分的新区块(例如,通过清除交易或创建虚假交易) 或维护一个分叉。 为了验证他们的区块,攻击者需要始终比网络中的任何其他人更快地算出nonce,从而让整个网络认为他们的链是最重的链(基于我们前面提到的 GHOST 协议的原则)。 除非攻击者拥有超过一半的网络挖矿能力,否则这是不可能的,这种情况被称为 51% 攻击。
挖矿的财富分配机制
除了提供安全的区块链之外,PoW还是一种财富分配机制,将财富分配给那些为保证区块链安全做出了计算贡献的矿工。上文中有提到,矿工因挖出一个区块而获得奖励,包括:
- 创建新区块奖励的3ETH
- 获取这个区块的交易中的gas
- 叔块奖励
为了确保使用 PoW 共识机制进行安全和财富分配的长期可持续性,以太坊努力实现这两点:
- 让尽可能多的人可以访问区块链。人们不应该需要专门的或专用硬件来运行算法。 这样做的目的是使财富分配模型尽可能开放,让任何人都可以提供任意数量的计算能力来赚取以太币。
- 减少单一节点获取巨额以太币的可能性。如果这样的事情发生了,就说明这个单一节点对区块链的影响很大,这将会降低整个网络的安全性。
在比特币区块链网络中,与上述两点相关的一个问题是 PoW 算法是一种 SHA256 哈希函数。 此算法弱点在于,使用专用硬件(也称为 ASIC)可以更高效地计算出哈希。
为了缓解这个问题,以太坊采用的是Ethash算法,该算法经过精心设计。采用Ethash计算 nonce需要大量内存和带宽。 大内存的要求,让计算机很难有足够大的内存同时发现多个 nonce,而高带宽要求使得即使是超级计算机也难以同时发现多个 nonce。 这降低了中心化的风险,并为进行节点验证创造了一个更公平的竞争环境。
需要注意的一点是,以太坊正在从 PoW 共识机制转变为所谓的“PoS(股权证明)”,预计2022年下半年实现迁移。
总结
这篇文章翻译自《How does Ethereum work, anyway?》。
文章中也许有很多要消化的东西,你可能需要多次阅读才能理解。 本人也是多次阅读以太坊黄皮书、白皮书和代码库的各个部分,然后才写出这篇文章。
尽管如此,我希望您发现此概述对你有所帮助。 如果有任何错误,请在评论区支出。
参考资料
[1] 也许是国内第一篇把以太坊工作量证明从算法层讲清楚的 - Tiny熊
[2] How does Ethereum work, anyway?