深度学习
7.3.1 部署准备
1.下载示例程序
我们将当前的工作目录切换到fabricsample目录中,下载官网提供的示例:git clone https://github.com/hyperledger/fabric-samples.git。
下载完毕后,在目录下多了一个fabric-samples目录,可看到如下文件:
其中包含了好几个示例,我们选择其中的first-network来做测试,进入到first-network目录,看到如下一组文件:
这里面大多数是yaml配置文件以及两个脚本文件:byfn.sh和script.sh。Fabric组件的运行需要使用到这些配置文件,而两个sh脚本则用来控制Fabric组件的运行。
通过查看配置文件,可以发现这是一个多节点Fabric网络示例,包含了4个对等节点以及1个共识服务节点,4个对等节点分成了2个组织域,大致是如下的运行示意图:
注意,图中的示例是指分成两个组,而不是两个通道。在这个示例中,4个节点共用一个通道。
2.查看帮助
到现在为止,我们已经安装了示例程序运行所需的基础环境,接下来就可以试一试这个first-network了, 进入到first-network目录中,我们看到有一个脚本程序byfn.sh,通过运行这个脚本可以启动这个Fabric示例网络,同时会启动一个容 器用来执行脚本在通道中加入新的节点以及部署和初始化智能合约,并且在合约上执行交易。文件名byfn其实就是build your first network的缩写,也就是“构建你的第一个网络”的意思。大家也可以在终端命令行中通过命令./byfn.sh-h查看这个脚本的使用帮助,执行结果 如下:
./byfn.sh -h
Usage:
byfn.sh -m up|down|restart|generate [-c <channel name>] [-t <timeout>]
byfn.sh -h|--help (print this message)
-m <mode> - one of 'up', 'down', 'restart' or 'generate'
- 'up' - bring up the network with docker-compose up
- 'down' - clear the network with docker-compose down
- 'restart' - restart the network
- 'generate' - generate required certificates and genesis block
-c <channel name> - channel name to use (defaults to "mychannel")
-t <timeout> - CLI timeout duration in microseconds (defaults to 10000)
Typically, one would first generate the required certificates and
genesis block, then bring up the network. e.g.:
byfn.sh -m generate -c <channelname>
byfn.sh -m up -c <channelname>
byfn.sh -m down -c <channelname>
Taking all defaults:
byfn.sh -m generate
byfn.sh -m up
byfn.sh -m down
命令中包含了启动网络、清除网络、重启网络以及生成证书和创世区块等使用说明,事实上详细的各个命令参数具体是怎 么运行的,也可以直接打开byfn.sh源码来查看,这就是一个bash脚本程序,实际上这个脚本就是通过调用我们下载的Fabric组件程序以及示例代 码的配置文件来部署整个示例网络的。
//生成证书与创世区块
byfn.sh -m generate
//启动部署在docker容器中的fabric网络
byfn.sh -m up
//停止并清除运行在docker容器中的fabric组件
// docker中的镜像并不删除,相当于byfn.sh -m up的逆过程
byfn.sh -m down
//重启fabric网络
byfn.sh -m restart
3.数据配置
Fabric是一套半成品的开发框架,用来开发符合我们需求的区块链系统,因此不像比特币、以太坊直接下载下来运行就可以了,而是需要进行一系列的数据配置,按照Fabric的运行要求,需要设定好创世区块、密钥证书等数据文件。
(1)生成证书和创世区块
./byfn.sh -m generate
执行这个命令需要用到configtxgen和cryptogen,因此别忘了把这两个命令程序复制到first-network目录中,执行过程中会有提示:
Generating certs and genesis block for with channel ‘mychannel’ and CLI timeout of ‘10000’
Continue (y/n)? y
按下y键继续即可,通过提示我们也能看到,命令将生成证书和创世区块,同时也可以看到,命令默认创建了名为mychannel的通道,若需要创建其他的名称,可以使用-c参数指定,在上述的命令帮助中也有说明。我们接下来看下命令的执行过程输出。
(2)生成证书及密钥
我们使用cryptogen工具来创建证书密钥,直接在命令行中运行即可,如下所示:
命令执行后,可以看到结果提示,其中org1.example.com与org2.example.com是创建的两个对等节点组织的域名,在 Fabric网络中,节点是由节点组织来管理的,无论是普通的对等节点还是共识服务节点,节点组织具有组织名和域名,本例中定义了如下的组织关系:
这些生成配置都定义在crypto-config.yaml中,我们看下crypto-config.yaml的主要内容:
//共识节点组织定义
OrdererOrgs:
- Name: Orderer //共识节点名称
Domain: example.com //域名
Specs:
- Hostname: orderer
//对等节点组织定义
PeerOrgs:
- Name: Org1 //第一个对等节点组织
Domain: org1.example.com //域名
Template:
Count: 2
Users:
Count: 1
- Name: Org2 //第二个对等节点组织
Domain: org2.example.com //域名
Template:
Count: 2
Users:
Count: 1
通过文件内容,可以看到有节点组织以及包含的节点数、节点名称和域名等配置信息。补充一点对于节点名称和域名的关系,在Fabric中,一个节点的名称组成如下:
{Hostname}.{Domain}
比如org1组织下管理两个节点,分别是peer0与peer1,则这两个peer节点的全名是:
peer0.org1.example.com
peer1.org1.example.com
在生成证书时,会为节点组织以及组织下的每个节点都生成一系列的证书,生成的数字证书和私钥都存储在crypto-config目录中,打开可以看到目录结构:
每个组织都会生成一个唯一的根证书ca-cert,使用根证书绑定节点(对等节点与共识节点),加入链的成员可以使用自己的证书进行获取授权。交易与通信使用节点的私钥签名,验证则使用公钥,这便是Fabric中的证书体系了,实际上就是一套公钥设施。
(3)生成创世区块
准备好证书密钥这些配置数据后,我们就可以开始了,首先创建创世区块,这是使用configtxgen工具来实现的,在命令行中执行此命令,如下所示:
生成的创世区块用来启动共识服务节点,共识服务节点首先拥有区块数据,然后使用通道与peer节点之间进行数据同步。以下的“通道配置事务”与“锚节点”也都是通过configtxgen命令程序生成的。
(4)生成通道配置事务
主要是对通道的事务规则配置。
(5)生成锚节点
锚节点是指通道中能被所有其他对等节点发现,并能进行通信的一种对等节点。通道中的每个成员都有一个(或多个,以防单点故障)锚节点,允许属于不同成员身份的节点来发现通道中存在的其他节点,相当于节点中的网关。
7.3.2 启动Fabric网络
经过了上述的准备工作后,就可以开始启动Fabric网络了,使用如下命令:
./byfn.sh -m up
这个命令做的事情不少,需要那么一段执行时间,而且一不小心还会有如下错误提示:
这是因为byfn.sh命令要求Docker容器安装的Fabric组件的tag是latest标签的,不过也无妨,如果组件安装是正常的,也可以直接修改tag。如果有上面这样的错误提示,可以使用Docker tag命令处理,命令格式如下:
docker tag 0403fd1c72c7 hyperledger/fabric-tools:latest
命令中的0403fd1c72c7是hyperledger/fabric-tools在Docker中的镜像 ID,读者遇到类似问题时,可以采用上述命令的方法修正,注意要替换成自己Docker容器中对应组件的镜像ID,其他的Fabirc组件也是同样的处理 方式,修正了tag之后,重新开始执行./byfn.sh-m up,可以看到执行了如下的步骤:
执行完毕后,输出一个结束符号:
至此,Fabric示例网络就运行起来了,上述过程中的org2/peer2就是指peer0.org2.example.com,org2/peer3 就是指peer0.org2.example.com,由于两个组织域中分别有peer0与peer1两个对等节点,为了便于称呼,将这4个对等节点依次 称为org1/peer0、org1/peer1、org2/peer2、org2/peer3。
可以看到,安装到Docker中的Fabric节点已经运行起来了,其中peer是指对等节点,orderer是指共 识节点,除了5个节点容器外,还有前面3行是指智能合约容器,系统会为节点上的智能合约操作启动一个容器,最后一个是Fabric工具组件,也启动在一个 容器中。
我们通过docker logs来查看下一个智能合约的容器日志。
docker logs dev-peer0.org1.example.com-mycc-1.0
//输出
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210
docker logs dev-peer0.org2.example.com-mycc-1.0
//输出
ex02 Init
Aval = 100, Bval = 200
docker logs dev-peer1.org2.example.com-mycc-1.0
//输出
ex02 Invoke
Query Response:{"Name":"a","Amount":"90"}
通过输出的容器日志,我们能看到在启动网络后运行智能合约的操作所带来的Aval与Bval两个资产金额的变化,要更详细地了解运行的智能合约的内容,请参见7.3.3节。
7.3.3 Fabric智能合约
通过前面的操作,整个启动程序完成了一系列的动作,并部署了智能合约,那么这份智能合约是什么样的 呢?Fabric用来搭建商用联盟链系统,而所谓的商用,首要的就是智能合约的应用,本节我们就来分析一下部署在合约中的源码文件,一窥Fabric中智 能合约的究竟。这份源码文件就在我们下载的示例程序目录中的chaincode文件夹中。
其中chaincode_example02.go就是合约的源码文件,合约代码是使用Go语言编写的,我们分段来看下这份合约代码。
(1)引入包
这是Go语言中的机制,类似于C语言中的引入头文件或者Java中的导入包,目的是将已经具备的一些功能代码直接导入进来,也可以简单地理解为现成的功能库,如下所示:
package main
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
除了标准的fmt与strconv外,后两个包都是在Fabric源码中定义的,其中的shim包是用于访问智能 合约的接口定义,最后一行引入的是与对等节点通信相关的Prototuf定义,Protobuf是Google提供的一个开源序列化框架,类似于XML、 JSON。
(2)定义结构类型
该段代码定义了一个名为SimpleChaincode的结构类型,后续都是定义在这个结构类型上的方法,这是Go语言中类似于Java和C++中类的概念。
// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}
(3)合约初始化方法
智能合约是用来定义一套规则的,而规则归根结底是用来在某个条件下更新合约中定义的数据的,比如资产金额等,因此我们在使用智能合约之前,就得先把这些数据给初始化了,初始化后的合约会写入在区块链账本中。
//参数ChaincodeStubInterface是一个接口定义,用于部署合约的应用访问和修改账本数据
//这个接口定义在fabric源码的fabric/core/chaincode/shim/interfaces.go文件中
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response
{
fmt.Println("ex02 Init")
//获得初始化方法接收的参数
//这个方法实际是返回两个值:
//一个是调用这个初始化方法的函数名称,一个就是传入的参数列表
_, args := stub.GetFunctionAndParameters()
//定义两个变量存储实体对象,可以认为是两个账户
var A, B string
//定义两个变量存储实体的资产金额
var Aval, Bval int
var err error
//如果参数个数不等于4则返回一个错误
if len(args) != 4 {
return shim.Error("Incorrect number of arguments. Expecting 4")
}
//第1个参数赋值为实体对象A
A = args[0]
//第2个参数为实体对象A的资产金额
Aval, err = strconv.Atoi(args[1])
if err != nil {
return shim.Error("Expecting integer value for asset holding")
}
//第3个参数赋值为实体对象B
B = args[2]
//第4个参数为实体对象B的资产金额
Bval, err = strconv.Atoi(args[3])
if err != nil {
return shim.Error("Expecting integer value for asset holding")
}
//控制台输出两个资产金额
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)
//将实体A及初始资产金额更新到账本数据
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
}
//将实体B及初始资产金额更新到账本数据
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
}
//返回一个json格式的成功响应
/*
func Success(payload []byte) pb.Response {
return pb.Response{
Status: OK,
Payload: payload,
}
}
*/
return shim.Success(nil)
}
(4)合约调用
以下是合约操作的调用方法,通过传入的指令参数,调用不同的操作方法。
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
fmt.Println("ex02 Invoke")
//获得调用本方法的函数名和参数
function, args := stub.GetFunctionAndParameters()
if function == "invoke" {
//若调用方法名为invoke,则调用一个执行从A到B的转账交易的方法
return t.invoke(stub, args)
} else if function == "delete" {
//若调用方法名为delete,则表示调用一个删除实体对象的方法
return t.delete(stub, args)
} else if function == "query" {
//若调用方法名为query,则表示调用一个查询实体对象的方法
return t.query(stub, args)
}
//若没有找到对应的方法,则返回一个"无效调用方法名称"的错误提示
return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}
(5)转账交易调用
这份合约中定义的就是A和B两个账户之间的资产金额转账,因此必须给出实现这个功能的方法,比如从A转账到B,其 过程就是先获得A的金额,然后写入到B,再从A中扣除等额的转账金额,最后将这些变更更新到区块链账本中。我们看到,除了基于区块链这一点外,业务逻辑的 实现与普通程序没有什么不同,我们看一下代码实现:
// 本调用方法实现从A转账一定数额到B的交易
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var A, B string // 定义A与B两个实体对象
var Aval, Bval int // 定义两个变量分别存储A与B的资产金额
var X int // 交易金额
var err error
//若传入的参数个数不等于3,则报出错误提示
if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
}
//分别将第一个参数与第二个参数赋值给A与B,这是传入的两个实体对象
A = args[0]
B = args[1]
//从账本中获得A当前的资产金额,若之前没有发生过交易则就是初始化的金额
Avalbytes, err := stub.GetState(A)
if err != nil {
return shim.Error("Failed to get state")
}
if Avalbytes == nil {
return shim.Error("Entity not found")
}
Aval, _ = strconv.Atoi(string(Avalbytes))
//从账本中获得B当前的资产金额,若之前没有发生过交易则就是初始化的金额
Bvalbytes, err := stub.GetState(B)
if err != nil {
return shim.Error("Failed to get state")
}
if Bvalbytes == nil {
return shim.Error("Entity not found")
}
Bval, _ = strconv.Atoi(string(Bvalbytes))
//传入的第三个参数为交易金额
X, err = strconv.Atoi(args[2])
if err != nil {
return shim.Error("Invalid transaction amount, expecting a integer value")
}
//从A转账给B,因此A的金额减掉X,B的金额加上X
Aval = Aval - X
Bval = Bval + X
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)
//将A交易后金额的变更写入到账本中
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
}
//将B交易后金额的变更写入到账本中
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
}
//返回成功
return shim.Success(nil)
}
(6)从账本中删除实体对象
这段代码实现的功能,是在不需要保留合约中某个对象时进行删除,比如不需要A账户或者B账户时可以删除掉,当然实际商业环境中的智能合约,要删除合约中定义的某个对象肯定需要一些条件或者验证,这里只是一个功能示例。
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
//若没有参数则报错
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
//从参数中获得需要删除的实体对象
A := args[0]
//执行删除
err := stub.DelState(A)
if err != nil {
return shim.Error("Failed to delete state")
}
return shim.Success(nil)
}
(7)余额查询
功能很简单,就是查询合约中定义的账户对象的余额,由于查询并不会改动合约中的数据对象,因此直接返回结果即可。
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var A string //定义一个变量存储实体对象
var err error
//若没有参数则报错
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
}
//将参数中的实体对象赋值给A
A = args[0]
//从账本中获得A的金额
Avalbytes, err := stub.GetState(A)
//若错误对象不为空则报错
if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
return shim.Error(jsonResp)
}
//若金额为空则返回金额为空的结果
if Avalbytes == nil {
jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
return shim.Error(jsonResp)
}
//以json格式返回余额结果
jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
fmt.Printf("Query Response:%s\n", jsonResp)
return shim.Success(Avalbytes)
}
//合约的入口启动方法,实例化后启动
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
至此,我们对示例合约代码就作了一个简单的注释分析,可以看到,这是一份功能非常简单的智能合约,创建资产对象/初始化/转账交易/删除对象/查询余额,就是这些基本的功能,这就是这个Fabric示例网络所部署的智能合约。
了解了智能合约的内容后,我们再来看一个命令行操作,根据上述的步骤我们已经启动了示例网络,那么再来访问下节点的智能合约,我们知道了这份智能合约的功能就是两个实体对象之间的资产金额管理,执行一个查询操作,首先进入fabric-tools的容器命令环境:
docker exec -it "cli" /bin/bash
进入命令环境后,查询一下a与b的资产金额:
peer chaincode query -C "mychannel" -n mycc -c '{"Args":["query","a"]}'
peer chaincode query -C "mychannel" -n mycc -c '{"Args":["query","b"]}'
通过输出的结果可以看到a的金额是90,b的金额是210,再做一次转账操作,从a转10到b:
homeaddr=/opt/gopath/src/github.com/hyperledger/fabric/peer
orderercertaddr=/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
peer chaincode invoke -o orderer.example.com:7050
--tls $CORE_PEER_TLS_ENABLED
--cafile $homeaddr$orderercertaddr
-C "mychannel" -n mycc
-c '{"Args":["invoke","a","b","10"]}'
执行后,输出一个提示“Chaincode invoke successful.result:status:200”,这就表示转账交易调用成功了,按照计算,现在a应该是有80,b有220,我们再来查询一下余额,查询方法与上同,可以看到输出的结果分别是:
Query Result: 80
Query Result: 220
到现在为止,我们已经完整地体验了一回Fabric网络的部署和使用以及智能合约代码的逻辑,在示例中我们只是使用了官方提供的测试用合约代码,读者感兴趣可以自行修改示例中的代码,体会一下基于Fabric的智能合约开发。
7.3.4 Fabric部署总结
基于上述步骤,可以知道通过Fabric部署一个智能合约的节点网络,大体上需要经过如下的步骤:
从部署步骤来看,与以太坊是类似的,只不过多了一些证书、通道、锚节点等额外的修饰功能,通过使用Fabric组件, 我们部署一个智能合约的过程相当简单,基本上主要工作只是编写智能合约文件,其他的基础设施功能都提供好了,通过SDK可以进行节点功能的调用。这些对于 普通的区块链应用开发者来说,就像是入住酒店,除了带自己的必需物品外,其他设施一应俱全了。
另外,超级账本项目中并不只是一个Fabric,其他的各个子项目也值得去学习试用一番,这些都是国际大公司贡献的代码。对于技术开发人员来说,仔细阅读理解其中的源码以及文档是大有裨益的,对于区块链底层设施的设计能有很好的提升。
来源:我是码农,转载请保留出处和链接!
本文链接:http://www.54manong.com/?id=76
微信号:qq444848023 QQ号:444848023
加入【我是码农】QQ群:864689844(加群验证:我是码农)
全站首页 | 数据结构 | 区块链| 大数据 | 机器学习 | 物联网和云计算 | 面试笔试
var cnzz_protocol = (("https:" == document.location.protocol) ? "https://" : "http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1276413723'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s23.cnzz.com/z_stat.php%3Fid%3D1276413723%26show%3Dpic1' type='text/javascript'%3E%3C/script%3E"));本站资源大部分来自互联网,版权归原作者所有!
评论专区