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

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

作者: RickJay | 来源:发表于2018-06-01 09:09 被阅读228次

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/kedhsftx.html