赞
踩
教程三会写一些,其他比较常用SDK方法的解释和使用方法
1、运行一条带有Doker_GoVM的链
2、建议直接用官方的在线IDE去写合约,因为写完可以直接测,缺点只是调试不方便。
3、如果自己拉环境在本地写合约,编译时注意编译环境,官方有提醒你去Linux下去编译。
4、如果你的链是2.3.+,使用编译器前,请先切换到2.3.+,以防不测
这里选择空白模板,其他模板好像是一些web3的规范模板
- /*
- 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 (
- "encoding/json"
- "fmt"
- "log"
- "strconv"
-
- "chainmaker/pb/protogo"
- "chainmaker/sandbox"
- "chainmaker/sdk"
- )
-
- type FactContract struct {
- }
-
- // 存证对象
- type Fact struct {
- FileHash string
- FileName string
- Time int
- }
-
- // 新建存证对象
- func NewFact(fileHash string, fileName string, time int) *Fact {
- fact := &Fact{
- 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.Save()
- case "findByFileHash":
- return f.FindByFileHash()
- default:
- return sdk.Error("invalid method")
- }
- }
-
- func (f *FactContract) Save() protogo.Response {
- params := sdk.Instance.GetArgs()
-
- // 获取参数
- 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(fileHash, fileName, time)
-
- // 序列化
- factBytes, err := json.Marshal(fact)
- if err != nil {
- return sdk.Error(fmt.Sprintf("marshal fact failed, err: %s", err))
- }
- // 发送事件
- sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})
-
- // 存储数据
- err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, 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)
-
- // 返回结果
- return sdk.Success([]byte(fact.FileName + fact.FileHash))
-
- }
-
- func (f *FactContract) FindByFileHash() protogo.Response {
- // 获取参数
- fileHash := string(sdk.Instance.GetArgs()["file_hash"])
-
- // 查询结果
- result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
- 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)
- }
-
- func main() {
- err := sandbox.Start(new(FactContract))
- if err != nil {
- log.Fatal(err)
- }
- }
-
21行:这里的FactContract是合约名称,对应的要和125行main函数err := sandbox.Start(new(FactContract))一致
type FactContract struct {
}
25行:Fact结构体就是要存在区块链中的结构体,根据你自己的需要去变更结构体的字段
type Fact struct {
FileHash string
FileName string
Time int
}
32行:新建存证对象,根据前面25行Fact 结构体的变化而变化
func NewFact(fileHash string, fileName string, time int) *Fact {
fact := &Fact{
FileHash: fileHash,
FileName: fileName,
Time: time,
}
return fact
}
41-47行: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"))
}
49行:InvokeContract是调用合约的方法,根据你的合约种有多少方法,依葫芦画瓢在case ....return 继续补充就行
func (f *FactContract) InvokeContract(method string) protogo.Response {
switch method {
case "save":
return f.Save()
case "findByFileHash":
return f.FindByFileHash()
default:
return sdk.Error("invalid method")
}
}
60行: Save(存证方法)
以下大部分依葫芦画瓢就好了,重点关注以下内容:
66-72行:做了time字段的字符校验,确保是数字
84行:发送事件函数EmitEvent,第一个参数是合约事件主题,第二个参数是合约事件参数、(注意合约事件的数据,参数数量不可大于16
) 写了事件、订阅之后可以监听到事件状态
87行:存储数据函数sdk.Instance.PutStateByte,三个参数 key、field 、value ,(原本我以为弄个key-value的存储参数就行了,为什么官方要弄个field,我也不理解,但是官方有解释,不过用长安链就遵从他的规则吧) 这里就是说key是一个命名空间,相当于一个域,真正的key是一个拼接串,value是存证的内容。
93、94行:记录日志,可记可不记,写了的话,节点的日志记录会存下来
97行:返回要遵从官方规范
func (f *FactContract) Save() protogo.Response {
params := sdk.Instance.GetArgs()// 获取参数
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(fileHash, fileName, time)// 序列化
factBytes, err := json.Marshal(fact)
if err != nil {
return sdk.Error(fmt.Sprintf("marshal fact failed, err: %s", err))
}
// 发送事件
sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})// 存储数据
err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, 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)// 返回结果
return sdk.Success([]byte(fact.FileName + fact.FileHash))}
取证方法:
以下大部分依葫芦画瓢就好了,重点关注以下内容:
func (f *FactContract) FindByFileHash() protogo.Response {
// 获取参数
fileHash := string(sdk.Instance.GetArgs()["file_hash"])// 查询结果
result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
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)
}
125行:main函数,这个new合约对象的时候保证名称和最开始21行的结构体名称一样就行了,其他的不用变。
func main() {
err := sandbox.Start(new(FactContract))
if err != nil {
log.Fatal(err)
}
}
以上就是main.go模板内容的解析,但是一般情况下,我们还有查询历史数据的需求,模板没有提供,所以这里根据模板继续补充一个查询历史记录的方法:
- func (f *FactContract) GetHistoryByFileHash() protogo.Response {
- // 获取参数
- fileHash := string(sdk.Instance.GetArgs()["file_hash"])
-
- // 查询结果
- iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
- if err != nil {
- return sdk.Error("failed to delere get_state")
- }
- defer iter.Close()
- var keyModifications []*sdk.KeyModification
- // 遍历结果
- for {
- if !iter.HasNext() {
- break
- }
- keyModification, err := iter.Next()
- if err != nil {
- sdk.Instance.Infof("Error iterating: %v", err)
- }
- if keyModification == nil {
- break
- }
-
- keyModifications = append(keyModifications, keyModification)
-
- }
-
- jsonBytes, err := json.Marshal(keyModifications)
- if err != nil {
- return sdk.Error(fmt.Sprintf("Error marshaling keyModifications: %v", err))
- }
-
- // 返回结果
- return sdk.Success(jsonBytes)
- }
方法解析:
这里我只解释重点步骤
1、调用查询历史数据接口
iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
返回值类型:
后面就是根据返回值的结构进行遍历,
var keyModifications []*sdk.KeyModification
把结果放在 keyModifications 然后进行序列化,返回
注意,这里虽然是一个删除方法,但是不是真的删除,只有有一个isDelete的字段,如果调用了这个方法,某个域对应的hash会被标记为删除。代码不在解释
- func (f *FactContract) DeleteByFileHash() protogo.Response {
- // 获取参数
- fileHash := string(sdk.Instance.GetArgs()["file_hash"])
-
- // 查询结果
- err := sdk.Instance.DelState("fact_bytes", fileHash)
- if err != nil {
- return sdk.Error("failed to delere get_state")
- }
-
- // 返回结果
- return sdk.Success(nil)
- }
- /*
- 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 {
- FileHash string
- FileName string
- Time int
- }
-
- // 新建存证对象
- func NewFact(fileHash string, fileName string, time int) *Fact {
- fact := &Fact{
- 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.Save()
- case "findByFileHash":
- return f.FindByFileHash()
- case "deltedByFileHash":
- return f.DeleteByFileHash()
- case "getHistoryByFileHash":
- return f.GetHistoryByFileHash()
- default:
- return sdk.Error("invalid method")
- }
- }
-
- func (f *FactContract) Save() protogo.Response {
- params := sdk.Instance.GetArgs()
-
- // 获取参数
- 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(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_bytes", fact.FileHash, 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) FindByFileHash() protogo.Response {
- // 获取参数
- fileHash := string(sdk.Instance.GetArgs()["file_hash"])
-
- // 查询结果
- result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
- 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)
- }
-
- func (f *FactContract) DeleteByFileHash() protogo.Response {
- // 获取参数
- fileHash := string(sdk.Instance.GetArgs()["file_hash"])
-
- // 查询结果
- err := sdk.Instance.DelState("fact_bytes", fileHash)
- if err != nil {
- return sdk.Error("failed to delere get_state")
- }
-
- // 返回结果
- return sdk.Success(nil)
- }
-
- func (f *FactContract) GetHistoryByFileHash() protogo.Response {
- // 获取参数
- fileHash := string(sdk.Instance.GetArgs()["file_hash"])
-
- // 查询结果
- iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
- if err != nil {
- return sdk.Error("failed to delere get_state")
- }
- defer iter.Close()
- var keyModifications []*sdk.KeyModification
- // 遍历结果
- for {
- if !iter.HasNext() {
- break
- }
- keyModification, err := iter.Next()
- if err != nil {
- sdk.Instance.Infof("Error iterating: %v", err)
- }
- if keyModification == nil {
- break
- }
-
- keyModifications = append(keyModifications, keyModification)
-
- sdk.Instance.Infof("Key: %s, Field: %s, Value: %s, TxId: %s, BlockHeight: %d, IsDelete: %t, Timestamp: %s, \n",
- keyModification.Key, keyModification.Field, keyModification.Value, keyModification.TxId, keyModification.BlockHeight, keyModification.IsDelete, keyModification.Timestamp)
- }
-
- jsonBytes, err := json.Marshal(keyModifications)
- if err != nil {
- return sdk.Error(fmt.Sprintf("Error marshaling keyModifications: %v", err))
- }
-
- // 返回结果
- return sdk.Success(jsonBytes)
- }
-
- func main() {
- err := sandbox.Start(new(FactContract))
- if err != nil {
- log.Fatal(err)
- }
- }
1、部署demo2合约
2、发起上链,上链了3条数据,其中file_hash我都是输入的1
3、查询某一个结果
4、删除一个结果
这也是一个上链操作
5、查询历史结果
这里没有格式化,现在拿去专门json格式化一下
- [
- {
- "Key": "fact_bytes",
- "Field": "1",
- "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
- "TxId": "254071cabbca415186ae64956644d2230be111ebb94144d79f168a2252995a88",
- "BlockHeight": 14,
- "IsDelete": false,
- "Timestamp": "1716864398"
- },
- {
- "Key": "fact_bytes",
- "Field": "1",
- "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
- "TxId": "ce705bbbaedb4315858b4e68b6331f4a947c3eb5a262433dbe2823ad3c87ee06",
- "BlockHeight": 15,
- "IsDelete": false,
- "Timestamp": "1716864410"
- },
- {
- "Key": "fact_bytes",
- "Field": "1",
- "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
- "TxId": "6e099f7ce81543bf8fd0bf565f741478de53b4bc72834112a8bc0bc5f06f9a47",
- "BlockHeight": 16,
- "IsDelete": false,
- "Timestamp": "1716864421"
- },
- {
- "Key": "fact_bytes",
- "Field": "1",
- "Value": "",
- "TxId": "5f76c217063e41ad8c0f2b4ab3fae2418d784c9f0ade416b94715e95214acfc5",
- "BlockHeight": 17,
- "IsDelete": true,
- "Timestamp": "1716864504"
- }
- ]
value就是存证的字符串,但是这里是base64编码,转码结果如下
(注意:这里你会发现所有的value都是一样的,因为是我存证的数据都是输入的一样的)
转码结果如下:
PutStateFromKey(key string, value string) error 就是教程一用的方法
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。