落地区块链

以太坊原理研读笔记

2018-09-24  本文已影响14人  CodingCattwo

https://ethfans.org/posts/ethereum-whitepaper 以太坊白皮书

Merkle Tree

默克尔帕树(Merkle Patricia tree/trie)

  1. 由Alan Reiner提出设想,并在瑞波协议中得到实现,是以太坊的主要数据结构,用于存储所有账户状态,以及每个区块中的交易和收据数据。MPT是默克尔树和帕特里夏树的结合缩写;
  2. 每个唯一键值对唯一映射到根的hash值;在MPT中,不可能仅用一个键值对来欺骗成员(除非攻击者有~2^128 的算力);
  3. 增、删、改键值对的时间复杂度是对数级别。

MPT的具体设计决策

  1. 有两类节点KV节点和离散节点 KV节点的存在提高了效率,因为如果在特定区域树是稀疏的,KV节点可作为一个“快捷方式”,代替深度为64的树。

  2. 离散节点是16进制,不是二进制
    这样让查找更有效率,我们现在认识到这种选择并不理想,因为16进制树的查找效率在二进制中可以通过批次存储节点来模拟。MPT树的结构实现是非常容易出错的,最终至少会造成状态根不匹配。

  3. 空值(empty value)与非会员(non-membership)之间没有区别
    这样做是为了简化逻辑,以太坊中未设置的值默认为0,空字符串也用0表示。然而,需要强调的是,这样做牺牲了一些通用性,因而也不是最优的。

  4. 终节点和非终节点的区别
    技术上,标识一个节点“是否是终节点”是没必要的,因为以太坊中所有的树都被用于存储静态秘钥长度,但为了增加通用性,我们还是会添加这个标识,以期望以太坊的MPT的实现方式能够被其他加密货币原样采纳。

  5. 在安全树中采用SHA3(k)作为秘钥(在状态树和账户存储树中使用)
    使用SHA3(k),通过设置离散节点的链的深度为64,以及反复调用SLOAD 和 SSTORE指令,使那些企图对树采取Dos攻击的行为变得非常困难。不过,这也让穷举树的节点变得困难,如果你希望你的客户端有穷举的能力,最简单的方法是维持一个数据库映射:sha3(k) -> k

RLP

RLP(recursive length prefix)###

Trie 树的使用

状态树、交易树、收据树

  1. 以太坊区块链中每个区块头都包含指向三个树的指针:状态树、交易树、收据树。
  2. 状态树代表访问区块后的整个状态;
  3. 交易树代表区块中所有交易,这些交易由index索引作为key;(例如,k0:第一个执行的交易,k1:第二个执行的交易)
  4. 收据树代表每笔交易相应的收据。交易的收据是一个RLP编码的数据结构:
[ medstate, gas_used, logbloom, logs ]

其中:

过时区块处理

问题背景

GHOST协议

  1. 区块时间12s 选择12s是因为这已经是最快的时间了,基本上比网络延迟更长。在2013年的一份关于测量比特币网络延迟的论文中,确定了12.6秒是新产生的区块传播到95%节点的时间;
  2. 7个祖先块的限制 这样设计的目的是希望只保留少量区块,而将更早之前的区块清除。已经证明7个区块可以提供大部分所需的效果。
  3. 1个后裔区块的限制 如c(c(p(p(p(head))))) c=child, p = parent,就不合法,因为它有两个后裔区块。这样设计的目的是为了简单,上面的模拟结果显示它并没有构成大的中心化风险。
  4. uncle块要求具有有效性 uncle块必须是有效的header,而不是有效的区块。这样做也是为了简化,将区块链模型保持为线性数据结构。不过,要求uncle块是有效的区块也是合法的方法。
  5. 奖金分配 7/8的挖矿基础奖励分配给uncle块,1/32分给nephew块,它们交易费用都是0%。如果费用占多数,从中心化的角度看,这会使uncle块激励机制无效;然而,这也是为什么只要我们继续使用PoW,以太坊就会不断发行以太币的原因。

难度更新算法

目前以太坊通过以下规则进行难度更新:

diff(genesis) = 2^32
diff(block) = diff.block.parent + floor(diff.block.parent / 1024) *
    1 if block.timestamp - block.parent.timestamp < 9 else
    -1 if block.timestamp - block.parent.timestamp >= 9

难度更新规则的设计目标如下:

Gas与费用

以太坊交易费用的基本机制

交易费用需要考虑到账户的许多方面,包括宽带费用,存储费用和计算费用。尤其重要的是,以太坊编程语言是图灵完备的,所以交易会使用任意数量的宽带、存储和计算成本。这就可能会导致在计算成本过程中,突遭停电而计算被迫中止。

gas消耗计算的特点

gas机制的经济学原理

gas机制的另一个重要部分是gas价格本身体现出的经济学原理。

投票系统设定gas值

虚拟机EVM

EVM的设计

临时/永久存储的区别
我们先来看看什么是临时存储和永久存储。
临时存储:存在于VM的每个实例中,并在VM执行结束后消失;
永久存储:存在于区块链状态层。
假设执行下面的树(S代表永久存储,M代表临时存储):

  1. A调用B;
  2. B设置B.S[0]=5,B.M[0]=9 ;
  3. B调用C;
  4. C调用B。

此时,如果B试图读取B.S[0],它将得到B前面存入的数据,也就是5;但如果B试图读取B.M[0],它将得到0,因为B.M是临时存储,读取它的时候是虚拟机的一个新的实例。
在一个内部调用中,如果设置B.M[0] = 13和 B.S[0] = 17 ,然后内部调用和C的调用都终止,再执行B的外部调用,此时读取M,将会看到B.M[0] = 9(此值在上一次同一VM执行实例中设置的),B.S[0] = 17。如果B的外部调用结束,然后A再次调用B,将看到B.M[0] = 0,B.S[0] = 17。
这个区别的目的是:

  1. 每个执行实例都分配有内存空间,不会因为循环调用而减损,这让安全编程更加容易。
  2. 提供一个能够快速操作的内存形式:因为需要修改树,所以存储更新必然很慢。

栈/内存模式
早期,计算状态有三种:栈(stack,一个32字节标准的LIFO),内存(memory,可无限扩展的临时字节数组),存储(storage,永久存储)。在临时存储端,栈和内存的替代方案是memory-only范式,或者是寄存器和内存的混合体(两者区别不大,寄存器本质上也是一种内存)。在这种情况下,每个指令都有三个参数,例如:ADD R1 R2 R3: M[R1] = M[R2] + M[R3] 。选择栈范式的原因很明显,它使代码缩小了4倍。
单词大小32字节
在大多数结构中,如比特币,单词大小是4或8字节。4或8字节对存储地址或加密计算来说局限性太大了。而太大的值又很难建立相应安全的gas模型。32字节是一个理想大小,因为它足够存储下许多加密算法的实现以及地址,又不会因为太大而导致效率低下。

使用了可变、可扩展的内存大小
固定内存的大小是不必要的限制,太小或太大都不合适。如果内存大小是固定的,每次访问内存都需要检查访问是否超出边界,显然这样的效率并不高。

栈大小没有限制
没什么特别理由!许多情况下,该设计不是绝对必要的;因为,gas的开销和区块层gas的限制总是会充当每种资源消耗的上限。

1024调用深度限制
许多编程语言在栈的深度过大时触发中断比在内存过载时触发中断的策略要快的多。所以区块中gas限制所隐含的限制是不够的。

SIGNEXTEND
SIGNEXTEND操作码的作用是为了方便从大的有符号整数到小的有符号整数的类型转换。小的有符号整数是很有用的,因为未来的即时编译虚拟机可能会检测长时间运行的代码块,小的有符号整数能加快处理。

以太坊社区知识库

https://ethfans.org/wikis/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E8%AE%BE%E8%AE%A1%E5%8E%9F%E7%90%86

上一篇下一篇

猜你喜欢

热点阅读