ETH源码学习(2)GetBalance

从这开始读,因为最简单。还能对重要的东西有所理解。如果是单节点debug,重启链之后余额会归零,所以最少要两个节点,同步区块就没这个问题了,具体原因还未知。

声明:eth版本:Geth/v1.10.7-unstable/darwin-arm64/go1.17.5,我对比了网上的一些文章,这块代码有改动,主要以下改变

  1. 从BlockChain的snap中读account

  2. 几乎放弃从trie中获取account,判断极为严苛,我想不到有什么情况能进入,应该是放弃了这层

分析

入口

//命令
eth.getBalance('0x0d7dd6dbabee2ec9b325aa7aa8b42d75068e8597')

//入口
func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
  //获取statedb
   state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
   if state == nil || err != nil {
      return nil, err
   }
//获取余额
   return (*hexutil.Big)(state.GetBalance(address)), state.Error()
}

创建statedb

func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
//根据最新的hader state root创建statedb
   if blockNr, ok := blockNrOrHash.Number(); ok {
      return b.StateAndHeaderByNumber(ctx, blockNr)
   }
   if hash, ok := blockNrOrHash.Hash(); ok {
      header, err := b.HeaderByHash(ctx, hash)
      if err != nil {
         return nil, nil, err
      }
      if header == nil {
         return nil, nil, errors.New("header for hash not found")
      }
      if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
         return nil, nil, errors.New("hash is not currently canonical")
      }
      stateDb, err := b.eth.BlockChain().StateAt(header.Root)
      return stateDb, header, err
   }
   return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
}
func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
   // Pending state is only known by the miner
  //有Pending的区块,从这里面获取statedb
   if number == rpc.PendingBlockNumber {
      block, state := b.eth.miner.Pending()
      return state, block.Header(), nil
   }
   // Otherwise resolve the block number and return its state
   header, err := b.HeaderByNumber(ctx, number)
   if err != nil {
      return nil, nil, err
   }
   if header == nil {
      return nil, nil, errors.New("header not found")
   }
//创建新的statedb
   stateDb, err := b.eth.BlockChain().StateAt(header.Root)
   return stateDb, header, err
}
//主要做了两件事,创建trie和snap
func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
   fmt.Println("root=====", root.String())
   tr, err := db.OpenTrie(root)
   if err != nil {
      return nil, err
   }
   sdb := &StateDB{
      db:                  db,
      trie:                tr,
      originalRoot:        root,
      snaps:               snaps,
      stateObjects:        make(map[common.Address]*stateObject),
      stateObjectsPending: make(map[common.Address]struct{}),
      stateObjectsDirty:   make(map[common.Address]struct{}),
      logs:                make(map[common.Hash][]*types.Log),
      preimages:           make(map[common.Hash][]byte),
      journal:             newJournal(),
      accessList:          newAccessList(),
      hasher:              crypto.NewKeccakState(),
   }
   if sdb.snaps != nil {
      if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
         sdb.snapDestructs = make(map[common.Hash]struct{})
         sdb.snapAccounts = make(map[common.Hash][]byte)
         sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
      }
   }
   return sdb, nil
}

statedb获取完毕,获取balance

回到最开始从GetBalance进入

func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
   fmt.Println("addr=====", addr.String())
   // Prefer live objects if any is available
  //从stateobject中获取
   if obj := s.stateObjects[addr]; obj != nil {
      return obj
   }
   // If no live objects are available, attempt to use snapshots
   var (
      data *Account
      err  error
   )
//从缓存中获取
   if s.snap != nil {
      if metrics.EnabledExpensive {
         defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now())
      }
      var acc *snapshot.Account
      if acc, err = s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())); err == nil {
         if acc == nil {
            return nil
         }
         data = &Account{
            Nonce:    acc.Nonce,
            Balance:  acc.Balance,
            CodeHash: acc.CodeHash,
            Root:     common.BytesToHash(acc.Root),
         }
         if len(data.CodeHash) == 0 {
            data.CodeHash = emptyCodeHash
         }
         if data.Root == (common.Hash{}) {
            data.Root = emptyRoot
         }
      }
   }
   // If snapshot unavailable or reading from it failed, load from the database
//从trie中获取
   if s.snap == nil || err != nil {
      if metrics.EnabledExpensive {
         defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now())
      }
      enc, err := s.trie.TryGet(addr.Bytes())
      if err != nil {
         s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err))
         return nil
      }
      if len(enc) == 0 {
         return nil
      }
      data = new(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(s, addr, *data)
   s.setStateObject(obj)
   return obj
}
func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) {
    dl.lock.RLock()
    defer dl.lock.RUnlock()

    // If the layer was flattened into, consider it invalid (any live reference to
    // the original should be marked as unusable).
    if dl.stale {
        return nil, ErrSnapshotStale
    }
    // If the layer is being generated, ensure the requested hash has already been
    // covered by the generator.
    if dl.genMarker != nil && bytes.Compare(hash[:], dl.genMarker) > 0 {
        return nil, ErrNotCoveredYet
    }
    // If we're in the disk layer, all diff layers missed
    snapshotDirtyAccountMissMeter.Mark(1)

    // Try to retrieve the account from the memory cache
        //从缓存中读
    if blob, found := dl.cache.HasGet(nil, hash[:]); found {
        snapshotCleanAccountHitMeter.Mark(1)
        snapshotCleanAccountReadMeter.Mark(int64(len(blob)))
        return blob, nil
    }
    // Cache doesn't contain account, pull from disk and cache for later
        //从硬盘中读
    blob := rawdb.ReadAccountSnapshot(dl.diskdb, hash)
    dl.cache.Set(hash[:], blob)

    snapshotCleanAccountMissMeter.Mark(1)
    if n := len(blob); n > 0 {
        snapshotCleanAccountWriteMeter.Mark(int64(n))
    } else {
        snapshotCleanAccountInexMeter.Mark(1)
    }
    return blob, nil
}

总结

get balance和传统项目的思路差不多,一层层读缓存,最终读数据库,然后再保存缓存,返回前端

  1. getBalance要先获取最新区块的root(stateRoot)来换stateDB

  2. 然后从stateDB的stateObject中获取

  3. 没有的话好像从snap中获取

    1. 先读缓存

    2. 再读levelDB

    3. 命中的话缓存至快照

  4. 没有的话再从trie中获取

  5. 命中则缓存至stateObject

  6. 返回前端

从上面了解到stateDB这个东西很重要,所有的操作都是围绕他来完成的。stateDB又是靠BlockChain里的数据来创建的

  1. 最新区块的state root

  2. snaps

  3. stateCache

这样就有了个大致的影响,然后通过后面的内容,更加深入的了解eth的架构