core-state Source Code Analysis for Etherfield Source Code Analysis in Block Chain Tutorial (II)

Keywords: Blockchain snapshot Database

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
state Packages provide state management capabilities for users and contracts. Various state transitions of States and contracts are managed. cach

Posted by abitshort on Sun, 27 Jan 2019 16:09:15 -0800