以太坊的源码启动和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了。创建和启动节点这个是整个程序的核心,所以下一次接着再展开。