前言
不要指望看完本文就能瞭解全部細節,本文的目的就是帶你快速瞭解相關概念,因爲直接深入代碼會越看越迷惑,其他文章很少提及本文所述內容,不知道是不是他們認爲這些基礎知識太簡單了
StateDB 用法與結構
以太坊是基於賬戶體系實現的,塊通過 parentHash 鏈在一起,每個塊都包含若干交易,每個交易都包含賬戶 from 和 to(部署合約時除外),全部的賬戶湊在一起就是組成了 StateDB,每個塊的 StateDB 都用一顆叫做 Trie 的樹來組織賬戶信息,具體結構如下圖:
保存賬戶信息的 StateDB 通常會存儲在磁盤上,通過 Block.StateRoot 來進行加載,StateRoot 是樹根,也是 leveldb 中的一個 key, 這個根只對應當前塊的交易相關的賬戶信息,value 是這棵樹的全部葉子節點,加載的時候會用葉子節點來構建下圖中的樹型結構
智能合約的地址也被當作賬戶管理,當 Account 爲一個智能合約時,那麼這個 stateObject 也會包含一顆樹,用來保存智能合約的最新狀態信息,這些信息是每次執行 evm 中 SSTORE 這個指令時的輸入信息,key 是合約的變量名,value 是最新值,這棵樹的加載過程與上圖中的過程完全一致
StateDB 的工作方式
用最少的代碼和最簡單的例子說明 StateDB 的主要工作原理與過程
- StateDB 的結構定義
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
// 由 snapshot id 和 journal 長度組成,用於回滾
validRevisions []revision
// 下一個可用的 snapshot id
nextRevisionId int
lock sync.Mutex
}
*StateDB 的關鍵方法
func (self *StateDB) AddBalance(addr common.Address, amount *big.Int)
func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
func (s *StateDB) Finalise(deleteEmptyObjects bool)
通過上面的三個方法可以演示整個 statedb 的操作流程
當執行AddBalance
方法會設置addr
的stateObject.Account.Balance += amount
,這時addr
對應的stateObject
會被放在StateDB.stateObjects
中緩存起來,
當執行Commit
方法時,會將StateDB.stateObjects
中的數據構建成一顆默克爾樹存放在StateDB.trie
中,修改數據時會產生journal
日誌,爲了方便出錯時的回滾,
當執行Finalise
方法時會刪除journal
刷新stateObject
的trie.root
(如果 stateObject 存在 trie)
執行到此時樹的結構已經確定,最終的樹根也已經確定,但是在以太坊中數據此時還沒有進入數據庫
爲了提高效率StateDB對數據做了緩存處理,大部分時間都是放在內存中的,StateDB.db 是 state.Database 接口的實現,定義如下:
// Database wraps access to tries and contract code.
type Database interface {
// blockchain 的 樹,root == block.stateRoot
OpenTrie(root common.Hash) (Trie, error)
// account 的 樹,addrHash == account.address
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
......
// TrieDB retrieves the low level trie database used for data storage.
TrieDB() *trie.Database
}
需要使用 TrieDB 這個方法返回的 trie.Database 對象來最終完成 trei 寫入 leveldb 的操作,這個方法在 WriteBlockWithState 中會被有條件調用,此處不再深入,謹以此文說明 Block、 StateDB、 Trie 之間的關係