干货以太坊设计原理Word格式文档下载.docx
- 文档编号:19981388
- 上传时间:2023-01-13
- 格式:DOCX
- 页数:10
- 大小:32.23KB
干货以太坊设计原理Word格式文档下载.docx
《干货以太坊设计原理Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《干货以太坊设计原理Word格式文档下载.docx(10页珍藏版)》请在冰豆网上搜索。
比如,在以太坊中就缺少类似比特币中的“时间锁定”功能。
但是,通过以下协议可以模拟出这个功能:
用户发送签名数据包到特定的合约中处理,如果数据包在特定合约中有效,则执行相应的函数。
5.没有风险规避机制如果风险的增加带来了可观的好处,我们愿意承担更高的风险(例如,广义状态转换,区块时间提升50倍,共识效率)。
这些原则指导着以太坊的发展,但它们并不是绝对的;
某些情况下,为了减少开发时间或者不希望一次作出过多改变,也会使我们推迟作出某些修改,把它留到将来的版本中去修改。
「区块链层协议」本节对以太坊中区块链层协议的改变进行了描述,包括区块和交易是如何工作的、数据如何序列化及存储、账户背后的机制。
账户,而非UTXO①
比特币及其许多衍生品,都将用户的余额信息存储在UTXO结构中,系统的整个状态由一系列的“有效的输出”组成(可以将这些“有效的输出”想象成钱币)。
每个UTXO都有拥有者和自身的价值属性。
一笔交易在消费若干个UTXO同时也会生成若干个新的UTXO。
“有效的输出”中有效需满足下面几点约束:
1.每个被引用的输入必须有效,且未被使用过;
2.交易的签名必须与每笔输入的所有者签名匹配;
3.输入的总值必须等于或大于输出的总值。
因此,比特币系统中,用户的“余额”是该用户的私钥能够有效签名的所有UTXO的总和。
下图展示了比特币系统中交易输入输出过程:
但是,以太坊抛弃了UTXO的方案,转而使用更简单的方法:
采用状态(state)的概念存储一系列账户,每个账户都有自己的余额,以及以太坊特有的数据(代码或内部存储器)。
如果交易发起方的账户余额足够支付交易费用,则交易有效,那么发起方账户会扣除相应金额,而接受账户则计入该金额。
某些情况下,接受账户内有需要执行的代码,则交易会触发该代码的执行,那么账户的内部存储器可能就会发生变化,甚至可能会创建额外的信息发送给其他账户,从而导致新的交易发生。
尽管以太坊没有采用UTXO的概念,但UTXO也不乏有一些优点:
1.较高程度的隐私保护如果用户每次交易都使用一个新的地址,那么账户之间的相互关联就很困难。
这样做适用于对安全性要求高的货币系统,但对任何dapp应用来说就不合适了。
因为dapp通常需要跟踪用户复杂的绑定状态,而dapp的状态并不能像货币系统中的状态那样简单地划分。
2.潜在地可扩展性UTXO在理论上可扩展性更好。
因为我们只能依靠那些金融货币拥有者来维护能够证明货币所有权的默克尔树,即使所有的人(包括数据的拥有者)都遗忘了某一数据,真正受损也只有数据的拥有者,其他人不受影响。
在以太坊账户系统中,如果一个账户对应在默克尔树中信息被所有人都丢失了,那么该账户将无法处理任何能够影响它的消息,包括发送给它的消息,它也无法处理。
不过,并非只有UTXO能够可扩展,也存在不依赖UTXO就能扩展的方式(此处没有扩展开来讲,译者注)。
账户的好处有以下几点:
1.节省大量空间如果一个账户有5个UTXO,则从UTXO模式转成账户模式所需空间会从300字节降到30字节。
具体计算如下:
300=(20328)*5(20是地址字节数,32是TX的id字节数,8是交易金额值字节数);
30=2082(20是地址字节数,8是交易金额值字节数,2是nonce②字节数)但实际节约并没有这么大,因为账户需要被存储在帕特里夏树中。
另外以太坊中交易也比比特币中的更小(以太坊中100字节,比特币中200-250字节),因为每次交易只需要生成一次引用,一次签名,以及一个输出。
2.可替代性更高在UTXO结构中,“有效输出”的源码实现中没有区块链层的概念,所以不管是在技术还是法律上,通过建立一个红名单/黑名单,并依据的这些“有效输出”的来源区分它们并不是很实际。
3.简单以太坊编码更简单、更易于理解,尤其是在涉及到复杂脚本时。
尽管任何去中心化应用都可以用UTXO方式来实现,但这种方式实质上是通过赋予一个脚本限制给定的UTXO能够使用以及请求的UTXO的种类的方式来实现,包括脚本评估的应用更改根状态的默克尔树证明。
因此,UTXO实现方式比以太坊使用账户的方式要复杂的多。
4.轻客户端轻客户端可以随时通过沿指定方向扫描状态树来访问与账户相关的所有数据。
在UTXO方式中,引用随着每个交易的变化而变化,这对于长时间运行并使用了上文提到的UTXO根状态传播机制的dapp应用来说,无疑是繁重的。
我们认为,账户的好处大大超过了其他方式,尤其是对于我们正在处理的包含任意状态和代码的dapp应用而言。
另外,本着“没有特点就是最大的特点”的指导原则,我们认为如果用户真的关心私密性,则可以通过合约中的签名数据包协议来建立一个加密“混合器”进行加密。
账户方式的一个弱点是:
为了阻止重播攻击,每笔交易必须有nonce,这就使得账户需要跟踪nonce的使用情况,并且只有在nonce最后使用后且值为1时才接受交易。
这就意味着,即使不再使用的账户,也不能从账户状态中移除。
解决这个问题的一个简单方法是让交易包含一个区块号,使它们在几个时间段内不重复,并且每个时间段重置nonce。
由于完全扫描账户的开销太大,所以为了删除未使用的账户,矿工或用户需要通过“Ping”操作来实现。
在1.0上我们没有实现这个机制,1.1及以上版本可能会使用这个机制。
「默克尔帕特里夏树」
默克尔帕特里夏树(MerklePatriciatree/trie),由AlanReiner提出设想,并在瑞波协议中得到实现,是以太坊的主要数据结构,用于存储所有账户状态,以及每个区块中的交易和收据数据。
MPT是默克尔树和帕特里夏树的结合缩写,结合这两种树创建的结构具有以下属性:
1.每个唯一键值对唯一映射到根的hash值;
在MPT中,不可能仅用一个键值对来欺骗成员(除非攻击者有~2^128的算力);
2.增、删、改键值对的时间复杂度是对数级别。
MPT为我们提供了一个高效、易更新、且代表整个状态树的“指纹”。
关于MPT更详细描述:
MPT的具体设计决策如下:
1.有两类节点KV节点和离散节点。
KV节点的存在提高了效率,因为如果在特定区域树是稀疏的,KV节点可作为一个“快捷方式”,代替深度为64的树。
2.离散节点是16进制,不是二进制这样让查找更有效率,我们现在认识到这种选择并不理想,因为16进制树的查找效率在二进制中可以通过批次存储节点来模拟。
MPT树的结构实现是非常容易出错的,最终至少会造成状态根不匹配。
3.空值(emptyvalue)与非会员(non-membership)之间没有区别这样做是为了简化逻辑,以太坊中未设置的值默认为0,空字符串也用0表示。
然而,需要强调的是,这样做牺牲了一些通用性,因而也不是最优的。
4.终节点和非终节点的区别技术上,标识一个节点“是否是终节点”是没必要的,因为以太坊中所有的树都被用于存储静态秘钥长度,但为了增加通用性,我们还是会添加这个标识,以期望以太坊的MPT的实现方式能够被其他加密货币原样采纳。
5.在安全树中采用SHA3(k)作为秘钥(在状态树和账户存储树中使用)使用SHA3(k),通过设置离散节点的链的深度为64,以及反复调用SLOAD和SSTORE指令,使那些企图对树采取Dos攻击的行为变得非常困难。
不过,这也让穷举树的节点变得困难,如果你希望你的客户端有穷举的能力,最简单的方法是维持一个数据库映射:
sha3(k)-&
gt;
k
「RLP」RLP(recursivelengthprefix):
递归长度前缀。
RLP编码是以太坊中主要的序列化格式,它的使用无处不在:
区块、交易、账户状态以及线路协议消息。
详见RLP正式描述:
RLP旨在成为高度简化的序列化格式,它唯一的目的是存储嵌套的字节数组③。
不同于protobuf、BSON等现有的解决方案,RLP并不定义任何指定的数据类型,如Boolean、floa、double或者integer。
它仅仅是以嵌套数组的形式存储结构,并将其留给协议来确定数组的含义。
RLP也没有明确支持map集合,半官方的建议是采用[[k1,v1],[k2,v2],...]的嵌套数组来表示键值对集合,k1,k2...按照字符串的标准排序。
与RLP具有相同功能的方案是protobuf或BSON,它们是一直被使用的算法。
然而,以太坊中,我们更偏向于使用RLP,因为:
1、它易于实现;
2、绝对保证字节的一致性。
许多语言的Map集合没有明确的排序,并且浮点格式有很多特殊情况,这可能造成相同数据却导致不同编码和hash值。
通过内部开发协议,我们能确保它是带着这些目标设计的(这是一般原则,也适用于代码的其他部分,如VM)。
BitTorrent使用的编码方式bencode也许可以替代RLP。
不过它采用的是十进制的编码方式,与采用二进制的RLP相比,稍微逊色了点。
「压缩算法」
线路协议和数据库都采用了一个自定义的压缩算法来存储数据。
该算法可描述为:
行程编码④值为0并同时保留其他值(除了一些特殊情况如sha3('
'
)),举例如下:
&
compress('
horse'
)'
donkeydragon1231231243'
\xf8\xaf\xf8\xab\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbe{b\xd5\xcd\x8d\x87\x97'
\xf8\xaf\xf8\xab\xa0\xfe\x9e\xbe{b\xd5\xcd\x8d\x87\x97'
\xc5\xd2F\x01\x86\xf7#&
lt;
\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82'
;
{\xfa\xd8\x04]\x85\xa4p'
)‘\xfe\x01'
压缩算法存在之前,以太坊协议的许多地方都有一些特殊情况,例如,sha3经常被覆盖,所以sha3('
'
)='
,这样不需要在账户中存储代码,可以节省64字节。
然而,最近所有这些使得以太坊数据结构变得臃肿的特殊情况都被删除了,取而代之的是将数据保存函数添加到区块链协议之外的层,也就是将其放入线路协议以及将其插入用户数据库实现。
这样增加了模块化能力,简化了共识层,使得压缩算法能持续更新以便相对容易部署。
「树(trie)的使用」
以太坊区块链中每个区块头都包含指向三个树的指针:
状态树、交易树、收据树。
状态树代表访问区块后的整个状态;
交易树代表区块中所有交易,这些交易由index索引作为key;
(例如,k0:
第一个执行的交易,k1:
第二个执行的交易)收据树代表每笔交易相应的收据。
交易的收据是一个RLP编码的数据结构:
[medstate,gas_used,logbloom,logs]
其中:
·
medstate:
交易处理后,树的根的状态;
gas_used:
交易处理后,gas的使用量;
logs:
是表格[address,[topic1,topic2...],data]元素的列表。
表格由交易执行期间调用的操作码LOG0...LOG4生成(包含主调用和子调用);
address是生成日志的合约的地址topics是最多4个32字节的值;
data是任意字节大小的数组;
logbloom:
交易中所有logs的address和topic组成的布隆过滤器⑤。
区块头中也存在一个布隆过滤器,它是区块中交易的所有布隆过滤器的逻辑或组成。
这样的构造使得以太坊协议轻客户端的使用尽可能的友好。
「Uncle块(过时区块)的奖励」
2013年10月,由乔森纳和特拉维夫首次提出的GHOST协议是一项不起的革新。
它是加快生成区块时间的第一个认真尝试。
因为区块在网络中传播需要一定时间,如果矿工A挖到一个区块并向全网广播,在广播的路上,B也挖出了区块,那么B的区块是过时的,且B的本次挖矿对网络的安全没有贡献。
GHOST的目的正是要解决挖矿过时造成的安全性降低的问题。
此外,还有一个中心化问题:
如果A是一个矿池,有30%的算力,B有10%的算力。
A有70%的时间产生过时的区块(因为另外的30%时间会产生最新区块,所以它会立即挖到数据),而B有90%的时间产生过时区块。
如果区块的产出时间间隔很短,那么过时率就会变高,则A凭借其更大的算力使挖矿效率也更高。
所以,区块生成过快,容易导致网络算力大的矿池控制挖矿过程。
根据乔森纳和特拉维夫的描述,GHOST解决了在计算哪个链是最长的链的过程中,因产生过时区块而造成的网络安全性下降的问题。
也就是说,不仅是父区块和更早的区块,同时Uncle区块⑥也被添加到计算哪个块具有最大的工作量证明中去。
为了解决第二个问题:
中心化问题,我们采用不同的策略:
对过时区块也提供区块奖励:
挖到过时区块的奖励是该区块基础奖励的7/8;
挖到包含过时区块的nephew区块将收到1/32的基础奖励作为赏金。
但是,交易费并不奖励给Uncle区块或nephew区块。
在以太坊,过时分叉上7代内的亲属区块才能称作过时区块。
之所以这样限制是因为,首先,GHOST协议若不限制过时区块数量,将会花费大量开销在计算过时区块的有效性上;
其次,无限制的过时区块激励政策会让矿工失去在主链上挖矿的热情;
最后,计算表明,过时区块奖励政策限制在7层内提供了大部分所需的效果,而且不会带来负面效应。
区块时间算法的设计决策包括:
区块时间12s:
选择12s是因为这已经是最快的时间了,基本上比网络延迟更长。
在2013年的一份关于测量比特币网络延迟的论文中,确定了12.6秒是新产生的区块传播到95%节点的时间;
然而,该论文还指出传播时间与区块大小成比例,因此在更快的货币中,我们可以期待传播时间大大减少。
传播间隔时间是恒定的,约为2秒。
然而,为了安全起见,在我们的分析中,我们假定区块的传播需要12秒。
7个祖先块的限制:
这样设计的目的是希望只保留少量区块,而将更早之前的区块清除。
已经证明7个区块可以提供大部分所需的效果。
1个后裔区块的限制:
如c(c(p(p(p(head)))))c=child,p=parent,就不合法,因为它有两个后裔区块。
这样设计的目的是为了简单,上面的模拟结果显示它并没有构成大的中心化风险。
uncle块要求具有有效性:
uncle块必须是有效的header,而不是有效的区块。
这样做也是为了简化,将区块链模型保持为线性数据结构。
不过,要求uncle块是有效的区块也是合法的方法。
奖金分配:
7/8的挖矿基础奖励分配给uncle块,1/32分给nephew块,它们交易费用都是0%。
如果费用占多数,从中心化的角度看,这会使uncle块激励机制无效;
然而,这也是为什么只要我们继续使用PoW,以太坊就会不断发行以太币的原因。
「难度更新算法」
目前以太坊通过以下规则进行难度更新:
diff(genesis)=2^32diff(block)=diff.block.parentfloor(diff.block.parent/1024)*1ifblock.timestamp-block.parent.timestamp&
9else-1ifblock.timestamp-block.parent.timestamp&
=9难度更新规则的设计目标如下:
快速更新:
区块间的时间应该随着hash算力的增减而快速调整;
低波动性:
如果Hash算力恒定,那么难度不应剧烈波动;
简单:
算法的实现应相对简单;
低内存:
算法不应依赖于过多的历史区块,要尽可能少的使用”内存变量“。
假设有最新的十个区块,将存储在这十个区块头部的内存变量相加,这些区块都可用于算法的计算;
非开发性:
算法不应让矿工有过多篡改时间戳或者矿池、反复添加或删除算力的能力,以使他们的收益最大化。
我们当前的算法在低波动性和非开发性上并不理想,至少我们计划切换时间戳比较父区块和祖父区块,所以矿工只有在连续挖2个区块时,才有动力去修改时间戳。
另一个更强大的模拟公式:
「Gas和费用」比特币中所有交易大体相同,因此它们的网络成本可以建成一个模型。
以太坊中的交易要更复杂,所以交易费用需要考虑到账户的许多方面,包括宽带费用,存储费用和计算费用。
尤其重要的是,以太坊编程语言是图灵完备的,所以交易会使用任意数量的宽带、存储和计算成本。
这就可能会导致在计算成本过程中,突遭停电而计算被迫中止。
以太坊交易费用的基本机制如下:
每笔交易必须指明一定数量的gas(即指定startgas的值),以及支付每单元gas所需费用(即gasprice),在交易执行开始时,startgas*gasprice价值的以太币会从发送者账户中扣除;
交易执行期间的所有操作,包括读写数据库、发送消息以及每一步的计算都会消耗一定数量的gas;
如果交易执行完毕,消耗的gas值小于指定的限制值,则交易执行正常,并将剩余的gas值赋予变量gas_rem;
在交易完成后,发送者会收到返回的gas_rem*gasprice价值的以太币,而给矿工的奖励是(startgas-gas_rem)*gasprice价值的以太币;
如果交易执行中,gas消耗殆尽,则所有的执行恢复原样,但交易仍然有效,只是交易的唯一结果是将startgas*gasprice价值的以太币支付给矿工,其他不变;
当一个合约发送消息给另一个合约,可以对这个消息引起的子执行设置一个gas限制。
如果子执行耗尽了gas,则子执行恢复原样,但gas仍然消耗。
上述提到的几点都是必须满足的,例如:
如果交易没有指定gas限制,那么恶意用户就会发送一个有数十亿步循环的交易。
没有人能够处理这样的交易,因为处理这样的交易花的时间可能很长很长,从而无法预先告知网络上的矿工,这会导致拒绝服务的漏洞产生。
替代严格的gas计数、时间限制等机制的方案不起作用,因为它们太主观了;
startgas*gasprice的整个值,在开始时就应该设置好,这样不至于在交易执行中因gas不够而造成交易终止。
注意,仅仅检查账户余额是不够的,因为账户可以在其他地方发送余额。
如果在gas不够的情况下,交易执行没有恢复操作(回滚),合约必须采用强有力的安全措施来防止合约发生变化。
如果子限制不存在,则恶意账户会通过与其他账户达成协议来对它们采取拒绝服务攻击。
在计算开始时插入一个大循环,那么发送消息给受害合约或者受害合约的任何补救尝试,都会使整个交易死锁。
要求交易发送者而不是合约来支付gas,这样大大增加了开发人员的可操作性。
以太坊早期的版本是由合约来支付gas的,这导致了一个相当严重的问题:
每个合约必须实现“守护”代码,确保每个传入的消息有足够的以太币供其消耗。
gas消耗计算有以下特点:
对于任何交易,都将收取21000gas的基本费用。
这些费用可用于支付运行椭圆曲线算法所需的费用。
该算法旨在从签名中恢复发送者的地址以及存储交易所花费的硬盘和带宽空间。
交易可以包括无限量的“数据”。
虚拟机中的某些操作码,可以让合约允许交易对这些数据的访问。
数据的固定消耗计算是:
每个零字节4gas,非零字节68gas。
这个公式的产生是因为合约中大部分的交易数据由一些列的32字节的参数组成,其中多数参数具有许多前导零字节。
该结构看起来似乎效率不高,但由于压缩算法的存在,实际上还是很有效率的。
我们希望此
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 干货 以太坊设计原理 以太 设计 原理