以太坊启动过程分析

admin 2023年5月4日20:59:26评论23 views字数 31161阅读103分52秒阅读模式

源码下载

以太坊官方提供了Go、C++、Python各个版本的实现,具体可以在官方GitHub(https://github.com/ethereum)中进行查找:

以太坊启动过程分析

本系列源码分析文章主要基于当前最新版本v 1.10.2版本进行分析:

https://github.com/ethereum/go-ethereum/releases

以太坊启动过程分析

目录结构

以太坊源码目录结构如下:

├─accounts                  账号相关├─build                     编译生成的程序├─cmd                       geth程序主体├─common                    工具函数库├─consensus                 共识算法├─console                   交互式命令├─contracts                 合约相关            ├─core                      以太坊核心部分├─crypto                    加密函数库   ├─docs                      说明文档├─eth                       以太坊协议├─ethclient                 以太坊RPC客户端├─ethdb                     底层存储├─ethstats                  统计报告├─event                     事件处理├─graphql                   ├─internal                  RPC调用├─les                       轻量级子协议├─light                     轻客户端部分功能├─log                       日志模块├─metrics                   服务监控相关├─miner                     挖矿相关├─mobile                    Geth的移动端API├─node                      接口节点├─p2p                       p2p网络协议 ├─params                    一些预设参数值├─rlp                       RLP系列化格式├─rpc                       RPC接口├─signer                    签名相关├─swarm                     分布式存储├─tests                     以太坊JSON测试└─trie                      Merkle Patricia实现

启动分析

以太坊启动的入口位于go-ethereum-1.10.2cmdgethmain.go文件中的geth函数:

// 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}

在这里主要做了一下几件事情:

1、准备操作内存缓存余量和度量设置

2、加载配置

3、启动节点

4、启动守护进程

准备工作

在这里我们一步一步来看,在prepare函数中首先会根据传入的参数来匹配一些已知的全局启动参数并打印其log,之后根据轻节点和全节点来设置分配给内部缓存的大小,然后进行度量设置:

文件位置:go-ethereum-1.10.2cmdgethmain.go L263

// 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定期收集有关运行过程的各种度量

文件位置:go-ethereum-1.10.2metricsmetrics.go L57

// 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加载配置文件:

文件位置:go-ethereum-1.10.2cmdgethconfig.go  L141

// 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来加载配置文件:

文件位置:go-ethereum-1.10.2cmdgethconfig.go L109

// 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.2cmdgethconfig.go  L99func 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.2nodedefaults.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.2metricsconfig.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.2cmdgethconfig.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.2cmdgethconfig.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.2nodenode.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.2cmdutilsflags.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.2cmdgethconfig.gofunc 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)  }
启动节点

之后调用startNode启动节点:

// filedir: go-ethereum-1.10.2cmdgethmain.go  L328// startNode boots up the system node and all registered protocols, after which// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the// miner.func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) {  debug.Memsize.Add("node", stack)
// Start up the node itself utils.StartNode(ctx, stack)
// Unlock any account specifically requested unlockAccounts(ctx, stack)
// Register wallet event handlers to open and auto-derive wallets events := make(chan accounts.WalletEvent, 16) stack.AccountManager().Subscribe(events)
// Create a client to interact with local geth node. rpcClient, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to self: %v", err) } ethClient := ethclient.NewClient(rpcClient)
go func() { // Open any wallets already attached for _, wallet := range stack.AccountManager().Wallets() { if err := wallet.Open(""); err != nil { log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) } } // Listen for wallet event till termination for event := range events { switch event.Kind { case accounts.WalletArrived: if err := event.Wallet.Open(""); err != nil { log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) } case accounts.WalletOpened: status, _ := event.Wallet.Status() log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
var derivationPaths []accounts.DerivationPath if event.Wallet.URL().Scheme == "ledger" { derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) } derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
event.Wallet.SelfDerive(derivationPaths, ethClient)
case accounts.WalletDropped: log.Info("Old wallet dropped", "url", event.Wallet.URL()) event.Wallet.Close() } } }()
// Spawn a standalone goroutine for status synchronization monitoring, // close the node when synchronization is complete if user required. if ctx.GlobalBool(utils.ExitWhenSyncedFlag.Name) { go func() { sub := stack.EventMux().Subscribe(downloader.DoneEvent{}) defer sub.Unsubscribe() for { event := <-sub.Chan() if event == nil { continue } done, ok := event.Data.(downloader.DoneEvent) if !ok { continue } if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), "age", common.PrettyAge(timestamp)) stack.Close() } } }() }
// Start auxiliary services if enabled if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { // Mining only makes sense if a full Ethereum node is running if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } ethBackend, ok := backend.(*eth.EthAPIBackend) if !ok { utils.Fatalf("Ethereum service not running: %v", err) } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) ethBackend.TxPool().SetGasPrice(gasprice) // start mining threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) if err := ethBackend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } }}

startNode的具体实现如下:

// filedir:go-ethereum-1.10.2cmdutilscmd.gofunc StartNode(ctx *cli.Context, stack *node.Node) {  if err := stack.Start(); err != nil {    Fatalf("Error starting protocol stack: %v", err)  }  go func() {    sigc := make(chan os.Signal, 1)    signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)    defer signal.Stop(sigc)
minFreeDiskSpace := ethconfig.Defaults.TrieDirtyCache if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) { minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name) } else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { minFreeDiskSpace = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 } if minFreeDiskSpace > 0 { go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024) }
<-sigc log.Info("Got interrupt, shutting down...") go stack.Close() for i := 10; i > 0; i-- { <-sigc if i > 1 { log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) } } debug.Exit() // ensure trace and CPU profile data is flushed. debug.LoudPanic("boom") }()}

在这里会调用node的start方法启动所有注册的生命周期、RPC服务和P2P网络:

// filedir:go-ethereum-1.10.2nodenode.go// Start starts all registered lifecycles, RPC services and p2p networking.// Node can only be started once.func (n *Node) Start() error {  n.startStopLock.Lock()  defer n.startStopLock.Unlock()
n.lock.Lock() switch n.state { case runningState: n.lock.Unlock() return ErrNodeRunning case closedState: n.lock.Unlock() return ErrNodeStopped } n.state = runningState // open networking and RPC endpoints err := n.openEndpoints() lifecycles := make([]Lifecycle, len(n.lifecycles)) copy(lifecycles, n.lifecycles) n.lock.Unlock()
// Check if endpoint startup failed. if err != nil { n.doClose(nil) return err } // Start all registered lifecycles. var started []Lifecycle for _, lifecycle := range lifecycles { if err = lifecycle.Start(); err != nil { break } started = append(started, lifecycle) } // Check if any lifecycle failed to start. if err != nil { n.stopServices(started) n.doClose(nil) } return err}

之后调用unlockAccounts来解锁账户:

// filedir:go-ethereum-1.10.2cmdgethmain.go   L426// unlockAccounts unlocks any account specifically requested.func unlockAccounts(ctx *cli.Context, stack *node.Node) {  var unlocks []string  inputs := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")  for _, input := range inputs {    if trimmed := strings.TrimSpace(input); trimmed != "" {      unlocks = append(unlocks, trimmed)    }  }  // Short circuit if there is no account to unlock.  if len(unlocks) == 0 {    return  }  // If insecure account unlocking is not allowed if node's APIs are exposed to external.  // Print warning log to user and skip unlocking.  if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() {    utils.Fatalf("Account unlock with HTTP access is forbidden!")  }  ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)  passwords := utils.MakePasswordList(ctx)  for i, account := range unlocks {    unlockAccount(ks, account, i, passwords)  }}

之后注册钱包事件:

  // Register wallet event handlers to open and auto-derive wallets  events := make(chan accounts.WalletEvent, 16)  stack.AccountManager().Subscribe(events)

之后监听钱包事件:

     // Listen for wallet event till termination    for event := range events {      switch event.Kind {      case accounts.WalletArrived:        if err := event.Wallet.Open(""); err != nil {          log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)        }      case accounts.WalletOpened:        status, _ := event.Wallet.Status()        log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
var derivationPaths []accounts.DerivationPath if event.Wallet.URL().Scheme == "ledger" { derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) } derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
event.Wallet.SelfDerive(derivationPaths, ethClient)
case accounts.WalletDropped: log.Info("Old wallet dropped", "url", event.Wallet.URL()) event.Wallet.Close() } } }

生成用于状态同步监视的独立goroutine

  // Spawn a standalone goroutine for status synchronization monitoring,  // close the node when synchronization is complete if user required.  if ctx.GlobalBool(utils.ExitWhenSyncedFlag.Name) {    go func() {      sub := stack.EventMux().Subscribe(downloader.DoneEvent{})      defer sub.Unsubscribe()      for {        event := <-sub.Chan()        if event == nil {          continue        }        done, ok := event.Data.(downloader.DoneEvent)        if !ok {          continue        }        if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {          log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),            "age", common.PrettyAge(timestamp))          stack.Close()        }      }    }()  }

之后启动辅助服务(例如:挖矿):

  // Start auxiliary services if enabled  if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {    // Mining only makes sense if a full Ethereum node is running    if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {      utils.Fatalf("Light clients do not support mining")    }    ethBackend, ok := backend.(*eth.EthAPIBackend)    if !ok {      utils.Fatalf("Ethereum service not running: %v", err)    }    // Set the gas price to the limits from the CLI and start mining    gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)    ethBackend.TxPool().SetGasPrice(gasprice)    // start mining    threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name)    if err := ethBackend.StartMining(threads); err != nil {      utils.Fatalf("Failed to start mining: %v", err)    }  }

挖矿函数的具体逻辑代码如下,这里首先会检查矿工是否运行、配置本地挖矿奖励地址、最后执行挖矿:

// filedir:go-ethereum-1.10.2ethbackend.go// StartMining starts the miner with the given number of CPU threads. If mining// is already running, this method adjust the number of threads allowed to use// and updates the minimum price required by the transaction pool.func (s *Ethereum) StartMining(threads int) error {  // Update the thread count within the consensus engine  type threaded interface {    SetThreads(threads int)  }  if th, ok := s.engine.(threaded); ok {    log.Info("Updated mining threads", "threads", threads)    if threads == 0 {      threads = -1 // Disable the miner from within    }    th.SetThreads(threads)  }  // If the miner was not running, initialize it  if !s.IsMining() {    // Propagate the initial price point to the transaction pool    s.lock.RLock()    price := s.gasPrice    s.lock.RUnlock()    s.txPool.SetGasPrice(price)
// Configure the local mining address eb, err := s.Etherbase() if err != nil { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } if clique, ok := s.engine.(*clique.Clique); ok { wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) return fmt.Errorf("signer missing: %v", err) } clique.Authorize(eb, wallet.SignData) } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. atomic.StoreUint32(&s.handler.acceptTxs, 1)
go s.miner.Start(eb) } return nil}

之后等待节点关闭:

// Wait blocks until the node is closed.func (n *Node) Wait() {  <-n.stop}

流程图示

根据上面的启动流程分析,我们可以简单的绘制一个启动流程图(这里主要以一些关键操作为主,忽略了一些if...else..条件判断语句,较为简化)

以太坊启动过程分析

启动公链

以太坊官方提供了编译打包好的二进制文件,我们可以直接下载对应的二进制文件来启动公链(你有可以使用docker来搭建):

以太坊启动过程分析

https://geth.ethereum.org/downloads/

以太坊启动过程分析

下面我们使用Geth来启动一个节点:

./geth --ropsten --rpc --rpcaddr 192.168.174.212 --rpcport 8989

以太坊启动过程分析

这里我们仅以启动为演示,至于挖矿参数和其他参数我们后面再做介绍~

原文始发于微信公众号(七芒星实验室):以太坊启动过程分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月4日20:59:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   以太坊启动过程分析https://cn-sec.com/archives/1706494.html

发表评论

匿名网友 填写信息