Skip to content

Commit

Permalink
[feature] #2103: support querying for blocks and transactions (#2210)
Browse files Browse the repository at this point in the history
* [feature] add FindAllTransactions query

Signed-off-by: Artemii Gerasimovich <gerasimovich@soramitsu.co.jp>

* [feature] add FindAllBlocks query

Signed-off-by: Artemii Gerasimovich <gerasimovich@soramitsu.co.jp>

* split valid and rejected txns in BlockValue

Signed-off-by: Artemii Gerasimovich <gerasimovich@soramitsu.co.jp>

* fix suggestions

Signed-off-by: Artemii Gerasimovich <gerasimovich@soramitsu.co.jp>
  • Loading branch information
QuentinI committed May 20, 2022
1 parent aa9dd35 commit 1d11a66
Show file tree
Hide file tree
Showing 17 changed files with 449 additions and 15 deletions.
5 changes: 5 additions & 0 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,11 @@ pub mod transaction {

use super::*;

/// Get query to find all transactions
pub fn all() -> FindAllTransactions {
FindAllTransactions::new()
}

/// Get query to retrieve transactions for account
pub fn by_account_id(
account_id: impl Into<EvaluatesTo<AccountId>>,
Expand Down
44 changes: 43 additions & 1 deletion core/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ use std::{collections::BTreeSet, error::Error, iter, marker::PhantomData};
use dashmap::{mapref::one::Ref as MapRef, DashMap};
use eyre::{eyre, Context, Result};
use iroha_crypto::{HashOf, KeyPair, MerkleTree, SignatureOf, SignaturesOf};
use iroha_data_model::{current_time, events::prelude::*, transaction::prelude::*};
use iroha_data_model::{
block_value::{BlockHeaderValue, BlockValue},
current_time,
events::prelude::*,
transaction::prelude::*,
};
use iroha_schema::IntoSchema;
use iroha_version::{declare_versioned_with_scale, version_with_scale};
use parity_scale_codec::{Decode, Encode};
Expand Down Expand Up @@ -716,6 +721,43 @@ impl VersionedCommittedBlock {
.verified_signatures()
.map(SignatureOf::transmute_ref)
}

/// Converts block to [`iroha_data_model`] representation for use in e.g. queries.
pub fn into_value(self) -> BlockValue {
let CommittedBlock {
header,
rejected_transactions,
transactions,
event_recommendations,
..
} = self.into_v1();

let BlockHeader {
timestamp,
height,
previous_block_hash,
transactions_hash,
rejected_transactions_hash,
invalidated_blocks_hashes,
..
} = header;

let header_value = BlockHeaderValue {
timestamp,
height,
previous_block_hash: *previous_block_hash,
transactions_hash,
rejected_transactions_hash,
invalidated_blocks_hashes: invalidated_blocks_hashes.into_iter().map(|h| *h).collect(),
};

BlockValue {
header: header_value,
transactions,
rejected_transactions,
event_recommendations,
}
}
}

/// When Kura receives `ValidBlock`, the block is stored and
Expand Down
19 changes: 19 additions & 0 deletions core/src/smartcontracts/isi/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! This module contains trait implementations related to block queries
use eyre::Result;
use iroha_data_model::block_value::BlockValue;
use iroha_telemetry::metrics;

use super::*;

impl<W: WorldTrait> ValidQuery<W> for FindAllBlocks {
#[metrics(+"find_all_blocks")]
fn execute(&self, wsv: &WorldStateView<W>) -> Result<Self::Output, query::Error> {
let mut blocks: Vec<BlockValue> = wsv
.blocks()
.map(|blk| blk.clone())
.map(VersionedCommittedBlock::into_value)
.collect();
blocks.reverse();
Ok(blocks)
}
}
1 change: 1 addition & 0 deletions core/src/smartcontracts/isi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! implementations.
pub mod account;
pub mod asset;
pub mod block;
pub mod domain;
pub mod expression;
pub mod permissions;
Expand Down
136 changes: 136 additions & 0 deletions core/src/smartcontracts/isi/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ impl<W: WorldTrait> ValidQuery<W> for QueryBox {
FindAllPeers(query) => query.execute_into_value(wsv),
FindAssetKeyValueByIdAndKey(query) => query.execute_into_value(wsv),
FindAccountKeyValueByIdAndKey(query) => query.execute_into_value(wsv),
FindAllBlocks(query) => query.execute_into_value(wsv),
FindAllTransactions(query) => query.execute_into_value(wsv),
FindTransactionsByAccountId(query) => query.execute_into_value(wsv),
FindTransactionByHash(query) => query.execute_into_value(wsv),
FindPermissionTokensByAccountId(query) => query.execute_into_value(wsv),
Expand Down Expand Up @@ -235,6 +237,140 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn find_all_blocks() -> Result<()> {
let wsv = Arc::new(WorldStateView::new(world_with_test_domains()));

let validator = TransactionValidator::new(
TransactionLimits {
max_instruction_number: 0,
max_wasm_size_bytes: 0,
},
AllowAll::new(),
AllowAll::new(),
Arc::clone(&wsv),
);

let first_block = PendingBlock::new(vec![], vec![])
.chain_first()
.validate(&validator)
.sign(ALICE_KEYS.clone())
.expect("Failed to sign blocks.")
.commit();

let mut curr_hash = first_block.hash();

wsv.apply(first_block).await?;

let num_blocks: u64 = 100;

for height in 1u64..num_blocks {
let block = PendingBlock::new(vec![], vec![])
.chain(
height,
curr_hash,
crate::sumeragi::view_change::ProofChain::empty(),
vec![],
)
.validate(&validator)
.sign(ALICE_KEYS.clone())
.expect("Failed to sign blocks.")
.commit();
curr_hash = block.hash();
wsv.apply(block).await?;
}

let blocks = FindAllBlocks::new().execute(&wsv)?;

assert_eq!(blocks.len() as u64, num_blocks);
assert!(blocks.windows(2).all(|wnd| wnd[0] >= wnd[1]));

Ok(())
}

#[tokio::test]
async fn find_all_transactions() -> Result<()> {
let wsv = Arc::new(WorldStateView::new(world_with_test_domains()));
let limits = TransactionLimits {
max_instruction_number: 1,
max_wasm_size_bytes: 0,
};
let huge_limits = TransactionLimits {
max_instruction_number: 1000,
max_wasm_size_bytes: 0,
};
let valid_tx = {
let tx = Transaction::new(ALICE_ID.clone(), Vec::<Instruction>::new().into(), 4000)
.sign(ALICE_KEYS.clone())?;
crate::VersionedAcceptedTransaction::from_transaction(tx, &limits)?
};

let invalid_tx = {
let isi = Instruction::Fail(FailBox::new("fail"));
let tx = Transaction::new(ALICE_ID.clone(), vec![isi.clone(), isi].into(), 4000)
.sign(ALICE_KEYS.clone())?;
crate::VersionedAcceptedTransaction::from_transaction(tx, &huge_limits)?
};

let first_block = PendingBlock::new(vec![], vec![])
.chain_first()
.validate(&TransactionValidator::new(
limits,
AllowAll::new(),
AllowAll::new(),
Arc::clone(&wsv),
))
.sign(ALICE_KEYS.clone())
.expect("Failed to sign blocks.")
.commit();

let mut curr_hash = first_block.hash();

wsv.apply(first_block).await?;

let num_blocks: u64 = 100;

for height in 1u64..=num_blocks {
let block = PendingBlock::new(vec![valid_tx.clone(), invalid_tx.clone()], vec![])
.chain(
height,
curr_hash,
crate::sumeragi::view_change::ProofChain::empty(),
vec![],
)
.validate(&TransactionValidator::new(
limits,
AllowAll::new(),
AllowAll::new(),
Arc::clone(&wsv),
))
.sign(ALICE_KEYS.clone())
.expect("Failed to sign blocks.")
.commit();
curr_hash = block.hash();
wsv.apply(block).await?;
}

let txs = FindAllTransactions::new().execute(&wsv)?;

assert_eq!(txs.len() as u64, num_blocks * 2);
assert_eq!(
txs.iter()
.filter(|txn| matches!(txn, TransactionValue::RejectedTransaction(_)))
.count() as u64,
num_blocks
);
assert_eq!(
txs.iter()
.filter(|txn| matches!(txn, TransactionValue::Transaction(_)))
.count() as u64,
num_blocks
);
assert!(txs.windows(2).all(|wnd| wnd[0] >= wnd[1]));

Ok(())
}

#[tokio::test]
async fn find_transaction() -> Result<()> {
let wsv = Arc::new(WorldStateView::new(world_with_test_domains()));
Expand Down
9 changes: 9 additions & 0 deletions core/src/smartcontracts/isi/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ use iroha_telemetry::metrics;

use super::*;

impl<W: WorldTrait> ValidQuery<W> for FindAllTransactions {
#[metrics(+"find_all_transactions")]
fn execute(&self, wsv: &WorldStateView<W>) -> Result<Self::Output, query::Error> {
let mut txs = wsv.transaction_values();
txs.reverse();
Ok(txs)
}
}

impl<W: WorldTrait> ValidQuery<W> for FindTransactionsByAccountId {
#[metrics(+"find_transactions_by_account_id")]
fn execute(&self, wsv: &WorldStateView<W>) -> Result<Self::Output, query::Error> {
Expand Down
28 changes: 28 additions & 0 deletions core/src/wsv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,34 @@ impl<W: WorldTrait> WorldStateView<W> {
self.new_block_notifier.subscribe()
}

/// Get all transactions
pub fn transaction_values(&self) -> Vec<TransactionValue> {
let mut txs = self
.blocks()
.flat_map(|block| {
let block = block.as_v1();
block
.rejected_transactions
.iter()
.cloned()
.map(Box::new)
.map(TransactionValue::RejectedTransaction)
.chain(
block
.transactions
.iter()
.cloned()
.map(VersionedTransaction::from)
.map(Box::new)
.map(TransactionValue::Transaction),
)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
txs.sort();
txs
}

/// Find a [`VersionedTransaction`] by hash.
pub fn transaction_value_by_hash(
&self,
Expand Down
85 changes: 85 additions & 0 deletions data_model/src/block_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! This module contains [`BlockValue`] and [`BlockHeaderValue`] structures, their implementation and related traits and
//! instructions implementations.
#[cfg(not(feature = "std"))]
use alloc::{format, string::String, vec::Vec};
use core::cmp::Ordering;

use iroha_crypto::{Hash, HashOf, MerkleTree};
use iroha_schema::IntoSchema;
use parity_scale_codec::{Decode, Encode};
use serde::{Deserialize, Serialize};

use crate::{
events::Event,
transaction::{VersionedRejectedTransaction, VersionedTransaction, VersionedValidTransaction},
};

/// Block header
#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)]
pub struct BlockHeaderValue {
/// Unix time (in milliseconds) of block forming by a peer.
pub timestamp: u128,
/// a number of blocks in the chain up to the block.
pub height: u64,
/// Hash of a previous block in the chain.
/// Is an array of zeros for the first block.
pub previous_block_hash: Hash,
/// Hash of merkle tree root of the tree of valid transactions' hashes.
pub transactions_hash: HashOf<MerkleTree<VersionedTransaction>>,
/// Hash of merkle tree root of the tree of rejected transactions' hashes.
pub rejected_transactions_hash: HashOf<MerkleTree<VersionedTransaction>>,
/// Hashes of the blocks that were rejected by consensus.
pub invalidated_blocks_hashes: Vec<Hash>,
}

impl PartialOrd for BlockHeaderValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.timestamp.cmp(&other.timestamp))
}
}

impl Ord for BlockHeaderValue {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp.cmp(&other.timestamp)
}
}

/// Representation of block on blockchain
#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Serialize, Deserialize, IntoSchema)]
pub struct BlockValue {
/// Header
pub header: BlockHeaderValue,
/// Array of transactions
pub transactions: Vec<VersionedValidTransaction>,
/// Array of rejected transactions.
pub rejected_transactions: Vec<VersionedRejectedTransaction>,
/// Event recommendations
pub event_recommendations: Vec<Event>,
}

impl BlockValue {
/// ...
pub fn nested_len(&self) -> usize {
self.event_recommendations.len()
+ self.transactions.len()
+ self.rejected_transactions.len()
+ self.header.invalidated_blocks_hashes.len()
}
}

impl PartialOrd for BlockValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.header.cmp(&other.header))
}
}

impl Ord for BlockValue {
fn cmp(&self, other: &Self) -> Ordering {
self.header.cmp(&other.header)
}
}

/// The prelude re-exports most commonly used traits, structs and macros from this crate.
pub mod prelude {
pub use super::{BlockHeaderValue, BlockValue};
}
2 changes: 1 addition & 1 deletion data_model/src/events/execute_trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::*;
use crate::prelude::*;

/// Trigger execution event. Produced every time the `ExecuteTrigger` instruction is executed.
#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, IntoSchema)]
#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Serialize, Deserialize, IntoSchema)]
pub struct Event {
/// Id of trigger to be executed
trigger_id: TriggerId,
Expand Down
Loading

0 comments on commit 1d11a66

Please sign in to comment.