【轉載請標明出處】https://blog.csdn.net/qq_25870633/article/details/82931782
首先,我們需要在本地搭建一個 geth的節點。
Geth的安裝:
下載Geth源碼及安裝Geth
- 使用 go get -v github.com/ethereum/go-ethereum 下載,或者使用 go clone [email protected]:ethereum/go-ethereum.git 下載。
- 在根目錄執行 make geth 進行編譯安裝,然後需要手動把生成的geth加入到環境變量中,或者加入到 /usr/local/bin 中 。
- 或者 在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是一個道理的】,那麼,今天我就寫到這裏了,祝大家國慶剩下的時間繼續耍嗨!