Hyperledger-Fabric环境搭建笔记
环境搭建
前提需要电脑中存在git
、docker
、docker-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
这个文件干了几件事:
- 编译生成Fabric公私钥、证书的程序,程序在目录:/opt/gopath/src/github.com/hyperledger/fabric/release/linux-amd64/bin
- 基于configtx.yaml生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。
- 基于crypto-config.yaml生成公私钥和证书信息,并保存在crypto-config文件夹中。
- 基于docker-compose-cli.yaml启动1Orderer+2org*2Peer+1CLI的Fabric容器。
- 在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并重新安装部署初始化了,具体操作下次记录。
参考链接: