Software Design from point of dependency Part1

We’ve all learned object oriented programming to keep our system being from monster. In object oriented programming, we always talk about Encapsulation, Polymorphism, Inheritance, IoC, DI and so on. But as a developer, we need to think about the point of object oriented programming.

Think about why we learn object oriented stuff? I think the answer is to keep our system maintainable. Then what is meaning of maintainable? Maintainable means we don’t need to code lots of crazy things to satisfy our ever changing business requirements.

What makes us to code all night to change such a little feature? I think one of biggest cause is lack of dependency management. How we create dependency between objects makes great impact on whole system architecture.

In other words we can see how we design our software as making decisions of how object will make relationship with other object. For example, ‘What code would be placed in this class’ or ‘What classes are going to be placed this package’, all these kinds of decisions are kind of software design.

Then is there any principle to put your code on which class or which packages? One simple but fundamental rule is focusing on changeability. If A and B should change together? Then place those in same packages. If not, do not.

Dependency

what is dependency
what is dependency

If A’s change makes B’s change, then we can say that B is depends on A. In other words, depdency is possibility to be change by other object’s change.

There are two basic kinds of dependency.

Association

association diagram
association diagram

If A has association with B then it means A has permanent path for accessing B. This is conceptual explanation and how to implement association is by A having fields for B.

Dependency

dependency diagram
dependency diagram

If A has dependency with B then it means A has temporary relationship with B. In the case of A having B as parameter or return type in method, we can say A has dependency with B.

Real World Example

We’ve talked that designing software architecture is about consideration where to place classes, packages, codes. Codes which have high possibility to change together should be placed nearby, i.e. in the same class or package, so that the code cohesion can be increased. System which has high cohesion can be changed easily by developers. Because each part of the system takes in charge of single responsibility which remove possibility to break system as a result of changing other component.

For further explain how could we improve our system in terms of dependency, let’s take a real world example. I brought scenario in which customer order product on e-commerce service. And we are operating e-commerce system.

The code of this application can be found here:

https://github.com/zeroFruit/oop-dependency/tree/master/ecommerce-00

Service Flow

Basic flow of our service is as such:

e-commerce basic user flow
  1. Customer orders item with options, for example customer ordered two “Fusion5 ProGlide Power razors” with “4 razor blade” and with color of “white”.
  2. If customer ordered successfully and payed, service takes commission fee and start shipping

Business Rules

And this e-commerce service has serveral business rules or restriction, we should follow these rules when designing our system:

  1. Our product list can be changed in real time, this can be happened because product suddenly out of stock or we may decided not to sell this product anymore.
  2. As a result if customer order on our service we should check whether ordered product name is matched with our service’s product name.
  3. In addition to product name, we should check ordered product’s option
  4. In our service, each product has minimum order amount, price.
  5. This is not ordinary but our service has closing hour (for system check hours 🙂 ), so we should check whether our service is opened.

Organize into class

We can see these kinds of rules as order validation logic in software point of view. We should check these kinds of things in our service:

  1. should compare order item name with product name.
  2. should compare order item option group name with product option group name.
  3. should compare order item option name with product option name
  4. should compare order item price with product price
  5. should check whether our service is available
  6. should check whether order item price is over product’s minimum order price

Based on business scenario diagram, we can map these rules into classes with its own methods.

e-commerce service class diagram
e-commerce service class diagram

With this diagram, we can find out what objects will be collaborated to work with validation and order and see which object will make relationship with others.

But before we dive into code to implement this design, there’s one more thing we need to decide: direction of relationship.

Direction of relationship

Direction of relationship in other words tells us what class will depend on what class. In the code this will be implemented by one class having other class as field variable or by having as method parameters.

For example, having other class as field variable, is called association. As we talked before association is permanent relationship.

Order has association with OrderItem
Order has association with OrderItem

With association, we can create path to navigate the other objects. For example, if Order has association with OrderItem and if we know Order, we can find any OrderItem through Order. If these two objects need to collaborate to satisfy business rules and we need a way to find the other object. We can make association.

Association is conceptual thing and there’s lots of way to implement this thing and this is one example. Order has OrderItem as a list. This two has strong relationship, whenever order is placed we need to get its information from order items.

As such we can decide direction of relationship.

direction of dependency between objects
direction of relationship between objects

The direction of arrow tells us which object is associate or dependent on other object. In the case of ‘Order’ and ‘Store’, arrow points to ‘Store’ with solid line. This means that ‘Order’ is associate with ‘Store’ and in the code Order class will have Store as field variable.

In the case of ‘OptionGroupSpecification’ and ‘OrderOptionGroup’ arrow points to ‘OrderOptionGroup’ with dotted line. This means that ‘OptionGroupSpecification’ is dependent on ‘OrderOptionGroup’ and in the code OptionGroupSpecification will reference OrderOptionGroup in the method as parameters or as local variable.

From the layered architecture view

Let’s think about these relationship from layered architecture.

layered architecture diagram
layered architecture diagram

All we’ve talked about objects and relationship goes into ‘domain’ layer. And such a word like ‘service’, ‘domain’ and ‘infrastructure’ is conceptual thing.

How we implement this is another topic. In java, these layer can be implemented through concept of packages.

So we can place our Order, Store classes into ‘domain’ package and OrderService, StoreService which use domain objects and make application API will be placed in ‘service’ package. Below diagram shows packages with its dependencies.

e-commerce service package diagram with dependency
e-commerce service package diagram

But there’s one big problem. You see? There’s cyclic dependency between ‘store’ and ‘order’ in domain layer.

Having cyclic dependency is a signal that architecture designed not that well and means that those packages are strongly coupled which cause seperation of responsibility hard and one component’s changes break other components, plus it is hard to test.

Problem

So where is the problem?

e-commerce service domain class diagram
e-commerce service domain class diagram

To figure out the cause, drew class diagram with its package. You may noticed OptionGroupSpecification directly reference OrderOptionGroup and OptionSpecification reference OrderOption from ‘order’ package. This two dotted lines create dependency from ‘store’ package to ‘order’ package.

Use abstraction to break dependency

The code of how we can refactoring with abstract can be found here

https://github.com/zeroFruit/oop-dependency/tree/master/ecommerce-01

To fix this problem, we are going to use abstraction. Instead of directly referencing ‘order’ package’s class, reference abstract class which is in the ‘store’ package.

Be cautious of the word of ‘abstract’. It is not abstract keyword in java programming language nor interface. I used this term to mean ‘not changed much’, ‘summery of point’, ‘concentrates on essential’.

This is our abstract class. There’s no abstract, interface keyword. Because Option and OptionGroup have essential fields for representing option and option group, we can say that Option, OptionGroup class is much abstracted than OptionSpecification, OptionGroupSpecification class in ‘store’ package or OrderOption, OrderOptionGroup class in ‘order’ package.

With these abstracted class, both ‘order’, ‘store’ domain implement validation logic, also increased reusability.

Result

e-commerce service package diagram with abstract class
e-commerce service package diagram with abstract class

This diagram shows result of refactoring. Now Order, OrderOption depends on OptionGroup, Option class in ‘store’. As a result, by creating abstract class and depends on it, we can remove cyclic dependency.

The way of how we refactor this module is somewhat about ‘Dependency Inversion Principle’.

The idea of this principle is that when designing the interaction between a high-level module and a low-level, the interaction should be done by abstraction between them.

In other words, high-level modules should be easily reusable and unaffected by changes in low-level modules. Also when designing low-level modules, developer should keep in mind with the interaction.

Still there’s some problem in this design, we are going to handle that in the next post.

Conclusion

One important thing when we design software architecture is dependency. Because how we make dependency or relationship between components makes grate impact on the system. And as a developer this can cause fragile codes: one component’s simple change can propagate to other components and break their codes.

There are two kinds of relationship between component: ‘association’, ‘dependency’. Association creates permanant path between components. If A and B has association and we want to know B’s information, we can get it from A.

One signal whether this architecture is well designed or not can be found by checking whether components are have cyclic dependency between them. Cyclic dependency should be avoided as possible because this creates strong coupling.

One way to break cyclic dependency is creating abstraction and making components which cause cyclic dependents to interact with it. This is kinds of ‘Dependency Inversion Principle’ because modules are interact with abstraction instead of concrete one.

Photo by Walter Lee Olivares de la Cruz on Unsplash

왜 JSON-RPC를 사용할까?

Intro

최근에 web3.js 코드를 살펴볼 일이 있었는데 이 때 JSON-RPC를 처음 접했다. 그리고 JSON-RPC 공식 홈페이지에서 JSON-RPC 2.0 스펙을 보았는데 가장 먼저 든 생각은 ‘왜 이걸 쓸까?’ 였다. 그래서 이번 포스트에서는 JSON-RPC가 어떤 것이고 어떤 장점이 있는지 정리하려고 한다.

우선 JSON-RPC가 등장한 시기를 살펴보면 2000년을 시작으로 REST 방식이 등장하고 2000년대 중순에 JSON-RPC 등장했고 마지막으로 2015년에 gRPC가 나왔다.

RPC와 gRPC에 대해서 궁금하다면 여기서 살펴볼 수 있다.

여러 글들에서 과거엔 어떤 서비스를 만들 때 가장 표준이 되었던 REST 방식을 사용했지만 최근에는 JSON-RPC도 많이 사용하고 있다고 한다. 그렇다면 JSON-RPC와 REST을 비교했을 때 JSON-RPC는 어떤 특징을 가지고 있을까?

REST vs JSON-RPC

TCP 위에서 동작한다

REST는 HTTP(S) 프로토콜 위에서 동작하는 표준인 반면에 JSON-RPC는 TCP 위에서 동작하는 표준이기 때문에 좀 더 다양한 프로토콜에서 사용할 수 있다. web3에서도 클라이언트와 서버가 웹소켓을 통해서 통신을 하는데 여기서 JSON-RPC를 사용할 수 있는 이유는 JSON-RPC가 HTTP 프로토콜뿐만아니라 TCP 위에서 동작할 수 있기 때문이다.

또한 REST는 다른 리소스를 얻기 위해서 다른 URL에 접근해야하는 반면에 JSON-RPC는 하나의 엔드포인트 URL에서 모든 요청과 응답을 받는다.

그리고 REST는 요청을 보낼 때 여러 HTTP Method를 통해서 보내는 반면에 JSON-RPC는 (HTTP의 경우) 하나의 Method를 통해서 통신하게 된다.

다양한 action을 나타낼 수 있다.

REST는 JSON-RPC에 비해 표현할 수 있는 operation의 범위가 비교적 한정적이다. 왜냐하면 REST 방식은 어떤 객체에 대해서 CRUD operation을 하는 것에 적합하게 설계되었고 반면에 JSON-RPC는 CRUD를 포함한 다양한 action을 나타내는 operation을 표현할 수 있다.

조금 추상적이니 예를 들어보자. 우리가 차를 얼마의 기간동안 렌트하려고 할 때 그 비용을 API를 통해 구하고 싶다. REST API를 설계하는 입장에서 우리는 어떤 URL을 뽑아내야할까?

몇 가지를 뽑아보면 일단 GET /car/rent 이 떠올랐을 수도 있다. 그런데 GET /car/rent 는 느낌이 이 API를 호출하면 rent라는 객체가 반환될 거 같은 느낌이다. 그리고 이후에 POST를 통해 rent를 추가할 수 있을 거 같기도 하다. POST /car/calculate_rent 가 떠올랐을 수도 있다. 하지만 POST /car/calculate_rent 는 URL이 명사로 이루어져있지 않기 때문에 RESTful 방식이 아니다.

또 다른 상황으로 주문을 검증하는 API를 만들어야한다고 생각해보자. GET /order/validate 가 그럴듯해보이지만 이는 마찬가지로 명사가 아니기 때문에 REST 방식이 아니다.

이처럼 REST 방식은 CRUD 기능을 나타내는데 적합하기 때문에 구체적인 동작을 나타내기에는 쉽지 않다.

반면에 위와 같은 API들을 JSON-RPC 방식으로 구현한다고 생각했을 때 각각의 메소드들은 다음과 같이 표현할 수 있다.

  • car.rent.calculate_fee: 차를 렌트하는데 요금을 계산하는 메소드
  • car.rent.create: 차를 렌트하는 메소드
  • order.validate: 주문에 대해서 검증하는 메소드

통일된 parameter 전달방식

REST가 JSON-RPC에 비해 번거로운 점 중 하나는 parameter를 전달하는 방법이 다양해서 이것을 핸들링할 수 있는 방법도 다양하다는 것이다.

일반적으로 GET method에 쿼리 조건을 붙이기 위해서 querystring을 붙이는 방법이 있고 POST로 데이터를 추가할 때는 보통 body에 데이터를 실어서 보낸다. PUT, DELETE에는 특정 객체의 상태를 바꾸기 위해서 URL에 id나 다른 인자를 붙여서 보낸다.

반면에 JSON-RPC는 parameter를 전달하는 방법이 한가지 밖에 없다. HTTP의 경우 body에 데이터를 싣는 방법이다. 핸들링하는 코드도 훨씬 단순해질 것이고 구조도 단순해질 수 있다.

심플하다

마지막으로 JSON-RPC에 대해서 찾아보던 중 인상깊은 글이 있어서 가져왔다.

I’ve been a big fan of REST in the past and it has many advantages over RPC on paper. You can present the client with different Content-Types, Caching, reuse of HTTP status codes, you can guide the client through the API and you can embed documentation in the API if it isn’t mostly self-explaining anyway.

But my experience has been that in practice this doesn’t hold up and instead you do a lot of unnecessary work to get everything right. Also the HTTP status codes often don’t map to your domain logic exactly and using them in your context often feels a bit forced.

But the worst thing about REST in my opinion is that you spend a lot of time to design your resources and the interactions they allow.

And whenever you do some major additions to your API you hope you find a good solution to add the new functionality and you didn’t design yourself into a corner already.

This often feels like a waste of time to me because most of the time I already have a perfectly fine and obvious idea about how to model an API as a set of remote procedure calls. … Our programs are based on calling procedures so building a good RPC client library is easy, …

https://stackoverflow.com/a/37823128/6433772

이처럼 JSON-RPC와 달리 REST에서는 한정된 operation으로 원하는 서비스를 제공해야하므로 불필요한 모델들을 추가하고 ‘어떻게 CRUD 내에서 기능을 제공할 수 있을까’에 대해서 고민해야한다. 그리고 새로운 기능을 추가할 때 그 기능에 대해서 기존의 REST API와 어떻게 하면 seamless하게 가져갈 수 있는지 고민도 필요할 것이다.

JSON-RPC는 action-based operation에 적합하기 때문에 내가 도메인 모델을 정해놓고 나면 그에 대한 행동을 묘사하는 메소드를 뽑아내는 것은 훨씬 간단한 일이다. 더불어 HTTP의 경우 HTTP에서 기본적으로 표준을 정해놓은 Status code를 RPC의 응답값과 힘들게 매핑시킬필요도 없다.

Reference

Photo by Spencer Davis on Unsplash

Kubernetes CRD와 Operator에 대해서

최근에 Kubernetes를 통해서 프로젝트를 진행했었다. Kubernetes의 기본적인 사용법과 어떻게 쓰는지는 시간이 지나면서 자연스레 익혀지지만 Kubernetes의 전체적인 철학이나 어떻게 Kubernetes가 동작하는지 원리에 대해서 개념이 머리에 명확하지 않은 것 같아서 시간을 내어 따로 정리해보려고 한다.

그 중에서도 CRD와 Operator 위주로 정리해보려고 하는데 이 두 개의 개념이 Kubernetes의 전반적인 개념과 동작 원리를 가장 잘 보여준다고 생각하기 때문이다.

Custom Resources

Kubernetes에서는 resource라는 개념이 있는데 공식문서에 따르면 resource는

Kubernetes Object를 모아놓은 Kubernetes API의 엔드포인트

라고 한다.

사실 정의에서부터 확실히 모르는 개념이 있었기 때문에 이해하기가 쉽지 않았다. 그래서 먼저 Kubernetes Object와 Kubernetes API에 대해서 먼저 정리해보려고 한다.

Kubernetes Objects

Kubernetes Objects는 공식문서에

persistent entities in the Kubernetes system

으로 표현되는데 Kubernetes는 이 object를 이용해서 다음과 같은 정보를 나타낼 수 있다.

  • application에 현재 붙어 있는 node들
  • application이 현재 이용할 수 있는 resource들
  • application이 어떻게 동작할지에 대한 policy: 간단한 예로 application이 어떻게 재시작할지 어떤 방식으로 업데이트할 것인지 fault-tolerance에 대한 정책들이 있을 수 있다.

Kubernetes의 독특한 점 중 하나인데 Kubernetes의 object는 java에서 말하는 object와는 느낌이 다르다. java에서는 object를 생성하게되면 그 순간 존재하는 것이지만 Kubernetes에서 object를 생성한다고 했을 때는 그것을 바로 생성하는 것은 아니다.

만약 사용자가 kubectl 을 통해서 pod을 1개 생성하는 요청을 보내면 Kubernetes system에서는 pod 하나가 존재하는 “상태”를 Kubernetes에 기록해놓는다. 그리고 Kubernetes 시스템은 그 상태를 맞추기 위해서 현재 상태와 사용자가 ‘원하는’ 상태를 비교하게 된다. 예를 들어 pod 한 개를 만들라는 상태를 kubernetes system에 기록해놓았는데 실제 클러스터에 pod 하나가 떠있지 않다면 Kubernetes system은 사용자가 ‘원하는’ 상태를 맞추기 위해서 실제로 pod을 클러스터에 생성할 것이다.

Object Spec and Status

모든 Kubernetes Object는 공통적으로 두 가지 필드를 가지고 있는데 specstatus가 그것이다.

  • Spec은 해당 object가 어떤 상태를 가질지에 대한 명세를 가지고 있다.
  • Status는 실제로 클러스터에 떠 있는 object가 어떤 상태를 가지고 있는지에 대한 정보를 담고 있는데 이것은 Kubernetes System에 의해서 바뀐다.

Kubernetes API

이와 같이 Kubernetes Object와 관련해서 CRUD 작업을 하기 위해서는 Kubernetes API를 통해서 해야한다. kubectl 은 사용자가 Kubernetes Object를 생성하고 싶다는 요청을 받으면 사용자 대신에 자신이 직접 Kubernetes API에 필요한 요청을 날리고 그 결과 클러스터에 해당 object가 생성되는 것이다.

kubectl 대신에 Kubernetes API 클라이언트 라이브러리를 통해서 프로그래밍적으로도 사용자가 직접 요청을 보낼 수 있다.

Custom Resources

이제 다시 정의로 넘어와보면

resource는 API object를 모아놓은 Kubernetes API의 엔드포인트

kubernetes에서 기본적으로 제공되는 pod resource를 생각해보았을 때 pod resource를 통해서 사용자는 pod과 관련해서 CRUD 를 수행할 수 있을 것이고 pod이라는 것이 어떤 내용을 담고 있는지 알 수 있을 것이다. 좀 더 추상적으로 정리하면 pod resource는 pod과 관련된 Kubernetes의 operation + pod에 대한 정의라고 볼 수 있을 거 같다.

custom resource는 Kubernetes에서 기본적으로 제공하는 resource들 이외에 사용자가 자신의 필요에 따라서 새로 정의한 resource를 말하는데 다르게 말하면 Kubernetes API를 확장하는 작업이라고 볼 수 있다. 이 부분과 관련해서는 Kubernetes에서 aggregation layer라는 개념이 있는데 이 부분은 직접 읽어보는 것이 나을 것 같아서 링크만 첨부한다.

한 번 custom resource가 특정 클러스터에 설치되면 다른 권한이 있는 유저들은 custom resource의 object를 kubectl 을 통해서 생성할 수 있다. 이것은 해당 resource에 대해서 REST API가 aggregation layer를 통해서 Kubernetes API가 확장되었기 때문에 가능한 것이라고 볼 수 있다.

Custom Controllers

Kubernetes controller는 기본적으로 유저가 ‘원하는’ 상태를 읽어서 현재 상태와 비교해 ‘원하는’ 상태로 클러스터의 싱크를 맞춰주는 역할을 한다.

Custom Controller는 어느 resource와도 같이 사용될 수 있지만 custom resource와 같이 사용될 때 가장 강력하다. Custom resource와 custom controller를 같이 사용하는 패턴을 Operator Pattern이라고 하는데 Operator pattern은 custom resource에 대해서 사용자가 원하는 상태를 유지하도록 만들어 준다.

Operator Pattern

Operator Pattern은 현실 세계에서 관리자(operator)의 컨셉을 따온 시스템이다. 현실 세계에서 관리자는 자신이 담당하고 있는 공정이나 서비스의 자세한 부분들을 속속히 알고 있고 자신이 알고 있는 지식에 따라서 각 공정이나 서비스가 어떻게 운영되어야하고 관리되야하는지를 명확하게 알고 있는 사람이다.

Kubernetes operator도 이와 비슷하다. Operator는 자신의 custom resource들을 관리할 때 application domain을 이용해서 resource들 각각이 어떻게 동작해야하고, 어떻게 배포되야하고, 문제에 대해서 어떻게 반응해야하는지 등에 대해서 정의하고 핸들링할 수 있다. 다시 말하면 operator는 application-specific한 controller라고 볼 수 있다.

operator 또다른 강점은 application-specific하기 때문에 resource에 대해서 application domain을 이용해서 부가적인 동작을 하도록 만들 수도 있다. 예시는 아래에서 소개하겠다.

operator에 대한 구체적인 예로는 다음과 같은 상황이 있을 수 있다.

  • SampleDB라는 custom resource를 사용자가 만들어서 클러스터에 생성하였다.
  • 사용자가 SampleDB object를 Kubernetes API를 통해서 클러스터에 생성했다. 그 결과 사용자가 원하는 상태가 업데이트 되었다. (예: 클러스터 내에 SampleDB application 1개가 떠있어라.)
  • SampleDB operator는 Kubernetes control plane에 쿼리하여 SampleDB가 어떤 상태로 클러스터에 존재할지를 확인한다.
  • SampleDB operator는 현재 상태와 원하는 상태를 비교해서 만약 다르다면 원하는 상태로 가기 위한 동작을 취할 것이다. 원하는 상태로 가기 위한 동작으로는 다음과 같은 것들이 있을 수 있다.
  • 현재 상태에 SampleDB가 존재하지 않아서 새로운 SampleDB application을 클러스터 내에 배포할 수 있다. 이 때 SampleDB resource를 구성하는 PersistentVolumeClaim, 실제 application을 띄우기 위한 StatefulSet, 그리고 배포 초기에 설정한 initial configuration 작업을 돌릴 Job object들을 각각 만들 것이다.
  • 현재 상태에 원하는 상태보다 SampleDB가 더 많이 존재해서 SampleDB 하나를 지울 수 있다. 이 때 operator는 삭제할 SampleDB resource의 스내배샷을 찍고 StatefulSet, Volume 등 SampleDB와 관련된 resource들을 삭제할 것이다.
  • operator가 application domain을 이용해서 부가적인 동작을 하도록 코드를 추가 할 수 있다. 예를 들면 주기적으로 SampleDB를 백업하도록 하거나 특정 SampleDB의 버전이 예전 것이라면 최신 버전이 되도록 할 수도 있다.

Kubernetes Control Plane

Kubernetes system과 사용자의 cluster 사이에서 어떤 방식으로 통신하는지에 대해서는 Kubernetes Control Plane이 정의하고 있다. Kubernetes Control Plane에는 다양한 컴포넌트들이 존재하는데 그 중엔 대표적으로 Kubernetes Master와 kubelet 프로세스가 있다.

Control Plane은 클러스터 내부에 존재하는 모든 Kubernetes Object들의 기록들을 관리하고 있으며 사용자가 ‘원하는’ 상태를 etcd에 저장하고 있다. 그 기록들을 통해 object 상태를 관리하기 위해서 control loop를 돌리고 있다. Control loop는 클러스터 내부 상태에 변화가 생길 때마다 그것을 감지하고 만약에 사용자가 원하는 상태와 현재 상태가 일치하지 않으면 그것을 맞추기 위해서 동작한다.

예를 들어 사용자가 Kubernetes API를 통해서 Deployment를 생성할 때 사용자가 원하는 Deployment state를 같이 제출한다. Kubernetes Control Plane은 이것을 기록해서 보관하고 있고 이 상태를 맞추기 위해서 Deployment spec에 제시한 application을 클러스터 내부에 배포하게 된다. 이처럼 Control Plane은 클러스터 내부에 변화가 생길 때마다 다시 사용자가 원하는 형태로 만드려고 한다.

Reference

  • https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#understanding-kubernetes-objects
  • https://kubernetes.io/docs/reference/using-api/api-overview/
  • https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
  • https://coreos.com/blog/introducing-operators.html
  • https://kubernetes.io/docs/concepts/#kubernetes-control-plane
  • https://kubernetes.io/docs/concepts/extend-kubernetes/operator/
  • https://admiralty.io/blog/kubernetes-custom-resource-controller-and-operator-development-tools/
  • https://medium.com/faun/writing-your-first-kubernetes-operator-8f3df4453234
Photo by Hayden Scott on Unsplash

What is gRPC?

Intro

grpc를 이용하여 몇 개의 프로젝트를 했음에도 불구하고 아직 gRPC가 어떤 것인지 명확하게 머리 속에 자리잡지 않은 것 같아서 gRPC 개념에 대해서 정리해보려고한다.

gRPC의 개념에 대해서 한 문장으로 정리하자면 HTTP/2 기반의 RPC 프로토콜이라고 할 수 있다. 그래서 이 개념에 대해서 이해하기 위해서는 RPCHTTP/2 프로토콜 대해서 먼저 알아야한다.

RPC

RPC는 Remote Procedure Call의 약자로 의미 그대로 원격 컴퓨터나 프로세스에 존재하는 함수를 호출하는 프로토콜 이름이다. 기존 로컬 주소 공간에 존재하는 함수(procedure)를 호출하는 것을 확장하여 다른 주소 공간에 존재하는 함수(procedure)를 호출할 수 있는 프로토콜이다. 여기서 다른 주소 공간이라함은 같은 머신 내의 다른 프로세스를 말할 수도 있고 네트워크가 연결되어 있는 다른 머신의 프로세스를 말할 수도 있다. 그렇기 때문에 RPC로 클라이언트-서버 구조의 어플리케이션을 만들 수 있다

RPC는 일반적으로 request parameter과 response parameter를 알아야한다. 그렇기 때문에 미리 서버와 클라이언트 양쪽의 인터페이스를 맞춰야 하는데 Interface Definition Language라고 부르는 IDL로 함수명, 인자, 반환 값에 대해서 정의한 뒤 rpcgen 컴파일러를 이용하여 stub 코드를 자동 생성한다. gRPC는 자신만의 rpcgen 컴파일러가 존재하는데 이는 뒤에서 살펴보겠다.

이 때 생성되는 stub 코드는 개발자들에게 친숙한 go, java와 같은 원시소스코드 형태로 만들어지고 클라이언트, 서버 프로그램에 포함하여 빌드한다.

mechanism

gRPC mechanism

RPC 프로토콜은 다음과 같은 과정을 거쳐서 이뤄진다.

1. 클라이언트는 client stub procedure를 호출하는데 호출하는 입장에서는 평소 로컬 함수를 호출하듯이 함수 인자를 함수에 전달한다. client stub은 클라이언트 주소 공간에 존재한다.

2. client stub은 전달받은 인자들을 marshal(pack)하여 메세지를 만든다. marshal하는 과정에서 데이터형을 XDR(eXternal Data Representation) 형식으로 변환하여 바꾼 인자들을 메세지에 포함시킨다. 이렇게 XDR로 변환하는 이유는 integer, float과 같은 기본 데이터 타입에 대해서 머신마다 메모리 저장 방식(i.e. little endian, big endian)이 CPU 아키텍처 별로 다르며, 네트워크 전송 과정에서 바이트 전송 순서를 보장하기 위해서이다.

3. 이렇게 marshaling을 거친 메세지들은 transport layer에 전달하는데 transport layer은 리모트 서버 머신에 메세지를 전달한다.

4. 서버에서는 전달받은 메세지를 server stub에 전달하고 unmarshal(unpack) 과정을 거처서 클라이언트가 전송한 인자들을 얻어내고 클라이언트가 호출하려고하는 함수를 서버에서 찾아내어 서버의 함수로 실행시킨다. 실행 결과는 다시 server stub에 전달되고 클라이언트가 서버에 함수 인자를 전달하는 방식과 마찬가지로 결과 값이 클라이언트에 반환된다.

RPC라는 개념이 존재하기 전부터 소켓 프로그래밍을 통해서 네트워크에 연결되어있는 다른 서비스를 호출할 수는 있다. 그렇지만 네트워크를 타는 통신은 서버가 응답하지 않는 등의 장애가 발생할 수 있다. 이때 소켓 프로그래밍으로 구현했다면 이와같은 장애에 대해서 개발자가 직접 핸들링해야 한다.

RPC는 이러한 번거로움을 덜어준다. 네트워크 통신과 같은 번거로운 로직들을 감추고 추상화된 통신 인터페이스를 제공해준다. 또한 HTTP 기반의 REST가 유행하면서 RPC는 많이 사라졌는데, REST의 경우 request parameter와 response 값이 명시적이지 않기 때문에 오류가 생길 여지가 있고 JSON을 HTTP를 통해서 쏘기 때문에 속도가 비교적 떨어진다는 단점을 가지고 있다.

Protocol Buffers

gRPC는 protobuf를 IDL로 사용할 수 있다. Protobuf는 구글에서 만들고 현재 사용되고 있는 데이터 직렬화 라이브러리다. protobuf 파일의 예시는 아래와 같다.

IDL로 정의된 메세지를 기반으로 데이터 접근을 위한 코드를 생성하기 위해서 protoc 컴파일러를 사용한다. 간단한 명령어로 개발자가 원하는 프로그래밍 언어로 stub 코드를 생성할 수 있고 getter/setter 등 서버, 클라이언트 인터페이스가 자동 생성된다.

HTTP/2

HTTP/1은 기본적으로 클라이언트가 서버에 요청을 보내고, 서버가 요청에 대한 응답을 다시 보내는 구조이다. 그렇기 때문에 클라이언트가 서버에 요청을 할 때마다 커넥션이 맺어지고 서버를 왕복해야한다. 또한 헤더의 크기는 불필요하게 큰 편이다. 이와 같은 이유로 HTTP/1은 느리다.

구글에서는 이러한 문제점을 해결하기 위해서 SPDY를 만들었고 이를 기반으로 HTTP/2 표준이 만들어졌다.

SPDY

과거에 비해 오늘날에는 훨씬 더 많은 리소스를 주고 받고 있으며 보안이 훨씬 중요한 이슈가 되고 있다. 이렇게 바뀐 환경을 고려하여 구글에서는 느린 HTTP를 보완한 SPDY 프로토콜을 발표했다. SPDY 프로토콜은 latency 문제를 주로 해결하고자 하였다.

SPDY 프로토콜의 특징은 다음과 같은 것들이 있다.

항상 TLS(Transport Layer Security) 위에서 동작

따라서 HTTPS로 작성된 웹에서만 적용 가능하다.

Header Compression

HTTP 통신을 할 때 메세지에 포함되는 HTTP 헤더에는 요청마다 반복되는 내용들이 매우 많다. 그렇기 때문에 헤더 압축만으로도 리소스를 엄청 절약할 수 있다. 구글 발표 자료에 따르면 HTTP 헤더 압축을 할 경우, 최초 요청 시에는 압축을 안했을 때에 비해 10~30%를 줄일 수 있고 long-lived connection과 같이 여러 번 요청을 주고 받을 경우에는 80~97%까지 헤더 크기를 감소시킬 수 있다.

Binary protocol

HTTP/1.1에 존재하지 않았던 프레임이라는 새로운 단위가 생겼는데 이는 통신상의 제일 작은 정보 단위를 말한다. SPDY 프로토콜은 프레임을 텍스트가 아닌 바이너리로 구성하여 파싱이 더 빠르고 오류 발생 가능성이 낮다.

Multiplexing

HTTP에서는 하나의 커넥션에서 한 번에 하나의 요청을 처리하며 요청에 대한 응답이 순차적으로 이뤄졌다. 이와 달리 SPDY에서는 하나의 커넥션 안에서 여러 개의 독립적인 스트림을 동시에 처리한다. 그렇기 때문에 적은 수의 커넥션으로 다수의 요청, 응답을 동시에 처리할 수 있다. 또한 HTTP에서는 요청들이 FIFO로 처리되기 때문에 하나의 요청에 대한 응답이 지연되면 나머지 응답도 모두 늦어지는 HTTP pipelining과는 달리 SPDY에서는 각각의 요청, 응답이 모두 독립적으로 처리된다.

Full-duplex interleaving, stream priority

스트림이 진행 중일 때 다른 스트림이 끼어드는 것을 허용하고 스트림에 대해서 우선 순위 설정도 가능하기 때문에 우선 순위가 낮은 데이터를 전송 중일 때 우선 순위가 높은 데이터가 끼어들어서 더 빨리 전달할 수 있다.

Server push

Long-polling과는 다르게 클라이언트의 요청이 없어도 서버에서 컨텐츠를 직접 push할 수 있으며 리소스 캐싱이 가능하다. 하지만 server push를 구현하기 위해서는 어플리케이션 로직에 추가 구현이 필요하다.

Server push와 같은 경우를 제외하면 SPDY를 적용하기 위해서 추가적으로 작업해야할 필요는 없다. 한 가지, 브라우저와 서버가 SPDY를 지원해야한다. 그리고 spdy:// 와 같이 프로토콜 scheme이 없고 브라우저에서도 SPDY 프로토콜 사용 여부에 대해서 어떤 표시도 하지 않기 때문에 사용자는 굳이 SPDY 사용여부를 알 필요 없이 투명하게 적용 가능하다.

HTTP/2는 SPDY를 기반으로 만들어졌기 때문에 위에서 언급했던 SPDY의 특징을 가지고 있다.

gRPC

gRPC는 HTTP/2 특징을 기반으로 하는 RPC 프로토콜이다. 그렇기 때문에 양방향 스트리밍이 가능하고 HTTP보다 통신 속도가 빠르다. 그리고 RPC의 특징처럼 네트워크 통신에 대한 추상화를 제공해주어서 사용자가 네트워크 프로그래밍에 대해 신경을 쓰지 않게 되고 비즈니스 로직 개발에 집중할 수 있도록 도와준다.

그리고 과거에 gRPC의 단점 중 하나였던 gRPC에 대해 api-gateway 도입이 어렵다는 것이 있었는데 현재는 grpc-gateway 프로젝트를 통해 이는 극복한 것 같다.

필요한 코드의 양이 적고 여러 프로그래밍 언어를 지원한다는 장점이 있지만 프로토콜의 한계상 데이터 포맷 변환이 런타임에 자유롭지 못하다는 단점이 있다.

Reference

  • https://medium.com/@goinhacker/microservices-with-grpc-d504133d191d
  • https://www.tutorialspoint.com/remote-procedure-call-rpc
  • https://www.geeksforgeeks.org/operating-system-remote-procedure-call-rpc/
  • https://leejonggun.tistory.com/9
  • https://d2.naver.com/helloworld/140351
  • https://www.chromium.org/spdy/spdy-whitepaper/
  • https://grpc.io/docs/guides/
  • https://grpc.io/docs/guides/concepts/

About RSA Algorithm

Overview

RSA는 기본적으로 public key cryptosystem이다. 1977년 Rivest, Shamir 그리고 Adleman에 의해서 만들어졌다.

다른 public key cryptosystem과 마찬가지로 RSA도 trapdoor function에 크게 의존하고 있는데 RSA도 마찬가지로 trapdoor function 중의 하나인 integer factorization problem을 이용해서 안전성을 확보하고 있다.

trapdoor-function concept

trapdoor function은 간단히 말하면 위의 그림과 같이 x를 가지고 f(x)를 구하는 것은 쉽지만 f(x)를 알고 있을 때 x를 구하는 것은 어려운 함수를 말한다.

대표적인 예로 RSA에서 사용하고 있는 integer factorization problem을 예로 들 수 있다. integer factorization problem은 어떤 f(x1, x2, …, xn)의 값을 알고 있을 때 그 값을 이루는 x1, x2, … xn을 찾는 문제를 말한다.

이 때 x1, x2, … xn을 알고 있다면 f(x1, x2, … xn)의 값을 찾는 것은 쉽지만 반대로 f(x1, x2, … xn)을 알고 있을 때 x1, x2, … xn을 특정 주어진 시간 동안 찾는 것은 쉽지 않다. 이렇게 한쪽 방향으로 해를 구하는 것은 쉽지만 반대 방향으로 해를 구하는 것은 쉽지 않기 때문에 integer factorization problem을 trapdoor function이라고 볼 수 있다.

Background

relative prime

relative prime은 두 정수를 두고 불리는데 예를 들어 a와 b가 relative prime이라면 (a, b)=1임을 뜻한다.

Euler’s Φ(n) function

Fermat’s little theorem에 따르면 p를 양의 소수라고 할 때 p와 relative prime인 양의 정수 a에 대해서 다음과 같은 식이 성립한다.

복잡할 거 없이 mod 의 의미대로 위의 식을 읽어보면, ap-1은 p로 나눠떨어진다는 뜻을 담고 있다. 여기서 우리는 위의 식을 한 걸음 더 나아가서 Euler’s Φ(n) function으로 일반화시킬 수 있다.

식이 무지막지하다. 첫 번째 식과 비교했을 때 달라진 것은 a의 지수가 Φ(n)로 바뀌었다는 것인데, Φ(n)이 Euler’s Φ(n) function에서 핵심이다. Φ(n) function은 n보다 작거나 큰 양의 정수 n’ (0<n'<=n)에 대해서 n과 relative prime인 n’의 개수를 그 값으로 가진다.

예를 들어보자. Φ(5)에서 5보다 작은 양의 정수는 1, 2, 3, 4가 있는데 이들 중 5와 relative prime인 숫자의 개수는 4개이다. 따라서 Φ(5)=4이다. Φ(8)은 어떨까? 8보다 양의 정수는 {1, … ,7}이 존재하는데 이때 8과 relative prime인 숫자는 1, 3, 5, 7 총 4개이므로 Φ(8)=4이다. 마지막으로 Φ(7)에 대해서 생각해보면 7보다 작은 양의 정수는 {1…6}이 존재하고 이들 중 7과 relative prime은 {1…6} 전체이므로 Φ(7)=6이다.

이와 같이 계산을 하다보면 n이 소수인 경우는 Φ(n)=n-1인 규칙을 가지는데 생각해보면 당연하다. 왜냐하면 소수의 정의가 n보다 작은 정수 n’에 대해서 (n, n’)=1이기 때문이다. 우리는 이런 특징을 RSA 알고리즘에 사용할 것이다.

Modular Inverse

어떤 정수 a에 대해서 a와 어떤 수 x를 곱했을 때 1을 만드는 x를 우리는 a의 곱셈 역원(multiplicative inverse)이라고 부르고 보통 a-1로 나타낸다.

모듈러 연산에서도 곱셈 역원과 비슷한 개념이 있다. 어떤 정수 a와 m에 대해서 a와 어떤 수 x를 곱한 것에 대해 (mod m)한 결과가 1이 되는 x를 우리는 모듈러 역원(modular inverse)이라고 부르고 a-1로 나타낸다.


위의 수식을 보면 중요한 점을 하나 느낄 수 있는데 a와 m이 relative prime일 때만 a가 modular inverse를 가진다.

그렇다면 이제 궁금해지는 것은 a-1는 어떻게 구할 수 있을까? 한 가지 방법은 brute-force 방법을 사용할 수 있다. [0, m-1] 중에 정수를 하나씩 가져와서 모듈러 연산을 해볼 수 있을 것이다.

여기서 한 가지 주목해야할 부분은 brute-force 방법을 이용해서 a-1 구할 때의 시간 복잡도이다. [0, m-1]의 정수에 대해서 하나씩 가져오기 때문에 O(m)이라고 생각할 수 있다. 그런데 cryptosystem에서 다루는 input은 입력값 그 자체가 아니라 입력값을 바이트(or 비트)를 다룬다. 그렇기 때문에 전체 시간 복잡도는 O(2m)이 된다.

이와 같이 어떤 정수 a와 m이 있을 때 a와 m의 a에 대한 모듈러 역원을 구하기 위해서는 m의 지수적인 시간이 걸린다. 하지만 a와 a의 모듈러 역원이라고 생각하는 a’가 있다면 a’가 정말로 a의 모듈러 역원인지 확인하는 것은 어려운 문제가 아니다. 그래서 modular inverse를 구하는 것trapdoor function으로 볼 수 있다.

RSA Algorithm

이제 RSA 알고리즘에 대해서 살펴보려고 한다. 여기에서 RSA 암호화 방식이 어떤 방식으로 진행되는지 알아보고 각 단계에 대해서 짚고 넘어가보자.

  • 먼저 2개의 큰 소수 p, q를 생성한다. 소수를 생성하는 방법 중 하나인 Rabin-Miller 알고리즘을 사용할 수도 있다.
  • n=pq를 계산해야한다. 이 때 계산한 n은 나중에 public key와 private key의 modular 연산에 사용되고 n을 bit로 나타냈을 때 그 길이는 RSA 알고리즘의 키길이가 된다.
  • public key e 파라미터를 구해야하는데, 이 때 e는 (e, Φ(n))=1을 만족해야한다. Φ(n)는 Euler’s Φ(n) function에서 살펴보았듯이 Φ(n)=(p-1)(q-1)로 계산할 수 있다.
  • 다음으로 private key d 파라미터를 구하기 위해서는 public key e의 modular inverse를 구해야한다. 이를 수식으로 나타내면 다음과 같다.

이렇게 d, e를 구하게되면 사용자는 (e, n)을 public key로 사용하고 (d, n)을 private key로 사용하게 된다. 그렇다면 이를 이용해서 어떻게 평문을 암호화하고 복호화할 수 있을까?

먼저 평문을 바이트 배열로 바꾼 뒤 n보다 작은 바이트 블럭들로 나눈다. 그리고 이렇게 나눈 각 블럭들에 대해서

을 이용해서 평문을 암호화할 수 있고 반대로 복호화를 할 때는

을 이용해서 암호화된 메세지를 다시 복호화할 수 있다.

Example

그럼 이제 이해를 돕기위해 실제 숫자를 넣어서 RSA 알고리즘 과정을 따라가보자.

  • 먼저 두 개의 큰 소수 p, q를 생성해야하는데 이번 예시에서는 p=17, q=23이라고 해보자.
  • 다음으로 n=pq=17 * 23 = 391을 구하고 Φ(n)=(p-1)(q-1)=(17-1)(23-1)=352 를 구할 수 있다.
  • public key 파라미터 e를 구하기 위해서 (e, Φ(n))=1을 만족하는 e를 구해야하고 이를 계산하면 e=21이다.
  • private key 파라미터 d는 e의 modular inverse를 통해서 구할 수 있고 이를 계산하면 d=285이다.
  • 사용자는 public key/private key 파라미터 e, d를 이용해 public key와 private key를 구할 수 있고 각각 (e, n)=(21, 391), (d, n)=(285, 391) 로 계산할 수 있다.
  • 이렇게 구한 public key/private key를 이용해서 사용자가 ‘a’란 문자열을 암호화하고 싶다고 해보자. 우선 ‘a’의 ASCII 코드는 97이다. 이를 이용해서 ‘a’의 ciphertext를 구하고 다시 복호화하기 위해서는 다음과 같은 수식을 통해 계산할 수 있다.

Cracking

공격자가 RSA 알고리즘으로 암호화된 메세지를 복호화하려고 하는 경우에 대해 쓰고 이번 글을 마무리하려고 한다. 먼저 공격자는 e, n을 알고 있다. 왜냐하면 e, n은 퍼블릭한 정보기 때문이다. 이제 공격자의 목표는 private key인 (d, n)을 구하는 것이다.

공격자는 RSA 알고리즘이 어떻게 돌아가는지 알고 있고 그래서 d * e mod Φ(n) = 1을 통해 d를 구하고자한다. 그런데 d를 구하는 문제는 modular inverse를 문제이고 이를 푸는데 걸리는 시간은 위에서 설명했다시피 O(2Φ(n))이다. 따라서 Φ(n)이 크면 클수록 RSA 알고리즘이 안전하다는 것을 알 수 있다.

AES algorithm overview

AES? — Step by Step 정리

이번 포스트에서는 AES 알고리즘이 어떤 방식으로 진행되는지 살펴보고 python AES 모듈을 이용해서 어떻게 메세지를 암호화하고 복호화 할 수 있는지에 대해서 알아보려고 합니다.

Background

AES의 등장 배경에는 DES가 있습니다. Data Encryption Standard(DES)를 간단하게 소개하자면 1970년 초 IBM에서 Horst Feistel의 디자인을 기초로 고안되었습니다. 그래서 DES는 Feistel-structure라고 불리기도 합니다. AES와 마찬가지로 대칭키 암호화 방식의 block cipher이고 56-bits 길이의 키를 가지고 있습니다. block cipher는 plaintext를 몇 개의 블럭으로 나누어서 암호화하는 방식을 말하는데 뒤에 가서 좀 더 설명하도록 하겠습니다.

그런데 지금은 DES가 중요한 정보를 암호화하는데는 적합하지 않다고 알려져있습니다. 1999년 초 DES Challenge III에서 DES로 암호화된 메세지가 brute force attack에 의해 해독되는데 22시간밖에 걸리지 않았습니다! 이렇게 DES가 안전하지 않은 주요한 원인 56-bits의 짧은 key size 때문입니다.

이와 같은 문제때문에 좀 더 안전한 cryptosystem이 필요했고, 2001년 Vincent Rijmen과 Joan Daemon에 의해서 AES가 만들어졌습니다. AES의 주요 강점은 DES보다 훨씬 긴 키 사이즈를 가진다는 것인데 AES는 128-bit, 192-bit, 256-bit 키를 가질 수 있고 이것은 DES의 56-bit key보다 기하급수적으로 더 강력합니다.

또한 AES는 DES보다 encryption 속도가 빠르기 때문에 낮은 레이턴시(latency)와 높은 처리율(thorughput)을 요구하는 소프트웨어 어플리케이션이나 펌웨어에 적합합니다. 그래서 다양한 프로토콜에 사용되고 있는데 SSL/TLS에도 사용되고 있고 방화벽이나 라우터 등에도 사용되고 있습니다.

Algorithm

AES algorithm overview
AES algorithm overview

AES 알고리즘의 큰 흐름은 위와 같습니다.

  • Add round key
  • Substitute bytes
  • Shift rows
  • Mix columns
  • Add round key

이제부터 각 단계에서 어떤 식으로 동작하는지 살펴보도록 하겠습니다.

Represent data as metrixes

block cipher represents data as metrixes
block cipher represents data as metrixes

먼저 알고리즘에 대해서 설명하기 전에 위에서 언급했던 block cipher에 대해서 짚고 넘어가려고 합니다. block cipher란 데이터 bit마다 알고리즘을 적용하는 것이 아니라 몇 개의 bit들을 묶어 하나의 block으로 만들고 block에 대해서 알고리즘과 키를 적용하는 cryptosystem을 말합니다.

AES도 block cipher로 평문, 암호문 그리고 키와 같은 데이터들을 위와같이 block들의 집합으로 나타냅니다. 한 블럭은 1byte (8bit)이고 총 16×8=128bit입니다. 키 길이가 128bit이기 때문에 평문 128bit 에 대해서 AES를 적용하게 됩니다. 그리고 p0, p1 … 순서를 확인하면 알 수 있겠지만 컬럼(column)별로 데이터를 나타내고 한 컬럼은 1word(32bits) 사이즈를 가집니다.

Add round key

add round key is XOR operation
add round key is XOR operation

먼저 AES를 진행하면 평문과 키를 가지고 add round key operation을 진행하게되는데 이는 XOR operation을 말합니다. 128bit 길이의 평문과 128bit의 키를 가지고 bit 별로 XOR operation을 진행합니다. 위의 다이어그램을 보면알 수 있지만 output 각각의 bit가 0 또는 1 될 확률은 50%입니다. 따라서 add round key operation을 진행하고 나면 평문값은 0과 1을 비슷한 비율로 가지게 됩니다.

Round function

전체 알고리즘 다이어그램에서 여러 개의 박스를 묶어서 Round function으로 나타낸 부분이 있습니다. DES와 마찬가지로 AES에서도 round라는 개념을 가지는데 묶어 놓은 알고리즘을 한 번 실행하는 것을 1 round가 진행된 것이라고 이해할 수 있습니다. 보통 128-bit key를 대상으로는 10 round 동안 알고리즘이 진행되고 192-bit key를 대상으로는 12 rounds, 256-bit keys를 대상으로는 14 rounds 동안 진행됩니다.

S-BOX: substitute bytes

S-BOX overview
S-BOX overview

s-box
https://en.wikipedia.org/wiki/Rijndael_S-box

다음은 S-BOX를 이용해 각 block의 bit들을 다른 bit들로 바꾸는 과정입니다. S-BOX는 간단히 말해서 lookup table이라고 생각할 수 있습니다. 해당 block의 bit들을 다른 것으로 바꾸는 과정은 다음과 같습니다. 위에서도 말했듯이 한 블럭엔 8-bit의 데이터가 담겨있습니다. 이 때 앞의 4bit를 row index로 보고 뒤의 4bit를 column index로 봐서 해당 S-BOX의 row, column의 값으로 바꾸게 됩니다.

shift rows

how shift rows work
how shift rows work

다음은 shift rows 단계입니다. 여기서는 각 row별로 circular left shift을 하는데 shift하는 step을 맨 윗줄 부터 0, 1, 2 그리고 3으로 잡고 위 그림과 같이 바꿉니다. 첫 번째 줄은 왼쪽으로 circular shift를 0만큼 하여 s0, s4, s8, s12이 원래 데이터였다면 shift rows를 마치고 나면 그대로 s0, s4, s8, s12가 되고 두 번째 줄은 왼쪽으로 circular shift를 1만큼 하여 s1, s5, s9, s13이 원래 데이터였다면 결과물은 s1자리에 s5가 오고 s5 자리에 s9가 오고 s9 자리에 s13가 오고 s13자리에 s1이 와서 s5, s9, s13, s1이 됩니다.

mix columns

mix columns 단계에서는 컬럼 별로 행렬곱(matrix-vector multiplication)을 수행합니다. 현재 matrix에서 컬럼을 가져와서 미리 정의된 circulant MD5 matrix에 곱하게됩니다.

mix columns
mix columns

보다시피 mix columns 단계에서는 bit 수준에서 덧셈과 곱셈을 할 수 있어야 합니다. 이 때 2와 3을 곱하는 연산을 해야하는데 우리는 여기서 2를 곱하는 것은 bit 수준에서 left shift로 생각할 수 있고 두 bit간의 덧셈은 두 bit의 XOR 연산으로 생각할 수 있습니다. 그렇다면 3을 곱하는 것은? 한 번의 left shift와 한 번의 XOR 연산의 조합으로 생각할 수 있습니다.

matrix-vector multiplication
matrix-vector multiplication

이와 같은 과정을 거쳐서 mix columns 단계를 마치게 됩니다. 이 때 주의해야할 점 중 하나는 mix columns 과정은 마지막 round에서는 시행하지 않습니다.

Add round key

이제 round의 마지막 단계인 add round key입니다. 이 때 첫 번째로 수행했던 add round key에서는 private key로 add round key를 수행했다면 이 때는 private key로부터 subkey를 만들어서 operation을 수행합니다.

subkey generation

subkey generation - 1
subkey generation – 1

1 — 먼저 위와 같이 이차원 배열로 나타난 private key를 가지고 있습니다. 위에서 말했듯이 한 block은 1byte를 가집니다.

subkey generation - 2
subkey generation – 2

2 — 먼저 가장 오른쪽에 있는 컬럼을 골라 컬럼 방향으로 circular upward shift를 수행합니다.

subkey generation - 3
subkey generation – 3

3 — 그리고 substitute bytes에서 사용했던 S-BOX를 이용하여 이전과 똑같은 방식으로 shift한 block byte들을 바꿉니다.

subkey generation - 4
subkey generation – 4

4 — 그리고 이전 단계에서 만든 컬럼의 값들과 Ki-4 컬럼 값들을 XOR 하고 미리 정의되어있는 rcon table에서 현재 round에 해당하는 값을 가져와 다시 XOR 연산을 하게됩니다. 그 결과 값이 다음 subkey의 첫번째 컬럼이 됩니다.

subkey generation - 5
subkey generation – 5

5 — 두 번째, 세 번째 그리고 마지막 column 값을 구하는 것은 간단합니다. Ki-1 컬럼과 Ki-4 컬럼을 XOR하면 끝입니다.

subkey generation - 6
subkey generation – 6

6 — 이와 같은 방식으로 128-bit subkey 생성을 마치고 나면 이제 만든 subkey로 add round key 과정을 진행합니다.

Example code

예제 코드는 python Crypto.Cipher 모듈에서 제공하는 AES를 이용하여 실제로 평문을 encrypt하고 decrypt하는 과정을 살펴보려고 합니다.

Import modules

Random 모듈은 우리가 간단하게 private key를 만들기 위해 사용할 것이고, binascii 는 나중에 우리가 encrypt한 데이터를 알아보기 쉬운 hexcode로 인코딩하기 위해서 사용할 것입니다.

Solve padding problems

이전에 언급했듯이 평문을 128-bit씩 나눠서 metrix를 만들고 AES 암호화를 진행합니다. 그런데 만약 평문이 128-bit 보다 작으면 어떻게 될까요? (더 큰 경우는 일단 없다고 가정하고 예제를 작성하였습니다) 이렇게 plaintext가 128-bit로 떨어지지 않는 문제가 있을 수 있기 때문에 128-bit로 나누어 떨어질 수 있게 padding value를 붙여줍니다.

그래서 위와 같이 append_space_paddingremove_space_padding 함수를 정의했습니다. append_space_padding 은 데이터를 encrypt 하기 전에 padding 값을 붙여주기 위해 필요하고 remove_space_padding 은 decrypt 했을 때 padding value를 없애주기 위해 정의했습니다.

어떤 것을 padding value로 할 지에는 몇 가지 옵션이 있습니다. 임의의 bit를 붙일 수도 있고 공백을 추가할 수도 있습니다. 그러나 이 예제에서 사용할 방법은 “a”라는 CMS(Cryptographic Message Syntax)를 정해서 필요한 padding 갯수만큼 추가하는 것입니다.

Encrypt, Decrypt

encrypt, decrypt 가 이번 예제의 비즈니스 로직이라고 할 수 있습니다. 이 함수에서 MODE_ECB 모드를 가진 새로운 인스턴스를 만들고 인스턴스의 메소드를 사용합니다.

ECB가 무엇인지는 이 포스트에서 자세하게 다루지 않습니다. 간단하게 ECB는 “Electronic Codebook”의 약자이고, ECB는 AES에서 데이터를 block으로 나누어서 알고리즘을 적용할 때 각 block에 대해서 독립적으로 알고리즘을 적용하는 방식을 말합니다. 이와 대조되는 모드 중 하나가 CBC 모드인데요. CBC 모드에서는 체이닝 메커니즘을 하용하여 각 block은 이전의 모든 block들에게 영향을 받아서 알고리즘이 진행됩니다.

Entry point

Results !

결과에서 우리는 paddedtext 의 길이가 a 문자가 붙어서 128로 나누어 떨어지는 숫자가 된 것을 볼 수 있습니다. 그리고 hexified ciphertext 는 AES 결과이고 마지막으로 decrypted text 에서 plaintext 와 동일한 Hello, AES! 문자를 출력하는 것을 확인할 수 있습니다.

Conclusion

지금까지 오늘날에도 다양한 곳에서 사용되고 있는 Advanced Encryption Standard(AES)에 대해서 살펴보았습니다. AES는 DES와 마찬가지로 block cipher이기 때문에 데이터를 block으로 나누어 encryption을 진행합니다. AES 알고리즘은 크게 다음과 같은 순서로 진행됩니다.

  • Add round key
  • Substitute bytes
  • Shift rows
  • Mix columns
  • Add round key

Reference

fabric-logo

Hyperledger Fabric Add New Org to Network- eyfn 뜯어보기

이번 포스팅에서는 네트워크 확장과 관련된 eyfn 튜토리얼에 대해서 살펴보려고한다. 이전 byfn 튜토리얼 뜯어보기 포스팅에서는 최초로 organization들이 모여서 네트워크를 구성하는 것에 대해서 살펴보았다면 eyfn에서는 이미 구성된 네트워크에 새로운 organization을 추가하기 위해서 어떻게 하는지를 알려준다. 그래서 이번 포스팅에서는 Hyperledger Fabric 네트워크에서 어떻게 새로운 organization을 추가할 수 있는지를 위주로 작성해보려고 한다.

Generate Crypto Config

eyfn.sh 를 살펴보면 up 명령어를 사용해서 시작했을 때 엔트리 포인트가 networkUp 함수인 것을 알 수 있다. networkUp 에서 하는 일은 먼저 다음과 같다:

generateCerts

First-network 튜토리얼에서는 CA를 따로 사용하지 않으므로 crypto material을 생성하기 위해서 cryptogen 을 사용한다. 다음에 CA를 이용해서 어떻게 인증서를 발급받고 그것을 이용하는지 살펴보도록 하겠다.

org3-crypto.yaml에 명시되어있는 TemplateUser 만큼 인증서를 생성한다. generateCerts 를 마치고 나면 org3-artifacts 디렉토리에 crypto-config가 생긴다.

generateChannelArtifacts

이 함수에서는 configtxgen 툴을 사용하게되는데 기본적으로 configtxgen 은 Fabric 네트워크 channel config와 관련된 파일들을 생성할 수 있게 도와준다. 그리고 configtxgen 이 작동하기 위해선 사용자가 자신이 어떻게 config를 구성할 것인지 먼저 포맷에 맞게 yaml파일을 작성해야하는데 configtxgen 은 이 yaml 파일을 환경변수 FABRIC_CFG_PATH 에서 찾는다.

eyfn에서 org3과 관련된 네트워크 config yaml 파일이 org3-artifacts 디렉토리에 config.yaml로 있기 때문에 해당 경로에 맞게 환경변수를 설정해준다.

다음으로 configtxgen -printOrg 를 이용해서 config.yaml에 Org3MSP에 해당하는 organization의 설정을 org3.json으로 쓴다. 나중에 이 org3.json 을 이용해서 기존에 존재하던 mychannel의 config를 수정하고 org3이 mychannel에 들어갈 수 있도록 할 것이다.

마지막으로 orderer의 MSP를 org3의 crypto crypto-config 로 복사를 하는데 이는 나중에 org3에서 ordering service를 이용하기 위해서이다.

Create Updated ConfigTx

이제 네트워크 밖에서 할 수 있는 사전작업은 모두 마쳤다. 다음으로 해야할 일은 mychannel의 config를 수정하고 mychannel에 속해있는 organization들에게 새로운 organization인 org3이 들어올 수 있도록 허가를 받아야한다. Hyperledger Fabric이 private chain이고 PoA이기 때문에 이와 같이 채널의 기존 그룹으로부터 허가를 받아야 들어올 수 있다.

그렇기 때문에 이제 위의 명령어처럼 도커로 직접 들어가서 작업하게된다. (여기서 조금 악랄한 부분이있다.. 좀 쉽게 만들 수 있었을 거 같은데..)

step1org3.sh에서 위와 같이 먼저 필요한 모듈들 임포트하고 jq 패키지를 설치한다.

fetchChannelConfig

먼저 mychannel 채널의 config를 수정하기 위해서 채널의 config block을 가져와서 그 중에서도 config를 담고 있는 부분만 가져온다.

채널의 configuration block을 가져오는 부분이다. 현재 우리는 cli 컨테이너에 들어와있고 네트워크에 떠있는 orderer MSP로 동작하기 위해서 setOrdererGlobals 를 사용했다. 그리고 peer channel을 통해서 mychannel의 config block을 가져온다. 그리고 tls를 사용한다면 위와 같이 orderer의 tls certification도 같이 제출해야한다.

위의 명령을 실행시키고 현재 디렉토리를 확인해보면 config_block.pb 의 이름으로 config block이 존재하는 것을 확인할 수 있다.

configtxlator는 기본적으로 json또는 protobuf 형식으로 되어있는 fabric transaction을 서로 반대의 형식으로 변환시켜준다. 또는 서로 다른 config transaction의 델타를 구하는데에도 사용된다. 이 두 번째 기능은 좀 더 나중에 사용된다.

지금은 먼저 configtxlator를 이용해서 protobuf 형태로 되어있는 config block을 JSON 형태로 바꾼다. 그리고 JSON 파싱을 도와주는 jq를 이용해서 config 값을 담고 있는 부분만 파싱해서 ${OUTPUT}에 저장한다.

위의 명령을 실행시키고 현재 디렉토리를 확인해보면 config.json 이 생성된 것을 확인할 수 있는데 vim이나 more을 통해서 확인해보면 org1, org2의 설정이 담겨있는 것을 확인할 수 있다.

Append the new organization to current configuration

이전 단계에서 현재 채널의 configuration을 가져왔으니 이제 generateChannelArtifacts 단계에서 만든 org3 configuration 파일인 org3.json 을 여기에 추가해야한다.

이 단계에서도 이전에 사용한 jq를 사용해서 JSON 파일을 수정하게된다. 위의 jq 명령어를 간단히 설명하면 우선 jq -s '<JSON_FILE_1> * <JSON_FILE_2>' 은 두 개의 JSON 파일의 합집합을 만들게 된다. 그래서 두 개의 JSON 파일을 비교해서 서로에게 없는 부분은 추가하게된다. 여기서 <JSON_FILE_1>.[0] 이 되고 <JSON_FILE_2>{"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1]}}}}} 이 된다. 그리고 [0], [1] 은 각각 config.json./channel-artifacts/org3.json 으로 치환된다.

그래서 결과적으로 기존의 configuration에 channel_group.groups.Application.groups.Org3MSP 를 키로 가지고 org3.json 을 벨류로 가지는 필드를 추가하게된다.

createConfigUpdate

여기까지 우리가 생성한 파일을 정리해보면 다음과 같다.

  • config_block.pb: 현재 채널의 configuration block

  • config.json: 현재 채널의 configuration block에서 추출한 channel configuration tx

  • modified_config.json: org3 organization의 정의를 추가한 channel configuration tx

이제 createConfigUpdate 에서 하는 일은 (1) 먼저 config.jsonmodified_config.json 의 차이를 가지는 JSON 형태의 transaction을 만들고 (2) 두 번째로는 이 transaction을 ordering service에 제출하기 위해서 envelope 형태로 만들게 된다. 이 다음 단계에서 우리는 현재 mychannel에 속해있는 organization들에게 서명을 받게된다.

config.jsonmodified_config.json 의 차이를 가지는 transaction을 만들기 위해서 먼저 각각의 JSON 파일들을 protobuf 형태로 바꾼다. 그리고 각각은 original_config.pb, modified_config.pb 의 이름을 가지게된다.

위의 스크립트는 이전에 말한 과정을 풀어낸 것이다. 먼저 org3을 추가하기 이전과 추가한 후의 차이를 가지는 transaction을 configtxlator compute_update 를 통해서 구한 뒤 configtxlator proto_decode를 통해서 JSON 형식으로 바꾼다. 다음으로 transaction을 ordering service에 제출하기 위해서 envelope 형태로 바꾸고 configtxlator proto_encode 를 통해서 protobuf 형식으로 바꾼다. 위 스크립트를 실행시키면 현재 디렉토리에 결과물인 org3_update_in_envelope.pb가 생성된다.

이제 남은 과정은 org3_update_in_envelope.pb 에 mychannel에 속한 organization들에게 서명을 받은 뒤 ordering service에 제출하면 새로운 org3가 mychannel에 들어올 준비가 끝난다.

Signing to config transaction

setGlobals 를 통해서 MSP를 바꾼다. 같은 cli 컨테이너 안이지만 MSP를 바꿈으로써 다른 노드(peer, orderer)의 역할을 할 수 있다. 먼저 setGlobals 0 1 을 통해서 org1이 이전 단계에서 만든 envelope에 peer channel signconfigtx 를 통해서 서명을 한다. 그리고 setGlobals 0 2 를 통해서 org2 MSP로 바꾸고 org1이 서명한 envelope을 ordering service에 제출하게 된다. 이 때 tls를 사용한다면 위의 명령어처럼 orderer의 ca certificate도 --cafile 옵션에 넣어서 같이 제출해야한다.

유효한 envelope을 만들었다면 ordering service에 envelope을 제출했을 때 위와 같이 성공했다는 메세지가 나온다.

Join Org3’s peers to network

이제 eyfn의 핵심적인 내용은 거의 마무리되었다. 위의 과정이 엄청 복잡하지만 잘 생각해보면 결국에는 Org3의 정의를 담은 transaction을 만들고 orderer에게 제출하기 위한 envelope을 만들었다. 그래서 이제 mychannel 채널 configuration에 Org3이 등록되었기 때문에 org3이 직접 mychannel에 join 요청을 보낼 수 있다.

그래서 이제 Org3cli 컨테이너로 들어간다. Hyperledger Fabric에서 채널로 들어가기 위해서는 해당 채널의 genesis block을 이용해야하다.

그래서 위와 같이 mychannel의 genesis block을 가져오기 위해서 orderer에게 요청을 한다. 이 때도 마찬가지로 tls를 사용한다면 orderer의 ca certificate도 같이 제출하여야한다. 위의 명령어를 실행하면 현재 디렉토리에 mychannel.block 이 생성된다. 이것이 mychannel의 genesis block이다.

이제 setGlobals 로 MSP를 바꿔가면서 peer channel join 명령어를 실행시킨다. 그리고 실제로 피어들이 추가됐는지 확인하기 위해서 다음과 같은 방법을 쓸 수 있다.

위와 같이 setGlobals로 확인하고 싶은 피어의 MSP를 설정한 뒤 peer channel list 로 현재 피어가 들어가 있는 채널을 확인해볼 수 있다.

Conclusion

이번 포스팅에서는 어떻게하면 Hyperledger Fabric 네트워크에 새로운 organization을 추가할 수 있는지에 대해서 eyfn 튜토리얼을 통해서 살펴보았다. Organization을 추가하기 위한 config transaction을 만드는 부분이 조금 복잡했다. 첫 번째로 기존의 channel config transaction에서 새로운 organization의 config를 추가했다. 두 번째로는 이전 것과 새로운 것의 델타를 이용해 config envelope을 만들었다. 마지막으로는 현재 채널에 존재하고 있는 organization들에게 서명을 받은 뒤 ordering service에 제출하였다. Hyperledger Fabric은 이와같이 프라이빗 체인이고 PoA이기 때문에 기존의 네트워크에서 허가를 해주어야 새로운 노드가 참여할 수 있다.

다음 포스팅에서는 생각해둔 Hyperledger Fabric와 관련된 튜토리얼이 있는데 그것에 대해서 작성해볼 예정이다.

Filecoin: A Decentralized Storage Network Protocol Overview

최근 SWIM에 발을 들이면서 같이 틈틈히 공부하고 있는 Decentralized Storage Network이다. 개인적으로 생각하기에 현재 작업 중에 있는 SWIM 네트워크 프로젝트를 마무리하고 이 위에 올렸을 때 효용성이 있는 서비스들 중에 하나가 아닐까하고 생각하고 있다. 무엇보다 재미있어보인다. 현재는 작업 시간 중 7-8할을 SWIM에 쏟고 있지만 이 프로젝트가 얼추 마무리되면 좀 더 관심을 가지고 개인적으로든 마음 맞는 사람이든 같이 구현해서 올려보는 것이 목표이다.

Introduction

IPFS와 같이 분산 시스템으로 데이터 콘텐츠를 처리하는 것의 유용성은 수천만개의 파일들을 p2p 네트워크로 서빙하면서 입증해왔다.

Filecoin은 클라우드 저장소를 알고리즘 시장으로 바꾼 분산 저장소 네트워크(Decentralized Storage Network)이다. 이 시장은 Filecoin이라 불리는 토큰의 블록체인 위에서 돌아간다. 마이너들은 클라이언트들에게 저장소를 제공함으로써 토큰을 채굴하게 된다. 반대로 클라이언트들은 파일과 데이터들을 분산 저장 하기 위해 마이너들을 고용하고 토큰을 대가로 주게된다. 비트코인과 마찬가지로 Filecoin 마이너들은 블럭을 채굴하기위해 다른 마이너들과 경쟁하게 되는데 이때 채굴 파워는 마이너들 각자가 가지고 있는 현재 활용되고 있는 저장소에 비례하게 된다. 그렇기 때문에 마이너들은 자연스럽게 클라이언트들에게 더 좋은 서비스를 제공하는 동기를 부여한다. 이러한 프로토콜은 막대한 양의 자원들이 유실되거나 파괴되더라도 다시 원 상태로 복구 되는 self-healing 저장소를 만들게 된다. 또한 네트워크는 분산된 데이터들을 복제함으로써 그 견고함을 유지하게되고 데이터 암호화를 하면서 end-to-end 보안을 하게 된다.

Filecoin은 Proof-of-Spacetime을 기반으로한 프로토콜 토큰이다 그리고 블럭들은 데이터들을 저장하고 있는 마이너들에 의해서 생성된다. Filecoin 프로토콜은 데이터 저장소를 제공하고 저장소에서 파일을 다운 받을 수 있는 서비스를 제공하는데 이는 한 명의 중재자를 통해서 제공되는 것이 아니라 데이터들을 저장하고 있는 여러 명의 독립적인 저장소 제공자들에 의해서 제공된다.

  1. 클라이언드들은 데이터를 저장하거나 다운 받기위해서 토큰을 지불해야한다.
  2. 저장소 마이너들은 클라이언트들에게 저장소를 제공함으로써 토큰을 얻게 된다.
  3. 데이터회수 마이너들은 클라이언트들에게 파일을 제공함으로써 토큰을 얻게 된다.

Elementary Components

Filecoin 프로토콜은 크게 다음과 같은 네 가지 컴포넌트들로 구성된다

  1. Decentralized Storage Network (DSN): Filecoin은 저장소 제공자에게 저장소를 제공하고 저장소에서 파일을 제공할 수 있게 DSN이라는 추상화된 네트워크를 제공한다.

  2. Proofs-of-Storage: Filecoin은 두 가지의 새로운 Proofs-of-Storage 자격증명 방식을 제시한다.

    1. Proof-of-Replication: Proof-of-Replication은 데이터 저장소 제공자들이 각자 자신이 가지고 있는 고유 물리적 저장소에 데이터를 저장 및 복사했다는 것을 증명할 수 있게한다. Proof-of-Replication은 확인자(verifier)가 증명자(prover)의 저장소에 데이터들을 복사해놓았는지를 확인한다.
    2. Proof-of-Spacetime: Proof-of-Spacetime은 데이터 저장소 제공자들이 특정 지정된 시간동안 특정 데이터를 저장해놓았는지를 증명하게 한다.
  3. Verifiable Markets: Filecoin은 데이터 저장 요청과 데이터 회수 요청을 두개의 decentralized verifiable market들의 주문(order)으로 모델링하였다. verifiable market은 데이터 저장 혹은 회수 서비스가 제대로 행해졌을 때에만 그 대가를 지불할 수 있게 한다. 그래서 두 개의 Verifiable Market(데이터 저장 요청을 처리하는 Storage Market과 데이터 회수 요청을 처리하는 Retrieval Market)에서 클라이언트와 마이너들은 각각 데이터 저장 요청을 보내고 데이터 회수 요청을 보낼 수 있다.

  4. Proof-of-Work: Filecoin은 비트코인과 다르게 Proof-of-Spacetime을 기반으로한 Proof-of-Work를 제공한다. 마이너들은 쓸데없는 계산파워를 사용하지 않고 데이터를 저장소에 저장하는 것으로 그것을 대신할 수 있다.

Protocol Overview

  • Filecoin 프로토콜은 분산 저장소 네트워크를 만들기 위한 프로토콜이다. Filecoin 프로토콜은 Filecoin 토큰을 기반으로한 블록체인 위에 만들어진다. 클라이언트들은 데이터를 저장하고 회수하기위해 토큰을 사용하고 마이너들은 데이터 저장과 회수 서비스를 제공하면서 토큰을 받게 된다.
  • Filecoin DSN(Decentralized Storage Network)는 데이터 저장과 데이터 회수 요청을 각각 두 개의 Verifiable market이란 개념을 통해 핸들링하게된다. 데이터 저장 서비스는 Storage Market에서 이루어지게되고 데이터 회수 서비스는 Retrieval Market에서 행해진다. 클라이언트들과 마이너들은 각각의 서비스에 대해서 가격을 매겨놓는다. 클라이언트의 경우는 자신이 이 서비스를 수행하기 위해서 얼마의 토큰을 지불할지를 명시해놓고 마이너의 경우는 서비스를 제공하기 위해서 그 대가로 얼마의 토큰을 받을지를 명시한다. 그리고 이 가격들과 요청은 각각 Verifiable Market으로 들어가게 된다.
  • Verifiable Market은 Filecoin DSN 위에서 동작하는데 내부적으로는 Proof-of-Spacetime과 Proof-of-Replication에 의해서 동작한다. 이 두가지 자격증명은 마이너들이 자기가 저장하겠다고 한 데이터를 제대로 저장하였는지를 확인한다.
  • 마이너들은 블록을 만들기 위해서 참여하는데 다음 블럭을 만들기 위한 마이너들의 영향력은 현재 각 마이너들의 저장소가 전체 네트워크에서 얼마나 사용되고 있는지에 비례하게 된다.

Filecoin Protocol Sketch

다음은 Filecoin 프로토콜이 전체적으로 어떻게 작동되는지를 묘사하기 위한 스케치이다.

filecoin logic flow overview
Filecoin Protocol을 다이어그램으로 나타난 것이다. 특히 클라이언트와 마이너의 상호작용을 나타내었다. Storage Market과 Retrieval Market은 각각 블록체인 아래에 나타내었고 왼쪽에서 오른쪽으로 시간이 흘러가는데 왼쪽은 Order Matching phase이고 오른쪽은 Settlement phase이다. 주목해야할 점은 데이터 회수의 대가로 micropayment를 하기전에 클라이언트는 반드시 자신의 펀드에 lock을 걸어야한다.

Network

네트워크에서 시간 t초동안 ledger안에서 다음과 같은 일이 일어난다.

  1. 새로운 블럭에 대해서는:

    1. 블럭이 제대로 된 포맷인지 확인한다.
    2. 블럭안의 트랜젝션이 유효한 것인지 확인한다
    3. 트랜젝션의 주문이 모두 유효한 것인지 확인한다.
    4. 자격증명이 유효한지 확인한다.
    5. 만약 위의 조건들 중 하나라도 만족하지 못하면 블럭을 버린다.
  2. t초 동안 새로운 주문 O에 대해서:

    1. O를 Storage Market의 오더북에 추가한다
    2. if O is bid: lock O.funds
    3. if O is ask: lock O.space
    4. if O is deal: run Put.AssignOrders
  3. Storage Market의 오더북에 있는 각각의 O에 대하여:

    1. check if O has expired (or canceled):

      1. remove O from the orderbook
      2. return unspent O.funds
      3. free O.space from AllocTable
    2. if O is a deal, check if the expected proofs exist by running Manage.RepairOrders

      1. if one missing, penalize the M(miner)’s pledge collateral(담보)
      2. if proofs are missing for more than delta fault, cancel order and re-introduce it to the market
      3. if the piece cannot be retrieved and re-constructed from the network, cancel order and re-fund the client

Client

client는 매순간 다음과 같은 일을 할 수 있다:

  1. 파일 저장요청을 Put.AddOrders를 통해 제출한다.

    1. find matching orders via Put.MatchOrders
    2. send file to the matched miner M
  2. 파일 회수요청을 Get.AddOrders를 통해 제출한다.

    1. find matching orders via Put.MatchOrders
    2. create a payment channel with M

저장소 마이너 M으로부터 deal을 하겠다는 주문 O를 받으면:

  1. deal 주문 O에 사인한다.
  2. 사인한 deal O를 Put.AddOrders를 통해서 블록체인에 제출한다.

데이터 p를 회수 마이너 M으로부터 받으면:

  1. p가 유효한 데이터인지 확인하고 클라이언트가 요청한 데이터가 맞는지 확인한다.
  2. 대가를 M에게 지불한다.

Storage Mine

마이너들은 매순간 다음과 같은 일을 할 수 있다.:

  1. 만기가 된 pledge들을 Manage.PledgeSector를 통해 갱신한다.
  2. 새 저장 공간을 Manage.PledgeSector를 통해서 담보를 잡는다.
  3. Put.AddOrder를 통해서 새 ask order를 제출한다.

매 시간 t 마다:

  1. 오더북에 있는 각각의 Oask에 대해서:

    1. Put.MatchOrders를 통해 매치되는 오더를 찾는다
    2. 매칭된 오더를 찾으면 해당 클라이언트와 컨택을해서 딜을 시작한다.
  2. 각각의 데이터들에 대해서:

    1. Manage.ProveSector를 통해서 해당 저장공간에 대해 자격증명 생성한다.
    2. delta proof epochs 시간 동안 자격증명시간이 끝나면 생성한 자격증명을 블록체인에 제출한다.

데이터 p를 클라이언트 C로부터 받으면:

  1. 데이터 p가 bid 오더에 명시되어있는 파일 사이즈가 맞는지 확인한다.
  2. 마이너는 deal 오더를 생성 및 사인을하고 서명을 클라이언트에게 보낸다
  3. 데이터를 저장 공간 섹터에 저장한다.
  4. if sector is full, run Manage.SealSector

Retrieval Mine

매 순간마다 다음과 같은 일을 한다:

  1. 가십으로 ask 오더를 네트워크에 전파한다.
  2. 네트워크의 bid 오더를 리슨하고 있는다.

클라이언트 C로부터 회수 요청이 오면:

  1. C와 payment channel을 시작한다.
  2. 데이터를 여러 파트로 나눈다.
  3. 페이가 올 때마다 데이터 파트를 보내준다.

Reference

  • Protocol Labs, Filecoin: A Decentralized Storage Network, 2017

How DDD Concept can be applied to Project

I’ve translated blog post (korean version) which is about 7 important concepts which is about DDD(Domain-Driven Design).

  • Ubiquitous language
  • Layers
  • Bounded contexts
  • Anti-Corruption Layer
  • Shared Kernal
  • Generic subdomain

After translating post I’ve done projects which apply DDD concepts. One is blockchain engine project, it-chain. You can see the codes on this link

In this post, I’m going to show how DDD concepts can be applied to real-world project codes and code snippets which will be shown in this post are based on it-chain project I’ve introduced above.

Ubiquitous language

Ubiquitous language is a language that define terms which are matching business requirements on Application and technology for implementing it. For example, blockchain component in it-chain project needs to manage blocks (save blocks, create blocks…). Managing blocks is business requirements for blockchain component. But the problem is the block is not all the same, there can be several different states on each block. For example, there can be the block which is just created with transactions, this block is not saved to repository and even not concensused with members of network. This ‘just created’ block should not be treated as block which is consensused successfully by members and saved to repository.

So the developers who are work on blockchain component think that they need to define terms about block states: ‘Created’, ‘Staged’, ‘Commited’. Then why did they defined those terms? One problem when developers work on big project, they cannot understand the codes developed by others easily. Even worse, one developer may misunderstand the code! This problem will getting worse as code base grow.

By defining common language(Ubiquitous language) before work on codes or before develop some features, the other developers can understand what this code is doing now and why this code is placed here. it-chain developers defined term ‘Created’, ‘Stateged’, ‘Commited’ right after they think they need to distinguish block states. Next time as new contributor came in blockchain component and looked at CreatedBlock in method. He/She can easily figure out ‘Oh, this method is about just created block which is not consensused and saved to repository’. Because we defined common language about block states.

Layers

Layering concept is used in other designs, but I imported several layers identified by DDD:

  • User Interface

    User interface responsible for draw screen which makes user to interface with Application, convert user’s input into Application commands. Important point here is that user in User Interface is not human. But Application also can be user if that Application use the other Application’s API.

    In it-chain project, we can see User Interface layer in cmd package.

    This code is about joining peer node to existing network. You can see it just receive user’s input (node ip addresss) and call rpc client.Call for joining into network. We cannot see application service logic, it just receives and then pass.

  • Application Layer

    Application Layer orchestrate domain objects for carrying out Application business. So Application Layer should not contain business logic.

    This code is about txpool component CreateTransaction API function. As function name imply it just create transaction with tx data and save it to repository. And there’s no business logic, TransactionApi use TransactionRepository, and domain’s function CreateTransaction. Application Service is abstracted with domain object. All the detailed (business logic) encapsulated inside domain object, function.

  • Domain Layer

    As DDD(‘Domain driven development’) name says domain layer is key part of Application. Domain object such as service, repository, factory, model contains all the business logic.

    This code is about txpool domain layer Transaction, CreateTransaction. We’ve seen CreateTransaction domain function is used inside Application Layer (TransactionApi). CreateTransaction says how to create transaction with txData. We can see all the detail business logic is encapsulated into Domain layer.

  • Infrastructure

    Infrastructure layer contains the techinical capabilities which support the layers above.

    This code is about blockchain component infrastructure layer. One easy example of infrastructure layer is database. BlockRepository have database library yggdrasill which helps save data to level-db. In Save function you can see br.BlockStorage.AddBlock(&block) which works as wrapper for external library and helps to carrying out blockchain component Application business.

Bounded Contexts

As Application’s domain grow, there can be more developers who work on the same code base. But this situation have problems. As developers who work on same code base grow, the codes each developer should understand larger and understanding may can be hard. And this increases the possibility of bugs or errors. Furthermore, as code base developers work on grow, managing each developer’s work can be hard.

One way to solve these problems is separating one huge code base and “bounded context” help this. Bounded context is context which can separate domains based on its own concern.

it-chain logical architecture
it-chain logical architecture

it-chain project separate whole domain into several bounded contexts based on its own concern. and we called each bounded context as “Component”. it-chain has following components:

  • Client Gateway

    Client gateway provides REST API for client application of it-chain

  • gRPC Gateway

    gRPC gateway is service for communication for nodes of network. Communication needs for blockchain synchronize, consensus etc.

  • TxPool

    TxPool temporarily save transactions which are not saved into block

  • Consensus

    Consensus component is for consensus of block, currently Consensus component provides PBFT algorithm

  • Blockchain

    Blockchain component helps to create, save block and synchronize blockchain

  • IVM

    IVM component manage it-chain’s smart contract called iCode

Anti-Corruption Layer

Anti-corruption layer basically work as middleware between two different bounded context. So instead of each bounded context communicate directly, they use anti-corruption layer. Then why anti-corruption layer?

If two different component communicate directly without anti-corruption layer, one change in a component can affect the others. And this can be a disaster as project grows, a small fix in a component can break the whole system. What if we use anti-corruption layer and each component communicates with anti-corruption layer? A change in a component only affects anti-corruption layer, and if we communicate with anti-corruption layer with its interface, there may no affects on the other system except its own component!

it-chain logical architecture
it-chain logical architecture

In it-chain project each component only communicates with RabbitMQ Interface. And this helps developers make only cares about its own component. Nothing outside my component is my concern.

This code is about sending transactions from TxPool component to EventService. Instead of directly send transactions to Blockchain component, txpool publish ProposeBlockEvent to message queue. So both txpool and blockchain don’t need to care about other side, just publish and subscribe.

Shared Kernel

However we separate whole system into bounded contexts, sometimes it is much more resonable to share some domain objects. With shared kernel each component strongly coupled with shared kernel but still make decoupled with the other components.

In it-chain project shared kernel is located in common package. One big decision we made recently is that place event and command which are used to communicate different components into shared kernel (common package) and every event, command must use primitive type.

  • before

  • After

The reasons are as followed:

  • Before event and command is located inside each component, the other component should reference other component’s event and command type for communication with other component. And it looks like we broke up the bounded context.
  • Before event and command using primitive type, they use its own domain type inside. And this feel we completely broke up bounded contexts, for example, if blockchain component wants to communicate with txpool component blockchain component should know about txpool domain type because blockchain should make txpool‘s event or command type.

Conclusion

In this post, we’ve looked at how DDD key concepts can be applied to real-world projects. With these examples, I hope you can get hint what is DDD.

Hyperledger Fabric: Transaction Flow

이번 포스팅에서는 Hyperledger Fabric의 transaction이 어떻게 생성되고 ledger에 최종적으로 commit되는지 이해가 잘 되도록 예시를 들어 설명되어 있는 글을 Hyperledger Fabric doc에서 발견해서 번역해보았다. 원본 링크: https://hyperledger-fabric.readthedocs.io/en/release-1.2/txflow.html

Scenario

상품 거래를 하는 시나리오를 가지고 어떻게 transaction이 발생하고 ledger에 commit되는지 그 과정에 대해서 설명하려고 한다. 예시로 사용할 시나리오에서는 A, B라는 두 명의 client가 있고 각각은 당근을 사고 팔려고 한다. client A, B 각각 자신의 네트워크에 피어를 가지고 있고 그 네트워크를 통해 각각의 transaction을 보내고 ledger에 기록한다.

Assumptions

이 시나리오에서는 channel이 세팅되어있다고 가정한다. 그리고 어플리케이션의 user들은 각 organization의 CA에 register & enroll하고 cryptomaterial materal을 생성했다. 그리고 user들은 이것을 이용해서 network authentication에 사용한다.

각 피어들에게 chaincode (당근 시장의 초기 상태를 나타내는 key & value pair들이 포함되어있다.)가 installed되었고 channel에 instantiated되었다. chaincode에는 transaction instructions set이 정의되어있다. Endorsement policy 또한 이 chaincode에 세팅되어 있고 이 때 policy는 모든 transaction에 대해서 피어 A, B 모두 endorse해야한다고 정의된다.

1. Client A initiates a transaction

먼저 구매자인 client A가 당근을 구매하겠다는 request를 보낸다. 이 때 이 request는 peerApeerB를 타겟으로 삼는다. 왜나하면 channel이 만들어질 때 정의된 endorsement policy에 따르면 모든 피어들이 transaction에 대해서 endorse 해야하기 때문이다.

그 다음, transaction proposal이 생성된다. SDK를 사용하고있는 어플리케이션에서는 SDK API 함수를 이용해서 transaction proposal을 생성한다. 이 proposal은 chaincode를 invoke 해달라는 요청인데, 결과적으로 이 proposal을 이용해서 ledger를 읽거나 쓸 수 있다. 여기서 사용하는 SDK는 transaction proposal을 gRPC를 통해 전송될 수 있는 포맷으로 패키징하는 역할을 해준다. 그리고 이 패키징에 user의 cryptographic credential을 이용해서 이 transaction proposal의 유일한 signature를 만들게 된다.

2. Endorsing peers verify signature & execute the transaction

그 다음 단계로 endorsing peer들은 이전 단계에서 만들어진 transaction proposal을 받게 되는데 다음과 같은 사항들을 확인한다.: (1)첫 번째로 이전 단계에서 만들어진 transaction proposal들이 잘 만들어졌는지 확인하고 그리고 (2)똑같은 transaction proposal이 이전에 요청되었는지도 확인한다. (replay-attack protection) (3) 세 번째로 signature가 유효한지 MSP를 이용해서 확인하고 (4) 마지막으로 요청을 전송한 submitter(현재 시나리오에서는 Client A이다.)가 현재 channel에 proposed operation을 할 수 있는 권한을 가지고 있는지 확인한다. 다시 말하면 submitter가 현재 channel의 Writers policy를 충족하는지 확인한다.

endorsing peer들은 transaction에서 invoke 되고 있는 chaincode의 함수 인자를 가지고 다시 chaincode를 현재 데이터베이스 상태에 대해서 실행시킨다. 그리고 그것들을 실행시킨 결과로 response value, read set 그리고 write set을 만들게 된다. 그런데 중요한 점은 chaincode의 함수를 실행시켜본 것이지 그 결과를 이용해서 ledger를 업데이트 시킨 것은 아니다. 아까 말한 세 가지 결과와 endorsing peer의 signature를 합쳐서 proposal response를 만들고 이것을 다시 SDK에 전송하게 된다. 어플리케이션에서는 payload를 파싱해서 사용하면 된다.

MSP

MSP는 client로부터 전달되는 transaction request들을 피어들로 하여금 검증할 수 있게 해주고 transaction를 검증한 결과에 대해서 sign 할 수 있게 해주는(endorsement) peer component이다. 이 때 writing policy는 channel이 생성될 때 정의되고 어떤 user들이 channel에 transaction을 제출할 수 있는지 명시한다.

3. Proposal responses are inspected

어플리케이션에서는 endorsing peer의 signature들을 검증하고 proposal response들을 가지고 서로 같은지 비교한다. 이 때 chaincode가 ledger를 조회하는 작업만 했다면 어플리케이션에서는 조회 결과만 확인하고 transaction을 Ordering Service에 제출하지 않는다.

만약 client 어플리케이션이 transaction을 Ordering Service에 ledger를 업데이트 시키기 위해 제출하려면 endorsement policy가 충족되었는지(peerApeerB 모두 endorse해야한다.)를 먼저 확인해야한다. 만약 어플리케이션 단에서 response를 확인하지 않고 unendorsed한 transaction을 보내거나 혹은 endorsement policy를 충족시키지 않는 transaction을 제출하게되면 commit validation 단계에서 reject 하게 된다.

4. Client assembles endorsements into a transaction

어플리케이션은 transaction proposal과 “transaction message”의 response를 Ordering Service에 보내게 된다. 그리고 transaction은 read/write set, endorsing peer의 signature들과 channel ID를 포함하고 있다. 이 때 Ordering Service는 transaction 전체를 살펴볼 필요가 없다. Ordering Service에서는 단순히 네트워크 상의 모든 channel에서 transaction을 받고 channel에 따라서 정렬한 다음 channel 별로 block을 생성하면 된다.

5. Transaction is validated and committed

위에서 생성된 block은 해당 channel의 모든 피어들에게 전송된다. 그리고 block에 있는 transaction들은 endorsement policy를 충족시키는지 검증하게되고 read set들에 대해서는 ledger state에 변화가 생기지 않았는지 확인한다. 이 검증 결과에 따라서 block의 transaction들은 valid나 invalid라는 태그를 달게된다.

6. Ledger updated

마지막으로 모든 피어들은 channel의 chain에 block을 연결시키게 되고 valid한 transaction의 write set들은 database에 commit된다. 그리고 client application에 transaction이 chain에 붙었다는 event가 전파되고 그 transaction이 검증된 것인지 아닌지도 알리게 된다.

Sequence Diagram

마지막으로 모든 step들을 sequence diagram으로 표현하면 다음과 같다.

https://hyperledger-fabric.readthedocs.io/en/release-1.2/txflow.html