长安链使用Golang编写智能合约教程(一)

2024-05-26 14:44

本文主要是介绍长安链使用Golang编写智能合约教程(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

编写前的注意事项:

1、运行一条带有Doker_GoVM的链

2、建议直接用官方的在线IDE去写合约,因为写完可以直接测,缺点只是调试不方便。

3、自己拉环境在本地写合约,编译时注意编译环境,官方有提醒你去Linux下去编译。


本教程使用官方的在线IDE去写合约

教程是基于官方文档写的,只是会多写一些解析步骤


1、首先新建一个合约

2、打开main.go文件(这是新增工程的默认存证模板)

package mainimport ("encoding/json""log""strconv""chainmaker/pb/protogo""chainmaker/shim"
)//FactContract 合约对象
type FactContract struct {
}//Fact 存证对象,存证合约的数据内容
type Fact struct {FileHash string FileName string Time     int
}//NewFact 新建存证对象
func NewFact(fileHash, fileName string, time int) *Fact {return &Fact{FileHash: fileHash,FileName: fileName,Time:     time,}
}//InitContract 合约初始化方法
func (f *FactContract) InitContract(stub shim.CMStubInterface) protogo.Response {return shim.Success([]byte("Init Success"))
}// UpgradeContract 合约升级方法
func (f *FactContract) UpgradeContract(stub shim.CMStubInterface) protogo.Response {return shim.Success([]byte("Upgrade Success"))
}//InvokeContract 调用合约
func (f *FactContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {//获取调用合约哪个方法method := string(stub.GetArgs()["method"])// 这里必须写成 switch {case "a": ... [case "b": ...[...]] default:...} 形式// 而且case后面的内容必须是字符串,不能是常量// 这里必须写成 switch {case "a": ... [case "b": ...[...]] default:...} 形式// 而且case后面的内容必须是字符串,不能是常量// 这里必须写成 switch {case "a": ... [case "b": ...[...]] default:...} 形式// 而且case后面的内容必须是字符串,不能是常量// 如果 method == "save", 执行FactContract的save方法// 如果 method == "findByFileHash", 执行FactContract的findByFileHash方法// 如果没有对应的 case 语句,返回错误switch method {case "save":return f.Save(stub)case "findByFileHash":return f.FindByFileHash(stub)default:return shim.Error("invalid method")}
}//save 存证,把数据存储到链上
func (f *FactContract) Save(stub shim.CMStubInterface) protogo.Response {// 获取调用合约的全部参数params := stub.GetArgs()// 获取指定的参数fileHash := string(params["file_hash"])fileName := string(params["file_name"])timeStr := string(params["time"])if fileHash == "" || fileName == "" || timeStr == "" {//返回合约执行错误,以及错误信息return shim.Error("fileHash and fileName and time must not empty")}time, err := strconv.Atoi(timeStr)if err != nil {msg := "time is [" + timeStr + "] not int"// 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示stub.Log(msg + err.Error())//返回合约执行错误,以及错误信息return shim.Error(msg)}fact := NewFact(fileHash, fileName, time)// 序列化factBytes, err := json.Marshal(fact)if err != nil {msg := "marshal data fail"stub.Log(msg + err.Error())return shim.Error(msg)}//向链上发送事件,发送的事件会在控制台的事件中显示stub.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})key := getHashKey(fact.FileHash)//把数据存到链上err = stub.PutStateFromKeyByte(key, factBytes)if err != nil {msg := "fail to save fact"stub.Log(msg + err.Error())return shim.Error(msg)}//打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示stub.Log("[save] file hash:" + fact.FileHash)stub.Log("[save] file name:" + fact.FileName)// 返回执行成功return shim.Success([]byte(fact.FileName + fact.FileHash))}//findByFileHash 根据文件哈希从链上查找数据
func (f *FactContract) FindByFileHash(stub shim.CMStubInterface) protogo.Response {// 获取调用合约的全部参数params := stub.GetArgs()// 获取指定参数fileHash := string(params["file_hash"])// 查询结果key := getHashKey(fileHash)result, err := stub.GetStateFromKeyByte(key)if err != nil {msg := "failed to call get_state"// 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示stub.Log(msg + err.Error())//返回合约执行错误,以及错误信息return shim.Error(msg)}// 反序列化var fact Facterr = json.Unmarshal(result, &fact)if err != nil {msg := "unmarshal data fail"stub.Log(msg + err.Error())return shim.Error(msg)}// 记录日志stub.Log("[find_by_file_hash] file hash:" + fact.FileHash)stub.Log("[find_by_file_hash] file name:" + fact.FileName)// 返回执行成功return shim.Success(result)}func getHashKey(hash string) string {return "fact_hash" + hash
}func main() {//运行合约err := shim.Start(new(FactContract))if err != nil {log.Fatal(err)}
}

3、模板解析

17行:Fact结构体就是要存在区块链中的,根据你自己的需要去变更结构体的字段

//Fact 存证对象,存证合约的数据内容
type Fact struct {
    FileHash string 
    FileName string 
    Time     int
}

 24行:新建存证对象,根据Fact 结构体的变化而变化

//NewFact 新建存证对象

func NewFact(fileHash, fileName string, time int) *Fact {
    return &Fact{
        FileHash: fileHash,
        FileName: fileName,
        Time:     time,
    }
}

InitContract、UpgradeContract、InvokeContract  三个方法解析

  • InitContract、UpgradeContract:这是合约默认必须要有的方法,不要动。如果你在13行把对应合约对象的名称改了,对应你在方法名前的名称也要改成一致。
  • InvokeContract:这里是合约方法、根据你写了几个方法,依葫芦画瓢,继续补充就行。

//InitContract 合约初始化方法
func (f *FactContract) InitContract(stub shim.CMStubInterface) protogo.Response {
    return shim.Success([]byte("Init Success"))
}

// UpgradeContract 合约升级方法
func (f *FactContract) UpgradeContract(stub shim.CMStubInterface) protogo.Response {
    return shim.Success([]byte("Upgrade Success"))
}

//InvokeContract 调用合约
func (f *FactContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {

//获取调用合约哪个方法
    method := string(stub.GetArgs()["method"])
    switch method {
    case "save":
        return f.Save(stub)
    case "findByFileHash":
        return f.FindByFileHash(stub)
    default:
        return shim.Error("invalid method")
    }
}

存证方法

以下大部分依葫芦画瓢就好了,重点关注以下内容:

  • 105行:key := getHashKey(fact.FileHash)  这是模板自定义的方法,在字符串前拼接一段字符,不必理会;
  • 108行:err = stub.PutStateFromKeyByte(key, factBytes),这就是最重要的将数据上链的方法!   其中 key相当于id值,你之后查的话就要根据key去查。(别忘了模板中在105行给key前加了一段字符,你查的时候也要加上)      关于上链的方法官方还提供了其他几种。
  • stub.Log  :都是可以输出在控制台上的,方便调试排查错误,可写可不写;
  • 120行,返回值要遵从官方的这个格式。

//save 存证,把数据存储到链上
func (f *FactContract) Save(stub shim.CMStubInterface) protogo.Response {
    // 获取调用合约的全部参数
    params := stub.GetArgs()

    // 获取指定的参数
    fileHash := string(params["file_hash"])
    fileName := string(params["file_name"])
    timeStr := string(params["time"])

    if fileHash == "" || fileName == "" || timeStr == "" {
        //返回合约执行错误,以及错误信息
        return shim.Error("fileHash and fileName and time must not empty")
    }

    time, err := strconv.Atoi(timeStr)
    if err != nil {
        msg := "time is [" + timeStr + "] not int"
        // 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
        stub.Log(msg + err.Error())
        //返回合约执行错误,以及错误信息
        return shim.Error(msg)
    }

    fact := NewFact(fileHash, fileName, time)

    // 序列化
    factBytes, err := json.Marshal(fact)
    if err != nil {
        msg := "marshal data fail"
        stub.Log(msg + err.Error())
        return shim.Error(msg)
    }

    //向链上发送事件,发送的事件会在控制台的事件中显示
    stub.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})

    key := getHashKey(fact.FileHash)

    //把数据存到链上
    err = stub.PutStateFromKeyByte(key, factBytes)
    if err != nil {
        msg := "fail to save fact"
        stub.Log(msg + err.Error())
        return shim.Error(msg)
    }

    //打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
    stub.Log("[save] file hash:" + fact.FileHash)
    stub.Log("[save] file name:" + fact.FileName)

    // 返回执行成功
    return shim.Success([]byte(fact.FileName + fact.FileHash))

}

取证方法

以下大部分依葫芦画瓢就好了,重点关注以下内容:

  • 133行:这里就是去拼接了字符串
  • 134行:取证的方法:stub.GetStateFromKeyByte(key)  ,返回的result是byte[ ]类型
  • 145行:反序列化
  • 157行:返回值遵从官方规范。

//findByFileHash 根据文件哈希从链上查找数据
func (f *FactContract) FindByFileHash(stub shim.CMStubInterface) protogo.Response {
    // 获取调用合约的全部参数
    params := stub.GetArgs()

    // 获取指定参数
    fileHash := string(params["file_hash"])

    // 查询结果
    key := getHashKey(fileHash)
    result, err := stub.GetStateFromKeyByte(key)
    if err != nil {
        msg := "failed to call get_state"
        // 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
        stub.Log(msg + err.Error())
        //返回合约执行错误,以及错误信息
        return shim.Error(msg)
    }

    // 反序列化
    var fact Fact
    err = json.Unmarshal(result, &fact)
    if err != nil {
        msg := "unmarshal data fail"
        stub.Log(msg + err.Error())
        return shim.Error(msg)
    }

    // 记录日志
    stub.Log("[find_by_file_hash] file hash:" + fact.FileHash)
    stub.Log("[find_by_file_hash] file name:" + fact.FileName)

    // 返回执行成功
    return shim.Success(result)

}

其他

这就是前面说的拼接字符串的方法,他在hash前加了“fact_hash”

func getHashKey(hash string) string {
    return "fact_hash" + hash
}

2、代码入口包名必须为main  (注意事项在注释中)

// sdk代码中,有且仅有一个main()方法
func main() {  // main()方法中,下面的代码为必须代码,不建议修改main()方法当中的代码// 其中,FactContract为用户实现合约的具体名称err := sandbox.Start(new(FactContract))if err != nil {log.Fatal(err)}
}




4、在官方模板上新增一个查询历史记录的方法

注意:

1、之前的合约是根据Key ,去进行存证、取证的。这个key和数据库中的唯一主键不同,在区块链中,你可以通过key 去多次存证,比如说你可以存  {key:1 ,value:2},{key:1 ,value:3},{key:1 ,value:4}

2、假设你用存证方法,存储了1中说的三条数据,你调用取证方法,只会返回最近最新的一条数据{key:1 ,value:4},这是因为联盟链中 有一个状态数据库和一个历史数据库,取证方法调用的是GetStateFromKeyByte()方法,他只会去状态数据库中查。

3、如果你想要查询出当时存证时输入key=1的所有存证记录,那么就要调用NewHistoryKvIterForKey()方法,官方的存证合约并没有这个方法,现在进行一步步补充。

我去,长安链时是分2.1.+和2.3.+两个版本,上面说的是2.1.+

算了,不写了,我用的是2.3.+   ,抽时间写个2.3.+的教程

 

这篇关于长安链使用Golang编写智能合约教程(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1004758

相关文章

鸿蒙中@State的原理使用详解(HarmonyOS 5)

《鸿蒙中@State的原理使用详解(HarmonyOS5)》@State是HarmonyOSArkTS框架中用于管理组件状态的核心装饰器,其核心作用是实现数据驱动UI的响应式编程模式,本文给大家介绍... 目录一、@State在鸿蒙中是做什么的?二、@Spythontate的基本原理1. 依赖关系的收集2.

Python基础语法中defaultdict的使用小结

《Python基础语法中defaultdict的使用小结》Python的defaultdict是collections模块中提供的一种特殊的字典类型,它与普通的字典(dict)有着相似的功能,本文主要... 目录示例1示例2python的defaultdict是collections模块中提供的一种特殊的字

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法

《golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法》:本文主要介绍golang获取当前时间、时间戳和时间字符串及它们之间的相互转换,本文通过实例代码给大家介绍的非常详细,感兴趣... 目录1、获取当前时间2、获取当前时间戳3、获取当前时间的字符串格式4、它们之间的相互转化上篇文章给大家介

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

Python 安装和配置flask, flask_cors的图文教程

《Python安装和配置flask,flask_cors的图文教程》:本文主要介绍Python安装和配置flask,flask_cors的图文教程,本文通过图文并茂的形式给大家介绍的非常详细,... 目录一.python安装:二,配置环境变量,三:检查Python安装和环境变量,四:安装flask和flas

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA