geth启动流程

当我们要启动geth网络,我们需要运行命令: geth --datadir=~/gethData --networkid 1 console。这期间,操作分为2步:

1. 启动geth运行程序,初始化运行环境

2. 运行命令

一、初始化运行环境

  //cmd/geth/main.go

func init() {
   // 初始化CLI(命令行界面),并启动geth
   app.Action = geth
   app.HideVersion = true // we have a command to print the version
   app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"
   app.Commands = []cli.Command{
      // See chaincmd.go:
      initCommand,
      importCommand,
      exportCommand,
      importPreimagesCommand,
      exportPreimagesCommand,
      copydbCommand,
      removedbCommand,
      dumpCommand,
      // See monitorcmd.go:
      monitorCommand,
      // See accountcmd.go:
      accountCommand,               //账户命令处理hansh,例如创建账户
      walletCommand,
      // See consolecmd.go:
      consoleCommand,               //console命令处理对象,即启动一个geth网络时的命令处理类
      attachCommand,
      javascriptCommand,
      // See misccmd.go:
      makecacheCommand,
      makedagCommand,
      versionCommand,
      bugCommand,
      licenseCommand,
      // See config.go
      dumpConfigCommand,
   }
   sort.Sort(cli.CommandsByName(app.Commands))

   app.Flags = append(app.Flags, nodeFlags...)
   app.Flags = append(app.Flags, rpcFlags...)
   app.Flags = append(app.Flags, consoleFlags...)
   app.Flags = append(app.Flags, debug.Flags...)
   app.Flags = append(app.Flags, whisperFlags...)

   //很重要,启动时需要优先执行的函数
   app.Before = func(ctx *cli.Context) error {
      runtime.GOMAXPROCS(runtime.NumCPU())
      if err := debug.Setup(ctx); err != nil {
         return err
      }
      // Cap the cache allowance and tune the garbage colelctor
      var mem gosigar.Mem
      if err := mem.Get(); err == nil {
         allowance := int(mem.Total / 1024 / 1024 / 3)
         if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance {
            log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
            ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance))
         }
      }
      // Ensure Go's GC ignores the database cache for trigger percentage
      cache := ctx.GlobalInt(utils.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))

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

      utils.SetupNetwork(ctx)
      return nil
   }

   app.After = func(ctx *cli.Context) error {
      debug.Exit()
      console.Stdin.Close() // Resets terminal mode.
      return nil
   }
}
---------------------------------------------------------------------
//程序入口main
func main() {
   if err := app.Run(os.Args); err != nil {
      fmt.Fprintln(os.Stderr, err)
      os.Exit(1)
   }
}
---------------------------------------------------------------------
// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
   a.Setup()      //仅做简单的配置,例如auth,email,command,输出流等

   shellComplete, arguments := checkShellCompleteFlag(a, arguments)

   // parse flags
   set, err := flagSet(a.Name, a.Flags)
   if err != nil {
      return err
   }

   set.SetOutput(ioutil.Discard)
   err = set.Parse(arguments[1:])
   nerr := normalizeFlags(a.Flags, set)
   context := NewContext(a, set, nil)
   if nerr != nil {
      fmt.Fprintln(a.Writer, nerr)
      ShowAppHelp(context)
      return nerr
   }
   context.shellComplete = shellComplete

   if checkCompletions(context) {
      return nil
   }

   if err != nil {
      if a.OnUsageError != nil {
         err := a.OnUsageError(context, err, false)
         HandleExitCoder(err)
         return err
      }
      fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
      ShowAppHelp(context)
      return err
   }

   if !a.HideHelp && checkHelp(context) {
      ShowAppHelp(context)
      return nil
   }

   if !a.HideVersion && checkVersion(context) {
      ShowVersion(context)
      return nil
   }

   if a.After != nil {          //退出时,执行init函数指定的after函数
      defer func() {
         if afterErr := a.After(context); afterErr != nil {
            if err != nil {
               err = NewMultiError(err, afterErr)
            } else {
               err = afterErr
            }
         }
      }()
   }

   if a.Before != nil {         //执行init函数指定的before函数
      beforeErr := a.Before(context)
      if beforeErr != nil {
         ShowAppHelp(context)
         HandleExitCoder(beforeErr)
         err = beforeErr
         return err
      }
   }

   args := context.Args()
   if args.Present() {
      name := args.First()
      c := a.Command(name)
      if c != nil {
         return c.Run(context)      //由于我们输入了console命令,所以在这里开始执行
      }
   }

   if a.Action == nil {
      a.Action = helpCommand.Action      //如果默认命令为空,那么默认执行help命令
   }

   // Run default Action
   err = HandleAction(a.Action, context)  //执行默认命令

   HandleExitCoder(err)
   return err
}
二、执行 geth --datadir=~/gethData --networkid 1 console
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
   if len(c.Subcommands) > 0 {
      return c.startApp(ctx)
   }

   if !c.HideHelp && (HelpFlag != BoolFlag{}) {
      // append help to flags
      c.Flags = append(
         c.Flags,
         HelpFlag,
      )
   }

   set, err := flagSet(c.Name, c.Flags)             //设置各种flag
   if err != nil {
      return err
   }
   set.SetOutput(ioutil.Discard)

   //解析命令行
   if c.SkipFlagParsing {
      err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
   } else if !c.SkipArgReorder {
      firstFlagIndex := -1
      terminatorIndex := -1
      for index, arg := range ctx.Args() {
         if arg == "--" {
            terminatorIndex = index
            break
         } else if arg == "-" {
            // Do nothing. A dash alone is not really a flag.
            continue
         } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
            firstFlagIndex = index
         }
      }

      if firstFlagIndex > -1 {
         args := ctx.Args()
         regularArgs := make([]string, len(args[1:firstFlagIndex]))
         copy(regularArgs, args[1:firstFlagIndex])

         var flagArgs []string
         if terminatorIndex > -1 {
            flagArgs = args[firstFlagIndex:terminatorIndex]
            regularArgs = append(regularArgs, args[terminatorIndex:]...)
         } else {
            flagArgs = args[firstFlagIndex:]
         }

         err = set.Parse(append(flagArgs, regularArgs...))
      } else {
         err = set.Parse(ctx.Args().Tail())
      }
   } else {
      err = set.Parse(ctx.Args().Tail())
   }

   nerr := normalizeFlags(c.Flags, set)
   if nerr != nil {
      fmt.Fprintln(ctx.App.Writer, nerr)
      fmt.Fprintln(ctx.App.Writer)
      ShowCommandHelp(ctx, c.Name)
      return nerr
   }
   //创建上下文
   context := NewContext(ctx.App, set, ctx)
   context.Command = c
   if checkCommandCompletions(context, c.Name) {
      return nil
   }

   if err != nil {
      if c.OnUsageError != nil {
         err := c.OnUsageError(context, err, false)
         HandleExitCoder(err)
         return err
      }
      fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
      fmt.Fprintln(context.App.Writer)
      ShowCommandHelp(context, c.Name)
      return err
   }

   if checkCommandHelp(context, c.Name) {
      return nil
   }

   if c.After != nil {
      defer func() {
         afterErr := c.After(context)
         if afterErr != nil {
            HandleExitCoder(err)
            if err != nil {
               err = NewMultiError(err, afterErr)
            } else {
               err = afterErr
            }
         }
      }()
   }

   if c.Before != nil {
      err = c.Before(context)
      if err != nil {
         ShowCommandHelp(context, c.Name)
         HandleExitCoder(err)
         return err
      }
   }

   if c.Action == nil {
      c.Action = helpSubcommand.Action
   }

   err = HandleAction(c.Action, context)      //开始执行命令

   if err != nil {
      HandleExitCoder(err)
   }
   return err
}
------------------------------------------------------------------------
// localConsole starts a new geth node, attaching a JavaScript console to it at the
// same time.
func localConsole(ctx *cli.Context) error {
   // Create and start the node based on the CLI flags
   node := makeFullNode(ctx)       //创建一个节点
   startNode(ctx, node)            //启动该节点
   defer node.Stop()

   // Attach to the newly started node and start the JavaScript console
   client, err := node.Attach()
   if err != nil {
      utils.Fatalf("Failed to attach to the inproc geth: %v", err)
   }
   config := console.Config{
      DataDir: utils.MakeDataDir(ctx),
      DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
      Client:  client,
      Preload: utils.MakeConsolePreloads(ctx),
   }

   console, err := console.New(config)     //创建一个console实例
   if err != nil {
      utils.Fatalf("Failed to start the JavaScript console: %v", err)
   }
   defer console.Stop(false)

   // If only a short execution was requested, evaluate and return
   if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
      console.Evaluate(script)
      return nil
   }
   // Otherwise print the welcome screen and enter interactive mode
   console.Welcome()                       //显示wellcome信息
   console.Interactive()                   //与用户交互

   return nil
}
----------------------------------------------------------------------------
//Node is a container on which services can be registered(node是一个容器,服务可以注册到node上)
type Node struct {
   eventmux *event.TypeMux // Event multiplexer used between the services of a stack
   config   *Config
   accman   *accounts.Manager        //账户管理器

   ephemeralKeystore string         // if non-empty, the key directory that will be removed by Stop
   instanceDirLock   flock.Releaser // prevents concurrent use of instance directory

   serverConfig p2p.Config            //p2p配置
   server       *p2p.Server           // Currently running P2P networking layer

   serviceFuncs []ServiceConstructor     // Service constructors (in dependency order)
   services     map[reflect.Type]Service // Currently running services

   rpcAPIs       []rpc.API   // List of APIs currently provided by the node
   inprocHandler *rpc.Server // In-process RPC request handler to process the API requests

   ipcEndpoint string       // IPC endpoint to listen at (empty = IPC disabled)
   ipcListener net.Listener // IPC RPC listener socket to serve API requests
   ipcHandler  *rpc.Server  // IPC RPC request handler to process the API requests

   httpEndpoint  string       // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
   httpWhitelist []string     // HTTP RPC modules to allow through this endpoint
   httpListener  net.Listener // HTTP RPC listener socket to server API requests
   httpHandler   *rpc.Server  // HTTP RPC request handler to process the API requests

   wsEndpoint string       // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
   wsListener net.Listener // Websocket RPC listener socket to server API requests
   wsHandler  *rpc.Server  // Websocket RPC request handler to process the API requests

   stop chan struct{} // Channel to wait for termination notifications
   lock sync.RWMutex

   log log.Logger
}

-----------------------------------------------------------------

func makeFullNode(ctx *cli.Context) *node.Node {
   stack, cfg := makeConfigNode(ctx)          //创建节点

   utils.RegisterEthService(stack, &cfg.Eth)   //注册以太坊服务

   if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
      utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
   }
   
   //Whisper(低语者)是以太坊的一个功能扩展。
   //以太坊的智能合约smart contract实现了分布式逻辑,
   //以太坊的Swarm实现了分布式存储,以太坊的Whisper实现了分布式消息)。
   //Whisper将实现智能合约间的消息互通,届时可以实现功能更加复杂的DApp。
   // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
   shhEnabled := enableWhisper(ctx)
   shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
   if shhEnabled || shhAutoEnabled {
      if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
         cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
      }
      if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
         cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
      }
      //注册ssh服务
      utils.RegisterShhService(stack, &cfg.Shh)
   }

   // Add the Ethereum Stats daemon if requested.
   //以太坊状态(Eth Stats)是一个用于跟踪以太坊网络状态的可视化平台,
  //他使用WebSockets技术从正在运行中的节点中接受统计信息并通过不同的规则来输出数据,
  //用户可以通过该网站直观的了解以太坊节点的运行情况。
   if cfg.Ethstats.URL != "" {
      utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
   }
   return stack
}
------------------------------------------------------------------------------------
// RegisterEthService adds an Ethereum client to the stack. (注册以太坊服务)
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
   var err error
   //如果同步模式是轻量级的同步模式。 那么启动轻量级的客户端。
   if cfg.SyncMode == downloader.LightSync {
      err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
         return les.New(ctx, cfg)
      })
   } else {  // 否则会启动全节点
      err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
         fullNode, err := eth.New(ctx, cfg)
         if fullNode != nil && cfg.LightServ > 0 {
            ls, _ := les.NewLesServer(fullNode, cfg)
            fullNode.AddLesServer(ls)
         }
         return fullNode, err
      })
   }
   if err != nil {
      Fatalf("Failed to register the Ethereum service: %v", err)
   }
}

以太坊有3中同步模式:

–fast Enable fast syncing through state downloads

–light Enable light client mode

–syncmode full

  • fast模式,获取区块的header,获取区块的body,在同步到当前块之前不处理任何事务。下载的数据大小约为50GB(截止2018-02-04)。然后获得一个快照,此后,像full节点一样进行后面的同步操作。这种方法用得最多,目的在不要在意历史数据,将历史数据按照快照的方式,不逐一验证,沿着区块下载最近数据库中的交易,有可能丢失历史数据。此方法可能会对历史数据有部分丢失,但是不影响今后的使用。

//使用此模式时注意需要设置–cache,默认16M,建议设置为1G(1024)到2G(2048)

geth –fast –cache 512

  • light模式,仅获取当前状态。验证元素需要向full节点发起相应的请求。

geth –light

  • full 模式,从开始到结束,获取区块的header,获取区块的body,从创始块开始校验每一个元素,
    需要下载所有区块数据信息。速度最慢,但是能获取到所有的历史数据。
    geth –syncmode full

----------------------------------------------------------------------------

func defaultNodeConfig() node.Config {
   cfg := node.DefaultConfig
   cfg.Name = clientIdentifier
   cfg.Version = params.VersionWithCommit(gitCommit)
   cfg.HTTPModules = append(cfg.HTTPModules, "eth", "shh")
   cfg.WSModules = append(cfg.WSModules, "eth", "shh")
   cfg.IPCPath = "geth.ipc"
   return cfg
}

//这个函数主要是通过配置文件和flag来生成整个系统的运行配置
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
   // Load defaults.
   cfg := gethConfig{
      Eth:       eth.DefaultConfig,
      Shh:       whisper.DefaultConfig,
      Node:      defaultNodeConfig(),
      Dashboard: dashboard.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)                 //创建一个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)
   }

   utils.SetShhConfig(ctx, stack, &cfg.Shh)
   utils.SetDashboardConfig(ctx, &cfg.Dashboard)

   return stack, cfg
}

---------------------------------------------------------

// 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
   }
   // 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"`)
   }
   // Ensure that the AccountManager method works before the node has started.
   // We rely on this in cmd/geth.
   am, ephemeralKeystore, err := makeAccountManager(conf)           //AccountManager负责Account的相关管理操作
   if err != nil {
      return nil, err
   }
   if conf.Logger == nil {
      conf.Logger = log.New()
   }
   // Note: any interaction with Config that would create/touch files
   // in the data directory or instance directory is delayed until Start.
   return &Node{                    //组装node并返回
      accman:            am,
      ephemeralKeystore: ephemeralKeystore,
      config:            conf,
      serviceFuncs:      []ServiceConstructor{},
      ipcEndpoint:       conf.IPCEndpoint(),
      httpEndpoint:      conf.HTTPEndpoint(),
      wsEndpoint:        conf.WSEndpoint(),
      eventmux:          new(event.TypeMux),
      log:               conf.Logger,
   }, nil
}

--------------------------------------------------------------

func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
   scryptN, scryptP, keydir, err := conf.AccountConfig()
   var ephemeral string
if keydir == "" { //如果不存在将创建一个默认的go-ethereum-keystore来保存相关信息
// There is no datadir. keydir, err = ioutil.TempDir( "" , "go-ethereum-keystore" ) ephemeral = keydir } if err != nil { return nil, "" , err } if err := os.MkdirAll(keydir, 0700 ); err != nil { return nil, "" , err } // Assemble the account manager and supported backends backends := []accounts.Backend{ keystore.NewKeyStore(keydir, scryptN, scryptP), } if !conf.NoUSB { // Start a USB hub for Ledger hardware wallets if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { log.Warn(fmt.Sprintf( "Failed to start Ledger hub, disabling: %v" , err)) } else { backends = append(backends, ledgerhub) } // Start a USB hub for Trezor hardware wallets if trezorhub, err := usbwallet.NewTrezorHub(); err != nil { log.Warn(fmt.Sprintf( "Failed to start Trezor hub, disabling: %v" , err)) } else { backends = append(backends, trezorhub) } } return accounts.NewManager(backends...), ephemeral, nil}

--------------------------------------------------------

// NewKeyStore creates a keystore for the given directory.
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
   keydir, _ = filepath.Abs(keydir)
   ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}}
   ks.init(keydir)
   return ks
}

------------------------------------------------------

func (ks *KeyStore) init(keydir string) {
   // Lock the mutex since the account cache might call back with events
   ks.mu.Lock()
   defer ks.mu.Unlock()

   // Initialize the set of unlocked keys and the account cache
   ks.unlocked = make(map[common.Address]*unlocked)
   ks.cache, ks.changes = newAccountCache(keydir)          //从缓冲区中获取account,如没有则从datadir中获取

   // TODO: In order for this finalizer to work, there must be no references
   // to ks. addressCache doesn't keep a reference but unlocked keys do,
   // so the finalizer will not trigger until all timed unlocks have expired.
   runtime.SetFinalizer(ks, func(m *KeyStore) {
      m.cache.close()
   })
   // Create the initial list of wallets from the cache
   accs := ks.cache.accounts()                   //读取账户列表
   ks.wallets = make([]accounts.Wallet, len(accs))
   for i := 0; i < len(accs); i++ {
      ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
   }
}

----------------------------------------------

func (ac *accountCache) accounts() []accounts.Account {
   ac.maybeReload()             //如果账户数据未被加载到内存中,则首先加载进来
   ac.mu.Lock()
   defer ac.mu.Unlock()
   cpy := make([]accounts.Account, len(ac.all))
   copy(cpy, ac.all)            //然后copy一份,防止外面对缓存的账户做修改
   return cpy
}

-------------------------------------------------

func (ac *accountCache) maybeReload() {
   ac.mu.Lock()

   if ac.watcher.running {
      ac.mu.Unlock()
      return // A watcher is running and will keep the cache up-to-date.
   }
   if ac.throttle == nil {
      ac.throttle = time.NewTimer(0)
   } else {
      select {
      case <-ac.throttle.C:
      default:
         ac.mu.Unlock()
         return // The cache was reloaded recently.
      }
   }
   // No watcher running, start it.  使用goroutine启动一个watcher来监测keystore目录,防止其变化
   ac.watcher.start()
   ac.throttle.Reset(minReloadInterval)
   ac.mu.Unlock()
   ac.scanAccounts()         //从datadir/keystore中读取账户数据,例如address
}

-----------------------------------------------------------

创建完node,接下来看一看start node

// 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) {
   debug.Memsize.Add("node", stack)

   // Start up the node itself
   utils.StartNode(stack)               //启动节点

   // Unlock any account specifically requested
   ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)

   passwords := utils.MakePasswordList(ctx)
   unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
   for i, account := range unlocks {
      if trimmed := strings.TrimSpace(account); trimmed != "" {
         unlockAccount(ctx, ks, trimmed, i, passwords)
      }
   }
   // Register wallet event handlers to open and auto-derive wallets
   events := make(chan accounts.WalletEvent, 16)
   stack.AccountManager().Subscribe(events)

   go func() {
      // Create a chain state reader for self-derivation
      rpcClient, err := stack.Attach()
      if err != nil {
         utils.Fatalf("Failed to attach to self: %v", err)
      }
      stateReader := ethclient.NewClient(rpcClient)

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

            if event.Wallet.URL().Scheme == "ledger" {
               event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader)
            } else {
               event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
            }

         case accounts.WalletDropped:
            log.Info("Old wallet dropped", "url", event.Wallet.URL())
            event.Wallet.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.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
         utils.Fatalf("Light clients do not support mining")
      }
      var ethereum *eth.Ethereum
      if err := stack.Service(&ethereum); err != nil {
         utils.Fatalf("Ethereum service not running: %v", err)
      }
      // Use a reduced number of threads if requested
      if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
         type threaded interface {
            SetThreads(threads int)
         }
         if th, ok := ethereum.Engine().(threaded); ok {
            th.SetThreads(threads)
         }
      }
      // Set the gas price to the limits from the CLI and start mining
      ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))  //给txPool设置gas price
      if err := ethereum.StartMining(true); err != nil {                            //启动挖矿
         utils.Fatalf("Failed to start mining: %v", err)
      }
   }
}

--------------------------

func StartNode(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)
      <-sigc
      log.Info("Got interrupt, shutting down...")
      go stack.Stop()
      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")
   }()
}

----------------------------------------

// 创建一个有生命的p2p节点,并启动它
func (n *Node) Start() error {
   n.lock.Lock()
   defer n.lock.Unlock()

   // Short circuit if the node's already running(如果节点已经运行,短路它)
   if n.server != nil {
      return ErrNodeRunning
   }
   //datadir是p2p程序用于保存数据的文件目录
   if err := n.openDataDir(); err != nil {
      return err
   }

   // Initialize the p2p server. This creates the node key and——
   // discovery databases.
   n.serverConfig = n.config.P2P    //p2p网络配置
   //NodeKey检索当前已配置的节点的私钥,首先检查任何手动设置key,
   //返回一个在配置数据文件夹datadir/node中找到的key。
   //如果没有找到关键字,就会生成一个新的。               
   //p2p/config.go里面为node生成一个key
   n.serverConfig.PrivateKey = n.config.NodeKey() 
   n.serverConfig.Name = n.config.NodeName()    //生成开发者模式p2p node的唯一标识
   n.serverConfig.Logger = n.log                //Logger是一个用于p2p.Server的自定义日志记录器。
   //静态节点返回配置为静态节点的节点enode url列表。其中node URLs 从data.dir的文件夹中的json文件加载过来.
   if n.serverConfig.StaticNodes == nil {
      n.serverConfig.StaticNodes = n.config.StaticNodes()
   }
   if n.serverConfig.TrustedNodes == nil {
      n.serverConfig.TrustedNodes = n.config.TrustedNodes()
   }
   if n.serverConfig.NodeDatabase == "" {
      n.serverConfig.NodeDatabase = n.config.NodeDB()
   }
   //启动p2p server并把节点的配置实例化server的配置
   running := &p2p.Server{Config: n.serverConfig}
   n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)

   // Otherwise copy and specialize the P2P configuration
   services := make(map[reflect.Type]Service)
   for _, constructor := range n.serviceFuncs {
      // Create a new context for the particular service
      ctx := &ServiceContext{
         config:         n.config,
         services:       make(map[reflect.Type]Service),
         EventMux:       n.eventmux,
         AccountManager: n.accman,
      }
      for kind, s := range services { // copy needed for threaded access
         ctx.services[kind] = s
      }
      // Construct and save the service
      service, err := constructor(ctx)
      if err != nil {
         return err
      }
      kind := reflect.TypeOf(service)
      if _, exists := services[kind]; exists {
         return &DuplicateServiceError{Kind: kind}
      }
      services[kind] = service
   }
   // Gather the protocols and start the freshly assembled P2P server
   for _, service := range services {
      running.Protocols = append(running.Protocols, service.Protocols()...)
   }
   if err := running.Start(); err != nil {          //正式启动运行
      return convertFileLockError(err)
   }
   // Start each of the services
   started := []reflect.Type{}
   for kind, service := range services {
      // Start the next service, stopping all previous upon failure
      if err := service.Start(running); err != nil {
         for _, kind := range started {
            services[kind].Stop()
         }
         running.Stop()

         return err
      }
      // Mark the service started for potential cleanup
      started = append(started, kind)
   }
   // Lastly start the configured RPC interfaces
   if err := n.startRPC(services); err != nil {              //启动rpc
      for _, service := range services {
         service.Stop()
      }
      running.Stop()
      return err
   }
   // Finish initializing the startup
   n.services = services
   n.server = running
   n.stop = make(chan struct{})

   return nil
}

--------------------------------------

// Start starts running the server.
// Servers can not be re-used after stopping.
func (srv *Server) Start() (err error) {
   srv.lock.Lock()
   defer srv.lock.Unlock()
   if srv.running {
      return errors.New("server already running")
   }
   srv.running = true
   srv.log = srv.Config.Logger
   if srv.log == nil {
      srv.log = log.New()
   }
   srv.log.Info("Starting P2P networking")

   // static fields
   if srv.PrivateKey == nil {
      return fmt.Errorf("Server.PrivateKey must be set to a non-nil key")
   }
   if srv.newTransport == nil {
      srv.newTransport = newRLPX
   }
   if srv.Dialer == nil {
      srv.Dialer = TCPDialer{&net.Dialer{Timeout: defaultDialTimeout}}
   }
   srv.quit = make(chan struct{})
   srv.addpeer = make(chan *conn)
   srv.delpeer = make(chan peerDrop)
   srv.posthandshake = make(chan *conn)
   srv.addstatic = make(chan *discover.Node)
   srv.removestatic = make(chan *discover.Node)
   srv.peerOp = make(chan peerOpFunc)
   srv.peerOpDone = make(chan struct{})

   var (
      conn      *net.UDPConn
      sconn     *sharedUDPConn
      realaddr  *net.UDPAddr
      unhandled chan discover.ReadPacket
   )

   if !srv.NoDiscovery || srv.DiscoveryV5 {
      addr, err := net.ResolveUDPAddr("udp", srv.ListenAddr)
      if err != nil {
         return err
      }
      conn, err = net.ListenUDP("udp", addr)
      if err != nil {
         return err
      }
      realaddr = conn.LocalAddr().(*net.UDPAddr)
      if srv.NAT != nil {
         if !realaddr.IP.IsLoopback() {
            go nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
         }
         // TODO: react to external IP changes over time.
         if ext, err := srv.NAT.ExternalIP(); err == nil {
            realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
         }
      }
   }

   if !srv.NoDiscovery && srv.DiscoveryV5 {
      unhandled = make(chan discover.ReadPacket, 100)
      sconn = &sharedUDPConn{conn, unhandled}
   }

   // node table
   if !srv.NoDiscovery {
      cfg := discover.Config{
         PrivateKey:   srv.PrivateKey,
         AnnounceAddr: realaddr,
         NodeDBPath:   srv.NodeDatabase,
         NetRestrict:  srv.NetRestrict,
         Bootnodes:    srv.BootstrapNodes,
         Unhandled:    unhandled,
      }
      ntab, err := discover.ListenUDP(conn, cfg)
      if err != nil {
         return err
      }
      srv.ntab = ntab
   }
   //启动发现节点的监听任务
   if srv.DiscoveryV5 {
      var (
         ntab *discv5.Network
         err  error
      )
      if sconn != nil {
         ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, realaddr, "", srv.NetRestrict) //srv.NodeDatabase)
      } else {
         ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, realaddr, "", srv.NetRestrict) //srv.NodeDatabase)
      }
      if err != nil {
         return err
      }
      if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil {
         return err
      }
      srv.DiscV5 = ntab
   }

   dynPeers := srv.maxDialedConns()
   dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)

   // handshake,握手协议
   srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
   for _, p := range srv.Protocols {
      srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
   }
   // listen/dial
   if srv.ListenAddr != "" {
      if err := srv.startListening(); err != nil {
         return err
      }
   }
   if srv.NoDial && srv.ListenAddr == "" {
      srv.log.Warn("P2P server will be useless, neither dialing nor listening")
   }

   srv.loopWG.Add(1)
   go srv.run(dialer)
   srv.running = true
   return nil
}

geth的启动流程十分复杂,代码量很多,因为有各种各样的数据结构,各种各样的flag,还有各种各样的服务,甚至牵扯到以太网整个架构,因此看一遍代码只能囫囵吞枣,能理解多少是多少吧。目前能够看懂的流程是:

1.加载配置文件,初始化各种config

2. 初始化CLI

3. 加载本地帐户列表

4. 创建一个geth节点

5. 启动geth节点的p2p服务


猜你喜欢

转载自blog.csdn.net/qq_36391986/article/details/80618614
今日推荐