当前位置:   article > 正文

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

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

长安链是分2.1.+和2.3.+两个版本,本节面说的是2.1.+的版本

需要2.3.+版本的合约,请看教程(二)

教程(二)我会写如何查历史数据

教程二:(长安链2.3.+的版本的智能合约编写)

教程三:(常见GO SDK的解释与使用)


编写前的注意事项:

1、运行一条带有Doker_GoVM的链

2、建议直接用官方的在线IDE去写合约,因为写完可以直接测,缺点只是调试不方便。

3、自己拉环境在本地写合约,编译时注意编译环境,官方有提醒你去Linux下去编译。


本教程使用官方的在线IDE去写合约

教程是基于官方文档写的,只是会多写一些解析步骤


1、首先新建一个合约

2、打开main.go文件(这是新增工程的默认存证模板)

  1. package main
  2. import (
  3. "encoding/json"
  4. "log"
  5. "strconv"
  6. "chainmaker/pb/protogo"
  7. "chainmaker/shim"
  8. )
  9. //FactContract 合约对象
  10. type FactContract struct {
  11. }
  12. //Fact 存证对象,存证合约的数据内容
  13. type Fact struct {
  14. FileHash string
  15. FileName string
  16. Time int
  17. }
  18. //NewFact 新建存证对象
  19. func NewFact(fileHash, fileName string, time int) *Fact {
  20. return &Fact{
  21. FileHash: fileHash,
  22. FileName: fileName,
  23. Time: time,
  24. }
  25. }
  26. //InitContract 合约初始化方法
  27. func (f *FactContract) InitContract(stub shim.CMStubInterface) protogo.Response {
  28. return shim.Success([]byte("Init Success"))
  29. }
  30. // UpgradeContract 合约升级方法
  31. func (f *FactContract) UpgradeContract(stub shim.CMStubInterface) protogo.Response {
  32. return shim.Success([]byte("Upgrade Success"))
  33. }
  34. //InvokeContract 调用合约
  35. func (f *FactContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {
  36. //获取调用合约哪个方法
  37. method := string(stub.GetArgs()["method"])
  38. // 这里必须写成 switch {case "a": ... [case "b": ...[...]] default:...} 形式
  39. // 而且case后面的内容必须是字符串,不能是常量
  40. // 这里必须写成 switch {case "a": ... [case "b": ...[...]] default:...} 形式
  41. // 而且case后面的内容必须是字符串,不能是常量
  42. // 这里必须写成 switch {case "a": ... [case "b": ...[...]] default:...} 形式
  43. // 而且case后面的内容必须是字符串,不能是常量
  44. // 如果 method == "save", 执行FactContract的save方法
  45. // 如果 method == "findByFileHash", 执行FactContract的findByFileHash方法
  46. // 如果没有对应的 case 语句,返回错误
  47. switch method {
  48. case "save":
  49. return f.Save(stub)
  50. case "findByFileHash":
  51. return f.FindByFileHash(stub)
  52. default:
  53. return shim.Error("invalid method")
  54. }
  55. }
  56. //save 存证,把数据存储到链上
  57. func (f *FactContract) Save(stub shim.CMStubInterface) protogo.Response {
  58. // 获取调用合约的全部参数
  59. params := stub.GetArgs()
  60. // 获取指定的参数
  61. fileHash := string(params["file_hash"])
  62. fileName := string(params["file_name"])
  63. timeStr := string(params["time"])
  64. if fileHash == "" || fileName == "" || timeStr == "" {
  65. //返回合约执行错误,以及错误信息
  66. return shim.Error("fileHash and fileName and time must not empty")
  67. }
  68. time, err := strconv.Atoi(timeStr)
  69. if err != nil {
  70. msg := "time is [" + timeStr + "] not int"
  71. // 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
  72. stub.Log(msg + err.Error())
  73. //返回合约执行错误,以及错误信息
  74. return shim.Error(msg)
  75. }
  76. fact := NewFact(fileHash, fileName, time)
  77. // 序列化
  78. factBytes, err := json.Marshal(fact)
  79. if err != nil {
  80. msg := "marshal data fail"
  81. stub.Log(msg + err.Error())
  82. return shim.Error(msg)
  83. }
  84. //向链上发送事件,发送的事件会在控制台的事件中显示
  85. stub.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})
  86. key := getHashKey(fact.FileHash)
  87. //把数据存到链上
  88. err = stub.PutStateFromKeyByte(key, factBytes)
  89. if err != nil {
  90. msg := "fail to save fact"
  91. stub.Log(msg + err.Error())
  92. return shim.Error(msg)
  93. }
  94. //打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
  95. stub.Log("[save] file hash:" + fact.FileHash)
  96. stub.Log("[save] file name:" + fact.FileName)
  97. // 返回执行成功
  98. return shim.Success([]byte(fact.FileName + fact.FileHash))
  99. }
  100. //findByFileHash 根据文件哈希从链上查找数据
  101. func (f *FactContract) FindByFileHash(stub shim.CMStubInterface) protogo.Response {
  102. // 获取调用合约的全部参数
  103. params := stub.GetArgs()
  104. // 获取指定参数
  105. fileHash := string(params["file_hash"])
  106. // 查询结果
  107. key := getHashKey(fileHash)
  108. result, err := stub.GetStateFromKeyByte(key)
  109. if err != nil {
  110. msg := "failed to call get_state"
  111. // 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
  112. stub.Log(msg + err.Error())
  113. //返回合约执行错误,以及错误信息
  114. return shim.Error(msg)
  115. }
  116. // 反序列化
  117. var fact Fact
  118. err = json.Unmarshal(result, &fact)
  119. if err != nil {
  120. msg := "unmarshal data fail"
  121. stub.Log(msg + err.Error())
  122. return shim.Error(msg)
  123. }
  124. // 记录日志
  125. stub.Log("[find_by_file_hash] file hash:" + fact.FileHash)
  126. stub.Log("[find_by_file_hash] file name:" + fact.FileName)
  127. // 返回执行成功
  128. return shim.Success(result)
  129. }
  130. func getHashKey(hash string) string {
  131. return "fact_hash" + hash
  132. }
  133. func main() {
  134. //运行合约
  135. err := shim.Start(new(FactContract))
  136. if err != nil {
  137. log.Fatal(err)
  138. }
  139. }

3、模板解析

17行:Fact结构体就是要存在区块链中的,根据你自己的需要去变更结构体的字段

//Fact 存证对象,存证合约的数据内容
type Fact struct {
    FileHash string 
    FileName string 
    Time     int
}

 24行:新建存证对象,根据Fact 结构体的变化而变化

//NewFact 新建存证对象

func NewFact(fileHash, fileName string, time int) *Fact {
    return &Fact{
        FileHash: fileHash,
        FileName: fileName,
        Time:     time,
    }
}

InitContract、UpgradeContract、InvokeContract  三个方法解析

  • InitContract、UpgradeContract:这是合约默认必须要有的方法,不要动。如果你在13行把对应合约对象的名称改了,对应你在方法名前的名称也要改成一致。
  • InvokeContract:这里是合约方法、根据你写了几个方法,依葫芦画瓢,继续补充就行。

//InitContract 合约初始化方法
func (f *FactContract) InitContract(stub shim.CMStubInterface) protogo.Response {
    return shim.Success([]byte("Init Success"))
}

// UpgradeContract 合约升级方法
func (f *FactContract) UpgradeContract(stub shim.CMStubInterface) protogo.Response {
    return shim.Success([]byte("Upgrade Success"))
}

//InvokeContract 调用合约
func (f *FactContract) InvokeContract(stub shim.CMStubInterface) protogo.Response {

//获取调用合约哪个方法
    method := string(stub.GetArgs()["method"])
    switch method {
    case "save":
        return f.Save(stub)
    case "findByFileHash":
        return f.FindByFileHash(stub)
    default:
        return shim.Error("invalid method")
    }
}

存证方法

以下大部分依葫芦画瓢就好了,重点关注以下内容:

  • 105行:key := getHashKey(fact.FileHash)  这是模板自定义的方法,在字符串前拼接一段字符,不必理会;
  • 108行:err = stub.PutStateFromKeyByte(key, factBytes),这就是最重要的将数据上链的方法!   其中 key相当于id值,你之后查的话就要根据key去查。(别忘了模板中在105行给key前加了一段字符,你查的时候也要加上)      关于上链的方法官方还提供了其他几种。
  • stub.Log  :都是可以输出在控制台上的,方便调试排查错误,可写可不写;
  • 120行,返回值要遵从官方的这个格式。

//save 存证,把数据存储到链上
func (f *FactContract) Save(stub shim.CMStubInterface) protogo.Response {
    // 获取调用合约的全部参数
    params := stub.GetArgs()

    // 获取指定的参数
    fileHash := string(params["file_hash"])
    fileName := string(params["file_name"])
    timeStr := string(params["time"])

    if fileHash == "" || fileName == "" || timeStr == "" {
        //返回合约执行错误,以及错误信息
        return shim.Error("fileHash and fileName and time must not empty")
    }

    time, err := strconv.Atoi(timeStr)
    if err != nil {
        msg := "time is [" + timeStr + "] not int"
        // 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
        stub.Log(msg + err.Error())
        //返回合约执行错误,以及错误信息
        return shim.Error(msg)
    }

    fact := NewFact(fileHash, fileName, time)

    // 序列化
    factBytes, err := json.Marshal(fact)
    if err != nil {
        msg := "marshal data fail"
        stub.Log(msg + err.Error())
        return shim.Error(msg)
    }

    //向链上发送事件,发送的事件会在控制台的事件中显示
    stub.EmitEvent("topic_vx", []string{fact.FileHash, fact.FileName})

    key := getHashKey(fact.FileHash)

    //把数据存到链上
    err = stub.PutStateFromKeyByte(key, factBytes)
    if err != nil {
        msg := "fail to save fact"
        stub.Log(msg + err.Error())
        return shim.Error(msg)
    }

    //打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
    stub.Log("[save] file hash:" + fact.FileHash)
    stub.Log("[save] file name:" + fact.FileName)

    // 返回执行成功
    return shim.Success([]byte(fact.FileName + fact.FileHash))

}

取证方法

以下大部分依葫芦画瓢就好了,重点关注以下内容:

  • 133行:这里就是去拼接了字符串
  • 134行:取证的方法:stub.GetStateFromKeyByte(key)  ,返回的result是byte[ ]类型
  • 145行:反序列化
  • 157行:返回值遵从官方规范。

//findByFileHash 根据文件哈希从链上查找数据
func (f *FactContract) FindByFileHash(stub shim.CMStubInterface) protogo.Response {
    // 获取调用合约的全部参数
    params := stub.GetArgs()

    // 获取指定参数
    fileHash := string(params["file_hash"])

    // 查询结果
    key := getHashKey(fileHash)
    result, err := stub.GetStateFromKeyByte(key)
    if err != nil {
        msg := "failed to call get_state"
        // 打印日志,使用 stub.Log 打印的日志会在控制台的输出中显示
        stub.Log(msg + err.Error())
        //返回合约执行错误,以及错误信息
        return shim.Error(msg)
    }

    // 反序列化
    var fact Fact
    err = json.Unmarshal(result, &fact)
    if err != nil {
        msg := "unmarshal data fail"
        stub.Log(msg + err.Error())
        return shim.Error(msg)
    }

    // 记录日志
    stub.Log("[find_by_file_hash] file hash:" + fact.FileHash)
    stub.Log("[find_by_file_hash] file name:" + fact.FileName)

    // 返回执行成功
    return shim.Success(result)

}

其他

这就是前面说的拼接字符串的方法,他在hash前加了“fact_hash”

func getHashKey(hash string) string {
    return "fact_hash" + hash
}

2、代码入口包名必须为main  (注意事项在注释中)

// sdk代码中,有且仅有一个main()方法
func main() {  
   // main()方法中,下面的代码为必须代码,不建议修改main()方法当中的代码
   // 其中,FactContract为用户实现合约的具体名称
	err := sandbox.Start(new(FactContract))
	if err != nil {
		log.Fatal(err)
	}
}



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

闽ICP备14008679号