环境搭建

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

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

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

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

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

当前版本是1.1。

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

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

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

1
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等功能。

成功后会看到提示:

1
2
3
4
5
6
7
8
===================== All GOOD, End-2-End execution completed =====================


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

查看创建的容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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命令都支持什么操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 显示智能合约

首先执行命令查询余额:

1
2
3
4
5
6
7
8
9
# 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

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

1
2
3
4
5
6
7
8
9
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并且初始化了。
如果后续需要创建或修改代码,则需要重新安装:

1
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中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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初始化代码就是:

1
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代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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函数如下,可以理解为所有日常操作的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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种操作,转帐、删除用户、查询余额。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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而转帐不指定则会报错?):

1
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的余额:

1
2
3
4
5
6
7
8
9
# 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的:

1
2
3
4
5
6
7
8
9
# 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。

1
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定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 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