天天看点

以太坊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