以太坊EVM源碼注釋之State
Ethereum State
EVM在給定的狀态下使用提供的上下文(Context)運作合約,計算有效的狀态轉換(智能合約代碼執行的結果)來更新以太坊狀态(Ethereum state)。是以可以認為以太坊是基于交易的狀态機,外部因素(賬戶持有者或者礦工)可以通過建立、接受、排序交易來啟動狀态轉換(state transition)。

從狀态的角度來看,可以将以太坊看作是一條狀态鍊;
從實作來看,可以将以太坊看作是一條區塊組成的鍊,即"區塊鍊(Blockchain)"。
在頂層,有以太坊世界狀态(world state),是以太坊位址(20位元組,160bit)到賬戶(account)的映射。
在更低的層面,每個以太坊位址表示一個包含餘額、nonce、storage、code的帳戶。以太坊賬戶分為兩種類型:
- EOA(Externally owned account), 由一個私鑰控制,不能包含EVM代碼,不能使用storage;
- Contract account,由EVM代碼控制。
可以認為是在以太坊世界狀态的沙箱副本上運作EVM,如果由于任何原因無法完成執行,則将完全丢棄這個沙箱版本。如果成功執行,那麼現實世界的狀态就會更新到與沙箱版本一緻,包括對被調用合約的存儲資料的更改、建立的新合約以及引起的賬戶餘額變化等。[3]
State子產品主要源代碼目錄如下:
~/go-ethereum-master/core/state# tree
.
├── database.go // 提供了trie樹的抽象,提供了一個資料庫的抽象。實作了CacheDB結構
├── dump.go // dump
├── iterator.go // 疊代trie,後序周遊整個狀态樹
├── iterator_test.go
├── journal.go // 記錄檔,針對各種操作的日志提供了對應的復原功能
├── statedb.go // stateDB結構定義及操作方法
├── statedb_test.go
├── state_object.go // stateObject結構定義及操作方法
├── state_object_test.go
├── state_test.go
├── sync.go // 用于狀态同步功能
└── sync_test.go
StateDB
以太坊state子產品實作了賬戶餘額模型,它記錄了每個賬戶的狀态資訊,每當有交易發生,就更改相應賬戶的狀态。state 子產品中的主要對象是
StateDB
,它通過大量的
stateObject
對象集合管理所有賬戶資訊,提供了各種管理賬戶資訊的方法。
StateDB
的
db
字段類型是
Database
接口,
Database
封裝了對樹(trie)和合約代碼的通路方法,在實際的調用代碼中,它隻有一個執行個體
cachingDB
。
cachingDB
封裝的
trie
的通路方法操作的都是
SecureTrie
對象,
SecureTrie
實作了
state.Trie
接口。
StateDB
有一個
state.Trie
類型成員
trie
,它又被稱為
storage trie
,這個MPT結構中存儲的都是
stateObject
對象,每個
stateObject
對象以其位址作為插入節點的Key;每次在一個區塊的交易開始執行前,trie由一個哈希值(hashNode)恢複(resolve)出來。另外還有一個map結構
stateObjects
,存放
stateObject
,位址作為map的key,用來緩存所有從資料庫中讀取出來的賬戶資訊,無論這些資訊是否被修改過都會緩存在這裡。
stateObjectsPending
用來記錄已經完成修改但尚未寫入
trie
的賬戶,
stateObjectsDirty
用來記錄哪些賬戶資訊被修改過了。需要注意的是,這兩個字段并不時刻與
stateObjects
對應,并且也不會在賬戶資訊被修改時立即修改這兩個字段。在進行
StateDB.Finalise
等操作時才會将
journal
字段中記錄的被修改的賬戶整理到
stateObjectsPending
和
stateObjectsDirty
中。在代碼實作中,這兩個字段用法并無太大差別,一般會成對出現,隻有在
createObjectChange
的
revert
方法中單獨出現了
stateObjectsDirty
。是以
stateObjectsPending
和
stateObjectsDirty
的差別可能在于:
stateObjectsPending
存的賬戶已經完成更改,狀态已經确定下來,隻是還沒有寫入底層資料庫,應該不會再進行復原;
stateObjectsDirty
的賬戶修改還沒最終确定,可能繼續修改,也有可能復原。但是state子產品的代碼并沒有展現出來這種差別,不清楚别的子產品代碼有沒有相關内容。
journal
字段記錄了
StateDB
進行的所有操作,以便将來進行復原。在調用
StateDB.Finalise
方法将
juournal
記錄的賬戶更改"最終确定(finalise)"到
stateObjects
以後,
journal
字段會被清空,無法再進行復原,因為不允許跨事務復原(一般會在事務結束時才會調用
stateObject
的
finalise
方法)。
如上圖所示,每當一個
stateObject
有改動,亦即賬戶狀态有變動時,這個
stateObject
會标為dirty,然後這個
stateObject
對象會更新,此時所有的資料改動還僅僅存儲在
stateObjects
裡。當調用
IntermediateRoot()
時,所有标為dirty的stateObject才會被一起寫入
trie
。而整個
trie
中的内容隻有在調用
Commit()
時被一起送出到底層資料庫。可見,
stateObjects
被用作本地的一級緩存,
trie
是二級緩存,底層資料庫是第三級,這樣逐級緩存資料,每一級資料向上一級送出的時機也根據業務需求做了合理的選擇。[7]
StateDB
結構源碼如下:
core/state/statedb.go
// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
// stateDB用來存儲以太坊中關于merkle trie的所有内容。 StateDB負責緩存和存儲嵌套狀态。
// 這是檢索合約和賬戶的一般查詢界面:
type StateDB struct {
db Database // 後端的資料庫
trie Trie // 樹 main account trie
// This map holds 'live' objects, which will get modified while processing a state transition.
// 下面的Map用來存儲目前活動的對象,這些對象在狀态轉換的時候會被修改。
stateObjects map[common.Address]*stateObject
// State objects finalized but not yet written to the trie 已完成修改的狀态對象(state object),但尚未寫入trie
// 隻記錄位址,并不記錄實際内容,也就是說隻要Map裡有鍵就行,不記錄值,對應的值從stateObjects找,然後進行相關操作。
stateObjectsPending map[common.Address]struct{}
// State objects modified in the current execution 在目前執行過程中修改的狀态對象(state object)
stateObjectsDirty map[common.Address]struct{}
// DB error. 資料庫錯誤
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
// stateObject會被共識算法的核心和VM使用,在這些代碼内部無法處理資料庫級别的錯誤。
// 在資料庫讀取期間發生的任何錯誤都會記錄在這裡,最終由StateDB.Commit傳回。
dbErr error
// The refund counter, also used by state transitioning.
// 退款計數器,用于狀态轉換
refund uint64
thash, bhash common.Hash // 目前的transaction hash 和block hash
txIndex int // 目前的交易的index
logs map[common.Hash][]*types.Log // 日志 key是交易的hash值
logSize uint // 日志大小
preimages map[common.Hash][]byte // SHA3的原始byte[], EVM計算的 SHA3->byte[]的映射關系
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
// 狀态修改日志。這是快照和復原到快照的支柱。
journal *journal
validRevisions []revision
nextRevisionId int
// Measurements gathered during execution for debugging purposes
// 為調試目的而在執行期間收集的度量
AccountReads time.Duration
AccountHashes time.Duration
AccountUpdates time.Duration
AccountCommits time.Duration
StorageReads time.Duration
StorageHashes time.Duration
StorageUpdates time.Duration
StorageCommits time.Duration
}
除了各種管理賬戶資訊的方法和
stateObject
對象的增删改查方法之外,
StateDB
還有幾個重要的方法需要解釋:
Copy
core/state/statedb.go
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
// Copy建立狀态的一個獨立的深拷貝。
func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones
// 複制所有的基礎字段,初始化記憶體字段
state := &StateDB{
db: s.db,
trie: s.db.CopyTrie(s.trie),
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
refund: s.refund,
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize,
preimages: make(map[common.Hash][]byte, len(s.preimages)),
journal: newJournal(),
}
// Copy the dirty states, logs, and preimages
// 複制髒狀态,日志,和原象(preimages)。hash = SHA3(byte[]),這裡的原始byte[]即為preimage。
for addr := range s.journal.dirties {
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
// and in the Finalise-method, there is a case where an object is in the journal but not
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
// nil
// 正如文檔和Finalise方法中所述,有這樣一種情況:對象在日志中但不在stateObjects中:
// 在拜占庭版本之前,在ripeMD上建立但出現了OOG(out of Gas)錯誤。是以,我們需要檢查nil
if object, exist := s.stateObjects[addr]; exist {
// Even though the original object is dirty, we are not copying the journal,
// so we need to make sure that anyside effect the journal would have caused
// during a commit (or similar op) is already applied to the copy.
// 即使原始對象是髒的,我們也不會複制日志 為什麼不複制journal??
// 是以我們需要確定在送出(或類似的操作)期間日志可能造成的任何副作用已經應用到副本上。
state.stateObjects[addr] = object.deepCopy(state)
// Mark the copy dirty to force internal (code/state) commits 将副本标記為dirty以強制執行内部(代碼/狀态)送出
state.stateObjectsDirty[addr] = struct{}{}
// Mark the copy pending to force external (account) commits 将副本标記為pending以強制外部(帳戶)送出
state.stateObjectsPending[addr] = struct{}{}
}
}
// Above, we don't copy the actual journal. This means that if the copy is copied, the
// loop above will be a no-op, since the copy's journal is empty.
// Thus, here we iterate over stateObjects, to enable copies of copies
// 以上,我們不複制實際的日志。這意味着如果複制的對象是副本,上面的循環将是no-op,因為副本的日志是空的。
// 是以,這裡我們周遊stateObjects,以便可以複制副本的副本。
for addr := range s.stateObjectsPending {
if _, exist := state.stateObjects[addr]; !exist { // 如果stateObjects裡還不存在
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) // 深拷貝
}
state.stateObjectsPending[addr] = struct{}{}
}
for addr := range s.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
}
state.stateObjectsDirty[addr] = struct{}{}
}
// 日志
for hash, logs := range s.logs {
cpy := make([]*types.Log, len(logs))
for i, l := range logs {
cpy[i] = new(types.Log)
*cpy[i] = *l
}
state.logs[hash] = cpy
}
// preimages
for hash, preimage := range s.preimages {
state.preimages[hash] = preimage
}
return state
}
Finalise
StateDB.Finalise
操作将
journal
字段中記錄的被修改的賬戶整理到
stateObjectsPending
和
stateObjectsDirty
中。
core/state/statedb.go
// Finalise finalises the state by removing the self destructed objects and clears
// the journal as well as the refunds. Finalise, however, will not push any updates
// into the tries just yet. Only IntermediateRoot or Commit will do that.
// Finalise通過移除自毀對象和清除日志以及退款來完成狀态的最後确定。
// 然而,Finalise不會将更新寫入trie。隻有IntermediateRoot 和 Commit做那個工作。
// 也就是說這個時候更改還在stateObjects裡,沒有寫入trie,更沒有寫入底層的資料庫落地成檔案。
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
for addr := range s.journal.dirties {
obj, exist := s.stateObjects[addr]
if !exist {
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
// touch-event will still be recorded in the journal. Since ripeMD is a special snowflake,
// it will persist in the journal even though the journal is reverted. In this special circumstance,
// it may exist in `s.journal.dirties` but not in `s.stateObjects`.
// Thus, we can safely ignore it here
// ripeMD在第1714175個塊建立,在交易号為0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2的交易中
// 那個tx的gas不足,雖然“touched”的概念并不存在,但touch-event仍然會被記錄在日志中。因為ripeMD是一個特殊的個例,
// 即使日志被復原,它也将在日志中保持。在這種特殊情況下,它可能存在于`s.journal.dirties`中,但不在`s.stateObjects`中。
// 是以,我們可以放心地忽略它
continue
}
if obj.suicided || (deleteEmptyObjects && obj.empty()) {
obj.deleted = true
} else {
obj.finalise()
}
s.stateObjectsPending[addr] = struct{}{}
s.stateObjectsDirty[addr] = struct{}{}
}
// Invalidate journal because reverting across transactions is not allowed.
// 使日志失效,因為不允許跨事務恢複。
s.clearJournalAndRefund()
}
IntermediateRoot
此方法将
StateDB.stateObjectsPending
字段中的賬戶的更改寫入
trie
中,然後将該字段清空。
core/state/statedb.go
// IntermediateRoot computes the current root hash of the state trie.
// It is called in between transactions to get the root hash that
// goes into transaction receipts.
// IntermediateRoot計算狀态樹的目前根hash。這個方法會在交易執行的過程中被調用。會被存入交易收據中
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Finalise all the dirty storage states and write them into the tries
// Finalise所有的髒存儲狀态并将它們寫入樹中。
s.Finalise(deleteEmptyObjects)
// 将更改寫入樹中
for addr := range s.stateObjectsPending {
obj := s.stateObjects[addr]
if obj.deleted {
s.deleteStateObject(obj)
} else {
obj.updateRoot(s.db)
s.updateStateObject(obj)
}
}
// 清空
if len(s.stateObjectsPending) > 0 {
s.stateObjectsPending = make(map[common.Address]struct{})
}
// Track the amount of time wasted on hashing the account trie
// 跟蹤在得到帳戶樹hash花費的時間
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
}
return s.trie.Hash()
}
Commit
此方法先調用
IntermediateRoot
方法,然後将
StateDB.stateObjectsDirty
字段中的賬戶的更改寫入
trie
中,然後将該字段清空。最後将更改都寫入資料庫。
core/state/statedb.go
// Commit writes the state to the underlying in-memory trie database.
// Commit将狀态寫入底層記憶體trie資料庫。
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
// Finalize any pending changes and merge everything into the tries
// Finalize挂起的更改,并将所有内容合并到樹中
s.IntermediateRoot(deleteEmptyObjects)
// Commit objects to the trie, measuring the elapsed time
// 将對象送出到trie,統計運作時間
for addr := range s.stateObjectsDirty {
if obj := s.stateObjects[addr]; !obj.deleted {
// Write any contract code associated with the state object
// 寫入與狀态對象相關聯的任何合約代碼,寫入代碼更改
if obj.code != nil && obj.dirtyCode {
s.db.TrieDB().InsertBlob(common.BytesToHash(obj.CodeHash()), obj.code)
obj.dirtyCode = false
}
// Write any storage changes in the state object to its storage trie
// 将狀态對象中的任何存儲更改寫入其存儲trie
if err := obj.CommitTrie(s.db); err != nil {
return common.Hash{}, err
}
}
}
if len(s.stateObjectsDirty) > 0 { // 清空
s.stateObjectsDirty = make(map[common.Address]struct{})
}
// Write the account trie changes, measuing the amount of wasted time
// 記錄賬戶的變化,統計花費的時間
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now())
}
// The onleaf func is called _serially_, so we can reuse the same account
// for unmarshalling every time.
// onleaf func被稱為_serially_,是以我們可以在每次解碼時重用相同的帳戶。
var account Account
// 調用trie的Commit方法。
return s.trie.Commit(func(leaf []byte, parent common.Hash) error {
if err := rlp.DecodeBytes(leaf, &account); err != nil {
return nil
}
if account.Root != emptyRoot {
// Reference添加父節點到子節點的新引用。
s.db.TrieDB().Reference(account.Root, parent)
}
code := common.BytesToHash(account.CodeHash)
if code != emptyCode {
s.db.TrieDB().Reference(code, parent)
}
return nil
})
}
快照和復原
StateDB
的狀态版本管理功能依賴于兩個資料結構:
revision
和
journalEntry
。
core/state/statedb.go
// 用于復原的版本 快照版本
type revision struct {
id int
journalIndex int
}
core/state/journal.go
// journalEntry is a modification entry in the state change journal that can be
// reverted on demand.
// journalEntry是狀态更改日志中的一個修改條目,可以根據需要恢複。
type journalEntry interface {
// revert undoes the changes introduced by this journal entry.
// revert還原由本日志條目引入的更改。undo操作
revert(*StateDB)
// dirtied returns the Ethereum address modified by this journal entry.
// dirtied傳回被此日志條目修改的以太坊位址。
dirtied() *common.Address
}
// journal contains the list of state modifications applied since the last state
// commit. These are tracked to be able to be reverted in case of an execution
// exception or revertal request.
// journal包含自上次狀态送出以來應用的狀态修改清單。對它們進行跟蹤是為了可以在執行異常或恢複請求的情況下進行恢複。
// journal 就是journalEntry的清單。
type journal struct {
entries []journalEntry // Current changes tracked by the journal. journal追蹤了目前的變化
dirties map[common.Address]int // Dirty accounts and the number of changes 髒帳戶和更改的數量
}
其中
journal
對象是
journalEntry
的清單,長度不固定,可任意添加元素。接口
journalEntry
存在若幹種實作體,描述了從單個賬戶操作到
account trie
變化的各種最小單元事件。
revision
用來描述一個版本,它的兩個整型成員
id
和
journalIndex
,都是基于
journal
清單進行操作的。
上圖簡述了
StateDB
中賬戶狀态的版本是如何管理的。首先
journal
清單會記錄所有發生過的操作事件;當某個時刻需要産生一個賬戶狀态版本時,調用
Snapshopt()
方法,會産生一個新
revision
對象,記錄下目前
journal
散列的長度,和一個自增1的版本号。
core/state/statedb.go
// Snapshot可以建立一個快照, 然後通過 RevertToSnapshot可以復原到哪個狀态,這個功能是通過journal來做到的。
// 每一步的修改都會往journal裡面添加一個undo日志。 如果需要復原隻需要執行undo日志就行了
// Snapshot returns an identifier for the current revision of the state.
// 快照傳回狀态的目前revision的辨別符。
func (s *StateDB) Snapshot() int {
id := s.nextRevisionId
s.nextRevisionId++
s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()})
return id
}
基于以上的設計,當發生回退要求時,隻要根據相應的
revision
中的
journalIndex
,在
journal
清單上,根據所記錄的所有
journalEntry
,即可使所有賬戶回退到那個狀态。
StateDB.Snapshot
方法建立一個快照,傳回一個
int
值作為快照的
ID
。
StateDB.RevertToSnapshot
用這個
ID
将
StateDB
的狀态恢複到某一個快照狀态。
StateDB.nextRevisionId
字段用來生成快照的有效
ID
,而
StateDB.validRevisions
記錄所有有效快照的資訊。
core/state/statedb.go
// RevertToSnapshot reverts all state changes made since the given revision.
// RevertToSnapshot将恢複自給定revision以來所做的所有狀态更改。
func (s *StateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots.
// 在有效快照的堆棧中找到快照。
idx := sort.Search(len(s.validRevisions), func(i int) bool {
return s.validRevisions[i].id >= revid
})
// 沒找到指定revid的revision
if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
snapshot := s.validRevisions[idx].journalIndex
// Replay the journal to undo changes and remove invalidated snapshots
// 重新執行日志以撤消更改并删除無效的快照
s.journal.revert(s, snapshot)
s.validRevisions = s.validRevisions[:idx]
}
journal.entries
中積累了所有操作的復原操作。當調用
StateDB.RevertToSnapshot
進行復原操作時,就會調用
journal.revert
方法:
core/state/journal.go
// revert undoes a batch of journalled modifications along with any reverted
// dirty handling too.
// revert撤消一批日志修改以及還原髒處理。
func (j *journal) revert(statedb *StateDB, snapshot int) { //根據snapshot編号復原。
for i := len(j.entries) - 1; i >= snapshot; i-- {
// Undo the changes made by the operation
// 撤消操作所做的更改
j.entries[i].revert(statedb)
// Drop any dirty tracking induced by the change
// 删除由更改引起的髒跟蹤
if addr := j.entries[i].dirtied(); addr != nil {
if j.dirties[*addr]--; j.dirties[*addr] == 0 { // 修改的數量減至0則删除。
delete(j.dirties, *addr)
}
}
}
j.entries = j.entries[:snapshot]
}
綜上,state 子產品實作快照和復原功能的過程如下[6]:
- 将所有可能的修改作一個統計。
- 實作所有可能單元操作對應的復原操作對象。
- 在每次進行操作前,将對應的復原對象加入到復原操作的數組中,例如
。journal.entries
- 要在目前狀态下建立一個快照,就記錄下目前
的長度。journal.entries
- 要恢複某個快照(即實作復原),就從
中最後一項開始,向前至指定的快照索引,逐一調用這些對象的journal.entries
方法。revert
stateObject
每個
stateObject
對應着一個正在修改的以太坊賬戶。
stateObject
的緩存結構與
StateDB
類似,結構如下:
core/state/state_object.go
// stateObject represents an Ethereum account which is being modified.
// stateObject表示一個正在修改的Ethereum帳戶。
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// Account values can be accessed and modified through the object.
// Finally, call CommitTrie to write the modified storage trie into a database.
// 使用模式如下:
// 首先,獲得一個狀态對象(stateObject)。
// 帳戶值可以通過這個對象通路和修改。
// 最後,調用CommitTrie将修改後的存儲樹寫入資料庫。
type stateObject struct {
address common.Address // 以太坊賬戶位址
addrHash common.Hash // hash of ethereum address of the account 以太坊賬戶位址的hash
data Account // 以太坊賬戶的資訊
db *StateDB // 狀态資料庫
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
// 資料庫錯誤。
// stateObject會被共識算法的核心和VM使用,在這些代碼内部無法處理資料庫級别的錯誤。
// 在資料庫讀取期間發生的任何錯誤都會記錄在這裡,最終由StateDB.Commit傳回。
dbErr error
// Write caches. 寫緩存
trie Trie // storage trie, which becomes non-nil on first access 使用者的存儲樹 ,在第一次通路的時候變得非空
code Code // contract bytecode, which gets set when code is loaded 合約位元組碼,加載代碼時被設定
// 原始對象的存儲緩存,用來避免對每個交易的重複重寫、重置。
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
// 需要在整個塊結束時刷入磁盤的使用者存儲對象。相當于寫緩存
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
// 在目前交易執行中被修改的存儲項
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.僞存儲,由調用者構造,用于調試目的。
// Cache flags.
// When an object is marked suicided it will be delete from the trie
// during the "update" phase of the state transition.
// Cache 标志
// 當一個對象被标記為自殺時,它将在狀态轉換的“更新”階段從樹中删除。
dirtyCode bool // true if the code was updated 如果代碼被更新,設定為true
suicided bool // 自殺标志
deleted bool // 删除标志
}
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
// Account是賬戶的以太坊一緻性表示。這些對象存儲在主帳戶樹(main account trie)中。
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
stateObject
内部也有一個
state.Trie
類型的成員
trie
,被稱為
storage trie
,它裡面存放的是
state
資料,
state
跟每個賬戶相關,是
[Hash, Hash]
鍵值對。
stateObject
内部也有類似
StateDB
一樣的二級資料緩存機制,用來緩存和更新這些
state
。
stateObject
的
originStorage
字段用來緩存原始對象,内容與資料庫中一緻;
pendingStorage
字段相當于寫緩存,暫時将完成修改的
state
在記憶體中儲存,稍後将更改寫入
trie
;
dirtyStorage
字段在目前交易執行中被修改的
state項
,當交易結束時,
finalise
方法将此字段的内容轉儲到
pendingStorage
中,稍後進行hash或者送出。
stateObject
定義了
storage
,結構為
type Storage map[common.Hash]common.Hash
,用來存放
state
資料。當調用
SetState()
時,
storage
内部相應的
state
被更新,同時标記為"dirty"。之後,待有需要時(比如調用
updateTrie()
方法),那些标為"dirty"的
state
被一起寫入
storage trie
,而
storage trie
中的所有内容在調用
CommitTrie()
時再一起送出到底層資料庫。[7]
deepCopy
core/state/state_object.go
// deepCopy提供了stateObject的深拷貝。
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
stateObject := newObject(db, s.address, s.data) // 配置設定一個新的stateObject
if s.trie != nil { // 複制存儲樹
stateObject.trie = db.db.CopyTrie(s.trie)
}
// 各種參數的複制
stateObject.code = s.code
stateObject.dirtyStorage = s.dirtyStorage.Copy()
stateObject.originStorage = s.originStorage.Copy()
stateObject.pendingStorage = s.pendingStorage.Copy()
stateObject.suicided = s.suicided
stateObject.dirtyCode = s.dirtyCode
stateObject.deleted = s.deleted
return stateObject
}
SetState
core/state/state_object.go
// SetState updates a value in account storage.
// SetState在賬戶存儲裡更新一個值。
func (s *stateObject) SetState(db Database, key, value common.Hash) {
// If the fake storage is set, put the temporary state update here.
// 如果設定了fake storage,将臨時狀态更新放在這裡。
if s.fakeStorage != nil {
s.fakeStorage[key] = value
return
}
// If the new value is the same as old, don't set
// 如果新值和原來的一樣,不做操作。
prev := s.GetState(db, key)
if prev == value {
return
}
// New value is different, update and journal the change
// 新值和原來的不同,更新它然後日志記錄此更改。
s.db.journal.append(storageChange{
account: &s.address,
key: key,
prevalue: prev,
})
s.setState(key, value)
}
// 更新狀态,直接将更改放入dirtyStorage。
func (s *stateObject) setState(key, value common.Hash) {
s.dirtyStorage[key] = value
}
finalise
core/state/state_object.go
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
// finalise将所有髒的存儲槽移動到挂起的寫緩存區域,稍後進行hash或者送出。它在每個事務結束時調用。
func (s *stateObject) finalise() {
// 轉儲
for key, value := range s.dirtyStorage {
s.pendingStorage[key] = value
}
if len(s.dirtyStorage) > 0 { // 清空
s.dirtyStorage = make(Storage)
}
}
updateTrie
core/state/state_object.go
// updateTrie writes cached storage modifications into the object's storage trie.
// It will return nil if the trie has not been loaded and no changes have been made
// updateTrie将緩存的存儲修改寫入對象的存儲樹。如果沒有加載樹并且沒有進行任何更改,那麼它将傳回nil
func (s *stateObject) updateTrie(db Database) Trie {
// Make sure all dirty slots are finalized into the pending storage area
// 確定所有的髒存儲槽都被轉儲到pending storage區域。
s.finalise()
// 沒有任何更改,直接傳回
if len(s.pendingStorage) == 0 {
return s.trie
}
// Track the amount of time wasted on updating the storge trie
// 跟蹤更新storge trie所耗費的時間
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
}
// Insert all the pending updates into the trie
// 将所有挂起的更新插入存儲樹中。
tr := s.getTrie(db)
for key, value := range s.pendingStorage {
// Skip noop changes, persist actual changes
// 跳過空操作,持久化實際更改。
if value == s.originStorage[key] {
continue
}
// 将新值加入原始對象的緩存
s.originStorage[key] = value
// 删除操作
if (value == common.Hash{}) {
s.setError(tr.TryDelete(key[:]))
continue
}
// Encoding []byte cannot fail, ok to ignore the error.
// 編碼操作不會出現錯誤,允許忽略傳回的錯誤
v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
// 更新操作
s.setError(tr.TryUpdate(key[:], v))
}
// 清空緩存
if len(s.pendingStorage) > 0 {
s.pendingStorage = make(Storage)
}
return tr
}
// UpdateRoot sets the trie root to the current root hash of
// UpdateRoot将樹根設定為目前根的hash
func (s *stateObject) updateRoot(db Database) {
// If nothing changed, don't bother with hashing anything
// 如果沒有任何更改,那就不用去對任何東西進行散列。
if s.updateTrie(db) == nil {
return
}
// Track the amount of time wasted on hashing the storge trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
}
s.data.Root = s.trie.Hash() // 換成目前根
}
CommitTrie
core/state/state_object.go
// CommitTrie the storage trie of the object to db.
// This updates the trie root.
// CommitTrie将對象的存儲樹送出到資料庫。
// 這将更新樹的根。
func (s *stateObject) CommitTrie(db Database) error {
// If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil {
return nil
}
// 若有錯誤,傳回錯誤
if s.dbErr != nil {
return s.dbErr
}
// Track the amount of time wasted on committing the storge trie
// 跟蹤送出storge trie所耗費的時間
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
}
root, err := s.trie.Commit(nil) // 送出
if err == nil {
s.data.Root = root
}
return err
}
參考文獻
-
Ethereum Yellow Paper
ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER
https://ethereum.github.io/yellowpaper/paper.pdf
-
Ethereum White Paper
A Next-Generation Smart Contract and Decentralized Application Platform
https://github.com/ethereum/wiki/wiki/White-Paper
-
ethereumbook
The Ethereum Virtual Machine
https://github.com/ethereumbook/ethereumbook/blob/develop/13evm.asciidoc
-
Ethereum EVM Illustrated
https://github.com/takenobu-hs/ethereum-evm-illustrated
-
Go Ethereum Code Analysis
https://github.com/ZtesoftCS/go-ethereum-code-analysis
-
以太坊源碼解析:state
https://yangzhe.me/2019/06/19/ethereum-state/
-
以太坊資料結構與存儲
https://blog.csdn.net/weixin_41545330/article/details/79394153