1. 程式人生 > >geth原始碼閱讀——啟動流程

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 servicesrpcAPIs       []rpc.API   // List of APIs currently provided by the nodeinprocHandler *rpc.Server // In-process RPC request handler to process the API requestsipcEndpoint 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 –cache512

  • 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 backendsbackends := []accounts.Backend{ keystore.NewKeyStore(keydir, scryptN, scryptP), } if !conf.NoUSB { // Start a USB hub for Ledger hardware walletsif 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 walletsif 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 ==