聊聊长安链的技术设计
长安链(chainmaker)是 2021 年初发布并开源的一款区块链产品,号称“我国首个自主可控的区块链软硬件技术体系”的区块链。从现有的信息来看,腾讯是这个产品的主要贡献者,代码托管在这里,还有文档在这里。
有源码就好办,翻下源码就知道,到底所谓的“自主可控”的区块链产品有何技术上的独到之处。
注:笔者写下此文的时间在2021.5月中旬,
chainmaker-go
项目刚有了v1.0.0
release分支,但尚未正式发布,主干master
分支已经开始有v1.1.0
的代码合并进来。当前chainmaker-go
项目master
分支的 git commit SHA 是de21646e1e33fac01248bf4aa8e4520f30e80d00
。
项目概览
经过简单的注册步骤,就可以看到长安链的全部代码了。总共有以下这些项目,
chainmaker / chainmaker-common 长安链通用组件库项目
chainmaker / chainmaker-cryptogen 长安链证书生成工具
chainmaker / chainmaker-docs 长安链相关文档,包括:用户手册、运维手册、快速上手指南等
chainmaker / chainmaker-go 长安链底层平台开源项目
chainmaker / chainmaker-pb 长安链protobuf项目
chainmaker / chainmaker-sdk-go 长安链go语言SDK项目
chainmaker / chainmaker-sdk-java 长安链java语言SDK项目
项目的主要代码都在chainmaker-go
项目下,主要的处理逻辑也是在这个项目里面实现,其他都是子项目。
不知道大家看到这个项目结构的时候第一感觉如何,反正我看到这个项目的感觉就是,这不是和 Hyperledger Fabric 一个模子刻出来的吗。稍微看一下chainmaker-go
的代码还会发现,这个项目真的和 Fabric 相似度地方太多了,比如:都有 cryptogen
程序;都有管理智能合约生命周期的智能合约;都有一个叫solo的共识算法用于测试;chainmaker-go
项目的目录结构和 Fabric 也很相似;还有,Fabric 标志性的 Policy 机制在长安链项目中也出现了。诸如此类,不一而足。
对两个相似功能的代码结构做比较,得出“两者有相似性”这样的判断可能有些主观。可以另外比较一下,长安链和以太坊(ethereum)的代码,长安链和Corda的代码,多比较几个区块链项目的源码(不用看代码,只看源码结构就好),相信你能和我得出相同的结论。
当然,单纯的代码结构相似,也不能抹杀长安链项目本身的创新。毕竟 Fabric 在联盟链领域已经有了相当的市场份额,设计上、功能上、代码实现上必定有些过人之处,后续的项目在某些方面借鉴一下也无妨。
仔细查看一下代码的话,发现chainmaker-go
在某些接口定义上还是有些和 Fabric 相似,但不相似的地方随着看的深入也更加多起来,慢慢的也不那么像 Fabric 了。
长安链是联盟链
长安链应该被定位为一款联盟链产品,而非公有链,或者公有链改出来的联盟链产品。长安链从诞生之初就是联盟链,这与国内其他的区块链开源项目是不同的,目前国内开源的联盟链,如 bcos、fisco 等,都是基于以太坊(公有链)项目的修改,在设计上有很多公有链的痕迹。国内也有一些基于 Fabric 修改的联盟链产品,但要么是暂时尚未开源,要么是开源了,但并不活跃。
因此,笔者会站在联盟链的角度,尝试去理解并分析长安链的设计,从技术设计的角度,评价这些设计的优劣。先说好的,再说中等的,最后说不好的,有些地方会和 Fabric 做一些对比。
长安链:好的地方
先来说说我觉得长安链设计的比较好的地方。
完全的联盟链设计
这一点刚才提到了,整体上,长安链是联盟链的设计。比如,长安链的共识机制主要包括solo、raft、tbft,还有将在 1.1.0 发布的 chainedbft(类似HotstuffBFT)。这些都是联盟链会使用的共识算法,没有提供 PoW、Pos、DPoS 这类公有链的共识机制。再比如,长安链的数据存储采用了 KV 模型,没有采用公有链的账本模型。还有,长安链提供了身份、角色、权限管理机制,而公有链不会有这些功能。
以上这些标志性的特征,都说明长安链的联盟链属性非常重。
更加实用的 Policy 机制
Policy 机制应该是由 Fabric 最早引入到区块链里面来的。这套机制是建立在身份认证(本质是数字证书机制)之上,用于权限管理功能的。长安链引入了这套机制,具体的设计思路和 Fabric 毫无二致,但做了实用的改进。
Fabric 的 Policy 可以支持复杂的权限定义表达式,例如,“OR(org0, AND(org1, org2))”,多层嵌套,造出复杂的权限定义。长安链的 Policy 机制只支持简单的表达式,并且不支持嵌套,例如,
policy{
orgList: []string{"org1", "org2", "org3"},
roleList: []protocol.Role{protocol.RoleAdmin, protocol.RoleClient, protocol.RoleCommonNode}
rule: protocol.RuleAll,
}
这个配置类似 Fabric 的“AND(org1, org2, org3)”,虽然长安链的表现形式长了,但技术实现上相比 Fabric 要简化一些。这个简化是有益的,现实场景中,需要支持嵌套的场景真的是太罕见了,这个改进会简化一些代码实现。过早的支持一些特殊场景,只会带来代码的复杂,并没什么用。
此外,长安链在角色分类上更加细致,定义了4种类别;Fabric 只有2种类别。长安链多出来的是 CONSENSUS 和 COMMON。还有,长安链在规则上,也多了一些,如分数规则和禁用规则。虽然这些改进比较简单,但是不失为一种有益的尝试。
有些地方没法和 Fabric 完全对应上,比如 Fabric 有 OrderOrg,可以用来区分 CONSENSUS 角色。此处仅做表面的比对。
长安链的 Policy 机制还略有一些不足,比如,Policy 配置还不是很清晰,rolelist中的规则默认是“或”的关系,即任何一个rolelist中的角色签名即可。但上述例子会让人误解成,是需要org1的ADMIN,org2的CLIENT,org3的COMMON来签名吗?再比如,Fabric 可以构建“OR(org0.member, org1.admin))”这样要求不同机构角色签名的场景,目前长安链也无法满足。但这样的应用场景应该也比较少。
瑕不掩瑜,长安链对 Policy 机制的改进,是有可取之处的。
压缩证书机制
在长安链的文档中提到了证书压缩机制,如下,
// Serialized member of blockchain message SerializedMember { // organization identifier of the member string org_id = 1; // member identity related info bytes bytes member_info = 2; // use cert compression // todo: is_full_cert -> compressed bool is_full_cert = 3; } * ChainId:链标识,表名本交易是针对哪条链的,防止一个交易在多个链中被打包。 * Sender:交易发送者信息 - OrgId:成员所属机构编号 - MemberInfo:成员的身份信息,可以是证书信息也可以是证书标识,依赖于IsFullCert字段 - IsFullCert:是否为全量证书,如果是,则MemberInfo填写用户证书的信息;如果不是,则MemberInfo填写用户证书标识(该标识需通过证书上链接口提前在链上登记)
主要就是SerializedMember
中的IsFullCert
和MemberInfo
两个字段。文档的内容已经说的很清楚了,SerializedMember
是存储成员身份信息的结构,当IsFullCert
为 true 的时候,MemberInfo
中存储的是完整的证书信息;当IsFullCert
为 false 的时候,MemberInfo
中存储的的是证书的标识(实际是哈希),证书本身已经提前在链上登记。哈希占用的存储空间当然比一个完整的证书要小多了,因此也达到了减少存储的作用。当然,多数情况下,长安链会采用压缩机制。
这个改进是100%有效的改进,降低了IO资源的消耗,尤其在交易本身内容比较少的时候(证书存储占比较高),改进特别明显。本来,这个可以是长安链中非常原创的改进,可惜,在某个未开源项目中已经了解到过与此几乎相同的设计,因此没有把这个特性放在首位。
修改链配置的简化
Fabric 修改链配置的步骤是比较复杂的,需要在当前配置块的基础上,计算出修改造成的差值,对修改请求签名,然后再进行修改。长安链是通过执行内部智能合约的方式来修改,具体是通过定义一种特别的请求类型TxType_UPDATE_CHAIN_CONFIG
来实现。这个方式比 Fabric 的要简便一些。
Fabric 的修改方案虽然复杂,但是可以任意的修改,自由度大;长安链受限与智能合约提供的方法,只能进行部分修改,但通常情况下也足够用了。
实话说,Fabric 修改配置碰到的问题还是挺常见的,很多项目都有这样的痛点,长安链的这个改进是有益的。
原生支持国密算法
当然,Fabric 原生并不支持国密算法,我估计很可能“永远”也不会添加这个特性。但是国内的区块链项目,如果没有支持国密,能支持的业务场景又会受限。因此,国内的基于 Fabric 修改的联盟链产品,基本都提供了国密算法的支持。
以长安链的背景,不支持国密是不可能的。密码这部分代码我都没有细看,但我相信,一定支持。
智能合约支持多引擎,多语言
这个特性本来我想放进下一个章节的,仔细思索,还是留在了这里。我一直觉得,项目早期阶段并不需要智能合约多引擎,多语言的支持,尤其是多语言。
因为,能用一种语言来写合约已经足够了,相同的逻辑支持另一种语言来写,实际没什么意义,智能合约里面并没有那么多复杂的逻辑来写,也几乎不存在一种语言能写出来,另一种语言写不出来的功能。至于智能合约引擎,多样化的需求会相对多一些。但实际使用时,一般都是在能用的引擎里面找一个稳定性和性能都比较平衡的一个,多数情况也没那么多选择。
长安链在初次发布的时候,就支持多引擎、多语言,在 1.1.0 版本中还加入了evm支持,感觉是设计的时候搞错了重点,没有把全部力量集中在做精产品,而是在求大、求全。多引擎、多语言应该是产品的能力已经得到充分的证明的情况下,在扩张其他场景的时候才需要考虑的功能。
话说回来,虽然我觉得这个特性引入的时机并不恰当,但其本身并不是坏功能。至少在产品推广的亲和力上会好很多,考虑到不同背景的区块链开发者,支持多种智能合约语言会降低开发门槛。
长安链:不好也不坏的地方
再来说说一些长安链设计上比较中性的地方。这些地方可能不见得好,也不见得坏,可能只是在有多种类似选择的时候,选择了其中一种;也可能是一种新特性,但相比其他产品,并没有太多的进步。
RwSet中 Key 版本号的设计
Fabric 中的版本号是设计成“BlockNumber + Tx Offset in Block”的形式,比如,某个Key当前版本号是“(3, 2)”,表示第3个块中的第2条交易最后写入了这个Key。换句话说,Fabric 的版本号是精确到 Tx 的,同一个 Tx 内发生的写入,版本号是一样的。
而长安链的版本号设计成“TxID + Key Offset in WriteSet”,有些不可思议,这意味着,同一个 Tx 写入的不同 Key 会有不同的版本号,这与常识的认知不符。因为对单一的 Tx,技术上应该是要满足事务属性,即满足“同时成功,或同时失败”,对于有这样特性的系统(如关系数据库和一些NoSQL数据库),我们通常会把同一个 Tx 内的写入标记为相同的版本。长安链做这样的选择明显是不必要的,可以说是一个设计失误。如果想版本号和 TxID 相关,直接设计成“TxID”即可。
我查了一下代码,发现版本号 KeyVersion 这个类在代码中并没有应用(可能是我看漏),这个“坏设计”暂时没有启用,应该也没有造成什么伤害。
退一步说,长安链即使是设计成只有“TxID”方式,对比 Fabric 的“BlockNumber + Tx Offset in Block”,也不见得就一定有优势。Fabric 这样的设计可以明显看出两个版本号哪一个更加新一点,因为版本号是单调递增的整数;长安链就不行,TxID是随机生成的,无法用来判断版本新旧。这个特性在追溯某个 Key 的变更历史的时候会很方便。
本来,这个特性会被我归类到下一个章节,至少从目前的分析上来看,这个设计毫无可取之处。但没有这样做的原因是,这个版本号设计并没有在代码中应用,万一,我是说万一,也许后面会改进呢?也许会有更好的我们没有想到的特性呢?
DAG 的设计
首先要说,长安链的 DAG 和通常理解的区块链里面的 DAG(有向无环图)“不一样”。
通常理解的区块链领域的 DAG 应该是这样和这样,代表项目有 IOTA,ByteBall。DAG 一般表示的是一种区块的组织方式,从数据结构的角度来看,区块链是将区块以链表的形式连接起来的,而 DAG 是将区块以有向无环图的形式连接起来的。这些都是在几年前就提出的概念,不再赘述。
而长安链中的 DAG 则完全不同,它是区块中的一个部分,其作用是整理当前区块中每个交易之间的关系,看是否有前一个交易和后一个交易有依赖关系的情况。当交易之间有依赖关系的时候,调度合约的时候需要按照依赖关系排序执行;如果交易之间彼此没有依赖关系,那么无所谓先后,大家都可以并行执行,可以带来效率的提升。
目前看起来,长安链的这个创新点还是很不错的,应该是原创(不排除有其他项目已经先实现了,只是我不知道),不是上面 Policy 机制、修改链配置那种小幅度的创新。但是,我还是没有把这个特性排到上一章节,因为,不管从实际的运行效果来看,还是设计的对比来看,这个改进对比 Fabric 的 Simulation+Validation 机制,实在很难说更好。