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

Keywords: Blockchain Database encoding

Brotherly Block Chain Tutorial Taifang Source Code Analysis core-state Source Code Analysis, core/state package mainly provides a layer of cache for the state trie of Taifang.

  • database mainly provides the abstraction of trie tree, provides the cache of trie tree and the cache of contract code length.
  • journal mainly provides the function of operation log and operation rollback.
  • state_object is the abstraction of account object, which provides some functions of account.
  • statedb mainly provides some functions of state trie.
## database.go
database.go Provides an abstraction of the database.

//data structure
    
    // Database wraps access to tries and contract code.
    type Database interface {
        // Accessing tries:
        // OpenTrie opens the main account trie.
        // OpenStorageTrie opens the storage trie of an account.
        // OpenTrie opens the trie tree for the main account
        // OpenStorageTrie opens a storage trie for an account
        OpenTrie(root common.Hash) (Trie, error)
        OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
        // Accessing contract code:
        // Access contract code
        ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
        // The size of the access contract. This method may be called frequently. Because of the cache.
        ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
        // CopyTrie returns an independent copy of the given trie.
        // CopyTrie returns a separate copy of the specified trie
        CopyTrie(Trie) Trie
    }
    
    // NewDatabase creates a backing store for state. The returned database is safe for
    // concurrent use and retains cached trie nodes in memory.
    func NewDatabase(db ethdb.Database) Database {
        csc, _ := lru.New(codeSizeCacheSize)
        return &cachingDB{db: db, codeSizeCache: csc}
    }
    
    type cachingDB struct {
        db ethdb.Database
        mu sync.Mutex
        pastTries []*trie.SecureTrie //Caching of trie tree
        codeSizeCache *lru.Cache         //Caching of contract code size
    }
   

OpenTrie, look in the cache. If you find a copy that returns the cached trie, otherwise rebuild a tree to return.


 
    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
    }

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


ContractCode and ContractCodeSize, ContractCodeSize There are caches.

    
    func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
        code, err := db.db.Get(codeHash[:])
        if err == nil {
            db.codeSizeCache.Add(codeHash, len(code))
        }
        return code, err
    }
    
    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)
        if err == nil {
            db.codeSizeCache.Add(codeHash, len(code))
        }
        return len(code), err
    }

cachedTrie Structure and commit Method, commit When called pushTrie The method put the previous one. Trie Trees are cached.

    // cachedTrie inserts its trie into a cachingDB on commit.
    type cachedTrie struct {
        *trie.SecureTrie
        db *cachingDB
    }
    
    func (m cachedTrie) CommitTo(dbw trie.DatabaseWriter) (common.Hash, error) {
        root, err := m.SecureTrie.CommitTo(dbw)
        if err == nil {
            m.db.pushTrie(m.SecureTrie)
        }
        return root, err
    }
    func (db *cachingDB) pushTrie(t *trie.SecureTrie) {
        db.mu.Lock()
        defer db.mu.Unlock()
    
        if len(db.pastTries) >= maxPastTries {
            copy(db.pastTries, db.pastTries[1:])
            db.pastTries[len(db.pastTries)-1] = t
        } else {
            db.pastTries = append(db.pastTries, t)
        }
    }

journal.go

journal represents the operation log and provides the corresponding rollback function for various operation logs. You can do some transaction type operations based on this log.

Type definition defines the journalEntry interface and provides undo functionality. journal is the list of journal Entry.

    type journalEntry interface {
        undo(*StateDB)
    }
    
    type journal []journalEntry
    

//Various log types and undo methods.

    createObjectChange struct { //Create logs of objects. The undo method is to delete the created object from StateDB.
        account *common.Address
    }
    func (ch createObjectChange) undo(s *StateDB) {
        delete(s.stateObjects, *ch.account)
        delete(s.stateObjectsDirty, *ch.account)
    }
    // For the modification of stateObject, the undo method is to change the value to the original object.
    resetObjectChange struct {
        prev *stateObject
    }
    func (ch resetObjectChange) undo(s *StateDB) {
        s.setStateObject(ch.prev)
    }
    // Suicide changes. Suicide should be deleting accounts, but without commit, the object has not been deleted from stateDB.
    suicideChange struct {
        account *common.Address
        prev bool // whether account had already suicided
        prevbalance *big.Int
    }
    func (ch suicideChange) undo(s *StateDB) {
        obj := s.getStateObject(*ch.account)
        if obj != nil {
            obj.suicided = ch.prev
            obj.setBalance(ch.prevbalance)
        }
    }

    // Changes to individual accounts.
    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
    }
    
    func (ch balanceChange) undo(s *StateDB) {
        s.getStateObject(*ch.account).setBalance(ch.prev)
    }
    func (ch nonceChange) undo(s *StateDB) {
        s.getStateObject(*ch.account).setNonce(ch.prev)
    }
    func (ch codeChange) undo(s *StateDB) {
        s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
    }
    func (ch storageChange) undo(s *StateDB) {
        s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
    }

    // I understand it is the refund processing of DAO events.
    refundChange struct {
        prev *big.Int
    }
    func (ch refundChange) undo(s *StateDB) {
        s.refund = ch.prev
    }
    // Added log modification
    addLogChange struct {
        txhash common.Hash
    }
    func (ch addLogChange) undo(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--
    }
    // This is to increase the original byte [] of SHA3 as seen by VM and the corresponding relationship of SHA3 hash - > byte [].
    addPreimageChange struct {
        hash common.Hash
    }
    func (ch addPreimageChange) undo(s *StateDB) {
        delete(s.preimages, ch.hash)
    }

    touchChange struct {
        account *common.Address
        prev bool
        prevDirty bool
    }
    var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
    func (ch touchChange) undo(s *StateDB) {
        if !ch.prev && *ch.account != ripemd {
            s.getStateObject(*ch.account).touched = ch.prev
            if !ch.prevDirty {
                delete(s.stateObjectsDirty, *ch.account)
            }
        }
    }

state_object.go

The stateObject indicates that the Ethernet square account is being modified.

data structure

    type Storage map[common.Hash]common.Hash
    
    // stateObject represents an Ethereum account which is being modified.
    // The stateObject indicates that the Ethernet square account is being modified.
    // 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.

    //The mode of use is as follows:
    //First you need to get a state_object.
    //Account values can be accessed and modified through objects.
    //Finally, CommitTrie is called to write the modified storage trie to the database.

    type stateObject struct {
        address common.Address
        addrHash common.Hash // hash of ethereum address of the account Ethernet square account address
        data Account // This is the actual information of the ETF account.
        db *StateDB //State database
    
        // 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.
        //
        //Database error.
        stateObject Will be the core of the consensus algorithm and VM Using this code, database-level errors cannot be handled internally.
        //Any errors that occur during database reading are stored here and eventually returned by StateDB.Commit.
        dbErr error
    
        // Write caches. Write caches
        trie Trie // storage trie, which becomes non-nil on first access user's storage trie, becomes non-empty on first access
        code Code // Contract by tecode, which gets set when code is loaded contract code, which is set when code is loaded
    
        cachedStorage Storage // Storage entry cache to avoid duplicate reads User Storage Object cache to avoid duplicate reads
        dirtyStorage Storage // Storage entries that need to be flushed to disk user storage objects that need to be brushed into disk
    
        // Cache flags. Cache logo
        // When an object is marked suicided it will be delete from the trie
        // during the "update" phase of the state transition.
        // When an object is marked as suicide, it is deleted from the tree during the "update" phase of state transition.
        dirtyCode bool // True if the code is updated, it is set to true if the code is updated
        suicided bool
        touched bool
        deleted bool
        onDirty func(addr common.Address) // Callback method to mark a state object new dirty is called the first time it is set to drive.
    }

    // Account is the Ethereum consensus representation of accounts.
    // These objects are stored in the main account trie.
    // Account is an account represented by Taifang Consensus. These objects are stored in main account trie.
    type Account struct {
        Nonce uint64
        Balance *big.Int
        Root common.Hash // merkle root of the storage trie
        CodeHash []byte
    }

Constructor

    // newObject creates a state object.
    func newObject(db *StateDB, address common.Address, data Account, onDirty func(addr common.Address)) *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,
            cachedStorage: make(Storage),
            dirtyStorage: make(Storage),
            onDirty: onDirty,
        }
    }


RLP The encoding method, only encoding Account Object.

    // EncodeRLP implements rlp.Encoder.
    func (c *stateObject) EncodeRLP(w io.Writer) error {
        return rlp.Encode(w, c.data)
    }

//Some functions of state change.
    
    func (self *stateObject) markSuicided() {
        self.suicided = true
        if self.onDirty != nil {
            self.onDirty(self.Address())
            self.onDirty = nil
        }
    }

    func (c *stateObject) touch() {
        c.db.journal = append(c.db.journal, touchChange{
            account: &c.address,
            prev: c.touched,
            prevDirty: c.onDirty == nil,
        })
        if c.onDirty != nil {
            c.onDirty(c.Address())
            c.onDirty = nil
        }
        c.touched = true
    }
    

Storage Processing

    // getTrie Returns Storage Trie to Account
    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 returns a value in account storage.
    // GetState returns a value of account storage, the type of which is Hash.
    // Explain that only hash values can be stored in account storage?
    // If there is a cache, look it up from the cache, otherwise query it from the database. It is then stored in the cache.
    func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
        value, exists := self.cachedStorage[key]
        if exists {
            return value
        }
        // Load from DB in case it is missing.
        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)
        }
        if (value != common.Hash{}) {
            self.cachedStorage[key] = value
        }
        return value
    }
    
    // SetState updates a value in account storage.
    // The types that set a value key value to account store age are all Hash types.
    func (self *stateObject) SetState(db Database, key, value common.Hash) {
        self.db.journal = append(self.db.journal, storageChange{
            account: &self.address,
            key: key,
            prevalue: self.GetState(db, key),
        })
        self.setState(key, value)
    }
    
    func (self *stateObject) setState(key, value common.Hash) {
        self.cachedStorage[key] = value
        self.dirtyStorage[key] = value
    
        if self.onDirty != nil {
            self.onDirty(self.Address())
            self.onDirty = nil
        }
    }


//Submit Commit

    // CommitTrie the storage trie of the object to dwb.
    // This updates the trie root.
    // Step, first open, then modify, then submit or roll back
    func (self *stateObject) CommitTrie(db Database, dbw trie.DatabaseWriter) error {
        self.updateTrie(db) // updateTrie writes the modified cache to the Trie tree
        if self.dbErr != nil {
            return self.dbErr
        }
        root, err := self.trie.CommitTo(dbw)
        if err == nil {
            self.data.Root = root
        }
        return err
    }

    // updateTrie writes cached storage modifications into the object's storage trie.
    func (self *stateObject) updateTrie(db Database) Trie {
        tr := self.getTrie(db)
        for key, value := range self.dirtyStorage {
            delete(self.dirtyStorage, key)
            if (value == common.Hash{}) {
                self.setError(tr.TryDelete(key[:]))
                continue
            }
            // Encoding []byte cannot fail, ok to ignore the error.
            v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
            self.setError(tr.TryUpdate(key[:], v))
        }
        return tr
    }
    
    // UpdateRoot sets the trie root to the current root hash of
    // Set the root of the account to the heel of the current trie tree.
    func (self *stateObject) updateRoot(db Database) {
        self.updateTrie(db)
        self.data.Root = self.trie.Hash()
    }
    

//Additionally, deepCopy provides a deep copy of state_object.
    
    
    func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject {
        stateObject := newObject(db, self.address, self.data, onDirty)
        if self.trie != nil {
            stateObject.trie = db.db.CopyTrie(self.trie)
        }
        stateObject.code = self.code
        stateObject.dirtyStorage = self.dirtyStorage.Copy()
        stateObject.cachedStorage = self.dirtyStorage.Copy()
        stateObject.suicided = self.suicided
        stateObject.dirtyCode = self.dirtyCode
        stateObject.deleted = self.deleted
        return stateObject
    }

To be continued uuuuuuuuuuuuu Thank you for continuing to pay attention to Brothers Block Chain Tutorial Sharing

Posted by russy on Sun, 27 Jan 2019 09:57:14 -0800