以太坊EVM源码注释之State

以太坊EVM源码注释之State

Ethereum State

EVM在给定的状态下使用提供的上下文(Context)运行合约,计算有效的状态转换(智能合约代码执行的结果)来更新以太坊状态(Ethereum state)。因此可以认为以太坊是基于交易的状态机,外部因素(账户持有者或者矿工)可以通过创建、接受、排序交易来启动状态转换(state transition)。
state-machine
从状态的角度来看,可以将以太坊看作是一条状态链;
chain-of-states 从实现来看,可以将以太坊看作是一条区块组成的链,即"区块链(Blockchain)"。
chain-of-blocks 在顶层,有以太坊世界状态(world state),是以太坊地址(20字节,160bit)到账户(account)的映射。
world-state
在更低的层面,每个以太坊地址表示一个包含余额、nonce、storage、code的帐户。以太坊账户分为两种类型:

  • EOA(Externally owned account), 由一个私钥控制,不能包含EVM代码,不能使用storage;
  • Contract account,由EVM代码控制。

account-type 可以认为是在以太坊世界状态的沙箱副本上运行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,用来缓存所有从数据库中读取出来的账户信息,无论这些信息是否被修改过都会缓存在这里。
stateDB
stateObjectsPending用来记录已经完成修改但尚未写入trie的账户,stateObjectsDirty用来记录哪些账户信息被修改过了。需要注意的是,这两个字段并不时刻与stateObjects对应,并且也不会在账户信息被修改时立即修改这两个字段。在进行StateDB.Finalise等操作时才会将journal字段中记录的被修改的账户整理到stateObjectsPendingstateObjectsDirty中。在代码实现中,这两个字段用法并无太大区别,一般会成对出现,只有在createObjectChangerevert方法中单独出现了stateObjectsDirty。因此stateObjectsPendingstateObjectsDirty的区别可能在于:stateObjectsPending存的账户已经完成更改,状态已经确定下来,只是还没有写入底层数据库,应该不会再进行回滚;stateObjectsDirty的账户修改还没最终确定,可能继续修改,也有可能回滚。但是state模块的代码并没有体现出来这种区别,不清楚别的模块代码有没有相关内容。
journal字段记录了StateDB进行的所有操作,以便将来进行回滚。在调用StateDB.Finalise方法将juournal记录的账户更改"最终确定(finalise)"到stateObjects以后,journal字段会被清空,无法再进行回滚,因为不允许跨事务回滚(一般会在事务结束时才会调用stateObjectfinalise方法)。
stateDB 如上图所示,每当一个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
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值