-
Notifications
You must be signed in to change notification settings - Fork 387
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
ICS 28: WASM Client #571
ICS 28: WASM Client #571
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for taking awhile to review this. Besides the nits, my substantive comment is that it would be better to allow the WASM code to read/write state and specify all the datatypes rather than partially specifying them here, I think.
|
||
## 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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it? The chain hosting the client should not be exposed to any correctness risk, only users who use the client
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't.
I've rephrased this:
This specification depends on the correct instantiation of the `Wasm client` and is decoupled from any specific implementation of the target `blockchain` consensus algorithm.
@cwgoes ^^ does that look good to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cwgoes bumping the question :)
|
||
### 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please capitalise WASM consistently (or not, but pick one)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe "Wasm" (not WASM or wasm) is the preferred abbreviation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then let's use that consistently :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"WASM" has been replaced with "Wasm" across the ICS. I think we can resolve this thread :)
`type` and `codeId` both are immutable. | ||
|
||
```typescript | ||
interface ClientState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't this whole type be up to the WASM client code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cwgoes That was the initial idea.
ClientState
, ConsensusState
and Misbehaviour
interfaces have some functions like GetProofSpec
, IsFrozen
, Validate
which do not give access to store and/or are being called during tx creation on client side. In both cases we do not have access to the WASM VM. But, we still have to return valid answer as per semantics of that function and to do that we need to have some structure instead of opaque blob of byte only understood by WASM code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point - if the spec defines data structures that must be validated without any access to storage of the VM, then these types must be understood by the Go code.
However, this is in the spec level and I wonder if "In both cases we do not have access to the WASM VM." is obvious in the spec or an implementation detail we could work to improve.
Could you link the functions that require inspection of these types, so they cannot be defined in Wasm. It might make sense to revisit those definitions with @cwgoes and see if we can make them more flexible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that's an excellent point, but I think we may want to alter the structure of IBC or whatever code is involved here so that the VM can be accessed - sounds like we're on the same page @ethanfrey.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cwgoes @ethanfrey These are methods without access to storage:
Client State (link):
ClientType() string
GetLatestHeight() Height
Validate() error
GetProofSpecs() []*ics23.ProofSpec
ZeroCustomFields() ClientState
Changes (with the latest IBC updates, some things can be removed, see comments):
interface ClientState {
codeId: []byte
data: []byte
frozen: boolean // to be removed (deprecated, in favor of "Status" method, that has access to storage)
frozen_height: Height // ^^ ditto
latest_height: Height
proof_specs: ProofSpec // not listed, but should be here
type: String // likely to be removed (we'll return "10-wasm" client type instead) - I'll explain in another comment
}
After:
interface ClientState {
codeId: []byte
data: []byte
latest_height: Height
proof_specs: ProofSpec // not listed, but should be here
}
Execution context (who is the caller? is there a WASM VM available?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed list of those methods.
ProofSpecs
is static over the lifetime of the client and there may be somewhere to include this in code not in state.
LatestHeight
obviously requires a query to the KV store. How does the current Go code handle returning this without storage access?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @ParthDesai and @mkaczanowski for the explanations. In principle, I'm ok with breaking any Go APIs that are not expected by counterparty chains or relayers if it will help make WASM and future client code more clean.
ClientType() string
: Agreed that you should just be returning 10-wasm
here.
Validate() error
: Since this is getting used in messages, it has to be stateless. Any additional checks you want to do with state must be done elsewhere. We could rename this to ValidateBasic
to emphasize this.
ZeroCustomFields
: This is just used for upgrades, we can provide store here if necessary.
Interfaces that may be expected by counterparty chains
GetProofSpecs
: The proof specs get used in the client Verify
functions. This of course doesn't require an external interface, in face we just use the clientstate field directly. However, it also gets used on connection handshake to ensure that the counterparty client is using the correct proof specs: here. Currently, it is not using the interface itself but rather getting the ProofSpecs
field directly because we are making an assumption that all counterparty clients for an SDK chain are 07-tendermint
clients. However, once WASM
client is introduced we need to remove that assumption since we could have a wasm smart contract client tracking the SDK chain (this could be better than 07-tendermint since it may have easier upgrade logic). So once that happens, we will have to rely on the interface itself. We cannot give you access to the store here, since this is getting called by the counterparty chain, which does not contain the store for your light client. You will need to find a way to provide this information in the client state struct itself.
GetLatestHeight() Height
: Similar to the ProofSpec
case, we check that the latest height of the counterparty client is not greater than our height during connection handshake here. Again we currently use the field directly, but will have to switch to using an interface.
We don't strictly require the interface, seeing as we could just execute separate ValidateSelfClient
logic for wasm/go clients, however the wasm client must still have proof specs and latest height directly retrievable from its client state in order to perform all necessary checks on connection handshake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LatestHeight obviously requires a query to the KV store. How does the current Go code handle returning this without storage access?
We "cache" the latest height directly into the client state on update: https://github.com/cosmos/ibc-go/blob/2e95805e2d/modules/light-clients/07-tendermint/types/update.go#L251
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @AdityaSripal for such a detailed response
however the wasm client must still have proof specs and latest height directly retrievable from its client state in order to perform all necessary checks on connection handshake.
We'll update the ICS with:
interface ClientState {
codeId: []byte
data: []byte
latestHeight: Height
proofSpecs: []ProofSpec
}
ZeroCustomFields: This is just used for upgrades, we can provide a store here if necessary.
Since the method intends to reset some fields in the ClientState, I don't think we need storage access. However, possibly changing the interface to allow returning the error
would be helpful. Please see the initial implementation:
https://github.com/ChorusOne/ibc-go/blob/ics_28_wasm_client/modules/light-clients/10-wasm/types/client_state.go#L228
what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated the ClientState
interface here
`type` and `codeId` both are immutable. | ||
|
||
```typescript | ||
interface ConsensusState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto, same question, why can't this all be up to the WASM code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cwgoes See my previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous response
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are methods without access to storage:
Consensus State (link):
ClientType() string
GetRoot() Root
GetTimestamp() uint64
ValidateBasic() error
Execution context (who is the caller? is there a WASM VM available?)
- GetRoot() - ibc-go doesn't seem to be called anywhere
- GetTimestamp() - here we don't have VM access
- ValidateBasic() - one place where there is no kv access
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetRoot() Root
: This is only getting used in internal client logic, we could remove it altogether if desired.
GetTimestamp() uint64
: We need the timestamp of the consensus state to process packet timeouts. We can give you the client store here. The function you posted is merely a convenience, we could use the local time instead.
ValidateBasic() error
: Again, this must be a stateless check. You can do very basic checks (e.g. codeId not empty), or even a no-op, and perform your stateful checks later on in message execution when you have access to the store.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetRoot() Root: This is only getting used in internal client logic, we could remove it altogether if desired.
Yes, if GetRoot
is removed from the interface, then there is no need for us to require root: MerkleRoot
field anymore.
GetTimestamp() uint64: We need the timestamp of the consensus state to process packet timeouts. We can give you the client store here. The function you posted is merely a convenience, we could use the local time instead.
Great, then we can modify the function modules/apps/transfer/client/cli/tx.go
and the only place where GetTimeout()
is called is 03-connection/keeper/keeper.go and here where we have access to the VM.
To pull the timestamp from the Wasm light client we would have to:
- call the VM, which may fail. The current interface
GetTimestamp() uint64
doesn't allow to return an error, so would it be alright to change the signature toGetTimestamp() (uint64, error)
? ConsensusState
doesn't holdcodeId
that is required to call the WasmVM. So far, it was only stored byClientState
, so here are the possible options:- change the signature
ConsensusState.GetTimestamp() uint64
->ConsensusState.GetTimestamp(cs ClientState) uint64
but that looks weird - add the
codeId
field toConsensusState
- other options?
- change the signature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated ConsensusState
here but this assumes that GetRoot()
is removed from the current interface.
WASM client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid. | ||
|
||
```typescript | ||
interface Misbehaviour { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't this be up to the WASM code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cwgoes See my previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous response
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are methods without access to the storage:
Misbehaviour (link):**
ClientType() string
GetClientID() string
ValidateBasic() error
This seems to be a stale definition:
interface Misbehaviour {
fromHeight: Height // doesn't seem to be used
client_id: string, // missing, used by GetClientID()
h1: Header
h2: Header
}
More correct version:
interface Misbehaviour {
client_id: string,
h1: Header
h2: Header
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the two headers, we should be able to compare the headers for misbehavior without storage access, just code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that sounds right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the two headers, we should be able to compare the headers for misbehavior without storage access, just code.
Agreed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated Misbehaviour
here
header: Header) { | ||
codeHandle = clientState.codeHandle() | ||
isValid, consensusState = codeHandle.validateHeaderAndCreateConsensusState(clientState, header, epoch) | ||
set("clients/{identifier}/consensusStates/{header.height}", consensusState) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we give the WASM code limited state read/write access instead, it would be more flexible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree completely. Since you are using wasmvm, it would not be hard to give each contract a sandboxed storage area, like we do in x/wasm
. The contract knows what data it needs and can just store it locally.
However, it seems you actually need to write to some particular ibc keys here. Could you not just make a prefix store of clients/{identifier}
and pass that to the contract? It could then store client state under the empty key and consensus state under /consensusStates/{header.height}
. Meaning full access to the state it manages, but no access to other ibc state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ethanfrey @cwgoes I did not want WASM code read/write access to storage, because current version of WASM manager does not allow validation of the WASM code thoroughly. So, to err on safe side, I removed storage access to the WASM code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If wasm can return results that will be written to some specified storage locations, you could also provide the wasm code direct read/write access to only those storage locations.
I would assume wasm code is potentially malicious (even if this code must have been verified by governance to run), but you should be able to define a sandbox where it can work without impacting other modules (just it's own state). It is trusted to approve packets for a subset of clients/connections/channels, so should be able to write any data relative to those without increasing the attack surface at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean, "does not allow validation of the WASM code thoroughly"? That sounds dangerous. I would be far more worried about compute issues than storage issues, since storage usage is gas-metered and safely sub-store-constrained by the keeper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If wasm can return results that will be written to some specified storage locations, you could also provide the wasm code direct read/write access to only those storage locations.
Agreed. The WASM code handlers should just be given the client-id prefixed store. This is already what is given to the client handlers. Core IBC already prevents the ability for one client to read/write outside of its sandbox. So WASM can follow same security model. Just pass the store that the light client receives directly into WASM, it is already sandboxed by core IBC.
But who can define such a wasm code as a light client? This is out of scope for ICS repo maybe, but I feel essential to understand how this integrates in a blockchain, and what security guarantees we need.
Can I upload some wasm as light-client-foo and then make a new client/connection on top of it? Does governance need to approve it? If this code misbehaves what is the worst that can happen? (I assume fake packets in any channels built on top of this client).
We may rollout more conservatively, but I'm of the belief that the spec itself should enable random users uploading wasm light client code. This is a big selling point of WASM clients imo, since you don't need buy in from governance to support your eclectic choice of consensus algorithm.
Since IBC is designed to sandbox any issues, any malicious light client code submitted by user can only affect users of that light client. Though of course, they can do whatever they want to the users of their light client.
Here it will be incumbent on counterparty chains to enforce wasm contract correctness, by saying I will only accept a connection where the client has this smart contract code hash(es). This way we do not allow malicious clients to be submitted against correct chains. By creating the restriction on the destination chain rather than the source chain, we can enable all the flexibility of dynamic light clients while still preventing malicious connections over correctly implemented chains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though we should double check that 02-client
does not make any assumptions that something will be in the store in ways that will affect its overall functioning.
It's ok if the client becomes non-functional or downright malicious imo.
What would be bad, is if deleting something (like say the clientstate itself), causes the client handler to panic and shutdown the node. In case, the wasm contract does something like this, we should just return an error and fail th tx whenever client state is needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just pass the store that the light client receives directly into WASM, it is already sandboxed by core IBC.
In the initial implementation we already do that, this is, use the default flow where 02-client/keeper/client.go
gets client-id prefixed store from ClientStore(), and passes it on, to the client handle.
@AdityaSripal @cwgoes I am not sure how to phrase it (could use your help here), but here's a try:
store = getStore("clients/{identifier}")
isValid, consensusState = codeHandle.validateHeaderAndCreateConsensusState(store, clientState, header, epoch)
store.set("/consensusStates/{header.height}", consensusState)
// save the client
store.set("", clientState)
We may rollout more conservatively, but I'm of the belief that the spec itself should enable random users uploading wasm light client code. This is a big selling point of WASM clients imo, since you don't need buy in from governance to support your eclectic choice of consensus algorithm.
Agreed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so what is needed beyond what is already available in the implementation. If anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To let the light-clients update their state, IBC interface would have to change slightly (it's going to be covered by #569).
For example:
CheckHeaderAndUpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, Header) (ClientState, ConsensusState, error)
to smth like this:
CheckHeaderAndUpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, Header) error
because the {client,consensus} states are going to be updated within CheckHeaderAndUpdateState
lightclient method
consensusState = get("clients/{identifier}/consensusStates/{misbehaviour.fromHeight}") | ||
assert(codeHandle.handleMisbehaviour(clientState, consensusState, misbehaviour)) | ||
// save the client | ||
set("clients/{identifier}", clientState) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto, can we give the WASM code limited state read/write access instead, it would be more flexible
@ParthDesai Bump on this, any thoughts on my comments? Anything I should clarify? |
Co-authored-by: Christopher Goes <[email protected]>
@cwgoes Replied. Please check and let me know. |
Co-authored-by: Christopher Goes <[email protected]>
…add-ibc-wasm-client
Responded! |
|
||
```typescript | ||
interface Header { | ||
data: []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods that don't have access to storage
Header (link)
ClientType() string
GetHeight() Height
ValidateBasic() error
This definition looks stale. It should be:
interface Header {
data: []byte,
height: Height, // required by GetHeight()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto on ValidateBasic
I'm a bit hesitant to make GetHeight
stateful here since it gets used in ValidateBasic checks and without it the ValidateBasic isn't doing much at all. If you could clarify why you need access to storage, and the difficulties with keeping it stateless that would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AdityaSripal sorry I phrased it wrongly. The storage is not needed here, but I wanted to give some context as to why the height: Height
is a separate field (not a part of data
), this is during the UpdateClient call the GetHeight()
is required to set the consensus state.
That's the only significant use case of GetHeight
I found
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated Header
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right but I'm saying ValidateBasic
should be doing some check on Height
and because that is stateless, there should be a stateless access to Height here as well even if not strictly used directly in code. cc: @colin-axner what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to interject without reading through all the context, but I think GetHeight()
will be removed soonish. In a couple months I'd like to start work on #569 which would move setting of IBC client information to light client implementations. This would mean the consensus state would be set by the light client, which could directly cast to its Header definition as opposed to using the header interface. The header interface would only be really used to pass the header from the user message to 02-client and finally to the IBC client (which would not require GetHeight()
usage)
I'm not really sure what this means for the decision being discussed, but whatever is decided should be fine since it'll likely be short lived. It sounds like GetHeight
will still be stateless in the short term? The height
field would just be removed from data once GetHeight
is removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pinging me on this. I'd like to confirm why storage is needed in these client interfaces from WASM.
From my understanding, this information would be in data
but you would need to retrieve the smart contract code from WASM store in order to parse it and return the value. If you can't retrieve code, then you will have to store directly in client state so that go handlers can implement the interfaces. Is that correct? If not, please clarify why WASM client needs storage to implement these interfaces.
In general, I'm open to changing the interfaces for ClientState
and ConsensusState
since those are mostly created and stored directly on-chain, and the GO apis can change.
I'm less comfortable changing the Header
and Misbehaviour
interfaces, since they are very client facing and so having some stateless interfaces is useful here.
I also believe you should just be passing the client-prefixed store into the wasm handlers. I don't see any security risk there. It's already an assumption of core IBC that clients may do arbitrary things to their own client-prefixed store.
`type` and `codeId` both are immutable. | ||
|
||
```typescript | ||
interface ClientState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @ParthDesai and @mkaczanowski for the explanations. In principle, I'm ok with breaking any Go APIs that are not expected by counterparty chains or relayers if it will help make WASM and future client code more clean.
ClientType() string
: Agreed that you should just be returning 10-wasm
here.
Validate() error
: Since this is getting used in messages, it has to be stateless. Any additional checks you want to do with state must be done elsewhere. We could rename this to ValidateBasic
to emphasize this.
ZeroCustomFields
: This is just used for upgrades, we can provide store here if necessary.
Interfaces that may be expected by counterparty chains
GetProofSpecs
: The proof specs get used in the client Verify
functions. This of course doesn't require an external interface, in face we just use the clientstate field directly. However, it also gets used on connection handshake to ensure that the counterparty client is using the correct proof specs: here. Currently, it is not using the interface itself but rather getting the ProofSpecs
field directly because we are making an assumption that all counterparty clients for an SDK chain are 07-tendermint
clients. However, once WASM
client is introduced we need to remove that assumption since we could have a wasm smart contract client tracking the SDK chain (this could be better than 07-tendermint since it may have easier upgrade logic). So once that happens, we will have to rely on the interface itself. We cannot give you access to the store here, since this is getting called by the counterparty chain, which does not contain the store for your light client. You will need to find a way to provide this information in the client state struct itself.
GetLatestHeight() Height
: Similar to the ProofSpec
case, we check that the latest height of the counterparty client is not greater than our height during connection handshake here. Again we currently use the field directly, but will have to switch to using an interface.
We don't strictly require the interface, seeing as we could just execute separate ValidateSelfClient
logic for wasm/go clients, however the wasm client must still have proof specs and latest height directly retrievable from its client state in order to perform all necessary checks on connection handshake.
`type` and `codeId` both are immutable. | ||
|
||
```typescript | ||
interface ClientState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LatestHeight obviously requires a query to the KV store. How does the current Go code handle returning this without storage access?
We "cache" the latest height directly into the client state on update: https://github.com/cosmos/ibc-go/blob/2e95805e2d/modules/light-clients/07-tendermint/types/update.go#L251
`type` and `codeId` both are immutable. | ||
|
||
```typescript | ||
interface ConsensusState { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetRoot() Root
: This is only getting used in internal client logic, we could remove it altogether if desired.
GetTimestamp() uint64
: We need the timestamp of the consensus state to process packet timeouts. We can give you the client store here. The function you posted is merely a convenience, we could use the local time instead.
ValidateBasic() error
: Again, this must be a stateless check. You can do very basic checks (e.g. codeId not empty), or even a no-op, and perform your stateful checks later on in message execution when you have access to the store.
|
||
```typescript | ||
interface Header { | ||
data: []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto on ValidateBasic
I'm a bit hesitant to make GetHeight
stateful here since it gets used in ValidateBasic checks and without it the ValidateBasic isn't doing much at all. If you could clarify why you need access to storage, and the difficulties with keeping it stateless that would be helpful.
WASM client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid. | ||
|
||
```typescript | ||
interface Misbehaviour { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the two headers, we should be able to compare the headers for misbehavior without storage access, just code.
Agreed.
header: Header) { | ||
codeHandle = clientState.codeHandle() | ||
isValid, consensusState = codeHandle.validateHeaderAndCreateConsensusState(clientState, header, epoch) | ||
set("clients/{identifier}/consensusStates/{header.height}", consensusState) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If wasm can return results that will be written to some specified storage locations, you could also provide the wasm code direct read/write access to only those storage locations.
Agreed. The WASM code handlers should just be given the client-id prefixed store. This is already what is given to the client handlers. Core IBC already prevents the ability for one client to read/write outside of its sandbox. So WASM can follow same security model. Just pass the store that the light client receives directly into WASM, it is already sandboxed by core IBC.
But who can define such a wasm code as a light client? This is out of scope for ICS repo maybe, but I feel essential to understand how this integrates in a blockchain, and what security guarantees we need.
Can I upload some wasm as light-client-foo and then make a new client/connection on top of it? Does governance need to approve it? If this code misbehaves what is the worst that can happen? (I assume fake packets in any channels built on top of this client).
We may rollout more conservatively, but I'm of the belief that the spec itself should enable random users uploading wasm light client code. This is a big selling point of WASM clients imo, since you don't need buy in from governance to support your eclectic choice of consensus algorithm.
Since IBC is designed to sandbox any issues, any malicious light client code submitted by user can only affect users of that light client. Though of course, they can do whatever they want to the users of their light client.
Here it will be incumbent on counterparty chains to enforce wasm contract correctness, by saying I will only accept a connection where the client has this smart contract code hash(es). This way we do not allow malicious clients to be submitted against correct chains. By creating the restriction on the destination chain rather than the source chain, we can enable all the flexibility of dynamic light clients while still preventing malicious connections over correctly implemented chains.
@AdityaSripal, thanks a lot for the feedback!
Once the Wasm light client binary (aka.
And pass all required arguments to the Wasm method itself. Right now, the
Yes. If the code can't be retrieved (i.e. client code without VM access, no
As I am reading my comments, I noticed the "storage" term is a bit unclear. Or at least I made it somewhat confusing, sorry. I referred to "needs storage" as "needs access to Wasm code identified by So for example,
Hopefully, that clarifies it a bit. |
update data structures
@cwgoes @AdityaSripal could you guys take another round on the comments + changes I've made? Thanks! |
|
||
```typescript | ||
interface Header { | ||
data: []byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right but I'm saying ValidateBasic
should be doing some check on Height
and because that is stateless, there should be a stateless access to Height here as well even if not strictly used directly in code. cc: @colin-axner what do you think?
header: Header) { | ||
codeHandle = clientState.codeHandle() | ||
isValid, consensusState = codeHandle.validateHeaderAndCreateConsensusState(clientState, header, epoch) | ||
set("clients/{identifier}/consensusStates/{header.height}", consensusState) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so what is needed beyond what is already available in the implementation. If anything?
|
||
`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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the wasm client code stored in the clientID-prefixed store provided by IBC or it it stored in a KV store owned by WASM?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the Wasm client code is stored in the KV store owned by 28-wasm module.
Example entry:
ibc/code_id/d6537444f6df8686bdfefc3dc42441cd1d332ac608d27ef5903dd083a537e85f
The updates look good to me 👍 |
I'm a little confused, do we not want the WASM code to be able to write state itself? I don't see changes to that effect. |
Thanks a lot for the comments! From what I can tell the last major thing to tackle in this ICS, is the question of whether Wasm code should be able to update its own client/consensus state, as pointed out by @cwgoes. As @colin-axner explained that upcoming changes are to give light clients such flexibility, so I need to update this ICS. I am not so fluent with pseudo-language, so here is what I came up with: @cwgoes @AdityaSripal can you please tell me if ^^ diff is alright? |
The diffs align with what I had in mind for the future implementation changes 👍 |
Yes, the store should be passed to the WASM code. |
Update ibc wasm client storage
@cwgoes @colin-axner @AdityaSripal I applied the remaining changes, plus renamed the ICS-28 to ICS-08 to match current ordering. Please let me know if you see anything else that requires changes, thanks! |
@cwgoes @colin-axner @AdityaSripal friendly ping. It would be great if you could go through this ICS one more time :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm basically fine with this, just checking - is the choice of having Go Merkle path validation (sub-state proofs) instead of letting WASM code do that intentional? It will constrain what WASM light clients can do, though it will probably be more efficient unless there are precompiles for hash functions or the like.
@cwgoes at first that's what we did (proof verification on the Go side), but as you said it's quite limiting.
it's not intentional, and thanks for catching this. Proposed changeset (please review)Ideally, the verify methods are just a pass-through to WASM code (example VerifyConnectionState method in the initial impl) So, here is the proposed changeset: please let me know if ^^ this looks fine to you |
These changes make a lot more sense to me 👍 This means the wasm client interface needs to be expanded as well? |
I am looking at this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!! Small nits to address before this is ready to merge
thanks @AdityaSripal , applied the requested changes |
Thanks @mkaczanowski , pending @cwgoes ack and then will merge |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK from me, thanks for the spec work!
Note: We will remove Since ref: cosmos/ics23#12 (comment) |
Reopened this PR: #506
cc: @AdityaSripal @hxrts @cwgoes