【我的區塊鏈之路】- go連接以太坊客戶端Geth及調用合約

【轉載請標明出處】https://blog.csdn.net/qq_25870633/article/details/82931782

首先,我們需要在本地搭建一個 geth的節點。

Geth的安裝

下載Geth源碼及安裝Geth

  1. 使用 go get -v github.com/ethereum/go-ethereum 下載,或者使用 go clone [email protected]:ethereum/go-ethereum.git 下載。
  2.  在根目錄執行  make geth 進行編譯安裝,然後需要手動把生成的geth加入到環境變量中,或者加入到 /usr/local/bin 中
  3. 或者 在go-ethereum/cmd/geth 目錄進行 go install 編譯安裝到 GOPATH/bin 目錄中 (等價於加入了環境變量中了, 因爲GOPATH 就是一個環境變量)

Geth的啓動私有鏈

創建創世塊配置文件 genesis.json

{
  "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "coinbase" : "0x0000000000000000000000000000000000000000",
    "difficulty" : "0x40000",
    "extraData" : "",
    "gasLimit" : "0xffffffff",
    "nonce" : "0x0000000000000042",
    "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
    "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp" : "0x00",
    "alloc": { }
}

來,我們來說一說,這個創世塊的配置文件中的各項分別是些什麼東西:

參數名稱 參數描述
mixhash 與nonce配合用於挖礦,由上一個區塊的一部分生成的hash。注意他和nonce的設置需要滿足以太坊的Yellow paper, 4.3.4. Block Header Validity, (44)章節所描述的條件。
nonce nonce就是一個64位隨機數,用於挖礦,注意他和mixhash的設置需要滿足以太坊的Yellow paper, 4.3.4. Block Header Validity, (44)章節所描述的條件。
difficulty 設置當前區塊的難度,如果難度過大,cpu挖礦就很難,這裏設置較小難度
alloc 用來預置賬號以及賬號的以太幣數量,因爲私有鏈挖礦比較容易,所以我們不需要預置有幣的賬號,需要的時候自己創建即可以。
coinbase 礦工的賬號,隨便填
timestamp 設置創世塊的時間戳
parentHash 上一個區塊的hash值,因爲是創世塊,所以這個值是0
extraData 附加信息,隨便填,可以填你的個性信息
gasLimit 該值設置對GAS的消耗總量限制,用來限制區塊能包含的交易信息總和,因爲我們是私有鏈,所以填最大。

其中 config 選項中的各項說明:

chainId 標識當前鏈並用於重放保護。您應該將其設置爲專用鏈的唯一值。主要用於EIP155 中
homesteadBlock 當前chain 不會被切換成 Homestead 版本,所以應當設置爲 0 。
eip155Block 當前chain 不會因 硬分叉 而改變,所以應當設置爲 0 。
eip158Block 同上。

 

初始化geth 私有鏈節點:

準備好創世區塊json配置文件後,需要初始化區塊鏈,將上面的創世區塊信息寫入到區塊鏈中。首先要新建一個目錄 data0 注意: 可以使任意命名的目錄哦,比如:我是用了 genesis-block用來存放區塊鏈數據【其實,這個目錄data0就相當於一個根節點。當我們基於genesis.json生成根節點後,其他人就可以來連接此根節點,從而能進行交易】。

在命令行輸入:【注意需要在 根目錄輸入】

geth --datadir genesis-block init genesis.json

 

 

上述命令的意思是,讀取genesis.json文件,根據其中的內容,將創世區塊寫入到區塊鏈中。當我們看到有 Successfully wrote genesis state 等日誌輸出時,代表初始化私有區塊鏈成功。

我們進入 自定義的 genesis-block 目錄,可以看到如下信息:

其中,geth/chaindata 中存放的是區塊數據,geth/lightchaindata 是 輕節點 所存放的區塊頭信息,keystore 中存放的是賬戶數據。

 

啓動私有鏈geth節點:

在初始化完成後,我們需要啓動私有鏈節點,在命令行輸入:【注意需要在 根目錄輸入】

geth --datadir genesis-block --networkid 1108 console

即可啓動本地私有鏈節點。 

然後,我們看到當前目錄的結構是這樣紙的:

我們這裏也順便講一講 geth 命令的一些參數:參考自 https://www.cnblogs.com/tinyxiong/p/7918706.html 

geth命令的用法: geth [選項] 命令 [命令選項] [參數…]

命令:

account    管理賬戶
attach     啓動交互式JavaScript環境(連接到節點)
bug        上報bug Issues
console    啓動交互式JavaScript環境
copydb     從文件夾創建本地鏈
dump       Dump(分析)一個特定的塊存儲
dumpconfig 顯示配置值
export     導出區塊鏈到文件
import     導入一個區塊鏈文件
init       啓動並初始化一個新的創世紀塊
js         執行指定的JavaScript文件(多個)
license    顯示許可信息
makecache  生成ethash驗證緩存(用於測試)
makedag    生成ethash 挖礦DAG(用於測試)
monitor    監控和可視化節點指標
removedb   刪除區塊鏈和狀態數據庫
version    打印版本號
wallet     管理Ethereum預售錢包
help,h     顯示一個命令或幫助一個命令列表

ETHEREUM選項:

--config value          TOML 配置文件
--datadir “xxx”         數據庫和keystore密鑰的數據目錄
--keystore              keystore存放目錄(默認在datadir內)
--nousb                 禁用監控和管理USB硬件錢包
--networkid value       網絡標識符【整型, 1=Frontier (主網), 2=Morden (棄用), 3=Ropsten, 4=Rinkeby】(默認: 1)
--testnet               Ropsten網絡:預先配置的POW(proof-of-work)測試網絡
--rinkeby               Rinkeby網絡: 預先配置的POA(proof-of-authority)測試網絡
--syncmode "fast"       同步模式 ("fast", "full", or "light") 對應代碼中的 syncmode 哦
--ethstats value        上報ethstats service  URL (nodename:secret@host:port)
--identity value        自定義節點名
--lightserv value       允許LES請求時間最大百分比(0 – 90)(默認值:0) 
--lightpeers value      最大LES client peers數量(默認值:20)
--lightkdf              在KDF強度消費時降低key-derivation RAM&CPU使用

開發者(模式)選項:

--dev               使用POA共識網絡,默認預分配一個開發者賬戶並且會自動開啓挖礦。
--dev.period value  開發者模式下挖礦週期 (0 = 僅在交易時) (默認: 0)

ethash (共識) 選項:

--ethash.cachedir                        ethash驗證緩存目錄(默認 = datadir目錄內)
--ethash.cachesinmem value               在內存保存的最近的ethash緩存個數  (每個緩存16MB ) (默認: 2)
--ethash.cachesondisk value              在磁盤保存的最近的ethash緩存個數 (每個緩存16MB) (默認: 3)
--ethash.dagdir ""                       存ethash DAGs目錄 (默認 = 用戶hom目錄)
--ethash.dagsinmem value                 在內存保存的最近的ethash DAGs 個數 (每個1GB以上) (默認: 1)
--ethash.dagsondisk value    

 

交易池選項:

--txpool.nolocals            爲本地提交交易禁用價格豁免
--txpool.journal value       本地交易的磁盤日誌:用於節點重啓 (默認: "transactions.rlp")
--txpool.rejournal value     重新生成本地交易日誌的時間間隔 (默認: 1小時)
--txpool.pricelimit value    加入交易池的最小的gas價格限制(默認: 1)
--txpool.pricebump value     價格波動百分比(相對之前已有交易) (默認: 10)
--txpool.accountslots value  每個帳戶保證可執行的最少交易槽數量  (默認: 16)
--txpool.globalslots value   所有帳戶可執行的最大交易槽數量 (默認: 4096)
--txpool.accountqueue value  每個帳戶允許的最多非可執行交易槽數量 (默認: 64)
--txpool.globalqueue value   所有帳戶非可執行交易最大槽數量  (默認: 1024)
--txpool.lifetime value      非可執行交易最大入隊時間(默認: 3小時)

性能調優的選項:

--cache value                分配給內部緩存的內存MB數量,緩存值(最低16 mb /數據庫強制要求)(默認:128)
--trie-cache-gens value      保持在內存中產生的trie node數量(默認:120)

帳戶選項:

--unlock value              需解鎖賬戶用逗號分隔
--password value            用於非交互式密碼輸入的密碼文件

API和控制檯選項:

--rpc                       啓用HTTP-RPC服務器
--rpcaddr value             HTTP-RPC服務器接口地址(默認值:“localhost”)
--rpcport value             HTTP-RPC服務器監聽端口(默認值:8545)
--rpcapi value              基於HTTP-RPC接口提供的API
--ws                        啓用WS-RPC服務器
--wsaddr value              WS-RPC服務器監聽接口地址(默認值:“localhost”)
--wsport value              WS-RPC服務器監聽端口(默認值:8546)
--wsapi  value              基於WS-RPC的接口提供的API
--wsorigins value           websockets請求允許的源
--ipcdisable                禁用IPC-RPC服務器
--ipcpath                   包含在datadir裏的IPC socket/pipe文件名(轉義過的顯式路徑)
--rpccorsdomain value       允許跨域請求的域名列表(逗號分隔)(瀏覽器強制)
--jspath loadScript         JavaScript加載腳本的根路徑(默認值:“.”)
--exec value                執行JavaScript語句(只能結合console/attach使用)
--preload value             預加載到控制檯的JavaScript文件列表(逗號分隔)

網絡選項:

--bootnodes value    用於P2P發現引導的enode urls(逗號分隔)(對於light servers用v4+v5代替)
--bootnodesv4 value  用於P2P v4發現引導的enode urls(逗號分隔) (light server, 全節點)
--bootnodesv5 value  用於P2P v5發現引導的enode urls(逗號分隔) (light server, 輕節點)
--port value         網卡監聽端口(默認值:30303)
--maxpeers value     最大的網絡節點數量(如果設置爲0,網絡將被禁用)(默認值:25)
--maxpendpeers value    最大嘗試連接的數量(如果設置爲0,則將使用默認值)(默認值:0)
--nat value             NAT端口映射機制 (any|none|upnp|pmp|extip:<IP>) (默認: “any”)
--nodiscover            禁用節點發現機制(手動添加節點)
--v5disc                啓用實驗性的RLPx V5(Topic發現)機制
--nodekey value         P2P節點密鑰文件
--nodekeyhex value      十六進制的P2P節點密鑰(用於測試)

礦工選項:

--mine                  打開挖礦
--minerthreads value    挖礦使用的CPU線程數量(默認值:8)
--etherbase value       挖礦獎勵地址(默認=第一個創建的帳戶)(默認值:“0”)
--targetgaslimit value  目標gas限制:設置最低gas限制(低於這個不會被挖?) (默認值:“4712388”)
--gasprice value        挖礦接受交易的最低gas價格
--extradata value       礦工設置的額外塊數據(默認=client version)

GAS價格選項:

--gpoblocks value      用於檢查gas價格的最近塊的個數  (默認: 10)
--gpopercentile value  建議gas價參考最近交易的gas價的百分位數,(默認: 50)

虛擬機的選項:

--vmdebug        記錄VM及合約調試信息

日誌和調試選項:

--metrics            啓用metrics收集和報告
--fakepow            禁用proof-of-work驗證
--verbosity value    日誌詳細度:0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3)
--vmodule value      每個模塊詳細度:以 <pattern>=<level>的逗號分隔列表 (比如 eth/*=6,p2p=5)
--backtrace value    請求特定日誌記錄堆棧跟蹤 (比如 “block.go:271”)
--debug                     突出顯示調用位置日誌(文件名及行號)
--pprof                     啓用pprof HTTP服務器
--pprofaddr value           pprof HTTP服務器監聽接口(默認值:127.0.0.1)
--pprofport value           pprof HTTP服務器監聽端口(默認值:6060)
--memprofilerate value      按指定頻率打開memory profiling    (默認:524288)
--blockprofilerate value    按指定頻率打開block profiling    (默認值:0)
--cpuprofile value          將CPU profile寫入指定文件
--trace value               將execution trace寫入指定文件

whisper 實驗選項:

--shh                        啓用Whisper
--shh.maxmessagesize value   可接受的最大的消息大小 (默認值: 1048576)
--shh.pow value              可接受的最小的POW (默認值: 0.2)

棄用選項:

--fast     開啓快速同步
--light    啓用輕客戶端模式

其他選項:

–help, -h    顯示幫助

 

這時候我們就可以在geth的console 輸入 json-rpc的api來操作 geth節點了。好了,現在我們先來創建下兩個賬戶:

我們可以看到當前用戶的餘額分別是 0 【這裏說的餘額是指 ether】。

 

好了,上述命令行基本是通過 js 的操作api操作的,目前私有節點已經被啓動了,下面我們來進入本次文章的重點,用go項目和這個 geth節點進行交互,及使用abigen 來把 *.sol 文件編譯成 *.go 文件,來在go項目中操作合約實例。

編寫合約文件 *.sol

首先,我們來編寫一個智能合約文件:

pragma solidity ^0.4.16;


contract Token{
    
    uint256 public totalSupply;

    function balanceOf(address _owner) public constant returns (uint256 balance);
    function transfer(address _to, uint256 _value) public returns (bool success);
    function transferFrom(address _from, address _to, uint256 _value) public returns   (bool success);

    function approve(address _spender, uint256 _value) public returns (bool success);

    function allowance(address _owner, address _spender) public constant returns (uint256 remaining);

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

/**
自定義的GAVC代幣
 */
contract GavinToken is Token {

    /**
    代幣名稱,例如"Gavin token"
     */
    string public name;  
    /**
    返回token使用的小數點後幾位。比如如果設置爲3,就是支持0.001表示.
    */                 
    uint8 public decimals; 
    /**
    token簡稱, GAVC
    */              
    string public symbol;               

    mapping (address => uint256) balances;
    mapping (address => mapping (address => uint256)) allowed;
    
    /**
    構造方法
     */
    function GavinToken(uint256 _initialAmount, string _tokenName, uint8 _decimalUnits, string _tokenSymbol) public {
        // 設置初始總量
        totalSupply = _initialAmount * 10 ** uint256(_decimalUnits); 
        /**
        初始token數量給予消息發送者,因爲是構造函數,所以這裏也是合約的創建者        
        */
        balances[msg.sender] = totalSupply; 
        name = _tokenName;                   
        decimals = _decimalUnits;          
        symbol = _tokenSymbol;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        //默認totalSupply 不會超過最大值 (2^256 - 1).
        //如果隨着時間的推移將會有新的token生成,則可以用下面這句避免溢出的異常
        require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]);
        require(_to != 0x0);
        //從消息發送者賬戶中減去token數量_value
        balances[msg.sender] -= _value;
        //往接收賬戶增加token數量_value
        balances[_to] += _value;
        //觸發轉幣交易事件
        Transfer(msg.sender, _to, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value);
        //接收賬戶增加token數量_value
        balances[_to] += _value;
        //支出賬戶_from減去token數量_value
        balances[_from] -= _value; 
        //消息發送者可以從賬戶_from中轉出的數量減少_value
        allowed[_from][msg.sender] -= _value;
        //觸發轉幣交易事件
        Transfer(_from, _to, _value);
        return true;
    }

    function balanceOf(address _owner) public constant returns (uint256 balance) {
        return balances[_owner];
    }

    function approve(address _spender, uint256 _value) public returns (bool success) { 
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }

    function allowance(address _owner, address _spender) public constant returns (uint256 remaining) {
        //允許_spender從_owner中轉出的token數
        return allowed[_owner][_spender];
    }

    
}

然後我們去到go-ethereum 源碼的 go-ethereum/cmd/abigen 目錄下,執行 go install 編譯出 abigen 可執行文件,且加入到 GOPATH/bin 目錄中。

然後,我們使用 solc 先分別把*sol 編譯成 abi 【應用程序二進制接口規範】及bin【十六進制字節碼】文件:

solc GavinToken.sol -o filedir --abi
solc GavinToken.sol -o filedir --bin

然後,我們再根據abi及bin文件使用 abigen 編譯出go文件:

abigen --abi filedir/GavinToken_sol_GavinToken.abi --bin filedir/GavinToken_sol_GavinToken.bin --pkg mytoken --out gavinToken.go

在這裏需要注意:

【注意】:網上的sol直接生成go文件:abigen --sol GavinToken.sol --pkg mytoken --out gavinToken.go 是有問題的可能會報一下錯誤: 
Failed to build Solidity contract: solc: exit status 1
Invalid option selected, must specify either --bin or --abi
或者
Failed to build Solidity contract: exit status 7

下面我們看一看如何使用ethClient 及 生成的 tokengo 文件:【核心代碼如下所示,完整代碼放置我的github了,go的框架是使用了 iris 搭建的】

package service

import (
	"blockclient/src/server/entity"
	"blockclient/src/server/common/stacode"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	myCommon "blockclient/src/server/common"
	"log"
	"strings"
	"context"
	"math/big"
	"fmt"
	"time"
	"bytes"
	"blockclient/src/server/tokens"
	//"encoding/json"
)

type EthService struct {
}


// get balance of a account by account's address
func (es *EthService) GetBalanceByAddress(addresses, num string) *entity.Result {
	if "" == strings.TrimSpace(addresses) {
		return errcode.REQUEST_PARAM_ERR.Result(nil)
	}
	//balance, err := myCommon.EthConn.BalanceAt(context.Background(),  common.HexToAddress(address), big.NewInt(number))
	block, err := myCommon.EthConn.BlockByNumber(context.Background(), nil)
	if nil != err {
		log.Println("get blockNumber err:", err)
		return errcode.SYSTEMBUSY_ERROR.Result(nil)
	}else {
		log.Println("blockNumber:", block.Number().String())
	}
	resMap := make(map[string]interface{}, 0)
	for _, address := range strings.Split(addresses, ",") {
		balance, err := myCommon.EthConn.BalanceAt(context.Background(),  common.HexToAddress(address), nil)
		if nil != err {
			log.Println("Failed to query ether balance by address: %v", err)
			log.Println("Failed to query ether balance by address: ", err.Error())
			return errcode.SYSTEMBUSY_ERROR.Result(nil)
		}
		resMap[address] = balance
	}
	return errcode.SUCCESS.Result(resMap)
}

func (es *EthService) DeployContract(total int64, decimals uint8, name, symbol, key, passphrase string) *entity.Result {

	// 【說明】
	// 安裝solidity編譯器solc (爲 solcjs): sudo npm install -g solc
	// 設置鏈接至 /usr/bin/中 (雖然可以用solcjs已經設置在 /usr/local/bin中了,但是有些地方需要以solc 啓動,如abigen就是):sudo ln -s /usr/local/lib/node_modules/solc/solcjs /usr/bin/solc
	// 查看下是否可以以solc訪問: solc --help
	// 進入到項目中的  *sol 文件所在目錄: cd ~/go-workspace/src/blockclient/src/server/tokens/
	// 生成abi文件: solc GavinToken.sol -o filedir --abi
	// 生成bin文件: solc GavinToken.sol -o filedir --bin
	// 生成go文件: abigen --abi filedir/GavinToken_sol_GavinToken.abi --bin filedir/GavinToken_sol_GavinToken.bin --pkg mytoken --out gavinToken.go
	// 【注意】:網上的sol直接生成go文件是有問題的可能會報一下錯誤: abigen --sol GavinToken.sol --pkg mytoken --out gavinToken.go
	// Failed to build Solidity contract: solc: exit status 1
	// Invalid option selected, must specify either --bin or --abi
	// 或者
	// Failed to build Solidity contract: exit status 7


	if "" == strings.TrimSpace(passphrase) {
		return errcode.REQUEST_PARAM_ERR.ResultWithMsg("must have pwd")
	}

	auth, err := bind.NewTransactor(strings.NewReader(key), passphrase)
	if nil != err {
		log.Panic("Failed to create authorized transactor: %v", err)
	}
	// 發佈合約返回 合約地址、 當前交易信息、 和當前的合約實例(操作合約abi用)
	address, tx, token, err := mytoken.DeployMytoken(auth, myCommon.EthConn, big.NewInt(total), name, decimals, symbol)
	if nil != err {
		log.Panic("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())

	// 等待挖礦確認
	addressAfterMined, err := bind.WaitDeployed(context.Background(), myCommon.EthConn, tx)
	if nil != err {
		log.Panic("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.Panic("mined address :%s,before mined address:%s", addressAfterMined, address)
	}

	// token的Name
	nameRes, err := token.Name(&bind.CallOpts{Pending: true})
	if nil != err {
		log.Panic("Failed to retrieve pending name: %v", err)
	}
	totalRes, err := token.TotalSupply(&bind.CallOpts{Pending: true})
	if nil != err {
		log.Panic("Failed to retrieve pending total: %v", err)
	}
	symbolRes, err := token.Symbol(&bind.CallOpts{Pending: true})
	if nil != err {
		log.Panic("Failed to retrieve pending symbol: %v", err)
	}
	decimalsRes, err := token.Decimals(&bind.CallOpts{Pending: true})
	if nil != err {
		log.Panic("Failed to retrieve pending decimals: %v", err)
	}

	res := make(map[string]interface{}, 0)
	res["txHAsh"] = tx.Hash()
	res["contractAddress"] = address.String()
	res["Name"] = nameRes
	res["total"] = totalRes.String()
	res["symbol"] = symbolRes
	res["decimals"] = decimalsRes

	return errcode.SUCCESS.Result(res)
}

func (es *EthService) TokenTransfer(contractAddress, to, key, pwd string, value *big.Int) *entity.Result {
	//go es.SubcribeEvents(contractAddress, to)
	token, err := mytoken.NewMytoken(common.HexToAddress(contractAddress), myCommon.EthConn)
	if err != nil {
		log.Panic("Failed to instantiate a Token contract: %v", err)
	}
	toAddress := common.HexToAddress(to)
	toPreval, _ := token.BalanceOf(nil, toAddress)

	// Create an authorized
	auth, err := bind.NewTransactor(strings.NewReader(key), pwd)
	if err != nil {
		log.Panic("Failed to create authorized transactor: %v", err)
	}

	// Create a transfer
	tx, err := token.Transfer(auth, toAddress, value)
	if err != nil {
		log.Panic("Failed to request token transfer: %v", err)
	}

	//var keyStorage entity.KeyStorage
	//json.Unmarshal([]byte(key), &keyStorage)
	//from := keyStorage.Address
	// start-up transfer event subcribe


	fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
	startTime := time.Now()
	fmt.Printf("TX start @:%s", time.Now())

	// waiting mining
	receipt, err := bind.WaitMined(context.Background(), myCommon.EthConn, tx)
	if err != nil {
		log.Panic("tx mining error:%v\n", err)
	}
	fmt.Printf("tx mining take time:%s\n", time.Now().Sub(startTime))

	val, _ := token.BalanceOf(nil, toAddress)

	res := make(map[string]interface{}, 0)
	res["toPreval"] = toPreval
	res["tx"] = tx.Hash()
	res["toVal"] = val
	res["receipt.TxHash"] = receipt.TxHash
	recp, _ := receipt.MarshalJSON()
	res["receiptJson"] = string(recp)
	return errcode.SUCCESS.Result(res)
}

func (es *EthService) QueryTokenBalance(contractAddress, eoas string) *entity.Result {
	if "" == strings.TrimSpace(contractAddress) || "" == strings.TrimSpace(eoas) {
		return errcode.REQUEST_PARAM_ERR.ResultWithMsg("contract and from  must is not empty")
	}
	token, err := mytoken.NewMytoken(common.HexToAddress(contractAddress), myCommon.EthConn)
	if err != nil {
		log.Panic("Failed to instantiate a Token contract: %v", err)
	}
	resMap := make(map[string]interface{}, 0)
	for _, from := range strings.Split(eoas, ",") {
		val, _ := token.BalanceOf(nil, common.HexToAddress(from))
		resMap[from] = val.String()
	}
	return errcode.SUCCESS.Result(resMap)
}


func (es *EthService) SubcribeEvents(contractAddress, to string) {
	filter, err := mytoken.NewMytokenFilterer(common.HexToAddress(contractAddress), myCommon.EthConn)
	ch := make(chan *mytoken.MytokenTransfer, 10)
	//sub, err := filter.WatchTransfer(&bind.WatchOpts{}, ch, []common.Address{common.HexToAddress(from)}, []common.Address{common.HexToAddress(to)})
	sub, err := filter.WatchTransfer(nil, ch, nil, []common.Address{common.HexToAddress(to)})
	if err != nil {
		log.Panic("watch transfer err %s", err)
	}
	go func() {
		for {
			select {
			case <-sub.Err():
				return
			case e := <-ch:
				log.Printf("new transfer event from %s to %s value=%s,at %d",
					e.From.String(), e.To.String(), e.Value, e.Raw.BlockNumber)
			}
		}
	}()
}

下面我們騷微來分析下代碼。

首先,我這裏只寫了4個簡單的小接口,分別是,查詢賬戶的ethe餘額、查詢賬戶的token餘額、發起一筆token交易、發佈一個合約等四個接口的demo。

由於我們需要操作合約,所以我們不止是需要使用到ethclient還需要使用到abigen先把sol文件編譯成go合約文件加到項目中以便操作合約的abi。關於abigen的使用上面我們已經有說明了。

首先,我們看下我們的router層:

可以看出,是隻定義了四個接口,然後我們再看看controller層:

package controller

import (
	"gopkg.in/kataras/iris.v5"
	"blockclient/src/server/service"
	"encoding/json"
	"blockclient/src/server/entity"
	"blockclient/src/server/common/stacode"
	"math/big"
)

type EthController struct {
}

var ethService = service.EthService{}

// query account's ether balance
func (ec *EthController) GetBlanceByAddress(ctx *iris.Context) {
	ctx.JSON(iris.StatusOK, ethService.GetBalanceByAddress(ctx.URLParam("addresses"), ctx.URLParam("num")))
}

// deploy a contract
func (ec *EthController) DeployContract(ctx *iris.Context){
	var res entity.DeployEntity
	if err := json.Unmarshal(ctx.PostBody(), &res); nil != err {
		ctx.JSON(iris.StatusOK, errcode.REQUEST_PARAM_ERR.Result(err))
		return
	}
	key, err :=  json.Marshal(res.Key)
	if nil != err {
		ctx.JSON(iris.StatusOK, errcode.REQUEST_PARAM_ERR.Result("parse key err: " + err.Error()))
		return
	}
	ctx.JSON(iris.StatusOK, ethService.DeployContract(res.Total, res.Decimals, res.Name, res.Symbol, string(key), res.Pwd))
}

func (ec *EthController) TokenTransfer (ctx *iris.Context) {
	var res entity.ToekenTransferEntity
	if err := json.Unmarshal(ctx.PostBody(), &res); nil != err {
		ctx.JSON(iris.StatusOK, errcode.REQUEST_PARAM_ERR.Result(err))
		return
	}
	key, err :=  json.Marshal(res.Key)
	if nil != err {
		ctx.JSON(iris.StatusOK, errcode.REQUEST_PARAM_ERR.Result("parse key err: " + err.Error()))
		return
	}
	ctx.JSON(iris.StatusOK, ethService.TokenTransfer(res.Address, res.To, string(key), res.Pwd, big.NewInt(int64(res.Value))))
}

func (ec *EthController) QueryTokenBalance (ctx *iris.Context) {
	ctx.JSON(iris.StatusOK, ethService.QueryTokenBalance(ctx.URLParam("contract"), ctx.URLParam("eoas")))
}

其中,有部分controller使用到了自定義的一些請求入參的entity:

package entity


type DeployEntity struct {
	Decimals 	uint8 		`json:"decimals"`
	Total		int64		`json:"total"`
	Name 		string		`json:"name"`
	Symbol 		string 		`json:"symbol"`

	// passphrase
	Pwd 		string 		`json:"pwd"`


	Key 		KeyStorage	`json:"key"`
}

type ToekenTransferEntity struct {
	To 	string 		`json:"to"`
	Pwd 	string 		`json:"pwd"`
	Address string 		`json:"address"`
	Value 	int 		`json:"value"`
	Key 	KeyStorage 	`json:"key"`
}

// Keystorage context
type KeyStorage struct{
	Address		string		`json:"address"`
	Crypto		struct{
		Cipher		string		`json:"cipher"`
		Ciphertext	string 		`json:"ciphertext"`
		Cipherparams	struct{
			Iv 	string 		`json:"iv"`
		}		`json:"cipherparams"`
		Kdf 		string		`json:"kdf"`
		Kdfparams 	struct{
			Dklen		int	`json:"dklen"`
			N 		int 	`json:"n"`
			P 		int 	`json:"p"`
			R 		int 	`json:"r"`
			Salt 		string 	`json:"salt"`

		}		`json:"kdfparams"`
		Mac 		string 		`json:"mac"`

	}		`json:"crypto"`

	Id 		string 		`json:"id"`
	Version 	int 		`json:"version"`

}

裏面有些請求entity要求我們傳入 keystorage裏面的內容,對應到Key字段。service部分就不做解釋了,自己看就ok!下面我們來看看本地使用Postman來測試接口:

查詢入參的賬戶的ether餘額的

 

發佈合約

 

調用token交易:

查詢token餘額的:

好了,以上我們就可以使用原生的 geth的RPC API 和geth節點交互查詢ether餘額、當前區塊數目、區塊的Hash值等等,也可以操作某個合約中的方法【其實每次調用合約的時候都需要我們入參合約的地址來實例化合約,而合約的go文件其實就相當於合約的abi了,這和在web3.js裏面操作合約時需知道合約的地址+合約的abi是一個道理的】,那麼,今天我就寫到這裏了,祝大家國慶剩下的時間繼續耍嗨!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章