以太坊作为全球领先的智能合约平台,其核心之一便是账户系统,理解以太坊如何管理账户,对于深入把握区块链的工作原理、开发安全的应用程序以及排查问题至关重要,本文将基于以太坊源码,详细探讨账户管理的核心机制,包括账户的类型、结构、存储、以及与之相关的核心数据结构和操作。
账户:以太坊世界的基本单元
在以太坊中,所有状态(余额、代码、存储等)都以账户的形式存在,账户是区块链上状态的基本单位,类似于传统银行系统中的账户,但功能更为强大和复杂,以太坊定义了两种主要的账户类型:
- 外部账户 (Externally Owned Accounts, EOAs):由用户通过私钥控制,没有关联的代码,最常见的EOAs就是普通用户的钱包地址,用于发送交易、支付ETH等。
- 合约账户 (Contract Accounts):由部署的智能代码控制,拥有代码和存储,当EOA向合约账户发送交易或调用其方法时,合约账户的代码会被执行。
账户的核心数据结构:Account与StateAccount
在以太坊的Go源码(主要在core/types和core/state包中)中,账户的定义是理解账户管理的基础。
core/types/account.go - 简化账户模型
// Account represents an Ethereum account.
// It only contains the metadata, the actual state is stored in a trie.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // Merkle root of the storage trie
CodeHash common.Hash
}
这个Account结构体是账户在状态树(Merkle Patricia Trie)中存储的简化表示,它包含了四个关键字段:
- Nonce:一个计数器,用于防止重放攻击,对于EOA,它代表该账户发送的交易数量;对于合约账户,它代表该账户创建的合约数量。
- Balance:账户持有的以太币数量,以
wei(1 ETH = 10^18 wei)为单位,使用big.Int表示以支持大数。 - Root:一个Merkle Patricia Trie的根哈希,这个 trie 存储了合约账户的持久化数据(即状态变量),对于EOA,这个字段为空。
- CodeHash:账户关联代码的哈希值,对于EOA,这个字段是空字符串的哈希(
c5d2460186f7233c9272727d6c5686c5d2460186f7233c9272727d6c5686c);对于合约账户,是其代码的哈希。
core/state/state_object.go - 内存中的账户对象
当账户被加载到内存中进行操作时,它会表示为StateObject结构体(位于core/state/state_object.go),这个结构体不仅包含了Account的所有信息,还提供了修改和查询账户的方法,以及缓存机制。
// StateObject represents an Ethereum account which is being modified.
type StateObject struct {
address common.Address
addrHash common.Hash // hash of the address
data Account
db *StateDB
// Cache fields
dirty bool
dirtyStorage map[common.Hash]common.Hash
suicide bool
}
- address/addrHash:账户地址及其哈希。
- data:嵌入的
Account结构体,包含账户的核心状态。 - db:指向
StateDB的引用,StateDB是管理所有账户状态的核心数据库,负责与底层的Merkle Trie交互。 - dirty/dirtyStorage/suicide:用于缓存和状态管理的标志位和字段。
dirty表示账户本身被修改,dirtyStorage表示账户的存储项被修改,suicide表示账户被标记为自毁(将在交易执行后被移除)。
账户的存储:合约账户的持久化数据
合约账户的持久化数据(即状态变量)存储在一个独立的Merkle Patricia Trie中,这个Trie的根哈希就是Account结构体中的Root字段。
- 存储布局:合约的存储是一个键值对映射,键和值都是32字节的数组(
common.Hash),Solidity中的状态变量会被映射到这些键上。 StateDB的存储管理:core/state/statedb.go中的StateDB负责管理所有合约账户的存储Trie,当读取或写入合约的存储时,StateDB会:- 获取或创建对应合约地址的
StateObject。 - 通过
StateObject的storage字段(通常是一个map[common.Hash]common.Hash)来访问内存中的缓存数据。 - 如果数据不在缓存中,则从底层的
Database(通常是ethdb.Database接口的实现,如LevelDB)中加载对应的存储Trie节点,并更新缓存。 - 写入操作会先更新内存缓存,并标记
dirtyStorage,后续再批量写入到Trie中。
- 获取或创建对应合约地址的
账户的创建、更新与删除
账户的生命周期管理是账户系统的重要组成部分。
-
账户创建:
- EOA创建:当用户使用私钥签名一笔交易时,如果目标地址不存在,状态管理模块会隐式地创建一个EOA(通常余额为0,Nonce为0)。
- 合约创建:当一笔交易的目的地址是空(或
nil),并且包含数据(即合约字节码)时,以太坊会创建一个新的合约账户,新合约账户的地址由发送者地址和发送者Nonce通过特定算法(CREATE2有不同算法)生成,合约代码被存储,CodeHash被设置,Root初始化为空Trie的根哈希。
-
账户更新:
- 余额修改

- 余额修改