diff --git a/README.md b/README.md index 4639010fb..21d896c82 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ All standards at or past the "Draft" stage are listed here in order of their ICS | [3](spec/ics-003-connection-semantics) | Instantiation | Connection Semantics | Draft | | [4](spec/ics-004-channel-and-packet-semantics) | Instantiation | Channel & Packet Semantics | Draft | | [5](spec/ics-005-port-allocation) | Interface | Port Allocation | Draft | +| [6](spec/ics-006-solo-machine-client) | Instantiation | Solo Machine Client | Draft | +| [7](spec/ics-007-tendermint-client) | Instantiation | Tendermint Client | Draft | | [18](spec/ics-018-relayer-algorithms) | Interface | Relayer Algorithms | Draft | | [23](spec/ics-023-vector-commitments) | Interface | Vector Commitments | Draft | | [24](spec/ics-024-host-requirements) | Interface | Host Requirements | Draft | diff --git a/assets/deps.png b/assets/deps.png index f7bf7500f..ac8136b15 100644 --- a/assets/deps.png +++ b/assets/deps.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67b0b94df75e479adea917af6a1947d2fc4f21165ae1bb145a36efcff05bfd7a -size 55757 +oid sha256:cf9e44c1827a7c4a262ec806fed66912821a35f4d69cba1ea9d847228d903f93 +size 69395 diff --git a/misc/aspell_dict b/misc/aspell_dict index e36c3aab6..ff796d506 100644 --- a/misc/aspell_dict +++ b/misc/aspell_dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 533 +personal_ws-1.1 en 544 ABCI ABI Agoric @@ -175,6 +175,7 @@ channelKey channelPath channelSet checkMisbehaviourAndUpdateState +checkSignature checkValidityAndUpdateState checkVersion cleanupPacket @@ -215,6 +216,7 @@ consensusHeight consensusState consensusStateKey consensusStatePath +consensusStates const counterintuitive counterparty @@ -271,6 +273,9 @@ exfiltration expectedConsensusState firstHeader freezeClient +fromHeight +fromValidatorSet +frozenHeight frozenKey fungibility generateAccount @@ -391,6 +396,7 @@ packetCommitmentKey packetCommitmentPath packetData parameterised +pastHeaders pastPublicKeys pendingDatagrams permissioned @@ -458,6 +464,8 @@ sendPacket sentPacketLogs serialisable sharding +signatureOne +signatureTwo slashable sourceChannel sourcePort @@ -508,6 +516,7 @@ untrusted updateClient updateConsensusState updatePort +updateable validRound validValue validateChannelIdentifier @@ -515,6 +524,8 @@ validateClientIdentifier validateConnectionIdentifier validatePortIdentifier validator +validatorSet +validatorSetHash validators validityPredicate verifications diff --git a/spec.pdc b/spec.pdc index d5a24d397..f64b2f818 100644 --- a/spec.pdc +++ b/spec.pdc @@ -91,10 +91,18 @@ urlcolor: cyan !include spec/ics-020-fungible-token-transfer/README.md.xfm -# ICS 27 - Interchain Accounts +# ICS 027 - Interchain Accounts !include spec/ics-027-interchain-accounts/README.md.xfm +# ICS 006 - Solo Machine Client + +!include spec/ics-006-solo-machine-client/README.md.xfm + +# ICS 007 - Tendermint Client + +!include spec/ics-007-tendermint-client/README.md.xfm + # Appendix A: Use-case Descriptions !include ibc/4_IBC_USECASES.md.xfm diff --git a/spec.pdf b/spec.pdf index d74029f87..0e28fed86 100644 Binary files a/spec.pdf and b/spec.pdf differ diff --git a/spec/ics-006-solo-machine-client/Makefile b/spec/ics-006-solo-machine-client/Makefile new file mode 100644 index 000000000..7f2733d09 --- /dev/null +++ b/spec/ics-006-solo-machine-client/Makefile @@ -0,0 +1,7 @@ +typecheck: + +build: + +clean: + +.PHONY: typecheck build clean diff --git a/spec/ics-006-solo-machine-client/README.md b/spec/ics-006-solo-machine-client/README.md new file mode 100644 index 000000000..1a33c841a --- /dev/null +++ b/spec/ics-006-solo-machine-client/README.md @@ -0,0 +1,264 @@ +--- +ics: 6 +title: Solo Machine Client +stage: draft +category: IBC/TAO +kind: instantiation +implements: 2 +author: Christopher Goes +created: 2019-12-09 +modified: 2019-12-09 +--- + +## Synopsis + +This specification document describes a client (verification algorithm) for a solo machine with a single updateable public key which implements the [ICS 2](../ics-002-client-semantics) interface. + +### Motivation + +Solo machines — which might be devices such as phones, browsers, or laptops — might like to interface with other machines & replicated ledgers which speak IBC, and they can do so through the uniform client interface. + +### Definitions + +Functions & terms are as defined in [ICS 2](../ics-002-client-semantics). + +### Desired Properties + +This specification must satisfy the client interface defined in [ICS 2](../ics-002-client-semantics). + +Conceptually, we assume "big table of signatures in the universe" - that signatures produced are public - and incorporate replay protection accordingly. + +## Technical Specification + +This specification contains implementations for all of the functions defined by [ICS 2](../ics-002-client-semantics). + +### Client state + +The `ClientState` of a solo machine is simply whether or not the client is frozen. + +```typescript +interface ClientState { + frozen: boolean +} +``` + +### Consensus state + +The `ConsensusState` of a solo machine consists of the current public key & sequence number. + +```typescript +interface ConsensusState { + sequence: uint64 + publicKey: PublicKey +} +``` + +### Headers + +`Header`s must only be provided by a solo machine when the machine wishes to update the public key. + +```typescript +interface Header { + sequence: uint64 + signature: Signature + newPublicKey: PublicKey +} +``` + +### Evidence + +`Evidence` of solo machine misbehaviour consists of a sequence and two signatures over different messages at that sequence. + +```typescript +interface Evidence { + sequence: uint64 + signatureOne: Signature + signatureTwo: Signature +} +``` + +### Client initialisation + +The solo machine client `initialise` function starts an unfrozen client with the initial consensus state. + +```typescript +function initialise(consensusState: ConsensusState): ClientState { + return { + frozen: false, + consensusState + } +} +``` + +### Validity predicate + +The solo machine client `checkValidityAndUpdateState` function checks that the currently registered public key has signed over the new public key with the correct sequence. + +```typescript +function checkValidityAndUpdateState( + clientState: ClientState, + header: Header) { + assert(sequence === clientState.consensusState.sequence) + assert(checkSignature(header.newPublicKey, header.sequence, header.signature)) + clientState.consensusState.publicKey = header.newPublicKey + clientState.consensusState.sequence++ +} +``` + +### Misbehaviour predicate + +Any duplicate signature on different messages by the current public key freezes a solo machine client. + +```typescript +function checkMisbehaviourAndUpdateState( + clientState: ClientState, + evidence: Evidence) { + h1 = evidence.h1 + h2 = evidence.h2 + pubkey = clientState.consensusState.publicKey + assert(evidence.h1.signature.data !== evidence.h2.signature.data) + assert(checkSignature(pubkey, evidence.sequence, evidence.h1.signature)) + assert(checkSignature(pubkey, evidence.sequence, evidence.h2.signature)) + clientState.frozen = true +} +``` + +### State verification functions + +All solo machine client state verification functions simply check a signature, which must be provided by the solo machine. + +```typescript +function verifyClientConsensusState( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + clientIdentifier: Identifier, + consensusState: ConsensusState) { + path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + consensusState + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} + +function verifyConnectionState( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + connectionIdentifier: Identifier, + connectionEnd: ConnectionEnd) { + path = applyPrefix(prefix, "connection/{connectionIdentifier}") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + connectionEnd + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} + +function verifyChannelState( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + channelEnd: ChannelEnd) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + channelEnd + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} + +function verifyPacketCommitment( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64, + commitment: bytes) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + commitment + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} + +function verifyPacketAcknowledgement( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64, + acknowledgement: bytes) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + acknowledgement + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} + +function verifyPacketAcknowledgementAbsence( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} + +function verifyNextSequenceRecv( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + nextSequenceRecv: uint64) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") + abortTransactionUnless(!clientState.frozen) + value = clientState.consensusState.sequence + path + nextSequenceRecv + assert(checkSignature(clientState.consensusState.pubKey, value, proof)) + clientState.consensusState.sequence++ +} +``` + +### Properties & Invariants + +Instantiates the interface defined in [ICS 2](../ics-002-client-semantics). + +## Backwards Compatibility + +Not applicable. + +## Forwards Compatibility + +Not applicable. Alterations to the client verification algorithm will require a new client standard. + +## Example Implementation + +None yet. + +## Other Implementations + +None at present. + +## History + +December 9th, 2019 - Initial version +December 17th, 2019 - Final first draft + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/spec/ics-007-tendermint-client/Makefile b/spec/ics-007-tendermint-client/Makefile new file mode 100644 index 000000000..7f2733d09 --- /dev/null +++ b/spec/ics-007-tendermint-client/Makefile @@ -0,0 +1,7 @@ +typecheck: + +build: + +clean: + +.PHONY: typecheck build clean diff --git a/spec/ics-007-tendermint-client/README.md b/spec/ics-007-tendermint-client/README.md new file mode 100644 index 000000000..d936d41a2 --- /dev/null +++ b/spec/ics-007-tendermint-client/README.md @@ -0,0 +1,315 @@ +--- +ics: 7 +title: Tendermint Client +stage: draft +category: IBC/TAO +kind: instantiation +implements: 2 +author: Christopher Goes +created: 2019-12-10 +modified: 2019-12-19 +--- + +## Synopsis + +This specification document describes a client (verification algorithm) for a blockchain using Tendermint consensus. + +### Motivation + +State machines of various sorts replicated using the Tendermint consensus algorithm might like to interface with other replicated state machines or solo machines over IBC. + +### Definitions + +Functions & terms are as defined in [ICS 2](../ics-002-client-semantics). + +The Tendermint light client uses the generalised Merkle proof format as defined in ICS 8. + +### Desired Properties + +This specification must satisfy the client interface defined in ICS 2. + +## Technical Specification + +This specification depends on correct instantiation of the [Tendermint consensus algorithm](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md) and [light client algorithm](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). + +### Client state + +The Tendermint client state tracks the current validator set, latest height, and a possible frozen height. + +```typescript +interface ClientState { + validatorSet: List> + latestHeight: uint64 + frozenHeight: Maybe +} +``` + +### Consensus state + +The Tendermint client tracks the validator set hash & commitment root for all previously verified consensus states (these can be pruned after awhile). + +```typescript +interface ConsensusState { + validatorSetHash: []byte + commitmentRoot: []byte +} +``` + +### Headers + +The Tendermint client headers include a height, the commitment root, the complete validator set, and the signatures by the validators who committed the block. + +```typescript +interface Header { + height: uint64 + commitmentRoot: []byte + validatorSet: List> + signatures: []Signature +} +``` + +### Evidence + +The `Evidence` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable. +Tendermint client `Evidence` consists of two headers at the same height both of which the light client would have considered valid. + +```typescript +interface Evidence { + fromValidatorSet: List> + fromHeight: uint64 + h1: Header + h2: Header +} +``` + +### Client initialisation + +Tendermint client initialisation requires a (subjectively chosen) latest consensus state, including the full validator set. + +```typescript +function initialize(consensusState: ConsensusState, validatorSet: List>, latestHeight: uint64): ClientState { + return ClientState{ + validatorSet, + latestHeight, + pastHeaders: Map.singleton(latestHeight, consensusState) + } +} +``` + +### Validity predicate + +Tendermint client validity checking uses the bisection algorithm described in the [Tendermint spec](https://github.com/tendermint/spec/blob/master/spec/consensus/light-client.md). If the provided header is valid, the client state is updated & the newly verified commitment written to the store. + +```typescript +function checkValidityAndUpdateState( + clientState: ClientState, + header: Header) { + // assert that header is newer than any we know + assert(header.height < clientState.latestHeight) + // call the `verify` function + assert(verify(clientState.validatorSet, clientState.latestHeight, header)) + // update latest height + clientState.latestHeight = header.height + // create recorded consensus state, save it + consensusState = ConsensusState{validatorSet.hash(), header.commitmentRoot} + set("consensusStates/{identifier}/{header.height}", consensusState) + // save the client + set("clients/{identifier}", clientState) +} +``` + +### Misbehaviour predicate + +Tendermint client misbehaviour checking determines whether or not two conflicting headers at the same height would have convinced the light client. + +```typescript +function checkMisbehaviourAndUpdateState( + clientState: ClientState, + evidence: Evidence) { + // assert that the heights are the same + assert(h1.height === h2.height) + // assert that the commitments are different + assert(h1.commitmentRoot !== h2.commitmentRoot) + // fetch the previously verified commitment root & validator set hash + consensusState = get("consensusStates/{identifier}/{evidence.fromHeight}") + // check that the validator set matches + assert(consensusState.validatorSetHash === evidence.fromValidatorSet.hash()) + // check if the light client "would have been fooled" + assert( + verify(evidence.fromValidatorSet, evidence.fromHeight, h1) && + verify(evidence.fromValidatorSet, evidence.fromHeight, h2) + ) + // set the frozen height + clientState.frozenHeight = min(h1.height, h2.height) + // save the client + set("clients/{identifier}", clientState) +} +``` + +### State verification functions + +Tendermint client state verification functions check a Merkle proof against a previously validated commitment root. + +```typescript +function verifyClientConsensusState( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + clientIdentifier: Identifier, + consensusState: ConsensusState) { + path = applyPrefix(prefix, "consensusStates/{clientIdentifier}") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that the provided consensus state has been stored + assert(root.verifyMembership(path, consensusState, proof)) +} + +function verifyConnectionState( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + connectionIdentifier: Identifier, + connectionEnd: ConnectionEnd) { + path = applyPrefix(prefix, "connection/{connectionIdentifier}") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that the provided connection end has been stored + assert(root.verifyMembership(path, connectionEnd, proof)) +} + +function verifyChannelState( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + channelEnd: ChannelEnd) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that the provided channel end has been stored + assert(root.verifyMembership(path, channelEnd, proof)) +} + +function verifyPacketCommitment( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64, + commitment: bytes) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that the provided commitment has been stored + assert(root.verifyMembership(path, commitment, proof)) +} + +function verifyPacketAcknowledgement( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64, + acknowledgement: bytes) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that the provided acknowledgement has been stored + assert(root.verifyMembership(path, acknowledgement, proof)) +} + +function verifyPacketAcknowledgementAbsence( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that no acknowledgement has been stored + assert(root.verifyNonMembership(path, proof)) +} + +function verifyNextSequenceRecv( + clientState: ClientState, + height: uint64, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + nextSequenceRecv: uint64) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") + // check that the client is at a sufficient height + assert(clientState.latestHeight >= height) + // check that the client is unfrozen or frozen at a higher height + assert(clientState.frozenHeight === null || clientState.frozenHeight > height) + // fetch the previously verified commitment root & verify membership + root = get("consensusStates/{identifier}/{height}") + // verify that the nextSequenceRecv is as claimed + assert(root.verifyMembership(path, nextSequenceRecv, proof)) +} +``` + +### Properties & Invariants + +Correctness guarantees as provided by the Tendermint light client algorithm. + +## Backwards Compatibility + +Not applicable. + +## Forwards Compatibility + +Not applicable. Alterations to the client verification algorithm will require a new client standard. + +## Example Implementation + +None yet. + +## Other Implementations + +None at present. + +## History + +December 10th, 2019 - Initial version +December 19th, 2019 - Final first draft + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/translation/ko/spec/ics-003-connection-semantics/README.md b/translation/ko/spec/ics-003-connection-semantics/README.md new file mode 100644 index 000000000..575c94837 --- /dev/null +++ b/translation/ko/spec/ics-003-connection-semantics/README.md @@ -0,0 +1,395 @@ +--- +ics: '3' +title: 커넥션 시멘틱 +stage: 초안 +category: IBC/TAO +requires: 2, 24 +required-by: 4, 25 +author: Christopher Goes +created: '2019-03-07' +modified: '2019-08-25' +--- + +## 개요 + +이 표준 문서는 IBC *연결*의 추상화: 두 개의 분리된 체인에 있는 두 개의 상태 객체(*연결 단말*)에 대해 설명합니다. 각 객체는 다른 체인의 라이트 클라이언트와 관련 있고, 이와 함께 crosschain -sub-state 검증 및 (채널을 통한) 패킷 교환이 가능하게 합니다. 연결을 안전하게 만들기 위한 두 체인 간의 프로토콜에 대한 설명입니다. + +### 동기 + +코어 IBC 프로토콜은 패킷에 대한 *인증*과 *순서* 시멘틱을 제공합니다. 이것은 패킷들이 송신 블록체인에 커밋되는 것과 (그리고 토큰 예치와 같은 해당 상태 전이의 실행), 특정 순서로 딱 한 번 커밋되는 것 및 같은 순서로 정확히 한 번 전달되는 것을 보장합니다. 이 표준과 [ICS 2](../ics-002-client-semantics)에서 명시된 *연결* 추상화는 IBC의 *인증* 시맨틱을 정의합니다. 순서 시멘틱은 [ICS 4](../ics-004-channel-and-packet-semantics)에 설명되어 있습니다. + +### 정의 + +클라이언트 관련 타입과 함수들은 [ICS 2](../ics-002-client-semantics)에 정의되어 있습니다. + +Commitment 증명 관련 타입과 함수들은 [ICS 23](../ics-023-vector-commitments)에 정의되어 있습니다. + +`식별자(Identifier)`와 다른 호스트 상태 머신 요구사항들은 [ICS 24](../ics-024-host-requirements)에 정의되어 있습니다. 식별자는 사람이 읽을 수 있는 명칭으로 지을 필요는 없습니다 (그러나 사용하지 않는 식별자들을 사용하는건 좋지 않습니다). + +개방 핸드쉐이크 프로토콜은 각 체인이 다른 체인에 대한 연결을 참조하기 위한 식별자를 검증하여 각 체인의 모듈들이 서로 다른 체인을 추론할 수 있도록 합니다. + +이 명세에서 언급되는 *행위자(actor)*는 (가스 또는 비슷한 메커니즘을 통해) 계산 / 저장 비용을 지불하여 신뢰할 수 없는 데이터그램을 실행할 수 있는 주체입니다. 가능한 행위자는 다음과 같습니다. + +- 계정 키를 이용해 서명한 단말 유저 +- 자율적으로 실행되거나, 다른 트랜잭션에 반응하는 온-체인 트랜잭션 +- 다른 트랜잭션에 대한 반응 혹은 예약된 형식에 따라 행동하는 온-체인 모듈 + +### 요구 속성 + +- 블록체인 구현은 신뢰할 수 없는 행위자와 안전하게 연결을 열고 갱신할 수 있어야 합니다. + +#### Pre-Establishment + +연결 설정 전: + +- 교차 체인의 하위 상태를 검증할 수 없기 때문에, 다른 IBC 하위 프로토콜이 작동해서는 안됩니다. +- (연결을 생성한) 시작 actor는 연결하는 체인의 초기 합의 상태와 연결된 체인 체인의 초기 합의 상태를 지정할 수 있어야 합니다. (예: 트랜잭션 전송) + +#### During Handshake + +핸드쉐이크가 시작되면: + +- 올바른 핸드쉐이크 데이터그램만이 순서대로 실행될 수 있습니다. +- 제 3의 체인이 연결 설정 중인 두 체인 중 하나로 둔갑할 수 없습니다. + +#### Post-Establishment + +핸드쉐이크가 완료됐을때: + +- 두 체인에 생성된 연결 객체들은 시작 actor에 의해 특정된 합의 상태를 포함해야 합니다. +- 데이터그램을 다시 실행하여 다른 체인에 악의적으로 다른 연결 객체를 만들 수 없습니다. + + +## 기술 명세 + +### 자료 구조 + +ICS는 `ConnectionState`와 `ConnectionEnd` 타입들을 정의합니다. + +```typescript +enum ConnectionState { + INIT, + TRYOPEN, + OPEN, +} +``` + +```typescript +interface ConnectionEnd { + state: ConnectionState + counterpartyConnectionIdentifier: Identifier + counterpartyPrefix: CommitmentPrefix + clientIdentifier: Identifier + counterpartyClientIdentifier: Identifier + version: string | []string +} +``` + +- `state` 필드는 연결 단말의 현재 상태를 나타냅니다. +- `counterpartyConnectionIdentifier` 필드는 이 연결과 관련된 상대 체인의 연결 단말을 식별합니다. +- `clientIdentifier` 필드는 이 연결과 관련된 클라이언트를 식별합니다. +- `counterpartyClientIdentifier` 필드는 이 연결과 관련된 상대 체인의 클라이언트를 식별합니다. +- `version` 필드는 이 연결을 사용하는 채널, 패킷의 인코딩 방법 또는 프로토콜을 나타내는 문자열입니다. + +### Store paths + +연결 경로는 유일한 식별자로 저장됩니다. + +```typescript +function connectionPath(id: Identifier): Path { + return "connections/{id}" +} +``` + +클라이언트에서의 (클라이언트를 사용하여 모든 연결을 조회하는데 사용되는) 일련의 연결에 대한 역방향 매핑은 클라이언트마다 고유한 접두사로 저장됩니다. + +```typescript +function clientConnectionsPath(clientIdentifier: Identifier): Path { + return "clients/{clientIdentifier}/connections" +} +``` + +### Helper functions + +`addConnectionToClient`는 클라이언트와 연관된 연결 세트에 연결 식별자를 추가하는데 사용됩니다. + +```typescript +function addConnectionToClient( + clientIdentifier: Identifier, + connectionIdentifier: Identifier) { + conns = privateStore.get(clientConnectionsPath(clientIdentifier)) + conns.add(connectionIdentifier) + privateStore.set(clientConnectionsPath(clientIdentifier), conns) +} +``` + +`removeConnectionFromClient`는 클라이언트와 연관된 연결 세트에서 연결 식별자를 삭제하는데 사용됩니다. + +```typescript +function removeConnectionFromClient( + clientIdentifier: Identifier, + connectionIdentifier: Identifier) { + conns = privateStore.get(clientConnectionsPath(clientIdentifier, connectionIdentifier)) + conns.remove(connectionIdentifier) + privateStore.set(clientConnectionsPath(clientIdentifier, connectionIdentifier), conns) +} +``` + +`CommitmentPrefix`의 자동 적용을 위한 두개의 헬퍼 함수가 정의되어 있습니다. 사양의 다른 부분에서, 이 함수들은 클라이언트에서 `verifyMembership`나 `verifyNonMembership` 함수를 직접 호출하는 대신, 반드시 다른 체인의 상태를 검사하는데 사용해야 합니다. + +```typescript +function verifyMembership( + connection: ConnectionEnd, + height: uint64, + proof: CommitmentProof, + path: Path, + value: Value): bool { + client = queryClient(connection.clientIdentifier) + client.verifyMembership(height, proof, applyPrefix(connection.counterpartyPrefix, path), value) +} +``` + +```typescript +function verifyNonMembership( + connection: ConnectionEnd, + height: uint64, + proof: CommitmentProof, + path: Path): bool { + client = queryClient(connection.clientIdentifier) + client.verifyNonMembership(height, proof, applyPrefix(connection.counterpartyPrefix, path)) +} +``` + +### Versioning + +핸드쉐이크 단계가 진행되는 동안, 연결의 두 단말은 연결과 관련된 버전 바이트 스트링에 동의합니다. 이 때, 버전 바이트 스트링의 내용은 IBC 코어 프로토콜과는 거리가 멉니다. 앞으로는 어떤 종류의 채널이 해당 연결을 사용할 수 있는지, 또는 채널 관련 데이터 그램에서 어떤 인코딩 형식을 사용할지 나타내는 데 사용될 수 있습니다. 지금은, 호스트 상태 머신은 버전 데이터를 이용하여 IBC 위에서의 사용자 정의 로직과 관련된 인코딩, 우선 순위 또는 연결과 관련된 메타데이터를 정할 수 있습니다. + +호스트 상태 머신은 버전 데이터를 안전하게 무시하거나, 빈 문자열을 지정할 수 있습니다. + +`checkVersion`은 두 버전이 호환되는지 결정하는 호스트 상태 머신에 의해 정의되며, boolean 타입을 반환하는 함수입니다. + +```typescript +function checkVersion( + version: string, + counterpartyVersion: string): boolean { + // defined by host state machine +} +``` + +이 명세의 다음 버전 또한 이 함수를 정의할 것입니다. + +### Sub-protocols + +이 ICS는 개방 핸드쉐이크 서브 프로토콜을 정의합니다. 연결이 열릴 때, 연결은 닫힐 수 없고 식별자는 재할당 될 수 없습니다 (이것은 패킷의 재발생 또는 인증 혼란을 방지해줍니다). + +헤더 추적과 오동작 감지는 [ICS 2](../ics-002-client-semantics)에 정의되어 있습니다. + +![State Machine Diagram](../../../spec/ics-003-connection-semantics/state.png) + +#### 식별자 검증 + +연결은 유일한 `Identifier` 접두어에 의해 저장됩니다. 검증 함수 `validateConnectionIdentifier`은 제공될 것입니다. + +```typescript +type validateConnectionIdentifier = (id: Identifier) => boolean +``` + +만약 제공되지 않는다면, 기본적인 `validateConnectionIdentifier` 함수는 항상 `true`를 반환합니다. + +#### Versioning + +구현은 반드시 지원하는 버전 리스트를 내림차순으로 반환하는 `getCompatibleVersions` 함수를 정의해야 합니다. + +```typescript +type getCompatibleVersions = () => []string +``` + +구현은 반드시 상대방이 제안한 버전 리스트에서 버전을 선택하는 `pickVersion` 함수를 정의해야 합니다. + +```typescript +type pickVersion = ([]string) => string +``` + +#### Opening Handshake + +핸드쉐이크 개방 서브 프로토콜은 두 체인의 합의 상태를 초기화합니다. + +개방 핸드쉐이크는 네 개의 데이터그램을 정의합니다: *ConnOpenInit*, *ConnOpenTry*, *ConnOpenAck*, 그리고 *ConnOpenConfirm*. + +올바른 프로토콜 실행은 다음과 같은 순서로 실행됩니다 (모든 호출은 ICS 25에 정의된 모듈들에 의해 실행됩니다): + +Initiator | Datagram | Chain acted upon | 이전 상태 (A, B) | 이후 상태 (A, B) +--- | --- | --- | --- | --- +Actor | `ConnOpenInit` | A | (none, none) | (INIT, none) +Relayer | `ConnOpenTry` | B | (INIT, none) | (INIT, TRYOPEN) +Relayer | `ConnOpenAck` | A | (INIT, TRYOPEN) | (OPEN, TRYOPEN) +Relayer | `ConnOpenConfirm` | B | (OPEN, TRYOPEN) | (OPEN, OPEN) + +서브 프로토콜을 구현하는 두 체인의 개방 핸드쉐이크의 종료 시점에서, 다음과 같은 속성들이 유지됩니다: + +- 각 체인은 초기 행위자(actor)가 지정한 대로 서로의 올바른 합의 상태를 갖고 있습니다. +- 각 체인은 다른 체인의 식별자를 알고 있으며, 이에 합의합니다. + +이 서브 프로토콜은 모듈로 안티 스팸 대책으로 허가 받을 필요는 없습니다. + +*ConnOpenInit*는 체인 A에서의 연결 시도를 초기화합니다. + +```typescript +function connOpenInit( + identifier: Identifier, + desiredCounterpartyConnectionIdentifier: Identifier, + counterpartyPrefix: CommitmentPrefix, + clientIdentifier: Identifier, + counterpartyClientIdentifier: Identifier) { + abortTransactionUnless(validateConnectionIdentifier(identifier)) + abortTransactionUnless(provableStore.get(connectionPath(identifier)) == null) + state = INIT + connection = ConnectionEnd{state, desiredCounterpartyConnectionIdentifier, counterpartyPrefix, + clientIdentifier, counterpartyClientIdentifier, getCompatibleVersions()} + provableStore.set(connectionPath(identifier), connection) + addConnectionToClient(clientIdentifier, identifier) +} +``` + +*ConnOpenTry*는 체인 A에서 체인 B로의 연결 시도 알림을 전달합니다 (이 코드는 체인 B에서 실행됩니다). + +```typescript +function connOpenTry( + desiredIdentifier: Identifier, + counterpartyConnectionIdentifier: Identifier, + counterpartyPrefix: CommitmentPrefix, + counterpartyClientIdentifier: Identifier, + clientIdentifier: Identifier, + counterpartyVersions: string[], + proofInit: CommitmentProof, + proofHeight: uint64, + consensusHeight: uint64) { + abortTransactionUnless(validateConnectionIdentifier(desiredIdentifier)) + abortTransactionUnless(consensusHeight <= getCurrentHeight()) + expectedConsensusState = getConsensusState(consensusHeight) + expected = ConnectionEnd{INIT, desiredIdentifier, getCommitmentPrefix(), counterpartyClientIdentifier, + clientIdentifier, counterpartyVersions} + version = pickVersion(counterpartyVersions) + connection = ConnectionEnd{state, counterpartyConnectionIdentifier, counterpartyPrefix, + clientIdentifier, counterpartyClientIdentifier, version} + abortTransactionUnless( + connection.verifyMembership(proofHeight, proofInit, + connectionPath(counterpartyConnectionIdentifier), + expected)) + abortTransactionUnless( + connection.verifyMembership(proofHeight, proofInit, + consensusStatePath(counterpartyClientIdentifier), + expectedConsensusState)) + abortTransactionUnless(provableStore.get(connectionPath(desiredIdentifier)) === null) + abortTransactionUnless(checkVersion(version, counterpartyVersion)) + identifier = desiredIdentifier + state = TRYOPEN + provableStore.set(connectionPath(identifier), connection) + addConnectionToClient(clientIdentifier, identifier) +} +``` + +*ConnOpenAck*는 체인 B에서 체인 A로의 연결 개방 시도에 대한 수락 메세지를 전달합니다 (이 코드는 체인 A에서 실행됩니다). + +```typescript +function connOpenAck( + identifier: Identifier, + version: string, + proofTry: CommitmentProof, + proofHeight: uint64, + consensusHeight: uint64) { + abortTransactionUnless(consensusHeight <= getCurrentHeight()) + connection = provableStore.get(connectionPath(identifier)) + abortTransactionUnless(connection.state === INIT) + abortTransactionUnless(checkVersion(connection.version, version)) + expectedConsensusState = getConsensusState(consensusHeight) + expected = ConnectionEnd{TRYOPEN, identifier, getCommitmentPrefix(), + connection.counterpartyClientIdentifier, connection.clientIdentifier, + version} + abortTransactionUnless( + connection.verifyMembership(proofHeight, proofTry, + connectionPath(connection.counterpartyConnectionIdentifier), + expected)) + abortTransactionUnless( + connection.verifyMembership(proofHeight, proofTry, + consensusStatePath(connection.counterpartyClientIdentifier), + expectedConsensusState)) + connection.state = OPEN + abortTransactionUnless(getCompatibleVersions().indexOf(version) !== -1) + connection.version = version + provableStore.set(connectionPath(identifier), connection) +} +``` + +*ConnOpenConfirm*는 두 체인 모두에서 연결이 개방된 이후에 체인 A에서 체인 B로의 연결을 확인합니다 (이 코드는 체인 B에서 실행됩니다). + +```typescript +function connOpenConfirm( + identifier: Identifier, + proofAck: CommitmentProof, + proofHeight: uint64) { + connection = provableStore.get(connectionPath(identifier)) + abortTransactionUnless(connection.state === TRYOPEN) + expected = ConnectionEnd{OPEN, identifier, getCommitmentPrefix(), connection.counterpartyClientIdentifier, + connection.clientIdentifier, connection.version} + abortTransactionUnless( + connection.verifyMembership(proofHeight, proofAck, + connectionPath(connection.counterpartyConnectionIdentifier), + expected)) + connection.state = OPEN + provableStore.set(connectionPath(identifier), connection) +} +``` + +#### Querying + +`queryConnection`를 사용하여 식별자로 연결은 조회될 수 있습니다. + +```typescript +function queryConnection(id: Identifier): ConnectionEnd | void { + return provableStore.get(connectionPath(id)) +} +``` + +특정 클라이언트와 관련된 연결은 `queryClientConnections`를 사용하여 클라이언트 식별자로 조회될 수 있습니다. + +```typescript +function queryClientConnections(id: Identifier): Set { + return privateStore.get(clientConnectionsPath(id)) +} +``` + +### Properties & Invariants + +- 연결 식별자들은 선착순입니다: 일단 연결이 성사되면, 두 체인 사이의 유일한 식별자 쌍이 존재하게 됩니다. +- 다른 블록 체인의 IBC 핸들러가 연결 핸드쉐이크를 중간에 간섭할 수 없습니다. + +## 하위 호환성 + +적용되지 않습니다. + +## 상위 호환성 + +이 ICS의 앞으로의 버전은 개방 핸드쉐이크의 버전 협의를 포함합니다. 연결이 성립되고 버전이 협의되면 ICS 6에 따라 향후 버전을 협의할 수 있습니다. + +합의 상태는 연결이 성립될 때 선택된 합의 프로토콜에 정의된 `updateConsensusState` 함수에 따라서만 변경될 수 있습니다. + +## 예제 구현 + +곧 구현 될 예정입니다. + +## 다른 구현 + +곧 구현 될 예정입니다. + +## History + +이 문서의 몇 부분은 [previous IBC specification](https://github.com/cosmos/cosmos-sdk/tree/master/docs/spec/ibc)를 참조했습니다. + +2019년 3월 29일 - 초안 제출 +2019년 5월 17일 - 초안 확정 +2019년 7월 29일 - 클라이언트와 관련된 연결 세트 추적을 위한 개정 + +## Copyright + +모든 컨텐츠는 [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 라이센스에 의해 보호 받습니다.