Skip to content

Commit

Permalink
[fix] #1821: add IntoSchema for MerkleTree and VersionedValidBlock, f…
Browse files Browse the repository at this point in the history
…ix HashOf and SignatureOf schemas

Signed-off-by: Marin Veršić <marin.versic101@gmail.com>
  • Loading branch information
mversic authored Jan 18, 2022
1 parent 4161360 commit f5a8aeb
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 93 deletions.
18 changes: 6 additions & 12 deletions crypto/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,13 @@ impl<T: Encode> HashOf<T> {

impl<T> IntoSchema for HashOf<T> {
fn schema(map: &mut MetaMap) {
let type_name = Self::type_name();

Hash::schema(map);
if !map.contains_key(&type_name) {
// Field was just inserted above
#[allow(clippy::expect_used)]
let wrapped_type_metadata = map
.get(&Hash::type_name())
.expect("Wrapped type metadata should have been present in the schemas")
.clone();

map.insert(type_name, wrapped_type_metadata);
}

map.entry(Self::type_name()).or_insert_with(|| {
Metadata::TupleStruct(UnnamedFieldsMeta {
types: vec![Hash::type_name()],
})
});
}
}

Expand Down
18 changes: 6 additions & 12 deletions crypto/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,13 @@ impl<T> Ord for SignatureOf<T> {

impl<T> IntoSchema for SignatureOf<T> {
fn schema(map: &mut MetaMap) {
let type_name = Self::type_name();

Signature::schema(map);
if !map.contains_key(&type_name) {
// Field was just inserted above
#[allow(clippy::expect_used)]
let wrapped_type_metadata = map
.get(&Signature::type_name())
.expect("Wrapped type metadata should have been present in the schemas")
.clone();

map.insert(type_name, wrapped_type_metadata);
}

map.entry(Self::type_name()).or_insert_with(|| {
Metadata::TupleStruct(UnnamedFieldsMeta {
types: vec![Signature::type_name()],
})
});
}
}

Expand Down
37 changes: 13 additions & 24 deletions data_model/src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,20 @@ impl<V: TryFrom<Value>> EvaluatesTo<V> {
}
}

const EXPRESSION: &str = "expression";

impl<V: TryFrom<Value> + IntoSchema> IntoSchema for EvaluatesTo<V> {
/// Returns unique type name.
/// WARN: `std::any::type_name` is compiler related, so is not unique.
/// I guess we should change it somehow later
fn type_name() -> String {
String::from(core::any::type_name::<Self>())
}

impl<V: TryFrom<Value>> IntoSchema for EvaluatesTo<V> {
fn schema(map: &mut MetaMap) {
let entry = if let btree_map::Entry::Vacant(free) = map.entry(Self::type_name()) {
free
} else {
return;
};
let _ = entry.insert(Metadata::Struct(NamedFieldsMeta {
declarations: vec![Declaration {
name: String::from(EXPRESSION),
ty: ExpressionBox::type_name(),
}],
}));
if !map.contains_key(&ExpressionBox::type_name()) {
ExpressionBox::schema(map)
}
ExpressionBox::schema(map);

map.entry(Self::type_name()).or_insert_with(|| {
const EXPRESSION: &str = "expression";

Metadata::Struct(NamedFieldsMeta {
declarations: vec![Declaration {
name: String::from(EXPRESSION),
ty: ExpressionBox::type_name(),
}],
})
});
}
}

Expand Down
78 changes: 42 additions & 36 deletions data_model/src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,42 @@ use alloc::{boxed::Box, vec, vec::Vec};
use std::collections::VecDeque;

use iroha_crypto::{Hash, HashOf};
use iroha_schema::IntoSchema;

/// [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) used to validate and prove data at
/// each block height.
/// Our implementation uses binary hash tree.
#[derive(Debug)]
#[derive(Debug, IntoSchema)]
pub struct MerkleTree<T> {
root_node: Node<T>,
}

/// Represents subtree rooted by the current node
#[derive(Debug, IntoSchema)]
pub struct Subtree<T> {
/// Left subtree
left: Box<Node<T>>,
/// Right subtree
right: Box<Node<T>>,
/// Hash of the node
hash: HashOf<Node<T>>,
}

/// Represents leaf node
#[derive(Debug, IntoSchema)]
pub struct Leaf<T> {
/// Hash of the node
hash: HashOf<T>,
}

/// Binary Tree's node with possible variants: Subtree, Leaf (with data or links to data) and Empty.
#[derive(Debug)]
#[derive(Debug, IntoSchema)]
#[allow(clippy::module_name_repetitions)]
pub enum Node<T> {
/// Node is root of a subtree
Subtree {
/// Left subtree
left: Box<Self>,
/// Right subtree
right: Box<Self>,
/// Hash of the node
hash: HashOf<Self>,
},
Subtree(Subtree<T>),
/// Leaf node
Leaf {
/// Hash of the node
hash: HashOf<T>,
},
Leaf(Leaf<T>),
/// Empty node
Empty,
}
Expand All @@ -50,7 +59,7 @@ impl<U> FromIterator<HashOf<U>> for MerkleTree<U> {
hashes.sort_unstable();
let mut nodes = hashes
.into_iter()
.map(|hash| Node::Leaf { hash })
.map(|hash| Node::Leaf(Leaf { hash }))
.collect::<VecDeque<_>>();
if nodes.len() % 2 != 0 {
nodes.push_back(Node::Empty);
Expand Down Expand Up @@ -112,50 +121,47 @@ impl<T> Default for MerkleTree<T> {
impl<T> Node<T> {
#[cfg(feature = "std")]
fn from_nodes(left: Self, right: Self) -> Self {
Self::Subtree {
Self::Subtree(Subtree {
hash: Self::nodes_pair_hash(&left, &right),
left: Box::new(left),
right: Box::new(right),
}
})
}

fn get_leaf_inner(&self, idx: usize) -> Result<HashOf<T>, usize> {
use Node::*;

match self {
Leaf { hash } if idx == 0 => Ok(*hash),
Subtree { left, right, .. } => match left.get_leaf_inner(idx) {
Node::Leaf(Leaf { hash }) if idx == 0 => Ok(*hash),
Node::Subtree(Subtree { left, right, .. }) => match left.get_leaf_inner(idx) {
Ok(hash) => Ok(hash),
Err(seen) => right
.get_leaf_inner(idx - seen)
.map_err(|index| index + seen),
},
Leaf { .. } | Empty => Err(1),
Node::Leaf { .. } | Node::Empty => Err(1),
}
}

#[cfg(feature = "std")]
fn from_node(left: Self) -> Self {
Self::Subtree {
Self::Subtree(Subtree {
hash: left.hash(),
left: Box::new(left),
right: Box::new(Node::Empty),
}
})
}

/// Return the `Hash` of the root node.
pub fn hash(&self) -> HashOf<Self> {
use Node::*;
match self {
Subtree { hash, .. } => *hash,
Leaf { hash } => (*hash).transmute(),
Empty => HashOf::from_hash(Hash([0; 32])),
Node::Subtree(Subtree { hash, .. }) => *hash,
Node::Leaf(Leaf { hash }) => (*hash).transmute(),
Node::Empty => HashOf::from_hash(Hash([0; 32])),
}
}

/// Returns leaf node hash
pub const fn leaf_hash(&self) -> Option<HashOf<T>> {
if let Self::Leaf { hash } = *self {
if let Self::Leaf(Leaf { hash }) = *self {
Some(hash)
} else {
None
Expand Down Expand Up @@ -195,7 +201,7 @@ impl<'a, T> Iterator for BreadthFirstIter<'a, T> {
fn next(&mut self) -> Option<Self::Item> {
match &self.queue.pop() {
Some(node) => {
if let Node::Subtree { left, right, .. } = *node {
if let Node::Subtree(Subtree { left, right, .. }) = *node {
self.queue.push(&*left);
self.queue.push(&*right);
}
Expand All @@ -222,15 +228,15 @@ mod tests {
#[test]
fn tree_with_two_layers_should_reach_all_nodes() {
let tree = MerkleTree {
root_node: Node::Subtree {
left: Box::new(Node::Leaf {
root_node: Node::Subtree(Subtree {
left: Box::new(Node::Leaf(Leaf {
hash: HashOf::<()>::from_hash(Hash([0; 32])),
}),
right: Box::new(Node::Leaf {
})),
right: Box::new(Node::Leaf(Leaf {
hash: HashOf::from_hash(Hash([0; 32])),
}),
})),
hash: HashOf::from_hash(Hash([0; 32])),
},
}),
};
assert_eq!(3, tree.into_iter().count());
}
Expand Down
62 changes: 57 additions & 5 deletions schema/bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

#![allow(clippy::print_stdout)]

use iroha_core::block::stream::prelude::*;
use iroha_schema::prelude::*;

fn build_schemas() -> MetaMap {
use iroha_core::genesis::RawGenesisBlock;
use iroha_data_model::prelude::*;
use iroha_core::{
block::{stream::prelude::*, VersionedValidBlock},
genesis::RawGenesisBlock,
};
use iroha_data_model::{merkle::MerkleTree, prelude::*};

macro_rules! schemas {
($($t:ty),* $(,)?) => {{
Expand All @@ -17,8 +19,10 @@ fn build_schemas() -> MetaMap {
}};
}

// It is sufficient to list top level types only
schemas! {
// It is sufficient to list top level types only
RawGenesisBlock,

VersionedBlockPublisherMessage,
VersionedBlockSubscriberMessage,
VersionedEventPublisherMessage,
Expand All @@ -27,7 +31,10 @@ fn build_schemas() -> MetaMap {
VersionedSignedQueryRequest,
VersionedTransaction,

RawGenesisBlock
// Even though these schemas are not exchanged between server and client,
// they can be useful to the client to generate and validate their hashes
MerkleTree<VersionedTransaction>,
VersionedValidBlock,
}
}

Expand All @@ -48,6 +55,42 @@ mod tests {

use super::*;

// NOTE: These type parameters should not be have their schema exposed
// By default `PhantomData` wrapped types schema will not be included
const SCHEMALESS_TYPES: Vec<&str> = vec![];

// For `PhantomData` wrapped types schemas aren't expanded recursively.
// This test ensures that schemas for those types are present as well.
fn find_missing_type_params(schemas: &MetaMap) -> HashMap<&str, Vec<&str>> {
let mut missing_schemas = HashMap::new();

for type_name in schemas.keys() {
// Missing `PhantomData` schemas
let params_list_start = type_name.find('<');
let params_list_end = type_name.rfind('>');

if let (Some(start), Some(end)) = (params_list_start, params_list_end) {
for generic in type_name[1 + start..end].split(',') {
let gen = generic.trim();

// This is const generic
if gen.parse::<usize>().is_ok() {
continue;
}

if !SCHEMALESS_TYPES.contains(&gen) && !schemas.contains_key(gen) {
missing_schemas
.entry(type_name.as_str())
.or_insert_with(Vec::new)
.push(gen);
}
}
}
}

missing_schemas
}

fn find_missing_schemas(schemas: &MetaMap) -> HashMap<&str, Vec<&str>> {
let mut missing_schemas = HashMap::new();

Expand Down Expand Up @@ -87,11 +130,20 @@ mod tests {
}
}

missing_schemas.extend(find_missing_type_params(schemas));

missing_schemas
}

#[test]
fn no_missing_schemas() {
assert!(find_missing_schemas(&build_schemas()).is_empty());
}

#[test]
fn no_alloc_prefix() {
assert!(build_schemas()
.keys()
.all(|type_name| !type_name.starts_with("alloc")));
}
}
8 changes: 4 additions & 4 deletions schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ pub trait IntoSchema {
/// Returns unique type name.
/// WARN: `core::any::type_name` is compiler related, so is not unique.
/// I guess we should change it somehow later
// TODO: Should return &str if possible
fn type_name() -> String {
let mut name = module_path!().to_owned();
name.push_str("::");
name.push_str(core::any::type_name::<Self>());
name
core::any::type_name::<Self>()
.replace("alloc::string::String", "String")
.replace("alloc::vec::Vec", "Vec")
}

/// Returns info about current type. Will return map from type names to its metadata
Expand Down

0 comments on commit f5a8aeb

Please sign in to comment.