菜单

状态管理

相关源文件

本文档解释了在 go-ethereum (Geth) 实现中,以太坊的世界状态是如何被维护、修改和存储的。状态管理是跟踪以太坊区块链中的账户余额、合约代码和存储数据的一个关键组件。

有关交易执行期间状态变化的信息,请参阅 交易处理。有关智能合约执行的具体细节,请参阅 以太坊虚拟机

以太坊状态概述

以太坊的世界状态是地址(账户)和账户状态之间的映射。它代表了区块链上所有账户的当前状态,并被维护为一个 Merkle Patricia Trie 结构,以确保数据的完整性和高效验证。

以太坊有两种类型的账户:

  1. 外部拥有账户 (EOAs):由私钥控制,拥有以太币余额。
  2. 合约账户:除了余额外,还包含代码和存储。

状态 Trie 将账户地址映射到账户对象,账户对象包括:

  • Nonce(EOA 的交易计数,合约的创建计数)
  • Balance(以 Wei 为单位的余额)
  • Storage Root(合约数据的存储 Trie 的根哈希)
  • Code Hash(合约字节码的哈希)

来源:core/state/statedb.go68-79 core/types/transaction.go54-63

状态数据结构

核心状态管理依赖于几个关键数据结构:

  1. StateDB:核心管理器,在状态 Trie 之上提供缓存层。
  2. stateObject:表示单个以太坊账户(EOA 和合约)。
  3. Trie:Merkle Patricia Trie,高效存储所有账户和存储数据。
  4. Journal:记录所有状态修改,以便能够撤销更改。

来源:core/state/statedb.go79-158 core/state/state_object.go42-86 trie/trie.go38-60

StateDB:核心状态管理器

StateDB (core/state/statedb.go) 是与以太坊状态交互的主要接口。它提供了以下方法:

  • 检索账户余额、nonce、代码和存储。
  • 修改账户数据(更新余额、代码、存储)。
  • 创建和销毁账户。
  • 管理 Gas 退款。
  • 跟踪状态更改以便回滚(通过 journal)。
  • 在执行过程中创建中间状态根。

StateDB 提供了访问和修改账户的方法,维护内存状态对象的缓存,并协调 Trie 操作。

来源:core/state/statedb.go161-188 core/state/statedb.go294-325 core/state/statedb.go407-434

创建和使用

新的 StateDB 在每个区块处理开始时创建,通常使用前一个区块的状态根。在交易执行期间,StateDB 用于读取和修改状态。

来源:core/state/statedb.go160-188 core/state/statedb.go294-305 core/state/statedb.go407-455

账户状态对象

stateObject 结构体表示在状态转换期间被修改的以太坊账户。它包含:

stateObject 处理数据缓存和账户状态的分层更新。

  • dirtyStorage:当前交易中进行的更改。
  • pendingStorage:当前区块中进行的更改,但已按交易最终确定。
  • uncommittedStorage:自上次提交以来的所有更改。
  • originStorage:来自数据库的原始值。

来源:core/state/state_object.go42-86 core/state/state_object.go89-110

状态对象生命周期

  1. 当通过 getOrNewStateObject 访问账户时创建。
  2. 在交易执行期间修改。
  3. 在每次交易后进行最终确定(finalise)。
  4. 在区块完成时提交(commit)。

在此过程中,状态对象会跟踪原始值、待定更改,并确保所有修改都可以通过状态根进行验证。

来源:core/state/statedb.go610-627 core/state/state_object.go274-287

状态存储管理

以太坊智能合约将数据存储在键值存储中,称为存储。每个合约都有自己的存储 Trie,其根存储在账户对象中。

合约存储操作通过以下几种方法进行管理:

  • GetState/SetState:读取和写入存储值。
  • GetCommittedState:获取存储值,不考虑未提交的更改。
  • updateTrie/updateRoot:将待定更改刷新到存储 Trie。

来源:core/state/state_object.go170-208 core/state/state_object.go210-234 core/state/state_object.go282-316

状态更改日志

Journal(core/state/journal.go)记录交易执行期间的所有状态更改,以便能够恢复到之前的状态。这对于处理交易失败、Gas 不足以及创建快照至关重要。

日志系统支持:

  1. 快照:通过 Snapshot() 创建恢复点。
  2. 回滚:通过 RevertToSnapshot() 回滚更改。
  3. 操作跟踪:按类型记录所有状态修改。

来源:core/state/statedb.go711-718 core/state/journal.go

状态转换和最终确定

当交易执行时,它们通过 StateDB 修改状态。该过程遵循几个阶段:

  1. 交易执行:通过 StateDB 方法修改账户。
  2. 最终确定:每笔交易后,调用 Finalise() 以便为下一笔交易做准备。
  3. 中间根计算:通过 IntermediateRoot() 生成 Trie 根哈希。
  4. 区块提交:在区块结束时,通过 Commit() 创建最终状态。

Finalise 方法负责处理交易后的清理工作:

  • 将自毁对象标记为待删除。
  • 使用待定更改更新存储 Trie。
  • 为下一笔交易准备对象。

来源: core/state/statedb.go728-766 core/state/statedb.go771-917

状态提交与持久化

当一个区块被确定时,所有状态更改必须被提交到持久存储。这分几个步骤完成:

  1. StateDB 上的 Commit 方法将所有状态对象最终确定
  2. 存储树被递归地更新并提交
  3. 账户数据被序列化并存储在状态树中
  4. 新的状态根被计算并返回
  5. 底层数据库提交树节点

提交过程确保所有状态更改都被持久存储,并且可以通过新的状态根哈希进行引用。

来源: core/state/state_object.go418-452 core/state/statedb.go771-917 trie/trie.go

性能优化

Go-ethereum 实现了多种状态管理的优化技术

  1. 缓存:账户和存储的内存缓存,以减少数据库读取
  2. 批量处理:将数据库写入分组,以提高 I/O 性能
  3. 预取:预测数据需求并提前加载节点
  4. 修剪:删除过时的状态以减少存储需求

预取器

Trie 预取器(StateDB 中的 prefetcher)通过以下方式优化 Trie 节点检索:

  • 预加载可能很快需要的节点
  • 降低状态访问操作的延迟
  • 批量请求节点以最小化数据库开销

来源: core/state/statedb.go190-223 core/state/statedb.go771-907

状态数据库存储

状态数据最终存储在分层数据库结构中

这种架构提供了:

  • 通过内存缓存快速访问最近状态
  • 历史状态的持久存储
  • 高效的修剪和归档机制

来源: core/state/database.go trie/trie.go ethdb/database.go core/rawdb/database.go

结论

Geth 中的状态管理通过一套复杂的 Trie、缓存和日志系统来维护以太坊的世界状态。这种架构平衡了几个关键要求:

  1. 正确性:确保状态转换遵循以太坊规则
  2. 性能:在高吞吐量环境中进行优化以提高速度
  3. 持久性:可靠地持久化状态更改
  4. 内存效率:最小化资源需求

StateDB 提供了该复杂系统的干净接口,抽象了底层的 Trie 操作和数据库交互,同时提供了对当前状态的一致视图。

来源: core/state/statedb.go core/state/state_object.go trie/trie.go