当前位置: 首页 > article >正文

如何编写智能合约——基于长安链的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的全流程历史的存证信息

结论

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


http://www.kler.cn/a/308020.html

相关文章:

  • LeetCode【0035】搜索插入位置
  • redis bind 127.0.0.1和bind 10.34.56.78的区别
  • 车-路-站-网”信息耦合的汽车有序充电
  • ubuntu中apt-get的默认安装路径。安装、卸载以及查看的方法总结
  • 【软件工程】一篇入门UML建模图(类图)
  • jmeter常用配置元件介绍总结之后置处理器
  • 我想要抓取新加坡当地电商平台数据,使用什么地区的IP最合适
  • C++数据排序( 附源码 )
  • linux-硬件与设备管理-硬件信息查看
  • 计算机网络 第3章 数据链路层
  • JS - 获取剪切板内容 Clipboard API
  • 数据结构——栈和队列(队列的定义、顺序队列以及链式队列的基本操作)
  • opencv学习:图像直方图均衡化与对比度受限的自适应直方图均衡化及实验代码
  • 针对特定接口记录审核日志类的写入数据库的方法
  • raksmart的G口大流量服务器怎么样?
  • C++学习, 数据抽象
  • 物联网架构
  • 单片机带隙电压基准电路
  • C#环境搭建和入门教程--vs2022之下
  • 51单片机应用开发---数码管的控制应用
  • c++_list
  • 前端开发macbook——NVM环境配置以及git配置流程
  • 《论软件需求管理》写作框架,软考高级系统架构设计师
  • TCP/IP - TCP
  • MySQL5.7基于mysqldump、xtrbackup、innobackupex工具进行全量备份/恢复、增量备份/恢复
  • 【编程基础知识】Java处理JSON格式转换的常用第三方库