超级账本fabric项目试玩




最近跟着这本书学习区块链相关入门知识,

区块链技术指南pdf下载:https://legacy.gitbook.com/download/pdf/book/yeasy/blockchain_guide

该pdf已出版纸质书,《区块链原理、设计与应用》:https://item.jd.com/12159265.html

总体来说这本书对想了解区块链技术的初学者非常有价值,可以了解到区块链相关技术的来龙去脉、基本原理、知识体系、相关开源项目等等。看完超级账本项目fabric之后,感觉其整体框架跟同为分布式系统的OpenStack、k8s也比较类似(当然只是从框架来看,从底层实现来讲还是有很大差异的),主要差异在于其数据存储方式上,共识机制方面,OpenStack、k8s都是以传统数据库或etcd作为基础的,类似区块链中共识算法的多个提案者+一个确认者的场景。都是由多个服务构成,比如nova服务有nova-api、nova-compute、nova-scheduler、nova-conductor等服务,k8s有kublet、kube-apiserver、kube-proxy、kube-scheduler等服务,而fabric则有peer、ca、order这几个服务,他们也都有对应的客户端(命令行、sdk)用来发送请求给服务端。

fabric具体能干啥就不提了,还是看书吧,我还是从已知的IaaS、PaaS等云计算技术来对比,总体来说它可以用高级编程语言编写链码,然后上传到fabric区块链网络的某个节点里(具体来说是某个peer节点的docker容器里,我这里还有个疑问没有找到答案,一个链码只能跑在一个peer节点的一个容器里面吗?我理解应该是可以跑在多个peer节点的,从而实现分布式应用,不然就没有意义了),链码可以实现各种各样的功能(实现智能合约、账本管理等),链码用到的持久化数据都在区块链的块里存储,区块相当于是一个分布式数据库(或者专业名词叫分布式账本),从而实现应用的分布式、高可用、高可靠,以及数据的不可篡改,相比较而言,利用IaaS、PaaS等云计算技术也可以实现类似的功能或架构,尤其是k8s的微服务架构,也能支持分布式应用,但其数据却通常还是集中式存储的,并且容易篡改(当然其适用场景也不是为了解决这个问题,我这里只是随意对比下),也可以用分布式数据库来存数据,但仍然不像区块链那样,数据是用链表+区块来存储的。

要说fabric和k8s唯一的关联,我理解就是他们都是把链码或者说应用跑在docker里面的。fabric通过gRPC协议调用链码接口(接口比较固定),而k8s的服务则一般是通过HTTP RestFul API来调用服务(当然也有其他服务使用其他协议,如tcp或者专有协议)。

代码跑在哪里、数据存在哪里其实都不是关键,关键是怎么样让代码跑的愉快,跑的稳定,跑的没压力,还有就是让数据存的可靠,存的准确,存的安全。去中心化的问题,也是相对的,如果你的应用跑在全世界各地,数据也做到分布式存储到世界各地,我理解这也算是去中心化的。

不胡扯了,下面讲下怎么搭fabric测试环境,我这也是现学现卖,跟着官网文档学的,跑了一个example。我这里用的是fabric-1.1版本,操作系统是CentOS-7.2 x86_64。

环境准备:https://hyperledger-fabric.readthedocs.io/en/release-1.1/prereqs.html

主要是两个部分,一个是golang环境,一个是docker环境,docker包含docker daemon和docker-compose两部分。

安装比较简单,yum install golang docker docker-compose,完事儿,其他依赖如curl一般操作系统都是自带有的,当然下面还要用到git clone代码,也可以提前装上。

docker最好配置下国内的镜像源,否则下镜像要等死。修改docker daemon的配置,之后重启docker服务,systemctl restart docker。

{
  "registry-mirrors": ["https://kuamavit.mirror.aliyuncs.com", "https://registry.docker-cn.com", "https://docker.mirrors.ustc.edu.cn"], 
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    }
}

上面的几个registry-mirrors不保证可用,最好自己找好用的替换。

之后就是下载代码了,需要下载两个项目,一个是fabric本身,一个是fabric-example,当然我是为了跑example示例才下载的第二个,如果你想手工搭fabric环境,可以不用下载第二个。

git clone https://github.com/hyperledger/fabric.git
# 如果默认不是release-1.1分支,需要切一下
git checkout origin/release-1.1 -b release-1.1

git clone https://github.com/hyperledger/fabric-samples.git
# 同样切分支,当然也可以git clone -b直接切,我不习惯那么用
git checkout origin/release-1.1 -b release-1.1

再之后就是准备fabric可执行文件了,脚本在fabric项目目录下的scripts目录下:

fabric/scripts/bootstrap.sh,直接cd到这个目录下,./bootstrap.sh执行就好了,因为我们已经checkout到1.1版本的release分支了,默认就是跑的1.1版本。

相关可执行文件会下载到scripts目录下的bin目录(get-docker-images.sh是代码库自带的,其他几个二进制文件是新下载的):

root@linux scripts [release-1.1] $ ll bin/
total 148968
-rwxrwxr-x 1 1001 1001 23653336 Mar 16 06:13 configtxgen
-rwxrwxr-x 1 1001 1001 25113376 Mar 16 06:13 configtxlator
-rwxrwxr-x 1 1001 1001 12473976 Mar 16 06:13 cryptogen
-rwxrwxr-x 1 1001 1001 20731176 Mar 16 09:33 fabric-ca-client
-rwxrwxr-x 1 1001 1001      757 Mar 16 06:14 get-docker-images.sh # 本身代码库就有
-rwxrwxr-x 1 1001 1001 31536304 Mar 16 06:14 orderer
-rwxrwxr-x 1 1001 1001 39016824 Mar 16 06:14 peer

看下bootstrap脚本,可以看到是用curl命令下载的文件,每个文件几十M,国外网站比较慢,建议加上代理或者找国内的镜像(我是挂的代理)。

之后还要把这几个可执行文件所在目录加到PATH环境变量里(后面部署环境跑example要用到,具体来说是byfn.sh里面要用),让它们在任何目录下都可以被调用,当然也可以加上软链接,或者直接copy到/usr/local/bin之类的默认已加入环境变量的路径下,我这里是用的添加环境变量方式:export PATH=$PATH:/root/fabric/scripts/bin/

之后就是参考官方上手文档跑example了:https://hyperledger-fabric.readthedocs.io/en/release-1.1/build_network.html

文档里说是执行3步(文档里面有-m参数,看了下byfn.sh,里面已经没用这个参数了,不过貌似不影响执行,被忽略了):

./byfn.sh generate  ## 生成配置
./byfn.sh up  ## 启动环境
./byfn.sh down  ## 清理环境

每一步都有一堆输出,参考官网文档就行了,这里不贴了。

主要就是启动了一坨docker容器(在执行./byfn.sh up命令过程中通过docker ps -a命令查看):

root@linux fabric [release-1.1] $ docker ps -a | grep hyper
CONTAINER ID            IMAGE                                                                                                 COMMAND                  CREATED              STATUS                      PORTS                                            NAMES
dbe4601358e7        hyperledger/fabric-tools:latest                                                                        "/bin/bash"              3 minutes ago        Up 2 minutes                                                                 cli
51b6bf001365        hyperledger/fabric-orderer:latest                                                                      "orderer"                3 minutes ago        Up 3 minutes              0.0.0.0:7050->7050/tcp                             orderer.example.com
4aabbc96e3ed        hyperledger/fabric-peer:latest                                                                         "peer node start"        3 minutes ago        Up 3 minutes              0.0.0.0:9051->7051/tcp, 0.0.0.0:9053->7053/tcp     peer0.org2.example.com
85288ba01bbf        hyperledger/fabric-peer:latest                                                                         "peer node start"        3 minutes ago        Up 3 minutes              0.0.0.0:8051->7051/tcp, 0.0.0.0:8053->7053/tcp     peer1.org1.example.com
c908076115cf        hyperledger/fabric-peer:latest                                                                         "peer node start"        3 minutes ago        Up 3 minutes              0.0.0.0:10051->7051/tcp, 0.0.0.0:10053->7053/tcp   peer1.org2.example.com
c96a1d20dac5        hyperledger/fabric-peer:latest                                                                         "peer node start"        3 minutes ago        Up 3 minutes              0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp     peer0.org1.example.com

通过镜像名就可以看出来容器跑的什么服务。

仔细看下byfn.sh就可以分析出fabric是怎么部署的(至少开发测试环境可以这么部署):

# Generate the needed certificates, the genesis block and start the network.
function networkUp () {
  checkPrereqs
  # generate artifacts if they don't exist
  if [ ! -d "crypto-config" ]; then 
    generateCerts
    replacePrivateKey
    generateChannelArtifacts
  fi
  if [ "${IF_COUCHDB}" == "couchdb" ]; then 
    IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1 
  else
    IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE up -d 2>&1 
  fi
  if [ $? -ne 0 ]; then 
    echo "ERROR !!!! Unable to start network"
    exit 1  
  fi
  # now run the end to end script
  docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT
  if [ $? -ne 0 ]; then 
    echo "ERROR !!!! Test failed" 
    exit 1  
  fi
}

部署环境主要就是跑了这句:IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE up -d 2>&1

然后就是在cli那个容器里跑example:docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT

跑example过程中会有一坨日志在屏幕上输出。

因此部署环境就是通过docker-compose -f $COMPOSE_FILE up -d来搞定的,IMAGETAG可以通过byfn.sh的-i命令指定,默认是latest。COMPOSE_FILE可以通过-f命令指定,默认是docker-compose-cli.yaml,我们看下这个yaml文件:

# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#

version: '2'

volumes:
  orderer.example.com:
  peer0.org1.example.com:
  peer1.org1.example.com:
  peer0.org2.example.com:
  peer1.org2.example.com:

networks:
  byfn:

services:

  orderer.example.com:
    extends:
      file:   base/docker-compose-base.yaml
      service: orderer.example.com
    container_name: orderer.example.com
    networks:
      - byfn

  peer0.org1.example.com:
    container_name: peer0.org1.example.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer0.org1.example.com
    networks:
      - byfn

  peer1.org1.example.com:
    container_name: peer1.org1.example.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer1.org1.example.com
    networks:
      - byfn

  peer0.org2.example.com:
    container_name: peer0.org2.example.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer0.org2.example.com
    networks:
      - byfn

  peer1.org2.example.com:
    container_name: peer1.org2.example.com
    extends:
      file:  base/docker-compose-base.yaml
      service: peer1.org2.example.com
    networks:
      - byfn

  cli:
    container_name: cli
    image: hyperledger/fabric-tools:$IMAGE_TAG
    tty: true
    stdin_open: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      #- CORE_LOGGING_LEVEL=DEBUG
      - CORE_LOGGING_LEVEL=INFO
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash
    volumes:
        - /var/run/:/host/var/run/
        - ./../chaincode/:/opt/gopath/src/github.com/chaincode
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
        - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    depends_on:
      - orderer.example.com
      - peer0.org1.example.com
      - peer1.org1.example.com
      - peer0.org2.example.com
      - peer1.org2.example.com
    networks:
      - byfn

可以看出里面定义了一个容器网络,networks: byfn,几个volumes与容器名称相同,在base/docker-compose-base.yaml文件里面有用到,6个service包含1个order节点、4个peer节点、一个cli节点(依赖其他5个service,这个容易理解,客户端必须要等服务端启动才能执行命令),每个service各包含一个容器实例,容器名称与service名称一样(就是上面贴出来的docker ps -a命令的看到的容器列表)。

除了cli这个service之外,其它几个都继续使用了base/docker-compose-base.yaml这个compose配置,这里就不贴了,而这个yaml里的peer服务又依赖了base/peer-base.yaml。总之就是定义了一坨docker容器,然后利用docker-compose编排功能把容器跑起来。

关于docker-compose,可以参考官方文档:

networks、volumes等配置文件中关键字的意义可以参考上面的文档,简单来说部分是给docker实例准备的各种参数,与k8s的service配置文件比较类似,都是为编排服务里各种容器的。这里networks默认就是一个Linux bridge,volumes就是一个临时目录mount到docker容器里给容器用来保存临时数据。

配置文件里端口映射、命令行、环境变量啥的就不说了。

可以看出,整个集群或者说区块链网络就是通过docker compose的编排功能实现的。比手工部署跑起来简单方便多了。