一、以太坊
比特币被称为区块链1.0,以太坊被称为区块链2.0
以太坊的符号是ETH,以太币的符号是Ether,单位是Wei;比特币的符号是BTC,单位是Satoshi
以太坊做出的改进:
在以太坊中出块时间减少到十几秒
比特币的mining puzzle是计算密集型,比拼的是计算哈希值的算力;以太坊使用的memory hard minig puzzle在一定程度上限制了asic芯片的使用(即asic resistance)
使用权益证明(proof of stake)代替工作量证明(proof of work)
对智能合约的支持(smart contract)
*智能合约的目的是通过技术手段保证合同的参与方在一开始就不可能违约
二、账户
以太坊中的交易使用的是基于账户的模型,可以显示余额,每次交易中也不用说明币的来源。这种模型对双花攻击有天然的防御作用,但需要额外在交易发布时添加付款方账户上总交易数量(nonce),防止另一种重放攻击(replay attack)
以太坊中的账户类型有外部账户(普通账户)和合约账户。外部账户具有balance和nonce,合约账户也具有nonce,但他不能自主地发起一个交易,交易只能通过普通账户发起,但合约账户是可以调用另一个合约账户的。除此之外约合账户还有代码(code)和storage(存储)。
创建合约的时候会返回一个地址,通过合约地址就可以调用这个合约,调用过程中状态发生变化,代码不变,存储发生变化。
比特币基于交易的模型能提供很好的隐私保护,但以太坊要求参与者具有稳定的身份。
三、状态树
1.Trie(字典树,前缀树)
举例:general、genesis、go、god、good
Trie树可以利用字符串的公共前缀来节约存储空间。如果系统中存在大量字符串且这些字符串基本没有公共前缀,则相应的Trie树将非常消耗内存,这也是Trie树的一个缺点
特点1:Trie的每个节点的分叉数目取决于key值里每个元素的取值范围
这个例子当中,每个都是英文单词,而且是小写的,所以每个节点的分叉数目最多是26个,加上一个结束标志位(表示这个单词到这个地方就结束了)
在以太坊中地址是表示成40个十六进制的数,因此分叉数目(branching factor)是17(十六进制的0~f,再加上结束标志位)
特点2:Trie的查找效率取决于key的长度。键值越长,查找需要访问内存的次数就越多
在这个例子当中,不同的单词键值长度是不同的
在以太坊中,所有键值都是40,因为地址都是40位十六进制的数
特点3:只要两个地址不一样,最后肯定映射到树中的不同分支,所以Trie是不会出现碰撞的
特点4:不同的节点,不论按照什么顺序插入这些账户,最后构造出来的树是一样的
特点5:更新操作的局部性很好
每次发布一个区块,系统中绝大多数账户的状态是不变的,只有个别受到影响的账户才会变
缺点:存储浪费
Patricia Tree/Patricia Trie
注意:对于Patricia Tree来说,新插入一个单词时,原来压缩的路径可能需要扩展开
键值分布比较稀疏的时候,路径压缩效果比较好,而以太坊中账户的地址是160位的,全世界的账户数目远小于2的160次方
3.MPT(Merkle Patricia Tree)
Merkle Tree和Binary Tree的区别:
就是区块链与普通链表的区别,把普通指针换成了哈希指针
Merkle Patricia就是所有的账户组织成一个Patricia Tree,用路径压缩提高效率,然后把普通指针换成哈希指针
账户状态最后组织成了一个Merkle Patricia Tree,状态树的根哈希值的作用:
作用1:防止篡改
只要根哈希值不变,整个树的任何部分都没有办法被篡改,也就是说每个账户的状态都能保证是没有被篡改过的
作用2:Merkle proof
1)能证明账户的余额是多少
你这个账户所在的分支自己向上作为Merkle proof发给轻节点,轻节点可以验证你的账户上有多少钱
2)能证明某个账户是不存在的
Sorted Merkle Tree的一个作用是能证明non-membership,这里的证明方法跟Sorted Merkle Tree类似
4.Modified MPT
以太坊中用到的不是原生的MPT,是Modified MPT,就是对MPT的结构做一些修改,这些修改不是很本质的修改
如果这个树中出现了路径压缩就会有一个Extension Node(扩展节点)
这个Modified MPT树的根节点取哈希之后得到的一个根哈希值,是要写在块头里的(左上角)
用的也是哈希指针
每次发布一个新的区块的时候,状态树中有一些节点的值会发生变化,这些改变不是在原地改,而是新建一些分支,原来的状态其实是保留下来的
可以看到,虽然每一个区块都有一个状态树,但是这两棵树的大部分节点是共享的。右边这棵树主要都是指向左边这棵树的节点,只有那些发生改变的节点是需要新建一个分支
系统当中有时候会出现分叉,临时性的分叉是很普遍的。以太坊把出块时间降低到十几秒之后,这种临时性的分叉是常态,因为区块在网上传播时间可能也需要十几秒,有时候可能要把当前状态退回到没有处理到这个区块中交易的前一个状态,要实现回滚就是要维护这些历史纪录
简单的转账交易回滚其实是比较容易的,以太坊中有智能合约。智能合约是图灵完备的,编程功能是很强的。从理论上说,可以实现很复杂的功能,跟比特币简单的脚本还不太一样。以太坊中如果不保存前面的状态,智能合约执行完之后,想再推算出前面是什么状态,这是不可能的,所以要想支持回滚,必须保存历史状态
5.以太坊的数据结构实现
1)block header的结构
ParentHash父区块块头的哈希值,是区块链中前一个区块块头的哈希值
UncleHash叔父区块块头的哈希值。以太坊中Uncle和Parent不一定是一个辈分的,Uncle比Parent可能大好多辈分
Coinbase挖出这个区块的矿工的地址
Root状态树的根哈希值
TxHash交易树的根哈希值(类似比特币系统中的那个根哈希值)
ReceiptHash收据树的根哈希值
Bloom布隆过滤器,提供一种高效的查询符合某种条件的交易的执行结果(跟收据树是相关的)
Diffculty挖矿难度,要根据需要调整
GasLimit单个区块允许的最多Gas总量(智能合约要消耗汽油费,类似于比特币中的交易费)
GasUsed该交易消耗的总Gas数量
Time区块的大致的产生时间
Nonce是挖矿时猜的那个随机数(类似于比特币的挖矿),以太坊中的挖矿也是要猜很多个随机数,写在块头里的随机数是最后找到的,符合难度要求的
MixDigest混合摘要,从nonce这个随机数经过一些计算,算出一个哈希值
2)区块的结构
header指向block header的指针
uncles指向叔父区块的header的指针,是个数组,因为一个区块可以有多个叔父区块
transactions这个区块中交易的列表
extblock是区块在网上发布的信息,就是block中的前三项会真正发布出去
3)状态树中的value的存储:RLP
状态树中保存的是key value pair对。key就是地址,前面主要讲的是键值,这个地址的管理方式
那么这个value呢,这个账户的状态呢,是怎么存储在状态树当中的呢?实际上是要经过一个序列化(RLP)的过程,然后再存储
RLP:Recursive Length Prefix,递归长度前缀,是一种序列化方法。特点是简单,极简主义,越简单越好
Protocal buffer:简称Protobuf,是个很有名的做序列化的库
跟这些库相比,RLP的理念就是越简单越好。它只支持一种类型:nested array bytes,嵌套数组字节。一个一个字节组成的数组,可以嵌套。以太坊里的所有的其他类型,比如整数或者比较复杂的哈希表,最后都要变成nested array bytes
所以实现RLP要比实现Protocal buffer简单很多,因为难的东西,都推给应用层了
四、交易树和收据树
交易树和收据树同样采用MPT的数据结构。每发布一个新区块,都会将其中包含的交易组织成一个交易树。此外,以太坊还增加了一个收据树,每个交易执行完之后会形成一个收据,记录交易的相关信息,交易树和收据树上的节点是一一对应的。由于以太坊智能合约执行较为复杂,通过增加收据树,便于快速查询执行结果
bloom filter
bloom filter支持比较高效的查找某个元素是不是在一个比较大的集合里面,将这个集合计算出一个紧凑的摘要,比如使用哈希函数将集合中的每个元素映射到一个向量,映射到的相应位置记为1,否则为0。当需要查找的一个元素计算后在向量的相应位置为0,则该元素一定不在集合内,但如果为1可能会出现误报
一个交易执行完就会形成一个收据,这个收据里面就会包含一个bloom filter,记录这个交易的类型、地址等其他信息,发布的区块在块头里也有一个总的bloom filter,是这个区块里所有交易的bloom filter的并集
以太坊的运作过程可以把它看成是交易驱动的状态机,他的状态转移都得是确定性的。以太坊和比特币中创建账户是不需要通知其他人的,只有这个账户第一次收到钱的时候,其他的节点才会知道这个账户的存在,这个时候才要在状态树中新插入的一个节点
问:能不能把状态树的设计改一下,改成每个区块的状态树也只包含这个区块中的交易相关的那些账户的状态,这样就跟交易树和收据树一致了,而且可以大幅度的削减每个区块所对应的状态树的大小,因为大部分的账户状态是不会变的?
这样设计的结果是每个区块没有一棵完整的状态树,只有当前区块中所包含的交易涉及到的账户的状态
这么设计的一个问题就是,如果要想查找某个账户的状态就不方便了。如果一个账户有较长的一段时间没有发生交易,要找它的账户信息,可能要从后往前,扫描很多个区块,才能找到最近一次的账户状态
还有一个更大的问题,如果交易中的账户是个新建的账户,这个时候就要找到创世纪块去,从当前区块一直找到创世纪块,发现这个账户没有,才知道原来是个新建的账户
五、GHOST协议
不在最长链上的区块就是orphan block(孤儿块) 或者stale block,由于系统中经常性会出现分叉,你挖到的区块最后没有成为最长合法链,则矿工挖到矿很大可能会被废弃,对矿工来说不公平,这会大大降低矿工挖矿积极性。而对于个人矿工来说,和大型矿池相比更是存在天然劣势。
最初版本的ghost协议为了补偿这些区块所属矿工所作的工作,给这些区块一些“补偿”,并称其为"Uncle Block"(叔父区块),下一个区块可以将两个叔父区块包含在内,被包含的叔父区块可以获得出块奖励的7/8,同时新挖出的区块每包含一个叔父区块就可以额外获得1/32的出块奖励,但最多只能包含两个叔父区块。
最初版本ghost协议的缺陷:
1)因为叔父区块最多只能包含两个,如果出现3个怎么办?或者新的区块已经发布完了,然后才收到叔父区块,那这个新听到的区块又变成了丢弃的区块
2)矿工出于商业利益,故意不包含叔父区块,导致叔父区块(对手)可以得到的7/8出块奖励没了,而自己仅仅损失1/32。如果甲、乙两个大型矿池存在竞争关系,那么他们可以采用故意不包含对方的叔父区块,因为这样对自己损失小而对对方损失大
新版本的ghost协议将叔父的概念扩展,隔几代后听到的区块仍然可以是叔父区块,但隔一代叔父区块的奖励为7/8,隔两代的奖励变为6/8...以此类推,叔父区块最多隔七代,而包含叔父区块的新区块获得奖励保持1/32不变。另外,如果叔父区块后跟随一长串区块,只有刚开始出现分叉的第一个区块可以获得安慰的奖励。
六、挖矿算法
经过长时间的检验,比特币的挖矿算法基本认为是安全的,但比特币系统存在的缺点是挖矿设备的专业化,必须用asic芯片挖矿,这与去中心化的理念和比特币的设计初衷是背道而驰的,同时让普通老百姓参与到挖矿还能使算力分散,增加系统的安全性。
在比特币之后的一些加密货币在设计mining puzzle时需要考虑的一点便是asic resistance,有一种方法是增加挖矿对内存的需求。曾经市值仅次于比特币的莱特币(LiteCoin)使用的是Scrypt,一个对内存要求很高的哈希函数
以太坊中有两个数据集,一个是16M的cache,一个是1G的dataset,叫做DAG。为方便验证,轻节点只需保存前者,而矿工需要保存后者大数据集。前者对于一个种子节点算出一个数据存放在数组的第一个元素,然后依次取哈希,后面每个元素都由前一个元素取哈希得到,从前往后填充尾随基数就得到一个cache。莱特币是直接按照伪随机的顺序进行计算,以太坊是先生成一个更大的数组,每个元素是在小数组中为随机的顺序取一个数,通过对哈希值进行一些更新迭代算出下一个要读取的位置,一直重复此过程读取256个数作为大数组的第一个元素,第二个元素亦是如此...求解puzzle的时候用的是大数据集中的元素,先根据节点的block header和nonce值算一个出事的哈希值,取出映射到数组中某个位置的元素和它相邻的元素,读取之后进行一些运算算出下一个读取的位置,也是一次读取两个元素,一直重复64次,取出128个元素。最后算出的哈希值与挖矿难度的目标阈值比较是否符合,不符合的话更换nonce继续计算。两个数据集的大小都是在不断扩大的。
伪代码如下
以太坊的挖矿算法是ethash,对内存有较高要求,目前以太坊主流挖矿设备是gpu挖矿。
以太坊采用了预挖矿(pre-mining)的过程,在当初发行货币的时候预留一部分货币给以太坊的开发者,与之相关的一个概念pre-sale是将预留的货币通过出售的方式用于加密货币的开发工作
部分人认为如果让通用计算机都能参与挖矿反而会影响安全性,因为使用asic芯片挖矿,发动攻击需要投入大量的硬件成本,一种asic芯片只能挖一种加密货币,除此之外别无他用。而且就算发动攻击成功,比特币系统被证实不安全,货币价格跳水,早期投入的硬件成本也收不回来了,采用通用计算器挖矿硬件成本就降低了。
七、难度调整
比特币中每2016个区块会调整挖矿难度保持出块时间在10分钟左右,以太坊是每个区块都有可能调整挖矿难度。以太坊中区块难度调整的具体代码如下
sigma的取值与两个因素有关,分别是出块时间和有无叔父区块,因为如果当前区块链的最后一个区块包含有叔父区块货币的总供应量是增加的,为了维持货币总供应量的稳定,当前正在挖的区块的挖矿难度要提升一个的单位。max函数中前一个式子是正数说明难度要上调,如果是负数说明难度要下调,其中如果包含叔父区块y等于2,没有等于1。-99是难度调整的下限。
难度炸弹
难度炸弹的设计是为了确保所有人都转入权益证明,防止出现硬分叉。难度炸弹呈指数增长,在前期几乎为0,当难度炸弹发挥威力的时候刚好是以太坊转入权益证明的时候。因为权益证明上线的一再推迟,难度炸弹的计算公式由原来的区号Hi转变为当前区号回退三百万
八、权益证明(Proof of stake)
基于工作量证明的共识机制受到普遍批评浪费电。以太坊完成一个交易的时候有可能会调用智能合约,但以太坊每个交易耗电量远小于比特币中一个交易的耗电量,因为比特币的出块时间长,挖矿需要的能耗就大。权益证明的基本思想就是与其投入资金挖矿不如从一开始就比拼钱,这也称为virtual mining。这样做的有点除了减少能耗、保护环境,也有利于维护区块链安全的资源形成一个闭环。挖矿是与加密货币生态系统之外所联系的,若想对区块链发起攻击可以通过聚集大量的资金的方式实现,但如果是权益证明攻击者就不能与区块链发生直接联系,发动攻击首先需要买入大量的币,但这样又会使价格大涨。
以太坊使用的权益证明协议叫做Casper the Frendly Finality Gadget(FFG)。finiality使一种最终的状态,包含在其中的交易不会被取消。Validator(验证者)是新引入的概念,成为验证者需要投入一定的以太币作为保证金,保证金会被系统锁定。验证者的职责是推动系统达成共识,投票哪条链是最长合法链,投票的权重取决于保证金的大小。以前的协议规定混用挖矿的时候每100个区块就会产生一个epoch,决定他能不能成为finality要进行投票,每次有两轮投票第一次是prepare message,第二次是commit message。但是实际中每50个区块形成一个epoch,每次只进行一次投票,对于当前的epoch是prepare message,对下一个epoch是commit message。
九、智能合约
智能合约是运行在区块链上的一段代码,代码的逻辑定义了合约的内容。智能合约的账户保存了合约当前的运行状态
一次a对b的转账交易,如果b是普通账户,那他就是一次普通转账交易;如果b是合约账户,实际上是对b合约的调用,调用的函数及参数在数据域中说明
除了外部账户能调用合约之外,一个合约也可以调用另一个合约函数
直接调用
合约账户不能主动发起一个交易,只有外部账户可以发起一个交易
2.address.call()
3.delegatecall
一个合约账户能接受外部转账的函数必须标注成payable
调用合约时如果未说明调用的函数或没找到所调用的函数就会调用fallback()函数
创建智能合约
直接调用会引起连锁式回滚,address.call的方式不会
block header中的gas limit是这个区块里所有交易能够消耗的汽油费的上限,gas used是消耗的所有汽油费之和。每个区块可以在上一个区块的gas limit的基础上上调或者下调1/1024,因为以太坊的出快速度很快,gas limit趋于所有矿工的平均意见
每个全节点必须先执行交易再挖矿,这是因为以太坊挖矿需要先确定Root、TxHash、ReceiptHash的值,block header的内容才能被确定,之后才能开始挖矿。对于已经在本地执行了交易但没挖到矿的节点,在别人发布区块后需要在本地将发布区块里的内容在执行一遍,更新本地三棵树的内容,与发布区块中的跟哈希值比较,并且这一切都是免费的没有任何补偿。
未挖到矿的全节点必须要进行验证这一步骤,因为只有将所有的交易重新执行一遍,更新本地三棵树的内容才能继续挖下一个区块,不然挖出的区块被人认为是错误的。
发布出来的交易不一定全是成功执行的,因为智能合约执行错误的交易如果不发布到区块链上汽油费就扣不掉
每个交易执行完后形成一个收据,收据中status域告知了这个交易执行的情况
智能合约不支持多线程,以太坊是交易驱动的状态机,每个合约执行的下一个状态必须是确定的,但多个核对内存访问顺序可能是不同的,执行结果有可能是不确定的。除多线程之外,任何导致执行结果不确定的操作都是不支持的,例如随机数,以太坊使用的是伪随机数,不能让每个全节点的执行结果不一致
重入攻击到最后会执行到合约账户余额不足、汽油费不足或者调用栈溢出。可以先将黑客的账户余额清零或者使用transfer函数使得发送的汽油费不足以调用下一个合约
十、TheDAO:Decentralized Autonomous Organization
去中心化的自治组织,DAO这个组织建立在代码基础上,组织的规章制度写在代码里,通过区块链的共识机制来维护这种规章制度的正常执行。
The DAO是致力于众筹投资的DAO,用来投资项目,但资金是在区块链上众筹的方法获得的,它本质上是一个运行在以太坊上的智能合约。参与The DAO可以将以太币发给这个智能合约换取代币,代币越多在选择投资项目时投票的权重越大,收益由智能合约上的规章制度分配。
若想取回自己的钱,是通过拆分的方法,但拆分的方法并不是单纯取回你的收益,而是通过一种建立子基金的方法叫做split DAO。同样如果有部分人与其他人的投资理念不同,也可以通过拆分DAO的方式建立自己的子基金。拆分前有一个辩论期,可以讨论这个拆分好不好以及决定是否加入,拆分后有一个28天的锁定期,The DAO会把代币收回而将相应的以太币打到子基金中,之后才能取回自己的以太币。
在The DAO被黑客攻击之后,以太坊团队先后推出了软分叉和硬分叉的方案,软分叉的方案出现了bug,而硬分叉之后以太坊分为了两条链,旧链上的货币叫做ETC,新链上的货币仍叫ETH。
十一、反思
智能合约并不智能,它更是一个自动化合约,一个代码合同
不可篡改性是一个双刃剑
没什么是真的不可篡改的
solidity的设计与常规思维是不符的,容易忽视代码编写上的bug
Many eyeball fallacy
以太坊团队并不能强迫大家硬分叉,分叉对于区块链来说反而是民主的体现
去中心化不等于分布式,区块链上的去中心化由多台计算机共同维护一个状态,而一般的分布式计算机执行的是不同的工作
十二、美链
美链中batch transfer函数的内容
如果每个人需要发送的代币value很大,最终算出的amout可能会溢出变成一个很小的数,但下面每个人发送的value不变,相当于系统凭空发行了很多代币