以太坊实践整理(二)Geth客户端

Geth 以太坊客户端众多,基于各种语言开发的都有,这与区块链崇尚开放的价值观有关。Geth(全称go-ethereum)是以太坊官方社区开发的客户端,是客户端里的领头羊。我们可以使用Geth命令搭建以太坊私链,也可以基于Geth提供的交互式命令控制台,与以太坊网络环境进行交互。
Geth安装 Geth是基于GO语言编写的客户端,需要先安装goland。
安装Go语言环境
下载二进制包:go1.4.linux-amd64.tar.gz;
将下载的二进制包解压至 /usr/local目录:

$ tar -C /usr/local -zxvf go1.17.linux-amd64.tar.gz

设置环境变量:
$ export PATH=$PATH:/usr/local/go/bin

生效环境变量:
$ source /etc/profile

验证:
$ go env

源码编译安装geth
下载源码:
$ cd /usr/local $ git clone https://github.com/ethereum/go-ethereum.git

编译源码:
$ cd go-ethereum $ make geth

国内用户编译过程可能无法访问proxy.golang.org,可以设置代理后再执行:
$ go env -w GOPROXY=https://goproxy.cn $ make geth

设置环境变量:
export PATH=$PATH:/usr/local/go-ethereum/build/bin

生效环境变量:
$ source /etc/profile

验证:
$ geth version

Geth启动节点 连接主链网络
启动一个节点连接到以太坊主链,并进入JavaScript交互式控制台:
$ geth console

执行命令后,输入如下:
Welcome to the Geth JavaScript console!instance: Geth/v1.10.8-unstable-dfeb2f7e-20210823/darwin-amd64/go1.17 at block: 0 (Thu Jan 01 1970 08:00:00 GMT+0800 (CST)) datadir: /Users/zhutx/Library/Ethereum modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0To exit, press ctrl-d

过一会儿后,控制台开始不断地输出信息,表示节点正在以快速同步(Fast-Sync)模式,下载最新区块链数据与状态。从刚开始执行命令时的输出里可以看到,数据默认存储在/Users/zhutx/Library/Ethereum目录下。
在主链上进行开发测试是不切实际的,现在你可以停掉节点了。节点所下载的区块链数据会迅速占据好些存储空间,可以删除该目录
连接测试网络
程序在开发阶段总是需要进行测试的,我们可以启动节点并连接至以太坊测试网络:
$ mkdir testdir // 连接Ropsten测试网络,指定存储目录为testdir $ geth --ropsten --datadir "./testdir" console

如果你连接测试网络进行开发,相关操作还是需要支付燃料(Gas)费的,除了挖矿让账户获得以太币外,我们也可以直接使用提供水龙头服务的网站(如:https://faucet.ropsten.be/),输入自己测试链的账户地址,直接获取以太币(测试链的以太币不具备实际价值)。
geth有很多参数,常用的后续还会介绍到几个,这里就不一一举例了,可以自行了解(geth --help)。
Geth搭建私链 利用Geth我们还可以搭建自己的以太坊私链。
创建数据存储目录
$ mkdir blockchain $ cd blockchain

编写创世区块配置
$ vim gensis.json

{ "config": { "chainId": 666, "homesteadBlock": 0, "eip150Block": 0, "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "ethash": {} }, "nonce": "0x0", "timestamp": "0x5ddf8f3e", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x47b760", "difficulty": "0x00002", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": {}, "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" }

创世区块参数说明【TODO】:
参数 描述
初始化区块链
// 新建一个目录用来存放区块链数据 $ mkdir db // 初始化区块链 $ geth --datadir "./db" init gensis.json

初始化成功后,会在db目录下生产geth和keystore目录,此时目录结构如下:
blockchain ├── db │├── geth ││├── chaindata │││├── 000001.ldb │││├── CURRENT │││├── LOCK │││├── LOG │││└── MANIFEST-000000 ││├── lightchaindata │││├── 000001.log │││├── CURRENT │││├── LOCK │││├── LOG │││└── MANIFEST-000000 │└── keystore └── gensis.jso

启动私有链
$ geth --rpc --rpcport 8545 --rpccorsdomain "*" --datadir "./db" --port 30303 --rpcapi "eth,net,web3,personal,admin,shh,txpool,debug,miner" --networkid 100000 --rpcaddr=0.0.0.0 --nodiscover --allow-insecure-unlock console 2>> eth.log

相关启动参数可以自行了解下
Geth的JavaScript控制台 在Geth的JavaScript控制台内置了一些对象,通过这些对象我们可以很方便地与以太坊交互:
  • eth:提供了操作区块链相关的方法
  • net:提供了查看p2p网络状态的方法
  • admin:提供了管理节点相关的方法
  • miner:提供启动和停止挖矿的方法
  • personal:提供了管理账户的方法
  • txpool:提供了查看交易内存池的方法
  • web3:除了包含以上对象中的方法外,还包含一些单位换算的方法
我们在上面搭建的私链上,以转账业务去演示下JavaScript控制台的部分操作,做个了解即可:
0.进入JavaScript Console环境
如果启动节点时未输入console,你可以单独通过attach命令,进入节点的JavaScript命令环境:
cd blockchain $ geth --datadir "./db" attach ipc:./db/geth.ipc

1. 新建账户
传入账户密码,执行成功返回公钥:
> personal.newAccount("123456") "0xf05ed6c1bab6800d94ae3af4471b77caf07860f0"

生成的账户文件在keystore文件夹下。我们执行两次,生成2个账号用于转账备用。
2. 查看账户
> eth.accounts ["0xf05ed6c1bab6800d94ae3af4471b77caf07860f0", "0x8efa17c5646c60d4b67d118445f2b9614d9ea3e7"]

3. 查看余额
> balance = web3.fromWei(eth.getBalance(eth.accounts[0]), "ether") 0

eth.getBalance(账户公钥),返回账户的余额,单位为wei,web3.fromWei将wei转换成ether。
4. 挖矿
为了获取以太币,需要先开启挖矿,我们把第一个账号设置为挖矿账户:
> miner.setEtherbase(eth.accounts[0]) true

查看是否设置成功:
> eth.coinbase "0xf05ed6c1bab6800d94ae3af4471b77caf07860f0"

开启挖矿:
> miner.start(1) null

先挖一会儿,确保账户有余额后先暂停挖矿,等交易需要打包区块的时候再打开。
在区块链领域,转账、部署智能合约、调用智能合约等操作都是交易。并非狭义的交易概念
关闭挖矿:
> miner.stop()

再次查看余额:
> balance = web3.fromWei(eth.getBalance(eth.accounts[0]), "ether") 234

现在账户上有234以太币了,转账交易之前需要先解锁账户,否则会报错。
5. 解锁账户
> personal.unlockAccount(eth.accounts[0], "123456") true

6. 交易
把零头34转给另一个账户:
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(34, "ether")}) "0x47872d357a318f0213e63d40311a990cb0d467ad2bb79a6d8c82a9b667a079b9"

向区块链网络中发送一笔转账交易,返回交易hash,此时的交易正在交易池中等待被打包。
查看交易池状态:
> txpool.status { pending: 1, queued: 0 }

pending表示已提交但还未被处理的交易,显示有1个交易等待打包。
查看pending交易详情:
> txpool.inspect.pending { 0xf05ED6C1baB6800D94Ae3af4471B77caf07860f0: { 0: "0x8eFA17c5646c60D4B67D118445f2B9614D9Ea3e7: 34000000000000000000 wei + 21000 gas × 1000000000 wei" } }

为了使交易被处理,必须开启挖矿。为了方便分析,让挖到一个区块之后就停止挖矿:
> miner.start(1); admin.sleepBlocks(1); miner.stop();

INFO [08-24|02:29:13.504] Updated mining threadsthreads=1 INFO [08-24|02:29:13.504] Transaction pool price threshold updated price=1,000,000,000 INFO [08-24|02:29:13.505] Commit new mining worknumber=118 sealhash=a408a5..244a99 uncles=0 txs=0 gas=0 fees=0 elapsed="146.423μs" INFO [08-24|02:29:13.505] Commit new mining worknumber=118 sealhash=ed24ca..e08e23 uncles=0 txs=1 gas=21000 fees=2.1e-05 elapsed="350.824μs" INFO [08-24|02:29:15.927] Successfully sealed new blocknumber=118 sealhash=ed24ca..e08e23 hash=c0b43c..28c921 elapsed=2.422s INFO [08-24|02:29:15.927]mined potential blocknumber=118 hash=c0b43c..28c921 INFO [08-24|02:29:15.927] Commit new mining worknumber=119 sealhash=e9df41..7397c0 uncles=0 txs=0 gas=0fees=0elapsed="187.308μs"

分别查看转出方和转让方的余额:
> balance = web3.fromWei(eth.getBalance(eth.accounts[0]), "ether") 202

> balance = web3.fromWei(eth.getBalance(eth.accounts[1]), "ether") 34

可以看到转入账户eth.accounts[1]有34个以太币了,而转出账户eth.accounts[0]账户余额并非200,因为该账号有挖矿收益;
7. 区块
查看交易详情:
> eth.getTransaction("0x47872d357a318f0213e63d40311a990cb0d467ad2bb79a6d8c82a9b667a079b9") { blockHash: "0xc0b43c476fc4406b4db342beed0ed0b0d4bad02330b2b353a354b558bc28c921", blockNumber: 118, from: "0xf05ed6c1bab6800d94ae3af4471b77caf07860f0", gas: 21000, gasPrice: 1000000000, hash: "0x47872d357a318f0213e63d40311a990cb0d467ad2bb79a6d8c82a9b667a079b9", input: "0x", nonce: 0, r: "0xe1d9e0678c2a67c0fcec81cab407f20caca4ff617fbc697249409d3d78a74ef0", s: "0x6db176315eebaafc07c695549fccbfed0dbe1317a3ffbbf398d9c3aa6b8bb250", to: "0x8efa17c5646c60d4b67d118445f2b9614d9ea3e7", transactionIndex: 0, type: "0x0", v: "0x557", value: 34000000000000000000 }

上面的命令是查看交易发起时的详情,如果要查看交易被打包进区块时的详细信息,用以下命令:
> eth.getTransactionReceipt("0x47872d357a318f0213e63d40311a990cb0d467ad2bb79a6d8c82a9b667a079b9") { blockHash: "0xc0b43c476fc4406b4db342beed0ed0b0d4bad02330b2b353a354b558bc28c921", blockNumber: 118, contractAddress: null, // 如果是合约创建交易,则返回合约地址,其他情况返回null cumulativeGasUsed: 21000, // 累计话费的gas总值 effectiveGasPrice: 1000000000, from: "0xf05ed6c1bab6800d94ae3af4471b77caf07860f0", gasUsed: 21000, // 执行当前这个交易单独话费的gas logs: [], logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", status: "0x1", to: "0x8efa17c5646c60d4b67d118445f2b9614d9ea3e7", // 如果是合约创建交易,返回null transactionHash: "0x47872d357a318f0213e63d40311a990cb0d467ad2bb79a6d8c82a9b667a079b9", transactionIndex: 0, // 交易在区块里的序号 type: "0x0" }

以下是其他查询区块的命令:
查看当前区块总数:
> eth.blockNumber 118

查看最新区块:
> eth.getBlock('latest') { difficulty: 131886, extraData: "0xd683010a08846765746886676f312e3137856c696e7578", gasLimit: 5273553, gasUsed: 21000, hash: "0xc0b43c476fc4406b4db342beed0ed0b0d4bad02330b2b353a354b558bc28c921", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0xf05ed6c1bab6800d94ae3af4471b77caf07860f0", mixHash: "0x009c9034e94034344fc03dd6914d3753a8b98b8a3b0e3b5d11d473d9ad5a1402", nonce: "0x66f00bbbcd357270", number: 118, parentHash: "0xa475ef4ac3b9c1d9b8390e5044a1d11186c57b6c369756b642420f345b7ec82f", receiptsRoot: "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 649, stateRoot: "0x798af7bcb3bd31b18c955c05e694ae342d1db654681b1703b817ff6e02639d69", timestamp: 1629743353, totalDifficulty: 15897610, transactions: ["0x47872d357a318f0213e63d40311a990cb0d467ad2bb79a6d8c82a9b667a079b9"], transactionsRoot: "0x979e07128c057ff81e2eaaba45b3704be371ffcb794876f570f88e88e00cd313", uncles: [] }

eth.getBlock(blockNumber|blockHash))可以获取具体区块的信息。
Geth多节点组网 当前私链是单一节点(节点1),下面我们再弄一个节点(节点2)连接进来,组成一个最简以太坊私链网络。
节点2安装geth客户端后,进入到JavaScript控制台,输入以下命令获取节点2的信息:
> admin.nodeInfo { enode: "enode://5873af9deabd8f7e40878ce0e3caec0bb86c79cf9bd99781be2aaab8a61e3c24aded28102971a38a7cb0ff2749b3a1b3624232cc96ac4f76dc9f035232629652@127.0.0.1:30303?discport=0", enr: "enr:-Ja4QCuP0rodmA2imicnqbuZ14kPiMqPrX-Dp_5z8FU1l1PvcU0IKz4cynQppjEqVguSlU_Tn-UlVkaw_ehxcCsM0XoEg2V0aMfGhJPR68WAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQJYc6-d6r2PfkCHjODjyuwLuGx5z5vZl4G-Kqq4ph48JIRzbmFwwIN0Y3CCdl8", id: "37829b9d6cdb1610dca7ba713a1c0647d6474957c686a3b4967b7cd77f0d47a5", ip: "127.0.0.1", listenAddr: "[::]:30303", name: "Geth/v1.10.8-unstable-dfeb2f7e-20210823/linux-amd64/go1.17", ports: { discovery: 0, listener: 30303 }, protocols: { eth: { config: { byzantiumBlock: 0, chainId: 666, constantinopleBlock: 0, eip150Block: 0, eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", eip155Block: 0, eip158Block: 0, ethash: {}, homesteadBlock: 0, istanbulBlock: 0, petersburgBlock: 0 }, difficulty: 15897610, genesis: "0xd3d6bb893a6e274cab241245d5df1274c58d664fbb1bfd6e59141c2e0bc5304a", head: "0xc0b43c476fc4406b4db342beed0ed0b0d4bad02330b2b353a354b558bc28c921", network: 100000 }, snap: {} } }

实际上只用到节点2里的enode信息,可以直接获取enode:
> admin.nodeInfo.enode "enode://5873af9deabd8f7e40878ce0e3caec0bb86c79cf9bd99781be2aaab8a61e3c24aded28102971a38a7cb0ff2749b3a1b3624232cc96ac4f76dc9f035232629652@127.0.0.1:30303?discport=0"

在节点1处执行命令连接节点2:
127.0.0.1改成节点2的公网地址
> admin.addPeer("enode://5873af9deabd8f7e40878ce0e3caec0bb86c79cf9bd99781be2aaab8a61e3c24aded28102971a38a7cb0ff2749b3a1b3624232cc96ac4f76dc9f035232629652@127.0.0.1:30303?discport=0") true

连接成功后,节点2就会开始同步节点1的区块,同步完成后,任意一个节点开始挖矿,另一个节点会同步区块,向一个节点发送交易,另一个节点也会收到该笔交易。
【以太坊实践整理(二)Geth客户端】以下命令可以查看已连接的远程节点:
> admin.peers

    推荐阅读