블록체인

400라인의 go코드로 구현한 하이퍼레저 패브릭 [1] - 전체조망

[하마] 이승현 (wowlsh93@gmail.com) 2019. 1. 24. 18:25


400라인의 go코드로 구현한 하이퍼레저 패브릭 [1] - 전체조망 

몇일전에 200라인으로 구현한 블록체인 (golang) 을 우연히 알게 됬는데 해당 블로그 주인은 블록체인/네트워크/P2P/POW/IPFS 등 다양하게 구현해 놓았습니다. 초창기 블록체인에 대한 이해가 잘 안갔을때 저렇게 간략히 구현된 코드를 보고 오히려 이해가 잘 됬던 경험이 떠올라 그렇다면 하이퍼레저 패브릭을 간단하게 구현해 놓으면 누군가에겐 도움이 될 수 있지 않을까 하여 코드를 구현 해 봤습니다.

왜 golang 냐구요? 
예전에 스칼라 기반의 Akka로 코딩을 즐겁게 한 기억이 있는데, go의 동시성 모델은 더욱 심플하고 자유로우며 훨씬 더 큰 재미가 있었습니다. 그리고 최근엔 많은 서비스에서 golang이 채택되고 있으며 특히 하이퍼레저 패브릭과 이더리움이 Go로 만들어져 있습니다.  :-) 

처음에는 유행(?) 에 따라서 200라인으로 짜려고 했는데 구성요소들이 너무 많아 도저히 불가능 하였으며, 400라인 정도지만 중요한 구성요소들은 시뮬레이션 구현 하였습니다. 수만라인 이상의 코드를 400라인으로 압축시켜 놓았기 때문에 디테일한 부분은 많이 생략되었으며, 작동방식의 차이도 있습니다. 하나의 파일에 모두 구현하고자 각자 모듈로 나누어져서  TCP스트리밍이나 RPC 로 커뮤니케이션하는 부분을 없애고 하나의 프로세스안에서 돌아가게 하였습니다. 글 마지막쯤에 완성도를 높히기 위해 추가하여야 할 것들을 정리 하였으니 참고하세요.


500,000 TPS!!

이 간략한 블록체인 시스템은 제 노트북에서 500,000tps가 나왔는데요, 대단하다구요? 전혀 아닙니다. 

1. 네트워킹 부하
2. 체인코드(스마트컨트랙트) 실행 부하
3. 암호화/복호화/서명/인증 부하
4. CFT(crash fault tolerance) 를 위한 부하 

위의 주요4가지 부하가 없어서 그런데요. 이 4가지말고 가장 중요한 부하가 또 하나 있는데, 그게 신뢰의 비용입니다.비트코인에서는POW의 비용이 가장 크거든요. 이렇듯 속도가 빠르다고 자랑하는 블록체인 시스템들은 신뢰의 비용을 줄였다고 생각하면 됩니다. 이더리움처럼 신뢰 비용을 최대한 줄이지 않으면서 속도를 빠르게 하려는 도전을 
묵직하게 하는 모습은 그런면에서 참 대단합니다. 


- 하이퍼레저 패브릭이란?

아키텍쳐

하이퍼레저 패브릭은 콘소시엄 블록체인으로 허가된 조직이 체인에 참여하여 신뢰를 구축하는 모양새를 가집니다. 하이퍼레저 패브릭을 적용하기 위해서는 먼저 해당 분야에 "조직" 이라는 네트워크가 존재해야하며, 그 "조직" 간에 어떠한 신뢰 비용이 생기는지 대략적으로 라도 파악 할 수 있어야 합니다. 그런 조직들 간의 신뢰비용을 낮추기 위해 사용되는 분산저장소라고 생각하시면 되요.

                                                               (이미지 - packtpub 출판사에서 참조)

 따라서 전반적인 구성은 일반 퍼블릭 블록체인보다는 다양하며, 해결해야하는 코어 문제는 퍼블릭 블록체인보다는 비교적 쉽지만 응용SI를 하기 위해서는 굉장히 복잡해 보이는게 사실 입니다. 400라인의 코드안에는 위의 도식에서 보이는 Endorse / Commit Peer, Orderer , Fabric-CA, Ledger, Bockchain, WorldState등이 간단히 구현되어 어떻게 서로 유기적으로 작동하는지 살펴 볼 수 있는데요 하나씩 간략하게 설명하면 

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

합의시스템

하이퍼레저도 블록체인이기 때문에 합의 시스템이 있답니다.



실행/보증-정렬-검증/저장 의 3단계를 거치는 합의가 하이퍼레저 패브릭의 기본 뼈대 합의를 이루며, 가운데의 Ordering부분을 여러가지 다양한 합의체로 구현하여 Pluggable 하게 붙힐 수 있는데요, Ordering부분의 합의는 신뢰의 합의는 아닙니다. 그저 Crash Fault Tolerance적인 합의에요.

- Hyperledger fabric 400 구성도 

제 코드의 주요 흐름은 2가지로 장부에 쓰기/읽기가 있습니다. 아래에 모듈간의 흐름을 그려보았는데요.

쓰기 순서
브라우저에서 신용장을 만들고 싶어서 해당 데이터를 백엔드로 보냅니다. 
벡엔드에서는 하이퍼레저 패브릭 시스템과 통신할 수 있는 SDK를 호출 합니다. 이 부분이 코드에서 미들웨어입니다.
1. 미들웨어는 Endorsing Peer들에게 트랜잭션을 요청함
2. Endorsing Peer 들은 해당 트랜잭션에 대한 체인코드를 호출하여 실행하고 결과값(RWSet)을 미들웨어로 돌려줌
3.  미들웨어는 RWSet을 받아서 보증에 대한 확인후 이상없으면 Orderer에 RWSet을 보냅니다.
4. Orderer들은 받은 트랜잭션을  Kafka의 채널에 Push하고 , Pull 하여 정렬합니다.
5. Orderer는 정렬된 트랜잭션 모음을 받아서 블록으로 가공합니다.
6. 가공된 블록을 Commit Peer로 보내고
7. Commit Peer는 블록을 검증하고 Ledger (Blockchain + State Storage)에 저장합니다.

읽기 순서
브라우저에서 신용장을 읽고 싶어서 해당 데이터를 백엔드로 보냅니다. 
벡엔드에서는 하이퍼레저 패브릭 시스템과 통신할 수 있는 SDK를 호출 합니다. 이 부분이 코드에서 미들웨어입니다.
1. 미들웨어는 Commiting Peer에게 트랜잭션을 요청함
2. Commit Peer는 Ledger (State Storage)에서 정보를 가져옵니다.
3. 미들웨어(클라이언트)에 반납합니다.

- 주요 코드를 살펴봅시다.

func WriteTrans(key string, value string) string {
rwset1, rwset2 := fabric.WriteTranaction(key, value, fabric.MSP_org1)
if rwset1.msp == fabric.MSP_peer1 && rwset2.msp == fabric.MSP_peer2 {
msps := [] string {rwset1.msp , rwset2.msp}
rwset := RWSet{ key:key, value: value, peers_msp: msps}
fabric.SendToOrderer(rwset)
return "ok"
}

return "failed"
}

미들웨어(client)에서 fabirc으로 저장정보와 자신의 신원증명을 매개변수로 트랜잭션을 일으킨 후 에 RWSet을 받아와서 각 피어들의 보증정보를 확인 후에 오더러로 전송합니다.(오더러 중에 살아있는 녀석으로 보냅니다.) fabric내부에서는 라운드로빈방식으로 각 오더러로 분배합니다.


func (o *Orderer) consumer() {
go func() {
for {
rwsets := o.kafka.Pull()
if rwsets == nil {
runtime.Gosched()
continue
}
newBlock := o.createBlock(rwsets)
for _, committer := range o.committer {
committer.addblock <- newBlock
}
}
}()
}

카프카를 통해서 받은 정렬된 트랜잭션을 가지고 임시블럭을 만든 후에 committing peer로 전송합니다. 


func (p *Peer) committing() {
go func() {
for {
select {
case block := <-p.addblock:
ok := p.validating(block)
if ok == false {
continue
}
for _, trans := range block.Trans {
p.ledger.setState(trans)
}
p.ledger.addBlock(block)
case <-p.peer_done:
return
}
}
}()
}

Orderer가 보낸 임시블럭을 받아서 검증하고 각각의 트랜잭션을 StateStorage에 저장하고 블록체인에도 연결해줍니다. 

func (l *Ledger) addBlock(block Block) {
ledger_mutex.Lock()
prevBlock := l.Blockchain[len(l.Blockchain)-1]
newBlock := l.generateBlock(prevBlock, block)
l.Blockchain = append(l.Blockchain, newBlock)
spew.Dump(newBlock)
ledger_mutex.Unlock()
}

리얼블록을 만들어서 이전 블록에 연결되고 있습니다.

- 추가 해야 할 것들 

@ 각 구성요소들을 모듈로 나누고 각각의 프로세스로 돌게 함.
@ 각 구성요소들간에 통신을 위한 TCP streamming 과 RPC를 구현한다.
@ 암호화 통신/프로토콜 정의/피어간 가쉽프로토콜 추가
@ 채널,조직,콘소시엄등에 대한 추상개념 적용 
@ Fabric-CA가 제대로된 PKI 기능을 하게 기능을 추가하고, 각 구성요소들은 MSP를 통해 검증기능을 갖게함
@ 체인코드 해석기
@ Order에 대한 합의알고리즘 구현 (kafaka방식/Raft방식/BFT방식)
@ LevelDB or CouchDB를 통한 상태저장
@ 커맨드라인옵션처리/Config/로깅/에러처리/주석/문서/도커배포 
@ 등등 

- 구현 코드 
https://github.com/wowlsh93/hyperledger-fabric-400

- 다음 시리즈 
400라인의 go코드로 구현한 하이퍼레저 패브릭 [2]- 블록전파/Gossip 프로토콜