天天看點

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

以太坊EVM源碼注釋之State

Ethereum State

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

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

從狀态的角度來看,可以将以太坊看作是一條狀态鍊;

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

從實作來看,可以将以太坊看作是一條區塊組成的鍊,即"區塊鍊(Blockchain)"。

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

在頂層,有以太坊世界狀态(world state),是以太坊位址(20位元組,160bit)到賬戶(account)的映射。

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

在更低的層面,每個以太坊位址表示一個包含餘額、nonce、storage、code的帳戶。以太坊賬戶分為兩種類型:

  • EOA(Externally owned account), 由一個私鑰控制,不能包含EVM代碼,不能使用storage;
  • Contract account,由EVM代碼控制。
以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

可以認為是在以太坊世界狀态的沙箱副本上運作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,用來緩存所有從資料庫中讀取出來的賬戶資訊,無論這些資訊是否被修改過都會緩存在這裡。

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

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

方法)。

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

如上圖所示,每當一個

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

清單進行操作的。

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

上圖簡述了

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]:

  1. 将所有可能的修改作一個統計。
  2. 實作所有可能單元操作對應的復原操作對象。
  3. 在每次進行操作前,将對應的復原對象加入到復原操作的數組中,例如

    journal.entries

  4. 要在目前狀态下建立一個快照,就記錄下目前

    journal.entries

    的長度。
  5. 要恢複某個快照(即實作復原),就從

    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或者送出。

以太坊EVM源碼注釋之State以太坊EVM源碼注釋之State

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
}
           

參考文獻

  1. Ethereum Yellow Paper

    ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER

    https://ethereum.github.io/yellowpaper/paper.pdf

  2. Ethereum White Paper

    A Next-Generation Smart Contract and Decentralized Application Platform

    https://github.com/ethereum/wiki/wiki/White-Paper

  3. ethereumbook

    The Ethereum Virtual Machine

    https://github.com/ethereumbook/ethereumbook/blob/develop/13evm.asciidoc

  4. Ethereum EVM Illustrated

    https://github.com/takenobu-hs/ethereum-evm-illustrated

  5. Go Ethereum Code Analysis

    https://github.com/ZtesoftCS/go-ethereum-code-analysis

  6. 以太坊源碼解析:state

    https://yangzhe.me/2019/06/19/ethereum-state/

  7. 以太坊資料結構與存儲

    https://blog.csdn.net/weixin_41545330/article/details/79394153