1. 程式人生 > >從命令列開始解析同步區塊的程式碼

從命令列開始解析同步區塊的程式碼

從命令列開始解析同步區塊的程式碼

一.同步簡介

我們都知道geth支援三種同步模式

  • fast模式:從開始到結束,獲取區塊的header,獲取區塊的body,從創始塊開始校驗每一個元素,需要下載所有區塊資料資訊。速度最慢,但是能獲取到所有的歷史資料。

  • full模式:獲取區塊的header,獲取區塊的body,在同步到當前塊之前不處理任何事務。然後獲得一個快照,此後,像full節點一樣進行後面的同步操作。這種方法用得最多,目的在不要在意歷史資料,將歷史資料按照快照的方式,不逐一驗證,沿著區塊下載最近資料庫中的交易,有可能丟失歷史資料。此方法可能會對歷史資料有部分丟失,但是不影響今後的使用。

  • light模式:僅獲取當前狀態。驗證元素需要向full節點發起相應的請求。

二.從命令列開始解析同步區塊程式碼

geth的main包配置函式utils.SyncModeFlag,在util包中可以看到Name和Usage引數

nodeFlags = []cli.Flag{
        utils.IdentityFlag,
        utils.UnlockedAccountFlag,
        utils.PasswordFileFlag,
        utils.BootnodesFlag,
        utils.BootnodesV4Flag,
        utils.BootnodesV5Flag,
        utils.DataDirFlag,
        utils.KeyStoreDirFlag,
        utils.NoUSBFlag,
        utils.DashboardEnabledFlag,
        utils.DashboardAddrFlag,
        utils.DashboardPortFlag,
        utils.DashboardRefreshFlag,
        utils.EthashCacheDirFlag,
        utils.EthashCachesInMemoryFlag,
        utils.EthashCachesOnDiskFlag,
        utils.EthashDatasetDirFlag,
        utils.EthashDatasetsInMemoryFlag,
        utils.EthashDatasetsOnDiskFlag,
        utils.TxPoolNoLocalsFlag,
        utils.TxPoolJournalFlag,
        utils.TxPoolRejournalFlag,
        utils.TxPoolPriceLimitFlag,
        utils.TxPoolPriceBumpFlag,
        utils.TxPoolAccountSlotsFlag,
        utils.TxPoolGlobalSlotsFlag,
        utils.TxPoolAccountQueueFlag,
        utils.TxPoolGlobalQueueFlag,
        utils.TxPoolLifetimeFlag,
        utils.FastSyncFlag,
        utils.LightModeFlag,
        utils.SyncModeFlag,
        utils.GCModeFlag,
        utils.LightServFlag,
        utils.LightPeersFlag,
        utils.LightKDFFlag,
        utils.CacheFlag,
        utils.CacheDatabaseFlag,
        utils.CacheGCFlag,
        utils.TrieCacheGenFlag,
        utils.ListenPortFlag,
        utils.MaxPeersFlag,
        utils.MaxPendingPeersFlag,
        utils.EtherbaseFlag,
        utils.GasPriceFlag,
        utils.MinerThreadsFlag,
        utils.MiningEnabledFlag,
        utils.TargetGasLimitFlag,
        utils.NATFlag,
        utils.NoDiscoverFlag,
        utils.DiscoveryV5Flag,
        utils.NetrestrictFlag,
        utils.NodeKeyFileFlag,
        utils.NodeKeyHexFlag,
        utils.DeveloperFlag,
        utils.DeveloperPeriodFlag,
        utils.TestnetFlag,
        utils.RinkebyFlag,
        utils.VMEnableDebugFlag,
        utils.NetworkIdFlag,
        utils.RPCCORSDomainFlag,
        utils.RPCVirtualHostsFlag,
        utils.EthStatsURLFlag,
        utils.MetricsEnabledFlag,
        utils.FakePoWFlag,
        utils.NoCompactionFlag,
        utils.GpoBlocksFlag,
        utils.GpoPercentileFlag,
        utils.ExtraDataFlag,
        configFileFlag,
    }

flag配置詳情

defaultSyncMode = eth.DefaultConfig.SyncMode
    SyncModeFlag    = TextMarshalerFlag{
        Name:  "syncmode",
        Usage: `Blockchain sync mode ("fast", "full", or "light")`,
        Value: &defaultSyncMode,
    }

flag相關配置的結構體型別

type TextMarshalerFlag struct {
    Name  string
    Value TextMarshaler
    Usage string
}

TextMarshalerFlag操作的成員方法

func (f TextMarshalerFlag) GetName() string {
    return f.Name
}

func (f TextMarshalerFlag) String() string {
    return fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)
}

func (f TextMarshalerFlag) Apply(set *flag.FlagSet) {
    eachName(f.Name, func(name string) {
        set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage)
    })
}

從全域性的flag配置中返回一個TextMarshalerFlag配置標誌

func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler {
    val := ctx.GlobalGeneric(name)
    if val == nil {
        return nil
    }
    return val.(textMarshalerVal).v
}

預設配置使用主網路的程式碼

var DefaultConfig = Config{
    SyncMode: downloader.FastSync,
    Ethash: ethash.Config{
        CacheDir:       "ethash",
        CachesInMem:    2,
        CachesOnDisk:   3,
        DatasetsInMem:  1,
        DatasetsOnDisk: 2,
    },
    NetworkId:     1,
    LightPeers:    100,
    DatabaseCache: 768,
    TrieCache:     256,
    TrieTimeout:   5 * time.Minute,
    GasPrice:      big.NewInt(18 * params.Shannon),

    TxPool: core.DefaultTxPoolConfig,
    GPO: gasprice.Config{
        Blocks:     20,
        Percentile: 60,
    },
}

下載模式配置的程式碼,本段程式碼存在於eth包中的config.go的config結構體內部

SyncMode  downloader.SyncMode

同步的型別是SyncMode,而SyncMode的真實型別是int。const常量的定義給不同模式分別賦值:

  • full:0
  • fast: 1
  • light: 2

    type SyncMode int
    
    const (
        FullSync  SyncMode = iota // Synchronise the entire blockchain history from full blocks
        FastSync                  // Quickly download the headers, full sync only at the chain head
        LightSync                 // Download only the headers and terminate afterwards
    )
    

整個模式的變更程式碼請看downloader包中的modes.go

此方法比較簡單,當傳入的mode大於等於0並且小於等於2時返回true。可以簡單理解為是一個合法性的校驗

func (mode SyncMode) IsValid() bool {
    return mode >= FullSync && mode <= LightSync
}

此段程式碼實現了stringer的介面,當被呼叫時會返回對應的字串描述:full,fast,light,unknown。此方法類似與Java中的toString方法。

func (mode SyncMode) String() string {
    switch mode {
    case FullSync:
        return "full"
    case FastSync:
        return "fast"
    case LightSync:
        return "light"
    default:
        return "unknown"
    }
}

此方法實現了encoding包下的TextMarshaler介面的MarshalText方法,根據傳入的同步型別值返回字串編碼(UTF-8-encoded)之後的文字內容。可以簡單理解為SyncMode(int)和文字內容的轉換。

func (mode SyncMode) MarshalText() ([]byte, error) {
    switch mode {
    case FullSync:
        return []byte("full"), nil
    case FastSync:
        return []byte("fast"), nil
    case LightSync:
        return []byte("light"), nil
    default:
        return nil, fmt.Errorf("unknown sync mode %d", mode)
    }
}

此方法實現了encoding包下的TextUnmarshaler介面的UnmarshalText方法,根據傳入的文字內容
返回SyncMode型別對應的值。可以簡單理解為文字內容和SyncMode(int)的轉換。

func (mode *SyncMode) UnmarshalText(text []byte) error {
    switch string(text) {
    case "full":
        *mode = FullSync
    case "fast":
        *mode = FastSync
    case "light":
        *mode = LightSync
    default:
        return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text)
    }
    return nil
}

同步模式中途的變更經過上面的程式碼分析我們是否就確定,如果不傳遞引數geth一直就是通過fast模式進行同步的麼?那麼,再看看下面的程式碼分析吧。

在eth/handler.go中方法NewProtocolManager中的程式碼:

// Figure out whether to allow fast sync or not
if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 {
      log.Warn("Blockchain not empty, fast sync disabled")
      mode = downloader.FullSync
  }
if mode == downloader.FastSync {
      manager.fastSync = uint32(1)
  }

這段程式碼是在建立ProtocolManager時進行同步模式的引數設定。blockchain.CurrentBlock()獲得當前的區塊資訊,NumberU64()返回的是最新區塊的頭部的number

func (b *Block) NumberU64() uint64 { 
    return b.header.Number.Uint64() 
}

現在整理一下這段程式碼的整體邏輯就是,當同步模式為fast並最新區塊的高度大於0(已經同步過一部分資料)時,程式自動將同步模式轉變為full,並列印警告資訊。

GitHub地址:

https://github.com/guoshijiang/go-ethereum-code-analysis