R3는 금융 서비스를 위해 설계된 엔터프라이즈 블록체인 플랫폼인 corda를 개발하고 서비스하는 소프트웨어 회사다. R3는 2014년 소규모의 가족 회사로 출발했는데 R3중 R은 CEO인 David Rutter의 성을 뜻하고, 3은 공동 설립자의 수를 뜻한다.

R3는 2014년 동안 암호화폐 회사들이 금융 기관들을 대체하고자 하는 시도를 계속해서 지켜봐왔고, 블록체인 기술이 금융시장에 가져올 혁신을 대응하기 위해 비트코인과 이더리움 등 성공적으로 동작하고 있는 블록체인에 대한 연구를 진행했다. 연구의 목적은 암호화폐 회사들의 공격에 대응하는 것도 있었지만, 블록체인을 금융시장에 어떻게 적용할지, 실무적인 측면에서 바라보고 금융시장에 적용할 수 있는 기존 블록체인 기술에 대한 리뷰와 선택을 진행하는 것도 포함되어 있었다.

R3는 2014부터 2015년까지 수차례의 컨퍼런스를 진행하면서 블록체인과 분산원장에 대한 차이점을 분명히 하는 최초의 논문인 CaaS(Consensus-as-a-Service)를 발표했고, FX 결제를 초함한 구체적인 금융 유즈케이스를 포함한 협의를 진행했다. 또한 다양한 은행과 블로체인 기업, 금융기관들이 함께하는 컨소시엄을 구성하게 되면서 2015년 11월 말 42개의 금융 기관이 참여하는 DLG(Distributed Ledger Group)를 발표하며 R3라는 이름을 대체하게 되었다.

IBM에서 하이퍼레저를 개발하던 Richard Brown은 여러차례의 컨퍼런스와 대화를 통해 기존의 블록체인 플랫폼으로는 실 금융시장에 적용할 수 없다는 판단을 내렸고, 영국 버클레이 은행의 아키택트였던 James Carlyle, 비트코인 초기 개발자 Mike Hearn 등을 영입하여 Corda라는 새로운 금융 특화 블록체인 플랫폼을 개발하였고 2016년 1분기 말에 Corda라는 새로운 시스템을 구축하였다. 2016년 4월 5일 Richard는 Corda가 무엇인지, 설계 목표가 무엇진지, Corda는 블록체인이나 Cryptocurrency가 아니라 분산 원장이라는는 점에 대해 처음으로 공개적으로 설명했다. (Corda의 블록체인에 대한 기술적 정의는 다음편에 다루는 것으로...) 

기존의 비트코인, 이더리움, 리플과 같은 블록체인 플랫폼을 실 금융시장에 적용할 수 없었던 이유에 대하여 백종찬님의 글을 인용하였다.

1. 데이터 프라이버시

기존의 블록체인 구조는 네트워크 참여자가 모든 거래 내역을 보관하고 열람하는 구조다. 즉 A와 B간의 거래를 C가 검증해야한다. 고객 또는 자산의 기밀성을 보호해야 하는 금융산업에서 이와 같은 공개적인 데이터 검증 구조는 적용될 수 없다.

2. 확장성

모든 네트워크 참여자가 거래에 대한 합의를 도출하고 검증을 하게 되면 참여자가 많아질수록 거래 처리의 속도가 느려진다. 업계에서 주로 "초당 거래수"라고 얘기하는데, 빠른 속도가 생경인 긍융거래에서 이러한 확장성의 한계는 큰 문제다.

3. 법률적 책임

스마트계약의 경우 기존의 블록체인은 법률적 책임의 소재가 불분명하다. 컴퓨터 코드에 의한 비가역적인 실행은 가능하지만 실제로 그것이 법률적 강제성을 가지는 것은 아니다.

4. 개연적 결제 완결성

기존의 블록체인, 특히 비트코인이나 이더리움과 같은 퍼블릭 블록체인의 경우, 결제의 완결성을 법률 또 기술적으로 100% 보장하지 않는다. 항상 네트워크의 포크 또는 블록 재종에 등의 가능성이 존재한다. 은행거래의 경우 결제의 완결성은 중앙은행이 법적으로 보장하기 때문에 법률적인 특면에서도 문제가 있다.

2017년 R3 컨소시엄은 공식적으로 두 그룹으로 나누었다. 한 그룹은 Corda 플랫폼 구축에 중점을 두고 있으며, 다른 하나는 컨소시엄 멤버에게 적절한 서비스를 제공하는데 중점을 두는 연구 그룹이다. 연구팀은 40개가 넘는 프로젝트를 진행중이며 20개 이상의 프로젝트를 완료했다. 그들이 진행하는 프로젝트들은 모두 이더리움, 리블, 패브릭, 코다, 캐나다 중앙은행, 바클레이즈 은행 등 블록체인에 국한되지 않은, 다양한 플랫폼과 관련되어 있다. 

R3 컨소시엄의 프로젝트 중 하나인 제네시스 프로젝트는 42개 금융사들이 블록체인 위에서 기업어음을 보내는 프로젝트를 진행했고 그 이후에도 60개가 넘는 다양한 프로젝트에 은행들이 자발적으로 참여하여 매우 빠른 발전을 이루었다. 또한 국내 사례로 2017년 5월 국내 은행 중 국민,신한,우리,하나,기업은행 등 5개 시중은행이 R와 함께 고객확인 정보를 블록체인으로 저장,관리하는 프로젝트를 성공한 바 있다. 다만 금융사들의 고객정보를 공유하는 행위 등 법적으로 금지되어 있는 문제가 있기 때문에 상용화까지 처리해야할 이슈가 있을것으로 보인다.

지금까지 R3와 Corda의 탄생 배경과 발전 과정을 알아보았다. 다음 글에서는 Corda의 기술적 바탕을 좀 더 살펴보고 기존의 블록체인과 다른점은 무엇인지, 엔터프라이즈 플랫폼으로서 하이퍼레져와 차이점은 무엇인지 등 기술적인 부분을 좀 더 살펴 보고 Corda에 대한 이해도를 높이고자 한다.

참조

A brief history of R3 – the Distributed Ledger Group 

THE CORDA WAY OF THINKING

R3, 넌 도데체 누구냐!  

개요

호가창 무결성을 보장하고 트랜잭션을 쉽게 관리하기위해서  RDB를 이용한 구현을 찾아봤다.

하지만 RDB를 이용한 구현은 수평적으로 확장이 어렵기 때문에 IO를 담당할 하나의 MASTER DB에 의존성이 매우 높다.

또한 물리적 성능 향상을 위해서는 더 좋은 장비를 구매해야하고 단일장비의 성능엔 한계가 존재한다. (물리적으로나 비용적으로나)

그래서 호가 매칭 알고리즘에서 IO가 많이 발생하는 사용자 주문 생성, 주문 매칭 단계는 수평적 확장이  쉬운 Redis를 사용하여 처리하고

매칭된 주문 트랜잭션 생성 및 처리는 Mysql에서 관리하는 방향으로 설계해 보았다.


Redis 살펴보기

  1. 레디스는 메모리 기반 저장소라서 클러스터 서버 전체 다운시 데이터가 날아간다??
    1. Redis가 Memory 기반 저장소라서 캐시 전용 저장소로 오해하는 부분이 많은데, Redis도 옵션에 따라 텍스트 형태의 파일로 데이터를 저장할 수 있다.
      때문에 클러스터가 전체 다운되어도 파일을 통해 데이터를 복구할 수 있다.
  2. 트랜잭션 관리가 어렵다??
    1. 레디스는 싱글 쓰레드 기반이라 트랜잭션 관리가 쉬운편이다.
      또한 Lua Script 를 사용하거나 트랜잭션을 지원하는 명령어 (MULTI) 를 통해서 트랜잭션을 보장할 수 있다.
    2. 오히려 싱글 쓰레드라 대량 작업시 블럭되는 점을 조심해야 한다. (keys 명령어나, 대용량 삭제와 같은 작업을 조심해야한다.)
  3. 대용량 데이터 저장이 어렵다??
    1. 하나의 Key 당 최대 512MB까지 저장가능하다. 적지않은 사이즈지만 히스토리 정보까지 모두 메모리에 올려두기엔 부담스러운게 사실이다.
      그래서 IO 관련 데이터들만 redis에 저장하고 거래/히스토리 데이터는 RDB에서 관리하는 방향으로 설계해야한다.
  4. 클러스터링을 지원하여 가용성이 높다?
    1. Redis를 사용하는 가장 큰 이유이기도하지만 가장 조심해야할 부분이기도 하다.
      클러스터 구성에 따라 다르지만 보통 레디스 클러스터는 Master - Slave 구조로 구성된다.
      Master가 다운될 시 Slave가 자동으로 Master로 승격되는데 이때 승격될 Slave 에 데이터가 비어있으면 모든 클러스터의 데이터가 비워지게 된다.
      우리는 Redis 클러스터를 직접 구현하지않고 AWS 를 사용할거라 다행이다 ^^

설계

    필요한 자료구조

    1. 주문 상황판
      (buy/sell 주문의 order price 별로 order quantity 를 가지는 Sorted Set<score, orderSum>)
    2. 주문 리스트
      (Map<typeAndCoinAndPrice, List<order>> buyList, Map<typeAndCoinAndPrice, List<order>> sellList ) 
    3. 내 주문
      (내 주문 상태를 관리하고 목록을 확인할 수 있는 Map<memberNo, Map<orderId, order>>)
    4. 매칭된 두개의 Order 를 저장/처리할 Transaction RDB 테이블

동작 시나리오

  1. 주문생성 (Create)  - (주문 생성 전 주문이 필요로하는 코인/현금만큼 사용자의 잔고를 freeze 해야한다.)
    1. 새로운 주문이 들어오면 시퀀스 시작. 
    2. (redis 트랜잭션 시작)
      '주문 상황판'을보고 매칭할 주문 리스트 진입점을 찾는다. (매칭되는 진입점이 없으면 v 단계로 이동)
      1. 주문 타입이 Buy면 주문 가격이하의 Sell List중 가장 저렴한 리스트를 찾는다.
      2. 주문 타입이 Sell이면 주문 가격 이상의 Buy List중 가장 비싼 리스트를 찾는
    3. 매칭되는 리스트가 있으면 LPOP으로 order를 꺼내오고, '주문 상황판'과 '내 주문'에서 해당 order를 제거한다. 
    4. 요청 들어온 주문과 매칭된 주문의 거래량을 비교하여 분기 시퀀스를 따른다. 
      1. IF (RequestOrder.quantity > MatchedOrder.quantity)면  두개의 주문을 갖는 Transaction 객체를 생성하고,
        RequestOrder.quantity 를 차감한다. 
        처리된 MatchedOrder를 상황판과 사용자별 주문에 반영한다.
        (redis 트랜잭션 커밋 후 다시 ii 단계부터 시작)
      2. ELSE IF (RequestOrder.quantity <= MatchedOrder.quantity) Matched Order을 RequestOrder.quantity 기준으로 분리한 후
        Transaction 객체를 추가하고, 분리된 여유분의 Order를  다시 주문리스트로 LPUSH한다. (RequestOrder 의 주문량은 0이됨)
        스플릿되어 처리된 MatchedOrder를 상황판과 사용자별 주문에 반영한다.
        (redis 트랜잭션 커밋후 vi 단계로 이동) 
    5. 매칭되는 리스트가 없으면 Request Order의 가격 리스트에 RPUSH 하고 상황판과 내 주문에 반영한다. 
      (redis 트랜잭션 커밋)
    6. 생성된 Transaction 객체들을 처리한다.
      1. 주문 밸리데이션 (잔고, 요청내용 2중 검증)
      2. 사용자별 잔고 증감 및 수수료 취득 
  2. 주문 관리 (Read)
    1. 고객별 My 주문 리스트는 사용자 별 '내 주문' 모델을 통해 관리한다.
    2. 전체 주문량은 '주문 상환판' 모델을 통해 관리한다.
    3. 체결내역은 Transaction 테이블을 통해 관리한다.
  3. 주문 수정 (Update)
    1. 주문량/주문가 수정의 경우
      (redis 트랜잭션 시작)
      대상 주문을 리스트에서 POP 하여 값 수정 후 RPUSH 
      주문 상황판, 내 주문도 업데이트
      (redis 트랜잭션 종료)
  4. 주문 취소(Delete)
    1. 주문 취소의 경우
      (redis 트랜잭션 시작)
      대상 주문을 리스트에서 POP
      주문 상황판, 내 주문도 업데이트
      (redis 트랜잭션 종료)

예외 처리

  1. 모든 주문은 요청 전에 검증(잔고확인, 소유자 확인, 권한 확인)을 통과해야한다.
  2. 검증된 주문이라도 트랜잭션 처리 전에 한번 더 검증한다.
  3. 주문처리 우선순위는 가격 > 시간이다.
  4. 트랜잭션 생성을 위해 주문을 LPOP 하여 거래량 차감 후 LPUSH 하는사이 시간 우선순위를 위배할 수 있다.   



개요

암호화폐 거래소를 구축하기 위해 주문(호가) 매칭 알고리즘을 찾아보던 중 좋은 글이 있어 번역과 사족을 달았다.

David Veksler 라는 소프트웨어 아키택트의 글이고, 설계한 암호화페 거래소를 중국 시장에 런칭까지 했다고 한다. 

원문과 더불어 중국 시장 거래소 런칭에 관련된 글도 있으니 같이 보면 좋겠다. 

번역은 녹색으로 표현하고 사족은 검은색으로 달았다.

결론

아주 단순한 구조의 설계지만 꼼꼼히 살펴볼수록 튼튼해 보인다.

Buy/Sell 주문을 하나의 테이블에 넣기 때문에 RDB 저장소를 사용하여도 괜찮은 IO를 보장할 것같다. (사실 IO 성능이 아닌 확장성 문제 때문이긴 하지만 하나의 마스터DB와 슬레이브들로 어느정도 서비스 가능한 TPS를 보장해 줄 수 있을듯)

아래에 사족을 달았듯이 Order 의 Status 검증을 주문 요청 전에 진행한다면 트래픽을 분산시켜 성능 높일 수 있을것 같다. 

테이블 구조도에서 Type 값들을 모두 정규화 했는데 반정규화해서 사용해도 무방해 보이고 (미약하나마 성능향상위해) OrderBook 과 Transaction 이 가지는 관계, 남은 거래량을 처리하는 방법이 단순하지만 명시적이라 좋은 방법이라 생각한다.

또한 매칭 모듈 시작 트리거를 고객이 주문한 시점으로만 제한하고있어서 개인적으로 모호했던 부분을 분명히 할 수 있어서 좋았다.

본문

비트코인 거래소 프로젝트 파트 2: 주문 매칭 알고리즘

요약

통화 거래소는 구매자와 판매자가 자신이 가진 통화를 서로 다른 통화 유형으로 교환 할 수 있는 시스템이다.  

본문

주문 매칭 모듈은 구매/판매 주문을 매칭하고 거래를 만들어 거래의 흐름을 기록하고 고객들의 잔고를 업데이트 한다.

트리거: 한명의 고객이 거래량과 가격을 입력하고 주문버튼(Buy 또는 Sell)을  클릭 한다.

본문에서는 액션으로 적혀있지만 트리거라고 이해하면 더 좋을것 같다. 이제부터 나오는 1~12번까지의 과정은 고객의 주문 트리거를 통해 실행된다.

1. 웹사이트는 고객의 Order를 Pending 상태로 OrderBook 이라는 테이블에 저장한다. 우선 주문을 처리하기 위해 쌓는다(filed).

그런 다음 오더 매칭 서비스는 Pending된 Order를 순차적으로 훑어본다.

[public int PlaceBuyBid(int customerId, decimal quantityOfBTC, decimal pricePerBTC, DateTime ?expirationDate = null) ,

public int PlaceSellOffer(int customerId, decimal quantityOfBTC, decimal pricePerBTC, DateTime ?expirationDate = null)]

2. 이 때 주문이 취소되었거나 만료되었는지를 확인하여 Order의 상태를 재검증 한다.

3. order 마다 자금 검증을 하고, 고객은 order 가 필요로 하는 자금을 가지고 있어야만 해당 order의 상태룰 active로 변경한다.

그렇지 않을 경우엔 suspended 되는데 고객이 이후에 자금을 입금하면 다시 활성화 한다.

검증을 통과하면 

a: order 의 상태가 Active로 변경된다.

b: 비용 지불이 필요한 order면(sell order를 뜻하는 듯) Frozen Balance를 더한다. (고객이  자신이 가진 자산보다 더 많은 주문생성을 못하게 하기 위함) - 하지만 이 기능은 나중에 제거했다 - 우리는 Available balance보다 더 많은 주문 생성을 가능하게 했고 거래 전에 검증하는 쪽으로 방향을 바꾸었다. 

Order suspended 는 그냥 pending 상태로 둔다는 말 같다. 앞서 말한 트리거가 발동할때마다 잔고를 확인해서 pending 또는 active로 처리한다. 잔고 확인하는 부분이 특이한데, 잔고 확인없이 주문을 생성해줄 경우 고객입장에서 주문생성을 빠르게 진행할 수 있지만 어뷰징이 걱정되고, 그냥 주문 요청시 잔고 체크하여 걸러 내는게 더 좋을것 같다. David는 이걸로도 부족했는지 order 검증시에도 잔액 검증을 빼고 거래 시점에 검증하는 쪽으로 바꿨다고한다.

4. 주문 매칭 시스템은 buy와 sell order 를 매칭하기 위해 동작한다. 주문을 매칭하기위해 모든 Acitve 상태인 Order의 가격을 검색한다.

* 만약 트리거 주문이 buy 이면 가격이 작거나 같은 주문을 찾는다.

* sell 이면 가격이 크거나 같은 주문을 찾는다.

* 시장가의 sell 이면 가장 비싼 주문을, buy 만 가장 싼 주문을 찾는다.

Order 인입시 검증을 해서 바로 Active 상태로 들여온 다음 1, 2, 3 과정을 생략하고 바로 4번으로 하는건 어떨까?

pending -> active 대신 active -> pending 으로 상태변경 과정을 바꾸고 외부 배치 작업으로 order 의 pending -> active, expired 작 업을 진행하는것도 좋을것 같다. 

우리는 buy order를 위해서 오름차순으로 매칭하고 sell order를 위해서 내림차순으로 매칭한다. 그럼 다음 가격이 매칭되면 시간순으로 정렬한다.

[ISpecification IsMatchingOrderQuery(decimal price, int orderTypeId, int wantAssetTypeId,int offerAssetTypeId, bool? isMarketOrder)]

5. 우리는 상위 3개의 매치들을 메모리로 불러온다. 3개까지 가져오는 이유는 매칭된 주문들이 추후 검증에서 실패할 수 있기 때문이다.

메모리로 불러온다는 말은 단순히 DB에서 시스템상으로 SELECT 해온다는 말 같다.

6. 우리는 order 와 match를 비교한다. 이것은 C#은 이용한 이중 검증이다. - Order가 이전 데이터베이스 쿼리 결과와 일치해야한다.

order는 다음 조건은 만족해야한다. ($/BTC, order types[buy/sell], non-two market orders, matching prices)  

     [OrderComparisonResult CompareOrders(Order firstOrder, Order secondOrder)]

쿼리로 매칭하는 주문들을 불러왔지만 시스템 상에서 다시 한번 검증한다는 말인듯.

7. 만약 order 비교가 성공하면 매칭됨을 기록(record)하는 transaction 을 만든다.

 [Transaction GetTransactionForTwoOrders(OrderComparisonResult comparisonResult)]

      * A_Order 는 buy order 

* B_Order 는 sell order

(A와 B를 사용하는 이유는 아직 어떤 주문이 Buy/Sell 이 될지 확실하지 않기 때문이다.)

Buy/Sell 두개의 오더를 가지는 하나의 Transaction 생성

Transaction 에 들어간 Order Status도 변경하는게 좋지 않을까?

8. 만약 주문간에 주문량이 정확히 일치하지 않으면 주문량이 남은 쪽 주문을 쪼개서 새로운 주문을 생성한다.

9.  [ActivateTakeProfitAndStopLossOrders(Order order)]  를 실행한다. 

TODO - 여기서 바로 시작하는게 아니라 스케쥴링한다. 자세한 내용은 다른 포스트 참고

무슨내용인지 궁금한데 연결된 포스트가 없다 ㅠㅠ

10. 주문이 매칭된 거래 (Transaction)을 처리하고 결과를 기록(record)한다.  

[public Order ProcessTransaction(Transaction transaction)] (note: this module is a database transaction)

a: 거래와 쪼개진 주문을 DB에 추가한다.

b: 거래에 포함된 두 주문 모두에게 

* 인출 금액을 차감한다. (고객의 달러나 BTC 계좌에서)

* 신용자산을 더한다. (고객의 달러나 BTC 계좌에서))

* 수수료 계좌에 수수료를 기록(record)한다.

* 동결 자산을 녹인다.

* 변경점들을 저장한다.

11. 쪼개진 주문의 남은 거래량이 0이 될때까지 대상으로 과정을 반복하거나, 매칭되는 주문이 없을때까지 반복 한 뒤 해당 주문을 active 상태로 유지한다.

foreach (OrderProcessResultModel n in ProcessOrder(splitOrder.OrderId)){
    yield return n;
}

거래량이 남았을경우 처리가 궁금했는데 단순하게 푼것 같다.

필요한 거래량만큼 여러 주문을 불러와서 트랜잭션 진행하는것보다 직관적인 방법이지만

시간 복잡도 N이 추가된다. 안정성이 중요하다면 이정도는 감수하는것도 좋을듯..

12. 만약 남은 거개량이 0이되면 status를 Completed 로 변경한다.


'BLOCKCHAIN' 카테고리의 다른 글

R3와 Corda의 탄생배경  (899) 2019.01.13
중앙 거래소 호가 매칭 알고리즘 설계  (933) 2018.10.24

+ Recent posts