以太坊区块链原理揭秘(上)

不管你们知不知道以太坊(Ethereum)是什么,但是你们大概都听说过以太坊。最近在新闻里出现过很多次,包括一些专业杂志的封面,但是如果你们对以太坊到底是什么没有一个基本的了解的话,看这些文章就会感觉跟看天书一样。

所以,什么是以太坊?本质上,就是一个永久保存数字交易记录的公共数据库。最重要的是,这个数据库不需要任何中央权威机构来监督和维持,它以一种“信任透明”的方式来运行—个体的运行无需依赖他人或者第三方机构的信任。

WTF,上面一段到底啥意思?别急,我的目标是在技术层面来解释以太坊的工作原理,但是不会出现很复杂的数学问题或看起来很可怕的公式。即使你不是一个程序员,你也可以看看,看完本篇文章你会对区块链的宏观技术有更清晰的认识。如果遇到难以理解的细节,可以直接跳过,本文不是为了学术而存在。

世所皆知,以太坊黄皮书yellowpaper.io是区块链技术的珠穆拉玛峰,每年都有一些勇敢的人试图挑战。本文的很多观点都是基于黄皮书的技术细节,但是添加了通俗易懂的解释。

OK,let's go.

区块链定义

区块链就是一个具有共享状态的、密码学安全的、支持交易的单机。晦涩难懂,是吧?让我们将它分开来看:

- “密码性安全(Cryptographically secure)”是指用一个很难被解开的复杂数学机制算法来保证数字货币生产的安全性。将它想象成类似于防火墙的这种。它们使得欺骗系统近乎是一个不可能的事情(比如:构造一笔假的交易,消除一笔交易等等)。

- “交易的单机(Transactional singleton machine)”是指只有一个权威的机器实例为系统中产生的交易负责任。换句话说,只有一个全球真相是大家所相信的。

- “具有共享状态(With shared-state)”是指在这台机器上存储的状态是共享的,对每个人都是开放的。

以太坊就是区块链的一种实现。

以太坊模型说明

以太坊的本质就是一个基于交易的状态机(transaction-based state machine)。在计算机科学中,一个状态机是指可以读取一系列的输入,然后根据这些输入,会转换成一个新的状态出来的东西。

根据以太坊的状态机,我们从创世状态(genesis state)开始:类似于一片空白的石板,在网络中还没有任何交易的产生状态。当交易被执行后,这个创世状态就会转变成最终状态。在任何时刻,这个最终状态都代表着以太坊当前的状态。

以太坊的状态有百万个交易。这些交易都被“组团”到一个区块中。一个区块包含了一系列的交易,每个区块都与它的前一个区块链接起来。

为了让一个状态转换成下一个状态,交易必须是有效的。为了让一个交易被认为是有效的,它必须要经过一个验证过程,此过程也就是挖矿。挖矿就是一组计算机节点(电脑)用它们的计算资源来创建一个包含有效交易的区块出来。

任何在网络上宣称自己是矿工的节点都可以尝试创建和验证区块,世界各地的很多矿工都在同一时间创建和验证区块,每个矿工在提交一个区块到区块链上的时候都会提供一个数学机制的“证明”,这个证明就像一个保证:如果这个证明存在,那么这个区块一定是有效的。

为了让一个区块添加到主链上,一个矿工必须要比其他矿工更快的提供出这个“证明”。可以通过一个数学证明来验证矿工提供的数据是合法的,这个数学证明既是工作量证明(proof of work, PoW)。

证实了一个新区块的矿工都会被奖励一定价值的奖赏, 奖赏是什么?以太坊使用一种内在数字代币—以太币(Ether)作为奖赏。每次矿工证明了一个新区块,那么就会产生数个新的以太币并被奖励给矿工。

你也许会在想:什么能确保每个人都只在区块的同一条链上呢?我们怎么能确定不会存在一部分矿工创建(或者说在自己的电脑上伪造)一个他们自己的链呢?

前面,我们定义了区块链就是一个具有共享状态的交易单机。使用这个定义,我们可以知道正确的当前状态是一个全球共享的状态,因此所有人都必须要接受它。如果同时拥有多个状态(或多个链)会摧毁这个系统,因为当你交易的时候你可能使用了一条错误的状态(链)。

这种不同的状态的产生,称之为区块链的分叉,如果链分叉了,你有可能在一条链上拥有10个币,另一条链上拥有20个币,第三条链上拥有40个币。在这种场景下,是没有办法确定哪个链才是最”有效的“,毕竟货币在多个地方存在不同的状态,肯定是危险且无效的,你可以理解为美元在全世界的认知都是一样的,状态也是一样的,如果现在某个国家伪造了一份美元,然后欺骗其它相邻国家也来使用这种伪造的美元,那世界经济就会出现大问题!

不论什么时候只要多个路径产生了,一个”分叉“就会出现。我们通常都想避免分叉,因为它们会破坏系统,强制人们去选择哪条链是他们相信的链。

为了确定哪个路径才是最有效的以及防止多条链的产生,以太坊使用了一个叫做“GHOST协议(GHOST protocol.)”的数学机制。

GHOST = Greedy Heaviest Observed Subtree

简单来说,GHOST协议就是让我们必须选择一个最长的路径(耗费计算最多的)。一个方法确定路径就是使用最近一个区块的区块号,区块号代表着当前路径上总的区块数(不包含创世纪区块)。区块号越大,路径就会越长,就说明越多的挖矿算力被消耗在此路径上以达到叶子区块。使用这种推理就可以允许我们赞同当前状态的权威版本。

现在你大概对区块链是什么有个理性的认识,让我们在再深入了地解一下以太坊系统主要组成部分:

账户(accounts)

状态(state)

损耗和费用(gas and fees)

交易(transactions)

区块(blocks)

交易执行(transaction execution)

挖矿(mining)

工作量证明(proof of work)

在开始之前需要注意的是:每当我说某某的hash, 我指的都是KECCAK-256hash, 以太坊就是使用这个hash算法。

账户

以太坊的全局“共享状态”是有很多小对象(账户)来组成的,这些账户可以通过消息传递架构来与对方进行交互。每个账户都有一个与之关联的状态(state)和一个20字节的地址(address)。在以太坊中一个地址是160位的标识符,用来识别账户的。

这是两种类型的账户:

- 外部拥有的账户,被私钥控制且没有任何代码与之关联

- 合约账户,被它们的合约代码控制且有代码与之关联

外部账户与合约账户的比较

理解外部账户和合约账户的基本区别是很重要的。一个外部账户可以通过创建和用自己的私钥来对交易进行签名,来发送消息给另一个外部账户或合约账户。在两个外部账户之间传送的消息只是一个简单的价值转移。但是从外部拥有账户到合约账户的消息会激活合约账户的代码,允许它执行各种动作。(比如转移代币,写入内部存储,挖出一个新代币,执行一些运算,创建一个新的合约等等)。

不像外部账户,合约账户不可以自己发起一个交易。相反,合约账户只有在接收到一个交易之后(从一个外部账户或另一个合约账户发起),为了响应此交易而触发一个交易。我们将会在“交易和消息”部分来了解关于合约与合约之间的通信。

因此,在以太坊上任何的动作,总是被外部账户触发的交易所发动的。

账户状态

账户状态有四个组成部分,不论账户类型是什么,都存在这四个组成部分:

- nonce:如果账户是一个外部拥有账户,nonce代表从此账户地址发送的交易序号。如果账户是一个合约账户,nonce代表此账户创建的合约序号

- balance: 此地址拥有Wei的数量。1Ether=10^18Wei

- storageRoot: Merkle Patricia树的根节点Hash值(我们后面在解释Merkle树)。Merkle树会将此账户存储内容的Hash值进行编码,默认是空值

- codeHash:此账户EVM(以太坊虚拟机,后面细说)代码的hash值。对于合约账户,就是被Hash的代码并作为codeHash保存。对于外部拥有账户,codeHash域是一个空字符串的Hash值

世界状态

好了,我们知道了以太坊的全局状态就是由账户地址和账户状态的一个映射组成。这个映射被保存在一个叫做Merkle Patricia树的数据结构中

Merkle Tree(也被叫做Merkle trie)是一种由一系列节点组成的二叉树,这些节点包括:

- 在树底的包含了源数据的大量叶子节点

- 一系列的中间的节点,这些节点是两个子节点的Hash值

- 一个根节点,同样是两个子节点的Hash值,代表着整棵树

树底的数据是通过分开我们想要保存到chunks的数据产生的,然后将chunks分成buckets,再然后再获取每个bucket的hash值并一直重复直到最后只剩下一个Hash:根Hash。

这棵树要求存在里面的值(value)都有一个对应的key。从树的根节点开始,key会告诉你顺着哪个子节点可以获得对应的值,这个值存在叶子节点。在以太坊中,key/value是地址和与地址相关联的账户之间状态的映射,包括每个账户的balance, nonce, codeHash和storageRoot(storageRoot自己就是一颗树)。

同样的树结构也用来存储交易和收据。更具体的说,每个块都有一个头(header),保存了三个不同Merkle trie结构的根节点的Hash,包括:

- 状态树

- 交易树

- 收据树

在Merkle tries中存储信息是非常高效的,而且在以太坊中的“轻客户端”和“轻节点”相当的有用。记住区块链就是一群节点来维持的,有两种节点类型:全节点和轻节点。

全节点通过下载整条链来进行同步,从创世纪块到当前块,执行其中包含的所有交易。通常,矿工会存储全节点,因为他们在挖矿过程中需要全节点,因此,一个全节点包含了整个链。

不过除非一个节点需要执行所有的交易或轻松访问历史数据,不然没必要保存整条链。这就是轻节点概念的来源。比起下载和存储整条链以及执行其中所有的交易,轻节点仅仅下载链的头,从创世纪块到当前块的头,不执行任何的交易或检索任何相关联的状态。由于轻节点可以访问块的头,而头中包含了3个tries的Hash,所有轻节点依然可以很容易来验证交易的合法性。

这个可以行的通是因为在Merkle树中hash值是向上传播的—如果一个恶意用户试图用一个假交易来交换Merkle树底的交易,这个会改变它上面节点的hash值,而它上面节点的值的改变也会导致上上一个节点Hash值的改变,以此类推,一直到树的根节点。

任何节点想要验证一些数据都可以通过Merkle证明来进行验证,Merkle 证明的组成:

- 一块需要验证的数据

- 树的根节点Hash

- 一个“分支”(从 chunk到根这个路径上所有的hash值)

任何可以读取证明的人都可以通过验证分支的hash是否连贯,来证明新的块是合法的。

总之,使用Merkle Patricia树的好处就是该结构的根节点加密取决于存储在树中的数据,而且节点hash还可以作为该数据的安全标识。由于块的头包含了状态、交易、收据树的根hash,所有任何节点都可以验证以太坊的一小部分状态而不用保存整个状态,这整个状态的的大小可能是非常大的。

Gas和费用

在以太坊中一个比较重要的概念就是交易或合约费用,由以太坊网络上的交易产生的每一次计算,都会产生费用—毕竟没有免费的午餐。这个费用就是Gas.

Gas就是用来衡量在一个具体交易(合约)中要求的费用单位。gas price就是你愿意在每个gas上花费以太币的数量,以“gwei”进行衡量。“Wei”是Ether的最小单位,1Ether表示10^18Wei. 1gwei是1,000,000,000 Wei。

对每个交易,发送者设置gas limit和gas price。gas limit和gas price就代表着发送者愿意为执行交易支付的Wei的最大值。

例如,假设发送者设置gas limit为50,000,gas price为20gwei。这就表示发送者愿意最多支付50,000*20gwei = 1,000,000,000,000,000 Wei = 0.001 Ether来执行此交易。

记住gas limit代表用户愿意花费在gas上的钱的最大值。如果在他们的账户余额中有足够的Ether来支付这个最大值费用,那么就没问题。在交易结束时任何未使用的gas都会被返回给发送者,以原始费率兑换。

在发送者没有提供足够的gas来执行交易,那么交易执行就会出现“gas不足”然后被认为是无效的。在这种情况下,交易处理就会被终止以及所有已改变的状态将会被恢复,最后我们就又回到了交易之前的状态—就像这笔交易从来没有发生。

这些gas的钱到底去了哪里?发送者在gas上花费的所有钱都发送给了“受益人”地址,通常情况下就是矿工的地址。因为矿工为了计算和验证交易做出了努力(需要消耗机器资源),所以矿工接收gas的费用作为奖励。

通常,发送者愿意支付更高的gas price,矿工从这笔交易总就能获得更多的价值。因此,矿工也就更加愿意选择这笔交易。这样的话,矿工可以自由的选择一笔交易自己愿意验证或忽略。为了引导发送者应该设置gas price为多少,矿工可以选择建议一个最小的gas值他们愿意执行一个交易。

存储也有费用

gas不仅仅是用来支付计算这一步的费用,而且也用来支付存储的费用。存储的总费用与所使用的32位字节的最小倍数成比例。

存储费用有一些比较细微的方面。比如,由于增加了的存储增加了所有节点上的以太坊状态数据库的大小,这样用户的硬盘会消耗得很多,因此需要激励来奖励用户。同时,因为需要激励用户尽量节省硬盘空间,如果一个交易的执行有一步是清除一个存储实体,那么为执行这个操作的费用就会被放弃,并且由于释放存储空间的退款就会被返回给发送者。

费用的作用是什么?

以太坊可以运作的一个重要方面就是每个网络执行的操作同时也被全节点所影响。然而,计算的操作在以太坊虚拟机上是非常昂贵的。因此,以太坊智能合约最好是用来执行最简单的任务,比如运行一个简单的业务逻辑或者验证签名和其他密码对象,而不是用于复杂的操作,比如文件存储,电子邮件,或机器学习,这些会给网络造成压力。施加费用可以防止用户执行过于复杂的操作,给整个网络带来过高的负载。

以太坊是一个图灵完备的机器(短而言之,图灵机器就是一个可以模拟任何电脑算法的机器)。因为支持图灵完备,所有必须要支持循环;支持了循环,就要考虑到无限(或次数过多)循环情况,这个时候费用就很有用了,用户每执行一步循环,扣除一次费用,如果费用不够,循环自动停止,从根本上防止了恶意攻击和错误的代码。

你也许会想,“为什么我们还需要为存储付费?” 其实就像计算一样,以太坊网络上的存储是整个网络都必须要负担的成本。

交易和消息

之前说过以太坊是一个基于交易的状态机。换句话说,在两个不同账户之间发生的交易才让以太坊在全球各地的状态从一个转换成另一个。

这里有一个最基本的概念:一笔交易就是被外部拥有账户通过加密签名生成的一段指令,序列化后,最后提交给区块链执行。

有两种类型的交易:消息通信和智能合约的创建。

不管什么类型的交易,都包含:

- nonce:发送者发送交易数的计数

- gasPrice:发送者愿意支付执行交易所需的每个gas的Wei数量

- gasLimit:发送者愿意为执行交易支付gas数量的最大值。这个数量被设置之后在任何计算完成之前就会被提前扣掉

- to:接收者的地址。在合约创建交易中,合约账户的地址还没有存在,所以值先空着

- value:从发送者转移到接收者的Wei数量。在合约创建交易中,value作为新建合约账户的开始余额

- v,r,s:用于产生标识交易发生着的签名

- init(只有在合约创建交易中存在):用来初始化新合约账户的EVM代码片段。init值会执行一次,然后就会被丢弃。当init第一次执行的时候,它返回一个账户代码体,也就是永久与合约账户关联的一段代码。

- data(可选域,只有在消息通信中存在):消息通话中的输入数据(也就是参数)。例如,如果智能合约就是一个域名注册服务,那么调用合约可能就会期待输入域例如域名和IP地址

在“账户”这个章节中我们学到交易—消息通信和合约创建交易两者都总是被外部拥有账户触发并提交到区块链的。换种思维思考就是,交易是外部世界和以太坊内部状态的桥梁。

但是这也并不代表一个合约与另一个合约无法通信。在以太坊状态全局范围内的合约可以与在相同范围内的合约进行通信。他们是通过“消息”或者“内部交易”进行通信的。我们可以认为消息或内部交易类似于交易,不过与交易有着最大的不同点—它们不是由外部拥有账户产生的。相反,他们是被合约产生的。它们是虚拟对象,与交易不同,没有被序列化而且只存在与以太坊执行环境。

当一个合约发送一个内部交易给另一个合约,存在于接收者合约账户相关联的代码就会被执行。

一个重要需要注意的事情是内部交易或者消息不包含gasLimit。因为gas limit是由原始交易的外部创建者决定的(也就是外部拥有账户)。外部拥有账户设置的gas limit必须要高到足够将交易完成,包括由于此交易而长生的任何”子执行”,例如合约到合约的消息。如果,在一个交易或者信息链中,其中一个消息执行使gas已不足,那么这个消息的执行会被还原,包括任何被此执行触发的子消息。不过,父执行没必要被还原。

未完待续。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180421G0VJ8N00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券