ABCI, and its Connection
들어가기 전에..
1.
이 글은 텐더민트 컨센서스에 대한 기초적인 이해가 있다고 가정하고 작성된 글이다. 그렇다고, 따로 공부하고 읽어야할 정도는 아니고, 중간중간 모르는 것들을 찾아보면서 읽는 정도로도 충분할 것이다. 또한 이전 포스팅 텐더민트와 ABCI을 읽는다면 더 편하게 이해할 수 있을 것이다.
2.
이 글은 PDAO(Postech DAO) 세미나를 준비하면서 만든 자료를 근거해 쓴 글이다. 만약 이 글에 대한 동영상 자료를 보고 싶다면, 링크에서 세미나를 볼 수 있다.
What is Tendermint, Not Tendermint Core
Goal of Tendermint
많은 곳에서 텐더민트에 대해서 검색하면, 컨센서스 알고리즘에 대한 설명이 나온다. 그 이유는 단순히 그것이 텐더민트의 핵심이기 때문이다. 그렇다면, 핵심이 아닌 전체는 뭘까?
세부적으로 들어가기 이전에, 텐더민트의 목적에 대해서 살펴보고 넘어가도록 하자. 텐더민트의 목적은 "Being General Consensus Engine"이다. 즉, 텐더민트는 General Consensus Engine에 대한 솔루션을 제공해줌으로써, 사람들이 더 Application layer에 집중하게 도와주는 프로그램이다.
어떤 Dapp을 만들기 위해서는, 앱이 체인 위에 올라가야 한다. 즉, app을 만들기 위해서 그 아래 레이어인 새로운 체인을 만만들어야 한다. 당연하게도, 쉽지 않고, 코스트가 많이 드는 일이다. 왜냐하면, 컨센서스나, 네트워크를 다 짜야 하기 때문이다.

따라서, 네트워크와 PoS-BFT스타일 컨센서스를 구현해서 컨센서스 엔진으로 만든 것이 Tendermint Core이고, 그러한 Core를 사용하기 위한 Generic Application Interface가 바로 Application Blockchain Interface, ABCI이다.

그리고, 텐더민트의 장점이라고 할 수 있는것이 바로, application layer에 대한 언어제한이 없다는 것이다. Go, Rust, C++, Python등 아무런 언어를 사용해도 된다. 이를 위해서 텐더민트는 몇 가지 방법을 지원해주는데 이는 뒤에서 알아보도록 하자.
텐더민트에 대해서 정리를 해보자면, 텐더민트는 두 가지 구성요소로 이뤄져 있고,
$Tendermint=TendermintCore+ABCI$
각 요소는
- Tendermint Core: Network, Consensus 기능을 구현한 Consensus Engine
- ABCI: Tendermint Core를 사용하기 위한 General Application Interface(API와 유사한 개념이라고 생각하면 이해가 편할듯 하다.)
와 같다.
이 프로그램의 목적은, Dapp을 구현할 때, 언어, 네트워크, 컨센서스 등 앱이 아닌 다른 문제를 해결해주는 것이다.
More details in ABCIs
텐더민트 코어는 메세지 시스템으로 그 상태가 변화한다. 노드들 사이에 메세지를 주고 받고, 그 주고 받은 메세지를 로그로 기록하면서, 특정 상태가 되면 변화하는 시스템이다.
가장 대표적으로, PREVOTE stage에서 특정 value(proposal)에 대해 prevote하는 메세지가 2f+1이상 오게 된다면, PRECOMMIT stage로 넘어가게 된다.
ABCI 역시 이러한 메세지 시스템을 사용한다. ABCI는 메세지 타입의 메소드 집합이라고 말할 수 있는데, 이러한 메세지 타입을 사용해서 app과 core(Tendermitn Core)가 소통한다.
ABCI 메소드는 두 가지로 분류할 수 있다. Request, Response이다.

Tendermint Core에서 Request의 메세지를 보내면, App에서는 Response 메세지를 보낸다.
e.g. RequestFlush $⟹$ ResponseFlush
Types of ABCI
ABCI 메소드는 사용처에 따라서 몇 가지로 나뉠 수 있다.
- Executing Tx(Transactions)
- Validating Tx
- Query
- for StateSync: 이후에 설명
- General functionality
이는 이후에 Connection과 관련이 있기 때문에 이러한 종류로 나뉠 수 있다 정도로 알아두면 좋을 것이다.
How to support language-free option?
이전 챕터에서 텐더민트는 Dapp을 언어와 상관없이 짜게 해준다고 했다. 그렇다면 이 기능을 어떻게 지원할까?
Case of Golang
만약 Dapp을 Golang을 사용하여 짜는 경우, 그냥 ABCI메소드를 함수를 부르듯이 쓰면 된다. 텐더민트가 Go로 만들어진 프로그램이기 때문에, 단순히 import해서 사용하듯 간단하게 사용할 수 있다.
이 경우, 아무래도, ABCI를 사용하는 방법이 단순하기 때문에 개발 시간/성능 면에서 뛰어나다고 말할 수 있다.
Case of gRPC
gRPC는 google에서 만든 RPC framework이다. (간단히 말하자면, 정해진 규격(protobuf in gRPC)를 사용하여 데이터의 형식을 맞춰주고, 그 데이터를 이용하여 함수를 호출해주는 시스템이다.) 구글의 척척박사님들이 만들어준 프로그램답게, 성능이 좋지만, 다른 프로그램을 하나를 끼고 작동하는 것은 오버헤드가 분명한 작업이다.
이 경우, 어느정도 메이저한 언어라면 gRPC에서 지원을 해주기 때문에 왠만한 언어를 다 사용할 수 있다.
Case of TSP
TSP(Tendermint Socket Protocol)는 텐더민트에서 만든 Socket Protocol로, 아무 언어와 protobuf를 사용해 직렬화한 데이터를 처리하는 방식이다.
Connection
그렇다면 Go/gRPC/TSP로 나눠서 생각하면 되는걸까?
답은 "아니오"이다. 언어의 종류외에도 고려해야할 사항이 하나 더 있다.
바로 "호출 환경"이다. Dapp은 체인 위에 올라가는만큼, 사용자가 컨센서스에 참여해 계속해서 체인을 따라가거나, 풀 노드에게 요청하거나 하는 방법을 써야 한다.
즉, SMR(State Machine Replication) Engine과 Dapp의 실행환경(프로세스)이 동일한지가 관건이다. 서로 다른 장소에서 실행하게 된다면, Dapp을 직접 호출할 수 없을 것이다.
그렇다고, SMR과 Dapp을 항상 같은 프로세스에서 실행하기는 어려운 법이다. 예를 들어, 사용하는 Dapp의 종류가 여러가지가 된다면, 한 프로세스에서 그 모든 것들을 처리하기 어려울 것이고, Dapp끼리 소통한다면 더더욱 힘들 것이다,
이러한 경우 gRPC or TSP를 사용해서 SMR과 Dapp을 연결해준다. 그리고, 이런 소통 환경은 불안정할 수 있기 때문에 Core와 ABCI 사이에 state를 만들어서 안전한 실행을 지원해준다.
이 때, 안전한 실행을 지원하기 위해서, 특정 타입의 ABCI를 맡아서 처리하고, state를 가지는 것이 바로 Connection이다.
Connection의 종류

Connection에 대해서 더 자세히 알아보자. 앞서, ABCI가 app과 Core를 연결하는 인터페이스라고 설명한 바 있다. 이러한 연결 방식을 Connection을 사용하여 더 구체화시킨 것이 오른쪽의 그림이다. App과 Core 사이에서 ABCI Connection이 존재하고, 이 중간 과정을 거쳐서 통신한다.
Connection의 종류로는
- Consensus Connection
- Mempool Connection
- Query Connection
- Snapshot Connection
가 있다.

각 Connection들은 ABCI Type에 해당되는 일을 수행한다. 그리고 5번의 경우는 General하게 모든 Connection에서 처리되어야 하는 ABCI Method이다.
각각 Connection이 어떤 역할을 하는지 더 자세히 알아보도록 하자.
Connection's Role
Consensus Connection
**DeliverTxState**라는 state를 유지하고 있다.
주로 블럭(Transactions)을 실행하는 ABCI method를 실행하는 메소드들을 담당하고, 그런 메소드들에 의해서 state가 업데이트된다.
[BeginBlock $→$ bunch of DeliverTx $→$ EndBlock] 에 의해서 state 변화가 일어나고, Commit을 할 경우, 변화된 state가 disk로 저장된다.
Mempool Connection
Mempool connection에 대해서 알아보기 전에, Mempool이 무엇인지 모르는 사람을 위해 설명하고 넘어가고자 한다.
Mempool?
Peer 혹은 Client로부터 동의 혹은 처리해줬으면 하는 Tx들이 들어오게 된다.
그렇게 받은 Tx들은 악의적이거나 조작된 것일 수 있기 대문에 검증하는 작업이 필요한데,
그런 검증 작업을 거친 후, 안전하고, 아직 처리되지 않은 Pending 상태의 Tx들을 모아두는 장소가 바로 Mempool이다.
Tx를 받고, 저장하고, 검증하고, 다시 전달하는 역할을 모두 수행한다.
Mempool connection은 **CheckTxState**라는 state를 유지하며, 이 state는 Tx를 검사하는 CheckTx에 의해서 상태가 변하게 된다.
내 생각에 이 커넥션의 가장 중요한 역할은 Replay Protection이다. mempool에 포함할 때나, Commit을 할 때, 이 커넥션은 Mempool안의 Tx를 수 차례 반복해서 검사하게 된다. 여기서 발생하는 문제는 이미 검사했거나, 검사할 필요가 없는 Tx까지 중복해서 검사를 하게 되는 것이다. 이를 막기 위한 방법이 Replay Protection이고, Type parameter(e.g. Recheck_type, or New_type)를 argument로 쓰는 등의 방법을 사용한다.
Query Connection
Query Connection은 **QueryState**를 유지하며, 이 state는 DeliverTxState가 disk로 업데이트 될 때, 동일하게 업데이트 된다. 즉, 안정되게 저장된 상태를 항상 유지하며, 그에 대한 정보를 제공한다.
또한, 저장하는 정보에는 past/current height를 비롯해, merkel proof등을 같이 저장하기 때문에 이 정보를 통해서 특정 peer나 노드를 필터링할 수 있다.
Snapshot Connection
Snaptshot Connection은 State Sync만을 위한 커넥션이다. 이 기능은 필수적이지 않기 때문에 이 커넥션을 만드는 것 역시 선택적인 옵션이다.
State Sync란?
Genesis부터 시작하는 노드가 빠르게 현재 Height까지 따라잡기 위한 옵션이다.
Snapshot이라고 하는 가장 필수적인 데이터를 피어로부터 제공받아서, 적용하는 방식이다.
Snapshot을 적용하는 과정은 다음과 같다.
- Peer들에게 Snapshot을 요청한다
- 각 Peer들의 Height나 다른 부가적인 요소를 고려해 Snapshot에 우선순위를 부여한다.
- 가장 높은 우선순위를 가진 Snapshot을 적용한다.
- 적용을 완료한 이후, 다른 Peer에게 쿼리를 보내서 올바른 상태로 왔는지 확인한다.(Through Query Connection)
이 작업은 Deterministic, Asynchronous, Consistent해야만 한다.
Connections When Commit
평소 커넥션들은 concurrent하게 작동한다. 단순하게 생각해봤을 때, 이는 당연하다. 쿼리를 하기 위해서 블럭 실행을 멈추거나 늦출 필요가 없기 때문이다.
따라서, 커넥션들은 distinct하고, concurrent한 state를 유지한다. 하지만, Commit을 수행할 때는 예외가 된다.
Commit은 Tx들을 실행하고 저장하는 작업이다. 그리고 우리는 항상 Deterministic하고 Consistent한 결과물이 저장되기를 바란다. 따라서 모든 커넥션들은 synchronized되어야할 필요가 있다.
ABCI++
ABCI가 어떤 것이고, 어떻게 전달되는지에 대해서 알아봤다. 그렇다면, ABCI에 단점은 없을까?
저 설명만 듣고, 뭐가 잘못되었는지 알아채기는 쉽지 않은 것 같다.
하지만 텐더민트에서 설명해주길, 몇 가지 개선안이 있다고 한다.
크게
- App과 Core의 interaction을 늘려 Scalability 증가
- Vote Extension을 통한 State Sync의 Scalability 증가
- ABCI Method 개편
으로 나눌 수 있다.
1.
기존 ABCI 구조에서 Block을 실행할 수 있는 때는 Commit때밖에 없었다. 이는 App과 Core의 interaction이 단 한 번, 그것도 커밋 때이다. 그래서 ABCI++에서는
1.1 When Proposal is created
이 때는 Core가 App에 Primitive proposal을 보내서 batch optimization을 실행시킨다. 그리고 App이 modified proposal을 돌려주면, modified proposal을 전파한다.
1.2 When Proposal is received
batch optimized proposal을 받게 되면, 이 proposal 안의 Tx를 먼저 실행해볼 수 있게 바뀌었다. 이를 통해서 실행할 수 있는 때를 늘리고, 동시에 invalid Tx를 걸러내고, 실행된 상태를 Cache해서 나중에 finalized할 때, Cache된 정보를 기반으로 더 빠른 finalize를 진행할 수 있다.
2.
Vote Extend라는 메소드와 Vote Extension이라는 binary data가 추가되었다. 이 Vote Extension 데이터는 Non-nil value Precommit을 진행할 때, Vote Extend 메소드를 통해서 Precommit 메세지에 덧붙일 수 있는 데이터이다.(0-length data가 될 수 있다.)
이 Vote Extension은 Proposal에 non-deterministic을 준다고 하는데, 이게 어떤 장점을 가지는지는 나도 잘 이해하지 못했다. 다만, 텐더민트 깃헙의 PR에서 알 수 있었던 것은 이 Vote Extension이 State Sync 혹은 Node Recovery에 중요한 역할을 한다는 것이다.
3.
마지막으로 Proposal을 실행할 때, [BeginBlock $→$ DeliverTx $→$ EndBlock] 순으로 메소드가 진행되었는데, 굳이 이렇게 보낼 필요가 없었던 것이다.
중간에 어떠한 이유로 끊기더라도, 어떤 Tx가 실행되었는지 체크해야하고, 혹은 처음부터 다시 실행해야하는데, 굳이 저렇게 나눠놓을 필요가 없다.
따라서 FinalizeBlock이라는 하나의 메소드로 합쳐서 사용하게 되었다.
마치며.
ABCI, ABCI++에 대해 배우면서 이게 뭔가 싶었다. 배워도 배우는 느낌이 나지 않는 것 같다.
이게 Application Layer에 덜 신경쓰게 해주려고 만든 건데, 원래는 얼마나 귀찮았을지 상상도 안간다.
다음 포스팅으로는 아마 PoW, PoS 혹은 Tendermint BFT algorithm(Latest gossip on BFT)중에서 할 것 같다.
PDAO활동을 하면서 배우는 것들도 많은데, 정리가 되지 않아서 뭘 잘 알고 뭘 잘 모르는지 모르겠다.
'블록체인' 카테고리의 다른 글
| 텐더민트, 그리고 ABCI (0) | 2025.02.02 |
|---|