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 14 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
25 changes: 15 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,11 +7,11 @@
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::utils::AnyCodec;

Expand Down Expand Up @@ -154,11 +154,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);
super::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 164 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#L164

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

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

self.remove(prefixed_height_key);

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

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

Check warning on line 190 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#L187-L190

Added lines #L187 - L190 were not covered by tests

Ok(())
}
Expand Down
109 changes: 60 additions & 49 deletions ibc-clients/cw-context/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@

use crate::api::ClientType;
use crate::types::{ContractError, GenesisMetadata, HeightTravel, MigrationPrefix};
use crate::utils::{parse_height, AnyCodec};
use crate::utils::AnyCodec;
use cosmwasm_std::Empty;
use cw_storage_plus::{Bound, Map};

type Checksum = Vec<u8>;

pub const CONSENSUS_STATE_HEIGHT_MAP: Map<'_, (u64, u64), Empty> =
Map::new(ITERATE_CONSENSUS_STATE_PREFIX);
Comment on lines +31 to +32
Copy link
Member 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
Member 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 +135,11 @@

/// 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)
.flatten()
.map(|(rev_number, rev_height)| Height::new(rev_number, rev_height))
.collect()
}

/// Searches for either the earliest next or latest previous height based on
Expand All @@ -146,22 +149,32 @@
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
.flatten()
.map(|((rev_number, rev_height), _)| 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 +204,36 @@
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)
.flatten();

Check warning on line 209 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#L207-L209

Added lines #L207 - L209 were not covered by tests

for (rev_number, rev_height) in iterator {
let height = Height::new(rev_number, rev_height)?;

Check warning on line 212 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#L211-L212

Added lines #L211 - L212 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 217 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#L214-L217

Added lines #L214 - L217 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 222 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#L219-L222

Added lines #L219 - L222 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)
.flatten();

Check warning on line 228 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#L226-L228

Added lines #L226 - L228 were not covered by tests

for (rev_number, rev_height) in iterator {
let height = Height::new(rev_number, rev_height)?;

Check warning on line 231 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#L230-L231

Added lines #L230 - L231 were not covered by tests

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

Check warning on line 236 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#L233-L236

Added lines #L233 - L236 were not covered by tests
Copy link
Member Author

Choose a reason for hiding this comment

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

What is the purpose of passing Height here?

Copy link
Member

Choose a reason for hiding this comment

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

As for the why, haven’t had a chance yet to explore where/how this GenesisMetaData gonna be used. But the implementation mirrors ibc-go.

}

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