区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码
  zqXXM0K5gNRE 2023年11月02日 30 0


导读



超级账本,作为最火热的联盟链技术,得到了广泛地使用,也得到了很多科研人员的青睐。


今天这篇文章,我们将应用《实践论》中的认识论,一起来看一下链码的结构,了解一下链码的具体内容。


让我们一起走进文章,来感受Fabric并深入理解联盟链吧!



1、说点题外话



我们学习一个东西,并不是说我们学了就会了。这是因为,我们学习了,只是听别人讲了,或者看别人写了。但是我们不一定会对别人讲的有很深的认识,除非我们真的自己真的对它有深刻的认识。


但是当我们接触一个新的事物的时候,我们是没有这样的认识的,那我们的认识是如何产生的呢?我们如何通过实践得到正确的认识呢?


毛泽东同志在《实践论》中讲到:


原来人在实践过程中,开始只是看到过程中各个事物的现象方面,看到各个事物的片面,看到各个事物之间的外部联系。……这些就是事物的现象,事物的各个片面以及这些事物的外部联系。这叫做认识的感性阶段,就是感觉和印象的阶段。……这是认识的第一个阶段。在这个阶段中,人们还不能造成深刻的概念,作出合乎论理(即合乎逻辑)的结论。社会实践的继续,使人们在实践中引起感觉和印象的东西反复了多次,于是在人们的脑子里生起了一个认识过程中的突变(即飞跃),产生了概念。概念这种东西已经不是事物的现象,不是事物的各个片面,不是它们的外部联系,而是抓着了事物的本质,事物的全体,事物的内部联系了。概念同感觉,不但是数量上的差别,而且有了性质上的差别。循此继进,使用判断和推理的方法,就可产生出合乎论理的结论来。……这是认识的第二个阶段。……这个概念、判断和推理的阶段,在人们对于一个事物的整个认识过程中是更重要的阶段,也就是理性认识的阶段。认识的真正任务在于经过感觉而到达于思维,到达于逐步了解客观事物的内部矛盾,了解它的规律性,了解这一过程和那一过程间的内部联系,即到达于论理的认识。

这就是告诉了我们,通过反复不断地认识,去把握事物最本质的东西,才能真正认识这个事物!


简单点说,我们最开始接触一个新领域是特别懵的,想想我们第一次接触编程,我们觉得编程是一件很复杂的事情,我们要了解编译器,了解他们的使用,了解语言的语法,我们只有通过不断地练习,在这个反复的过程中,实现对于知识的深入认识。再比如第一次接触一些高难度算法。我们几乎没有办法第一次学习就深入了解算法的思想,算法的灵活应用,算法的各种变形。但是当我们反复应用算法去解决具体的问题,去不断地将算法的思想和社会实践相结合的时候,我们会发现,我们对于算法的认识,对于将社会实践建模为一个算法的理解更深刻了。


当我们通过不断实践对于某个算法和编程语言有深入认识的时候,我们就可以应用他们去解决某个实际的问题,这个时候,我们会有一个通透的感觉,我们在脑中会对某个知识有全面深刻地了解,我们能应用不同的方式去阐述他。这就是说明了毛泽东同志所讲的:


我们的实践证明:感觉到了的东西,我们不能立刻理解它,只有理解了的东西才更深刻地感觉它。感觉只解决现象问题,理论才解决本质问题。这些问题的解决,一点也不能离开实践。无论何人要认识什么事物,除了同那个事物接触,即生活于(实践于)那个事物的环境中,是没有法子解决的。


因此,我们说毛泽东同志的思想是正确的,不是歌功颂德的说法,而是在我们的实践中,我们发现了他的思想,给我们指明了做事情的方法,做事情的原则,做事情的逻辑,我们能够应用这个思想少走很多弯路。


《毛泽东选集》有一个非常重要的特点,就是平民化的语言。一个只有小学水平的人,也能够看懂这些通俗易懂的语言,并且,文章中还结合实际情况,举了生动形象地例子,告诉我们思想如何去应用。我们反对教条的看待毛泽东思想,反对的方法是用具体的例子去阐述毛泽东思想为什么是正确的!而不是一直强调,不要教条,要灵活应用。这些没用的废话是谁都会说的,没用的“大道理”是有点脑子都明白的!



2、预备工作


1、Go语言开发基础


今天要深入到链码中了,需要大家对于链码中的一些语法有一定的了解。


因为我们今天是用Go为例来讲解,所以需要大家掌握一定的Go语法基础。当然,如果大家对其他的语言更熟悉(特别是Java),大家也可以去学习java的链码开发。后续根据实际情况我也会更新Java的链码开发,但是现在时间有限,就只能先更新Go的啦!


当然,如果大家没有学过Go语言也不要紧。我们如何应用《实践论》中的认识论去学习一个新的内容呢?一方面,我们要基于已有的知识,去举一反三,然后在学习的过程中深入理解;另一方面我们需要通过不断地反复学习实践,去强化我们的理解。


如果大家有C语言、C++、Java的基础,再去看Go,那就是小意思了。我们可以简单理解,Go就是C/C++/Java的简化版,很多地方的风格跟这三个语言是非常类似的。如果对C/C++或者Java比较熟悉的话,大家用不到半个小时时间,就可以将Go的基本语法掌握。


主要去看:


输入输出
变量与常量
运算符
条件语句与循环语句
函数
数组
指针
结构体
接口
错误处理


不用担心内容多,以大学C/C++为例,除了接口和错误处理外,都是基础内容,接口和错误处理也没有很难。


也不用担心Go本身的语法特点一下子学不会,我们不是为了考试,不是为了得分,我们的目的是为了学懂知识,能够把知识应用于实践。不熟练,多通过具体的项目进行实践就好啦!


也不用担心代码看不懂,只要了解大概,我们知道代码不懂的时候,上网怎么找能让自己会就好啦!


2、环境测试


在今天的内容之前,要保证我们的区块链环境和网络环境没有问题。前面的几篇文章,已经针对不同的网络问题进行了讲解,当然,我没有办法预见所有的问题,遇到了问题,我们就根据具体的情况,具体分析,按照毛泽东同志告诉我们的方法去解决。


测试完成后,确保都没问题后,我们就可以学习今天的内容啦!


2

认识链码



1、简述


前面我们已经了解链码的部署以及操作。那么,链码是如何写出来的呢?


链码可以由Go、Java、JS等多种语言编写,写好后的链码就可以进行部署和操作了,今天的内容,我们围绕Go语言的链码进行讲解。


2、全部代码


首先我们先给出链码的全部代码,后面我们再一步一步分析:


package main

import (
"fmt"

"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-protos-go/peer"
)

type SimpleAsset struct {
}

func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
args := stub.GetStringArgs() // Get the args from the transaction proposal
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}

func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fn, args := stub.GetFunctionAndParameters() // Extract the function and args from the transaction proposal

var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}

return shim.Success([]byte(result)) // Return the result as success payload
}

func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}

err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}

func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}

value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}

func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}



3、结构详解


接下来,我们详细讲解一下链码的结构。


1、Go基本结构


首先是Go语言的基本结构,也就是有包和主函数:


package main //声明一个包

import ( //引入的额外包

)

func main() { //主函数

}


第一行声明了包是main,这个是最最基本的,程序执行就是从main这个包开始。


import是引入了其他的包,每个引入的包都需要用双引号。这些包用于辅助我们实现一些功能,比如“fmt”用于实现输入输出等功能。如果只有一个包,括号可以省略掉,但是如果有多个,就要分行写。fmt可以说是最基本的包了,示例代码中还有两个包,是用于区块链项目的包shim和peer:


"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-protos-go/peer"


其中shim包是用于函数返回值的。shim包的几个常用方法及其功能如下:


Success                  #向客户端返回正确消息
Error #向客户端返回错误消息
Start #启动chaincode
LogLevel #将字符串类型转化成LoggingLevel类型
SetLoggingLevel #设置链码shim包运行日志等级
IsEnabledForLogLevel #检查合约的是否使能制定的等级


peer我们比较熟悉了,我们很多操作都是以对等节点执行的,peer就是提供了对等节点的一些必要操作。


第三部分就是一个主函数,我们的绝大多数功能,都是要写在函数里面的,然后通过函数之间的相互调用实现各种需求。


对于基本结构,我们可以举一个例子,这个例子也通常是用第一个入门Go语言的例子:


package main

import "fmt"

func main() {
fmt.Println("Hello, AI与区块链技术!")
}


有了基本结构,我们就可以继续后面的内容啦。


2、结构体与接口


Go语言的结构体定义如下:


type [结构体名称] struct {
[成员变量名1] [成员变量类型1]
[成员变量名2] [成员变量类型2]
……
[成员变量名n] [成员变量类型n]
}


例如:


// 入门
package main

import (
"fmt"
)

// struct
type Leader struct {
name string
birth string //XXXX-XX-XX
death string //XXXX-XX-XX
book string
age int
}

func main() {
fmt.Println("Hello, AI与区块链技术!")

//struct
var MZD Leader

MZD.name = "毛泽东"
MZD.birth = "1893-12-26"
MZD.death = "1976-09-09"
MZD.book = "毛泽东选集"
MZD.age = 83

fmt.Printf("%s同志(%s -- %s)是伟大的马克思主义者,是无产阶级的领袖。\n他的著作《%s》引领着有志青年们实现伟大的共产主义理想。\n在%d年的时光里,他不断探索中国的救亡之路,并在实践的过程中认识了马克思主义,成为了坚定地马克思主义者。\n他带领中国人民建立了新中国。他出生的时候,这个国家是列强们争相欺凌的对象,是暗无天日的旧社会;\n他离开的时候,这个国家已经是一个人民当家作主,拥有两弹一星,工业门类齐全的独立自主的共和国,成为了维护世界和平的重要力量!\n", MZD.name, MZD.birth, MZD.death, MZD.book, MZD.age)

}


执行结果如下:


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_区块链


在我们的区块链代码中,结构体没有自己的成员变量,因此,大括号中是空的。此外,我们应用Go语言中的接口(可以简单理解为C++中的类函数)实现了两个非常重要的方法Init和Invoke:


type SimpleAsset struct {
}

func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

}

func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

}


接下来我们来看一下每个方法的细节:


首先是Init方法,这个是链码的初始化方法,在这个方法里,我们应用shim包中的方法实现了错误的判断,如果没有错误,就返回一个成功的空值:


func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
args := stub.GetStringArgs() // Get the args from the transaction proposal
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}


除了初始化以外,链码升级的时候,也会调用这个方法,Init可以帮我们做一些初步的错误检测,保证后面的正确运行。


在这个示例中, 一共做了两个错误检测,第一个是参数的检测,看下参数是否满足我们的要求(一个键值对,根据不同的项目,可以做不同的要求,以及不同的错误反馈)。


args := stub.GetStringArgs() // Get the args from the transaction proposal
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}


如果没有问题,我们将做第二个检测,是否能够正确将参数转换为状态存在账本中,如果无法正确转换,得到的是一个空的对象,就会返回错误。其中nil是go语言自带的,用来表示空指针(C++中用NULL表示)。


err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}


如果上面检测都没问题,就返回成功。


return shim.Success(nil)


第二个接口是Invoke方法:


func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fn, args := stub.GetFunctionAndParameters() // Extract the function and args from the transaction proposal

var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}

return shim.Success([]byte(result)) // Return the result as success payload
}


我们之前的教程中,经常会用到Invoke方法,Invoke会被链码上的每个方法调用,在我们的这个Invoke方法中,我们要先获取要执行的函数和参数:


fn, args := stub.GetFunctionAndParameters() // Extract the function and args from the transaction proposal


函数有两个选择项get和set。这两个函数的具体实现在后面。不管是哪个函数,都会返回两个值,一个结果result和一个指针err:


var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}


err会返回是否有错误,如果没有就是空,如果有,就不是空,我们就可以调用指针的方法Error()返回具体的错误。这些都会在后面的get和set函数具体实现。


if err != nil {
return shim.Error(err.Error())
}


如果这里没有返回,就说明程序没有问题,我们就将结果作为成功负载返回即可:


return shim.Success([]byte(result))


3、get与set函数


了解了前面的内容,接下来我们看一下get和set函数。


get和set函数用于我们前面接口返回结果和错误。


在get函数中,我们获取一个参数,即密钥,如果参数个数有问题,就会报错:


if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}


如果没有问题,就可以从参数对应的状态中获取值和错误情况:


value, err := stub.GetState(args[0])


然后对获取到的值和错误情况分别判断并返回:


if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil


在set函数中,我们需要获取参数并生成状态,然后根据情况做具体的返回,这个过程和初始化过程是类似的:


func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}

err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}


4、主函数


最后就是主函数啦!这里的主函数比较简单


func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}


3

后续操作



了解了链码细节,我想大家也能够大概了解链码的组成部分了,接下来,我们了解一下后续的一些操作:


打包智能合约
安装链码包
批准链码定义
提交链码定义到通道


1、后续基本操作


还记得我们之前学习链码的时候,我们将链码部署有四个重要的操作:


打包智能合约
安装链码包
批准链码定义
提交链码定义到通道



这些操作就是写好链码的基本操作了。


具体的操作过程,大家可以看:



区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_成员变量_02

​区块链 | Hyperledger Fabric 04 超详细图解——智能合约的部署、调用与升级​


为了更好地搞懂这个过程发生了什么,我们需要对项目下的文件做一些更深入地分析。


2、样例项目下的其他文件


首先我们来看样例下的其他文件。我们会发现,除了链码本身的go文件,还有两个名字为go的文件:


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_成员变量_03


其中go.mod文件,是依赖项文件,我们可以使用如下命令查看:


cat go.mod


更详细地说,为了确保一致性构建,Go引入了go.mod文件来标记每个依赖包的版本,在构建过程中go命令会下载go.mod中的依赖包,下载的依赖包会缓存在本地,以便下次构建。


然而,下载的依赖包有可能被篡改,因此,一个单独的go.mod文件有时候不能确保一致性构建,为了解决这个问题,又引入了go.sum文件,来记录每个依赖包的hash值,如果发现hash值不一致就不会构建。我们可以用文本编辑器打开:


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_成员变量_04


更详细的内容,大家可以去官网或者其他文章中去了解,我们这里就不过多赘述啦!


3、管理扩展依赖



我们前面的两个文件,就是用来存依赖项的,为此,我们需要将依赖包进行安装并管理,我们用到如下两个命令:


go mod tidy
go mod vendor


执行结果如下:


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_区块链_05


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_区块链_06


为了统一,第二个命令,我们采取了我们之前的教程中的内容。


执行完成后,在go文件夹中会发现一个新的vendor文件夹:


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_区块链_07


在这个文件夹里,是链码的扩展依赖。


区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_Go_08


做完了这些,后面我们就可以按照我们之前的教程,将一个链码打包、安装并部署啦!


4、回顾一下整个流程


到这里,基本的Fabric教程就差不多啦!


所谓的基本的教程,就是说最基本的区块链的流程,我们已经都讲到了,包括:


如何打开和关闭一个区块链网络?
链码的一个基本格式
有了链码之后,如何将链码进行打包部署?
打包部署之后,如何调用链码实现具体的应用?


有了这些当然是不够的,我们还要结合具体的场景,实现具体的功能,那就还需要用到更多的知识,比如当有新的组织,新的节点加入怎么办?不同的链码如何开发?如何搭建自己的网络?如何设置自己的共识机制?


这就需要我们后续不断学习,不断提升啦!




4

说在后面的话



这里的链码只是帮助大家了解链码的基本结构,真正了解链码,需要大家结合不同的场景和需求进行实践!需要用毛泽东思想中的认识的过程,通过反复地实践去强化自己的认识!



一起加油啦!




区块链 | Hyperledger Fabric 06 用《实践论》中的认识论,深入链码,认识链码_成员变量_09

长按二维码关注




【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
zqXXM0K5gNRE