如何编写智能合约——基于长安链的Go语言的合约开发

场景设计:文件存证系统

在数字化时代,文件存证和版本追踪变得越来越重要。设想一个场景:在一个法律事务管理系统中,用户需要提交和管理各种文件的版本记录,以确保每个文件在不同时间点的状态可以被准确追踪。文件可能经历多个版本,例如合同的修订、文件内容的更新等。为了确保文件的合法性和准确性,需要一个系统来记录每次修改,并能够查询和管理这些版本历史。

我们的智能合约将实现一个文件存证系统,该系统不仅允许存储和检索文件信息,还支持版本管理和历史记录查询。用户可以保存文件、查询特定版本的文件,并获取某类型文件的所有历史记录。

本合约场景主要包括以下几个步骤:

  1. 文件存证:用户将文件的哈希值、类型、版本、文件名及时间等信息存储在区块链上,确保其合法性和完整性。

  2. 文件查询:用户可以通过文件类型和版本号查询存证信息,验证文件是否已经存证。

  3. 历史记录查询:用户可以查看某种文件类型下所有历史版本的存证信息。

合约编写过程:

1. 引入必要的包

要撰写智能合约,首先需要引入 Chainmaker 框架的相关依赖包,如 sandbox、sdk 和 protogo,这些包提供了与区块链交互的功能,并用于处理智能合约中的各种操作。

Go 复制代码
package main

import (
	"chainmaker/pb/protogo"
	"chainmaker/sandbox"
	"chainmaker/sdk"
	"encoding/json"
	"fmt"
	"log"
	"strconv"
)

2. 定义智能合约结构体

定义 FactContract 作为合约的核心结构体。我们还定义了 Fact 结构体来存储文件的存证信息,包括证据类型、版本、文件哈希、文件名和时间。

Go 复制代码
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() 用于合约的升级操作,确保升级后的合约能正确被执行。

Go 复制代码
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 方法中,根据不同的请求方法调用相应的功能模块。包括保存存证、查询存证、删除存证以及获取历史记录。

Go 复制代码
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 方法用于将文件的存证信息保存到区块链上,存储的字段包括文件类型、版本号、哈希值、文件名和时间。

Go 复制代码
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 方法根据文件类型和版本号查询指定文件的存证信息。

Go 复制代码
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 方法用于查询某个文件类型下的所有历史版本信息。

Go 复制代码
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 方法作为合约的入口,启动合约。

Go 复制代码
func main() {
	err := sandbox.Start(new(FactContract))
	if err != nil {
		log.Fatal(err)
	}
}

9.完整代码

Go 复制代码
/*
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)

  1. 文件1的修订版

• 证据类型: "contract"

• 版本: "v1.1"

• 文件哈希: "abc124"

• 文件名: "Contract_A_Revision.pdf"

• 时间: 1695273600 (对应的时间为2023-09-22 00:00:00)

  1. 文件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的全流程历史的存证信息

结论

通过以上演示,我们展示了如何使用智能合约进行文件存证和版本追踪。通过保存、查询和获取历史记录的方法,用户可以有效地管理文件的各个版本,并确保文件信息的完整性和准确性。这些功能使得文件的存证和版本管理更加高效、透明和可追溯。

相关推荐
加密社9 小时前
【加密社】深入理解TON智能合约 (FunC语法)
智能合约
AiMateZero3 天前
【无标题】
rust·智能合约
花花花14 天前
如何使用智能合约铸造 NFT —— 以 NftMarket 合约为例
去中心化·区块链·智能合约·以太坊·nft·sepolia
dingzd954 天前
Web3入门指南:从基础概念到实际应用
人工智能·web3·去中心化·区块链·智能合约
加密社5 天前
Solana核心漏洞技术详解
区块链·智能合约
加密社5 天前
TON智能合约stdlib_ext库:扩展功能一览
智能合约
zhuqiyua7 天前
如何通俗易懂的解释TON的智能合约
智能合约
dingzd957 天前
从去中心化到智能化:Web3如何与AI共同塑造数字生态
ai·web3·去中心化·区块链·智能合约
清 晨8 天前
未来的去中心化网络:Web3与AI的深度融合探讨
ai·web3·去中心化·智能合约·隐私保护