如何编写链代码并调用链代码函数

Chaincode是我们定义应用程序逻辑(函数)的地方。Chaincode需要安装在将与Hyperledger fabric交互的每个节点上。它还需要在通道上实例化。之后,最终用户可以通过与网络节点连接的客户端应用程序调用链代码功能。Chaincode是用Go,node.js或Java编写的程序,它实现了指定的接口。

如何编写链代码?

要在Golang中编写链代码,我们将使用shim包。它为链代码提供API以访问其状态变量,交易上下文和调用其他链代码。让我们创建文件夹结构和文件,我们将在其中编写链代码。文件夹结构如下图所示。

导航到上一篇文章中创建的crypto-kajmak文件夹:

1
$ cd crypto-kajmak

创建必要的新文件夹和文件kajmak-chaincode.go

1
2
3
4
5
$ mkdir chaincode
$ cd chaincode
$ mkdir kajmak-app
$ cd kajmak-app
$ touch kajmak-chaincode.go

kajmak-chaincode.go将包含下面列出的所有链代码。首先,我们将导入必要的包:

1
2
3
4
5
6
7
8
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
  • bytes实现了操作字节切片的函数。它类似于strings包。
  • json实现了RFC 7159中定义的JSON编码和解码。JSON和Go值之间的映射是通过使用MarshalUnmarshal函数实现的。
  • fmt实现了格式化的I/O,其功能类似于C的printfscanf。格式verbs来自C,但更简单。
  • strconv实现与基本数据类型的字符串表示的转换。

接下来,我们实现SmartContract结构,如下所示:

1
2
type SmartContract struct {
}

这是必须由所有链代码实现的链代码接口。我们将在main函数中使用它在实例化期间启动容器中的链代码。在链代码中,我们定义将存储在分类帐中的资产。在我们的例子中,资产是kajmak

1
2
3
4
5
6
7
8
9
10
//Kajmak struct definition
type Kajmak struct {
Name string `json:"name"`
Owner string `json:"owner"`
Animal string `json:"animal"`
Location string `json:"location"`
Quantity string `json:"quantity"`
ProductionDate string `json:"production_date"`
ExpirationDate string `json:"expiration_date"`
}

每个链代码必须实现两个函数:InitInvoke。在链代码实例化期间调用Init函数,目的是为将来的请求准备分类帐。在CryptoKajmak应用程序中,如果函数的调用成功,我们将返回nil

1
2
3
4
//Init method definition
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}

从客户端应用程序向区块链网络的所有未来请求调用调用函数。在这里,我们定义所有自定义函数或我们希望应用程序执行的操作(例如,从分类帐读取资产,在分类帐中写入资产,更新资产,删除资产)。

CryptoKajmak应用程序具有以下功能,这些功能可以构成应用程序的逻辑或代表应用程序可以执行的操作:

  • initLedgerFirst,将ID为1的kajmak添加到分类帐中。此函数的目的是在实例化后启动Org1的chaincode。
  • docker容器,以便最终用户在开始使用应用程序时不必等待(启动容器的过程需要一些时间)。
  • initLedgerSecond,将ID为2的kajmak添加到分类帐中。此函数的目的是在实例化之后为Org2启动chaincode。
  • docker容器,以便最终用户在开始使用应用程序时不必等待(启动容器的过程需要一些时间)。
  • recordKajmak,将新的kajmak记录到分类帐中。
  • queryAllKajmak,从分类帐中读取所有kajmak。
  • changeKajmakOwner,当kajmak更改其所有者时,更新kajmak的所有者attribut。
  • deleteKajmak,从分类帐中删除kajmak。
  • changeKajmakQuantity,在混合两个kajmaks的过程中更新kajmak的数量attribut。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Invoke method definition
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
function, args := APIstub.GetFunctionAndParameters()
if function == "initLedgerFirst" {
return s.initLedgerFirst(APIstub)
} else if function == "initLedgerSecond" {
return s.initLedgerSecond(APIstub)
} else if function == "recordKajmak" {
return s.recordKajmak(APIstub, args)
} else if function == "queryAllKajmak" {
return s.queryAllKajmak(APIstub)
} else if function == "changeKajmakOwner" {
return s.changeKajmakOwner(APIstub, args)
} else if function == "deleteKajmak" {
return s.deleteKajmak(APIstub, args)
} else if function == "changeKajmakQuantity" {
return s.changeKajmakQuantity(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}

initLedgerFirst函数的实现:

1
2
3
4
5
6
7
8
//initLedgerFirst method deifinition
func (s *SmartContract) initLedgerFirst(APIstub shim.ChaincodeStubInterface) sc.Response {
var kajmak = Kajmak{Name: "Kajmak1", Owner: "majra", Animal: "Sheep", Location: "Vlasic", Quantity: "340", ProductionDate: "05.10.2018. 10:55 am", ExpirationDate: "15.10.2019. 10:55 am"}
kajmakAsBytes, _ := json.Marshal(kajmak)
APIstub.PutState(strconv.Itoa(1), kajmakAsBytes)
fmt.Println("Added", kajmak)
return shim.Success(nil)
}

initLedgerSecond函数的实现:

1
2
3
4
5
6
7
8
//initLedgerSecond method deifinition
func (s *SmartContract) initLedgerSecond(APIstub shim.ChaincodeStubInterface) sc.Response {
var kajmak = Kajmak{Name: "Kajmak2", Owner: "daca", Animal: "Cow", Location: "Trebinje", Quantity: "540", ProductionDate: "06.10.2018. 10:56 pm", ExpirationDate: "16.10.2019. 10:56 pm" }
kajmakAsBytes, _ := json.Marshal(kajmak)
APIstub.PutState(strconv.Itoa(2), kajmakAsBytes)
fmt.Println("Added", kajmak)
return shim.Success(nil)
}

queryAllKajmak函数的实现:

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
//queryAllKajmak method definition
func (s *SmartContract) queryAllKajmak(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "0"
endKey := "999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
//buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add comma before array members,suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
//Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- queryAllKajmak:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}

recordKajmak函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//recordKajmak method definition
func (s *SmartContract) recordKajmak(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 8 {
return shim.Error("Incorrect number of arguments. Expecting 8")
}
var kajmak = Kajmak{ Name: args[1], Owner: args[2], Animal: args[3], Location: args[4], Quantity: args[5], ProductionDate: args[6], ExpirationDate: args[7] }
kajmakAsBytes, _ := json.Marshal(kajmak)
err := APIstub.PutState(args[0], kajmakAsBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to record tuna catch: %s", args[0]))
}
fmt.Printf("- recordKajmak:\n%s\n", kajmak)
return shim.Success(nil)
}

changeKajmakOwner函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//changeKajmakOwner method definition
func (s *SmartContract) changeKajmakOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
kajmakAsBytes, _ := APIstub.GetState(args[0])
if kajmakAsBytes == nil {
return shim.Error("Could not locate kajmak")
}
kajmak := Kajmak{}
json.Unmarshal(kajmakAsBytes, &kajmak)
kajmak.Owner = args[1]
kajmakAsBytes, _ = json.Marshal(kajmak)
err := APIstub.PutState(args[0], kajmakAsBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to change kajmak owner: %s", args[0]))
}
fmt.Printf("-changeKajmakOwner:\n")
return shim.Success(nil)
}

changeKajmakQuantity函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//changeKajmakQuantity method definition
func (s *SmartContract) changeKajmakQuantity(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
kajmakAsBytes, _ := APIstub.GetState(args[0])
if kajmakAsBytes == nil {
return shim.Error("Could not locate kajmak")
}
kajmak := Kajmak{}
json.Unmarshal(kajmakAsBytes, &kajmak)
kajmak.Quantity = args[1]
kajmakAsBytes, _ = json.Marshal(kajmak)
err := APIstub.PutState(args[0], kajmakAsBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to change kajmak quantity: %s", args[0]))
}
fmt.Printf("- changeKajmakQuantity:\n")
return shim.Success(nil)
}

deleteKajmak函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//deleteKajmak method definition
func (s *SmartContract) deleteKajmak(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 8 {
return shim.Error("Incorrect number of arguments. Expecting 8")
}
err := APIstub.DelState(args[0])
if (err != nil) {
return shim.Error(fmt.Sprintf("Failed to delete kajmak: %s", args[0]))
}
eventPayload := "Kajmak with ID " + args[0] + " whose owner is " + args[2] + " was deleted because it has expired on date " + args[7]
payloadAsBytes := []byte(eventPayload)
eventErr := APIstub.SetEvent("deleteEvent",payloadAsBytes)
if (eventErr != nil) {
return shim.Error(fmt.Sprintf("Failed to emit event"))
}
fmt.Printf("- deleteKajmak:\n%s\n", args[0])
return shim.Success(nil);
}

在上述功能中,我们使用此API访问分类帐:

getFunctionAndParameters

GetFunctionAndParameters将第一个参数作为函数名返回,其余参数作为字符串数组中的参数返回。如果客户端传递要用作字符串的参数,则仅使用GetFunctionAndParameters

GETSTATE

GetState从分类帐返回指定key的值。请注意,GetState不会从writeset读取数据,而writeset尚未提交到分类帐。换句话说,GetState不会考虑由PutState修改的尚未提交的数据。如果状态数据库中不存在该键,则返回(nil,nil)。

PutState

PutState将指定的keyvalue作为数据写入提议放入交易的writeset中。在交易验证并成功提交之前,PutState不会影响分类帐。简单键不能是空字符串,并且不能以空字符(0x00)开头,以避免范围查询与复合键冲突,复合键在内部以0x00为前缀作为复合键名称空间。

DelState

DelState记录要在交易提议的writeset中删除的指定key。当交易验证并成功提交时,key及其值将从分类帐中删除。在链代码的末尾,我们实现了在实例化期间在容器中启动链代码的main函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
*
/*
* main function *
calls the Start function
The main function starts the chaincode in the container during instantiation.
*/
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}

我们将编写startKajmak.sh脚本来帮助我们:

  • 安装节点模块。
  • 设置链码路径。
  • 启动docker容器。
  • 在同行上安装链码。
  • 在渠道上实例化链码。
  • 调用initLedgerFirst和initLedgerSecond函数。

startKajmak.sh脚本:

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
#!/bin/bash
set -e
starttime=$(date +%s)
function installNodeModules() {
echo
if [ -d node_modules ]; then
echo "============== node modules installed already ============="
else
echo "============== Installing node modules ============="
npm install
fi
echo
}
installNodeModules
# Language defaults to "golang"
LANGUAGE="golang"
##set chaincode path
function setChaincodePath(){
LANGUAGE=`echo "$LANGUAGE"
tr '[:upper:]' '[:lower:]'`
case "$LANGUAGE" in
"golang")
CC_SRC_PATH="$PWD/chaincode/kajmak-app"
;;
*) printf "\n ------ Language $LANGUAGE is not supported yet ------\n"$
exit 1
esac
}
setChaincodePath
//start the docker containers
cd kajmak-network
./start.sh
//start the cli container
docker-compose -f ./docker-compose.yaml up -d cli
//install chaincode on peer0 from org1
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n kajmak-app -v 1.0 -p github.com/kajmak-app
//install chaincode on peer0 from org2
docker exec -e "CORE_PEER_LOCALMSPID=Org2MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" -e "CORE_PEER_ADDRESS=peer0.org2.example.com:7051" cli peer chaincode install -n kajmak-app -v 1.0 -p github.com/kajmak-app
//instantiate chaincode on the channel
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n kajmak-app -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
sleep 10
//invoke initLedgerFirst and initLedgerSecond functions
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n kajmak-app -c '{"function":"initLedgerFirst","Args":[""]}'
docker exec -e "CORE_PEER_LOCALMSPID=Org2MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" -e "CORE_PEER_ADDRESS=peer0.org2.example.com:7051" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n kajmak-app -c '{"function":"initLedgerSecond","Args":[""]}'
#printf "\nTotal execution time : $(($(date +%s) - starttime)) secs ...\n\n"
printf "\nBlockchain network is up and running! \n"

我们为你提供了github存储库,其中包含目前为止编写的所有文件。

实际上,存储库包含最终版本,代表完全开发的CryptoKajmak区块链Web应用程序,包括用AngularJS编写的前端和用Node.js编写的后端。

有关文件夹和文件之间关系的更多详细信息,请参见存储库中的README文件。

注意:github存储库上的crypto-kajmak文件夹包含我们将在以后的文章中讨论的概念(例如事件和注册以及使用证书颁发机构将新用户注册到网络中)。此外,我们已经创建的一些文件可能看起来略有不同,这是因为我们将在以后的文章中更改它们。

======================================================================

如果你想学习区块链并在Blockchain Technologies建立职业生涯,那么请查看我们分享的一些以太坊、比特币、EOS、Fabric等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • ERC721以太坊通证实战,课程以一个数字艺术品创作与分享DApp的实战开发为主线,深入讲解以太坊非同质化通证的概念、标准与开发方案。内容包含ERC-721标准的自主实现,讲解OpenZeppelin合约代码库二次开发,实战项目采用Truffle,IPFS,实现了通证以及去中心化的通证交易所。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • c#比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在C#代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是C#工程师不可多得的比特币开发学习课程。
  • EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • 深入浅出玩转EOS钱包开发,本课程以手机EOS钱包的完整开发过程为主线,深入学习EOS区块链应用开发,课程内容即涵盖账户、计算资源、智能合约、动作与交易等EOS区块链的核心概念,同时也讲解如何使用eosjs和eosjs-ecc开发包访问EOS区块链,以及如何在React前端应用中集成对EOS区块链的支持。课程内容深入浅出,非常适合前端工程师深入学习EOS区块链应用开发。
  • Hyperledger Fabric 区块链开发详解,本课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链代码通信接口等核心概念,也包含Fabric网络设计、nodejs链代码与应用开发的操作实践,是Nodejs工程师学习Fabric区块链开发的最佳选择。
  • Hyperledger Fabric java 区块链开发详解,课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链代码通信接口等核心概念,也包含Fabric网络设计、java链代码与应用开发的操作实践,是java工程师学习Fabric区块链开发的最佳选择。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是如何编写链代码并调用链代码函数