hyperledger-fabric测试环境搭建

2020-09-29  本文已影响0人  不能吃的坚果j

本文作者:陈进坚
个人博客:https://jian1098.github.io
CSDN博客:https://blog.csdn.net/c_jian
简书:https://www.jianshu.com/u/8ba9ac5706b6
联系方式:jian1098@qq.com

环境安装


安装docker

必须是CE(社区)版,如果装企业版的只能卸载重装,否则会出错;如果已安装可跳过,下面是CentOS安装步骤

yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install docker-ce
docker -v

Ubuntu安装docker

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

安装docker-compose

如果已安装可跳过

curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version

安装go语言环境

需要 go1.11以上版本,如果已安装可跳过

到官网https://golang.google.cn/dl/复制最新的下载地址,然后下载压缩包

wget https://dl.google.com/go/go1.13.1.linux-amd64.tar.gz

解压

tar zxvf go1.13.1.linux-amd64.tar.gz -C /opt/
mkdir go        #创建项目目录
vi /etc/profile

将下面的GOPATH路径修改为你的项目路径,然后将3条命令添加到文件的最后,保存;第一个是工作目录,第二个是go程序目录

export GOROOT=/opt/go
export GOPATH=/home/jian/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

执行下面的命令使环境变量生效

source /etc/profile

查看配置好的go环境变量

go env

查看版本

go version

安装python

需要python 2.7.x版本,一般系统已自带有,如果没有,可以按下面的步骤安装

https://www.python.org/ftp/python/选择合适的版本,以下例子基于python 2.7.9,其他版本同理

安装node.js及npm

如果你将用Node.js的Hyperledger Fabric SDK开发Hyperledger Fabric的应用程序,则需安装Node.js的8.9.x版本

依次执行下面的命令即可,要装其他版本可以修改10.x为你要的版本

curl -sL https://rpm.nodesource.com/setup_10.x | bash -
sudo yum clean all && sudo yum makecache fast
sudo yum install -y gcc-c++ make
sudo yum install -y nodejs

查看版本

node -v

如果你想要其他版本的Node.js的话,那么执行命令可以用下面的命令将已安装的Node.js移除,然后重新安装即可

yum remove nodejs

搭建fabric环境


参照官方文档https://hyperledger-fabric.readthedocs.io/en/latest/install.html,2.2及以后版本参考https://hyperledger-fabric.readthedocs.io/en/release-2.2/test_network.html,步骤如下

安装示例、二进制文件和 Docker 镜像

下面的命令下载并执行一个 bash 脚本,该脚本将下载并提取设置网络所需的所有特定于平台的二进制文件,并将它们放入fabric-samples文件夹中;然后,该脚本会将从 Docker Hub 上下载 Hyperledger Fabric docker 镜像到本地 Docker 注册表中,并将其标记为 ‘latest’。

curl -sSL http://bit.ly/2ysbOFE | bash -s       # 服务器需要科学上网

如果要指定版本需要加一个 Fabric、Fabric-ca 和第三方 Docker 镜像的版本号

curl -sSL http://bit.ly/2ysbOFE | bash -s -- 1.4.2 1.4.2 0.4.15

如果你的服务器无法科学上网,可以到http://note.youdao.com/noteshare?id=4fb074480d296adf1e931c734e18d3bd&sub=2C7210BDD6D04349B332CD66131C58ED获取脚本,然后保存到bootstrap.sh文件中,然后添加权限chmod +xbootstrap.sh `执行脚本即可

chmod +x bootstrap.sh
bash ./bootstrap.sh

执行完后会得到fabric-samples目录

生成网络构件

2.2以前的版本进入/fabric-samples/first-network目录,执行下面的命令,然后输入Y继续,注意2.2版本及之后的版本不需要执行

./byfn.sh generate

上面的命令为我们的各种网络实体生成证书和秘钥。创世区块 genesis block 用于引导排序服务,也包含了一组配置 Channel 所需要的配置交易集合

关闭网络

执行下面的命令,然后输入Y继续

2.2以前的版本的目录在fabric-samples/test-network

./byfn.sh down          //2.2以前的版本
./network.sh down       //2.2及以后的版本

上面的命令会结束掉你所有的容器,移除加密的材料和四个构件,并且从 Docker 仓库删除链码镜像

启动网络

执行下面的命令,然后输入Y继续

./byfn.sh up                # 默认golang启动
./byfn.sh up -l javascript   #启动node.js版本,旧版本的命令是./byfn.sh up -l node

2.2及以后的版本将./byfn.sh换成./network.sh即可

上面的命令会编译 Golang 智能合约的镜像并且启动相应的容器。Go 语言是默认的链码语言,但是它也支持 Node.jsJava 的链码,详情可以看官方文档https://hyperledger-fabric.readthedocs.io/zh_CN/release-1.4/build_network.html;

这一步会启动所有的容器,然后启动一个完整的 end-to-end 应用场景,并且会打印下面的日志

Continue? [Y/n]
proceeding ...
Creating network "net_byfn" with the default driver
Creating peer0.org1.example.com
Creating peer1.org1.example.com
Creating peer0.org2.example.com
Creating orderer.example.com
Creating peer1.org2.example.com
Creating cli


 ____    _____      _      ____    _____
/ ___|  |_   _|    / \    |  _ \  |_   _|
\___ \    | |     / _ \   | |_) |   | |
 ___) |   | |    / ___ \  |  _ <    | |
|____/    |_|   /_/   \_\ |_| \_\   |_|

Channel name : mychannel
Creating channel...

等两分钟后命令会自动结束然后打印下面的日志

Querying chaincode on peer1.org2...
===================== Querying on peer1.org2 on channel 'mychannel'... ===================== 
+ peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
Attempting to Query peer1.org2 ...3 secs
+ res=0
+ set +x

90
===================== Query successful on peer1.org2 on channel 'mychannel' ===================== 

========= All GOOD, BYFN execution completed =========== 


 _____   _   _   ____   
| ____| | \ | | |  _ \  
|  _|   |  \| | | | | | 
| |___  | |\  | | |_| | 
|_____| |_| \_| |____/  

如果启动报错,建议先执行一次关闭网络操作清空数据

至此,fabric的环境搭建完成

名词解释


fabric ca

数字签名授权,任何一个操作都需要数字签名证书

fabric peer

节点,区块存储好的位置

ordering服务

创建区块,验证和排序服务

channel

每个channel都是独立的fabric实例,数据不互通

一个peer可以有多个channel,一个channel可能有多个peer

chaincode

智能合约,也称链码,chaincode属于某个channel

生命周期

MSP

membership service provider,会员服务提供者,管理peer的身份和访问许可,每个peer都有自己的MSP证书

工作流程


提案

通过sdk向各个peer发起更新数据提案

背书

endorsing,足够多的peer发回响应给sdk

更新申请

sdk将更新申请发送给orderer

调用更新

orderer节点验证更新操作(消息队列)和数字证书没问题后各个peer执行更新数据

Chaincode链码

链码的生命周期

链码结构

node.js

const shim = require('fabric-shim');
 
const Chaincode = class {
    async Init(stub) {  //初始化参数
        await stub.putState(key, Buffer.from(aStringValue));
         return shim.success(Buffer.from('Initialized Successfully!'));
    }
 
    async Invoke(stub) {    //调用方法,主要写逻辑业务
        // 读取数据
        let oldValue = await stub.getState(key);
 
        // 写入数据,如果key存在则为更新
        let newValue = oldValue + delta;    //定义数据,只能是键值对,存数组可以转json
        await stub.putState(key, Buffer.from(newValue));
        
        //删除数据
        await stub.deleteState(key);
        
        return shim.success(Buffer.from(newValue.toString()));
    }
};

shim.start(new Chaincode());

用NodeJs编写链码

创建目录

mkdir /root/fish/chaincode/fishcc

安装依赖

cd /root/fish/chaincode/fishcc
npm install --save fabric-shim

编写合约代码

vi index.js

示例:江苏省农牧厅渔业管理系统

'use strict'
const shim = require('fabric-shim');
const util = require('util');

let Chaincode = class {
    //初始化方法,不用写什么东西
    async Init(stub) {
        console.info('初始化成功');
        return shim.success();
    }
    
    //调用方法,主要写逻辑业务
    async Invoke(stub) {    
        let ret = stub.getFunctionAndParameters();  //获取函数和参数
 
        let method = this[ret.fcn];
        if (!method) {
            console.error('找不到要调用的函数,函数名:' + ret.fcn);
            throw new Error('找不到要调用的函数,函数名:' + ret.fcn);
        }
        
        try {
            let payload = await method(stub,ret.params);    //直接调用函数,获取返回值
            return shim.success(payload);
        } catch (err) {
            console.log(err);
            return shim.error(err);
        }
    }
    
    //查询fish信息
    async queryFish(stub,args) {
        if(args.length != !){
            throw new Error('错误的调用函数,实例:FISH01');
        }
        let fishNumber = args[0];
        let fishAsBytes = await stub.getState(fishNumber);  //从账本中获取fish的信息,账本是二进制存储的
        if (!fishAsBytes || fishAsBytes.toString().length <= 0) {
            throw new Error(fishAsBytes + '不存在');
        }
        console.log(fishAsBytes.toString());
        return fishAsBytes;
    }

    //初始化账本方法,官方建议单独写,不用最上面点Init方法
    async initLedger(stub,args){
        console.info('开始:初始化账本');
        let fishes = [];
        fishes.push({
            vessel:"奋进号38A",
            location:"12,34",
            timestamp:"1598509989",
            holder:"wang"
        });
        fishes.push({
            vessel:"奋进号39A",
            location:"123,346",
            timestamp:"1598509989",
            holder:"gao"
        });
        fishes.push({
            vessel:"奋进号40A",
            location:"1234,3467",
            timestamp:"1598509989",
            holder:"liu"
        });

        for (let i = 0; i < fishes.length; i++) {
            await stub.putState('FISH' + i,Buffer.from(JSON.stringify(fishes[i])));
            console.info('Add <--> ',fishes[i]);
        }
        console.info('结束:初始化账本');
    }

    //记录fish信息
    async recoredFish(stub,args){
        console.info('开始:记录fish信息');
        if (args.length != 5) {
            throw new Error('需要5个参数');
        }

        var fish = {
            vessel:args[1],
            location:args[2],
            timestamp:args[3],
            holder:args[4]
        }
        await stub.putState(args[0],Buffer.from(JSON.stringify(fishes)));
        console.info('结束:记录fish信息');
    }

    //查询所有fish
    async queryAllFish(stub,args){
        let startKey = 'FISH0';
        let endKey = 'FISH999';
        let iterator = await stub.getStateByRange(startKey,endKey);

        let allResults = [];
        while (true) {
            let res = await iterator.next();

            if (res.value && res.value.value.toString()) {
                let jsonRes = {};
                console.log(res.value.value.toString('utf8'));

                jsonRes.Key = res.value.key;
                try {
                    jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
                } catch (err) {
                    console.log(err);
                    jsonRes.Record = res.value.value.toString('utf8');
                }
                allResults.push(jsonRes);
            }

            if (res.done) {
                console.log('end of data');
                await iterator.close();
                console.info(allResults);
                return Buffer.from(JSON.stringify(allResults));
            }
        }
    }

    //更改归属人
    async changeFishHolder(stub,args){
        console.info('开始:更改归属人');
        if (args.length != 2) {
            throw new Error('需要2个参数');
        }

        let fishAsBytes = await stub.getState(args[0]);
        let fish = JSON.parse(fishAsBytes);
        fishAsBytes.holder = args[1];

        await stub.putState(args[0],Buffer.from(JSON.stringify(fish)));
        console.info('结束:更改归属人');
    }
}

shim.start(new Chaincode());

用Golang编写链码

示例:两个账户转账与查询

package main

import (
    "fmt"
    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-protos-go/peer"
    "strconv"
)

type ChainCode struct{}

func main() {
    err := shim.Start(new(ChainCode))

    if( err!= nil){
        fmt.Printf("Error starting Simple Chaincode is %s \n",err)
    }
}

//链码初始化
func (cc *ChainCode) Init(stub shim.ChaincodeStubInterface) peer.Response {
    fmt.Println("链码实例例化")
    _, args := stub.GetFunctionAndParameters()
    var accountA, accountB string // 定义账号
    var balanceA, balanceB int    // 定义余额
    var err error

    if len(args) != 4 {
        return shim.Error("参数数量错误")
    }

    // 初始化余额
    accountA = args[0]
    balanceA, err = strconv.Atoi(args[1])
    if err != nil {
        return shim.Error("请输入整数余额")
    }
    accountB = args[2]
    balanceB, err = strconv.Atoi(args[3])
    if err != nil {
        return shim.Error("请输入整数余额")
    }
    fmt.Printf("A余额 = %d, B余额 = %d\n", balanceA, balanceB)

    // 数据上链
    err = stub.PutState(accountA, []byte(strconv.Itoa(balanceA)))
    if err != nil {
        return shim.Error(err.Error())
    }

    err = stub.PutState(accountB, []byte(strconv.Itoa(balanceB)))
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

//调用链码
func (cc *ChainCode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    fmt.Println("链码调用")
    function, args := stub.GetFunctionAndParameters()
    if function == "transfer" {
        // 转账
        return cc.transfer(stub, args)
    } else if function == "delete" {
        // 删除账户
        return cc.delete(stub, args)
    } else if function == "query" {
        //查询余额
        return cc.query(stub, args)
    } else if function == "create" {
        //创建账户
        return cc.create(stub, args)
    }

    return shim.Error("请输入正确的方法名. 方法名只能是 \"invoke\" \"delete\" \"query\" \"create\"")
}

// 从A账户转移资产给B账户
func (cc *ChainCode) transfer(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var accountA, accountB string // 定义账号
    var balanceA, balanceB int    // 定义余额
    var X int                     // 交易数量
    var err error

    if len(args) != 3 {
        return shim.Error("参数数量错误")
    }

    accountA = args[0]
    accountB = args[1]

    // 读取余额
    balanceAbytes, err := stub.GetState(accountA)
    if err != nil {
        return shim.Error("获取数据失败")
    }
    if balanceAbytes == nil {
        return shim.Error("找不到账号信息")
    }
    balanceA, _ = strconv.Atoi(string(balanceAbytes))

    balanceBbytes, err := stub.GetState(accountB)
    if err != nil {
        return shim.Error("获取数据失败")
    }
    if balanceBbytes == nil {
        return shim.Error("找不到账号信息")
    }
    balanceB, _ = strconv.Atoi(string(balanceBbytes))

    // 执行转账
    X, err = strconv.Atoi(args[2])
    if err != nil {
        return shim.Error("转账数量必须是整数")
    }

    if balanceA < X {
        return shim.Error("余额不足")
    }

    balanceA = balanceA - X
    balanceB = balanceB + X
    fmt.Printf("转账后A余额 = %d, B余额 = %d\n", balanceA, balanceB)

    // 数据写入账本
    err = stub.PutState(accountA, []byte(strconv.Itoa(balanceA)))
    if err != nil {
        return shim.Error(err.Error())
    }

    err = stub.PutState(accountB, []byte(strconv.Itoa(balanceB)))
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

// 删除某个账户实体
func (cc *ChainCode) delete(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    if len(args) != 1 {
        return shim.Error("参数数量错误")
    }

    A := args[0]

    // 删除数据
    err := stub.DelState(A)
    if err != nil {
        return shim.Error("删除数据失败")
    }

    return shim.Success(nil)
}

// 查询账户的资产,对应peer chaincode query
func (cc *ChainCode) query(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var account string
    var err error

    if len(args) != 1 {
        return shim.Error("参数数量错误")
    }

    account = args[0]

    // 从账本中获取状态
    balanceAbytes, err := stub.GetState(account)
    if err != nil {
        jsonResp := "获取" + account + "数据失败"
        return shim.Error(jsonResp)
    }

    if balanceAbytes == nil {
        jsonResp := "找不到账号信息"
        return shim.Error(jsonResp)
    }

    jsonResp := "{\"Name\":\"" + account + "\",\"Amount\":\"" + string(balanceAbytes) + "\"}"
    fmt.Printf("查询结果:%s\n", jsonResp)
    return shim.Success(balanceAbytes)
}

//创建账户
func (cc *ChainCode) create(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var account string
    var balanceA int
    var err error

    if len(args) != 2 {
        return shim.Error("参数数量错误")
    }

    // 初始化账号信息
    account = args[0]
    balanceA, err = strconv.Atoi(args[1])
    if err != nil {
        return shim.Error("余额输入错误")
    }

    fmt.Printf("balanceA余额 = %d\n", balanceA)

    // 写入状态到账本
    err = stub.PutState(account, []byte(strconv.Itoa(balanceA)))
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

参考文档

视频:

https://www.bilibili.com/video/BV1554y1q7iE

https://www.bilibili.com/video/BV1zt411H7qX/

文档

https://github.com/itheima1/hyperledger

上一篇 下一篇

猜你喜欢

热点阅读