作者简介
何鑫铭,携程技术中心创新研发部区块链技术专家,携程区块链技术平台技术负责人,精通当前主流区块链开源技术框架,热衷于研究区块链底层设计和区块链应用创新。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/5aa3bcdc11feb022bcaa55d50cc16652.png)
HyperLedger Fabric作为一个架构灵活的企业级区块链平台,正在被越来越多的企业用于生产环境。之前我分享过一篇文章《HyperLedger Fabric在携程区块链服务平台的应用实战》介绍了一些携程对于HyperLedger Fabric的落地经验,并重点分享了Fabric框架在代码结构灵活性上的一些认识和在Fabric开源框架的基础上做的一些延伸开发。本文将分享我们在Fabric排序服务做的一个延伸点。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/5aa3bcdc11feb022bcaa55d50cc16652.png)
我们先以最简单的solo为例,看一下Fabric的orderer节点接收排序请求后的主要处理逻辑。
首先,orderer的排序服务需要实现consensus包的Consenter(排序引擎)interface,Consenter接口只需要实现一个HandleChain方法,该方法需要返回一个Chain对象。
Chain对象需要实现Chaininterface,实现Order(普通交易排序)、Configure(配置交易排序)、Start等主要方法。那我们现在来看一下当一个orderer节点启动后,将会经过怎样的步骤,如何实现对交易的排序。
1、当我们在fabric网络定义排序类型为solo的情况时,orderer节点启动会初始化一个solo consenter对象(参考代码orderer/common/server/main.go中的方法initializeMultichannelRegistrar)。
2、当orderer启动后,orderer节点会检查本地账本中存在的通道,此时发现只有一个testchainid通道(了解fabric的话,我们会知道当区块链网络创世时,会有一条默认名为testchainid的系统通道),solo consenter会为系统通道testchainid创建一个chain对象保存在orderer内存中,并启动监听来自orderer节点接收到的系统通道配置交易(testchainid只会接受配置类交易,如创建新的通道请求)。
chain对象请求监听的原理其实是在chain的start方法中会启动一个goroutine监听一个名为sendChan的go chan。
3、此时我们假设有一个创建名为mychannel新通道的交易被orderer接收到,只能是testchainid系统通道对应的chain对象来进行处理,此时orderer节点会判断该交易类型为配置类型交易,则调用chain对象的configure方法,configure方法将交易写入chain.sendChan中。
4、此时,会触发sendChan的监听服务,监听服务会检查交易并将交易通过ch.support.BlockCutter().Ordered方法放入本地队列中等待出块,出块任务在启动orderer节点时时会启动一个chan timer,每隔固定时间(设置的batchTimeOut)会从队列中取出一定的交易数量(不超过设置的每个块最大的交易量)出块,并写入orderer本地账本,当mychannel的创建交易被成功受理出块,即意味着名为mychannel的新通道已经被创建。
5、mychannel通道创建后,solo consenter会通过HandleChain方法为之创建一个新的chain对象,mychannel chain对象会受理mychannel通道的交易排序,原理与以上同。
以上只是以solo的order type为例,fabric截止到目前的1.4版本,官方推荐使用更稳定的kafka排序。
kafka排序与上述例子中solo排序的区别是:可以支持多个orderer节点,所有的交易可以请求任何一个orderer节点,请求的orderer节点本地排序出块后会通过kafka集群将数据同步给其他的orderer节点,意味着排序服务可以实现更高的可用性。
我们在查看orderer源码时,发现了官方已经在做raft排序。简单测试了一下,过程是能够走通了,大家可以耐心等release版本。
![640?wx_fmt=png](https://i-blog.csdnimg.cn/blog_migrate/5aa3bcdc11feb022bcaa55d50cc16652.png)
我们认为目前已经release的kafka排序是能够满足初级的联盟链需求的。
背景是,现在的联盟链更多是一强多弱型企业联盟,如一个大型公司主导区块链业务与技术,其上下游机构合作参与;如一个集团性企业布道区块链,其分子公司合作参与;如aws、阿里云等云厂商提供baas云服务,企业使用只需要向baas申请节点资源。
以上情况中,kafka集群大都需要部署到大型公司、集团总部或者云厂商,保证高计算能力和高可用,是可以支持比较高并发的上链请求的(官方数据是TPS可以达到3000以上)。
为什么说是初级呢?我们可以发现以上列举的情况实际是有偏离区块链初衷的,区块链在联盟中更应该作为联盟企业间平等、互信合作的基础设施。当联盟间各个企业不存在一强多弱,真正是几家平等的企业在合作时,在技术设计中就会存在一个比较大的疑惑,kafka排序服务应该部署在哪里呢?也许部署到公有云上是一个选择,但当云厂商本身是利益的相关方呢?
所以我们认为,无论是官方正在开发的raft排序还是我们做的pbft排序,最重要的目的就是首先要允许orderer节点部署到不同的企业,每个企业都参与到fabric的排序服务,而不是像kafka排序一样需要将排序服务部署到一个中心化的机构。
Tendermint提供了一个高性能、一致的、安全的BFT共识引擎,严格的分叉问责保证能够控制作恶者的行为。
Tendermint非常适合用于扩展异构区块链,包括公有链以及注重的性能的许可链/联盟链,像Ethermint 就是一次对Ethereum以太坊POS机制的快速实现。使用Tendermint在区块链领域中的成功案例包括Hyperledger Burrow、cosmos等著名项目 。
tendermint项目的团队是正在进行著名跨链项目Cosmos研发的团队(相信很多同学一定关注过这个明星项目),而tendermint也是作为共识协议用于在Cosmos Hub上构建第一个分区。
Tendermint独有的abci定义了区块链执行的标准接口,可以允许用户自定义实现接口内容,不需要修改tendermint源代码来集成他,并支持跨语言通过上层接口进行调用。这就为许多其他技术栈甚至不同语言的区块链底层的集成提供了思路。关于更多tendermint的介绍这里不再赘述。
这里,我们通过tendermint的abci来实现fabric的orderer服务。
fabric的排序方式,需要peer节点将交易proposal 发送给任一orderer节点,kafka排序是orderer节点借助kafka消息队列,而raft排序是orderer节点借助etcd实现区块传递给其他orderer节点。而在这里,我们让orderer节点借助其内部的tendermint节点服务,将消息传递给其他orderer节点,并能够兼容其中的拜占庭节点。
下面我们借助tendermint的abci接口,实现代码不侵入,完成orderer的tendermint pbft排序。
首先,新建一个全新的package实现fabric consensus的所有接口方法。这里可以判断,每次handleChain方法触发(一个新的fabric channel创建)时,尝试调用tendermint包的node.NewNode方法启动tendermint服务,可以多个channel只启动一个tendermint服务。
然后,每当有新的交易传递到orderer时,envelope类型的交易都会通过order方法和configure方法传递进来,这里我们只需要在两个方法中,将交易序列化为tendermint可以传递的数据类型如byte数组,调用tendermint节点的Mempool.checkTx方法,将交易打包到tendermint的内存池中即可。
之后的事情,打包到tendermint内存池中的交易,将进行多个orderer节点的pbft共识,这里会执行tendermint的标准p2p通信和多轮共识。
完成共识之后,需要我们通过tendermint的abci的DeliverTx和commit方法获取到共识后的交易,并调用fabric的CreateNextBlock方法和WriteBlock方法打包生成区块。即完成一个完整的交易共识并记账。
这里,有一个比较容易产生疑问的问题,我们知道fabric是多通道的账本结构,而tendermint是单通道账本,如何做到兼容两边?
在这里,实际我们是需要写fabric和tendermint两套账本的,从上述过程我们可以看到,共识交易完成后需要每个orderer节点自行调用fabric自带的写区块方法在对应的通道中进行写块,而同时我们在abci中也创建了一个tendermint的基于leveldb的账本。即每一笔交易,我们也在tendermint的账本中记录了一份,只是没有区分通道,因为本来fabric中的orderer也是记录的全通道数据。
该账本主要用于fabric追块,当某个orderer节点的tendermint块高度比其他节点小时,会触发tendermint的追块功能,从tendermint中读取交易后写入自己的tendermint账本,同时写入fabric对应通道的区块中。
以上整个过程,没有动过tendermint的源代码,只需要扩展一个新的实现fabric的consensus接口的类,在类中同时实现tendermint的abci接口即可。简单代码请参考https://github.com/hxmhlt/fabric/tree/develop-1.4-tmpbft。
代码一些功能还未完成,如动态添加orderer节点需要结合tendermint动态添加validator功能来做、tendermint配置文件的自动生成、性能也未进一步优化,也或许还有一些其他问题,代码截至撰写本文还未用于生产环境。开源部分的源代码仅供参考,也欢迎各位读者在评论区交流~
【推荐阅读】