全局


创建状态树
状态数据库的定义如下:
1 2 3 4 5 6 7 8
| type StateDB struct { db Database 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
| func New(root hash.Hash, db Database) (*StateDB, error) { tr, err := db.OpenTrie(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 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
| 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 中,只需要遍历此集合 ② 便可以提交所有修改。
- 处理完每个需要提交的账户内容外,最后需要将账户树提交 ⑦。在提交过程中涉及账户内容作为叶子节点,在发送变动时,将更新账户节点和父节点的关系。