Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADR 027: Add support for WASM based light client #163

Merged
merged 5 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/OLD_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ consensus states in order to verify their membership in the counterparty clients
* [ADR 020 - Protocol Buffer Transaction Encoding](./../../docs/architecture/adr-020-protobuf-transaction-encoding.md): Client side migration to Protobuf.
* [ADR 021 - Protocol Buffer Query Encoding](../../../docs/architecture/adr-020-protobuf-query-encoding.md): Queries migration to Protobuf.
* [ADR 026 - IBC Client Recovery Mechanisms](../../../docs/architecture/adr-026-ibc-client-recovery-mechanisms.md): Allows IBC Clients to be recovered after freezing or expiry.
* [ADR 027 - IBC WASM Client](../../../docs/architecture/adr-027-ibc-wasm.md)

### SDK Modules

Expand Down
147 changes: 147 additions & 0 deletions docs/architecture/adr-027-ibc-wasm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# ADR 27: Add support for Wasm based light client

## Changelog

- 26/11/2020: Initial Draft

## Status

*Draft*

## Abstract

In the Cosmos SDK light clients are current hardcoded in Go. This makes upgrading existing IBC light clients or add
support for new light client is multi step process which is time-consuming.
fedekunze marked this conversation as resolved.
Show resolved Hide resolved

To remedy this, we are proposing a WASM VM to host light client bytecode, which allows easier upgrading of
existing IBC light clients as well as adding support for new IBC light clients without requiring a code release and corresponding
hard-fork event.

## Context
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great context section!!

Currently in the SDK, light clients are defined as part of the codebase and are implemented as submodules under
`/x/ibc/light-clients/`.
fedekunze marked this conversation as resolved.
Show resolved Hide resolved

Adding support for new light client or update an existing light client in the event of security
issue or consensus update is multi-step process which is both time consuming and error prone:

1. To add support for new light client or update an existing light client in the
event of security issue or consensus update, we need to modify the codebase and integrate it in numerous places.

2. Governance voting: Adding new light client implementations require governance support and is expensive: This is
not ideal as chain governance is gatekeeper for new light client implementations getting added. If a small community
want support for light client X, they may not be able to convince governance to support it.

3. Validator upgrade: After governance voting succeeds, validators need to upgrade their nodes in order to enable new
IBC light client implementation.

Another problem stemming from the above process is that if a chain wants to upgrade its own consensus, it will need to convince every chain
or hub connected to it to upgrade its light client in order to stay connected. Due to time consuming process required
to upgrade light client, a chain with lots of connections needs to be disconnected for quite some time after upgrading
its consensus, which can be very expensive in terms of time and effort.

We are proposing simplifying this workflow by integrating a WASM light client module which makes adding support for
a new light client a simple transaction. The light client bytecode, written in Wasm-compilable Rust, runs inside a WASM
VM. The Wasm light client submodule exposes a proxy light client interface that routes incoming messages to the
appropriate handler function, inside the Wasm VM for execution.

With WASM light client module, anybody can add new IBC light client in the form of WASM bytecode (provided they are able to pay the requisite gas fee for the transaction)
as well as instantiate clients using any created client type. This allows any chain to update its own light client in other chains
without going through steps outlined above.


## Decision

We decided to use WASM light client module as a light client proxy which will interface with the actual light client
uploaded as WASM bytecode. This will require changing client selection method to allow any client if the client type
has prefix of `wasm/`.

```go
// IsAllowedClient checks if the given client type is registered on the allowlist.
func (p Params) IsAllowedClient(clientType string) bool {
if p.AreWASMClientsAllowed && isWASMClient(clientType) {
return true
}

for _, allowedClient := range p.AllowedClients {
if allowedClient == clientType {
return true
}
}

return false
}
```

To upload new light client, user need to create a transaction with Wasm byte code which will be
processed by IBC Wasm module.

```go
func (k Keeper) UploadLightClient (wasmCode: []byte, description: String) {
wasmRegistry = getWASMRegistry()
id := hex.EncodeToString(sha256.Sum256(wasmCode))
assert(!wasmRegistry.Exists(id))
assert(wasmRegistry.ValidateAndStoreCode(id, description, wasmCode, false))
}
```

As name implies, Wasm registry is a registry which stores set of Wasm client code indexed by its hash and allows
client code to retrieve latest code uploaded.

`ValidateAndStoreCode` checks if the wasm bytecode uploaded is valid and confirms to VM interface.

### How light client proxy works?

The light client proxy behind the scenes will call a cosmwasm smart contract instance with incoming arguments in json
serialized format with appropriate environment information. Data returned by the smart contract is deserialized and
returned to the caller.

Consider an example of `CheckProposedHeaderAndUpdateState` function of `ClientState` interface. Incoming arguments are
packaged inside a payload which is json serialized and passed to `callContract` which calls `vm.Execute` and returns the
array of bytes returned by the smart contract. This data is deserialized and passed as return argument.

```go
func (c *ClientState) CheckProposedHeaderAndUpdateState(context sdk.Context, marshaler codec.BinaryMarshaler, store sdk.KVStore, header exported.Header) (exported.ClientState, exported.ConsensusState, error) {
// get consensus state corresponding to client state to check if the client is expired
consensusState, err := GetConsensusState(store, marshaler, c.LatestHeight)
if err != nil {
return nil, nil, sdkerrors.Wrapf(
err, "could not get consensus state from clientstore at height: %d", c.LatestHeight,
)
}

payload := make(map[string]map[string]interface{})
payload[CheckProposedHeaderAndUpdateState] = make(map[string]interface{})
inner := payload[CheckProposedHeaderAndUpdateState]
inner["me"] = c
inner["header"] = header
inner["consensus_state"] = consensusState

encodedData, err := json.Marshal(payload)
if err != nil {
return nil, nil, sdkerrors.Wrapf(ErrUnableToMarshalPayload, fmt.Sprintf("underlying error: %s", err.Error()))
}
out, err := callContract(c.CodeId, context, store, encodedData)
if err != nil {
return nil, nil, sdkerrors.Wrapf(ErrUnableToCall, fmt.Sprintf("underlying error: %s", err.Error()))
}
output := clientStateCallResponse{}
if err := json.Unmarshal(out.Data, &output); err != nil {
return nil, nil, sdkerrors.Wrapf(ErrUnableToUnmarshalPayload, fmt.Sprintf("underlying error: %s", err.Error()))
}
if !output.Result.IsValid {
return nil, nil, fmt.Errorf("%s error ocurred while updating client state", output.Result.ErrorMsg)
}
output.resetImmutables(c)
return output.NewClientState, output.NewConsensusState, nil
}
```

## Consequences

### Positive
- Adding support for new light client or upgrading existing light client is way easier than before and only requires single transaction.
- Improves maintainability of Cosmos SDK, since no change in codebase is required to support new client or upgrade it.

### Negative
- Light clients need to be written in subset of rust which could compile in Wasm.
- Introspecting light client code is difficult as only compiled bytecode exists in the blockchain.