Hyperledger-Fabric环境搭建笔记

环境搭建

前提需要电脑中存在gitdockerdocker-compose命令以及有golang开发环境。

首先创建目录存放Fabric代码,注意路径和权限,在启动服务时候会向其中写一些文件,最开始我就是没注意到这点报错了。

sudo mkdir /opt/gopath/src/github.com/hyperledger/

进入刚才创建的目录后拉取代码:

cd /opt/gopath/src/github.com/hyperledger
git clone https://github.com/hyperledger/fabric.git

当前版本是1.1。

然后拉取所需镜像,这步速度慢需要耐心等待。

sh /opt/gopath/src/github.com/hyperledger/fabric/scripts/bootstrap.sh

完成后进入/opt/gopath/src/github.com/hyperledger/fabric/examples/e2e_cli目录,执行:

bash network_setup.sh up

这个文件干了几件事:

  1. 编译生成Fabric公私钥、证书的程序,程序在目录:/opt/gopath/src/github.com/hyperledger/fabric/release/linux-amd64/bin
  2. 基于configtx.yaml生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。
  3. 基于crypto-config.yaml生成公私钥和证书信息,并保存在crypto-config文件夹中。
  4. 基于docker-compose-cli.yaml启动1Orderer+2org*2Peer+1CLI的Fabric容器。
  5. 在CLI启动的时候,会运行scripts/script.sh文件,这个脚本文件包含了创建Channel,加入Channel,安装Example02,运行Example02等功能。

成功后会看到提示:

===================== All GOOD, End-2-End execution completed =====================


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

查看创建的容器:

docker ps --format "{{.ID}}:{{.Names}}"

a2adbbe090ce:dev-peer1.org2.example.com-mycc-1.0
434f146382d3:dev-peer0.org1.example.com-mycc-1.0
f07e03296c11:dev-peer0.org2.example.com-mycc-1.0
6eee69b127ff:cli
f323f3bf5b0e:orderer.example.com
f8eae1488dce:kafka3
3b79d89b3928:kafka0
dd5cd1d8c35a:kafka2
c7953a8951f1:kafka1
7d28f6dc8c3b:peer0.org1.example.com
aeb4ce4d57c3:zookeeper2
734e25a4430e:zookeeper0
6b051d7edb2e:peer1.org2.example.com
f19e5663a764:zookeeper1
9494e1f87c56:peer1.org1.example.com
7bada708f807:peer0.org2.example.com

默认安装了:4个peer(2个是org1的,2个是org2的)节点、4节点构成的kafka集群、3节点构成的zookeeper集群、1个orderer节点。这是因为:fabric提供的共识机制,PBFT目前还未达到生产级别的应用,只能靠kafka+zookeeper实现PAXOS算法下的共识机制(不能有作恶结点)

简单操作

先进入容器中docker exec -it cli sh,然后看看peer命令都支持什么操作:

docker exec -it cli sh

# pwd
/opt/gopath/src/github.com/hyperledger/fabric/peer
# peer -h
Usage:
  peer [flags]
  peer [command]

Available Commands:
  chaincode   Operate a chaincode: install|instantiate|invoke|package|query|signpackage|upgrade|list.
  channel     Operate a channel: create|fetch|join|list|update|signconfigtx|getinfo.
  logging     Log levels: getlevel|setlevel|revertlevels.
  node        Operate a peer node: start|status.
  version     Print fabric peer version.

Flags:
      --logging-level string   Default logging level and overrides, see core.yaml for full syntax
  -v, --version                Display current version of fabric peer server

Use "peer [command] --help" for more information about a command.

和以太坊类似,fabric中交易也要通过chaincode操作。chancode支持的命令如下:

  • package 智能合约需要打包后才能使用
  • install 智能合约必须安装后才能使用
  • instantiate 置初始状态。比如设系统一开始用户a有100元,用户b有200元
  • invoke 调用智能合约
  • query 查询状态
  • signpackage 包签名
  • upgrade 智能合约升级
  • list 显示智能合约

首先执行命令查询余额:

# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
2018-06-15 03:16:39.226 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-06-15 03:16:39.226 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-06-15 03:16:39.226 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-06-15 03:16:39.227 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-06-15 03:16:39.227 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2018-06-15 03:16:39.227 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0ABE070A6608031A0B0897DF8CD90510...6D7963631A0A0A0571756572790A0161
2018-06-15 03:16:39.227 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: 916F0EE6336EADD59FEBF6538B30645BED4C60C64AD65255ABFCADD189655300
Query Result: 90

等等,为什么我们什么安装操作都没做就能执行查询命令而且还有结果呢?原因在启动服务的过程中有这么一段:

Installing chaincode on org1/peer0...
....
2018-06-15 02:35:10.647 UTC [container] WriteFileToPackage -> DEBU 00c Writing file to tarball: src/github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02/chaincode_example02.go
2018-06-15 02:35:10.648 UTC [container] WriteFileToPackage -> DEBU 00d Writing file to tarball: src/github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02/chaincode_example02_test.go
2018-06-15 02:35:10.648 UTC [msp/identity] Sign -> DEBU 00e Sign: plaintext: 0AB4070A5C08031A0C08DECB8CD90510...83C77F030000FFFF1E416A37002E0000
2018-06-15 02:35:10.648 UTC [msp/identity] Sign -> DEBU 00f Sign: digest: 45EC14B3196DC03ACB6A413DED651FBBAF8BEF4B109DA6F0D8E821F150DA7ED1
2018-06-15 02:35:10.650 UTC [chaincodeCmd] install -> DEBU 010 Installed remotely response:<status:200 payload:"OK" >
2018-06-15 02:35:10.650 UTC [main] main -> INFO 011 Exiting.....
===================== Chaincode is installed on remote peer PEER0 =====================

启动脚本中已经帮我们安装好src/github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02/chaincode_example02.go这个Chaincode并且初始化了。 如果后续需要创建或修改代码,则需要重新安装:

peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02

其中-n表示合约名字,-p指向合约文件目录路径,-v是版本号。

而初始化则需要注意,是否开启了tls方法不同,见/opt/gopath/src/github.com/hyperledger/fabric/examples/e2e_cli/scripts/script.sh中:

instantiateChaincode () {
    PEER=$1
    setGlobals $PEER
    # while 'peer chaincode' command can get the orderer endpoint from the peer (if join was successful),
    # lets supply it directly as we know it using the "-o" option
    if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then
        peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR    ('Org1MSP.member','Org2MSP.member')" >&log.txt
    else
        peer chaincode instantiate -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR  ('Org1MSP.member','Org2MSP.member')" >&log.txt
    fi
    res=$?
    cat log.txt
    verifyResult $res "Chaincode instantiation on PEER$PEER on channel '$CHANNEL_NAME' failed"
    echo "===================== Chaincode Instantiation on PEER$PEER on channel '$CHANNEL_NAME' is successful ===================== "
    echo
}

比如没开启tls初始化代码就是:

peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR    ('Org1MSP.peer','Org2MSP.peer')"

其中,-C指向channel名字,-c则是初始构造json格式的消息,-P是背书策略,-o指定共识节点。这里置帐户a初始余额为100,帐户b初始余额为200。

每个chaincode都要实现Init和Invoke两个方法,其中前者用于初始化,后者是日常调用。 以我们调用的Example02.go代码为例:

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("ex02 Init")
	_, args := stub.GetFunctionAndParameters() //获取传入的参数并解析
	var A, B string    // Entities
	var Aval, Bval int // Asset holdings
	var err error

	if len(args) != 4 {
		return shim.Error("Incorrect number of arguments. Expecting 4")
	}

	// Initialize the chaincode
	A = args[0]
	Aval, err = strconv.Atoi(args[1])
	if err != nil {
		return shim.Error("Expecting integer value for asset holding")
	}
	B = args[2]
	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)

	// Write the state to the ledger
	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}

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

	return shim.Success(nil)
}

invoke函数如下,可以理解为所有日常操作的入口:

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("ex02 Invoke")
	function, args := stub.GetFunctionAndParameters()
	if function == "invoke" {
		// Make payment of X units from A to B
		return t.invoke(stub, args)
	} else if function == "delete" {
		// Deletes an entity from its state
		return t.delete(stub, args)
	} else if function == "query" {
		// the old "Query" is now implemtned in invoke
		return t.query(stub, args)
	}

	return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}

可以看到实现了3种操作,转帐、删除用户、查询余额。

先看查询余额,函数定义如下:

// query callback representing the query of a chaincode
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	var A string // Entities
	var err error

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
	}

	A = args[0] //帐户名

	// Get the state from the ledger
	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)
}

先正常从b给a支付50,这里注意我们开启了tls(疑问:查询时候为啥不用指定tls而转帐不指定则会报错?):

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem  -C mychannel -n mycc -c '{"Args":["invoke","b","a","50"]}'

查看a的余额:

# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
2018-06-15 03:31:01.268 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-06-15 03:31:01.268 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-06-15 03:31:01.268 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-06-15 03:31:01.268 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-06-15 03:31:01.268 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2018-06-15 03:31:01.268 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0ABF070A6708031A0C08F5E58CD90510...6D7963631A0A0A0571756572790A0161
2018-06-15 03:31:01.268 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: 6BAE5E272C5A3FD13D3921B98F0C4C35D6BBD6433DD66CFFF42B04561CDBE688
Query Result: 140

再看b的:

# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
2018-06-15 03:48:24.458 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-06-15 03:48:24.458 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-06-15 03:48:24.458 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-06-15 03:48:24.458 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-06-15 03:48:24.458 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2018-06-15 03:48:24.459 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0ABF070A6708031A0C0888EE8CD90510...6D7963631A0A0A0571756572790A0162
2018-06-15 03:48:24.459 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: B422147DF2CBDF964CAFA860B2FEF3E0928F1B317F599188284860D5EA5902A2
Query Result: 160

a有140,b有160。

peer chaincode invoke -o orderer.example.com:7050  --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem  -C mychannel -n mycc -c '{"Args":["invoke","a","b","150"]}'

这里我们从a向b转150,结果也成功执行了,a余额变成-10,因为invoke代码中并没有对余额和转帐金额的大小进行判断。

转帐函数invoke定义如下:

// Transaction makes payment of X units from A to B
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	var A, B string    // Entities
	var Aval, Bval int // Asset holdings
	var X int          // Transaction value
	var err error

	if len(args) != 3 {
		return shim.Error("Incorrect number of arguments. Expecting 3")
	}

	A = args[0] //转出帐户
	B = args[1] //转入帐户

	// Get the state from the ledger
	// TODO: will be nice to have a GetAllState call to ledger
	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))

	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))

	// Perform the execution
	X, err = strconv.Atoi(args[2])
	if err != nil {
		return shim.Error("Invalid transaction amount, expecting a integer value")
	}
  //关键在于这里缺少判断
	Aval = Aval - X
	Bval = Bval + X
	fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

	// Write the state back to the ledger
	err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
	if err != nil {
		return shim.Error(err.Error())
	}

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

	return shim.Success(nil)
}

这样,我们就可以尝试修改这个chaincode并重新安装部署初始化了,具体操作下次记录。


参考链接:

  1. http://www.taohui.pub/530.html
  2. http://nm1024.com/?p=10