以太坊原始碼分析(39)geth啟動流程分析
阿新 • • 發佈:2019-02-07
geth是我們的go-ethereum最主要的一個命令列工具。 也是我們的各種網路的接入點(主網路main-net 測試網路test-net 和私有網路)。支援執行在全節點模式或者輕量級節點模式。 其他程式可以通過它暴露的JSON RPC呼叫來訪問以太坊網路的功能。
如果什麼命令都不輸入直接執行geth。 就會預設啟動一個全節點模式的節點。 連線到主網路。 我們看看啟動的主要流程是什麼,涉及到了那些元件。
## 啟動的main函式 cmd/geth/main.go看到main函式一上來就直接運行了。 最開始看的時候是有點懵逼的。 後面發現go語言裡面有兩個預設的函式,一個是main()函式。一個是init()函式。 go語言會自動按照一定的順序先呼叫所有包的init()函式。然後才會呼叫main()函式。
func main() { if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
main.go的init函式app是一個三方包gopkg.in/urfave/cli.v1的例項。 這個三方包的用法大致就是首先構造這個app物件。 通過程式碼配置app物件的行為,提供一些回撥函式。然後執行的時候直接在main函式裡面執行 app.Run(os.Args)就行了。
import ( ... "gopkg.in/urfave/cli.v1" )
var (
app = utils.NewApp(gitCommit, "the go-ethereum command line interface") // flags that configure the node nodeFlags = []cli.Flag{ utils.IdentityFlag, utils.UnlockedAccountFlag, utils.PasswordFileFlag, utils.BootnodesFlag, ... } rpcFlags = []cli.Flag{ utils.RPCEnabledFlag, utils.RPCListenAddrFlag, ... } whisperFlags = []cli.Flag{ utils.WhisperEnabledFlag, ... } ) func init() { // Initialize the CLI app and start Geth // Action欄位表示如果使用者沒有輸入其他的子命令的情況下,會呼叫這個欄位指向的函式。 app.Action = geth app.HideVersion = true // we have a command to print the version app.Copyright = "Copyright 2013-2017 The go-ethereum Authors" // Commands 是所有支援的子命令 app.Commands = []cli.Command{ // See chaincmd.go: initCommand, importCommand, exportCommand, removedbCommand, dumpCommand, // See monitorcmd.go: monitorCommand, // See accountcmd.go: accountCommand, walletCommand, // See consolecmd.go: consoleCommand, attachCommand, javascriptCommand, // See misccmd.go: makecacheCommand, makedagCommand, versionCommand, bugCommand, licenseCommand, // See config.go dumpConfigCommand, } sort.Sort(cli.CommandsByName(app.Commands)) // 所有能夠解析的Options 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 } // 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 } }
如果我們沒有輸入任何的引數,那麼會自動呼叫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. // 如果沒有指定特殊的子命令,那麼geth是系統主要的入口。 // 它會根據提供的引數建立一個預設的節點。並且以阻塞的模式執行這個節點,等待著節點被終止。 func geth(ctx *cli.Context) error { node := makeFullNode(ctx) startNode(ctx, node) node.Wait() return nil }
makeFullNode函式, func makeFullNode(ctx *cli.Context) *node.Node { // 根據命令列引數和一些特殊的配置來建立一個node stack, cfg := makeConfigNode(ctx) // 把eth的服務註冊到這個節點上面。 eth服務是以太坊的主要的服務。 是以太坊功能的提供者。 utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode // Whisper是一個新的模組,用來進行加密通訊的功能。 需要顯式的提供引數來啟用,或者是處於開發模式。 shhEnabled := enableWhisper(ctx) shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.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) } // 註冊Shh服務 utils.RegisterShhService(stack, &cfg.Shh) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { // 註冊 以太坊的狀態服務。 預設情況下是沒有啟動的。 utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) } // Add the release oracle service so it boots along with node. // release oracle服務是用來檢視客戶端版本是否是最新版本的服務。 // 如果需要更新。 那麼會通過列印日誌來提示版本更新。 // release 是通過智慧合約的形式來執行的。 後續會詳細討論這個服務。 if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { config := release.Config{ Oracle: relOracle, Major: uint32(params.VersionMajor), Minor: uint32(params.VersionMinor), Patch: uint32(params.VersionPatch), } commit, _ := hex.DecodeString(gitCommit) copy(config.Commit[:], commit) return release.NewReleaseService(ctx, config) }); err != nil { utils.Fatalf("Failed to register the Geth release oracle service: %v", err) } return stack }
makeConfigNode。 這個函式主要是通過配置檔案和flag來生成整個系統的執行配置。 func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ Eth: eth.DefaultConfig, Shh: whisper.DefaultConfig, Node: defaultNodeConfig(), } // 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) } utils.SetShhConfig(ctx, stack, &cfg.Shh) return stack, cfg }
RegisterEthService
// 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 { // 預設LightServ的大小是0 也就是不會啟動LesServer // LesServer是給輕量級節點提供服務的。 ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) } return fullNode, err }) } if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } }
startNode
// 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) { // 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 an 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) { // Mining only makes sense if a full Ethereum node is running var ethereum *eth.Ethereum if err := stack.Service(ðereum); 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)) if err := ethereum.StartMining(true); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } }
總結:
整個啟動過程其實就是解析引數。然後建立和啟動節點。 然後把服務注入到節點中。 所有跟以太坊相關的功能都是以服務的形式實現的。
如果什麼命令都不輸入直接執行geth。 就會預設啟動一個全節點模式的節點。 連線到主網路。 我們看看啟動的主要流程是什麼,涉及到了那些元件。
## 啟動的main函式 cmd/geth/main.go看到main函式一上來就直接運行了。 最開始看的時候是有點懵逼的。 後面發現go語言裡面有兩個預設的函式,一個是main()函式。一個是init()函式。 go語言會自動按照一定的順序先呼叫所有包的init()函式。然後才會呼叫main()函式。
func main() { if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
main.go的init函式app是一個三方包gopkg.in/urfave/cli.v1的例項。 這個三方包的用法大致就是首先構造這個app物件。 通過程式碼配置app物件的行為,提供一些回撥函式。然後執行的時候直接在main函式裡面執行 app.Run(os.Args)就行了。
import ( ... "gopkg.in/urfave/cli.v1" )
var (
app = utils.NewApp(gitCommit, "the go-ethereum command line interface") // flags that configure the node nodeFlags = []cli.Flag{ utils.IdentityFlag, utils.UnlockedAccountFlag, utils.PasswordFileFlag, utils.BootnodesFlag, ... } rpcFlags = []cli.Flag{ utils.RPCEnabledFlag, utils.RPCListenAddrFlag, ... } whisperFlags = []cli.Flag{ utils.WhisperEnabledFlag, ... } ) func init() { // Initialize the CLI app and start Geth // Action欄位表示如果使用者沒有輸入其他的子命令的情況下,會呼叫這個欄位指向的函式。 app.Action = geth app.HideVersion = true // we have a command to print the version app.Copyright = "Copyright 2013-2017 The go-ethereum Authors" // Commands 是所有支援的子命令 app.Commands = []cli.Command{ // See chaincmd.go: initCommand, importCommand, exportCommand, removedbCommand, dumpCommand, // See monitorcmd.go: monitorCommand, // See accountcmd.go: accountCommand, walletCommand, // See consolecmd.go: consoleCommand, attachCommand, javascriptCommand, // See misccmd.go: makecacheCommand, makedagCommand, versionCommand, bugCommand, licenseCommand, // See config.go dumpConfigCommand, } sort.Sort(cli.CommandsByName(app.Commands)) // 所有能夠解析的Options 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 } // 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 } }
如果我們沒有輸入任何的引數,那麼會自動呼叫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. // 如果沒有指定特殊的子命令,那麼geth是系統主要的入口。 // 它會根據提供的引數建立一個預設的節點。並且以阻塞的模式執行這個節點,等待著節點被終止。 func geth(ctx *cli.Context) error { node := makeFullNode(ctx) startNode(ctx, node) node.Wait() return nil }
makeFullNode函式, func makeFullNode(ctx *cli.Context) *node.Node { // 根據命令列引數和一些特殊的配置來建立一個node stack, cfg := makeConfigNode(ctx) // 把eth的服務註冊到這個節點上面。 eth服務是以太坊的主要的服務。 是以太坊功能的提供者。 utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode // Whisper是一個新的模組,用來進行加密通訊的功能。 需要顯式的提供引數來啟用,或者是處於開發模式。 shhEnabled := enableWhisper(ctx) shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.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) } // 註冊Shh服務 utils.RegisterShhService(stack, &cfg.Shh) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { // 註冊 以太坊的狀態服務。 預設情況下是沒有啟動的。 utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) } // Add the release oracle service so it boots along with node. // release oracle服務是用來檢視客戶端版本是否是最新版本的服務。 // 如果需要更新。 那麼會通過列印日誌來提示版本更新。 // release 是通過智慧合約的形式來執行的。 後續會詳細討論這個服務。 if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { config := release.Config{ Oracle: relOracle, Major: uint32(params.VersionMajor), Minor: uint32(params.VersionMinor), Patch: uint32(params.VersionPatch), } commit, _ := hex.DecodeString(gitCommit) copy(config.Commit[:], commit) return release.NewReleaseService(ctx, config) }); err != nil { utils.Fatalf("Failed to register the Geth release oracle service: %v", err) } return stack }
makeConfigNode。 這個函式主要是通過配置檔案和flag來生成整個系統的執行配置。 func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ Eth: eth.DefaultConfig, Shh: whisper.DefaultConfig, Node: defaultNodeConfig(), } // 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) } utils.SetShhConfig(ctx, stack, &cfg.Shh) return stack, cfg }
RegisterEthService
// 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 { // 預設LightServ的大小是0 也就是不會啟動LesServer // LesServer是給輕量級節點提供服務的。 ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) } return fullNode, err }) } if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } }
startNode
// 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) { // 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 an 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) { // Mining only makes sense if a full Ethereum node is running var ethereum *eth.Ethereum if err := stack.Service(ðereum); 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)) if err := ethereum.StartMining(true); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } }
總結:
整個啟動過程其實就是解析引數。然後建立和啟動節點。 然後把服務注入到節點中。 所有跟以太坊相關的功能都是以服務的形式實現的。
網址:http://www.qukuailianxueyuan.io/
欲領取造幣技術與全套虛擬機器資料
區塊鏈技術交流QQ群:756146052 備註:CSDN
尹成學院微信:備註:CSDN