区块链 2.0
以太坊概述
- 更改出块时间为15s,并使用一套相适应的GHOST协议(该协议非以太坊发明,只是改进)
- 更改mining puzzle,变成memory-hard mining puzzle,一定程度上ASIC-resistant
- 将来会将工作量证明Proof of Work替换成权益证明Proof of Stake,按照类似股份投票的方法决定下一区块如何产生
- 智能合约(Smart Contract):比特币实现货币的去中心化,智能合约实现合同的去中心化,从代码层面保证一开始就不会违约
- 以太坊block header中包含三个哈希值,分别对应状态树、交易树、收据树的根哈希
- 以太币没有矿工费定期减半的规定
以太坊账户
- 不同于比特币基于交易的账本,以太坊是基于账户的
- 分类:
- 外部账户(Externally Owned Account):包含账户余额(balance)及计数器(nonce)
- 智能合约账户(Smart Contract Account):包含账户余额(balance)、计数器(nonce)、代码(code,不变)和存储(storage,数据结构为MPT)
- 原因:对智能合约的支持要求账户、身份的稳定性
- 基于账户的账本天然地对双花攻击有抵抗性,但是却多了replay attack的风险
- 双花攻击:转账者是恶意节点,将手里的一笔钱花两次
- 重放攻击:收款人是恶意节点,将转账者的钱转两次到自己账上
- 重入攻击:利用智能合约中的安全漏洞,将合约中的余额多次转到黑客账户上
状态树
- 目的:实现账户地址到账户状态的映射(addr为160bit,40个16进制数)
- 数据结构
- 如果直接采用哈希表,那么如何签名?将整个哈希表做成merkel tree再取哈希代价太大,也不好修改
- 如果采用merkel tree,那么叶节点必须有序,否则各个区块链节点产生的根哈希不同。即使采用sorted Merkel Tree,插入新账户时,可能插在中间,那么大半棵树就要重新生成,代价太大。
- 前缀树(Trie):如字典单词go、god、genesis、generate
- 压缩前缀树(PT)(键值分布稀疏时,压缩效果较好)
- 以太坊采用的是(Modified )Merkel Patricia Trie(MPT):增加了Extension Node、Branch Node,用哈希指针代替普通PT中的指针
- 状态树中MPT是共享的,只有实际发生变更的节点需要新建分支
- 账户以key-value的形式存储在MPT中,key为账户hash,value以RLP(Recursive Length Prefix,feature:极简) 编码作序列化后存储
- 为什么状态树中不能只包含最近发生交易的账户?主要是不方便查询。e.g. A → B(10ETH),需要沿着链查找A、B账户余额。如果B为新账户,就需要一直搜索到创世纪块,效率太低
交易树&收据树
- 交易树包含这段时间内发布的新交易,与比特币的Merkel Tree类似;收据树包含所有交易执行的结果,主要是为了便于查找,特别是对于智能合约。
- 对比:状态树包含所有账户,会大量引用之前的MPT;交易树和状态树只包含新产生的交易。
- 交易树和收据树都采用MPT,可能只是为了代码的统一。此外,MPT支持自顶向下查找。
- bloom filter
- 支持比较高效地查找某笔交易是否在某个集合里
- 根据收据树的log生成。通过哈希函数对收据树中每笔交易进行处理,然后将哈希表中相应位置置1(初值0)
- 可能发生哈希碰撞,出现false positive。查询某笔交易时,hash后查询哈希表,若相应位置为0,说明该笔交易不存在;若为1,则可能存在,也可能不存在(只是跟其他交易发生了哈希碰撞)
- 由于哈希碰撞的存在,一般不支持删除操作。除非将哈希表元素改为计数器,但这会使数据结构复杂化
- 查找一段时间内某笔交易是否存在:先根据每个block header里的bloom filter过滤掉一定不存在该交易的区块,在从剩余block里仔细查找交易
GHOST协议
- 以太坊出块时间15s,比较短。容易出现多个分叉。如果继续采用BTC中的方式,对产生分叉区块的节点不公平。
- 最初的GHOST协议
- 每个区块可以包含两个上一代的叔父区块
- 被包含的uncle block获得7/8的出块奖励(block reward),新区块每包含一个uncle block,获得出块奖励的1/32
- 未被包含的uncle block没有奖励
- 叔父区块上的交易作废,无法得到矿工费(gas fee)
- 缺点:① uncle block个数大于2时,部分未能被包含 ②新节点故意不包含某个叔父区块
- 改进后的GHOST协议
- 新区块可以包含之多7代以内的叔父区块,最多6代叔父。uncle reward随代数递减7/8、6/8……2/8。以此鼓励叔父区块尽早合并
- 叔父区块分支上只有第一个区块能获得奖励,否则forking attack代价就太小了
挖矿算法
-
ASIC resistance —— memory hard mining puzzle
-
ASIC芯片算力强,但在内存访问速率上和普通芯片差距不大
-
莱特币(LiteCoin):追求ASIC/GPU Resistance。mining puzzle采用Scrypt,通过产生一个大的哈希表,同时算法中涉及到伪随机(后一个元素根据前一个元素生成),以此增大对内存的要求。但对轻节点不友好,所以LiteCoin将内存要求设为128k。但128k内存对GPU和ASIC的限制不大,后期GPU和ASIC挖矿均出现了
-
以太坊挖矿算法(ethash)用一大一小两个数据集——16M Cache和 1G dataset(DAG)(每3w区块,对seed取hash生成新seed,数据集大小增大初始大小的1/128,即128k和8M,以适应内存发展)
-
cache中第一个元素由seed取hash得到;后一个元素由前一个元素取hash得到
-
dataset中第i个元素从cache中取256次元素计算得到。dataset中的元素彼此独立,可以分别根据序号 i 和 cache 计算得到
-
对于每个nonce,根据header和nonce取hash,然后以伪随机的方法从dataset中取128个数(64次,每次2个数),最终得到一个hash。如最终的hash<=target,则满足要求
-
因为矿工需要验证非常多nonce,如果每次都从cache生成,效率太低,所以需要保存到内存。而轻节点只需验证一个nonce,所以只需根据cache计算即可
-
权益证明
智能合约
组成
- 智能合约是比特币和以太坊的最大区别
- 智能合约时运行在区块链上的一段代码,代码的逻辑定义了合约的内容
- 智能合约账户保存了智能合约当前的运行状态,包括:balance、nonce、code、storage
- 智能合约一般用solidity编写,语法和JavaScript类似
- 要运行函数能够接受外部转账,需要写出payable
- fallback()函数:
function() public [payable] { ... }
- 匿名函数,没有参数也没有返回值
- 两种情况会调用:
- 直接向合约地址转账而data域为空
- 调用的函数不存在
- 一般payable是需要的。否则转账金额不为零时抛出异常
调用流程
- 外部账户调用智能合约流程:创建一个交易,接收地址为要调用的智能合约账户地址,data域中填写要调用的函数及其参数的编码值
- 内部调用(一个智能合约调用另一个智能合约的函数):
- 直接调用:直接在合约B中使用另一个合约A的成员函数。但若调用的函数在执行中抛出异常,则B也会抛出异常。可以通过通过.gas()和.value()调整提供的gas或ETH
- 使用call()调用:在合约B中使用call(要调用的函数签名, 参数)调用函数,若调用的函数抛出异常,则call()返回false,否则为true。合约B不会抛出异常,而会正常执行。可以通过通过.gas()和.value()调整提供的gas或ETH
- 使用delegatecall():与call()类似,但不会切换上下文(余额、存储等),只会使用到调用的函数代码。不能通过.value()调整提供的gas或ETH
智能合约创建&运行
- 智能合约代码写完后,需要编译成bytecode
- 创建合约:外部账户发起一个转账交易到0x0地址
- 转账金额为0,但需支付汽油费
- 合约代码写在data域里
- 智能合约运行在EVM(Ethereum Virtual Machine),主要是为了增加可移植性
- 以太坊是一个交易驱动的状态机
- 调用智能合约的交易发布到区块链上后,每个矿工都会执行这个交易,从当前状态确定性的转移到下一个状态
- 汽油费:
- 智能合约是一个图灵完备的模型
- 出现死循环怎么办?
- 执行合约中的指令需要支付汽油费,汽油费由交易发起者支付。矿工一次性扣除price*gasLimit,再多退少补
- EVM中不同指令消耗的汽油费是不一样的——简单指令便宜,复杂指令贵
- 智能合约是一个图灵完备的模型
错误处理
-
以太坊中的交易具有原子性
-
智能合约中没有try-catch结构,但出现异常时,会发生回滚,恢复到交易前的状态
-
可以抛出错误的语句:
- assert:条件不满足则抛出,一般用于内部错误
- require:条件不满足则抛出,一般用于外部错误
- revert:无条件抛出,终止运行并回滚
-
嵌套调用(一个合约调用另一个合约中的函数)时,如果是直接调用,则调用者和被调用的函数都会连锁式回滚;如果是间接调用,比如用call(),则只会返回false,调用者不会回滚
-
一个合约向另一个合约账户中转账,仍可以发生嵌套调用(fallback函数)
-
地址类型
-
智能合约可以获得的调用信息
-
以太坊中先运行合约再挖矿,还是先挖矿再计算合约?答案:先运行合约,否则三个根哈希值不确定
-
每个节点都需要独立验证合约合法性
-
某个节点能否不进行验证,直接接受区块?-- 不行,不独立验证的话,本地的三个根哈希值无法更新
-
不支持多线程,因为产生的结果可能具有不确定性,其他节点无法验证。随机数同理,只能支持伪随机数。
-
合约代码在发布到区块链之前必须经过仔细的测试,否则一旦上链,即使是bug也无法修改
DAO
- DAO: Decentralized Autonomous Organization
- DAC: Decentralized Autonomous Corporation
- The DAO: 利于DAO理念的一个投资基金,由于受黑客重入攻击,只存活了3个月
反思
- Smart contract is anything but smart.
- Irrevocability is a double-edged sword.
- 不可篡改性保证了合约的公正性
- 当区块链中存在问题时,难以修改
- Nothing is revocable.
- 分叉攻击
- 比如The DAO事件中,以太坊团队通过软件升级回退交易
- Is solidity is the right programming language?
- What does decentralization mean? 存在分叉恰恰是一种民主的体现
- decentralized ≠ distributed
- 去中心化不一定是好的。The business model is bad, then it is bad in the internet.