go로 간략히 구현한 블록전파/Gossip Protocol

지난 글(400라인의 go코드로 구현한 하이퍼레저 패브릭)에 이어 이번에는 하이퍼레저 패브릭의 블록전파/가쉽프로토콜을 추가 구현 해 보는 시간을 갖겠습니다. 이번 설연휴동안 여유있게 코딩했는데 역시나 세세한 부분이 많이 생략되습니다만 중요맥락에 대해서는 대략 비슷하게 동작되게 하는 목표로 구현하였습니다. (더 줄일 수도 있었지만, 앞으로 추가될 기능들을 위한 기반으로 600라인이 넘어갔습니다.) 

1개의 리더피어와 여러개의 일반 Commit 피어가 작동하게 될 것이며, 각 피어는 포트로 구분 될 것입니다. 리더피어가 오더러에게 메세지를 10초에 한번씩 가져와서 몇개의 피어에만 전파시키면 결국은 모든 피어가 모두 동일한 데이터를 담고 있게 할 것입니다. 시작되는 피어는 부트스트랩노드에서 초기 피어정보를 가져올 것이며, 30초에 한번씩 주변 노드로부터 노드정보들을 가져와서 업데이트 할 것입니다. 중간에 몇몇개의 피어가 멈추어도 동작 할 것이며,  그 피어 중 하나를 다시 살리면, (혹은 새로운 피어가 들어가도) 그 피어는 그 동안의 모든 데이터를 이웃으로 부터 받게 될 것입니다. 리더피어가 멈추면, 리더를 선출하게 됩니다. 

400 라인 하이퍼레저 패브릭 구성 요소 복습 

* Endorse Peer  -  클라이언트가 발생시킨 트랜잭션을 계산/보증한 후 Read/Write Set을 리턴
* Commit  Peer -  Orderer가 보내준 블럭을 장부(블록체인 및 상태저장소) 에 기록
* Orderer         Read/Write Set 을 받아 정렬 한후 블록으로 만들어 Commit Peer에 전달.
* Kafaka           -  Orderer를 도와서 정렬작업을 합니다. 
* Fabric CA       -  각 Peer 와 사용자(조직)등에 대한 암호화 재료를 만들어 주며, CA의 역할을 합니다.
* MSP             -  각 조직및 사용자에 대한 신원검증을 처리 합니다.
* Ledger          -  append only인 블록체인과 상태저장소를 가지고 있습니다.
* LevelDB         -  key,value 맵으로 상태를 저장하고 있습니다.

심플하게 구현된 하이퍼레저 패브릭은 위의 구성요소로 되어 있으며, 6번 오더러(O)에서 리드피어로 블록을 주면, 리드피어는 블록을 저장하고 끝났습니다. 근데 원래는 리드피어(C)는 자신의 동료 Commit피어들에게 가쉽프로토콜로 블록을 전파하거든요. 그 부분이 이번에 새로 추가한 코드입니다. 

해당 부분을 위해 간략하게 정리한 아이디어는 아래와 같습니다. (참고로 완전하고 효율적으로 가쉽프로토콜을 작성하는것은 매우 어려운 도전적인 과제같습니다. 참고로 저는 잘 모름) 

블록전파/가쉽프로토콜 아이디어 정리

1. 암호화 핸드쉐이크 (TLS)  - 생략
2. 프로토콜 핸드쉐이크  - 생략
3. 스트리밍 인코딩/디코딩 - 생략
4. UDP 및 NAT Traversal - 퍼블릭 블록체인 아니면 필요가 없음. 
5. 서로의 이름만 확인 후 간략한 패킷정의로 TCP 스트리밍으로 통신 (RPC사용안함)

6. 부트스트랩 피어/리더 피어/ 오더러 설정 - 하드코딩 /  채널,조직 등 추상개념 제외 
7. 각 피어는 시작시부트스트랩 피어에 접속하여, 부트스트랩 피어들이 가지고 있는 다른 피어정보들을 가져와서 저장.
8. 3초에 한번씩 자신이 살아있다는 정보를 모든 피어에 알림.- 생략
9. 자신이 가지고 있는 피어에게 alive메세지가 5초안에 안날라오면, V에서 제거하여 H로 이동.- 생략
10. Alive메세지를 받으면 그게 내 V에 없는 피어라면 V에 넣는다. 있는데 seq,시작시간가 둘다 크면 업데이트. - 생략
11. 리더에게 alive메세지가 안오면, 피어들은 부트스트랩에 랜덤으로 리더로 선출해서 서로에게 알림. 모든 피어는 서로의 투표를 받아서, 가장 많이 투표된 피어를 선출하고, 그 피어는 스스로 리더피어가되서 오더러에서 데이터를 가져옴. - 생략

12. 리더피어는 5초에 한번씩 블록을 가져옴. (오더로에서 가져왔다고 시뮬레이션함.실제 연결안함)
13  부트스트랩으로 부터 초기 연결정보를 가져오고, 후에는 30초에 한번씩 주변피어정보를 수집함. 
14. 수집된 피어 중 자신이 가지고 있지 않은 피어에 즉시 연결 요청함. 
15. 블록 받으면 이웃노드들중 랜덤 3개에 전파 (Push모델) 
16. 한번 받은 블록을 기록해두고, 똑같은 것을 받으면 버림. (같은 블록 처리를 방지) 
17. 램덤 Push 모델로는 구멍이 생기기 마련, Pull로 자신의 구멍을 메우는 호출도 함. 생략
17. 새로 발견된 이웃이 블록을 적게 가지고 있으면 블록 전파  - 생략
18. 새로 접속한 피어는 주변에 alive메세지와 함께 블록 정보도 뭍혀서 날림. 주변 피어들은 자신들의 블록 id를 리턴해주고 랜덤선택하여 블록 요청 (가장 큰 블록 id를 가진 피어한테 요청하면 그 피어가 병목이 될 수도) - 생략
19. 자신의 블록id와 2이상 차이가 날 경우에만 블록 요청 - 생략

구성도

이런식으로 리더피어가 동채널/동조직 commit피어집단에 모두 전파하는게 아니며 


  이런식으로 주변 랜덤n개에만 전파하면 각각 그들도 주변에 전파합니다. 

Node : 서비스의 메인모듈이며, 리모트피어로 부터의 접속을 받아드리고, 접속을 수행하는 역할을 합니다. 최종연결된 피어에 대해 단독개체를 만들어서 PeerManager에게 전달합니다.
PeerManager: 리모트 피어들의 생존을 관리하며, 브로드케스트 같은 전체 피어에게 행하는 행위를 합니다.
Peer : 리모트 피어 각각과 연결된 개체입니다.

소스코드

https://github.com/wowlsh93/hyperledger-fabric-400-gossip

실행
터미널 7개 정도 띄운후 
터미널1:  go run peer.go -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader
터미널2:  go run peer.go -name 127.0.0.1:28001 -ip 127.0.0.1 -port 28001 -bootstrap 127.0.0.1:28000
터미널3:  go run peer.go -name 127.0.0.1:28002 -ip 127.0.0.1 -port 28002 -bootstrap 127.0.0.1:28000
터미널4:  go run peer.go -name 127.0.0.1:28003 -ip 127.0.0.1 -port 28003 -bootstrap 127.0.0.1:28000
터미널5:  go run peer.go -name 127.0.0.1:28004 -ip 127.0.0.1 -port 28004 -bootstrap 127.0.0.1:28000
터미널6:  go run peer.go -name 127.0.0.1:28005 -ip 127.0.0.1 -port 28005 -bootstrap 127.0.0.1:28000
터미널7:  go run peer.go -name 127.0.0.1:28006 -ip 127.0.0.1 -port 28006 -bootstrap 127.0.0.1:28000


* 리드피어는 오더러에서 블록을 가져 온다.


* 이웃피어로부터 정보를 수집한다. 

* 블록을 주변에 전파한다.  (전파하는 와중에도 동일한 블록이 들어 올 수 있으나, 이미 받은 블록이면 버림) 


- 다음 시리즈 

400라인의 go코드로 구현한 하이퍼레저 패브릭 [3]- 도커/쿠버네이트로 디플로이



읽을꺼리
https://www.ibm.com/developerworks/community/wikis/form/anonymous/api/wiki/0be57f75-2769-40b2-9ea4-99fbec0f9073/page/5145e8c8-4833-4e5b-b623-d73d579c562e/attachment/e978b030-1d7a-4f76-8ad6-ef3dde66c409/media/Fabric%E4%BB%A3%E7%A0%81%E8%A7%A3%E6%9E%90%E7%AC%AC%E4%BA%8C%E8%AE%B2.pdf
https://hyperledger-fabric.readthedocs.io/en/release-1.4/gossip.html
https://jira.hyperledger.org/browse/FAB-170
http://www.inf.u-szeged.hu/~jelasity/ddm/gossip.pdf
http://disi.unitn.it/~montreso/ds/papers/montresor17.pdf
https://munin.uit.no/bitstream/handle/10037/13115/thesis.pdf?sequence=2&isAllowed=y



1. Gossip 프로토콜 일반 

이더리움의 DEVp2p 네트워킹보다는 비교적으로 간단한 편인 하이퍼레저 패브릭에서의 네트워킹구조 를 살펴보자.
참고로 이더리움의 DEVp2p 에 관련되어 이전에 작성한 글이 있으니 Public 체인에서는 어떻게 하는지 참고 하자.

-> [Ethereum] Node Discovery with Kademlia 
-> [이더리움 코어] DevP2P 소스코드 분석 (feat. golang) 

암튼 둘다 Goosip 을 이용하는데, Gossip 즉 소문이란 무엇인가? 내가 주변 몇사람한테 연예인에 대한 잘못된 소문을 내는 순간에 그들이 또 각자 소문을 내고 이런식으로 내가 전체에게 알리지 않아도 전체가 알게 되는 것을 말하며 특징은 이런것이 있을 수 있겠다..

- 전체에 말하지 않아도 되는 편리함.
- a 라는 사람에게 도착하는 순서가 정해져 있지 않음.  
- 어떤 경로가 끊어져도, a 는 다른 경로를 통해서 전달 받을 수 있다.

- 중복 전달 될 수도 있다. 
- 최악의 상황에선 전달 못 받을 수도 있다. 
- 구라가 횡횡 할 수 있다. 

이런 특징을 그대로 구현한것이 Goosip 프로토콜이다. 근데 좀 룰을 정한게 있는데 

- 한 노드는 부트스트랩 노드를 통해 시작하며, 부트스트랩 노드 각각이 가지고 있는 Alive 노드정보를 합쳐서 셋 구성. 
- 한 노드는 주변 연결된 노드들이 살아있는지 계속 확인 해야한다. 
- 한 노드는 자기가 알고 있는 노드들의 전체 정보를 주변에 주기적으로 알려줘서 전체 네트워크가 현재 상황에 대해 알 수 있게 한다.
- 소문을 낼 때에는 전체노드중 몇개를 랜덤하게 정해서 소문을 퍼트린다.
- 소문을 반복해서 퍼트리지 않는다.전에 받은 소문은 다시 처리하지 않는다.
- 이더리움은 구라를 수용하면서 신뢰를 만들고, 하이퍼레저 패브릭은 인증을 통해서 원천봉쇄한다. 

자 이 아이디어를 머리에 담고 하이퍼레저 패브릭을 살펴보자.

이미 아시는 분도 있겠지만 하이퍼레저 패브릭의 워크플로우를 간단히 설명하면,

1. 클라이언트는 어플리케이션(SDK)를 통해서 Peer 들에게 트랜잭션을 실행 시킨다.
2. Endorsement역할을 하는 이 Peer 들은 체인코드를 실행시키고 장착된 체인코드 로직에 따라서 결과를 내어 다시 클라쪽으로 read/write 셋을 전달 한다.(이 과정에서 장부를 업데이트 하지 않음)
3. 이 결과 셋을 가지고 orderer 서비스에게 순서를 정해서 블록화 해달라고 요청한다.
4. orerer 서비스는 블록화 하면 Peer 들은 이 블록을 가져와서 검증하고 저장한다.

이 과정 중에서 Gossip 프로토콜이 이용되는것이 바로 4번 flow 에서이다. 
즉 orderer 는 모든 peer과 커뮤니케이션을 하는게 아니라, 조직별 대표 peer 하나에게 알리면 이 peer 가 gossip 을 통해 점진적으로 전체로 전달되게 되는 것이다. 각 피어는 전달받은 블록(트랜잭션 뭉치들)을 검증하고 장부(상태DB&블록체인) 에 저장한다. (Gossip은 또한 Peer간 Sync를 맞추는데도 사용된다.)



조금 더 구체적인 위의 그림에서는 5번에 해당한다. 리더피어(그림이 잘못됨. 앵커피어가 아니다) 에게만 전달하면 그것이 소문(블록정보)을 퍼트리기 시작한다. 그림에는 브로드캐스트 처럼 보이지만 저기서 가쉽이 사용되는데, 조직마다 리더피어가 있으며, 그 녀석이 오더러에게서 Pull 하면서 시작된다. 그림에는 구분이 안되어 있지만 추가적으로 하나의 조직이 다른 조직의 Peer 와 연계할 때에 Anchor Peer 를 이용한다. 리더피어=Anchor Peer 로도 설정 할 수도 있다.

2. Gossip 프로토콜 with 하이퍼레저 패브릭 

다시 좀 더 자세히 알아 보자. 위에서 설명했듯이 하이퍼레저 패브릭에서는 트랜잭션 실행 피어와 트랜잭션 정렬 피어 사이에서 업무를 분담해서 CPU부하(고루틴을 통함)  및 네트웍 부하를 처리한다. 이런 분리된 네트워크에서 확장성,보안성, 일관성 등에 대한 처리를 유연하게 하기 위해 패브릭은  가쉽 데이터 전파 프로토콜 을 만들었다. 

각 가십메세지들은 서명되어서 전달되며, 그에 따라 중간에 악의적인 노드의 메세지도 쉽게 확인되며, 가쉽 프로토콜 특성상 늦게 도착하거나, 몇몇 노드들의 네트워크 분단 상황에서도 결국 싱크는 맞춰지게된다.   

패프릭 네트워크에서 가쉽 데이터 전파 프로토콜  주요 3가지 기능으로는 다음과 같다.

  1. 피어 발견 및 채널 멤버쉽을 관리한다. (이용 가능한 피어들을 계속해서 체크함)
  2. 장부에 기록 할 데이터들을 모든 채널 상의 피어들에 전파.싱크가 안맞는 피어들을 확인하여 모자란 블럭 정보들을 계속해서 공급해줌. 
  3. 새로운 피어가 참여하면 peer to peer 로 장부 데이터들을 업데이트 해줌. 

동일한 채널위의 피어들은 메세지를 계속해서 수신하고,주변 피어에 전파하며, 싱크를 맞추게 된다. 이웃피어의 갯수는 설정으로 정해져 있으며, Pull 메커니즘을 따른다. 따라서 메세지가 올 때 까지 기다리는게 아니라, 적극적으로 가지고 오려는 행동을 한다. 채널 상의 각 조직의 리더 피어는 오더러에게 데이터를 가져(Pull)온 후 자신의 조직에 포함된 피어들에게 전파하기 시작한다.

리더 선출 

다음과 같이 2가지 방식이 있다.

  1. Static – 시스템 관리자는 조직마다 하나의 피어를 설정으로 정한다.

    core.yaml: 파일에서 

    peer: gossip: useLeaderElection: false orgLeader: true

    환경 설정에선 이렇게 하면 core.yaml을 덮어 쓴다. 

    export CORE_PEER_GOSSIP_USELEADERELECTION=false
    export CORE_PEER_GOSSIP_ORGLEADER=true
  2. Dynamic – 리드 선출 프로세스에 따라 결정되는데, 리드 피어가 문제가 생기면 각 피어들은 리드 선출과정에 돌입한다. 리드 선출 프로세스는 피어의 메타데이터에 기반하는데 각 피어가 가지고 있는 ID를 정렬해서 낮은 것이 리드가 되는데,  이 과정에서 여러개의 피어가 자신이 리더라고 주장 할 수 있지만 가장 낮은 이름을 가진 피어가 결국 우선권을 가지며  정해 진 시간 동안 선출이 안되면 그 다음 낮은 피어가 자신이 리더라고 주장 할 수 있게 된다.   좀 더 자세히 -> 요기 

3. 앵커(Anchor) 피어 

앵커 피어는 다른 조직들 간의 네트워크 정보를 가지고 있으며,  Service Discovery에 의해 사용 될 수 있다 즉 클라이언트는 네트워크에 누가 있는지 없어졌는지 등에 관한 상태정보를 알아야 한다. 또한 가쉽 커뮤니케이션을 용이하게 하기 위해 사용된다. 즉 하나의 조직에서 다른 조직에 메세지를 전달 할 때 앵커피어에게 전달한다. 체인코드를 호출 할 때 보증을 맡는 피어가 자신의 조직에만 있는 것은 아니기 않나. 그리고 이 말은 앵커피어는 조직 간 커뮤니케이션에 SPOF도 된다는 의미이다.앵커피어에 대한 정보는 채널 설정에서 정의 되어 있다. 참고로 앵커피어가 리더피어 역할을 해 되도되며, 독립적일 수 도 있다. 

Note

1. 각 피어간의 메세지 보안은 TLS 레이어에 의해 작동하며 서명을 요구하지 않는다. 피어들은 그들의 신원서(CA에 의해 부여된 certificates) 로 인증(authenticated)되며, TLS certs(ECerts를 가지고 TCA에 의해 만들어지는)가 사용될 지라도, 가쉽레이어에서 인증되는 것은 피어신원서이다. 장부블록은 오더러 서비스에 의해 서명되며, 채널의 리더피어들에게 전달된다. 

2. 인증(Authentication)은 피어의 "MSP" 에 의해 조정받는다. 피어가 처음으로 채널상에 접속하게 되면,TLS 세션은 멤버쉽 아이덴터티와 엮이는데, 이것은 기본적으로 각각의 피어를 채널과 네트웍상의 멤버쉽으로 인증하는 것이 된다. 

4. Dissenmination protocol

가십 구성요소는 다음 2개의 모듈로 구성된다.

discovery 모듈 - 활성 상태 및 응답하지 않는 피어 세트 유지

communication모듈 - 연결 유지 및 메시지 배포

discovery 모듈은 통신 모듈을 사용하여 자신의 정보를 보내고, 통신 모듈은 discovery 관련된 메세지를 discovery 모듈에 전달.

discovery 프로토콜 흐름 
각 피어는 생성시 부트스트랩 피어 세트 B를 받는다.
각 피어는 알려진 멤버 집합을 나타내는 <id, endpoint, logical_timeestamp> 의 세트 V응답하지 않는 피어의 유사한 세트 H를 가지고 있으며, V[id]는  ID 요소를 가진 피어의 마지막 활성 메시지를 나타낸다.
메시지는 네트워크 CA에서 발급한 Pub(Priv) 키를 이용하여 암호화된 방식으로 서명(검증)할 수 있다.

discovery 프로토콜 정의 

1) H=BV={}
2) 시작할 때 H의 모든 p에 연결을 시도 하고 성공하면 p를 V에 추가하고 통신 모듈을 업데이트.
3) 
각 p(i) 로 부터 그들이 가지고 있는 V 를 통합하여 자신의 V를 구성. 
4) 
V가 변경된 경우 통신 모듈을 업데이트하여 새로 추가된 모든 피어에 대한 연결을 생성하고 응답하지 않는 피어에 대한 연결을 제거
5) 매 T초 마다 
피어 p는 다른피어들(랜덤이지 않을까)에 자신의 alive 메시지를 전달.메시지에는 다음이 포함된다.  

          P(id), P(endpoint), m (inc_number: P가 돌기 시작한 시간, seq# :각 alive메세지마다 하나씩 증가 )
6) 
discovery 세트 V의 각 p 멤버에 대해, Alive 메시지의 마지막 시간을 추적한다. 받은지 5초가지났다면 
         V세트로부터 그 p를 제거 / H세트에 그 p 추가 / 통신모듈에서 커넥션을 종료 
7) 어떤 
피어 p에서 Alive 메시지 m 을 수신 시:
     - m.id이 V에 없으면 추가 
    - m.timestamp.inc_number > V[id].timestamp.inc_number , m.timestamp.seq# > V[id].timestamp.seq# 면 V에 추가 

Communication 프로토콜 흐름 
원장에 전달되지 않은 메시지에 대한 내부 버퍼 유지
피어에서 수신한 각 메시지에는 확인할 수 있는 ID가 있는데 이 ID는 Alive 메시지인 경우 <m.timeestamp, p.id> 또는 원장 배포 블록인 경우 원장 블록의 해시입니다. 어느 쪽이든 프로토콜 통신 프로토콜은 메시지의 ID를 m.id로 참조합니다. - 각 피어는 이전에 발생한 메시지 ID의 세트 R과 수신 시간을 주기적으로 보유하고 5 초 이상 전에 수신 된 오래된 메시지 아이디를 삭제합니다.

 Communication 프로토콜 정의  1) 시간 t에 피어 p(i)로부터 메시지 m을 수신하면 -> 메시지가 Alive 메시지 인 경우 ->discovery 모듈로 전달 2) 이전에 m.id ID를 받은 경우 (R에서 발견할수있음) 메시지를 삭제 3) 그렇지 않다면    3-1)  <m.id, m.t> 를 R 로 추가 
   3-2)  V로 부터 
k = log (| V |) + 3 개의 무작위 피어를 선택 - 각각의 p(i)가 응답하지 않으면 discovery 모듈을 업데이트하여 p(i) 를 H에 추가 - m 을 무작위 피어 p(i) 들에 전파 (모아 보낼 수도)
4) T 초마다 한 번, V의 각 P에 대해 k = log (| V |) + 3 개의 무작위 응답 피어 세트를 선택하고 4-1) V 전체를 p(i) 들에게 보냄.
   4-2) V(p,i), H(p,i)를 얻어서 V(p,i) , H(p,i) 에 있는 각 p를 V,H에 추가한다. 4-3) Send to p your highest ledger originated message sequence number s for which you received before all m s.t ,i < s 4-4) If you have before received a message m(j) that wasn't passed to the ledger yet such that j>s , send to p a bitmap b of all sequence numbers from s to j , And receive back from p(i) a set of messages M(i) and (optionally) its own s , b 4-5) M의 메시지를 내부적으로 저장 4-6) (Optionally) send back to p(i) a crafted M of your own according to p(i) 's s(i) , b(i) sent 5) 사용자 또는 discovery 모듈로부터 메시지 m을 수신하면 : 5-1) <m.id, m.t>를 R에 추가하십시오. 5-2) k = log (| V |) + 3 개의 랜덤 피어 집합을 선택하고 m을 그들에게 보낸다.

이렇게 정리된 내용을 토대로 다음에는 실제 하이퍼레저 패브릭의 gossip 에 관련된 코드를 분석해 보도록 한다. 언제하냐고? 신이 나면~~

개인적으로 간략하게 구현 해 본 블록전파/가쉽프로토콜 바로가기  



레퍼런스:

https://hyperledger-fabric.readthedocs.io/en/release-1.2/gossip.html
https://github.com/hyperledger/fabric/tree/release-1.2/gossip
https://jira.hyperledger.org/browse/FAB-170


+ Recent posts