以太坊 Geth 私有链部署与原理

环境:Kali Linux + Docker + Geth v1.10.26

目标:用 3 个节点搭一条自己的以太坊私有链,完成创世、互联、挖矿、账户与转账。


1. 实验要做成什么样

以太坊主网是全球共用的一条链。本实验不连主网,而是在本机用 Docker 起 3 个 Geth 进程,让它们共用同一份创世块,互相加为对等节点,自己挖矿出块。

完成后应看到:

  • 三个容器 peer1 / peer2 / peer3 同时运行
  • 三个节点的创世块 hash 完全相同
  • admin.peers 不为空(每个节点连着另外两个)
  • miner.start(1) 之后 eth.blockNumber 持续增加
  • 能创建账户、查余额、发转账

一张对应表:

概念 本实验里的实物
节点软件 Geth(镜像 ethereum/client-go:v1.10.26)
一台节点 一个 Docker 容器,例如 myethereumnet-peer1-1
账本数据 /root/MyEthereumNet/peer1/ 等三个目录
链的身份证 genesis.json 里的 chainId: 15
节点之间打招呼 admin.addPeer(...) 或 static-nodes.json
出块 miner.start(1)

2. 以太坊原理(只讲本实验会碰到的)

2.1 账本、区块、链

以太坊可以看成一台全网共用的记账计算机:

  • 每个人有账户(地址 + 私钥)
  • 转账、部署合约都是一笔交易
  • 交易被打包装进区块
  • 区块用 parentHash 指向上一块,串成链
  • 每个全节点各自存一份账本,靠协议保证看到的是同一条链

创世块是第 0 块,没有父块。三个节点必须用同一份 genesis.json 初始化,得到同一个创世 hash,否则它们会认为对方是另一条链,连上也同步不了。

一块里实验中常看的字段:

字段 含义
number 高度,创世块为 0
hash 本块哈希
parentHash 上一块哈希,构成链式结构
miner / coinbase 这块是谁挖出来的,奖励打给谁
difficulty 挖矿有多难
gasLimit 本块最多能消耗多少 gas
transactions 本块打包的交易列表
nonce / mixHash Ethash 挖矿的工作量证明材料

2.2 账户、密钥、单位

一个账户是一对密钥:

  • 私钥:能签名、能花钱。丢了或泄露都不可挽回。
  • 地址 :由公钥算出的 20 字节,形如 0xf52cc7096054a62d8da117b40c60cf2c68461b64。

Geth 把私钥加密后放在 keystore/ 目录。personal.newAccount("123456") 里的 123456 是加密 keystore 的口令,不是私钥本身。忘记口令,文件还在也解不开。

余额单位:

  • 链上最小单位是 wei
  • 1 ETH = 10^18 wei
  • 控制台 eth.getBalance(...) 默认返回 wei,所以会看到一长串 0
  • 换成 ETH:web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")

本实验的 Ethash 旧规则:每挖出 1 块奖励 5 ETH。所以大约:

复制代码
余额 ≈ 块高 × 5

例如高度 150 时,挖矿账户约 750 ETH。

2.3 交易

转账本质是:用私钥签一条消息「从 A 转 N wei 给 B」,广播给节点,矿工打包进块。没进块之前只是内存池里的 pending。

转账前必须 unlockAccount,因为签名需要私钥,而私钥躺在加密的 keystore 里。

只有链上有人在挖矿(或出块),交易才会被打包。miner.stop() 之后转账会一直 pending。

2.4 挖矿(Ethash)与为什么不用最新 Geth

挖矿就是不断改区块里的 nonce 做哈希,直到结果小于当前难度目标。谁先算出谁出块。

本实验链:

  • 起始难度 0x40000(十进制 262144),很低,笔记本 CPU 就能挖
  • 共识是 Ethash 工作量证明
  • 控制台用 miner.start(1) 启动 1 个挖矿线程

公网以太坊 2022 年已改为权益证明(PoS),没有这种本地 CPU 挖矿。Geth 1.14 之后去掉了 Ethash。若 docker pull ethereum/client-go 拉到最新镜像:

  • 这份旧 genesis.json 初始化会失败或无法挖矿
  • miner.start 失效

因此实装把镜像钉在 ethereum/client-go:v1.10.26 (2022 年稳定版),并打成 latest 标签,compose 里仍写 image: ethereum/client-go,和课件一致。

2.5 chainId、networkId、enode

  • chainId:写进创世块。交易签名会带上它,防止一条链上的签名拿到另一条链重放(EIP-155)。

  • networkId :P2P 分组号。两边都是 15,才是封闭私链。启动参数 --networkid 15。

  • enode:节点在 P2P 层的身份证,格式:

    enode://<公钥>@:<端口>

admin.addPeer(对方enode) 等于把对方加进好友列表。

admin.peers 为空 = 孤岛;长度为 2 = 三个节点已经两两连上。

--nodiscover 关闭自动发现,避免误连公网。私链里要靠手动 addPeer 或 static-nodes.json。

重启容器后,账本还在,内存里的好友列表会丢 。所以要写 static-nodes.json,或准备重连脚本。


3. 工具分别干什么

3.1 Docker

把「Geth + 它所在的 Linux 环境」打成镜像,再变成容器跑。不必在 Kali 宿主机里直接装 geth。

三个容器互相隔离,但走同一条 Docker 网桥 myethereumnet_default,可以用 IP 或服务名互访。compose 服务名 peer1 / peer2 / peer3 就是 DNS 名,容器里 getent hosts peer2 能解析。

常用:

bash 复制代码
docker --version
docker ps
docker images
docker logs myethereumnet-peer1-1
docker exec -it myethereumnet-peer1-1 /bin/sh
docker pull ethereum/client-go:v1.10.26

Kali 安装 Docker:

bash 复制代码
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
# 或
curl -sSL https://get.daocloud.io/docker | sh

本机若已有 Docker,跳过。

3.2 Docker Compose

一份 YAML 同时定义 3 个容器的镜像、命令、端口、数据卷。

docker-compose,新版 Docker 也可能是 docker compose(中间有空格)。Kali Rolling 可用:

bash 复制代码
sudo apt-get update
sudo apt-get install -y docker-compose
docker-compose --version

工作目录内:

bash 复制代码
cd /root/MyEthereumNet
docker-compose up -d          # 后台启动
docker-compose ps             # 看这组服务
docker-compose logs -f peer1  # 跟踪 peer1 日志
docker-compose restart        # 重启(P2P 连接可能丢)
docker-compose down           # 停并删容器,数据目录保留

command 参数含义:

参数 含义
--ipcpath /root/.ethereum/geth.ipc 本机 JS 控制台走的 Unix 套接字路径
--port 23134 P2P 监听端口
--nodiscover 不要去公网找节点
--networkid 15 私有网络号
--maxpeers 25 最多连多少个对等节点

数据卷 ./peer1/:/root/.ethereum/ 把宿主机目录挂进容器。账本、keystore、IPC 文件都落在磁盘上,删容器不会丢链。

3.3 Geth

Go 语言写的以太坊客户端。一个进程 = 一个节点。它同时:

  1. 把链存在 LevelDB(geth/chaindata)
  2. 跟别的节点同步区块
  3. 执行交易
  4. 可选:挖矿
  5. 提供 IPC / HTTP / WS 接口

本实验用 IPC (geth.ipc)进 JavaScript 控制台,没有对外开 8545 JSON-RPC。浏览器、MetaMask 暂时连不上。

控制台是 JS ,不是 bash。正确:eth.blockNumber。错误:eth blockNumber。


4. 目录与文件长什么样

建议工作目录:/root/MyEthereumNet(root 登录 Kali)或自己的家目录。结构:

复制代码
MyEthereumNet/
├── docker-compose.yml
├── genesis.json
├── reconnect-peers.sh          # 可选:重启后重连
├── peer1/
│   ├── genesis.json
│   ├── geth.ipc                # 节点起来后出现
│   ├── keystore/               # 账户文件
│   └── geth/
│       ├── chaindata/          # 账本
│       ├── nodekey             # 本节点 P2P 私钥,决定 enode
│       └── static-nodes.json   # 可选:开机自动加同伴
├── peer2/
│   └── ...
└── peer3/
    └── ...

5. 完整部署步骤

5.1 确认 Docker

bash 复制代码
docker --version
sudo apt-get install -y docker-compose
docker-compose --version

5.2 建目录

bash 复制代码
mkdir -p /root/MyEthereumNet/{peer1,peer2,peer3}
cd /root/MyEthereumNet

5.3 写 docker-compose.yml

yaml 复制代码
version: '3'
services:
  peer1:
    image: ethereum/client-go
    command: --ipcpath "/root/.ethereum/geth.ipc" --port 23134 --nodiscover --networkid 15 --maxpeers 25
    ports:
      - "17450:4535"
      - "17460:4536"
      - "13303:23134"
      - "13303:23134/udp"
      - "13304:23135/udp"
    volumes:
      - /etc/localtime:/etc/localtime
      - ./peer1/:/root/.ethereum/
    environment:
      - TZ=Asia/Beijing

  peer2:
    image: ethereum/client-go
    command: --ipcpath "/root/.ethereum/geth.ipc" --port 23134 --nodiscover --networkid 15 --maxpeers 25
    ports:
      - "19450:4535"
      - "19460:4536"
      - "14303:23134"
      - "14303:23134/udp"
      - "14304:23135/udp"
    volumes:
      - /etc/localtime:/etc/localtime
      - ./peer2/:/root/.ethereum/
    environment:
      - TZ=Asia/Beijing

  peer3:
    image: ethereum/client-go
    command: --ipcpath "/root/.ethereum/geth.ipc" --port 23134 --nodiscover --networkid 15 --maxpeers 25
    ports:
      - "29450:4535"
      - "29460:4536"
      - "24303:23134"
      - "24303:23134/udp"
      - "24304:23135/udp"
    volumes:
      - /etc/localtime:/etc/localtime
      - ./peer3/:/root/.ethereum/
    environment:
      - TZ=Asia/Beijing

新版 compose 会提示 version 字段过时,不影响运行。

5.4 写 genesis.json

json 复制代码
{
    "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "coinbase" : "0x0000000000000000000000000000000000000000",
    "difficulty" : "0x40000",
    "extraData" : "",
    "gasLimit" : "0xffffffff",
    "nonce" : "0x0000000000000042",
    "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
    "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp" : "0x00",
    "alloc": { }
}

字段说明:

字段 作用
chainId 链 ID,实装用 15,避免和主网的 1 冲突
homesteadBlock 等 硬分叉从第 0 块就生效,私链最简单
difficulty 起始挖矿难度,0x40000 很低
gasLimit 单块 gas 上限拉满,方便实验
alloc 预分配余额。空对象表示一开始谁都没钱,靠挖矿挣
nonce / mixhash / parentHash 创世块工作量证明相关;创世块 parent 为全 0

复制到三个节点目录,容器内才能读到:

bash 复制代码
cp genesis.json peer1
cp genesis.json peer2
cp genesis.json peer3

5.5 拉镜像

bash 复制代码
docker pull ethereum/client-go:v1.10.26
docker tag ethereum/client-go:v1.10.26 ethereum/client-go:latest
docker images | grep client-go

国内 Docker Hub 若超时,可改镜像站后再 tag:

bash 复制代码
docker pull docker.m.daocloud.io/ethereum/client-go:v1.10.26
docker tag docker.m.daocloud.io/ethereum/client-go:v1.10.26 ethereum/client-go:v1.10.26
docker tag ethereum/client-go:v1.10.26 ethereum/client-go:latest

成功时应看到约 50MB 的 ethereum/client-go:v1.10.26。

5.6 第一次启动(可先确认容器能起来)

bash 复制代码
cd /root/MyEthereumNet
docker-compose up -d
docker-compose ps
docker ps

三个容器状态为 Up 即可。此时它们还没有按私有创世块初始化(或带着默认空库),下一步专门 init。

5.7 初始化创世块

Geth 正在运行时占用 datadir 锁,不要 在运行中的容器里 rm -rf geth 再 geth init。正确做法:先停,再 init,再启动。

bash 复制代码
cd /root/MyEthereumNet
docker-compose down
rm -rf peer1/geth peer2/geth peer3/geth

docker run --rm -v /root/MyEthereumNet/peer1:/root/.ethereum ethereum/client-go init /root/.ethereum/genesis.json
docker run --rm -v /root/MyEthereumNet/peer2:/root/.ethereum ethereum/client-go init /root/.ethereum/genesis.json
docker run --rm -v /root/MyEthereumNet/peer3:/root/.ethereum ethereum/client-go init /root/.ethereum/genesis.json

每条成功日志里应出现:

复制代码
Successfully wrote genesis state   hash=a0e580..a5e82e

三个节点的 hash 必须相同。 本次实装为:

复制代码
0xa0e580c6769ac3dd80894b2a256164a76b796839d2eb7f799ef6b9850ea5e82e

5.8 按创世块重新拉起集群

bash 复制代码
docker-compose up -d
sleep 3
docker-compose ps
ls -l peer1/geth.ipc peer2/geth.ipc peer3/geth.ipc

IPC 文件出现,说明控制台可以 attach。日志里应有:

复制代码
IPC endpoint opened   url=/root/.ethereum/geth.ipc
Started P2P networking

至此部署完成,下一步进控制台、互联、挖矿。


6. 进入 Geth 控制台

Geth 1.10 的 --exec 是全局参数,必须写在 attach 前面。

6.1 交互式(推荐截图、练手)

bash 复制代码
docker exec -it myethereumnet-peer1-1 geth attach /root/.ethereum/geth.ipc

提示符变成 >。退出:exit。

6.2 一次性查询(适合脚本)

bash 复制代码
docker exec myethereumnet-peer1-1 geth --exec 'eth.blockNumber' attach /root/.ethereum/geth.ipc

6.3 宿主机 attach

数据卷已经把 geth.ipc 映射到 ./peer1/geth.ipc。宿主机若安装了同版本 geth:

bash 复制代码
geth attach ./peer1/geth.ipc

Kali 没装 geth 时,用 6.1 的 docker exec 即可,不必再装一份。


7. 节点互联

7.1 看网络 IP

bash 复制代码
docker network ls
docker network inspect myethereumnet_default

本次实装示例(重启后 IP 可能变):

容器 IPv4
myethereumnet-peer1-1 172.18.0.4
myethereumnet-peer2-1 172.18.0.2
myethereumnet-peer3-1 172.18.0.3

7.2 取 enode

bash 复制代码
docker exec myethereumnet-peer1-1 geth --exec 'admin.nodeInfo.enode' attach /root/.ethereum/geth.ipc
docker exec myethereumnet-peer2-1 geth --exec 'admin.nodeInfo.enode' attach /root/.ethereum/geth.ipc
docker exec myethereumnet-peer3-1 geth --exec 'admin.nodeInfo.enode' attach /root/.ethereum/geth.ipc

输出里的 @127.0.0.1 是节点自己眼中的地址,加同伴时必须改成对方容器 IP 。--nodiscover 时末尾常为 ?discport=0,addPeer 时可去掉。

示例(公钥以实际输出为准):

复制代码
enode://<peer2公钥>@172.18.0.2:23134
enode://<peer3公钥>@172.18.0.3:23134

7.3 addPeer

先看应为空:

js 复制代码
admin.peers

若这里冒出大量公网节点,说明连到了公共网络,前面某步(chainId / nodiscover)有问题。

在 peer1 控制台把 peer2、peer3 加上;再在 peer2 把 peer3 加上,形成全连接:

js 复制代码
admin.addPeer("enode://<peer2公钥>@172.18.0.2:23134")
admin.addPeer("enode://<peer3公钥>@172.18.0.3:23134")

返回 true 只表示命令被接受。等一两秒再查:

js 复制代码
admin.peers
net.peerCount

每个节点 peerCount 应为 2。连接是单向发起、双向建立的,不必在每个节点上把另外两个都加一遍;全连接更稳。

宿主机一条命令写法:

bash 复制代码
docker exec myethereumnet-peer1-1 geth --exec 'admin.addPeer("enode://...@172.18.0.2:23134")' attach /root/.ethereum/geth.ipc

7.4 让重启后自动重连

把对方 enode 写成 static-nodes.json,放进各节点的 geth/ 目录。Docker DNS 可用服务名,IP 变了也能解析:

peer1/geth/static-nodes.json 示例:

json 复制代码
[
  "enode://<peer2公钥>@peer2:23134",
  "enode://<peer3公钥>@peer3:23134"
]

公钥来自该节点的 geth/nodekey,只要不删 peerX/geth/nodekey,enode 前半段不变。此文件在下次启动时生效。

也可在目录下放 reconnect-peers.sh:读当前 IP、改写 enode、再 addPeer。重启后若 admin.peers 仍为空就执行它。


8. 账户、挖矿与转账

以下均在 peer1 的 > 控制台中执行。

8.1 创建账户并指定 coinbase

js 复制代码
personal.newAccount("123456")
eth.accounts
miner.setEtherbase(eth.accounts[0])
eth.coinbase

口令仅用于本实验。本次实装挖矿地址:

复制代码
0xf52cc7096054a62d8da117b40c60cf2c68461b64

8.2 挖矿

js 复制代码
miner.start(1)
eth.mining
eth.blockNumber
web3.fromWei(eth.getBalance(eth.coinbase), "ether")

难度 0x40000 时,大约十几秒出一块。三个节点高度应一起涨,说明同步正常:

bash 复制代码
docker exec myethereumnet-peer1-1 geth --exec 'eth.blockNumber' attach /root/.ethereum/geth.ipc
docker exec myethereumnet-peer2-1 geth --exec 'eth.blockNumber' attach /root/.ethereum/geth.ipc
docker exec myethereumnet-peer3-1 geth --exec 'eth.blockNumber' attach /root/.ethereum/geth.ipc

截图结束后建议:

js 复制代码
miner.stop()

否则会一直占 CPU。

8.3 看块

js 复制代码
eth.getBlock(0)           // 创世块
eth.getBlock("latest")    // 最新一块
eth.getBlock(1).miner
eth.getBlock(1).parentHash

核对:第 1 块的 parentHash 应等于第 0 块的 hash。

8.4 转账练习

再开一个收款账户,从挖矿账户转 1 ETH:

js 复制代码
personal.newAccount("123456")
personal.unlockAccount(eth.accounts[0], "123456", 300)
eth.sendTransaction({
  from: eth.accounts[0],
  to: eth.accounts[1],
  value: web3.toWei(1, "ether")
})

返回交易 hash。然后:

js 复制代码
eth.getTransaction("0x交易hash")
eth.pendingTransactions
eth.blockNumber
web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")

value 也可用 wei 直写:1000000000000000000 就是 1 ETH。必须保持 eth.mining === true,否则交易不会进块。

8.5 控制台命令表

功能 命令
当前高度 eth.blockNumber
创世块 eth.getBlock(0)
最新块 eth.getBlock("latest")
是否在同步 eth.syncing
本节点 enode admin.nodeInfo.enode
已连接同伴 admin.peers
同伴数量 net.peerCount
加同伴 admin.addPeer("enode://...")
本地账户 eth.accounts
挖矿收益地址 eth.coinbase
余额(wei) eth.getBalance(eth.accounts[0])
余额(ETH) web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
新建账户 personal.newAccount("密码")
解锁 300 秒 personal.unlockAccount(eth.accounts[0], "密码", 300)
转账 1 ETH eth.sendTransaction({from: eth.accounts[0], to: "0x...", value: web3.toWei(1,"ether")})
开始挖矿 miner.start(1)
停止挖矿 miner.stop()
指定矿工地址 miner.setEtherbase(eth.accounts[0])
是否在挖 eth.mining
退出 exit

9. 常用命令速查

9.1 宿主机 / Docker

bash 复制代码
cd /root/MyEthereumNet

docker-compose ps
docker-compose logs --tail 50 peer1
docker-compose restart
docker-compose down
docker-compose up -d

docker logs myethereumnet-peer1-1
docker exec -it myethereumnet-peer1-1 /bin/sh
docker network inspect myethereumnet_default

# 重启后重连(若已放置脚本)
/root/MyEthereumNet/reconnect-peers.sh

9.2 一键看三节点高度

bash 复制代码
for p in peer1 peer2 peer3; do
  echo -n "$p "
  docker exec myethereumnet-${p}-1 geth --exec 'eth.blockNumber' attach /root/.ethereum/geth.ipc
done

9.3 清理(慎用)

bash 复制代码
# 只停容器,保留链数据
docker-compose down

# 连账本一起删,等于重做一条链
docker-compose down
rm -rf peer1/geth peer2/geth peer3/geth peer1/keystore peer2/keystore peer3/keystore

# 删镜像
docker rmi ethereum/client-go:v1.10.26 ethereum/client-go:latest

10. 排错

现象 原因与处理
geth init 报 datadir 被锁 容器里 geth 还在跑。先 docker-compose down 再 init
三个创世 hash 不一致 genesis.json 没拷全,或某节点旧 geth/ 没删干净
admin.peers 很多公网节点 用了 chainId 1 且没加 --nodiscover。改 15 并加 --nodiscover 后重建
admin.peers 一直 [] enode 里还是 127.0.0.1;应换成对方容器 IP 或服务名。重启后要重新 addPeer
addPeer 返回 true 但 peers 仍空 等 2 秒;检查 networkid 是否同为 15;检查创世 hash 是否相同
miner.start 没反应 / 报错 镜像太新,Ethash 已移除。改用 v1.10.26
高度不涨 没 miner.start,或 coinbase 没设账户
三节点高度不一致 P2P 没连上。查 admin.peers,跑重连脚本
转账一直 pending 没有人在挖矿
Fatal: invalid command-line: too many arguments Geth 1.10 要把 --exec 写在 attach 前面
docker: unknown command: docker compose 安装 docker-compose 包,或用带连字符的命令
IPC 不存在 容器还没起来或 geth 崩了。看 docker logs
端口冲突 ss -tulpn 查 13303/14303/24303,改 compose 或停占用进程

日志:

bash 复制代码
docker logs --tail 80 myethereumnet-peer1-1

关注 IPC endpoint opened、Started P2P networking、Successfully wrote genesis state、Block synchronisation started。

相关推荐
磐链科技3 小时前
元链科技:随着区块链技术持续发展,Web3逐渐成为数字资产管理、去中心化应用和数字经济创新的重要技术方向
科技·web3·区块链
磐链科技4 小时前
Web3区块链开发需要哪些核心技术?解析分布式网络、数字钱包与跨链交互方案
分布式·web3·区块链
IvorySQL13 小时前
PostgreSQL 日报|恢复期 COPY TO 未日志表修复(10 月 21 日)
数据库·postgresql·区块链
ZDGJ60991 天前
国际黄金期货交易时间的优势
区块链
IvorySQL1 天前
PostgreSQL 日报| OpenCode 遭黑客入侵 API 密钥失窃(10 月 20 日)
数据库·postgresql·区块链
Haoyunyi2 天前
2026年做期货量化,和三年前已经不是一回事了
大数据·人工智能·python·金融·区块链
极光通讯3 天前
涨价周期里的元器件议价策略:如何判断供应商报价是否合理
区块链
重明链迹实验室3 天前
重明链迹丨每周区块链安全要闻(1012-1018)
安全·web3·区块链
ZDGJ60993 天前
国际黄金期货交易趋势规则
区块链