go-ethereum中core-state源码学习

core里的state包提供了账户状态的管理,包括状态转换以及回滚等操作。

datebase.go

首先提供了数据库的抽象,定义了一个接口:

type Database interface {
	OpenTrie(root common.Hash) (Trie, error)

	OpenStorageTrie(addrHash, root common.Hash) (Trie, error)

	CopyTrie(Trie) Trie

	ContractCode(addrHash, codeHash common.Hash) ([]byte, error)

	ContractCodeSize(addrHash, codeHash common.Hash) (int, error)

	TrieDB() *trie.Database
}

OpenTrie是打开主账号的树,OpenStorageTrie是打开某个账号的数,CopyTrie就是复制,ContractCode是访问合约代码,ContractCodeSize获取合约大小,TrieDB访问底层数据库

new

提供了两个创建该数据库的方法,NewDatabase和NewDatabaseWithCache就是一个带缓冲一个不带缓冲

func NewDatabase(db ethdb.Database) Database {
	return NewDatabaseWithCache(db, 0)
}

func NewDatabaseWithCache(db ethdb.Database, cache int) Database {
	csc, _ := lru.New(codeSizeCacheSize)
	return &cachingDB{
		db:trie.NewDatabaseWithCache(db, cache), 
		codeSizeCache: csc,
	}
}

构建了一个cachingDB对象,其中有一个实际的数据库用的是trie的NewDatabaseWithCache方法创建的

// go-ethereum\trie\database.go
func NewDatabaseWithCache(diskdb ethdb.Database, cache int) *Database {
	var cleans *bigcache.BigCache
	if cache > 0 {
		cleans, _ = bigcache.NewBigCache(bigcache.Config{
			Shards:             1024,
			LifeWindow:         time.Hour,
			MaxEntriesInWindow: cache * 1024,
			MaxEntrySize:       512,
			HardMaxCacheSize:   cache,
		})
	}
	return &Database{
		diskdb:    diskdb,
		cleans:    cleans,
		dirties:   map[common.Hash]*cachedNode{{}: {}},
		preimages: make(map[common.Hash][]byte),
	}
}

OpenTrie

func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
	db.mu.Lock()
	defer db.mu.Unlock()

	for i := len(db.pastTries) - 1; i >= 0; i-- {
		if db.pastTries[i].Hash() == root {
			return cachedTrie{db.pastTries[i].Copy(), db}, nil
		}
	}
	tr, err := trie.NewSecure(root, db.db, MaxTrieCacheGen)
	if err != nil {
		return nil, err
	}
	return cachedTrie{tr, db}, nil
}

前面说过这个方法是打开主账户的树,这里首先从pastTries中查找,有的话用一个cachedTrie对象包装后返回,没有的话调用NewSecure区创建一个,创建的是一个SecureTrie类型的数,前面我们分析trie源码时听到过,SecureTrie就是trie的包装。前面没有详细分析SecureTrie源码,这里来看一下:

func NewSecure(root common.Hash, db *Database, cachelimit uint16) (*SecureTrie, error) {
	if db == nil {
		panic("trie.NewSecure called without a database")
	}
	trie, err := New(root, db)
	if err != nil {
		return nil, err
	}
	trie.SetCacheLimit(cachelimit)
	return &SecureTrie{trie: *trie}, nil
}

创建很简单,就是用New方法创建一个普通的树,然后构造出一个SecureTrie对象。OpenTrie方法最后返回的是一个cachedTrie对象,里面包装了SecureTrie和cachingDB。

OpenStorageTrie

这个很简单直接创建一个SecureTrie

func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
	return trie.NewSecure(root, db.db, 0)
}

ContractCode & ContractCodeSize

func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
	code, err := db.db.Node(codeHash)
	if err == nil {
		db.codeSizeCache.Add(codeHash, len(code))
	}
	return code, err
}

Node实际上就根据hash从数据库中取值,取值完成后我们会将其放入前面创建的缓存中,这是一个lru缓存,便于下次取。

func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
	if cached, ok := db.codeSizeCache.Get(codeHash); ok {
		return cached.(int), nil
	}
	code, err := db.ContractCode(addrHash, codeHash)
	return len(code), err
}

也是一个先取后计算长度的过程

Commit

Commit就是将树缓存起来

func (m cachedTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) {
	root, err := m.SecureTrie.Commit(onleaf)
	if err == nil {
		m.db.pushTrie(m.SecureTrie)
	}
	return root, err
}

首先调用了SecureTrie的Commit方法,检查源码实际上调用了其包装的trie的commit方法,之前分析过trie源码,Commit是序列化方法,是将一颗树存储到数据库中,返回的是树根的hash。在cachedTrie的Commit方法中,如果没有错首先将原来的树缓存到pastTries中,然后返回提交的结果。

journal.go

先看数据结构

type journal struct {
	entries []journalEntry         // Current changes tracked by the journal
	dirties map[common.Address]int // Dirty accounts and the number of changes
}

包含了一个journalEntry数组和一个map,journalEntry相当于一条日志,dirties记录了某个账户改动次数。journalEntry是一个接口,如下定义

type journalEntry interface {
	revert(*StateDB)

	dirtied() *common.Address
}

revert是用于撤销操作的,dirtied返回对应的账户地址。对应接口定义了大量的journalEntry类型对象

type (
	createObjectChange struct {
		account *common.Address
	}
	resetObjectChange struct {
		prev *stateObject
	}
	suicideChange struct {
		account     *common.Address
		prev        bool // whether account had already suicided
		prevbalance *big.Int
	}

	balanceChange struct {
		account *common.Address
		prev    *big.Int
	}
	nonceChange struct {
		account *common.Address
		prev    uint64
	}
	storageChange struct {
		account       *common.Address
		key, prevalue common.Hash
	}
	codeChange struct {
		account            *common.Address
		prevcode, prevhash []byte
	}

	refundChange struct {
		prev uint64
	}
	addLogChange struct {
		txhash common.Hash
	}
	addPreimageChange struct {
		hash common.Hash
	}
	touchChange struct {
		account   *common.Address
		prev      bool
		prevDirty bool
	}
)

每种struct都对应了不同的事件日志。当然种都有revert和dirtied方法我们后面具体遇到了在分析。

state_object.go

stateObject代表了正在修改的以太坊账户,数据结构如下

type stateObject struct {
	address  common.Address
	addrHash common.Hash 
	data     Account
	db       *StateDB

	dbErr error

	trie Trie 
	code Code 

	originStorage Storage 
	dirtyStorage  Storage 


	dirtyCode bool // true if the code was updated
	suicided  bool
	deleted   bool
}

根据源码注释,要想修改账户,需要首先获取stateObject,然后通过这个对象访问修改,最后提交进行持久化存储。

new

func newObject(db *StateDB, address common.Address, data Account) *stateObject {
	if data.Balance == nil {
		data.Balance = new(big.Int)
	}
	if data.CodeHash == nil {
		data.CodeHash = emptyCodeHash
	}
	return &stateObject{
		db:            db,
		address:       address,
		addrHash:      crypto.Keccak256Hash(address[:]),
		data:          data,
		originStorage: make(Storage),
		dirtyStorage:  make(Storage),
	}
}

需要传入地址和账户的对象才能构造一个stateObject对象。

SetState

SetState是更新账户存储中的某个值

func (self *stateObject) SetState(db Database, key, value common.Hash) {
	prev := self.GetState(db, key)
	if prev == value {
		return
	}
	self.db.journal.append(storageChange{
		account:  &self.address,
		key:      key,
		prevalue: prev,
	})
	self.setState(key, value)
}

func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
	value, dirty := self.dirtyStorage[key]
	if dirty {
		return value
	}
	return self.GetCommittedState(db, key)
}

func (self *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
	value, cached := self.originStorage[key]
	if cached {
		return value
	}
	enc, err := self.getTrie(db).TryGet(key[:])
	if err != nil {
		self.setError(err)
		return common.Hash{}
	}
	if len(enc) > 0 {
		_, content, _, err := rlp.Split(enc)
		if err != nil {
			self.setError(err)
		}
		value.SetBytes(content)
	}
	self.originStorage[key] = value
	return value
}

func (c *stateObject) getTrie(db Database) Trie {
	if c.trie == nil {
		var err error
		c.trie, err = db.OpenStorageTrie(c.addrHash, c.data.Root)
		if err != nil {
			c.trie, _ = db.OpenStorageTrie(c.addrHash, common.Hash{})
			c.setError(fmt.Errorf("can't create storage trie: %v", err))
		}
	}
	return c.trie
}

首先用了GetState方法去取旧的值,GetState方法中先访问dirtyStorage,这也是一个缓存机制,取不到的话调用GetCommittedState,GetCommittedState也是先访问originStorage这个缓存,originStorage与dirtyStorage的区别是,originStorage是已经持久化过的,dirtyStorage而是未持久化的。还找不到的话就需要访问本地数据库了,调用getTrie获取树。GetCommittedState方法如果是从数据库取值的话就向originStorage从缓存一份。

回到SetState中,如果旧值和新值一样,就直接返回,否则新建一个storageChange类型的journalEntry放入journal的entries中,最后调用setState方法,setState主要是向dirtyStorage中缓存。我们顺便看一下storageChange

storageChange struct {
	account       *common.Address
	key, prevalue common.Hash
}

func (ch storageChange) revert(s *StateDB) {
	s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
}

func (ch storageChange) dirtied() *common.Address {
	return ch.account
}

storageChange主要存储了状态改变前后的新旧值和对应地址。所以revert方法就是取对应状态恢复到旧值

SetBalance

func (self *stateObject) SetBalance(amount *big.Int) {
	self.db.journal.append(balanceChange{
		account: &self.address,
		prev:    new(big.Int).Set(self.data.Balance),
	})
	self.setBalance(amount)
}

func (self *stateObject) setBalance(amount *big.Int) {
	self.data.Balance = amount
}

就是修改账户的余额,给日志添加了balanceChange,还有两个加减方法

func (c *stateObject) AddBalance(amount *big.Int) {
	if amount.Sign() == 0 {
		if c.empty() {
			c.touch()
		}

		return
	}
	c.SetBalance(new(big.Int).Add(c.Balance(), amount))
}

func (c *stateObject) SubBalance(amount *big.Int) {
	if amount.Sign() == 0 {
		return
	}
	c.SetBalance(new(big.Int).Sub(c.Balance(), amount))
}

都要首先判断加减值是否为0,对于加的情况,如果值为0而且账户也为空(即nonce为0,余额为0,,账户代码也为空)则调用touch方法,touch方法个日志添加了touchChange操作。

下面我们先看一下balanceChange。touchChange没有实际操作

balanceChange struct {
	account *common.Address
	prev    *big.Int
}
func (ch balanceChange) revert(s *StateDB) {
	s.getStateObject(*ch.account).setBalance(ch.prev)
}

func (ch balanceChange) dirtied() *common.Address {
	return ch.account
}

也很简单,就是存储了先前的值,然后回滚时设置余额为之前的值。

SetCode

func (self *stateObject) SetCode(codeHash common.Hash, code []byte) {
	prevcode := self.Code(self.db.db)
	self.db.journal.append(codeChange{
		account:  &self.address,
		prevhash: self.CodeHash(),
		prevcode: prevcode,
	})
	self.setCode(codeHash, code)
}

func (self *stateObject) Code(db Database) []byte {
	if self.code != nil {
		return self.code
	}
	if bytes.Equal(self.CodeHash(), emptyCodeHash) {
		return nil
	}
	code, err := db.ContractCode(self.addrHash, common.BytesToHash(self.CodeHash()))
	if err != nil {
		self.setError(fmt.Errorf("can't load code hash %x: %v", self.CodeHash(), err))
	}
	self.code = code
	return code
}

func (self *stateObject) setCode(codeHash common.Hash, code []byte) {
	self.code = code
	self.data.CodeHash = codeHash[:]
	self.dirtyCode = true
}

首先取之前的code,取得方法还是先从缓存取,取不到再去数据库取,有两集缓存,一级是stateObject的code字段,每次set都会更新,;另一级是Database的ContractCode取的时候会坚持codeSizeCache缓存,都没有再从数据库取。设置code时,也给日志添加了codeChange。

codeChange struct {
	account            *common.Address
	prevcode, prevhash []byte
}
	
func (ch codeChange) revert(s *StateDB) {
	s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
}

func (ch codeChange) dirtied() *common.Address {
	return ch.account
}	

和storageChange逻辑类似

SetNonce

func (self *stateObject) SetNonce(nonce uint64) {
	self.db.journal.append(nonceChange{
		account: &self.address,
		prev:    self.data.Nonce,
	})
	self.setNonce(nonce)
}

func (self *stateObject) setNonce(nonce uint64) {
	self.data.Nonce = nonce
}

nonceChange struct {
	account *common.Address
	prev    uint64
}

func (ch nonceChange) revert(s *StateDB) {
	s.getStateObject(*ch.account).setNonce(ch.prev)
}

func (ch nonceChange) dirtied() *common.Address {
	return ch.account
}

和balance一样的逻辑,不在多说

CommitTrie

刚才的set方法都只是改变了stateObject的值,没有实际的持久化,要持久化需要这个方法

func (self *stateObject) CommitTrie(db Database) error {
	self.updateTrie(db)
	if self.dbErr != nil {
		return self.dbErr
	}
	root, err := self.trie.Commit(nil)
	if err == nil {
		self.data.Root = root
	}
	return err
}

func (self *stateObject) updateTrie(db Database) Trie {
	tr := self.getTrie(db)
	for key, value := range self.dirtyStorage {
		delete(self.dirtyStorage, key)

		if value == self.originStorage[key] {
			continue
		}
		self.originStorage[key] = value

		if (value == common.Hash{}) {
			self.setError(tr.TryDelete(key[:]))
			continue
		}
		v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
		self.setError(tr.TryUpdate(key[:], v))
	}
	return tr
}

首先调用updateTrie更新树,updateTrie中现获取了树,随后遍历了dirtyStorage,前面说过这是未持久化的值,取出的每个值先和originStorage中的比较,一样的就不操作,随后对于空值记录一个错误,最后先rlp编码,最后调用TryUpdate更新,TryUpdate是SecureTrie中的方法:

func (t *SecureTrie) TryUpdate(key, value []byte) error {
	hk := t.hashKey(key)
	err := t.trie.TryUpdate(hk, value)
	if err != nil {
		return err
	}
	t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
	return nil
}

还是调用trie的TryUpdate方法,TryUpdate方法之前分析trie源码时没有将,不过也很简单

func (t *Trie) TryUpdate(key, value []byte) error {
	k := keybytesToHex(key)
	if len(value) != 0 {
		_, n, err := t.insert(t.root, nil, k, valueNode(value))
		if err != nil {
			return err
		}
		t.root = n
	} else {
		_, n, err := t.delete(t.root, nil, k)
		if err != nil {
			return err
		}
		t.root = n
	}
	return nil
}

首先对于key长度不为0的话就进行插入来更新,否则执行删除逻辑。回到stateobject中,在更新完毕后,如果没错,就调用commit方法持久化存储。

statedb.go

StateDB负责缓存和存储嵌套状态。主要用于检索合约和账户。

type StateDB struct {
	db   Database
	trie Trie

	stateObjects      map[common.Address]*stateObject
	stateObjectsDirty map[common.Address]struct{}


	dbErr error

	refund uint64

	thash, bhash common.Hash
	txIndex      int
	logs         map[common.Hash][]*types.Log
	logSize      uint

	preimages map[common.Hash][]byte

	journal        *journal
	validRevisions []revision
	nextRevisionId int
}

基本包含了前面分析的三种结构体。

new

func New(root common.Hash, db Database) (*StateDB, error) {
	tr, err := db.OpenTrie(root)
	if err != nil {
		return nil, err
	}
	return &StateDB{
		db:                db,
		trie:              tr,
		stateObjects:      make(map[common.Address]*stateObject),
		stateObjectsDirty: make(map[common.Address]struct{}),
		logs:              make(map[common.Hash][]*types.Log),
		preimages:         make(map[common.Hash][]byte),
		journal:           newJournal(),
	}, nil
}

首先调用了OpenTrie打开主账户的树,然后构造了StateDB对象。

AddLog

这个log并不是程序log,它代表合约的log事件

func (self *StateDB) AddLog(log *types.Log) {
	self.journal.append(addLogChange{txhash: self.thash})

	log.TxHash = self.thash
	log.BlockHash = self.bhash
	log.TxIndex = uint(self.txIndex)
	log.Index = self.logSize
	self.logs[self.thash] = append(self.logs[self.thash], log)
	self.logSize++
}

首先补全log信息,然后按交易hash放到logs中,关于thash和bhash是在Prepare中设置的

func (self *StateDB) Prepare(thash, bhash common.Hash, ti int) {
	self.thash = thash
	self.bhash = bhash
	self.txIndex = ti
}

在AddLog开始的时候还向journal添加了addLogChange

addLogChange struct {
	txhash common.Hash
}
func (ch addLogChange) revert(s *StateDB) {
	logs := s.logs[ch.txhash]
	if len(logs) == 1 {
		delete(s.logs, ch.txhash)
	} else {
		s.logs[ch.txhash] = logs[:len(logs)-1]
	}
	s.logSize--
}

func (ch addLogChange) dirtied() *common.Address {
	return nil
}

addLogChange只有一个字段,它的回滚就是将最后的log删除。此外关于log还有两个方法,都是取的

func (self *StateDB) GetLogs(hash common.Hash) []*types.Log {
	return self.logs[hash]
}

func (self *StateDB) Logs() []*types.Log {
	var logs []*types.Log
	for _, lgs := range self.logs {
		logs = append(logs, lgs...)
	}
	return logs
}

GetXxx & SetXxx

有一系列get和set方法,也就体现了statedb作为对外开放接口的作用

func (self *StateDB) GetBalance(addr common.Address) *big.Int {
	stateObject := self.getStateObject(addr)
	if stateObject != nil {
		return stateObject.Balance()
	}
	return common.Big0
}

func (self *StateDB) GetNonce(addr common.Address) uint64 {
	stateObject := self.getStateObject(addr)
	if stateObject != nil {
		return stateObject.Nonce()
	}

	return 0
}

func (self *StateDB) GetCode(addr common.Address) []byte {
	stateObject := self.getStateObject(addr)
	if stateObject != nil {
		return stateObject.Code(self.db)
	}
	return nil
}

func (self *StateDB) GetCodeSize(addr common.Address) int {
	stateObject := self.getStateObject(addr)
	if stateObject == nil {
		return 0
	}
	if stateObject.code != nil {
		return len(stateObject.code)
	}
	size, err := self.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash()))
	if err != nil {
		self.setError(err)
	}
	return size
}

func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
	stateObject := self.getStateObject(addr)
	if stateObject == nil {
		return common.Hash{}
	}
	return common.BytesToHash(stateObject.CodeHash())
}

func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
	stateObject := self.getStateObject(addr)
	if stateObject != nil {
		return stateObject.GetState(self.db, hash)
	}
	return common.Hash{}
}

func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
	stateObject := self.getStateObject(addr)
	if stateObject != nil {
		return stateObject.GetCommittedState(self.db, hash)
	}
	return common.Hash{}
}

func (self *StateDB) StorageTrie(addr common.Address) Trie {
	stateObject := self.getStateObject(addr)
	if stateObject == nil {
		return nil
	}
	cpy := stateObject.deepCopy(self)
	return cpy.updateTrie(self.db)
}

func (self *StateDB) HasSuicided(addr common.Address) bool {
	stateObject := self.getStateObject(addr)
	if stateObject != nil {
		return stateObject.suicided
	}
	return false
}

set类方法如下

func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
	stateObject := self.GetOrNewStateObject(addr)
	if stateObject != nil {
		stateObject.AddBalance(amount)
	}
}

func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) {
	stateObject := self.GetOrNewStateObject(addr)
	if stateObject != nil {
		stateObject.SubBalance(amount)
	}
}

func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) {
	stateObject := self.GetOrNewStateObject(addr)
	if stateObject != nil {
		stateObject.SetBalance(amount)
	}
}

func (self *StateDB) SetNonce(addr common.Address, nonce uint64) {
	stateObject := self.GetOrNewStateObject(addr)
	if stateObject != nil {
		stateObject.SetNonce(nonce)
	}
}


func (self *StateDB) SetCode(addr common.Address, code []byte) {
	stateObject := self.GetOrNewStateObject(addr)
	if stateObject != nil {
		stateObject.SetCode(crypto.Keccak256Hash(code), code)
	}
}


func (self *StateDB) SetState(addr common.Address, key, value common.Hash) {
	stateObject := self.GetOrNewStateObject(addr)
	if stateObject != nil {
		stateObject.SetState(self.db, key, value)
	}
}

func (self *StateDB) Suicide(addr common.Address) bool {
	stateObject := self.getStateObject(addr)
	if stateObject == nil {
		return false
	}
	self.journal.append(suicideChange{
		account:     &addr,
		prev:        stateObject.suicided,
		prevbalance: new(big.Int).Set(stateObject.Balance()),
	})
	stateObject.markSuicided()
	stateObject.data.Balance = new(big.Int)

	return true
}

基本上都是通过getStateObject先获取stateObject,前面说过stateObject代表正在修改的以太坊账户,通过他也就可以访问各项内容

操作StateObject

get方法:

func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
	if obj := self.stateObjects[addr]; obj != nil {
		if obj.deleted {
			return nil
		}
		return obj
	}

	enc, err := self.trie.TryGet(addr[:])
	if len(enc) == 0 {
		self.setError(err)
		return nil
	}
	var data Account
	if err := rlp.DecodeBytes(enc, &data); err != nil {
		log.Error("Failed to decode state object", "addr", addr, "err", err)
		return nil
	}
	obj := newObject(self, addr, data)
	self.setStateObject(obj)
	return obj
}

func (self *StateDB) deleteStateObject(stateObject *stateObject) {
	stateObject.deleted = true
	addr := stateObject.Address()
	self.setError(self.trie.TryDelete(addr[:]))
}

func (self *StateDB) updateStateObject(stateObject *stateObject) {
	addr := stateObject.Address()
	data, err := rlp.EncodeToBytes(stateObject)
	if err != nil {
		panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
	}
	self.setError(self.trie.TryUpdate(addr[:], data))
}

func (self *StateDB) setStateObject(object *stateObject) {
	self.stateObjects[object.Address()] = object
}

func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
	stateObject := self.getStateObject(addr)
	if stateObject == nil || stateObject.deleted {
		stateObject, _ = self.createObject(addr)
	}
	return stateObject
}

func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
	prev = self.getStateObject(addr)
	newobj = newObject(self, addr, Account{})
	newobj.setNonce(0) // sets the object to dirty
	if prev == nil {
		self.journal.append(createObjectChange{account: &addr})
	} else {
		self.journal.append(resetObjectChange{prev: prev})
	}
	self.setStateObject(newobj)
	return newobj, prev
}

首先尝试stateObjects回去,没有的话用newObject构造一个,同时存入setStateObject中。

快照

Snapshot可以创建一个快照,然后通过RevertToSnapshot可以回滚到哪个状态,这个功能是通过journal来做到的。

func (self *StateDB) Snapshot() int {
	id := self.nextRevisionId
	self.nextRevisionId++
	self.validRevisions = append(self.validRevisions, revision{id, self.journal.length()})
	return id
}

func (self *StateDB) RevertToSnapshot(revid int) {
	idx := sort.Search(len(self.validRevisions), func(i int) bool {
		return self.validRevisions[i].id >= revid
	})
	if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
		panic(fmt.Errorf("revision id %v cannot be reverted", revid))
	}
	snapshot := self.validRevisions[idx].journalIndex

	self.journal.revert(self, snapshot)
	self.validRevisions = self.validRevisions[:idx]
}

func (j *journal) revert(statedb *StateDB, snapshot int) {
	for i := len(j.entries) - 1; i >= snapshot; i-- {
		j.entries[i].revert(statedb)

		if addr := j.entries[i].dirtied(); addr != nil {
			if j.dirties[*addr]--; j.dirties[*addr] == 0 {
				delete(j.dirties, *addr)
			}
		}
	}
	j.entries = j.entries[:snapshot]
}

可见创建快照是记录了RevisionId和journal中日志数量。恢复则是确定该位置的journal中日志数量snapshot,然后调用revert,将日志集合中snapshot之后的事件全部执行回滚。

commit

commit用于提交更改

func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
	defer s.clearJournalAndRefund()

	for addr := range s.journal.dirties {
		s.stateObjectsDirty[addr] = struct{}{}
	}
	for addr, stateObject := range s.stateObjects {
		_, isDirty := s.stateObjectsDirty[addr]
		switch {
		case stateObject.suicided || (isDirty && deleteEmptyObjects && stateObject.empty()):
			s.deleteStateObject(stateObject)
		case isDirty:
			if stateObject.code != nil && stateObject.dirtyCode {
				s.db.TrieDB().InsertBlob(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
				stateObject.dirtyCode = false
			}
			if err := stateObject.CommitTrie(s.db); err != nil {
				return common.Hash{}, err
			}
			s.updateStateObject(stateObject)
		}
		delete(s.stateObjectsDirty, addr)
	}
	root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
		var account Account
		if err := rlp.DecodeBytes(leaf, &account); err != nil {
			return nil
		}
		if account.Root != emptyState {
			s.db.TrieDB().Reference(account.Root, parent)
		}
		code := common.BytesToHash(account.CodeHash)
		if code != emptyCode {
			s.db.TrieDB().Reference(code, parent)
		}
		return nil
	})
	log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads())
	return root, err
}

func (s *StateDB) clearJournalAndRefund() {
	s.journal = newJournal()
	s.validRevisions = s.validRevisions[:0]
	s.refund = 0
}

首先遍历了journal.dirties,他存储着每个地址操作的次数,然后在stateObjectsDirty进行记录,表示相应地址有改动。随后遍历stateObjects,他保存着各个地址的stateObject。

如果某个地址有过操作,首先处理code,如果code有更新则调用InsertBlob进行插入。随后调用stateObject的CommitTrie提交这个签名分析过,最终结果是将树写入数据库,之后更新了StateObject。注意前面只是提交各个stateObject的树,回顾stateObject源码,stateObject成员中的树是通过OpenStorageTrie获取的,其中的root参数是通过StateDB的树种获取的,而StateDB成员中的树是通过OpenTrie获取的。stateObject只是代表一个个以太坊账户,所以还要对StateDB的树进行提交。

题图来自unsplash:https://unsplash.com/photos/1Z2niiBPg5A