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.0
To 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 threads threads=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 work number=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 work number=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 block number=118 sealhash=ed24ca..e08e23 hash=c0b43c..28c921 elapsed=2.422s
INFO [08-24|02:29:15.927] mined potential block number=118 hash=c0b43c..28c921
INFO [08-24|02:29:15.927] Commit new mining work number=119 sealhash=e9df41..7397c0 uncles=0 txs=0 gas=0 fees=0 elapsed="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的区块,同步完成后,任意一个节点开始挖矿,另一个节点会同步区块,向一个节点发送交易,另一个节点也会收到该笔交易。
以下命令可以查看已连接的远程节点:
> admin.peers