如何编写智能合约——基于长安链的Go语言的合约开发
场景设计:文件存证系统
在数字化时代,文件存证和版本追踪变得越来越重要。设想一个场景:在一个法律事务管理系统中,用户需要提交和管理各种文件的版本记录,以确保每个文件在不同时间点的状态可以被准确追踪。文件可能经历多个版本,例如合同的修订、文件内容的更新等。为了确保文件的合法性和准确性,需要一个系统来记录每次修改,并能够查询和管理这些版本历史。
我们的智能合约将实现一个文件存证系统,该系统不仅允许存储和检索文件信息,还支持版本管理和历史记录查询。用户可以保存文件、查询特定版本的文件,并获取某类型文件的所有历史记录。
本合约场景主要包括以下几个步骤:
1. 文件存证:用户将文件的哈希值、类型、版本、文件名及时间等信息存储在区块链上,确保其合法性和完整性。
2. 文件查询:用户可以通过文件类型和版本号查询存证信息,验证文件是否已经存证。
3. 历史记录查询:用户可以查看某种文件类型下所有历史版本的存证信息。
合约编写过程:
1. 引入必要的包
要撰写智能合约,首先需要引入 Chainmaker 框架的相关依赖包,如 sandbox、sdk 和 protogo,这些包提供了与区块链交互的功能,并用于处理智能合约中的各种操作。
package main
import (
"chainmaker/pb/protogo"
"chainmaker/sandbox"
"chainmaker/sdk"
"encoding/json"
"fmt"
"log"
"strconv"
)
2. 定义智能合约结构体
定义 FactContract 作为合约的核心结构体。我们还定义了 Fact 结构体来存储文件的存证信息,包括证据类型、版本、文件哈希、文件名和时间。
type FactContract struct {
}
type Fact struct {
EvidenceType string
Version string
FileHash string
FileName string
Time int
}
// 新建存证对象
func NewFact(evidenceType string, version string, fileHash string, fileName string, time int) *Fact {
return &Fact{
EvidenceType: evidenceType,
Version: version,
FileHash: fileHash,
FileName: fileName,
Time: time,
}
}
3. 实现合约的初始化和升级方法
智能合约必须实现 InitContract() 和 UpgradeContract() 方法。
• InitContract() 用于合约的初始部署,成功后会返回一条确认消息。
• UpgradeContract() 用于合约的升级操作,确保升级后的合约能正确被执行。
func (f *FactContract) InitContract() protogo.Response {
return sdk.Success([]byte("Init contract success"))
}
func (f *FactContract) UpgradeContract() protogo.Response {
return sdk.Success([]byte("Upgrade contract success"))
}
4. 实现智能合约的调用方法
在 InvokeContract 方法中,根据不同的请求方法调用相应的功能模块。包括保存存证、查询存证、删除存证以及获取历史记录。
func (f *FactContract) InvokeContract(method string) protogo.Response {
switch method {
case "save":
return f.SaveEvidence()
case "find":
return f.FindEvidence()
case "getHistory":
return f.GetHistoryByEvidenceType()
default:
return sdk.Error("invalid method")
}
}
5. 文件存证功能
SaveEvidence 方法用于将文件的存证信息保存到区块链上,存储的字段包括文件类型、版本号、哈希值、文件名和时间。
func (f *FactContract) SaveEvidence() protogo.Response {
params := sdk.Instance.GetArgs()
evidenceType := string(params["evidence_type"])
version := string(params["version"])
fileHash := string(params["file_hash"])
fileName := string(params["file_name"])
timeStr := string(params["time"])
time, err := strconv.Atoi(timeStr)
if err != nil {
msg := "time is [" + timeStr + "] not int"
sdk.Instance.Errorf(msg)
return sdk.Error(msg)
}
fact := NewFact(evidenceType, version, fileHash, fileName, time)
factBytes, err := json.Marshal(fact)
if err != nil {
return sdk.Error(fmt.Sprintf("传过来的参数序列化失败, err: %s", err))
}
sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})
err = sdk.Instance.PutStateByte(fact.EvidenceType, fact.Version, factBytes)
if err != nil {
return sdk.Error("fail to save fact bytes")
}
createUser, _ := sdk.Instance.GetSenderRole()
sdk.Instance.Infof("[saveUser] create=" + createUser)
return sdk.Success([]byte(fact.FileName + fact.FileHash))
}
6. 文件查询功能
FindEvidence 方法根据文件类型和版本号查询指定文件的存证信息。
func (f *FactContract) FindEvidence() protogo.Response {
evidenceType := string(sdk.Instance.GetArgs()["evidence_type"])
version := string(sdk.Instance.GetArgs()["version"])
result, err := sdk.Instance.GetStateByte(evidenceType, version)
if err != nil {
return sdk.Error("failed to call get_state")
}
var fact Fact
if err = json.Unmarshal(result, &fact); err != nil {
return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
}
sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)
return sdk.Success(result)
}
7. 文件历史查询功能
GetHistoryByEvidenceType 方法用于查询某个文件类型下的所有历史版本信息。
func (f *FactContract) GetHistoryByEvidenceType() protogo.Response {
evidenceType := string(sdk.Instance.GetArgs()["evidence_type"])
iter, err := sdk.Instance.NewIteratorPrefixWithKey(evidenceType)
if err != nil {
return sdk.Error("failed to create iterator")
}
defer iter.Close()
var results []Data
for {
key, field, value, err := iter.Next()
if err != nil {
sdk.Instance.Infof("Error iterating: %v", err)
}
if key == "" {
break
}
results = append(results, Data{
Key: key,
Field: field,
Value: string(value),
})
}
jsonBytes, err := json.Marshal(results)
if err != nil {
return sdk.Error(fmt.Sprintf("Error marshaling results: %v", err))
}
return sdk.Success(jsonBytes)
}
8. 合约入口
最后,使用 main 方法作为合约的入口,启动合约。
func main() {
err := sandbox.Start(new(FactContract))
if err != nil {
log.Fatal(err)
}
}
9.完整代码
/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"chainmaker/pb/protogo"
"chainmaker/sandbox"
"chainmaker/sdk"
"encoding/json"
"fmt"
"log"
"strconv"
)
type FactContract struct {
}
// 存证对象
type Fact struct {
EvidenceType string
Version string
FileHash string
FileName string
Time int
}
// 新建存证对象
func NewFact(evidenceType string, version string, fileHash string, fileName string, time int) *Fact {
fact := &Fact{
EvidenceType: evidenceType,
Version: version,
FileHash: fileHash,
FileName: fileName,
Time: time,
}
return fact
}
func (f *FactContract) InitContract() protogo.Response {
return sdk.Success([]byte("Init contract success"))
}
func (f *FactContract) UpgradeContract() protogo.Response {
return sdk.Success([]byte("Upgrade contract success"))
}
func (f *FactContract) InvokeContract(method string) protogo.Response {
switch method {
case "save":
return f.SaveEvidence()
case "find":
return f.FindEvidence()
case "getHistory":
return f.GetHistoryByEvidenceType()
default:
return sdk.Error("invalid method")
}
}
func (f *FactContract) SaveEvidence() protogo.Response {
params := sdk.Instance.GetArgs()
// 获取参数
evidenceType := string(params["evidence_type"])
version := string(params["version"])
fileHash := string(params["file_hash"])
fileName := string(params["file_name"])
timeStr := string(params["time"])
time, err := strconv.Atoi(timeStr)
if err != nil {
msg := "time is [" + timeStr + "] not int"
sdk.Instance.Errorf(msg)
return sdk.Error(msg)
}
// 构建结构体
fact := NewFact(evidenceType, version, fileHash, fileName, time)
// 序列化
factBytes, err := json.Marshal(fact)
if err != nil {
return sdk.Error(fmt.Sprintf("传过来的参数序列化失败, err: %s", err))
}
// 发送事件
sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})
// 存储数据
err = sdk.Instance.PutStateByte(fact.EvidenceType, fact.Version, factBytes)
if err != nil {
return sdk.Error("fail to save fact bytes")
}
// 记录日志
// sdk.Instance.Infof("[save] fileHash=" + fact.FileHash)
// sdk.Instance.Infof("[save] fileName=" + fact.FileName)
createUser, _ := sdk.Instance.GetSenderRole()
sdk.Instance.Infof("[saveUser] create=" + createUser)
// 返回结果
return sdk.Success([]byte(fact.FileName + fact.FileHash))
}
func (f *FactContract) FindEvidence() protogo.Response {
// 获取参数
evidenceType := string(sdk.Instance.GetArgs()["evidence_type"])
version := string(sdk.Instance.GetArgs()["version"])
// 查询结果
result, err := sdk.Instance.GetStateByte(evidenceType, version)
if err != nil {
return sdk.Error("failed to call get_state")
}
// 反序列化
var fact Fact
if err = json.Unmarshal(result, &fact); err != nil {
return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
}
// 记录日志
sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)
// 返回结果
return sdk.Success(result)
}
// 定义数据结构
type Data struct {
Key string `json:"key"`
Field string `json:"field"`
Value string `json:"value"`
}
func (f *FactContract) GetHistoryByEvidenceType() protogo.Response {
// 获取参数
evidenceType := string(sdk.Instance.GetArgs()["evidence_type"])
// 查询结果
iter, err := sdk.Instance.NewIteratorPrefixWithKey(evidenceType)
if err != nil {
return sdk.Error("failed to delere get_state")
}
defer iter.Close()
var results []Data
// 遍历结果
for {
key, field, value, err := iter.Next()
if err != nil {
sdk.Instance.Infof("Error iterating: %v", err)
}
if key == "" {
break
}
// 将当前的 key, field, value 保存到结果数组
results = append(results, Data{
Key: key,
Field: field,
Value: string(value), // 将 byte[] 转换为 string
})
}
jsonBytes, err := json.Marshal(results)
if err != nil {
return sdk.Error(fmt.Sprintf("Error marshaling results: %v", err))
}
// 返回结果
return sdk.Success(jsonBytes)
}
func main() {
err := sandbox.Start(new(FactContract))
if err != nil {
log.Fatal(err)
}
}
10.部署测试
我们使用长安链的长安链IDE (chainmaker.org.cn)部署测试我们的代码。
我们将创建以下虚拟文件证据数据:
1. 文件1
• 证据类型: “contract”
• 版本: “v1.0”
• 文件哈希: “abc123”
• 文件名: “Contract_A.pdf”
• 时间: 1694668800 (对应的时间为2023-09-13 00:00:00)
2. 文件1的修订版
• 证据类型: “contract”
• 版本: “v1.1”
• 文件哈希: “abc124”
• 文件名: “Contract_A_Revision.pdf”
• 时间: 1695273600 (对应的时间为2023-09-22 00:00:00)
3. 文件2
• 证据类型: “report”
• 版本: “v1.0”
• 文件哈希: “def456”
• 文件名: “Report_B.docx”
• 时间: 1694860800 (对应的时间为2023-09-16 00:00:00)
演示步骤
1、调用Save方法对文件1进行存证
2、调用Save方法对文件1的修订版进行存证
3、调用Save方法对文件2进行存证
4、调用find方法查询文件1的存证信息
5、调用find方法查询文件2的存证信息
6、调用getHistory方法查询文件1的全流程历史的存证信息
结论
通过以上演示,我们展示了如何使用智能合约进行文件存证和版本追踪。通过保存、查询和获取历史记录的方法,用户可以有效地管理文件的各个版本,并确保文件信息的完整性和准确性。这些功能使得文件的存证和版本管理更加高效、透明和可追溯。