지난 글에서 geth 클라이언트를 실행할 때 어떤 일이 일어나는지, 시작점부터 실행 순서대로 보도록 하겠다고 했다.
하지만 코드를 살펴본 결과 들어가기에 앞서 geth에서 사용하는 몇 가지 타입에 대해 살펴보면 이해가 더 쉬울 것 같다.
이번 글 부터는 이해를 위해 필요한 코드가 있으면 전부 가져올 예정이라 앞으로의 글들은 좀 길 예정이다.
cli 패키지를 살짝 알아보고 아래 4가지 타입을 먼저 확인해보도록 하겠다.
- cli 패키지의 App, Context
- Node 패키지의 node
- ethapi 패키지의 Backend
cli 패키지
import 문을 보면 https://github.com/urfave/cli/v2 에서 가져온 것을 확인할 수 있는데,,
# https://cli.urfave.org/
urfave/cli is a simple, fast, and fun package for building command line apps in Go
Go에서 command line app을 만드는 가볍고 빠르고 재밌는 패키지라고 한다.
스타 수가 2만개가 넘긴 하지만, 월드 컴퓨터인 이더리움이 외부 패키지에 의존하고 있다니 보안적으로 취약한 거 아닌가 싶다가서도 모든 기능을 자체적으로 구현할 수는 없지 싶었다.
// https://cli.urfave.org/v2/getting-started/
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "boom",
Usage: "make an explosive entrance",
Action: func(*cli.Context) error {
fmt.Println("boom! I say!")
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
이건 패키지에서 지원하는 docs에서 가져온 예시로 이런 형태로 사용한다.
이 형태는 후에 이더리움 앱의 시작점에서도 볼 수 있다.
cli.App
App이라는 타입은 아래와 같다.
// 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
// DefaultCommand is the (optional) name of a command
// to run if no command names are passed as CLI arguments.
DefaultCommand string
// List of commands to execute
Commands []*Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command and help flag
HideHelp bool
// Boolean to hide built-in help command but keep help flag.
// Ignored if HideHelp is true.
HideHelpCommand bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// categories contains the categorized commands and is populated on app startup
categories CommandCategories
// flagCategories contains the categorized flags and is populated on app startup
flagCategories FlagCategories
// An action to execute when the shell completion flag is set
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The action to execute when no subcommands are specified
Action ActionFunc
// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
// Execute this function if a usage error occurs
OnUsageError OnUsageErrorFunc
// Execute this function when an invalid flag is accessed from the context
InvalidFlagAccessHandler InvalidFlagAccessFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []*Author
// Copyright of the binary if any
Copyright string
// Reader reader to write input to (useful for tests)
Reader io.Reader
// Writer writer to write output to
Writer io.Writer
// ErrWriter writes error output
ErrWriter io.Writer
// ExitErrHandler processes any error encountered while running an App before
// it is returned to the caller. If no function is provided, HandleExitCoder
// is used as the default behavior.
ExitErrHandler ExitErrHandlerFunc
// Other custom info
Metadata map[string]interface{}
// Carries a function which returns app specific info.
ExtraInfo func() map[string]string
// CustomAppHelpTemplate the text template for app help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomAppHelpTemplate string
// SliceFlagSeparator is used to customize the separator for SliceFlag, the default is ","
SliceFlagSeparator string
// DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false
DisableSliceFlagSeparator bool
// Boolean to enable short-option handling so user can combine several
// single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
// Enable suggestions for commands and flags
Suggest bool
// Allows global flags set by libraries which use flag.XXXVar(...) directly
// to be parsed through this library
AllowExtFlags bool
// Treat all flags as normal arguments if true
SkipFlagParsing bool
didSetup bool
separator separatorSpec
rootCommand *Command
}
App은 Cli 앱의 메인 구조로 cli.NewApp()을 통해 만들기를 권장한다고 한다.
그럼 후에 cli.NewApp()을 통해 App을 만들어주는 과정이 있을 것이라고 추측해 볼 수 있다.
App타입에는 다양한 필드들이 있는데 짚고 넘어가면 좋을 것은
Commands, Flags, Action, Before, After 이다.
Commands []*Command
Commands 필드는 Command 타입의 포인터로 구성된 슬라이스인데 Command 타입은 아래와 같다.
Command 타입은 우리가 geth를 실행할 때 입력하는 명령어를 정의해 놓은 것이다.
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// Custom text to show on USAGE section of help
UsageText string
// A longer explanation of how the command works
Description string
// A short description of the arguments of this command
ArgsUsage string
// The category the command is part of
Category string
// The function to call when checking for bash command completions
BashComplete BashCompleteFunc
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before BeforeFunc
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After AfterFunc
// The function to call when this command is invoked
Action ActionFunc
// Execute this function if a usage error occurs.
OnUsageError OnUsageErrorFunc
// List of child commands
Subcommands []*Command
// List of flags to parse
Flags []Flag
flagCategories FlagCategories
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command and help flag
HideHelp bool
// Boolean to hide built-in help command but keep help flag
// Ignored if HideHelp is true.
HideHelpCommand bool
// Boolean to hide this command from help or completion
Hidden bool
// Boolean to enable short-option handling so user can combine several
// single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
commandNamePath []string
// CustomHelpTemplate the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomHelpTemplate string
// categories contains the categorized commands and is populated on app startup
categories CommandCategories
// if this is a root "special" command
isRoot bool
separator separatorSpec
}
우리가 터미널 창에 geth 관련 명령어를 입력하면 아래와 같이 하게 된다.
이 때 'COMMANDS'에 표시된 account 등등이 모두 Command 타입인 것이다.
그리고 Command 타입에 Subcommands 라는 필드가 있다.
예를 들어 ' geth account list' 같이 account 아래에도 하위 커맨드가 있는데 그것 또한 Command 타입으로 되어 있다.
NAME:
geth - the go-ethereum command line interface
USAGE:
geth [global options] command [command options] [arguments...]
COMMANDS:
account Manage accounts
attach Start an interactive JavaScript environment (connect to node)
...(생략)
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
ACCOUNT
--allow-insecure-unlock (default: false)
Allow insecure account unlocking when account-related RPCs are exposed by http
--keystore value
Directory for the keystore (default = inside the datadir)
(생략)
Flags []Flag
geth를 실행하면서 옵션이나 매개변수를 전달하게 되는데 이 때 쓰이는 것이 플래그다.
아래와 같이 인터페이스가 있으며 패키지에서 기본적으로 제공하는 flag(stringflag, intflag, boolflag 등)도 있고 사용자 정의 flag를 만들어서 사용할 수도 있다.
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet) error
Names() []string
IsSet() bool
}
예를 들자면 아래에서 GLOBAL OPTIONS 가 flag라고 보면 된다.
NAME:
geth - the go-ethereum command line interface
USAGE:
geth [global options] command [command options] [arguments...]
COMMANDS:
...(생략)
GLOBAL OPTIONS:
(생략)
즉,
global options (flag 타입) - cli의 flag
command (command타입) - cli의 command
subcommand (command 타입) - command 의 subcommand
command options (flag 타입) - command 의 flag
위와 같은 형태를 띄고 있다.
cmd/utils/flags.go 에서 현재 사용하는 모든 flag를 확인할 수 있고, DirectoryFlag가 있는데 이건 cli 패키지에서 제공하는 것이 아닌 직접 만든 것이다.
Action, Before, After
Action 은 메인 액션을 정의해주고, before과 after는 메인 액션 혹은 서브커맨드 전 후로 실행되는 함수라고 생각하면 될 것 같다.
중요한 부분은 Action이다. (Action == 메인)
Action ActionFunc
// ActionFunc is the action to execute when no subcommands are specified
type ActionFunc func(*Context) error
Before BeforeFunc
// BeforeFunc is an action to execute before any subcommands are run, but after
// the context is ready if a non-nil error is returned, no subcommands are run
type BeforeFunc func(*Context) error
After AfterFunc
// AfterFunc is an action to execute after any subcommands are run, but after the
// subcommand has finished it is run even if Action() panics
type AfterFunc func(*Context) error
cli.Context
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific args and
// parsed command-line options.
type Context struct {
context.Context
App *App
Command *Command
shellComplete bool
flagSet *flag.FlagSet
parentContext *Context
}
context 타입은 cli application에서 각 핸들러에 전달되는 타입으로 특정한 인자를 검색하거나 명령줄 옵션을 파싱하는데 사용된다.
App - 현재 실행 중인 앱에 대한 포인터
Command - 현재 실행 중인 커맨드에 대한 포인터
Context 는 아래와 같은 interface로 이루어져있다.
// 주석이 많아 주석 전부 제거
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
즉 cli.Context의 경우 어떠한 실행이 있을 때 전달되는 것 정도로 이해하면 될 것 같다.
node.Node
Node는 서비스를 등록할 수 있는 컨테이너다.
그러니까 흔히 우리가 얘기하는 노드,, 이더리움의 노드 그 자체인 것이다.
이벤트 구독, 설정, 계정 관리, 로깅, 등등 다양한 기능과 노드에 대한 정보를 갖고 있다고 보면 될 것 같다.
// Node is a container on which services can be registered.
type Node struct {
eventmux *event.TypeMux
config *Config
accman *accounts.Manager
log log.Logger
keyDir string // key store directory
keyDirTemp bool // If true, key directory will be removed by Stop
dirLock *flock.Flock // prevents concurrent use of instance directory
stop chan struct{} // Channel to wait for termination notifications
server *p2p.Server // Currently running P2P networking layer
startStopLock sync.Mutex // Start/Stop are protected by an additional lock
state int // Tracks state of node lifecycle
lock sync.Mutex
lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
rpcAPIs []rpc.API // List of APIs currently provided by the node
http *httpServer //
ws *httpServer //
httpAuth *httpServer //
wsAuth *httpServer //
ipc *ipcServer // Stores information about the ipc http server
inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
databases map[*closeTrackingDB]struct{} // All open databases
}
ethapi.Backend
백엔드 타입은 api service 를 제공해주는 인터페이스라고 한다.
백엔드라 하면 사용자가 직접적으로 보거나 상호작용하지 않는 서버, 애플리케이션, 데이터베이스 등의 시스템이라고 볼 수 있는데 그런 시스템과 상호작용할 수 있는 api에 대한 인터페이스라고 생각하면 될 것 같다.
backend는 블록체인의 데이터 관리, 네트워크 통신, 트랜잭션 처리 등 다양한 기능을 하는 부분이 담겨있다고 보면 될 것 같다.
// Backend interface provides the common API services (that are provided by
// both full and light clients) with access to necessary functions.
type Backend interface {
// General Ethereum API
SyncProgress() ethereum.SyncProgress
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error)
ChainDb() ethdb.Database
AccountManager() *accounts.Manager
ExtRPCEnabled() bool
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
UnprotectedAllowed() bool // allows only for EIP155 transactions.
// Blockchain API
SetHead(number uint64)
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
CurrentHeader() *types.Header
CurrentBlock() *types.Header
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
PendingBlockAndReceipts() (*types.Block, types.Receipts)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetTd(ctx context.Context, hash common.Hash) *big.Int
GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int)
TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction)
TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
// This is copied from filters.Backend
// eth/filters needs to be initialized from this backend type, so methods needed by
// it must also be included here.
GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error)
GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
}
정리
이 정도만 알면 이러한 타입들을 볼 때 무슨 일을 하는 지 어렴풋이 알고 있기 때문에 geth가 처음 시작될 때 어떻게 되는지도 이해하기 훨씬 편하다.
후에 node.Node 타입은 stack이라는 이름으로 쓰이고 ethapi.Backend 타입은 backend 라는 이름으로 쓰이게 될 것이다.
그럼 다음 글에서는 진짜 geth의 시작점으로 가서 코드를 순차적으로 보도록 하겠다.
'blockchain > geth 소스코드 분석' 카테고리의 다른 글
Geth 소스코드 분석 4 - makeFullNode 1편 - makeConfigNode() (0) | 2024.02.07 |
---|---|
Geth 소스코드 분석 3 - 시작지점 살펴보기 (0) | 2024.01.30 |
Geth 소스코드 분석 1 - 시작하기 및 전체 구조 (0) | 2024.01.27 |