-
Notifications
You must be signed in to change notification settings - Fork 96
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
Changes from 14 commits
5e1ec84
db93358
8aa653b
b88734b
ebfbb7b
4de6db9
f5bc322
94683f4
7a10782
5133dfb
9c76cc8
edcaab3
c6331d5
7aa0c57
b442f93
41bed59
4a4679c
b082200
6dc68cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
|
||
/// 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>> { | ||
|
@@ -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 | ||
|
@@ -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. | ||
|
@@ -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(); | ||
|
||
for (rev_number, rev_height) in iterator { | ||
let height = Height::new(rev_number, rev_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)?, | ||
}); | ||
} | ||
|
||
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(); | ||
|
||
for (rev_number, rev_height) in iterator { | ||
let height = Height::new(rev_number, rev_height)?; | ||
|
||
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(), | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the purpose of passing There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
|
||
Ok(Some(metadata)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,6 @@ | |
)] | ||
#![forbid(unsafe_code)] | ||
|
||
extern crate alloc; | ||
|
||
pub mod api; | ||
pub mod context; | ||
pub mod handlers; | ||
|
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(), | ||
}), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started using this for a better interface. I am curious - if anything breaks if I do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. We should be good as long as the key produced by the
Map
matches theiteration_key()
(?)Specifically, we only need to use the
iteration_key
here. (Connected to your question below)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized in the old implementation, we should have used this
range_with_prefix
from cw-storage-plus. Note the trick forNone
and exclusive or inclusive cases incalc_start_bound
andcalc_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.