当前位置:   article > 正文

长安链使用Golang编写智能合约教程(二)

长安链使用Golang编写智能合约教程(二)

长安链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文件(有一份示例合约)

  1. /*
  2. Copyright (C) BABEC. All rights reserved.
  3. Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.
  4. SPDX-License-Identifier: Apache-2.0
  5. */
  6. package main
  7. import (
  8. "encoding/json"
  9. "fmt"
  10. "log"
  11. "strconv"
  12. "chainmaker/pb/protogo"
  13. "chainmaker/sandbox"
  14. "chainmaker/sdk"
  15. )
  16. type FactContract struct {
  17. }
  18. // 存证对象
  19. type Fact struct {
  20. FileHash string
  21. FileName string
  22. Time int
  23. }
  24. // 新建存证对象
  25. func NewFact(fileHash string, fileName string, time int) *Fact {
  26. fact := &Fact{
  27. FileHash: fileHash,
  28. FileName: fileName,
  29. Time: time,
  30. }
  31. return fact
  32. }
  33. func (f *FactContract) InitContract() protogo.Response {
  34. return sdk.Success([]byte("Init contract success"))
  35. }
  36. func (f *FactContract) UpgradeContract() protogo.Response {
  37. return sdk.Success([]byte("Upgrade contract success"))
  38. }
  39. func (f *FactContract) InvokeContract(method string) protogo.Response {
  40. switch method {
  41. case "save":
  42. return f.Save()
  43. case "findByFileHash":
  44. return f.FindByFileHash()
  45. default:
  46. return sdk.Error("invalid method")
  47. }
  48. }
  49. func (f *FactContract) Save() protogo.Response {
  50. params := sdk.Instance.GetArgs()
  51. // 获取参数
  52. fileHash := string(params["file_hash"])
  53. fileName := string(params["file_name"])
  54. timeStr := string(params["time"])
  55. time, err := strconv.Atoi(timeStr)
  56. if err != nil {
  57. msg := "time is [" + timeStr + "] not int"
  58. sdk.Instance.Errorf(msg)
  59. return sdk.Error(msg)
  60. }
  61. // 构建结构体
  62. fact := NewFact(fileHash, fileName, time)
  63. // 序列化
  64. factBytes, err := json.Marshal(fact)
  65. if err != nil {
  66. return sdk.Error(fmt.Sprintf("marshal fact failed, err: %s", err))
  67. }
  68. // 发送事件
  69. sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})
  70. // 存储数据
  71. err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, factBytes)
  72. if err != nil {
  73. return sdk.Error("fail to save fact bytes")
  74. }
  75. // 记录日志
  76. sdk.Instance.Infof("[save] fileHash=" + fact.FileHash)
  77. sdk.Instance.Infof("[save] fileName=" + fact.FileName)
  78. // 返回结果
  79. return sdk.Success([]byte(fact.FileName + fact.FileHash))
  80. }
  81. func (f *FactContract) FindByFileHash() protogo.Response {
  82. // 获取参数
  83. fileHash := string(sdk.Instance.GetArgs()["file_hash"])
  84. // 查询结果
  85. result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
  86. if err != nil {
  87. return sdk.Error("failed to call get_state")
  88. }
  89. // 反序列化
  90. var fact Fact
  91. if err = json.Unmarshal(result, &fact); err != nil {
  92. return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
  93. }
  94. // 记录日志
  95. sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
  96. sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)
  97. // 返回结果
  98. return sdk.Success(result)
  99. }
  100. func main() {
  101. err := sandbox.Start(new(FactContract))
  102. if err != nil {
  103. log.Fatal(err)
  104. }
  105. }

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模板内容的解析,但是一般情况下,我们还有查询历史数据的需求,模板没有提供,所以这里根据模板继续补充一个查询历史记录的方法:

  1. func (f *FactContract) GetHistoryByFileHash() protogo.Response {
  2. // 获取参数
  3. fileHash := string(sdk.Instance.GetArgs()["file_hash"])
  4. // 查询结果
  5. iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
  6. if err != nil {
  7. return sdk.Error("failed to delere get_state")
  8. }
  9. defer iter.Close()
  10. var keyModifications []*sdk.KeyModification
  11. // 遍历结果
  12. for {
  13. if !iter.HasNext() {
  14. break
  15. }
  16. keyModification, err := iter.Next()
  17. if err != nil {
  18. sdk.Instance.Infof("Error iterating: %v", err)
  19. }
  20. if keyModification == nil {
  21. break
  22. }
  23. keyModifications = append(keyModifications, keyModification)
  24. }
  25. jsonBytes, err := json.Marshal(keyModifications)
  26. if err != nil {
  27. return sdk.Error(fmt.Sprintf("Error marshaling keyModifications: %v", err))
  28. }
  29. // 返回结果
  30. return sdk.Success(jsonBytes)
  31. }

方法解析:

这里我只解释重点步骤

1、调用查询历史数据接口

iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)

返回值类型:

后面就是根据返回值的结构进行遍历,

var keyModifications []*sdk.KeyModification

把结果放在 keyModifications  然后进行序列化,返回

 5、新增一个删除方法

注意,这里虽然是一个删除方法,但是不是真的删除,只有有一个isDelete的字段,如果调用了这个方法,某个域对应的hash会被标记为删除。代码不在解释

  1. func (f *FactContract) DeleteByFileHash() protogo.Response {
  2. // 获取参数
  3. fileHash := string(sdk.Instance.GetArgs()["file_hash"])
  4. // 查询结果
  5. err := sdk.Instance.DelState("fact_bytes", fileHash)
  6. if err != nil {
  7. return sdk.Error("failed to delere get_state")
  8. }
  9. // 返回结果
  10. return sdk.Success(nil)
  11. }

6、完整合约

  1. /*
  2. Copyright (C) BABEC. All rights reserved.
  3. Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.
  4. SPDX-License-Identifier: Apache-2.0
  5. */
  6. package main
  7. import (
  8. "chainmaker/pb/protogo"
  9. "chainmaker/sandbox"
  10. "chainmaker/sdk"
  11. "encoding/json"
  12. "fmt"
  13. "log"
  14. "strconv"
  15. )
  16. type FactContract struct {
  17. }
  18. // 存证对象
  19. type Fact struct {
  20. FileHash string
  21. FileName string
  22. Time int
  23. }
  24. // 新建存证对象
  25. func NewFact(fileHash string, fileName string, time int) *Fact {
  26. fact := &Fact{
  27. FileHash: fileHash,
  28. FileName: fileName,
  29. Time: time,
  30. }
  31. return fact
  32. }
  33. func (f *FactContract) InitContract() protogo.Response {
  34. return sdk.Success([]byte("Init contract success"))
  35. }
  36. func (f *FactContract) UpgradeContract() protogo.Response {
  37. return sdk.Success([]byte("Upgrade contract success"))
  38. }
  39. func (f *FactContract) InvokeContract(method string) protogo.Response {
  40. switch method {
  41. case "save":
  42. return f.Save()
  43. case "findByFileHash":
  44. return f.FindByFileHash()
  45. case "deltedByFileHash":
  46. return f.DeleteByFileHash()
  47. case "getHistoryByFileHash":
  48. return f.GetHistoryByFileHash()
  49. default:
  50. return sdk.Error("invalid method")
  51. }
  52. }
  53. func (f *FactContract) Save() protogo.Response {
  54. params := sdk.Instance.GetArgs()
  55. // 获取参数
  56. fileHash := string(params["file_hash"])
  57. fileName := string(params["file_name"])
  58. timeStr := string(params["time"])
  59. time, err := strconv.Atoi(timeStr)
  60. if err != nil {
  61. msg := "time is [" + timeStr + "] not int"
  62. sdk.Instance.Errorf(msg)
  63. return sdk.Error(msg)
  64. }
  65. // 构建结构体
  66. fact := NewFact(fileHash, fileName, time)
  67. // 序列化
  68. factBytes, err := json.Marshal(fact)
  69. if err != nil {
  70. return sdk.Error(fmt.Sprintf("传过来的参数序列化失败, err: %s", err))
  71. }
  72. // 发送事件
  73. sdk.Instance.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})
  74. // 存储数据
  75. err = sdk.Instance.PutStateByte("fact_bytes", fact.FileHash, factBytes)
  76. if err != nil {
  77. return sdk.Error("fail to save fact bytes")
  78. }
  79. // 记录日志
  80. // sdk.Instance.Infof("[save] fileHash=" + fact.FileHash)
  81. // sdk.Instance.Infof("[save] fileName=" + fact.FileName)
  82. createUser, _ := sdk.Instance.GetSenderRole()
  83. sdk.Instance.Infof("[saveUser] create=" + createUser)
  84. // 返回结果
  85. return sdk.Success([]byte(fact.FileName + fact.FileHash))
  86. }
  87. func (f *FactContract) FindByFileHash() protogo.Response {
  88. // 获取参数
  89. fileHash := string(sdk.Instance.GetArgs()["file_hash"])
  90. // 查询结果
  91. result, err := sdk.Instance.GetStateByte("fact_bytes", fileHash)
  92. if err != nil {
  93. return sdk.Error("failed to call get_state")
  94. }
  95. // 反序列化
  96. var fact Fact
  97. if err = json.Unmarshal(result, &fact); err != nil {
  98. return sdk.Error(fmt.Sprintf("unmarshal fact failed, err: %s", err))
  99. }
  100. // 记录日志
  101. sdk.Instance.Infof("[find_by_file_hash] fileHash=" + fact.FileHash)
  102. sdk.Instance.Infof("[find_by_file_hash] fileName=" + fact.FileName)
  103. // 返回结果
  104. return sdk.Success(result)
  105. }
  106. func (f *FactContract) DeleteByFileHash() protogo.Response {
  107. // 获取参数
  108. fileHash := string(sdk.Instance.GetArgs()["file_hash"])
  109. // 查询结果
  110. err := sdk.Instance.DelState("fact_bytes", fileHash)
  111. if err != nil {
  112. return sdk.Error("failed to delere get_state")
  113. }
  114. // 返回结果
  115. return sdk.Success(nil)
  116. }
  117. func (f *FactContract) GetHistoryByFileHash() protogo.Response {
  118. // 获取参数
  119. fileHash := string(sdk.Instance.GetArgs()["file_hash"])
  120. // 查询结果
  121. iter, err := sdk.Instance.NewHistoryKvIterForKey("fact_bytes", fileHash)
  122. if err != nil {
  123. return sdk.Error("failed to delere get_state")
  124. }
  125. defer iter.Close()
  126. var keyModifications []*sdk.KeyModification
  127. // 遍历结果
  128. for {
  129. if !iter.HasNext() {
  130. break
  131. }
  132. keyModification, err := iter.Next()
  133. if err != nil {
  134. sdk.Instance.Infof("Error iterating: %v", err)
  135. }
  136. if keyModification == nil {
  137. break
  138. }
  139. keyModifications = append(keyModifications, keyModification)
  140. sdk.Instance.Infof("Key: %s, Field: %s, Value: %s, TxId: %s, BlockHeight: %d, IsDelete: %t, Timestamp: %s, \n",
  141. keyModification.Key, keyModification.Field, keyModification.Value, keyModification.TxId, keyModification.BlockHeight, keyModification.IsDelete, keyModification.Timestamp)
  142. }
  143. jsonBytes, err := json.Marshal(keyModifications)
  144. if err != nil {
  145. return sdk.Error(fmt.Sprintf("Error marshaling keyModifications: %v", err))
  146. }
  147. // 返回结果
  148. return sdk.Success(jsonBytes)
  149. }
  150. func main() {
  151. err := sandbox.Start(new(FactContract))
  152. if err != nil {
  153. log.Fatal(err)
  154. }
  155. }

7、结果展示

1、部署demo2合约

2、发起上链,上链了3条数据,其中file_hash我都是输入的1

3、查询某一个结果

4、删除一个结果

这也是一个上链操作

5、查询历史结果

这里没有格式化,现在拿去专门json格式化一下

  1. [
  2. {
  3. "Key": "fact_bytes",
  4. "Field": "1",
  5. "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
  6. "TxId": "254071cabbca415186ae64956644d2230be111ebb94144d79f168a2252995a88",
  7. "BlockHeight": 14,
  8. "IsDelete": false,
  9. "Timestamp": "1716864398"
  10. },
  11. {
  12. "Key": "fact_bytes",
  13. "Field": "1",
  14. "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
  15. "TxId": "ce705bbbaedb4315858b4e68b6331f4a947c3eb5a262433dbe2823ad3c87ee06",
  16. "BlockHeight": 15,
  17. "IsDelete": false,
  18. "Timestamp": "1716864410"
  19. },
  20. {
  21. "Key": "fact_bytes",
  22. "Field": "1",
  23. "Value": "eyJGaWxlSGFzaCI6IjEiLCJGaWxlTmFtZSI6IjIiLCJUaW1lIjozfQ==",
  24. "TxId": "6e099f7ce81543bf8fd0bf565f741478de53b4bc72834112a8bc0bc5f06f9a47",
  25. "BlockHeight": 16,
  26. "IsDelete": false,
  27. "Timestamp": "1716864421"
  28. },
  29. {
  30. "Key": "fact_bytes",
  31. "Field": "1",
  32. "Value": "",
  33. "TxId": "5f76c217063e41ad8c0f2b4ab3fae2418d784c9f0ade416b94715e95214acfc5",
  34. "BlockHeight": 17,
  35. "IsDelete": true,
  36. "Timestamp": "1716864504"
  37. }
  38. ]

value就是存证的字符串,但是这里是base64编码,转码结果如下

(注意:这里你会发现所有的value都是一样的,因为是我存证的数据都是输入的一样的)

转码结果如下:




8、个人理解

  • 官方文档有错:官方文档把key叫做命名空间取了一个固定值,field作为存证hash,这里应该是他们写反了,field 才有域、空间的意思。上面的解析,我还是按照官方错误的来说的。因为他最后存在level_db中的key是拼接的,所以写反写没事,都一样。你可以改成对的。
  • 之所以引入一个命名空间的概念,我猜测应该是为了在数据库中方便查看,因为同一份id可能会被存多次,加了一个空间会方便区分。不过官方也提供了没有命名空间的存储方法PutStateFromKey(key string, value string) error  就是教程一用的方法
  • 删除函数,并不是真的删除,会新上链一条数据,标明某个域、某个key被删除了,但是已经上链的数据不会变动。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/675979
推荐阅读
  

闽ICP备14008679号