链码(chaincode) 会对 Fabric应用程序 发送的交易做出响应,执行代码逻辑,与 账本 进行交互。
再复习下他们之间的逻辑关系:
Hyperledger Fabric 中,Chaincode 默认运行在 Docker 容器中。
Peer 通过调用 Docker API 来创建和启动 Chaincode 容器。
Chaincode 容器启动后跟 Peer 之间创建 gRPC 连接,双方通过发送 ChaincodeMessage 来进行交互通信。
Chaincode 容器利用 core.chaincode.shim 包提供的接口来向 Peer 发起请求。
每个chaincode程序都必须实现chaincode接口,接口中的方法会在响应传来的交易时被调用。
type Chaincode interface {
Init (stub ChaincodeStubInterface) pb.Response
Invoke (stub ChaincodeStubInterface) pb.Response
}
- Init(初始化)方法会在chaincode接收到instantiate(实例化)或者upgrade(升级)交易时被调用,进而使得chaincode顺利执行必要的初始化操作,包括初始化应用的状态。
- Invoke(调用)方法会在响应invoke(调用)交易时被调用以执行交易。
现在,Fabric支持多种计算机语言实现的链码,包括Golang、JavaScript、Java等。
一个链码的必要结构
package main
//(1)引入必要的包
//fmt是golang系统提供的通用输入输出包,后面两个包是必须的。
//第二个包 shim包是Fabric系统提供的上下文环境,包含了Chaincode和Fabric交互的接口,在Chaincode中,执行赋值、查询等功能都需要通过shim。
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
//(2)声明一个结构体,即chaincode的主体结构体
//该结构体需要实现Fabric提供的接口"github.com/hyperledger/fabric/protos/peer",其中必须实现下面的两个方法。
type DemoChaincode struct { } //(3)Init() 和 Invoke() 函数的实现,在其中利用 shim.ChaincodeStubInterface 结构,实现跟账本的交互逻辑。
func (t *DemoChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
// 该方法中实现链码初始化或升级是的处理逻辑
// 编写时可以灵活使用stub中的API
return stub.Success(nil)
}
func (t *DemoChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response
//该方法中实现链码运行中被调用或查询时的处理逻辑
//编写时可以灵活使用stub中的API
return stub.Success(nil)
}
//(4)主函数,需要调用shim.Start()方法
func main() {
err := shim.Start(new(DemoChaincode))
if err != nil {
fmt.Printf("Error starting DemoChaincode: %s", err)
}
}
ChainCode编写的步骤
这个实例是 fabric-sample/fabcar目录下:有一个关于car 交易的 app
1)引入相关包
首先,我们先进行准备工作。对于每一个chaincode,它都会实现预定义的chaincode接口,特别是Init和Invoke函数接口。所以我们首先为我们的chaincode引入必要的依赖。这里的shim层是节点与链码交互的中间层。如下图所示:
package main import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
2)初始化Chaincode
接下来,我们将实现Init函数。
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}
值得留意的是chaincode升级同样会调用该函数。当我们编写的chaincode会升级现有chaincode时,需要确保适当修正Init函数。特别地,如果没有“迁移”操作或其他需要在升级中初始化的东西,那么就提供一个空的“Init”方法。我们这里仅仅提供了一个简单的Init方法。
3)编写Chaincode接口函数
首先,添加Invoke函数签名
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {}
我们需要调用ChaincodeStubInterface来获取参数。我们将调用ChaincodeStubInterface并以键值为参数传入。如果一切正常,那么我们会收到表明初始化成功的peer.Response返回对象。Invoke函数所需的传入参数正是应用想要调用的chaincode的名称。在我们的应用里面,我们有几个简单的功能函数:queryCar, initLedger, createCar, queryAllCars, changeCarOwner等。
下面,我们将使这几个函数名正式生效,并调用这些chaincode应用函数,经由shim.Success或shim.Error函数返回一个合理的响应。这两个shim成员函数可以将响应序列化为gRPC protobuf消息。
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
function, args := APIstub.GetFunctionAndParameters()
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
} return shim.Error("Invalid Smart Contract function name.")
}
4)编写Chaincode的调用函数
如上文所述,我们的chaincode应用实现了五个函数,并可以被Invoke函数调用。下面我们就来真正实现这些函数。注意,就像上文一样,我们调用chaincode shim API中的ChaincodeStubInterface.PutState和ChaincodeStubInterface.GetState函数来访问账本。
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != {
return shim.Error("Incorrect number of arguments. Expecting 1")
} carAsBytes, _ := APIstub.GetState(args[])
return shim.Success(carAsBytes)
} func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
} i :=
for i < len(cars) {
fmt.Println("i is ", i)
carAsBytes, _ := json.Marshal(cars[i])
APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
fmt.Println("Added", cars[i])
i = i +
} return shim.Success(nil)
} func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != {
return shim.Error("Incorrect number of arguments. Expecting 5")
} var car = Car{Make: args[], Model: args[], Colour: args[], Owner: args[]} carAsBytes, _ := json.Marshal(car)
APIstub.PutState(args[], carAsBytes) return shim.Success(nil)
} func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0"
endKey := "CAR999" 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 a 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("- queryAllCars:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes())
} func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != {
return shim.Error("Incorrect number of arguments. Expecting 2")
} carAsBytes, _ := APIstub.GetState(args[])
car := Car{} json.Unmarshal(carAsBytes, &car)
car.Owner = args[] carAsBytes, _ = json.Marshal(car)
APIstub.PutState(args[], carAsBytes) return shim.Success(nil)
} func main() { // Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
这里可以看到,在最后关头我们写了main函数,它将调用shim.Start 函数,main函数的作用,是在容器里启动chaincode。
5)整合全部代码
将上面的代码整合在一起如下:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/ /*
* The sample smart contract for documentation topic:
* Writing Your First Blockchain Application
*/ package main /* Imports
* 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
* 2 specific Hyperledger Fabric specific libraries for Smart Contracts
*/
import (
"bytes"
"encoding/json"
"fmt"
"strconv" "github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
) // Define the Smart Contract structure
type SmartContract struct {
} // Define the car structure, with 4 properties. Structure tags are used by encoding/json library
type Car struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
} /*
* The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network
* Best practice is to have any Ledger initialization in separate function -- see initLedger()
*/
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
} /*
* The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
* The calling application program has also specified the particular smart contract function to be called, with arguments
*/
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response { // Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
} return shim.Error("Invalid Smart Contract function name.")
} func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != {
return shim.Error("Incorrect number of arguments. Expecting 1")
} carAsBytes, _ := APIstub.GetState(args[])
return shim.Success(carAsBytes)
} func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
} i :=
for i < len(cars) {
fmt.Println("i is ", i)
carAsBytes, _ := json.Marshal(cars[i])
APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
fmt.Println("Added", cars[i])
i = i +
} return shim.Success(nil)
} func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != {
return shim.Error("Incorrect number of arguments. Expecting 5")
} var car = Car{Make: args[], Model: args[], Colour: args[], Owner: args[]} carAsBytes, _ := json.Marshal(car)
APIstub.PutState(args[], carAsBytes) return shim.Success(nil)
} func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0"
endKey := "CAR999" 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 a 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("- queryAllCars:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes())
} func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != {
return shim.Error("Incorrect number of arguments. Expecting 2")
} carAsBytes, _ := APIstub.GetState(args[])
car := Car{} json.Unmarshal(carAsBytes, &car)
car.Owner = args[] carAsBytes, _ = json.Marshal(car)
APIstub.PutState(args[], carAsBytes) return shim.Success(nil)
} // The main function is only relevant in unit test mode. Only included here for completeness.
func main() { // Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
调试ChainCode
在Fabric中,调用chainCode有两种方式,一种是通过SDK编写应用程序来调用,比如fabcar项目中,通过nodejs程序调用ChainCode。还有一种方式,就是使用cli命令来调用ChainCode。
1)应用程序编写
fabric-sample/fabcar目录下:有一个关于car 交易的 app
https://www.tuoluocaijing.cn/article/detail-45754.html
https://segmentfault.com/a/1190000014350821
https://www.jianshu.com/p/b0c11a76ff67
2)cli命令调用
更详细的说明见:
《区块链核心技术与应用》
《区块链开发实战》