区块链:初探ETH私链搭建

0x00 ETH的前世今生

比特币开创了去中心化加密货币的先河,自诞生起,五年多的时间充分检验了区块链技术的可行性和安全性。比特币的区块链事实上是一套分布式的数据库,在其中加进一个符号比特币,并规定一套协议使得这个符号可以在数据库上安全地转移,并且无需信任第三方,这些特征的组合完美地构造了一个货币传输体系“比特币网络”。
然而比特币并不完美,其中协议的扩展性是一项不足,例如比特币网络里只有一种符号比特币,用户无法自定义另外的符号,这些符号可以是代表公司的股票,或者是债务凭证等,这就损失了一些功能。另外,比特币协议里使用了一套基于堆栈的脚本语言,这语言虽然具有一定灵活性,使得像多重签名这样的功能得以实现,然而却不足以构建更高级的应用,例如去中心化交易所等。以太坊从设计上就是为了解决比特币扩展性不足的问题。
以太坊(英文Ethereum)是一个开源的有智能合约功能的公共区块链平台,通过其专用加密货币以太币(Ether,简称“ETH”)提供去中心化的以太虚拟机(Ethereum Virtual Machine)来处理点对点合约。
以太坊的概念首次在2013至2014年间由程序员Vitalik Buterin受比特币启发后提出,大意为“下一代加密货币与去中心化应用平台”,在2014年通过ICO众筹开始得以发展。
截至2018年2月,以太币是市值第二高的加密货币,仅次于比特币。

0x01 初步环境部署

以下所有操作基于CentOS 7 x64 1908 2core 4GB 100G

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 安装go环境
$ yum -y install golang

# 下载geth
# https://geth.ethereum.org/downloads/
# 执行help显示如下内容则安装成功
$ geth -h
NAME:
geth - the go-ethereum command line interface

Copyright 2013-2019 The go-ethereum Authors

USAGE:
geth [options] command [command options] [arguments...]

VERSION:
1.9.14-stable-6d74d1e5

0x02 搭建私有链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 创建存放数据与配置的目录
$ mkdir -p /data/eth/{chaindata,config}

$ cat /data/eth/config/genesis.json
{
"config": {
"chainId": 10,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"ethash": {}
},
"nonce": "0x0000000000000042",
"timestamp": "0x00",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0xffffffff",
"difficulty": "0x20000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
初始化参数作用
mixhash与nonce配合用于挖矿,由上一个区块的一部分生成的hash。
noncenonce是一个64位的随机数,用于挖矿用的。他和mixhash的设置需要满足以太坊的官方条件
difficulty设置当前区块的难度,如果难度过大,cpu挖矿就很难,这里设置较小难度
alloc用来预置账号以及账号的以太币数量,因为私有链挖矿比较容易,所以我们不需要预置有币的账号,需要的时候自己创建即可以。
coinbase矿工的账号
timestamp设置创世块的时间戳
parentHash上一个区块的hash值,因为是创世块,所以这个值是0
extraData附加信息,随便填,可以填你的个性信息
gasLimit该值设置对GAS的消耗总量限制,用来限制区块能包含的交易信息总和,因为我们是私有链,所以填最大。

初始化:写入创世区块

准备好创世区块配置文件后,需要初始化区块链,将上面的创世区块信息写入到区块链中。首先要新建一个目录用来存放区块链数据,假设新建的数据目录为/data/eth/chaindata,genesis.json保存在/data/eth/config/中,此时目录结构应该是这样的:

1
2
3
eth
├── chaindata
└── config
初始化命令作用
Identity区块链的标示,随便填写,用于标示目前网络的名字
Init指定创世块文件的位置,并创建初始块
datadir设置当前区块链网络数据存放的位置
rpc启动rpc通信,可以进行智能合约的部署和调试
rpcportHTTP-RPC服务器监听端口(default: 8545)
rpcapi设置允许连接的rpc的客户端,默认只有eth,net,web3
networkid设置当前区块链的网络ID,用于区分不同的网络,是一个数字
console启动命令行模式,可以在Geth中执行命令
rpccorsdomain指定一个可以接收请求来源的以逗号间隔的域名列表(浏览器访问的话,要强制指定该选项)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 执行初始化命令:
$ geth --datadir /data/eth/chaindata init /data/eth/config/genesis.json
# 输出Successfully wrote genesis state则代表初始化成功。
# 初始化成功后数据目录生成geth和keystore两个文件夹,其中 geth/chaindata中存放的是区块数据,keystore中存放的是账户数据。

# 启动私有链节点并进入控制台
$ geth --datadir /data/eth/chaindata/ --networkid 666 console
Welcome to the Geth JavaScript console!

instance: Geth/v1.9.14-stable-6d74d1e5/linux-amd64/go1.14.2
at block: 0 (Thu Jan 01 1970 08:00:00 GMT+0800 (CST))
datadir: /data/eth/chaindata
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

# 这是一个交互式的Javascript执行环境,在这里面可以执行Javascript代码,其中>是命令提示符。在这个环境里也内置了一些用来操作以太坊的Javascript对象,可以直接使用这些对象。这些对象主要包括:

eth:包含一些跟操作区块链相关的方法
net:包含以下查看p2p网络状态的方法
admin:包含一些与管理节点相关的方法
miner:包含启动&停止挖矿的一些方法
personal:主要包含一些管理账户的方法
txpool:包含一些查看交易内存池的方法
web3:包含了以上对象,还包含一些单位换算的方法

# 其中常用命令有:

personal.newAccount():创建账户;
personal.unlockAccount():解锁账户;
eth.accounts:枚举系统中的账户;
eth.getBalance():查看账户余额,返回值的单位是 Wei(Wei - 是以太坊中最小货币面额单位,类似比特币中的聪,1 ether = 10^18 Wei);
eth.blockNumber:列出区块总数;
eth.getTransaction():获取交易;
eth.getBlock():获取区块;
miner.start():开始挖矿;
miner.stop():停止挖矿;
web3.fromWei():Wei 换算成以太币;
web3.toWei():以太币换算成 Wei;
txpool.status:交易池中的状态;
admin.addPeer():连接到其他节点;

0x03 探索JavaScript Console

进入以太坊Javascript Console后,就可以使用里面的内置对象做一些操作,这些内置对象提供的功能很丰富,比如查看区块和交易、创建账户、挖矿、发送交易、部署智能合约等。 接下来介绍几个常用功能,下面的操作中,前面带>的表示在Javascript Console中执行的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 前面只是搭建了私有链,并没有自己的账户,可以在js console中输入eth.accounts来验证:
> eth.accounts
[]

# 创建用户
# 接下来使用personal对象来创建一个账户
# 注意:此命令区分大小写。
> personal.newAccount()
Passphrase:
Repeat passphrase:
INFO [05-27|15:57:01.715] Your new key was generated address=0x3bd8f1aDf6fCe874B6FC004629cCdb30f16E5283
WARN [05-27|15:57:01.715] Please backup your key file! path=/data/eth/chaindata/keystore/UTC--2020-05-27T07-56-59.917964036Z--3bd8f1adf6fce874b6fc004629ccdb30f16e5283
WARN [05-27|15:57:01.715] Please remember your password!
"0x3bd8f1adf6fce874b6fc004629ccdb30f16e5283"

# 会提示输入密码和确认密码,输入密码不会有显示,只要输入就可以了,之后就会显示新创建的账户地址。可以创建多个账户。
# 账户默认会保存在数据目录的keystore文件夹中。查看目录结构,发现keystore中多了个文件,这个文件就对应刚才创建的账户,这是json格式的文本文件,可以打开查看,里面存的是私钥经过密码加密后的信息。
├── chaindata
└── keystore
└── UTC--2020-05-27T07-56-59.917964036Z--3bd8f1adf6fce874b6fc004629ccdb30f16e5283

# 查看账户
> admin.nodeInfo.enode
"enode://4e9dc8b2c36bd39fc183063a90cd3bbdd52b2aba48ab6225a54a43b56ef0f230ef5be787807e19df83cd35946187335c9e8d0a20d4ec91535266271a30826c42@113.110.225.141:30303?discport=2072"

# 查看账户余额
> eth.getBalance("0x3bd8f1adf6fce874b6fc004629ccdb30f16e5283")
0
# 目前账户的以太币余额是0,要使账户有余额,可以从其他账户转账过来,或者通过挖矿来获得以太币奖励。

0x04 致富之道~挖矿

通过miner.start()来启动挖矿
通过miner.stop()来停止挖矿
其中start的参数表示挖矿使用的线程数。第一次启动挖矿会先生成挖矿所需的DAG文件,这个过程有点慢,等进度达到100%后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> miner.start(2)
INFO [05-27|16:11:45.236] Generating DAG in progress epoch=0 percentage=0 elapsed=1.140s
INFO [05-27|16:11:46.440] Generating DAG in progress epoch=0 percentage=1 elapsed=2.345s
INFO [05-27|16:11:47.686] Generating DAG in progress epoch=0 percentage=2 elapsed=3.591s
INFO [05-27|16:11:48.842] Generating DAG in progress epoch=0 percentage=3 elapsed=4.747s
INFO [05-27|16:11:49.969] Generating DAG in progress epoch=0 percentage=4 elapsed=5.873s
INFO [05-27|16:11:51.178] Generating DAG in progress epoch=0 percentage=5 elapsed=7.082s
INFO [05-27|16:11:52.375] Generating DAG in progress epoch=0 percentage=6 elapsed=8.280s
INFO [05-27|16:11:53.534] Generating DAG in progress epoch=0 percentage=7 elapsed=9.439s
INFO [05-27|16:11:54.778] Generating DAG in progress epoch=0 percentage=8 elapsed=10.682s
INFO [05-27|16:11:55.954] Generating DAG in progress epoch=0 percentage=9 elapsed=11.859s
INFO [05-27|16:11:57.155] Generating DAG in progress epoch=0 percentage=10 elapsed=13.060s
INFO [05-27|16:11:58.420] Generating DAG in progress epoch=0 percentage=11 elapsed=14.324s


# 矿工账户: 挖到一个区块会奖励5个以太币,挖矿所得的奖励会进入矿工的账户,这个账户叫做coinbase,默认情况下coinbase是本地账户中的第一个账户,通过miner.setEtherbase()可将其他账户设置成coinbase。

# 查看余额
> eth.getBalance(eth.accounts[0])
570000000000000000000

# 别看这里那么多个0 其实只有570个以太币。
# getBalance()返回值的单位是wei,wei是以太币的最小单位,1个以太币=10的18次方个wei。要查看有多少个以太币,可以用web3.fromWei()将返回值换算成以太币:
> web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')
570

0x05 交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 先查看一下我们两个账户的余额。 账户0 有570个以太币 账户 1 为零个。
> web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')
570
> web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')
0

# 可以通过发送一笔交易,从A账户转移N个以太币到B账户;
# 注意:在交易过程中,无论交易的代币是什么,都需要把这些代币转为 wei 存储在以太坊区块链中
> amount = web3.toWei(5,'ether')
"5000000000000000000"

# 这里 amount = web3.toWei(5,’ether’) 是把5个以太币转换为wei 赋值给amount。
# 发送交易
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})
WARN [05-27|16:29:39.024] Served eth_sendTransaction reqid=50 t=2.022748ms err="authentication needed: password or unlock"
Error: authentication needed: password or unlock
at web3.js:6347:37(47)
at web3.js:5081:62(37)
at <eval>:1:20(16)

# 执行报错 是因为账户每过一段时间就会被锁住,想要发送交易,必须先解锁账户,由于我们要从账户0发送交易,所以要解锁账户0:
> personal.unlockAccount(eth.accounts[0])
true

# 函数的第一个参数 是发送者地址,第二个接收者的地址,第三个是转账的金额,以wei为单位。然后执行转账操作。
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})
INFO [05-27|16:37:32.294] Setting new local account address=0x3bd8f1aDf6fCe874B6FC004629cCdb30f16E5283
INFO [05-27|16:37:32.294] Submitted transaction fullhash=0xcd8d8788703b52aedb10642a93490144b858fb1a851a80780005c5f26af54a69 recipient=0x5407117Bbb2bfA61C8ccAeFB209d10ffB82224cF
"0xcd8d8788703b52aedb10642a93490144b858fb1a851a80780005c5f26af54a69"

# 回显这个说明交易已经提交到区块链里了,返回的hash值是交易hash,但是交易并未 被处理。 可以通过txpool.status来查看状态。
> txpool.status
{
pending: 1,
queued: 0
}
# 其中有一条pending的交易,pending表示已提交但还未被处理的交易。

# 要使交易被处理,必须要挖矿。这里我们启动挖矿,然后等待挖到一个区块之后就停止挖矿:
> miner.start(1);admin.sleepBlocks(1);miner.stop();

# 当miner.stop()返回true后,txpool中pending的交易数量应该为0了,说明交易已经被处理了:
> txpool.status
{
pending: 0,
queued: 0
}

# 此时,交易已经生效,账户一应该已经收到了5个以太币了:
> web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')
5

0x06 查看交易和区块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# eth对象封装了查看交易和区块信息的方法。
# 查看当前区块总数:
> eth.blockNumber
304

# 通过区块号(高度)查看区块信息,通过交易hash查看交易:
> eth.getBlock(304)
{
difficulty: 144223,
extraData: "0xd88301090e846765746888676f312e31342e32856c696e7578",
gasLimit: 3191286136,
gasUsed: 0,
hash: "0xe544e23bc3e46ad075da379cc752d649e445fe1e4ddbce906d32b7ebab6bcf11",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x3bd8f1adf6fce874b6fc004629ccdb30f16e5283",
mixHash: "0x30d20e4aac3266f4f561bb23b2eca5a1f54733ba1113ea40b4ea0ae9a22a4fb3",
nonce: "0x615b10f022b804a6",
number: 304,
parentHash: "0xed01bbb45f9f42b4a6f0623f358995b6353a7c6a443b8c6ec5ad1c3c09f15f1b",
receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 539,
stateRoot: "0x10b647884f1252a94d8031e8323e8f3a15db904c7439d252099b4ccd7443d6ca",
timestamp: 1590569121,
totalDifficulty: 42861137,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}

# 也可以通过交易hash 查看交易记录

> eth.getTransaction("0xcd8d8788703b52aedb10642a93490144b858fb1a851a80780005c5f26af54a69")
{
blockHash: "0x2fc36d2f47f521762eafaf349507be8f6db2c6fc05ff0d726e4ff810d40b71e2",
blockNumber: 286,
from: "0x3bd8f1adf6fce874b6fc004629ccdb30f16e5283",
gas: 21000,
gasPrice: 1000000000,
hash: "0xcd8d8788703b52aedb10642a93490144b858fb1a851a80780005c5f26af54a69",
input: "0x",
nonce: 0,
r: "0x92aecdc0b6236e0eb50f5209e9d71ce4114803755c2ffd7213aef023efd18dc0",
s: "0x23f32849c259e367de89318904d11e33c99bf124e9bf6b8ec369625f06455011",
to: "0x5407117bbb2bfa61c8ccaefb209d10ffb82224cf",
transactionIndex: 0,
v: "0x37",
value: 5000000000000000000
}

还有更多的功能请自行了解与探索。