자바 개발자의 go-ethereum 소스 읽기: Day 2 by woojin.joe

View this thread on steempeak.com
· @woojin.joe · (edited)
$0.02
자바 개발자의 go-ethereum 소스 읽기: Day 2
# 자바 개발자의 go-ethereum 소스 읽기: Day 2
![main_logo](https://steemitimages.com/DQmWD8NAiZnbdwXrZArK2knyXvrxeQBDqFtrsWErvTXgnZ5/main_logo.jpg)

이 글은 자바 개발자의 go-ethereum(geth 클라이언트) 소스 분석기 시리즈의 연재 중 두 번째 글입니다. 앞으로 다음과 같은 내용으로 연재를 계획하고 있습니다.

1. [Day 01: Geth 1.0 소스 받기 및 코드 분석을 위한 개발환경 셋팅(VS Code)](https://steemit.com/kr/@woojin.joe/go-ethereum-geth-day-01)
2. **(본 글)** Day 02: CLI 라이브러리 기반  `geth`의 전체 실행 구조
3. [Day 03: VS Code를 사용한 geth 디버깅](https://steemit.com/kr/@woojin.joe/go-ethereum-geth-day-3)
...

전체 연재 목록은 아래 페이지에서 확인해 주세요
http://www.notforme.kr/block-chain/geth-code-reading

> Eng Version: [Java developer's adventure to analyze go-ethereum(geth): Day 02](https://steemit.com/ethereum/@woojin.joe/java-developer-s-analysis-of-go-ethereum-geth-day-02)

## 대상 독자

이 연재는 먼저 독자 분들이 적어도 Java와 같은 OOP 계열의 언어로 프로그래밍 경험이 있다는 것을 가정합니다. 또한 계정, 채굴 등 블록체인과 이더리움과 관련된 기초적인 개념을 알고 있다고 가정합니다.



## 다루는 내용

이 글에서는 `geth` 실행과 관련된 구조를 살펴봅니다. `geth`는 [cli](https://github.com/urfave/cli) 라이브러리를 통해서 CLI기반의 인터페이스를 제공합니다. cli 라이브러리의 구성은 `geth`의 아키텍쳐와 깊게 관련되어 있습니다. 따라서  `geth`가 어떻게 cli 라이브러리를 사용하여 실행하는지 이해하는 일은 `geth`의 전체 구조를 이해하는 핵심이 됩니다.

이번 글에서는 `golang`의 문법과 관련해서 `short variable declaration`과 구조체도 살펴볼 것입니다.



그럼 지난 시간에 마지막으로 살펴보았던 `utils.NewApp()` 함수에서부터 시작해 봅시다.



## NewApp 함수의 정의

`utils.NewApp` 함수는  `cmd/utils/flags.go` 파일에 선언되어 있습니다. 다음은 함수의 전체 [코드](https://github.com/ethereum/go-ethereum/blob/0cdc7647aaabb796d241ea257b2df2f0c26701d4/cmd/utils/flags.go#L77-L86)입니다. 먼저 코드부터 봅시다.

```go
// NewApp creates an app with sane defaults.
func NewApp(version, usage string) *cli.App {
	app := cli.NewApp()
	app.Name = filepath.Base(os.Args[0])
	app.Author = ""
	//app.Authors = nil
	app.Email = ""
	app.Version = version
	app.Usage = usage
	return app
}
```

\
함수의 시그니쳐를 보면 `NewApp` 함수는 `cli.App` 타입의 포인터를 반환하는 함수라는 것을 알 수 있습니다. 함수의 구현에서 가장 중요한 부분은 3번 라인입니다. 이 코드를 보면 `app` 인스턴스가 `cli.NewApp` 함수의 호출 결과로 초기화 된 것을 알 수 있습니다. 여기서  `app := cli.NewApp()`를 보면  뭔가 어색하지 않으신가요?  (저만 그런 것인가요…)  `app` 변수는 패키지에 선언된 변수가 아님에도 불구하고 아무런 선언 없이 이 함수 안에서 사용하고 있습니다.이제 `golang`의 새로운 구문을 하나 가볍게 살펴볼 떄가 되었습니다.



### Golang의 변수 선언

방금 본 코드에서, 다소 어색한 `:=` 연산자를 만났습니다. 이 연산자는 `golang`의 [short variable declaration](https://tour.golang.org/basics/10) 입니다. 변수를 선언할 때 `golang` 은 `var` 키워드를 먼저 쓰고 변수의 이름과 타입이 따라옵니다. 이와 관련된 예제 코드를 한 번 보겠습니다.

```go
var foo int = 10;

func bar() {
    foo := 10;  // You can omit `var` keyword and type information!
}
```

\
첫 문장의 변수 선언 방식과 달리 **함수 내에서는** 특별히 `var` 키워드와 타입 정보를 생략하고 변수를 선언할 수 있습니다. 다만 반드시 `short variable declaration`로 변수를 선언하려면  `:=` 로 변수에 값을 할당해야 합니다.



자 이제 `golang` 의 변수 선언방법을 확인했으니 다시 `utils.NewApp` 로 돌아가겠습니다. 



### Cli.NewApp 함수의 래퍼

사실 `utils.NewApp` 함수에서 우리는  `app` 인스턴스에 대한 구체적인 정보를 얻지 못했습니다. 이 함수는 그저  `cli.NewApp` 함수의 결과를 `app` 할당한 것이 전부입니다. 다른 말로 표현하면 `utils.NewApp` 함수는 `cli.NewApp` 함수의 래퍼 함수로 `cli.NewApp`의 반환결과를 추상화한 것이 전부입니다. 따라서 우리가 지난시간부터 조사했던 `app` 인스턴스의 본질은  `cli.NewApp` 함수 안에 있습니다.

그런데 `cli.NewApp` 함수의 구현은 어디에 있을까요? `cmd/utils/flags.go`파일의 상단부 `import` 부분을 보면 다음과 같은 힌트를 얻을 수 있습니다. 


```
"github.com/codegangsta/cli"
```
\
 `cli`는 `geth`에 있는 구현체가 아닙니다.  외부 라이브러리입니다. 이제 `app` 인스턴스의 구현부를 확인하기 위해서 위 주소로 Github에 들어가서 직접 확인해 보고자 합니다.




## CLI 라이브러리

먼저 https://github.com/codegangsta/cli에 방문하면 아래 페이지로 리다이렉트 됩니다.

- https://github.com/urfave/cli

이 리파지토리의 `README.md` 에 다음과 같은 공지가 있습니다.:

> This is the library formerly known as github.com/codegangsta/cli -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity.

공지내용은 우리가 확인하려던 라이브러리가 리다이렉트된 라이브러리로 변경되었다는 것을 말합니다. 사실 지난 시간부터 살펴본 `geth` 코드는 `v1.0.0`으로 과거 버전입니다. 따라서 외부 라이브러리의 변경사항이 충분히 있을 수 있습니다. `codegangsta/cli` 케이스도 마찬가지로 `urfave/cli` 로 변경되었습니다. 하지만 cli 라이브러리의 경우 인터페이스의 큰 차이가 없어 현재 시점에 `geth` 코드 `v1.0.0  ` 을 기준으로 살펴봐도 문제가 없습니다. 편한 마음으로  `urface/cli` 의 내부를 살펴봅시다.



### 실제 NewApp() 함수의 정의

`NewApp` 함수의 정의는 실제로는 `urfave/cli` 라이브러리 [app.go](https://github.com/urfave/cli/blob/8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff/app.go#L113-L125) 파일에 있습니다. 다음 함수의 구현부를 함께 봅시다.



```go
// NewApp creates a new cli Application with 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`  타입의 레퍼런스를 반환합니다. `App` 타입은 같은 파일 27번 라인에 아래와 같이 선언되어 있습니다. 

```go
// App is the main structure of a cli application. It is recommended that
// an app be created with the cli.NewApp() function
type App struct {
	// The name of the program. Defaults to path.Base(os.Args[0])
	Name string
	// Full name of command for help, defaults to Name
	HelpName string
	// Description of the program.
    Usage string
	// Text to override the USAGE section of help
	UsageText string
	// Description of the program argument format.
	ArgsUsage string
	// Version of the program
	Version string
	// Description of the program
	Description string
	// List of commands to execute
	Commands []Command
	// List of flags to parse
	Flags []Flag
	// Boolean to enable bash completion commands
    ...
```

\
이 코드와 주석을 읽어보면 `App` 타입의 주요 필드와 역할을 이해할 수 있습니다. 이 필드는 실제 `geth`의 CLI 환경을 지원하는데 사용되는 정보입니다. 이 시점에서 코드를 더 깊게 들어가기 전에 간단히  `golang`  의  구조체( `struct` ) 문법을 알아보겠습니다.

### Golang 구조체

구조체( `struct` )는 복수의 필드의 모음입니다. 다음 코드는 `golang` 의 구조체 선언 예입니다.

```go
type User struct {
	name sting
	age int
}
```

\
 `C` 언어의 구조체 문법과 크게 다르지 않습니다. `User` 타입의 객체 하나를 생성하기 위해서 아래 예제와 같이 구조체 이름과 함께 `{ }` 안에 필드의 이름과 실제 값의 쌍을 선언하면 됩니다.

```go
person{name: "Alice", age: 30}
```

\
`golang` 의 구조체와 관련된 좀 더 자세한 정보는 다음  경로의 컨텐츠를 참조해 주세요:

- https://gobyexample.com/structs
- https://tour.golang.org/moretypes/1



여기서 이해할 수 있는 `cli.NewApp()`  의 역할은 `App` 구조체를 구현하고 레퍼런스로 반환하는 것이 전부입니다. 

## 목표 재점검

지금까지 우리는 `geth`가 어떻게 동작하는지 이해하기 위해 긴 여정을 떠나 왔씁니다. 이제 거의 다 와갑니다. 마지막 종착점에 이르기 전에 우리의 여정을 정리해 봅시다.



우리는 먼저 `geth`의 최초 실행 포인트인 메인 함수를 찾았습니다.  이어서 `main` 함수에서 사용되는 `app`  인스턴스의 존재를 찾아 해맸습니다. `app` 인스턴스가 중요한 이유는 `main`  함수 안에서 `Run`  메서드를 실행하는 것이 `geth`의 출발점이기 때문입니다. `app` 인스턴스는 겉으로는  `utils.NewApp()` 의 결과 값이지만 실제로는 외부라이브러리인 `cli`가 반환한 결과라는 것을 알아냈습니다.



`geth` 는 CLI 환경의 도구 입니다. `cli` 라이브러리는 CLI 기반의  소프트웨어에 필요한 공통적인 기능을 제공하는 일종의 프레임워크와 같습니다. [README.md](https://github.com/urfave/cli/blob/master/README.md) 파일의 내용에서 `cli` 라이브러리에 대한 소개를 다음과 같이 볼 수 있습니다.

> cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.



소개를 한마디로 요약하면 CLI 개발을 위한 효율적인 툴입니다. 이제 마지막으로 `geth`실행의 근간이 되는 `cli` 라이브러를 살펴봅시다.

## CLI 라이브러리의 주요 컴포넌트

우리는 전에 `App` 타입의 선언을 살펴봤습니다. `geth` 의 실행과 관련해서 꾸준히가장 중요한 2개의 컴포넌트가 있습니다. 각각에 대해서 한번 살펴봅시다. 

1. []Command
2. []Flag





### Command

`Command` 타입은 `metadata`와 캡쳐 함수가 있습니다. 예를 들어 여러분이 `geth`가 설치된 컴퓨터의 터미널에서  `geth version ` 을  입력하면 다음과 같은 응답을 볼 수 있습니다.



```sh
$ geth version
Geth
Version: 1.8.5-unstable
Git Commit: b15eb665ee3c373a361b050cd8fc726e31c4a750
Architecture: amd64
Protocol Versions: [63 62]
Network Id: 1
Go Version: go1.10
Operating System: darwin
GOPATH=(Your own GOPATH...)
GOROOT=(Your own GOROOT...)
```

\
이 명령은 `cli`'s `Command` 타입을 사용해서 출력된 것입니다. 전체 [코드](https://github.com/ethereum/go-ethereum/blob/744428cb03ea8de8f219708f57d2e197acb6689b/cmd/geth/misccmd.go#L60-L69)는 최신 커밋 기준([744428c](https://github.com/ethereum/go-ethereum/tree/744428cb03ea8de8f219708f57d2e197acb6689b))으로 다음과 같습니다.



```
	versionCommand = cli.Command{
		Action:    utils.MigrateFlags(version),
		Name:      "version",
		Usage:     "Print version numbers",
		ArgsUsage: " ",
		Category:  "MISCELLANEOUS COMMANDS",
		Description: `
The output of this command is supposed to be machine-readable.
`,
	}
```

\
코드에서 중요한 부분은 라인 2-3번 입니다. 2번 라인의  `Action` 필드는 실제 `version` 명령요청을 실행할 핸들러 함수로 선언되어 있습니다. 3번 라인의 `Name` 은 CLI 환경에서 실행할 명령의 이름을 의미합니다.

`version` 외에 `geth`에서 사용되는 모든 CLI 명령은 위와 유사한 형태로 `cli` 라이브러리를 사용하여 등록됩니다. 이제 우리는 손쉽게 `geth`의 각 명령의 구현체가 어디인지 찾을 수 있습니다.



### Flag

`Flag`  타입은 `Command`보다 이해하기 더 쉽습니다. `Flag`는 단순히 불리언 타입의 flag 역할입니다. `geth` 명령을 CLI에서 실행할 때, 다양한 인자를 옵션으로 주어 특정 기능의 활성화/비활성화를 제어할 수 있습니다. 바로 이 때 각 인자가 `geth` 코드 안에서는 `Flag` 타입으로 맵핑됩니다. 마찬가지로  `Flag`의 코드 역시 `Command`와 유사한 구조를 같습니다. 다음은 rpc 기능을 사용할 때 줄 수 있는 `rpc` 인자에 대응하는 `Flag` 선언 [코드](https://github.com/ethereum/go-ethereum/blob/744428cb03ea8de8f219708f57d2e197acb6689b/cmd/utils/flags.go#L376-L379) 입니다. 역시 다음 코드 또한 커밋 [744428c](https://github.com/ethereum/go-ethereum/tree/744428cb03ea8de8f219708f57d2e197acb6689b) 기준입니다.



```Go
	RPCEnabledFlag = cli.BoolFlag{
		Name:  "rpc",
		Usage: "Enable the HTTP-RPC server",
	}
```





### Hook Interface

`cli` 는 애플리케이션의 실행과 관련하여 3가지 이벤트의 훅 인터페이스를 제공합니다. 

1. app.Before:   `app` 인스턴스가 시작하기 전의 후킹할 수 있는 인터페이스.
2. app.Action: 애플리케이션의 본 로직 
3. app.After:  `app` 인스턴스가 종료되기 전의 후킹할 수 있는 인터페이스.

앞으로 각 핸들러가 실제로 `geth` 에서 어떻게 사용되는지 살펴보게 될 것입니다. 여기서는 `app.Action` 이 제일 중요한 부분이라는 점만 기억하면 됩니다. 왜냐하면  `app.Action` 가 실제 주어진 인자와 상관없이 `geth`가 실행하는 메인 함수이기 때문입니다. 



### 트리거

마지막으로 남은 건 `cli`  에 등록한 `Command`와 `Flag` 와 후킹한 구현체를 언제 실행하는지가 중요합니다. 지난 글에서 최초 진입점인  `main` 함수를  떠올려볼까요?



```go
func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
```
\
코드를 보면 `main` 함수에서  `app.Run` 함수를 실행하는 걸 지난 글에서 확인했습니다. `main` 함수가 터미널에서 인자로 받은 인자인 `osArgs  `를 전달하면서  `Run` 함수를 실행합니다. 바로 이 코드가 실제 `cli.App`을 트리거하는 액션입니다. 결론적으로 터미널에서 `geth` 명령과 함께 전달한 인자들이 `app.Run` 함수를 통해 전달되어 등록된 `Command`와 `Flag`에 맞게 `geth` 가 동작하게 됩니다.


정리하면 지금까지 살펴본 `geth`와 `cli` 라이브러리는 다음의 그림으로 요약할 수 있습니다.

![d02_cur_arch.png](https://steemitimages.com/DQmdBLZ4t4gRT6MsrVkSXjQnGmLzs81J7xTnhFL5kPgG1rZ/d02_cur_arch.png)



## Conclusion

오늘은  [cli](https://github.com/urfave/cli) 라이브러리를 기반으로 하는 `geth` 실행 구조를 살펴봤습니다. 우리는 또한 `golang`의  `short variable declaration` 문법과 구조체도 가볍게 알아봤습니다. 



오늘의 내용은 앞으로 `geth` 소스를 분석하는 아주 중요한 틀이 됩니다. `geth`의 전체 실행 흐름을 파악했기 때문에 굳이 `v1.0.0` 버전에 머물러 있을 필요가 없습니다. 따라서 이제 `geth`의 최신 소스로 돌아가고자 합니다.  



```Sh
$ git checkout master
```



다음 시간에는,  `geth`를 직접 빌드하고 매뉴얼에 따라 로컬 네트워크를 실행해 볼 예정입니다. 그리고 테스트 한 내용을 직접 코드에서 어떻게 구현했는지 살펴보겠습니다.



꾸준히 포기하지 않고 공부한 내용을 연재로 이어나갈 수 있도록 응원(?) 부탁 드립니다. ^^
👍  , , ,
properties (23)
post_id45,273,406
authorwoojin.joe
permlinkgo-ethereum-geth-day-02
categorykr
json_metadata"{"links": ["https://steemit.com/kr/@woojin.joe/go-ethereum-geth-day-01", "https://steemit.com/kr/@woojin.joe/go-ethereum-geth-day-3", "http://www.notforme.kr/block-chain/geth-code-reading", "https://steemit.com/ethereum/@woojin.joe/java-developer-s-analysis-of-go-ethereum-geth-day-02", "https://github.com/urfave/cli", "https://github.com/ethereum/go-ethereum/blob/0cdc7647aaabb796d241ea257b2df2f0c26701d4/cmd/utils/flags.go#L77-L86", "https://tour.golang.org/basics/10", "https://github.com/codegangsta/cli\uc5d0", "https://github.com/urfave/cli/blob/8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff/app.go#L113-L125", "https://gobyexample.com/structs", "https://tour.golang.org/moretypes/1", "https://github.com/urfave/cli/blob/master/README.md", "https://github.com/ethereum/go-ethereum/blob/744428cb03ea8de8f219708f57d2e197acb6689b/cmd/geth/misccmd.go#L60-L69", "https://github.com/ethereum/go-ethereum/tree/744428cb03ea8de8f219708f57d2e197acb6689b", "https://github.com/ethereum/go-ethereum/blob/744428cb03ea8de8f219708f57d2e197acb6689b/cmd/utils/flags.go#L376-L379"], "format": "markdown", "app": "steemit/0.1", "image": ["https://steemitimages.com/DQmWD8NAiZnbdwXrZArK2knyXvrxeQBDqFtrsWErvTXgnZ5/main_logo.jpg"], "tags": ["kr", "kr-dev", "go", "ethereum", "geth"]}"
created2018-04-25 03:27:30
last_update2018-04-28 09:11:27
depth0
children1
net_rshares4,094,703,835
last_payout2018-05-02 03:27:30
cashout_time1969-12-31 23:59:59
total_payout_value0.024 SBD
curator_payout_value0.000 SBD
pending_payout_value0.000 SBD
promoted0.000 SBD
body_length10,365
author_reputation131,488,838,444
root_title"자바 개발자의 go-ethereum 소스 읽기: Day 2"
beneficiaries[]
max_accepted_payout1,000,000.000 SBD
percent_steem_dollars10,000
author_curate_reward""
vote details (4)
@noelkim ·
좋은 글 감사드립니다
👍  
properties (23)
post_id47,330,823
authornoelkim
permlinkre-woojinjoe-go-ethereum-geth-day-02-20180507t055534446z
categorykr
json_metadata"{"app": "steemit/0.1", "tags": ["kr"]}"
created2018-05-07 05:55:36
last_update2018-05-07 05:55:36
depth1
children0
net_rshares0
last_payout2018-05-14 05:55:36
cashout_time1969-12-31 23:59:59
total_payout_value0.000 SBD
curator_payout_value0.000 SBD
pending_payout_value0.000 SBD
promoted0.000 SBD
body_length11
author_reputation0
root_title"자바 개발자의 go-ethereum 소스 읽기: Day 2"
beneficiaries[]
max_accepted_payout1,000,000.000 SBD
percent_steem_dollars10,000
author_curate_reward""
vote details (1)