diff --git a/Cargo.lock b/Cargo.lock index ff829a740..467622f79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5180,6 +5180,7 @@ dependencies = [ "sp-runtime 7.0.0", "sp-runtime-interface 7.0.0", "sp-std 5.0.0", + "tendermint 0.28.0", "tendermint-proto 0.28.0", "thiserror", "twox-hash", diff --git a/contracts/pallet-ibc/src/client.rs b/contracts/pallet-ibc/src/client.rs index 177562c6d..c013659cd 100644 --- a/contracts/pallet-ibc/src/client.rs +++ b/contracts/pallet-ibc/src/client.rs @@ -30,7 +30,7 @@ pub struct HostConsensusProof { pub header: Vec, pub extrinsic: Vec, pub extrinsic_proof: Vec>, - pub code_id: Option>, + pub checksum: Option>, } impl ClientReader for Context @@ -284,7 +284,7 @@ where AnyConsensusState::wasm(cs).map_err(ICS02Error::encode)? }, _ => - if connection_proof.code_id.is_some() { + if connection_proof.checksum.is_some() { log::trace!(target: "pallet_ibc", "in client : [host_consensus_state] >> using wasm code id"); AnyConsensusState::wasm(cs).map_err(ICS02Error::encode)? } else { diff --git a/contracts/pallet-ibc/src/events.rs b/contracts/pallet-ibc/src/events.rs index b32391396..8071b78dd 100644 --- a/contracts/pallet-ibc/src/events.rs +++ b/contracts/pallet-ibc/src/events.rs @@ -4,7 +4,7 @@ use ibc::{ core::{ ics02_client::{ events as ClientEvents, - events::{CodeId, NewBlock}, + events::{Checksum, NewBlock}, }, ics03_connection::events as ConnectionEvents, ics04_channel::{events as ChannelEvents, packet::Packet}, @@ -218,7 +218,7 @@ pub enum IbcEvent { /// App module AppModule { kind: Vec, module_id: Vec }, /// Push WASM Code - PushWasmCode { wasm_code_id: CodeId }, + PushWasmCode { wasm_checksum: Checksum }, } impl From for IbcEvent { @@ -439,8 +439,8 @@ impl From for IbcEvent { module_id: ev.module_name.to_string().as_bytes().to_vec(), }, RawIbcEvent::PushWasmCode(ev) => { - let wasm_code_id = ev.0; - IbcEvent::PushWasmCode { wasm_code_id } + let wasm_checksum = ev.0; + IbcEvent::PushWasmCode { wasm_checksum } }, } } @@ -545,8 +545,8 @@ impl TryFrom for RawIbcEvent { consensus_height: Height::new(consensus_revision_number, consensus_height), }, ))), - IbcEvent::PushWasmCode { wasm_code_id } => - Ok(RawIbcEvent::PushWasmCode(ClientEvents::PushWasmCode(wasm_code_id))), + IbcEvent::PushWasmCode { wasm_checksum } => + Ok(RawIbcEvent::PushWasmCode(ClientEvents::PushWasmCode(wasm_checksum))), IbcEvent::OpenInitConnection { revision_height, revision_number, diff --git a/contracts/pallet-ibc/src/light_clients.rs b/contracts/pallet-ibc/src/light_clients.rs index 66f97d4ea..60a0ba748 100644 --- a/contracts/pallet-ibc/src/light_clients.rs +++ b/contracts/pallet-ibc/src/light_clients.rs @@ -3,15 +3,11 @@ use frame_support::{ pallet_prelude::{StorageValue, ValueQuery}, traits::StorageInstance, }; -use ibc::{ - core::{ - ics02_client, - ics02_client::{ - client_consensus::ConsensusState, client_message::ClientMessage, - client_state::ClientState, - }, +use ibc::core::{ + ics02_client, + ics02_client::{ + client_consensus::ConsensusState, client_message::ClientMessage, client_state::ClientState, }, - Height, }; use ibc_derive::{ClientDef, ClientMessage, ClientState, ConsensusState, Protobuf}; use ibc_primitives::runtime_interface; @@ -25,12 +21,8 @@ use ics07_tendermint::{ consensus_state::TENDERMINT_CONSENSUS_STATE_TYPE_URL, }; use ics08_wasm::{ - client_message::{ - WASM_CLIENT_MESSAGE_TYPE_URL, WASM_HEADER_TYPE_URL, WASM_MISBEHAVIOUR_TYPE_URL, - }, - client_state::WASM_CLIENT_STATE_TYPE_URL, - consensus_state::WASM_CONSENSUS_STATE_TYPE_URL, - Bytes, + client_message::WASM_CLIENT_MESSAGE_TYPE_URL, client_state::WASM_CLIENT_STATE_TYPE_URL, + consensus_state::WASM_CONSENSUS_STATE_TYPE_URL, Bytes, }; use ics10_grandpa::{ client_message::{ @@ -279,13 +271,13 @@ impl AnyClientState { } impl AnyClientState { - pub fn wasm(inner: Self, code_id: Bytes) -> Result { + pub fn wasm(inner: Self, checksum: Bytes) -> Result { Ok(Self::Wasm( ics08_wasm::client_state::ClientState:: { data: inner.encode_to_vec()?, latest_height: inner.latest_height(), inner: Box::new(inner), - code_id, + checksum, _phantom: Default::default(), }, )) @@ -310,7 +302,6 @@ pub enum AnyConsensusState { impl AnyConsensusState { pub fn wasm(inner: Self) -> Result { Ok(Self::Wasm(ics08_wasm::consensus_state::ConsensusState { - timestamp: inner.timestamp().nanoseconds(), data: inner.encode_to_vec()?, inner: Box::new(inner), })) @@ -334,69 +325,16 @@ pub enum AnyClientMessage { } impl AnyClientMessage { - pub fn maybe_header_height(&self) -> Option { - match self { - Self::Tendermint(inner) => match inner { - ics07_tendermint::client_message::ClientMessage::Header(h) => Some(h.height()), - ics07_tendermint::client_message::ClientMessage::Misbehaviour(_) => None, - }, - Self::Beefy(inner) => match inner { - ics11_beefy::client_message::ClientMessage::Header(_) => - unimplemented!("beefy header height"), - ics11_beefy::client_message::ClientMessage::Misbehaviour(_) => None, - }, - Self::Grandpa(inner) => match inner { - ics10_grandpa::client_message::ClientMessage::Header(h) => Some(h.height()), - ics10_grandpa::client_message::ClientMessage::Misbehaviour(_) => None, - }, - Self::Wasm(inner) => match inner { - ics08_wasm::client_message::ClientMessage::Header(h) => - h.inner.maybe_header_height(), - ics08_wasm::client_message::ClientMessage::Misbehaviour(_) => None, - }, - #[cfg(test)] - Self::Mock(inner) => match inner { - ibc::mock::header::MockClientMessage::Header(h) => Some(h.height()), - ibc::mock::header::MockClientMessage::Misbehaviour(_) => None, - }, - } - } - pub fn wasm(inner: Self) -> Result { - let maybe_height = inner.maybe_header_height(); - Ok(match maybe_height { - Some(height) => Self::Wasm(ics08_wasm::client_message::ClientMessage::Header( - ics08_wasm::client_message::Header { - data: inner.encode_to_vec()?, - height, - inner: Box::new(inner), - }, - )), - None => Self::Wasm(ics08_wasm::client_message::ClientMessage::Misbehaviour( - ics08_wasm::client_message::Misbehaviour { - data: inner.encode_to_vec()?, - inner: Box::new(inner), - }, - )), - }) - } - - pub fn unpack_recursive(&self) -> &Self { - match self { - Self::Wasm(ics08_wasm::client_message::ClientMessage::Header(h)) => - h.inner.unpack_recursive(), - Self::Wasm(ics08_wasm::client_message::ClientMessage::Misbehaviour(m)) => - m.inner.unpack_recursive(), - _ => self, - } + Ok(Self::Wasm(ics08_wasm::client_message::ClientMessage { + data: inner.encode_to_vec()?, + inner: Box::new(inner), + })) } pub fn unpack_recursive_into(self) -> Self { match self { - Self::Wasm(ics08_wasm::client_message::ClientMessage::Header(h)) => - h.inner.unpack_recursive_into(), - Self::Wasm(ics08_wasm::client_message::ClientMessage::Misbehaviour(m)) => - m.inner.unpack_recursive_into(), + Self::Wasm(ics08_wasm::client_message::ClientMessage { inner, data }) => *inner, _ => self, } } @@ -446,16 +384,6 @@ impl TryFrom for AnyClientMessage { ics08_wasm::client_message::ClientMessage::decode_vec(&value.value) .map_err(ics02_client::error::Error::decode_raw_header)?, )), - WASM_HEADER_TYPE_URL => - Ok(Self::Wasm(ics08_wasm::client_message::ClientMessage::Header( - ics08_wasm::client_message::Header::decode_vec(&value.value) - .map_err(ics02_client::error::Error::decode_raw_header)?, - ))), - WASM_MISBEHAVIOUR_TYPE_URL => - Ok(Self::Wasm(ics08_wasm::client_message::ClientMessage::Misbehaviour( - ics08_wasm::client_message::Misbehaviour::decode_vec(&value.value) - .map_err(ics02_client::error::Error::decode_raw_header)?, - ))), _ => Err(ics02_client::error::Error::unknown_consensus_state_type(value.type_url)), } } @@ -464,15 +392,9 @@ impl TryFrom for AnyClientMessage { impl From for Any { fn from(client_msg: AnyClientMessage) -> Self { match client_msg { - AnyClientMessage::Wasm(msg) => match msg { - ics08_wasm::client_message::ClientMessage::Header(h) => Any { - type_url: WASM_HEADER_TYPE_URL.to_string(), - value: h.encode_vec().expect("encode_vec failed"), - }, - ics08_wasm::client_message::ClientMessage::Misbehaviour(m) => Any { - type_url: WASM_MISBEHAVIOUR_TYPE_URL.to_string(), - value: m.encode_vec().expect("encode_vec failed"), - }, + AnyClientMessage::Wasm(msg) => Any { + type_url: WASM_CLIENT_MESSAGE_TYPE_URL.to_string(), + value: msg.encode_vec().expect("encode_vec failed"), }, AnyClientMessage::Grandpa(msg) => match msg { ics10_grandpa::client_message::ClientMessage::Header(h) => Any { diff --git a/hyperspace/core/src/chain.rs b/hyperspace/core/src/chain.rs index 243b85d8a..2517d8892 100644 --- a/hyperspace/core/src/chain.rs +++ b/hyperspace/core/src/chain.rs @@ -31,7 +31,7 @@ use ibc::{ core::{ ics02_client::{ client_state::ClientType, - events::{CodeId, UpdateClient}, + events::{Checksum, UpdateClient}, msgs::{create_client::MsgCreateAnyClient, update_client::MsgUpdateAnyClient}, }, ics03_connection::msgs::{ @@ -101,7 +101,7 @@ chains! { Cosmos(CosmosClientConfig, CosmosClient), } -fn wrap_any_msg_into_wasm(msg: Any, code_id: Bytes) -> Result { +fn wrap_any_msg_into_wasm(msg: Any, checksum: Bytes) -> Result { // TODO: consider rewriting with Ics26Envelope use ibc::core::{ ics02_client::msgs::{ @@ -119,7 +119,7 @@ fn wrap_any_msg_into_wasm(msg: Any, code_id: Bytes) -> Result::decode_vec(&msg.value).unwrap(); msg_decoded.consensus_state = AnyConsensusState::wasm(msg_decoded.consensus_state)?; - msg_decoded.client_state = AnyClientState::wasm(msg_decoded.client_state, code_id)?; + msg_decoded.client_state = AnyClientState::wasm(msg_decoded.client_state, checksum)?; msg_decoded.to_any() }, CONN_OPEN_TRY_TYPE_URL => { @@ -147,5 +147,5 @@ fn wrap_any_msg_into_wasm(msg: Any, code_id: Bytes) -> Result, - pub code_id: Bytes, + pub checksum: Bytes, } diff --git a/hyperspace/core/src/command.rs b/hyperspace/core/src/command.rs index bcd1cca34..eb4e4e4bb 100644 --- a/hyperspace/core/src/command.rs +++ b/hyperspace/core/src/command.rs @@ -105,10 +105,10 @@ impl UploadWasmCmd { let mut config: AnyConfig = toml::from_str(&file_content)?; let client = config.clone().into_client().await?; let wasm = tokio::fs::read(&self.wasm_path).await?; - let code_id = client.upload_wasm(wasm).await?; - let code_id_str = hex::encode(code_id); - println!("{code_id_str}"); - config.set_wasm_code_id(code_id_str); + let checksum = client.upload_wasm(wasm).await?; + let checksum_str = hex::encode(checksum); + println!("{checksum_str}"); + config.set_wasm_checksum(checksum_str); Ok(config) } diff --git a/hyperspace/core/src/macros.rs b/hyperspace/core/src/macros.rs index 528cf0c27..fff0ab73f 100644 --- a/hyperspace/core/src/macros.rs +++ b/hyperspace/core/src/macros.rs @@ -808,7 +808,7 @@ macro_rules! chains { Self::Wasm(chain) => { let messages = messages .into_iter() - .map(|msg| wrap_any_msg_into_wasm(msg, chain.code_id.clone())) + .map(|msg| wrap_any_msg_into_wasm(msg, chain.checksum.clone())) .collect::, _>>()?; chain.inner.submit(messages).await.map_err(AnyError::into) }, @@ -989,15 +989,15 @@ macro_rules! chains { impl AnyConfig { pub async fn into_client(self) -> anyhow::Result { - let maybe_wasm_code_id = self.wasm_code_id(); + let maybe_wasm_checksum = self.wasm_checksum(); let chain = match self { $( $(#[$($meta)*])* AnyConfig::$name(config) => AnyChain::$name(<$client>::new(config).await?), )* }; - if let Some(code_id) = maybe_wasm_code_id { - Ok(AnyChain::Wasm(WasmChain { inner: Box::new(chain), code_id })) + if let Some(checksum) = maybe_wasm_checksum { + Ok(AnyChain::Wasm(WasmChain { inner: Box::new(chain), checksum })) } else { Ok(chain) } @@ -1036,25 +1036,25 @@ macro_rules! chains { } } - pub fn wasm_code_id(&self) -> Option { - let maybe_code_id = match self { + pub fn wasm_checksum(&self) -> Option { + let maybe_checksum = match self { $( $(#[$($meta)*])* - Self::$name(chain) => chain.wasm_code_id.as_ref(), + Self::$name(chain) => chain.wasm_checksum.as_ref(), )* }; - let maybe_code_id = - maybe_code_id.map(|s| hex::decode(s).expect("Wasm code id is hex-encoded")); + let maybe_checksum = + maybe_checksum.map(|s| hex::decode(s).expect("Wasm checksum is hex-encoded")); - maybe_code_id + maybe_checksum } - pub fn set_wasm_code_id(&mut self, code_id: String) { + pub fn set_wasm_checksum(&mut self, checksum: String) { match self { $( $(#[$($meta)*])* Self::$name(chain) => { - chain.wasm_code_id = Some(code_id); + chain.wasm_checksum = Some(checksum); }, )* } diff --git a/hyperspace/core/src/substrate/macros.rs b/hyperspace/core/src/substrate/macros.rs index aad874a94..6265f5594 100644 --- a/hyperspace/core/src/substrate/macros.rs +++ b/hyperspace/core/src/substrate/macros.rs @@ -487,8 +487,8 @@ macro_rules! define_ibc_event_wrapper { RawIbcEvent::AppModule { kind, module_id }, MetadataIbcEvent::Empty => RawIbcEvent::Empty, MetadataIbcEvent::ChainError => RawIbcEvent::ChainError, - MetadataIbcEvent::PushWasmCode{ wasm_code_id } => RawIbcEvent::PushWasmCode { - wasm_code_id + MetadataIbcEvent::PushWasmCode{ wasm_checksum } => RawIbcEvent::PushWasmCode { + wasm_checksum }, $($additional)* } diff --git a/hyperspace/cosmos/src/client.rs b/hyperspace/cosmos/src/client.rs index 0fce2c50b..c49c7be68 100644 --- a/hyperspace/cosmos/src/client.rs +++ b/hyperspace/cosmos/src/client.rs @@ -212,7 +212,7 @@ pub struct CosmosClientConfig { pub max_tx_size: usize, /// All the client states and headers will be wrapped in WASM ones using the WASM code ID. #[serde(default)] - pub wasm_code_id: Option, + pub wasm_checksum: Option, /* Here is a list of dropped configuration parameters from Hermes Config.toml that could be set to default values or removed for the MVP phase: diff --git a/hyperspace/cosmos/src/events.rs b/hyperspace/cosmos/src/events.rs index 612c70b00..b4e71ef8a 100644 --- a/hyperspace/cosmos/src/events.rs +++ b/hyperspace/cosmos/src/events.rs @@ -211,17 +211,17 @@ pub fn client_misbehaviour_try_from_abci_event( pub fn push_wasm_code_try_from_abci_event( abci_event: &AbciEvent, ) -> Result { - let mut code_id = None; + let mut checksum = None; for tag in &abci_event.attributes { let key = tag.key.as_str(); let value = tag.value.as_str(); - if let client_events::WASM_CODE_ID_ATTRIBUTE_KEY = key { - code_id = Some(hex::decode(value).map_err(IbcEventError::from_hex_error)?) + if let client_events::WASM_CHECKSUM_ATTRIBUTE_KEY = key { + checksum = Some(hex::decode(value).map_err(IbcEventError::from_hex_error)?) } } - Ok(client_events::PushWasmCode(code_id.ok_or_else(|| { - IbcEventError::missing_key(client_events::WASM_CODE_ID_ATTRIBUTE_KEY.to_owned()) + Ok(client_events::PushWasmCode(checksum.ok_or_else(|| { + IbcEventError::missing_key(client_events::WASM_CHECKSUM_ATTRIBUTE_KEY.to_owned()) })?)) } diff --git a/hyperspace/cosmos/src/provider.rs b/hyperspace/cosmos/src/provider.rs index bf74afb72..8a2ed98bf 100644 --- a/hyperspace/cosmos/src/provider.rs +++ b/hyperspace/cosmos/src/provider.rs @@ -1286,7 +1286,7 @@ where .flat_map(|e| ibc_event_try_from_abci_event(e, height).ok().into_iter()) .filter(|e| matches!(e, IbcEvent::PushWasmCode(_))) .collect::>(); - let code_id = if result.len() != 1 { + let checksum = if result.len() != 1 { return Err(Error::from(format!( "Expected exactly one PushWasmCode event, found {}", result.len() @@ -1309,7 +1309,7 @@ where // Error::from(format!("Failed to upload wasm code to grpc endpoint: {:?}", e)) // })?; - Ok(code_id) + Ok(checksum) } } diff --git a/hyperspace/parachain/src/lib.rs b/hyperspace/parachain/src/lib.rs index efeb6d137..0761901cf 100644 --- a/hyperspace/parachain/src/lib.rs +++ b/hyperspace/parachain/src/lib.rs @@ -186,7 +186,7 @@ pub struct ParachainClientConfig { pub key_type: String, /// All the client states and headers will be wrapped in WASM ones using the WASM code ID. #[serde(default)] - pub wasm_code_id: Option, + pub wasm_checksum: Option, } impl ParachainClient diff --git a/hyperspace/parachain/src/provider.rs b/hyperspace/parachain/src/provider.rs index cd515fc5c..8a563ecd8 100644 --- a/hyperspace/parachain/src/provider.rs +++ b/hyperspace/parachain/src/provider.rs @@ -561,8 +561,8 @@ where fetch_timestamp_extrinsic_with_proof(&self.para_client, Some(header.hash())) .await .map_err(Error::BeefyProver)?; - let code_id = if let AnyClientState::Wasm(client_state) = &client_state { - Some(client_state.code_id.clone()) + let checksum = if let AnyClientState::Wasm(client_state) = &client_state { + Some(client_state.checksum.clone()) } else { None }; @@ -570,7 +570,7 @@ where header: header.encode(), extrinsic: extrinsic_with_proof.ext, extrinsic_proof: extrinsic_with_proof.proof, - code_id, + checksum, }; Ok(Some(host_consensus_proof.encode())) } diff --git a/hyperspace/testsuite/tests/parachain_cosmos.rs b/hyperspace/testsuite/tests/parachain_cosmos.rs index a15d04d3f..1bc925ec9 100644 --- a/hyperspace/testsuite/tests/parachain_cosmos.rs +++ b/hyperspace/testsuite/tests/parachain_cosmos.rs @@ -86,7 +86,7 @@ async fn setup_clients() -> (AnyChain, AnyChain) { finality_protocol: FinalityProtocol::Grandpa, private_key: "//Alice".to_string(), key_type: "sr25519".to_string(), - wasm_code_id: None, + wasm_checksum: None, }; let mut config_b = CosmosClientConfig { @@ -106,7 +106,7 @@ async fn setup_clients() -> (AnyChain, AnyChain) { mnemonic: "oxygen fall sure lava energy veteran enroll frown question detail include maximum" .to_string(), - wasm_code_id: None, + wasm_checksum: None, channel_whitelist: vec![], common: CommonClientConfig { skip_optional_client_updates: true, @@ -118,8 +118,8 @@ async fn setup_clients() -> (AnyChain, AnyChain) { let chain_b = CosmosClient::::new(config_b.clone()).await.unwrap(); let wasm_data = tokio::fs::read(&args.wasm_path).await.expect("Failed to read wasm file"); - let code_id = match chain_b.upload_wasm(wasm_data.clone()).await { - Ok(code_id) => code_id, + let checksum = match chain_b.upload_wasm(wasm_data.clone()).await { + Ok(checksum) => checksum, Err(e) => { let e_str = format!("{e:?}"); if !e_str.contains("wasm code already exists") { @@ -128,8 +128,8 @@ async fn setup_clients() -> (AnyChain, AnyChain) { sha2_256(&wasm_data).to_vec() }, }; - let code_id_str = hex::encode(code_id); - config_b.wasm_code_id = Some(code_id_str); + let checksum_str = hex::encode(checksum); + config_b.wasm_checksum = Some(checksum_str); let mut chain_a_wrapped = AnyConfig::Parachain(config_a).into_client().await.unwrap(); let mut chain_b_wrapped = AnyConfig::Cosmos(config_b).into_client().await.unwrap(); diff --git a/hyperspace/testsuite/tests/parachain_parachain.rs b/hyperspace/testsuite/tests/parachain_parachain.rs index bb575f74a..80a8f7995 100644 --- a/hyperspace/testsuite/tests/parachain_parachain.rs +++ b/hyperspace/testsuite/tests/parachain_parachain.rs @@ -73,7 +73,7 @@ async fn setup_clients() -> (ParachainClient, ParachainClient (ParachainClient, ParachainClient::new(config_a).await.unwrap(); diff --git a/ibc/modules/src/core/ics02_client/events.rs b/ibc/modules/src/core/ics02_client/events.rs index 7b11566a9..74a43193c 100644 --- a/ibc/modules/src/core/ics02_client/events.rs +++ b/ibc/modules/src/core/ics02_client/events.rs @@ -34,8 +34,8 @@ pub const CLIENT_TYPE_ATTRIBUTE_KEY: &str = "client_type"; /// The content of the `key` field for the attribute containing the height. pub const CONSENSUS_HEIGHT_ATTRIBUTE_KEY: &str = "consensus_height"; -/// The content of the `key` field for the attribute containing WASM code ID. -pub const WASM_CODE_ID_ATTRIBUTE_KEY: &str = "wasm_code_id"; +/// The content of the `key` field for the attribute containing WASM checksum. +pub const WASM_CHECKSUM_ATTRIBUTE_KEY: &str = "wasm_checksum"; /// NewBlock event signals the committing & execution of a new block. // TODO - find a better place for NewBlock @@ -264,11 +264,11 @@ impl From for IbcEvent { } } -pub type CodeId = Vec; +pub type Checksum = Vec; /// Signals a recent pushed WASM code to the chain. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] -pub struct PushWasmCode(pub CodeId); +pub struct PushWasmCode(pub Checksum); impl From for IbcEvent { fn from(v: PushWasmCode) -> Self { diff --git a/ibc/modules/src/core/ics23_commitment/commitment.rs b/ibc/modules/src/core/ics23_commitment/commitment.rs index 19b3a2f16..90d1e7390 100644 --- a/ibc/modules/src/core/ics23_commitment/commitment.rs +++ b/ibc/modules/src/core/ics23_commitment/commitment.rs @@ -15,6 +15,8 @@ use crate::{core::ics23_commitment::error::Error, prelude::*, proofs::ProofError}; use core::{convert::TryFrom, fmt}; +#[cfg(feature = "cosmwasm")] +use cosmwasm_schema::schemars::{self, JsonSchema}; use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof; use serde::{Deserialize, Serialize}; use subtle_encoding::{Encoding, Hex}; @@ -61,7 +63,8 @@ impl From> for CommitmentRoot { #[derive(Clone, Debug, PartialEq)] pub struct CommitmentPath; -#[derive(Clone, PartialEq, Eq, Serialize)] +#[cfg_attr(feature = "cosmwasm", derive(JsonSchema))] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct CommitmentProofBytes { #[serde(serialize_with = "crate::serializers::ser_hex_upper")] diff --git a/ibc/proto/src/prost/ibc.lightclients.wasm.v1.rs b/ibc/proto/src/prost/ibc.lightclients.wasm.v1.rs index 69b4ef2a0..0e9d9c5ce 100644 --- a/ibc/proto/src/prost/ibc.lightclients.wasm.v1.rs +++ b/ibc/proto/src/prost/ibc.lightclients.wasm.v1.rs @@ -12,7 +12,7 @@ pub struct MsgPushNewWasmCode { #[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgPushNewWasmCodeResponse { #[prost(bytes = "vec", tag = "1")] - pub code_id: ::prost::alloc::vec::Vec, + pub checksum: ::prost::alloc::vec::Vec, } /// Generated client implementations. #[cfg(feature = "client")] @@ -238,7 +238,7 @@ pub mod msg_server { #[derive(Clone, PartialEq, ::prost::Message)] pub struct WasmCodeQuery { #[prost(string, tag = "1")] - pub code_id: ::prost::alloc::string::String, + pub checksum: ::prost::alloc::string::String, } /// WasmCode response #[allow(clippy::derive_partial_eq_without_eq)] @@ -475,7 +475,7 @@ pub struct ClientState { #[prost(bytes = "vec", tag = "1")] pub data: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "2")] - pub code_id: ::prost::alloc::vec::Vec, + pub checksum: ::prost::alloc::vec::Vec, #[prost(message, optional, tag = "3")] pub latest_height: ::core::option::Option, } @@ -485,24 +485,11 @@ pub struct ClientState { pub struct ConsensusState { #[prost(bytes = "vec", tag = "1")] pub data: ::prost::alloc::vec::Vec, - /// timestamp that corresponds to the block height in which the ConsensusState - /// was stored. - #[prost(uint64, tag = "2")] - pub timestamp: u64, } -/// Wasm light client Header +/// Wasm light client Client Message #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct Header { - #[prost(bytes = "vec", tag = "1")] - pub data: ::prost::alloc::vec::Vec, - #[prost(message, optional, tag = "2")] - pub height: ::core::option::Option, -} -/// Wasm light client Misbehaviour -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Misbehaviour { +pub struct ClientMessage { #[prost(bytes = "vec", tag = "1")] pub data: ::prost::alloc::vec::Vec, } diff --git a/light-clients/ics07-tendermint-cw/src/client.rs b/light-clients/ics07-tendermint-cw/src/client.rs index 4f27dc160..c5d192107 100644 --- a/light-clients/ics07-tendermint-cw/src/client.rs +++ b/light-clients/ics07-tendermint-cw/src/client.rs @@ -23,10 +23,10 @@ use crate::{ use ibc::{ core::{ ics02_client::{ - client_consensus::ConsensusState as _, client_state::ClientType, context::{ClientKeeper, ClientReader, ClientTypes}, error::Error, + events::Checksum, }, ics24_host::identifier::ClientId, }, @@ -184,8 +184,29 @@ impl<'a, H: HostFunctionsProvider + 'static> ClientKeeper for Context<'a, H> { client_state: Self::AnyClientState, ) -> Result<(), Error> { let client_states = ReadonlyClientStates::new(self.storage()); - let data = client_states.get().ok_or_else(|| Error::client_not_found(client_id.clone()))?; - let encoded = Self::encode_client_state(client_state, data)?; + let checksum = match self.checksum.clone() { + None => { + let encoded_wasm_client_state = client_states + .get() + .ok_or_else(|| Error::client_not_found(client_id.clone()))?; + let any = Any::decode(&*encoded_wasm_client_state).map_err(Error::decode)?; + let wasm_client_state = ics08_wasm::client_state::ClientState::< + FakeInner, + FakeInner, + FakeInner, + >::decode_vec(&any.value) + .map_err(|e| { + Error::implementation_specific(format!( + "[client_state]: error decoding client state bytes to WasmConsensusState {}", + e + )) + })?; + wasm_client_state.checksum + }, + Some(x) => x, + }; + + let encoded = Self::encode_client_state(client_state, checksum)?; let mut client_state_storage = ClientStates::new(self.storage_mut()); client_state_storage.insert(encoded); Ok(()) @@ -271,18 +292,11 @@ impl<'a, H: Clone> Context<'a, H> { pub fn encode_client_state( client_state: ClientState, - encoded_wasm_client_state: Vec, + checksum: Checksum, ) -> Result, Error> { - let any = Any::decode(&*encoded_wasm_client_state).map_err(Error::decode)?; let mut wasm_client_state = - ics08_wasm::client_state::ClientState::::decode_vec( - &any.value, - ) - .map_err(|e| { - Error::implementation_specific(format!( - "[client_state]: error decoding client state bytes to WasmConsensusState {e}" - )) - })?; + ics08_wasm::client_state::ClientState::::default(); + wasm_client_state.checksum = checksum; wasm_client_state.data = client_state.to_any().encode_to_vec(); wasm_client_state.latest_height = client_state.latest_height(); let vec1 = wasm_client_state.to_any().encode_to_vec(); @@ -292,7 +306,6 @@ impl<'a, H: Clone> Context<'a, H> { pub fn encode_consensus_state(consensus_state: ConsensusState) -> Vec { let wasm_consensus_state = ics08_wasm::consensus_state::ConsensusState { data: consensus_state.to_any().encode_to_vec(), - timestamp: consensus_state.timestamp().nanoseconds(), inner: Box::new(FakeInner), }; wasm_consensus_state.to_any().encode_to_vec() diff --git a/light-clients/ics07-tendermint-cw/src/context.rs b/light-clients/ics07-tendermint-cw/src/context.rs index 39f59f1a6..7fecd9ba1 100644 --- a/light-clients/ics07-tendermint-cw/src/context.rs +++ b/light-clients/ics07-tendermint-cw/src/context.rs @@ -15,24 +15,31 @@ use crate::{ ics23::{ - ClientStates, ConsensusStates, ReadonlyClientStates, ReadonlyConsensusStates, + ClientStates, ConsensusStates, FakeInner, ReadonlyClientStates, ReadonlyConsensusStates, ReadonlyProcessedStates, }, ContractError, }; -use cosmwasm_std::{DepsMut, Env, Storage}; +use cosmwasm_std::{Deps, DepsMut, Env, Storage}; use ibc::{ - core::{ics02_client::error::Error, ics26_routing::context::ReaderContext}, + core::{ + ics02_client::{error::Error, events::Checksum}, + ics24_host::identifier::ClientId, + ics26_routing::context::ReaderContext, + }, Height, }; +use ibc_proto::google::protobuf::Any; use ics07_tendermint::{ client_state::ClientState, consensus_state::ConsensusState, HostFunctionsProvider, }; use std::{fmt, fmt::Debug, marker::PhantomData}; pub struct Context<'a, H> { - pub deps: DepsMut<'a>, + pub deps_mut: Option>, + pub deps: Option>, pub env: Env, + pub checksum: Option, _phantom: PhantomData, } @@ -58,19 +65,35 @@ impl<'a, H> Clone for Context<'a, H> { impl<'a, H> Context<'a, H> { pub fn new(deps: DepsMut<'a>, env: Env) -> Self { - Self { deps, _phantom: Default::default(), env } + Self { deps_mut: Some(deps), deps: None, _phantom: Default::default(), env, checksum: None } + } + + pub fn new_ro(deps: Deps<'a>, env: Env) -> Self { + Self { deps_mut: None, deps: Some(deps), _phantom: Default::default(), env, checksum: None } } pub fn log(&self, msg: &str) { - self.deps.api.debug(msg) + match &self.deps_mut { + Some(deps_mut) => deps_mut.api.debug(msg), + None => unimplemented!(), + } } pub fn storage(&self) -> &dyn Storage { - self.deps.storage + match &self.deps_mut { + Some(deps_mut) => deps_mut.storage, + None => match &self.deps { + Some(deps) => deps.storage, + None => unimplemented!(), + }, + } } pub fn storage_mut(&mut self) -> &mut dyn Storage { - self.deps.storage + match &mut self.deps_mut { + Some(deps_mut) => deps_mut.storage, + None => unimplemented!(), + } } } @@ -138,13 +161,41 @@ where &mut self, client_state: ClientState, prefix: &[u8], + client_id: ClientId, ) -> Result<(), ContractError> { + use prost::Message; + use tendermint_proto::Protobuf; let client_states = ReadonlyClientStates::new(self.storage()); - let data = client_states.get_prefixed(prefix).ok_or_else(|| { - ContractError::Tendermint("no client state found for prefix".to_string()) - })?; - let encoded = Context::::encode_client_state(client_state, data).map_err(|e| { - ContractError::Tendermint(format!("error encoding client state: {e:?}")) + let checksum = match self.checksum.clone() { + None => { + let encoded_wasm_client_state = + client_states.get_prefixed(prefix).ok_or_else(|| { + ContractError::Tendermint(Error::client_not_found(client_id).to_string()) + })?; + let any = Any::decode(&*encoded_wasm_client_state) + .map_err(Error::decode) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let wasm_client_state = ics08_wasm::client_state::ClientState::< + FakeInner, + FakeInner, + FakeInner, + >::decode_vec(&any.value) + .map_err(|e| { + ContractError::Tendermint( + Error::implementation_specific(format!( + "[client_state]: error decoding client state bytes to WasmConsensusState {}", + e + )) + .to_string(), + ) + })?; + wasm_client_state.checksum + }, + Some(x) => x, + }; + + let encoded = Context::::encode_client_state(client_state, checksum).map_err(|e| { + ContractError::Tendermint(format!("error encoding client state: {:?}", e)) })?; let mut client_states = ClientStates::new(self.storage_mut()); client_states.insert_prefixed(encoded, prefix); diff --git a/light-clients/ics07-tendermint-cw/src/contract.rs b/light-clients/ics07-tendermint-cw/src/contract.rs index 1174cd11d..c57cef78e 100644 --- a/light-clients/ics07-tendermint-cw/src/contract.rs +++ b/light-clients/ics07-tendermint-cw/src/contract.rs @@ -22,10 +22,9 @@ use crate::{ }, ics23::ReadonlyProcessedStates, msg::{ - CheckForMisbehaviourMsg, ContractResult, ExecuteMsg, ExportMetadataMsg, InstantiateMsg, - QueryMsg, QueryResponse, StatusMsg, UpdateStateMsg, UpdateStateOnMisbehaviourMsg, - VerifyClientMessage, VerifyMembershipMsg, VerifyNonMembershipMsg, - VerifyUpgradeAndUpdateStateMsg, + CheckForMisbehaviourMsg, ContractResult, ExportMetadataMsg, QueryMsg, QueryResponse, + StatusMsg, SudoMsg, UpdateStateMsg, UpdateStateOnMisbehaviourMsg, VerifyClientMessage, + VerifyMembershipMsg, VerifyNonMembershipMsg, VerifyUpgradeAndUpdateStateMsg, }, state::{get_client_state, get_consensus_state}, }; @@ -33,18 +32,25 @@ use crate::{ use cosmwasm_std::entry_point; use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use ed25519_consensus::VerificationKey; -use ibc::core::{ - ics02_client::{ - client_def::{ClientDef, ConsensusUpdateResult}, - context::{ClientKeeper, ClientReader}, +use ibc::{ + core::{ + ics02_client::{ + client_def::{ClientDef, ConsensusUpdateResult}, + context::{ClientKeeper, ClientReader}, + }, + ics24_host::identifier::ClientId, }, - ics24_host::identifier::ClientId, + Height, }; +use ibc_proto::google::protobuf::Any; use ics07_tendermint::{ client_def::{verify_membership, verify_non_membership, TendermintClient}, + client_state::ClientState, + consensus_state::ConsensusState, HostFunctionsProvider, }; -use ics08_wasm::SUBJECT_PREFIX; +use ics08_wasm::{instantiate::InstantiateMessage, SUBJECT_PREFIX}; +use prost::Message; use sha2::{Digest, Sha256}; use std::str::FromStr; use tendermint::{ @@ -56,6 +62,7 @@ use tendermint::{ PublicKey, Signature, }; use tendermint_light_client_verifier::operations::CommitValidator; +use tendermint_proto::Protobuf; #[derive(Clone, Copy, Debug, PartialEq, Default, Eq)] pub struct HostFunctions; @@ -121,37 +128,50 @@ impl Verifier for HostFunctions { impl CommitValidator for HostFunctions {} impl HostFunctionsProvider for HostFunctions {} -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - _msg: InstantiateMsg, -) -> Result { - let _client = TendermintClient::::default(); - let mut ctx = Context::::new(deps, env); - let client_id = ClientId::from_str("08-wasm-0").expect("client id is valid"); - let client_state = ctx - .client_state(&client_id) +fn process_instantiate_msg( + msg: InstantiateMessage, + ctx: &mut Context, + client_id: ClientId, +) -> Result { + let any = Any::decode(&mut msg.client_state.as_slice())?; + let client_state = ClientState::decode_vec(&any.value)?; + let any = Any::decode(&mut msg.consensus_state.as_slice())?; + let consensus_state = ConsensusState::decode_vec(&any.value)?; + + ctx.checksum = Some(msg.checksum); + let height = client_state.latest_height(); + ctx.store_client_state(client_id.clone(), client_state) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + ctx.store_consensus_state(client_id.clone(), height, consensus_state) .map_err(|e| ContractError::Tendermint(e.to_string()))?; - ctx.store_update_height(client_id.clone(), client_state.latest_height, ctx.host_height()) + + ctx.store_update_height(client_id.clone(), height, ctx.host_height()) .map_err(|e| ContractError::Tendermint(e.to_string()))?; - ctx.store_update_time(client_id, client_state.latest_height, ctx.host_timestamp()) + ctx.store_update_time(client_id, height, ctx.host_timestamp()) .map_err(|e| ContractError::Tendermint(e.to_string()))?; - Ok(Response::default()) + Ok(to_binary(&ContractResult::success())?) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( +pub fn instantiate( deps: DepsMut, env: Env, _info: MessageInfo, - msg: ExecuteMsg, + msg: InstantiateMessage, ) -> Result { + let client_id = ClientId::from_str(env.contract.address.as_str()).expect("client id is valid"); + let mut ctx = Context::::new(deps, env); + let data = process_instantiate_msg(msg, &mut ctx, client_id.clone())?; + let mut response = Response::default(); + response.data = Some(data); + Ok(response) +} +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result { let client = TendermintClient::::default(); + let client_id = ClientId::from_str(env.contract.address.as_str()).expect("client id is valid"); let mut ctx = Context::::new(deps, env); - let client_id = ClientId::from_str("08-wasm-0").expect("client id is valid"); let data = process_message(msg, client, &mut ctx, client_id)?; let mut response = Response::default(); response.data = Some(data); @@ -159,76 +179,14 @@ pub fn execute( } fn process_message( - msg: ExecuteMsg, + msg: SudoMsg, client: TendermintClient, ctx: &mut Context, client_id: ClientId, ) -> Result { //log!(ctx, "process_message: {:?}", msg); let result = match msg { - ExecuteMsg::VerifyMembership(msg) => { - let client_state = ctx - .client_state(&client_id) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let msg = VerifyMembershipMsg::try_from(msg)?; - verify_delay_passed(ctx, msg.height, msg.delay_time_period, msg.delay_block_period) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let consensus_state = ctx - .consensus_state(&client_id, msg.height) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - verify_membership::( - &client_state, - &msg.prefix, - &msg.proof, - &consensus_state.root, - msg.path, - msg.value, - ) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - Ok(()).map(|_| to_binary(&ContractResult::success())) - }, - ExecuteMsg::VerifyNonMembership(msg) => { - let client_state = ctx - .client_state(&client_id) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let msg = VerifyNonMembershipMsg::try_from(msg)?; - verify_delay_passed(ctx, msg.height, msg.delay_time_period, msg.delay_block_period) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let consensus_state = ctx - .consensus_state(&client_id, msg.height) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - - verify_non_membership::( - &client_state, - &msg.prefix, - &msg.proof, - &consensus_state.root, - msg.path, - ) - .map_err(|e| ContractError::Tendermint(e.to_string())) - .map(|_| to_binary(&ContractResult::success())) - }, - ExecuteMsg::VerifyClientMessage(msg) => { - let client_state = ctx - .client_state(&client_id) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let msg = VerifyClientMessage::try_from(msg)?; - client - .verify_client_message(ctx, client_id, client_state, msg.client_message) - .map_err(|e| ContractError::Tendermint(format!("{e:?}"))) - .map(|_| to_binary(&ContractResult::success())) - }, - ExecuteMsg::CheckForMisbehaviour(msg) => { - let client_state = ctx - .client_state(&client_id) - .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let msg = CheckForMisbehaviourMsg::try_from(msg)?; - client - .check_for_misbehaviour(ctx, client_id, client_state, msg.client_message) - .map_err(|e| ContractError::Tendermint(e.to_string())) - .map(|result| to_binary(&ContractResult::success().misbehaviour(result))) - }, - ExecuteMsg::UpdateStateOnMisbehaviour(msg_raw) => { + SudoMsg::UpdateStateOnMisbehaviour(msg_raw) => { let client_state = ctx .client_state(&client_id) .map_err(|e| ContractError::Tendermint(e.to_string()))?; @@ -242,7 +200,7 @@ fn process_message( Ok(to_binary(&ContractResult::success())) }) }, - ExecuteMsg::UpdateState(msg_raw) => { + SudoMsg::UpdateState(msg_raw) => { let client_state = ctx .client_state(&client_id) .map_err(|e| ContractError::Tendermint(e.to_string()))?; @@ -254,13 +212,16 @@ fn process_message( .map_err(|e| ContractError::Tendermint(e.to_string())) .and_then(|(cs, cu)| { let height = cs.latest_height(); + let mut heights: Vec = vec![]; match cu { ConsensusUpdateResult::Single(cs) => { + heights.push(height); ctx.store_consensus_state(client_id.clone(), height, cs) .map_err(|e| ContractError::Tendermint(e.to_string()))?; }, ConsensusUpdateResult::Batch(css) => for (height, cs) in css { + heights.push(height); ctx.store_consensus_state(client_id.clone(), height, cs) .map_err(|e| ContractError::Tendermint(e.to_string()))?; }, @@ -269,24 +230,66 @@ fn process_message( ctx.store_client_state(client_id, cs) .map_err(|e| ContractError::Tendermint(e.to_string()))?; } - Ok(to_binary(&ContractResult::success())) + Ok(to_binary(&ContractResult::success().heights(heights))) }) }, - ExecuteMsg::CheckSubstituteAndUpdateState(_msg) => + SudoMsg::MigrateClientStore(_msg) => check_substitute_and_update_state::(ctx) .map_err(|e| ContractError::Tendermint(e.to_string())) .and_then(|(cs, cu)| { let height = cs.latest_height(); ctx.store_consensus_state_prefixed(height, cu, SUBJECT_PREFIX); - ctx.store_client_state_prefixed(cs, SUBJECT_PREFIX) + ctx.store_client_state_prefixed(cs, SUBJECT_PREFIX, client_id) .map_err(|e| ContractError::Tendermint(e.to_string()))?; Ok(to_binary(&ContractResult::success())) }), - ExecuteMsg::VerifyUpgradeAndUpdateState(msg) => { + SudoMsg::VerifyMembership(msg) => { + let client_state = ctx + .client_state(&client_id) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let msg = VerifyMembershipMsg::try_from(msg)?; + verify_delay_passed(&ctx, msg.height, msg.delay_time_period, msg.delay_block_period) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let consensus_state = ctx + .consensus_state(&client_id, msg.height) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + verify_membership::( + &client_state, + &msg.prefix, + &msg.proof, + &consensus_state.root, + msg.path, + msg.value, + ) + .map_err(|e| ContractError::Tendermint(e.to_string())) + .map(|_| to_binary(&ContractResult::success())) + }, + SudoMsg::VerifyNonMembership(msg) => { + let client_state = ctx + .client_state(&client_id) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let msg = VerifyNonMembershipMsg::try_from(msg)?; + verify_delay_passed(&ctx, msg.height, msg.delay_time_period, msg.delay_block_period) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let consensus_state = ctx + .consensus_state(&client_id, msg.height) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + + verify_non_membership::( + &client_state, + &msg.prefix, + &msg.proof, + &consensus_state.root, + msg.path, + ) + .map_err(|e| ContractError::Tendermint(e.to_string())) + .map(|_| to_binary(&ContractResult::success())) + }, + SudoMsg::VerifyUpgradeAndUpdateState(msg) => { let old_client_state = ctx .client_state(&client_id) .map_err(|e| ContractError::Tendermint(e.to_string()))?; - let msg: VerifyUpgradeAndUpdateStateMsg = + let msg: VerifyUpgradeAndUpdateStateMsg = VerifyUpgradeAndUpdateStateMsg::try_from(msg)?; verify_upgrade_and_update_state::( ctx, @@ -313,23 +316,34 @@ fn process_message( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - let client_id = ClientId::from_str("08-wasm-0").expect("client id is valid"); - //deps.api.debug("In query"); + let client_id = ClientId::from_str(env.contract.address.as_str()).expect("client id is valid"); match msg { + QueryMsg::CheckForMisbehaviour(msg) => { + let ctx = Context::::new_ro(deps, env); + let client = TendermintClient::::default(); + let client_state = ctx + .client_state(&client_id) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let msg = CheckForMisbehaviourMsg::try_from(msg)?; + client + .check_for_misbehaviour(&ctx, client_id, client_state, msg.client_message) + .map_err(|e| ContractError::Tendermint(e.to_string())) + .map(|result| to_binary(&QueryResponse::success().misbehaviour(result)))? + }, QueryMsg::ClientTypeMsg(_) => unimplemented!("ClientTypeMsg"), QueryMsg::GetLatestHeightsMsg(_) => unimplemented!("GetLatestHeightsMsg"), QueryMsg::ExportMetadata(ExportMetadataMsg {}) => { let ro_proceeded_state = ReadonlyProcessedStates::new(deps.storage); - to_binary(&QueryResponse::genesis_metadata(ro_proceeded_state.get_metadata())) + to_binary(&QueryResponse::success().genesis_metadata(ro_proceeded_state.get_metadata())) }, QueryMsg::Status(StatusMsg {}) => { - let client_state = match get_client_state::(deps) { + let client_state = match get_client_state::(deps, client_id.clone()) { Ok(client_state) => client_state, - Err(_) => return to_binary(&QueryResponse::status("Unknown".to_string())), + Err(_) => return to_binary(&QueryResponse::success().status("Unknown".to_string())), }; if client_state.frozen_height().is_some() { - to_binary(&QueryResponse::status("Frozen".to_string())) + to_binary(&QueryResponse::success().status("Frozen".to_string())) } else { let height = client_state.latest_height(); match get_consensus_state(deps, &client_id, height) { @@ -338,13 +352,38 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { let tp = client_state.trusting_period.as_secs(); let now = env.block.time.seconds(); if (last_update + tp) < now { - return to_binary(&QueryResponse::status("Expired".to_string())) + return to_binary( + &QueryResponse::success().status("Expired".to_string()), + ) } - to_binary(&QueryResponse::status("Active".to_string())) + to_binary(&QueryResponse::success().status("Active".to_string())) }, - Err(_) => to_binary(&QueryResponse::status("Expired".to_string())), + Err(_) => to_binary(&QueryResponse::success().status("Expired".to_string())), } } }, + QueryMsg::TimestampAtHeight(msg) => { + let ctx = Context::::new_ro(deps, env); + let consensus_state = ctx + .consensus_state(&client_id, msg.height) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + to_binary( + &QueryResponse::success().timestamp( + consensus_state.timestamp.unix_timestamp_nanos().unsigned_abs() as u64, + ), + ) + }, + QueryMsg::VerifyClientMessage(msg) => { + let ctx = Context::::new_ro(deps, env); + let client = TendermintClient::::default(); + let client_state = ctx + .client_state(&client_id) + .map_err(|e| ContractError::Tendermint(e.to_string()))?; + let msg = VerifyClientMessage::try_from(msg)?; + client + .verify_client_message(&ctx, client_id, client_state, msg.client_message) + .map_err(|e| ContractError::Tendermint(format!("{e:?}"))) + .map(|_| to_binary(&QueryResponse::success()))? + }, } } diff --git a/light-clients/ics07-tendermint-cw/src/error.rs b/light-clients/ics07-tendermint-cw/src/error.rs index 332ef6eb0..32541b02d 100644 --- a/light-clients/ics07-tendermint-cw/src/error.rs +++ b/light-clients/ics07-tendermint-cw/src/error.rs @@ -55,3 +55,9 @@ impl From for ContractError { ContractError::Tendermint(e.to_string()) } } + +impl From for StdError { + fn from(e: ContractError) -> Self { + StdError::GenericErr { msg: e.to_string() } + } +} diff --git a/light-clients/ics07-tendermint-cw/src/helpers.rs b/light-clients/ics07-tendermint-cw/src/helpers.rs index 70bef7e9e..ae353146a 100644 --- a/light-clients/ics07-tendermint-cw/src/helpers.rs +++ b/light-clients/ics07-tendermint-cw/src/helpers.rs @@ -20,8 +20,8 @@ use serde::{Deserialize, Serialize}; use crate::{ context::Context, - ics23::{ConsensusStates, FakeInner, ProcessedStates}, - msg::ExecuteMsg, + ics23::{ConsensusStates, ProcessedStates}, + msg::SudoMsg, }; use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use ibc::core::{ @@ -32,12 +32,8 @@ use ibc::core::{ ics23_commitment::{commitment::CommitmentProofBytes, merkle::MerkleProof}, ics24_host::identifier::ClientId, }; -use ibc_proto::{ - google::protobuf::Any, - ibc::core::commitment::v1::{MerklePath, MerkleProof as RawMerkleProof}, -}; +use ibc_proto::ibc::core::commitment::v1::{MerklePath, MerkleProof as RawMerkleProof}; use prost::Message; -use tendermint_proto::Protobuf; use ics07_tendermint::{ client_state::{ClientState, UpgradeOptions}, @@ -46,10 +42,7 @@ use ics07_tendermint::{ HostFunctionsProvider, }; -use ics08_wasm::{ - client_state::ClientState as WasmClientState, - consensus_state::ConsensusState as WasmConsensusState, SUBJECT_PREFIX, SUBSTITUTE_PREFIX, -}; +use ics08_wasm::{SUBJECT_PREFIX, SUBSTITUTE_PREFIX}; /// CwTemplateContract is a wrapper around Addr that provides a lot of helpers /// for working with this. @@ -61,7 +54,7 @@ impl CwTemplateContract { self.0.clone() } - pub fn call>(&self, msg: T) -> StdResult { + pub fn call>(&self, msg: T) -> StdResult { let msg = to_binary(&msg.into())?; Ok(WasmMsg::Execute { contract_addr: self.addr().into(), msg, funds: vec![] }.into()) } @@ -98,8 +91,8 @@ pub fn verify_upgrade_and_update_state( ctx: &mut Context, client_id: ClientId, old_client_state: ClientState, - upgrade_client_state: WasmClientState, - upgrade_consensus_state: WasmConsensusState, + upgrade_client_state: ClientState, + upgrade_consensus_state: ConsensusState, proof_upgrade_client: CommitmentProofBytes, proof_upgrade_consensus_state: CommitmentProofBytes, ) -> Result<(ClientState, ConsensusState), Ics02Error> { @@ -162,21 +155,17 @@ pub fn verify_upgrade_and_update_state( ) .unwrap(); - let any = Any::decode(&mut upgrade_client_state.data.as_slice()).unwrap(); - let upgrade_client_state_inner = ClientState::::decode_vec(&any.value).unwrap(); let new_client_state = old_client_state.upgrade( - upgrade_client_state_inner.latest_height, + upgrade_client_state.latest_height, UpgradeOptions { - unbonding_period: upgrade_client_state_inner.unbonding_period, - proof_specs: upgrade_client_state_inner.proof_specs.clone(), - upgrade_path: upgrade_client_state_inner.upgrade_path.clone(), + unbonding_period: upgrade_client_state.unbonding_period, + proof_specs: upgrade_client_state.proof_specs.clone(), + upgrade_path: upgrade_client_state.upgrade_path.clone(), }, - upgrade_client_state_inner.chain_id, + upgrade_client_state.chain_id, ); - let any = Any::decode(&mut upgrade_consensus_state.data.as_slice()).unwrap(); - let upgrade_consensus_state_inner = ConsensusState::decode_vec(&any.value).unwrap(); - Ok((new_client_state, upgrade_consensus_state_inner)) + Ok((new_client_state, upgrade_consensus_state)) } pub fn check_substitute_and_update_state( diff --git a/light-clients/ics07-tendermint-cw/src/msg.rs b/light-clients/ics07-tendermint-cw/src/msg.rs index aef9f1e49..ac9627401 100644 --- a/light-clients/ics07-tendermint-cw/src/msg.rs +++ b/light-clients/ics07-tendermint-cw/src/msg.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{contract::HostFunctions, ics23::FakeInner, Bytes, ContractError}; +use crate::{Bytes, ContractError}; use core::{str::FromStr, time::Duration}; use cosmwasm_schema::cw_serde; use ibc::{ @@ -27,12 +27,12 @@ use ibc::{ }; use ibc_proto::{google::protobuf::Any, ibc::core::client::v1::Height as HeightRaw}; use ics07_tendermint::{ - client_message::{ClientMessage, Header, Misbehaviour}, + client_message::{ + ClientMessage, Header, Misbehaviour, TENDERMINT_HEADER_TYPE_URL, + TENDERMINT_MISBEHAVIOUR_TYPE_URL, + }, client_state::ClientState, -}; -use ics08_wasm::{ - client_message::Header as WasmHeader, client_state::ClientState as WasmClientState, - consensus_state::ConsensusState as WasmConsensusState, + consensus_state::ConsensusState, }; use prost::Message; use serde::{Deserializer, Serializer}; @@ -57,71 +57,90 @@ pub struct GenesisMetadata { #[cw_serde] pub struct QueryResponse { - pub status: String, + pub is_valid: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, #[serde(skip_serializing_if = "Option::is_none")] pub genesis_metadata: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub found_misbehaviour: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp: Option, } impl QueryResponse { - pub fn status(status: String) -> Self { - Self { status, genesis_metadata: None } + pub fn success() -> Self { + Self { + is_valid: true, + status: None, + genesis_metadata: None, + found_misbehaviour: None, + timestamp: None, + } + } + + pub fn status(mut self, status: String) -> Self { + self.status = Some(status); + self + } + + pub fn genesis_metadata(mut self, genesis_metadata: Option>) -> Self { + self.genesis_metadata = genesis_metadata; + self } - pub fn genesis_metadata(genesis_metadata: Option>) -> Self { - Self { status: "".to_string(), genesis_metadata } + pub fn misbehaviour(mut self, found_misbehavior: bool) -> Self { + self.found_misbehaviour = Some(found_misbehavior); + self + } + + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = Some(timestamp); + self } } #[cw_serde] pub struct ContractResult { - pub is_valid: bool, - pub error_msg: String, #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option>, - pub found_misbehaviour: bool, + pub heights: Option>, } impl ContractResult { pub fn success() -> Self { - Self { is_valid: true, error_msg: "".to_string(), data: None, found_misbehaviour: false } + Self { heights: None } } - pub fn error(msg: String) -> Self { - Self { is_valid: false, error_msg: msg, data: None, found_misbehaviour: false } - } - - pub fn misbehaviour(mut self, found: bool) -> Self { - self.found_misbehaviour = found; - self - } - - pub fn data(mut self, data: Vec) -> Self { - self.data = Some(data); + pub fn heights(mut self, heights: Vec) -> Self { + self.heights = Some(heights); self } } #[cw_serde] -pub struct InstantiateMsg {} - -#[cw_serde] -pub enum ExecuteMsg { - VerifyMembership(VerifyMembershipMsgRaw), - VerifyNonMembership(VerifyNonMembershipMsgRaw), - VerifyClientMessage(VerifyClientMessageRaw), - CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +pub enum SudoMsg { + MigrateClientStore(MigrateClientStoreMsg), UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), UpdateState(UpdateStateMsgRaw), - CheckSubstituteAndUpdateState(CheckSubstituteAndUpdateStateMsg), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), } #[cw_serde] pub enum QueryMsg { + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), ClientTypeMsg(ClientTypeMsg), GetLatestHeightsMsg(GetLatestHeightsMsg), ExportMetadata(ExportMetadataMsg), Status(StatusMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), +} + +#[cw_serde] +pub struct TimestampAtHeightMsg { + pub height: Height, } #[cw_serde] @@ -141,6 +160,102 @@ pub struct MerklePath { pub key_path: Vec, } +#[cw_serde] +pub struct VerifyClientMessageRaw { + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, +} + +pub struct VerifyClientMessage { + pub client_message: ClientMessage, +} + +impl TryFrom for VerifyClientMessage { + type Error = ContractError; + + fn try_from(raw: VerifyClientMessageRaw) -> Result { + let client_message = Self::decode_client_message(raw.client_message)?; + Ok(Self { client_message }) + } +} + +impl VerifyClientMessage { + fn decode_client_message(raw: Bytes) -> Result { + let any = Any::decode(&mut raw.as_slice())?; + let client_message = match &*any.type_url { + TENDERMINT_HEADER_TYPE_URL => ClientMessage::Header(Header::decode_vec(&any.value)?), + TENDERMINT_MISBEHAVIOUR_TYPE_URL => + ClientMessage::Misbehaviour(Misbehaviour::decode_vec(&any.value)?), + _ => return Err(ContractError::Tendermint("unknown client message type".to_string())), + }; + Ok(client_message) + } +} + +#[cw_serde] +pub struct CheckForMisbehaviourMsgRaw { + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, +} + +pub struct CheckForMisbehaviourMsg { + pub client_message: ClientMessage, +} + +impl TryFrom for CheckForMisbehaviourMsg { + type Error = ContractError; + + fn try_from(raw: CheckForMisbehaviourMsgRaw) -> Result { + let client_message = VerifyClientMessage::decode_client_message(raw.client_message)?; + Ok(Self { client_message }) + } +} + +#[cw_serde] +pub struct UpdateStateOnMisbehaviourMsgRaw { + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, +} + +pub struct UpdateStateOnMisbehaviourMsg { + pub client_message: ClientMessage, +} + +impl TryFrom for UpdateStateOnMisbehaviourMsg { + type Error = ContractError; + + fn try_from(raw: UpdateStateOnMisbehaviourMsgRaw) -> Result { + let client_message = VerifyClientMessage::decode_client_message(raw.client_message)?; + Ok(Self { client_message }) + } +} + +#[cw_serde] +pub struct UpdateStateMsgRaw { + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, +} + +pub struct UpdateStateMsg { + pub client_message: ClientMessage, +} + +impl TryFrom for UpdateStateMsg { + type Error = ContractError; + + fn try_from(raw: UpdateStateMsgRaw) -> Result { + let client_message = VerifyClientMessage::decode_client_message(raw.client_message)?; + Ok(Self { client_message }) + } +} + +#[cw_serde] +pub struct MigrateClientStoreMsg {} + #[cw_serde] pub struct VerifyMembershipMsgRaw { #[schemars(with = "String")] @@ -227,113 +342,13 @@ impl TryFrom for VerifyNonMembershipMsg { } #[cw_serde] -pub struct WasmMisbehaviour { +pub struct VerifyUpgradeAndUpdateStateMsgRaw { #[schemars(with = "String")] #[serde(with = "Base64", default)] - pub data: Bytes, -} - -#[cw_serde] -pub enum ClientMessageRaw { - Header(WasmHeader), - Misbehaviour(WasmMisbehaviour), -} - -#[cw_serde] -pub struct VerifyClientMessageRaw { - pub client_message: ClientMessageRaw, -} - -pub struct VerifyClientMessage { - pub client_message: ClientMessage, -} - -impl TryFrom for VerifyClientMessage { - type Error = ContractError; - - fn try_from(raw: VerifyClientMessageRaw) -> Result { - let client_message = Self::decode_client_message(raw.client_message)?; - Ok(Self { client_message }) - } -} - -impl VerifyClientMessage { - fn decode_client_message(raw: ClientMessageRaw) -> Result { - let client_message = match raw { - ClientMessageRaw::Header(header) => { - let any = Any::decode(&mut header.data.as_slice())?; - ClientMessage::Header(Header::decode_vec(&any.value)?) - }, - ClientMessageRaw::Misbehaviour(misbehaviour) => { - let any = Any::decode(&mut misbehaviour.data.as_slice())?; - ClientMessage::Misbehaviour(Misbehaviour::decode_vec(&any.value)?) - }, - }; - Ok(client_message) - } -} - -#[cw_serde] -pub struct CheckForMisbehaviourMsgRaw { - pub client_message: ClientMessageRaw, -} - -pub struct CheckForMisbehaviourMsg { - pub client_message: ClientMessage, -} - -impl TryFrom for CheckForMisbehaviourMsg { - type Error = ContractError; - - fn try_from(raw: CheckForMisbehaviourMsgRaw) -> Result { - let client_message = VerifyClientMessage::decode_client_message(raw.client_message)?; - Ok(Self { client_message }) - } -} - -#[cw_serde] -pub struct UpdateStateOnMisbehaviourMsgRaw { - pub client_message: ClientMessageRaw, -} - -pub struct UpdateStateOnMisbehaviourMsg { - pub client_message: ClientMessage, -} - -impl TryFrom for UpdateStateOnMisbehaviourMsg { - type Error = ContractError; - - fn try_from(raw: UpdateStateOnMisbehaviourMsgRaw) -> Result { - let client_message = VerifyClientMessage::decode_client_message(raw.client_message)?; - Ok(Self { client_message }) - } -} - -#[cw_serde] -pub struct UpdateStateMsgRaw { - pub client_message: ClientMessageRaw, -} - -pub struct UpdateStateMsg { - pub client_message: ClientMessage, -} - -impl TryFrom for UpdateStateMsg { - type Error = ContractError; - - fn try_from(raw: UpdateStateMsgRaw) -> Result { - let client_message = VerifyClientMessage::decode_client_message(raw.client_message)?; - Ok(Self { client_message }) - } -} - -#[cw_serde] -pub struct CheckSubstituteAndUpdateStateMsg {} - -#[cw_serde] -pub struct VerifyUpgradeAndUpdateStateMsgRaw { - pub upgrade_client_state: WasmClientState, - pub upgrade_consensus_state: WasmConsensusState, + pub upgrade_client_state: Bytes, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub upgrade_consensus_state: Bytes, #[schemars(with = "String")] #[serde(with = "Base64", default)] pub proof_upgrade_client: Bytes, @@ -342,20 +357,21 @@ pub struct VerifyUpgradeAndUpdateStateMsgRaw { pub proof_upgrade_consensus_state: Bytes, } -pub struct VerifyUpgradeAndUpdateStateMsg { - pub upgrade_client_state: WasmClientState, - pub upgrade_consensus_state: WasmConsensusState, +pub struct VerifyUpgradeAndUpdateStateMsg { + pub upgrade_client_state: ClientState, + pub upgrade_consensus_state: ConsensusState, pub proof_upgrade_client: CommitmentProofBytes, pub proof_upgrade_consensus_state: CommitmentProofBytes, } -impl TryFrom for VerifyUpgradeAndUpdateStateMsg { +impl TryFrom for VerifyUpgradeAndUpdateStateMsg { type Error = ContractError; fn try_from(raw: VerifyUpgradeAndUpdateStateMsgRaw) -> Result { - let any = Any::decode(&mut raw.upgrade_client_state.data.as_slice())?; - let upgrade_client_state: ics07_tendermint::client_state::ClientState = - ClientState::decode_vec(&any.value)?; + let any = Any::decode(&mut raw.upgrade_client_state.as_slice())?; + let upgrade_client_state = ClientState::decode_vec(&any.value)?; + let any = Any::decode(&mut raw.upgrade_consensus_state.as_slice())?; + let upgrade_consensus_state = ConsensusState::decode_vec(&any.value)?; if upgrade_client_state.trust_level != TrustThreshold::ZERO || upgrade_client_state.trusting_period != Duration::ZERO || upgrade_client_state.max_clock_drift != Duration::ZERO || @@ -367,8 +383,8 @@ impl TryFrom for VerifyUpgradeAndUpdateStateM } Ok(VerifyUpgradeAndUpdateStateMsg { - upgrade_client_state: raw.upgrade_client_state, - upgrade_consensus_state: raw.upgrade_consensus_state, + upgrade_client_state, + upgrade_consensus_state, proof_upgrade_client: CommitmentProofBytes::try_from(raw.proof_upgrade_client)?, proof_upgrade_consensus_state: CommitmentProofBytes::try_from( raw.proof_upgrade_consensus_state, diff --git a/light-clients/ics07-tendermint-cw/src/state.rs b/light-clients/ics07-tendermint-cw/src/state.rs index 5dcef9e59..057892a82 100644 --- a/light-clients/ics07-tendermint-cw/src/state.rs +++ b/light-clients/ics07-tendermint-cw/src/state.rs @@ -25,10 +25,13 @@ use ics07_tendermint::{client_state::ClientState, consensus_state::ConsensusStat use prost::Message; /// Retrieves raw bytes from storage and deserializes them into [`ClientState`] -pub fn get_client_state(deps: Deps) -> Result, Error> { +pub fn get_client_state( + deps: Deps, + client_id: ClientId, +) -> Result, Error> { deps.storage .get(&"clientState".to_string().into_bytes()) - .ok_or_else(|| Error::unknown_client_state_type("08-wasm-0".to_string())) + .ok_or_else(|| Error::unknown_client_state_type(client_id.to_string())) .and_then(|client_state| deserialize_client_state(client_state, deps)) } diff --git a/light-clients/ics08-wasm/src/client_def.rs b/light-clients/ics08-wasm/src/client_def.rs index 0b8336aee..3725850fd 100644 --- a/light-clients/ics08-wasm/src/client_def.rs +++ b/light-clients/ics08-wasm/src/client_def.rs @@ -77,12 +77,8 @@ where client_state: Self::ClientState, client_msg: Self::ClientMessage, ) -> Result<(), Error> { - self.inner.verify_client_message( - ctx, - client_id, - *client_state.inner, - client_msg.into_inner(), - ) + self.inner + .verify_client_message(ctx, client_id, *client_state.inner, *client_msg.inner) } fn update_state( @@ -92,15 +88,12 @@ where client_state: Self::ClientState, client_msg: Self::ClientMessage, ) -> Result<(Self::ClientState, ConsensusUpdateResult), Error> { - let (inner_client_state, inner_consensus_update_result) = self.inner.update_state( - ctx, - client_id, - *client_state.inner, - client_msg.into_inner(), - )?; + let (inner_client_state, inner_consensus_update_result) = + self.inner + .update_state(ctx, client_id, *client_state.inner, *client_msg.inner)?; let client_state = ClientState { data: client_state.data.clone(), - code_id: client_state.code_id.clone(), + checksum: client_state.checksum.clone(), inner: Box::new(inner_client_state), latest_height: client_state.latest_height, _phantom: PhantomData, @@ -115,10 +108,10 @@ where ) -> Result { let inner_client_state = self .inner - .update_state_on_misbehaviour(*client_state.inner, client_msg.into_inner())?; + .update_state_on_misbehaviour(*client_state.inner, *client_msg.inner)?; Ok(ClientState { data: client_state.data.clone(), - code_id: client_state.code_id.clone(), + checksum: client_state.checksum.clone(), inner: Box::new(inner_client_state), latest_height: client_state.latest_height, _phantom: PhantomData, @@ -132,12 +125,8 @@ where client_state: Self::ClientState, client_msg: Self::ClientMessage, ) -> Result { - self.inner.check_for_misbehaviour( - ctx, - client_id, - *client_state.inner, - client_msg.into_inner(), - ) + self.inner + .check_for_misbehaviour(ctx, client_id, *client_state.inner, *client_msg.inner) } fn verify_upgrade_and_update_state( @@ -165,7 +154,7 @@ where ClientState { inner: Box::new(client_state), data: old_client_state.data.clone(), - code_id: old_client_state.code_id.clone(), + checksum: old_client_state.checksum.clone(), latest_height: old_client_state.latest_height, _phantom: Default::default(), }, @@ -192,7 +181,7 @@ where )?; let client_state = ClientState { data: old_client_state.data.clone(), - code_id: old_client_state.code_id.clone(), + checksum: old_client_state.checksum.clone(), inner: Box::new(inner_client_state), latest_height: old_client_state.latest_height, _phantom: PhantomData, diff --git a/light-clients/ics08-wasm/src/client_message.rs b/light-clients/ics08-wasm/src/client_message.rs index b9106d669..42799f821 100644 --- a/light-clients/ics08-wasm/src/client_message.rs +++ b/light-clients/ics08-wasm/src/client_message.rs @@ -12,7 +12,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - #[cfg(feature = "cosmwasm")] use crate::msg::Base64; use crate::Bytes; @@ -25,40 +24,26 @@ use core::fmt::Display; #[cfg(feature = "cosmwasm")] use cosmwasm_schema::cw_serde; use ibc::{ - core::ics02_client::{client_message::ClientMessage as IbcClientMessage, error::Error}, - protobuf::Protobuf, - Height, + core::ics02_client::client_message::ClientMessage as IbcClientMessage, protobuf::Protobuf, }; use ibc_proto::{ - google::protobuf::Any, - ibc::lightclients::wasm::v1::{Header as RawHeader, Misbehaviour as RawMisbehaviour}, + google::protobuf::Any, ibc::lightclients::wasm::v1::ClientMessage as RawClientMessage, }; use prost::Message; -pub const WASM_HEADER_TYPE_URL: &str = "/ibc.lightclients.wasm.v1.Header"; -pub const WASM_MISBEHAVIOUR_TYPE_URL: &str = "/ibc.lightclients.wasm.v1.Misbehaviour"; pub const WASM_CLIENT_MESSAGE_TYPE_URL: &str = "/ibc.lightclients.wasm.v1.ClientMessage"; -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ClientMessage { - Header(Header), - Misbehaviour(Misbehaviour), -} - -impl ClientMessage { - pub fn inner(&self) -> &AnyClientMessage { - match self { - ClientMessage::Header(h) => &h.inner, - ClientMessage::Misbehaviour(m) => &m.inner, - } - } - - pub fn into_inner(self) -> AnyClientMessage { - match self { - ClientMessage::Header(h) => *h.inner, - ClientMessage::Misbehaviour(m) => *m.inner, - } - } +//#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "cosmwasm", cw_serde)] +#[cfg_attr(not(feature = "cosmwasm"), derive(Clone, Debug, PartialEq))] +#[derive(Eq)] +pub struct ClientMessage { + #[cfg_attr(feature = "cosmwasm", serde(skip))] + #[cfg_attr(feature = "cosmwasm", schemars(skip))] + pub inner: Box, + #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] + #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] + pub data: Bytes, } impl IbcClientMessage for ClientMessage @@ -72,142 +57,48 @@ where } } -impl Protobuf for ClientMessage -where - AnyClientMessage: Clone, - AnyClientMessage: TryFrom, - >::Error: Display, -{ -} - -impl TryFrom for ClientMessage -where - AnyClientMessage: Clone, - AnyClientMessage: TryFrom, - >::Error: Display, -{ - type Error = Error; - - fn try_from(any: Any) -> Result { - let msg = match &*any.type_url { - WASM_HEADER_TYPE_URL => - Self::Header(Header::decode(&*any.value).map_err(Error::decode_raw_header)?), - WASM_MISBEHAVIOUR_TYPE_URL => Self::Misbehaviour( - Misbehaviour::decode(&*any.value).map_err(Error::decode_raw_misbehaviour)?, - ), - _ => return Err(Error::malformed_header()), // TODO: choose a better error - }; - - Ok(msg) - } -} - -impl From> for Any +impl ClientMessage where AnyClientMessage: Clone, - AnyClientMessage: TryFrom, + AnyClientMessage: TryFrom + IbcClientMessage, >::Error: Display, { - fn from(msg: ClientMessage) -> Self { - match msg { - ClientMessage::Header(header) => Any { - value: header.encode_vec().expect("encode header"), - type_url: WASM_HEADER_TYPE_URL.to_string(), - }, - ClientMessage::Misbehaviour(misbheaviour) => Any { - value: misbheaviour.encode_vec().expect("encode misbehaviour"), - type_url: WASM_MISBEHAVIOUR_TYPE_URL.to_string(), - }, + pub fn to_any(&self) -> Any { + Any { + value: self.encode_vec().expect("encode client message"), + type_url: WASM_CLIENT_MESSAGE_TYPE_URL.to_string(), } } } -impl Protobuf for Misbehaviour -where - AnyClientMessage: Clone, - AnyClientMessage: TryFrom, - >::Error: Display, -{ -} - -impl TryFrom for Misbehaviour +impl TryFrom for ClientMessage where - AnyClientMessage: TryFrom, + AnyClientMessage: Clone + TryFrom, >::Error: Display, { type Error = String; - fn try_from(raw: RawMisbehaviour) -> Result { - let any = Any::decode(&mut &raw.data[..]).map_err(|e| e.to_string())?; - let inner = AnyClientMessage::try_from(any).map_err(|e| e.to_string())?; - Ok(Self { inner: Box::new(inner), data: raw.data }) + fn try_from(raw: RawClientMessage) -> Result { + let any = Any::decode(&mut &raw.data[..]) + .map_err(|e| format!("failed to decode ClientMessage::data into Any: {}", e))?; + let inner = AnyClientMessage::try_from(any).map_err(|e| { + format!("failed to decode ClientMessage::data into ClientMessage: {}", e) + })?; + Ok(Self { data: raw.data, inner: Box::new(inner) }) } } -impl Protobuf for Header -where - AnyClientMessage: Clone, - AnyClientMessage: TryFrom, - >::Error: Display, +impl From> + for RawClientMessage { + fn from(value: ClientMessage) -> Self { + Self { data: value.data } + } } -impl TryFrom for Header +impl Protobuf for ClientMessage where - AnyClientMessage: TryFrom, + AnyClientMessage: Clone + IbcClientMessage + TryFrom, >::Error: Display, { - type Error = String; - - fn try_from(raw: RawHeader) -> Result { - let any = Any::decode(&mut &raw.data[..]) - .map_err(|e| format!("failed to decode raw header into Any: {e}"))?; - let inner = AnyClientMessage::try_from(any) - .map_err(|e| format!("failed to decode raw header into AnyClientMessage: {e}"))?; - - let header = Self { - inner: Box::new(inner), - data: raw.data, - height: raw - .height - .ok_or_else(|| { - "failed to decode raw header into Header: missing height".to_string() - })? - .into(), - }; - Ok(header) - } -} - -#[cfg_attr(feature = "cosmwasm", cw_serde)] -#[cfg_attr(not(feature = "cosmwasm"), derive(Clone, Debug, PartialEq))] -#[derive(Eq)] -pub struct Header { - #[cfg_attr(feature = "cosmwasm", serde(skip))] - #[cfg_attr(feature = "cosmwasm", schemars(skip))] - pub inner: Box, - #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] - #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] - pub data: Bytes, - pub height: Height, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Misbehaviour { - // #[schemars(with = "String")] - // #[serde(with = "Base64", default)] - pub inner: Box, - pub data: Bytes, -} - -impl From> for RawMisbehaviour { - fn from(value: Misbehaviour) -> Self { - RawMisbehaviour { data: value.data } - } -} - -impl From> for RawHeader { - fn from(value: Header) -> Self { - RawHeader { data: value.data, height: Some(value.height.into()) } - } } diff --git a/light-clients/ics08-wasm/src/client_state.rs b/light-clients/ics08-wasm/src/client_state.rs index 03f6910b4..b386cc1ba 100644 --- a/light-clients/ics08-wasm/src/client_state.rs +++ b/light-clients/ics08-wasm/src/client_state.rs @@ -55,7 +55,7 @@ pub struct ClientState { pub data: Bytes, #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] - pub code_id: Bytes, + pub checksum: Bytes, pub latest_height: Height, #[cfg_attr(feature = "cosmwasm", serde(skip))] #[cfg_attr(feature = "cosmwasm", schemars(skip))] @@ -166,7 +166,7 @@ where let inner = AnyClientState::try_from(any).map_err(|e| e.to_string())?; Ok(Self { data: raw.data, - code_id: raw.code_id, + checksum: raw.checksum, inner: Box::new(inner), latest_height: raw .latest_height @@ -186,7 +186,7 @@ where fn from(client_state: ClientState) -> Self { Self { data: client_state.data, - code_id: client_state.code_id, + checksum: client_state.checksum, latest_height: Some(client_state.latest_height.into()), } } @@ -202,3 +202,17 @@ where AnyClient: Clone, { } + +impl Default + for ClientState +{ + fn default() -> Self { + ClientState { + data: vec![], + checksum: vec![], + latest_height: Default::default(), + inner: Box::new(AnyClientState::default()), + _phantom: Default::default(), + } + } +} diff --git a/light-clients/ics08-wasm/src/consensus_state.rs b/light-clients/ics08-wasm/src/consensus_state.rs index 42dd961f9..33dab6555 100644 --- a/light-clients/ics08-wasm/src/consensus_state.rs +++ b/light-clients/ics08-wasm/src/consensus_state.rs @@ -29,7 +29,9 @@ use core::{ use cosmwasm_schema::cw_serde; use ibc::{ core::{ - ics02_client::client_consensus::ConsensusState as IbcConsensusState, + ics02_client::client_consensus::{ + ConsensusState as IcsConsensusState, ConsensusState as IbcConsensusState, + }, ics23_commitment::commitment::CommitmentRoot, }, protobuf::Protobuf, @@ -49,7 +51,6 @@ pub struct ConsensusState { #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] pub data: Bytes, - pub timestamp: u64, #[cfg_attr(feature = "cosmwasm", serde(skip))] #[cfg_attr(feature = "cosmwasm", schemars(skip))] pub inner: Box, @@ -68,7 +69,7 @@ where } fn timestamp(&self) -> Timestamp { - Timestamp::from_nanoseconds(self.timestamp).expect("timestamp is valid") + self.inner.timestamp() } fn encode_to_vec(&self) -> Result, tendermint_proto::Error> { @@ -105,20 +106,27 @@ where let inner = AnyConsensusState::try_from(any).map_err(|e| { format!("failed to decode ConsensusState::data into ConsensusState: {e}") })?; - Ok(Self { data: raw.data, timestamp: raw.timestamp, inner: Box::new(inner) }) + Ok(Self { data: raw.data, inner: Box::new(inner) }) } } -impl From> for RawConsensusState { +impl From> + for RawConsensusState +{ fn from(value: ConsensusState) -> Self { - Self { data: value.data, timestamp: value.timestamp } + Self { data: value.data } } } impl Protobuf for ConsensusState where - AnyConsensusState: Clone, - AnyConsensusState: TryFrom, + AnyConsensusState: Clone + IbcConsensusState + TryFrom, >::Error: Display, { } + +impl Default for ConsensusState { + fn default() -> Self { + ConsensusState { data: vec![], inner: Box::new(AnyConsensusState::default()) } + } +} diff --git a/light-clients/ics08-wasm/src/instantiate.rs b/light-clients/ics08-wasm/src/instantiate.rs new file mode 100644 index 000000000..91c9a6d17 --- /dev/null +++ b/light-clients/ics08-wasm/src/instantiate.rs @@ -0,0 +1,18 @@ +use crate::{msg::Base64, Bytes}; +#[cfg(feature = "cosmwasm")] +use cosmwasm_schema::cw_serde; + +#[cfg_attr(feature = "cosmwasm", cw_serde)] +#[cfg_attr(not(feature = "cosmwasm"), derive(Clone, Debug, PartialEq))] +#[derive(Eq)] +pub struct InstantiateMessage { + #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] + #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] + pub client_state: Bytes, + #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] + #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] + pub consensus_state: Bytes, + #[cfg_attr(feature = "cosmwasm", schemars(with = "String"))] + #[cfg_attr(feature = "cosmwasm", serde(with = "Base64", default))] + pub checksum: Bytes, +} diff --git a/light-clients/ics08-wasm/src/lib.rs b/light-clients/ics08-wasm/src/lib.rs index 4070ece03..9cc2b5a1d 100644 --- a/light-clients/ics08-wasm/src/lib.rs +++ b/light-clients/ics08-wasm/src/lib.rs @@ -24,6 +24,7 @@ pub mod client_def; pub mod client_message; pub mod client_state; pub mod consensus_state; +pub mod instantiate; pub mod msg; pub type Bytes = Vec; diff --git a/light-clients/ics10-grandpa-cw/Cargo.toml b/light-clients/ics10-grandpa-cw/Cargo.toml index dd48aa8ff..43a87a451 100644 --- a/light-clients/ics10-grandpa-cw/Cargo.toml +++ b/light-clients/ics10-grandpa-cw/Cargo.toml @@ -61,6 +61,8 @@ hyperspace-primitives = { path = "../../hyperspace/primitives", features = ["tes pallet-ibc = { path = "../../contracts/pallet-ibc" } serde-json-wasm = { version = "0.5.0", default-features = false } serde_json = { version = "1.0.93", default-features = false } +tendermint = { git = "https://github.com/informalsystems/tendermint-rs", rev = "e81f7bf23d63ffbcd242381d1ce5e35da3515ff1", default-features = false } + [features] # for more explicit tests, cargo test --features=backtraces diff --git a/light-clients/ics10-grandpa-cw/src/client.rs b/light-clients/ics10-grandpa-cw/src/client.rs index 368af2e56..7dbd63dc9 100644 --- a/light-clients/ics10-grandpa-cw/src/client.rs +++ b/light-clients/ics10-grandpa-cw/src/client.rs @@ -20,16 +20,15 @@ use crate::{ ClientStates, ConsensusStates, FakeInner, ReadonlyClientStates, ReadonlyClients, ReadonlyConsensusStates, }, - log, }; use grandpa_light_client_primitives::HostFunctions; use ibc::{ core::{ ics02_client::{ - client_consensus::ConsensusState as _, client_state::ClientType, context::{ClientKeeper, ClientReader, ClientTypes}, error::Error, + events::Checksum, }, ics24_host::identifier::ClientId, }, @@ -56,11 +55,8 @@ impl<'a, H: HostFunctions
> ClientTypes for Context<'a impl<'a, H: HostFunctions
> ClientReader for Context<'a, H> { fn client_type(&self, client_id: &ClientId) -> Result { - log!(self, "in client : [client_type] >> client_id = {:?}", client_id); - let clients = ReadonlyClients::new(self.storage()); if !clients.contains_key(client_id) { - log!(self, "in client : [client_type] >> read client_type is None"); return Err(Error::client_not_found(client_id.clone())) } @@ -74,19 +70,14 @@ impl<'a, H: HostFunctions
> ClientReader for Context<' })?; match ClientType::from_str(&data) { Err(_err) => Err(Error::unknown_client_type(data.to_string())), - Ok(val) => { - log!(self, "in client : [client_type] >> client_type : {:?}", val); - Ok(val) - }, + Ok(val) => Ok(val), } } fn client_state(&self, client_id: &ClientId) -> Result, Error> { - log!(self, "in client : [client_state] >> client_id = {:?}", client_id); let client_states = ReadonlyClientStates::new(self.storage()); let data = client_states.get().ok_or_else(|| Error::client_not_found(client_id.clone()))?; let state = Self::decode_client_state(&data)?; - log!(self, "in client : [client_state] >> any client_state: {:?}", state); Ok(state) } @@ -95,23 +86,11 @@ impl<'a, H: HostFunctions
> ClientReader for Context<' client_id: &ClientId, height: Height, ) -> Result { - log!(self, "in client : [consensus_state] >> height = {:?}", height); - let consensus_states = ReadonlyConsensusStates::new(self.storage()); let value = consensus_states .get(height) .ok_or_else(|| Error::consensus_state_not_found(client_id.clone(), height))?; - log!( - self, - "in client : [consensus_state] >> consensus_state (raw): {}", - hex::encode(&value) - ); let any_consensus_state = Self::decode_consensus_state(&value)?; - log!( - self, - "in client : [consensus_state] >> any consensus state = {:?}", - any_consensus_state - ); Ok(any_consensus_state) } @@ -148,7 +127,6 @@ impl<'a, H: HostFunctions
> ClientReader for Context<' } fn host_height(&self) -> Height { - log!(self, "in client: [host_height]"); Height::new(self.env.block.height, 0) } @@ -174,7 +152,6 @@ impl<'a, H: HostFunctions
> ClientReader for Context<' fn client_counter(&self) -> Result { let count = CLIENT_COUNTER.load(self.storage()).unwrap_or_default(); - log!(self, "in client : [client_counter] >> client_counter: {:?}", count); Ok(count as u64) } } @@ -193,11 +170,30 @@ impl<'a, H: HostFunctions
> ClientKeeper for Context<' client_id: ClientId, client_state: Self::AnyClientState, ) -> Result<(), Error> { - log!(self, "in client : [store_client_state]"); let client_states = ReadonlyClientStates::new(self.storage()); - let data = client_states.get().ok_or_else(|| Error::client_not_found(client_id.clone()))?; - let vec1 = Self::encode_client_state(client_state, data)?; - log!(self, "in cliden : [store_client_state] >> wasm client state (raw)"); + let checksum = match self.checksum.clone() { + None => { + let encoded_wasm_client_state = client_states + .get() + .ok_or_else(|| Error::client_not_found(client_id.clone()))?; + let any = Any::decode(&*encoded_wasm_client_state).map_err(Error::decode)?; + let wasm_client_state = ics08_wasm::client_state::ClientState::< + FakeInner, + FakeInner, + FakeInner, + >::decode_vec(&any.value) + .map_err(|e| { + Error::implementation_specific(format!( + "[client_state]: error decoding client state bytes to WasmConsensusState {}", + e + )) + })?; + wasm_client_state.checksum + }, + Some(x) => x, + }; + + let vec1 = Self::encode_client_state(client_state, checksum)?; let mut client_state_storage = ClientStates::new(self.storage_mut()); client_state_storage.insert(vec1); Ok(()) @@ -205,23 +201,11 @@ impl<'a, H: HostFunctions
> ClientKeeper for Context<' fn store_consensus_state( &mut self, - client_id: ClientId, + _client_id: ClientId, height: Height, consensus_state: Self::AnyConsensusState, ) -> Result<(), Error> { - log!( - self, - "in client : [store_consensus_state] >> client_id = {:?}, height = {:?}", - client_id, - height, - ); - let encoded = Self::encode_consensus_state(consensus_state); - log!( - self, - "in client : [store_consensus_state] >> wasm consensus state (raw) = {}", - hex::encode(&encoded) - ); let mut consensus_states = ConsensusStates::new(self.storage_mut()); consensus_states.insert(height, encoded); Ok(()) @@ -285,18 +269,11 @@ impl<'a, H: Clone> Context<'a, H> { pub fn encode_client_state( client_state: ClientState, - encoded_wasm_client_state: Vec, + checksum: Checksum, ) -> Result, Error> { - let any = Any::decode(&*encoded_wasm_client_state).map_err(Error::decode)?; let mut wasm_client_state = - ics08_wasm::client_state::ClientState::::decode_vec( - &any.value, - ) - .map_err(|e| { - Error::implementation_specific(format!( - "[client_state]: error decoding client state bytes to WasmConsensusState {e}" - )) - })?; + ics08_wasm::client_state::ClientState::::default(); + wasm_client_state.checksum = checksum; wasm_client_state.data = client_state.to_any().encode_to_vec(); wasm_client_state.latest_height = client_state.latest_height(); let vec1 = wasm_client_state.to_any().encode_to_vec(); @@ -306,7 +283,6 @@ impl<'a, H: Clone> Context<'a, H> { pub fn encode_consensus_state(consensus_state: ConsensusState) -> Vec { let wasm_consensus_state = ics08_wasm::consensus_state::ConsensusState { data: consensus_state.to_any().encode_to_vec(), - timestamp: consensus_state.timestamp().nanoseconds(), inner: Box::new(FakeInner), }; wasm_consensus_state.to_any().encode_to_vec() diff --git a/light-clients/ics10-grandpa-cw/src/context.rs b/light-clients/ics10-grandpa-cw/src/context.rs index 42e93af2b..71ad1dce7 100644 --- a/light-clients/ics10-grandpa-cw/src/context.rs +++ b/light-clients/ics10-grandpa-cw/src/context.rs @@ -18,12 +18,22 @@ use crate::{ GRANDPA_BLOCK_HASHES_CACHE_SIZE, GRANDPA_HEADER_HASHES_SET_STORAGE, GRANDPA_HEADER_HASHES_STORAGE, }, - ics23::{ClientStates, ConsensusStates, ReadonlyClientStates, ReadonlyConsensusStates}, + ics23::{ + ClientStates, ConsensusStates, FakeInner, ReadonlyClientStates, ReadonlyConsensusStates, + }, ContractError, }; -use cosmwasm_std::{DepsMut, Env, Storage}; +use cosmwasm_std::{Deps, DepsMut, Env, Storage}; use grandpa_light_client_primitives::HostFunctions; -use ibc::{core::ics26_routing::context::ReaderContext, Height}; +use ibc::{ + core::{ + ics02_client::{error::Error, events::Checksum}, + ics24_host::identifier::ClientId, + ics26_routing::context::ReaderContext, + }, + Height, +}; +use ibc_proto::google::protobuf::Any; use ics10_grandpa::{ client_message::RelayChainHeader, client_state::ClientState, consensus_state::ConsensusState, }; @@ -31,8 +41,10 @@ use sp_core::H256; use std::{fmt, fmt::Debug, marker::PhantomData}; pub struct Context<'a, H> { - pub deps: DepsMut<'a>, + pub deps_mut: Option>, + pub deps: Option>, pub env: Env, + pub checksum: Option, _phantom: PhantomData, } @@ -58,19 +70,35 @@ impl<'a, H> Clone for Context<'a, H> { impl<'a, H> Context<'a, H> { pub fn new(deps: DepsMut<'a>, env: Env) -> Self { - Self { deps, _phantom: Default::default(), env } + Self { deps_mut: Some(deps), deps: None, _phantom: Default::default(), env, checksum: None } + } + + pub fn new_ro(deps: Deps<'a>, env: Env) -> Self { + Self { deps_mut: None, deps: Some(deps), _phantom: Default::default(), env, checksum: None } } pub fn log(&self, msg: &str) { - self.deps.api.debug(msg) + match &self.deps_mut { + Some(deps_mut) => deps_mut.api.debug(msg), + None => unimplemented!(), + } } pub fn storage(&self) -> &dyn Storage { - self.deps.storage + match &self.deps_mut { + Some(deps_mut) => deps_mut.storage, + None => match &self.deps { + Some(deps) => deps.storage, + None => unimplemented!(), + }, + } } pub fn storage_mut(&mut self) -> &mut dyn Storage { - self.deps.storage + match &mut self.deps_mut { + Some(deps_mut) => deps_mut.storage, + None => unimplemented!(), + } } pub fn insert_relay_header_hashes(&mut self, headers: &[H256]) { @@ -151,13 +179,40 @@ where &mut self, client_state: ClientState, prefix: &[u8], + client_id: ClientId, ) -> Result<(), ContractError> { + use prost::Message; + use tendermint_proto::Protobuf; let client_states = ReadonlyClientStates::new(self.storage()); - let data = client_states.get_prefixed(prefix).ok_or_else(|| { - ContractError::Grandpa("no client state found for prefix".to_string()) - })?; - let encoded = Context::::encode_client_state(client_state, data) - .map_err(|e| ContractError::Grandpa(format!("error encoding client state: {e:?}")))?; + let checksum = match self.checksum.clone() { + None => { + let encoded_wasm_client_state = + client_states.get_prefixed(prefix).ok_or_else(|| { + ContractError::Grandpa(Error::client_not_found(client_id).to_string()) + })?; + let any = Any::decode(&*encoded_wasm_client_state) + .map_err(Error::decode) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + let wasm_client_state = ics08_wasm::client_state::ClientState::< + FakeInner, + FakeInner, + FakeInner, + >::decode_vec(&any.value) + .map_err(|e| { + ContractError::Grandpa( + Error::implementation_specific(format!( + "[client_state]: error decoding client state bytes to WasmConsensusState {}", + e + )) + .to_string(), + ) + })?; + wasm_client_state.checksum + }, + Some(x) => x, + }; + let encoded = Context::::encode_client_state(client_state, checksum) + .map_err(|e| ContractError::Grandpa(format!("error encoding client state: {:?}", e)))?; let mut client_states = ClientStates::new(self.storage_mut()); client_states.insert_prefixed(encoded, prefix); Ok(()) diff --git a/light-clients/ics10-grandpa-cw/src/contract.rs b/light-clients/ics10-grandpa-cw/src/contract.rs index bb1c49cad..8503bc5aa 100644 --- a/light-clients/ics10-grandpa-cw/src/contract.rs +++ b/light-clients/ics10-grandpa-cw/src/contract.rs @@ -18,10 +18,9 @@ use crate::{ error::ContractError, log, msg::{ - CheckForMisbehaviourMsg, CheckSubstituteAndUpdateStateMsg, ContractResult, ExecuteMsg, - ExportMetadataMsg, InstantiateMsg, QueryMsg, QueryResponse, StatusMsg, UpdateStateMsg, - UpdateStateOnMisbehaviourMsg, VerifyClientMessage, VerifyMembershipMsg, - VerifyNonMembershipMsg, VerifyUpgradeAndUpdateStateMsg, + CheckForMisbehaviourMsg, ContractResult, ExportMetadataMsg, QueryMsg, QueryResponse, + StatusMsg, SudoMsg, UpdateStateMsg, UpdateStateOnMisbehaviourMsg, VerifyClientMessage, + VerifyMembershipMsg, VerifyNonMembershipMsg, VerifyUpgradeAndUpdateStateMsg, }, state::{get_client_state, get_consensus_state}, Bytes, @@ -30,7 +29,9 @@ use byteorder::{ByteOrder, LittleEndian}; use core::hash::Hasher; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, +}; use cw_storage_plus::{Item, Map}; use digest::Digest; use grandpa_light_client_primitives::justification::AncestryChain; @@ -42,7 +43,8 @@ use ibc::core::{ }, ics24_host::identifier::ClientId, }; -use ics08_wasm::{SUBJECT_PREFIX, SUBSTITUTE_PREFIX}; +use ibc_proto::google::protobuf::Any; +use ics08_wasm::{instantiate::InstantiateMessage, SUBJECT_PREFIX, SUBSTITUTE_PREFIX}; use ics10_grandpa::{ client_def::GrandpaClient, client_message::{ClientMessage, RelayChainHeader}, @@ -50,10 +52,12 @@ use ics10_grandpa::{ consensus_state::ConsensusState, }; use light_client_common::{verify_membership, verify_non_membership}; +use prost::Message; use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, Header}; use sp_runtime_interface::unpack_ptr_and_len; use std::{collections::BTreeSet, str::FromStr}; +use tendermint_proto::Protobuf; /* // version info for migration info const CONTRACT_NAME: &str = "crates.io:ics10-grandpa-cw"; @@ -113,26 +117,46 @@ impl grandpa_light_client_primitives::HostFunctions for HostFunctions { } } -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: InstantiateMsg, -) -> Result { - Ok(Response::default()) +fn process_instantiate_msg( + msg: InstantiateMessage, + ctx: &mut Context, + client_id: ClientId, +) -> Result { + let any = Any::decode(&mut msg.client_state.as_slice())?; + let client_state = ClientState::decode_vec(&any.value)?; + let any = Any::decode(&mut msg.consensus_state.as_slice())?; + let consensus_state = ConsensusState::decode_vec(&any.value)?; + + let height = client_state.latest_height(); + ctx.checksum = Some(msg.checksum); + ctx.store_client_state(client_id.clone(), client_state) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + ctx.store_consensus_state(client_id, height, consensus_state) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + Ok(to_binary(&ContractResult::success())?) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( +pub fn instantiate( deps: DepsMut, env: Env, _info: MessageInfo, - msg: ExecuteMsg, + msg: InstantiateMessage, ) -> Result { + let client_id = ClientId::from_str(env.contract.address.as_str()).expect("client id is valid"); + let mut ctx = Context::::new(deps, env); + let data = process_instantiate_msg(msg, &mut ctx, client_id.clone())?; + + let mut response = Response::default(); + response.data = Some(data); + Ok(response) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result { let client = GrandpaClient::::default(); + let client_id = ClientId::from_str(env.contract.address.as_str()).expect("client id is valid"); let mut ctx = Context::::new(deps, env); - let client_id = ClientId::from_str("08-wasm-0").expect("client id is valid"); let data = process_message(msg, client, &mut ctx, client_id)?; let mut response = Response::default(); response.data = Some(data); @@ -140,79 +164,14 @@ pub fn execute( } fn process_message( - msg: ExecuteMsg, + msg: SudoMsg, client: GrandpaClient, ctx: &mut Context, client_id: ClientId, ) -> Result { // log!(ctx, "process_message: {:?}", msg); let result = match msg { - ExecuteMsg::VerifyMembership(msg) => { - let msg = VerifyMembershipMsg::try_from(msg)?; - let consensus_state = ctx - .consensus_state(&client_id, msg.height) - .map_err(|e| ContractError::Grandpa(e.to_string()))?; - verify_membership::( - &msg.prefix, - &msg.proof, - &consensus_state.root, - msg.path, - msg.value, - ) - .map_err(|e| ContractError::Grandpa(e.to_string()))?; - Ok(()).map(|_| to_binary(&ContractResult::success())) - }, - ExecuteMsg::VerifyNonMembership(msg) => { - let msg = VerifyNonMembershipMsg::try_from(msg)?; - let consensus_state = ctx - .consensus_state(&client_id, msg.height) - .map_err(|e| ContractError::Grandpa(e.to_string()))?; - - verify_non_membership::( - &msg.prefix, - &msg.proof, - &consensus_state.root, - msg.path, - ) - .map_err(|e| ContractError::Grandpa(e.to_string())) - .map(|_| to_binary(&ContractResult::success())) - }, - ExecuteMsg::VerifyClientMessage(msg) => { - let client_state = ctx - .client_state(&client_id) - .map_err(|e| ContractError::Grandpa(e.to_string()))?; - let msg = VerifyClientMessage::try_from(msg)?; - - if let ClientMessage::Misbehaviour(misbehavior) = &msg.client_message { - let first_proof = &misbehavior.first_finality_proof; - let first_base = - first_proof.unknown_headers.iter().min_by_key(|h| *h.number()).ok_or_else( - || ContractError::Grandpa("Unknown headers can't be empty!".to_string()), - )?; - let first_parent = first_base.parent_hash; - if !ctx.contains_relay_header_hash(first_parent) { - Err(ContractError::Grandpa( - "Could not find the known header for first finality proof".to_string(), - ))? - } - } - - client - .verify_client_message(ctx, client_id, client_state, msg.client_message) - .map_err(|e| ContractError::Grandpa(format!("{e:?}"))) - .map(|_| to_binary(&ContractResult::success())) - }, - ExecuteMsg::CheckForMisbehaviour(msg) => { - let client_state = ctx - .client_state(&client_id) - .map_err(|e| ContractError::Grandpa(e.to_string()))?; - let msg = CheckForMisbehaviourMsg::try_from(msg)?; - client - .check_for_misbehaviour(ctx, client_id, client_state, msg.client_message) - .map_err(|e| ContractError::Grandpa(e.to_string())) - .map(|result| to_binary(&ContractResult::success().misbehaviour(result))) - }, - ExecuteMsg::UpdateStateOnMisbehaviour(msg_raw) => { + SudoMsg::UpdateStateOnMisbehaviour(msg_raw) => { let client_state = ctx .client_state(&client_id) .map_err(|e| ContractError::Grandpa(e.to_string()))?; @@ -226,7 +185,7 @@ fn process_message( Ok(to_binary(&ContractResult::success())) }) }, - ExecuteMsg::UpdateState(msg_raw) => { + SudoMsg::UpdateState(msg_raw) => { let client_state = ctx .client_state(&client_id) .map_err(|e| ContractError::Grandpa(e.to_string()))?; @@ -257,57 +216,56 @@ fn process_message( store_client_and_consensus_states(ctx, client_id.clone(), cs, cu) }) }, - ExecuteMsg::CheckSubstituteAndUpdateState(msg) => { - let _msg = CheckSubstituteAndUpdateStateMsg::try_from(msg)?; - // manually load both states from the combined storage using the appropriate prefixes - let mut old_client_state = ctx - .client_state_prefixed(SUBJECT_PREFIX) - .map_err(|e| ContractError::Grandpa(e.to_string()))?; + SudoMsg::MigrateClientStore(_msg) => { + // load the substitute client state from the combined storage using the appropriate + // prefix let substitute_client_state = ctx .client_state_prefixed(SUBSTITUTE_PREFIX) .map_err(|e| ContractError::Grandpa(e.to_string()))?; - // Check that the substitute client state is valid: - // all fields should be the same as in the old state, except for the `relay_chain`, - // `para_id`, `latest_para_height`, `latest_relay_height`, `latest_relay_hash`, - // `frozen_height`, `current_authorities`, `current_set_id` - let ClientState { - relay_chain, - latest_relay_height, - latest_relay_hash, - frozen_height, - latest_para_height, - para_id, - current_set_id, - current_authorities, - _phantom, - } = substitute_client_state.clone(); - old_client_state.relay_chain = relay_chain; - old_client_state.para_id = para_id; - old_client_state.latest_para_height = latest_para_height; - old_client_state.latest_relay_height = latest_relay_height; - old_client_state.latest_relay_hash = latest_relay_hash; - old_client_state.frozen_height = frozen_height; - old_client_state.current_authorities = current_authorities.clone(); - old_client_state.current_set_id = current_set_id; - - if old_client_state != substitute_client_state { - return Err(ContractError::Grandpa( - "subject client state does not match substitute client state".to_string(), - )) - } - let substitute_client_state = old_client_state; + // No items for the grandpa client state are required to be the same + let height = substitute_client_state.latest_height(); // consensus state should be replaced as well let substitute_consensus_state = ctx.consensus_state_prefixed(height, SUBSTITUTE_PREFIX)?; ctx.store_consensus_state_prefixed(height, substitute_consensus_state, SUBJECT_PREFIX); - ctx.store_client_state_prefixed(substitute_client_state, SUBJECT_PREFIX) + ctx.store_client_state_prefixed(substitute_client_state, SUBJECT_PREFIX, client_id) .map_err(|e| ContractError::Grandpa(e.to_string()))?; Ok(()).map(|_| to_binary(&ContractResult::success())) }, - ExecuteMsg::VerifyUpgradeAndUpdateState(msg) => { + SudoMsg::VerifyMembership(msg) => { + let msg = VerifyMembershipMsg::try_from(msg)?; + let consensus_state = ctx + .consensus_state(&client_id, msg.height) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + verify_membership::( + &msg.prefix, + &msg.proof, + &consensus_state.root, + msg.path, + msg.value, + ) + .map_err(|e| ContractError::Grandpa(e.to_string())) + .map(|_| to_binary(&ContractResult::success())) + }, + SudoMsg::VerifyNonMembership(msg) => { + let msg = VerifyNonMembershipMsg::try_from(msg)?; + let consensus_state = ctx + .consensus_state(&client_id, msg.height) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + + verify_non_membership::( + &msg.prefix, + &msg.proof, + &consensus_state.root, + msg.path, + ) + .map_err(|e| ContractError::Grandpa(e.to_string())) + .map(|_| to_binary(&ContractResult::success())) + }, + SudoMsg::VerifyUpgradeAndUpdateState(msg) => { let old_client_state = ctx .client_state(&client_id) .map_err(|e| ContractError::Grandpa(e.to_string()))?; @@ -333,29 +291,101 @@ fn process_message( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - let client_id = ClientId::from_str("08-wasm-0").expect("client id is valid"); +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let client_id = ClientId::from_str(env.contract.address.as_str()).expect("client id is valid"); match msg { + QueryMsg::CheckForMisbehaviour(msg) => { + let ctx = Context::::new_ro(deps, env); + let client = GrandpaClient::::default(); + let client_state = ctx + .client_state(&client_id) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + let msg = CheckForMisbehaviourMsg::try_from(msg)?; + client + .check_for_misbehaviour(&ctx, client_id, client_state, msg.client_message) + .map_err(|e| ContractError::Grandpa(e.to_string())) + .map(|result| to_binary(&QueryResponse::success().misbehaviour(result)))? + }, QueryMsg::ClientTypeMsg(_) => unimplemented!("ClientTypeMsg"), QueryMsg::GetLatestHeightsMsg(_) => unimplemented!("GetLatestHeightsMsg"), QueryMsg::ExportMetadata(ExportMetadataMsg {}) => - to_binary(&QueryResponse::genesis_metadata(None)), + to_binary(&QueryResponse::success().genesis_metadata(None)), QueryMsg::Status(StatusMsg {}) => { - let client_state = match get_client_state::(deps) { + let client_state = match get_client_state::(deps, client_id.clone()) { Ok(client_state) => client_state, - Err(_) => return to_binary(&QueryResponse::status("Unknown".to_string())), + Err(_) => return to_binary(&QueryResponse::success().status("Unknown".to_string())), }; if client_state.frozen_height().is_some() { - to_binary(&QueryResponse::status("Frozen".to_string())) + to_binary(&QueryResponse::success().status("Frozen".to_string())) } else { let height = client_state.latest_height(); match get_consensus_state(deps, &client_id, height) { - Ok(_) => to_binary(&QueryResponse::status("Active".to_string())), - Err(_) => to_binary(&QueryResponse::status("Expired".to_string())), + Ok(consensus_state_raw) => { + let consensus_state = + Context::::decode_consensus_state(&consensus_state_raw) + .map_err(|e| { + StdError::serialize_err(e.to_string(), e.to_string()) + })?; + if client_state.expired(core::time::Duration::from_secs( + env.block.time.seconds() - + consensus_state.timestamp.unix_timestamp() as u64, + )) { + return to_binary( + &QueryResponse::success().status("Expired".to_string()), + ) + } + to_binary(&QueryResponse::success().status("Active".to_string())) + }, + Err(_) => to_binary(&QueryResponse::success().status("Expired".to_string())), } } }, + QueryMsg::TimestampAtHeight(msg) => { + let ctx = Context::::new_ro(deps, env); + let consensus_state = ctx + .consensus_state(&client_id, msg.height) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + to_binary( + &QueryResponse::success().timestamp( + consensus_state.timestamp.unix_timestamp_nanos().unsigned_abs() as u64, + ), + ) + }, + QueryMsg::VerifyClientMessage(msg) => { + let ctx = Context::::new_ro(deps, env); + let client = GrandpaClient::::default(); + let client_state = ctx + .client_state(&client_id) + .map_err(|e| ContractError::Grandpa(e.to_string()))?; + let msg = VerifyClientMessage::try_from(msg)?; + + match &msg.client_message { + ClientMessage::Misbehaviour(misbehavior) => { + let first_proof = &misbehavior.first_finality_proof; + let first_base = first_proof + .unknown_headers + .iter() + .min_by_key(|h| *h.number()) + .ok_or_else(|| { + ContractError::Grandpa("Unknown headers can't be empty!".to_string()) + })?; + let first_parent = first_base.parent_hash; + if !ctx.contains_relay_header_hash(first_parent) { + Err(ContractError::Grandpa( + "Could not find the known header for first finality proof".to_string(), + ))? + } + }, + _ => {}, + } + + let f = client + .verify_client_message(&ctx, client_id, client_state, msg.client_message) + .map_err(|e| ContractError::Grandpa(format!("{e:?}"))) + .map(|_| to_binary(&QueryResponse::success()))?; + f + }, } } @@ -369,15 +399,18 @@ where H: grandpa_light_client_primitives::HostFunctions
, { let height = client_state.latest_height(); + let mut heights: Vec = vec![]; match consensus_update { ConsensusUpdateResult::Single(cs) => { log!(ctx, "Storing consensus state: {:?}", height); + heights.push(height); ctx.store_consensus_state(client_id.clone(), height, cs) .map_err(|e| ContractError::Grandpa(e.to_string()))?; }, ConsensusUpdateResult::Batch(css) => for (height, cs) in css { log!(ctx, "Storing consensus state: {:?}", height); + heights.push(height); ctx.store_consensus_state(client_id.clone(), height, cs) .map_err(|e| ContractError::Grandpa(e.to_string()))?; }, @@ -385,7 +418,7 @@ where log!(ctx, "Storing client state with height: {:?}", height); ctx.store_client_state(client_id, client_state) .map_err(|e| ContractError::Grandpa(e.to_string()))?; - Ok(to_binary(&ContractResult::success())) + Ok(to_binary(&ContractResult::success().heights(heights))) } // The FFIs below are required because of sp-io dependency that expects the functions to be @@ -433,3 +466,59 @@ pub extern "C" fn ext_hashing_twox_64_version_1(data: i64) -> i32 { let out_ptr = Box::leak(hash).as_ptr(); out_ptr as i32 } + +#[cfg(test)] +mod tests { + use cosmwasm_std::{ + from_binary, + testing::{mock_dependencies, mock_env}, + }; + use ibc::core::ics02_client::client_state::ClientState; + use tendermint::Time; + + use crate::ics23::ClientStates; + + use super::*; + #[test] + fn test_query() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + for (expected, offset) in + [("Active", 0i64), ("Expired", env.block.time.seconds() as i64 - 10), ("Frozen", 0i64)] + { + let mut client_state = + ics10_grandpa::client_state::ClientState::::default(); + let mut consensus_state = ics10_grandpa::consensus_state::ConsensusState::new( + vec![], + Time::from_unix_timestamp(0, 0).unwrap(), + ); + let height = Height { revision_number: 0, revision_height: 1000 }; + client_state.latest_para_height = height.revision_height as _; + + consensus_state.timestamp = + Time::from_unix_timestamp(env.block.time.seconds() as i64 - offset, 0).unwrap(); + let deps_mut = deps.as_mut(); + if expected == "Frozen" { + let height = + Height { revision_number: 0, revision_height: height.revision_height - 100 }; + client_state = client_state.with_frozen_height(height.clone()).unwrap(); + } + + let mut client_states = ClientStates::new(deps_mut.storage); + client_states.insert(client_state.encode_to_vec().unwrap()); + + let mut context = Context::new(deps_mut, env.clone()); + context.store_client_state(ClientId::default(), client_state).unwrap(); + context + .store_consensus_state(ClientId::default(), height, consensus_state) + .unwrap(); + + let resp = query(deps.as_ref(), mock_env(), QueryMsg::Status(StatusMsg {})).unwrap(); + + let resp: QueryResponse = from_binary(&resp).unwrap(); + + assert_eq!(resp, QueryResponse::success().status(expected.to_string())); + } + } +} diff --git a/light-clients/ics10-grandpa-cw/src/error.rs b/light-clients/ics10-grandpa-cw/src/error.rs index cefc3c7a6..ba39107ca 100644 --- a/light-clients/ics10-grandpa-cw/src/error.rs +++ b/light-clients/ics10-grandpa-cw/src/error.rs @@ -55,3 +55,9 @@ impl From for ContractError { ContractError::Grandpa(e.to_string()) } } + +impl From for StdError { + fn from(e: ContractError) -> Self { + StdError::GenericErr { msg: e.to_string() } + } +} diff --git a/light-clients/ics10-grandpa-cw/src/helpers.rs b/light-clients/ics10-grandpa-cw/src/helpers.rs index 097b925fa..684306031 100644 --- a/light-clients/ics10-grandpa-cw/src/helpers.rs +++ b/light-clients/ics10-grandpa-cw/src/helpers.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; -use crate::msg::ExecuteMsg; +use crate::msg::SudoMsg; /// CwTemplateContract is a wrapper around Addr that provides a lot of helpers /// for working with this. @@ -30,7 +30,7 @@ impl CwTemplateContract { self.0.clone() } - pub fn call>(&self, msg: T) -> StdResult { + pub fn call>(&self, msg: T) -> StdResult { let msg = to_binary(&msg.into())?; Ok(WasmMsg::Execute { contract_addr: self.addr().into(), msg, funds: vec![] }.into()) } diff --git a/light-clients/ics10-grandpa-cw/src/msg.rs b/light-clients/ics10-grandpa-cw/src/msg.rs index 0aea7b7eb..13d7d0c99 100644 --- a/light-clients/ics10-grandpa-cw/src/msg.rs +++ b/light-clients/ics10-grandpa-cw/src/msg.rs @@ -26,11 +26,13 @@ use ibc::{ }; use ibc_proto::{google::protobuf::Any, ibc::core::client::v1::Height as HeightRaw}; use ics08_wasm::{ - client_message::Header as WasmHeader, client_state::ClientState as WasmClientState, + client_state::ClientState as WasmClientState, consensus_state::ConsensusState as WasmConsensusState, }; use ics10_grandpa::{ - client_message::{ClientMessage, Header, Misbehaviour}, + client_message::{ + ClientMessage, Header, Misbehaviour, GRANDPA_HEADER_TYPE_URL, GRANDPA_MISBEHAVIOUR_TYPE_URL, + }, client_state::ClientState, consensus_state::ConsensusState, }; @@ -57,46 +59,62 @@ pub struct GenesisMetadata { #[cw_serde] pub struct QueryResponse { - pub status: String, + pub is_valid: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, #[serde(skip_serializing_if = "Option::is_none")] pub genesis_metadata: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub found_misbehaviour: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp: Option, } impl QueryResponse { - pub fn status(status: String) -> Self { - Self { status, genesis_metadata: None } + pub fn success() -> Self { + Self { + is_valid: true, + status: None, + genesis_metadata: None, + found_misbehaviour: None, + timestamp: None, + } } - pub fn genesis_metadata(genesis_metadata: Option>) -> Self { - Self { status: "".to_string(), genesis_metadata } + pub fn status(mut self, status: String) -> Self { + self.status = Some(status); + self + } + + pub fn genesis_metadata(mut self, genesis_metadata: Option>) -> Self { + self.genesis_metadata = genesis_metadata; + self + } + + pub fn misbehaviour(mut self, found_misbehavior: bool) -> Self { + self.found_misbehaviour = Some(found_misbehavior); + self + } + + pub fn timestamp(mut self, timestamp: u64) -> Self { + self.timestamp = Some(timestamp); + self } } #[cw_serde] pub struct ContractResult { - pub is_valid: bool, - pub error_msg: String, #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option>, - pub found_misbehaviour: bool, + pub heights: Option>, } impl ContractResult { pub fn success() -> Self { - Self { is_valid: true, error_msg: "".to_string(), data: None, found_misbehaviour: false } - } - - pub fn error(msg: String) -> Self { - Self { is_valid: false, error_msg: msg, data: None, found_misbehaviour: false } + Self { heights: None } } - pub fn misbehaviour(mut self, found: bool) -> Self { - self.found_misbehaviour = found; - self - } - - pub fn data(mut self, data: Vec) -> Self { - self.data = Some(data); + pub fn heights(mut self, heights: Vec) -> Self { + self.heights = Some(heights); self } } @@ -110,26 +128,40 @@ pub struct ClientStateCallResponse { } #[cw_serde] -pub struct InstantiateMsg {} +pub struct InitializeState { + pub client_state: WasmClientState, + pub consensus_state: WasmConsensusState, +} #[cw_serde] -pub enum ExecuteMsg { - VerifyMembership(VerifyMembershipMsgRaw), - VerifyNonMembership(VerifyNonMembershipMsgRaw), - VerifyClientMessage(VerifyClientMessageRaw), - CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +pub struct ClientCreateRequest { + client_create_request: WasmConsensusState, +} + +#[cw_serde] +pub enum SudoMsg { + MigrateClientStore(MigrateClientStoreMsgRaw), UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), UpdateState(UpdateStateMsgRaw), - CheckSubstituteAndUpdateState(CheckSubstituteAndUpdateStateMsgRaw), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), } #[cw_serde] pub enum QueryMsg { + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), ClientTypeMsg(ClientTypeMsg), GetLatestHeightsMsg(GetLatestHeightsMsg), ExportMetadata(ExportMetadataMsg), Status(StatusMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), +} + +#[cw_serde] +pub struct TimestampAtHeightMsg { + pub height: Height, } #[cw_serde] @@ -150,93 +182,10 @@ pub struct MerklePath { } #[cw_serde] -pub struct VerifyMembershipMsgRaw { - #[schemars(with = "String")] - #[serde(with = "Base64", default)] - pub proof: Bytes, - pub path: MerklePath, - #[schemars(with = "String")] - #[serde(with = "Base64", default)] - pub value: Bytes, - pub height: HeightRaw, - pub delay_block_period: u64, - pub delay_time_period: u64, -} - -pub struct VerifyMembershipMsg { - pub prefix: CommitmentPrefix, - pub proof: CommitmentProofBytes, - pub path: Path, - pub value: Vec, - pub height: Height, -} - -impl TryFrom for VerifyMembershipMsg { - type Error = ContractError; - - fn try_from(mut raw: VerifyMembershipMsgRaw) -> Result { - let proof = CommitmentProofBytes::try_from(raw.proof)?; - let prefix = raw.path.key_path.remove(0).into_bytes(); - let path_str = raw.path.key_path.join(""); - let path = Path::from_str(&path_str)?; - let height = Height::from(raw.height); - Ok(Self { - proof, - path, - value: raw.value, - height, - prefix: CommitmentPrefix::try_from(prefix)?, - }) - } -} - -#[cw_serde] -pub struct VerifyNonMembershipMsgRaw { - #[schemars(with = "String")] - #[serde(with = "Base64", default)] - pub proof: Bytes, - pub path: MerklePath, - pub height: HeightRaw, - pub delay_block_period: u64, - pub delay_time_period: u64, -} - -pub struct VerifyNonMembershipMsg { - pub prefix: CommitmentPrefix, - pub proof: CommitmentProofBytes, - pub path: Path, - pub height: Height, -} - -impl TryFrom for VerifyNonMembershipMsg { - type Error = ContractError; - - fn try_from(mut raw: VerifyNonMembershipMsgRaw) -> Result { - let proof = CommitmentProofBytes::try_from(raw.proof)?; - let prefix = raw.path.key_path.remove(0).into_bytes(); - let path_str = raw.path.key_path.join(""); - let path = Path::from_str(&path_str)?; - let height = Height::from(raw.height); - Ok(Self { proof, path, height, prefix: CommitmentPrefix::try_from(prefix)? }) - } -} - -#[cw_serde] -pub struct WasmMisbehaviour { +pub struct VerifyClientMessageRaw { #[schemars(with = "String")] #[serde(with = "Base64", default)] - pub data: Bytes, -} - -#[cw_serde] -pub enum ClientMessageRaw { - Header(WasmHeader), - Misbehaviour(WasmMisbehaviour), -} - -#[cw_serde] -pub struct VerifyClientMessageRaw { - pub client_message: ClientMessageRaw, + pub client_message: Bytes, } pub struct VerifyClientMessage { @@ -253,16 +202,13 @@ impl TryFrom for VerifyClientMessage { } impl VerifyClientMessage { - fn decode_client_message(raw: ClientMessageRaw) -> Result { - let client_message = match raw { - ClientMessageRaw::Header(header) => { - let any = Any::decode(&mut header.data.as_slice())?; - ClientMessage::Header(Header::decode_vec(&any.value)?) - }, - ClientMessageRaw::Misbehaviour(misbehaviour) => { - let any = Any::decode(&mut misbehaviour.data.as_slice())?; - ClientMessage::Misbehaviour(Misbehaviour::decode_vec(&any.value)?) - }, + fn decode_client_message(raw: Bytes) -> Result { + let any = Any::decode(&mut raw.as_slice())?; + let client_message = match &*any.type_url { + GRANDPA_HEADER_TYPE_URL => ClientMessage::Header(Header::decode_vec(&any.value)?), + GRANDPA_MISBEHAVIOUR_TYPE_URL => + ClientMessage::Misbehaviour(Misbehaviour::decode_vec(&any.value)?), + _ => return Err(ContractError::Grandpa("unknown client message type".to_string())), }; Ok(client_message) } @@ -270,7 +216,9 @@ impl VerifyClientMessage { #[cw_serde] pub struct CheckForMisbehaviourMsgRaw { - pub client_message: ClientMessageRaw, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, } pub struct CheckForMisbehaviourMsg { @@ -288,7 +236,9 @@ impl TryFrom for CheckForMisbehaviourMsg { #[cw_serde] pub struct UpdateStateOnMisbehaviourMsgRaw { - pub client_message: ClientMessageRaw, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, } pub struct UpdateStateOnMisbehaviourMsg { @@ -306,7 +256,9 @@ impl TryFrom for UpdateStateOnMisbehaviourMsg { #[cw_serde] pub struct UpdateStateMsgRaw { - pub client_message: ClientMessageRaw, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub client_message: Bytes, } pub struct UpdateStateMsg { @@ -323,24 +275,99 @@ impl TryFrom for UpdateStateMsg { } #[cw_serde] -pub struct CheckSubstituteAndUpdateStateMsgRaw {} +pub struct MigrateClientStoreMsgRaw {} -pub struct CheckSubstituteAndUpdateStateMsg {} +pub struct MigrateClientStoreMsg {} -impl TryFrom for CheckSubstituteAndUpdateStateMsg { +impl TryFrom for MigrateClientStoreMsg { type Error = ContractError; fn try_from( - CheckSubstituteAndUpdateStateMsgRaw {}: CheckSubstituteAndUpdateStateMsgRaw, + MigrateClientStoreMsgRaw {}: MigrateClientStoreMsgRaw, ) -> Result { Ok(Self {}) } } +#[cw_serde] +pub struct VerifyMembershipMsgRaw { + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub proof: Bytes, + pub path: MerklePath, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub value: Bytes, + pub height: HeightRaw, + pub delay_block_period: u64, + pub delay_time_period: u64, +} + +pub struct VerifyMembershipMsg { + pub prefix: CommitmentPrefix, + pub proof: CommitmentProofBytes, + pub path: Path, + pub value: Vec, + pub height: Height, +} + +impl TryFrom for VerifyMembershipMsg { + type Error = ContractError; + + fn try_from(mut raw: VerifyMembershipMsgRaw) -> Result { + let proof = CommitmentProofBytes::try_from(raw.proof)?; + let prefix = raw.path.key_path.remove(0).into_bytes(); + let path_str = raw.path.key_path.join(""); + let path = Path::from_str(&path_str)?; + let height = Height::from(raw.height); + Ok(Self { + proof, + path, + value: raw.value, + height, + prefix: CommitmentPrefix::try_from(prefix)?, + }) + } +} + +#[cw_serde] +pub struct VerifyNonMembershipMsgRaw { + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub proof: Bytes, + pub path: MerklePath, + pub height: HeightRaw, + pub delay_block_period: u64, + pub delay_time_period: u64, +} + +pub struct VerifyNonMembershipMsg { + pub prefix: CommitmentPrefix, + pub proof: CommitmentProofBytes, + pub path: Path, + pub height: Height, +} + +impl TryFrom for VerifyNonMembershipMsg { + type Error = ContractError; + + fn try_from(mut raw: VerifyNonMembershipMsgRaw) -> Result { + let proof = CommitmentProofBytes::try_from(raw.proof)?; + let prefix = raw.path.key_path.remove(0).into_bytes(); + let path_str = raw.path.key_path.join(""); + let path = Path::from_str(&path_str)?; + let height = Height::from(raw.height); + Ok(Self { proof, path, height, prefix: CommitmentPrefix::try_from(prefix)? }) + } +} #[cw_serde] pub struct VerifyUpgradeAndUpdateStateMsgRaw { - pub upgrade_client_state: WasmClientState, - pub upgrade_consensus_state: WasmConsensusState, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub upgrade_client_state: Bytes, + #[schemars(with = "String")] + #[serde(with = "Base64", default)] + pub upgrade_consensus_state: Bytes, #[schemars(with = "String")] #[serde(with = "Base64", default)] pub proof_upgrade_client: Vec, @@ -360,9 +387,9 @@ impl TryFrom for VerifyUpgradeAndUp type Error = ContractError; fn try_from(raw: VerifyUpgradeAndUpdateStateMsgRaw) -> Result { - let any = Any::decode(&mut raw.upgrade_client_state.data.as_slice())?; + let any = Any::decode(&mut raw.upgrade_client_state.as_slice())?; let upgrade_client_state = ClientState::decode_vec(&any.value)?; - let any = Any::decode(&mut raw.upgrade_consensus_state.data.as_slice())?; + let any = Any::decode(&mut raw.upgrade_consensus_state.as_slice())?; let upgrade_consensus_state = ConsensusState::decode_vec(&any.value)?; Ok(VerifyUpgradeAndUpdateStateMsg { upgrade_client_state, diff --git a/light-clients/ics10-grandpa-cw/src/state.rs b/light-clients/ics10-grandpa-cw/src/state.rs index 42d8da946..23b9813f4 100644 --- a/light-clients/ics10-grandpa-cw/src/state.rs +++ b/light-clients/ics10-grandpa-cw/src/state.rs @@ -25,10 +25,13 @@ use ics10_grandpa::client_state::ClientState; use prost::Message; /// Retrieves raw bytes from storage and deserializes them into [`ClientState`] -pub fn get_client_state(deps: Deps) -> Result, Error> { +pub fn get_client_state( + deps: Deps, + client_id: ClientId, +) -> Result, Error> { deps.storage .get(&"clientState".to_string().into_bytes()) - .ok_or_else(|| Error::unknown_client_state_type("08-wasm-0".to_string())) + .ok_or_else(|| Error::unknown_client_state_type(client_id.to_string())) .and_then(deserialize_client_state) } diff --git a/scripts/build-parachain-node-docker.sh b/scripts/build-parachain-node-docker.sh index 4594a4e7a..d3da02a24 100755 --- a/scripts/build-parachain-node-docker.sh +++ b/scripts/build-parachain-node-docker.sh @@ -3,4 +3,4 @@ set -e set -x -DOCKER_BUILDKIT=0 docker build --platform linux/amd64 -f scripts/parachain.Dockerfile . -t parachain-node:latest \ No newline at end of file +DOCKER_BUILDKIT=0 docker build --platform linux/amd64 -f scripts/parachain.Dockerfile . -t parachain-node:latest diff --git a/scripts/parachain.Dockerfile b/scripts/parachain.Dockerfile index 797c40773..35cfffb62 100644 --- a/scripts/parachain.Dockerfile +++ b/scripts/parachain.Dockerfile @@ -29,6 +29,11 @@ RUN apt update && \ update-ca-certificates && \ rm -rf /var/lib/apt/lists/* +RUN useradd -m -u 1000 -U -s /bin/sh -d /parachain parachain && \ + chown -R parachain:parachain /usr/local/bin + +USER parachain + # check if executable works in this container RUN /usr/local/bin/parachain-node --version diff --git a/utils/subxt/generated/parachain.rs b/utils/subxt/generated/parachain.rs index 9217fea9f..7ad43c69d 100644 --- a/utils/subxt/generated/parachain.rs +++ b/utils/subxt/generated/parachain.rs @@ -23254,7 +23254,7 @@ pub mod api { module_id: ::std::vec::Vec<::core::primitive::u8>, }, #[codec(index = 24)] - PushWasmCode { wasm_code_id: ::std::vec::Vec<::core::primitive::u8> }, + PushWasmCode { wasm_checksum : ::std::vec::Vec<::core::primitive::u8> }, } } pub mod ics20_fee { diff --git a/utils/subxt/generated/src/composable/parachain.rs b/utils/subxt/generated/src/composable/parachain.rs index c75eba666..769a8eee9 100644 --- a/utils/subxt/generated/src/composable/parachain.rs +++ b/utils/subxt/generated/src/composable/parachain.rs @@ -31585,7 +31585,7 @@ pub mod api { module_id: ::std::vec::Vec<::core::primitive::u8>, }, #[codec(index = 24)] - PushWasmCode { wasm_code_id: ::std::vec::Vec<::core::primitive::u8> }, + PushWasmCode { wasm_checksum: ::std::vec::Vec<::core::primitive::u8> }, } } pub mod ics20_fee { diff --git a/utils/subxt/generated/src/dali/parachain.rs b/utils/subxt/generated/src/dali/parachain.rs index 3cdeca56d..87a5b827d 100644 --- a/utils/subxt/generated/src/dali/parachain.rs +++ b/utils/subxt/generated/src/dali/parachain.rs @@ -30504,7 +30504,7 @@ pub mod api { module_id: ::std::vec::Vec<::core::primitive::u8>, }, #[codec(index = 24)] - PushWasmCode { wasm_code_id: ::std::vec::Vec<::core::primitive::u8> }, + PushWasmCode { wasm_checksum : ::std::vec::Vec<::core::primitive::u8> }, } } pub mod pallet { diff --git a/utils/subxt/generated/src/default/parachain.rs b/utils/subxt/generated/src/default/parachain.rs index d39dcee07..0a5a752c5 100644 --- a/utils/subxt/generated/src/default/parachain.rs +++ b/utils/subxt/generated/src/default/parachain.rs @@ -13474,7 +13474,7 @@ pub mod api { module_id: ::std::vec::Vec<::core::primitive::u8>, }, #[codec(index = 24)] - PushWasmCode { wasm_code_id: ::std::vec::Vec<::core::primitive::u8> }, + PushWasmCode { wasm_checksum: ::std::vec::Vec<::core::primitive::u8> }, } } pub mod pallet { diff --git a/utils/subxt/generated/src/picasso_kusama/parachain.rs b/utils/subxt/generated/src/picasso_kusama/parachain.rs index 4c9db4414..36e5bedb6 100644 --- a/utils/subxt/generated/src/picasso_kusama/parachain.rs +++ b/utils/subxt/generated/src/picasso_kusama/parachain.rs @@ -35933,7 +35933,7 @@ pub mod api { module_id: ::std::vec::Vec<::core::primitive::u8>, }, #[codec(index = 24)] - PushWasmCode { wasm_code_id: ::std::vec::Vec<::core::primitive::u8> }, + PushWasmCode { wasm_checksum: ::std::vec::Vec<::core::primitive::u8> }, } } pub mod ics20_fee { diff --git a/utils/subxt/generated/src/picasso_rococo/parachain.rs b/utils/subxt/generated/src/picasso_rococo/parachain.rs index 04d1f0ee0..579d5bb94 100644 --- a/utils/subxt/generated/src/picasso_rococo/parachain.rs +++ b/utils/subxt/generated/src/picasso_rococo/parachain.rs @@ -34281,7 +34281,7 @@ pub mod api { module_id: ::std::vec::Vec<::core::primitive::u8>, }, #[codec(index = 24)] - PushWasmCode { wasm_code_id: ::std::vec::Vec<::core::primitive::u8> }, + PushWasmCode { wasm_checksum: ::std::vec::Vec<::core::primitive::u8> }, } } pub mod ics20_fee {