区块链技术如火如荼,各种学习资料也层出不穷,很多人更关心如何“发币”,而作为开发者可能更关心:区块链究竟是什么?利用什么技术,解决什么问题?最早的区块链应用是比特币,随后出现的以太坊把该技术推向了大众的视野,二者都是以数字货币为目标的区块链应用。而 Hyperledger Fabric 的出现让区块链走出了“币圈”走向了实际应用。
Hyperledger Fabric 是 IBM 主导的一个开源的商用级区块链账本,它的架构是面向企业的联盟链或者私有链,它拥有卓越的性能,官方数据可以达到 3500TPS(比特币是 7TPS、以太坊是 15TPS)。本达人课是写给开发者看的区块链入门教程,共计9篇文章,涵盖了区块链和 Hyperledger Fabric 的基本知识,并通过“高校图书分享”的搭建,在实战中带大家快速掌握 Hyperledger Fabric 的开发。
本系列文章共包括三大部分:
第一部分(第01-02课),带您从技术角度了解区块链,从零开始搭建 Hyperledger Fabric 环境并感受区块链。
第二部分(第03-06课),介绍 Hyperledger Fabric 开发的基础,包括 Hyperledger Fabric 作为区块链系统背后的技术架构、工作原理。带领大家学习如何通过编写链码操作 Hyperledger Fabric,通过 SDK 把 Hyperledger Fabric 和我们的应用程序集成起来。
第三部分(第07-08课),以一个“高校图书分享”为例子开发一个区块链系统,介绍如何在真实的网络中部署该系统。
邢森,现就职于上海电信信息网络部担任架构师,公众账号“写程序的康德”运营者。喜欢探索研究新技术,发掘问题本质,用最精简的方式表达繁琐的问题。擅长分布式系统架构、云计算、SDN 网络,对 Linux 内核、TCP/IP 协议栈有狂热的兴趣。
读书不是为了雄辩和驳斥,也不是为了轻信和盲从,而是为了思考和权衡。
——培根
区块链技术的出现是一个意外。2008 年全球金融危机引起很多人的讨论和思考,在互联网上有一个匿名为“中本聪”的人提出了一种叫比特币的技术(比特币的出现是否和金融危机有直接关系不得而知)。他以《比特币白皮书:一种点对点的电子现金系统》为标题写下了一段文字,开头部分这样写道:
我们无法实现完全不可逆的交易,因为金融机构总是不可避免地会出面协调争端。而金融中介的存在,也会增加交易的成本,并且限制了实际可行的最小交易规模,也限制了日常的小额支付交易。
中本聪的这篇文章其实够不上“论文”的标准,这段文字有点颠三倒四。这里试着归纳一下:
同时中本聪还编写了一个简单的 Demo 放到 SourceForge 上(这是早起的开源软件聚集地,地位相当于现在的 Github)。他希望能建立起一套自治的系统自动记账以实现上述两点——交易记录不可篡改和数据分布式存放。读者如果对最早的代码感兴趣可以在 Github 上看到这份代码。
关于中本聪本人究竟如何思考我们不得而知,但是我们可以仔细思考一下。中本聪所描述和编写的比特币的核心技术是什么?是数据库系统或者叫存储系统,因为记录交易的是数据库,传统数据库是可以进行修改的;因为金融机构是用 IT 系统、用数据库、存储系统记录交易,而这些系统是可能出现故障的。在比特币中解决的本质问题是用一个不可逆的、分布式的数据库去代替传统数据库。
最开始的时候没人把比特币当做“真实的货币”,比特币能有今天的价格也实在是匪夷所思。当然始终对它感兴趣的人还是有的,比如一个名叫 Vitalik Buterin 的 俄罗斯 90 后,他就对比特币非常感兴趣,并且一直在为比特币社区做一些事情。2013 年的时候比特币社区的一些开发者开始讨论比特币的核心技术,也就是后来被称为“区块链”的技术。他们希望可以把这个技术单独剥离出来用在更多的场景中,Vitalik Buterin 就提出了可以运行任意形式(图灵完备)的应用程序而不仅仅是脚本。
比特币的脚本是指 Pay-to-Script-Hash(P2SH),它是为了解决多重签名的问题。Vitalik Buterin 强调的图灵完备性其实是针对 P2SH,P2SH 是基于堆栈的简单虚拟机,只有简单的堆栈指令;缺少结构化程序设计的分支、循环。而图灵完备性就是指结构化程序设计所必须支持的顺序、分支、循环。
Vitalik Buterin 这个想法没有让比特币社区有足够的兴趣,所以比特币并没有实现这个想法。2014 年 Vitalik Buterin 招募到开发者成立了以太坊项目,并且把上述思想写到白皮书中,称之为“智能合约(Smart Contract)”。这个名字带有强烈的“数字货币色彩”,或者说它的出发点还是为了解决“多重签名”的问题。
这个时期的区块链已经脱离比特币,成为一种独立的技术。如前文所述,比特币的核心技术就是一种数据存储技术或者叫数据库,但是这个概念还比较模糊,因为缺少数据访问接口(就像关系型数据库和 SQL)。无论是否有意,以太坊的智能合约都为解决这个问题提供了足够的思路和火花。
无论是比特币还是以太坊它们本质上还是数字货币,都是为了解决“金融问题”,所以它们都属于币圈。把比特币、以太坊的核心理念提取出来除了用于数字货币还可以有更加广泛的应用场景,这就是 Hyperledger Fabric 的历史使命。
Hyperledger Fabric 是 IBM 贡献给 Linux 基金会的商用分布式账本系统,自项目创立伊始就吸引了金融、银行、互联网、传统行业领域的巨头们的眼光。
Hyperledger Fabric 是基于 Golang 实现的可插拔的区块链系统,它主要面向企业之间或者企业多个部门之间提供服务。
上图是 Hyperledger Fabric 的逻辑架构图,垂直方向划分为三部分。
本达人课是从开发者角度理解、使用区块链,包括三大部分:
第一部分(第01-02课),带您从技术角度了解区块链,从零开始搭建一个 Hyperledger Fabric 环境感受区块链。
第二部分(第03-06课),介绍 Hyperledger Fabric 开发的基础,包括 Hyperledger Fabric 作为区块链系统背后的技术架构、工作原理。带领大家学习如何通过编写链码操作 Hyperledger Fabric,通过 SDK 把 Hyperledger Fabric 和我们的应用程序集成起来。集中的证书管理中心在企业中更为常见,所以最后为大家介绍 Fabric CA 的用法和集成方法。
第三部分(第07-08课)以一个“高校图书分享”为例子开发一个区块链系统,介绍如何在真实的网络中部署该系统。
开头的“名人名言”:
培根是英国经验学派创始人,现代科学所提倡的实验,就属于经验派的哲学理念。他们认为科学技术是通过经验归纳而来的,主张通过经验去探索知识。
当我们接触到一个新知识或者新技术时不应该去盲从它,如果没有经过思考和权衡而只是人云亦云的臆想,那么我们可能永远得不到“知识的真相”。就像区块链技术,有人说它是继互联网之后的另一伟大技术,那么我们就要问你所说的“互联网技术”究竟是什么?TCP/IP?HTTP 吗?HTML 吗?也有人把它和“去中心化”各种臆想的商业、法律概念联系在一起。真的是这样吗?思考是一切的开始。
木工是根据理念来制造我们所使用的床和桌子,按床的理念制造床,按桌子的理念制造桌子。其它事物亦同样。
——柏拉图
“区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。”这是百度百科对区块链的定义,很多资料直接引用了这个定义。这个定义不够完整,它只罗列了一些技术名词而没有正面回答区块链到底是什么?从语法上分析它最后的谓语“应用模式”是一个“空洞的概念”,——这是一个非常糟糕的定义。
区块链是一种分布式多写的、防篡改的数据库,这是本文对区块链的定义。谓语非常明显:是数据库,形容词也进行了精简——分布式多写、防篡改。一个好的定义不但直接描述它的特点(不是技术实现)而且要正面回答是什么。
提到数据库大家想到的是 MySQL、SQLServer,这些常见的关系型数据库(RDBMS)。这是比较狭隘的理解,幸运的是 NoSQL 的迅猛发展,让人们认识到了数据库是可以有多种多样的,比如:现在没有人会否认 MongoDB 是数据库。
传统 RDBMS 是基于通用场景下设计的,适用范围比较广。但是未来的场景是多种多样的,“通用数据库”不再一招鲜吃遍天。 2014 年图灵奖得主 Stonebraker M (关系型数据库领域专家),他曾经明确指出由于新硬件的发展和应用多样性的要求 DBMS 并不再“通用”。未来的数据库发展方向是领域数据库——即:针对某一业务或者场景而出现的专门的数据存储系统。
区块链是一个领域数据库,它的特点是“多写”、“防篡改”,基于这两个特点提供给我们的服务是共享的、可信的数据存储服务。
定义中有两个关键字——分布式多写和防篡改,这是区块链区别于其他数据库的特点。在架构中一般称呼这两个为架构属性,它是指一个技术架构所满足的特性。架构师的工作就是从架构属性推导出所需要的架构实现。
分布式系统常见的架构是 Master-Slave 架构。如上图所示,所有的请求都直接交给 Master 节点,由 Master 节点进行判断或者直接返回或者转交由 Slave 处理。以 MySQL 的 Master Slave 架构为例:
应用程序通过“数据库访问中间件”决定要访问的目标服务器地址,不同的数据库访问中间件有不同的设计策略。比如Mycat、MySQL Proxy 会对请求进行转发(图中红色的线);sharding-jdbc、Zebra 会直接决定目标服务器地址由应用发起实际请求(图中蓝色的线)。
“直接访问”模式要比“转发”模式高效,它一般是通过代码的方式和应用程序做集成,由应用决定把请求转交给哪个服务器处理。而“转发”模式中这个逻辑是由“中间件”(如 Mycat)完成的,应用程序对数据库是否分布式,由哪些节点提供服务无感知。
MySQL 也提供了多 Master 架构,但是这种架构设计的目的不是为了“多写”而是为了解决“单 Master 节点挂掉影响整个系统”。所以本质上 MySQL 的多 Master 架构还是 Master Slave。
本文避免用多 Master 这个术语,强调多写,可以用一幅图表示二者的区别。
多写是指每个应用可以访问不同(各自)的 Master 进行数据写入,数据一致性由数据库系统完成。
MongoDB 的分片技术也是一种“多写”架构,它内嵌了“数据访问层”,根据不同的数据“路由”到不同的节点完成写入(数据切片)。但是这种架构的前提是对数据进行切分,不同的数据被存储在不同的节点上。而区块链是“全局共享”——即每次写入影响是全局性的。
如果你有过一些系统集成或者运维的经验可能会发现上图的多写架构似曾相识。
左边的图是某厂商双活存储的架构图,两个应用各自访问不同的存储,存储之间通过高速网络(一般 Fibre Channel 互联)复制数据,当两个存储有一个出现问题时“仲裁”会决定谁有资格“活下来”。
一般这种硬件设备会配备多路径访问软件(如 Linux 下的 DM-Multipath),它的工作机制类似于“数据库访问中间件”,维护多个存储设备地址根据一定策略(比如:存活状态)决定把 I/O 请求转发给哪个设备。设备在收到 I/O 请求之后,对于读 I/O 请求,直接从设备读取返回;对于写入 I/O 请求,首先会进行并行访问互斥,获取写入权限后,将 I/O 请求同时写入到本地 Cache 和对端的 Cache。
区别于数据库的架构,双活存储在写入数据的时候会竞争锁,比较常用的算法是 Paxos 。拿到锁的存储才有资格进行“写入”操作,写入的时候要两边都要写入。
双活的写其实是“双写”,区块链的多写涉及到系统中所有的节点是地地道道的“多写”,所以双活的系统架构不适合区块链,但是技术架构通常都是可以相互借鉴的,从双活存储的架构上能看到“多写”面临的技术挑战——并发访问。
如图所示,初始状态四台服务器的数据库都是一致的;某一时刻 S1 写入了一条新数据 a=10,通过网络复制到其他节点,因为网络一定会有延时(再牛逼的网络传输数据也需要时间)所以 S3 可能在没有收到数据之前也产生了一条数据 a=20。这就是并发要解决的问题。
原则上每个服务器收到一个“写入请求”后立即写入,只要是按顺序写入肯定没有问题。遗憾的是这个“只要”在网络上是不可能实现的,网络本身就是有延时的、“并发”的。客观上 S1 发送 a=10,S3 后发送 a=20,但是每个服务器收到的次序很可能不一样,比如 S2 最先收到 S1 的 a=10 然后收到 a=20,而 S4 很可能先收到 a=20 然后才收到 a=10,此时大家的数据就不一致了。
并发要解决的问题是用一种机制保证数据一致性,即保证大家最后看到的顺序是一致的。在并发中对数据的修改在微观层面都会变成“串行”。如果没有一个“固定”规则决定次序那么结果就是不可预测的。仔细回忆一下,并发中的锁是不是在解决类似的问题?我们借助锁让执行结果可预测,用一个规则让乱序的“偶然”变成顺序的“必然”。
在多写或者去区块链中这个规则就叫共识算法。
到目前为止搞清楚要解决的问题了——多写一定要保证数据最终一致性;也搞清楚解决问题的办法——通过“固定的”规则决定顺序,节点中的所有服务器只要遵循这个规则那么数据最终一定一致。
很多读者不止一次尝试理解 PoW 的工作原理,现在本文给用几幅图表示 PoW 的工作原理,看它是如何解决“数据最终一致”的问题。
当 S1 产新数据的时候并没有把数据直接写到数据库,而是向网络中发送了一条广播“<算个 Hash 值,a=10>”,这个 Hash 值只能“遍历”没有投机取巧的办法。
S3 也产生了新数据并且发送广播“<算个 Hash 值,a=20>”,于是网络中所有的机器都吭哧吭哧的算 Hash 值(也包括 S1 和 S3)。注意,此时网络中所有节点都收到了 S1 和 S3 的新增请求(没收到的直接就已经被淘汰了,即便算出来也不可能有别人“长”)
S4 大吼一声,老子算出来了,把算出的 Hash 值和自己收到数据的顺序打包在一起。S1、S2、S3 最终都会收到这条数据,然后校验一下——哎呦,真的厉害啊,听大佬的。于是按 S4 给出的 a=20,a=10 的顺序写入数据库,最终a=10。
PoW 是一个非常“搞笑”的算法,数据库最重要的是吞吐率,但是如果按照 PoW 算法来解决多写一致性问题那么吞吐率一定很慢,因为这个算法本身就是要用“慢”解决问题。当然这个“慢”也不是完全没用途,比特币中要控制出币的速度,充分利用了“慢”的特点。
比特币和以太坊都是一种电子货币,它们都基于 PoW 算法,大家最早熟悉区块链很多时候都是从它们开始的,结果都被带到沟里去了。它们只是作为区块链在“发币”这个场景下的应用,而区块链技术本身的概念不太会被接触到,所有我身边有很多朋友搞了大半年区块链一张嘴不是钱包就是货币。
鉴于“拜占庭将军问题”的例子太烂,直接总结一下问题的本质:拜占庭将军问题是要解决在一个消息可能丢失、重复、伪造的环境达到数据一致问题。
接下来看 Practical Byzantine Fault Tolerance(PBFT)算法,它的过程如下:
PoW 目的是降低“数据写入速度”,这个恰恰是数据库最重要的性能指标。所以其实它是一个“玩具式”的算法,有点开玩笑逗你玩的意思。 PBFT则具有很强的工程意味,全网通过一个排序节点决定顺序,非常直接的解决问题。
PBFT只适合于内部使用(联盟链或者私有链),因为互联网上不太可能确定“全网排序节点”。
数据库写入数据都是按照一定格式写入的,只要我们经过精心设计就可以通过直接写入文件的方式把数据库里面的数据修改掉(直接修改数据文件)。这是区块链要解决的第二个问题——防篡改。
如何发现一个文件的变化?最简单直接的办法是切割成 N 个小块,比如每一小块 1k,然后针对每块数据做 Hash 形成校验和(Checksum)。当数据发生变化时我们只要比对两次的 Checksum 列表有哪些差异就知道哪一部分发生了变化(比如 rsync 就是这种设计,一些云盘的备份也是类似的算法)。
但是这种方法并不适合区块链,在区块链中一个区块中会包含多条事务,这些事务的大小并不是固定的,按数据文件的方式记录和切割是不合适的。所以区块链采用了新的数据结构,用一棵二叉树来记录数据的 Hash 值。
如上图所示,所有的数据(事务)都放在二叉树的叶子节点,非叶子节点是其对应子节点的 Hash 值。当数据发生变化会引起非叶子节点的连环变化,最终引起 Root 的变化。从 Root 往下遍历又可以很容易判断出是哪一块数据发生了变化。
这个二叉树有个专门的名字(被申请专利了)叫 Merkle Root。
区块链的底层存储是一个链表,包括区块头和区块体。头部是一个链表结构,分别是前一块的 Hash 值、Merkle Root、本数据块的 Hash 值(Nonce);区块体是一个树结构,叶子节点是事务(Tx),其他节点是下级节点的 Hash 值,最终树根的 Hash 值就叫 Merkle Root,放在头部。
如果数据发生变化会引起 Merkle Root 的变化,此时本区块的 Hash 值也会发生变化,那么“该节点的所有前驱节点”都会发生变化。
如图所示,修改第二个数据块就必须把第三个数据块也修改掉,以此类推第四、第五、第六……。区块的设计环环相扣,任一区块若被篡改,都会引发其后所有区块哈希指针的连锁改变。所以如果要篡改某个区块必须把该区块的所有前驱都给修改掉,修改比较麻烦——注意不是不能修改
对于比特币来说仅仅做到这一点是不够的,它毕竟是数字货币,利益诱惑比较大。所以比特币设计了两个门槛:
比特币通过这两种策略让篡改变得“算力不够”,如果未来算力突然爆发(比如量子计算机)那么比特币也就没得玩了。
区块链是特殊领域的数据库系统,它有两大特征:分布式多写一致性(共识算法),防篡改。不同于常见的 Master Salve 架构和双活架构,区块链的多写架构是指每个节点都可以参与写数据,系统必须保证数据最终一致。防篡改是指数据写入之后,如果有人直接通过“读写文件”的方式篡改数据,系统必须有办法感知到。比特币、以太坊是区块链的特殊应用,它们运行在公网环境追求的是数字货币,所以它们采用了不在乎效率的 PoW 算法,存储结构上也根据 PoW 的特点做了特殊设计让篡改变得“算力达不到”。
开头的“名人名言”:
理念论是柏拉图最重要的哲学贡献,他认为所有的东西在被“造”出来之前一定是先有一个“理念”在我们的头脑里。比如木工做椅子,那么他一定有一个“椅子的概念”在脑海里。我习惯在正式学习任何东西之前先有一个基本概念在脑海里。(1)它解决什么问题,(2)它用什么方式解决这些问题,(3)有没有和它相似的解决方案,有没有和它相似的产品。
一切知识都从经验开始。
——康德
Hyperledger 是一个开放源代码的区块链和相关工具的总括项目,由 Linux 基金会管理。Fabric 是一个商用分布式账本,它最早由 IBM 和 Digital Asset 贡献给了 Hyperledger 项目。它主要用于“企业内部使用”,技术架构选择了和比特币、以太坊完全不同的策略。
多个组织共同组成的一个区块链系统叫联盟链,一个组织内部多个部门组成的区块链系统叫私有链。这两种区块链都属于“内部系统”,Hyperledger Fabric 就是适用于这样场景的区块链系统。
比特币、以太坊属于数字货币,所以它们不在乎“写入速度”,甚至刻意限制写入速度(印钞票的速度不能太快)。Fabric 被设计为一个数据存储系统,写入速度是一个非常重要的性能指标,所以不可能采用 PoW 算法。
回忆一下上一篇文章的内容,共识就是要解决排序问题。 Fabric 采用了一个很简单、粗暴,而又充满工程化考虑的设计方案——排序节点(PBFT 算法)。在整个网络中有一个或者一组节点充当排序节点,其他节点都按照排序节点给出的顺序进行数据写入,完整的过程如下图所示。
应用程序首先向所有 Peer 节点(也有文档叫背书节点、记账节点)发起“记账要求”(Proposal);每个节点把数据记录下来——此时还没有提交到状态数据库(没有生效),然后返回应用程序一个“事务签名”(Proposal Response);应用程序把“事务签名”发送给排序服务器,排序服务器可能同时收到很多事务,按照一定规则给出事务的顺序,发送给记账节点;记账节点在收到排序服务器的顺序后会校验一下“事务签名”确定是自己给出的票然后更新状态数据库,数据生效。这个过程可以简单的归纳为投票 -> 排序 -> 验证。
排序服务器排序的规则有两种,SOLO 和 Kafka 。可以把 SOLO 机制理解为单机版的队列,排序服务器按收到的顺序依次放入队列。SOLO 的性能并不高,在开发环境中用这种机制,生产环境建议使用“真刀真枪的 MQ”——Kafka。
关于 SOLO 这个名字我做了一下考证(像我这么无聊的人才会干出这么无聊的事情)。在一些合作的演出中可能会有大段的“个人 Show Time ”,比如在慢摇(摇滚乐)中会有一大段吉他独奏;集体舞蹈中穿插了个人独舞。通常称这个为 SOLO 。在一些游戏中(比如 LOL、风暴英雄、英雄联盟)有些玩家喜欢 1v5 卖弄风骚、 1v1 单打独斗或者单独带线(专业型英雄),这种行为也叫 SOLO。所以我觉得引入这个名字的开发者应该是个游戏玩家,用这个名字暗示——我单,你们团(“单”是 SOLO 机制,“团”就是 Kafka 集群咯)。
比特币防篡改的机制是基于 PoW 精心设计的,如果要修改数据那么必须“算力足够”,所以比特币的一切都是围绕 PoW 设计,而设计目标则是——发币。对于没有采用 PoW 的 Fabric 来说防篡改机制必须另谋出路。
这个“出路”其实不是技术考虑,更多的是业务场景考虑。Fabric 主要面向内部使用(联盟链或者私有链)“内部环境”比较容易实现“审计机制”,我们可以通过 Fabric 的 Event 记录整个系统的运行过程,当数据不一致的时候通过“审计机制”发现谁的数据有问题(需要人工介入)然后把它“踢”出去。简单来说——Fabric 的防篡改是“人肉审计”机制。
Fabric 提供一个模块化的构架,其中的节点、智能合约、共识机制、成员服务都是模块化,可扩展的。如果开发者觉得 Fabric 的设计策略不适合,那么可以做相关的扩展开发。
Fabric 中的概念比较多,为了方便理解这里用和数据库做类比的方式介绍。
Fabric 网络可以是由一个物理节点组成,也可以由多个不同的物理节点组成。一般在开发环境中运行在同一个物理节点上(开发者自己的机器),在生产环境中需要根据组织或者部门在不同的节点上启动相关进程。
Fabric 提供了各个平台的二进制文件,但是一般不直接使用这些二进制文件,而是通过容器的方式部署系统。
本次搭建的实验环境是一个联盟链,有两个组织组成。拓扑结构如下:
涉及到五个节点,org1、org2 分别表示两个组织,他们各自提供两个 Peer,其中 Peer0 和 Orderer 节点通讯拉取状态数据,Peer1 则从 Peer0 中获得更新,所以 Peer0 是 Org1 的“代表",在 Fabric 中叫锚节点 Peer (Anchor peer)。
(1)配置证书。
在 Fabric 网络中节点之间通讯(包括 Client )都需要提供“证书”用于标识自己的身份。因为证书太多,Fabric 提供了叫 cryptogen 的工具快速生成相关证书。
mkdir fabric-env && cd fabric-env
新建一个 fabric-env
文件夹存放环境相关的所有配置文件和数据文件,用编辑器创建 crypto-config.yaml
文件:
后续如没有特殊说明所有操作都是在
fabric-env
目录下进行,文中出现的工作目录也是指这个目录。
保存之后目录结构如下图所示:
docker run --rm -v `pwd`:/data hyperledger/fabric-tools:x86_64-1.1.0 \ cryptogen generate --config=/data/crypto-config.yaml --output=/data/crypto-config
docker run
启动一个容器,添加上 --rm
参数表示容器结束之后自动删除,-v
指定把工作目录挂载到容器的 /data
目录中,hyperledger/fabric-tools:x86_64-1.1.0
表示使用的容器镜像版本是 x86 的 1.1.0,cryptogen 是要执行的命令。
fabric-tools 镜像包含了 Fabric 所有的二进制程序并且设置 PATH 变量,可以直接通过 Docker 执行相关命令,不必指定完整路径。
cryptogen 进程读取名为 /data/crypto-config.yaml
的配置文件(容器中的 /data
目录,其实就是工作目录映射进去的)。--output
参数指定 cryptogen 进程证书文件输出到 /data/crypto-config
中。
Linux 或者 Mac 用户通过
pwd
获取当前目录,Windows 用户可以通过%cd%
获取当前目录。上面命令可以变成:
docker run -v %pwd%:/data --rm hyperledger/fabric-tools:x86_64-1.1.0 cryptogen generate --config=/data/crypto-config.yaml --output=/data/crypto-config
执行后屏幕上会输出 org1.fireflyc.im
的字样,程序结束后查看当前文件夹已经多了一个叫 crypto-config
的文件夹。
(2)配置系统链创世块。
Fabric 系统配置是存放在区块链上的,系统成功运行至少要有一个链——系统链。系统链的第一个数据块保存了用户定义的一些配置,不能够统一自动生成,所以需要通过 configtxgen 生成。
用编辑器创建 configtx.yaml
文件:
configtxgen 读取 configtx.yaml 文件和 crypto-config
文件夹下的证书用于可以生成 Channel 的创世块。
mkdir configtx && docker run -v `pwd`:/data -e FABRIC_CFG_PATH=/data --rm hyperledger/fabric-tools:x86_64-1.1.0 \ configtxgen -profile TwoOrgsOrdererGenesis -outputBlock /data/configtx/genesis.block
configtxgen 会去环境变量 FABRIC_CFG_PATH
所指示的路径下搜索 configtx.yaml,configtxgen 不会自动创建 configtx 目录,所以需要在执行 docker 命令之前手工创建。
生成的 genesis.block 是系统链的第一个区块,里面存放了 Orderer 节点、Orderer 管理员的证书,在 Orderer 节点启动时会用到这个数据块。最终的目录结构如下:
使用数据库时开发者一般不直接在数据库的“系统数据库”中创建表,使用 Fabric 时也不会直接使用系统链(Channel),所以需要单独创建一个“用户级别”的 Channel。
创建 Channel 需要生成两类数据块,一个记录 Channel 配置的数据块(只有一个),一个记录 Anchor Peer 配置的数据块(每个组织一个)。
创建第一类数据块,Channel 的名字叫 hello。
docker run -v `pwd`:/data -e FABRIC_CFG_PATH=/data --rm hyperledger/fabric-tools:x86_64-1.1.0 configtxgen -profile TwoOrgsChannel -outputCreateChannelTx /data/configtx/hello.tx -channelID hello
创建第二类数据块,Org1 和 Org2 的 Anchor Peer。
docker run -v `pwd`:/data -e FABRIC_CFG_PATH=/data --rm hyperledger/fabric-tools:x86_64-1.1.0 \ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate /data/configtx/Org1MSPanchors_hello.tx -channelID hello -asOrg Org1MSPdocker run -v `pwd`:/data -e FABRIC_CFG_PATH=/data --rm hyperledger/fabric-tools:x86_64-1.1.0 \ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate /data/configtx/Org2MSPanchors_hello.tx -channelID hello -asOrg Org2MSP
最终的目录结构如下图所示:
(1)编排应用。
Docker 的命令行一次只能启动一个容器,多个容器在一起形成的一个系统,称为容器编排,Docker Compose 就是一种编排工具。
Docker Compose 在你安装 Docker 的时候已经安装了,运行它需要提供给一个 docker-compose.yaml
的配置(编排)文件。
networks 指令定义了所有容器使用的虚拟网络(网桥),接下来进行 orderer 节点的配置,系统链的创世块映射到相应的目录中;orderer 节点的 MSP 证书映射到相应目录中;把 var 目录映射进容器中,orderer 节点的数据会回写入在这个目录下。
Peer 节点同样也需要 MSP 证书和 var 目录(Peer 节点产生的数据回写到这个目录下)。/var/run
目录是必须的,在 Fabric 中 Chaincode 是基于容器运行的,Peer 节点会在收到“实例化” Chaincode 的指令后启动一个容器。所以要保证 Peer 节点可以调用底层的 Docker。/var/run
文件夹中存放了 Docker 的 Unix Socket 文件,通过这个文件可以调用 Docker 的 API。
Chaincode 启动之后需要和 Peer 保持网络通讯,要保证二者在同一个虚拟网络中。通过 CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE
参数指定 Chaincode 网络所连接的网桥(Docker Compose 会把网桥加上一个 Project Name 的前缀,运行时我们通过命令行的环境变量 PROJECT_NAME
把 Docker Compose 生成 Project Name 传递过来)。
通过 cli 完成 Channel 操作(前面只是生成数据块并没有附加到链上);发布、升级、实例化 Chaincode。所以这个镜像需要 org 管理员证书,Channel 的数据块(映射 crypto-config
、configtx)。
完成上述配置后执行 PROJECT_NAME=fabricenv docker-compose up -d
启动容器,-d
参数指定把容器转入后台运行。
输入 docker-compose down
停止并且删除所有容器。
(2)初始化第一个链。
启动 Farbic 网络后可以看到5个容器正在执行。
查看 var 目录下的 orderer 回写的数据,在 chains 文件夹中只有一个系统链(叫 testchainid 是不是一个很“个性”的做法?囧),之前创建的 Hello Channel 并没有被生效,这是因为之前只是生成 Channel 的数据块并没有把数据块附加到链上。
通过 cli 容器的工具把之前生成的 Hello Channel 附加到链上:
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ cli peer channel create -o orderer.fireflyc.im:7050 -c hello -f /opt/configtx/hello.txdocker exec cli mv /hello.block /opt/configtx/
两个 CORE_PEER_xxx
环境变量表示是以 Org1 的身份执行后续命令的,peer channel create
会把之前生成的事务块附加到链上,并且返回新的数据块放入本地,名称为 hello.block。后续执行 Channel 的更新操作需要提供该文件,所以我们把它移动到 /opt/configtx
目录下。
上述命令只是在 Orderer 节点上创建了 Channel,Peer 节点和 Channel 还没有做关联。
通过下面的命令把 org1、org2 各自的 peer0、peer1 加入到 Channel:
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ -e CORE_PEER_ADDRESS=peer0.org1.fireflyc.im:7051 \ cli peer channel join -b /opt/configtx/hello.blockdocker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ -e CORE_PEER_ADDRESS=peer1.org1.fireflyc.im:7051 \ cli peer channel join -b /opt/configtx/hello.blockdocker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org2.fireflyc.im/users/Admin@org2.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org2MSP" \ -e CORE_PEER_ADDRESS=peer0.org2.fireflyc.im:7051 \ cli peer channel join -b /opt/configtx/hello.blockdocker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org2.fireflyc.im/users/Admin@org2.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org2MSP" \ -e CORE_PEER_ADDRESS=peer1.org2.fireflyc.im:7051 \ cli peer channel join -b /opt/configtx/hello.block
执行成功后应该可以在 Peer 节点下看到 hello 文件夹。
接下为更新 Channel,指定 Anchor Peer,只需要更新:
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ -e CORE_PEER_ADDRESS=peer0.org1.fireflyc.im:7051 \ cli peer channel update -o orderer.fireflyc.im:7050 -c hello -f /opt/configtx/Org1MSPanchors_hello.txdocker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org2.fireflyc.im/users/Admin@org2.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org2MSP" \ -e CORE_PEER_ADDRESS=peer0.org2.fireflyc.im:7051 \ cli peer channel update -o orderer.fireflyc.im:7050 -c hello -f /opt/configtx/Org2MSPanchors_hello.tx
Fabric 网络提供了一套基础架构,可以理解为数据库。对这个数据库进行操作必须通过专门的“工具”,比如关系型数据库中有 SQL、存储过程,在 Fabric 中就是 ChainCode。通过上面的配置我们已经有了一个基本的 Fabric 网络,现在我们尝试让这个网络做一些实际的工作——发布一个 ChainCode。
在 Fabric 的源代码中提供了一些 ChainCode 例子,这里用 example01 作为演示。该例子中只有一个 golang 文件。在工作目录下新建 chaincode/demo1
文件夹,把文件下载到这个文件夹中。
修改 docker-compose.yaml
,把 ChainCode 挂载到 cli 容器中,然后再次启动 Fabric 网络:
启动成功后通过 cli 安装 ChainCode:
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ -e CORE_PEER_ADDRESS=peer0.org1.fireflyc.im:7051 \ cli peer chaincode install -n demo1 -v 1.0 -p github.com/demo1
安装 ChainCode 的过程就是把代码复制到 Peer 的过程,注意日志输出中的 WriteFileToPackage 部分:
因为我们是在 Peer0 上安装的 ChainCode,所以现在只有这个上面有相应的数据,也只有这个节点才可以进行后续的实例化。
chaincode_example01
中有一个 Init 函数,它接收四个参数,分别表示第一个变量的名字和值,第二个变量的名字和值。比如下面的命令实例化的时候分别设置变量 a=100,变量 b=200:
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ -e CORE_PEER_ADDRESS=peer0.org1.fireflyc.im:7051 \ cli peer chaincode instantiate -o orderer.fireflyc.im:7050 -C hello -n demo1 -v 1.0 -c '{"Args":["init", "a", "100", "b", "200"]}'
ChainCode 和 Channel 没有强绑定关系,install 的过程仅仅是复制代码。只有在实例化的时候才需要指定在哪个链上启动,后续 ChainCode 的写入操作就直接针对这个链,相当于做了二者的绑定。
实例化的动作只需要做一次,后续在其他 Peer 上执行 install 后可以直接 invoke。
-C
参数用于指定绑定的 Channel,-n
指定实例化的 ChainCode,-v
指定版本,-c
指定传递的参数。
实例化成功后可以看到多了一个容器:
通过 docker logs -f 390df4f5e701
查看日志:
接下来通过 invoke 调用里面的方法。例子中提供了转账过程的演示,提供一个变量,A 减少这个数字,B 增加这个数字。
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/crypto-config/peerOrganizations/org1.fireflyc.im/users/Admin@org1.fireflyc.im/msp \ -e CORE_PEER_LOCALMSPID="Org1MSP" \ -e CORE_PEER_ADDRESS=peer0.org1.fireflyc.im:7051 \ cli peer chaincode invoke -C hello -n demo1 -v 1.0 -c '{"Args":["invoke", "10"]}'
为了方便看到 ChainCode 中的变化,演示中在新窗口执行上述例子。可以看到 A 已经变成了90,B 变成了210。
本文主要介绍了 Fabric 作为区块链系统,它的共识、防篡改的设计方法,并利用 Docker 和 Docker Compose 从 0 搭建了一个 Fabric 网络,实际部署了一个 ChainCode 去操作链完成数据的读写。这个环境以后会作为我们的开发环境,后续的操作都基于这个环境进行。
本文所涉及到的配置文件都放在 Github 上,涉及到的命令都在 create_hello.sh
文件中。
开头的“名人名言”:
这句话截取自《纯粹理性批判》的开头:“我们的一切知识都从经验开始,这是没有任何怀疑的……尽管我们的一切知识都是以经验开始,它们并不因此就都是从经验中发源的”。我们学习一个工具应该先从经验入手,通过动手操作去熟悉它的运作方式,但是仅仅操作也是不够的,知识只是从经验开始而不是“发源地”。真正让我们掌握一个工具的应该是用我们已经掌握的“理论”去统辖它。
阅读全文: http://gitbook.cn/gitchat/column/5b1798c0a6e54424e9335528