区块链底层-状态机StateDB

本文最后更新于:2023年6月19日 晚上

全局

image.png
image.png

创建状态树

状态数据库的定义如下:

1
2
3
4
5
6
7
8
type StateDB struct {
db Database //操作状态的底层数据库,在实例化 StateDB 时指定 ②。
trie trie.Trie //世界状态所在的树实例对象
stateObjects map[account.Address]*account.StateObject //已账户地址为键的账户状态对象,能够在内存中维护使用过的账户
stateObjectsDirty map[account.Address]struct{}//标记被修改过的账户
dbErr error
lock sync.Mutex
}

首先,

1
2
db: = state.NewDatabase(levelDB)
statedb, err := state.New(block.Root(), db)

这里的 New 函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//state/statedb.go:27
func New(root hash.Hash, db Database) (*StateDB, error) {
tr, err := db.OpenTrie(root)//打开指定状态版本(root)的含世界状态的顶层树
if err != nil {
return nil, err
}
return &StateDB{
db: db,//②
trie: tr,
stateObjects: make(map[account.Address]*stateObject),
stateObjectsDirty: make(map[account.Address]struct{}),
preimages: make(map[hash.Hash][]byte),
journal: newJournal(),
}, nil
}

世界态中的所有状态都是已账户为基础单位存在的,因此为了便于账户隔离管理,使用不开放的 stateObject 来维护某个账户下的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type StateObject struct {
//账户哈希
addrHash string
data User //账户属性
//底层数据库
db *state.StateDB
// 写缓存
trie Trie // 存储树,第一次访问时初始化
}
type User struct {
//账户地址
Address []byte
//账户余额
Amount float64
//交易个数
Nonce uint64
//storage树根哈希值
Root string
}

可以看到 stateObject 中维护关于某个账户的所有信息,涉及账户地址、账户地址哈希、底层数据库、存储树等内容。
在区块中,将交易作为输入条件,来根据一系列动作修改状态。 在完成区块挖矿前,只是获得在内存中的状态树的 Root 值。 StateDB 可视为一个内存数据库,状态数据先在内存数据库中完成修改,所有关于状态的计算都在内存中完成。

1
2
3
4
5
6
7
8
9
10
//将每个交易对应的的账户状态树进行修改
usr := getUserByAddress(t.Address)
if usr.Amount - t.Fee <0 {
return err
}
usr.Nonce = usr.Nonce + 1
uMPT := getMPT(usr.Root)
data := *(*[]byte)(unsafe.Pointer(&t))
uMPT.Put([]byte(t.Hash),data)
usr.Root := uMPT.RootHash()

在将区块持久化时完成有内存到数据库的更新存储,此更新属于增量更新,仅仅修改涉及到被修改部分。

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
// state/statedb.go:122
func (s *StateDB) Commit(deleteEmptyObjects bool) (root hash.Hash, err error) {
for addr := range s.journal.dirties {//①⑧⑨⑩
s.stateObjectsDirty[addr] = struct{}{}
}
for addr, stateObject := range s.stateObjects {//②
_, isDirty := s.stateObjectsDirty[addr]

if isDirty{
//如果集合中的账户有变更
if err := stateObject.CommitTrie(s.db); err != nil {//⑤
return common.Hash{}, err
}
s.updateStateObject(stateObject)//需要提交此账户
}
delete(s.stateObjectsDirty, addr)
}
}
//...
root, err = s.trie.Commit(func(leaf []byte, parent hash.Hash) error {//⑦
var account Account
if err := rlp.DecodeBytes(leaf, &account); err != nil {
return nil
}
if account.Root != emptyRoot {
s.db.TrieDB().Reference(account.Root, parent)
}
return nil
})
return root, err
}
  • 因为在修改某账户信息是,将会记录变更流水(journal),因此在提交保存修改时只需要将在流水中存在的记录作为修改集 ①。
  • 所有访问过的账户信息,均被记录在 stateObjects 中,只需要遍历此集合 ② 便可以提交所有修改。
  • 处理完每个需要提交的账户内容外,最后需要将账户树提交 ⑦。在提交过程中涉及账户内容作为叶子节点,在发送变动时,将更新账户节点和父节点的关系。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!