From 1f459e395e1c8a8a1d0b766311dd83d40928a624 Mon Sep 17 00:00:00 2001 From: Parth Desai Date: Fri, 7 May 2021 20:56:26 +0400 Subject: [PATCH 01/12] add 028 wasm client --- spec/client/ics-028-wasm-client/README.md | 451 ++++++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 spec/client/ics-028-wasm-client/README.md diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md new file mode 100644 index 000000000..70b908d46 --- /dev/null +++ b/spec/client/ics-028-wasm-client/README.md @@ -0,0 +1,451 @@ +--- +ics: 28 +title: WASM Client +stage: draft +category: IBC/TAO +kind: instantiation +implements: 2 +author: Parth Desai +created: 2020-10-13 +modified: 2020-10-13 +--- + +## Synopsis + +This specification document describes an interface to a client (verification algorithm) stored as a wasm bytecode for a blockchain. + +### Motivation + +WASM based light clients are decoupled from SDK source code, which enables one to upgrade existing light client or add support for other blockchain without modifying SDK source code. + +### Definitions + +Functions & terms are as defined in [ICS 2](../ics-002-client-semantics). + +`currentTimestamp` is as defined in [ICS 24](../ics-024-host-requirements). + +`WASM Client Code` refers to wasm bytecode stored in the client store, which provides target blockchain specific implementation of [ICS 2](../ics-002-client-semantics). + +`WASM Client` refers to a particular instance of `WASM Client Code` defined as a tuple `(WASM Client Code, ClientID)`. + +`WASM VM` refers to a virtual machine capable of executing valid wasm bytecode. + + +### Desired Properties + +This specification must satisfy the client interface defined in ICS 2. + +## Technical Specification + +This specification depends on correctness of the `WASM Client code` in context of consensus algorithm of its target `blockchain`, as well as correct instantiation of `WASM Client`. + +### Client state + +The wasm client state tracks location of the wasm bytecode via `codeId`. Binary data represented by `data` field is opaque and only interpreted by the WASM Client Code. `type` represents client type. +`type` and `codeId` both are immutable. + +```typescript +interface ClientState { + codeId: []byte + data: []byte + frozen: boolean + frozen_height: Height + latest_height: Height + type: String +} +``` + +### Consensus state + +The WASM consensus state tracks the timestamp (block time), `WASM Client code` specific fields and commitment root for all previously verified consensus states. +`type` and `codeId` both are immutable. + +```typescript +interface ConsensusState { + codeId: []byte + data: []byte + timestamp: uint64 + root: MerkleRoot + type: String +} +``` + +### Height + +The height of a WASM light client instance consists of two `uint64`s: the epoch number, and the height in the epoch. + +```typescript +interface Height { + epochNumber: uint64 + epochHeight: uint64 +} +``` + +Comparison between heights is implemented as follows: + +```typescript +function compare(a: Height, b: Height): Ord { + if (a.epochNumber < b.epochNumber) + return LT + else if (a.epochNumber === b.epochNumber) + if (a.epochHeight < b.epochHeight) + return LT + else if (a.epochHeight === b.epochHeight) + return EQ + return GT +} +``` + +This is designed to allow the height to reset to `0` while the epoch number increases by one in order to preserve timeouts through zero-height upgrades. + +### Headers + +Contents of wasm client headers depend upon `WASM Client Code`. + +```typescript +interface Header { + data: []byte +} +``` + +### Misbehaviour + +The `Misbehaviour` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable. +WASM client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid. + +```typescript +interface Misbehaviour { + fromHeight: Height + h1: Header + h2: Header +} +``` + +### Client initialisation + +WASM client initialisation requires a (subjectively chosen) latest consensus state interpretable by the target WASM Client Code. `wasmCodeId` field is unique identifier for `WASM Client Code`, and `initializationData` refers to opaque data required for initialization of the particular client managed by that WASM Client Code. + +```typescript +function initialise( + wasmCodeId: String, + initializationData: []byte, + consensusState: []byte, + ): ClientState { + codeHandle = getWASMCode(wasmCodeId) + assert(codeHandle.isInitializationDataValid(initializationData, consensusState)) + set("clients/{identifier}/consensusStates/{height}", consensusState) + return codeHandle.initialise(initializationData, consensusState) +} +``` + +The `latestClientHeight` function returns the latest stored height, which is updated every time a new (more recent) header is validated. + +```typescript +function latestClientHeight(clientState: ClientState): Height { + codeHandle = clientState.codeHandle(); + codeHandle.latestClientHeight(clientState) +} +``` + +### Validity predicate + +WASM client validity checking uses underlying WASM Client code. If the provided header is valid, the client state is updated & the newly verified commitment written to the store. + +```typescript +function checkValidityAndUpdateState( + clientState: ClientState, + epoch: uint64, + header: Header) { + codeHandle = clientState.codeHandle() + isValid, consensusState = codeHandle.validateHeaderAndCreateConsensusState(clientState, header, epoch) + set("clients/{identifier}/consensusStates/{header.height}", consensusState) + // save the client + set("clients/{identifier}", clientState) +} +``` + +### Misbehaviour predicate + +WASM 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, + misbehaviour: Misbehaviour) { + codeHandle = clientState.codeHandle() + consensusState = get("clients/{identifier}/consensusStates/{misbehaviour.fromHeight}") + assert(codeHandle.handleMisbehaviour(clientState, consensusState, misbehaviour)) + // save the client + set("clients/{identifier}", clientState) +} +``` + +### Upgrades + +The chain which this light client is tracking can elect to write a special pre-determined key in state to allow the light client to update its client state (e.g. with a new chain ID or epoch) in preparation for an upgrade. + +As the client state change will be performed immediately, once the new client state information is written to the predetermined key, the client will no longer be able to follow blocks on the old chain, so it must upgrade promptly. + +```typescript +function upgradeClientState( + clientState: ClientState, + newClientState: ClientState, + height: Height, + proof: CommitmentPrefix) { + codeHandle = clientState.codeHandle() + codeHandle.verifyNewClientState(oldClientState, newClientState, height) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the provided consensus state has been stored + assert(root.verifyMembership(path, newClientState, proof)) + // update client state + clientState = newClientState + set("clients/{identifier}", clientState) +} +``` + +In case of wasm client, upgrade of `WASM Client Code` is also possible via blockchain specific management functionality. + +### State verification functions + +WASM client state verification functions check a Merkle proof against a previously validated commitment root. + +```typescript +function verifyClientConsensusState( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + clientIdentifier: Identifier, + consensusStateHeight: Height, + consensusState: ConsensusState) { + path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}") + codeHandle = getCodeHandleFromClientID(clientIdentifier) + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the provided consensus state has been stored + assert(root.verifyMembership(path, consensusState, proof)) +} + +function verifyConnectionState( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + connectionIdentifier: Identifier, + connectionEnd: ConnectionEnd) { + path = applyPrefix(prefix, "connections/{connectionIdentifier}") + codeHandle = clientState.codeHandle() + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the provided connection end has been stored + assert(root.verifyMembership(path, connectionEnd, proof)) +} + +function verifyChannelState( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + channelEnd: ChannelEnd) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") + codeHandle = clientState.codeHandle() + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the provided channel end has been stored + assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, channelEnd, proof)) +} + +function verifyPacketData( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64, + data: bytes) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") + codeHandle = clientState.codeHandle() + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the provided commitment has been stored + assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, hash(data), proof)) +} + +function verifyPacketAcknowledgement( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64, + acknowledgement: bytes) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") + codeHandle = clientState.codeHandle() + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the provided acknowledgement has been stored + assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, hash(acknowledgement), proof)) +} + +function verifyPacketReceiptAbsence( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + sequence: uint64) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/receipts/{sequence}") + codeHandle = clientState.codeHandle() + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that no acknowledgement has been stored + assert(root.verifyNonMembership(codeHandle.getProofSpec(clientState), path, proof)) +} + +function verifyNextSequenceRecv( + clientState: ClientState, + height: Height, + prefix: CommitmentPrefix, + proof: CommitmentProof, + portIdentifier: Identifier, + channelIdentifier: Identifier, + nextSequenceRecv: uint64) { + path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") + codeHandle = clientState.codeHandle() + assert(codeHandle.isValidClientState(clientState, height)) + // fetch the previously verified commitment root & verify membership + root = get("clients/{identifier}/consensusStates/{height}") + // verify that the nextSequenceRecv is as claimed + assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, nextSequenceRecv, proof)) +} +``` + +### WASM Client Code Interface + +#### What is code handle? +Code handle is an object that facilitates interaction between WASM code and go code. For example, consider the method `isValidClientState` which could be implemented like this: + +```go +func (c *CodeHandle) isValidClientState(clientState ClientState, height u64) { + clientStateData := json.Serialize(clientState) + packedData := pack(clientStateData, height) + // VM specific code to call WASM contract +} +``` + +#### WASM Client interface +Every WASM client code need to support ingestion of below messages in order to be used as light client. + +```rust +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct MisbehaviourMessage { + pub client_state: Vec, + pub consensus_state: Vec, + pub height: u64, + pub header1: Vec, + pub header2: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CreateConsensusMessage { + pub client_state: Vec, + pub epoch: u64, + pub height: u64 +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct InitializeClientStateMessage { + pub initialization_data: Vec, + pub consensus_state: Vec +} + + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HandleMsg { + HandleMisbehaviour(MisbehaviourMessage), + TryCreateConsensusState(CreateConsensusMessage), + InitializeClientState(InitializeClientStateMessage) +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ValidateClientStateMessage { + client_state: Vec, + height: u64 +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ValidateNewClientStateMessage { + client_state: Vec, + new_client_state: Vec, + height: u64 +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ValidateInitializationDataMessage { + init_data: Vec, + consensus_state: Vec +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ValidityPredicate { + ClientState(ValidateClientStateMessage), + NewClientState(ValidateNewClientStateMessage), + InitializationData(ValidateInitializationDataMessage), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + IsValid(ValidityPredicate), + LatestClientHeight(Vec), + ProofSpec(Vec) +} + +``` + +### Properties & Invariants + +Correctness guarantees as provided by the underlying algorithm implemented by `WASM Client Code`. + +## Backwards Compatibility + +Not applicable. + +## Forwards Compatibility + +As long as `WASM Client Code` keeps interface consistent with `ICS 02` it should be forward compatible + +## Example Implementation + +None yet. + +## Other Implementations + +None at present. + +## History + + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From dbea20900d5c35dc8892a3bb10df57eec3cfe30e Mon Sep 17 00:00:00 2001 From: Parth Date: Mon, 31 May 2021 20:33:55 +0530 Subject: [PATCH 02/12] Accepting suggestion Co-authored-by: Christopher Goes --- spec/client/ics-028-wasm-client/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md index 70b908d46..63db84710 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-028-wasm-client/README.md @@ -24,7 +24,7 @@ Functions & terms are as defined in [ICS 2](../ics-002-client-semantics). `currentTimestamp` is as defined in [ICS 24](../ics-024-host-requirements). -`WASM Client Code` refers to wasm bytecode stored in the client store, which provides target blockchain specific implementation of [ICS 2](../ics-002-client-semantics). +`WASM Client Code` refers to WASM bytecode stored in the client store, which provides a target blockchain specific implementation of [ICS 2](../ics-002-client-semantics). `WASM Client` refers to a particular instance of `WASM Client Code` defined as a tuple `(WASM Client Code, ClientID)`. From 54a551e5b8bae2033779e7c2dbcfcc4d2acd499e Mon Sep 17 00:00:00 2001 From: Parth Desai Date: Mon, 31 May 2021 20:53:25 +0530 Subject: [PATCH 03/12] use WASM everywhere --- spec/client/ics-028-wasm-client/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md index 63db84710..c23395fe5 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-028-wasm-client/README.md @@ -12,7 +12,7 @@ modified: 2020-10-13 ## Synopsis -This specification document describes an interface to a client (verification algorithm) stored as a wasm bytecode for a blockchain. +This specification document describes an interface to a client (verification algorithm) stored as a WASM bytecode for a blockchain. ### Motivation @@ -28,7 +28,7 @@ Functions & terms are as defined in [ICS 2](../ics-002-client-semantics). `WASM Client` refers to a particular instance of `WASM Client Code` defined as a tuple `(WASM Client Code, ClientID)`. -`WASM VM` refers to a virtual machine capable of executing valid wasm bytecode. +`WASM VM` refers to a virtual machine capable of executing valid WASM bytecode. ### Desired Properties @@ -41,7 +41,7 @@ This specification depends on correctness of the `WASM Client code` in context o ### Client state -The wasm client state tracks location of the wasm bytecode via `codeId`. Binary data represented by `data` field is opaque and only interpreted by the WASM Client Code. `type` represents client type. +The WASM client state tracks location of the WASM bytecode via `codeId`. Binary data represented by `data` field is opaque and only interpreted by the WASM Client Code. `type` represents client type. `type` and `codeId` both are immutable. ```typescript @@ -100,7 +100,7 @@ This is designed to allow the height to reset to `0` while the epoch number incr ### Headers -Contents of wasm client headers depend upon `WASM Client Code`. +Contents of WASM client headers depend upon `WASM Client Code`. ```typescript interface Header { @@ -204,7 +204,7 @@ function upgradeClientState( } ``` -In case of wasm client, upgrade of `WASM Client Code` is also possible via blockchain specific management functionality. +In case of WASM client, upgrade of `WASM Client Code` is also possible via blockchain specific management functionality. ### State verification functions From 0937614a9fba360efd62d2a5ad04cd30d2011a90 Mon Sep 17 00:00:00 2001 From: Parth Date: Mon, 31 May 2021 20:56:27 +0530 Subject: [PATCH 04/12] Accepting suggestion Co-authored-by: Christopher Goes --- spec/client/ics-028-wasm-client/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md index c23395fe5..d8ceea492 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-028-wasm-client/README.md @@ -16,7 +16,7 @@ This specification document describes an interface to a client (verification alg ### Motivation -WASM based light clients are decoupled from SDK source code, which enables one to upgrade existing light client or add support for other blockchain without modifying SDK source code. +WASM-based light clients are decoupled from SDK source code, which enables one to upgrade an existing light client or add support for other blockchain without modifying SDK source code. ### Definitions From 67481036d4378d15355bc0a13337b06bd81fa3fe Mon Sep 17 00:00:00 2001 From: Parth Desai Date: Mon, 31 May 2021 20:59:07 +0530 Subject: [PATCH 05/12] replace WASM with Wasm --- spec/client/ics-028-wasm-client/README.md | 48 +++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md index c23395fe5..15fc2b4c3 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-028-wasm-client/README.md @@ -1,6 +1,6 @@ --- ics: 28 -title: WASM Client +title: Wasm Client stage: draft category: IBC/TAO kind: instantiation @@ -12,11 +12,11 @@ modified: 2020-10-13 ## Synopsis -This specification document describes an interface to a client (verification algorithm) stored as a WASM bytecode for a blockchain. +This specification document describes an interface to a client (verification algorithm) stored as a Wasm bytecode for a blockchain. ### Motivation -WASM based light clients are decoupled from SDK source code, which enables one to upgrade existing light client or add support for other blockchain without modifying SDK source code. +Wasm based light clients are decoupled from SDK source code, which enables one to upgrade existing light client or add support for other blockchain without modifying SDK source code. ### Definitions @@ -24,11 +24,11 @@ Functions & terms are as defined in [ICS 2](../ics-002-client-semantics). `currentTimestamp` is as defined in [ICS 24](../ics-024-host-requirements). -`WASM Client Code` refers to WASM bytecode stored in the client store, which provides a target blockchain specific implementation of [ICS 2](../ics-002-client-semantics). +`Wasm Client Code` refers to Wasm bytecode stored in the client store, which provides a target blockchain specific implementation of [ICS 2](../ics-002-client-semantics). -`WASM Client` refers to a particular instance of `WASM Client Code` defined as a tuple `(WASM Client Code, ClientID)`. +`Wasm Client` refers to a particular instance of `Wasm Client Code` defined as a tuple `(Wasm Client Code, ClientID)`. -`WASM VM` refers to a virtual machine capable of executing valid WASM bytecode. +`Wasm VM` refers to a virtual machine capable of executing valid Wasm bytecode. ### Desired Properties @@ -37,11 +37,11 @@ This specification must satisfy the client interface defined in ICS 2. ## Technical Specification -This specification depends on correctness of the `WASM Client code` in context of consensus algorithm of its target `blockchain`, as well as correct instantiation of `WASM Client`. +This specification depends on correctness of the `Wasm Client code` in context of consensus algorithm of its target `blockchain`, as well as correct instantiation of `Wasm Client`. ### Client state -The WASM client state tracks location of the WASM bytecode via `codeId`. Binary data represented by `data` field is opaque and only interpreted by the WASM Client Code. `type` represents client type. +The Wasm client state tracks location of the Wasm bytecode via `codeId`. Binary data represented by `data` field is opaque and only interpreted by the Wasm Client Code. `type` represents client type. `type` and `codeId` both are immutable. ```typescript @@ -57,7 +57,7 @@ interface ClientState { ### Consensus state -The WASM consensus state tracks the timestamp (block time), `WASM Client code` specific fields and commitment root for all previously verified consensus states. +The Wasm consensus state tracks the timestamp (block time), `Wasm Client code` specific fields and commitment root for all previously verified consensus states. `type` and `codeId` both are immutable. ```typescript @@ -72,7 +72,7 @@ interface ConsensusState { ### Height -The height of a WASM light client instance consists of two `uint64`s: the epoch number, and the height in the epoch. +The height of a Wasm light client instance consists of two `uint64`s: the epoch number, and the height in the epoch. ```typescript interface Height { @@ -100,7 +100,7 @@ This is designed to allow the height to reset to `0` while the epoch number incr ### Headers -Contents of WASM client headers depend upon `WASM Client Code`. +Contents of Wasm client headers depend upon `Wasm Client Code`. ```typescript interface Header { @@ -111,7 +111,7 @@ interface Header { ### Misbehaviour The `Misbehaviour` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable. -WASM client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid. +Wasm client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid. ```typescript interface Misbehaviour { @@ -123,7 +123,7 @@ interface Misbehaviour { ### Client initialisation -WASM client initialisation requires a (subjectively chosen) latest consensus state interpretable by the target WASM Client Code. `wasmCodeId` field is unique identifier for `WASM Client Code`, and `initializationData` refers to opaque data required for initialization of the particular client managed by that WASM Client Code. +Wasm client initialisation requires a (subjectively chosen) latest consensus state interpretable by the target Wasm Client Code. `wasmCodeId` field is unique identifier for `Wasm Client Code`, and `initializationData` refers to opaque data required for initialization of the particular client managed by that Wasm Client Code. ```typescript function initialise( @@ -149,7 +149,7 @@ function latestClientHeight(clientState: ClientState): Height { ### Validity predicate -WASM client validity checking uses underlying WASM Client code. If the provided header is valid, the client state is updated & the newly verified commitment written to the store. +Wasm client validity checking uses underlying Wasm Client code. If the provided header is valid, the client state is updated & the newly verified commitment written to the store. ```typescript function checkValidityAndUpdateState( @@ -166,7 +166,7 @@ function checkValidityAndUpdateState( ### Misbehaviour predicate -WASM client misbehaviour checking determines whether or not two conflicting headers at the same height would have convinced the light client. +Wasm client misbehaviour checking determines whether or not two conflicting headers at the same height would have convinced the light client. ```typescript function checkMisbehaviourAndUpdateState( @@ -204,11 +204,11 @@ function upgradeClientState( } ``` -In case of WASM client, upgrade of `WASM Client Code` is also possible via blockchain specific management functionality. +In case of Wasm client, upgrade of `Wasm Client Code` is also possible via blockchain specific management functionality. ### State verification functions -WASM client state verification functions check a Merkle proof against a previously validated commitment root. +Wasm client state verification functions check a Merkle proof against a previously validated commitment root. ```typescript function verifyClientConsensusState( @@ -332,21 +332,21 @@ function verifyNextSequenceRecv( } ``` -### WASM Client Code Interface +### Wasm Client Code Interface #### What is code handle? -Code handle is an object that facilitates interaction between WASM code and go code. For example, consider the method `isValidClientState` which could be implemented like this: +Code handle is an object that facilitates interaction between Wasm code and go code. For example, consider the method `isValidClientState` which could be implemented like this: ```go func (c *CodeHandle) isValidClientState(clientState ClientState, height u64) { clientStateData := json.Serialize(clientState) packedData := pack(clientStateData, height) - // VM specific code to call WASM contract + // VM specific code to call Wasm contract } ``` -#### WASM Client interface -Every WASM client code need to support ingestion of below messages in order to be used as light client. +#### Wasm Client interface +Every Wasm client code need to support ingestion of below messages in order to be used as light client. ```rust #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -425,7 +425,7 @@ pub enum QueryMsg { ### Properties & Invariants -Correctness guarantees as provided by the underlying algorithm implemented by `WASM Client Code`. +Correctness guarantees as provided by the underlying algorithm implemented by `Wasm Client Code`. ## Backwards Compatibility @@ -433,7 +433,7 @@ Not applicable. ## Forwards Compatibility -As long as `WASM Client Code` keeps interface consistent with `ICS 02` it should be forward compatible +As long as `Wasm Client Code` keeps interface consistent with `ICS 02` it should be forward compatible ## Example Implementation From d7c33fc5c01ff4c07000f89ff9da04c31c27ed23 Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Fri, 25 Jun 2021 17:16:00 +0200 Subject: [PATCH 06/12] update data structures --- spec/client/ics-028-wasm-client/README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md index 15fc2b4c3..598f06761 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-028-wasm-client/README.md @@ -48,10 +48,8 @@ The Wasm client state tracks location of the Wasm bytecode via `codeId`. Binary interface ClientState { codeId: []byte data: []byte - frozen: boolean - frozen_height: Height - latest_height: Height - type: String + latestHeight: Height + proofSpecs: []ProofSpec } ``` @@ -65,8 +63,6 @@ interface ConsensusState { codeId: []byte data: []byte timestamp: uint64 - root: MerkleRoot - type: String } ``` @@ -105,6 +101,7 @@ Contents of Wasm client headers depend upon `Wasm Client Code`. ```typescript interface Header { data: []byte + height: Height } ``` @@ -115,7 +112,7 @@ Wasm client `Misbehaviour` consists of two headers at the same height both of wh ```typescript interface Misbehaviour { - fromHeight: Height + clientId: string h1: Header h2: Header } @@ -131,7 +128,7 @@ function initialise( initializationData: []byte, consensusState: []byte, ): ClientState { - codeHandle = getWASMCode(wasmCodeId) + codeHandle = getWasmCode(wasmCodeId) assert(codeHandle.isInitializationDataValid(initializationData, consensusState)) set("clients/{identifier}/consensusStates/{height}", consensusState) return codeHandle.initialise(initializationData, consensusState) From 9ed51b2a6cdd6ee0a5d2a23c6d0c8c2013cb725a Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Mon, 2 Aug 2021 20:29:36 +0200 Subject: [PATCH 07/12] allow light client to update its own consensus/client state --- spec/client/ics-028-wasm-client/README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-028-wasm-client/README.md index 598f06761..050165679 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-028-wasm-client/README.md @@ -5,7 +5,7 @@ stage: draft category: IBC/TAO kind: instantiation implements: 2 -author: Parth Desai +author: Parth Desai , Mateusz Kaczanowski created: 2020-10-13 modified: 2020-10-13 --- @@ -37,7 +37,7 @@ This specification must satisfy the client interface defined in ICS 2. ## Technical Specification -This specification depends on correctness of the `Wasm Client code` in context of consensus algorithm of its target `blockchain`, as well as correct instantiation of `Wasm Client`. +This specification depends on the correct instantiation of the `Wasm client` and is decoupled from any specific implementation of the target `blockchain` consensus algorithm. ### Client state @@ -130,8 +130,9 @@ function initialise( ): ClientState { codeHandle = getWasmCode(wasmCodeId) assert(codeHandle.isInitializationDataValid(initializationData, consensusState)) - set("clients/{identifier}/consensusStates/{height}", consensusState) - return codeHandle.initialise(initializationData, consensusState) + + store = getStore("clients/{identifier}") + return codeHandle.initialise(store, initializationData, consensusState) } ``` @@ -153,11 +154,11 @@ function checkValidityAndUpdateState( clientState: ClientState, epoch: uint64, header: Header) { + store = getStore("clients/{identifier}") codeHandle = clientState.codeHandle() - isValid, consensusState = codeHandle.validateHeaderAndCreateConsensusState(clientState, header, epoch) - set("clients/{identifier}/consensusStates/{header.height}", consensusState) - // save the client - set("clients/{identifier}", clientState) + + // verify that provided header is valid and state is saved + assert(codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header, epoch)) } ``` @@ -169,11 +170,9 @@ Wasm client misbehaviour checking determines whether or not two conflicting head function checkMisbehaviourAndUpdateState( clientState: ClientState, misbehaviour: Misbehaviour) { + store = getStore("clients/{identifier}") codeHandle = clientState.codeHandle() - consensusState = get("clients/{identifier}/consensusStates/{misbehaviour.fromHeight}") - assert(codeHandle.handleMisbehaviour(clientState, consensusState, misbehaviour)) - // save the client - set("clients/{identifier}", clientState) + assert(codeHandle.handleMisbehaviour(store, clientState, misbehaviour)) } ``` From a778337c7f9191972e38fc904e1eda04fca534d4 Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Mon, 2 Aug 2021 20:32:21 +0200 Subject: [PATCH 08/12] rename ics-028-wasm-client ics-008-wasm-client --- .../{ics-028-wasm-client => ics-008-wasm-client}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename spec/client/{ics-028-wasm-client => ics-008-wasm-client}/README.md (99%) diff --git a/spec/client/ics-028-wasm-client/README.md b/spec/client/ics-008-wasm-client/README.md similarity index 99% rename from spec/client/ics-028-wasm-client/README.md rename to spec/client/ics-008-wasm-client/README.md index 050165679..e0f6bfc60 100644 --- a/spec/client/ics-028-wasm-client/README.md +++ b/spec/client/ics-008-wasm-client/README.md @@ -1,5 +1,5 @@ --- -ics: 28 +ics: 8 title: Wasm Client stage: draft category: IBC/TAO From 5750485b64fb4d7f21a3c30a5e8fd94e9abf71c3 Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Tue, 7 Sep 2021 16:18:42 +0200 Subject: [PATCH 09/12] remove proof verification on the Go side --- spec/client/ics-008-wasm-client/README.md | 56 ++++------------------- 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/spec/client/ics-008-wasm-client/README.md b/spec/client/ics-008-wasm-client/README.md index e0f6bfc60..521143b8c 100644 --- a/spec/client/ics-008-wasm-client/README.md +++ b/spec/client/ics-008-wasm-client/README.md @@ -189,11 +189,8 @@ function upgradeClientState( height: Height, proof: CommitmentPrefix) { codeHandle = clientState.codeHandle() - codeHandle.verifyNewClientState(oldClientState, newClientState, height) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the provided consensus state has been stored - assert(root.verifyMembership(path, newClientState, proof)) + assert(codeHandle.verifyNewClientState(clientState, newClientState, height, proof)) + // update client state clientState = newClientState set("clients/{identifier}", clientState) @@ -215,13 +212,8 @@ function verifyClientConsensusState( clientIdentifier: Identifier, consensusStateHeight: Height, consensusState: ConsensusState) { - path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}") codeHandle = getCodeHandleFromClientID(clientIdentifier) - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the provided consensus state has been stored - assert(root.verifyMembership(path, consensusState, proof)) + assert(codeHandle.isValidClientState(clientState, height, prefix, clientIdentifier, proof, consensusStateHeight, consensusState)) } function verifyConnectionState( @@ -231,13 +223,8 @@ function verifyConnectionState( proof: CommitmentProof, connectionIdentifier: Identifier, connectionEnd: ConnectionEnd) { - path = applyPrefix(prefix, "connections/{connectionIdentifier}") codeHandle = clientState.codeHandle() - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the provided connection end has been stored - assert(root.verifyMembership(path, connectionEnd, proof)) + assert(codeHandle.verifyConnectionState(clientState, height, prefix, proof, connectionIdentifier, connectionEnd)) } function verifyChannelState( @@ -248,13 +235,8 @@ function verifyChannelState( portIdentifier: Identifier, channelIdentifier: Identifier, channelEnd: ChannelEnd) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}") codeHandle = clientState.codeHandle() - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the provided channel end has been stored - assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, channelEnd, proof)) + assert(codeHandle.verifyChannelState(clientState, height, prefix, proof, porttIdentifier, channelIdentifier, channelEnd)) } function verifyPacketData( @@ -266,13 +248,8 @@ function verifyPacketData( channelIdentifier: Identifier, sequence: uint64, data: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}") codeHandle = clientState.codeHandle() - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the provided commitment has been stored - assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, hash(data), proof)) + assert(codeHandle.verifyPacketData(clientState, height, prefix, proof, portportIdentifier, channelIdentifier, sequence, data)) } function verifyPacketAcknowledgement( @@ -284,13 +261,8 @@ function verifyPacketAcknowledgement( channelIdentifier: Identifier, sequence: uint64, acknowledgement: bytes) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}") codeHandle = clientState.codeHandle() - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the provided acknowledgement has been stored - assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, hash(acknowledgement), proof)) + assert(codeHandle.verifyPacketAcknowledgement(clientState, height, prefix, proof, portportIdentifier, channelIdentifier, sequence, acknowledgement)) } function verifyPacketReceiptAbsence( @@ -301,13 +273,8 @@ function verifyPacketReceiptAbsence( portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/receipts/{sequence}") codeHandle = clientState.codeHandle() - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that no acknowledgement has been stored - assert(root.verifyNonMembership(codeHandle.getProofSpec(clientState), path, proof)) + assert(codeHandle.verifyPacketReceiptAbsence(clientState, height, prefix, proof, portIdentifier, channelIdentifier, sequence)) } function verifyNextSequenceRecv( @@ -318,13 +285,8 @@ function verifyNextSequenceRecv( portIdentifier: Identifier, channelIdentifier: Identifier, nextSequenceRecv: uint64) { - path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv") codeHandle = clientState.codeHandle() - assert(codeHandle.isValidClientState(clientState, height)) - // fetch the previously verified commitment root & verify membership - root = get("clients/{identifier}/consensusStates/{height}") - // verify that the nextSequenceRecv is as claimed - assert(root.verifyMembership(codeHandle.getProofSpec(clientState), path, nextSequenceRecv, proof)) + assert(codeHandle.verifyNextSequenceRecv(clientState, height, prefix, proof, portIdentifier, channelIdentifier, nextSequenceRecv)) } ``` From ecb602fe8cd89c983999b387ede153073e3a2b6e Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Thu, 9 Sep 2021 11:01:52 +0200 Subject: [PATCH 10/12] rename epoch{number,height} to revision{number, height} --- spec/client/ics-008-wasm-client/README.md | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/client/ics-008-wasm-client/README.md b/spec/client/ics-008-wasm-client/README.md index 521143b8c..7efc31e6e 100644 --- a/spec/client/ics-008-wasm-client/README.md +++ b/spec/client/ics-008-wasm-client/README.md @@ -68,12 +68,12 @@ interface ConsensusState { ### Height -The height of a Wasm light client instance consists of two `uint64`s: the epoch number, and the height in the epoch. +The height of a Wasm light client instance consists of two `uint64`s: the revision number, and the height in the revision. ```typescript interface Height { - epochNumber: uint64 - epochHeight: uint64 + revisionNumber: uint64 + revisionHeight: uint64 } ``` @@ -81,18 +81,18 @@ Comparison between heights is implemented as follows: ```typescript function compare(a: Height, b: Height): Ord { - if (a.epochNumber < b.epochNumber) + if (a.revisionNumber < b.revisionNumber) return LT - else if (a.epochNumber === b.epochNumber) - if (a.epochHeight < b.epochHeight) + else if (a.revisionNumber === b.revisionNumber) + if (a.revisionHeight < b.revisionHeight) return LT - else if (a.epochHeight === b.epochHeight) + else if (a.revisionHeight === b.revisionHeight) return EQ return GT } ``` -This is designed to allow the height to reset to `0` while the epoch number increases by one in order to preserve timeouts through zero-height upgrades. +This is designed to allow the height to reset to `0` while the revision number increases by one in order to preserve timeouts through zero-height upgrades. ### Headers @@ -152,13 +152,13 @@ Wasm client validity checking uses underlying Wasm Client code. If the provided ```typescript function checkValidityAndUpdateState( clientState: ClientState, - epoch: uint64, + revision: uint64, header: Header) { store = getStore("clients/{identifier}") codeHandle = clientState.codeHandle() // verify that provided header is valid and state is saved - assert(codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header, epoch)) + assert(codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header, revision)) } ``` @@ -178,7 +178,7 @@ function checkMisbehaviourAndUpdateState( ### Upgrades -The chain which this light client is tracking can elect to write a special pre-determined key in state to allow the light client to update its client state (e.g. with a new chain ID or epoch) in preparation for an upgrade. +The chain which this light client is tracking can elect to write a special pre-determined key in state to allow the light client to update its client state (e.g. with a new chain ID or revision) in preparation for an upgrade. As the client state change will be performed immediately, once the new client state information is written to the predetermined key, the client will no longer be able to follow blocks on the old chain, so it must upgrade promptly. @@ -321,7 +321,7 @@ pub struct MisbehaviourMessage { #[serde(rename_all = "snake_case")] pub struct CreateConsensusMessage { pub client_state: Vec, - pub epoch: u64, + pub revision: u64, pub height: u64 } From ae1fdad334eacfd4ec4fb054bccdf94da6e759f1 Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Thu, 9 Sep 2021 11:50:45 +0200 Subject: [PATCH 11/12] rephrase misbehaviour description --- spec/client/ics-008-wasm-client/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/client/ics-008-wasm-client/README.md b/spec/client/ics-008-wasm-client/README.md index 7efc31e6e..e5794bce4 100644 --- a/spec/client/ics-008-wasm-client/README.md +++ b/spec/client/ics-008-wasm-client/README.md @@ -108,7 +108,7 @@ interface Header { ### Misbehaviour The `Misbehaviour` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable. -Wasm client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid. +Wasm client `Misbehaviour` consists of two conflicting headers both of which the light client would have considered valid. ```typescript interface Misbehaviour { From 4d2787b659b49388401883804561f736594362b4 Mon Sep 17 00:00:00 2001 From: Kaczanowski Mateusz Date: Thu, 7 Oct 2021 09:37:00 +0200 Subject: [PATCH 12/12] apply @AdityaSripal feedback --- spec/client/ics-008-wasm-client/README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/spec/client/ics-008-wasm-client/README.md b/spec/client/ics-008-wasm-client/README.md index e5794bce4..340369422 100644 --- a/spec/client/ics-008-wasm-client/README.md +++ b/spec/client/ics-008-wasm-client/README.md @@ -152,13 +152,12 @@ Wasm client validity checking uses underlying Wasm Client code. If the provided ```typescript function checkValidityAndUpdateState( clientState: ClientState, - revision: uint64, header: Header) { store = getStore("clients/{identifier}") codeHandle = clientState.codeHandle() // verify that provided header is valid and state is saved - assert(codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header, revision)) + assert(codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header)) } ``` @@ -213,7 +212,7 @@ function verifyClientConsensusState( consensusStateHeight: Height, consensusState: ConsensusState) { codeHandle = getCodeHandleFromClientID(clientIdentifier) - assert(codeHandle.isValidClientState(clientState, height, prefix, clientIdentifier, proof, consensusStateHeight, consensusState)) + assert(codeHandle.verifyClientConsensusState(clientState, height, prefix, clientIdentifier, proof, consensusStateHeight, consensusState)) } function verifyConnectionState( @@ -236,10 +235,10 @@ function verifyChannelState( channelIdentifier: Identifier, channelEnd: ChannelEnd) { codeHandle = clientState.codeHandle() - assert(codeHandle.verifyChannelState(clientState, height, prefix, proof, porttIdentifier, channelIdentifier, channelEnd)) + assert(codeHandle.verifyChannelState(clientState, height, prefix, proof, portIdentifier, channelIdentifier, channelEnd)) } -function verifyPacketData( +function verifyPacketCommitment( clientState: ClientState, height: Height, prefix: CommitmentPrefix, @@ -247,9 +246,9 @@ function verifyPacketData( portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64, - data: bytes) { + commitment: bytes) { codeHandle = clientState.codeHandle() - assert(codeHandle.verifyPacketData(clientState, height, prefix, proof, portportIdentifier, channelIdentifier, sequence, data)) + assert(codeHandle.verifyPacketCommitment(clientState, height, prefix, proof, portIdentifier, channelIdentifier, sequence, commitment)) } function verifyPacketAcknowledgement( @@ -312,7 +311,7 @@ Every Wasm client code need to support ingestion of below messages in order to b pub struct MisbehaviourMessage { pub client_state: Vec, pub consensus_state: Vec, - pub height: u64, + pub height: Height, pub header1: Vec, pub header2: Vec, } @@ -321,8 +320,7 @@ pub struct MisbehaviourMessage { #[serde(rename_all = "snake_case")] pub struct CreateConsensusMessage { pub client_state: Vec, - pub revision: u64, - pub height: u64 + pub height: Height } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -345,7 +343,7 @@ pub enum HandleMsg { #[serde(rename_all = "snake_case")] pub struct ValidateClientStateMessage { client_state: Vec, - height: u64 + height: Height } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -353,7 +351,7 @@ pub struct ValidateClientStateMessage { pub struct ValidateNewClientStateMessage { client_state: Vec, new_client_state: Vec, - height: u64 + height: Height } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]