在以太坊里,账户的状态数据最终是以键值对的形式存储在LevelDB,所有的交易或者操作的结果,作为账户的状态(state)存在,账户是以stateObject体现,而所有的stateObject都接授stateDB的管理。

  比较直观的表示就是这样:

  我们按照StateObjectAddressStateDB自底向上的顺序来看一下各部分的细节。

StateObject

  一个以太坊账户对应的就是一个StateObject,其中包含着很多非常有用的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type stateObject struct {
address common.Address
addrHash common.Hash
data Account
db *StateDB
dbErr error
trie Trie
code Code
cachedStorage Storage
dirtyStorage Storage
dirtyCode bool
suicided bool
touched bool
deleted bool
onDirty func(addr common.Address)
}

   其中比较重要的字段有address,data,code,trie

  • address: 记录了合约的地址;
  • data: 保存了合约的余额,默克尔树根等一些元信息;
  • code: 以[]byte的形式保存了合约的内容;
  • trie: 是StateObject最为核心的字段,通过MPT树来管理合约的数据;

  在以太坊中有大量的账户,要找到这些账户就需要addressstateDB也是通过address来管理这些账户的。

StateDB

  StateDB用来管理数量众多的账户。
  其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type StateDB struct {
db Database
trie Trie
// 用于暂存活跃的账户
stateObjects map[common.Address]*stateObject
stateObjectsDirty map[common.Address]struct{}
dbErr error
refund uint64
thash, bhash common.Hash
txIndex int
logs map[common.Hash][]*types.Log
logSize uint
preimages map[common.Hash][]byte
// 用于快照,回滚
journal journal
validRevisions []revision
nextRevisionId int
lock sync.Mutex

  乍一看的反应是账户数据以一个map的形式存在stateObjects字段里,但事实是账户的数据是存储在trie中的,存储的形式和单个合约里状态的状态是一样的。
  stateObjects只是作为一个缓存,当创建,获得stateObject的时候会添加进去,提升读取的性能。
  我们知道这些账户数据最终是存储在LevelDB中的,stateObjects相当于一级缓存,如果查询不到,可以到trie中查询,如果都查询不到就只能去查找数据库了。
  StateDB也支持快照和回滚操作,实现的比较简单,具体的数据在jornal中,而快照的索引,需要回滚的时候只需求拿到索引暂存的数据反序列化回来就可以了。