1. 程式人生 > >以太坊原始碼分析(39)geth啟動流程分析

以太坊原始碼分析(39)geth啟動流程分析

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(&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))            if err := ethereum.StartMining(true); err != nil {                utils.Fatalf("Failed to start mining: %v", err)            }        }    }
總結:
整個啟動過程其實就是解析引數。然後建立和啟動節點。 然後把服務注入到節點中。 所有跟以太坊相關的功能都是以服務的形式實現的。




網址:http://www.qukuailianxueyuan.io/



欲領取造幣技術與全套虛擬機器資料

區塊鏈技術交流QQ群:756146052  備註:CSDN

尹成學院微信:備註:CSDN