区块链核心技术与应用/实战篇
第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指定需要被注销的成员。
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字段则定义了容器中的环境变量。