Fabric 和 Sawtooth 技术分析(下)
Posted earvin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fabric 和 Sawtooth 技术分析(下)相关的知识,希望对你有一定的参考价值。
http://blog.talkingdata.com/?p=6172
在前一篇文章(Fabric和Sawtooth技术分析(上))中,我们着重跟大家分享了 Fabric 相关的内容,在本篇文章中,我们将围绕着 Sawtooth 进行一些分析和探讨。
Sawtooth 结构及分析Sawtooth 是 Intel 公司推出的企业级区块链,2018年 Intel 将其贡献给 Hypherlegder 项目。本文中笔者主要从 Sawtooth 的存储结构、数据结构、网络结构方面做简要介绍。
Sawtooth
Sawtooth 的存储结构
Sawtooth 使用名为 Radix Merkle Tree 的存储结构,它融合了 Radix Tree 和 Merkle Hash Tree 的功能,先看看这两种结构:
- Radix Tree
Radix Tree 概念比较拗口,简单地说就记住在这个树上,任何一个叶子节点的位置和一个 01 串唯一对应,因此我们可以根据一个 01 串组成的地址确定叶节点是谁。
下图是一个 Sawtooth 所使用的 Radix Tree 对应的字符串,由70个16进制字符组成,前6位称为命名空间前缀(Namespace prefix),后边的是该前缀所对应的空间内可分配的地址范围。
我们以 Sawtooth 预定义的一个 Transaction Family-IntegerKey 为例,注意 Sawtooth中 的 Transaction Family 相当于 Fabric 中的 chaincode 或者 Ethereum 中的 Smart Contract 。IntegerKey 的前缀(prefix)计算法方法是:hashlib.sha512(‘intkey’.encode(‘utf-8’)).hexdigest()[0:6],运算结果是 ‘1cf126’。那么,存储该 transaction family 中一个 block 的地址就是:
address = “1cf126” + hashlib.sha512(‘name’.encode(‘utf-8’)).hexdigest()[-64:]
当然,地址的构成也可以更复杂一些,比如,有个自定义 Transaction Family 的前缀是 prefix = “my-transaction-family-namespace-example”。命名空间可以进一步划分为 object-type 和 unique-object-identifier 。其中,object-type = “widget-type”,unique-object-identifier = ”unique-widget-identifier”。那么,对应的字符串就可以计算如下:
hashlib.sha256(“my-transaction-family-namespace-example”.encode(‘utf-8’)).hexdigest()[:6] + hashlib.sha256(“widget-type”.encode(‘utf-8’)).hexdigest()[:4]
+ hashlib.sha256(“unique-widget-identifier”.encode(‘utf-8’)).hexdigest()[:60]
最后得到下面的地址:
‘4ae1df0ad3ac05fdc7342c50d909d2331e296badb661416896f727131207db276a908e’
众所周知,2的10次方是1K,20次方是1M,30次方是1G,40次方是1T,对于一个名字空间,Sawtooth 为其保留64位,从存储空间需求上讲,即使有一些位被用来做子空间划分,应该也够用了。在查找时,完全可以根据 Transaction 的名字找到它的存储位置,所以检索速度也会非常快。
我们可以认为,Sawtooth 就像一个原始的区块链,每向后一层都可以分叉,以树的形式组织数据存储,而不再是以线性的方式来组织数据存储。也可以把原始的比特币区块链理解为只有一个Transaction Family 的 Sawtooth。在完整的分析过 Sawtooth 以后,最应该记住的就是 Sawtooth 的存储结构,它其后的所有设计都是基于这一结构。
- Merkle Hash Tree
Merkle Hash Tree 的特点是所有节点的值都是哈希值,每个哈希值是根据其子节点的哈希值计算出来的,所以任何一个节点和哈希值出现变化,它的上层节点的哈希值都会跟着变。
Sawtooth 采用 Radix Merkle Tree 结构做数据存储的好处就是给定 Block 名及类别,直接计算哈希值,就找到它的存储位置了。而且存储空间是隔离的,每个 transaction family 的存储空间和其它的都是分开的,互不影响。所以,从存储结构来看,基于 Sawtooth 的区块链天生是多条连的(Forked),很容易去解析它的分叉。
Sawtooth 的数据结构
Fabric 没有去严格定义数据结构,Sawtooth 的数据结构也没有什么值得特别提出的亮点。只要知道 Sawtooth 定义了 Transaction 包括Header 和 Payload 两部分即可,而 Sawtooth 要求不管是一个还是多个 Transaction 必须被封装在 Batch 中才可以提交给 Transactio n的 Processor ,或者说 Transaction Processor 只接受以 Batch 为最小单位的 Transactions 。同样地,Batch 也包括 Header 和 Payload 两部分,其关系如下图所示:
Sawtooth 的网络结构
如下图所示,Sawtooth 的一个节点可能由如下几个部件组成:Validator、Transaction Proc essor、REST API、以及 Client。Validator 是 Sawtooth 的核心部件,主要功能包括接收 Transaction 请求,并将其转发给相应的 Transaction Processor 来处理,根据 Transaction Processor 的处理结果,决定如何生存新的区块,如何给 Client 回显。Validator 还要与其他的 Validator 协同,以保持 Sawtooth 网络的全局状态一致。Transaction Processor 顾名思义就是用来专门处理某一类型 Transaction Family 的 Transactions 的 Processor 。
Client 需要按照 Transaction 和 Batch 规定的数据结构生成请求,REST API 则是标准化的网络传输数据格式。之所以说可能由这几部分组成,是因为对 Sawtooth 来说,只有 Validator 属于其固定结构,比如图中有 Validator1 和 Validator2 ,而 Validator2 就没有连接其他部件,而是只与 Validator1 相连。从构成角度看,一个 Sawtooth 网络可以只由一个 Validator 构成。从网络方面看,其他的 Validator 可以动态加入网络。从部件方面看,Transaction Processor 可以动态注册到 Validator ,然后 Client 提交相应 Transaction 就有对应的 Processor 来进行处理。网络节点和部件可以分别使用不同的端口来区分。这样,Sawtooth 网络就变成完全动态的了,每个组成部分都可以动态插拔。
接下来,我们先看看 Validator 的组成结构,Validator 的软件实现部分称为 Journal,如下图。从功能上看,Journal 包括 Completer、ChainController 和 BlockPublisher 三个主要部分。
当 Batch 被提交给 Validator 后,先被交付到 Completer ,它先检查是否 Batch 的所有依赖项都得到了满足。完整且满足依赖的 Batch 会被提交给 ChainController 。Sawtooth 的这种设计可以支持串行和并行的 Batch,注意这已经不是进程级并行了,而是线程级并行。接下来再看看 ChainController 是干什么的:
ChainController 需要完成4个工作:
- 1)确定块的头在哪
- 2)确定当前块在哪-先去 BlockCache 里找,再去 BlockStore 里找。
- 3)验证块有没有损坏
- 4)把新块写进区块链
写入新区块链后的发布工作则是由 BlockPublisher 完成。从图中可以看到,ChainController 和 BlockPublisher 本质上都是接口,具体的实现由更下层的共识(Consensus)机制完成,共识机制向上提供 BlockVerifier,ForkResolver 和 BlockPublisher 三个功能。
从整体上看,Validator 的结构比较简单。接下来再看看 Validator 之间是怎么连接起来的。Sawtooth 的 Validator 的网络连接方式如下图,可能会有点乱,同时解释地也不是很清楚,这里笔者的理解是:把它看做一个 Ad Hoc 网络,组网的过程完全就是模拟 Ad Hoc 网络路由节点发现的。开始的时候有初始(生成 Genesis block 的)节点,它可以发出广播包,问谁在它边上,可以按照设定的规则加入网络,如果有人应答就可以加入,然后这些节点继续广播,每个广播包只传播给距离自己1跳的 Validator 节点,这样网络很快就组成了。有新节点想加入也是这样,发广播包,看看自己周围有没有可以直连的节点,退出就无所谓了,反正少一个节点不影响网络。我们知道 Ad Hoc 网络的健壮性和灵活性都是非常高的,所以 Sawtooth 的 Validator 网络中任何节点都是可以动态加入或退出的,只要网络规模足够大,理论上,网络的健壮性是有保障的。
这里有两个关键问题其实没有说清。
- 1)哪些 Validator 可以加入该网络
- 2)Validator 接受哪些 client 提交的 Batch
这两个问题就构成了访问控制和隐私保护功能的核心,而 Fabric 花大力气实现的体系结构也正是为了回答这两个问题,稍后会详细说明,核心网络介绍完毕以后,会想到 Client 提交了 Transaction,那Transaction 执行与否?所以,还差一个事件机制来实现消息传递和回显功能。这里的事件机制要确定这么几件事:
- 1)谁应该被告知事件—(广播?还是根据注册情况组播单播)
- 2)事件应该包括什么—(消息格式-收据(Transaction Receipt))
- 3)事件在什么情况下,或什么时候才算有效—(放在 Transaction Receipt Store 中,通知发起 Transaction 的 client 来拿)
下图是 Sawtooth 的事件机制示意图,它把技术实现和组件名称混到一起,看起来也比较乱。大体的意思是左上部是事件子系统用 Zero message queue 的技术实现,其特点是在需要的时候可以随时创建,右上部是写好的类库,注册后,只要满足约束就可以调用它。下部说的是 Transaction Processor 调用具体的 Handler 处理 Transaction 后会告诉 ChainController 的 Scheduler 和 Executor 执行 Transaction 的结果情况,ChainController 除了把新的 block 写到它应该在的地方之外,还会把 Transaction 的 Receipt 放到一个叫 Transaction Receipt Store 的地方,这时候 ChainObserver (Client 注册后产生的一个部件)就会告诉 Client, Transaction 执行完了,来拿收据吧。
下面是一些事件的例子,可以帮助我们理解事件的格式:
1Example events generated by BlockEventExtractor:
2Event {
3 event_type = "sawtooth/block-commit",
4 attributes = [
5 Attribute { key = "block_id", value = "abc...123" },
6 Attribute { key = "block_num", value = "523" },
7 Attribute { key = "state_root_hash", value = "def...456" },
8 Attribute { key = "previous_block_id", value = "acf...146" },
9 ],
10}
11
12
13Example events generated by ReceiptEventExtractor:
14// Example transaction family specific event
15Event {
16 event_type = "xo/create",
17 attributes = [Attribute { key = "game_name", value = "game0" }],
18}
19
20// Example sawtooth/block-commit event
21Event {
22 event_type = "sawtooth/state-delta",
23 attributes = [Attribute { key = "address", value = "abc...def" }],
24 event_data = <bytes>
25}
Sawtooth的访问权限控制
Sawtooth 的权限(Permissions)机制应该参考过 Fabric。主要功能包括网络权限的设置(哪些 Validator 可以加入这个网络),和 Transaction 权限设置(哪些 client 提交的 Batch 可以被 Validator 执行)两种。和 Fabric 一样的是,Sawtooth 也需要配置文件,如果所有功能全部用配置文件完成则称为 Off-Chain transactor Permission,通常来说小规模网络,极限情况下,只有一个节点的网络完全可以使用 Off-Chain 的方式实现。在 Off-Chain Permission 中,权限是静态设置的。在配置文件 validator.toml 中,直接配置:
[permissions]
ROLE = POLICY_NAME
Policy file:
PERMIT_KEY <key>
DENY_KEY <key>
这里的 ROLE,POLICY_NAME 暂不解释,key 一般是一个公钥,PERMIT_KEY 和 DENY_KEY 就将它理解为一个 bool 值,一个是1,一个是0,含义就是允许不允许。
和 Fabric 不一样的地方是,Sawtooth 还有一种配置方式叫 On-Chain transactor Permission 。就是在区块链上直接设置访问权限,还专门为此设置了一个叫 Identity 的 Transaction Family 。这样 transactor Permission 就有自己的存储空间,其当前值也好,变化也罢,所有节点都可以同步过去,不会存在各个节点配置文件不一样导致系统出错的可能性。
接下来具体看下 Identity。Identity Namespace 以 key-value 对的形式存储 roles,key 是 role name,value 是 policy。所有的 role 都以 transactor 为前缀。比如下面:
transactor.SUB_ROLE = POLICY_NAME
首先,第一个问题是开始谁可以设置访问权限。和 Fabric 例子中类似,机构 R4 通过网络配置文件设置访问权限一样。在 Sawtooth 中,理所当然的应该由创始区块的生成者来设置初始权限。它执行如下命令来设定允许给别人授权的人的公钥:
sawset proposal create sawtooth.identity.allowed_keys=00000
这里的00000是创始区块的生成者自己的公钥,那现在就它自己可以给别人授权。这个类似于 Fabric 里设定,公钥设定以后可以利用 identity-tp 进行授权,也可以继续用 sawset proposal create 命令让其他 Validator 有权做网络或者 Transaction 授权。proposal 这个子命令其实就能猜到 Sawtooth 设计访问权限的时候肯定是参考了 Fabric 的。
具体的 Transaction 授权命令的例子如下:
sawtooth identity policy create policy_1 “PERMIT_KEY *”,这个的意思是创建一个叫 policy_1 的策略,对所有人都是允许的,也就是谁都可以提交 Transaction ,注意这仅仅是个策略,还可以定义其他的策略,相当于 Fabric 里的 Deploy 而不是 Invoke 。可以调用 sawtooth identity policy list,查询当前有哪些策略,比如在执行了刚才的命令后,会得到如下回显:
$ sawtooth identity policy list
policy_1:
Entries:
PERMIT_KEY *
接下来执行如下命令:
$ sawtooth identity role create transactor policy_1
就会把 transactor 的策略设置为 policy_1。换句话说,这时,就真正让 policy_1 生效了,类似于 Fabric 里的 Invoke。然后可以调用sawtooth identity role list,查询当前角色的状态:
$ sawtooth identity role list
transactor: policy_1
上边我们都用 transactor 为例子,其实 role 可以有如下几种:
default、transactor、transactor.transaction_signer、transactor.transaction_signer.{tp_name}、transactor.batch_signer。
意思其实从字面上能看出来,这里 transactor 可以是 organization,一个 transactor.batch_signer 可以是一个 organization 下边的部门,transactor.transaction_signer 可以是该部门的一个用户,如果有好多 tp 的话,该用户可以只具有其中某些 tp 的执行权限。
比如,我们现在自己写了一个新的 tp 叫 supply_chain,新定义了两个用户,一个的公钥是12345,另一个的公钥是23456,我们希望这个 tp 只有这两个新用户可以运行,这时我们就可以执行命令:
sawtooth identity policy create policy_2 “PERMIT_KEY 12345” “PERMIT_KEY 23456”
sawtooth identity role create transactor.transaction_signer.supply_chain policy_2
网络权限设置和 tp 执行权限设置差不多,比如有个 Validator 对外的公钥是00001,然后执行如下命令,它就不能加入网络了:
$sawtooth identity policy create policy_3 “DENY_KEY 00001”
$ sawtooth identity role create network policy_3
$ sawtooth identity role list
network: policy_3
Fabric 和 Sawtooth 的比较
相信看完了 Fabric 和 Sawtooth 的介绍,大家对这两个项目都有了自己的认识。笔者再谈一下个人对这两个项目的理解。
首先,从背景上看,Fabric 是脱胎于 Ethereum 的,或者说能在 Fabric 上看到 Ethereum 的影子,而 Sawtooth 已经看不到 Ethereum 的任何痕迹了。反而,个人觉得 Sawtooth 更加纯粹一点,它就是一个有共同起点的 比特币区块链,只是比特币没有不同 Transaction 的概念,而 Sawtooth 把 Transaction 按照用途分类,且允许用户根据需要自己定义新的 Transaction (这个概念是 Ethereum 提出来的)。
从体系结构角度看,我认为 Sawtooth 明显优于 Fabric 。Fabric 像是针对特定问题的一个专用解决方案,而不像一个通用架构。Sawtooth 可以认为是一个通用架构,然后根据需求变为一个专用解决方案。从访问控制角度看,Fabric 像是根据实际情况建设了一套管网,让涉及隐私的数据只能在其中运行。Sawtooth 则更像是在一套管网上加装了阀门,通过阀门来控制数据的流向。这可能和 IBM 以及Intel 的业务特点和公司文化息息相关。我的印象中,IBM 是一家咨询公司,专门针对企业级用户设计解决方案。Intel 是处理器公司,不管你的业务是什么样的,它提供通用中央处理器,让你能根据自己的需要配置成自己的专用解决方案。
当然,二者也有一些具体的差异:Fabric 中,多个节点收到相同的输入后分别独立执行,以期得到一致的结果。Sawtooth 的 SGX 和基于 SGX 的 PoET 验证的是在一台机器上的执行结果,没有再让每个节点都去执行一遍,而是一个执行完了以后去和别的同步结果。二者的假设不同,效率上也有差别。Fabric 的权限控制依赖形成 channel 这种体系结构,Sawtooth 的权限控制通过 Transaction 本身进行设置。或者说,Fabric 只有特定的人能看,但能看到特定范围内的全部。Sawtooth 所有人都可以看,但 Transaction 的 Permissions 限制了能看什么。Fabric 的 orderer 完成 Transaction 排序,可以实现进程级并行。Sawtooth 的排序是在 batch 里指明依赖关系,通过Completer 排序实现线程级并行。
对 Fabric 来说,要设计它时,大家认可的网络结构就是 Ethereum 了,怎么能稍作修改,实现隐私保护和访问控制是它的设计目标。Sawtooth 没这种包袱,可以重新设计网络。所以,我感觉 Fabric 更像是一个过渡性的产品。从我的角度看,Sawtooth 的结构设计的比较精巧,可扩展性强,这是它比 Fabric 强的地方。但它还可以借鉴 Fabric,增加类似 CA 的机制确保用户可以被识别,也应该增加权限配置的灵活性,比如引入 ABAC,等等。不过,现在这样就已经非常不错了。
以上是关于Fabric 和 Sawtooth 技术分析(下)的主要内容,如果未能解决你的问题,请参考以下文章
特约专栏Hyperledger Fabric 源代码解读系列