blockchain/geth 소스코드 분석

Geth 소스코드 분석 4 - makeFullNode 1편 - makeConfigNode()

uzzam 2024. 2. 7. 20:19

저번 글에서 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, ","),
    })
}

 

 

정리