以太坊系列之十六:golang進行智能合約開發


以太坊系列之十六: 使用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進行操作.

直接上代碼,然后解釋

package main

import (
	"fmt"
	"log"
	"math/big"
	"strings"
	"time"

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/ethclient"
	"token-contract/mytoken"
)

const key = `
{
  "address": "1a9ec3b0b807464e6d3398a59d6b0a369bf422fa",
  "crypto": {
    "cipher": "aes-128-ctr",
    "ciphertext": "a471054846fb03e3e271339204420806334d1f09d6da40605a1a152e0d8e35f3",
    "cipherparams": {
      "iv": "44c5095dc698392c55a65aae46e0b5d9"
    },
    "kdf": "scrypt",
    "kdfparams": {
      "dklen": 32,
      "n": 262144,
      "p": 1,
      "r": 8,
      "salt": "e0a5fbaecaa3e75e20bccf61ee175141f3597d3b1bae6a28fe09f3507e63545e"
    },
    "mac": "cb3f62975cf6e7dfb454c2973bdd4a59f87262956d5534cdc87fb35703364043"
  },
  "id": "e08301fb-a263-4643-9c2b-d28959f66d6a",
  "version": 3
}
`

func main() {
	// Create an IPC based RPC connection to a remote node and an authorized transactor
	conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	auth, err := bind.NewTransactor(strings.NewReader(key), "123")
	if err != nil {
		log.Fatalf("Failed to create authorized transactor: %v", err)
	}
	// Deploy a new awesome contract for the binding demo
	address, tx, token, err := mytoken.DeployMyToken(auth, conn, big.NewInt(9651), "Contracts in Go!!!", 0, "Go!")
	if err != nil {
		log.Fatalf("Failed to deploy new token contract: %v", err)
	}
	fmt.Printf("Contract pending deploy: 0x%x\n", address)
	fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
	startTime := time.Now()
	fmt.Printf("TX start @:%s", time.Now())
	ctx := context.Background()
	addressAfterMined, err := bind.WaitDeployed(ctx, conn, tx)
	if err != nil {
		log.Fatalf("failed to deploy contact when mining :%v", err)
	}
	fmt.Printf("tx mining take time:%s\n", time.Now().Sub(startTime))
	if bytes.Compare(address.Bytes(), addressAfterMined.Bytes()) != 0 {
		log.Fatalf("mined address :%s,before mined address:%s", addressAfterMined, address)
	}
	name, err := token.Name(&bind.CallOpts{Pending: true})
	if err != nil {
		log.Fatalf("Failed to retrieve pending name: %v", err)
	}
	fmt.Println("Pending name:", name)
}

1.賬戶問題

部署合約是需要有以太坊賬戶的,賬戶一般位於/home/xxx/.eth/geth/keystore 目錄里面,找到一個賬戶,然后把內容直接粘貼到key里面即可.
因為部署合約是要消耗以太幣的,所以必須保證里面有以太幣,並且在`bind.NewTransactor(strings.NewReader(key), "123")`時,還需提供密碼.

2. 連接到geth

`ethclient.Dial("\\\\.\\pipe\\geth.ipc")`就是連接到本地的geth,你可以通過http等通道,要是使用http通道,記得geth啟動的時候要加上
`--rpcapi "eth,admin,web3,net,debug" `,否則很多rpc api是無法使用的

3. 部署合約

真正的部署合約反而是比較簡單,因為有了token的golang封裝,就像直接調用構造函數一樣. 只不過多了兩個參數,第一個是auth,也就是賬戶的封裝;
第二個是ethclient的連接.

4. 測試部署結果

在testrpc或者其他模擬區塊鏈上,因為合約部署不需要花時間,所以`name, err := token.Name(&bind.CallOpts{Pending: true})`是可以獲取到name的,
但是在真實的區塊鏈上有一個比較大的延時,所以運行結果會是:
Contract pending deploy: 0xa9b61a3cc7cc1810e133174caa7ead7ef909d701
Transaction waiting to be mined: 0xf832802f6f262677f02eca761ffe65ae21bbe41e983ceeb6cf645166073f4eb5

TX start @:2017-09-04 11:13:57.217 +0800 CSTtx mining take time:34.009s
Pending name: Contracts in Go!!!

5. 等待成功部署到區塊鏈上

這里的成功可能不是真正的成功,大家都知道區塊鏈穩定下來要等至少12個周期.不過通過`bind.WaitDeployed`
基本上可以確定該合約已經進入了區塊鏈,並且可以在上面進行操作了.

golang 查詢合約

前一個例子中我們借助remix查詢到已經到賬了,實際上golang完全可以做到,並且做起來也很簡單.
先看代碼,再做解釋.

func main() {
	// Create an IPC based RPC connection to a remote node and instantiate a contract binding
	conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	token, err := mytoken.NewMyToken(common.HexToAddress("0x5e300171d7dc10e43f959877dba98a44df5d1466"), conn)
	if err != nil {
		log.Fatalf("Failed to instantiate a Token contract: %v", err)
	}

	contractName, err := token.Name(nil)
	if err != nil {
		log.Fatalf("query name err:%v", err)
	}
	fmt.Printf("MyToken Name is:%s\n", contractName)
	balance, err := token.BalanceOf(nil, common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401"))
	if err != nil {
		log.Fatalf("query balance error:%v", err)
	}
	fmt.Printf("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401's balance is %s\n", balance)
}

運行結果:

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

token.Name,token.BalanceOf就是讀取合約上的數據,因為這些操作並不會修改合約的狀態,所以不會發起tx,也不需要auth.
讀取合約的第一個參數是bind.CallOpts,定義如下:

// CallOpts is the collection of options to fine tune a contract call request.
type CallOpts struct {
	Pending bool           // Whether to operate on the pending state or the last known one
	From    common.Address // Optional the sender address, otherwise the first account is used

	Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}

大多數時候直接使用nil即可,我們不需要特殊指定什么.

調用合約

這里說調用實際上指的是要發生tx,這里舉的例子就是token中進行轉賬操作,因為這個操作修改了合約的狀態,所以它必須是一個tx(事務).

先看完整的例子

func main() {
	// Create an IPC based RPC connection to a remote node and instantiate a contract binding
	conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	token, err := mytoken.NewMyToken(common.HexToAddress("0xa9b61a3cc7cc1810e133174caa7ead7ef909d701"), conn)
	if err != nil {
		log.Fatalf("Failed to instantiate a Token contract: %v", err)
	}
	toAddress := common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401")
	val, _ := token.BalanceOf(nil, toAddress)
	fmt.Printf("before transfer :%s\n", val)
	// Create an authorized transactor and spend 1 unicorn
	auth, err := bind.NewTransactor(strings.NewReader(key), "123")
	if err != nil {
		log.Fatalf("Failed to create authorized transactor: %v", err)
	}
	tx, err := token.Transfer(auth, toAddress, big.NewInt(387))
	if err != nil {
		log.Fatalf("Failed to request token transfer: %v", err)
	}
	ctx := context.Background()
	receipt, err := bind.WaitMined(ctx, conn, tx)
	if err != nil {
		log.Fatalf("tx mining error:%v\n", err)
	}
	val, _ = token.BalanceOf(nil, toAddress)
	fmt.Printf("after transfere:%s\n", val)
	fmt.Printf("tx is :%s\n", tx)
	fmt.Printf("receipt is :%s\n", receipt)
}

執行結果:

before transfer :0
after transfere:387
tx is :
	TX(3028e06dbe037731f05c7c73c4694080df72460cf39a70bdb8df76e771800958)
	Contract: false
	From:     1a9ec3b0b807464e6d3398a59d6b0a369bf422fa
	To:       a9b61a3cc7cc1810e133174caa7ead7ef909d701
	Nonce:    29
	GasPrice: 0x430e23400
	GasLimit  0x8e73
	Value:    0x0
	Data:     0xa9059cbb0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce84010000000000000000000000000000000000000000000000000000000000000183
	V:        0x1b
	R:        0xbb57f25039d5e33c1f74607f4d1733cd77ecf99dc6ffff5a7ac90404f6208ea2
	S:        0x1e6685f69d51654d30cae14207f74f6858a9ffb9ccc5f1d3d9c852027d49f6c3
	Hex:      f8a91d850430e23400828e7394a9b61a3cc7cc1810e133174caa7ead7ef909d70180b844a9059cbb0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce840100000000000000000000000000000000000000000000000000000000000001831ba0bb57f25039d5e33c1f74607f4d1733cd77ecf99dc6ffff5a7ac90404f6208ea2a01e6685f69d51654d30cae14207f74f6858a9ffb9ccc5f1d3d9c852027d49f6c3

receipt is :receipt{med=5c0564d6b6568328a4407dfd86da58c1a8d26b38f93cbbd2b8c7cca13b3a792b cgas=36466 bloom=00000000000000000000000000000000001000000000000000000000000000000000000000010000000000000010000000000000000000000000000000200000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000008000000000000000000000000000000000020000000000000000000000000000000000400000000000000040000000000000 logs=[log: a9b61a3cc7cc1810e133174caa7ead7ef909d701 [ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000001a9ec3b0b807464e6d3398a59d6b0a369bf422fa 0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce8401] 0000000000000000000000000000000000000000000000000000000000000183 3028e06dbe037731f05c7c73c4694080df72460cf39a70bdb8df76e771800958 0 ccb6f7f26ddcb2d1438f98f51046e3115b8eb27cfab9ffcbc3bd259b68e73d11 0]}
首先同樣要連接到geth,然后才能進行后續操作.

1. 直接構造合約

因為合約已經部署到區塊鏈上了,我們直接基於地址構造合約就可以了.
`token, err := mytoken.NewMyToken(common.HexToAddress("0x5e300171d7dc10e43f959877dba98a44df5d1466"), conn)`
這次我們不需要auth,因為這個操作實際上是僅讀取區塊鏈上的內容.

2. 創建賬戶

進行轉賬修改了合約的狀態,必須需要auth,和上次一樣創建即可.

3.進行轉賬(函數調用)

轉賬操作`token.Transfer(auth, common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401"), big.NewInt(387))`,是要給賬戶
0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401轉387個代幣,這實際上是調用了sol中的
    /* Send coins */
    function transfer(address _to, uint256 _value) {
        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
        balanceOf[msg.sender] -= _value;                     // Subtract from the sender
        balanceOf[_to] += _value;                            // Add the same to the recipient
        Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place
    }
可以看出,和在sol中是差不多的,只不過多了一個auth,他實際上起到的作用就是要對這個事務進行簽名.

4. 等待tx完成

可以通過bind.WaitMined來等待事務真正被礦工處理完畢,這時候通過條用BalanceOf就可以查詢到轉賬前后的數值變化.

5.通過remix來查詢結果

轉賬是一個tx,必須等待礦工挖礦,提交到區塊鏈中以后才能查詢到結果,除了在程序中等待一段時間進行查詢,也可以自己等待一會兒,然后直接在remix中進行查詢了.

    #### (1) 打開http://ethereum.github.io/browser-solidity/#version=soljson-v0.4.16+commit.d7661dd9.js
    #### (2) 粘貼token.sol的內容
    #### (3) 切換到Web3 Provider
    #### (4) 使用At Address創建合約
        這是因為我們合約已經創建完畢了,通過指定地址就可以直接與我們的合約進行交互了
        然后可以調用balanceof來查詢已經到賬了.截圖如下:

轉賬結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM