以太坊源码分析之一整体流程

以太坊的源码启动和ONT本体的启动有些基本相似,都是依赖第三方包gopkg.in/urfave/cli.v1的实例启动做命令行的交互方式。只不过,从实际情况来看后者应用的更简单暴力一些,以太坊应用的更复杂一些,或者些更高明一些。

geth主程序的精简的让人吃惊:

func main() {

         iferr := app.Run(os.Args); err != nil {

                   fmt.Fprintln(os.Stderr,err)

                   os.Exit(1)

         }

}

廖廖几行代码,让人感觉怎么有点儿反常呢。后来还是看到APP里才发现了这里是别有洞天。看一下调用的相关代码:

app = utils.NewApp(gitCommit, "thego-ethereum command line interface")

看app生成的的方法,这也是GO语言里常用的套路:

// NewApp creates an app with sanedefaults.

func NewApp(gitCommit, usage string)*cli.App {

         app:= cli.NewApp()

         app.Name= filepath.Base(os.Args[0])

         app.Author= ""

         //app.Authors= nil

         app.Email= ""

         app.Version= params.Version

         iflen(gitCommit) >= 8 {

                   app.Version+= "-" + gitCommit[:8]

         }

         app.Usage= usage

         returnapp

}

重点其实还是APP的生成函数,下面基本是一些条件的判断和变量的赋值:

// NewApp creates a new cli Applicationwith some reasonable defaults for Name,

// Usage, Version and Action.

func NewApp() *App {

         return&App{

                   Name:         filepath.Base(os.Args[0]),

                   HelpName:     filepath.Base(os.Args[0]),

                   Usage:        "A new cli application",

                   UsageText:    "",

                   Version:      "0.0.0",

                   BashComplete:DefaultAppComplete,

                   Action:      helpCommand.Action,

                   Compiled:     compileTime(),

                   Writer:       os.Stdout,

         }

}

到这里可以清晰的看到一个App的对象被生成并返回了其指针,这里面的BashComplete和Action这里先放下,后面会详细说。

然后回到最初的代码,应该是到Run这个函数了:

func (a *App) Run(arguments []string) (errerror) {

         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)

         iferr != nil {

                   returnerr

         }

         set.SetOutput(ioutil.Discard)

         err= set.Parse(arguments[1:])

         nerr:= normalizeFlags(a.Flags, set)

         context:= NewContext(a, set, nil)

         ifnerr != nil {

                   fmt.Fprintln(a.Writer,nerr)

                   ShowAppHelp(context)

                   returnnerr

         }

         context.shellComplete= shellComplete

         ifcheckCompletions(context) {

                   returnnil

         }

         iferr != nil {

                   ifa.OnUsageError != nil {

                            err:= a.OnUsageError(context, err, false)

                            HandleExitCoder(err)

                            returnerr

                   }

                   fmt.Fprintf(a.Writer,"%s %s\n\n", "Incorrect Usage.", err.Error())

                   ShowAppHelp(context)

                   returnerr

         }

         if!a.HideHelp && checkHelp(context) {

                   ShowAppHelp(context)

                   returnnil

         }

         if!a.HideVersion && checkVersion(context) {

                   ShowVersion(context)

                   returnnil

         }

         ifa.After != nil {

                   deferfunc() {

                            ifafterErr := a.After(context); afterErr != nil {

                                     iferr != nil {

                                               err= NewMultiError(err, afterErr)

                                     }else {

                                               err= afterErr

                                     }

                            }

                   }()

         }

         ifa.Before != nil {

                   beforeErr:= a.Before(context)

                   ifbeforeErr != nil {

                            ShowAppHelp(context)

                            HandleExitCoder(beforeErr)

                            err= beforeErr

                            returnerr

                   }

         }

         args:= context.Args()

         ifargs.Present() {

                   name:= args.First()

                   c:= a.Command(name)

                   ifc != nil {

                            returnc.Run(context)

                   }

         }

         ifa.Action == nil {

                   a.Action= helpCommand.Action

         }

         //Run default Action

         err= HandleAction(a.Action, context)

         HandleExitCoder(err)

         returnerr

}

抛开各种判断,假如只搞默认,那么一定会调用最后的err = HandleAction(a.Action, context)

,a.Action是什么,在哪里赋值,好,看一下Go中的init函数:

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-2017 The go-ethereum Authors"

         app.Commands= []cli.Command{

                   //See chaincmd.go:

                   initCommand,

                   importCommand,

                   exportCommand,

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

                   iferr := debug.Setup(ctx); err != nil {

                            returnerr

                   }

                   //Start system runtime metrics collection

                   gometrics.CollectProcessMetrics(3 * time.Second)

                   utils.SetupNetwork(ctx)

                   returnnil

         }

         app.After= func(ctx *cli.Context) error {

                   debug.Exit()

                   console.Stdin.Close()// Resets terminal mode.

                   returnnil

         }

}

那么会发现,Action是geth,看一下这个函数的代码:

func geth(ctx *cli.Context) error {

         node:= makeFullNode(ctx)

         startNode(ctx,node)

         node.Wait()

         returnnil

}

这个函数挺重要,主要做了两点,makeFullNode,创建一个全节点,startNode,启动之。也就是说看字面都能理解。

回到默认的启动函数中接着看代码:

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 {

                   returna(context)

         }else if a, ok := action.(func(*Context)); ok { // deprecated function signature

                   a(context)

                   returnnil

         }else {

                   returnerrInvalidActionType

         }

}

这里解决了上面说的Action,还有一个Command,马上就来到了。上面说了半天,其实是单独启动一个geth,可是在实际大家都会发现,在启动geth的时候儿,后面会带一大串的参数,举一个例子:

geth --datadir=./dev/data0 --networkid 1console

那么这一大堆的参数肯定是调不到刚才说到的默认的Action了。那么去哪儿了呢?上面才提到app.Commands = []cli.Command,在这里面有一个consoleCommand,OK,就是它了。

在Run函数里有一个a.Setup(),进去看看:

func (a *App) Setup() {

……

         newCmds:= []Command{}

         for_, c := range a.Commands {

                   ifc.HelpName == "" {

                            c.HelpName= fmt.Sprintf("%s %s", a.HelpName, c.Name)

                   }

                   newCmds= append(newCmds, c)

         }

         a.Commands= newCmds

……

}

只看重点,就是把命令重新格式化后增加到新的Commands中去。不过没啥特殊的重点得回到Run函数里:

         args:= context.Args()

         ifargs.Present() {

                   name:= args.First()

                   c:= a.Command(name)

                   ifc != nil {

                            returnc.Run(context)

                   }

         }

这里一定看清楚,都是Run,但是前边默认的是app.go中的,这个是command.go中的。别绕晕。不过最后还是殊途同归。

在这里判断发现上面输入的一大串geth --datadir=./dev/data0 --networkid 1 console

就会发现这里其实就进来了,不会到上面提到的最后的HandleAction这个函数里了。args.Present()判断有参数,所以调用c.Run,那么调用哪个,console,对,那就是consoleCommand,这个函数进去:

         consoleCommand= cli.Command{

                   Action:   utils.MigrateFlags(localConsole),

                   Name:     "console",

                   Usage:    "Start an interactive JavaScriptenvironment",

                   Flags:    append(append(append(nodeFlags,rpcFlags...), consoleFlags...), whisperFlags...),

                   Category:"CONSOLE COMMANDS",

                   Description:`

The Geth console is an interactive shellfor the JavaScript runtime environment

which exposes a node admin interface aswell as the Ðapp JavaScript API.

Seehttps://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.`,

         }

看最后的注释,说明来对了。它又会调用 localConsole,跳进去:

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

         defernode.Stop()

         //Attach to the newly started node and start the JavaScript console

         client,err := node.Attach()

         iferr != nil {

                   utils.Fatalf("Failedto 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)

         iferr != nil {

                   utils.Fatalf("Failedto start the JavaScript console: %v", err)

         }

         deferconsole.Stop(false)

         //If only a short execution was requested, evaluate and return

         ifscript := ctx.GlobalString(utils.ExecFlag.Name); script != "" {

                   console.Evaluate(script)

                   returnnil

         }

         //Otherwise print the welcome screen and enter interactive mode

         console.Welcome()

         console.Interactive()

         returnnil

}

前几行代码是不是有点熟悉,对,和上面介绍的默认的geth是一样的。瞬间明白的感觉是不是。

再插入一张调试图

看图片上第一个参数是绿色的console吧,正好查找到命令consoleCommand。

已经说过殊途同归,最后别管怎么折腾,都得调用app.go中的HandleAction,来达到调用的目的。

整体流程在创建节点并启动后就OK了。创建和启动节点这个是整个程序的核心,所以下一次接着再展开。

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/80379819