hyperledger-fabric测试环境搭建

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

环境安装


安装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
  • 安装docker-ce
yum install docker-ce
  • 查看版本
docker -v

Ubuntu安装docker

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

安装docker-compose

如果已安装可跳过

  • 下载

    https://github.com/docker/compose/releases查看最新版本,替换下面的链接,后面不要带-rc

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,其他版本同理

  • 下载

    wget https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz
    
  • 安装

    依次执行下面5行命令即可

    tar -zxvf Python-2.7.9.tgz
    cd Python-2.7.9
    ./configure --prefix=/usr/local/python-2.7.9
    make
    make install
    
  • 查看版本

    python --version
    

安装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目录,2.2以后的版本的目录在fabric-samples/test-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

生命周期

  • 安装install
  • 实例化init
  • 调用invoke

MSP

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

工作流程


提案

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

背书

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

更新申请

sdk将更新申请发送给orderer

调用更新

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

Chaincode链码

  • 开发者必须同时实现chaincode的InitInvoke方法,chaincode编写完需要通过peerchaincode install命令安装
  • fabric 的数据以键值对的形式存放在peerlevelDB中,可以切换为couchDB

链码的生命周期

  • install:将已编写完成的链码安装在网络节点中。
  • instantiate:对已安装的链码进行实例化。
  • upgrade:对已有链码进行升级。链代码可以在安装后根据具体需求的变化进行升级。
  • package:对指定的链码进行打包的操作。
  • singnpackage:签名。

链码结构

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

猜你喜欢

转载自blog.csdn.net/C_jian/article/details/108870030