저번 글에서 geth 클라이언트를 실행하면 크게 세 가지 함수인 prepare, makeFullNode, startNode가 실행되는 것을 보았다.
이번 글에서는 makeFullNode 함수를 볼 예정이다.
// makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend)
makeFullNode 함수의 주석을 보면 geth 설정을 불러오고 이더리움 백엔드를 만든다고 한다.
이더리움 백엔드는 타입에 대해 다룬 두 번째 글에서 살펴본 backend 타입을 생각하면 된다.
cli.Context를 받아서 node.Node와 ethapi.Backend를 리턴해준다.
그럼 전체 코드를 한 번 보자
// makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
stack, cfg := makeConfigNode(ctx)
if ctx.IsSet(utils.OverrideCancun.Name) {
v := ctx.Uint64(utils.OverrideCancun.Name)
cfg.Eth.OverrideCancun = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information
if eth != nil { // The 'eth' backend may be nil in light mode
var protos []string
for _, p := range eth.Protocols() {
protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version))
}
metrics.NewRegisteredGaugeInfo("geth/info", nil).Update(metrics.GaugeInfoValue{
"arch": runtime.GOARCH,
"os": runtime.GOOS,
"version": cfg.Node.Version,
"protocols": strings.Join(protos, ","),
})
}
// Configure log filter RPC API.
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
// Configure GraphQL if requested.
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
}
// Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
}
// Configure full-sync tester service if requested
if ctx.IsSet(utils.SyncTargetFlag.Name) {
hex := hexutil.MustDecode(ctx.String(utils.SyncTargetFlag.Name))
if len(hex) != common.HashLength {
utils.Fatalf("invalid sync target length: have %d, want %d", len(hex), common.HashLength)
}
utils.RegisterFullSyncTester(stack, eth, common.BytesToHash(hex))
}
// Start the dev mode if requested, or launch the engine API for
// interacting with external consensus client.
if ctx.IsSet(utils.DeveloperFlag.Name) {
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), eth)
if err != nil {
utils.Fatalf("failed to register dev mode catalyst service: %v", err)
}
catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon)
stack.RegisterLifecycle(simBeacon)
} else {
err := catalyst.Register(stack, eth)
if err != nil {
utils.Fatalf("failed to register catalyst service: %v", err)
}
}
return stack, backend
}
makeFullNode 함수에서 실행하는 함수는 아래와 같다.
- makeconfigNode
- RegisterEthService
- RegisterFilterAPI
- RegisterGraphQLService
- RegisterEthStatsService
- RegisterFullSyncTester
- RegisterSimulatedBeaconAPIs
- RegisterLifecycle
이름에서 볼 수 있듯이 register 해주는게 makeFullNode 함수의 역할이라고 할 수 있다.
makeFullNode의 반환 타입인 Node 와 Backend 는 어디서 올까?
// makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
stack, cfg := makeConfigNode(ctx)
// 생략
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// 생략
return stack, backend
}
makeConfigNode 에서 Node를 만들어주고
utils.RegisterEthService 에서 backend를 만들어준다.
그럼 함수들을 하나씩 살펴보자
makeConfigNode
geth 설정을 불러오고 빈 노드를 만들어주는 함수다.
// makeConfigNode loads geth configuration and creates a blank node instance.
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
cfg := loadBaseConfig(ctx)
stack, err := node.New(&cfg.Node)
if err != nil {
utils.Fatalf("Failed to create the protocol stack: %v", err)
}
// Node doesn't by default populate account manager backends
if err := setAccountManagerBackends(stack.Config(), stack.AccountManager(), stack.KeyStoreDir()); err != nil {
utils.Fatalf("Failed to set account manager backends: %v", err)
}
utils.SetEthConfig(ctx, stack, &cfg.Eth)
if ctx.IsSet(utils.EthStatsURLFlag.Name) {
cfg.Ethstats.URL = ctx.String(utils.EthStatsURLFlag.Name)
}
applyMetricConfig(ctx, &cfg)
return stack, cfg
}
loadBaseConfig(ctx)
커맨드라인 파라미터들과 config 파일을 읽어서 gethConfig를 리턴해준다.
// loadBaseConfig loads the gethConfig based on the given command line
// parameters and config file.
func loadBaseConfig(ctx *cli.Context) gethConfig {}
gethConfig 타입은 아래와 같다.
gethConfig에 대해 조금 말을 붙이자면, 우리가 보고 있는 것은 geth 클라이언트이다.
geth 아래에 node와 eth가 있으니 gethConfig 타입을 통해 해당 설정들을 한 번에 관리해주는 것이다.
type gethConfig struct {
Eth ethconfig.Config
Node node.Config
Ethstats ethstatsConfig
Metrics metrics.Config
}
전체 함수를 보자면 default 설정을 불러와 주고, 설정파일에 있는 설정을 불러와준다.
그리고 이러한 설정을 가지고 node만의 설정을 해준다.
func loadBaseConfig(ctx *cli.Context) gethConfig {
// Load defaults.
cfg := gethConfig{
Eth: ethconfig.Defaults,
Node: defaultNodeConfig(),
Metrics: metrics.DefaultConfig,
}
// Load config file.
if file := ctx.String(configFileFlag.Name); file != "" {
if err := loadConfig(file, &cfg); err != nil {
utils.Fatalf("%v", err)
}
}
// Apply flags.
utils.SetNodeConfig(ctx, &cfg.Node)
return cfg
}
노드만의 설정이란..
node.Node 타입의 config를 설정해주는 것인데 파라미터로 context와 config 포인터를 받음을 알 수 있다.
context에는 우리가 geth 실행할 때 넣어준 인자들이 있다.
그 인자를 통해 적절한 설정을 해주는 것이다.
SetNodeConfig 함수를 보자면 아래와 같이 node.Config를 설정해준다 라고 보면된다.
// SetNodeConfig applies node-related command line flags to the config.
func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
SetP2PConfig(ctx, &cfg.P2P)
setIPC(ctx, cfg)
setHTTP(ctx, cfg)
setGraphQL(ctx, cfg)
setWS(ctx, cfg)
setNodeUserIdent(ctx, cfg)
SetDataDir(ctx, cfg)
setSmartCard(ctx, cfg)
if ctx.IsSet(JWTSecretFlag.Name) {
cfg.JWTSecret = ctx.String(JWTSecretFlag.Name)
}
.. 생략
node.New()
cfg := loadBaseConfig(ctx)
stack, err := node.New(&cfg.Node)
loadBaseConfig() 함수를 통해 노드의 기본 설정을 세팅해줬다.
설정을 세팅했으니 이제 설정을 바탕으로 노드를 만드는 것이다.
node.New() 함수를 통해 P2P 노드를 만들어준다.
New() 함수는 꽤나 길다.
그래도 노드를 만든다는 함수라 재밌을 것 같아서 다 가져왔다.
전체적인 내용은
1. 노드에 대한 기본 설정을 가지고 와서
2. node := &Node{}를 통해 새로운 노드를 만들어서
3. 노드의 멤버변수(server, config 등등)들을 설정해주고
4. 중간중간 문제되지 않게 에러처리 해주고
5. 그 노드를 반환해준다.
이번 글에서는 정도로만 이해하면 좋을 것 같다.
// New creates a new P2P node, ready for protocol registration.
func New(conf *Config) (*Node, error) {
// Copy config and resolve the datadir so future changes to the current
// working directory don't affect the node.
confCopy := *conf
conf = &confCopy
if conf.DataDir != "" {
absdatadir, err := filepath.Abs(conf.DataDir)
if err != nil {
return nil, err
}
conf.DataDir = absdatadir
}
if conf.Logger == nil {
conf.Logger = log.New()
}
// Ensure that the instance name doesn't cause weird conflicts with
// other files in the data directory.
if strings.ContainsAny(conf.Name, `/\`) {
return nil, errors.New(`Config.Name must not contain '/' or '\'`)
}
if conf.Name == datadirDefaultKeyStore {
return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`)
}
if strings.HasSuffix(conf.Name, ".ipc") {
return nil, errors.New(`Config.Name cannot end in ".ipc"`)
}
server := rpc.NewServer()
server.SetBatchLimits(conf.BatchRequestLimit, conf.BatchResponseMaxSize)
node := &Node{
config: conf,
inprocHandler: server,
eventmux: new(event.TypeMux),
log: conf.Logger,
stop: make(chan struct{}),
server: &p2p.Server{Config: conf.P2P},
databases: make(map[*closeTrackingDB]struct{}),
}
// Register built-in APIs.
node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
// Acquire the instance directory lock.
if err := node.openDataDir(); err != nil {
return nil, err
}
keyDir, isEphem, err := conf.GetKeyStoreDir()
if err != nil {
return nil, err
}
node.keyDir = keyDir
node.keyDirTemp = isEphem
// Creates an empty AccountManager with no backends. Callers (e.g. cmd/geth)
// are required to add the backends later on.
node.accman = accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed})
// Initialize the p2p server. This creates the node key and discovery databases.
node.server.Config.PrivateKey = node.config.NodeKey()
node.server.Config.Name = node.config.NodeName()
node.server.Config.Logger = node.log
node.config.checkLegacyFiles()
if node.server.Config.NodeDatabase == "" {
node.server.Config.NodeDatabase = node.config.NodeDB()
}
// Check HTTP/WS prefixes are valid.
if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil {
return nil, err
}
if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil {
return nil, err
}
// Configure RPC servers.
node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts)
node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
return node, nil
}
utils.SetEthConfig()
이건 eth 에 대한 설정을 해주는 함수이다.
이게 왜 makeConfigNode 함수 안에 있는지는 아직 의문이다.
왜냐면 gethconfig에 node에 대한 config와 eth에 대한 config에 대한 변수가 각각 있는 것처럼
node와 eth는 약간 다르다고 볼 수 있다고 생각하기 때문이다,,
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {}
eth 관련 커맨드 라인 flag를 config에 넣어준다고 한다.
여하튼 함수를 보자면 SetNodeConfig와 비슷하게 context를 가지고 ethconfig를 설정해준다고 보면 된다.
node.Node를 인자로 받는 이유는 node와 eth가 공유하는 부분이 있기 때문이다.
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag)
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
// Set configurations from CLI flags
setEtherbase(ctx, cfg)
setGPO(ctx, &cfg.GPO)
setTxPool(ctx, &cfg.TxPool)
setMiner(ctx, &cfg.Miner)
setRequiredBlocks(ctx, cfg)
setLes(ctx, cfg)
// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
if err == nil {
if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024)
mem.Total = 2 * 1024 * 1024 * 1024
}
allowance := int(mem.Total / 1024 / 1024 / 3)
if cache := ctx.Int(CacheFlag.Name); cache > allowance {
log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
ctx.Set(CacheFlag.Name, strconv.Itoa(allowance))
}
}
... 생략
applyMetricConfig()
setMetricConfig 라고 보면된다.
SetNodeConfig, SetEthconfig와 비슷하게 컨텍스트를 기반으로 metric에 관한 설정을 해준다.
if ctx.IsSet(utils.MetricsEnabledFlag.Name) {
cfg.Metrics.Enabled = ctx.Bool(utils.MetricsEnabledFlag.Name)
}
if ctx.IsSet(utils.MetricsEnabledExpensiveFlag.Name) {
cfg.Metrics.EnabledExpensive = ctx.Bool(utils.MetricsEnabledExpensiveFlag.Name)
}
이런 식으로 flag를 확인해서 ethconfig에 넣어준다.
함수 이름에서 보듯이 metric 관련 flag 일 것이다.
makeConfigNode() 정리
makeConfigNode는 geth 설정을 불러오고 빈 노드를 만들어주는 함수, 노드와 gethConfig를 반환한다.
loadBaseConfig를 통해 가져온 기본 설정을 이용하여 노드를 생성
ethConfig와 metricConfig 적용 후 노드와 config 리턴해준다.
기타
if ctx.IsSet(utils.OverrideCancun.Name) {
v := ctx.Uint64(utils.OverrideCancun.Name)
cfg.Eth.OverrideCancun = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
cfg.Eth.OverrideVerkle = &v
}
// Create gauge with geth system and build information
if eth != nil { // The 'eth' backend may be nil in light mode
var protos []string
for _, p := range eth.Protocols() {
protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version))
}
metrics.NewRegisteredGaugeInfo("geth/info", nil).Update(metrics.GaugeInfoValue{
"arch": runtime.GOARCH,
"os": runtime.GOOS,
"version": cfg.Node.Version,
"protocols": strings.Join(protos, ","),
})
}
정리
'blockchain > geth 소스코드 분석' 카테고리의 다른 글
Geth 소스코드 분석 3 - 시작지점 살펴보기 (0) | 2024.01.30 |
---|---|
Geth 소스코드 분석 2 - 몇 가지 타입 살펴보기 (0) | 2024.01.29 |
Geth 소스코드 분석 1 - 시작하기 및 전체 구조 (0) | 2024.01.27 |