[연재] geth로 private network cluster 구성

Geth로 private network cluster 구성


블럭체인은 망이다. 노드 하나만 있어도 개발하기엔 무리가 없지만, 실재 망이 되기 위해서는 여러 노드들을 띄우고 서로 연결하여야 한다. 이번 장에서는 노드 간 연결을 해보겠다.

앞 연재 여러 곳에서 얘기했듯이 블럭체인은 p2p 망이다.아래는 p2p 네트워크에 대한 위키백과 내용이다.

...중략...
순수 P2P 파일 전송 네트워크는 클라이언트나 서버란 개념 없이,
오로지 동등한 계층 노드들(peer nodes)이 서로 클라이언트와 서버 역할을 동시에 네트워크 위에서 하게 된다.
이 네트워크 구성 모델은 보통 중앙 서버를 통하는 통신 형태의 클라이언트-서버 모델과는 구별된다.
...중략...

출처: https://ko.wikipedia.org/wiki/P2P


p2p망은 서버와 클라이언트란 개념이 없다. 모든 노드들은 동등하다. 중앙집권적인 모델이 아니다. 이것이 네트웍에서의 탈중앙화 모델이다.
우리가  p2p 망을 사용하는 가장 쉬운 예는 바로 토렌토 클라이언트로 영화를 다운받는 것이다.

seed 파일을 올리면 p2p 망 속에서 이를 가진 사람들한테서 자동으로 연결하여 다운 받는 것, 이것이 p2p 망의 핵심이다.


블럭체인도 마찬가지다. seed 파일과 같은 genesis 블럭으로 네트웍을 초기화하면 네트웍 아이디와 genesis 블럭이 동일한 노드를  망에서 찾아 서로 데이터를 주고 받는다.
실재로 geth를 실행할 때 verbosity 값을 4로 실행하면 주위에서 노드를 찾아 악수(Handsake)하며 서로 데이터를 주고 받을 수 있는지 확인하는 것을 콘솔로 확인할 수 있다.

$> geth --datadir ./data --verbosity 4 console
INFO [06-18|15:15:21] Maximum peer count                       ETH=25 LES=0 total=25
DEBUG[06-18|15:15:21] FS scan times                            list=22.624µs set=700ns diff=2.201µs
INFO [06-18|15:15:21] Starting peer-to-peer node               instance=Geth/v1.8.6-stable-12683fec/linux-amd64/go1.10
INFO [06-18|15:15:21] Allocated cache and file handles         database=/home/vingorius/projects/ethereum/test_geth/data/geth/chaindata cache=768 handles=512
INFO [06-18|15:15:21] Writing default main-net genesis block
DEBUG[06-18|15:15:21] Trie cache stats after commit            misses=0 unloads=0
INFO [06-18|15:15:21] Persisted trie from memory database      nodes=12356 size=2.34mB time=47.502832ms gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [06-18|15:15:21] Initialised chain configuration          config="{ChainID: 1 Homestead: 1150000 DAO: 1920000 DAOSupport: true EIP150: 2463000 EIP155: 2675000 EIP158: 2675000 Byzantium: 4370000 Constantinople: <nil> Engine: ethash}"

...중략...

instance: Geth/v1.8.6-stable-12683fec/linux-amd64/go1.10
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

DEBUG[06-18|15:15:27] Found seed node in database              id=78de8a0916848093 addr=191.235.84.50:30303 age=261.700944ms
DEBUG[06-18|15:15:36] Revalidated node                         b=14 id=0efa939a67ba0d17
DEBUG[06-18|15:15:37] Adding p2p peer                          name=Geth/v1.8.10-stable-... addr=118.184.77.118:30303 peers=1
DEBUG[06-18|15:15:37] Ethereum peer connected                  id=6c722fa48544d5d9 conn=dyndial name=Geth/v1.8.10-stable-4dceebd5/linux-amd64/go1.10
DEBUG[06-18|15:15:37] Ethereum handshake failed                id=6c722fa48544d5d9 conn=dyndial err="Genesis block mismatch - 82270b80fc90beb0 (!= d4e56740f876aef8)"
DEBUG[06-18|15:15:37] Removing p2p peer                        id=6c722fa48544d5d9 conn=dyndial duration=78.995ms peers=0 req=false err="Genesis block mismatch - 82270b80fc90beb0 (!= d4e56740f876aef8)"
DEBUG[06-18|15:15:37] Adding p2p peer                          name=Glyff/v1.0.0-unstabl...                         addr=185.141.24.38:30303  peers=1
DEBUG[06-18|15:15:37] Ethereum peer connected                  id=923afccdd4443fa2 conn=dyndial name=Glyff/v1.0.0-unstable-39845a30/linux-amd64/go1.8.3
DEBUG[06-18|15:15:38] Ethereum handshake failed                id=923afccdd4443fa2 conn=dyndial err=EOF
DEBUG[06-18|15:15:38] Removing p2p peer                        id=923afccdd4443fa2 conn=dyndial duration=389.171ms peers=0 req=true  err="subprotocol error"
DEBUG[06-18|15:15:40] Revalidated node                         b=3  id=81cba0d14d80aaf6
DEBUG[06-18|15:15:41] Recalculated downloader QoS values       rtt=20s confidence=1.000 ttl=1m0s
...중략...



이번 장에서는 노드를 추가해서 기존 노드와 연결하여 데이터를 싱크하는 방법에 대하여 차근 차근 알아보도록 하겠다.
큰 시나리오는 이렇다.

. Node 1에서 블럭체인을 초기화하고 블럭을 쌓아 나간다.
. Node 2를 만들고 노드 1 블럭을 연결하여 동기화 한다.

. Node 2가 Node 1과 같은지 확인한다.

절차는 다음과 같다.

Genesis 블럭 만들기
Node 1 초기화
Node 1 실행 - 블럭을 쌓아나감
Node 1 신규 계정 생성
Node 1 etherbase 등록
Node 1 마이닝 시작

Node 1 enode 확인
Node 2 초기화
Node 2 실행

Node 2 에 Node 1 등록
Node 2 에서 블럭이 제대로 쌓여 가는지 확인

주의 사항

네트웍이 열려 있는지 확인하자.

Genesis 블럭에 특정 노드 정보를 기록해서는 안된다.

1. genesis 블럭 만들기

제일 먼저 할 일은 최초 블럭 바로 genesis 블럭을 만드는 일이다.  genesis 블럭은 참조하는 블럭이 없는 유일한 블럭이다.
따라서 손으로 직접 만들어야 하며, 고려할 것들이 꽤 있다. 각각의  옵션이 무엇을 의미하는지는 여기를 참조하라. 한 번 잘못 만들면 고칠 수가 없다.

{
    "config": {
        "chainId": 1988,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "difficulty": "400",
    "gasLimit": "2100000",
    "alloc": {}
}


네트웍을 동기화하는데서 제일 중요한 것은 chainId 다. chainId는 나중에 geth를 실행할 때 networkid로 참조할 네트웍 식별자다. 가급적 큰 값을 주어 다른 네트웍과 충돌할 가능성을 피하기 바란다. 물론 같은 번호라 할지라도  genesis 블럭이 다르면 서로 다른 네트웍으로 인식한다. genesis 블럭을 만들 때 주의할 점은 이 글 마지막에 다시 다루겠다.

2. Node 1 초기화

노드를 생성하기 위해서는 다음의 명령어로 노드를 init하는 것이다. 이 과정은 최초 블럭 genesis 블럭을 생성하는 과정이다.

geth init genesis.json  --datadir ./data


$ geth init genesis.json --datadir ./data
INFO [06-18|16:10:01] Maximum peer count                       ETH=25 LES=0 total=25
INFO [06-18|16:10:01] Allocated cache and file handles         database=/home/vingorius/projects/ethereum/test_geth/data/geth/chaindata cache=16 handles=16
INFO [06-18|16:10:01] Writing custom genesis block
INFO [06-18|16:10:01] Persisted trie from memory database      nodes=0 size=0.00B time=2.401µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [06-18|16:10:01] Successfully wrote genesis state         database=chaindata                                                       hash=ab944c…55600c
INFO [06-18|16:10:01] Allocated cache and file handles         database=/home/vingorius/projects/ethereum/test_geth/data/geth/lightchaindata cache=16 handles=16
INFO [06-18|16:10:01] Writing custom genesis block
INFO [06-18|16:10:01] Persisted trie from memory database      nodes=0 size=0.00B time=1.809µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [06-18|16:10:01] Successfully wrote genesis state         database=lightchaindata                                                       hash=ab944c…55600c

5번째 줄을 보면 genesis block 을 생성하고 그 Hash 값이  ab944c…55600c 임을 확인할 수 있다.

3. Node 1 실행

다음 명령어로 Node 1을 실행하자.

geth --datadir ./data --nodiscover --networkid 1988 console

자 첫번째 노드를 생성했으면 0번 블럭 즉 genesis 블럭의 해쉬값을 확인해보자.
geth 콘솔창에서 다음과 같이 입력한다.


> web3.eth.getBlock(0)
{
  difficulty: 400,
...중략...
  hash: "0xab944ccf947757af38fadbc7c447e09d45fcccdb458bb6399ff95c8f5055600c",
...중략...
}
>


참고로 실행할 때 --nodiscover 옵션을 주는 것은 주위 노드들 중 나를 수동으로 등록하지 않는 이상 자동으로 스캔하여 등록하지 말라는 뜻이다. 이 옵션이 어떤 역할을 하는지는 --nodiscover를 생략하고  --verbosity 4로 실행해보면 주위 이더넷 망들이 Node 1을 스캔하여 등록하려고 계속 시도하는 것을 확인할 수 있다.

geth --datadir ./data --verbosity 4 --networkid 1988 console


4. Node 1 신규 계정 생성

Node를 실행하고 블럭을 쌓으려면 이더가 필요하고, 이더를 받으려면 마이닝이란 작업을 해야 한다. 그러면 마이닝 결과 받게 될 이더를 쌓을 계정을 만들어 보자. 아래 "password"에 암호를 입력하면 계정이 생성된다.

> personal.newAccount("password")
"0xf5ad07d8ebf8c316a8dc3dc56bf72c7ae223c8fb"


5. Node 1 etherbase 등록

생성된 계정을 etherbase로 등록한다.

> miner.setEtherbase("0xf5ad07d8ebf8c316a8dc3dc56bf72c7ae223c8fb")
true


6. Node 1 마이닝 시작

모든 준비가 끝났으니 다음 명령러로 마이닝을 시작해보자. 블럭이 쌓여가는 것을 확인할 수 있을 것이다.

miner.start(마이닝 쓰레드 개수)

> miner.start(1);
INFO [06-18|17:17:08] Updated mining threads                   threads=1
INFO [06-18|17:17:08] Transaction pool price threshold updated price=18000000000
INFO [06-18|17:17:08] Starting mining operation
INFO [06-18|17:17:08] Commit new mining work                   number=8 txs=0 uncles=0 elapsed=375.881µs
INFO [06-18|17:17:13] Successfully sealed new block            number=8 hash=6af741…68320e
INFO [06-18|17:17:13]


7.Node 1 enode 확인

웹에서 특정 사이트에 들어가려면 http로 시작하는 URL이 있어야 하듯이 블럭체인에서는 enode가 필요하다. enode를 확인하는 방법은 다음과 같이 간단하다.

 > admin.nodeInfo.enode
"enode://743f4972098a6145b038e31e00c6aeb8c00a55076113faf11f742c961dc59819c03bafc97c38e5c17de261bbb66346fde0a8b504d706f81a2ada339626ec4d8f@[::]:30303?discport=0"

[::] 부분에 Node 1의 IP 주소를 기입하면 된다. 예를 들면 다음과 같다.

enode://743f4972098a6145b038e31e00c6aeb8c00a55076113faf11f742c961dc59819c03bafc97c38e5c17de261bbb66346fde0a8b504d706f81a2ada339626ec4d8f@192.168.191.166:30303?discport=0

이 주소는 다른 노드에서 Node 1을 참조할 때 사용한다.


8. Node 2 초기화

Node 2 초기화도 Node 1과 동일하다. 중요한 것은 genesis 블럭이 같아야 한다. 그렇지 않으면 다른 해쉬값이 생성된다.


9.Node 2 실행

Node 2 실행도 Node 1과 동일하다.

geth --datadir ./data --nodiscover --networkid 1988 console
콘솔이 깜빡거리면 0번 블럭 즉 genesis 블럭의 해쉬값을 확인해보자.

> web3.eth.getBlock(0).hash
"0xab944ccf947757af38fadbc7c447e09d45fcccdb458bb6399ff95c8f5055600c"

Node1의 해쉬값과 동일한가? 해쉬값과 networkid가 같아야만 동일 블럭체인 망으로 인식한다.


10. Node 2 에 Node 1 등록

특정 노드에 연결하려면 해당 노드의 enode 주소를 알아야 한다. 7번에서 확인한 Node 1의 enode 주소를 다음과 같이 Node 2에 등록하자.

> admin.addPeer("enode://743f4972098a6145b038e31e00c6aeb8c00a55076113faf11f742c961dc59819c03bafc97c38e5c17de261bbb66346fde0a8b504d706f81a2ada339626ec4d8f@192.168.191.166:30303?discport=0")
true


Node 1에 제대로 연결되었는지 확인하려면  Node 1에서 다음과 같이 실행한다.

> admin.peers
[{
    caps: ["eth/63"],
    id: "b7015d063f8819df7dc873f17a7c8af135a64179c78abcaa777c06ffe017e1a390ecf04126b0e3714dfe82780cf3898f514d82aa7ab96351486b9099d5031ed0",
    name: "Geth/v1.8.6-stable-12683fec/linux-amd64/go1.10",
    network: {
      inbound: true,
      localAddress: "192.168.191.166:30303",
      remoteAddress: "192.168.191.158:37250",
      static: false,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 400,
        head: "0xab944ccf947757af38fadbc7c447e09d45fcccdb458bb6399ff95c8f5055600c",
        version: 63
      }
    }
}]

admin.peers 배열에 방금 연결한 Node 2가 remoteAddress 에 들어와 있으면 제대로 등록된 것이다.


11. Node 2 에서 블럭이 제대로 쌓여 가는지 확인

이를 확인하기 위해서는 Node 2에 생성된 마지막 블럭의 해쉬값과 Node 1에서 해당 블럭 해쉬값이 동일한지 확인해 보면 된다.

먼저 Node 2에서 마지막 블럭 번호와 해쉬값을 확인하자.

> web3.eth.getBlock('latest')
{
  difficulty: 145535,
  extraData: "0xda8301080a846765746888676f312e31302e328777696e646f7773",
  gasLimit: 2597584,
  gasUsed: 0,
  hash: "0x5c75252357efc520bf7662ad4e4482fa574db31beae8fbf558ff90ed517b2429",
  logsBloom: "0x00000000000000000000000000000000...중략...0000000000000000000000",
  miner: "0xf5ad07d8ebf8c316a8dc3dc56bf72c7ae223c8fb",
  mixHash: "0x1a07ff8cc98b5a37458b1f0b2fa7e9cc234d13b9ecd6935774ba87f7924a0656",
  nonce: "0x77374ae26b71a47a",
  number: 218,
  parentHash: "0xc40a0bfe78898bd1d7bb8e9b92e0b0bca92d899f65408492501135176aaa63fc",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 539,
  stateRoot: "0x6b3482eabdce6cc123c1b3988a79d8586fb4786daf0d566eb507daddcff66579",
  timestamp: 1529372075,
  totalDifficulty: 30116515,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}


마지막 블럭 번호가 218번이고 해쉬값이 "0x5c75252357...중략...b2429"이다. 그러면  Node 1에서 218번 블럭 해쉬값이 아래와 같이 동일한 지 확인한다.

> web3.eth.getBlock(218).hash
"0x5c75252357efc520bf7662ad4e4482fa574db31beae8fbf558ff90ed517b2429"

여기까지 확인되었으면 Node 2에서 Node 1 연결은 끝난 것이다.


주의 사항

네트웍이 열려 있는지 확인하자.

enode 주소에서도 확인할 수 있듯이 이더리움 망은 30303 udp 포트를 사용한다. 연결하려는 노드의 포트가 열려 있는지 nmap을 사용하여 확인하자.

 ~ $ sudo nmap 192.168.191.166 -p30303 -sTU

Starting Nmap 7.60 ( https://nmap.org ) at 2018-06-19 10:52 KST
Nmap scan report for 192.168.191.166
Host is up (0.0011s latency).

PORT      STATE         SERVICE
30303/tcp open          unknown
30303/udp open|filtered unknown
MAC Address: 34:64:A9:16:D2:C3 (Hewlett Packard)

Nmap done: 1 IP address (1 host up) scanned in 0.65 second

udp 확인은 슈퍼유저로만 가능하다.


Genesis 블럭에 특정 노드 정보를 기록해서는 안된다.

 coinbase나  extraData, alloc를 노드마다 별도로 설정하려고 해서는 안된다. 실재 이 값이 노드마다 다르면 genesis 블럭이 다르고, 따라서 생성되는 해쉬값이 다르기 때문에 서로 다른 블럭체인 망으로 인식한다. 

{
    "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "difficulty": "200000000",
    "gasLimit": "2100000",
    "coinbase" : "7df9a875a174b3bc565e6424a0050ebc1b2d1d82",
    "extraData" : "Node 1",
    "alloc": {
        "7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "300000" },
        "f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "400000" }
    }
}


결론

이번 연재에서는 블럭체인 망을 만드는 방법과 주의할 점에 대해서 알아보았다. 처음에 하다보면 이것 저것 안되는 것들이 많다. 시쳇말로 삽질이 많이 필요하다. 블럭체인 데이터가 쌓이는 데이터 디렉터리를, 위 예제에서는 ./data 를 새로운 테스트할 때마다 삭제하고 명령어를 실행하라. --datadir에 쌓여 있는 블럭에서부터 시작하다 보니 앞 테스트의 영향을 받게 된다.