长安链使用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文件(这是新增工程的默认存证模板)

Go 复制代码
package main

import (
	"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 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)

}

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



相关推荐
The_Ticker6 小时前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
程序猿阿伟6 小时前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
TechubNews6 小时前
Helius:从数据出发,衡量 Solana 的真实去中心化程度
去中心化·区块链
dingzd958 小时前
Web3的核心技术:区块链如何确保信息安全与共享
web3·去中心化·区块链
清 晨8 小时前
Web3与智能合约:区块链技术下的数字信任体系
web3·区块链·智能合约
CertiK9 小时前
Web3.0安全开发实践:Clarity最佳实践总结
web3·区块链·clarity
加密新世界9 小时前
Move on Sui入门 004-在sui链上发布Coin合约和Faucet Coin合约
区块链
YSGZJJ13 小时前
股指期货的套保策略如何精准选择和规避风险?
人工智能·区块链
web3探路者1 天前
深入探索Solana链上的Meme生态:创新、潜力与挑战#区块链开发#dapp开发
web3·区块链·团队开发·dapp开发·区块链技术·链游开发·交易所开发
加密新世界1 天前
指南: 如何在 MEV 项目中使用 Yul
区块链