前阵子,笔者学习了Gavin Wood博士的Polkadot白皮书,获益良多,决心再接再厉,追根溯源,学习Gavin Wood博士在区块链领域的开山之作《以太坊黄皮书》。这篇黄皮书的正式标题是《以太坊:一个安全,去中心化,通用的交易账本,拜占庭版本 e94ebda - 2018-06-05》(《ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGERBYZANTIUM VERSION e94ebda - 2018-06-05》),这份黄皮书不仅奠定了Gavin Wood博士在以太坊的地位,更奠定了他在整个区块链发展史上的地位。
以下所有文字描述皆为笔者根据自己的理解总结和归纳。
互联网发展至今,已经无处不在,和我们形影不离。互联网让信息的交流和传递变得异常廉价。
后来比特币的出现证明通过共识机制,我们可以建立一个全球的,基于互联网的去中心化价值传递系统。这是一种特殊的基于加密安全的交易状态机。
以太坊是一种通用的技术,基于此种技术,人们不仅可以构建各种状态机,还可以创建从未有过的软件架构:一个可信的对象传送计算系统。
以太坊项目的缘起及动机很多,其中一个关键的动机是为了能让(由于地理位置的分隔,通信系统的差异,通信费用的昂贵等因素)无法建立信任关系的双方能够有绝对的信心进行交易。
在这个领域,前人已经做了不少探索。
Vitalik Buterin在2013年11月提出了以太坊的核心概念,试图创造一个图灵完备的区块链系统。
Dwork和Naor在1992年提出了基于工作量证明的机制(“PoW”)试图在互联网上进行价值转移。
Vishnumurthy在2003年第一个用工作量证明机制(“PoW”)创造了一种经济方式并发行数字货币。这个案例所发行的数字货币被用于点对点之间传输文件的交易,让文件接收者用这种数字货币向文件提供者支付报酬。这种基于PoW机制的交易用数字签名和账本来保障交易记录的安全和不被篡改,同时也保证交易者无法进行交易欺诈。
5年后的2008年,中本聪提出了另一个基于PoW的想法,创造了比特币,并迅速成为全世界使用最广的去中心化交易账本。
在比特币之后,迅速出现了一批竞争币,它们对比特币的协议进行了修改,比如莱特币,质数币(Primecoin),域名币(Namecoin)等。
还有一些项目希望丰富比特币的功能,增加核心协议的内容比如Willett2013年提出的Mastercoin,Rosenfeld2012年提出的染色币等。
另一方面,瑞波币(Ripple)尝试拿掉了比特币的去中心化特质,创建一种类似联邦制的交易系统极大提高了转账和交易效率。
1997年Szabo和Miller提出了智能合约的概念,人们憧憬未来法律的执行将由智能合约掌控,但遗憾的是至今仍没有一个具体的系统能实现这个想法。以太坊因此应运而生,要成为一个基于密码学的法制系统。
一. 以太坊区块链概要
以太坊整体而言可被视为一个交易状态机。从创世纪状态开始,随着交易的陆续执行,状态开始发生变化直到达到某个最终状态。我们把这个最终状态视为以太坊在某个时间的最终状态。状态信息可以包含账户余额,信誉信息甚至是现实物理世界中的信息等,简而言之就是任何可被信息化的数据。交易是状态转换的桥梁。状态转换可能有效也可能无效。无效的状态转换包括欺诈交易,无效交易等。
在以太坊中,状态的转换通过状态转换方程来实现。交易被核准打包进区块。每一个区块记录的信息包括:一段时间内所执行的一系列交易,前一个区块的信息以及在这些交易驱动下以太坊达到的最终状态的标识符(注意:区块不记录最终的状态信息,只记录代表最终状态的标识符。因为最终状态的信息量非常大,若在区块中记录最终状态信息,将会占用大量区块空间)。区块与区块之间通过哈希值指针相互链接形成链表结构。
以太坊也有挖矿,挖矿是为了激励区块的产生。挖矿基于PoW工作量证明机制。
以太坊的流通代币为Ether,其最小单位为“Wei”,一个Ether等于“Wei”的10的18次方。
由于以太坊是个去中心化的系统,因此系统中每个节点都可以创建自己的区块。因此系统中所有区块形成的结构是个树状结构,也就是说,在某段时间内,以太坊是有可能有多个状态的。这种情况我们要尽量避免。但如果发生了这种情况,在这个树状结构中,哪一个从根到叶子的路径所代表的区块链才是有效的区块链呢?这需要一个共识机制来决定,以太坊中这个共识机制是简化版的GHOST协议。
二. 以太坊的区块,状态和交易
下面我们更深入地探讨以太坊中的区块,状态和交易。
我们先来看“world state”。这是以太坊地址(一个160 bit的标识符)和账户状态(RLP)之间的映射。它并不存储在区块链中,而是保存在一个默克尔树(Merkle Patricia tree,简称trie)里。trie会用一个状态数据库存储这些映射。
我们再来看账户状态。它包含以下信息:
1. nonce:记录该账户转出的交易数。如果该账户是合约账户,则表示该账户创建的合约数。
2. balance:该账户拥有的Ether,以Wei为单位计量。
3. storageRoot:trie根节点的哈希值。该根节点所存储的内容被编码成RLP格式的256-bit整数哈希值。
4. codeHash:若该账户是合约账户,则该信息表示该账户内EVM代码的哈希值。当该账户是合约账户时,一旦收到调用消息,该EVM代码就会被执行。该EVM代码一旦部署,就无法更改。EVM代码被存在状态数据库里。
以太坊中的交易是指被加密签名过的指令。以太坊中有两种交易:会产生消息调用的交易和会产生账户(该账户包含EVM代码)的交易。这两种交易都包含以下信息:
1. nonce:记录交易发起方发起的交易数。
2. gasPrice:支付的每一笔gas的实际价格,以Wei计量。
3. gasLimit:执行每一笔交易要付的gas的上限。
4. to:交易接收方的(160 bit)地址。
5. value:交易转账的金额,以Wei计量。
6. v,r,s:交易签名的值,用于标识交易的发起方。
7. init:不限长度的字节数组,实际是一段EVM代码用来定义如何初始化账户。该代码执行完后返回“body”。“body”也是一段EVM代码,每当该账户接收到消息调用时,就会执行“body”代码。“init”仅仅在账户创建时执行,并且执行完后立刻被废弃。
8. data:不限长度的字节数组,用来定义消息调用的输入数据。
以太坊中的区块包含本区块的区块头,一段时间内的交易数据以及和本区块共一个祖父(本区块沿着区块链往创世纪区块方向回溯的第一个区块被称为父区块,第二个区块被称为祖父)的其它所有区块(这些区块被称为“ommers”)的区块头。区块具体包含的信息如下:
1. parentHash:父区块的区块头哈希值,256-bit。
2. ommersHash:ommers区块列表的哈希值,256-bit。
3. beneficiary:一个160-bit的地址。挖矿产生本次区块所得的收益将发送到这个地址。
4. stateRoot:本区块包含的所有交易都执行完后,交易状态形成的trie结构中,根节点的哈希值,256-bit。
5. transactionsRoot:本区块所包含的所有交易所形成的trie结构中,根节点的哈希值,256-bit。
6. receiptsRoot:本区块所包含的所有交易的接收方所形成的trie结构中,根节点的哈希值,256-bit。
7. logsBloom:本区块所包含的所有交易的接收方所产生的日志地址和主题的过滤器。
8. difficulty:挖出该区块的难度系数。
9. number:区块链上本区块所有祖先区块的总数,创世纪区块中这个值为0。
10. gasLimit:每个区块所消耗的gas上限。
11. gasUsed:本区块中所有交易消耗的gas的总和。
12. timestamp:本区块的创立时间。
13. extraData:长度不超过32个字节的数组,可以用来存储任何信息。
14. mixHash:一个256-bit和nonce相关的哈希值,证明本区块被挖出时消耗了一定算力。
15. nonce:一个64-bit数值,和mixHash相关,证明本区块被挖出时消耗了一定算力。
16. 另外还有一个包含所有ommer区块头的列表和一个包含所有交易的列表。
三. 以太坊中的GAS和支付
为了避免滥用网络资源,并规避图灵完备系统可能导致的问题,以太坊中所有程序的执行必须要付费。费用以gas来定义。每一个交易也都有对应的费用。如果一个交易的发起方账户中所剩的余额不足以支付这笔交易的gas费用,则这笔交易无效。在交易中,如果有多余的费用没有用完,余额会被返回给发起方。
四. 交易的执行
以太坊中最复杂的部分是交易的执行过程。在交易执行过程中,有一个状态转换方程。交易在执行前会检查下列事项:
1. 交易的格式是否符合RLP。
2. 交易签名是否有效。
3. 交易的nonce是否有效。
4. gaslimit不能小于交易所需的gas。
5. 交易发起方的账户余额足够支付交易的gas。
在交易执行的全过程中,系统会收集不同状态的信息,我们把这些状态称为子状态(substate)。这些状态包括交易执行完后要清理的账户信息,EVM代码执行过程中产生的日志信息,交易过程中遇到的空账户,交易过程中返还的gas余额。
当交易开始执行后,状态的变化将不可逆转。交易发起方的nonce在每执行一个交易时就加一。发起方的账户余额随交易发生被扣除交易费。无论是合约交易还是消息调用,一旦开始执行,就证明该交易有效,并且其最终状态将是确定的。交易执行完后,开始计算返还余额,一旦返还余额最终确定,本次交易的最终状态也就确定了。本次交易的gas费用将被奖励给矿工。在最后阶段当所有无用的账户被清理后,系统的最终状态被核定。
五. 智能合约的创建
当一个智能合约账户被创建时,需要用到若干内部参数,包括发送者(s),交易发起方(o),可用gas(g),gas价格(p),捐款(v)以及一串任意长度的字节数组(i),该数组存储EVM代码,消息调用/合约创建的堆栈深度(e)以及对状态更改的授权(w)。
在相关参数被设定好后,首先EVM代码“i”被执行进行账户初始化,在这个过程中,若干系统状态会被改变,并且代码会检查系统的各个状态,若账户初始化顺利完成,系统将扣除创建合约所需的费用;若过程中遇到各种意外,系统将及时处理,最终只有两个结果:合约顺利被创建或合约创建失败。
有几点要注意的是,1)在这个过程中,该合约地址内还不包含任何代码,所以如果这时该地址接到消息调用请求,将不会执行任何代码;2)如果初始化代码执行到了SELFDESTRUCT指令,结果将无法判断,并且该账户最终会被注销;3)如果代码执行到了STOP指令或者返回空值,该账户将变为僵尸账户,账户内的余额将被永远锁定,无法使用。
六. 消息调用
执行消息调用需要用到这些参数:发送者(s),交易发起方(o),交易接收方(r),包含待执行代码的账户(c),通常该账户就是接收方账户,可用gas(g),转账金额(v),gas价格(p),以及一串任意长度的字节数组(d),该数组存储该消息调用的输入数据,消息调用/合约创建的堆栈深度(e)以及对状态更改的授权(w)。
消息调用中除了也会像智能合约创建过程那样检查各种状态和子状态外,它还有个新的参数:输出数据(o)。当执行交易时,不会有消息调用,但当执行EVM代码时,可能会发起消息调用。
七. 执行模型
执行模型定义了以太坊系统在一些列给定指令和环境变量下如何进行状态的转换。这个模型就是虚拟状态机,也被称为“以太坊虚拟机”(“EVM”)。这个虚拟机实际上只算是个“准图灵完备机”(“quasi-Turing-Complete machine”),之所以说这只是个“准”的图灵完备机是因为系统所执行的计算是有限的,受到gas的限制。
EVM是个堆栈结构,每个“字”(“word”)的定义为256-bit。这是为了符合Keccak-256哈希体系和椭圆曲线算法的规定。EVM的内存模型以“字”为寻址方式的字节数组。堆栈的最大深度为1024。EVM还有一个独立的存储模型,和内存模型类似,但不同的是,它是以“字”为寻址方式的字数组。内存中存储的值是可变的,但存储模型中存储的值是不变的,存储模型存储的内容属于状态的一部分。内存和存储中所有变量的初始值都为0。
EVM和典型的冯诺依曼结构不同,它的代码不存储于内存或存储器中(可以随意读取),而是存储在一个虚拟的ROM中,并且只能通过一条特殊指令读取。
EVM还能处理各种意外(exception),比如当gas不足时合约执行产生了意外,系统会终止执行并向负责执行合约的执行方(比如)报错,然后由执行方单独处理。
EVM在执行代码时会在三种情况下收取gas。最常见的是执行一条指令时要收取gas;另外当要发起一个消息调用或创建一个合约时会收取gas;最后当内存的需求不足需要额外内存是要收取gas。
EVM的执行还需要一些执行环境的设置。这些设置包括下列项:
1. Ia:包含代码的账户的地址。
2. Io:交易发起方的地址。
3. Ip:交易方发起交易时,该交易的gas价格。
4. Id:本次执行的输入数据,通常为一组字节数组。如果执行方是一个交易,则此数据为交易数据。
5. Is:触发代码执行的账户的地址。如果执行方是一个交易,则此地址为交易发起方。
6. Iv:本次执行的金额,以Wei计价。如果执行方是个交易,则此参数为交易金额。
7. Ib:所执行的代码,通常为一组字节数组。
8. IH:本区块的区块头。
9. Ie:本次执行的消息调用的次数或合约创建的次数。
10. Iw:对状态修改的权限。
八. 区块树和区块链
以太坊中,实际上所有的区块形成的是树结构,根节点是创世纪区块。从根节点到每一个叶节点所经的路径都会包含若干区块,这些区块都会前后链接形成一个区块链,因此,在这个树结构中有很多条区块链,而只有唯一一条会被系统裁定为以太坊的区块链。
那么根据什么共识标准来裁定到底哪条才是以太坊的区块链呢?系统的标准是:哪一条区块链上所消耗的算力资源最多(或者说哪一条区块链最“重”)那么那一条区块链就被裁定为以太坊的区块链。按这个标准,首先这条链上必须包含的区块最多。另外每个区块的区块头都包含了“difficulty”(挖出该区块的难度系统,难度系数越大,所消耗的算力也越大)。那么我们把一条链上所有区块的难度系统加起来就得到了整条链的难度系数,那么哪个系数最大,它所消耗的算力也就最多。这样我们就能根据这个标准裁定以太坊的区块链了。
九. 区块的认定
认定区块的过程包括以下四个步骤:
1. 验证(挖矿)ommers区块。这一步主要验证每个区块的区块头和区块间的关系。
2. 验证(挖矿)交易。这一步主要验证gasUsed(本区块中所有交易消耗的gas的总和)确实等于本区块中所有交易消耗的gas总和。
3. 发放奖励。这一步主要是把相应的奖励发放给挖出本区块的矿工的地址以及挖出本区块的ommers区块的矿工地址。
4. 验证状态和区块的“nonce”值。这一步主要是确认各个状态值和nonce值。
以太坊中现在的挖矿机制是基于PoW。在设计这种机制时,我们希望尽可能多的人能参与挖矿,因此我们要尽量减少挖矿硬件上的门槛。同时我们也要避免给矿工超线性利润(super-linear profit)防止因为矿工的算力过强得到的回报过多。
目前采用PoW的区块链系统中最大的问题就是ASIC矿机对算力的垄断。那用什么样的方式来防止ASIC矿机对PoW算法的垄断呢?
目前有两种方式:
1. 让计算哈希值的函数需要消耗大量内存以及带宽资源,这样内存就无法被用作并行处理同时计算多个哈希值。
2. 让计算的类型更通用,也即是不让这种算法太过于特殊而导致用特殊的硬件更有计算上的优势。
以太坊采取第一种方式,具体的实现在Ethash里。
至此,本篇黄皮书的精华已全部呈现。书后还附有更多对一些细节的详细解释以及数学证明过程,待学有余力时可再慢慢学习。
参考链接:https://ethereum.github.io/yellowpaper/paper.pdf
领取专属 10元无门槛券
私享最新 技术干货