以太坊

以太坊

以太坊账户

    ### 状态改变(BTC VS ETH)

    ### 以太坊账户

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

    有两种类型的账户:

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

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

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

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

    ### 账户状态

    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值

图灵完备

    ### 比特币

    尽管比特币脚本语言可以支持多种计算,但是它不能支持所有的计算。最主要的缺失是循环语句。不支持循环语句的目的是避免交易确认时出现无限循环。可以用多次if代替,但是这样做会导致脚本空间利用上的低效率,以及带来额外的开销。

    ### 解决思路-GAS

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

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

    如果该交易需要使用的gas数量小于或等于所设置的gas limit,那么这个交易会被成功处理。如果gas总消耗超过gas limit,那么所有的操作都会被复原(回滚),但是交易费仍然会被矿工收取 (矿工先收费,在执行)。这样如果恶意用户在交易中包括了死循环,那么不论付出多少gas,最终都会消耗完。

    每个块还有block gas limit,限制一个区块中能够包含的交易的数量。

价值盲

    ### 比特币

    UTXO脚本不能为账户的取款额度提供精细的的控制。非常低效地采用许多有不同面值的UTXO。(例如对应于最大为30的每个k,有一个2^k的UTXO)

    - UTXO的优点:

        1. 较高程度的隐私 保护。

            如果用户每次交易都使用一个新的地址 ,那么账户之间的相互关联就很困难。这样做适用于对安全性要求高的货币系统。

        2. 潜在地可扩展性。

            UTXO在理论上可扩展性更好。对于维护交易的Merkle树,即使所有的人(包括数据的拥有者)都遗忘了某一数据,真正受损也只有数据的拥有者,其他人不受影响。

             但在以太坊账户系统中,任何人弄丢了一个账户对应在默克尔树中信息,那么将无法处理任何能够影响账户的消息。

            相当于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应用来说,无疑是繁重的。

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

    ### 三棵树

    以太坊在区块头部中会包括三棵树,分别是世界状态树(world state trie),交易树(transaction trie)以及receipt树。

    #### 世界状态树

    世界状态可以被视作为随着交易的执行而持续更新的全局状态,也是以太坊中唯一的全局的数据。以太坊中所有的账户信息都体现在世界状态之中,并由世界状态树保存,状态树持续更新。对于以太坊网络中的每一个账户,状态树中存放了一个键值对,其中键key是160位的账户的地址,值value是账户的相关信息。

    如果想知道某一账户的余额,或者某智能合约当前的状态,就需要通过查询世界状态树来获取该账户的具体状态信息。其中账户存储树是保存与账户相关联数据的结构。

    为什么以太坊中需要保存用户状态的历史记录 ,而比特币中不需要呢?

    - 因为,比特币中区块生成速度较慢,产生分叉的可能性也较低。而且,即使产生了分叉,由于比特币系统中的交易比较简单,只是简单的转账交易,因此回滚起来比较方便。比较容易实现将被抛弃的分叉中的交易进行回滚。

    - 而以太坊就不一样了。以太坊的区块生成速度比较快,十几秒就会产生一个区块,因此产生分叉非常频繁 ,需要经常进行回滚操作。

    - 最重要的一点是,以太坊中有智能合约,使得以太坊是图灵完备的,可以实现很复杂的交易 。因此,如果不保存历史记录,就很难进行回滚操作

    #### 交易树

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

    #### 收据树

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

    以太坊拥有智能合约,而智能合约的执行过程比较复杂,通过增加收据树,有利于系统快速查询 执行的结果。

    在以太坊中,每个交易 对应的收据中都会包含一个Bloom filter ,记录这个交易的类型、地址等信息。发布的区块的块头 中也有一个总的Bloom filter ,这个Bloom filter是所有收据中的Bloom filter的并集。
    

    如果我们需要查找过去一段时间内发生的和某个智能合约相关的所有交易,首先需要在区块的块头中的Bloom filter中看看有没有我们要查找的交易的类型。

        如果有的话,再到区块中的每个交易对应的收据的Bloom filter中进行查找;

        而如果没有的话,那么该区块中一定没有我们要查找的交易类型。

    通过该种方法,就可以快速排除掉无关的收据,从而提高查找速度。

缺少状态

    ### 比特币

    UTXO只能是已花费或者未花费状态,这就没有给需要任何其它内部状态的多阶段合约或者脚本留出生存空间。

    二元状态与价值盲结合在一起意味着另一个重要的应用-取款限额-是不可能实现的。

区块链盲

    ### 比特币

    UTXO看不到区块链的数据,例如随机数和上一个区块的哈希。这一缺陷剥夺了脚本语言所拥有的基于随机性的潜在价值,严重地限制了博彩等其它领域应用。

GHOST协议

经典的Proof-of-Work(POW)是以取最长的主链为基本原则,GHOST协议则是以包含块数最多为基本原则。

以太坊平均10多秒发布一个区块,更短的出块时间意味着,临时性分叉的几率将大幅提升

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

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

Ghost协议的优势在于:

  • 以太坊十几秒的出块间隔,大大增加了孤块的产生,并且降低了安全性。通过鼓励引用叔块,使引用主链获得更多的安全保证(因为孤块本身也是合法的)

  • 比特币中,采矿中心化(大量的集中矿池)成为一个问题。Ghost协议下,叔块也是能够获得报酬,可以一定程度上缓解这个问题。

由于以上特性,以太坊的轻客户端比Bitcoin的轻客户端功能更强。Bitcoin的轻客户端 可以证明包含的交易,但是它不能进行涉及当前状态的证明 (如数字资产的持有,名称注册,金融合约的状态等)

MPT树

不同于交易历史记录,状态树需要经常地进行更新:账户余额和账户的随机数nonce经常会更变,更重要的是,新的账户会频繁地插入,存储的键( key)也会经常被插入以及删除。MPT的数据结构设计,使得我们可以在一次插入、更新编辑或者删除操作之后,快速地计算出新的树根(tree root),而无需重新计算整颗树。此外,它还有两个非常好的特性:

  1. 树的深度是有限制的。

    考虑到攻击者会故意地制造一些交易,使得这棵树尽可能地深,从而可以通过操纵树的深度,执行拒绝服务攻击(DOS attack),使得更新变得极其缓慢。

  1. 树的根只取决于数据,和其中的更新顺序无关。

    换个顺序进行更新,甚至重新从头计算树,并不会改变根。

Merkle Patricia Tree(又称为Merkle Patricia Trie)是一种经过改良的、融合了默克尔树和前缀树两种树结构优点的数据结构,是以太坊中用来组织管理账户数据、生成交易集合哈希的重要数据结构。

Trie树

Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。它有3个基本性质:

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。

  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

  3. 每个节点的所有子节点包含的字符都不相同。

缺点:

  • 直接查找效率较低

    前缀树的查找效率是O(m),m为所查找节点的key长度,而哈希表的查找效率为O(1)。且一次查找会有m次IO开销,相比于直接查找,无论是速率、还是对磁盘的压力都比较大。

  • 可能会造成空间浪费

    当存在一个节点,其key值内容很长(如一串很长的字符串),当树中没有与他相同前缀的分支时,为了存储该节点,需要创建许多非叶子节点来构建根节点到该节点间的路径,造成了存储空间的浪费。

Patricia树

Patricia trie,压缩前缀树,是一种更节省空间的Trie。对于基数树的每个节点,如果该节点是唯一的儿子的话,就和父节点合并。

MPT树

在以太坊中的MPT树中,树节点可以分为以下四类:

  • 空节点:(represented as the empty string)

  • 分支节点:A 17-item node[ v0 ... v15, vt ]

  • 叶子节点:A 2-item node[ encodedPath, value ]

  • 扩展节点:A 2-item node[ encodedPath, key ]

Key值编码

    #### Raw

    Raw编码就是原生的key值,不做任何改变。这种编码方式的key,是MPT对外提供接口的默认编码方式。

    > 例如一条key为“cat”,value为“dog”的数据项,其Raw编码就是['c', 'a', 't'],换成ASCII表示方式就是[63, 61, 74]

    #### Hex编码

    为了减少分支节点孩子的个数,需要将key的编码进行转换,将原key的高低四位分拆成两个字节进行存储。这种转换后的key的编码方式,就是Hex编码。

    从Raw编码向Hex编码的转换规则是:

    - 将Raw编码的每个字符,根据高4位低4位(nibble)拆成两个字节;

    - 若该Key对应的节点存储的是真实的数据项内容(即该节点是叶子节点 ),则在末位添加 10(一个ASCII值为16的字符)作为终止标志符;

    - 若该key对应的节点存储的是另外一个节点的哈希索引(即该节点是扩展节点),则不加任何字符;

    > key为“cat”, value为“dog”的数据项,其Hex编码为[6, 3, 6, 1, 7, 4, 10]

    Hex编码用于对内存中MPT树节点key进行编码

    #### Hex-Prefix编码

    MPT树中另外一个重要的概念是一个特殊的十六进制前缀(hex-prefix, HP)编码,用来对key进行编码。因为有两种[key,value]节点(叶节点和扩展节点),所以需要对它们进行区分。此时,引进一种特殊的标识(一个bit即可)来标识key所对应的是值是叶子,还是其他节点的hash。如果标识符是1,那么key对应的是叶节点,反之则是扩展节点。

    hex char bits | node type partial path length

    ----------------------------------------------------------

        0 0000 | extension even

        1 0001 | extension odd

        2 0010 | terminating (leaf) even

        3 0011 | terminating (leaf) odd

posted @ 2021-12-13 22:22  紫羊  阅读(84)  评论(0)    收藏  举报