【我的区块链之路】- 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

 

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第1张图片

 

上述命令的意思是,读取genesis.json文件,根据其中的内容,将创世区块写入到区块链中。当我们看到有 Successfully wrote genesis state 等日志输出时,代表初始化私有区块链成功。

我们进入 自定义的 genesis-block 目录,可以看到如下信息:

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第2张图片

其中,geth/chaindata 中存放的是区块数据,geth/lightchaindata 是 轻节点 所存放的区块头信息,keystore 中存放的是账户数据。

 

启动私有链geth节点:

在初始化完成后,我们需要启动私有链节点,在命令行输入:【注意需要在 根目录输入】

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

即可启动本地私有链节点。 

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第3张图片

然后,我们看到当前目录的结构是这样纸的:

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第4张图片

我们这里也顺便讲一讲 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:) (默认: “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      每个模块详细度:以 =的逗号分隔列表 (比如 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节点了。好了,现在我们先来创建下两个账户:

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第5张图片

我们可以看到当前用户的余额分别是 0 【这里说的余额是指 ether】。

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第6张图片

 

好了,上述命令行基本是通过 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层:

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第7张图片

可以看出,是只定义了四个接口,然后我们再看看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余额的

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第8张图片

 

发布合约

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第9张图片

 

调用token交易:

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第10张图片

查询token余额的:

【我的区块链之路】- go连接以太坊客户端Geth及调用合约_第11张图片

好了,以上我们就可以使用原生的 geth的RPC API 和geth节点交互查询ether余额、当前区块数目、区块的Hash值等等,也可以操作某个合约中的方法【其实每次调用合约的时候都需要我们入参合约的地址来实例化合约,而合约的go文件其实就相当于合约的abi了,这和在web3.js里面操作合约时需知道合约的地址+合约的abi是一个道理的】,那么,今天我就写到这里了,祝大家国庆剩下的时间继续耍嗨!

 

你可能感兴趣的:(区块链,以太坊,abigen,ethClient,geth,私有链)