公链启动过程安全分析
Al1ex 发表于 四川 区块链安全 775浏览 · 2024-02-03 03:06

文章前言

公链作为区块链技术的核心应用之一,具有分布式、去中心化和安全性高的特点,被广泛应用于数字货币、智能合约等领域,然而公链的启动过程涉及到众多复杂的安全考量和步骤,这是确保公链顺利启动并保护用户资产安全的关键环节,本文将深入探讨公链启动过程中的安全分析

基本介绍

公链启动运行过程是指公链从初始状态到正式运行的过程,包括节点的启动、网络的初始化、区块的生成和验证等步骤

  • 节点启动和连接:公链的启动需要一组节点来参与网络,每个节点都是公链网络的一部分,负责存储区块链的数据、执行共识算法并与其他节点进行通信,在启动过程中节点需要进行身份验证并通过网络连接到其他节点
  • 初始区块和创世区块:公链启动时需要创建初始区块和创世区块,初始区块是公链网络中的第一个区块,它包含了公链的初始状态和配置信息,创世区块是初始区块的特殊区块,包含了公链的初始交易记录和网络参数,初始区块和创世区块的生成标志着公链的正式启动
  • 区块生成和共识算法:公链启动后节点将开始生成新的区块并将其添加到区块链中,区块生成是通过共识算法进行的,不同的公链可能采用不同的共识算法,例如:工作量证明(Proof of Work)或权益证明(Proof of Stake),共识算法确保节点能够就区块的有效性达成一致并防止恶意行为和双花攻击
  • 区块验证和同步:在公链运行过程中每个节点都会验证新生成的区块的有效性并将其添加到自己的本地副本中,节点还需要与其他节点进行区块的同步以保持整个公链网络的一致性,节点之间通过网络协议进行通信和交换区块数据,确保所有节点都具有相同的区块链状态
  • 交易处理和智能合约执行:公链的运行过程中涉及到交易的处理和智能合约的执行,当用户发起交易时节点会验证交易的有效性并将其打包进新的区块中,智能合约是公链上的可编程代码,节点会执行智能合约的逻辑并将结果记录在区块链上,交易处理和智能合约执行是保证公链功能正常运行的重要环节
  • 安全监控和维护:在公链启动运行过程中安全监控和维护是至关重要的,节点需要实时监测网络的状况,检测可能的攻击或异常行为并及时采取相应的安全措施,节点还需要进行定期的维护工作,例如:数据库备份、软件更新和漏洞修复,以确保公链的稳定性和安全性

源码分析

在这里我们主要以当下应用面最广的以太坊为例进行分析:

启动入口

在以太坊源码中公链的启动入口为位于go-ethereum-1.10.2\cmd\geth\main.go文件中的geth函数,该函数首先检查是否存在命令行参数,接着调用prepare函数加载配置,随后调用makeFullNode函数创建一个基于命令行参数的全节点,这个函数返回一个节点栈(stack)和一个后端(backend),在创建完节点之后,使用defer语句注册一个函数调用stack.Close()以确保在函数结束时关闭节点,调用startNode函数,启动节点并以阻塞模式运行,等待节点被关闭,最后调用stack.Wait()等待节点关闭完成,函数返回nil,表示执行成功

// geth is the main entry point into the system if no special subcommand is ran.
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
func geth(ctx *cli.Context) error {
    if args := ctx.Args(); len(args) > 0 {
        return fmt.Errorf("invalid command: %q", args[0])
    }

    prepare(ctx)
    stack, backend := makeFullNode(ctx)
    defer stack.Close()

    startNode(ctx, stack, backend)
    stack.Wait()
    return nil
}

准备工作

下面我们一起看以下上面的prepare函数究竟做了什么预处理操作,首先可以看到的是这里会根据传入的参数来匹配一些已知的全局启动参数并打印其log,随后根据轻节点和全节点来设置分配给内部缓存的大小,然后进行度量设置,这里需要说明的是这里会根据参数不同而启动不同的网络,目前以太坊主要有一下几种网络的支持:

  • 主网(Mainnet):主网是以太坊的主要和最重要的网络模式,它是一个公共的、去中心化的区块链网络,被广泛应用于各种以太坊应用和智能合约的部署,主网上的交易和合约执行是真实的并且需要经过矿工的验证和确认,主网上的以太币(ETH)是真实的加密货币,可以用于支付交易费用和价值转移
  • 测试网络(Testnet):测试网络是为了开发者测试和调试智能合约、以太坊应用和新功能而创建的网络,它模拟了主网的环境,但不同的是使用的虚拟代币,无需实际支付以太币,测试网络的目的是提供一个相对安全的环境,供开发者进行实验和测试以确保他们的应用和合约在主网上能够正常运行
  • Ropsten测试网络:Ropsten是以太坊最早和最常用的测试网络之一,使用Proof of Work(PoW)共识算法,开发者可以在Ropsten上进行各种测试和演示
  • Rinkeby测试网络:Rinkeby是另一个常用的以太坊测试网络,也是使用Proof of Authority(PoA)共识算法,PoA采用了许可制度,由特定的验证节点进行验证和确认交易
  • Goerli测试网络:Goerli是另一个常用的以太坊测试网络,同样采用了Proof of Authority(PoA)共识算法,Goerli网络的目标是提供一个稳定和可靠的测试环境
// prepare manipulates memory cache allowance and setups metric system.
// This function should be called before launching devp2p stack.
func prepare(ctx *cli.Context) {
    // If we're running a known preset, log it for convenience.
    switch {
    case ctx.GlobalIsSet(utils.RopstenFlag.Name):
        log.Info("Starting Geth on Ropsten testnet...")

    case ctx.GlobalIsSet(utils.RinkebyFlag.Name):
        log.Info("Starting Geth on Rinkeby testnet...")

    case ctx.GlobalIsSet(utils.GoerliFlag.Name):
        log.Info("Starting Geth on Görli testnet...")

    case ctx.GlobalIsSet(utils.YoloV3Flag.Name):
        log.Info("Starting Geth on YOLOv3 testnet...")

    case ctx.GlobalIsSet(utils.DeveloperFlag.Name):
        log.Info("Starting Geth in ephemeral dev mode...")

    case !ctx.GlobalIsSet(utils.NetworkIdFlag.Name):
        log.Info("Starting Geth on Ethereum mainnet...")
    }
    // If we're a full node on mainnet without --cache specified, bump default cache allowance
    if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {
        // Make sure we're not on any supported preconfigured testnet either
        if !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) {
            // Nope, we're really on mainnet. Bump that cache up!
            log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096)
            ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096))
        }
    }
    // If we're running a light client on any network, drop the cache to some meaningfully low amount
    if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) {
        log.Info("Dropping default light client cache", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 128)
        ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128))
    }

    // Start metrics export if enabled
    utils.SetupMetrics(ctx)

    // Start system runtime metrics collection
    go metrics.CollectProcessMetrics(3 * time.Second)
}

这里的CollectProcessMetrics则主要用于定期收集有关运行过程的各种指标,通过读取CPU、内存和磁盘的统计数据并计算差值,更新相应的指标值

// CollectProcessMetrics periodically collects various metrics about the running
// process.
func CollectProcessMetrics(refresh time.Duration) {
    // Short circuit if the metrics system is disabled
    if !Enabled {
        return
    }
    refreshFreq := int64(refresh / time.Second)

    // Create the various data collectors
    cpuStats := make([]*CPUStats, 2)
    memstats := make([]*runtime.MemStats, 2)
    diskstats := make([]*DiskStats, 2)
    for i := 0; i < len(memstats); i++ {
        cpuStats[i] = new(CPUStats)
        memstats[i] = new(runtime.MemStats)
        diskstats[i] = new(DiskStats)
    }
    // Define the various metrics to collect
    var (
        cpuSysLoad    = GetOrRegisterGauge("system/cpu/sysload", DefaultRegistry)
        cpuSysWait    = GetOrRegisterGauge("system/cpu/syswait", DefaultRegistry)
        cpuProcLoad   = GetOrRegisterGauge("system/cpu/procload", DefaultRegistry)
        cpuThreads    = GetOrRegisterGauge("system/cpu/threads", DefaultRegistry)
        cpuGoroutines = GetOrRegisterGauge("system/cpu/goroutines", DefaultRegistry)

        memPauses = GetOrRegisterMeter("system/memory/pauses", DefaultRegistry)
        memAllocs = GetOrRegisterMeter("system/memory/allocs", DefaultRegistry)
        memFrees  = GetOrRegisterMeter("system/memory/frees", DefaultRegistry)
        memHeld   = GetOrRegisterGauge("system/memory/held", DefaultRegistry)
        memUsed   = GetOrRegisterGauge("system/memory/used", DefaultRegistry)

        diskReads             = GetOrRegisterMeter("system/disk/readcount", DefaultRegistry)
        diskReadBytes         = GetOrRegisterMeter("system/disk/readdata", DefaultRegistry)
        diskReadBytesCounter  = GetOrRegisterCounter("system/disk/readbytes", DefaultRegistry)
        diskWrites            = GetOrRegisterMeter("system/disk/writecount", DefaultRegistry)
        diskWriteBytes        = GetOrRegisterMeter("system/disk/writedata", DefaultRegistry)
        diskWriteBytesCounter = GetOrRegisterCounter("system/disk/writebytes", DefaultRegistry)
    )
    // Iterate loading the different stats and updating the meters
    for i := 1; ; i++ {
        location1 := i % 2
        location2 := (i - 1) % 2

        ReadCPUStats(cpuStats[location1])
        cpuSysLoad.Update((cpuStats[location1].GlobalTime - cpuStats[location2].GlobalTime) / refreshFreq)
        cpuSysWait.Update((cpuStats[location1].GlobalWait - cpuStats[location2].GlobalWait) / refreshFreq)
        cpuProcLoad.Update((cpuStats[location1].LocalTime - cpuStats[location2].LocalTime) / refreshFreq)
        cpuThreads.Update(int64(threadCreateProfile.Count()))
        cpuGoroutines.Update(int64(runtime.NumGoroutine()))

        runtime.ReadMemStats(memstats[location1])
        memPauses.Mark(int64(memstats[location1].PauseTotalNs - memstats[location2].PauseTotalNs))
        memAllocs.Mark(int64(memstats[location1].Mallocs - memstats[location2].Mallocs))
        memFrees.Mark(int64(memstats[location1].Frees - memstats[location2].Frees))
        memHeld.Update(int64(memstats[location1].HeapSys - memstats[location1].HeapReleased))
        memUsed.Update(int64(memstats[location1].Alloc))

        if ReadDiskStats(diskstats[location1]) == nil {
            diskReads.Mark(diskstats[location1].ReadCount - diskstats[location2].ReadCount)
            diskReadBytes.Mark(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes)
            diskWrites.Mark(diskstats[location1].WriteCount - diskstats[location2].WriteCount)
            diskWriteBytes.Mark(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes)

            diskReadBytesCounter.Inc(diskstats[location1].ReadBytes - diskstats[location2].ReadBytes)
            diskWriteBytesCounter.Inc(diskstats[location1].WriteBytes - diskstats[location2].WriteBytes)
        }
        time.Sleep(refresh)
    }
}

配置加载

紧接着会调用makeFullNode函数,它用于加载Geth(Go Ethereum)配置并创建以太坊后端,函数接受一个cli.Context参数,用于获取命令行上下文,函数的第一行代码调用了makeConfigNode函数,该函数返回一个stack和cfg变量,stack是一个节点堆栈,cfg是Geth的配置,接下来代码使用ctx.GlobalIsSet函数检查命令行上下文中是否设置了utils.OverrideBerlinFlag标志,如果设置了该标志,代码会将其值作为cfg.Eth.OverrideBerlin的新值(big.Int类型)用于覆盖配置中的伯林硬分叉设置,然后代码调用utils.RegisterEthService函数将stack和cfg.Eth作为参数传递给该函数以注册以太坊服务并返回一个后端,如果命令行上下文中设置了utils.GraphQLEnabledFlag标志,代码会进入if语句块,调用utils.RegisterGraphQLService函数,该函数接受stack、后端和cfg.Node作为参数,用于注册GraphQL服务,接下来代码检查cfg.Ethstats.URL是否为空字符串,如果不为空则说明配置中设置了以太坊统计信息的URL,代码调用utils.RegisterEthStatsService函数,将stack、后端和URL作为参数传递给该函数,用于注册以太坊统计信息服务,最后函数返回stack和后端作为结果

// makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
    stack, cfg := makeConfigNode(ctx)
    if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) {
        cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name))
    }
    backend := utils.RegisterEthService(stack, &cfg.Eth)

    // Configure GraphQL if requested
    if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
        utils.RegisterGraphQLService(stack, backend, cfg.Node)
    }
    // Add the Ethereum Stats daemon if requested.
    if cfg.Ethstats.URL != "" {
        utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
    }
    return stack, backend
}

makeConfigNode具体实现如下所示:

// makeConfigNode loads geth configuration and creates a blank node instance.
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
    // Load defaults.
    cfg := gethConfig{
        Eth:     ethconfig.Defaults,
        Node:    defaultNodeConfig(),
        Metrics: metrics.DefaultConfig,
    }

    // Load config file.
    if file := ctx.GlobalString(configFileFlag.Name); file != "" {
        if err := loadConfig(file, &cfg); err != nil {
            utils.Fatalf("%v", err)
        }
    }

    // Apply flags.
    utils.SetNodeConfig(ctx, &cfg.Node)
    stack, err := node.New(&cfg.Node)
    if err != nil {
        utils.Fatalf("Failed to create the protocol stack: %v", err)
    }
    utils.SetEthConfig(ctx, stack, &cfg.Eth)
    if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) {
        cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
    }
    applyMetricConfig(ctx, &cfg)

    return stack, cfg
}

从上面的代码中我们可以看到这里会加载默认设置,主要有以下几个:

  • ethconfig.Defaults:以太坊主网上使用的默认设置(缓存配置、数据库配置、网络ID配置、交易查询限制、Gas设置等)
// Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{
    SyncMode: downloader.FastSync,
    Ethash: ethash.Config{
        CacheDir:         "ethash",
        CachesInMem:      2,
        CachesOnDisk:     3,
        CachesLockMmap:   false,
        DatasetsInMem:    1,
        DatasetsOnDisk:   2,
        DatasetsLockMmap: false,
    },
    NetworkId:               1,
    TxLookupLimit:           2350000,
    LightPeers:              100,
    UltraLightFraction:      75,
    DatabaseCache:           512,
    TrieCleanCache:          154,
    TrieCleanCacheJournal:   "triecache",
    TrieCleanCacheRejournal: 60 * time.Minute,
    TrieDirtyCache:          256,
    TrieTimeout:             60 * time.Minute,
    SnapshotCache:           102,
    Miner: miner.Config{
        GasFloor: 8000000,
        GasCeil:  8000000,
        GasPrice: big.NewInt(params.GWei),
        Recommit: 3 * time.Second,
    },
    TxPool:      core.DefaultTxPoolConfig,
    RPCGasCap:   25000000,
    GPO:         FullNodeGPO,
    RPCTxFeeCap: 1, // 1 ether
}
  • defaultNodeConfig:默认节点配置
// fileDir:go-ethereum-1.10.2\cmd\geth\config.go  L99
func defaultNodeConfig() node.Config {
    cfg := node.DefaultConfig
    cfg.Name = clientIdentifier
    cfg.Version = params.VersionWithCommit(gitCommit, gitDate)
    cfg.HTTPModules = append(cfg.HTTPModules, "eth")
    cfg.WSModules = append(cfg.WSModules, "eth")
    cfg.IPCPath = "geth.ipc"
    return cfg
}

// filedir:go-ethereum-1.10.2\node\defaults.go  L39
// DefaultConfig contains reasonable default settings.
var DefaultConfig = Config{
    DataDir:             DefaultDataDir(),
    HTTPPort:            DefaultHTTPPort,
    HTTPModules:         []string{"net", "web3"},
    HTTPVirtualHosts:    []string{"localhost"},
    HTTPTimeouts:        rpc.DefaultHTTPTimeouts,
    WSPort:              DefaultWSPort,
    WSModules:           []string{"net", "web3"},
    GraphQLVirtualHosts: []string{"localhost"},
    P2P: p2p.Config{
        ListenAddr: ":30303",
        MaxPeers:   50,
        NAT:        nat.Any(),
    },
}
  • metrics.DefaultConfig:度量的默认配置
// filedir:go-ethereum-1.10.2\metrics\config.go
// DefaultConfig is the default config for metrics used in go-ethereum.
var DefaultConfig = Config{
    Enabled:          false,
    EnabledExpensive: false,
    HTTP:             "127.0.0.1",
    Port:             6060,
    EnableInfluxDB:   false,
    InfluxDBEndpoint: "http://localhost:8086",
    InfluxDBDatabase: "geth",
    InfluxDBUsername: "test",
    InfluxDBPassword: "test",
    InfluxDBTags:     "host=localhost",
}

之后加载用户自定义的配置:

// filedir:go-ethereum-1.10.2\cmd\geth\config.go   L118
// Load config file.
    if file := ctx.GlobalString(configFileFlag.Name); file != "" {
        if err := loadConfig(file, &cfg); err != nil {
            utils.Fatalf("%v", err)
        }
    }

之后调用SetNodeConfig应用对应的配置参数:

// filedir:go-ethereum-1.10.2\cmd\geth\config.go  L126
// Apply flags.
    utils.SetNodeConfig(ctx, &cfg.Node)
    stack, err := node.New(&cfg.Node)
    if err != nil {
        utils.Fatalf("Failed to create the protocol stack: %v", err)
    }
    utils.SetEthConfig(ctx, stack, &cfg.Eth)
    if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) {
        cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
    }
    applyMetricConfig(ctx, &cfg)

    return stack, cfg

然后调用node.New方法来创建一个新的P2P节点,函数根据传入的配置参数创建节点对象并对节点的各个字段进行初始化,最后函数返回创建的节点对象和可能的错误作为结果

// filedir:go-ethereum-1.10.2\node\node.go
// New creates a new P2P node, ready for protocol registration.
func New(conf *Config) (*Node, error) {
    // Copy config and resolve the datadir so future changes to the current
    // working directory don't affect the node.
    confCopy := *conf
    conf = &confCopy
    if conf.DataDir != "" {
        absdatadir, err := filepath.Abs(conf.DataDir)
        if err != nil {
            return nil, err
        }
        conf.DataDir = absdatadir
    }
    if conf.Logger == nil {
        conf.Logger = log.New()
    }

    // Ensure that the instance name doesn't cause weird conflicts with
    // other files in the data directory.
    if strings.ContainsAny(conf.Name, `/\`) {
        return nil, errors.New(`Config.Name must not contain '/' or '\'`)
    }
    if conf.Name == datadirDefaultKeyStore {
        return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`)
    }
    if strings.HasSuffix(conf.Name, ".ipc") {
        return nil, errors.New(`Config.Name cannot end in ".ipc"`)
    }

    node := &Node{
        config:        conf,
        inprocHandler: rpc.NewServer(),
        eventmux:      new(event.TypeMux),
        log:           conf.Logger,
        stop:          make(chan struct{}),
        server:        &p2p.Server{Config: conf.P2P},
        databases:     make(map[*closeTrackingDB]struct{}),
    }

    // Register built-in APIs.
    node.rpcAPIs = append(node.rpcAPIs, node.apis()...)

    // Acquire the instance directory lock.
    if err := node.openDataDir(); err != nil {
        return nil, err
    }
    // Ensure that the AccountManager method works before the node has started. We rely on
    // this in cmd/geth.
    am, ephemeralKeystore, err := makeAccountManager(conf)
    if err != nil {
        return nil, err
    }
    node.accman = am
    node.ephemKeystore = ephemeralKeystore

    // Initialize the p2p server. This creates the node key and discovery databases.
    node.server.Config.PrivateKey = node.config.NodeKey()
    node.server.Config.Name = node.config.NodeName()
    node.server.Config.Logger = node.log
    if node.server.Config.StaticNodes == nil {
        node.server.Config.StaticNodes = node.config.StaticNodes()
    }
    if node.server.Config.TrustedNodes == nil {
        node.server.Config.TrustedNodes = node.config.TrustedNodes()
    }
    if node.server.Config.NodeDatabase == "" {
        node.server.Config.NodeDatabase = node.config.NodeDB()
    }

    // Check HTTP/WS prefixes are valid.
    if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil {
        return nil, err
    }
    if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil {
        return nil, err
    }

    // Configure RPC servers.
    node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
    node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
    node.ipc = newIPCServer(node.log, conf.IPCEndpoint())

    return node, nil
}

然后调用SetEthConfig将eth相关命令行标志应用于配置

// filedir:go-ethereum-1.10.2\cmd\utils\flags.go
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
    // Avoid conflicting network flags
    CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag)
    CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light")
    CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
    if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 {
        ctx.GlobalSet(TxLookupLimitFlag.Name, "0")
        log.Warn("Disable transaction unindexing for archive node")
    }
    if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 {
        log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited")
    }
    var ks *keystore.KeyStore
    if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
        ks = keystores[0].(*keystore.KeyStore)
    }
    setEtherbase(ctx, ks, cfg)
    setGPO(ctx, &cfg.GPO, ctx.GlobalString(SyncModeFlag.Name) == "light")
    setTxPool(ctx, &cfg.TxPool)
    setEthash(ctx, cfg)
    setMiner(ctx, &cfg.Miner)
    setWhitelist(ctx, cfg)
    setLes(ctx, cfg)

    // Cap the cache allowance and tune the garbage collector
    mem, err := gopsutil.VirtualMemory()
    if err == nil {
        if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
            log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024)
            mem.Total = 2 * 1024 * 1024 * 1024
        }
        allowance := int(mem.Total / 1024 / 1024 / 3)
        if cache := ctx.GlobalInt(CacheFlag.Name); cache > allowance {
            log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
            ctx.GlobalSet(CacheFlag.Name, strconv.Itoa(allowance))
        }
    }
    // Ensure Go's GC ignores the database cache for trigger percentage
    cache := ctx.GlobalInt(CacheFlag.Name)
    gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))

    log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
    godebug.SetGCPercent(int(gogc))

    if ctx.GlobalIsSet(SyncModeFlag.Name) {
        cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
    }
    if ctx.GlobalIsSet(NetworkIdFlag.Name) {
        cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name)
    }
    if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) {
        cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
    }
    cfg.DatabaseHandles = MakeDatabaseHandles()
    if ctx.GlobalIsSet(AncientFlag.Name) {
        cfg.DatabaseFreezer = ctx.GlobalString(AncientFlag.Name)
    }

    if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
        Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
    }
    if ctx.GlobalIsSet(GCModeFlag.Name) {
        cfg.NoPruning = ctx.GlobalString(GCModeFlag.Name) == "archive"
    }
    if ctx.GlobalIsSet(CacheNoPrefetchFlag.Name) {
        cfg.NoPrefetch = ctx.GlobalBool(CacheNoPrefetchFlag.Name)
    }
    // Read the value from the flag no matter if it's set or not.
    cfg.Preimages = ctx.GlobalBool(CachePreimagesFlag.Name)
    if cfg.NoPruning && !cfg.Preimages {
        cfg.Preimages = true
        log.Info("Enabling recording of key preimages since archive mode is used")
    }
    if ctx.GlobalIsSet(TxLookupLimitFlag.Name) {
        cfg.TxLookupLimit = ctx.GlobalUint64(TxLookupLimitFlag.Name)
    }
    if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
        cfg.TrieCleanCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
    }
    if ctx.GlobalIsSet(CacheTrieJournalFlag.Name) {
        cfg.TrieCleanCacheJournal = ctx.GlobalString(CacheTrieJournalFlag.Name)
    }
    if ctx.GlobalIsSet(CacheTrieRejournalFlag.Name) {
        cfg.TrieCleanCacheRejournal = ctx.GlobalDuration(CacheTrieRejournalFlag.Name)
    }
    if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
        cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
    }
    if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) {
        cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
    }
    if !ctx.GlobalBool(SnapshotFlag.Name) {
        // If snap-sync is requested, this flag is also required
        if cfg.SyncMode == downloader.SnapSync {
            log.Info("Snap sync requested, enabling --snapshot")
        } else {
            cfg.TrieCleanCache += cfg.SnapshotCache
            cfg.SnapshotCache = 0 // Disabled
        }
    }
    if ctx.GlobalIsSet(DocRootFlag.Name) {
        cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
    }
    if ctx.GlobalIsSet(VMEnableDebugFlag.Name) {
        // TODO(fjl): force-enable this in --dev mode
        cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name)
    }

    if ctx.GlobalIsSet(EWASMInterpreterFlag.Name) {
        cfg.EWASMInterpreter = ctx.GlobalString(EWASMInterpreterFlag.Name)
    }

    if ctx.GlobalIsSet(EVMInterpreterFlag.Name) {
        cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name)
    }
    if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) {
        cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name)
    }
    if cfg.RPCGasCap != 0 {
        log.Info("Set global gas cap", "cap", cfg.RPCGasCap)
    } else {
        log.Info("Global gas cap disabled")
    }
    if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) {
        cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name)
    }
    if ctx.GlobalIsSet(NoDiscoverFlag.Name) {
        cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
    } else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) {
        urls := ctx.GlobalString(DNSDiscoveryFlag.Name)
        if urls == "" {
            cfg.EthDiscoveryURLs = []string{}
        } else {
            cfg.EthDiscoveryURLs = SplitAndTrim(urls)
        }
    }
    // Override any default configs for hard coded networks.
    switch {
    case ctx.GlobalBool(MainnetFlag.Name):
        if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
            cfg.NetworkId = 1
        }
        cfg.Genesis = core.DefaultGenesisBlock()
        SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
    case ctx.GlobalBool(RopstenFlag.Name):
        if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
            cfg.NetworkId = 3
        }
        cfg.Genesis = core.DefaultRopstenGenesisBlock()
        SetDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash)
    case ctx.GlobalBool(RinkebyFlag.Name):
        if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
            cfg.NetworkId = 4
        }
        cfg.Genesis = core.DefaultRinkebyGenesisBlock()
        SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash)
    case ctx.GlobalBool(GoerliFlag.Name):
        if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
            cfg.NetworkId = 5
        }
        cfg.Genesis = core.DefaultGoerliGenesisBlock()
        SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash)
    case ctx.GlobalBool(YoloV3Flag.Name):
        if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
            cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3x")).Uint64() // "yolov3x"
        }
        cfg.Genesis = core.DefaultYoloV3GenesisBlock()
    case ctx.GlobalBool(DeveloperFlag.Name):
        if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
            cfg.NetworkId = 1337
        }
        // Create new developer account or reuse existing one
        var (
            developer  accounts.Account
            passphrase string
            err        error
        )
        if list := MakePasswordList(ctx); len(list) > 0 {
            // Just take the first value. Although the function returns a possible multiple values and
            // some usages iterate through them as attempts, that doesn't make sense in this setting,
            // when we're definitely concerned with only one account.
            passphrase = list[0]
        }
        // setEtherbase has been called above, configuring the miner address from command line flags.
        if cfg.Miner.Etherbase != (common.Address{}) {
            developer = accounts.Account{Address: cfg.Miner.Etherbase}
        } else if accs := ks.Accounts(); len(accs) > 0 {
            developer = ks.Accounts()[0]
        } else {
            developer, err = ks.NewAccount(passphrase)
            if err != nil {
                Fatalf("Failed to create developer account: %v", err)
            }
        }
        if err := ks.Unlock(developer, passphrase); err != nil {
            Fatalf("Failed to unlock developer account: %v", err)
        }
        log.Info("Using developer account", "address", developer.Address)

        // Create a new developer genesis block or reuse existing one
        cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
        if ctx.GlobalIsSet(DataDirFlag.Name) {
            // Check if we have an already initialized chain and fall back to
            // that if so. Otherwise we need to generate a new genesis spec.
            chaindb := MakeChainDatabase(ctx, stack, true)
            if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
                cfg.Genesis = nil // fallback to db content
            }
            chaindb.Close()
        }
        if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) {
            cfg.Miner.GasPrice = big.NewInt(1)
        }
    default:
        if cfg.NetworkId == 1 {
            SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
        }
    }
}

最后调用applyMetricConfig进行全局设置

// filedir:go-ethereum-1.10.2\cmd\geth\config.go
func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) {
    if ctx.GlobalIsSet(utils.MetricsEnabledFlag.Name) {
        cfg.Metrics.Enabled = ctx.GlobalBool(utils.MetricsEnabledFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsEnabledExpensiveFlag.Name) {
        cfg.Metrics.EnabledExpensive = ctx.GlobalBool(utils.MetricsEnabledExpensiveFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsHTTPFlag.Name) {
        cfg.Metrics.HTTP = ctx.GlobalString(utils.MetricsHTTPFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsPortFlag.Name) {
        cfg.Metrics.Port = ctx.GlobalInt(utils.MetricsPortFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsEnableInfluxDBFlag.Name) {
        cfg.Metrics.EnableInfluxDB = ctx.GlobalBool(utils.MetricsEnableInfluxDBFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsInfluxDBEndpointFlag.Name) {
        cfg.Metrics.InfluxDBEndpoint = ctx.GlobalString(utils.MetricsInfluxDBEndpointFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsInfluxDBDatabaseFlag.Name) {
        cfg.Metrics.InfluxDBDatabase = ctx.GlobalString(utils.MetricsInfluxDBDatabaseFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsInfluxDBUsernameFlag.Name) {
        cfg.Metrics.InfluxDBUsername = ctx.GlobalString(utils.MetricsInfluxDBUsernameFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsInfluxDBPasswordFlag.Name) {
        cfg.Metrics.InfluxDBPassword = ctx.GlobalString(utils.MetricsInfluxDBPasswordFlag.Name)
    }
    if ctx.GlobalIsSet(utils.MetricsInfluxDBTagsFlag.Name) {
        cfg.Metrics.InfluxDBTags = ctx.GlobalString(utils.MetricsInfluxDBTagsFlag.Name)
    }

由于篇幅限制,后续内容在下篇进行分析介绍

0 条评论
某人
表情
可输入 255