美文网首页区块链
以太坊源码解析 - geth 入口代码

以太坊源码解析 - geth 入口代码

作者: 豆瓣奶茶 | 来源:发表于2018-06-01 09:07 被阅读8次

    geth main函数

    geth启动命令:

    geth --datadir data0 --networkid 110 console
    
    • --datadir 设置区块链数据存放路径
    • --networkid 网络设置启动的区块链网路默认值是1表示以太坊公网,2,3,4 表示测试网路,大于4表示本地私有网路
    • console 表示启动控制台

    geth的源码入口 main 函数在 /cmd/geth/main.go

    // /cmd/geth/main.go  
    
    func init() {
        // Initialize the CLI app and start 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,
            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))
    
        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
        }
    }
    
    func main() {
        if err := app.Run(os.Args); err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
    

    执行 main() 函数,会先调用 init()init 主要是做了一些初始化的工作,其中比较重要 app.Action = geth app.Commands中consoleCommand

    app.Run() 位于 go-ethereum/vendor/gopkg.in/urfave/cli.v1/app.goapp.Run()方法执行

    func (a *App) Run(arguments []string) (err error) {
        a.Setup()
    
        // handle the completion flag separately from the flagset since
        // completion could be attempted after a flag, but before its value was put
        // on the command line. this causes the flagset to interpret the completion
        // flag name as the value of the flag before it which is undesirable
        // note that we can only do this because the shell autocomplete function
        // always appends the completion flag at the end of the 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 {
            defer func() {
                if afterErr := a.After(context); afterErr != nil {
                    if err != nil {
                        err = NewMultiError(err, afterErr)
                    } else {
                        err = afterErr
                    }
                }
            }()
        }
    
        if a.Before != nil {
            beforeErr := a.Before(context)
            if beforeErr != nil {
                fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
                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)
            }
        }
    
        if a.Action == nil {
            a.Action = helpCommand.Action
        }
    
        // Run default Action
        err = HandleAction(a.Action, context)
    
        HandleExitCoder(err)
        return err
    }
    

    a.Setup仅仅是做了些简单的处理,比如相关的Auther、Email、重新创建Command切片等,主要看下面的 if 判断:

        if args.Present() {
            name := args.First()
            c := a.Command(name)
            if c != nil {
                return c.Run(context)
            }
        }
    

    我们在控制台输入的命令 geth --datadir data0 --networkid 110 console 长度不为0,因此执行

    c.Run(context)
    

    操作,此时的命令其实就是我们的console命令。接下来我们看看Run方法,Run方法的代码如下:

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

    该主要是设置flag、解析输入的命令行参数、创建全局的context、将当前命令保存到全局context中,接下来调用HandleAction来处理命令,HandleAction的函数实现如下:

    func HandleAction(action interface{}, context *Context) (err error) {
        if a, ok := action.(ActionFunc); ok {
            return a(context)
        } else if a, ok := action.(func(*Context) error); ok {
            return a(context)
        } else if a, ok := action.(func(*Context)); ok { // deprecated function signature
            a(context)
            return nil
        } else {
            return errInvalidActionType
        }
    }
    
    

    action的类型是「func(*Context) error」,此时将执行a(context)方法,那么此时调用前面提到的 App.init() 初始化命令时的 consoleCommand,接下来我们来看看 cmd/geth/consolecmd 中的 consoleCommand

    consoleCommand = cli.Command{
            Action:   utils.MigrateFlags(localConsole),
            Name:     "console",
            Usage:    "Start an interactive JavaScript environment",
            Flags:    append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...),
            Category: "CONSOLE COMMANDS",
            Description: `
    The Geth console is an interactive shell for the JavaScript runtime environment
    which exposes a node admin interface as well as the Ðapp JavaScript API.
    See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`,
        }
    

    localConsole

    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)
        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()
        console.Interactive()
    
        return nil
    }
    

    localConsole 主要完成以下几件事情

    • 首先会创建一个节点、同时启动该节点
    • 创建一个console的实例
    • 显示Welcome信息
    • 创建一个无限循环用于在控制台交互

    我们来看该Node是如何创建的,makeFullNode函数的实现如下:

    makeFullNode

    //代码位于 /cmd/geth/config.go
    
    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来生成整个系统的运行配置。

    //代码位于 /cmd/geth/config.go
    
    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)
        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
    }
    

    RegisterEthService

    把eth的服务注册到节点上面。 eth服务是以太坊的主要的服务。 是以太坊功能的提供者。

    // go-ethereum/cmd/utils/flags.go
    
    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)
        }
    }
    

    enableWhisper

    // enableWhisper returns true in case one of the whisper flags is set.
    func enableWhisper(ctx *cli.Context) bool {
        for _, flag := range whisperFlags {
            if ctx.GlobalIsSet(flag.GetName()) {
                return true
            }
        }
        return false
    }
    

    startNode

    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))
            if err := ethereum.StartMining(true); err != nil {
                utils.Fatalf("Failed to start mining: %v", err)
            }
        }
    }
    

    该函数内部首先将节点启动起来,然后建立跟RPC Server建立一个连接,用于RPC通信。我们来看看节点如何启动起来的。/cmd/utils/cmd.go文件如下:

    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")
        }()
    }
    

    该函数调用了Node.go中的start方法,其实现如下:

    // go-ethereum/node/node.go
    // Start create a live P2P node and starts running it.
    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
        }
        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
        n.serverConfig.PrivateKey = n.config.NodeKey()
        n.serverConfig.Name = n.config.NodeName()
        n.serverConfig.Logger = n.log
        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()
        }
        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 {
            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
    }
    

    该方法首先打开datadir目录,接着初始化serverConfig的相关配置,接着创建一个p2p.server的一个变量,然后启动该节点,在接着把RPC的Service启动起来 if err := running.Start();,我们先看看节点的启动。/p2p/server.go中Start方法如下:

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

    该方法首先建立一个UDP连接,并且调用server.run启动起来,跟其它可用的节点建立连接等。到此,整个节点就启动起来了。

    console.New

    func New(config Config) (*Console, error) {
        // Handle unset config values gracefully
        if config.Prompter == nil {
            config.Prompter = Stdin
        }
        if config.Prompt == "" {
            config.Prompt = DefaultPrompt
        }
        if config.Printer == nil {
            config.Printer = colorable.NewColorableStdout()
        }
        // Initialize the console and return
        console := &Console{
            client:   config.Client,
            jsre:     jsre.New(config.DocRoot, config.Printer),
            prompt:   config.Prompt,
            prompter: config.Prompter,
            printer:  config.Printer,
            histPath: filepath.Join(config.DataDir, HistoryFile),
        }
        if err := os.MkdirAll(config.DataDir, 0700); err != nil {
            return nil, err
        }
        if err := console.init(config.Preload); err != nil {
            return nil, err
        }
        return console, nil
    }
    

    该函数首先对Config做一些默认设置,接着调用其jsre.New函数,在借这个调用init方法初始化相关RPC API。jsre.New方法的实现如下:

    func New(assetPath string, output io.Writer) *JSRE {
        re := &JSRE{
            assetPath:     assetPath,
            output:        output,
            closed:        make(chan struct{}),
            evalQueue:     make(chan *evalReq),
            stopEventLoop: make(chan bool),
        }
        go re.runEventLoop()
        re.Set("loadScript", re.loadScript)
        re.Set("inspect", re.prettyPrintJS)
        return re
    }
    

    该函数创建一个JSRE的变量然后使用goroutine的方式开启一个事件监听的循环,该事件为控制台输入后,经过一些处理通过channel的方式发送过来。具体接收到的命令后续处理,请读者自己跟踪其逻辑实现。创建console的目的主要是监听控制台输入的命名。

    console.Welcome

    func (c *Console) Welcome() {
        // Print some generic Geth metadata
        fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n")
        c.jsre.Run(`
            console.log("instance: " + web3.version.node);
            console.log("coinbase: " + eth.coinbase);
            console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")");
            console.log(" datadir: " + admin.datadir);
        `)
        // List all the supported modules for the user to call
        if apis, err := c.client.SupportedModules(); err == nil {
            modules := make([]string, 0, len(apis))
            for api, version := range apis {
                modules = append(modules, fmt.Sprintf("%s:%s", api, version))
            }
            sort.Strings(modules)
            fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " "))
        }
        fmt.Fprintln(c.printer)
    }
    

    该方法首先打印「Welcome to the Geth JavaScript console!」信息,这个可以直接在控制台查看到,接着使用上面创建console的jsre执行四个控制台输出写版本、区块等信息。你可以直接复制出来在命令行执行,也能看到相同的结果。下面的为该函数的输出信息:

    Welcome to the Geth JavaScript console!
    
    instance: Geth/v1.8.10-unstable-ccc0debb/darwin-amd64/go1.10.2
     modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
    
    > 
    

    相关文章

      网友评论

        本文标题:以太坊源码解析 - geth 入口代码

        本文链接:https://www.haomeiwen.com/subject/fddhsftx.html