이더리움 합의 알고리즘 추가하기 — Part 1

Luke Park
CURG
Published in
9 min readJun 18, 2019

본 포스팅은 블록체인과 이더리움에 관한 기본적인 배경 지식이 있는 독자를 대상으로 한다. 또한 go 언어에 친숙하다면 코드 리뷰가 용이할 것이다. 매우 많은 코드가 기다리고 있다.

Go로 작성된 이더리움 클라이언트(이하 geth)에는 기본 두 종류의 합의 알고리즘이 존재한다. 하나는 일반적으로 우리가 알고있는 PoW(Proof-of-Work)의 일종인 ethash이고, 나머지 하나는 PoA(Proof-of-Authority)인 clique이다.

이번 시리즈에서는 rawpow라 이름붙인 나만의 합의 알고리즘을 geth에 추가할 것이다. 구동 가능한 구현체는 다음의 깃허브 저장소에서 확인할 수 있다.

main.go에서 시작하기

geth 코드의 흐름에 따라 기본적인 구조를 살펴보자. 우리가 어떤 부분을 수정해야 새로운 합의 알고리즘을 추가할 수 있을지 생각해보자.

geth 바이너리를 실행하면 cmd/geth/main.go의 main 함수가 실행된다.

https://github.com/lukepark327/ethash-executable-geth/blob/master/go-ethereum/cmd/geth/main.go#L295

물론 go 언어의 특성상 init 함수가 먼저 수행될 것이다. init()에서는 CLI 앱을 초기화하고 geth를 시작한다. app.Action = geth에 주목하자. 여기서 geth는 다음과 같이 정의된다.

https://github.com/lukepark327/ethash-executable-geth/blob/master/go-ethereum/cmd/geth/main.go#L305

geth(ctx *cli.Context)는 명령줄 인자들로부터 기본 노드를 생성하고 구동하는 역할을 수행한다. makeFullNode(ctx)를 살펴보자. config.go에 정의돼 있다. 이어 RegisterEthService()를 살펴보자. cmd/utils/flags.go에 정의돼 있다.

https://github.com/lukepark327/ethash-executable-geth/blob/master/go-ethereum/cmd/geth/config.go#L151
https://github.com/lukepark327/ethash-executable-geth/blob/master/go-ethereum/cmd/utils/flags.go#L1514

RegisterEthService()에서는 eth서비스를 등록한다. eth.New()eth/backend.go에서 그 구조를 살펴볼 수 있다. eth가 정의되고 초기화되는 부분에 주목하자.

https://github.com/lukepark327/ethash-executable-geth/blob/master/go-ethereum/eth/backend.go#L146

engine: CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb)가 바로 우리가 수정해야 할 부분이다. CreateConsensusEngine()은 이더리움 서비스를 위해 지정된 합의 알고리즘 엔진을 생성한다.

CreateConsensusEngine

https://github.com/ethereum/go-ethereum/blob/master/eth/backend.go#L250

위는 geth의 CreateConsensusEngine() 코드이다. chainConfigClique가 nil이 아니라면 Clique, 즉 PoA 합의 알고리즘으로 세팅됨을 알 수 있다. 만일 나머지 경우에는, 즉 PoW 합의 알고리즘의 경우에는 configPowMode에 따라 Fake, Test, Shared, 그리고 기본(default) ethash로 세팅된다.

chainConfig

chainConfigparams/config.go에 정의돼 있다.

https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L273

ChainConfig는 블록체인 세팅을 담당하는 핵심 요소들의 구조체이다. 체인ID(ChainID) 설정에서부터, Homestead, Byzantium, Constantinople, Petersburg 업그레이드를 적용할 블록 번호, EIP의 적용, 그리고 어떤 합의 알고리즘을 사용할지를 지정하는 부분 등을 포괄한다. 이 ChainConfig는 노드를 시작할 때 원하는 설정을 담은 json 파일을 지정하는 것으로 구성할 수 있다. 가령 다음과 같다.

{
"config": {
"chainId": 950327,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"clique": {
"period": 5,
"epoch": 30000
}
},
"difficulty": "20",
"gasLimit": "7000000",
"alloc": {
"6282ad5f86c03726722ec397844d2f87ced3af89": { "balance": "60000000000000000000" }
}
}

다음과 같이 위 json 파일을 지정할 수 있다.

geth init ./myConfig.json

Clique에 period와 epoch을 지정하는 것으로 PoA 합의 알고리즘의 세부 사항을 조정할 수 있다. 이는 CliqueConfig가 다음과 같이 정의돼 있기 때문이다.

https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L313

EthashConfig는 다음과 같이 빈 구조체로 정의돼 있다.

https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L305

Engine 인터페이스

CreateConsensusEngine()의 반환값은consensus.Engine이다. consensus/consensus.go에 정의된 Engine 인터페이스(interface)를 살펴보자.

https://github.com/ethereum/go-ethereum/blob/master/consensus/consensus.go#L52

Engine 인터페이스는 위와 같은 메소드(method)들을 가지고 있다. 이들은 블록과 블록체인의 검증과 관계된 매우 중요한 메소드들이며, 각각을 수정하는 것만으로도 다른 종류의 합의 알고리즘을 만들 수 있다.

새로운 합의 알고리즘을 추가하고자 한다면 최소한 위 인터페이스의 규격을 준수해야 한다. 달리 말하자면, 우리의 새로운 rawpow 합의 알고리즘은 적어도 다음의 메소드들을 가질 것이다.

Author

Author()는 주어진 블록에 대해 블록을 생성한 이더리움 주소를 반환한다. 합의 엔진에 따라 블록 헤더의 코인베이스(coinbase) 주소와 다를 수 있다.

VerifyHeader

VerifyHeader()는 블록 헤더가 합의 규칙을 준수하는지 검증한다. 이 메소드에서 모든 검증을 수행할 수도 있지만, 별도의 VerifySeal() 메소드에서 seal의 검증을 수행할 수도 있다.

VerifyHeaders

VerifyHeaders()VerifyHeader()와 유사한 역할을 수행하나 하나의 블록 헤더가 아닌 다수의 블록 헤더 묶음을 검증한다.

VerifyUncles

VerifyUncles()는 주어진 블록의 엉클 블록이 합의 규칙을 준수하는지 검증한다.

VerifySeal

VerifySeal()은 블록 헤더에서 seal의 검증을 담당한다. 가령 Ethash에서는 주어진 블록이 PoW 난이도 요구를 충족했는지를 검증한다.

Prepare

Prepare()은 블록 헤더 중 합의에 필요한 영역을 초기화한다. 가령 Ethash에서는 Prepare()에서 난이도 영역을 CalcDifficulty()를 통해 초기화한다.

Finalize

Finalize()는 블록의 최종 집계이다. 가령 해당 블록의 보상, 엉클 블록의 보상, 그리고 최종 상태(state)를 집계해 최종 블록 헤더를 구성한다.

Seal

Seal()은 주어진 입력에 대해 seal된 새 블록을 생성한다. 가령 Ethash에서는 흔히 채굴(mine)이라 불리는 과정을 수행하며, 블록 난이도 조건을 충족하는 논스(nonce)를 찾는다.

CalcDifficulty

CalcDifficulty()는 난이도 조정 알고리즘이다. 블록이 충족해야 할 난이도를 반환한다.

APIs

APIs()는 지정한 합의 엔진이 제공하는 RPC API들을 반환한다. 본 시리즈에서는 새로운 API를 추가하는 방법 역시 소개할 것이다.

Part 1에서는 합의 엔진을 중점으로 geth의 대략적인 흐름을 살펴봤다. 또한 새로운 합의 알고리즘을 추가하기 위해서 geth의 어떤 부분을 수정해야 할 것인지를 살펴봤다. eth/backend.goparams/config.go를 수정해야 할 것으로 보인다. 다음 포스팅에서 실제로 이 두 코드를 수정해 rawpow 합의 알고리즘을 지원하도록 할 것이다.

또한 consensus/consensus.go에 정의된 Engine 인터페이스의 메소드들을 살펴보고, 각 메소드의 역할을 가볍게 살펴봤다. 추가하고자하는 rawpow 합의 알고리즘은 어떤 규칙을 가지고 있으며, 따라서 각 메소드를 어떻게 구성해야 하는지는 다음 포스팅에서 자세히 다뤄보겠다.

박상현(Luke Park) @서울대학교 가상머신 및 최적화 연구실
lukepark327@gmail.com

--

--