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

feat(testkit): add store implementations in ibc-testkit #1048

Closed
wants to merge 9 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [ibc-testkit] Add blockchain store implementations.
([\#1045](https://github.com/cosmos/ibc-rs/issues/1045))
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = [
"ibc",
"ibc-query",
"ibc-testkit",
"ibc-testkit/store",
]
exclude = [
"ci/cw-check",
Expand All @@ -57,6 +58,10 @@ sha2 = { version = "0.10.8", default-features = false }
serde = { version = "1.0", default-features = false }
serde_json = { package = "serde-json-wasm", version = "1.0.0", default-features = false }
subtle-encoding = { version = "0.5", default-features = false }
base64 = { version = "0.21", default-features = false }
tracing = { version = "0.1", default-features = false }
ics23 = { version = "0.11", default-features = false }
prost = { version = "0.12", default-features = false }

# ibc dependencies
ibc = { version = "0.49.1", path = "./ibc", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion ibc-clients/ics08-wasm/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = { workspace = true }

[dependencies]
# external dependencies
base64 = { version = "0.21", default-features = false, features = ["alloc"] }
base64 = { workspace = true, features = ["alloc"] }
displaydoc = { workspace = true }
serde = { workspace = true , optional = true }
cosmwasm-schema = { version = "1.4.1", default-features = false, optional = true }
Expand Down
2 changes: 1 addition & 1 deletion ibc-testkit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
subtle-encoding = { workspace = true }
tracing = { version = "0.1.40", default-features = false }
tracing = { workspace = true }
typed-builder = { version = "0.18.0" }

# ibc dependencies
Expand Down
30 changes: 30 additions & 0 deletions ibc-testkit/store/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "ibc-testkit-store"
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
readme = "README.md"
keywords = ["blockchain", "store", "merkle", "avl"]
description = """
Maintained by `ibc-rs`, a simple implementation of an AVL store tailored for the `ibc-testkit`.
"""


# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
displaydoc = { workspace = true }
tendermint = { workspace = true }
ibc = { workspace = true }
ics23 = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }
prost = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
rstest = { workspace = true }
63 changes: 63 additions & 0 deletions ibc-testkit/store/src/avl/as_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! # AsBytes trait definition
//!
//! This module hosts the `AsBytes` trait, which is used by the AVL Tree to convert value to raw
//! bytes. This is helpful for making the AVL Tree generic over a wide range of data types for its
//! keys (the values still need to implement `Borrow<[u8]>), as long as they can be interpreted as
//! a slice of bytes.
//!
//! To add support for a new type in the AVL Tree, simply implement the `AsByte` trait for that type.

pub enum ByteSlice<'a> {
Slice(&'a [u8]),
Vector(Vec<u8>),
}

impl AsRef<[u8]> for ByteSlice<'_> {
fn as_ref(&self) -> &[u8] {
match self {
ByteSlice::Slice(s) => s,
ByteSlice::Vector(v) => v.as_slice(),

Check warning on line 19 in ibc-testkit/store/src/avl/as_bytes.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/as_bytes.rs#L19

Added line #L19 was not covered by tests
}
}
}

/// A trait for objects that can be interpreted as a slice of bytes.
pub trait AsBytes {
fn as_bytes(&self) -> ByteSlice<'_>;
}

impl AsBytes for Vec<u8> {
fn as_bytes(&self) -> ByteSlice<'_> {
ByteSlice::Slice(self)
}

Check warning on line 32 in ibc-testkit/store/src/avl/as_bytes.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/as_bytes.rs#L30-L32

Added lines #L30 - L32 were not covered by tests
}

impl AsBytes for [u8] {
fn as_bytes(&self) -> ByteSlice<'_> {
ByteSlice::Slice(self)
}

Check warning on line 38 in ibc-testkit/store/src/avl/as_bytes.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/as_bytes.rs#L36-L38

Added lines #L36 - L38 were not covered by tests
}

impl AsBytes for str {
fn as_bytes(&self) -> ByteSlice<'_> {
ByteSlice::Slice(self.as_bytes())
}

Check warning on line 44 in ibc-testkit/store/src/avl/as_bytes.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/as_bytes.rs#L42-L44

Added lines #L42 - L44 were not covered by tests
}

impl AsBytes for &str {
fn as_bytes(&self) -> ByteSlice<'_> {
ByteSlice::Slice((*self).as_bytes())
}
}

impl AsBytes for String {
fn as_bytes(&self) -> ByteSlice<'_> {
ByteSlice::Slice(self.as_bytes())
}

Check warning on line 56 in ibc-testkit/store/src/avl/as_bytes.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/as_bytes.rs#L54-L56

Added lines #L54 - L56 were not covered by tests
}

impl AsBytes for [u8; 1] {
fn as_bytes(&self) -> ByteSlice<'_> {
ByteSlice::Slice(self)
}
}
29 changes: 29 additions & 0 deletions ibc-testkit/store/src/avl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! # AVL Tree
//!
//! This module hosts a simple implementation of an AVL Merkle Tree that support the `get` and
//! `insert` instructions (no delete yet, it's not needed as the on-chain store is supposed to be
//! immutable).
//!
//! Proof of existence are supported using [ICS23](https://github.com/confio/ics23), but proof of
//! non-existence are not yet implemented.
//!
//! Keys needs to implement `Ord` and `AsBytes` (see `as_bytes` module), while values are required
//! to implement `Borrow<[u8]>`.
//!
//! For more info, see [AVL Tree on wikipedia](https://en.wikipedia.org/wiki/AVL_tree),

pub use as_bytes::{AsBytes, ByteSlice};
pub use node::AvlNode;
pub use proof::get_proof_spec;
use tendermint::hash::Algorithm;
pub use tree::AvlTree;

mod as_bytes;
mod node;
mod proof;
mod tree;

#[cfg(test)]
mod tests;

const HASH_ALGO: Algorithm = Algorithm::Sha256;
139 changes: 139 additions & 0 deletions ibc-testkit/store/src/avl/node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use std::borrow::Borrow;
use std::mem;

use sha2::{Digest, Sha256};
use tendermint::hash::Hash;

use crate::avl::as_bytes::AsBytes;
use crate::avl::{proof, HASH_ALGO};

pub type NodeRef<T, V> = Option<Box<AvlNode<T, V>>>;

/// A node in the AVL Tree.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct AvlNode<K: Ord, V> {
pub key: K,
pub value: V,
pub hash: Hash,
pub merkle_hash: Hash,
pub height: u32,
pub left: NodeRef<K, V>,
pub right: NodeRef<K, V>,
}

/// Wrap a key + value couple into a `NodeRef`.
#[allow(clippy::unnecessary_wraps)]
pub fn as_node_ref<K: Ord + AsBytes, V>(key: K, value: V) -> NodeRef<K, V>
where
V: Borrow<[u8]>,
{
Some(Box::new(AvlNode::new(key, value)))
}

impl<K: Ord + AsBytes, V> AvlNode<K, V>
where
V: Borrow<[u8]>,
{
fn new(key: K, value: V) -> Self {
let mut sha = Sha256::new();
sha.update(proof::LEAF_PREFIX);
sha.update(key.as_bytes().as_ref());
sha.update(value.borrow());
let hash = sha.finalize();
let merkle_hash = Hash::from_bytes(HASH_ALGO, &Sha256::digest(hash)).unwrap();
let hash = Hash::from_bytes(HASH_ALGO, &hash).unwrap();

AvlNode {
key,
value,
hash,
merkle_hash,
height: 0,
left: None,
right: None,
}
}

/// Set the value of the current node.
pub(crate) fn set_value(&mut self, value: V) -> V {
let hash = Self::local_hash(&self.key, &value);
self.hash = hash;
mem::replace(&mut self.value, value)
}

Check warning on line 62 in ibc-testkit/store/src/avl/node.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/node.rs#L58-L62

Added lines #L58 - L62 were not covered by tests

/// The left height, or `None` if there is no left child.
fn left_height(&self) -> Option<u32> {
self.left.as_ref().map(|left| left.height)
}

/// The right height, or `None` if there is no right child.
fn right_height(&self) -> Option<u32> {
self.right.as_ref().map(|right| right.height)
}

/// Compute the local hash for a given key and value.
fn local_hash(key: &K, value: &V) -> Hash {
let mut sha = Sha256::new();
sha.update(proof::LEAF_PREFIX);
sha.update(key.as_bytes());
sha.update(value.borrow());
let hash = sha.finalize();
Hash::from_bytes(HASH_ALGO, &hash).unwrap()
}

Check warning on line 82 in ibc-testkit/store/src/avl/node.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/node.rs#L75-L82

Added lines #L75 - L82 were not covered by tests

/// The left merkle hash, if any
pub fn left_hash(&self) -> Option<&[u8]> {
Some(self.left.as_ref()?.merkle_hash.as_bytes())
}

/// The right merkle hash, if any
pub fn right_hash(&self) -> Option<&[u8]> {
Some(self.right.as_ref()?.merkle_hash.as_bytes())
}

/// Update the height of this node by looking at the height of its two children.
/// The height of this node is computed as the maximum among the height of its two children, and
/// incremented by 1.
fn update_height(&mut self) {
match &self.right {
None => match &self.left {
None => self.height = 0,
Some(left) => self.height = left.height + 1,
},
Some(right) => match &self.left {
None => self.height = right.height + 1,
Some(left) => self.height = std::cmp::max(left.height, right.height) + 1,
},
}
}

/// Update the node's merkle hash by looking at the hashes of its two children.
fn update_hashes(&mut self) {
let mut sha = Sha256::new();
if let Some(left) = &self.left {
sha.update(left.merkle_hash.as_bytes());
}
sha.update(self.hash.as_bytes());
if let Some(right) = &self.right {
sha.update(right.merkle_hash.as_bytes())
}
self.merkle_hash = Hash::from_bytes(HASH_ALGO, sha.finalize().as_slice()).unwrap();
}

/// Update node meta data, such as its height and merkle hash, by looking at its two
/// children.
pub fn update(&mut self) {
self.update_hashes();
self.update_height();
}

/// Returns the node's balance factor (left_height - right_height).
pub fn balance_factor(&self) -> i32 {
match (self.left_height(), self.right_height()) {
(None, None) => 0,

Check warning on line 133 in ibc-testkit/store/src/avl/node.rs

View check run for this annotation

Codecov / codecov/patch

ibc-testkit/store/src/avl/node.rs#L133

Added line #L133 was not covered by tests
(None, Some(h)) => -(h as i32),
(Some(h), None) => h as i32,
(Some(h_l), Some(h_r)) => (h_l as i32) - (h_r as i32),
}
}
}
38 changes: 38 additions & 0 deletions ibc-testkit/store/src/avl/proof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! # ICS23 Proof
//!
//! This module provides the ICS23 proof spec, which can be used to verify the existence of a value
//! in the AVL Tree.
use ics23::{HashOp, InnerSpec, LeafOp, LengthOp, ProofSpec};

pub const LEAF_PREFIX: [u8; 64] = [0; 64]; // 64 bytes of zeroes.

#[allow(dead_code)]
/// Return the `ProofSpec` of tendermock AVL Tree.
pub fn get_proof_spec() -> ProofSpec {
ProofSpec {
leaf_spec: Some(LeafOp {
hash: HashOp::Sha256.into(),
prehash_key: HashOp::NoHash.into(),
prehash_value: HashOp::NoHash.into(),
length: LengthOp::NoPrefix.into(),
prefix: LEAF_PREFIX.to_vec(),
}),
inner_spec: Some(InnerSpec {
child_order: vec![0, 1, 2],
child_size: 32,
min_prefix_length: 0,
max_prefix_length: 64,
empty_child: vec![0, 32],
hash: HashOp::Sha256.into(),
}),
max_depth: 0,
min_depth: 0,
prehash_key_before_comparison: false,
}
}

#[cfg(test)]
mod test {
#[test]
fn proof() {}
}
Loading
Loading