[IBC Research] 1. Cosmos IBC Process

Sujine
Decipher Media |디사이퍼 미디어
24 min readAug 29, 2022

본 게시글은 Cosmos의 IBC(Inter-Blockchain Communication protocol)에 대해 분석합니다. IBC 분석 글은 시리즈로 게시되었으며, 이번 글에서는 코스모스에서의 IBC를 통한 통신의 전체적인 과정을 다룹니다.

IBC (Source: github)

Author
황수진
Seoul Nat’l Univ. Blockchain Academy Decipher(@decipher-media)
Reviewed By 정재환

[IBC Research]

  1. Cosmos IBC Process
  2. IBC Handshake & 트랜잭션 검증
  3. ICA & IBC query

목차

  1. IBC
  2. Source Chain Process
  3. Source Chain — Destination Chain Process
  4. IBC Token Transfer

1. IBC

IBC 로 연결된 텐더민트 기반 체인들(source: mintscan)

IBC Inter-blockchain communication protocol 의 약자로, 서로 다른 애플리케이션과 밸리데이터(validator)를 갖는 블록체인 간 토큰과 데이터를 전송할 수 있도록 상호 운용성을 제공해주는 인터체인 프로토콜 입니다. 소스 체인(source chain) 에서 발생한 트랜잭션을 목적지 체인(destination)으로 전송할 때 가장 중요한 고려사항은 정보가 악의적인 체인에서 올 수 있으므로 연결을 설정하기 전에 해당 데이터가 유효한 것인지 확인해야 한다는 것입니다. 이를 위해 존재하는 것이 IBC 이며, 때문에 두 블록체인 간 양방향 신뢰 루트를 설정하는 것은 IBC 활동에 필요한 전제 조건입니다. 이번 글에서는 IBC 시리즈의 시작으로, IBC 가 상호운용성 제공을 위해 어떻게 데이터를 전송하고 있는지 전체적인 과정을 살펴보려고 합니다.

2. Source Chain Process

과정 설명에 앞서 코스모스 체인의 구조에 대해 알아보겠습니다.

코스모스 구조(source : cosmos network)

각 체인은 다음 세 가지 계층으로 구성됩니다.

  • 애플리케이션(Application): 주어진 트랜잭션 집합의 상태(state) 업데이트
  • 네트워킹(Networking): 트랜잭션 및 합의 관련 메시지의 전파
  • 합의(Consensus): 노드가 시스템의 현재 상태(state)에 합의 / 텐더민트 비잔틴 장애 허용(TBFT; Tendermint Byzantine Fault Tolerant) 합의 알고리즘 사용

이 중 네트워킹과 합의 계층을 텐더민트로 묶어 일반 엔진으로써 제공하고 있습니다.

텐더민트

텐더민트는 텐더민트 코어(Tendermint Core) 라는 합의 엔진과 ABCI(Application BlockChain Interface) 애플리케이션 인터페이스 두 가지 주요 기술 컴포넌트로 구성되어 있습니다. 텐더민트 코어는 트랜잭션이 모든 시스템에 동일한 순서로 기록되도록 하며, ABCI 는 모든 프로그래밍 언어로 트랜잭션을 처리할 수 있도록 도와줍니다

텐더민트 코어 합의

텐더민트 합의 과정 (source : tendermint docs)

텐더민트 합의에서 프로토콜 참여자를 밸리데이터(validator, 합의를 담당하는 투표권이 있는 특수 풀 노드)라고 합니다. 밸리데이터는 교대로 트랜잭션 블록을 제안하고 블록 유효성에 대해 투표합니다. 이때, 블록을 제안하는 밸리데이터를 제안자(proposer)라고 하며, 이 제안자는 알고리즘에 의해 무작위로 선출됩니다. 2/3 이상의 투표를 얻지 못할 경우, 블록이 커밋되지 않을 수 있으며, 이 경우 프로토콜은 다음 라운드로 이동하고 새로운 밸리데이터가 주어진 높이에 대한 블록을 제안합니다.

블록을 성공적으로 커밋하기 위해서는 두 단계의 투표가 필요합니다.

  1. Pre-vote Consensus round: 블록을 전파받은 노드들은 주어진 블록을 투표하고 이를 다시 다른 노드들에게 전파합니다.
  2. Pre-commit Consensus round: 다른 노드들의 2/3 이상이 블록에 대해 Pre-vote 했다는 것을 확인하면 노드는 이를 Pre-commit 하고 전파합니다.

2/3 이상의 노드들이 Pre-commit 하면 해당 블록은 커밋되어 블록 높이가 증가하고, 다음 라운드로 넘어갑니다.

이 과정에서 텐더민트는 트랜잭션을 바이트로만 처리할 뿐, 텐더민트는 이러한 바이트가 정확히 무엇을 의미하는지 알지 못합니다. 텐더민트가 하는 것은 이러한 트랜잭션 바이트를 결정적(deterministic)으로 정렬하는 것입니다. 여기서 결정적이라는 것은 노드가 동일한 상태(state)에서 시작하여 동일한 트랜잭션 시퀀스를 재생하는 경우, 상태 머신은 항상 동일한 최종 상태로 끝난다는 것을 말합니다. 이때, 상태 머신은 애플리케이션을 가리킵니다. 텐더민트는 트랜잭션 처리를 위해 합의를 거쳐 트랜잭션 바이트를 전파하고 정렬하며, 실질적인 트랜잭션 바이트 실행은 상태 머신 (애플리케이션)에서 수행됩니다. (더 자세한 내용은 cosmos sdk 문서를 참고하세요)

ABCI

(source : cosmos-sdk docs)

이 때문에 상태 머신(애플리케이션)과 합의 엔진은 통신할 필요가 있는데, 이를 위해 사용하는 것이 ABCI (“Application Blockchain Interface”)입니다. 텐더민트는 ABCI 를 통해 트랜잭션 바이트를 애플리케이션으로 전송하고, 트랜잭션에 포함된 메세지가 성공적으로 처리되었는지 여부를 알려주는 리턴 코드를 기대합니다. 다음은 ABCI 가 텐더민트 코어로부터 트랜잭션을 받아 애플리케이션으로 전달할 때 사용하는 3가지 주요 메세지입니다.

CheckTx

  • Tendermint Core에서 트랜잭션을 받은 경우, 기본적인 필요 조건을 만족시켰는지 확인하기 위해 애플리케이션으로 전달합니다.
  • AnteHandler 라고 불리는 핸들러는 수수료 및 서명 유효성 확인 같은 일련의 유효성 검사 단계를 실행하는 데 사용됩니다.
  • 만약 이러한 확인들이 유효하다면, 트랜잭션은 mempool 에 추가되고, peer 노드에 전달됩니다. (relayed)
  • 트랜잭션은 아직 블록에 포함되지 않았기 때문에 상태 변경이 발생하지 않습니다.

DeliverTx

  • Tendermint Core에서 유효한 블록을 받은 경우, 블록의 각 트랜잭션은 처리되기 위해 DeliverTx 을 통해 애플리케이션에 전달되고, 이 단계에서 상태 변경이 발생합니다.
  • AnteHandler는 트랜잭션의 각 메시지에 대해 실제 Msg service RPC를 따라 다시 실행됩니다.

Commit

  • 다음 블록의 헤더에 들어갈 현재 애플리케이션 상태(state)의 암호학적 commitment를 계산합니다.

CheckTx 메시지는 DeliverTx와 유사하지만, 트랜잭션 유효성 검사를 위한 메시지입니다. 텐더민트 코어의 mempool은 먼저 CheckTx를 사용하여 트랜잭션의 유효성을 검사하고 유효한 트랜잭션만 해당 피어(peer)에게 전달합니다. DeliverTx 는 애플리케이션이 처리하는 주된 메세지로, 애플리케이션은 검증 후 트랜잭션 수행 결과를 키-값 저장소(key-value store) 에 값을 바인딩하거나 데이터베이스를 업데이트하여 애플리케이션 상태(state)를 변경합니다. commit 메시지는 현재 애플리케이션 상태에 대한 암호학적 commitment 를 계산하여 다음 블록 헤더에 배치하는 데 사용됩니다.

이러한 ABCI 메세지는 체인에서 발생한 트랜잭션을 처리하는 데 이용됩니다. 그렇다면 IBC 프로토콜을 사용한 서로 다른 체인간 통신에서는 어떨까요? 소스 체인 A 에서 목적지 체인 B 로 토큰을 전송하고자 할 때, 먼저 소스 체인 A 에서 유저의 트랜잭션을 받아 처리한 후, 관련 내용을 목적지 체인 B 에 전송할 것입니다. 이 상황에서 목적지 체인으로 전달하기 직전까지만 본다면, 소스 체인 A의 트랜잭션 처리는 일반 트랜잭션 처리 과정과 같습니다. 다음 문단에서 트랜잭션을 받은 소스 체인이 ABCI 메세지를 통해 트랜잭션을 처리하는 과정을 살펴보겠습니다.

트랜잭션 흐름

다음은 ABCI 를 통한 트랜잭션의 흐름을 나타난 그림입니다.

ABCI를 통한 Message 흐름(tendermint docs 참고)

Addition to Mempool

  1. 트랜잭션이 생성되면, 풀 노드(Full node)는 트랜잭션을 수신합니다. 이때, 트랜잭션을 받은 풀 노드는 텐더민트를 실행 중입니다.
  2. 트랜잭션을 수신한 각 풀 노드는 애플리케이션 계층에 ABCI 메시지 CheckTx를 전송하여 유효성을 확인합니다. checkTx는 트랜잭션 검사를 위해, 풀 노드는 stateless 와 stateful check 를 수행하며 추가적인 검증은 DeliverTx 단계에서 일어납니다.
  3. 풀 노드는 checkTx 에 대한 응답으로 abci.ResponseCheckTx를 수신합니다.
  4. checkTx의 검사에 통과하면, 트랜잭션은 각 노드에 고유한 트랜잭션의 in-memory pool 인 mempool에 유지되며, 블록에 포함할 때까지 대기합니다(pending). 정상 노드인 경우, 잘못된 트랜잭션은 무시합니다.

Inclusion in a Block

5. 합의(consensus)는 밸리데이터 노드가 수락할 트랜잭션을 합의하는 과정으로, 라운드 별로 이루어집니다. 각 라운드는 제안자(proposer)가 가장 최신 트랜잭션 블록을 생성하는 것으로 시작하고, 생성된 블록에 대한 밸리데이터의 동의/거절 합의로 끝이 납니다. (거절로 끝난 경우에는 해당 블록은 nil 이 됩니다. )

합의의 첫 단계는 블록 제안(block proposal) 입니다. 합의를 담당하는 밸리데이터 중 한 명의 제안자(proposer)가 블록을 생성하고 제안하도록 선택됩니다. 이때 트랜잭션이 블록에 포함되려면 해당 proposer의 mempool 에 있어야 합니다. 이후 밸리데이터는 합의에 도달하기 위해 애플리케이션에 대한 ABCI 요청을 사용하여 Tendermint BFT 와 같은 합의 알고리즘을 실행합니다.

6. 합의 과정 후 새 블록이 생성됩니다.

State Changes

7. 올바른 제안자로부터 블록 제안을 받은 모든 풀 노드는 ABCI 함수 BeginBlock과 각 트랜잭션에 대한 DeliverTx 및 EndBlock을 호출하여 트랜잭션을 실행합니다. DeliverTx ABCI 함수는 대부분의 상태 전환을 수행하고, 트랜잭션이 잘못되었거나 GasMeter(트랜잭션을 실행하는 동안 얼마나 많은 가스가 사용되었는지 추적)가 run out 되어 실패한 상태(state) 변경이 있는 경우, 트랜잭션 처리가 종료되고 모든 상태 변경이 revert 됩니다.

블록 제안에서 잘못된 트랜잭션이 있는 경우에는 밸리데이터 노드가 블록을 거부하고, 대신 nil 블록에 투표하도록 합니다. 각 풀 노드는 모든 내용을 로컬에서 실행하지만 메시지의 상태 전환이 결정론적(deterministic)이고 트랜잭션이 블록 제안에서 명시적으로 정렬되기 때문에 이 프로세스는 단일하고 명확한 결과를 산출합니다.

8. 마지막 단계는 노드가 블록 및 상태 변경을 commit하는 것입니다. 밸리데이터 노드는 트랜잭션 검증을 위해 상태 전환을 실행하는 이전 단계를 수행한 후, 블록에 서명하여 확인합니다. 이때, 밸리데이터가 아닌 풀 노드는 합의에 참여하지 않아 투표에 참여하지 않지만, 상태 변화의 여부를 확인하기 위해 투표를 listen 합니다.

2/3 이상의 충분한 밸리데이터 투표를 받으면, 풀 노드는 블록체인에 추가될 새로운 블록을 커밋하고 애플리케이션 계층에서 상태 전환을 완료합니다. 이 과정에서 상태 전환에 대한 머클 증명으로 제공할 새 state 루트가 생성되고, 애플리케이션은 Commit ABCI 메소드를 사용하여 deliverState(DeliverTx 진행 상태) 를 애플리케이션의 내부 상태에 기록함으로써 모든 상태 전환을 동기화합니다. 상태 변경이 커밋되는 즉시, checkState(CheckTx 진행 상태)를 가장 최근에 커밋된 상태에서 새로 시작하고, deliverState를 일관되도록 nil 로 재설정하여 변경 사항을 반영합니다.

이 시점에서 트랜잭션의 라이프 사이클은 종료됩니다. 노드는 트랜잭션의 유효성 검증 후, 상태 변경을 실행함으로써 트랜잭션을 전달하며, 변경 사항을 커밋합니다. 최종적으로 []bytes 형태의 트랜잭션은 블록에 저장되고 블록체인에 추가됩니다.

지금까지 소스 체인에서 발생한 트랜잭션 처리 과정을 살펴보았습니다. 그렇다면, 이렇게 처리된 트랜잭션을 어떻게 목적지 체인으로 어떻게 전송할까요?

(cosmos sdk docs 참고)

소스 체인에서의 트랜잭션 처리 후 “릴레이어(relayer)”에 전달하는 것을 볼 수 있습니다. IBC 통신에서 각 체인은 직접적으로 통신하지 않고 relayer 를 통해 통신하는데, 이 부분에 대해서는 다음 섹션에서 더 자세히 알아보도록 하겠습니다.

3. Source Chain — Destination Chain Process

체인간 통신을 할 때 패킷은 정보를 보내고 받았다는 증거로 Merkle-proofs를 게시하여 한 체인에서 다른 체인으로 전달되는데, 이 메커니즘을 블록체인 간 통신(inter-blockchain communication) 또는 줄여서 IBC라고 합니다.

IBC 프로토콜에서 블록체인은 특정 데이터를 다른 체인으로 전송하려는 의도만 기록할 수 있기 때문에, 물리적 패킷 전달은 TCP/IP와 같은 전송 계층에 액세스할 수 있는 오프체인에서 수행되어야 합니다. 따라서 패킷 전달을 위해 체인 상태를 쿼리하는 릴레이어(relayer)가 필요합니다.

릴레이어와 모듈 구조 (source: ibc github)

그림을 보면 소스 체인과 목적지 체인 이외에 릴레이어라이트 클라이언트(light client)가 존재합니다. IBC 에서 모듈은 메시지를 직접 보내지 않고 물리적으로 중계(relayed)하는데, 이 역할을 하는 것이 “릴레이어(relayer)” 입니다. 릴레이어는 오프체인으로 실행되고 2개의 체인 각각에 연결된 라이트 클라이언트를 통해 각 체인의 상태를 지속적으로 스캔하며, 데이터가 커밋될 때 다른 체인에서 트랜잭션을 실행시키기도 합니다. 따라서 두 체인간의 연결에서 올바른 작동 및 처리를 위해 IBC는 체인간에 중계할 수 있는 하나 이상의 정확한 릴레이어 프로세스가 존재해야 합니다.

라이트 클라이언트는 상대 체인의 상태 변경이 제대로 수행되었는지를 알기 위해 상대 체인에 쿼리하고, 각 클라이언트는 릴레이어로부터 상대 체인의 헤더를 받아 블록의 유효성을 검증합니다.

릴레이어

릴레이어의 통신 (source : ibc-rust)

릴레이어는 패킷을 전달하고, 라이트 클라이언트에게 상대 체인의 최신 블록 헤더를 전달하는 역할을합니다. IBC 통신 시 릴레이어는 체인 A 와 체인 B에 각각 상대 체인의 정보를 가진 클라이언트를 생성하고, ABCI 쿼리를 통해 정보를 얻어 각 체인의 클라이언트 정보를 업데이트합니다. 이후 각 모듈 간 연결(connection) 및 채널을 오픈합니다. 릴레이어는 소스 체인에 기록된 트랜잭션 정보를 읽어와 패킷으로 만들고, 이를 상대 체인에 전송합니다.

IBC 모듈

IBC 모듈의 구조를 바탕으로 IBC 통신 과정을 아래와 같이 나타낼 수 있습니다.

(ibc-go 참고 )

IBC 모듈의 코어에는 연결(connection), 채널(channel), 포트(port)를 담당하는 부분이 있습니다. IBC 를 통해 패킷을 전송하기 전, 포트를 부여하고 같은 포트 내에 채널을 형성하여 서로 다른 체인의 모듈 사이에 연결(connection) 을 형성합니다. 서로 다른 체인간 연결이 형성되면, 내부에 상대 체인의 상태를 추적하는 각 체인의 라이트 클라이언트가 만들어지고, 릴레이어는 라이트 클라이언트의 상태(state)를 주기적으로 업데이트해 줍니다. 라이트 클라이언트가 하는 일은 패킷을 전송할 때, 상대 체인에 제대로 수신되었는지, 정확한 송신 체인에서 전송된 패킷을 수신한 것인지를 검증하기 위해 블록체인의 합의 상태와 proof 구현(specification) 을 트래킹하는 것입니다. 이렇게 라이트 클라이언트, 채널과 연결까지 형성하였다면, 소스 체인과 목적지 체인은 만들어진 채널을 통해 패킷을 주고받습니다.

연결 및 채널 형성을 위해 소스 체인 IBC 모듈 코어는 sendPacket 함수를 통해 앱으로부터 패킷 정보를 받아오고, 수신 체인 IBC 모듈 코어는 OnRecvPacket 함수를 통해 받아온 패킷 정보를 앱으로 보내줍니다. 여기서 앱은 IBC 모듈 내의 앱으로 IBC 로부터 받은 트랜잭션을 수행하는 부분입니다.

아래의 나오는 내용들은 ibc-go 레포지토리의 코드를 분석한 결과이며, 날짜는 22.07.24 기준입니다.

패킷의 송수신 : SendPacket, RecvPacket, OnRecvePacket

소스 체인 IBC 모듈과 목적지 체인 IBC 모듈이 릴레이어를 연결되어 패킷을 주고 받는 것까지 보았습니다. 그렇다면 릴레이어는 소스 체인에서 발생한 트랜잭션을 어떻게 알고 어떻게 전달하는 것일까요? IBC 프로토콜에서 블록체인은 특정 데이터를 다른 체인으로 전송하려는 의도만 기록할 수 있기 때문에 앞서 릴레이어가 존재한다고 설명했습니다. 소스 체인은 이 의도를 “이벤트(event)”로 기록합니다. 아래는 소스 체인의 SendPacket 함수에서 이벤트를 출력해주는 부분입니다.

ibc-go/modules/core/04-channel/keeper/packet.go

릴레이어는 소스 체인이 기록한 이벤트를 읽어 패킷을 생성합니다. 릴레이어는 이벤트에 기록된 정보로 패킷을 생성하여 목적지 체인의 RecvPacket 함수를 호출합니다. 이때 풀 노드로부터 Commitment Proof(Merkle proof)를 받아 패킷과 함께 인자로 넣어 호출합니다.

ibc-go/modules/core/04-channel/keeper/packet.go

목적지 체인에서 패킷과 함께 Proof를 받게 되면, 패킷을 받은 체인은 소스 체인에서 실제로 요청 트랜잭션이 있었고, 상태(state)가 변경되었는지 검증합니다. 코스모스에서는 상태(state)를 IAVL 트리로 저장을 하며, proof를 통해 체인의 상태에 포함 포함되어 있다는 것을 증명할 수 있습니다. 때문에 목적지 체인에서는 소스 체인의 IAVL트리의 루트를 가지고 있어야 하며, 이는 라이트 클라이언트가 트래킹하는 CommitmentRoot(머클 루트) 사용합니다. 검증이 올바르다면, 패킷의 데이터를 토대로 다시 트랜잭션으로 변경하여 수행하는데, 이때 IBC 트랜잭션의 종류에 따라서 트랜잭션을 처리하는 방법이 달라집니다.

모듈 내에서의 트랜잭션 처리 (source : atom_crypto)

현재 주요 IBC 트랜잭션 종류에는 1) ICA(Interchain Account) 2) 토큰 전송(transfer)이 있습니다. ICA로 들어오는 트랜잭션은 Cosmos SDK에서 사용되는 다른 모듈(예. Bank 모듈)이 실행할 트랜잭션으로, IBC 모듈은 트랜잭션을 중계만 합니다. 토큰 전송 트랜잭션 처리는 Keeper가 트랜잭션에 맞는 적절한 모듈을 호출하여 IBC 모듈 내에서 트랜잭션을 수행합니다.

IBC 동작

ibc 패킷 흐름 (source: ibc protocol)

체인 A에서 체인 B로 토큰을 보내는 상황을 가정할 때, 목표 액션은 패킷을 체인 A의 모듈에서 체인 B의 모듈로 전달하는 것입니다. 체인 간 소통하고자 할 때, 먼저 체인 A 와 B는 각각 상대 체인의 정보를 받을 라이트 클라이언트를 생성하고, 소통하기 위한 채널 및 연결(connection)을 설정합니다. 이때, 안전한 패킷 전달을 위해 채널과 연결은 핸드쉐이크를 통해 이루어지고, 각 클라이언트는 릴레이어로부터 상대 체인의 헤더를 받아 블록의 유효성을 검증합니다. 이때 라이트 라이언트는 클라이언트 스테이트와 컨센서스 스테이트를 갖습니다.

  • 클라이언트 스테이트(Client State) : 상대 체인에 대한 정보로 체인 아이디, 신뢰 구간, 최신 블록 높이 등을 포함
  • 컨센서스 스테이트(Consensus State) : 상대 체인에서 받은 proof를 검증할 때 필요한 정보로 블록 타임 스탬프, 밸리데이터 집합 해시, commitmentRoot 등을 포함

채널이 설정되면 두 체인은 다음과 같이 패킷(=임의 바이트 시퀀스)을 송수신할 수 있습니다. 이때 패킷에 시퀀스 번호를 부여하여 ordered 채널에서 번호가 더 작은 패킷이 더 큰 패킷보다 먼저 송수신될 수 있도록 올바른 전송을 보장합니다. (* IBC 패킷 송수신 채널에는 ordered 와 unordered 채널이 존재하는데, ordered 채널은 패킷이 전송한 순서대로 처리되어야 하고, unordered 채널은 전송된 순서와 다른 임의의 순서로 패킷을 처리할 수 있는 채널입니다. 더 자세한 내용은 다음을 참고하세요.)

- sendPacket : 시퀀스 번호가 N + 1(= sequence + 1) 인 새로운 패킷 정보를 생성하여 소스 체인에 저장하고, 시퀀스 번호가 N 인 패킷을 전송합니다.

ibc-go/modules/core/04-channel/keeper/packet.go

송신 포트와 채널에 다음 시퀀스 번호(N+1)를 설정하고, N 번째 패킷을 전송합니다. 이는 다음 패킷을 전송하고자 할 때, 보내려는 패킷 시퀀스가 설정된 다음 시퀀스 번호(N+1)와 동일한지 확인하기 위함입니다.

- recvPacket : 소스 체인이 패킷을 정확히 전송(생성)한 것으로 확인되면, 시퀀스 번호가 N + 1 인 새로운 패킷을 생성하여 저장 후 시퀀스 번호 N 패킷을 수신합니다.

ibc-go/modules/core/04-channel/keeper/packet.go

패킷 수신 후, 수신 포트와 채널이 다음에 수신할 패킷의 시퀀스 번호를 저장합니다. sendPacket 과 동일하게 패킷 수신 시 다음에 수신할 패킷의 시퀀스 번호와 일치한 지 확인하고 아니라면 에러를, 일치한다면 수신 이벤트를 출력합니다.

- acknowledgePacket :시퀀스 번호가 N인 패킷은 처리가 완료되었으므로 관련 데이터가 삭제됩니다.

ibc-go/modules/core/04-channel/keeper/packet.go

AcknowledgePacket 는 송신 채널과 연결에 다음 응답 패킷의 시퀀스 번호를 저장하고, 처리된 패킷의 commitment 를 삭제합니다. 여기서 recvPacket 과 acknowledgePacket은 릴레이어에 의해 실행되지만 sendPacket 은 애플리케이션에 의해 실행되며, SendPacket 과 RecvPacket 은 패킷의 시퀀스 번호 뿐만 아니라 채널 및 연결의 오픈 여부나 포트 소유 여부 등도 확인합니다.

정리하자면 IBC 모듈의 동작은 다음과 같습니다.

  1. 소스 체인 A 에서 토큰 전송을 원하는 트랜잭션이 발생하면, 합의 후 커밋(commit)된 블록, 소스(source)와 목적지(destination) 채널, 타임 아웃 등에 대한 내용을 포함한 패킷을 생성합니다.
  2. IBC 모듈은 소유한 채널로 데이터를 전송하는 SendPacket 함수를 통해, 앱으로부터 체인 A의 IBC 모듈 코어로 패킷을 전송합니다. 이때, 소스 체인 A 와 목적지 체인B의 IBC 모듈 코어는 통신을 위한 포트와 채널이 부여된 상태입니다.
  3. 릴레이어는 IBC 패킷 전송 이벤트를 중계하는 채널에서 수신 대기를 하고 있는데, 릴레이어는 SendPacket 을 통해 채널로 넘어온 패킷의 이벤트를 읽고, 목적지 체인 B의 RecvPacket 함수를 호출하여 패킷을 목적지 체인으로 전달합니다.
  4. RecvPacket 을 통해 전달받는 패킷 데이터는 목적지 체인 B의 IBC 모듈 코어로 전달됩니다.
  5. IBC 모듈로 전달된 패킷은 onRecvPacket 을 통해 IBC 모듈로부터 애플리케이션으로 가져와 실행되고 응답을 반환합니다.
  6. 목적지 체인은 패킷이 정상적으로 수신 되었음을 알리는 응답 패킷을 전송합니다.

4. IBC Token Transfer

지금까지 살펴보았던 내용을 바탕으로 체인 A 에서 체인 B 로 토큰을 전송하고 싶은 상황을 가정해봅시다. 체인 A에서 트랜잭션이 발생하고 합의를 거쳐 Commit 되면 ABCI 함수를 실행합니다. 트랜잭션이 ABCI 함수를 통해 텐더민트로부터 애플리케이션으로 전달되면, 내부에 정의된 라우터를 사용하여 적절한 모듈로 메시지가 라우트 되고 메시지 처리 후 상태 변경을 수행합니다.

  1. Connection & Channel

체인 A와 B 는 각각 상대 체인 정보를 받아올 클라이언트를 생성합니다. 이후 각 모듈은 포트에 바인드되어 서로 패킷을 주고 받을 수 있게 됩니다. 이 처리는 IBC 모듈 코어에서 일어나며, 이때 형성된 채널을 통해 패킷을 주고 받습니다. 따라서 채널 및 연결 형성이 안전하게 이루어져야 합니다.

2. Tracking

서로 통신을 시도하려는 각 블록체인 A,B는 밸리데이터 집합을 추적하기 위해 헤더 파일을 주고받습니다. 간단히 말하자면, 이들은 서로의 체인을 추적하기 위해 서로의 라이트 클라이언트를 실행합니다. 이때 라이트 클라이언트들은 위에서 설명한대로 릴레어어로부터 정보를 받아옵니다.

체인간 통신시 토큰 동작 (source : interchinacademy)

3. Bonding

IBC 전송이 시작되면, 전송하고자 하는 토큰은 체인 A에 잠깁니다. 이후 소스 체인은 IBC 전송을 하고 싶다는 것을 알리기 위해 SendPacket 함수를 통해 관련 정보 이벤트를 출력합니다.

4. Proof Relay

릴레이어는 소스 체인에 출력된 이벤트를 읽고, 패킷을 생성합니다. 또한 해당 패킷이 소스 체인에서 형성되었고, 토큰이 잠겼다는 proof 를 받아 체인 B 로 전달합니다.

5. Validation

체인 B 는 체인 A의 헤더에 대한 proof 를 검증하며, 검증이 유효하다면 체인 B에서 해당 토큰에 상응하는 바우처가 생성됩니다. 이때, 체인 B에서 생성된 토큰은 실제 체인 A의 토큰이 아니라 체인 B에서의 체인 A 토큰에 대한 표현입니다.

아래는 체인 A 에서 보내고자 했던 토큰에 상응하는 바우처를 생성하는 부분입니다.

ibc-go/modules/apps/transfer/keeper/relay.go

지금까지 텐더민트 기반 서로 다른 체인이 IBC 를 이용해 어떻게 상호 운용될 수 있는지를 살펴보았습니다. 그러나 이더리움이나 비트코인의 경우에는 텐더민트 기반 합의 알고리즘을 사용하지 않습니다. 따라서 다음을 구별할 필요가 있습니다.

Fast-Finality Chain

fast-finality 합의 알고리즘(ex. 텐더민트 합의 알고리즘)을 사용하는 블록체인은 IBC를 적용하여 코스모스와 연결할 수 있습니다. 대표적으로 현재 텐더민트 합의 알고리즘을 사용하고 있는 Juno, Osmosis 체인을 Fast-Finality 체인이라고 할 수 있습니다. 만약, 이더리움이 캐스퍼 FFG(Friendly Finality Gadget)로 전환될 경우 IBC를 캐스퍼와 연동시켜 코스모스 생태계와 직접적으로 연결 될 수 있습니다.

Probabilistic-Finality Chain

Proof-of-Work 체인과 같이 빠른 완성도를 가지지 않는 블록체인의 경우, 좀 더 까다로운 처리가 필요해집니다. 때문에 코스모스는 페그존(peg-zone) 이라고 불리는 특별한 종류의 프록시 체인 peggy 를 개발하고 있으며, 페그 존을 통해 텐더민트 기반 이외의 체인간 상호운용성 제공을 시도하고 있습니다.

코스모스의 허브-존 구조 (source : cosmos network)

만약 여러 블록체인들이 서로 통신을 시도하는 경우에는 너무 많은 연결이 생성되어 중단되는 등의 문제가 발생할 수도 있습니다. 바로 이러한 상황이 코스모스 허브(Hub)가 작동하는 상황입니다. 허브는 어그리게이터(aggregator)의 역할을 수행하며, 단일 블록체인(Zone)이 다른 많은 블록체인들과 통신 할수 있도록 해줍니다. 이러한 허브는 또한 블록체인 네트워크 혹은 “블록체인 인터넷”을 가능하게 해줍니다

이번 글에서는 체인, 릴레이어, 라이트 클라이언트가 어떻게 IBC 를 이용하고, IBC 통신이 어떻게 구현되어 있는지 전체적인 과정을 살펴보았습니다. 다음 글에서는 다른 체인으로부터 전달받은 트랜잭션을 어떻게 검증하고 어떻게 안전한 연결을 형성하는지에 대해 알아보겠습니다.

Reference:

--

--