前言
不要指望看完本文就能了解全部細節(jié),本文的目的就是帶你快速了解相關概念,因為直接深入代碼會越看越迷惑,其他文章很少提及本文所述內(nèi)容,不知道是不是他們認為這些基礎知識太簡單了
StateDB 用法與結構
以太坊是基于賬戶體系實現(xiàn)的,塊通過 parentHash 鏈在一起,每個塊都包含若干交易,每個交易都包含賬戶 from 和 to(部署合約時除外),全部的賬戶湊在一起就是組成了 StateDB,每個塊的 StateDB 都用一顆叫做 Trie 的樹來組織賬戶信息,具體結構如下圖:

保存賬戶信息的 StateDB 通常會存儲在磁盤上,通過 Block.StateRoot 來進行加載,StateRoot 是樹根,也是 leveldb 中的一個 key, 這個根只對應當前塊的交易相關的賬戶信息,value 是這棵樹的全部葉子節(jié)點,加載的時候會用葉子節(jié)點來構建下圖中的樹型結構

智能合約的地址也被當作賬戶管理,當 Account 為一個智能合約時,那么這個 stateObject 也會包含一顆樹,用來保存智能合約的最新狀態(tài)信息,這些信息是每次執(zhí)行 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 的操作流程
當執(zhí)行AddBalance方法會設置addr的stateObject.Account.Balance += amount,這時addr對應的stateObject會被放在StateDB.stateObjects中緩存起來,
當執(zhí)行Commit方法時,會將StateDB.stateObjects中的數(shù)據(jù)構建成一顆默克爾樹存放在StateDB.trie中,修改數(shù)據(jù)時會產(chǎn)生journal日志,為了方便出錯時的回滾,
當執(zhí)行Finalise方法時會刪除journal刷新stateObject的trie.root(如果 stateObject 存在 trie)
執(zhí)行到此時樹的結構已經(jīng)確定,最終的樹根也已經(jīng)確定,但是在以太坊中數(shù)據(jù)此時還沒有進入數(shù)據(jù)庫
為了提高效率StateDB對數(shù)據(jù)做了緩存處理,大部分時間都是放在內(nèi)存中的,StateDB.db 是 state.Database 接口的實現(xiàn),定義如下:
// 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 中會被有條件調(diào)用,此處不再深入,謹以此文說明 Block、 StateDB、 Trie 之間的關系
statedb 存儲結構
去數(shù)據(jù)庫里讀一下狀態(tài)信息,再去迭代一下 trie ,即可了解整個 statedb 的存儲結構,
觸發(fā) state 存儲只有 3 個條件
- preimages 到 4MB
- stateobject 到 20MB
- Stop node 時
比如 100塊時保存過 State,其中包含了 [A,B,C] 燦哥賬戶,到了 150 塊時又要保存 State 這是增加了 [D,E] 兩個賬戶的信息,此時 150 保存的就是 [A,B,C,D,E] ,如此看來整個 statedb 的不定期存儲,而且是全量備份數(shù)據(jù);
TODO : 是不是當快速同步時,下載到目標塊以后,開始下載狀態(tài)數(shù)據(jù),就只下載目標塊對應的狀態(tài)即可?這個還要繼續(xù)閱讀有關同步部分的代碼,才能確定.