Hyperledger Fabric 链码

article/2025/10/3 7:08:28

懂哪写哪,随时补充

链码结构

链码API

链码在开发过程中需要实现链码接口,交易的类型决定了哪个接口函数将会被调用,链码的接口定义如下:

type Chaincode interface {Init(stub ChaincodeStubInterface) pb.ResponseInvoke(stub ChaincodeStubInterface) pb.Response
}

链码的基本结构

链码的必要结构如下:

package main//引入必要的包
import(
"github.com/hyperledger/fabric/core/chaincode/shim"
pb"github.com/hyperledger/fabric/protos/peer"
)//声明一个结构体
type SimpleChaincode struct {}//为结构体添加Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{//在该方法中实现链码初始化或升级时的处理逻辑//编写时可灵活使用stub中的API
}//为结构体添加Invoke方法
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{//在该方法中实现链码运行中被调用或查询时的处理逻辑//编写时可灵活使用stub中的API
}//主函数,需要调用shim.Start( )方法
func main() {err:=shim.Start(new(SimpleChaincode))if err != nil {fmt.Printf("Error starting Simple chaincode: %s", err)}
}

链码开发API

shim.ChaincodeStubInterface接口

参数读取API

GetArgs() [][]byte
//以byte数组的数组的形式获得传入的参数列表GetStringArgs() []string
//以字符串数组的形式获得传入的参数列表GetFunctionAndParameters() (string, []string)
//将字符串数组的参数分为两部分,第一个参数作为被调用的函数名称,剩下的参数作为函数的执行参数(若交易只有一个参数,则为空列表)GetArgsSlice() ([]byte, error)
//以byte切片的形式获得参数列表eg:
function, args := stub.GetFunctionAndParameters()

应用开发案例

转账

1、Init方法

Init方法中,首先通过stub的GetFunctionAndParameters()方法提取本次调用的交易中所指定的参数:

_, args := stub.GetFunctionAndParameters()
//本例中,用下划线忽略了返回的function值,用args变量记录其他参数。

接下来检查args参数数量,必须为4,否则会通过shim.Error()函数创建并返回一个状态为ERROR的Response消息:

if len(args) != 4 {return shim.Error("Incorrect number of arguments. Expecting 4")
}

分别读取4个参数。设用该链码实现转账的两个实体分别为a和b,则A、Aval、B、Bval的值分别表示a的名称、a的初始余额、b的名称、b的初始余额:

A = args[0]
Aval, err = strconv.Atoi(args[1])//strconv.Atoi()将string类型转为int类型
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")
}

之后,最为关键的是将必要的状态值记录到分布式账本中。stub的PutState()函数可以尝试在账本中添加或更新一对键值(需要等待Committer节点验证通过,才真正写入账本得到确认)。

Pustate()方法格式为PutState(key string,value[]byte)error,其中key为键,类型是string;value为值,类型是字节数组。以下代码向账本中存入了两对键值,分别记录了a和b的余额:

err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {return shim.Error(err.Error())
}err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
//strconv.Itoa函数的参数是一个整型数字,它可以将数字转换成对应的字符串类型的数字。
if err != nil {return shim.Error(err.Error())
}

最后,通过shim.Success(nil)创建并返回状态为OK的Response消息。

2、Invoke方法

Invoke方法中,同样通过stub的GetFunctionAndParameters()方法提取本次调用的交易中所指定的参数:

unction, args := stub.GetFunctionAndParameters()

之后根据function值的不同,执行不同的分支处理逻辑。

本例在Invoke中实现了三个分支处理逻辑:query、invoke和delete。由代码可见,为每个分支的处理逻辑都编写了一个方法:

if function == "invoke" {return t.invoke(stub, args)
} else if function == "delete" {return t.delete(stub, args)
} else if function == "query" {return t.query(stub, args)
}

资产权属管理

链码代码可参考examples/chaincode/go/marbles02/marbles_chaincode.go。

1、链码结构:

package main// 引入必要的包
import ("bytes""encoding/json""fmt""strconv""strings""time""github.com/hyperledger/fabric/core/chaincode/shim"pb "github.com/hyperledger/fabric/protos/peer"
)// 声明名为SimpleChaincode的结构体
type SimpleChaincode struct {
}// 声明大理石(marble)结构体
type marble struct {ObjectType string `json:"docType"`Name       string `json:"name"`Color      string `json:"color"`Size       int    `json:"size"`Owner      string `json:"owner"`
}// 主函数,需要调用shim.Start()方法
func main() {err := shim.Start(new(SimpleChaincode))if err != nil {fmt.Printf("Error starting Simple chaincode: %s", err)}
}// 为SimpleChaincode添加Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {// 不做具体处理return shim.Success(nil)
}// 为SimpleChaincode添加Invoke方法
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {function, args := stub.GetFunctionAndParameters()fmt.Println("invoke is running " + function)// 定位到不同的分支处理逻辑if function == "initMarble" {return t.initMarble(stub, args)} else if function == "transferMarble" {return t.transferMarble(stub, args)} else if function == "transferMarblesBasedOnColor" {return t.transferMarblesBasedOnColor(stub, args)} else if function == "delete" {return t.delete(stub, args)} else if function == "readMarble" {return t.readMarble(stub, args)} else if function == "queryMarblesByOwner" {return t.queryMarblesByOwner(stub, args)} else if function == "queryMarbles" {return t.queryMarbles(stub, args)} else if function == "getHistoryForMarble" {return t.getHistoryForMarble(stub, args)} else if function == "getMarblesByRange" {return t.getMarblesByRange(stub, args)}fmt.Println("invoke did not find func: " + function) // errorreturn shim.Error("Received unknown function invocation")
}

在链码中,可以自定义结构体类型来表示一种资产,并设定资产的各种属性。本例中定义了大理石(marble)资产,其属性包括类型、名称、颜色、尺寸、拥有者。具体映射到代码中,对marble类型的声明如下:

type marble struct {ObjectType string `json:"docType"`Name       string `json:"name"`Color      string `json:"color"`Size       int    `json:"size"`Owner      string `json:"owner"`
}

2、Invoke方法

链码的Init方法中未进行任何处理,Invoke方法中则包含了9个分支方法。

在这里插入图片描述

在这里插入图片描述

下面对分支方法逐一进行介绍。

  1. initMarble方法

initMarble方法根据输入参数创建一个大理石,并写入账本。

方法接受4个参数,依次表示大理石名称、颜色、尺寸、拥有者名称。例如,如果调用链码时指定参数{“Args”: [“initMarble”,“marble1”,“blue”,“35”,“tom”]},则功能为创建并记录一个名称为marble1、蓝色、尺寸为 35的大理石,拥有者为tom。

读取参数后,首先使用stub.GetState()进行查重。如果同样名称的大理石在账本中已经存在,则返回error的Response:

// 检查大理石是否已经存在
marbleAsBytes, err := stub.GetState(marbleName)
if err != nil {return shim.Error("Failed to get marble: " + err.Error())
} else if marbleAsBytes != nil {fmt.Println("This marble already exists: " + marbleName)return shim.Error("This marble already exists: " + marbleName)
}

创建相应的marble类型变量,并用json.Marshal()方法将其序列化到JSON对象中。自定义类型的变量序列化之后才可以写入账本,同理,对于从账本中读取出的信息需要反序列化后才便于进行操作:

// 创建marble,并序列化为JSON对象
objectType := "marble"
marble := &marble{objectType, marbleName, color, size, owner}
marbleJSONasBytes, err := json.Marshal(marble)
if err != nil {return shim.Error(err.Error())
}

之后,用stub.PutState()将序列化后的内容写入账本,以大理石名称marbleName为键:

// 将marbleJSONasBytes存入状态
err = stub.PutState(marbleName, marbleJSONasBytes)
if err != nil {return shim.Error(err.Error())
}

在initMarble中,为了支持之后针对某一特定颜色的大理石进行范围查找,需要将该大理石的颜色与名称这两个属性组合起来创建一个复合键,并记录在账本中。这里,复合键的意义是将一部分属性也构造为了索引的一部分,使得针对这部分属性做查询时,可以直接根据索引返回查询结果,而不需要具体提取完整信息来作比对:

indexName := "color~name"
colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name})
if err != nil {return shim.Error(err.Error())
}

这里调用了stub的CreateCompositeKey方法来创建复合键。该方法格式为 CreateCompositeKey(objectType string,attributes[]string)(string,error),实际上会将objectType和attributes中的每个 string串联起来,中间用U+0000分割;同时在开头加上\x00,标明该键为复合键。

最后,以复合键为键,以0x00为值,将复合键记录入账本中:

value := []byte{0x00}
stub.PutState(colorNameIndexKey, value)
  1. readMarble方法

根据大理石名称,readMarble方法会在账本中查询并返回大理石信息。

方法接受1个参数,即大理石名称。例如,如果调用链码时指定参数{“Args”:[“readMarble”,“marble1”]},则功能为查找名称为marble1的大理石,如果找到,返回其信息:

valAsbytes, err := stub.GetState(name)
if err != nil {jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}"return shim.Error(jsonResp)
} else if valAsbytes == nil {jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}"return shim.Error(jsonResp)
}return shim.Success(valAsbytes)
  1. delete方法

根据大理石名称,delete方法会在账本中删除大理石信息。

方法接受1个参数,即大理石名称。例如,如果调用链码时指定参数{“Args”:[“delete”,“marble1”]},则功能为删除名称为marble1的大理石的信息。

**除了删除以大理石名称为键的状态,还需删除该大理石的颜色与名称复合键。**所以方法中第一步需要读取该大理石的颜色:

var marbleJSON marblevalAsbytes, err := stub.GetState(marbleName)
if err != nil {jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}"return shim.Error(jsonResp)
} else if valAsbytes == nil {jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}"return shim.Error(jsonResp)
}err = json.Unmarshal([]byte(valAsbytes), &marbleJSON)
if err != nil {jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}"return shim.Error(jsonResp)
}

其中用json.Unmarshal方法将从账本中读取到的值反序列化为marble类型变量marbleJSON。则大理石颜色为marbleJSON.Color。

删除以大理石名称为键的状态:

err = stub.DelState(marbleName)
if err != nil {return shim.Error("Failed to delete state:" + err.Error())
}

删除以大理石的颜色与名称复合键为键的状态:

indexName := "color~name"
colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name})
if err != nil {return shim.Error(err.Error())
}err = stub.DelState(colorNameIndexKey)
if err != nil {return shim.Error("Failed to delete state:" + err.Error())
}
  1. transferMarble方法

transferMarble方法用于更改一个大理石的拥有者。

方法接受两个参数,依次为大理石名称和新拥有者名称。例如,如果调用链码时指定参数{“Args”:[“transferMarble”,“marble2”,“jerry”]},则功能是将名称为marble2的大理石的拥有者改为jerry。

首先用stub.GetState()方法从账本中取得信息,再用json.Unmarshal()方法将其反序列化为marble类型

marbleAsBytes, err := stub.GetState(marbleName)
if err != nil {return shim.Error("Failed to get marble:" + err.Error())
} else if marbleAsBytes == nil {return shim.Error("Marble does not exist")
}marbleToTransfer := marble{}
err = json.Unmarshal(marbleAsBytes, &marbleToTransfer)
if err != nil {return shim.Error(err.Error())
}

更改大理石的拥有者:

marbleToTransfer.Owner = newOwner

最后将更改后的状态写入账本:

marbleJSONasBytes, _ := json.Marshal(marbleToTransfer)
err = stub.PutState(marbleName, marbleJSONasBytes)
if err != nil {return shim.Error(err.Error())
}
  1. getMarblesByRange方法

给定大理石名称的起始和终止,getMarblesByRange可以进行范围查询,返回所有名称在指定范围内的大理石信息。

方法接受两个参数,依次为字典序范围的起始(包括)终止(不包括)。例如,调用链码时可以指定参数**{“Args”:[“getMarblesByRange”,“marble1”,“marble3”]}**进行范围查询,返回查找到的结果的键值。

方法中调用了stub.GetStateByRange(startKey,endKey)进行范围查询,其返回结果是一个迭代器StateQueryIteratorInterface结构,可以按照字典序迭代每个键值对,最后需调用Close()方法关闭:

resultsIterator, err := stub.GetStateByRange(startKey, endKey)
if err != nil {return shim.Error(err.Error())
}
defer resultsIterator.Close()

通过迭代器的迭代构造出查询结果的JSON数组,最后通过shim.Success()方法来返回结果:

var buffer bytes.Buffer
buffer.WriteString("[")bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {queryResponse, err := resultsIterator.Next()if err != nil {return shim.Error(err.Error())}if bArrayMemberAlreadyWritten == true {buffer.WriteString(",")}buffer.WriteString("{\"Key\":")buffer.WriteString("\"")buffer.WriteString(queryResponse.Key)buffer.WriteString("\"" )buffer.WriteString(", \"Record\":")// 记录本身就是一个 JSON 对象buffer.WriteString(string(queryResponse.Value))buffer.WriteString("}")bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String())return shim.Success(buffer.Bytes())

Go 语言拼接字符串有五种方法,分别是:使用+号拼接、使用 sprintf 拼接、使用 join 函数拼接、使用 buffer.WriteString 函数拼接、使用 buffer.Builder 拼接。

使用 join 函数拼接:

var str []string = []string{s1, s2}
s := strings.Join(str, "")

使用 buffer.WriteString 函数拼接:

var bt bytes.Buffer
bt.WriteString(s1)
bt.WriteString(s2)
//获得拼接后的字符串
s3 := bt.String()

使用 buffer.Builder 拼接:

var build strings.Builder
build.WriteString(s1)
build.WriteString(s2)
s3 := build.String()

Go言拼接字符串有五种方法,分别是:使用+号拼接、使用 sprintf 拼接、使用 join 函数拼接、使用 buffer.WriteString 函数拼接、使用 buffer.Builder 拼接。

使用 join 函数拼接:

var str []string = []string{s1, s2}
s := strings.Join(str, "")

使用 buffer.WriteString 函数拼接:

var bt bytes.Buffer
bt.WriteString(s1)
bt.WriteString(s2)
//获得拼接后的字符串
s3 := bt.String()

使用 buffer.Builder 拼接:

var build strings.Builder
build.WriteString(s1)
build.WriteString(s2)
s3 := build.String()

http://chatgpt.dhexx.cn/article/xe2vHqqO.shtml

相关文章

fabric2.3链码对比1.4链码小记

最近实验室的项目要部署到fabric2.0以上版本,之前写的都是1.4的链码,现在看2.0版本的链码还是有些不一样的,主要是链码api改了: 前提:如果想在fabric2.0以上环境中还是想用shim和peerAPI的话:也就是&#…

Hyperledger Fabric 链码生命周期

目录 一、什么是链码 二、部署链码 2.1 安装和定义链码 2.1.1 打包智能合约 2.1.2 peer节点安装链码 2.1.3 组织批准链码 2.1.4 将链码提交到通道 2.2 升级链码 总结 一、什么是链码 ChainCode(链码)是一个程序,用Go、Node.js或Java编…

matlab freeman链码,对Freeman链码分析的角点检测算法

图像中的角点是图像的重要特征, 具有旋转不变性, 决定了图像形状, 可以降低图像信息的存储效率, 在目标跟踪, 目标检测, 图像匹配, 图像轮廓拟合等领域都有重要的应用价值. 近几十年来, 国内外学者提出的图像角点检测算法[, 各有各的优缺点, 大致可分为三大类: 基于灰度强度的角…

Fabric链码升级

一、修改链码,上传 二、打包链码 1、设置组织1环境变量 export PATH${PWD}/../bin:$PATH export FABRIC_CFG_PATH$PWD/../config/ # Environment variables for Org1 export CORE_PEER_TLS_ENABLEDtrue export CORE_PEER_LOCALMSPID"Org1MSP" export C…

图像特征-链码(Freeman code)

目录 引言链码编程实现轮廓提取链码计算 总结 引言 本文介绍了图像的形状特征–链码,以及通过python和opencv实现的链码提取方法。所有opencv的版本为3.4.2,已经移除了直接返回链码的选项。 链码 链码用于描述图像的形状特征,首先需要获得图像…

Java知识扫盲——向上转型(类向上转型、接口向上转型)以及向上转型的优势、灵活运用

目录 普通类示例 抽象类的向上转型, 接口向上转型: 作用:使用向上转型可以,提高代码的简洁性、灵活性、适用性。 普通类示例 父类: package 普通类向上转型;public class Father {public int age 48;public void…

java 向上转换_java的向上转型总结

在《think of java》中对向上转型有着如下的描述 看完之后很蒙圈,所以自己在网上找了一些描述,并自己做了简单的总结 简单的例子 class A{ public void A1(){ System.out.println("父类方法1"); } public void A2(){ System.out.println("…

JAVA中多态以及向上转型向下转型、重写的讲解

重写 重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 注意!!!返回值和形参都不能改变。 重写的好处在于子类可以根据需要&#x…

JAVA中的向上转型和向下转型

一、向上转型和向下转型 向上转型和向下转型是在JAVA继承操作中用到的东西,在讲到转型之前我们需要认识到继承过程中的对象类型转换,这种转换有两个特点: 1.这是继承过程中发生的操作 2.新类是现有类的一种类型。这种说法主要是因为现有类的方…

java转型 内存_Java向上转型及内存分析

学习设计模式的时候,发现很多模式都用到了向上转型(eg. 工厂方法)。而我对向上转型(upcasting)的机制并不十分熟悉。这篇文章将深入分析向上转型的机制、内存分析。 概念 先从几个基本概念开始: 1. Java中的引用类型(reference type) Java中的数据类型分…

java向上转型_Java向上转型和向下转型

一、向上转型 简单的说向.上转型就是:将-一个子类类型的对象赋值给- -个父类类型的变量, 基本的实现语法: 父类类型变量new 子 类类型(); DEMO:向上转型 发现了可以使用父类类型的变量调用子类覆写了父类的方法,但是为什么不调用Worker的say()方法呢?原…

C++ 向上转型

在 C 中经常会发生数据类型的转换,例如将 int 类型的数据赋值给 float 类型的变量时,编译器会先把 int 类型的数据转换为 float 类型再赋值;反过来,float 类型的数据在经过类型转换后也可以赋值给 int 类型的变量。 数据类型转换…

java 对象向上转型_JAVA对象向上转型和向下转型

今天做了一个测试的题目,发现自己还是很多问题没有静下心来做。很多问题是可以自己解决的但是自己一是没有读清题意,二是自己心里太急躁了。所以这个要自己应以为鉴! 对象的转型问题其实并不复杂,我们记住一句话:“父类…

java 向上转型_Java 转型问题(向上转型和向下转型)

Java 转型问题其实并不复杂,只要记住一句话:父类引用指向子类对象。 什么叫父类引用指向子类对象? 从 2 个名词开始说起:向上转型(upcasting) 、向下转型(downcasting)。 举个例子:有2个类,Father 是父类&a…

java向上转型_Java向上转型

向上类型转换: 1.可以通过父类引用变量调用的方法是子类覆盖或继承父类的方法。 2.父类引用变量无法调用子类新增成员变量和新增成员方法。 举个例子 public classAnimal { public voidsleep() { System.out.println("Animal sleep"); } public voidjump(…

向上转型与向下转型(超详细)

本文利用代码例子解释向上转型与向下转型,文末有举例整合原代码。 首先,我们要知道:转型发生在继承后,也就是父类子类存在的前提下。其次,我们要清楚:向下转型的前提是已经发生了向上转型,向下转…

java向上转型

我们知道按照现有类的类型来创建新类的方式为继承。 换句话说 “新类是现有类的一种类型” 因为继承可以确保父类中所有的方法在子类中也同样有效,所以能够向父类发送的所有信息也同样可以向子类发送。 这儿有一个例子: public class Test {public sta…

类中的向上转型与向下转型详解

我们的类与类之间会存在继承关系,子类继承父类,一个父类可以有多个子类,例如Animal类就可以有Cat子类,Dog子类,等等。那么我们在运用的时候根据不同的场景会出现向上转型和向下转型的情况。 一、向上转型 1、Animal a…

【基础知识】向上转型和向下转型

向上转型和向下转型的利弊 一、向上转型 好处:隐藏了子类型,提高了代码的扩展性。 坏处:只能使用父类的功能,不能使用子类特有功能,功能被限定。 使用场景:不需要面对子类型,通过提高扩展性&am…

Java向上转型的作用(有详细代码介绍)

今天看到一道Java的练习题,题目如下 当时对于主函数中 Car car (Car) new Benz(); 不是很理解,也并不知道有什么意义,因为如果仅仅写Car car new Benz(); 程序运行结果是一样的。 在经过阅读书籍和查看别的博主写的关于向上向下转型的…