

半夜里写这篇文章时不知怎么头脑中冒出这么一句话来,可能是有感于在浮华的社会去追寻世道本源的灵光吧,放在讲区块链技术的文章里起头也算有一点点关联。话说人之社会的礼法规约,世俗人情也是这亿万分布众生几千年共识而成。闭塞社会的共识形成会历经几朝几代,而信息社会的共识形成也就几年几月,机器社会的共 识则会是弹指一挥间,所带来的是社会化大生产的协同效率的根本性的提高,一切尽在不言中,一切却都已使使然。
区块链大家都已经并不陌生,其本质我认为还是想用大量分布的机器去模拟和取代人类社会的行为,用机器间的通讯和数学形成共识取代人类用言语交流形成共识,乃至形 成群居动物式的各型自治组织。由于个体能力的差异,错综复杂的人性和社会关系,信息传播的延迟和谬误,导致信息在个体分布的极大不对称,才形成了各种中心化的强权组织和阶级分化。信息社会特别是社交媒体的出现已经极大减少了这种不对称,当前所谓以客户为中心,强调客户体验也是由于信息在生产者和消费者之间 变得不是那么不对称的结果。那么我们可能做到绝对的信息对称吗,答案应该是否定的,至少在我们人类还能掌控这个社会的阶段是不可能的。信息对称也只能做到 相对对称,这就决定了在机器社会至少还是存在中心化组织的,或弱中心化的组织。但即使是这样,这也会是一种深刻且迅速的社会关系变革,我们期待并拥抱这种 变革。
从比特币到Ripple币和以太坊,从超级账本到R3 Corda,不同的社会主体会出于不同的理想愿景和战略目的,但迫于社会趋势这些区块链技术肯定都会是开源的,我们没有理由去拒绝任何方式的区块链技术。 我在之前写过关于以太坊的初体验文章《使用Ethereum创建自己的BlockChain私链》,如有兴趣,请关注本公众号,查看历史文章。标准,实 现,治理是任何技术成功应用的三要素,区块链技术和应用随着时间推移也必然会在这三要素上越来越丰满。当前还处于技术野蛮发展的过程,但是我们需要有这个 趋势性的认识,为了能在更多的领域运用区块链技术,在标准形成之前,技术框架和平台显得很重要,这也是标准形成的基础。
Linux基金会主导的超级账本项目就是希望从实现角度成为一个具有公信的为企业提供模块化开源组件和平台的国际协作组织。
超 级账本Fabric是由IBM基于其Open Block Chain贡献出来的通用技术框架和平台解决方案,特别是针对商业应用设计,考虑到安全,性能和可用性。另外两个方案分别是Intel贡献的 Sawtooth Lake和Soramitsu贡献的适用移动应用的iroha。Sawtooth Lake提供了适用于大用户群的流逝时间证明(PoET,Proof of Elapsed Time)和适用于即时交易的法定人数投票(Quorum Voting)2种共识算法。本文是关于超级账本Fabric的初体验探索,适合于初学技术人员参考,文中也会夹杂个人的理解。
不 像Ethereum执行smart contract有自己专门的vm和指令集,Fabric 执行chaincode的vm目前的唯一选择是采用docker容器,不过其插件形式的架构在未来也可能会集成进其他的专用vm作为chaincode的 部署和执行容器,但是目前使用docker容器方式运行Fabric所有的服务(member service和validator peer)无疑是最方便的,docker本身也是目前开源虚拟化最好最热的技术,其已经作为各大云平台的构建必备,这也是blockchain上云最好的 路径。
我们最终的验证架构将会是实现一个由4个验证节点+1个成员服务节点+2个客户端节点构成的多docker容器架构。注:拜占庭容错(PBFT,Practical Byzantine Fault Tolerant)共识算法最少需要存在4个验证节点。

图 中每一个最外层的方框都是一个进程(docker container),蓝色的为Hyperledger Fabric组件,包括独立运行的4个验证节点和1个成员服务节点,以及以API方式提供的Node.js HFC客户端组件,绿色的是应用程序开发组件,包括两个应用客户端和4个在docker container编译和部署运行的chaincode vm,这4个vm分别对应于4个验证节点,客户端通过Node.js SDK HFC同验证节点进行通讯,执行chaincode的部署,调用和查询。每一个chaincode的部署,这4个验证节点都会全新启动4个docker container执行chaincode的编译和部署,后续客户端对chaincode的调用也是通过验证节点分别调用这4个docke chaincode vm执行验证的。
本次实践我们部署的docker都是运行在一个Ubuntu Linux上,这个Ubuntu Linux是我通过vmware虚拟出来的一个虚机,虚机网络都采用NAT方式,共享host主机的外部网络。其主要参数如下:
Ubuntu 16.04 LTS
IP: 192.168.5.172 gateway: 192.168.5.2 hostname: ubu-blockchain2
Docker Network Bridge
gateway: 172.17.0.1 上图11个docker containers: 172.17.0.2~12
下文将分成几个章节循序进行说明:
一、Fabric环境资源准备
1. docker的安装
Ubuntu Linux请参照 https://docs.docker.com/engine/installation/linux/ubuntulinux/ 进行ubuntu版本的安装,其他的OS版本请参照https://docs.docker.com/engine/installation/。
1.1 更新apt 源
安装https和ca确保apt可以以https进行通讯
$ sudo apt-get update
$ sudo apt-get install apt-transport-https ca-certificates安装新的GPG key
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D添加apt源docker.list
$ echo "deb https://apt.dockerproject.org/repo ubuntu-xenial main" | sudo tee /etc/apt/sources.list.d/docker.list
1.2 确认新增的apt源从正确的docker库pull程序
$ sudo apt-get update
$ sudo apt-cache policy docker-engine
1.3 为了docker可以使用aufs存储images,需要安装linux-image-extra-*内核包
$ sudo apt-get update
$ sudo apt-get install linux-image-extra-$(uname -r) linux-image-extra-virtual

2. 执行docker安装
2.1 执行apt-get docker安装
$ sudo apt-get update
$ sudo apt-get install docker-engine
2.2 配置docker
1)配置docker dns
2)docker daemon启用2375端口 (未来安全起见启用带有TLS的2376替代端口)
$ sudo mkdir -p /etc/systemd/system/docker.service.d/
$ sudo vi /etc/systemd/system/docker.service.d/etc/default/docker-tcp.conf
ExecStart=/usr/bin/docker daemon --dns 192.168.5.2 -H=tcp://0.0.0.0:2375 -H=unix:///var/run/docker.sock

3)配置当前用户可以执行docker
由于默认只有root用户才可以执行docker,如果当前用户想执行docker命令,需要将当前用户加入到docker组中。
$ sudo groupadd docker
$ sudo usermod -aG docker $USER
2.2 安装后启动docker并确认
1)启动docker服务并确认开启tcp socket和unix socket
$ sudo systemctl docker.service start
$ sudo ps -ef | grep docker
$ sudo netstat -anp | grep 2375
$ sudo netstat -a | grep docker.sock
2)执行验证安装用的hello-world docker
$ docker run hello-world
$ docker images
$ docker ps -a

3. 执行docker-compose安装
docker-compose 是用于定义和运行复杂docker应用的工具,以yaml定义语言在一个docker-compose.yaml文件中定义一个包括多容器的应用,用一条命令即可启动应用中包括的所有docker container,容器启动所有依赖的动作都会被工具自动完成。
curl -L https://github.com/docker/compose/releases/download/1.3.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
如果curl没有安装,使用sudo apt-get curl 执行安装
至此,我们所有的资源准备工作都已经做好,下面开始进行Fabric开发环境的构建。
二、Fabric开发环境构建和验证
使用docker-compose比较方便的地方是,可以使用别人已经编好的docker-compose.yaml 自动从docker hub pull images并且自动构建应用。
Fabric的应用开发包括应用客户端开发和chaincode开发两个部分。开发环境主要是验证客户端到chaincode的部署,调用,查询可以正常执行,对于多验证节点和拜占庭容错pbft是不必须的,这些都是基础架构需要搭建好的,基础架构的搭建会在“第三章节 Fabric运行环境构建和验证”中说明。开发环境会启动一个验证节点并采用无共识算法的noops设置。
1. 构建包括hyperledger组件的docker images
1)首先从hyperledger github获取样例docker-compose.yaml 和 Dockerfile
$ curl -o docker-compose.yml https://raw.githubusercontent.com/hyperledger/fabric/master/examples/sdk/node/docker-compose.yml
查看其内容

可以看到其定义了3个container service:成员服务(membersrvc),单节点(peer),客户端(starter)。
其中peer定义的共识算法plugin的参数设置CORE_PEER_VALIDATOR_CONSENSUS_PLUGIN=noops,即无需共识。
membersrvc和peer是标准的hyperledger fabric组件,采用hyperledger在docker hub发布的images hyperledger/fabric-membersrvc:latest和hyperledger/fabric-peer:latest,不再做自定义的docker build,而starter作为开发客户端会经常改变,所以我们可以自己build个性化的开发客户端,此处我们从github样例库中下载Dockerfile,进行本地docker image的构建。
$ curl -o Dockerfile https://raw.githubusercontent.com/hyperledger/fabric/master/examples/sdk/node/Dockerfile下载后查看其内容

其也是基于hyperledger/fabric-peer:latest 进行定制化的build,go编译出示例chaincode: github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02,然后在带有Node.js app.js 的目录用Node.js 的npm安装app.js运行所依赖的js库hyperledger fabric client (hfc)。
2)执行客户端Dockerfile的docker build
在Dockerfile所在目录执行这个docker image的build,设置本地tag为hyperledger/fabric-starter-kit:latest
$ docker build -t hyperledger/fabric-starter-kit:latest .由于hyperledger/fabric-peer:latest本地库并不存在,需要从docker hub下载

上述过程执行完成后就会产生hyperledger/peer:latest和hyperledger/fabric-starter-kit:latest 2个image。由于此image很大,之前也下载过,这里就中断后用其他方式执行。
由于我已经在另外一台机器(ubu-blockchain1)上下载了这个image,可以通过image复制的方式导入到这台机器上
在ubu-blockchain1上执行
$ docker save -o fabric-peer.tar hyperledger/fabric-peer:latest
在ubu-blockchain2上执行
$ docker load -i fabric-peer.tar
也可以在ubu-blockchain1上用管道+scp命令方式直接到ubu-blockchain2的对导
$docker save hyperledger/fabric-peer:latest | bzip2 | pv | ssh rodger@192.168.5.172 'bunzip2 | docker load'再次执行docker build


执行完后查看本地repository中的images
$ docker ps -a

可以看到产生了hyperledger/peer:latest和hyperledger/fabric-starter-kit:latest 2个images。
而启动这个starter客户端的docker-compose.yml中在定义starter时,设置了自启动命令:
command: sh -c "sleep 20;
/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02/chaincode_example02这个就是我们编译好的chaincode代码,在测试环境启动时一起运行。
这样我们运行这个docker container后,就可以通过node app执行客户端程序调用这个编译好的chaincode代码了。
3)以同样的方式从docker hub pull hyperledger/fabric-membersrvc:latest
执行docker pull命令
$ docker pull hyperledger/fabric-membersrvc:latest
注:这一步骤也可以在下一步执行时由docker-compose自动执行。
此处我们还是采用从已有机器上复制image的方式
在ubu-blockchain1上执行
$ docker save -o fabric-membersrvc.tar hyperledger/fabric-membersrvc:latest在ubu-blockchain2上执行
$ docker load -i fabric-membersrvc.tar
当前的images如下:

4)运行fabric开发环境
在包括docker-compose.yml的目录执行命令,启动所有定义组件 membersrvc,peer和客户端starter
$ docker-compose -f docker-compose.yml up -d
$ docker attach peer

5)客户端执行chaincode部署,调用,查询
我们先看一下这个要执行的chaincode,先登录到starter docker container上
$ docker exec -it starter bash在starter docker container内进入待部署的chaincode目录,查看其内容
# cd /opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02
# vi chaincode_example02.go
一个chaincode包括3个public方法:Init,Invoke,Query
Init方法定义了chaincode在部署时执行的动作,其中就包括了将状态写入区块链world state中。上图的Init方法初始化通过参数送入的两个状态A,B,并且将状态A,B的资产值通过PutState方法写入到区块链world state中。

Invoke方法执行chaincode的状态调整,调整后的值也需要通过PutState方法存到区块链world state上。这里示例是将A的资产值调减到B上,调减值作为参数送入。

Query方法查询chaincode中在world state上状态的资产值,通过GetState方法获得。
下面再看一下调用客户端,这是个Node.js 客户端,依赖hfc库,这个库在build 这个客户端docker image时就已经下载了。
# cd /opt/gopath/src/github.com/hyperledger/fabric/examples/sdk/node
# vi app.js

enroll()方法会使用admin用户登录membersrvc,admin作为registrar,可以代理注册其预定义权限内的其他用户,所有预定义权限的用户定义在membersrvc的配置中membersrvc.yaml中

可以看到admin可以代理注册client,尽管JohnDoe不在上述用户中,但是通过admin可以代理登录到fabric区块链网络。

deploy()方法负责chaincode到区块链的部署,其会初始化两个资产状态: a=100, b=200。

invoke()方法执行从a调减2个资产单位给b的动作,这个调减动作和最终调减后状态会记录在区块链上。

query()方法执行对a状态资产值的查询。任何具有权限的用户都可以查询到该资产值。
为了区分上述过程,我对app.js进行了扩展,扩展成了3个文件,app-deploy.js负责通过admin代理的JohnDoe用户进行chaincode的发布。app-invoke.js 由某个具有权限的用户执行chaincode调用,进行资产a的调减到资产b上。app-query.js 又由另外一个用户执行查询。
在starter docker container的bash内执行chaincode部署:
# node app-deploy.js
可以看到初始化chaincode后,状态a的资产值为[49,48,48],即100

上面为部署后各个container打出的日志,由于peer启动时设置为开发模式,chaincode运行时是在starter上执行的,没有针对chaincode专门构建和在单独的docker vm中执行。
在starter docker container的bash内执行chaincode调用:
# node app-invoke.js
可以看到通过用户diego调用chaincode后,状态a的资产值为[57,56],即98

# node app-query.js
可以看到,换成lukas用户后,再调用chaincode后,状态a的资产值仍然为[57,56],即98。
至此,Fabric开发环境已经配置和测试完成。
三、Fabric运行环境构建和验证
下面以现有机器ubu-blockchain1上已经构建好的环境进行说明。
1. Fabric运行环境docker image资源准备
激活consensus plugin 拜占庭容错共识算法后,需要同时设置至少4个验证节点,由于chaincode是动态部署到一个新build的docker 容器vm中的,动态构建这个chaincode vm docker container时需要依赖于hyperledger/fabric-baseimage,我们从docker hub pull一个适用于本机器的image,hyperledger/fabric-baseimage:x86_64-0.2.1,由于这image并没有github.com/hyperledger/fabric 源码,但是在编译chaincode时需要,所以我基于现有的hyperledger/fabric-baseimage:x86_64-0.2.1 通过Dockerfile再制作出了hyperledger/fabric-baseimage:latest。若非如此,在部署集群架构的chaincode时会报错。

在当前目录执行命令docker build
$ docker build -t hyperledger/fabric-baseimage:latest .另外为了区分之前build的starter客户端,基于之前build starter的Dockerfile,这里也重新build了一个叫hfc的客户端image
$ docker build -t hfc:latest .这些build完成后,当前的images列表如下:
$ docker images -a
2. 进行Fabric运行环境docker-compose配置文件准备
docker-compose配置文件支持基于父文件service的继承,我们可以将通用的不变参数配置的基类service上,所有变化的参数在具体service上设置,这样可以统一调整通用变量,也可以显著区分出各service变化的值,这些变化的值往往是名称,端口等。
对于所有的验证节点,定义了基类service:vp-pbft

其很重要的参数设定是
#启用pbft共识算法插件
- CORE_PEER_VALIDATOR_CONSENSUS_PLUGIN=pbft
#批量消息通讯
- CORE_PBFT_GENERAL_MODE=batch
# 4个验证节点
- CORE_PBFT_GENERAL_N=4
这些针对github.com/hyperledger/fabric/peer/core.yaml 中的参数
其他的参数主要是针对pbft的配置和超时配置,这对应于源码的github.com/hyperledger/fabric/consensus/pbft/config.yaml
另外注意 command: sh -c "sleep 8; peer node start"
启动vp docker container时,先sleep几秒再启动peer node,目的是等待membersrvc服务启动完后,再发起到membersrvc的vp用户身份验证通讯。
对于membersrvc和hfc客户端,也分别定义了基类service

成员服务将docker内的7054端口暴露给ubuntu linux host 7054端口。另外再启动其他验证节点时就可直接使用相同的membersrvc。
hfc客户端同开发环境的starter客户端唯一的区别在于,DEPLOY_MODE参数的设置,这影响hfc stub设置是否使用开发模式部署chaincode,以及部署的超时时间。参考app.js:

以下是docker-compose.yaml的具体service内容

定义了一个叫mymembersrvc的成员服务。vp0和vp1是定义的验证节点,具体验证节点service的特别设置有
#设定该节点的唯一ID
- CORE_PEER_ID=vp0
#设定该节点在membersrvc上的验证用户和密码
- CORE_SECURITY_ENROLLID=test_vp0
- CORE_SECURITY_ENROLLSECRET=MwYpmSRjupbT
vp1 service的特殊设置
#设置通过vp0进行其他节点的发现
- CORE_PEER_DISCOVERY_ROOTNODE=vp0:7051

vp2和vp3的设置基本类似vp1,都通过vp0进行其他节点发现

定义了2个hfc客户端,分别连接vp0和vp3,以myhfc service为例,特殊设置有
#客户端连接的节点
- PEER_ADDRESS=vp0:7051
#客户端到membersrvc进行身份验证用户名和密码
- USER_NAME=lukas - USER_PASSWORD=NPKYL39uKbkj
3. 启动Fabric运行环境
在上述配置文件所在目录启动pbft运行集群环境
$ docker-compose up

没有出现任何ERROR信息,说明启动正常,各个节点都在mymembersrvc成功验证,各节点之间已经可以互相知晓。
4. 验证Fabric运行集群环境
先登录到myhfc docker container上
$ docker exec -it myhfc bash进入到myhfc 客户端所在的docker container后,我们使用测试环境测试所用相同的Node.js 测试应用进行环境验证测试。
执行chaincode部署
# cd /hfc
# node app-deploy.js

可以看到初始化chaincode后,状态a的资产值为[49,48,48],即100。部署后的chaincode id是“5c0e7241354a22bca08f23fa8058ccd7148e03efe562971e2e977fc4003107fd”,后续可以通过这个chaincode id进行区块链chaincode的调用和查询。

后台执行日志看出是通过vp0进行chaincode部署的。
执行完部署,通过docker images还会发现多出4个docker images

dev-vp0-5c0e7241354a22bca08f23fa8058ccd7148e03efe562971e2e977fc4003107fd
dev-vp1-5c0e7241354a22bca08f23fa8058ccd7148e03efe562971e2e977fc4003107fd
dev-vp2-5c0e7241354a22bca08f23fa8058ccd7148e03efe562971e2e977fc4003107fd
dev-vp3-5c0e7241354a22bca08f23fa8058ccd7148e03efe562971e2e977fc4003107fd
这4个images就是动态生成出的执行chaincode的images
通过docker ps会发现基于上述images的4个chaincode vm 也在运行中

每一个验证节点就是通过对应的chaincode vm进行验证,然后对验证结果进行拜占庭共识的。
我们打开另一个连着vp3的myhfc3客户端,执行chaincode的调用验证。
$ docker exec -it myhfc3 bash# cd /hfc
# node app-invoke.js
可以看到通过用户diego登录后,通过vp3调用chaincode后,状态a的资产值调减为[57,56],即98

执行日志上看出,是通过vp3发起的chaincode调用。
我们再通过myhfc通过vp0查询一下区块链资产a的状况

可以看到,使用lukas用户进行登录,通过vp0,查询到chaincode状态a的资产值仍然为[57,56],即98。

执行日志上看出,是通过vp0发起的chaincode调用。
至此,我们Fabric运行环境的配置和验证工作也都已经完成。