ethDB.md
July 25, 2018 · View on GitHub
StateDB
StateDB有一个trie.Trie类型成员trie,它又被称为storage trie,这个MPT结构中存储的都是stateObject对象,每个stateObject对象以其地址作为插入节点的Key。
StateDB结构定义如下:
// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
// StateDB 使用the merkle trie 存储以太坊协议中使用的任何内容
// StateDB负责缓存和存储嵌套状态
type StateDB struct {
db Database
trie Trie // 负责管理 stateObject 对象
// This map holds 'live' objects, which will get modified while processing a state transition.
// Address ===> stateObject
stateObjects map[common.Address]*stateObject
stateObjectsDirty map[common.Address]struct{}
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// The refund counter, also used by state transitioning.
refund uint64
thash, bhash common.Hash
txIndex int
logs map[common.Hash][]*types.Log
logSize uint
preimages map[common.Hash][]byte
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
validRevisions []revision
nextRevisionId int
lock sync.Mutex
}
在 StateDB 的主要作用是管理 stateObject 对象, stateObject 与具体的以太坊账户 Address 关联。StaseObject 对象中主要存储着以太坊 Address 相关的余额(Account)、合约字节码(Code)。每当一个stateObject有改动,亦即“账户”信息(如:收到一笔转账,余额增加了)有变动时,这个stateObject对象会更新,并且这个stateObject会标为 dirty (即,dirtyCode 为 True),此时所有的数据改动还仅仅存储在map里。
当IntermediateRoot()调用时,所有标为dirty的stateObject才会被一起写入trie。
// IntermediateRoot 计算当前 stateTrie的 root hash
// 在事务之间调用以获取进入事务收据的根哈希。
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.Finalise(deleteEmptyObjects)
return s.trie.Hash()
}
而整个trie中的内容只有在 CommitTo()调用时被一起提交到底层数据库。可见,这个map被用作本地的一级缓存,trie是二级缓存,底层数据库是第三级,各级数据结构的界限非常清晰,这样逐级缓存数据,每一级数据向上一级提交的时机也根据业务需求做了合理的选择。
具体关系图如下所示:

再来看一下stateObject对象:
// StaseObject 表示正在修改的以太坊帐户
// 使用模式如下:
// 首先,获取stateObject
// 可以通过stateObject访问和修改帐户值(Account)
// 最后,调用CommitTrie将修改后的 storage trie写入数据库中
type stateObject struct {
address common.Address // 账户地址
addrHash common.Hash // 以太坊账户地址的hash值
data Account // 账户相关数据
db *StateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// 写缓存
trie Trie // storage trie, which becomes non-nil on first access
code Code // 合约字节码,在加载合约时被设置
cachedStorage Storage // Storage entry cache to avoid duplicate reads
dirtyStorage Storage // 需要刷新到磁盘的存储条目
// 缓存标志
// 当stateObject对象被标记为suicided时,将在状态 update 阶段将该对象从Trie中删除。
dirtyCode bool // 如果代码已更新,则为 True
suicided bool
deleted bool
}
// StateDB 提供方法SetCode(),可以将指令数组Code存储在某个stateObject对象中;方法GetCode(),可以从某个stateObject对象中读取已有的指令数组Code。
func (self *StateDB) GetCode(addr common.Address) []byte {
stateObject := self.getStateObject(addr)
if stateObject != nil {
return stateObject.Code(self.db)
}
return nil
}
func (self *StateDB) GetCodeSize(addr common.Address) int {
stateObject := self.getStateObject(addr)
if stateObject == nil {
return 0
}
if stateObject.code != nil {
return len(stateObject.code)
}
size, err := self.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash()))
if err != nil {
self.setError(err)
}
return size
}
每个stateObject对象管理着Ethereum世界里的一个“账户”。stateObject有一个成员变量data,类型是Accunt结构体,里面存有账户Ether余额,合约发起次数,最新发起合约指令集的哈希值,以及一个MPT结构的顶点哈希值。

Account 结构如下:
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
Nonce uint64 // 合约调用次数
Balance *big.Int // 账户余额
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
stateObject内部也有一个Trie类型的成员trie,被称为storage trie,它里面存放的是一种被称为State的数据。State跟每个账户相关,格式是[Hash, Hash]键值对。有意思的是,stateObject内部也有类似StateDB一样的二级数据缓存机制,用来缓存和更新这些State。

stateObject定义了一种类型名为storage的map结构,用来存放[]Hash,Hash]类型的数据对,也就是State数据。当SetState()调用发生时,storage内部State数据被更新,相应标示为"dirty"。之后,待有需要时(比如updateRoot()调用),那些标为"dirty"的State数据被一起写入storage trie,而storage trie中的所有内容在CommitTo()调用时再一起提交到底层数据库。
StateDB 并不是真正的数据库,只是一行为类似数据库的结构体。它在内部以Trie的数据结构来管理各个基于地址的账户,可以理解成一个cache;当该账户的信息有变化时,变化先存储在Trie中。仅当整个Block要被插入到BlockChain时,StateDB 里缓存的所有账户的所有改动,才会被真正的提交到底层数据库。