eth_state

state trie的存储模型

state/Trie接口

1
2
3
4
5
6
7
8
9
10
11
// Trie is a Ethereum Merkle Trie.
type Trie interface {
TryGet(key []byte) ([]byte, error)
TryUpdate(key, value []byte) error
TryDelete(key []byte) error
Commit(onleaf trie.LeafCallback) (common.Hash, error)
Hash() common.Hash
NodeIterator(startKey []byte) trie.NodeIterator
GetKey([]byte) []byte // TODO(fjl): remove this when SecureTrie is removed
Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) error
}

Trie接口中定义的方法有Get、Put等操作/查询数据的方法,除此之外,Trie作为数据结构,在进行持久化时需要自定义持久化的数据结构,Commit方法则为进行持久化时的操作。

Trie接口的实现主要是SecureTrie。cachedTrie结构中包含了匿名成员SecureTrie,所以cachedTrie也算Trie接口的实现。SecureTrie位于trie包下,内部是trie/Trie结构体(非接口)的封装,trie/Trie结构体是Trie数据结构的具体实现。

state/Database接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Database wraps access to tries and contract code.
type Database interface {
// OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error)

// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)

// CopyTrie returns an independent copy of the given trie.
CopyTrie(Trie) Trie

// ContractCode retrieves a particular contract's code.
ContractCode(addrHash, codeHash common.Hash) ([]byte, error)

// ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)

// TrieDB retrieves the low level trie database used for data storage.
TrieDB() *trie.Database
}

state/Database接口,提供了从数据库读出数据一系列方法,主要是读出数据并组成Trie树数据结构。同时还提供了获取db的接口以供直接操作db。

trie.Database是底层数据库的直接封装实现,增删改查一系列方法。这里使用的是leveldb,leveldb采用key-value的形式,相关知识暂时不展开了。

trie包下的Trie是树的具体实现,Database是数据库的具体实现。

state包下的Trie接口是接口(= =).. Database主要是围绕state进行的读取接口。其具体实现也是对trie/Trie和trie/Database的封装。cachingDB另外有一层缓存实现。

组织模型

statedb是key-value结构,value类型为stateObject,最后持久化到数据库的只有stateObject中的data,statedb封装了Database和Trie,可直接进行数据操作。从数据库的key-value来看,存储的value为stateObject中的data(Account类型),组织trie树的key为addr,从数据操作上来看,state可直接进行账户的操作(setBalance、getBalance等等),其实操作的都是对应stateObject中的data,最后Commit的时候持久化到数据库。中间还涉及别的很多东西,例如缓存、快照等,以供事务处理。

statedb内部封装的操作是可以直接存储数据库的,stateObject中data类型为account,内部直接封装了value的序列化和反序列化。如果想存储非account类型的数据呢,statedb也提供了相关方法SetStateGetState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var (
stateAddr = common.BytesToAddress([]byte{1, 1, 1, 1, 2})
key = common.BytesToHash([]byte("names_service"))
)
func TestSingleTrie(t *testing.T) {
db := ethdb.NewMemDatabase()
state, _ := state.New(common.Hash{}, state.NewDatabase(db))
state.SetState(stateAddr, key, common.BytesToHash([]byte{0, 0, 0}))
state.IntermediateRoot(false)
hash := state.GetState(stateAddr, key)
if hash != common.BytesToHash([]byte{0, 0, 0}) {
t.Error("unexpected err")
}
}

setState的value的类型为hash。也就是说setState只能存储一个哈希值。如果想存储别的东西,可以直接使用trie/database相关接口进行存储,然后将存储的key作为setState的value,查询时先getState获取key,然后再查询value,这样就可以进行索引到存储的value了。如智能合约代码的存储,stateObject.data中只存储了合约代码的hash,合约代码存放在另外的地方。

trie/Database下提供的直接存储/查询接口为

1
2
3
4
//查询
func (db *Database) Node(hash common.Hash) ([]byte, error)
//存储
func (db *Database) InsertBlob(hash common.Hash, blob []byte)

智能合约代码的存储/读取就采用了以上两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
code, err := db.db.Node(codeHash)
if err == nil {
db.codeSizeCache.Add(codeHash, len(code))
}
return code, err
}

func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
...
// Write any contract code associated with the state object
if stateObject.code != nil && stateObject.dirtyCode {
s.db.TrieDB().InsertBlob(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
stateObject.dirtyCode = false
}
...
}

以下示例为存储额外的Trie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func TestSingleTrie(t *testing.T) {
db := ethdb.NewMemDatabase()
state, _ := state.New(common.Hash{}, state.NewDatabase(db))
state.SetState(stateAddr, key, common.BytesToHash([]byte{0, 0, 0}))
state.IntermediateRoot(false)
hash := state.GetState(stateAddr, key)
if hash != common.BytesToHash([]byte{0, 0, 0}) {
t.Error("unexpected err")
}


err = trie.TryUpdate([]byte("anny"), []byte("0xccccc"))
if err != nil {
t.Error("update err, ", err)
}
t.Log(trie.Hash())
trie.Commit(nil)
t.Log(trie.Hash())
err = trie.TryUpdate([]byte("anny"), []byte("0xccccb"))
if err != nil {
t.Error("update err, ", err)
}
err = trie.TryUpdate([]byte("jim"), []byte("0xbbbbbc"))
if err != nil {
t.Error("update err, ", err)
}
trie.Commit(nil)
t.Log(trie.Hash())
state.SetState(stateAddr, key, trie.Hash())
hash = state.GetState(stateAddr, key)
if hash != trie.Hash() {
t.Error("unexpected err")
}
trie1, err := state.Database().OpenStorageTrie(common.Hash{}, hash)
if err != nil {
t.Error("unexpected err", err)
}
value, err := trie1.TryGet([]byte("anny"))
if err != nil {
t.Error("unexpected err", err)
}
if string(value) != "0xccccb" {
t.Error("unexpected err")
}
}