在部署单机多节点Fabric 网络时,我们使用的是e2e_cli测试样例中的智能合约:example02,其路径为:/opt/gopath/src/github.com/hyperledger/fabric/aberic/chaincode/go/chaincode_example02,本章将对这个简单的只能合约做深度的解析。
首先安装智能合约的命令为:
peer chaincode install -n mychannel -p github.com/hyperledger/fabric/aberic/
chaincode/go/chaincode/go/chaincode_example02 -v 1.0
安装智能合约的日志如下:
2020-06-08 07:00:42.296 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2020-06-08 07:00:42.296 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2020-06-08 07:00:42.296 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2020-06-08 07:00:42.296 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2020-06-08 07:00:42.408 UTC [golang-platform] getCodeFromFS -> DEBU 005 getCodeFromFS github.com/hyperledger/fabric/aberic/chaincode/go/chaincode_example02
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 006 Discarding GOROOT package fmt
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 007 Discarding provided package github.com/hyperledger/fabric/core/chaincode/shim
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 008 Discarding provided package github.com/hyperledger/fabric/protos/peer
2020-06-08 07:00:42.643 UTC [golang-platform] func1 -> DEBU 009 Discarding GOROOT package strconv
2020-06-08 07:00:42.643 UTC [golang-platform] GetDeploymentPayload -> DEBU 00a done
2020-06-08 07:00:42.644 UTC [msp/identity] Sign -> DEBU 00b Sign: plaintext: 0A86070A5C08031A0C089AC4F7F60510...2FBAFE130000FFFFBC6386C1002C0000
2020-06-08 07:00:42.644 UTC [msp/identity] Sign -> DEBU 00c Sign: digest: B8C34039C216016ABD32E7B74CA92DE3268BF433FB2717666208CF766E75FB1F
2020-06-08 07:00:42.649 UTC [chaincodeCmd] install -> DEBU 00d Installed remotely response:<status:200 payload:"OK" >
2020-06-08 07:00:42.649 UTC [main] main -> INFO 00e Exiting.....
安装完成后需要进行实例化chaincode,执行如下命令:
peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n
mychannel -c '{"Args":["init","A","10","B","10"]}' -P "OR ('Org1MSP.member')" -v 1.0
实例化chaincode的日志如下:
2020-06-08 07:05:59.323 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2020-06-08 07:05:59.323 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2020-06-08 07:05:59.324 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2020-06-08 07:05:59.324 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2020-06-08 07:05:59.324 UTC [msp/identity] Sign -> DEBU 005 Sign: plaintext: 0A91070A6708031A0C08D7C6F7F60510...314D53500A04657363630A0476736363
2020-06-08 07:05:59.324 UTC [msp/identity] Sign -> DEBU 006 Sign: digest: 58805553D0FCD1F5B30140EA706BC2CAB22E56627011C1CD55E3DCBDFDFD4B00
2020-06-08 07:06:22.303 UTC [msp/identity] Sign -> DEBU 007 Sign: plaintext: 0A91070A6708031A0C08D7C6F7F60510...C6BE9382352C9F3E65C5165CB5442EFF
2020-06-08 07:06:22.303 UTC [msp/identity] Sign -> DEBU 008 Sign: digest: 92235FAD70B935B6AC7A58928D7B6DF0B768010BC3C5F8910A43DE945FC6788B
2020-06-08 07:06:22.308 UTC [main] main -> INFO 009 Exiting.....
example02的源码如下所示:
package main
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}
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)
}
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\"")
}
// 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)
}
// Deletes an entity from state
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
A := args[0]
// Delete the key from the state in ledger
err := stub.DelState(A)
if err != nil {
return shim.Error("Failed to delete state")
}
return shim.Success(nil)
}
// 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)
}
jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
fmt.Printf("Query Response:%s\n", jsonResp)
return shim.Success(Avalbytes)
}
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
首先,链码启动必须通过调用shim包中的start函数,传递一个类型为chaincode的参数,所以,我们首先来看看chaincode类型接口。
type chaincode interface{
Init(stub ChaincodestubInterface) peer.Response
Invoke(stub ChaincodestubInterface) peer.Response
}
ChainCode的Go代码需要定义一个SimpleChaincode这样一个struct,然后在该struct上定义Init和Invoke两个函数,然后还要定义一个main函数,作为ChainCode的启动入口。其中Init在链码实例化或者升级的时候被调用,而Invoke则在更新或查询账本数据状态时被调用,需要在此方法中实现响应调用或查询的业务逻辑。
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
fmt.Println("ex02 Init")
_, args := stub.GetFunctionAndParameters()//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)
}
其中:
_, args := stub.GetFunctionAndParameters()
获取调用的时候传入的参数, 将字符串数组的参数分为两部分,数组第一个字是Function,剩下的都是Parameter
if len(args) != 4 {
return shim.Error("Incorrect number of arguments. Expecting 4")
}
判断输入参数是否为4个,若不为4个,则返回错误:Incorrect number of arguments. Expecting 4
A = args[0]
Aval, err = strconv.Atoi(args[1])//strconv为格式转换函数,strconv.Atoi()为int转字符串
if err != nil {
return shim.Error("Expecting integer value for asset holding")
}
获取A账户(A赋值第一个参数),A账户给予10的初始资产(Aval赋值第二个参数,值为10),若查询失败,则返回错误:Expecting integer value for asset holding
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)
向日志中打印AB的余额。
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())
}
将账户 A,B 的状态写入账本中
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\"")
}
其中:
function, args := stub.GetFunctionAndParameters()
function保存执行的函数名,args保存函数后跟的参数
然后根据function的不同,执行invoke、delete或query函数。
基本语法和之前的相似,就不一一说明了,在代码中标注
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var A, B string // 账户 A 和 B
var Aval, Bval int // 账户余额
var X int // 转账金额
var err error
if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
}
A = args[0] // 账户 A 用户名
B = args[1] // 账户 B 用户名
// Get the state from the ledger从账本中获取 A 的余额
// 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))
//从账本中获取 B 的余额
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))
//// X 为 转账金额
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)
// 更新转账后账本中 A 余额
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
}
// 更新转账后账本中 B 余额
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
A := args[0]// 账户用户名
// 从账本中删除该账户状态
err := stub.DelState(A)
if err != nil {
return shim.Error("Failed to delete state")
}
return shim.Success(nil)
}
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]//账户用户名
// 从账本中获取该账户余额
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)
}
jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
fmt.Printf("Query Response:%s\n", jsonResp)
// 返回转账金额
return shim.Success(Avalbytes)
}