In the second half of 2018, the block chain industry is gradually fading away from the impetuosity and rationality at the beginning of its development. On the surface, it seems that the demand and status of relevant talents are declining. But in fact, it is the gradual decline of the initial bubble that gives people more attention to the real technology of the block chain.
## statedb.go
StateDB is used to store everything about merkle trie in the Ethernet workshop. StateDB is responsible for caching and storing nested states. This is the general query interface for retrieving contracts and accounts:
data structure
type StateDB struct { db Database // Backend database trie Trie // Trie tree main account trie // This map holds 'live' objects, which will get modified while processing a state transition. // The following Map is used to store currently active objects that are modified during state transitions. // State Objects are used to cache objects // State Objects Dirty is used to cache modified objects. stateObjects map[common.Address]*stateObject 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. dbErr error // The refund counter, also used by state transitioning. // The refund counter. The function is not clear yet. refund *big.Int thash, bhash common.Hash //Current transaction hash and block hash txIndex int // index of current transactions logs map[common.Hash][]*types.Log // The log key is the hash value of the transaction logSize uint preimages map[common.Hash][]byte // Mapping Relations of SHA3->byte[] Computed by EVM // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. // Status modification log. This is the pillar of Snapshot and RevertToSnapshot. journal journal validRevisions []revision nextRevisionId int lock sync.Mutex }
Constructor
// General usage: state db,: = state. New (common. Hash {}, state. NewDatabase (db))
// Create a new state from a given trie
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{}), refund: new(big.Int), logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), }, nil
}
Processing Log
State provides the processing of Log, which is quite unexpected, because Log is actually stored in the block chain, not in the state trie. State provides the processing of Log, using several functions based on the following. Strangely, I haven't seen how to delete the information in logs for the time being. If I don't delete it, I think it will accumulate more and more. TODO logs deletion
Prepare function, which is executed at the beginning of transaction execution.
AddLog function, which is executed by VM during transaction execution. Add logs. At the same time, it links the log with the transaction and adds some transaction information.
GetLogs function, take away when the transaction is completed.
// Prepare sets the current transaction hash and index and block hash which is
// used when the EVM emits new state logs.
func (self *StateDB) Prepare(thash, bhash common.Hash, ti int) {
self.thash = thash self.bhash = bhash self.txIndex = ti
}
func (self StateDB) AddLog(log types.Log) {
self.journal = append(self.journal, 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++
}
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
}
stateObject processing
getStateObject is first retrieved from the cache, if not from the trie tree, and loaded into the cache.
// Retrieve a state object given my the address. Returns nil if not found.
func (self StateDB) getStateObject(addr common.Address) (stateObject stateObject) {
// Prefer 'live' objects. if obj := self.stateObjects[addr]; obj != nil { if obj.deleted { return nil } return obj } // Load the object from the database. 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 } // Insert into the live set. obj := newObject(self, addr, data, self.MarkStateObjectDirty) self.setStateObject(obj) return obj
}
MarkStateObject Dirty, set a stateObject to Dirty. Insert an empty structure directly into the address corresponding to stateObjectDirty.
// MarkStateObjectDirty adds the specified object to the dirty map to avoid costly
// state object cache iteration to find a handful of modified ones.
func (self *StateDB) MarkStateObjectDirty(addr common.Address) {
self.stateObjectsDirty[addr] = struct{}{}
}
Snapshot and rollback functions
Snapshot can create a snapshot and then roll back to which state through RevertToSnapshot, which is done through journal. Each step of the modification adds an undo log to the journal. If you need to roll back, you just need to execute the undo log.
// Snapshot returns an identifier for the current revision of the state.
func (self *StateDB) Snapshot() int {
id := self.nextRevisionId self.nextRevisionId++ self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)}) return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
func (self *StateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots. 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 // Replay the journal to undo changes. for i := len(self.journal) - 1; i >= snapshot; i-- { self.journal[i].undo(self) } self.journal = self.journal[:snapshot] // Remove invalidated snapshots from the stack. self.validRevisions = self.validRevisions[:idx]
}
Get the root hash value of the intermediate state
IntermediateRoot is used to calculate the hash value of the root of the current state trie. This method is called during the execution of the transaction. Will be stored in transaction receipt
The Finalise method calls the update method to write the changes stored in the cache layer into the trie database. But at this time, it has not been written to the underlying database. commit hasn't been invoked yet, the data is still in memory and hasn't been filed yet.
// Finalise finalises the state by removing the self destructed objects
// and clears the journal as well as the refunds.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
for addr := range s.stateObjectsDirty { stateObject := s.stateObjects[addr] if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) { s.deleteStateObject(stateObject) } else { stateObject.updateRoot(s.db) s.updateStateObject(stateObject) } } // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund()
}
// 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.
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.Finalise(deleteEmptyObjects) return s.trie.Hash()
}
commit method
CommitTo is used to commit changes.
// CommitTo writes the state to the given database.
func (s *StateDB) CommitTo(dbw trie.DatabaseWriter, deleteEmptyObjects bool) (root common.Hash, err error) {
defer s.clearJournalAndRefund() // Commit objects to the trie. for addr, stateObject := range s.stateObjects { _, isDirty := s.stateObjectsDirty[addr] switch { case stateObject.suicided || (isDirty && deleteEmptyObjects && stateObject.empty()): // If the object has been removed, don't bother syncing it // and just mark it for deletion in the trie. s.deleteStateObject(stateObject) case isDirty: // Write any contract code associated with the state object if stateObject.code != nil && stateObject.dirtyCode { if err := dbw.Put(stateObject.CodeHash(), stateObject.code); err != nil { return common.Hash{}, err } stateObject.dirtyCode = false } // Write any storage changes in the state object to its storage trie. if err := stateObject.CommitTrie(s.db, dbw); err != nil { return common.Hash{}, err } // Update the object in the main account trie. s.updateStateObject(stateObject) } delete(s.stateObjectsDirty, addr) } // Write trie changes. root, err = s.trie.CommitTo(dbw) log.Debug("Trie cache stats after commit", "misses", trie.CacheMisses(), "unloads", trie.CacheUnloads()) return root, err
}
summary
The state package provides state management for users and contracts. Various state transitions of States and contracts are managed. cach