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

长安链是分2.1.+和2.3.+两个版本,本节面说的是2.1.+的版本

需要2.3.+版本的合约,请看教程(二)!

教程(二)我会写如何查历史数据

教程二:(长安链2.3.+的版本的智能合约编写)

教程三:(常见GO SDK的解释与使用)


编写前的注意事项:

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)}
}



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/19054.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在WHM中如何调整max_post_size参数大小

今日我们在搭建新网站时需要调整一下PHP参数max_post_size 的大小,我们公司使用的Hostease的美国独立服务器产品默认5个IP地址,也购买了cPanel面板,因此联系Hostease的技术支持,寻求帮助了解到如何在WHM中调整PHP参数,…

反转!Greenplum 还在,快去 Fork 源码

↑ 关注“少安事务所”公众号,欢迎⭐收藏,不错过精彩内容~ 今早被一条消息刷爆群聊,看到知名开源数仓 Greenplum 的源码仓“删库跑路”了。 要知道 GP 新东家 Broadcom 前几日才刚刚免费开放了 VMware Workstation PRO 17 和 VMware Fusion P…

linux系统的逻辑卷管理及磁盘配额

目录 逻辑卷管理 磁盘配额 逻辑卷管理 lvm:logical volume manager 逻辑卷管理 linux系统下对硬盘分区的一种管理机制。 lvm机制特别适合于管理大存储设备,可以动态的对硬盘进行扩容。 逻辑上的磁盘,概念上的磁盘&a…

QTP——功能测试

一、前言(课设目的及内容) QTP是quicktest Professional的简称,是一种自动测试工具。使用QTP的目的是想用它来执行重复的手动测试,主要是用于回归测试和测试同一软件的新版本。因此你在测试前要考虑好如何对应用程序进行测试&…

RedissonClient的配置解析

RedissonClient 的默认配置旨在提供一种平衡性能和资源消耗的合理基础配置,适用于大多数应用场景。了解并适当调整这些默认值可以更好地满足特定应用需求。 默认配置解析 在默认情况下,Redisson 使用的连接池配置和连接管理参数如下: 连接池…

python基础-内置常量

文章目录 内置常量FalseTrueNoneNotImplementedEllipsis\_\_debug\_\_ 命令行界面 内置常量 在 Python 的内置命名空间中存在一些常量,它们具有特定的含义和用途。让我们一起来了解一下这些常量: False False 是 bool 类型的假值。它在逻辑运算中通常…

机器学习之二分类提升决策树(Two-class Boosted Decision Tree)

二分类提升决策树(Two-class Boosted Decision Tree)是一种常用的机器学习方法,主要用于分类任务。该方法结合了决策树模型和提升(boosting)算法的优点,通过多个弱分类器(通常是简单的决策树)来构建一个强分类器。下面是关于二分类提升决策树的主要概念和工作流程: 1…

五个超级好用的Prompt网站,让你的GPT效率碾压旁人!

五个超级好用的Prompt网站,让你的GPT效率碾压旁人! 1. 150 Best ChatGPT Prompts for All Kinds of Workflow 该网站包含了150个能够显著提升工作流程效率的ChatGPT Prompt。从制作引人入胜的内容到简化项目,这些提示应该有助于将 ChatGPT …

【Python】解决Python报错:IndentationError: expected an indented block

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

基于51单片机的酒精浓度检测仪的设计

一.硬件方案 硬件部分为利用MQ3气敏传感器测量空气中酒精浓度,并转换为电压信号,经A/D转换器转换成数字信号后传给单片机系统,由单片机及其相应外围电路进行信号的处理,显示酒精浓度值以及超阈值声光报警。电路主要由51单片机最小…

项目运行mysql语言

前置 注意vs中要引用mysql的类库。 MySqlCommand MySqlCommand类代表了要在MySQL数据库上执行的SQL语句或存储过程。提供了许多方法来执行不同类型的SQL命令,比如查询(SELECT)、插入(INSERT)、更新(UPDA…

【刷题(13)】二分查找

一、二分查找基础 &#xff08;1&#xff09;int mid ((right - left) >> 1) left; &#xff08;2&#xff09;lower_bound的底层实现 int lower_bound(vector<int>& nums, int x) {int left 0;int right nums.size() - 1;// 区间为 左闭右闭while (lef…

基于python实现生命游戏

文章目录 一、生命游戏是什么二、生命游戏规则解释1.相邻细胞2.细胞状态 三、代码实现1.邻居细胞2.更新状态 四、整体代码 一、生命游戏是什么 生命游戏&#xff08;Game of Life&#xff09;是由英国数学家约翰何顿康威在1970年发明的一种细胞自动机&#xff08;Cellular Aut…

基于iptables 实现 ip 黑名单、白名单

1. 创建端口集合、黑名单ip集合、白名单ip 集合 2. 首次访问非正确的端口&#xff0c;即认为是黑名单ip 3. 若是黑名单ip 且不是白名单ip drop 4. 通过本次请求 标记为白名单ip ## 设置黑名单 ip ipset create scanner-ip-set hash:ip## 设置白名单 ipset create white-ip-s…

(超详细)字符函数和字符串函数【上】

前言 C 语言中对字符和字符串的处理很是频繁&#xff0c;但是 C 语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符串 中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数 . 1.求字符串长度函数 strlen函数 我们要求一个字符串函数的长度…

Firefox国际版

Firefox国际版官方网址&#xff1a; Download the Firefox Browser in English (US) and more than 90 other languagesEveryone deserves access to the internet — your language should never be a barrier. That’s why — with the help of dedicated volunteers around…

C语言序列化和反序列化--TPL(一)

TPL TPL说明网站 C语言中高效的序列化 您可以使用tpl快速轻松地存储和重新加载C数据。Tpl是一个用于序列化C数据的库。数据以自然二进制形式存储。该API很小&#xff0c;并试图保持“不碍事”。Tpl可以序列化许多C数据类型&#xff0c;包括结构。Tpl与文件、内存缓冲区和文件…

使用cmd下载远程服务器的文件

直接上命令&#xff1a; scp root192.168.40.99:/home/nest/xc/…/img_return.png ./ 其中&#xff0c;root为username&#xff0c;后面为服务器地址&#xff0c;文件地址&#xff0c;./为下载到当前目录下

继承基础实战

文章目录 1.继承基础2.子类调用析构函数顺序3.继承中函数组合对象构造,析构函数调用4.重写父类同名的函数5.多重继承1.继承基础 2.子类调用析构函数顺序 3.继承中函数组合对象构造,析构函数调用 4.重写父类同名的函数 5.多重继承 #include <iostream> #include &l…

自学动态规划——爬楼梯(加强版)

爬楼梯&#xff08;加强版&#xff09; 57. 爬楼梯&#xff08;第八期模拟笔试&#xff09; (kamacoder.com) 虽然看起来和完全背包没有什么关系&#xff0c;实际上还是有背包的影子的。 首先&#xff0c;题目要求方法数量&#xff0c;那么就应该想到递推公式&#xff1a;dp…