Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix(cw-context): encoding, decoding and iteration of ConsensusState heights #1176

Merged
merged 19 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ tendermint-testgen = { version = "0.34.0", default-features = fals
cosmwasm-schema = { version = "1.5.2" }
cosmwasm-std = { version = "1.5.2" }
cosmwasm-vm = { version = "1.5.2" }
cw-storage-plus = { version = "1.2.0" }

# parity dependencies
parity-scale-codec = { version = "3.6.5", default-features = false, features = ["full"] }
Expand Down
1 change: 1 addition & 0 deletions ibc-clients/cw-context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ibc-client-wasm-types = { workspace = true, features = ["cosmwasm"] }
# cosmwasm dependencies
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-storage-plus = { workspace = true }

[features]
default = ["std"]
Expand Down
26 changes: 16 additions & 10 deletions ibc-clients/cw-context/src/context/client_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
use ibc_core::client::types::Height;
use ibc_core::handler::types::error::ContextError;
use ibc_core::host::types::identifiers::ClientId;
use ibc_core::host::types::path::{iteration_key, ClientConsensusStatePath, ClientStatePath};
use ibc_core::host::types::path::{ClientConsensusStatePath, ClientStatePath};
use ibc_core::primitives::proto::{Any, Protobuf};
use ibc_core::primitives::Timestamp;

use super::Context;
use super::{Context, StorageMut};
use crate::api::ClientType;
use crate::context::CONSENSUS_STATE_HEIGHT_MAP;
use crate::utils::AnyCodec;

impl<'a, C: ClientType<'a>> ClientValidationContext for Context<'a, C> {
Expand Down Expand Up @@ -154,11 +155,15 @@

self.insert(prefixed_height_key, revision_height_vec);

let iteration_key = iteration_key(height.revision_number(), height.revision_height());

let height_vec = height.to_string().into_bytes();

self.insert(iteration_key, height_vec);
CONSENSUS_STATE_HEIGHT_MAP
.save(
self.storage_mut(),
(height.revision_number(), height.revision_height()),
&Default::default(),
)
.map_err(|e| ClientError::Other {
description: e.to_string(),

Check warning on line 165 in ibc-clients/cw-context/src/context/client_ctx.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/client_ctx.rs#L165

Added line #L165 was not covered by tests
})?;

Ok(())
}
Expand All @@ -180,9 +185,10 @@

self.remove(prefixed_height_key);

let iteration_key = iteration_key(height.revision_number(), height.revision_height());

self.remove(iteration_key);
CONSENSUS_STATE_HEIGHT_MAP.remove(
self.storage_mut(),
(height.revision_number(), height.revision_height()),
);

Check warning on line 191 in ibc-clients/cw-context/src/context/client_ctx.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/client_ctx.rs#L188-L191

Added lines #L188 - L191 were not covered by tests

Ok(())
}
Expand Down
137 changes: 87 additions & 50 deletions ibc-clients/cw-context/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

use std::str::FromStr;

use cosmwasm_std::{Deps, DepsMut, Env, Order, Storage};
use cosmwasm_std::{Deps, DepsMut, Empty, Env, Order, Storage};
use cw_storage_plus::{Bound, Map};
use ibc_client_wasm_types::client_state::ClientState as WasmClientState;
use ibc_core::client::context::client_state::ClientStateCommon;
use ibc_core::client::types::error::ClientError;
Expand All @@ -18,10 +19,18 @@

use crate::api::ClientType;
use crate::types::{ContractError, GenesisMetadata, HeightTravel, MigrationPrefix};
use crate::utils::{parse_height, AnyCodec};
use crate::utils::AnyCodec;

type Checksum = Vec<u8>;

/// - [`Height`] can not be used directly as keys in the map,
/// as it doesn't implement some cw_storage specific traits.
/// - Only a sorted set is needed. So the value type is set to
/// [`Empty`] following
/// ([cosmwasm-book](https://book.cosmwasm.com/cross-contract/map-storage.html#maps-as-sets)).
pub const CONSENSUS_STATE_HEIGHT_MAP: Map<'_, (u64, u64), Empty> =
Map::new(ITERATE_CONSENSUS_STATE_PREFIX);
Comment on lines +31 to +32
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I started using this for a better interface. I am curious - if anything breaks if I do this.

Copy link
Member

Choose a reason for hiding this comment

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

Cool. We should be good as long as the key produced by the Map matches the iteration_key() (?)
Specifically, we only need to use the iteration_key here. (Connected to your question below)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I realized in the old implementation, we should have used this range_with_prefix from cw-storage-plus. Note the trick for None and exclusive or inclusive cases in calc_start_bound and calc_end_bound.

Since this brings cw-storage-plus dependency anyway, it's best to use their storage abstractions, built on top of these low-level functions.


/// Context is a wrapper around the deps and env that gives access to the
/// methods under the ibc-rs Validation and Execution traits.
pub struct Context<'a, C: ClientType<'a>> {
Expand Down Expand Up @@ -130,13 +139,16 @@

/// Returns the storage of the context.
pub fn get_heights(&self) -> Result<Vec<Height>, ClientError> {
let iterator = self.storage_ref().range(None, None, Order::Ascending);

let heights: Vec<_> = iterator
.filter_map(|(_, value)| parse_height(value).transpose())
.collect::<Result<_, _>>()?;

Ok(heights)
CONSENSUS_STATE_HEIGHT_MAP
.keys(self.storage_ref(), None, None, Order::Ascending)
.map(|deserialized_result| {
let (rev_number, rev_height) =
deserialized_result.map_err(|e| ClientError::Other {
description: e.to_string(),

Check warning on line 147 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L147

Added line #L147 was not covered by tests
})?;
Height::new(rev_number, rev_height)
})
.collect()
}

/// Searches for either the earliest next or latest previous height based on
Expand All @@ -146,22 +158,37 @@
height: &Height,
travel: HeightTravel,
) -> Result<Option<Height>, ClientError> {
let iteration_key = iteration_key(height.revision_number(), height.revision_height());

let mut iterator = match travel {
HeightTravel::Prev => {
self.storage_ref()
.range(None, Some(&iteration_key), Order::Descending)
}
HeightTravel::Next => {
self.storage_ref()
.range(Some(&iteration_key), None, Order::Ascending)
}
let iterator = match travel {
HeightTravel::Prev => CONSENSUS_STATE_HEIGHT_MAP.range(
self.storage_ref(),
None,
Some(Bound::exclusive((
height.revision_number(),
height.revision_height(),
))),
Order::Descending,
),
HeightTravel::Next => CONSENSUS_STATE_HEIGHT_MAP.range(
self.storage_ref(),
Some(Bound::exclusive((
height.revision_number(),
height.revision_height(),
))),
None,
Order::Ascending,
),
};

iterator
.map(|deserialized_result| {
let ((rev_number, rev_height), _) =
deserialized_result.map_err(|e| ClientError::Other {
description: e.to_string(),

Check warning on line 186 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L186

Added line #L186 was not covered by tests
})?;
Height::new(rev_number, rev_height)
})
.next()
.map_or(Ok(None), |(_, height)| parse_height(height))
.transpose()
}

/// Returns the key for the client update time.
Expand Down Expand Up @@ -191,38 +218,48 @@
pub fn get_metadata(&self) -> Result<Option<Vec<GenesisMetadata>>, ContractError> {
let mut metadata = Vec::<GenesisMetadata>::new();

let start_key = ITERATE_CONSENSUS_STATE_PREFIX.to_string().into_bytes();

let iterator = self
.storage_ref()
.range(Some(&start_key), None, Order::Ascending);

for (_, encoded_height) in iterator {
let height = parse_height(encoded_height)?;

match height {
Some(height) => {
let processed_height_key = self.client_update_height_key(&height);
metadata.push(GenesisMetadata {
key: processed_height_key.clone(),
value: self.retrieve(&processed_height_key)?,
});
let processed_time_key = self.client_update_time_key(&height);
metadata.push(GenesisMetadata {
key: processed_time_key.clone(),
value: self.retrieve(&processed_time_key)?,
});
}
None => continue,
}
let iterator = CONSENSUS_STATE_HEIGHT_MAP
.keys(self.storage_ref(), None, None, Order::Ascending)
.map(|deserialized_result| {
let (rev_number, rev_height) =
deserialized_result.map_err(|e| ClientError::Other {
description: e.to_string(),
})?;
Height::new(rev_number, rev_height)
});

Check warning on line 229 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L221-L229

Added lines #L221 - L229 were not covered by tests

rnbguy marked this conversation as resolved.
Show resolved Hide resolved
for height_result in iterator {
let height = height_result?;

Check warning on line 232 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L231-L232

Added lines #L231 - L232 were not covered by tests

let processed_height_key = self.client_update_height_key(&height);
metadata.push(GenesisMetadata {
key: processed_height_key.clone(),
value: self.retrieve(&processed_height_key)?,

Check warning on line 237 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L234-L237

Added lines #L234 - L237 were not covered by tests
});
let processed_time_key = self.client_update_time_key(&height);
metadata.push(GenesisMetadata {
key: processed_time_key.clone(),
value: self.retrieve(&processed_time_key)?,

Check warning on line 242 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L239-L242

Added lines #L239 - L242 were not covered by tests
});
}

let iterator = self
.storage_ref()
.range(Some(&start_key), None, Order::Ascending);
let iterator = CONSENSUS_STATE_HEIGHT_MAP
.keys(self.storage_ref(), None, None, Order::Ascending)
.map(|deserialized_result| {
let (rev_number, rev_height) =
deserialized_result.map_err(|e| ClientError::Other {
description: e.to_string(),
})?;
Height::new(rev_number, rev_height)
});

Check warning on line 254 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L246-L254

Added lines #L246 - L254 were not covered by tests

for height_result in iterator {
let height = height_result?;

Check warning on line 257 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L256-L257

Added lines #L256 - L257 were not covered by tests

for (key, height) in iterator {
metadata.push(GenesisMetadata { key, value: height });
metadata.push(GenesisMetadata {
key: iteration_key(height.revision_number(), height.revision_height()),
value: height.encode_vec(),
});

Check warning on line 262 in ibc-clients/cw-context/src/context/mod.rs

View check run for this annotation

Codecov / codecov/patch

ibc-clients/cw-context/src/context/mod.rs#L259-L262

Added lines #L259 - L262 were not covered by tests
}

Ok(Some(metadata))
Expand Down
2 changes: 0 additions & 2 deletions ibc-clients/cw-context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
)]
#![forbid(unsafe_code)]

extern crate alloc;

pub mod api;
pub mod context;
pub mod handlers;
Expand Down
22 changes: 0 additions & 22 deletions ibc-clients/cw-context/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
mod codec;

pub use codec::*;
use ibc_core::client::types::error::ClientError;
use ibc_core::client::types::{Height, HeightError};

/// Decodes a `Height` from a UTF-8 encoded byte array.
pub fn parse_height(encoded_height: Vec<u8>) -> Result<Option<Height>, ClientError> {
let height_str = match alloc::str::from_utf8(encoded_height.as_slice()) {
Ok(s) => s,
// In cases where the height is unavailable, the encoded representation
// might not be valid UTF-8, resulting in an invalid string. In such
// instances, we return None.
Err(_) => return Ok(None),
};
match Height::try_from(height_str) {
Ok(height) => Ok(Some(height)),
// This is a valid case, as the key may contain other data. We just skip
// it.
Err(HeightError::InvalidFormat { .. }) => Ok(None),
Err(e) => Err(ClientError::Other {
description: e.to_string(),
}),
}
}
Loading