如何编写智能合约——基于长安链的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 小时前
区块链技术在生产数据管理中的应用:Hyperledger Fabric与蚂蚁链智能合约设计
区块链·智能合约·fabric
野老杂谈3 天前
如何快速学习智能合约开发语言 Solidity
开发语言·学习·智能合约·solidity·以太坊·区块链开发
老程序员刘飞4 天前
hardhat 搭建智能合约
开发语言·php·智能合约
许强0xq10 天前
Gas优化大师目录
web3·区块链·智能合约·solidity·foundry·ethernaut·gas优化
Joy T13 天前
Solidity智能合约开发入门攻略
web3·区块链·智能合约·solidity·以太坊·共识算法
Joy T13 天前
Solidity智能合约存储与数据结构精要
数据结构·区块链·密码学·智能合约·solidity·合约function
友莘居士16 天前
Java基于Web3j调用智能智能合约案例
java·web3·智能合约
安当加密18 天前
智能合约在分布式密钥管理系统中的应用
分布式·智能合约
RainWeb325 天前
Hardhat3-node-npm-基础版安装-V1
程序员·智能合约
天涯学馆1 个月前
Solidity多重签名合约:打造超安全的区块链投票机制
智能合约·solidity