文档章节

区块链教程以太坊源码分析core-state源码分析(一)

兄弟连区块链入门教程
 兄弟连区块链入门教程
发布于 10/22 16:13
字数 2272
阅读 11
收藏 0

兄弟连区块链教程以太坊源码分析core-state源码分析,core/state 包主要为以太坊的state trie提供了一层缓存层(cache)

  • database主要提供了trie树的抽象,提供trie树的缓存和合约代码长度的缓存。
  • journal主要提供了操作日志,以及操作回滚的功能。
  • state_object是account对象的抽象,提供了账户的一些功能。
  • statedb主要是提供了state trie的部分功能。
## database.go
database.go 提供了一个数据库的抽象。

数据结构
    
    // 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 打开了主账号的trie树
        // OpenStorageTrie 打开了一个账号的storage trie
        OpenTrie(root common.Hash) (Trie, error)
        OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
        // Accessing contract code:
        // 访问合约代码
        ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
        // 访问合约的大小。 这个方法可能经常被调用。因为有缓存。
        ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
        // CopyTrie returns an independent copy of the given trie.
        // CopyTrie 返回了一个指定trie的独立的copy
        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 //trie树的缓存
        codeSizeCache *lru.Cache         //合约代码大小的缓存
    }
   

OpenTrie,从缓存里面查找。如果找到了返回缓存的trie的copy, 否则重新构建一颗树返回。


 
    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 和 ContractCodeSize, ContractCodeSize有缓存。

    
    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的结构和commit方法,commit的时候会调用pushTrie方法把之前的Trie树缓存起来。

    // 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代表了操作日志, 并针对各种操作的日志提供了对应的回滚功能。 可以基于这个日志来做一些事务类型的操作。

类型定义,定义了journalEntry这个接口,提供了undo的功能。 journal 就是journalEntry的列表。

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

各种不同的日志类型以及undo方法。

    createObjectChange struct { //创建对象的日志。 undo方法就是从StateDB中删除创建的对象。
        account *common.Address
    }
    func (ch createObjectChange) undo(s *StateDB) {
        delete(s.stateObjects, *ch.account)
        delete(s.stateObjectsDirty, *ch.account)
    }
    // 对于stateObject的修改, undo方法就是把值改为原来的对象。
    resetObjectChange struct {
        prev *stateObject
    }
    func (ch resetObjectChange) undo(s *StateDB) {
        s.setStateObject(ch.prev)
    }
    // 自杀的更改。自杀应该是删除账号,但是如果没有commit的化,对象还没有从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)
    }

    // 我理解是DAO事件的退款处理
    refundChange struct {
        prev *big.Int
    }
    func (ch refundChange) undo(s *StateDB) {
        s.refund = ch.prev
    }
    // 增加了日志的修改
    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--
    }
    // 这个是增加 VM看到的 SHA3的 原始byte[], 增加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

stateObject表示正在修改的以太坊帐户。

数据结构

    type Storage map[common.Hash]common.Hash
    
    // stateObject represents an Ethereum account which is being modified.
    // stateObject表示正在修改的以太坊帐户。
    // 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.

    使用模式如下:
    首先你需要获得一个state_object。
    帐户值可以通过对象访问和修改。
    最后,调用CommitTrie将修改后的存储trie写入数据库。

    type stateObject struct {
        address common.Address
        addrHash common.Hash // hash of ethereum address of the account 以太坊账号地址的hash值
        data Account // 这个是实际的以太坊账号的信息
        db *StateDB //状态数据库
    
        // 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
    
        // Write caches. 写缓存
        trie Trie // storage trie, which becomes non-nil on first access 用户的存储trie ,在第一次访问的时候变得非空
        code Code // contract bytecode, which gets set when code is loaded 合约代码,当代码被加载的时候被设置
    
        cachedStorage Storage // Storage entry cache to avoid duplicate reads 用户存储对象的缓存,用来避免重复读
        dirtyStorage Storage // Storage entries that need to be flushed to disk 需要刷入磁盘的用户存储对象
    
        // Cache flags. Cache 标志
        // When an object is marked suicided it will be delete from the trie
        // during the "update" phase of the state transition.
        // 当一个对象被标记为自杀时,它将在状态转换的“更新”阶段期间从树中删除。
        dirtyCode bool // true if the code was updated 如果代码被更新,会设置为true
        suicided bool
        touched bool
        deleted bool
        onDirty func(addr common.Address) // Callback method to mark a state object newly dirty 第一次被设置为drity的时候会被调用。
    }

    // Account is the Ethereum consensus representation of accounts.
    // These objects are stored in the main account trie.
    // 帐户是以太坊共识表示的帐户。 这些对象存储在main account trie。
    type Account struct {
        Nonce uint64
        Balance *big.Int
        Root common.Hash // merkle root of the storage trie
        CodeHash []byte
    }

构造函数

    // 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的编码方式,只会编码 Account对象。

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

一些状态改变的函数。
    
    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的处理

    // getTrie返回账户的Storage Trie
    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 返回account storage 的一个值,这个值的类型是Hash类型。
    // 说明account storage里面只能存储hash值?
    // 如果缓存里面存在就从缓存里查找,否则从数据库里面查询。然后存储到缓存里面。
    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.
    // 往 account storeage 里面设置一个值 key value 的类型都是Hash类型。
    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
        }
    }


提交 Commit

    // CommitTrie the storage trie of the object to dwb.
    // This updates the trie root.
    // 步骤,首先打开,然后修改,然后提交或者回滚
    func (self *stateObject) CommitTrie(db Database, dbw trie.DatabaseWriter) error {
        self.updateTrie(db) // updateTrie把修改过的缓存写入Trie树
        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
    // 把账号的root设置为当前的trie树的跟。
    func (self *stateObject) updateRoot(db Database) {
        self.updateTrie(db)
        self.data.Root = self.trie.Hash()
    }
    

额外的一些功能 ,deepCopy提供了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
    }

未完待续…感谢继续关注兄弟连区块链教程分享

© 著作权归作者所有

共有 人打赏支持
兄弟连区块链入门教程
粉丝 5
博文 174
码字总数 182165
作品 0
延庆
私信 提问
区块链教程以太坊源码分析core-state源码分析(二)

兄弟连区块链教程以太坊源码分析core-state源码分析,2018年下半年,区块链行业正逐渐褪去发展之初的浮躁、回归理性,表面上看相关人才需求与身价似乎正在回落。但事实上,正是初期泡沫的渐退...

兄弟连区块链入门教程
10/22
0
0
区块链从入门到放弃-区块链入门汇总整理

一个完整的入门整理帖,所以到处搜罗搬运了一些觉得挺不错的来,直接贴链接应该不算侵权吧 入门介绍与原理: 一、比特币 1.比特币白皮书 这是一切的开始 2.精通比特币 讲比特币很详细的一本书...

雪花又一年
04/13
0
0
区块链入门教程以太坊源码分析ethdb源码分析

兄弟连区块链入门教程以太坊源码分析ethdb源码分析,2018年下半年,区块链行业正逐渐褪去发展之初的浮躁、回归理性,表面上看相关人才需求与身价似乎正在回落。但事实上,正是初期泡沫的渐退...

兄弟连区块链入门教程
10/23
0
0
区块链教程以太坊源码分析chain-indexer区块链索引一

兄弟连区块链教程以太坊源码分析chain-indexer区块链索引一 chain_indexer 区块链索引 chain_indexer.go 源码解析 chainindexer 顾名思义, 就是用来给区块链创建索引的功能。 之前在eth协议...

兄弟连区块链入门教程
10/19
0
0
区块链教程以太坊源码分析downloader-peer源码分析

  兄弟连区块链教程以太坊源码分析downloader-peer源码分析,区块链行业正逐渐褪去发展之初的浮躁、回归理性,表面上看相关人才需求与身价似乎正在回落。但事实上,正是初期泡沫的渐退,让...

兄弟连区块链入门教程
10/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

docker部署springboot项目

安装docker 菜鸟教程 springboot项目 maven依赖 <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001......

yimingkeji
今天
10
0
ios多个target

1.建立3个target,分别为heroone,heroone test,heroone dev;分别为正式环境,test环境,dev环境 2.注意取消掉autocreate以防止名字不对,分别以Duplicate的方式建立另外两个scheme 3.创建...

HeroHY
今天
5
0
php获取客户端IP

php获取客户端IP 首先先阅读关于IP真实性安全的文章:如何正確的取得使用者 IP? 「任何從客戶端取得的資料都是不可信任的!」 HTTP_CLIENT_IP头是有的,但未成标准,不一定服务器都实现。 ...

DrChenXX
昨天
0
0
. The valid characters are defined in RFC 7230 and RFC 问题

通过这里的回答,我们可以知道: Tomcat在 7.0.73, 8.0.39, 8.5.7 版本后,添加了对于http头的验证。 具体来说,就是添加了些规则去限制HTTP头的规范性 参考这里 具体来说: org.apache.tom...

west_coast
昨天
1
0
刷leetcode第704题-二分查找

今天双十一买的算法书到货了,路上刷到有人说的这个题,借(chao)鉴(xi)一下别人的思路,这个是C++标准库里面的经典方法,思路精巧,优雅好品味 int search(int* nums, int numsSize, in...

锟斤拷烫烫烫
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部