以太坊作为全球领先的智能合约平台,其核心在于维护一个全球共享的、不断变化的状态数据库,这个状态数据库记录了所有账户(外部账户和合约账户)的余额、nonce、代码以及存储数据等关键信息,理解以太坊状态存储的源码,对于深入把握以太坊的运作机制、进行底层开发或优化至关重要,本文将尝试从源码层面,剖析以太坊状态存储的核心数据结构与关键机制。
以太坊状态存储概述
以太坊的状态可以被看作是一个巨大的、持久化的键值(Key-Value)存储,每个账户都有一个唯一的地址(Address)作为键,对于账户本身,其基本属性(如余额、nonce、根哈希、代码)直接存储在状态中,而对于合约账户,其更复杂的存储数据(即合约变量)则以另一种键值结构存储,通常被称为“存储”(Storage)。
状态存储的实现需要满足以下关键需求:
- 持久化:状态数据需要长期保存,不能因节点重启而丢失。
- 一致性:在区块执行过程中,状态变更需要原子性和一致性。
- 高效查询与更新:频繁的读取和写入操作需要高效的性能。
- Merkle Patricia Trie(MPT)支持:状态数据需要组织成MPT结构,以便生成状态根哈希,保障数据完整性并支持轻客户端验证。
在以太坊的Go语言实现(go-ethereum)中,状态存储的核心逻辑主要集中在 core/state 包和 trie 包中。
核心数据结构
StateDB - 状态数据库的入口
StateDB 是 core/state 包中的核心结构,它代表了以太坊的状态数据库实例,它维护了以下关键信息:
type StateDB struct {
db Database // 底层数据库接口,如LevelDB
// ... 其他字段如缓存、临时状态等
// 以下是与状态直接相关的字段
StateCache StateCache // 状态缓存,用于加速访问
// ...
preimages map[common.Hash][]byte // 预映像,用于某些哈希计算
}
Database:这是一个底层数据库的抽象接口,实际可能是LevelDB、BadgerDB等。StateDB通过这个接口与磁盘上的持久化数据交互。StateCache:为了提高性能,StateDB广泛使用缓存来存储最近访问或修改的账户和存储数据。
Account - 账户结构
账户信息本身也有一个结构:
type Account struct {
Root common.Hash // 合约账户的存储根哈希(指向MPT)
Balance *big.Int // 余额
Nonce uint64 // 交易计数
CodeHash common.Hash // 代码哈希
// ... 可能还有其他字段,如Dirty标记等
}
Root:对于合约账户,这个字段指向一个MPT的根哈希,这个MPT存储了该合约的所有存储变量(键值对)。CodeHash:账户代码的哈希值,外部账户的代码哈希为空。
trie.Trie - Merkle Patricia Trie
trie 包实现了MPT数据结构。StateDB 内部会持有一个顶层的MPT根节点,这个根节点的哈希就是当前状态根(State Root)。
// trie.Trie 接口(简化)
type Trie interface {
Get(key []byte) ([]byte, error)
Update(key, value []byte) error
Delete(key []byte) error
Hash() common.Hash
Root() []byte
// ...
}
Get(key):根据键获取值。Update(key, value):插入或更新键值对。Delete(key):删除键值对。Hash():计算并返回当前Trie的根哈希。
在以太坊中:
- 状态Trie:以账户地址为键,以账户的RLP编码为值,这个Trie的根哈希就是区块头中的
StateRoot。 - 存储Trie:对于每个合约账户,其
Root字段指向的Trie就是该合约的存储Trie,这个存储Trie以一个256位的键(通常由变量slot索引派生)为键,以变量的RLP编码为值。
关键机制与源码剖析
状态初始化与加载 (NewStateDB)
func NewStateDB(db Database, config *StateDBConfig) *StateDB {
// ...
s := &StateDB{
db: db,
originalRoot: common.Hash{},
stateCache: newStateCache(config.CacheSize),
// ...
}
// ...
return s
}
NewStateDB 函数根据传入的底层数据库接口 Database 创建一个新的 StateDB 实例,它初始化了缓存等结构。
账户操作 (GetAccount, CreateAccount, UpdateAccount)
-
获取账户 (
GetAccount):