-
[블록체인] 하이퍼레저 패브릭으로 네트워크 생성하자블록체인 2022. 3. 24. 10:48반응형
1. 비트, 이더와 공통점 / 차이점
비트와 이더는 가장 많이 쓰이는 퍼블릭 블록체인입니다.
패브릭과 비교했을 때 가장 큰 차이는 비트와 이더는 '프로그램 하나가 모든 것을 처리한다' 이고,
패브릭은 프로그램이 하나가 아니라 구성요소가 나뉘어 있어서 각 역할이 분배돼 있다.
즉, 비트와 이더는 블록 생성자와 스마트 컨트랙트 처리 노드가 동일하지만, 패브릭은 각각을 오더와 피어로 나누어 수행한다.
비트와 이더에서는 마이닝, 지갑, 블록체인 저장, 월드 스테이트를 갖고 있어서 프로그램을 하나 깔면 모든 기능을 할 수 있다.
패브릭의 경우 역할이 나눠져 있으므로
오더러(orderer)는 블록을 생성하는 역할만 한다. 피어(peer)는 시뮬레이팅과 블록체인 보관을 하는 역할을 한다.
물론 오더러도 블록체인을 보관하긴 한다.
또한 비트와 이더는 개인키를 기반으로 한다. 이 개인 키를 이용해서 돈을 주고 받는다. 그러나 패브릭에서는 사용자(컴포넌트)들끼리 상호작용을 하기 위해서 '인증서'가 필요하다.(인증서 기반)
공인인증서를 사용하는 예시를 생각해 보자면, 공인 인증서는 은행에서 발급받아서 인터넷에서 사용한다. 이처럼 은행 같은 중간 관리자가 필요하다.
패브릭에서는 공인 인증서처럼 중간 관리자가 있고 이것의 이름은 패브릭CA(Certificate Authority)이다. 이것도 프로그램이다.
얘가 피어랑 오더러에게 인증서를 발급해주고, 사용자는 이를 활용해 활동한다.
마지막으로, 패브릭은 하나의 키 값을 한 블록에서 여러 번 바꿀 수가 없다. 패브릭은 블록의 값을 쉽게 바꿀 수가 없는데, 이 때문에 패브릭의 체인코드는 살짝 다르다.
2. 패브릭의 구성요소
1. 피어
2. 오더러
3. app(유저)
피어는 블록체인을 보관한다. 블록체인을 보관한다는 건 원장을 갖고 있다는 얘기. 원장 안에는 월드 스테이트가 보관돼 있다.
또, 피어는 트랜잭션을 시뮬레이팅하는 역할을 한다.
오더러는 블록을 생성하는 역할을 한다. 트랜잭션을 모아서 블록을 생성만 한다. 시뮬레이팅은 하지 않는다.
앱은 인증서, 지갑을 갖고 피어와 오더러랑 상호작용한다.
패브릭에서 피어와 오더러가 나뉘어져 있기 때문에 컨트랙트를 짜는 로직이 달라지게 된다.
앱에서 트랜잭션이 생성되면, 이것들을 피어에게 보낸다. (proposal)
이 거래는 블록에 들어가는 게 아니라 시뮬레이팅 하게 된다. 각 피어는 시뮬레이팅 결과를 반환한다(BFT, 투표)
거래가 인증(피어가 ok하면) 앱은 오더러에게 트랜잭션을 보낸다.
오더러는 트랜잭션을 모아서 피어들에게 해당 트랜잭션이 허락을 받았는지 검증을 한다. 이후 블록을 생성해 피어에게 보낸다.
피어들은 블록체인에 생성된 블록을 연결하고 월드 스테이트를 업데이트 하게 된다.
비트와 이더에서는 한 노드(프로그램)가 트랜잭션을 모으고 블록을 생성해서 모든 걸 다하는데, 패브릭에서는 트랜잭션을 시뮬레이팅하는 부분과 블록을 만드는 부분이 나뉘어져 있다.
3. 원장 (Ledger)
원장에는 두 가지 구성 요소가 있다.
1) 블록체인
2) 월드 스테이트 : 최신 상태
패브릭에서 월드 스테이트는 피어만 보관하고 있다. 그리고 피어만 이 원장을 활용한다.
4. 합의 (Endorsement)
패브릭에서는 합의를 위해 투표 방식을 채택한다. 이 투표 방식의 이름은 BFT이다.
5. 패브릭의 과정
애플리케이션이 인터넷에 연결돼 있다고 한다. 그말인즉슨 피어들과 연결돼 있다는 뜻이다.
앱에서 트랜잭션을 생성한다. ( == invoke chaincode) ( == proposal)
proposal은 피어에게 전달돼 피어들이 시뮬레이팅한다.
시뮬레이팅 ) peer invokes chaincode with proposal and then chaincode generates query or update proposal response
피어는 시뮬레이팅한 결과를 앱에게 전달한다. (proposal response)
앱은 피어들의 결과를 오더러에게 보낸다. (request that transaction is ordered)
오더러는 피어들이 합의한 결과를 확인한 후 블록을 생성한다.
생성한 블록은 오더러가 피어들에게 준다.
피어는 오더러에게 받은 블록을 원장에 기록한다.
일반적으로 앱에서 최초에 생성한 트랜잭션은 다음과 같은 정보를 갖고 있다.
1) clientID
2) chaincodeID (스마트 컨트랙트의 아이디)
3) txPayload (내용들) tx는 트랜잭션이라는 뜻
4) timestamp
5) clientSig
6. read set , write set (read-write set semantics)
패브릭에서 체인코드가 어떤 식으로 읽고 쓰는지
체인코드를 작성하고 체인 코드 구조를 설계할 때의 핵심이 된다. 이 부분을 이해해야 tps가 높은 체인코드를 설계할 수 있다.
패브릭에서 체인 코드가 값을 쓰는 방식은 KVS(key-value Store) 방식이다. Level DB, couch DB를 쓸 수 있다.
레벨DB는 속도가 빠르고, 카우치DB는 복잡한 query문을 날릴 수 있다.
체인코드(스마트 컨트랙트) 안에서 어떻게 쓰냐면 setKey해서 a:100, b:hello 이런 식으로 값을 저장하는 방식이 끝이다.
특징은 한 블록 내에서 하나의 키 값을 여러 번 읽어서 바꿔 쓸 수가 없다. 즉, 한 번 write한 값을 다시 읽어서 바꾸는 건 허용되지 않는다.(read-your-writes semantics are not supported)
ex) 한 번 write 한 키에 대해서는 read를 하면 실패한다.
World state: (k1, 1, v1), (k2, 1, v2), (k3, 1, v3), (k4, 1, v4), (k5, 1, v5) T1 -> Write(k1, v1'), Write(k2, v2') T2 -> Read(k1), Write(k3, v3') T3 -> Write(k2, v2'') T4 -> Write(k2, v2'''), read(k2) T5 -> Write(k6, v6), read(k5)
월드 스테이트에는 5개의 키와 이에 대응 하는 값이 저장돼 있다. 1은 버전 넘버
각 트랜잭션들은 한 블록에 들어가게 되는데 이 트랜잭션들의 성공/실패 여부를 따진다면,
T1) 키 값을 바꾼다. == passes validation
T2) k1을 읽는다. 근데 k1은 T1에서 이미 write 했으므로 == fails validation
T3) passes validation
T4) fails
T5) passes
비트나 이더에서는 값을 여러 번 읽고 쓰는 게 가능하다. 이더에서는 블럭을 만드는 마이너가 트랜잭션이 외부에서 들어 올 때마다 트랜잭션을 보관하고 시뮬레이팅한 후 pow를 성공하게 되면 블록을 추가하게 된다. 즉 마이너가 모든 트랜잭션을 보관하고 있기 때문에 히스토리를 알고 있는 것이다.
그러나 패브릭의 경우에는 피어가 승인한 값이 앱을 거쳐 오더러에게 전달되고, 피어는 트랜잭션을 검증한 후에 이를 보관하지 않는다. 그래서 새로운 블록이 오더러에게 생성되어 피어에게 전달되기 전까지 피어들은 read를 할 수 없다.
이에 패브릭은 키를 어떻게 주냐면 high-throughput network를 사용한다. 태깅
A: 100 (기존 블록)
A1 : +100
A2: -50
A3: +100
A: 150
한 블록에 한 트랜잭션만 담으면 오버헤드가 커지므로 저렇게 태그를 이용해서 연산을 모아서 처리한다. 또 다른 방법으로는 utxo 구조를 사용할 수 있다.
7. 하이퍼레저 패브릭 예제 구현
하이퍼레저 패브릭 전체 구성에 대해 알아보고 이를 예제 코드로 구현해 보도록 한다.
하이퍼레저 패브릭은, 허가형 네트워크 이므로 각 조직을 나타내는 네트워크가 구성돼야 한다.
앞으로 엔도싱 / 커밋 피어 / 오더러 / CA/ 원장/ 블록체인 / 월드 스테이트를 간단히 구현하여 어떻게 상호작용 하는지 알아보도록 한다.
각 구성 요소에 대한 설명은 다음과 같다.
- 엔도싱 피어 - 클라이언트가 발생시킨 트랜잭션을 계산/보증한 후 리턴
- 커밋 피어 - 오더러가 보낸 블록을 장부에 기록
- 오더러 - 보증된 트랜잭션(Read/Write Set)을 받아서 정렬한 후 블록으로 만들어서 커밋 피어에게 전달
- 카프카 - 오더러가 정렬할 때 사용하는 도구
- CA - 피어와 사용자에게 암호화 재료를 만들어 주는 도구
- MSP - 허가형이므로 신원 검증을 하는 도궁
- 원장 - 블록체인과 상태저장소를 갖고 있음
- LevelDB - key-value 맵으로 상태를 저장함
합의 시스템
블록체인에 블록을 저장하기 위해서는 피어들의 합의가 필요한데, 하이퍼레저 패브릭에서 합의 과정은 다음과 같다.
<실행 & 보증> - <정렬> - <검증 & 저장>
장부에 쓰기와 읽기를 구현하는 코드를 생성해 보자.
- 쓰기
쓸 데이터를 백엔드로 보내면 백엔드에서는 하이퍼레저 패브릭 시스템과 통신할 수 있는 SDK를 호출한다. 이 연결고리를 미들웨어라고 한다.
1. 미들웨어는 엔도싱 피어들에게 트랜잭션을 요청한다.
2. 엔도싱 피어들은 해당 트랜잭션에 대한 체인코드를 호출하여 실행하고 결과값(RWSet)을 미들웨어로 돌려준다.
3. 미들웨어는 RWSet을 받아서 보증에 대해 확인한다. 확인 후 오더러에게 RWSet을 보내준다.
4. 오더러들은 받은 트랜잭션을 카프카 채널에 Push하고, Pull하여 정렬한다.
5. 오더러는 정렬된 트랜잭션 모음을 카프카에게 받아서 블록으로 생성한다.
6. 완성된 블록을 커밋 피어로 보낸다.
7. 커밋 피어는 블록을 검증하고 원장에 저장한다.
-읽기
백엔드에서 하이퍼레저 패브릭 시스템과 통신할 수 있는 SDK를 호출한다.
1. 미들웨어는 커밋 피어에게 트랜잭션을 요청한다.
2. 커밋 피어는 원장에서 정보를 가져온다.
3. 미들웨어에 반납한다.
func WriteTrans(key string, value string) string { rwset1, rwset2 := fabric.WriteTransaction(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" }
미들웨어에서 패브릭으로 저장 정보(key, value)와 자신의 신원증명(msp)을 매개변수로 트랜잭션을 일으킨다.
반환 값(rwset)을 받아와서 각 피어들의 보증정보를 확인한 후에 오더러로 전송한다(SendToOrderer)
패브릭에서 오더러에게 전송하는 값들은 라운드 로빈 방식으로 처리한다.
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 } } }() }
카프카를 이용해 정렬한 트랜잭션들을 가지고 임시 블록을 만든 다음에 커밋 피어로 전송한다.
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 } } }() }
오더러가 보낸 임시 블록을 받아서 검증하고 각각의 트랜잭션을 StateStorage에 저장하고 블록체인에도 연결한다.
func (l *Ledger) addBlock(block Block){ ledger_mutex.Lock() prevBlock := l.Blockchain[len(l.Blockcahin)-1] newBlock := l.generateBlock(prevBlock, block) l.Blockchain = append(l.Blockchain, newBlock) spew.Dump(newBlock) ledger_mutex.Unlock() }
블록을 만들어서 이전 블록에 연결하는 함수
오더러에서 커밋피어로 블록을 주면, 커밋 피어가 블록을 저장하고 끝나는 코드를 작성했다.
그런데 원래는 커밋피어는 커밋 피어들끼리 가십프로토콜을 통해 블록을 전달한다. 이 부분을 코딩해 본다.
블록전파(가십프로토콜)
다른 피어에게 랜덤하게 블록을 전파하는 부분
func (p *Peer) propagate(msg Msg){ fmt.Printf("[Peer] propagate [&s} \n", msg.Payload) p.node.pm.propagationBlockSig <- msg }
분산 네트워크를 구성하기 위해 도커를 이용한다. 가십 프로토콜을 이미지로 생성한다.
# Start from a Debian image with the latest version of Go installed
# and a workspace (GOPATH) configured at /go.
FROM golang
# Copy the local package files to the container's workspace.
ADD . /go/src/github.com/wowlsh93/hyperledger-fabric-400-gossip
#Build the gossip command inside the container
RUN go install githuc.com/wowlsh93/hyperledger-fabric-400-gossip/gossip위의 도커파일을 통해 gossip이라는 이름의 도커이미지를 만든다.
docker build -t gossip .
이미지는 golang을 사용하여 빌드될 때 현재 프로젝트를 컨테이너의 /go/src/github.com/wowlsh93/hyperledger-fabric-400-gossip 위치에 복사해 놓는다.
만들어진 이미지로 실행해 보자.
docker run -p 28000:28000 --net host --name gossip1 --rm
출처: https://hamait.tistory.com/1019 [HAMA 블로그]-p : 외부 포트와 내부 포트
-rm : 컨테이너가 내려가면 자동으로 삭제되게 한다.
두 번째 터미널을 열어서 일반 피어를 위한 컨테이너를 띄운다.
docker run -p 28001:28001 --net host --name gossip2 --rm
예제 함수를 통해 하이퍼레저 패브릭의 각 요소들이 상호작용하는 모습을 이해했다.
하이퍼레저 패브릭을 사용한 개발은 UI가 부족하므로 개발 단에서 우분투에 익숙해져야 하며, 기존 네트워크를 사용하는 것이 아닌 새로운 네트워크를 구성하며 직접 코드를 구현하므로 정말, 스크래치부터 시작하는 것임을 알 수 있다.
반응형'블록체인' 카테고리의 다른 글
[블록체인] 패브릭 아이덴티티 (0) 2022.03.25 [블록체인] 패브릭 네트워크 세팅 (0) 2022.03.25 [블록체인] 체인코드 동작 과정 (0) 2022.03.23 [블록체인] 하이퍼레저 패브릭 - 블록체인 (0) 2022.03.22 [블록체인] 블록체인의 수익 창출 원리 (0) 2022.03.19