使用 Go 與以太坊區塊鏈互動(三)
前言
上一篇文章(ofollow,noindex">使用 Go 與以太坊區塊鏈互動(二) )介紹了以 go-ethereum 產生私鑰並簽章一筆交易,這篇文章就介紹怎麼在節點上做智慧合約的操作,包含取得公開的變數值、交易和事件的處理。
文章內沒有說明怎麼建立連線,關於連線的方式可以參考這篇文章。
文章內沒有說明怎麼建置 Go 的開發環境,關於 Go 的開發環境環境建置可以去谷歌查詢。
關於智慧合約的開發及說明可以參考NICLIN 大大的文章。
智慧合約
從外部跟以太坊結點互動要使用 JSON RPC API,而與智慧合約互動會使用到的有以下幾種:
- 獲取 state 值:使用 eth_call
- 更改 state 值或是部署合約:使用 eth_sendRawTransaction 或是eth_sendTransaction(只有在更改合約 state 的時候才需要簽章)
- 聽取事件(subscribe):使用 filter 相關的 API,例如:eth_newFilter
每次呼叫智慧合約函式,其實底層是將呼叫的函式名稱跟帶入的引數進行編譯,編譯的規則分為動態引數和靜態引數,有興趣的可以參考官方檔案 。
編譯完後再將資料帶入交易的 data,比較特別的是部署合約的交易只需要帶入合約的 bytecode 並設 to 為 null。
合約交易如果帶入 value 則該函式必須設為 payable,部署的時候則要設 fallback 函式為 payable。
Solidity 環境建置
開始開發智慧合約前,我們要先安裝 solidity compiler 和 abigen,並產生合約函式相關的 go 檔案。
安裝 solidity compiler
$npm install -g solc
這邊的 solc 在指令中是 solcjs,為了使安裝 abigen 沒有問題,需要將指令複製為 solc。
編譯合約檔案 *.sol
$solcjs --bin --abi HW.sol EIP20.sol SafeMath.sol EIP20interface.sol
安裝 abigen
$go get go-ethereum $cd $GOPATH/src/github.com/ethereum/go-ethereum/ $make devtools
產生合約的 go 檔案
$abigen --bin HW_sol_HW.bin --abi HW_sol_HW.abi --pkg contract --out HW.go
接下來會以 ERC20 token 為例子:
- 部署 ERC20 token
- 獲取某地址的餘額
- 聽取 Transfer 事件
- 轉帳給某地址
部署 ERC20 token
部署合約我們可以自己產生交易或是呼叫合約中的 DeployContract。
types 產生交易並簽章
amount := big.NewInt(0) gasLimit := uint64(4600000) gasPrice := big.NewInt(1000000000) data := common.FromHex(Contract.ContractBin) tx := types.NewContractCreation(nonce, amount, gasLimit, gasPrice, data) // EIP155 signer // signer := types.NewEIP155Signer(big.NewInt(4))signer := types.HomesteadSigner{} signedTx, _ := types.SignTx(tx, signer, privKey)
建立 transaction options
// makeTxOpts make transaction options for contrac function func makeTxOpts(from common.Address, nonce *big.Int, value *big.Int, gasPrice *big.Int, gasLimit uint64, privKey *ecdsa.PrivateKey, chainID int64) *bind.TransactOpts { txOpts := &bind.TransactOpts{ From:from, Nonce: nonce, Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { var txSigner types.Signer if chainID != 0 { // EIP155 signer txSigner = types.NewEIP155Signer(big.NewInt(chainID)) } else { // default is homestead signer txSigner = signer } signedTx, err := types.SignTx(tx, txSigner, privKey) if err != nil { return nil, err } return signedTx, nil }, Value:value, GasPrice: gasPrice, GasLimit: gasLimit, } return txOpts }
signer 預設為 Homestead,要帶入 chainId 的話則要另外帶入 EIP155
呼叫合約中的 DeployContract
non := big.NewInt(int64(nonce)) txOpts := makeTxOpts(from, non, amount, gasPrice, gasLimit, privKey, 0) if contractAddress, deployTx, contract, err := Contract.DeployContract(txOpts, client.EthClient); err != nil { fmt.Errorf(err.Error()) } else { fmt.Println(contractAddress, deployTx.Hash().String(), contract)}
DeployContract 回傳的合約位置是計算出來(原始碼 ),這時合約不一定被部署上去,需要再確認交易是否有被完成。
獲取某地址的餘額
部署完合約後,我們來取得某地址的餘額。
實體化合約物件
contract, _ := Contract.NewContract(contractAddress, client.EthClient)
取得某地址的餘額
from := common.HexToAddress('0x9b23a6a9a60b3846f86ebc451d11bef20ed07930') balance, _ := contract.BalanceOf(nil, from) fmt.Printf("balance of %s is %d\n", from.String(), balance)
聽取 Transfer 事件
聽取事件是用 websocket 主動推播,要使用有開啟 websocket 的節點
// watch transfer, you need to use websocket to watch event ch := make(chan *Contract.ContractTransfer) sub, _ := contract.WatchTransfer(nil, ch, nil, nil)
go func() { for { select { case err := <-sub.Err(): fmt.Errorf(err.Error()) os.Exit(1) case log := <-ch: fmt.Printf("[Transfer event]\n") fmt.Printf("From: %s\n", log.From.String()) fmt.Printf("To: %s\n", log.To.String()) fmt.Printf("Value: %d\n", log.Value) os.Exit(0) } } }() // waiting <-ch
如果沒有開啟 websocket 的服務,則可以 filter log
if logs, err := contract.FilterTransfer(filterOpts, nil, nil); err != nil { fmt.Errorf(err.Error()) os.Exit(1) } else { // do something log := logs.Event }
轉帳給某地址
轉帳的函式要帶入兩個引數,分別為 to common.Address 和 value *big.Int
txOpts := makeTxOpts(from, non, amount, gasPrice, gasLimit, privKey, 0) toAddress := common.HexToAddress("0x9b23a6a9a60b3846f86ebc451d11bef20ed07930") bal := big.NewInt(10000) if transferTx, err := contract.Transfer(txOpts, toAddress, bal); err != nil { fmt.Errorf(err.Error()) } else { // do something }
結論
這篇文章介紹以 go-ethereum 做智慧合約的操作,文章中只貼上部分的程式碼,完整的程式碼可以在Github 看到,有任何錯誤或是問題也歡迎各位發 issue。