最近,以太坊又一次出现大拥堵,美图基于以太坊框架实现了 DPoS 算法并且对代码进行了开源(链接见文末),希望借助此方案能让以太坊发展有更多的选择的可能。
图:最近一周以太坊交易又出现大范围拥堵
有些人对于以太坊不是特别熟悉,所以开始之前先简单介绍一下以太坊一些基础。
以太坊整体包含几个模块:
协议管理,一个是外部访问和交互的 JSON-RPC 协议(HTTP),一个用来节点发现和块/交易数据(TCP/UDP) 同步;
交易池,存储当前未打包的交易;
共识算法,目前官方版本里面包含了 PoW 和 PoA(Clique), 共识算法主要的作用就是决定产生新块,合法性验证以及冲突解决;
EVM, 用来执行智能合约代码的虚拟机
StateDB, 由 MPT(merkle patric trie) 和 KV 存储两部分组成,以太坊所有数据最终都会落到 KV 存储。
钱包通过 JSON-RPC 将签名过的交易发送到交易池,然后由共识算法来决定能否出块以及时机(非挖矿节点不会主动出块,只有被动接收块),然后将块写到 DB。
由于以太坊是根据 Gas Price 决定优先打包哪些交易,所以我们可以看到以太坊的交易池里面会有一个最大堆用来放当前价格较高的交易 ID。
另外,为了避免交易重放问题,以太坊还在账号里面引入了 nonce,用来表示这个是当前用户发出的交易编号(从 0 开始严格 +1 递增)。比如 A 给 B, C 两个人转账的两笔交易分别是 tx1(nonce=1) 和 tx2 (nonce=2), 这两笔交易实际上是有依赖关系的,节点在未收到 nonce = 1 这笔交易的情况,nonce = 2 是不能被打包的(假设当前节点里面 A 账号 nonce = 0)。
所以就有了我们下面图中的 Queued Pool 和 Peding Pool, 如果一笔交易过来会先到 Queued Pool, 如果 nonce 值符合要求那么会迁移到 Pending Pool, Pending Pool 里面所有交易都是认为可打包的。Pending Pool 也可能因为块回滚会导致交易重新回到 Queued Pool。
接着就由共识算法部分来决定当前节点能否出块以及出块的时机,这个不同共识算法不太一样,这里不进行详细描述,最终被打包的块就会写入到 DB 里面来,至于 DB 里面如何存储这些账号以及对应的资产,不同账号模型存储实现不一样。
比特币的账号模型是 UTXOs(unspent transaction ouputs), 下面的图来自 Ethereum Design Rationale。
关于优缺点下面引用的以太坊设计文档里面也有详细的的描述,我这里先不罗列。主要先说明两种的区别:
Account 账号模型跟我们传统业务类似很好理解,就是直接 DB 里面存储余额但也引入其他问题,比如本身无法判断交易是否重放问题,需要其他手段比如 account nonce 来辅助解决。
UTXOs 模型由上图可以看到存储不再是余额,而是 unspent transaction outputs, 这个机制可以简单理解就是一次性的支票,收款的类似别给你签完名一张一次性的支票(只能被使用一次),转账也是类似,看看自己有哪些面额的支票,找出足额的支票签名后放到一起给对方。
相关索引: Ethereum Design Rationale
https://github.com/ethereum/wiki/wiki/Design-Rationale。
接着引申一个问题就是如何存储账号的余额?
以太坊的账号余额数据是存储在由 Patricia Trie 和 Merkle Trie 组成的(MPT)树上,主要利用 Patricia Trie 的特性实现账号余额的快速查找和 Merkle Trie 来证明交易是否被篡改过。Patricia Trie 和 Merkle Trie 之前在很多非区块链场景都使用到了(比如 kernel 的新 ip 路由查找算法使用的就是 patricia trie )。
存储余额的树是所有区块共享的, 如下图:
这个可以很直观的看到,每次块内交易产生交易的时候实际上只拷贝了被修改到的账号和对应的父节点相关数据并产生新的 trie root, 这样在不同块往下查找到的余额就是对应块的最终余额,比如我们最近的余额就是沿着最新块查找的余额。
每个块头除了包含余额这个树之外还有交易和收据树,这两个树都是每个块独有的,块和块之间是没有任何关联。如果要弄懂以太坊的就一定先理解清楚 merkle patricia trie。
如果要弄懂以太坊的就一定先理解清楚 Merkle Patricia trie,另外的存储就是将树的节点内容存储到 KV(当前以太坊用的 leveldb), 这个替换 KV 存储也特别简单,只要实现 GET/PUT/DELETE 几个接口。
上面就是以太坊整体模块的一些简单介绍, 接下来会细化 DPoS 算法的一些内容,作为之前文章的补充。
之前文章里面提到以太坊三种机制对于开发影响很大,如果是想基于以太坊做改造的要特别注意一下:
full sync 同步全量区块并回放所有交易数据来构造全局状态树;
fast sync(默认) 同步全量区块数据以及第 N 块的全局状态树,同时只回放从第 N 块之后的交易数据。这个机制很像 Redis 的 RDB + AOF 机制,这种方式避免回放所带来的性能问题(回放交易主要的性能瓶颈是在于随机 IO 读写);
light sync 只同步区块头部信息不同步具体交易内容,主要是用于钱包实现 SPV 功能。
之前我们开发过程中踩到一个坑就是 Fast Sync 导致的,这个在之前的文章已经描述过,这里不再赘述。Fast Sync 在 Geth 作者开发这个特性的时候也有比较详细的描述, 具体见 PR: https://github.com/ethereum/go-ethereum/pull/1889。
这里面没有提到另外一个比较重要的点是由于 Fast Sync 点之前的块实际上是没有回放的,直接把最终的余额数同步过来,所以这个点之前是没有办法根据块来获取到余额的,这个大家需要注意一下。
DPoS 本身其实是足够简单,相对于其他的几个算法就是多存储几个全局状态树:
EpochTrie 记录每个周期的验证人列表
VoteTrie 记录投票人对应验证人
CandidateTrie 记录候选人列表
DelegateTrie 记录验证人以及对应投票人的列表
MintCntTrie 记录验证人在周期内的出块数目
这几个树的作用主要是为了避免获取相关数据需要从创世块开始回放数据而增加,所以这几个树的角色和我们账号的余额树是一样的,都是全局状态树(所有块共享)并把树的 root 存储在块头。这里我们之前踩到的坑其实还是跟 Fast Sync 有关,原始代码里由于只有账号这个全局状态树,所有同步的时候也只同步这个账号状态树,其他没有发送就会导致我们选举有问题。
其他开发过程主要是一些细节和逻辑的开发,也比较简单,我们这里就不在展开去说,欢迎大家去 Github 上面看看代码。
Q&A
Q1: DPoS 是否背离了去中心化?
个人认为是背离的,出块的 21 个节点就是一个小中心,DPoS 算法本身就是牺牲去中心化来换取性能(现在很多人也认为 PoW 的矿池算力集中也是另外一种中心化)。另外从 BM 解释 DPoS 的另一个优化就是假设出块节点都是一些知名节点(如交易所, 大的矿池等等),他们在物理上能够做到互通,所以在出块之后不是随机广播而是直接先发送给下一个节点,这样理论上在出块后传播的时间只有物理链路的 RTT, 比如美西到中国可能只需要几百 ms。BM 早期跟在 BitcoinTalk 提出 Bitcoin 性能太差需要修改共识算法就被中本聪怼了(If you don't believe me or don't get it, I don't have time to try to convince you, sorry),之前 V 神也忒过 DPoS。个人在性能提升方面也更加倾向于分片或者 offchain 这种减少主链交易次数的方式,DPoS 在没有强大的技术或者信誉背书的情况下还是比较难进行的, 另外就是如何产生动力驱动社区用户不断去投票从而不断更新和迭代验证人。
相关索引:
BM 解释 BFT DPoS 视频 https://www.youtube.com/watch?v=Xs1dyZFhIr4
Governance, Part 2: Plutocracy Is Still Bad https://vitalik.ca/general/2018/03/28/plutocracy.html
BitcoinTalk 关于中本聪怼 BM 的贴着找不到了, 网上还有一些截图
DPOS vs. POW by Dan Larimer http://bytemaster.github.io/bitshares/2015/01/04/Delegated-Proof-of-Stake-vs-Proof-of-Work/
Q2:既然认为 DPoS 背离了去中心化,那为啥还要撸一个美图 DPoS ?
正如之前文章的引言,我们更多是探索,另外 DPoS 虽然一定程度背离去中心但也有自己的优点。
Q3: 不知这个项目你们开发了多久 ?比如大约用了多少人月?
我们从真正开始接触以太坊源码到改造完成差不多是 2 个月左右,主要是阅读代码周期比较长。人力加起来差不多是 3-4个人边看边做。
Q4:以后会有源码分析的直播么?
不会,太细节的代码解析分享意义不大, 比较欢迎上去直接看代码,有问题随时讨论。
Q5:目前做过真实网络环境的测试吗?运行数据怎么样?瓶颈在哪里?DPoS原理上可以更快吧?
现在就是在内网验证,没有在多个地区部署验证。现在把默认的出块时间调的比较久(5s), 所以性能也就是以太坊的2-3倍。5s 是我们的调试的默认时间,如果假设出块节点都是直连的场景下出块时间可以跟 Steemit 一样调整到 1s, 另外EOS 出块时间另外一个优化就是新块优先给下一个出块节点,这样时间可以调整到几百毫秒。
Q6: 投票和成为候选人是任何时候都可以投以及任何节点都可成为候选人?
投票和成为候选人跟普通交易一样随时可以进行,只是投票和候选⼈结果只能在下一个选举周期生效。另外任何节点都可以成为候选人,所以在线上真正运行还是需要控制成为候选人的门槛(或者优化算法),否则候选人数过多在选举的时候节点在排序和计算时可能会耗时比较久甚至节点不可用。
Q7:投票或者成为候选人在美图修改 Ethereum 版本⾥⾯是如何体现的?
我们把投票和候选人相关的操作都当做是普通的交易,在交易⾥面增加一个 type 字段来区分是普通交易、投票还是候选⼈相关操作,这个在之前的⽂文章⾥⾯也有体现。
写在后面
未来美图技术团队将会结合自身的业务做更多的拓展,我们也期待和更多的技术人员交流。
代码开源地址:https://github.com/meitu/go-ethereum,后续有什么问题可以随时讨论。
相关阅读:
只用200行Go代码写一个自己的区块链!
200行Go代码实现自己的区块链——区块生成与网络通信
200行Go代码实现区块链 —— 挖矿算法
超越比特币以太坊的区块链技术:石墨烯项目简介
以太坊完全指南
重磅!美图技术团队发布开源 Ethereum DPoS 实现
特别推荐:
比特币、以太坊、ERC20、PoW、PoS、智能合约、闪电网络……
想深入了解及讨论这些话题?高可用架构在知识星球(小密圈)创建了区块链学习小组,共同学习区块链包括数字货币前沿技术,欢迎点击链接加入。
区块链学习小组
高可用架构
改变互联网的构建方式
长按二维码 关注「高可用架构」公众号