본문 바로가기
Blockchain/Ethereum

[ Geth ] 파헤치기 3 번 - geth Main 함수 살펴보기

by 기저귀찬개발자 2019. 6. 14.

관련글

2019/04/15 - [Blockchain/Ethereum] - [ Geth ] 파헤치기 1 번 - Geth 실행 및 디버깅 방법

2019/05/06 - [Blockchain/Ethereum] - [ Geth ] 파헤치기 2 번 - 시작 Log 분석하기

2019/06/14 - [Blockchain/Ethereum] - [ Geth ] 파헤치기 3 번 - geth Main 함수 살펴보기

2019/06/24 - [Blockchain/Ethereum] - [ Geth ] 파헤치기 4 번 - geth 함수 살펴보기

 

geth를 실행시키면 어떤 과정을 처리하는지 소스를 통하여 알아보자 

아래 소스는 /cmd/geth/main.go 에 있는 main 함수이다.

우리가 터미널에서 geth를 실행시키면 아래 함수가 호출될 것이다.

func main() {

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

        fmt.Fprintln(os.Stderr, err)

        os.Exit(1)

    }

}

 

아래는 main 함수에 들어가는 package와 변수 init 함수의 내용이다.

이중에서 중요한 점은 go 언어의 특성상 main 함수 이전에 init 함수가 먼저 호출된다.

그리고 포함된 package들의 init 함수 또한 모두 실행된 뒤에 main 함수가 호출된다.

package main

import ( 
	"fmt"
	"math"
	"os"
	godebug "runtime/debug"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/elastic/gosigar"
	"github.com/ethereum/go-ethereum/accounts"
	"github.com/ethereum/go-ethereum/accounts/keystore"
	"github.com/ethereum/go-ethereum/cmd/utils"
	
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/console"
	"github.com/ethereum/go-ethereum/eth"
	"github.com/ethereum/go-ethereum/eth/downloader"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/internal/debug"
	"github.com/ethereum/go-ethereum/log"
	"github.com/ethereum/go-ethereum/metrics"
	"github.com/ethereum/go-ethereum/node"
	cli "gopkg.in/urfave/cli.v1"
)

const (
	clientIdentifier = "geth" // Client identifier to advertise over the network
)

var (
	// Git SHA1 commit hash of the release (set via linker flags)
	gitCommit = ""
	// The app that holds all commands and flags.

	app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
	// flags that configure the node
	nodeFlags = []cli.Flag{
		utils.IdentityFlag,
		생략...
		utils.EVMInterpreterFlag,
		configFileFlag,
	}

	rpcFlags = []cli.Flag{
		utils.RPCEnabledFlag,
		생략...
		utils.RPCGlobalGasCap,
	}

	whisperFlags = []cli.Flag{
		utils.WhisperEnabledFlag,
		생략...
		utils.WhisperRestrictConnectionBetweenLightClientsFlag,
	}

	metricsFlags = []cli.Flag{
		utils.MetricsEnabledFlag,
		생략...
		utils.MetricsInfluxDBTagsFlag,
	}
)

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-2019 The go-ethereum Authors"
	app.Commands = []cli.Command{
		// See chaincmd.go:
		initCommand,
		생략...
		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.Flags = append(app.Flags, metricsFlags...)

	app.Before = func(ctx *cli.Context) error {
		생략...
		return nil
	}

	app.After = func(ctx *cli.Context) error {
		생략...
		return nil
	}
}

 

모든 패키지들을 다 볼 수 없으니 main 함수의 첫줄인 app.Run() 함수에 대해 알아보자

var 변수 선언부를 보면 utils 패키지의 NewApp을 호출 하는 것을 볼 수 있다. 

 

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

 

/cmd/utils/flags.go 파일을 살펴보면 NewApp 함수를 확인할 수 있다.

반환값으로는 cli.App 를 반환하게 되는데 cli 패키지는 https://gopkg.in/urfave/cli.v1 해당 페이지에서 확인할 수 있다.

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.VersionWithMeta

	if len(gitCommit) >= 8 {
		app.Version += "-" + gitCommit[:8]
	}
	app.Usage = usage

	return app
}

 

cli의 NewApp까지 살펴본다면 https://github.com/urfave/cli/blob/v1.20.0/app.go 해당 페이지를 살펴보면 app이라는 

실행 환경에 대한 정보가 들어있는 객체를 반환하는 점을 확인할 수 있다. 

그 중 반환 객체의 구조체를 살펴보자면 아래와 같다.

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

 

다시 /geth/main.go의 init 함수 부문으로 돌아와서 확인하여 보면 해당 객체에 여러가지 설정 정보들과 플래그 값들을 

넘겨주고 있는 것을 확인할 수 있다.

위에서 생략하였던 부분을 살펴보면 app.Before와 app.After 를 보면 이름으로도 알 수 있듯이 app.Run 실행시 앞 뒤로 호출하는

함수이다. 

Before 단계에서 실행 옵션에 따라 네트워크 분류를 하고 있다. 중간에 gosigar는 sigar api 를 go 언어로 구현해놓은 부분으로

해당 객체를 통해서 OS의 각종 정보들에 접근할 수 있다.

After 단계에서는 설정 리딩 부분을 종료 함수를 설정한다.

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

	logdir := ""
	if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
		logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
	}
	if err := debug.Setup(ctx, logdir); err != nil {
		return err
	}
	// If we're a full node on mainnet without --cache specified, bump default cache allowance
	if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {
		// Make sure we're not on any supported preconfigured testnet either
		if !ctx.GlobalIsSet(utils.TestnetFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) {
			// Nope, we're really on mainnet. Bump that cache up!
			log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096)
			ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096))
		}
	}
	// If we're running a light client on any network, drop the cache to some meaningfully low amount
	if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) {
		log.Info("Dropping default light client cache", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 128)
		ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128))
	}
	// Cap the cache allowance and tune the garbage collector
	var mem gosigar.Mem

	if err := mem.Get(); err == nil {
		allowance := int(mem.Total / 1024 / 1024 / 3)
		if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance {
			log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
			ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance))
		}
	}
	// Ensure Go's GC ignores the database cache for trigger percentage
	cache := ctx.GlobalInt(utils.CacheFlag.Name)
	gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))

	log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
	godebug.SetGCPercent(int(gogc))

	// Start metrics export if enabled
	utils.SetupMetrics(ctx)

	// Start system runtime metrics collection
	go metrics.CollectProcessMetrics(3 * time.Second)

	return nil
}

app.After = func(ctx *cli.Context) error {
	debug.Exit()
	console.Stdin.Close() // Resets terminal mode.
	return nil
}

 

init 단계 단계가 끝나고 메인 함수의 첫번째 줄의 app.Run이 실행되면  https://github.com/urfave/cli/blob/v1.20.0/app.go 의 

run이 실행되고 init 단계에서 app.Action 에 정의된 geth가 실행된다.

// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
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 {
			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
}

 

댓글