Hyperledger Fabric v1.1 release

부제: 하이퍼레저 패브릭 1.1 버전, 뭐가 달라졌나

Special Thanks to Maciek Jędrzejczyk for having us read the fantastic original article and letting me publish it into Korean.


본 포스팅은 IBM Blockchain Technical Leader인 Maciek Jędrzejczyk의 Linkedin 포스트를 번역하고, 역자로서 제 의견을 더하는 방식으로 작성되었습니다. 이 글은 세 개의 챕터로 나뉩니다. 가장 먼저 하이퍼레저 패브릭의 핵심적인 변화에 대해서 설명하고, 그 다음에는 Fabric-CA 관련된 주요 신규 기능을, 그 다음에는 Nodejs Fabric SDK에 대해 살펴볼 것입니다.

이 글은, 이전 버전과 비교하는 글이다보니 Hyperledger Fabric에 대한 기본 기능 및 아키텍쳐에 대한 이해가 있다는 것을 전제하고 있습니다. 처음 접해보는 개념이 없으시도록 역자의 주를 추가로 달아놓았습니다만, 각각에 대한 상세한 내용은 추후 아키텍쳐 시리즈에서 보다 자세하게 다룰 예정이니 참고 부탁드립니다. :)

v1.1에서 새로 릴리즈된 기능들을 검토하면서 제 개인적으로 드는 생각은, ‘상용화’를 최우선의 목적으로 정말 빠르게 달리고 있다는 겁니다. 여러 인큐베이팅 프로젝트도 같은 맥락에서 이해할 수 있지만, 실험적 요소가 많은 반면 main release는 보다 현실적인 문제들을 빠른 속도로 해치우고 있는 모양새입니다.


1. Hyperledger Fabric 의 핵심 변화

1.1 Rolling upgrade 방식 도입

프로덕트 릴리즈가 되는 것을 항상 프로젝트에 반영할 수는 없습니다. 스프린트의 기간과 개수에 따라, 하나의 블록체인 네트워크에도 여러 Fabric 버전이 존재할 수 있죠. (JIRA : FAB-5556) 하이퍼레저 패브릭 0.6 상용화의 가장 큰 걸림돌은 Fabric 플랫폼의 버전 업그레이드였습니다. 프로젝트 경험상, PoC에서 상용 단계의 솔루션으로 진화하는 데에는 롤링 업그레이드 방식이 반드시 필요합니다. 롤링 업그레이드는, 블록체인 네트워크가 자신이 연결된 다른 컴포넌트의 버전을 감지하여 추가로 활성화해야 할 기능이 있으면 스스로 이를 감지할 수 있게 해줍니다. capability framework가 가능하게 하는 것이죠. 버전 간의 분리는 해치지 않으면서, 예상치 못한 포크를 막는 동시에 개발자/운영자에게 compatibility 이슈가 있을 수 있음을 알려주는 겁니다. 아주 우아하게요.

  • 역자의 주: 이미 채널 컨피그 파일에서는, 트랜잭션에 대한 검증을 위한 룰(MSP 정의 및 정책)을 정의하고 있습니다. 그래서 이 기능을 확장해서, 이 채널을 정확히 파싱하기 위해서는 어떤 코드 레벨이 필요한지 결정할 수 있습니다. capability map을 정의하는 컨피그 값을 하나 추가한 것만으로 capability framework를 달성할 수 있었던 거죠! 트랜잭션 검증하듯이, 기존 네트워크와 호환되는 버전이 아니면 해당 노드의 접근을 거부합니다.

큰 맥락 상에서 Fabric network를 1.1 버전으로 업그레이드 하기 위해서는 다음과 같은 절차를 밟아야 합니다:

  • Orderers, peers, fabric-ca의 바이너리를 업그레이드한다. 이 업그레이드는 각노드에 한꺼번에 진행될 수 있습니다.
  • 클라이언트 SDKs 업그레이드.
  • v1.1 channel capability 요구사항 적용
  • (선택) Kafka 클러스터 업그레이드.

제 테스트 네트워크에서 업그레이드를 수행했을 때에는 GO 바탕의 스마트 컨트랙과 Nodejs SDK 앱과 사실 문제 없이 호환이 됐습니다. 수정할 필요 없이 잘 돌아갑니다. 그렇지만 혹시 모르니 여러분 환경에서 반드시 테스트를 거치시길 권장드립니다. 네트워크 업그레이드에 대한 가이드는 다음 링크를 참조하세요: 네트워크 업그레이드 튜토리얼, 서버 업그레이드 가이드.

1.2 이벤트 서비스

1.1 버전에서 가장 많이 달라진 것이 있다면 이벤트 시스템(JIRA: FAB-6911 and FAB-5481)일 것입니다. 이제는 채널 레벨에서도 이벤트를 발생시킬 수 있습니다. 즉, 여러분이 어떤 채널의 멤버로서 해당 채널의 네트워크 상태를 인지할 수 있다는 이야기입니다. 뿐만 아니라 정보 수신 방식도 유연해집니다. 여기에는 이벤트 시스템이 오더링 서비스(향후 완전히 pluggable한 형태로 변경될)에 기대서는 안된다는 하이퍼레저의 철학(?)이 반영됐다고 볼 수 있습니다. 이벤트 시스템의 확장으로 인해, 클라이언트 SDK를 통해 OSN 레이어와 같이 신뢰하기 어려운 대상이 아니라 신뢰 가능한 소스(예: 기관 peer)에 데이터를 요청할 수 있습니다. 또한 데이터를 보다 더 세분화하여 처리할 수 있기 때문에, 불필요한 정보를 수신하거나 정보들끼리 섞여버리는 현상을 최소화할 수 있습니다. 이 기능에 대한 보충 설명은 다음 링크에서 찾을 수 있습니다: peer channel-based event system

what is channel? OSN이란?? 이벤트 시스템이 그 전에는 어땠는데 지금은 일케 바뀌는 것임? 원리가 뭐임?

  • 역자의 주: 여기서는 다음과 같은 Hyperledger Fabric의 핵심 개념을 한 번 짚어볼 필요가 있습니다. 
    - Channel: Hyperledger Fabric의 ‘서브넷’이라고 보시면 됩니다. 블록체인 네트워크에 참가하고 있는 특정 멤버들끼리 프라이빗한 트랜잭션을 해야 할 경우가 있을 때 사용됩니다. 
    - OSN (Ordering Service Node): 일단 오더러(orderer)는 블록에 가해지는 트랜잭션의 순서를 결정하는 노드입니다. 네트워크 내 모든 채널에서 일어나는 모든 트랜잭션을 발생한 순서에 따라 정의하죠. SOLO 나 Kafka 등 어느 합의 알고리즘을 따르든 플러그인 될 수 있습니다. 즉, 발생한 트랜잭션을 쌓는 레이어이기 때문에 그 노드 자체보다는 오류를 갖고 있을 확률이 높습니다. 그래서 이 글에서 ‘신뢰하기 어려운 대상'이라고 표현되어 있죠. 사실 블록체인 기술 개념상 그렇게까지 문자 그대로 신뢰하기 어려운 것은 아니니 참고하세요.
    - 이전의 이벤트 시스템: 이전에는 어떤 이벤트를 발생시킨 노드만이 그 이벤트를 받을 수 있었습니다. 다른 노드는 그 네트워크에 어떤 이벤트가 발생했는지 전혀 모르죠. 게다가 이벤트를 받을 권한이 있는 노드라면 어느 채널에 속해있는지와 관계 없이 그냥 무조건 모든 이벤트를 모두 받았습니다.

1.3 CouchDB 인덱스

Fabric 1.1 에는 CouchDB 인덱스를 체인코드로 패키징하여 원장 state 값을 쿼리하는 기능이 신규 추가되었습니다 (JIRA: FAB-3067). CouchDB 같은 rich DB의 단점은 트랜잭션 퍼포먼스가 상대적으로 떨어진다는 것입니다. 트랜잭션이 많은 상황이라면 훨씬 빠른 LevelDB를 대신 사용하죠. 그러나 LevelDB에 동반되는 key-value 아키텍쳐의 한계를 극복하기 위한 데이터모델 대안이 필요합니다. 또, HA 구성 및 재해복구 설계로 인해 Fabric의 Peer 밖에서 트랜잭션이 필연적으로 일어나는 구조가 되기 때문에, LevelDB 사용은 상용 단계로 가는 데에 지장을 줄 수도 있죠.

인덱스가 이 성능 이슈를 해결해줄 묘안입니다. 이번 1.1 버전 릴리즈에서는 인덱스가 체인코드와 함께 패키징되어 그 안에서 실행될 수 있습니다. 예를 들어, 아래Marbles02 chaincode를 보시죠. queryMarblesByOwner() 함수가owner id를 체인코드에 넘겨주고 나서, “marble”의 docType, owner id와 일치하는 JSON 문서의 상태 정보를 쿼리합니다.

func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 0
// "bob"
if len(args) < 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
owner := strings.ToLower(args[0])
queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner)
queryResults, err := getQueryResultForQueryString(stub, queryString)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(queryResults)
}

체인코드와 함께 임포팅된 인덱스META-INF/statedb/couchdb/indexes에 위치하여 다음과 같은 형태로 위 함수를 활성화해줍니다:

{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}

체인코드와 인덱스가 합쳐진 결과, 이 둘이 코드 상에서는 분리되어(decoupled) 있지만 어떤 트랜잭션에 대한 업데이트는 동시에 된다는 것입니다. 그래서 상용 목적의 체인코드 라이프사이클 관리를 위한 전략으로서, 인덱싱이 1.1버전에 전격 포함된 것이죠. 이렇게 하면 또한 간단하게 네트워크 상의 특정 인물만이(예: 오디터) 리치 쿼리를 할 수 있게 설정할 수 있습니다. 자세한 설정에 대한 내용은 다음 링크에서 확인해보세요: couchDB as a state DB

  • 역자의 주: 리치 쿼리란?

1.4 Javascript chaincode

Hyperledger Fabric 1.1 에서는 javascript로 체인코드를 작성하기 위한 지원도 확장됐습니다(JIRA: FAB-2331). node.js 실행 환경 컨텍스트를 유지한 채 체인코드를 호스팅하기 위한 별도의 도커 이미지가 생성되었습니다. javascript 기반의 체인코드가, 일반 js 어플리케이션처럼 npm 및 다른 의존 모듈 활용 등 node.js의 모든 기능을 이용할 수 있도록 해줍니다. Go와 Java 체인코드와 비슷한 방식으로 디플로이가 진행됩니다. 설치하고 인스턴스에 올리고 오더를 업그레이드하면 됩니다. Fabric-sample GIT 레파지토리에는 example02, fabcar , Marbles와 같은 유명 체인코드 샘플들이 업로드되어 있으니 한 번 확인해보세요. 이 외에도 nodejs에 맞는 스크립트형 체인코드를 위한 BYFN scenario and Fabcar도 업데이트되어 있습니다. 개인적으로는 Golang으로 작성된 체인코드를 javascript로 변환하지 않고 아직은 유지할 예정입니다. Go와 Java 기반의 체인코드와 퍼포먼스 비교가 수행되고 나면 마음이 좀 바뀔 수도 있겠죠.

1.5 TLS communication

Fabric 1.1 continues with new features by supporting TLS communication which can be used one-way (server only) and two-way (server and client) for authentication (JIRA: FAB-6715). This is made true as Fabric Peers and Orderers are now TLS servers and clients at the same time. Configuration for unidirectional or mutual TLS can be embedded into Peer configuration or injected during Docker bootstrap (via docker-compose file). Examples of the latter for setting up TLS client authentication are presented below:

Fabric 1.1에서는 단방향(서버)과 양방향(서버, 클라이언트) 인증을 위한 TLS 커뮤니케이션을 지원합니다(JIRA: FAB-6715). Fabric의 Peer와 Orderer는 TLS 서버, 클라이언트 역할을 동시에 하기 때문에 이는 꼭 필요한 기능입니다. 단방향/양방향 TLS 여부는 Peer 설정 파일에서 정의하거나 Docker bootstrap 과정 중에 docker-compose file에서 설정될 수 있습니다. Docker를 통한 TLS 클라이언트 인증 설정 방법에 대한 예를 들어보겠습니다:

  • 역자의 주: TLS란 Transport Layer Security의 줄임말입니다. TCP/IP 네트워크를 사용하는 통신에 적용되며, 통신 과정에서 전송계층 종단간 보안과 데이터 무결성을 확보해주는 암호 규약입니다.
CORE_PEER_TLS_ENABLED = true
CORE_PEER_TLS_CERT_FILE = fully qualified path of the server certificate
CORE_PEER_TLS_KEY_FILE = fully qualified path of the server private key
CORE_PEER_TLS_ROOTCERT_FILE = fully qualified path of the CA chain file
CORE_PEER_TLS_CLIENTAUTHREQUIRED = true
CORE_PEER_TLS_CLIENTROOTCAS_FILES = fully qualified path of the CA chain file
CORE_PEER_TLS_CLIENTCERT_FILE = fully qualified path of the client certificate
CORE_PEER_TLS_CLIENTKEY_FILE = fully qualified path of the client key

Orderer에게도 비슷한 방식으로 설정할 수 있습니다:

ORDERER_GENERAL_TLS_ENABLED = true
ORDERER_GENERAL_TLS_PRIVATEKEY = fully qualified path of the file that contains the server private key
ORDERER_GENERAL_TLS_CERTIFICATE = fully qualified path of the file that contains the server certificate
ORDERER_GENERAL_TLS_ROOTCAS = fully qualified path of the file that contains the certificate chain of the CA that issued TLS server certificate
ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED = true
ORDERER_GENERAL_TLS_CLIENTROOTCAS = fully qualified path of the file that contains the certificate chain of the CA that issued TLS server certificate

TLS에 관한 official documentationcode-snipper도 참고해보세요.

1.6 원장 암호화

Fabric 1.1에서 새로 추가된 기능 중에서 흥미로운 것은, 체인코드 암호화 라이브러리를 통해 이제 원장 자체를 암호화할 수 있다는 점입니다(JIRA: FAB-830). 정말 중요한 업데이트이자 제가 오랫동안 기다려왔던 기능입니다. 원장 암호화 기능이 없어서, 사실상 블록체인 안에 담을 수 있는 데이터 자체를 리모델링해야 하는 상황이었습니다. 개인정보나 비즈니스 정보 같이 민감한 정보들은 담을 수 없었기 때문에 블록체인에서 덜어내야 하는 정보가 있었던 것입니다. Fabric 1.1 에서 암호화 기능이 추가되면서, 블록체인 네트워크의 확장 가능성이 강화되었다고 할 수 있습니다. 암호화 예시를 한 번 보시죠:

import (
"encoding/json"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/chaincode/shim/ext/entities"
"github.com/pkg/errors"
)

각 entity에 대한 상세 코드는 github 링크에서 찾아볼 수 있습니다. BCCSP wrapper를 이용한 entity 확장을 통해, 문서 암호화 및 타원 곡선 암호 알고리즘 등의 암호화를 수행할 수 있습니다. 이 기능에 대한 자세한 내용은 여기에서 확인해보세요. entity 간 확장을 일으키는 invoke 암호화 프로세스는 다음 블럭을 참고해보세요:

  • 역자의 주: BCCSP wrapper란, BlockChain Cryptographic Service Provider 라는 패키지를 이용한 암호화 방식입니다.
func encryptAndPutState(stub shim.ChaincodeStubInterface, ent entities.Encrypter, key string, value []byte) error {
// at first we use the supplied entity to encrypt the value
ciphertext, err := ent.Encrypt(value)
if err != nil {
return err
}
return stub.PutState(key, ciphertext)
}

복호화와 원장 내 쿼리는 다음과 같이 비슷한 방식으로 일어납니다:

func getStateAndDecrypt(stub shim.ChaincodeStubInterface, ent entities.Encrypter, key string) ([]byte, error) {
// at first we retrieve the ciphertext from the ledger
ciphertext, err := stub.GetState(key)
if err != nil {
return nil, err
}
// GetState will return a nil slice if the key does not exist.
// Note that the chaincode logic may want to distinguish between
// nil slice (key doesn't exist in state db) and empty slice
// (key found in state db but value is empty). We do not
// distinguish the case here
if len(ciphertext) == 0 {
return nil, errors.New("no ciphertext to decrypt")
}
return ent.Decrypt(ciphertext)
}

code-snipper를 통해 원장 암호화/복호화에 대한 내용을 더 확인해보실 수 있습니다.

1.7 Attribute 기반 체인코드 접근 제어 & 체인코드 API를 통한 클라이언트 아이덴티티 확인

이 역시 제가 굉장히 기다렸던 기능입니다 (JIRA: FAB-5346 and FAB-6089). 이전에는 트랜잭션 issuer의 키를 복호화한 결과값을 가지고 한 땀 한 땀 체인코드 함수를 구현해야 했습니다. 그렇지만 이제 ABAC (Attribute Based Access Control)로, 이미 생성된 키에 attribute를 추가할 수 있게 됐습니다. 누가 어떤 함수를 실행할 수 있게 할 것인지, 네트워크에 가입한 조직에 따라 쉽게 결정할 수 있게 되는 거죠. 다음 링크에서 확인해보세요: official documentation, github.


2. Hyperledger Fabric-CA

  • 역자 주석: Fabric CA란, Certificate Authority의 줄임말로서 Fabric 네트워크에 조인하는 개인/조직의 신원을 등록하고, ECert (Enrollment Certificate)을 발행하는 서비스입니다.

2.1 CRLs (Certificate Revocation List)

이제 인증서 철회 리스트가 CA에 의해 업데이트되고 나서도 조직, 채널 레벨에서 MSP를 다시 발행할 수 있습니다 (JIRA: FAB-5300). 이 기능은, 네트워크가 계속해서 변화하는 상용 수준 어플리케이션에서 특히 더 중요하게 작동합니다. 보안 관점에서도 굉장히 중요한 기능인데요, 보안 문제가 발생한 인증서가 있더라도 서비스 운영에 영향을 주지 않고 조용히 처리할 수 있게 해주기 때문입니다. 여기를 한 번 확인해보세요: http://hyperledger-fabric-ca.readthedocs.io/en/release-1.1/users-guide.html

  • 역자의 주: MSP (Membership Service Provider)란 멤버십 운영 아키텍처를 위한 컴포넌트입니다. MSP는 인증서를 발행하고 인증하여 사용자를 인증하는 역할을 합니다. 그 아이덴티티를 인증하는 방식은 MSP 마다 다를 수 있습니다. 그리고 한 네트워크에는 여러 MSP가 공존할 수 있지요.

2.2 Fabric-CA 재구성 강화

1.1 버전이 등장하면서, 컨피그 추가, 삭제, 수정을 네트워크 재시작 없이 적용할 수 있게 됐습니다 (JIRA: FAB-5726). CA 자체가 끊임 없이 쿼리를 받고 사용자 생성, 삭제를 진행해야 하는 환경에서는 특히 반길 만한 기능입니다. 이제는 서비스 중단 없이 서버 컨피그를 수정할 수 있으니 말입니다. 다음 링크에서 자세한 내용을 확인할 수 있습니다: http://hyperledger-fabric-ca.readthedocs.io/en/release-1.1/users-guide.html#dynamic-server-configuration-update


3. Hyperledger Fabric 용 Nodejs SDK

3.1 Connection Profiles

이번에 새롭게 도입된 Node.js SDK Connection Profile 기능 덕분에 Fabric node에 연결이 아주 간단해졌습니다 (JIRA: FAB-5363). 이 기능의 도입을 통해 어플리케이션 간, 피어간, OSN 간의 커넥션 설정 난이도가 확 낮아졌습니다. 공식 문서에는 ‘connection profile에는, Hyperledger Fabric 네트워크를 정의하는 entries가 들어 있고 네트워크에 접근하는 클라이언트 정보까지 한 번에 정의됩니다. 블록체인 어플리케이션이 실행되면 컨피그 파일이 로드되고, 이 파일을 기준으로 클라이언트들이 네트워크 접속 및 사용을 위해 필요한 설정을 완료합니다. connection profile에는 네트워크 요소 요소의 주소와 세팅이 정의되어 있다’고 밝히고 있습니다. 이전에는 이 connection profile이라는 것을 처음부터 쌩으로 개발하고, 어플리케이션 레이어와 디커플하는 작업까지 했습니다. 여기다가 어떤 peer가 endorser인지 committer인지 판별하는 것까지 했어야 했으니 수고가 이만 저만이 아니었죠. 다음 코드를 보면 특정 채널에 connection profile을 셋업하고, 그 안의 참여자들에게 기능을 분배하는 게 얼마나 쉬워졌는지 볼 수 있습니다:

channels:
mychannel:
orderers:
- orderer.example.com
peers:
peer0.org1.example.com:
endorsingPeer: true
chaincodeQuery: true
ledgerQuery: true
eventSource: true
peer0.org2.example.com:
endorsingPeer: true
chaincodeQuery: false
ledgerQuery: true
eventSource: false

Peer 컨피그 내용도 확인하기가 아주 쉬워졌죠:

organizations:
Org1:
mspid: Org1MSP
peers:
- peer0.org1.example.com
certificateAuthorities:
- ca-org1
adminPrivateKey:
path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/keystore/9022d671ceedbb24af3ea69b5a8136cc64203df6b9920e26f48123fcfcb1d2e9_sk
signedCert:
path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/signcerts/Admin@org1.example.com-cert.pem
  Org2:
mspid: Org2MSP
peers:
- peer0.org2.example.com
certificateAuthorities:
- ca-org2
adminPrivateKey:
path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/keystore/5a983ddcbefe52a7f9b8ee5b85a590c3e3a43c4ccd70c7795bec504e7f74848d_sk
signedCert:
path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/signcerts/Admin@org2.example.com-cert.pem
orderers:
orderer.example.com:
url: grpcs://localhost:7050
grpcOptions:
ssl-target-name-override: orderer.example.com
grpc-max-send-message-length: 15
tlsCACerts:
path: test/fixtures/channel/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tlscacerts/example.com-cert.pem
peers:
peer0.org1.example.com:
url: grpcs://localhost:7051
eventUrl: grpcs://localhost:7053
grpcOptions:
ssl-target-name-override: peer0.org1.example.com
grpc.keepalive_time_ms: 600000
tlsCACerts:
path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tlscacerts/org1.example.com-cert.pem
  peer0.org2.example.com:
url: grpcs://localhost:8051
eventUrl: grpcs://localhost:8053
grpcOptions:
ssl-target-name-override: peer0.org2.example.com
tlsCACerts:
path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tlscacerts/org2.example.com-cert.pem
certificateAuthorities:
ca-org1:
url: https://localhost:7054
httpOptions:
verify: false
tlsCACerts:
path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/ca/org1.example.com-cert.pem
registrar:
- enrollId: admin
enrollSecret: adminpw
caName: caorg1
  ca-org2:
url: https://localhost:8054
httpOptions:
verify: false
tlsCACerts:
path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/ca/org2.example.com-cert.pem
registrar:
- enrollId: admin
enrollSecret: adminpw
caName: caorg2