Hi!请登陆

区块链技术11:以太坊简介

2022-6-14 55 6/14

从这一节开始,我们准备介绍两个对比特币的重大变化,以太坊和EOS。

以太坊的概念首次在2013至2014年间由程序员Vitalik Buterin受比特币启发后提出,大意为下一代加密货币与去中心化应用平台,在2014年通过ICO众筹开始得以发展。截至2018年2月,以太币是市值第二高的加密货币,仅次于比特币。可以查看参考[1]了解以太坊的发展历史。

介绍完之前关于比特币的内容之后,我们应该可以看懂以太坊白皮书前面一部分的内容了。白皮书中对比特币的重要技术和特征作了一个回顾和总结。大家可以自己看看这个白皮书,确认一下自己之前的学习效果。

那接下来,我们主要讲的是从对比特币的介绍之后的内容,也即白皮书中对比特币脚本语言的限制的描述,通过分析以太坊对比特币的缺陷,我们来理解以太坊的特征。以太坊白皮书

比特币系统的脚本语言存在一些严重的限制:

  • 缺少图灵完备性– 这就是说,尽管比特币脚本语言可以支持多种计算,但是它不能支持所有的计算。最主要的缺失是循环语句。不支持循环语句的目的是避免交易确认时出现无限循环。理论上,对于脚本程序员来说,这是可以克服的障碍,因为任何循环都可以用多次重复if 语句的方式来模拟,但是这样做会导致脚本空间利用上的低效率,例如,实施一个替代的椭圆曲线签名算法可能将需要256次重复的乘法,而每次都需要单独编码。
  • 价值盲(Value-blindness)。UTXO脚本不能为账户的取款额度提供精细的的控制。例如,预言机合约(oracle contract)的一个强大应用是对冲合约,A和B各自向对冲合约中发送价值1000美元的比特币,30天以后,脚本向A发送价值1000美元的比特币,向B发送剩余的比特币。虽然实现对冲合约需要一个预言机(oracle)决定一比特币值多少美元,但是与现在完全中心化的解决方案相比,这一机制已经在减少信任和基础设施方面有了巨大的进步。然而,因为UTXO是不可分割的,为实现此合约,唯一的方法是非常低效地采用许多有不同面值的UTXO(例如对应于最大为30的每个k,有一个2^k的UTXO)并使预言机挑出正确的UTXO发送给A和B。
  • 缺少状态– UTXO只能是已花费或者未花费状态,这就没有给需要任何其它内部状态的多阶段合约或者脚本留出生存空间。这使得实现多阶段期权合约、去中心化的交换要约或者两阶段加密承诺协议(对确保计算奖励非常必要)非常困难。这也意味着UTXO只能用于建立简单的、一次性的合约,而不是例如去中心化组织这样的有着更加复杂的状态的合约,使得元协议难以实现。二元状态与价值盲结合在一起意味着另一个重要的应用-取款限额-是不可能实现的。
  • 区块链盲(Blockchain-blindness)- UTXO看不到区块链的数据,例如随机数和上一个区块的哈希。这一缺陷剥夺了脚本语言所拥有的基于随机性的潜在价值,严重地限制了博彩等其它领域应用。

在白皮书中,引入以太坊的时候,这样介绍:

以太坊的目的是基于脚本、竞争币和链上元协议(on-chain meta-protocol)概念进行整合和提高,使得开发者能够创建任意的基于共识的、可扩展的、标准化的、特性完备的、易于开发的和协同的应用。……并且因为图灵完备性、价值知晓(value-awareness)、区块链知晓(blockchain-awareness)和多状态所增加的力量而比比特币脚本所能提供的智能合约强大得多。

既然以太坊社区选择让以太坊拥有如此强大的功能,那么相对应的,他们也必须为此做好准备。我们一条条看一下,为了实现对比特币的改进,以太坊必须添加什么功能。

对比一下以太坊白皮书中列出的比特币和以太坊的状态转换图:

比特币作为状态转移系统

区块链技术11:以太坊简介

以太坊的状态转换:

区块链技术11:以太坊简介

在上图中,起始状态保护了四个账户,分别是14c5f8ba、bb75a980、892bf92f、4096ad65。其中14c5f8ba和4096ad65仅维护了余额;bb75a980和892bf92f除了余额之外,还包括一部分的代码和数据。交易数据由14c5f8ba发送到bb75a980,转账10个以太币。同时发送了两个数据2和charlie。这个数据传递到账户bb75a980时,触发了bb75a980账户中的代码,意思是如果在对应于交易中携带的数据中的第一个(作为索引),也即tx.data[0],相对应的位置处的数据是0,则对该位置出的数据进行更新,更新为第二个数据,也即‘charlie’。

所以,这里我们首先需要理解一下以太坊账户的概念。

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

有两种类型的账户:

  1. 外部拥有的账户,被私钥控制且没有任何代码与之关联
  2. 合约账户,被它们的合约代码控制且有代码与之关联
区块链技术11:以太坊简介

外部拥有账户(EOA)与合约账户的比较

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

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

区块链技术11:以太坊简介

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

区块链技术11:以太坊简介

账户状态

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

  1. nonce:如果账户是一个外部拥有账户,nonce代表从此账户地址发送的交易序号。如果账户是一个合约账户,nonce代表此账户创建的合约序号
  2. balance: 此地址拥有Wei的数量。1 Ether=10^18 Wei
  3. storageRoot: Merkle Patricia树的根节点Hash值。Merkle树会将此账户存储内容的Hash值进行编码,默认是空值
  4. codeHash:此账户EVM(以太坊虚拟机,后面细说)代码的hash值。对于合约账户,就是被Hash的代码并作为codeHash保存。对于外部拥有账户,codeHash域是一个空字符串的Hash值
区块链技术11:以太坊简介

可以简单理解EOA就是实际的用户;而合约账户就是EOA用户部署的合约,外部所有的账户(EOA externally owned account)(由私钥控制的)和合约账户(由合约代码控制)。外部所有的账户没有代码,人们可以通过创建和签名一笔交易从一个外部账户发送消息。每当合约账户收到一条消息,合约内部的代码就会被激活,允许它对内部存储进行读取和写入,和发送其它消息或者创建合约。

(一)图灵完备性

之前在介绍比特币脚本的时候,我们提到过比特币的脚本是故意设计成不完备的,为什么呢?因为之前分析过,在交易中写的脚本,是需要有矿工来执行并进行确认的。如果是图灵完备的,那么有可能会出现死循环的代码,对于矿工而言是非常不利的。

既然以太坊明确地在挑刺儿,也就意味着在以太坊中实现的脚本语言是图灵完备的。既然这样,以太坊必须要解决的问题是,如何应对死循环可能会无限地消耗?

以太坊中引入了gas(瓦斯、油价等中文翻译)的概念。以太坊在区块链上实现了一个运行环境,被称为以太坊虚拟机(EVM),参与到网络的节点都会运行EVM,验证区块中的每个交易并在EVM中运行交易所触发的代码。合约可以利用的每个命令都会有一个相应的费用值,费用使用gas作为单位计数,也即用户付给矿工的佣金。这里列了一些命令的gas消耗。例:PUSH操作需要消耗3个gas一次转账一般要消耗21000 gas,gas使用ether(以太币)来支付。

Gas常用的单位是wei,wei和ether的关系如下所示:

区块链技术11:以太坊简介

每笔交易都被要求包括gas limit(startGas,限制)和Gas Price(价格)。Gas Limit 是用户愿意为执行某个操作或确认交易支付的最大Gas量(最少21,000)。Gas Price 是 Gwei 的数量,用户愿意花费于每个 Gas 单位的价钱。发送者支付的Gas Price越高,则其交易的优先级越重要,因为矿工打包该交易获得的报酬会更高,这样这个交易会较快地被打包到区块中,更早地获得确认。

区块链技术11:以太坊简介

如果该交易需要使用的gas数量小于或等于所设置的gas limit,那么这个交易会被成功处理。如果gas总消耗超过gas limit,那么所有的操作都会被复原(回滚),但是交易费仍然会被矿工收取。区块链会显示这笔交易完成尝试,但因为没有提供足够的gas导致所有的合约命令都被复原。

区块链技术11:以太坊简介

如果交易里gas没有被消耗完毕,剩下的gas都会以以太币(ether)的形式打回给交易发起者。因为gas消耗一般只是一个大致估算,所以许多用户会超额支付gas来保证他们的交易会被接受。这样如果恶意用户在交易中包括了死循环,那么不论付出多少gas,最终都会消耗完。另外,也正是因为这样的代价问题,虽然以太坊的脚本语言是图灵完备的,也即当前的所有代码都可以在以太坊区块链上运行,但是作为开发者,需要认真考虑代码的效率。两个同样功能的合同,效率高的那个才能生存下来。一个帮助人们理解以太坊合同实际能力的启发问题是:这个功能是否能在一个1999年的智能手机上实现?

总结,当进行每笔交易时,用户设定Gas Limit 和Gas Price,在运行时,矿工首先计算 Gas Limit*Gas Price ,就得到了ETH交易佣金的成本,然后这笔费用首先从用户的账户中扣除,交易运行完毕,如果有剩余,再还给用户。如果不够,矿工也不还钱,只是把交易全部回滚。

发送者支付的Gas Price越高,则其交易的优先级越重要,因为矿工的报酬会更高。 但是,通过设置较低燃料价格(GasPrice),发送者可以节省资金。以太坊客户端的Frontier版本有一个默认的gasPrice,即0.05e12 wei。矿工为了最大化他们的收益,如果大量的交易都是使用默认gasPrice即0.05e12 wei,那么基本上就很难要矿工去接受一个低gasPrice交易,更别说0 gasPrice交易了。

除了计算交易的花费之外,将交易或者合约上传也需要费用。虽然读取本地区块链是免费的,但写入和运算是花钱的。储存更是尤其昂贵,因为任何写入的信息都会被永久的储存着。相比之下,CPU运算很便宜。以太坊是图灵完备的,谁也拦不住你写一个视频解码器然后发布在区块链上;只不过估计你没钱运行它。假设这样的程序的代码至少有几千行,即使把它上传到区块链上也不会便宜。譬如,gas limit=2000,假设交易长为170字节,每字节的费用是5,减去850,所以还剩1150,剩下的才是运行交易能使用的gas上限。

除此之外,每个块还有Block gas limit,这个值在创世区块的配置文件中可以指定,譬如查看一个区块的信息如下,也即区块的gas limit是3573388。block gas limit的用意是限制一个区块中能够包含的交易的数量。

区块链技术11:以太坊简介

(二)Value-blindness

同样的,来回忆一下,比特币为什么放弃了基于账户的概念,而采取UTXO机制的呢?

使用UTXO可以避免维护账户余额的麻烦。为什么维护余额很麻烦呢?因为为了使用余额,也即能够让矿工检查和验证余额,余额必须是全局可见的数据。而基本上每个交易都会对余额产生影响和带来变化。也即,余额必须要全局可见,能快速地更新,而且可以验证。

【UTXO的优点: 1.较高程度的隐私保护。如果用户每次交易都使用一个新的地址,那么账户之间的相互关联就很困难。这样做适用于对安全性要求高的货币系统。2.潜在地可扩展性。UTXO在理论上可扩展性更好。对于维护交易的Merkle树,即使所有的人(包括数据的拥有者)都遗忘了某一数据,真正受损也只有数据的拥有者,其他人不受影响。 但在以太坊账户系统中,任何人弄丢了一个账户对应在默克尔树中信息,那么将无法处理任何能够影响账户的消息。相当于UTXO是一次性的;而账户是可重用的。】

既然以太坊期望可以有明确的价值,必须能够实现余额的概念。余额相对于UTXO也有一些优点:

【1.节省空间。如果一个账户有5个UTXO,则从UTXO模式转成账户模式所需空间会从300字节降到30字节。具体计算如下: 300 = (20 32 8)* 5 (20是地址字节数,32是TX的id字节数,8是交易金额值字节数); 30 = 20 8 2 ( 20是地址字节数,8是交易金额值字节数,2是nonce②字节数) 但实际节约并没有这么大,因为账户需要被存储在帕特里夏树中。另外以太坊中交易也比比特币中的更小(以太坊中100字节,比特币中200-250字节),因为每次交易只需要生成一次引用,一次签名,以及一个输出。2.可替代性更高。在UTXO结构中,有效输出的源码实现中没有区块链层的概念,所以不管是在技术还是法律上,通过建立一个红名单/黑名单,并依据的这些有效输出的来源区分它们并不是很实际。3.简单。以太坊编码更简单、更易于理解,尤其是在涉及到复杂脚本时。尽管任何去中心化应用都可以用UTXO方式来实现,但这种方式实质上是通过赋予一个脚本限制给定的UTXO能够使用以及请求的UTXO的种类的方式来实现,包括脚本评估的应用更改根状态的默克尔树证明。因此,UTXO实现方式比以太坊使用账户的方式要复杂的多。4.轻客户端轻客户端可以随时通过沿指定方向扫描状态树来访问与账户相关的所有数据。在UTXO方式中,引用随着每个交易的变化而变化,这对于长时间运行并使用了UTXO根状态传播机制的dapp应用来说,无疑是繁重的。】

账户方式的一个弱点是:为了阻止重播攻击,每笔交易必须有nonce,nonce的值是上一次使用的nonce值 1,这就使得账户需要跟踪nonce的使用情况,并且必须确认交易的Nonce值比上次使用的Nonce值大1。解决这个问题的一个简单方法是让交易包含一个区块号,这样过一段时间之后,交易便不能重放了。

为了实现账户方式,以太坊的做法是采用状态(state)的概念存储一系列账户,每个账户都有自己的余额,以及以太坊特有的数据(代码或内部存储器)。

以太坊的状态由每个交易改变,

区块链技术11:以太坊简介

为了维护这个状态,和比特币不同,比特币在区块中只包括了交易的merkle树根,而以太坊在区块头部中会包括三棵树,分别是世界状态树(world state trie),交易树(transaction trie)以及receipt树。

区块链技术11:以太坊简介

世界状态

世界状态是地址(账户)到账户状态的映射。虽然世界状态不保存在区块链上,但在黄皮书的描述中,世界状态也由树来保存数据(此树也被称为状态数据库或者状态树)。世界状态可以被视作为随着交易的执行而持续更新的全局状态,也是以太坊中唯一的全局的数据。

以太坊中所有的账户信息都体现在世界状态之中,并由世界状态树保存,状态树持续更新。对于以太坊网络中的每一个账户,状态树中存放了一个键值对,其中键key是160位的账户的地址,值value是账户的相关信息,如上面图中所展示的,包括

- nonce
- balance
- storageRoot
- codeHash

如果想知道某一账户的余额,或者某智能合约当前的状态,就需要通过查询世界状态树来获取该账户的具体状态信息。其中账户存储树是保存与账户相关联数据的结构。该项只有合约账户才有,而在 EOA 中, storageRoot 留空、 codeHash 则是一串空字符串的哈希值。

区块链技术11:以太坊简介

为什么以太坊中需要保存用户状态的历史记录,而比特币中不需要呢?因为,比特币中区块生成速度较慢,产生分叉的可能性也较低。而且,即使产生了分叉,由于比特币系统中的交易比较简单,只是简单的转账交易,因此回滚起来比较方便。比较容易实现将被抛弃的分叉中的交易进行回滚。而以太坊就不一样了。以太坊的区块生成速度比较快,十几秒就会产生一个区块,因此产生分叉非常频繁,需要经常进行回滚操作。最重要的一点是,以太坊中有智能合约,使得以太坊是图灵完备的,可以实现很复杂的交易。因此,如果不保存历史记录,就很难进行回滚操作。

交易树

每当发布一个区块时,区块中包含的所有交易,会被组织成一棵交易树,该树是一棵Merkle Patricia Tree,查找的键值是交易在发布时的序号(交易的排列顺序是由发布区块的节点决定的)。交易树用来证明某笔交易在某个区块当中。交易树中包括的交易的细节:

nonce,
gas price,
gas limit,
recipient,
transfer value,
transaction signature values, and
account initialization (if transaction is of contract creation type), or transaction data (if transaction is a message call)

收据树

每个交易执行完之后,会形成一个收据,记录交易的相关信息,而这些收据会被组织成一棵收据树,该树是一棵Merkle Patricia Tree,查找的键值是交易在发布时的序号(交易的排列顺序是由发布区块的节点决定的)。收据树中对应的交易的信息:

post-transaction state,
the cumulative gas used,
the set of logs created through execution of the transaction, and
the Bloom filter composed from information in those logs

那么,既然已经有了交易树,为什么还需要收据树这个数据结构呢?因为,以太坊拥有智能合约,而智能合约的执行过程比较复杂,通过增加收据树,有利于系统快速查询执行的结果。在以太坊中,每个交易对应的收据中都会包含一个Bloom filter,记录这个交易的类型、地址等信息。发布的区块的块头中也有一个总的Bloom filter,这个Bloom filter是所有收据中的Bloom filter的并集。如果我们需要查找过去一段时间内发生的和某个智能合约相关的所有交易,首先需要在区块的块头中的Bloom filter中看看有没有我们要查找的交易的类型,如果有的话,再到区块中的每个交易对应的收据的Bloom filter中进行查找;而如果没有的话,那么该区块中一定没有我们要查找的交易类型。通过该种方法,就可以快速排除掉无关的收据,从而提高查找速度。

(三)状态

(四)区块链盲

在脚本图灵完备以及维护了状态树的情况下,以上两个特征实现起来就容易了。

GHOST协议

GHOST(Greedy Heaviest Observed Subtree)是一种主链(非侧链)选择协议。举例来说:经典的Proof-of-Work(POW)是以取最长的主链为基本原则,GHOST协议则是以包含块数最多为基本原则。

在比特币协议中,最长的链被认为是绝对的正确。如果一个块不是最长链的一部分,那么它被称为是孤块。一个孤立的块也是合法的,但是发现的稍晚,或者是网络传输稍慢,而没有能成为最长的链的一部分。在比特币中,孤块没有意义,随后将被抛弃,发现这个孤块的矿工也拿不到采矿相关的奖励。例如:挖矿节点A是一个矿池占有全网30%的算力,挖矿节点B占有全网算力的10%,节点A会有70%的概率产生废块,节点B有90%的概率产生废块。

以太坊平均10多秒发布一个区块,更短的出块时间意味着,临时性分叉的几率将大幅提升。这是因为当矿工A挖出一个新区块后,需要向全网广播,广播的过程需要时间,由于以太坊出块时间短,其他节点可能还没有收到矿工A发布的区块,就已经挖出了同一高度的区块,这就造成了临时分叉。在以太坊网络中,临时性分叉发生的几率在8%左右。

区块链技术11:以太坊简介

同时,如果新旧区块之间产生的间隔太短, 在上面的例子中,则节点A会因为规模效应而比B节点更为高效。所以新旧区块的间隔过短会导致单一的矿池主导全网的挖矿过程。而区块中的数据重复验证6次(若干次)立即永久封存地区块中,一旦51%算力攻击一旦发生,double-spend等糟糕问题会出现,恶意的数据容易永久封存于区块中,整个blockchain系统需要足够长的时间来处理和恢复黑客恶意攻击所造成的破坏。

在以太坊中,根据GHOST协议,不认为孤块没有价值,而是会给与发现孤块的矿工以回报。孤块被称为叔块(uncle block),它们可以为主链的安全作出贡献,也同样能获得奖励,这激励了矿工在新发现的块中去引用叔块,减少了孤块的产生。

Ghost协议解决了两个问题:摒弃了单一的最长链原则, 取而代之的是最大子数原则;孤块奖励问题。

如下图所示:

区块链技术11:以太坊简介

如果单纯的计算最长链原则, 主链应该是 0 -

相关推荐