[연재] POA 합의 알고리즘으로 구현하는 Private Network

POA 합의 알고리즘으로 구현하는 Private Network

이더리움 알고리즘과 PoA

이더리움에서 제공하는 합의 알고리즘에는 제각각 이름이 있다.  PoW는 Ethash, PoS는 Casper, PoA는 Clique.
오늘 알아볼 것은 전용망에서 많이 사용하는 합의 알고리즘인 PoA(Proof of Authority)다. PoA에 대한 자세한 사항은 여기(https://en.wikipedia.org/wiki/Proof-of-authority)를 참고하기 바란다.

여기서는 개발자 입장에서 어떻게 PoA를 구성하는가에 대해 살펴보겠다.

PoA는 검증자(validator)라 불리는 Authority들이 있다. 이들이 돌아가면서 정해진 주기(Period)로 블럭을 생성한다.

따라서 몇개의 Authority들로 네트웍을 구성할 것인가? 이것이 가장 중요하다. 물론 네트웍이 실행되는 순간에도 기존 Authority들의 합의에 의해 새로운 Authority를 추가할 수도 있지만, genesis 파일에 최초 Authority들을 등록하여야 한다. 처음 genesis 파일 설계에 PoA에 대한 고려가 없었던지, Authority들 목록은 extraData 항목에 서술한다.

PoA용 Genesis 파일

제일 먼저 할 일은 genesis 파일을 만드는 것이다. 다음은 PoA genesis 파일 구조다.

{
    "config": {
        "chainId": 12345,
        "homesteadBlock": 0,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0,
        "byzantiumBlock": 0,
        "clique": {
            "period": 5,
            "epoch": 30000
        }
    },
    "nonce": "0x0",
    "timestamp": "0x5a722c92",
"extraData": "0x00000000000000000000000000000000000000000000000000000000000000000a57f15f4e3c22c4d79c52307ef5fce8aff69e4c2fd8cd8b880727c11ad7c65dc83255b46be0f9cd8fb13b3c7e3868df8de563bf7e133a0cec5b1ec90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",     "gasLimit": "0x59A5380",
    "difficulty": "0x1",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "alloc": {
    },
    "number": "0x0",
    "gasUsed": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}


여기서 PoW 와 다른 부분은 Clique 부분과 extraData 부분이다.
Clique 부분에는 블럭이 매 몇초마다 생성될지 정의하는 부분이 period 항목에 들어간다.
그리고 Authority들 목록 즉 마이닝을 할 계정이 extraData에 0x를 빼고 차례로 들어가면 된다.

Authority 계정 생성

이를 위해 Authority 계정 3개를 만들어 네트웍을 구성하자.
계정을 만드는 것은 간단하다. 비밀번호는 '1234'로 해서 3개 계정을 만들겠다.

Nod1

 ~/poanet $ geth --datadir node2/ account new
INFO [09-17|11:21:53.046] Maximum peer count                       ETH=25 LES=0 total=25
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {2fd8cd8b880727c11ad7c65dc83255b46be0f9cd}

  Nod2

 ~/poanet $ geth --datadir node2/ account new
INFO [09-17|11:21:53.046] Maximum peer count                       ETH=25 LES=0 total=25
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {2fd8cd8b880727c11ad7c65dc83255b46be0f9cd}

Node3

 ~/poanet $ geth --datadir node3/ account new
INFO [09-17|11:22:03.279] Maximum peer count ETH=25 LES=0 total=25
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {8fb13b3c7e3868df8de563bf7e133a0cec5b1ec9}


Genesis 파일 생성

gensis 파일을 만드는 방법은 2가지다. 첫번째가 이더리움 설정 툴인 puppeth를 사용하는 것이고, 두번째는 직접 손으로 작성하는 것이다.
여기서는 위 샘플파일을 토대로 직접 손으로 작성해보겠다. 단지 Authority 계정 3개만 extraData에 추가하면 된다.

위 샘플에서 형광펜으로 표시된 부분을 지우고,거기에 위에서 만든 계정 3개(0x를 제외한)를 넣으면 된다.

{
    "config": {
        "chainId": 12345,
        "homesteadBlock": 0,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0,
        "byzantiumBlock": 0,
        "clique": {
            "period": 5,
            "epoch": 30000
        }
    },
    "nonce": "0x0",
    "timestamp": "0x5a722c92",
   "extraData": "0x000000000000000000000000000000000000000000000000000000000000000002fd8cd8b880727c11ad7c65dc83255b46be0f9cd2fd8cd8b880727c11ad7c65dc83255b46be0f9cd8fb13b3c7e3868df8de563bf7e133a0cec5b1ec90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "gasLimit": "0x59A5380",
    "difficulty": "0x1",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "alloc": {
    },
    "number": "0x0",
    "gasUsed": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}


Bootnode 생성

bootnode는 geth의 기능 중 노드들끼리 연결을 돕는 기능만 뽑아낸 툴이다. 매번 동일한 enode 번호를 생성하기 위해선 bootnode를 실행하기 위한 bootkey 파일이 필요하다.

bootkey 파일은 다음과 같이 생성한다.

 ~/poanet $ bootnode -genkey boot.key

이제 생성된 boot.key 파일로 bootnode를 실행하자.

 ~/poanet $ bootnode -nodekey boot.key -verbosity 9 -addr :30301
INFO [09-17|13:46:46.750] UDP listener up                          self=enode://a1de3ef130544423e8d1c3951a9502659238baa5149428b5cf3bd78f1525a9567bd4e4c25cc30ca2dc18a947bf4c47f7678f88a1f47bc84fd18015b27910c156@[::]:30301

bootnode는 기본으로 30301 포트로 실행되므로 -addr 옵션을 생략해도 되지만, 여기서는 명시적으로 표시했다.

명령을 실행하고 나온 결과를 보면 enode 번호가 보인다. 이 번호가 bootnode 번호이며 [::]  부분을  자신의 주소로 다음과 같이 바꾼다.

enode 번호

enode://a1de...중략...910c156@[::]:30301

node 상에 들어갈 enode 번호

enode://a1de...중략...910c156@127.0.0.1:30301

자 이제 bootnode가 준비되었으니 Authority 노드들을 실행해보자.


Authority 노드 초기화/실행

노드 1

다음의 명령어로 Authority 노드를 초기화한다.

  ~/poanet $ geth --datadir node1/ init genesis.json
INFO [09-17|14:04:49.640] Maximum peer count                       ETH=25 LES=0 total=25
INFO [09-17|14:04:49.641] Allocated cache and file handles         database=/home/vingorius/poanet/node1/geth/chaindata cache=16 handles=16
INFO [09-17|14:04:49.665] Writing custom genesis block
INFO [09-17|14:04:49.665] Persisted trie from memory database      nodes=0 size=0.00B time=2.776µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [09-17|14:04:49.665] Successfully wrote genesis state         database=chaindata                                   hash=a228c6…28397f
INFO [09-17|14:04:49.665] Allocated cache and file handles         database=/home/vingorius/poanet/node1/geth/lightchaindata cache=16 handles=16
INFO [09-17|14:04:49.693] Writing custom genesis block
INFO [09-17|14:04:49.693] Persisted trie from memory database      nodes=0 size=0.00B time=3.971µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [09-17|14:04:49.693] Successfully wrote genesis state         database=lightchaindata                                   hash=a228c6…28397f

 해쉬 hash=a228c6…28397f 로 genesis 블럭이 성공적으로 생성되었다. 이제 첫번째 노드를 실행보자.

 ~/poanet $ geth --datadir node1 \
     --syncmode 'full' --port 30302 \
     --bootnodes 'enode://a1de3ef130544423e8d1c3951a9502659238baa5149428b5cf3bd78f1525a9567bd4e4c25cc30ca2dc18a947bf4c47f7678f88a1f47bc84fd18015b27910c156@127.0.0.1:30301' \
     --networkid 12345 --gasprice '1' \
     --unlock '0x0a57f15f4e3c22c4d79c52307ef5fce8aff69e4c' --password ./password.txt --mine console

bootnode를 30301번으로 띄웠으니, Authority 노드는 30302,30303,30304번으로 실행하자.

--bootnode 옵션에는 앞에서 생성한 bootnode 번호를 넣으면 되고, networkid에는 genesis 파일에 있는 id를 넣어야 한다.

그리고 Authority 노드로 실행하기 위하여 계정의 락을 풀어야 한다. 패스워드는 password.txt 파일에 앞에서 설정한 패스워드로 저장해두면 된다.


노드 2


노드2도 노드1과 동일하게 실행하면 된다. 다만 디렉토리 이름과 계정명, 포트만 변경하면 된다.

노드 초기화

 ~/poanet $ geth --datadir ./node2 init genesis.json

노드 실행

geth --datadir node2 \
     --syncmode 'full' --port 30303 \
     --bootnodes 'enode://a1de3ef130544423e8d1c3951a9502659238baa5149428b5cf3bd78f1525a9567bd4e4c25cc30ca2dc18a947bf4c47f7678f88a1f47bc84fd18015b27910c156@127.0.0.1:30301' \
     --networkid 12345 --gasprice '1' \
     --unlock '0x2fd8cd8b880727c11ad7c65dc83255b46be0f9cd' --password ./password.txt --mine console


노드 3

노드 초기화

 ~/poanet $ geth --datadir ./node3 init genesis.json

노드 실행

geth --datadir node3 \
--syncmode 'full' --port 30304 \
--bootnodes 'enode://a1de3ef130544423e8d1c3951a9502659238baa5149428b5cf3bd78f1525a9567bd4e4c25cc30ca2dc18a947bf4c47f7678f88a1f47bc84fd18015b27910c156@127.0.0.1:30301' \
--networkid 12345 --gasprice '1' \
--unlock '0x8fb13b3c7e3868df8de563bf7e133a0cec5b1ec9' --password ./password.txt --mine console


Bootnode 연결 확인

30302,30303,30304 번 포트로 실행한 Authority 노드가 bootnode에 제대로 붙었는지는 bootnode 로그를 확인해보면 된다.


위 화면과 같이 각각의 노드에 대한 Ping/Pong 후 Findnode로 연결되면 된다.


Authority 노드 마이닝 확인

노드1,노드2, 노드3이 매 5초마다 번갈아가며 블럭을 마이닝하는 것을 콘솔에서 확인하기 바란다.

> INFO [09-17|14:19:35.685] Imported new chain segment               blocks=1 txs=0 mgas=0.000 elapsed=248.207µs mgasps=0.000 number=1 hash=4bed6b…95ee3f cache=0.00B
INFO [09-17|14:19:35.685] Commit new mining work                   number=2 sealhash=9abb44…15e7ec uncles=1 txs=0 gas=0 fees=0 elapsed=77.267µs
INFO [09-17|14:19:40.551] Successfully sealed new block            number=2 sealhash=9abb44…15e7ec hash=19ebd3…32efd2 elapsed=4.866s
INFO [09-17|14:19:40.551]


마치며

PoA 네트웍은 기본적으로 Authority 노드들이 정해진 시간(초)에 돌아가면서 마이닝을 하기 때문에 마이닝 보상이 없다. 따라서 이 네트웍으로 뭔가를 하려면 genesis 파일 alloc 항목에 충분히 많은 ether를 Authority 들에게 할당하여야 한다.

둘째로 PoA 네트웍이 마이닝을 하기 위해서는 노드에 참여하는 Authority 노드가 최소한 int(n/2 + 1) 개 있어야 한다. 예를 들면 Authority 노드가 3개인 경우 적어도 2개 이상의 노드가 실행되고 있어야 마이닝이 된다. 이 보다 적을 때 들어온 트랜잭션은 팬딩트랜젝션으로 저장되었다가, 새로운 노드가 참여하여 마이닝이 계속되면 블럭에 기록된다.

예외적으로 Authority 노드가 genesis 파일 상 1개인 경우에도 PoA 망이 실행된다.