以太坊系列之十六: 使用golang与智能合约进行交互

官方提供的使用web3来进行智能合约的部署,调用等,实际上使用go也是可以的,这样更接近geth源码,更多的库可以使用.

此例子的目录结构

方便大家对照使用



我是在windows下进行的,在linux以及mac下都差不多,只需要更改里面的ipc地址即可

token contract

这是官方提供的一个智能合约的例子,比较简单,是一个典型的基于智能合约的代币.代码位于:

token源码.

智能合约的golang wrapper

go直接和智能合约交互,有很多琐碎的细节需要照顾到,比较麻烦.以太坊专门为我们提供了一个abigen的工具,他可以根据sol或者abi文件生成

特定语言的封装,方便进行交互,支持golang,objc,java三种语言.

abigen --sol token.sol --pkg mytoken --out token.go

token.go地址

可以看到里面把合约里面所有导出的函数都进行了封装,方便调用.

部署合约

使用go进行部署合约思路上和web3都差不多,首先需要启动geth,然后通过我们的程序通过ipc连接到geth进行操作.

直接上代码,然后解释

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "math/big"
  6. "strings"
  7. "time"
  8. "github.com/ethereum/go-ethereum/accounts/abi/bind"
  9. "github.com/ethereum/go-ethereum/ethclient"
  10. "token-contract/mytoken"
  11. )
  12. const key = `
  13. {
  14. "address": "1a9ec3b0b807464e6d3398a59d6b0a369bf422fa",
  15. "crypto": {
  16. "cipher": "aes-128-ctr",
  17. "ciphertext": "a471054846fb03e3e271339204420806334d1f09d6da40605a1a152e0d8e35f3",
  18. "cipherparams": {
  19. "iv": "44c5095dc698392c55a65aae46e0b5d9"
  20. },
  21. "kdf": "scrypt",
  22. "kdfparams": {
  23. "dklen": 32,
  24. "n": 262144,
  25. "p": 1,
  26. "r": 8,
  27. "salt": "e0a5fbaecaa3e75e20bccf61ee175141f3597d3b1bae6a28fe09f3507e63545e"
  28. },
  29. "mac": "cb3f62975cf6e7dfb454c2973bdd4a59f87262956d5534cdc87fb35703364043"
  30. },
  31. "id": "e08301fb-a263-4643-9c2b-d28959f66d6a",
  32. "version": 3
  33. }
  34. `
  35. func main() {
  36. // Create an IPC based RPC connection to a remote node and an authorized transactor
  37. conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc")
  38. if err != nil {
  39. log.Fatalf("Failed to connect to the Ethereum client: %v", err)
  40. }
  41. auth, err := bind.NewTransactor(strings.NewReader(key), "123")
  42. if err != nil {
  43. log.Fatalf("Failed to create authorized transactor: %v", err)
  44. }
  45. // Deploy a new awesome contract for the binding demo
  46. address, tx, token, err := mytoken.DeployMyToken(auth, conn, big.NewInt(9651), "Contracts in Go!!!", 0, "Go!")
  47. if err != nil {
  48. log.Fatalf("Failed to deploy new token contract: %v", err)
  49. }
  50. fmt.Printf("Contract pending deploy: 0x%x\n", address)
  51. fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
  52. startTime := time.Now()
  53. fmt.Printf("TX start @:%s", time.Now())
  54. ctx := context.Background()
  55. addressAfterMined, err := bind.WaitDeployed(ctx, conn, tx)
  56. if err != nil {
  57. log.Fatalf("failed to deploy contact when mining :%v", err)
  58. }
  59. fmt.Printf("tx mining take time:%s\n", time.Now().Sub(startTime))
  60. if bytes.Compare(address.Bytes(), addressAfterMined.Bytes()) != 0 {
  61. log.Fatalf("mined address :%s,before mined address:%s", addressAfterMined, address)
  62. }
  63. name, err := token.Name(&bind.CallOpts{Pending: true})
  64. if err != nil {
  65. log.Fatalf("Failed to retrieve pending name: %v", err)
  66. }
  67. fmt.Println("Pending name:", name)
  68. }

1.账户问题

  1. 部署合约是需要有以太坊账户的,账户一般位于/home/xxx/.eth/geth/keystore 目录里面,找到一个账户,然后把内容直接粘贴到key里面即可.
  2. 因为部署合约是要消耗以太币的,所以必须保证里面有以太币,并且在`bind.NewTransactor(strings.NewReader(key), "123")`时,还需提供密码.

2. 连接到geth

  1. `ethclient.Dial("\\\\.\\pipe\\geth.ipc")`就是连接到本地的geth,你可以通过http等通道,要是使用http通道,记得geth启动的时候要加上
  2. `--rpcapi "eth,admin,web3,net,debug" `,否则很多rpc api是无法使用的

3. 部署合约

  1. 真正的部署合约反而是比较简单,因为有了tokengolang封装,就像直接调用构造函数一样. 只不过多了两个参数,第一个是auth,也就是账户的封装;
  2. 第二个是ethclient的连接.

4. 测试部署结果

  1. testrpc或者其他模拟区块链上,因为合约部署不需要花时间,所以`name, err := token.Name(&bind.CallOpts{Pending: true})`是可以获取到name的,
  2. 但是在真实的区块链上有一个比较大的延时,所以运行结果会是:
  1. Contract pending deploy: 0xa9b61a3cc7cc1810e133174caa7ead7ef909d701
  2. Transaction waiting to be mined: 0xf832802f6f262677f02eca761ffe65ae21bbe41e983ceeb6cf645166073f4eb5
  3. TX start @:2017-09-04 11:13:57.217 +0800 CSTtx mining take time:34.009s
  4. Pending name: Contracts in Go!!!

5. 等待成功部署到区块链上

  1. 这里的成功可能不是真正的成功,大家都知道区块链稳定下来要等至少12个周期.不过通过`bind.WaitDeployed`
  2. 基本上可以确定该合约已经进入了区块链,并且可以在上面进行操作了.

golang 查询合约

前一个例子中我们借助remix查询到已经到账了,实际上golang完全可以做到,并且做起来也很简单.

先看代码,再做解释.

  1. func main() {
  2. // Create an IPC based RPC connection to a remote node and instantiate a contract binding
  3. conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc")
  4. if err != nil {
  5. log.Fatalf("Failed to connect to the Ethereum client: %v", err)
  6. }
  7. token, err := mytoken.NewMyToken(common.HexToAddress("0x5e300171d7dc10e43f959877dba98a44df5d1466"), conn)
  8. if err != nil {
  9. log.Fatalf("Failed to instantiate a Token contract: %v", err)
  10. }
  11. contractName, err := token.Name(nil)
  12. if err != nil {
  13. log.Fatalf("query name err:%v", err)
  14. }
  15. fmt.Printf("MyToken Name is:%s\n", contractName)
  16. balance, err := token.BalanceOf(nil, common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401"))
  17. if err != nil {
  18. log.Fatalf("query balance error:%v", err)
  19. }
  20. fmt.Printf("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401's balance is %s\n", balance)
  21. }

运行结果:

  1. MyToken Name is:Contracts in Go!!!
  2. 0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401's balance is 387

token.Name,token.BalanceOf就是读取合约上的数据,因为这些操作并不会修改合约的状态,所以不会发起tx,也不需要auth.

读取合约的第一个参数是bind.CallOpts,定义如下:

  1. // CallOpts is the collection of options to fine tune a contract call request.
  2. type CallOpts struct {
  3. Pending bool // Whether to operate on the pending state or the last known one
  4. From common.Address // Optional the sender address, otherwise the first account is used
  5. Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
  6. }

大多数时候直接使用nil即可,我们不需要特殊指定什么.

调用合约

  1. 这里说调用实际上指的是要发生tx,这里举的例子就是token中进行转账操作,因为这个操作修改了合约的状态,所以它必须是一个tx(事务).
  2. 先看完整的例子

  1. func main() {
  2. // Create an IPC based RPC connection to a remote node and instantiate a contract binding
  3. conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc")
  4. if err != nil {
  5. log.Fatalf("Failed to connect to the Ethereum client: %v", err)
  6. }
  7. token, err := mytoken.NewMyToken(common.HexToAddress("0xa9b61a3cc7cc1810e133174caa7ead7ef909d701"), conn)
  8. if err != nil {
  9. log.Fatalf("Failed to instantiate a Token contract: %v", err)
  10. }
  11. toAddress := common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401")
  12. val, _ := token.BalanceOf(nil, toAddress)
  13. fmt.Printf("before transfer :%s\n", val)
  14. // Create an authorized transactor and spend 1 unicorn
  15. auth, err := bind.NewTransactor(strings.NewReader(key), "123")
  16. if err != nil {
  17. log.Fatalf("Failed to create authorized transactor: %v", err)
  18. }
  19. tx, err := token.Transfer(auth, toAddress, big.NewInt(387))
  20. if err != nil {
  21. log.Fatalf("Failed to request token transfer: %v", err)
  22. }
  23. ctx := context.Background()
  24. receipt, err := bind.WaitMined(ctx, conn, tx)
  25. if err != nil {
  26. log.Fatalf("tx mining error:%v\n", err)
  27. }
  28. val, _ = token.BalanceOf(nil, toAddress)
  29. fmt.Printf("after transfere:%s\n", val)
  30. fmt.Printf("tx is :%s\n", tx)
  31. fmt.Printf("receipt is :%s\n", receipt)
  32. }

执行结果:

  1. before transfer :0
  2. after transfere:387
  3. tx is :
  4. TX(3028e06dbe037731f05c7c73c4694080df72460cf39a70bdb8df76e771800958)
  5. Contract: false
  6. From: 1a9ec3b0b807464e6d3398a59d6b0a369bf422fa
  7. To: a9b61a3cc7cc1810e133174caa7ead7ef909d701
  8. Nonce: 29
  9. GasPrice: 0x430e23400
  10. GasLimit 0x8e73
  11. Value: 0x0
  12. Data: 0xa9059cbb0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce84010000000000000000000000000000000000000000000000000000000000000183
  13. V: 0x1b
  14. R: 0xbb57f25039d5e33c1f74607f4d1733cd77ecf99dc6ffff5a7ac90404f6208ea2
  15. S: 0x1e6685f69d51654d30cae14207f74f6858a9ffb9ccc5f1d3d9c852027d49f6c3
  16. Hex: f8a91d850430e23400828e7394a9b61a3cc7cc1810e133174caa7ead7ef909d70180b844a9059cbb0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce840100000000000000000000000000000000000000000000000000000000000001831ba0bb57f25039d5e33c1f74607f4d1733cd77ecf99dc6ffff5a7ac90404f6208ea2a01e6685f69d51654d30cae14207f74f6858a9ffb9ccc5f1d3d9c852027d49f6c3
  17. receipt is :receipt{med=5c0564d6b6568328a4407dfd86da58c1a8d26b38f93cbbd2b8c7cca13b3a792b cgas=36466 bloom=00000000000000000000000000000000001000000000000000000000000000000000000000010000000000000010000000000000000000000000000000200000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000008000000000000000000000000000000000020000000000000000000000000000000000400000000000000040000000000000 logs=[log: a9b61a3cc7cc1810e133174caa7ead7ef909d701 [ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000001a9ec3b0b807464e6d3398a59d6b0a369bf422fa 0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce8401] 0000000000000000000000000000000000000000000000000000000000000183 3028e06dbe037731f05c7c73c4694080df72460cf39a70bdb8df76e771800958 0 ccb6f7f26ddcb2d1438f98f51046e3115b8eb27cfab9ffcbc3bd259b68e73d11 0]}
  1. 首先同样要连接到geth,然后才能进行后续操作.

1. 直接构造合约

  1. 因为合约已经部署到区块链上了,我们直接基于地址构造合约就可以了.
  2. `token, err := mytoken.NewMyToken(common.HexToAddress("0x5e300171d7dc10e43f959877dba98a44df5d1466"), conn)`
  3. 这次我们不需要auth,因为这个操作实际上是仅读取区块链上的内容.

2. 创建账户

  1. 进行转账修改了合约的状态,必须需要auth,和上次一样创建即可.

3.进行转账(函数调用)

  1. 转账操作`token.Transfer(auth, common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401"), big.NewInt(387))`,是要给账户
  2. 0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401387个代币,这实际上是调用了sol中的
  1. /* Send coins */
  2. function transfer(address _to, uint256 _value) {
  3. if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough
  4. if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
  5. balanceOf[msg.sender] -= _value; // Subtract from the sender
  6. balanceOf[_to] += _value; // Add the same to the recipient
  7. Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place
  8. }
  1. 可以看出,和在sol中是差不多的,只不过多了一个auth,他实际上起到的作用就是要对这个事务进行签名.

4. 等待tx完成

  1. 可以通过bind.WaitMined来等待事务真正被矿工处理完毕,这时候通过条用BalanceOf就可以查询到转账前后的数值变化.

5.通过remix来查询结果

  1. 转账是一个tx,必须等待矿工挖矿,提交到区块链中以后才能查询到结果,除了在程序中等待一段时间进行查询,也可以自己等待一会儿,然后直接在remix中进行查询了.
  2. #### (1) 打开http://ethereum.github.io/browser-solidity/#version=soljson-v0.4.16+commit.d7661dd9.js
  3. #### (2) 粘贴token.sol的内容
  4. #### (3) 切换到Web3 Provider
  5. #### (4) 使用At Address创建合约
  6. 这是因为我们合约已经创建完毕了,通过指定地址就可以直接与我们的合约进行交互了
  7. 然后可以调用balanceof来查询已经到账了.截图如下:

以太坊系列之十六: 使用golang与智能合约进行交互的更多相关文章

  1. 以太坊系列之十六:golang进行智能合约开发

    以太坊系列之十六: 使用golang与智能合约进行交互 以太坊系列之十六: 使用golang与智能合约进行交互 此例子的目录结构 token contract 智能合约的golang wrapper ...

  2. 以太坊系列之十八: 百行go代码构建p2p聊天室

    百行go代码构建p2p聊天室 百行go代码构建p2p聊天室 1. 上手使用 2. whisper 原理 3. 源码解读 3.1 参数说明 3.1 连接主节点 3.2 我的标识 3.2 配置我的节点 3 ...

  3. 以太坊系列之十四: solidity特殊函数

    solidity中的特殊函数 括号里面有类型和名字的是参数,只有类型是返回值. block.blockhash(uint blockNumber) returns (bytes32): hash of ...

  4. 以太坊系列之十二: solidity变量存储

    solidity中变量的存储 变量存储主要分为两个区域,一个是storage(对应指定是SLOAD,SSTORE),一个是Memory(MLOAD,MSTORE), 这和普通编程语言的内存模型是不一样 ...

  5. 以太坊系列之十七: 使用web3进行合约部署调用以及监听

    以太坊系列之十七: 使用web3进行智能合约的部署调用以及监听事件(Event) 上一篇介绍了使用golang进行智能合约的部署以及调用,但是使用go语言最大的一个问题是没法持续监听事件的发生. 比如 ...

  6. Web 前端开发人员和设计师必读精华文章【系列二十六】

    <Web 前端开发精华文章推荐>2014年第5期(总第26期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  7. OSGi 系列(十六)之 JDBC Service

    OSGi 系列(十六)之 JDBC Service compendium 规范提供了 org.osgi.service.jdbc.DataSourceFactory 服务 1. 快速入门 1.1 环境 ...

  8. Unity3D脚本中文系列教程(十六)

    Unity3D脚本中文系列教程(十五) ◆ function OnPostprocessAudio (clip:AudioClip):void 描述:◆  function OnPostprocess ...

  9. SQL注入之Sqli-labs系列第二十六关(过滤空格、注释符、逻辑运算符注入)和第二十六A

    开始挑战第二十六关(Trick with comments and space) 0x1看看源代码 (1)过滤了#  or and  /**/  /  \ ,通过判断也过滤了空格 (2)这样一来只能看 ...

随机推荐

  1. swift 3.0 基础练习 面向对象 类

    模拟需求 创建100个女朋友 1.用面向对象思想 2.名字随机 3.年龄随机 4.拥有约会功能 5.将所有女朋友信息输出 class GirlFirend: NSObject { var name:S ...

  2. 有关java 8

    http://www.iteye.com/news/27608     Java 8 发布时间敲定,延期半年 http://www.iteye.com/news/24631/   Java 8 的重要 ...

  3. python pygame--倒计时

    import pygame,sys,time,datetime class decTime(object): #将秒转化为时分秒 def __init__(self,totalTime): self. ...

  4. HDU 6097---Mindis(二分)

    题目链接 Problem Description The center coordinate of the circle C is O, the coordinate of O is (0,0) , ...

  5. MongoDB安全策略

    一,修改启动端口 mongo的默认端口为27017 如果启用的是27017端口并且在公网上很容易被人攻击,所以第一点我们要修改端口 sudo ./mongod --dbpath=/data/db -- ...

  6. Xcode调试时查看变量的方法

    在Debug Console窗口中(lldb)后可以使用两个指令: 1.print 变量名:打印出变量值 2.po 对象名:print object,打印出对象中的值: 另外,Command+Shif ...

  7. Eclipse创建Maven项目报错的解决

    报错1:Could not resolve archetype org.apache.maven.archetypes:maven-archetype-quickstart 起因:删除一个用quick ...

  8. Linux的nginx环境的vue 部署

    1.使用WebStrom编程好的vue 进入到Terminal运行npm run build           2.使用WinSCP进入到nginx 目录配置文件下面,找到nginx.conf文件: ...

  9. 易云捷讯MySQL云数据库上线,推进IaaS与PaaS融合战略布局

    日前宣布,其基于MySQL的关系型云数据库已经正式上线公测,用户可通过易云管理控制台创建.监控与管理mysql数据库.此服务包括在线扩容.自动备份.灵活配置和监控告警等功能,旨在帮助用户实现便捷的运维 ...

  10. jQuery.Gantt

    使用jQuery.Gantt过程中,遇到的问题!