方法定义源代码解析

首选从源代码中找到eth/downloader/modes.go。从文件的名字就可得知此文件是用来定义模式的,所谓modes就是指的同步模式。下面我们看一下此文件内的所有源代码,然后再逐一分析。

  1. package downloader

  2. import "fmt"

  3. // SyncMode represents the synchronisation mode of the downloader.

  4. type SyncMode int

  5. const (

  6.    FullSync  SyncMode = iota // Synchronise the entire blockchain history from full blocks

  7.    FastSync                  // Quickly download the headers, full sync only at the chain head

  8.    LightSync                 // Download only the headers and terminate afterwards

  9. )

  10. func (mode SyncMode) IsValid() bool {

  11.    return mode >= FullSync && mode <= LightSync

  12. }

  13. // String implements the stringer interface.

  14. func (mode SyncMode) String() string {

  15.    switch mode {

  16.    case FullSync:

  17.        return "full"

  18.    case FastSync:

  19.        return "fast"

  20.    case LightSync:

  21.        return "light"

  22.    default:

  23.        return "unknown"

  24.    }

  25. }

  26. func (mode SyncMode) MarshalText() ([]byte, error) {

  27.    switch mode {

  28.    case FullSync:

  29.        return []byte("full"), nil

  30.    case FastSync:

  31.        return []byte("fast"), nil

  32.    case LightSync:

  33.        return []byte("light"), nil

  34.    default:

  35.        return nil, fmt.Errorf("unknown sync mode %d", mode)

  36.    }

  37. }

  38. func (mode *SyncMode) UnmarshalText(text []byte) error {

  39.    switch string(text) {

  40.    case "full":

  41.        *mode = FullSync

  42.    case "fast":

  43.        *mode = FastSync

  44.    case "light":

  45.        *mode = LightSync

  46.    default:

  47.        return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text)

  48.    }

  49.    return nil

  50. }

SyncMode类型定义

  1. type SyncMode int

同步的类型是SyncMode,而SyncMode的真实类型是int。

SyncMode常量定义

  1. const (

  2.    FullSync  SyncMode = iota // 同步完整的区块信息

  3.    FastSync                  // 快速同步header,然后再跟进header同步全部内容

  4.    LightSync                 // 只下载header并在之后终止

  5. )

const常量的定义给不同模式分别赋值:

  • full:0

  • fast: 1

  • light: 2

String接口实现

此段代码实现了stringer的接口,当被调用时会返回对应的字符串描述:full,fast,light,unknown。此方法类似与Java中的toString方法。

  1. // String implements the stringer interface.

  2. func (mode SyncMode) String() string {

  3.    switch mode {

  4.    case FullSync:

  5.        return "full"

  6.    case FastSync:

  7.        return "fast"

  8.    case LightSync:

  9.        return "light"

  10.    default:

  11.        return "unknown"

  12.    }

  13. }

IsValid方法

  1. func (mode SyncMode) IsValid() bool {

  2.    return mode >= FullSync && mode <= LightSync

  3. }

此方法比较简单,当传入的mode大于等于0并且小于等于2时返回true。可以简单理解为是一个合法性的校验。

TextMarshaler方法实现

  1. func (mode SyncMode) MarshalText() ([]byte, error) {

  2.    switch mode {

  3.    case FullSync:

  4.        return []byte("full"), nil

  5.    case FastSync:

  6.        return []byte("fast"), nil

  7.    case LightSync:

  8.        return []byte("light"), nil

  9.    default:

  10.        return nil, fmt.Errorf("unknown sync mode %d", mode)

  11.    }

  12. }

此方法实现了encoding包下的TextMarshaler接口的MarshalText方法,根据传入的同步类型值返回字符串编码(UTF-8-encoded)之后的文本内容。可以简单理解为SyncMode(int)和文本内容的转换。

UnmarshalText方法实现

  1. func (mode *SyncMode) UnmarshalText(text []byte) error {

  2.    switch string(text) {

  3.    case "full":

  4.        *mode = FullSync

  5.    case "fast":

  6.        *mode = FastSync

  7.    case "light":

  8.        *mode = LightSync

  9.    default:

  10.        return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text)

  11.    }

  12.    return nil

  13. }

此方法实现了encoding包下的TextUnmarshaler接口的UnmarshalText方法,根据传入的文本内容返回SyncMode类型对应的值。可以简单理解为文本内容和SyncMode(int)的转换。

方法使用源代码解析

上面我们了解了同步模式的类型和方法定义,现在我们就看一下同步的过程中是怎么使用的。

默认同步模式的定义

在cmd/utils/flags.go文件内定义了项目启动时的基本配置参数,我们可以找到此段代码:

  1. defaultSyncMode = eth.DefaultConfig.SyncMode

  2.    SyncModeFlag    = TextMarshalerFlag{

  3.        Name:  "syncmode",

  4.        Usage: `Blockchain sync mode ("fast", "full", or "light")`,

  5.        Value: &defaultSyncMode,

  6.    }

这里定义了默认的同步模式,也就是说如果不传递参数时,同步模式就是按照这里的定义来执行。进一步看一下eth.DefaultConfig.SyncMode的值为多少:

  1. var DefaultConfig = Config{

  2.    SyncMode: downloader.FastSync,

  3.    Ethash: ethash.Config{

  4.        CacheDir:       "ethash",

  5.        CachesInMem:    2,

  6.        CachesOnDisk:   3,

  7.        DatasetsInMem:  1,

  8.        DatasetsOnDisk: 2,

  9.    },

  10.    NetworkId:     1,

  11.    LightPeers:    100,

  12.    DatabaseCache: 768,

  13.    TrieCache:     256,

  14.    TrieTimeout:   5 * time.Minute,

  15.    GasPrice:      big.NewInt(18 * params.Shannon),

  16.    TxPool: core.DefaultTxPoolConfig,

  17.    GPO: gasprice.Config{

  18.        Blocks:     20,

  19.        Percentile: 60,

  20.    },

  21. }

大家已经看到了,默认的值为downloader.FastSync,至此已经得知如果不传递同步类型参数,geth采用fast模式进行同步。

同步模式中途的变更

经过上面的代码分析我们是否就确定,如果不传递参数geth一直就是通过fast模式进行同步的么?那么,再看看下面的代码分析吧。

在eth/handler.go中方法NewProtocolManager中有这样一段代码:

  1. // Figure out whether to allow fast sync or not

  2.    if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 {

  3.        log.Warn("Blockchain not empty, fast sync disabled")

  4.        mode = downloader.FullSync

  5.    }

  6.    if mode == downloader.FastSync {

  7.        manager.fastSync = uint32(1)

  8.    }

这段代码是在创建ProtocolManager时进行同步模式的参数设置。blockchain.CurrentBlock()获得当前的区块信息,而NumberU64()的实现如下:

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

也就是说NumberU64()返回的是最新区块的头部的number。

现在整理一下这段代码的整体逻辑就是,当同步模式为fast并最新区块的高度大于0(已经同步过一部分数据)时,程序自动将同步模式转变为full,并打印警告信息。到这里,整个区块的同步模式已经豁然开朗了。

归纳总结

通过上面代付的分析,我们得到以下结论:

  • geth支持三种同步模式:fast,full,light。

  • 启动geth节点时,如果不传递同步模式参数,默认使用fast模式进行同步。

  • 如果不传递同步模式参数或传递fast,第一次启动默认使用fast并且当前区块为0,因此采用fast进行同步。

  • 如果不传递同步模式或传递fast,第二次启动默认使用fast,但当前区块的header编号不为0,程序自动将fast模式转变为full模式。

  • 因此,同步区块数据量的多少与使用full时的时机有一定关系。