Hyperledger Fabric(5)ChainCode的编写步骤
链码(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命令调用
更详细的说明见:
《区块链核心技术与应用》
《区块链开发实战》
Hyperledger Fabric(5)ChainCode的编写步骤的更多相关文章
- Hyperledger Fabric java chaincode 编译部署(1.4V)
前提条件: 构建好了一个拥有四个peer 一个Order 的1.4版本的Fabric网络. 证书通过Cryptogen生成,没有使用CA服务. 开启TLS. 网络中的peer都加入了一个 名为mych ...
- Hyperledger Fabric java chaincode 中文乱码问题
开发java chaincode过程中遇到一个中文乱码的问题.都是官方的demo,请求的sdk是用java写的,部署的chaincode有两种选择(不考虑node),一种go语言写的chaincode ...
- Hyperledger Fabric 1.2 --- Chaincode Operator 解读和测试(一)
前言 本文主要目的是用于整理Hyperledger Fabric中关于chaincode 管理和操作的内容,作者以release-1.2为范本进行讲解. 主要参考链接: https://hyperl ...
- Hyperledger Fabric(4)链码ChainCode
智能合约,是一个抽象的概念,智能合约的历史可以追溯到 1990s 年代.它是由尼克萨博(Nick Szabo)提出的理念,几乎与互联网同龄. 我们这里所说的智能合约只狭义的指区块链中.它能够部署和运行 ...
- Hyperledger Fabric Chaincode for Operators——实操智能合约
什么是Chaincode(智能合约)? chaincode是一个程序,它是使用Go语言编写的,最终在Java等其他编程语言中实现了指定的接口.chaincode运行在一个被背书peer进程独立出来的安 ...
- HyperLedger Fabric ChainCode开发——shim.ChaincodeStubInterface用法
深蓝前几篇博客讲了Fabric的环境搭建,在环境搭建好后,我们就可以进行Fabric的开发工作了.Fabric的开发主要分成2部分,ChainCode链上代码开发和基于SDK的Application开 ...
- Hyperledger Fabric 1.2 --- Chaincode Operator 解读和测试(二)
本文接上一节是测试部分 搭建一个模拟测试环境 作者将fabric release1.2工程中的 example-e2e进行了改造来进行本次实验: (1)首先我们将examples/e2e_cli/sc ...
- 搭建基于hyperledger fabric的联盟社区(七) --升级chaincode
上个版本的chaincode有很多功能不完备,所以要部署新版本的chaincode.Fabric支持在保留现有状态的前提对chaincode进行升级. 一.新版chaincode 新版本的chainc ...
- 搭建基于hyperledger fabric的联盟社区(四) --chaincode开发
前几章已经分别把三台虚拟机环境和配置文件准备好了,在启动fabric网络之前我们要准备好写好的chaincode.chaincode的开发一般是使用GO或者JAVA,而我选择的是GO语言.先分析一下官 ...
随机推荐
- python中requests.session的妙用
在进行接口测试的时候,我们会调用多个接口发出多个请求,在这些请求中有时候需要保持一些共用的数据,例如cookies信息. 1.requests库的session对象能够帮我们跨请求保持某些参数,也会在 ...
- MongodDB基本命令
MongoDB基本命令 成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作. 输入help可以看到基本操作命令: show dbs:显示数据库列表 show co ...
- iptable规则的执行顺序
众所周知,iptable的中包含了各种各样的table和规则链条.这篇博文对规则链的执行顺序做一个简单的介绍. Chain OUTPUT (policy ACCEPT)target prot opt ...
- Pytorch构建ResNet
学了几天Pytorch,大致明白代码在干什么了,贴一下.. import torch from torch.utils.data import DataLoader from torchvision ...
- element UI实现动态生成多级表头
一.效果图 二.封装两个组件,分别为DynamicTable.vue和TableColumn.vue,TableColumn.vue主要是使用递归来对表头进行循环生成 DynamicTable.vue ...
- 在学习python的Django\Flask\Tornado前你需要知道的,what is web?
我们都在讲web开发web开发,那到底什么是web呢? 如果你正在学习python三大主流web框架,那这些你必须要知道了 软件开发架构: C/S架构:Client/Server 客户端与服务端 ...
- linux学习命令收集
多看看大神的博客 https://blog.csdn.net/tao934798774/article/details/79491951 ip addr 查看ip地址 ifconfig 查看i ...
- Linux文件权限基础回顾介绍
文件的权限 文件权限的概述: 权限贯穿运维的一生
- Javascript学习笔记——操作浏览器对象
Javascript学习笔记 目前尝试利用javascript去对于一个浏览器对象完成一系列的访问及修改, 浏览器是网页显示.运行的平台,常用的浏览器有IE.火狐(Firefox).谷歌(Chrome ...
- python列表和if语句的简单结合
将列表所有元素打印出来 cars = ['toyota', 'honda', 'mazda', 'nissan', 'mitsubishi', 'subaru', 'suzuki', 'isuzu'] ...