做简单视频网站自己看,站长工具seo综合查询方法,网站排版策划,专业团队为您服务的句子长安链2.3.0的go合约虚拟机和2.3.0以下的不兼容#xff0c;编译的方式也有差异#xff0c;所以在ide上做了区分。 教程三会写一些#xff0c;其他比较常用SDK方法的解释和使用方法
教程一#xff1a;#xff08;长安链2.1.的版本的智能合约#xff09;
教程三#xff…长安链2.3.0的go合约虚拟机和2.3.0以下的不兼容编译的方式也有差异所以在ide上做了区分。 教程三会写一些其他比较常用SDK方法的解释和使用方法
教程一长安链2.1.的版本的智能合约
教程三常见GO SDK的解释与使用 编写前的注意事项
1、运行一条带有Doker_GoVM的链
2、建议直接用官方的在线IDE去写合约因为写完可以直接测缺点只是调试不方便。
3、如果自己拉环境在本地写合约编译时注意编译环境官方有提醒你去Linux下去编译。
4、如果你的链是2.3.使用编译器前请先切换到2.3.以防不测 1、首先去新建一个合约工程
这里选择空白模板其他模板好像是一些web3的规范模板 2、打开main.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 mainimport (encoding/jsonfmtlogstrconvchainmaker/pb/protogochainmaker/sandboxchainmaker/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 intsdk.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 Factif 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)}
}
3、示例模板解析
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)) } 取证方法
以下大部分依葫芦画瓢就好了重点关注以下内容
134行取证的方法sdk.Instance.GetStateByte 这里的“fact_bytes”就是这个合约的域所以这里填写的要和你在存证中填写的域一致才行。其他按照规范以葫芦画瓢 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) } } 4、新增一个查询历史数据的方法
以上就是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 然后进行序列化返回 5、新增一个删除方法
注意这里虽然是一个删除方法但是不是真的删除只有有一个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)
}
6、完整合约
/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.SPDX-License-Identifier: Apache-2.0
*/package mainimport (chainmaker/pb/protogochainmaker/sandboxchainmaker/sdkencoding/jsonfmtlogstrconv
)type FactContract struct {
}// 存证对象
type Fact struct {FileHash stringFileName stringTime 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 intsdk.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 Factif 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)}
}7、结果展示
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都是一样的因为是我存证的数据都是输入的一样的
转码结果如下 8、个人理解
官方文档有错官方文档把key叫做命名空间取了一个固定值field作为存证hash这里应该是他们写反了field 才有域、空间的意思。上面的解析我还是按照官方错误的来说的。因为他最后存在level_db中的key是拼接的所以写反写没事都一样。你可以改成对的。之所以引入一个命名空间的概念我猜测应该是为了在数据库中方便查看因为同一份id可能会被存多次加了一个空间会方便区分。不过官方也提供了没有命名空间的存储方法PutStateFromKey(key string, value string) error 就是教程一用的方法删除函数并不是真的删除会新上链一条数据标明某个域、某个key被删除了但是已经上链的数据不会变动。