以太坊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对象集合管理所有账户信息,提供了各种管理账户信息的方法。
StateDBdb字段类型是Database接口,Database封装了对树(trie)和合约代码的访问方法,在实际的调用代码中,它只有一个实例cachingDBcachingDB封装的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字段中记录的被修改的账户整理到stateObjectsPendingstateObjectsDirty中。在代码实现中,这两个字段用法并无太大区别,一般会成对出现,只有在createObjectChangerevert方法中单独出现了stateObjectsDirty。因此stateObjectsPendingstateObjectsDirty的区别可能在于:stateObjectsPending存的账户已经完成更改,状态已经确定下来,只是还没有写入底层数据库,应该不会再进行回滚;stateObjectsDirty的账户修改还没最终确定,可能继续修改,也有可能回滚。但是state模块的代码并没有体现出来这种区别,不清楚别的模块代码有没有相关内容。
journal字段记录了StateDB进行的所有操作,以便将来进行回滚。在调用StateDB.Finalise方法将juournal记录的账户更改"最终确定(finalise)"到stateObjects以后,journal字段会被清空,无法再进行回滚,因为不允许跨事务回滚(一般会在事务结束时才会调用stateObjectfinalise方法)。
如上图所示,每当一个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 uint64thash, bhash common.Hash                  // 当前的transaction hash 和block hashtxIndex      int                          // 当前的交易的indexlogs         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        *journalvalidRevisions []revisionnextRevisionId int// Measurements gathered during execution for debugging purposes// 为调试目的而在执行期间收集的度量AccountReads   time.DurationAccountHashes  time.DurationAccountUpdates time.DurationAccountCommits time.DurationStorageReads   time.DurationStorageHashes  time.DurationStorageUpdates time.DurationStorageCommits 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)错误。因此,我们需要检查nilif 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}// preimagesfor hash, preimage := range s.preimages {state.preimages[hash] = preimage}return state
}

Finalise

StateDB.Finalise操作将journal字段中记录的被修改的账户整理到stateObjectsPendingstateObjectsDirty中。
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// 将状态对象中的任何存储更改写入其存储trieif 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的状态版本管理功能依赖于两个数据结构:revisionjournalEntry
core/state/statedb.go

// 用于回滚的版本 快照版本
type revision struct {id           intjournalIndex 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用来描述一个版本,它的两个整型成员idjournalIndex,都是基于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.nextRevisionIds.nextRevisionId++s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()})return id
}

基于以上的设计,当发生回退要求时,只要根据相应的revision中的journalIndex,在journal列表上,根据所记录的所有journalEntry,即可使所有账户回退到那个状态。
StateDB.Snapshot方法创建一个快照,返回一个 int 值作为快照的 IDStateDB.RevertToSnapshot 用这个 IDStateDB 的状态恢复到某一个快照状态。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的revisionif 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 以太坊账户地址的hashdata     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 executionfakeStorage  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 如果代码被更新,设置为truesuicided  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    uint64Balance  *big.IntRoot     common.Hash // merkle root of the storage trieCodeHash []byte
}

stateObject内部也有一个state.Trie类型的成员trie,被称为storage trie,它里面存放的是state数据,state跟每个账户相关,是[Hash, Hash]键值对。stateObject内部也有类似StateDB一样的二级数据缓存机制,用来缓存和更新这些statestateObjectoriginStorage字段用来缓存原始对象,内容与数据库中一致;pendingStorage字段相当于写缓存,暂时将完成修改的state在内存中保存,稍后将更改写入triedirtyStorage字段在当前交易执行中被修改的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) // 分配一个新的stateObjectif s.trie != nil {                              // 复制存储树stateObject.trie = db.db.CopyTrie(s.trie)}// 各种参数的复制stateObject.code = s.codestateObject.dirtyStorage = s.dirtyStorage.Copy()stateObject.originStorage = s.originStorage.Copy()stateObject.pendingStorage = s.pendingStorage.Copy()stateObject.suicided = s.suicidedstateObject.dirtyCode = s.dirtyCodestateObject.deleted = s.deletedreturn 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] = valuereturn}// 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 trieif 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 anythingif 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

以太坊EVM源码注释之State相关推荐

  1. 以太坊EVM源码注释之数据结构

    以太坊EVM源码分析之数据结构 EVM代码整体结构 EVM相关的源码目录结构: ~/go-ethereum-master/core/vm# tree . ├── analysis.go // 分析合约 ...

  2. 以太坊EVM源码注释之执行流程

    以太坊EVM源码分析之执行流程 业务流程概述 EVM是用来执行智能合约的.输入一笔交易,内部会将之转换成一个Message对象,传入 EVM 执行.在合约中,msg 全局变量记录了附带当前合约的交易的 ...

  3. 以太坊控制台源码分析

    最近有网友提到以太坊控制台的代码看不太明白,抽了点时间整理了一下. 当我们通过geth console或者geth attach与节点交互的时候,输入的命令是如何被处理的呢?看下面这张流程图就明白了: ...

  4. 以太坊Go-ethereum源码分析之启动流程

    以太坊源码编译需要gov1.7以上,及C编译器,执行make geth 即可编译项目,编译后可执行的geth文件. Makefile文件: geth:build/env.sh go run build ...

  5. 以太坊DPOS源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 一.前言: 任何共识机制都必须回答包括但不限于如下的问题: 下一个添加到数据库的新区块应该由谁来生成? 下一个块应该何时产生? ...

  6. 以太坊挖矿源码:clique算法

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. clique 以太坊的官方共识算法是ethash算法,这在前文已经有了详细的分析: 它是基于POW的共识机制的,矿工需要 ...

  7. 以太坊挖矿源码:ethash算法

    本文具体分析以太坊的共识算法之一:实现了POW的以太坊共识引擎ethash. 关键字:ethash,共识算法,pow,Dagger Hashimoto,ASIC,struct{},nonce,FNV ...

  8. 以太坊地址算法php,以太坊ETH源码分析(1):地址生成过程

    一.生成一个以太坊钱包地址 通过以太坊命令行客户端geth可以很简单的获得一个以太坊地址,如下: ~/go/src/github.com/ethereum/go-ethereum/build/bin$ ...

  9. 以太坊ETH源码分析(1):地址生成过程

    一.生成一个以太坊钱包地址 通过以太坊命令行客户端geth可以很简单的获得一个以太坊地址,如下: ~/go/src/github.com/ethereum/go-ethereum/build/bin$ ...

最新文章

  1. WCF 第四章 绑定 msmqIntegrationBinding
  2. python语言程序设计2019版第二章课后答案-python语言程序设计基础(嵩天)第二章课后习题...
  3. c++中的引用和python中的引用_对比 C++ 和 Python,谈谈指针与引用
  4. 6套制造业大数据解决方案免费送,内含详细步骤手把手教会你
  5. nginx的root alias 指令
  6. C++学习之路 | PTA乙级—— 1040 有几个PAT (25 分)(精简)
  7. 强悍的 ubuntu —— samba 实现 ubuntu 与 windows 之间的文件共享
  8. 40. Combination Sum II
  9. 本地chrome调试服务器node
  10. [问题探讨]ECharts实现带钓鱼岛和南海诸岛的中国(China)地图
  11. 人工智能基础——2.3.2产生式系统
  12. iso 系统降级的处理方法
  13. ACL'22 | 陈丹琦提出CoFi模型剪枝,加速10倍,精度几乎无损
  14. 常见耳机品牌简介及鉴赏
  15. Android vivo美颜相机相册 照亮你的美 图片压缩 图片滤镜,裁剪(附带完整项目源码)
  16. 墙内电线漏电了怎么办
  17. NHibernate3剖析:Configuration篇之SessionFactory lambda配置
  18. 微信IOT模块 用户手册(串口向微信发送消息,串口与微信通讯)
  19. Python画图之饼图
  20. 2万字库PHP汉字转拼音(UTF-8)

热门文章

  1. linux 怎么截取字符串,Linux中截取字符串
  2. java控制台打印各种图形
  3. promis all allSettled
  4. 【SAP】复制-粘贴 小技巧
  5. 菜鸟收藏:熟知Word箭头样式类型设置【系统收藏】
  6. iOS开发:SQLite使用,5年经验Android程序员面试27天
  7. fts16_FT230XS-xxxx
  8. iPhone诞生以后,从此UI设计行业就迅速发生了翻天覆地的变化!...
  9. MAC 彻底删除库乐队,清空音乐创作所占内存
  10. 华为机试真题 C++ 实现【免单统计】