2023. 7. 7. 12:48ㆍBlockchain/Cosmos
Inter-Blockchain Communication은 줄여서 IBC로 불린다. 이 IBC는 Cosmos-SDK 기반 체인 간의 자산 이동 및 데이터 전송을 처리하는 프로토콜이다. Cosmos 진영이 추구하는 블록체인 인터넷은 결국 체인 간 연결이며, 체인의 연결은 이 IBC를 통해 이루어진다. 그만큼 IBC는 Cosmos 블록체인에서의 핵심이라고 볼 수 있다. IBC를 제대로 알아야 Cosmos의 "Interchain"을 제대로 이해할 수 있기에 이번에는 IBC를 구현하기 위한 시스템 아키텍쳐와 IBC relayer를 통한 실제 IBC token 전송을 확인하는 시간을 가지려고 한다.
IBC
IBC는 Cosmos-SDK 기반 체인의 연결을 위하여 ICS(Inter Chain Standard)를 만족하는 프로토콜이며, IBC module을 통해 체인 간 표준 통신 채널을 제공한다. IBC는 주로 Tendermint 기반의 Cosmos 체인들을 연결해주고, 각 체인들의 IBC 연결은 chain registry에서도 정보를 확인할 수 있다. 현재는 Cosmos 체인 간 연결을 주로 다루지만 Cosmos 진영은 Gravity Bridge 등으로 EVM 기반 체인 연결을 도모하며, 아직 어디까지 진행됐는지는 잘 모르겠지만 InterBTC라는 프로젝트 명으로 bitcoin 까지 연결할 계획을 가지고 있다.
IBC는 ICS의 정의에 따라 IBC/TAO(Transport, Authentication, Ordering) IBC/APP으로 표준 정의가 되며 각각은 아래와 같은 기능을 가진다.
- IBC/TAO: packet transfer, authentication, ordering 등 인프라 layer이며, Core나 client, IBC relayer 등 물리적 전송을 위한 표준이다.
- IBC/APP: TAO를 통해 전달되는 data packet에 대한 app handler를 정의하며, ICS-20(token transfer), ICS-721(NFT transfer) 등 application layer에서 동작하는 모든 표준들을 지칭한다.
IBC/TAO에는 Light Client(ICS-2/6/7/8/9), Connection(ICS-3), Channel(ICS-4), Port(ICS-5) 등이 포함되며 IBC/APP에서는 FT transfer(ICS-20), NFT transfer(ICS-721), Interchain account(ICS-27) 등이 포함된다.
이 포스팅에서는 IBC/TAO와 관련된 아키텍처와 기능들을 주로 소개할 예정이다.
IBC/TAO architecture
IBC를 이용하기 위해서는 위에서 언급된 Client, Connection, Channel에 대한 개념과 동작 방식을 잘 이해해야 할것이다.
Client
IBC Light Client는 체인에 unique하게 확인되는 client ID로 구분된다. 이 IBC client는 연결되는 체인의 consensus 상태와 client의 consensus 상태에 대한 proof를 검증하기 위해 주기적으로 양 체인의 블록 정보를 확인한다. Client는 블록 정보 및 모든 tx의 정보를 수집, 저장해야하는 일반 node와는 달리, 연결할 다른 체인의 timestamp, root hash, 다음 validator set hash의 정보만을 블록이 생성되었을 시 지속적으로 확인한다.
Relayer를 통해 주고 받는 packet은 client 내부에 packet commit이 존재함을 증명하고, 이러한 정보를 확인하여 상대 체인이 정상적이라는 것을 판명하는 증명을 client에서 수행한다. 그렇기 때문에 3rd party인 relayer의 신뢰성에 의지하지 않고, 체인 노드에 access 할 수 있는 누구나 relayer를 구축해서 블록체인을 연결할 수 있다. 이렇게 3rd party relayer의 신뢰성에 의지하지 않고 토큰 전송을 하는 브리지를 trust-less bridge라 명하며, 이 Cosmos의 IBC가 그에 속한다.
즉 IBC의 보안은 client가 수행하는 proof 검증을 통해 보장되며, client는 체인의 내부에 존재하기 때문에 보안은 체인의 보안성에 의존될 수 있으므로 안전한 IBC transfer가 가능해 진다.
Connection
다른 두개의 체인을 연결하기 위해서는 IBC connection을 설정해야 한다. 4-handshake 방식으로 연결이 되며, 이 connection은 상대 체인의 identity를 확립하며 해커가 상대 체인인 것처럼 가장하여(spoofing) 잘못된 정보를 전달하는 것을 방지하는 역할을 한다. Connection은 client위에 구축되며 각 client는 여러 connection을 가질 수 있다.
Connection을 위한 4-handshake 단계는 위 그림과 같다. 단계별로 OpenInit
, OpenTry
, OpenAck
, OpenConfirm
이며 그림상의 파란색 A체인과 보라색 B체인이 각각 어떤 packet을 주고 받았는지에 따라 connection state가 변경된다.
1. ConnOpenInit
여기서 connection 시작 시에 A체인이 먼저 src chain, B체인을 dst chain이라 가정한다. Relayer는 MsgUpdateClient
로 handshake 시작 이전에 A체인이 src chain인 것을 submit하고 chain A의 light client에 chain B의 마지막 consensus state로 초기화 함으로써 업데이트 한다.
A체인에서 ConnOpenInit
를 통해 handshake를 시작하면 connection의 상태가 INIT
으로 변경된다. A체인에서는 client 내의 connection list에 새로 생성할 고유의 connection ID를 추가한다.
2. ConnOpenTry
B체인의 light client에 A체인에 대한 정보(Latest block의 root hash를 포함한 consensus state의 알고리즘, 마지막 snapshot 및 다음 validator set정보)에 따라 B가 A의 identity를 확인한다. 이를 통해 체인 B는 A가 연결하기로 예상되는 체인임을 확인하며, A가 B체인 자신의 정보를 정확하게 가지고 있는지 판별한다.
Relayer는 handshake 전에 A, B 둘다에게 MsgUpdateClient
를 전송하여서 OpenTry 단계에서의 state 확인이 성공했는지 판별하기 위해 A, B의 light client를 업데이트한다. B체인에서 handshake를 시작하면 connection의 상태가 TRYOPEN
으로 업데이트 된다.
3. ConnOpenAck
OpenInit
과 유사하지만 source chain이 B, destination chain이 A인 것만 변경된다. 앞선 OpenTry
와 같이 relayer가 MsgUpdateClient
를 양쪽 체인에게 제출하고 light client를 업데이트 한다. Chain A에서 handshake를 시작하면 connection state가 OPEN
으로 업데이트된다.
4. ConnOpenConfirm
마지막으로 B체인이 자신과 A의 identity를 식별한것을 성공하고, handshake를 시작하면 connection state가 TRYOPEN
에서 OPEN
으로 변경되고 connection state 업데이트가 종료된다.
Channel
IBC에서의 체인 간 ICS 20 토큰 전송을 처리하는 라우팅은 channel에서 수행된다. Connection에는 channel이 다수 있을 수 있지만 한 set의 체인 간 IBC transfer(e.g. 체인 A와 체인 B)의 통로는 connection ID와 연결된 하나의 channel/port ID에서만 사용된다. Connection 설정과 비슷하게 channel 설정도 아래 그림과 같이 4-way handshake를 통해 설정된다.
ChanOpenInit
: Relayer는 가장 먼저ChanOpenInit
메시지를 통해 A체인을INIT
상태로 설정한다. 이 단계에서 port가 올바르게 설정되어 있는지, channel의 order/unorder가 원하는대로 설정이 되어 있는지 등에 대한 custom callback을 호출한다.ChanOpenTry
: Chain B의 channel state를TRY
변경하며ChanOpenInit
처럼 custom callback을 호출한다.ChanOpenAck
: Chain A의 state를OPEN
으로 설정한다.ChanOpenConfirm
: 마지막으로 Chain B의 state를OPEN
으로 설정한다.
App chain의 module은 IBC channel을 통해 packet을 전송하여 통신하지만, IBC module이 실제 네트워크로 메시지를 보내 직접 전달하는 구조는 아니다. ICS-20 토큰 전송을 예로 들어보자.
Bank module은 IBC transfer가 일어났을 시 transfer하기로 설정된 amount만큼 escrow address에 전달하고 escrow proof를 저장한다. Relayer는 channel을 지속적으로 모니터링 하고 있다가 state update를 위한 event가 발생했을 시 packet data가 포함된 메시지를 state transition 증거와 함께 상대 체인으로 relay 한다. 상대 체인은 relayer를 통해 state로 커밋된 packet data에 대한 검증을 하고, 검증 완료 시 체인의 state를 update한다.
IBC relayer
앞선 단락에서 IBC를 동작시키기 위한 필수 구성 요소들에 대해 확인했다. 실제로 relayer를 통해 IBC client, connection, channel을 구성하고 IBC token까지 전달하는 과정을 확인해 볼것이다. Relayer는 Golang 기반 cosmos relayer를 사용할 것이다.
Setup relayer
Relayer를 먼저 설치해본다. 메인넷 간의 relayer 연결은 chain registry에 등록된 정보로 자동 설정을 할 수 있지만, 이번 테스트는 로컬환경을 구성하여 process를 하나하나 확인해본다. 연결되는 체인은 각각 gaia와 evmos를 사용할 것이다.
- git clone을 통해 먼저 relayer를 다운로드한다.
$ git clone https://github.com/cosmos/relayer.git
$ cd relayer
$ make install
- 그 후 relayer를 initializing 한다.
$ rly config init --memo "local custom relayer test"
위 명령어를 실행하면 home directory에 .relayer
폴더가 생성되며 .relayer/config/config.yaml
파일이 생성된다. yaml 파일은 아무 설정도 하지 않았기 때문에 아래와 같이 확인할 수 있다.
global:
api-listen-addr: :5183
timeout: 10s
memo: local custom relayer test
light-cache-size: 20
chains: {}
paths: {}
- Relayer에 chain을 regist해야 한다. 형식이 지정되어 있는 json 파일을 이용하며, 테스트를 위한 evmos와 gaia는 각각 아래와 같이 설정되어 있다. 아래에서 RPC address는 localhost에서 docker로 띄워놔서 TCP port가 저렇지만 보통의 RPC는 26657로 사용한다.
evmos (./evmos.json
)
{
"type": "cosmos",
"value": {
"key": "default",
"chain-id": "evmos_9000-1",
"rpc-addr": "http://localhost:46655",
"account-prefix": "evmos",
"keyring-backend": "file",
"gas-adjustment": 1.2,
"gas-prices": "10aevmos",
"debug": true,
"timeout": "20s",
"output-format": "json",
"sign-mode": "direct"
}
}
gaia (./gaia.json
)
{
"type": "cosmos",
"value": {
"key": "default",
"chain-id": "theta-testnet-001",
"rpc-addr": "http://localhost:25655",
"account-prefix": "cosmos",
"keyring-backend": "file",
"gas-adjustment": 1.2,
"gas-prices": "1uatom",
"debug": true,
"timeout": "20s",
"output-format": "json",
"sign-mode": "direct"
}
}
위와 같이 설정한 뒤 명령어를 실행한다.
$ rly chains add -f ./evmos.json
$ rly chains add -f ./gaia.json
CLI 명령어를 실행하면 config.yaml
파일에 각각의 정보가 기입되는 것을 확인할 수 있다.
cf) 아래 파일의 hsm-key-name과 signer-url은 원래 IBC relayer에 없는 파라미터이며, 커스터마이징 시 추가된 파라미터임을 참고바란다.
global:
api-listen-addr: :5183
timeout: 10s
memo: local custom relayer test
light-cache-size: 20
chains:
evmos:
type: cosmos
value:
key: default
chain-id: evmos_9000-1
rpc-addr: http://localhost:46655
account-prefix: evmos
keyring-backend: file
gas-adjustment: 1.2
gas-prices: 10aevmos
debug: true
timeout: 20s
output-format: json
sign-mode: direct
hsm-key-name: ""
signer-url: ""
gaia:
type: cosmos
value:
key: default
chain-id: theta-testnet-001
rpc-addr: http://localhost:25655
account-prefix: cosmos
keyring-backend: file
gas-adjustment: 1.2
gas-prices: 1uatom
debug: true
timeout: 20s
output-format: json
sign-mode: direct
hsm-key-name: ""
signer-url: ""
paths: {}
- 이제 relayer가 각 체인에 transaction을 날리기 위한 account를 설정해줘야 한다. Tx를 전송하기 때문에 각각의 account에는 당연히 각 체인의 fee amount가 들어가 있어야 한다. Account는 아래와 같이 설정할 수 있다.
# rly keys restore [chain-name] [key-name] [mnemonic] [flags]
$ rly keys restore evmos evmoskey1 "crunch oil robust faint ..." --coin-type 60
$ rly keys restore gaia gaiakey1 "social fringe repair ..." --coin-type 118
참고로 evmos는 evm과 같은 HD deriviation path를 사용하기 위해 coin type을 60으로 지정해 두어서 flag --coin-type 60
을 사용해주어야 한다. 실행하면 .relayer/keys
디렉토리가 생성되며 각각의 chain ID로 keyring-backend
로 설정한 keyring이 생성된다. 여기에서는 backend를 "file"로 설정했기 때문에 keyring-file
폴더가 생성되며 내부에 key가 존재한다.
현재 keyring-backend
설정은 테스트 및 정확한 단계별 확인을 위해 "file"로 사용했지만 relayer가 자동으로 tx fee를 지불할 수 있게 하려면 "test"로 설정하여서 따로 keyring passphrase 입력 없이 동작 할 수 있게 해야한다.
생성한 key-name으로 config.yaml
내의 key
파라미터를 수정해야 한다. 기본적으로 key: default
라 설정되어 있으며 위에서 설정한 key-name으로 수정을 해야 해당 key를 사용하겠다는 것을 지정할 수 있기 때문에, 직접 config를 변경한다. 변경한 결과는 아래와 같다.
global:
api-listen-addr: :5183
timeout: 10s
memo: local custom relayer test
light-cache-size: 20
chains:
evmos:
type: cosmos
value:
key: evmoskey1
chain-id: evmos_9000-1
rpc-addr: http://localhost:46655
account-prefix: evmos
keyring-backend: file
gas-adjustment: 1.2
gas-prices: 10aevmos
debug: true
timeout: 20s
output-format: json
sign-mode: direct
hsm-key-name: ""
signer-url: ""
gaia:
type: cosmos
value:
key: gaiakey1
chain-id: theta-testnet-001
rpc-addr: http://localhost:25655
account-prefix: cosmos
keyring-backend: file
gas-adjustment: 1.2
gas-prices: 1uatom
debug: true
timeout: 20s
output-format: json
sign-mode: direct
hsm-key-name: ""
signer-url: ""
paths: {}
- 현재까지의 설정이 정상적으로 이루어져 있는지 확인이 필요하다. 아래 명령어를 통해 각 체인들의 relayer account가 tx fee를 내기위한 balances를 가지고 있는지 테스트를 할 수 있다.
$ rly chains list
정상적으로 설정이 되어있으면 위 그림과 같이 확인할 수 있다. Balances가 있어도 만약 통신 쪽 문제 등으로 key 정보를 체인에서 가져올 수 없다면 체크표시가 확인되지 않는다. 그림에서 아직 X 표시가 된 부분은 path를 설정하지 않아서 확인이 되는 부분이다.
- 연결하기 위해 새롭게 path를 만들어야 한다. path는 두 체인간의 IBC 설정을 가진다.
# rly paths new [src-chain-id] [dst-chain-id] [path-name] [flags]
$ rly paths new evmos_9000-1 theta-testnet-001 test_path
명령어를 실행하면 아래와 같은 yaml을 확인할 수 있다.
global:
api-listen-addr: :5183
timeout: 10s
memo: local custom relayer test
light-cache-size: 20
chains:
evmos:
type: cosmos
value:
key: evmoskey1
chain-id: evmos_9000-1
rpc-addr: http://localhost:46655
account-prefix: evmos
keyring-backend: file
gas-adjustment: 1.2
gas-prices: 10aevmos
debug: true
timeout: 20s
output-format: json
sign-mode: direct
hsm-key-name: ""
signer-url: ""
gaia:
type: cosmos
value:
key: gaiakey1
chain-id: theta-testnet-001
rpc-addr: http://localhost:25655
account-prefix: cosmos
keyring-backend: file
gas-adjustment: 1.2
gas-prices: 1uatom
debug: true
timeout: 20s
output-format: json
sign-mode: direct
hsm-key-name: ""
signer-url: ""
paths:
test_path:
src:
chain-id: evmos_9000-1
dst:
chain-id: theta-testnet-001
src-channel-filter:
rule: ""
또한 다시 rly chains list
를 실행하면 아래와 같이 path도 정상적으로 확인된다는 표시를 볼 수 있다.
- Relayer를 통해 IBC light client를 설정한다.
$ rly transact clients test_path
Cmd 내에서 create client 확인이 되며 config에도 path 내에 client의 정보가 입력된다.
paths:
test_path:
src:
chain-id: evmos_9000-1
client-id: 07-tendermint-0
dst:
chain-id: theta-testnet-001
client-id: 07-tendermint-0
src-channel-filter:
rule: ""
channel-list: []
- 또한 연결될 connection도 설정한다.
$ rly transact connection test_path
명령을 실행하면 앞서 언급된 4-handshake를 거치고 connection이 생성되며, yaml 내에도 connection 정보가 입력된다.
paths:
test_path:
src:
chain-id: evmos_9000-1
client-id: 07-tendermint-0
connection-id: connection-0
dst:
chain-id: theta-testnet-001
client-id: 07-tendermint-0
connection-id: connection-0
src-channel-filter:
rule: ""
channel-list: []
- IBC/TAO의 마지막 설정을 위해 channel 또한 생성한다.
$ rly transact channel test_path
마찬가지로 4-handshake를 거치고 channel이 생성된다.
모든 설정은 완료가 되었으며, 마지막으로 relayer를 구동시키면, relayer가 IBC transaction event가 발생하는지 확인하면서 event 발생 시 relay를 시작한다.
$ rly start test_path
IBC transafer
Relayer를 구동시켰으면 IBC transfer를 통해 evmos에서 gaia로 token을 전송해본다. CLI를 통한 IBC transfer는 아래와 같이 실행할 수 있다.
# evmosd tx ibc-transfer transfer [channel-port] [channel-ID] [recipient] [amount] [flags]
$ evmosd tx ibc-transfer transfer transfer channel-0 cosmos17dye3rr0jxgzfra8l4ks782kln9tzrp3at9kvs 1000000aevmos
Recipient의 balance를 확인해보면 relayer를 통해 IBC token이 생성된 것을 확인할 수 있다.
그림에서 확인되는 IBC를 통한 denomination은 복잡한 hash 결과로 확인된다. IBC denom을 만드는 과정은 아래와 같다.
// hash() representing a SHA256 hashing function returning a string
ibc_denom := 'ibc/' + hash('path' + 'base_denom')
이 denom은 transfer/channel-0/aevmos
로 생성된 것이며 IBC module에서 제공하는 denom tracing으로 정보를 확인할 수 있다.
전송한 1000000aevmos는 evmos 체인에서 escrow address에 lock 되어 있으며 evmosd query ibc-transfer escrow-address [channel-port] [channel-ID]
로 확인할 수 있다. 현재 evmos의 channel-0/transfer 가 보유하여 lock된 물량은 query bank balances를 통해 escrow balance를 아래처럼 확인할 수 있다.
반대로 gaia의 uatom도 evmos로 전달이 가능하며, 전달 완료 시에 위에서 IBC denom을 생성하는 방식과 똑같이 denom이 설정된다. 또한 IBC token도 다시 원래의 체인으로 전달도 가능하다. IBC token을 원래의 체인으로 전송하면 escrow address에 lock된 amount 중 요청한 amount 만큼 release된다.
Conclusion
IBC를 구성하는 아키텍처를 확인해보고, IBC relayer를 통하여 IBC transfer를 진행해 보았다. Cosmos의 inter chain은 IBC로 구현되기 때문에 cosmos 생태계를 이해하는데 있어 IBC는 필수적이다. IBC는 잘 만들어진 sw이고 이를 통해 Hub-Zone으로 체인 상호간 연결을 시킬 수 있지만, 서비스를 운영할 때 relayer는 결국 bridge이기 때문에 SPoF가 될 수 있다. IBC module의 안정성은 보장되어 있지만 relayer가 사용하는 account key 등은 fee를 가지고 있으니 조심할 필요가 있다는 뜻이다.
IBC가 발전되어 internet처럼 블록체인 연결이 더 원활해 지길 바라며, 다음에는 IBC를 통한 CosmWasm contract 간 상호 운용에 대해 정리해보겠다.
참고
https://tutorials.cosmos.network/academy/3-ibc/1-what-is-ibc.html
https://github.com/cosmos/relayer
'Blockchain > Cosmos' 카테고리의 다른 글
Evmos의 Ethereum Tx 처리 (1) | 2024.01.23 |
---|---|
Cosmos에서의 gRPC (0) | 2023.09.15 |
Cosmos state query 방법 (0) | 2023.06.22 |
Cosmos SDK 기반 체인 다중 validator 환경 구성 (0) | 2023.06.15 |
Ethereum과 Cosmos의 genesis (0) | 2023.06.15 |