go-ethereum 源码笔记(概览)

花了一点时间读以太坊的源代码,内容太多,所以一边看一边写,没想到越写越多,于是想着把笔记整理出来。

源码解读是一件费力不讨好的事情,因为看代码的时候我们看到的是最终解决方案,虽然可以看到 commit 的历史,但我们看不到作者的思路,踩过的坑,而且 geth 的设计文档,功能的相关讨论等信息不像 Kubernetes 那么规范透明,所以我看这些代码的时候很多地方也只是一知半解,欢迎有更多经验的朋友批评指正,不吝赐教,也欢迎有价值的讨论。看了一些书和文章,所以我这也算不上多原创,基本上所有看过的链接都记在 reference 里面了。有的代码暂时看不懂就只能靠猜了,等待之后的实践中验证想法。尽管如此,对于同样想要阅读 geth 的源代码的人来说总还是有些作用,对于我个人来说整理出来这些文章也是一个总结知识,把点连成面的过程,希望同时能帮到其他人。

geth 版本:master 分支,a1eb9c7d13240fd250866219a502d0cdc9924e06

这是第一篇,可以作为索引,后续发布其他文章后会不断更新👇

涉及到的计算机专业知识

  • 网络知识,p2p 网络(Kad 算法) ,rpc 等等
  • 加密学,暂时不需要很深入,除非是像 How to build blockchain from scratch? 说的需要构建加密函数相关的库,在大多数的场景下只需要知道加密函数的基本原理和使用。当然越深入越好,这一块也可以做出创新;安全对于很多行业是命脉,对于区块链来说更是如此。推荐阅读密码学入门经典:图解密码技术
  • 编译原理,看 EVM 相关的实现时需要知道一些基本概念。
  • 分布式系统原理(一致性算法)
  • 智能合约
  • 数据库(LevelDB),了解 LSM 的特点,不需要太深入的了解,知道 API 调用即可。当然知道底层原理最好,这块也有改进的空间,微博上的邓草原同学在做这方面的工作,针对区块链的数据特点设计专门的存储引擎,TPS 有不小的提升,不过我还没有细看代码,从讨论来看应该是参考了 Kafka 的存储特点。
  • 一些数据结构
    • MPT
    • DAG
    • 布隆过滤器

写完了这个系列可能会加个思维导图。

架构

总体架构图(来自 Ethereum block architecture

https://i.stack.imgur.com/afWDt.jpg

分层架构

以太坊技术详解与实战这本书里介绍了以太坊的分层架构,其中的图片还挺有参考价值的,网络上没有找到原图,所以我自己画了一下:

ethereum-leveld-architecture

底层服务

底层服务包括 P2P 网络、LevelDB 数据库、密码学算法和分片优化等基础服务。

核心层

核心层包括区块链、共识算法和以太坊虚拟机等。

顶层应用

这一层包括 API 接口、智能合约以及去中心化应用。

重要的数据结构

/core/types/block.go 区块的数据结构

区块的数据结构在 core/types/block.go 中定义。先混个眼熟吧。

1
2
3
4
5
6
7
8
9
10
type Block struct {
header *Header
uncles []*Header
transactions Transactions
hash atomic.Value
size atomic.Value
td *big.Int
ReceivedAt time.Time
ReceivedFrom interface{}
}
重要字段 描述
header header 指向 Header 结构(之后会详细说明),header 存储一个区块的基本信息。
uncles 指向 Header 结构
transactions 一组 transaction 结构
hash 当前区块的哈希值
size 当前区块的大小
td 当前区块高度
ReceivedAt 接收时间
ReceivedFrom 来源

交易组成区块,一个一个区块以单向链表的形式连在一起组成区块链,毋庸置疑,这是最基础的数据结构,在 geth 的源代码中大量用到。

其中 Header 的数据结构定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time *big.Int `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash" gencodec:"required"`
Nonce BlockNonce `json:"nonce" gencodec:"required"`
}
字段 描述
ParentHash 父区块的哈希值
UncleHash 叔区块的哈希值
Coinbase 矿工得到奖励的账户,一般是矿工本地第一个账户
Root 表示当前所有用户状态
TxHash 本区块所有交易 Hash,即摘要
ReceiptHash 本区块所有收据 Hash,即摘要
Bloom 布隆过滤器,用来搜索收据
Difficulty 该区块难度,动态调整,与父区块和本区块挖矿时间有关。可参考 github.com/ethereum/go-ethereum/consensus/ethash/consensus.goCalcDifficulty
Number 该区块高度
GasLimit gas 用量上限,该数值根据父区块 gas 用量调节,如果 parentGasUsed > parentGasLimit * (2/3) ,则增大该数值,反之则减小该数值。可参看 github.com/ethereum/go-ethereum/core/block_validator.goCalcGasLimit
GasUsed 实际花费的 gas
Time 新区块的出块时间,严格来说是开始挖矿的时间
Extra 额外数据
MixDigest 混合哈希,与 nonce 结合使用
Nonce 加密学中的概念,在基本概念章节中有介绍

它包含区块的属性信息,ParentHash 表示该区块的父区块哈希,我们通过 ParentHash 这个字段将一个一个区块连接起来组成区块链,但实际上我们并不会直接将链整个的存起来,它是以一定的数据结构一块一块存放的,geth 的底层数据库用的是 LevelDB,这是一个 key-value 数据库,要得到父区块时,我们通过 ParentHash 以及其他字符串组成 key,在 LevelDB 中查询该 key 对应的值,就能拿到父区块。

/core/blockchain.go 区块链的数据结构

core/blockchain.goBlockChain 结构体定义了区块链的数据结构

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
type BlockChain struct {
chainConfig *params.ChainConfig
cacheConfig *CacheConfig

db ethdb.Database
triegc *prque.Prque
gcproc time.Duration

hc *HeaderChain
rmLogsFeed event.Feed
chainFeed event.Feed
chainSideFeed event.Feed
chainHeadFeed event.Feed
logsFeed event.Feed
scope event.SubscriptionScope
genesisBlock *types.Block

mu sync.RWMutex
chainmu sync.RWMutex
procmu sync.RWMutex

checkpoint int
currentBlock atomic.Value
currentFastBlock atomic.Value

stateCache state.Database
bodyCache *lru.Cache
bodyRLPCache *lru.Cache
blockCache *lru.Cache
futureBlocks *lru.Cache

quit chan struct{}
running int32
procInterrupt int32
wg sync.WaitGroup

engine consensus.Engine
processor Processor
validator Validator
vmConfig vm.Config

badBlocks *lru.Cache
}

UncleHashBlock 结构体成员 uncles 的 RLP 哈希值,uncles 是一个 Headers 数组,关于叔区块,可以查阅以太坊的设计原理。在下一篇 go-ethereum 源码笔记(基础知识) 会详细介绍叔区块。

Block 对象中还有一个比较重要的数据结构,那就是 Transaction,它是交易相关逻辑的基础。

/core/types/transaction.go 交易的数据结构

交易的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}

type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"`
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`

V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`

Hash *common.Hash `json:"hash" rlp:"-"`
}

转账的定义中只有转入方,转出方的地址没有直接暴露。每一笔转账都有独立的 Price 和 GasLimit,这是 Ethereum 的安全保护策略,是一个值得称赞的设计,如果你对这个不熟悉,请查阅:wiki/Glossary,在之后的文章中也会有介绍。

geth 目录结构简述

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
accounts        实现以太坊账户管理
build 编译和构建的一些脚本和配置
cmd 命令行工具,又分了很多的命令行工具,下面会一个一个介绍
abigen 将以太坊智能合约定义转换为类型安全的 Go 或 Java 包的源码转换器
bootnode 启动一个用于网络发现的节点
evm 以太坊虚拟机的开发工具,用来提供一个可配置的,受隔离的代码调试环境
faucet 以太坊 faucet 测试网络
geth 以太坊命令行客户端,最重要的一个工具
p2psim 提供了一个工具来模拟 p2p 的 API
puppeth 创建一个新的以太坊网络的向导
rlpdump 提供了一个 RLP 数据的格式化输出
swarm swarm 网络的接入点
util 提供了一些公共的工具
wnode 这是一个简单的 Whisper 节点。它可以用作独立的引导节点。此外可以用于不同的测试和诊断目的
common 提供了一些公共的工具类
consensus 提供了以太坊的一些共识算法,比如 ethhash, clique(proof-of-authority)
console 与终端交互相关的代码
containers Docker 容器相关的代码
contracts 以太坊域名服务,票据支付方案
core 以太坊的核心数据结构和算法(虚拟机,状态,区块链,布隆过滤器)
crypto 加密和 hash 算法
dashboard 以太坊后台管理 UI
eth 实现了以太坊的协议
ethclient 提供了以太坊的 RPC 客户端
ethdb 封装 geth 的数据库(包括实际使用的 Leveldb 和供测试使用的内存数据库)
ethstats 提供网络状态的报告
event 处理实时的事件
les 实现了以太坊的轻量级协议子集
light 为以太坊轻量级客户端提供按需检索的功能
log 提供对人机都友好的日志信息
metrics 提供磁盘计数器
miner 提供以太坊的区块创建和挖矿
mobile 移动端使用的一些 warpper
node 以太坊的多种类型的节点
p2p 以太坊 p2p 网络协议
rlp 以太坊编码算法
rpc 远程方法调用
swarm swarm 网络
tests 测试
trie 实现以太坊中的默克尔帕特里夏树
whisper 提供了 whisper 节点的协议

从编译源代码开始

按照官方文档的建议设置好开发环境,在 go-ethereum 执行 make build 之后,我们可以在 build/bin 目录下找到 abigen, ethkey 等等可执行文件,实际上这些文件的入口函数都在 cmd 目录下,接下来的文章里我们会逐一介绍这些命令。

参考资料