区块链核心技术与应用/实战篇

第7章 比特币

1。比特币特点

2。比特币P2P网络

3。比特币的发行机制

4。比特币的账号系统

5。比特币的生态系统

6。开发实施一个比特币存证应用

第8章 以太坊 --公有链

1。以太坊关键概念

2。以太坊架构

3。以太坊智能合约

4。以太坊适用场景剖析

第9章 超级账本Fabric --联盟链

fabric里包括若干不同的项目,每个项目根据发展程度可处于5个阶段:提案,孵化,活跃,弃用,终止。

截止2018.6,通过提案进入孵化或活跃的项目共10个:

区块链框架类项目5个:Fabric,Sawtooth,Iroha,Buroow,Indy

区块链工具类项目5个:Cello,Composer,Explorer,Quilt,Caliper

1。Fabric基础架构

(一)。fabric总架构图

(1)。网络层

网络层由多个分布式节点组成。这些节点构成了一个点对点(P2P)的网络;

采用Gossip协议进行节点间互相发现和数据传输;

并采用gPRC的框架互相调用接口功能;

(2)。核心层

A。共识机制

核心层中的共识机制是区块链系统的核心模块,它确保各个节点对数据达成共识。

Fabric 1.0仅支持用于开发测试的SOLO模式和用于生产系统的Kafka方式。

其中Kafka实现的是CFT(Crash Fault Tolerant)的容错类型,需要假定联盟链网络中没有故意作恶的节点。

Fabric还允许用插拔的方式增加BFT(Byzantine Fault Tolerant)的容错类型。

B。区块链存储

区块链的存储主要包含以文件形式存储的链式区块数据,以及在数据库保存的键值对(Key-Value Pair)状态数据。

其中链式区块数据存放的是交易的原始数据区块,通过区块的哈希值形成防篡改的链式结构。

状态数据库的作用主要是加速对数据的访问。因为区块链数据采用链式顺序存放,在读取数据时通常需要遍历整个链的数据块,采用数据库能够从索引迅速定位到所需数据。

C。链码管理

Fabric中的智能合约称为“链码”(chain code)

链码部署在节点上,采用容器技术形成隔离的运行环境。

链码的生命周期管理主要包括链码的安装、实例化、调用和终止等。

D。身份管理

作为联盟链方案,Fabric包含管理成员身份的功能。

参与区块链网络的成员身份必须是明确的,成员之间知道彼此组织身份信息,每个交易都有确定的参与方和背书方,

这是绝大多数商用系统的需求。相比之下,许多公有链的用户身份是匿名的,参与方无须确认身份信息。

(3)。服务层

服务层利用核心层的基础功能,封装成服务的形式,提供给应用端来使用。

A。账本服务和交易服务通过核心层的共识算法和区块链存储,实现基本的区块链数据操作能力。

B。链码服务提供智能合约的功能封装。

C。事件服务则提供应用侦听系统事件并处理的功能。

D。权限服务根据成员和用户的身份信息,对其操作权限进行控制,成为商业应用中安全管理的一部分。

(4)。接口层

接口层的目的是使Fabric的应用(客户端)能够方便地调用区块链的服务。

接口主要以API的形式提供,能够完成通道、链码、交易等方面的操作。

为了便于编程语言的调用,Fabric提供了绑定不同语言的SDK,如Node和Java等的SDK。

此外,Fabric还提供了命令行接口CLI,可无须编程,通过命令直接调用Fabric的功能。

(二)。fabric主要组件

(三)。P2P网络 (基于Gossip协议)

Fabric的节点组成了一个P2P网络。每个节点既是请求者又是响应者。提供网络资源和服务,达到资源共享。

Fabric中P2P技术主要用于网络节点间的健康检测和账本同步。

节点间的P2P网络由Gossip协议实现,在通道中的各个节点会持续广播和接收Gossip消息。

Gossip内容:节点状态,账本数据,通道数据等信息

Gossip消息需发送者签名,目的:防篡改,及信息隔离。

通过Gossip协议,受延迟,网络分区或其他因素导致的账本没有同步的节点,最终均会同步到最新的账本状态。

(四)。通道(channel)

Fabric是以通道为基础的多链多账本系统。

(1)设计通道channel目的:提供成员间的隐私保护。(类比:群,群内成员可见,私有通信息的“子网”)

(2)通道由排序服务管理,创建通道时需定义它的

A。成员

B。组织:同一组织的节点会选举主导节点(leading peer),负责接收从排序服务发来的区块,并转发给本组织其他节点。

C。锚节点(anchor peer):代表本组织与其他组织的节点交互,以发现通道中的所有节点。

D。排序服务节点

(3)通道初始配置信息记录在区块链的创世区块(第一个区块)中,若要更改,可增加一个新的配置区块。

(4)fabric网络中可能同时存在多个彼此隔离的通道,每个channel包含一条私有区块链和一个私有账本。

通道中可实例化一个或多个链码,以操作区块链上的数据。因此fabric是以通道为基础的多链多账本系统。

(五)。分布式账本

fabric里的数据以分布式账本的形式存储。

世界状态:账本中的数据项以链值对的形式存放,账本中所有的链值对构成了账本的状态(记录所有数据的全部状态改变),也称世界状态。

每个channel中有唯一的账本,由channel中所有成员共同维护。

每个确认节点上都保存了它所属通道的账本的一个副本,因此是分布式账本。

对账本的访问需要通过链码实现对账本链值的增,删,改,查等操作。

账本由区块链和状态DB组成:

(1)区块链:记录全部交易日志

(2)状态DB:记录账本中所有链值对的当前值,相当于对当前账本的交易日志做了索引

(六)。共识机制

Fabric网络节点本质上是互相复制的状态机,节点间需保持相同的账本状态。各节点需通过共识,对账本状态变化达成一致。

Fabric共识过程包括:背书,排序,校验

(1)背书(endorsement)

背书节点对C端发来的交易预案进行合法性检验,然后模拟执行链码得到交易结果,最后根据设定的背书逻辑判断是否支持该交易预案。

A。支持交易预案:将预案签名并发回给C端

B。不支持交易预案,返回C端错误信息

客户端通常根据链码的背书策略(定义需要哪些节点背书交易才有效),向一个/多个成员的背书节点发出背出请求。C端收集足够背书后广播交易才有效。

(2)排序(ordering)

由排序服务对交易进行排序,确定交易间的时序关系。

排序服务把一段时间内收到的交易进行排序,然后把排序后的交易打包成区块,再把区块广播给通道中的成员。

Fabric1.0中的排序服务支持可插拔的架构。除了提供SOLO和Kafka模式外,用户还可添加第三方的排序服务。

A。SOLO:单机确认模式,仅适合于开发测试

B。Kafka:基于Kafka开源的分布式数据流平台,具有高扩展性和容错能力。适用于生产系统

注:Kafka只提供了CFT类型的容错能力,即仅可对节点的一般故障失效容错,缺乏对节点故意作恶行为进行容错的能力。因此需第三方的容错方案来支持BFT

排序服务采用轻量级设计,只完成确定交易顺序的功能,不参与其他操作。

3)校验(validation)

校验阶段:确认节点对排序后的交易进行一系列的检验,包括:

A。交易数据的完整性检查(有没有被篡改)

B。是否重复交易(双花)

C。背书签名是否符合背书策略的要求(收集足够背书)

D。交易的读写集是否符合多版本并发控制MVCC(Multiversion Concurrency Control)的校验

(七)。智能合约(链码:chaincode)

智能合约:是一套以数字形式定义的承诺(Promises),包括合约参与方可在上面执行这些承诺的协议。

智能合约也称“链码”,分“用户链码”和“系统链码”。通常所说的链码为“用户链码”。

链码安装在背书节点上,需在某个通道上实例化并且定义相应背书策略后才能运行。

链码部署后不可更改,可通过升级链码发布新功能或修复问题。

Fabric中链码运行在一个安全的Docker容器沙盒内,该容器由背书节点创建和管理,以隔离背书节点和链码的运行环境。

(八)。成员服务提供者

fabric与其他公有链系统重要区别:Fabric具有成员身份和权限管理的能力。

成员服务提供者MSP(Membership Service Provider)

成员服务是成员服务提供者的具体实现,成员服务最重要的一个部件是参与者所持有的“身份证书”。该证书由CA签发,当若干参与者的身份证书可追溯到同一根CA,则认为参与者处于同一信任链。

(九)。交易流程

2。架构详细原理

(一)成员身份管理

(1)fabric属于许可链,与公有链最大区别:

        在于要求参与者先注册身份,该身份即为参与者在区块链网络中的标识。

(2)fabric中,身份证书是每个参与者在区块链网络中的标识,同时也是权限管理的基础,

        证书采用了x.509标准且通过椭圆曲线密码学算法来生成公私钥对。

【1】成员服务提供者组件(MSP)

成员服务提供者组件MSP是对身份证书的抽象表达。

网络中每个参与者都拥有MSP,它包含了参与者的身份证书,数字签名,验证算法,若干判断身份是否有效的规则。

Fabric中通道(重要概念)实现了数据隔离,只有授权的网络实体才能访问通道内的数据。

   --每个channel定义一个/多个MSP实体用于控制数据的访问权限。

   --其中每个MSP均对应一个根CA/中间CA。只有由MSP对应的CA签发的身份证书才能通过相应的MSP身份校验。

【2】节点MSP:定义了节点在网络中的身份

A。节点的身份属性

在Fabric中,每个成员的身份都有一些特殊的属性以进行某些权限操作,

如某个身份的attrs中有hf.Registrar.Roles字段且该字段的值为“client, user, peer”

则说明该身份可以签发其他身份证书,可签发的证书类型为client、user和peer。

除了上述的hf.Registrar. Roles外,还有hf.Revoker和hf.IntermediateCA两个常用属性分别对应该实体能否注销用户和签发中间CA

B。节点MSP基础组成

在Fabric启动peer节点或排序节点前,需要先配置localMspId环境变量来指定节点的MSP名称。

其次是mspConfigPath环境变量,该变量为节点设定了MSP存放的目录,节点运行时会从该目录加载相应的MSP数据。

MSP的目录结构包括如下子目录(以下提及的证书均采用X.509标准)

 

cacerts:存放一组CA的自签名根证书,这些证书构成整个组织证书信任的基础。

intermediatecerts(可选):存放一组受信任的中间证书,这组证书充当了中间CA,可用于验证证书的合法性,由根证书签发。

admincerts:存放一组MSP管理员的身份证书,管理员拥有权限修改本MSP的配置,如删除cacerts目录中的某些证书。

signcerts:存放本节点的身份证书,身份证书必须通过本组织的CA签发。

keystore:存放与本节点身份证书对应的私钥,用于对消息签名。

tlscacerts:存放用于TLS通信协议的自签名证书。

crls(可选):存放若干已经撤销的身份证书。

config.yaml(可选):存放组织单元列表。若身份证书中的ou域包含在ous中,且由cacerts中的根证书签发,则该身份证书有效。

 

假设存在MSP实体Org1MSP,若某个网络实体要通过Org1MSP校验,则该实体的证书必须满足以下条件:

❑证书链的根在Org1MSP的cacerts目录中;

❑证书不在Org1MSP的crls目录中;

❑若Org1MSP定义了组织单元,则证书中的ou域必须包含组织单元中定义的一个或多个元素。

C。生成节点的MSP目录

若需要生成节点的上述msp目录文件,可使用cryptogen工具和Fabric CA server 两种方法

1)使用cryptogen工具(fabric命令行工具)生成msp目录

一个简单的crypto-config.yaml例子:

更多配置可参考cryptogen的描述 

运行命令:cryptogen,会生成crypto-config目录,结构:

在编辑好crypto-config.yaml文件后,可用下述命令生成msp目录

//生成msp目录
$cryptogen generate --config=./crypto-config.yaml

2)通过fabric-CA server生成msp目录

CA节点是每个组织给本组织成员提供数字证书的身份信息的服务。CA节点中运行的主要是Fabric-CA server这个服务。

该服务启动的方法有2种,分别是通过fabric-ca-server二进制可执行文件直接启动和通过Docker容器镜像启动。

无论选择哪种方式,都可以很方便地配置server的参数。通常与fabric-ca-server配套使用的还有客户端fabric-ca-client,

该客户端程序可以方便用户与CA server之间的交互。

下面介绍2种启动Fabric-CA Server的方法:

① 使用fabric-ca-server可执行文件启动CA服务。

下面以Ubuntu16.0.4为例。请先确保Go语言已经安装并且设置GOPATH环境变量。

❑安装libtool和libtdhl-dev:$ sudo apt install libtool libltdl-dev

❑安装Fabric-CA server和Fabric-CA client:$ go get -u github.com/hyperledger/fabric-ca/cmd/...

❑创建test_ca文件夹,存放Fabric-CA server的配置文件:$ mkdir $GOPATH/src/test_ca && cd $GOPATH/src/test_ca

❑初始化Fabric-CA server:$ fabric-ca-server init -b admin:adminpw

  该命令在当前目录创建CA server的证书、私钥和数据库等文件,其中fabric-ca-server-config.yaml为CA server配置文件。

  配置文件提供了丰富的参数设置以满足不同的需求。参数-b为server设定管理员的初始ID和secret。

  管理员可从客户端通过admin:adminpw来获取默认管理员的根证书,以进行后续的证书签发、证书撤销等操作。

❑启动Fabric-CA server:$ fabric-ca-server start

   该命令读取当前目录下fabric-ca-server-config.yaml配置文件,载入相应的证书和私钥并启动CA server服务。

② 通过Docker容器方式启动CA服务

先确保系统已安装17.03或以上版本的Docker程序,以及1.11或以上版本的docker-compose程序。

❑从Docker Hub上下载fabric-ca镜像:$ docker pull hyperledger/fabric-ca:x86_64-1.0.0

❑创建test_ca文件夹,存放Fabric-CA server的配置文件:$ mkdir $GOPATH/src/test_ca && cd $GOPATH/src/test_ca

❑创建docker-compose.yaml文件:

fabric-ca-server:

  image: hyperledger/fabric-ca:x86_64-1.0.0

  container_name: fabric-ca-server

  ports:

    - "7054:7054"

  environment:

    - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server

  volumes:

    - "./fabric-ca-server:/etc/hyperledger/fabric-ca-server"

  command: bash -c 'while true; do sleep 3; done'

 

❑启动CA服务:

$ docker-compose -f docker-compose.yaml up -d

$ docker exec fabric-ca-server fabric-ca-server start -b admin:adminpw

 

当容器启动后,会在当前目录下新创建fabric-ca-server目录以存放CA Server的证书、私钥配置文件等

 

③fabric-ca-client客户端的使用方法

 

使用fabric-ca-client命令可以方便地跟Fabric CA Server进行交互。一些常用的操作如下:

 

❑登录管理员账户,获取证书签发权限。

 

上述启动CA Server的流程中定义了管理员的ID和密码分别为admin和adminpw,通过以下命令登录管理员用户:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/admin

$ fabric-ca-client enroll -u http://admin:adminpw@localhost:7054

 

执行上面的命令,会在$FABRIC_CA_CLIENT_HOME目录下生成一个msp目录和一个fabric-ca-client-config.yaml配置文件。

 

❑注册成员peer1:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/admin

$ fabric-ca-client register --id.name peer1--id.type peer --id.secret peer1pw

 

在上述命令中,通过id.name、id.type、id.secret分别声明了注册用户的名称、类型和登录密码。

 

❑登录新注册的peer1:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/peer1

$ fabric-ca-client enroll -u http://peer1:peer1pw@localhost:7054

 

第一条命令通过环境变量FABRIC_CA_CLIENT_HOME为peer1指定了主目录$HOME/fabric-ca/clients/peer1,该目录用于存放Fabric server返回的文件。

 

第二条命令用于获取peer1用户的证书文件,该命令会在$HOME/fabric-ca/clients/peer1目录下生成一个msp目录,其中msp目录存放着peer1的证书和对应的私钥。

 

❑注销成员peer1:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/admin

$ fabric-ca-client revoke -e peer1

 

上述命令使用admin的身份来注销成员,参数-e指定需要被注销的成员。

 

fabric-ca-server配置参数详细描述

 

fabric-ca-client配置参数详细描述

 

D。通道MSP

除了节点需要MSP,通道也需要MSP。

只有由MSP对应的CA签发的身份证书才能通过相应MSP的身份校验。

在通道中定义MSP可实现对通道中数据的访问权限控制。

(二)通道的结构

(1)系统通道和应用通道

(2)使用configtxgen工具生成通道的配置

configtxgen是fabric提供的工具,用于生成通道所需要的配置文件。

configtxgen工具以一个yaml文件作为输入,一般称为configtx.yaml。该文件定义了将要创建通道的配置信息。

该文件通常包括以下部分

1)Profiles:包含了通道的配置模板,通过configtxgen工具的参数-profile来指定使用哪个模板。

2)Organizations:定义了组织以及与之对应的MSP。

3)Orderer:定义系统通道的相关配置,如排序节点地址、共识算法。

4)Application:定义应用通道相关配置,被profile引用。

profile定义如下:

Profiles:

    Genesis:

        Orderer:     ##系统通道必须orderer和Consortiums 两部分

            <<: *OrdererDefaults ##引用orderer字段定义的内容,通过该引用定义系统通道的配置信息,如共识算法,orderer地址等

            Organizations:   ##定义参与此系统通道的组织信息

                - *OrdererOrg

        Consortiums: ##定义为哪些联盟提供服务,即只有联盟中的组织能够创建通道

            SampleConsortium:

                Organizations:

                    - *PeerOrg1

                    - *PeerOrg2

    Channel:

        Consortium: SampleConsortium  ##Consortium指定了与应用通道相关联联盟的名称

        Application:  ##应用通道必须Application和Consortium两部分.Application定义了应用通道内的组织信息

            <<: *ApplicationDefaults

            Organizations: ##定义orderer类型和普通类型(每个普通类型组织均需定义一个锚节点,用于代表本组织与其他组织通信)

                - *PeerOrg1

                - *PeerOrg2

 

(3)通道相关命令

对通道的管理可通过命令行的方式。与通道相关的命令如下。

❑peer channel create:用于创建通道,主要参数有-c、-f、-o,分别用于指定通道ID、configtx的路径和orderer的地址。

❑peer channel fetch:抓取通道中的特定区块,通过-c和-f参数来指定通道ID和orderer地址。

❑peer channel join :加入通道,通过-b参数指定初始区块。

❑peer channel list :列出peer加入的通道。

❑peer channel update:签名并且发送configtx以升级通道配置,需要通过-c、-f、-o参数分别指定通道ID、configtx的路径以及排序节点的地址。

 

(4)动态修改通道配置

在通道创建后,通道相关的配置以区块的形式存在于通道的账本中。如果需要修改通道的配置,可通过生成新的配置区块去更新。修改通道配置的步骤如下:

❑通过SDK或CLI获得最新的配置区块;

❑编辑配置区块;

❑计算配置更新量;

❑为配置区块添加配置更新量;

❑SDK或CLI签名并发送配置区块。

若新的配置区块通过验证,则通道配置以最新配置区块为准。

由于获取的配置区块以二进制的proto格式返回,导致配置区块的信息难以直接阅读。

一般通过Fabric自带的configtxlator工具来把配置区块转化为JSON格式用以阅读和编辑。

当编辑完成后再次使用工具把修改好的JSON文件转化为proto格式,最后把proto格式的配置区块发回给排序节点验证

(三)链码

链码运行在背书节点上的Docker容器沙盒中,以便与背书节点的其他进程隔离。

(1)链码的生命周期管理

Fabric提供命令行工具管理链码的生命周期,常用命令如下。

❑peer chaincode install:把链码打包成可部署格式,并将其存入到背书节点的文件系统目录下(CORE_PEER_FILESYSTEMPATH/chaincode)。

❑peer chaincode instantiate:把安装到背书节点上的链码实例化到指定的通道上。该命令会在节点上创建运行链码的Docker容器,并初始化链码。

❑peer chaincode invoke:调用指定链码,若执行交易的节点还没创建运行链码的容器,则背书节点会先创建该容器再执行交易。

❑peer chaincode query:查询指定链码,若执行交易的节点还没创建运行链码的容器,则背书节点会先创建该容器再执行交易。该交易只查询节点上的状态,不生成区块。

❑peer chaincode package:把链码打包成可部署格式。

❑peer chaincode signpackage:签名打包后的链码。

❑peer chaincode upgrade:升级链码,需要先用peer chaincode install命令安装最新的代码,然后使用本命令来升级已经实例化的代码。

(2)链码的背书策略

(3)链码开发

链码开发过程中需要实现链码接口。交易的类型决定了哪个接口函数将会被调用,

如instantiate和upgrade类型会调用链码的Init接口,而invoke类型的交易则调用链码的Invoke接口。

链码的接口定义如下:

type Chaincode interface {
    Init(stub ChaincodeStubInterface) pb.Response
    Invoke(stub ChaincodeStubInterface) pb.Response
}

 

下面通过一个例子讲解链码的开发流程。示例链码根据交易的类型创建键值对并记录到账本中,或者根据键名到账本中查找与之相对应的值。

请先确保Go语言环境已经安装并且正确设置GOPATH环境变量。

step1:创建链码存放目录

创建keyValueStore目录以存放链码,同时进入目录:

mkdir $GOPATH/src/keyValueStore

cd $GOPATH/src/keyValueStore

创建并编辑链码文件keyValueStore.go。完整代码如下

import (
        "fmt"
        "github.com/hyperledger/fabric/core/chaincode/shim"
        "github.com/hyperledger/fabric/protos/peer"
)
type KeyValueStore struct {
    }
func (t * KeyValueStore) Init(stub shim.ChaincodeStubInterface) peer.Response {
    args := stub.GetStringArgs()
    if len(args) ! = 2 {
        return shim.Error("Incorrect arguments. Expecting a key and a value")
    }
    err := stub.PutState(args[0], []byte(args[1]))
    if err ! = nil {
        return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
    }
    return shim.Success(nil)
}
func (t *KeyValueStore) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
        fn, args := stub.GetFunctionAndParameters()
        var result string
        var err error
        if fn == "set" {
                result, err = set(stub, args)
        } else {
                    result, err = get(stub, args)
            }
            if err ! = nil {
                    return shim.Error(err.Error())
            }
            return shim.Success([]byte(result))
    }
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
     if len(args) ! = 2 {
         return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
      }
     err := stub.PutState(args[0], []byte(args[1]))
     if err ! = nil {
         return "", fmt.Errorf("Failed to set asset: %s", args[0])
        }
            return args[1], nil
    }
    func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
            if len(args) ! = 1 {
                    return "", fmt.Errorf("Incorrect arguments. Expecting a key")
            }
            value, err := stub.GetState(args[0])
            if err ! = nil {
                    return "", fmt.Errorf("Failed to get asset: %s with error: %s",
                                          args[0], err)
            }
            if value == nil {
                    return "", fmt.Errorf("Asset not found: %s", args[0])
            }
            return string(value), nil
    }
    func main() {
            if err := shim.Start(new(KeyValueStore)); err ! = nil {
              fmt.Printf("Error starting KeyValueStore chaincode: %s", err)
            }
    }

 

step2:链码的源代码分析:

1)导入头文件。

链码必须依赖chaincode shim包和peer protobuf包,它们分别用于链码的控制与数据传输。

其次定义KeyValueStore类型,作为chaincode shim的载体。

package main

import (

        "fmt"

        "github.com/hyperledger/fabric/core/chaincode/shim"

        "github.com/hyperledger/fabric/protos/peer"

)

type KeyValueStore struct {

    }

 

2)实现Init方法。

Init方法通过shim.ChaincodeStubInterface接口来获取实例化链码交易的相关信息。

该接口的GetStringArgs方法可获取交易传给链码的参数。

链码实例化时接收key和value两个参数,因此先对参数个数进行验证。

若验证通过,则将第一个和第二个参数分别作为key和value存入到账本中。

把状态存入账本需要借助shim.ChaincodeStubInterface接口PutState方法来完成。

由于账本中的数据都以键值对的形式储存,因此该方法也只接受key、value两个参数。其中value为byte格式,里面还包含多个JSON格式的键值对。

由于执行结果需要以消息的形式返回给客户端,因此还需要把返回消息封装成fabric/protos/peer中的Response格式。

值得注意的是,链码升级的时候都会调用Init方法,编写升级链码时应注意Init方法的实现,以避免重新初始化或覆盖上一版本的账本状态。

func (t * KeyValueStore) Init(stub shim.ChaincodeStubInterface) peer.Response {

    args := stub.GetStringArgs()

    if len(args) ! = 2 {

        return shim.Error("Incorrect arguments. Expecting a key and a value")

    }

    err := stub.PutState(args[0], []byte(args[1]))

    if err ! = nil {

        return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))

    }

    return shim.Success(nil)

}

 

3)实现Invoke方法。

与Init方法类似,Invoke方法通过shim.ChaincodeStubInterface的GetFunctionAndParameters方法来获取invoke交易的参数。

其中返回的fn与args分别为交易调用的具体函数名以及相应参数。此时Invoke方法进一步判断fn的值以进行下一步操作(set或者get),并把操作结果存放在result变量中以返回操作结果。

func (t *KeyValueStore) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

    fn, args := stub.GetFunctionAndParameters()

    var result string

    var err error

    if fn == "set" {

            result, err = set(stub, args)

    } else {

            result, err = get(stub, args)

    }

    if err ! = nil {

            return shim.Error(err.Error())

    }

    return shim.Success([]byte(result))

}

为了完成对账本的读写,链码还需要实现以下2个方法。

❑set:把输入的键值对记录在账本中。

❑get:根据键读取账本中与之对应的值。

4)实现get和put方法。

正如前面所说,invoke方法根据fn的值来执行相应的get或put函数。这两个函数也需要shim.ChaincodeStubInterface接口来访问账本数据。

 func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {

        if len(args) ! = 2 {

            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")

        }

        err := stub.PutState(args[0], []byte(args[1]))

        if err ! = nil {

            return "", fmt.Errorf("Failed to set asset: %s", args[0])

        }

        return args[1], nil

    }

    func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {

        if len(args) ! = 1 {

            return "", fmt.Errorf("Incorrect arguments. Expecting a key")

        }

        value, err := stub.GetState(args[0])

        if err ! = nil {

            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0],err)

        }

        if value == nil {

            return "", fmt.Errorf("Asset not found: %s", args[0])

        }

        return string(value), nil

    }

 

5)实现主函数main()

链码需要在main函数中调用shim.Start()方法用于链码的部署。

func main() {

    if err := shim.Start(new(KeyValueStore)); err ! = nil {

        fmt.Printf("Error starting KeyValueStore chaincode: %s", err)

    }

}

 

step3:测试链码

链码的测试需要通过完整的Fabric网络。使用官方提供的例子可以快速构建测试网络,从而简化链码的开发流程。这里介绍搭建测试网络的步骤。

1)参考9.3.1节安装示例代码库。

2)进入fabric-samples目录:

$ cd $GOPATH/src/github.com/hyperledger/fabric-samples

3)把新编写的链码放入fabric-samples的chaincode目录下:

$ cp -r $GOPATH/src/keyValueStore ./chaincode

4)进入chaincode-docker-devmode目录并启动网络,命令中会创建一个名为myc的通道:

$ cd chaincode-docker-devmode

$ docker-compose -f docker-compose-simple.yaml up -d

5)进入chaincode容器,编译并运行链码:

$ docker exec -it chaincode

$ cd keyValueStore && go build

$ export CORE_PEER_ADDRESS=peer:7051

$ export CORE_CHAINCODE_ID_NAME=mycc:0

$ ./keyValueStore

$ exit

6)进入CLI容器并初始化链码,链码ID为mycc,版本号为0,部署的通道名称是myc:

$ docker exec -it cli bash

$ peer chaincode install -p chaincodedev/chaincode/keyValueStore -n mycc -v 0

$ peer chaincode instantiate -n mycc -v 0-c '{"Args":["a", "10"]}' -C myc

7)Invoke和Query链码:

$ peer chaincode query -n mycc -c '{"Args":["query", "a"]}' -C myc

$ peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc

$ peer chaincode query -n mycc -c '{"Args":["query", "a"]}' -C myc

正常情况下,2次query返回的结果分别为10和20。

开发链码时可以通过上述过程进行测试,但需避免使用相同的链码ID,以免链码实例化失败。另外,对于链码升级来说,链码的ID应该保持不变,同时新链码的版本号需要比先前实例化的版本高,并通过upgrade交易来更新链码在通道中的状态。

假设对链码keyValueStore.go进行了更改,并把最新的链码保存在$GOPATH/src/keyValueStoreNew下,则升级链码的操作如下。

1)进入fabric-samples目录并拷贝最新链码到chaincode目录:

$ cd $GOPATH/src/fabric-samples

$ cp -r $GOPATH/src/keyValueStoreNew ./chaincode

2)进入chaincode容器,编译并运行更新后的链码:

$ docker exec -it chaincode bash

$ cd keyValueStoreNew && go build

$ export CORE_PEER_ADDRESS=peer:7051

$ export CORE_CHAINCODE_ID_NAME=mycc:1

$ ./keyValueStoreNew

$ exit

3)进入cli容器并升级链码:

$ docker exec -it cli bash

$ peer chaincode install -p chaincodedev/chaincode/keyValueStoreNew -n mycc -v 1

$ peer chaincode upgrade -n mycc -v 1-c '{"Args":["a", "10"]}' -C myc

至此升级链码完毕,可以对最新的链码mycc进行操作。

(4)ChaincodeStubInterface常用的接口方法

❑GetTXID:获取交易的ID;

❑GetCreator:获取交易发起者的信息;

❑GetState(key):获取账本中的key值;

❑PutState(key, value):往账本中的key写入value;

❑DelState(key):删除账本中的key。

3。应用开发流程

示例:构建完整的区块链网络,包括成员结构定义,节点的启动及通道的创建等。

本示例通道中只有一个组织参与,可修改通道配置达到动态添加组织目的。

注:

若mychannl中本来就已经存在多个组织,则无法通过命令行工具进行通道的升级。

此时正确的方法是把构造好的更新交易通过SDK的signChannelConfig方法收集所有成员的签名,

然后通过updateChannel方法向排序节点发送带有签名的交易。若排序节点对签名的验证通过,则执行更新通道操作。

 

 Step1:前期准备

假设已准备运行环境为Ubuntu 16.04的主机,并正确安装docker、docker-compose、golang以及git等常用软件。
1)从下面地址下载本文例子的代码库。例子源自Fabric官方示例代码。为了说明开发原理,笔者做了少量修改。
$ cd $GOPATH/src
$ git clone https://github.com/LordGoodman/fabric-samples.git
2)下载Fabric常用工具集:
    $ cd~/Downloads
    $ curl https://nexus.hyperledger.org/content/repositories/releases/org/hyperledger/
fabric/hyperledger-fabric/linux-amd64-1.0.0/hyperledger-fabric-linux-amd64-1.0.0.tar.gz
| tar xz
3)把工具集加入到PATH环境变量中:
$ echo 'export PATH=$PATH:$HOME/Downloads/bin' >>~/.profile
$ source~/.profile
4)下载Fabric的Docker镜像:
$ cd $GOPATH/src/fabric-samples/scripts && bash fabric-preload.sh

Step2:定义fabric集群

为了部署车辆登记应用,下面将启动一个简单的Fabric集群。

该集群仅包含了一个peer节点(同时具有背书节点和确认节点的功能)、一个排序节点以及一个CA组织。

peer节点的证书由该CA组织签发。

A.证书以及通道的初始区块生成

启动Fabric集群之前,先使用cryptogen和configtxgen来生成必要的身份证书(存放在crypto-config目录)、通道初始区块(存放在config目录)等文件,

$GOPATH/src/fabric-samples/basic-network目录下的配置文件crypto-config.yaml定义了9.3.2节中描述的集群。

同时,在同一目录下的configtx.yaml文件定义了只包含一个组织的应用通道,链码将会在该通道中部署。

默认情况下,$GOPATH/src/fabric-samples/basic-network下已经预先生成好了crypto-config和config目录。

用户想要重新生成这两个目录可以运行generate.sh脚本。

该脚本会先删除目录,然后再通过cryptogen、configtxgen以及相应的配置文件重新生成。

B.编写peer的docker-compose文件

由于Fabric中的节点都通过Docker容器来运行,因此当文件准备好后,还需要编写docker-compose.yaml文件。

该文件定义了容器所使用的镜像以及容器内的环境变量。

在$GOPATH/src/fabric-samples/basic-network目录下的docker-compose.yaml文件已经定义好了本章所需的集群。

下面以该文件中peer容器配置为例进行简要分析

eer0.org1.example.com:
    container_name: peer0.org1.example.com
    image: hyperledger/fabric-peer
    environment:
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_LOGGING_PEER=debug
      - CORE_CHAINCODE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      working_dir:/opt/gopath/src/github.com/hyperledger/fabric
command: peer node start
ports:
      -7051:7051
      -7053:7053
    volumes:
        - /var/run/:/host/var/run/
- ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.
com/msp:/etc/hyperledger/msp/peer
        - ./config:/etc/hyperledger/configtx
    depends_on:
      - orderer.example.com
    networks:
      - basic

上述文件定义了名为peer0.org1.example.com的容器。容器使用的镜像为hyperledger/fabric-peer:1.0.0,而environment字段则定义了容器中的环境变量。

 

posted @ 2018-11-25 11:28  kaixinyufeng  阅读(1402)  评论(0编辑  收藏  举报