以太坊作为全球领先的智能合约平台,其核心在于账户模型的设计与管理,理解以太坊账户的源码实现,对于深入把握以太坊的工作原理、安全机制以及开发安全可靠的DApp至关重要,本文将基于以太坊客户端(以Go客户端geth为例)的源码,对以太坊账户的核心实现与机制进行深入分析。
以太坊账户模型概览
在深入源码之前,我们先简要回顾以太坊的账户模型,以太坊主要有两种账户类型:
- 外部账户 (Externally Owned Accounts, EOAs):由用户通过私钥控制,没有关联的代码,发起交易、部署合约等操作通常由EOAs驱动,其标识是地址(Address)。
- 合约账户 (Contract Accounts):由智能合约代码控制,拥有状态(State),可以通过接收交易或调用其他合约来改变自身状态,其标识也是地址(由创建者地址和nonce等生成)。
这两种账户共同构成了以太坊的状态基础,所有账户信息都存储在以太坊的MPT(Merkle Patricia Trie)状态数据库中。
账户数据结构:Account 与 StateObject
在以太坊Go客户端(如geth)的core/state包中,账户的核心数据结构是Account和StateObject。
core/types.Account 结构体
这个结构体定义了账户在状态 trie 中存储的基本数据格式,它是一个序列化的结构:
// Account represents an account in the state database.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // Merkle root of the storage trie
CodeHash common.Hash
}
- Nonce:一个递增的计数器,用于防止重放攻击,对于EOA,它代表该账户发起的交易数量;对于合约账户,它代表该账户创建的合约数量。
- Balance:账户持有的以太币数量,以wei为单位。
- Root:存储合约账户数据的Merkle Patricia Trie的根哈希,对于EOA,此值为空。
- CodeHash:账户代码的哈希值,对于EOA,此值为空字符串的哈希;对于合约账户,此值为合约代码的哈希。
这个Account结构体是状态数据库中存储的“信息。
core/state.StateObject 结构体
StateObject则是内存中账户对象的表示,它包含了Account结构体的信息,并提供了更多的方法和功能,用于修改和查询账户状态。
// StateObject represents an Ethereum account object in the state trie.
type StateObject struct {
address common.Address
data Account
db Database
dbErr error
dirty bool // 是否已被修改
deleted bool // 是否已被删除
onDirty func(addr common.Address) // 当状态改变时的回调函数
// 以下字段用于合约账户
code Code
storage map[common.Hash]common.Hash // 合约的存储槽
storageDirty map[common.Hash]struct{} // 标记哪些存储槽被修改
}
- address:账户地址。
- data:嵌入的
Account结构体,包含账户的基本信息。 - db:指向底层数据库(通常是MPT状态数据库)的接口,用于持久化状态变更。
- dirty/deleted:标记账户状态是否被修改或删除,用于后续的状态提交。
- onDirty:回调函数,在账户状态改变时触发。
- code:合约账户的代码(
Code类型,包含字节码和哈希)。 - storage/storageDirty:合约账户的存储槽及其修改标记。
StateObject是账户状态在内存中的“活性”代表,所有对账户状态的修改(如转账、合约调用读写存储)都通过StateObject进行。
账户的创建与管理
账户的创建
- EOA创建:EOA由用户通过导入私钥或创建新钱包生成,其
Nonce从0开始,Balance初始为0(或转入一定数量),Root和CodeHash为空。 - 合约账户创建:当EOA发送一个创建合约的交易时,以太坊节点会执行以下步骤:
- 创建一个新的合约账户地址(基于创建者地址和创建者的Nonce)。
- 初始化该合约账户的
StateObject,设置Nonce为1(因为创建了一个合约),Balance通常为0(除非在构造函数中转入),Root初始化为空(对应一个空的存储trie),CodeHash为空字符串的哈希。 - 将合约的字节码部署到该账户,并更新
CodeHash为字节码的哈希。 - 执行合约的构造函数(init code),修改合约账户的存储状态。
- 将修改后的
StateObject标记为dirty,等待写入状态数据库。
