From 5ce393a998d183c5219d93cdd12ab6c12cc12289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Ver=C5=A1i=C4=87?= Date: Wed, 28 Feb 2024 15:50:38 +0300 Subject: [PATCH] [refactor] #4315: split pipeline events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marin Veršić --- cli/src/lib.rs | 2 +- client/benches/tps/utils.rs | 5 +- client/src/client.rs | 49 +- client/src/config.rs | 2 +- client/tests/integration/asset.rs | 13 +- .../integration/domain_owner_permissions.rs | 9 +- client/tests/integration/events/pipeline.rs | 55 ++- client/tests/integration/permissions.rs | 20 +- client/tests/integration/roles.rs | 9 +- .../integration/triggers/time_trigger.rs | 36 +- client_cli/src/main.rs | 11 +- config/src/parameters/defaults.rs | 10 - configs/swarm/executor.wasm | Bin 533713 -> 536294 bytes core/benches/blocks/apply_blocks.rs | 8 +- core/benches/blocks/common.rs | 2 + core/benches/blocks/validate_blocks.rs | 15 +- .../blocks/validate_blocks_benchmark.rs | 6 +- .../benches/blocks/validate_blocks_oneshot.rs | 4 +- core/benches/kura.rs | 1 + core/benches/validation.rs | 2 +- core/src/block.rs | 286 ++++++++---- core/src/block_sync.rs | 27 +- core/src/kura.rs | 6 +- core/src/lib.rs | 6 +- core/src/queue.rs | 113 +++-- core/src/smartcontracts/isi/query.rs | 6 + core/src/smartcontracts/isi/triggers/set.rs | 32 +- .../isi/triggers/specialized.rs | 4 +- core/src/smartcontracts/wasm.rs | 4 +- core/src/state.rs | 83 ++-- core/src/sumeragi/main_loop.rs | 179 ++++---- core/src/sumeragi/message.rs | 4 +- core/src/sumeragi/mod.rs | 43 +- core/test_network/src/lib.rs | 12 +- crypto/src/lib.rs | 7 - data_model/derive/src/model.rs | 11 +- data_model/src/account.rs | 2 + data_model/src/block.rs | 79 +--- data_model/src/events/data/filters.rs | 1 - data_model/src/events/mod.rs | 122 +++-- data_model/src/events/pipeline.rs | 433 +++++++++++------- data_model/src/lib.rs | 12 - data_model/src/query/predicate.rs | 2 +- data_model/src/smart_contract.rs | 2 +- data_model/src/transaction.rs | 35 +- data_model/src/trigger.rs | 16 +- docs/source/references/schema.json | 227 +++++---- schema/gen/src/lib.rs | 28 +- telemetry/derive/src/lib.rs | 9 +- telemetry/src/metrics.rs | 16 +- tools/parity_scale_decoder/src/main.rs | 1 + torii/src/event.rs | 2 +- 52 files changed, 1222 insertions(+), 847 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index eb631467a21..021fb137326 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -251,7 +251,7 @@ impl Iroha { }); let state = Arc::new(state); - let queue = Arc::new(Queue::from_config(config.queue)); + let queue = Arc::new(Queue::from_config(config.queue, events_sender.clone())); match Self::start_telemetry(&logger, &config).await? { TelemetryStartStatus::Started => iroha_logger::info!("Telemetry started"), TelemetryStartStatus::NotStarted => iroha_logger::warn!("Telemetry not started"), diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index d215d1ce203..3b82ca153e2 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -18,6 +18,7 @@ use iroha_client::{ prelude::*, }, }; +use iroha_data_model::events::pipeline::{BlockEventFilter, BlockStatus}; use serde::Deserialize; use test_network::*; @@ -172,9 +173,7 @@ impl MeasurerUnit { fn spawn_event_counter(&self) -> thread::JoinHandle> { let listener = self.client.clone(); let (init_sender, init_receiver) = mpsc::channel(); - let event_filter = PipelineEventFilter::new() - .for_entity(PipelineEntityKind::Block) - .for_status(PipelineStatusKind::Committed); + let event_filter = BlockEventFilter::default().for_status(BlockStatus::Applied); let blocks_expected = self.config.blocks as usize; let name = self.name; let handle = thread::spawn(move || -> Result<()> { diff --git a/client/src/client.rs b/client/src/client.rs index ce942c1752c..156a3e68eda 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -14,7 +14,10 @@ use eyre::{eyre, Result, WrapErr}; use futures_util::StreamExt; use http_default::{AsyncWebSocketStream, WebSocketStream}; pub use iroha_config::client_api::ConfigDTO; -use iroha_data_model::query::QueryOutputBox; +use iroha_data_model::{ + events::pipeline::{BlockStatus, PipelineEventBox, TransactionEventFilter, TransactionStatus}, + query::QueryOutputBox, +}; use iroha_logger::prelude::*; use iroha_telemetry::metrics::Status; use iroha_torii_const::uri as torii_uri; @@ -605,7 +608,7 @@ impl Client { let mut event_iterator = { let event_iterator_result = tokio::time::timeout_at( deadline, - self.listen_for_events_async(PipelineEventFilter::new().for_hash(hash.into())), + self.listen_for_events_async(TransactionEventFilter::default().for_hash(hash)), ) .await .map_err(Into::into) @@ -631,17 +634,34 @@ impl Client { event_iterator: &mut AsyncEventStream, hash: HashOf, ) -> Result> { + let mut block_height = None; + while let Some(event) = event_iterator.next().await { - if let Event::Pipeline(this_event) = event? { - match this_event.status() { - PipelineStatus::Validating => {} - PipelineStatus::Rejected(ref reason) => { - return Err(reason.clone().into()); + if let EventBox::Pipeline(this_event) = event? { + match this_event { + PipelineEventBox::Transaction(transaction_event) => { + match transaction_event.status() { + TransactionStatus::Queued => {} + TransactionStatus::Approved => { + block_height = transaction_event.block_height; + } + TransactionStatus::Rejected(reason) => { + return Err(reason.clone().into()); + } + TransactionStatus::Expired => return Err(eyre!("Transaction expired")), + } + } + PipelineEventBox::Block(block_event) => { + if Some(block_event.header().height()) == block_height { + if let BlockStatus::Applied = block_event.status() { + return Ok(hash); + } + } } - PipelineStatus::Committed => return Ok(hash), } } } + Err(eyre!( "Connection dropped without `Committed` or `Rejected` event" )) @@ -904,9 +924,7 @@ impl Client { pub fn listen_for_events( &self, event_filter: impl Into, - ) -> Result>> { - let event_filter = event_filter.into(); - iroha_logger::trace!(?event_filter); + ) -> Result>> { events_api::EventIterator::new(self.events_handler(event_filter)?) } @@ -919,8 +937,6 @@ impl Client { &self, event_filter: impl Into + Send, ) -> Result { - let event_filter = event_filter.into(); - iroha_logger::trace!(?event_filter, "Async listening with"); events_api::AsyncEventStream::new(self.events_handler(event_filter)?).await } @@ -933,8 +949,11 @@ impl Client { &self, event_filter: impl Into, ) -> Result { + let event_filter = event_filter.into(); + iroha_logger::trace!(?event_filter); + events_api::flow::Init::new( - event_filter.into(), + event_filter, self.headers.clone(), self.torii_url .join(torii_uri::SUBSCRIPTION) @@ -1284,7 +1303,7 @@ pub mod events_api { pub struct Events; impl FlowEvents for Events { - type Event = crate::data_model::prelude::Event; + type Event = crate::data_model::prelude::EventBox; fn message(&self, message: Vec) -> Result { let event_socket_message = EventMessage::decode_all(&mut message.as_slice())?; diff --git a/client/src/config.rs b/client/src/config.rs index 34b7e8663c7..72bb909d8c7 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -9,7 +9,7 @@ use iroha_config::{ base, base::{FromEnv, StdEnv, UnwrapPartial}, }; -use iroha_crypto::prelude::*; +use iroha_crypto::KeyPair; use iroha_data_model::{prelude::*, ChainId}; use iroha_primitives::small::SmallStr; use serde::{Deserialize, Serialize}; diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index fe95e30f348..34a102afd93 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -10,6 +10,7 @@ use iroha_config::parameters::actual::Root as Config; use iroha_data_model::{ asset::{AssetId, AssetValue, AssetValueType}, isi::error::{InstructionEvaluationError, InstructionExecutionError, Mismatch, TypeError}, + transaction::error::TransactionRejectionReason, }; use serde_json::json; use test_network::*; @@ -463,17 +464,17 @@ fn fail_if_dont_satisfy_spec() { .expect_err("Should be rejected due to non integer value"); let rejection_reason = err - .downcast_ref::() - .unwrap_or_else(|| panic!("Error {err} is not PipelineRejectionReason")); + .downcast_ref::() + .unwrap_or_else(|| panic!("Error {err} is not TransactionRejectionReason")); assert_eq!( rejection_reason, - &PipelineRejectionReason::Transaction(TransactionRejectionReason::Validation( - ValidationFail::InstructionFailed(InstructionExecutionError::Evaluate( - InstructionEvaluationError::Type(TypeError::from(Mismatch { + &TransactionRejectionReason::Validation(ValidationFail::InstructionFailed( + InstructionExecutionError::Evaluate(InstructionEvaluationError::Type( + TypeError::from(Mismatch { expected: AssetValueType::Numeric(NumericSpec::integer()), actual: AssetValueType::Numeric(NumericSpec::fractional(2)) - })) + }) )) )) ); diff --git a/client/tests/integration/domain_owner_permissions.rs b/client/tests/integration/domain_owner_permissions.rs index e0945b85f70..af78eff12ac 100644 --- a/client/tests/integration/domain_owner_permissions.rs +++ b/client/tests/integration/domain_owner_permissions.rs @@ -3,6 +3,7 @@ use iroha_client::{ crypto::KeyPair, data_model::{account::SignatureCheckCondition, prelude::*}, }; +use iroha_data_model::transaction::error::TransactionRejectionReason; use serde_json::json; use test_network::*; @@ -37,14 +38,12 @@ fn domain_owner_domain_permissions() -> Result<()> { .expect_err("Tx should fail due to permissions"); let rejection_reason = err - .downcast_ref::() - .unwrap_or_else(|| panic!("Error {err} is not PipelineRejectionReason")); + .downcast_ref::() + .unwrap_or_else(|| panic!("Error {err} is not TransactionRejectionReason")); assert!(matches!( rejection_reason, - &PipelineRejectionReason::Transaction(TransactionRejectionReason::Validation( - ValidationFail::NotPermitted(_) - )) + &TransactionRejectionReason::Validation(ValidationFail::NotPermitted(_)) )); // "alice@wonderland" owns the domain and can register AssetDefinitions by default as domain owner diff --git a/client/tests/integration/events/pipeline.rs b/client/tests/integration/events/pipeline.rs index 30f17528219..a16aa2d69f2 100644 --- a/client/tests/integration/events/pipeline.rs +++ b/client/tests/integration/events/pipeline.rs @@ -9,6 +9,14 @@ use iroha_client::{ }, }; use iroha_config::parameters::actual::Root as Config; +use iroha_data_model::{ + events::pipeline::{ + BlockEvent, BlockEventFilter, BlockStatus, TransactionEventFilter, TransactionStatus, + }, + isi::error::InstructionExecutionError, + transaction::error::TransactionRejectionReason, + ValidationFail, +}; use test_network::*; // Needed to re-enable ignored tests. @@ -17,24 +25,28 @@ const PEER_COUNT: usize = 7; #[ignore = "ignore, more in #2851"] #[test] fn transaction_with_no_instructions_should_be_committed() -> Result<()> { - test_with_instruction_and_status_and_port(None, PipelineStatusKind::Committed, 10_250) + test_with_instruction_and_status_and_port(None, &TransactionStatus::Approved, 10_250) } #[ignore = "ignore, more in #2851"] // #[ignore = "Experiment"] #[test] fn transaction_with_fail_instruction_should_be_rejected() -> Result<()> { - let fail = Fail::new("Should be rejected".to_owned()); + let msg = "Should be rejected".to_owned(); + + let fail = Fail::new(msg.clone()); test_with_instruction_and_status_and_port( Some(fail.into()), - PipelineStatusKind::Rejected, + &TransactionStatus::Rejected(Box::new(TransactionRejectionReason::Validation( + ValidationFail::InstructionFailed(InstructionExecutionError::Fail(msg)), + ))), 10_350, ) } fn test_with_instruction_and_status_and_port( instruction: Option, - should_be: PipelineStatusKind, + should_be: &TransactionStatus, port: u16, ) -> Result<()> { let (_rt, network, client) = @@ -56,9 +68,9 @@ fn test_with_instruction_and_status_and_port( let mut handles = Vec::new(); for listener in clients { let checker = Checker { listener, hash }; - let handle_validating = checker.clone().spawn(PipelineStatusKind::Validating); + let handle_validating = checker.clone().spawn(TransactionStatus::Queued); handles.push(handle_validating); - let handle_validated = checker.spawn(should_be); + let handle_validated = checker.spawn(should_be.clone()); handles.push(handle_validated); } // When @@ -78,15 +90,14 @@ struct Checker { } impl Checker { - fn spawn(self, status_kind: PipelineStatusKind) -> JoinHandle<()> { + fn spawn(self, status_kind: TransactionStatus) -> JoinHandle<()> { thread::spawn(move || { let mut event_iterator = self .listener .listen_for_events( - PipelineEventFilter::new() - .for_entity(PipelineEntityKind::Transaction) + TransactionEventFilter::default() .for_status(status_kind) - .for_hash(*self.hash), + .for_hash(self.hash), ) .expect("Failed to create event iterator."); let event_result = event_iterator.next().expect("Stream closed"); @@ -96,13 +107,11 @@ impl Checker { } #[test] -fn committed_block_must_be_available_in_kura() { +fn applied_block_must_be_available_in_kura() { let (_rt, peer, client) = ::new().with_port(11_040).start_with_runtime(); wait_for_genesis_committed(&[client.clone()], 0); - let event_filter = PipelineEventFilter::new() - .for_entity(PipelineEntityKind::Block) - .for_status(PipelineStatusKind::Committed); + let event_filter = BlockEventFilter::default().for_status(BlockStatus::Committed); let mut event_iter = client .listen_for_events(event_filter) .expect("Failed to subscribe for events"); @@ -111,21 +120,17 @@ fn committed_block_must_be_available_in_kura() { .submit(Fail::new("Dummy instruction".to_owned())) .expect("Failed to submit transaction"); - let event = event_iter.next().expect("Block must be committed"); - let Ok(Event::Pipeline(PipelineEvent { - entity_kind: PipelineEntityKind::Block, - status: PipelineStatus::Committed, - hash, - })) = event - else { - panic!("Received unexpected event") - }; - let hash = HashOf::from_untyped_unchecked(hash); + let event: BlockEvent = event_iter + .next() + .expect("Block must be committed") + .expect("Block must be committed") + .try_into() + .expect("Received unexpected event"); peer.iroha .as_ref() .expect("Must be some") .kura - .get_block_height_by_hash(&hash) + .get_block_by_height(event.header().height()) .expect("Block committed event was received earlier"); } diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index e7fea53ac18..9a4578b8660 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -6,7 +6,9 @@ use iroha_client::{ crypto::KeyPair, data_model::prelude::*, }; -use iroha_data_model::permission::PermissionToken; +use iroha_data_model::{ + permission::PermissionToken, transaction::error::TransactionRejectionReason, +}; use iroha_genesis::GenesisNetwork; use serde_json::json; use test_network::{PeerBuilder, *}; @@ -104,14 +106,12 @@ fn permissions_disallow_asset_transfer() { .submit_transaction_blocking(&transfer_tx) .expect_err("Transaction was not rejected."); let rejection_reason = err - .downcast_ref::() - .expect("Error {err} is not PipelineRejectionReason"); + .downcast_ref::() + .expect("Error {err} is not TransactionRejectionReason"); //Then assert!(matches!( rejection_reason, - &PipelineRejectionReason::Transaction(TransactionRejectionReason::Validation( - ValidationFail::NotPermitted(_) - )) + &TransactionRejectionReason::Validation(ValidationFail::NotPermitted(_)) )); let alice_assets = get_assets(&iroha_client, &alice_id); assert_eq!(alice_assets, alice_start_assets); @@ -156,14 +156,12 @@ fn permissions_disallow_asset_burn() { .submit_transaction_blocking(&burn_tx) .expect_err("Transaction was not rejected."); let rejection_reason = err - .downcast_ref::() - .expect("Error {err} is not PipelineRejectionReason"); + .downcast_ref::() + .expect("Error {err} is not TransactionRejectionReason"); assert!(matches!( rejection_reason, - &PipelineRejectionReason::Transaction(TransactionRejectionReason::Validation( - ValidationFail::NotPermitted(_) - )) + &TransactionRejectionReason::Validation(ValidationFail::NotPermitted(_)) )); let alice_assets = get_assets(&iroha_client, &alice_id); diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 12a03f333c1..6f260e3709f 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -6,6 +6,7 @@ use iroha_client::{ crypto::KeyPair, data_model::prelude::*, }; +use iroha_data_model::transaction::error::TransactionRejectionReason; use serde_json::json; use test_network::*; @@ -164,14 +165,12 @@ fn role_with_invalid_permissions_is_not_accepted() -> Result<()> { .expect_err("Submitting role with invalid permission token should fail"); let rejection_reason = err - .downcast_ref::() - .unwrap_or_else(|| panic!("Error {err} is not PipelineRejectionReason")); + .downcast_ref::() + .unwrap_or_else(|| panic!("Error {err} is not TransactionRejectionReason")); assert!(matches!( rejection_reason, - &PipelineRejectionReason::Transaction(TransactionRejectionReason::Validation( - ValidationFail::NotPermitted(_) - )) + &TransactionRejectionReason::Validation(ValidationFail::NotPermitted(_)) )); Ok(()) diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index 1f29a0d8ba9..d016cb70222 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -5,12 +5,30 @@ use iroha_client::{ client::{self, Client, QueryResult}, data_model::{prelude::*, transaction::WasmSmartContract}, }; -use iroha_config::parameters::defaults::chain_wide::DEFAULT_CONSENSUS_ESTIMATION; +use iroha_config::parameters::defaults::chain_wide::{DEFAULT_BLOCK_TIME, DEFAULT_COMMIT_TIME}; +use iroha_data_model::events::pipeline::{BlockEventFilter, BlockStatus}; use iroha_logger::info; use test_network::*; use crate::integration::new_account_with_random_public_key; +const DEFAULT_CONSENSUS_ESTIMATION: Duration = + match DEFAULT_BLOCK_TIME.checked_add(match DEFAULT_COMMIT_TIME.checked_div(2) { + Some(x) => x, + None => unreachable!(), + }) { + Some(x) => x, + None => unreachable!(), + }; + +fn curr_time() -> core::time::Duration { + use std::time::SystemTime; + + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time") +} + /// Macro to abort compilation, if `e` isn't `true` macro_rules! const_assert { ($e:expr) => { @@ -33,7 +51,7 @@ fn time_trigger_execution_count_error_should_be_less_than_15_percent() -> Result let (_rt, _peer, mut test_client) = ::new().with_port(10_775).start_with_runtime(); wait_for_genesis_committed(&vec![test_client.clone()], 0); - let start_time = current_time(); + let start_time = curr_time(); // Start listening BEFORE submitting any transaction not to miss any block committed event let event_listener = get_block_committed_event_listener(&test_client)?; @@ -66,7 +84,7 @@ fn time_trigger_execution_count_error_should_be_less_than_15_percent() -> Result )?; std::thread::sleep(DEFAULT_CONSENSUS_ESTIMATION); - let finish_time = current_time(); + let finish_time = curr_time(); let average_count = finish_time.saturating_sub(start_time).as_millis() / PERIOD.as_millis(); let actual_value = get_asset_value(&mut test_client, asset_id); @@ -92,7 +110,7 @@ fn change_asset_metadata_after_1_sec() -> Result<()> { let (_rt, _peer, mut test_client) = ::new().with_port(10_660).start_with_runtime(); wait_for_genesis_committed(&vec![test_client.clone()], 0); - let start_time = current_time(); + let start_time = curr_time(); // Start listening BEFORE submitting any transaction not to miss any block committed event let event_listener = get_block_committed_event_listener(&test_client)?; @@ -220,7 +238,7 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { let event_listener = get_block_committed_event_listener(&test_client)?; // Registering trigger - let start_time = current_time(); + let start_time = curr_time(); let schedule = TimeSchedule::starting_at(start_time).with_period(Duration::from_millis(TRIGGER_PERIOD_MS)); let register_trigger = Register::trigger(Trigger::new( @@ -272,10 +290,8 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { /// Get block committed event listener fn get_block_committed_event_listener( client: &Client, -) -> Result>> { - let block_filter = PipelineEventFilter::new() - .for_entity(PipelineEntityKind::Block) - .for_status(PipelineStatusKind::Committed); +) -> Result>> { + let block_filter = BlockEventFilter::default().for_status(BlockStatus::Committed); client.listen_for_events(block_filter) } @@ -292,7 +308,7 @@ fn get_asset_value(client: &mut Client, asset_id: AssetId) -> Numeric { /// Submit some sample ISIs to create new blocks fn submit_sample_isi_on_every_block_commit( - block_committed_event_listener: impl Iterator>, + block_committed_event_listener: impl Iterator>, test_client: &mut Client, account_id: &AccountId, timeout: Duration, diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index 807d504a280..5173858371f 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -249,13 +249,17 @@ mod filter { mod events { + use iroha_client::data_model::events::pipeline::{BlockEventFilter, TransactionEventFilter}; + use super::*; /// Get event stream from iroha peer #[derive(clap::Subcommand, Debug, Clone, Copy)] pub enum Args { - /// Gets pipeline events - Pipeline, + /// Gets block pipeline events + BlockPipeline, + /// Gets transaction pipeline events + TransactionPipeline, /// Gets data events Data, /// Get execute trigger events @@ -267,7 +271,8 @@ mod events { impl RunArgs for Args { fn run(self, context: &mut dyn RunContext) -> Result<()> { match self { - Args::Pipeline => listen(PipelineEventFilter::new(), context), + Args::TransactionPipeline => listen(TransactionEventFilter::default(), context), + Args::BlockPipeline => listen(BlockEventFilter::default(), context), Args::Data => listen(DataEventFilter::Any, context), Args::ExecuteTrigger => listen(ExecuteTriggerEventFilter::new(), context), Args::TriggerCompleted => listen(TriggerCompletedEventFilter::new(), context), diff --git a/config/src/parameters/defaults.rs b/config/src/parameters/defaults.rs index ff55704ee09..74b03df1b00 100644 --- a/config/src/parameters/defaults.rs +++ b/config/src/parameters/defaults.rs @@ -61,16 +61,6 @@ pub mod chain_wide { // TODO: wrap into a `Bytes` newtype pub const DEFAULT_WASM_MAX_MEMORY_BYTES: u32 = 500 * 2_u32.pow(20); - /// Default estimation of consensus duration. - pub const DEFAULT_CONSENSUS_ESTIMATION: Duration = - match DEFAULT_BLOCK_TIME.checked_add(match DEFAULT_COMMIT_TIME.checked_div(2) { - Some(x) => x, - None => unreachable!(), - }) { - Some(x) => x, - None => unreachable!(), - }; - /// Default limits for metadata pub const DEFAULT_METADATA_LIMITS: MetadataLimits = MetadataLimits::new(2_u32.pow(20), 2_u32.pow(12)); diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index fb05db1652ebebd21b800200616392a24d65d64b..e5562d1960050313004030ad9bf9404d8615bd14 100644 GIT binary patch delta 137969 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 Ds0db> delta 135147 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 z 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 BSPTFF diff --git a/core/benches/blocks/apply_blocks.rs b/core/benches/blocks/apply_blocks.rs index f85921695d5..104f6728dca 100644 --- a/core/benches/blocks/apply_blocks.rs +++ b/core/benches/blocks/apply_blocks.rs @@ -39,7 +39,7 @@ impl StateApplyBlocks { let state = build_state(rt, &account_id, &key_pair); instructions .into_iter() - .map(|instructions| -> Result<_> { + .map(|instructions| { let mut state_block = state.block(); let block = create_block( &mut state_block, @@ -47,11 +47,11 @@ impl StateApplyBlocks { account_id.clone(), &key_pair, ); - state_block.apply_without_execution(&block)?; + let _wsv_events = state_block.apply_without_execution(&block); state_block.commit(); - Ok(block) + block }) - .collect::, _>>()? + .collect::>() }; Ok(Self { state, blocks }) diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index e4070b458c5..d88514f7c9f 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -42,7 +42,9 @@ pub fn create_block( ) .chain(0, state) .sign(key_pair) + .unpack(|_| {}) .commit(&topology) + .unpack(|_| {}) .unwrap(); // Verify that transactions are valid diff --git a/core/benches/blocks/validate_blocks.rs b/core/benches/blocks/validate_blocks.rs index 3390d7aaebe..6aa027d5f65 100644 --- a/core/benches/blocks/validate_blocks.rs +++ b/core/benches/blocks/validate_blocks.rs @@ -1,4 +1,3 @@ -use eyre::Result; use iroha_core::{prelude::*, state::State}; use iroha_data_model::{isi::InstructionBox, prelude::*}; @@ -21,11 +20,11 @@ impl StateValidateBlocks { /// - Failed to parse [`AccountId`] /// - Failed to generate [`KeyPair`] /// - Failed to create instructions for block - pub fn setup(rt: &tokio::runtime::Handle) -> Result { + pub fn setup(rt: &tokio::runtime::Handle) -> Self { let domains = 100; let accounts_per_domain = 1000; let assets_per_domain = 1000; - let account_id: AccountId = "alice@wonderland".parse()?; + let account_id: AccountId = "alice@wonderland".parse().unwrap(); let key_pair = KeyPair::random(); let state = build_state(rt, &account_id, &key_pair); @@ -38,12 +37,12 @@ impl StateValidateBlocks { .into_iter() .collect::>(); - Ok(Self { + Self { state, instructions, key_pair, account_id, - }) + } } /// Run benchmark body. @@ -61,7 +60,7 @@ impl StateValidateBlocks { key_pair, account_id, }: Self, - ) -> Result<()> { + ) { for (instructions, i) in instructions.into_iter().zip(1..) { let mut state_block = state.block(); let block = create_block( @@ -70,11 +69,9 @@ impl StateValidateBlocks { account_id.clone(), &key_pair, ); - state_block.apply_without_execution(&block)?; + let _wsv_events = state_block.apply_without_execution(&block); assert_eq!(state_block.height(), i); state_block.commit(); } - - Ok(()) } } diff --git a/core/benches/blocks/validate_blocks_benchmark.rs b/core/benches/blocks/validate_blocks_benchmark.rs index 454e07e3f4c..c3592b506f2 100644 --- a/core/benches/blocks/validate_blocks_benchmark.rs +++ b/core/benches/blocks/validate_blocks_benchmark.rs @@ -15,10 +15,8 @@ fn validate_blocks(c: &mut Criterion) { group.significance_level(0.1).sample_size(10); group.bench_function("validate_blocks", |b| { b.iter_batched( - || StateValidateBlocks::setup(rt.handle()).expect("Failed to setup benchmark"), - |bench| { - StateValidateBlocks::measure(bench).expect("Failed to execute benchmark"); - }, + || StateValidateBlocks::setup(rt.handle()), + StateValidateBlocks::measure, criterion::BatchSize::SmallInput, ); }); diff --git a/core/benches/blocks/validate_blocks_oneshot.rs b/core/benches/blocks/validate_blocks_oneshot.rs index 118ce739b99..8c8b20b1343 100644 --- a/core/benches/blocks/validate_blocks_oneshot.rs +++ b/core/benches/blocks/validate_blocks_oneshot.rs @@ -20,6 +20,6 @@ fn main() { } iroha_logger::test_logger(); iroha_logger::info!("Starting..."); - let bench = StateValidateBlocks::setup(rt.handle()).expect("Failed to setup benchmark"); - StateValidateBlocks::measure(bench).expect("Failed to execute bnechmark"); + let bench = StateValidateBlocks::setup(rt.handle()); + StateValidateBlocks::measure(bench); } diff --git a/core/benches/kura.rs b/core/benches/kura.rs index 06f78dcfc9b..521e242f60e 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -56,6 +56,7 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { BlockBuilder::new(vec![tx], topology, Vec::new()) .chain(0, &mut state_block) .sign(&KeyPair::random()) + .unpack(|_| {}) }; for _ in 1..n_executors { diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 8aff8c01ce0..d7e5459f090 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -186,7 +186,7 @@ fn sign_blocks(criterion: &mut Criterion) { b.iter_batched( || block.clone(), |block| { - let _: ValidBlock = block.sign(&key_pair); + let _: ValidBlock = block.sign(&key_pair).unpack(|_| {}); count += 1; }, BatchSize::SmallInput, diff --git a/core/src/block.rs b/core/src/block.rs index 4a6f210502e..c15d0da5449 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -6,7 +6,6 @@ //! [`Block`]s are organised into a linear sequence over time (also known as the block chain). use std::error::Error as _; -use iroha_config::parameters::defaults::chain_wide::DEFAULT_CONSENSUS_ESTIMATION; use iroha_crypto::{HashOf, KeyPair, MerkleTree, SignatureOf, SignaturesOf}; use iroha_data_model::{ block::*, @@ -18,6 +17,7 @@ use iroha_genesis::GenesisTransaction; use iroha_primitives::unique_vec::UniqueVec; use thiserror::Error; +pub(crate) use self::event::WithEvents; pub use self::{chained::Chained, commit::CommittedBlock, valid::ValidBlock}; use crate::{prelude::*, sumeragi::network_topology::Topology, tx::AcceptTransactionFail}; @@ -93,6 +93,8 @@ pub enum SignatureVerificationError { pub struct BlockBuilder(B); mod pending { + use std::time::SystemTime; + use iroha_data_model::transaction::TransactionValue; use super::*; @@ -110,7 +112,7 @@ mod pending { /// Transaction will be validated when block is chained. transactions: Vec, /// Event recommendations for use in triggers and off-chain work - event_recommendations: Vec, + event_recommendations: Vec, } impl BlockBuilder { @@ -123,7 +125,7 @@ mod pending { pub fn new( transactions: Vec, commit_topology: Topology, - event_recommendations: Vec, + event_recommendations: Vec, ) -> Self { assert!(!transactions.is_empty(), "Empty block created"); @@ -136,22 +138,20 @@ mod pending { fn make_header( previous_height: u64, - previous_block_hash: Option>, + prev_block_hash: Option>, view_change_index: u64, transactions: &[TransactionValue], ) -> BlockHeader { BlockHeader { - timestamp_ms: iroha_data_model::current_time() - .as_millis() - .try_into() - .expect("Time should fit into u64"), - consensus_estimation_ms: DEFAULT_CONSENSUS_ESTIMATION + creation_time_ms: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time") .as_millis() .try_into() .expect("Time should fit into u64"), height: previous_height + 1, view_change_index, - previous_block_hash, + prev_block_hash, transactions_hash: transactions .iter() .map(|value| value.as_ref().hash()) @@ -222,16 +222,16 @@ mod chained { impl BlockBuilder { /// Sign this block and get [`SignedBlock`]. - pub fn sign(self, key_pair: &KeyPair) -> ValidBlock { + pub fn sign(self, key_pair: &KeyPair) -> WithEvents { let signature = SignatureOf::new(key_pair, &self.0 .0); - ValidBlock( + WithEvents::new(ValidBlock( SignedBlockV1 { payload: self.0 .0, signatures: SignaturesOf::from(signature), } .into(), - ) + )) } } } @@ -245,7 +245,7 @@ mod valid { /// Block that was validated and accepted #[derive(Debug, Clone)] #[repr(transparent)] - pub struct ValidBlock(pub(crate) SignedBlock); + pub struct ValidBlock(pub(super) SignedBlock); impl ValidBlock { /// Validate a block against the current state of the world. @@ -264,7 +264,7 @@ mod valid { topology: &Topology, expected_chain_id: &ChainId, state_block: &mut StateBlock<'_>, - ) -> Result { + ) -> WithEvents> { if !block.header().is_genesis() { let actual_commit_topology = block.commit_topology(); let expected_commit_topology = &topology.ordered_peers; @@ -272,20 +272,23 @@ mod valid { if actual_commit_topology != expected_commit_topology { let actual_commit_topology = actual_commit_topology.clone(); - return Err(( + return WithEvents::new(Err(( block, BlockValidationError::TopologyMismatch { expected: expected_commit_topology.clone(), actual: actual_commit_topology, }, - )); + ))); } if topology .filter_signatures_by_roles(&[Role::Leader], block.signatures()) .is_empty() { - return Err((block, SignatureVerificationError::LeaderMissing.into())); + return WithEvents::new(Err(( + block, + SignatureVerificationError::LeaderMissing.into(), + ))); } } @@ -293,48 +296,51 @@ mod valid { let actual_height = block.header().height; if expected_block_height != actual_height { - return Err(( + return WithEvents::new(Err(( block, BlockValidationError::LatestBlockHeightMismatch { expected: expected_block_height, actual: actual_height, }, - )); + ))); } - let expected_previous_block_hash = state_block.latest_block_hash(); - let actual_block_hash = block.header().previous_block_hash; + let expected_prev_block_hash = state_block.latest_block_hash(); + let actual_prev_block_hash = block.header().prev_block_hash; - if expected_previous_block_hash != actual_block_hash { - return Err(( + if expected_prev_block_hash != actual_prev_block_hash { + return WithEvents::new(Err(( block, BlockValidationError::LatestBlockHashMismatch { - expected: expected_previous_block_hash, - actual: actual_block_hash, + expected: expected_prev_block_hash, + actual: actual_prev_block_hash, }, - )); + ))); } if block .transactions() .any(|tx| state_block.has_transaction(tx.as_ref().hash())) { - return Err((block, BlockValidationError::HasCommittedTransactions)); + return WithEvents::new(Err(( + block, + BlockValidationError::HasCommittedTransactions, + ))); } if let Err(error) = Self::validate_transactions(&block, expected_chain_id, state_block) { - return Err((block, error.into())); + return WithEvents::new(Err((block, error.into()))); } let SignedBlock::V1(block) = block; - Ok(ValidBlock( + WithEvents::new(Ok(ValidBlock( SignedBlockV1 { payload: block.payload, signatures: block.signatures, } .into(), - )) + ))) } fn validate_transactions( @@ -379,24 +385,33 @@ mod valid { /// /// - Not enough signatures /// - Not signed by proxy tail - pub(crate) fn commit_with_signatures( + pub fn commit_with_signatures( mut self, topology: &Topology, signatures: SignaturesOf, - ) -> Result { + ) -> WithEvents> { if topology .filter_signatures_by_roles(&[Role::Leader], &signatures) .is_empty() { - return Err((self, SignatureVerificationError::LeaderMissing.into())); + return WithEvents::new(Err(( + self, + SignatureVerificationError::LeaderMissing.into(), + ))); } if !self.as_ref().signatures().is_subset(&signatures) { - return Err((self, SignatureVerificationError::SignatureMissing.into())); + return WithEvents::new(Err(( + self, + SignatureVerificationError::SignatureMissing.into(), + ))); } if !self.0.replace_signatures(signatures) { - return Err((self, SignatureVerificationError::UnknownSignature.into())); + return WithEvents::new(Err(( + self, + SignatureVerificationError::UnknownSignature.into(), + ))); } self.commit(topology) @@ -411,19 +426,19 @@ mod valid { pub fn commit( self, topology: &Topology, - ) -> Result { + ) -> WithEvents> { if !self.0.header().is_genesis() { if let Err(err) = self.verify_signatures(topology) { - return Err((self, err.into())); + return WithEvents::new(Err((self, err.into()))); } } - Ok(CommittedBlock(self)) + WithEvents::new(Ok(CommittedBlock(self))) } /// Add additional signatures for [`Self`]. #[must_use] - pub fn sign(self, key_pair: &KeyPair) -> Self { + pub fn sign(self, key_pair: &KeyPair) -> ValidBlock { ValidBlock(self.0.sign(key_pair)) } @@ -443,14 +458,10 @@ mod valid { pub(crate) fn new_dummy() -> Self { BlockBuilder(Chained(BlockPayload { header: BlockHeader { - timestamp_ms: 0, - consensus_estimation_ms: DEFAULT_CONSENSUS_ESTIMATION - .as_millis() - .try_into() - .expect("Should never overflow?"), + creation_time_ms: 0, height: 2, view_change_index: 0, - previous_block_hash: None, + prev_block_hash: None, transactions_hash: None, }, transactions: Vec::new(), @@ -458,6 +469,7 @@ mod valid { event_recommendations: Vec::new(), })) .sign(&KeyPair::random()) + .unpack(|_| {}) } /// Check if block's signatures meet requirements for given topology. @@ -628,31 +640,7 @@ mod commit { /// Represents a block accepted by consensus. /// Every [`Self`] will have a different height. #[derive(Debug, Clone)] - pub struct CommittedBlock(pub(crate) ValidBlock); - - impl CommittedBlock { - pub(crate) fn produce_events(&self) -> Vec { - let tx = self.as_ref().transactions().map(|tx| { - let status = tx.error.as_ref().map_or_else( - || PipelineStatus::Committed, - |error| PipelineStatus::Rejected(error.clone().into()), - ); - - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status, - hash: tx.as_ref().hash().into(), - } - }); - let current_block = core::iter::once(PipelineEvent { - entity_kind: PipelineEntityKind::Block, - status: PipelineStatus::Committed, - hash: self.as_ref().hash().into(), - }); - - tx.chain(current_block).collect() - } - } + pub struct CommittedBlock(pub(super) ValidBlock); impl From for ValidBlock { fn from(source: CommittedBlock) -> Self { @@ -666,12 +654,103 @@ mod commit { } } - // Invariants of [`CommittedBlock`] can't be violated through immutable reference impl AsRef for CommittedBlock { fn as_ref(&self) -> &SignedBlock { &self.0 .0 } } + + #[cfg(test)] + impl AsMut for CommittedBlock { + fn as_mut(&mut self) -> &mut SignedBlock { + &mut self.0 .0 + } + } +} + +mod event { + use super::*; + + pub trait EventProducer { + fn produce_events(&self) -> impl Iterator; + } + + #[derive(Debug)] + #[must_use] + pub struct WithEvents(B); + + impl WithEvents { + pub(super) fn new(source: B) -> Self { + Self(source) + } + } + + impl WithEvents> { + pub fn unpack(self, f: F) -> Result { + match self.0 { + Ok(ok) => Ok(WithEvents(ok).unpack(f)), + Err(err) => Err(WithEvents(err).unpack(f)), + } + } + } + impl WithEvents { + pub fn unpack(self, f: F) -> B { + self.0.produce_events().for_each(f); + self.0 + } + } + + impl WithEvents<(B, E)> { + pub(crate) fn unpack(self, f: F) -> (B, E) { + self.0 .1.produce_events().for_each(f); + self.0 + } + } + + impl EventProducer for ValidBlock { + fn produce_events(&self) -> impl Iterator { + let block_height = self.as_ref().header().height; + + let tx_events = self.as_ref().transactions().map(move |tx| { + let status = tx.error.as_ref().map_or_else( + || TransactionStatus::Approved, + |error| TransactionStatus::Rejected(error.clone().into()), + ); + + TransactionEvent { + block_height: Some(block_height), + hash: tx.as_ref().hash(), + status, + } + }); + + let block_event = core::iter::once(BlockEvent { + header: self.as_ref().header().clone(), + status: BlockStatus::Approved, + }); + + tx_events + .map(PipelineEventBox::from) + .chain(block_event.map(Into::into)) + } + } + + impl EventProducer for CommittedBlock { + fn produce_events(&self) -> impl Iterator { + let block_event = core::iter::once(BlockEvent { + header: self.as_ref().header().clone(), + status: BlockStatus::Committed, + }); + + block_event.map(Into::into) + } + } + + impl EventProducer for BlockValidationError { + fn produce_events(&self) -> impl Iterator { + core::iter::empty() + } + } } #[cfg(test)] @@ -690,7 +769,11 @@ mod tests { pub fn committed_and_valid_block_hashes_are_equal() { let valid_block = ValidBlock::new_dummy(); let topology = Topology::new(UniqueVec::new()); - let committed_block = valid_block.clone().commit(&topology).unwrap(); + let committed_block = valid_block + .clone() + .commit(&topology) + .unpack(|_| {}) + .unwrap(); assert_eq!( valid_block.0.hash_of_payload(), @@ -733,13 +816,26 @@ mod tests { let topology = Topology::new(UniqueVec::new()); let valid_block = BlockBuilder::new(transactions, topology, Vec::new()) .chain(0, &mut state_block) - .sign(&alice_keys); + .sign(&alice_keys) + .unpack(|_| {}); // The first transaction should be confirmed - assert!(valid_block.0.transactions().next().unwrap().error.is_none()); + assert!(valid_block + .as_ref() + .transactions() + .next() + .unwrap() + .error + .is_none()); // The second transaction should be rejected - assert!(valid_block.0.transactions().nth(1).unwrap().error.is_some()); + assert!(valid_block + .as_ref() + .transactions() + .nth(1) + .unwrap() + .error + .is_some()); } #[tokio::test] @@ -795,13 +891,26 @@ mod tests { let topology = Topology::new(UniqueVec::new()); let valid_block = BlockBuilder::new(transactions, topology, Vec::new()) .chain(0, &mut state_block) - .sign(&alice_keys); + .sign(&alice_keys) + .unpack(|_| {}); // The first transaction should fail - assert!(valid_block.0.transactions().next().unwrap().error.is_some()); + assert!(valid_block + .as_ref() + .transactions() + .next() + .unwrap() + .error + .is_some()); // The third transaction should succeed - assert!(valid_block.0.transactions().nth(2).unwrap().error.is_none()); + assert!(valid_block + .as_ref() + .transactions() + .nth(2) + .unwrap() + .error + .is_none()); } #[tokio::test] @@ -852,17 +961,30 @@ mod tests { let topology = Topology::new(UniqueVec::new()); let valid_block = BlockBuilder::new(transactions, topology, Vec::new()) .chain(0, &mut state_block) - .sign(&alice_keys); + .sign(&alice_keys) + .unpack(|_| {}); // The first transaction should be rejected assert!( - valid_block.0.transactions().next().unwrap().error.is_some(), + valid_block + .as_ref() + .transactions() + .next() + .unwrap() + .error + .is_some(), "The first transaction should be rejected, as it contains `Fail`." ); // The second transaction should be accepted assert!( - valid_block.0.transactions().nth(1).unwrap().error.is_none(), + valid_block + .as_ref() + .transactions() + .nth(1) + .unwrap() + .error + .is_none(), "The second transaction should be accepted." ); } diff --git a/core/src/block_sync.rs b/core/src/block_sync.rs index d2e5c6b7219..ef7f5b8c10a 100644 --- a/core/src/block_sync.rs +++ b/core/src/block_sync.rs @@ -91,16 +91,13 @@ impl BlockSynchronizer { /// Sends request for latest blocks to a chosen peer async fn request_latest_blocks_from_peer(&mut self, peer_id: PeerId) { - let (previous_hash, latest_hash) = { + let (prev_hash, latest_hash) = { let state_view = self.state.view(); - ( - state_view.previous_block_hash(), - state_view.latest_block_hash(), - ) + (state_view.prev_block_hash(), state_view.latest_block_hash()) }; message::Message::GetBlocksAfter(message::GetBlocksAfter::new( latest_hash, - previous_hash, + prev_hash, self.peer_id.clone(), )) .send_to(&self.network, peer_id) @@ -138,7 +135,7 @@ pub mod message { /// Hash of latest available block pub latest_hash: Option>, /// Hash of second to latest block - pub previous_hash: Option>, + pub prev_hash: Option>, /// Peer id pub peer_id: PeerId, } @@ -147,12 +144,12 @@ pub mod message { /// Construct [`GetBlocksAfter`]. pub const fn new( latest_hash: Option>, - previous_hash: Option>, + prev_hash: Option>, peer_id: PeerId, ) -> Self { Self { latest_hash, - previous_hash, + prev_hash, peer_id, } } @@ -190,21 +187,21 @@ pub mod message { match self { Message::GetBlocksAfter(GetBlocksAfter { latest_hash, - previous_hash, + prev_hash, peer_id, }) => { let local_latest_block_hash = block_sync.state.view().latest_block_hash(); if *latest_hash == local_latest_block_hash - || *previous_hash == local_latest_block_hash + || *prev_hash == local_latest_block_hash { return; } - let start_height = match previous_hash { + let start_height = match prev_hash { Some(hash) => match block_sync.kura.get_block_height_by_hash(hash) { None => { - error!(?previous_hash, "Block hash not found"); + error!(?prev_hash, "Block hash not found"); return; } Some(height) => height + 1, // It's get blocks *after*, so we add 1. @@ -223,9 +220,9 @@ pub mod message { // The only case where the blocks array could be empty is if we got queried for blocks // after the latest hash. There is a check earlier in the function that returns early // so it should not be possible for us to get here. - error!(hash=?previous_hash, "Blocks array is empty but shouldn't be."); + error!(hash=?prev_hash, "Blocks array is empty but shouldn't be."); } else { - trace!(hash=?previous_hash, "Sharing blocks after hash"); + trace!(hash=?prev_hash, "Sharing blocks after hash"); Message::ShareBlocks(ShareBlocks::new(blocks, block_sync.peer_id.clone())) .send_to(&block_sync.network, peer_id.clone()) .await; diff --git a/core/src/kura.rs b/core/src/kura.rs index 3dc536f9c2d..49bbf9d401a 100644 --- a/core/src/kura.rs +++ b/core/src/kura.rs @@ -154,7 +154,7 @@ impl Kura { let mut block_indices = vec![BlockIndex::default(); block_index_count]; block_store.read_block_indices(0, &mut block_indices)?; - let mut previous_block_hash = None; + let mut prev_block_hash = None; for block in block_indices { // This is re-allocated every iteration. This could cause a problem. let mut block_data_buffer = vec![0_u8; block.length.try_into()?]; @@ -162,13 +162,13 @@ impl Kura { match block_store.read_block_data(block.start, &mut block_data_buffer) { Ok(()) => match SignedBlock::decode_all_versioned(&block_data_buffer) { Ok(decoded_block) => { - if previous_block_hash != decoded_block.header().previous_block_hash { + if prev_block_hash != decoded_block.header().prev_block_hash { error!("Block has wrong previous block hash. Not reading any blocks beyond this height."); break; } let decoded_block_hash = decoded_block.hash(); block_hashes.push(decoded_block_hash); - previous_block_hash = Some(decoded_block_hash); + prev_block_hash = Some(decoded_block_hash); } Err(error) => { error!(?error, "Encountered malformed block. Not reading any blocks beyond this height."); diff --git a/core/src/lib.rs b/core/src/lib.rs index ab0b9be0d6b..06a0bd4103f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -18,7 +18,7 @@ use core::time::Duration; use gossiper::TransactionGossip; use indexmap::IndexSet; -use iroha_data_model::prelude::*; +use iroha_data_model::{events::EventBox, prelude::*}; use iroha_primitives::unique_vec::UniqueVec; use parity_scale_codec::{Decode, Encode}; use tokio::sync::broadcast; @@ -41,8 +41,8 @@ pub type PeersIds = UniqueVec; /// Parameters set. pub type Parameters = IndexSet; -/// Type of `Sender` which should be used for channels of `Event` messages. -pub type EventsSender = broadcast::Sender; +/// Type of `Sender` which should be used for channels of `Event` messages. +pub type EventsSender = broadcast::Sender; /// The network message #[derive(Clone, Debug, Encode, Decode)] diff --git a/core/src/queue.rs b/core/src/queue.rs index d463a655a4c..cf5e18c0e5b 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -1,6 +1,6 @@ //! Module with queue actor use core::time::Duration; -use std::num::NonZeroUsize; +use std::{num::NonZeroUsize, time::SystemTime}; use crossbeam_queue::ArrayQueue; use dashmap::{mapref::entry::Entry, DashMap}; @@ -8,13 +8,17 @@ use eyre::Result; use indexmap::IndexSet; use iroha_config::parameters::actual::Queue as Config; use iroha_crypto::HashOf; -use iroha_data_model::{account::AccountId, transaction::prelude::*}; +use iroha_data_model::{ + account::AccountId, + events::pipeline::{TransactionEvent, TransactionStatus}, + transaction::prelude::*, +}; use iroha_logger::{trace, warn}; use iroha_primitives::must_use::MustUse; use rand::seq::IteratorRandom; use thiserror::Error; -use crate::prelude::*; +use crate::{prelude::*, EventsSender}; impl AcceptedTransaction { // TODO: We should have another type of transaction like `CheckedTransaction` in the type system? @@ -48,6 +52,7 @@ impl AcceptedTransaction { /// Multiple producers, single consumer #[derive(Debug)] pub struct Queue { + events_sender: EventsSender, /// The queue for transactions tx_hashes: ArrayQueue>, /// [`AcceptedTransaction`]s addressed by `Hash` @@ -96,8 +101,9 @@ pub struct Failure { impl Queue { /// Makes queue from configuration - pub fn from_config(cfg: Config) -> Self { + pub fn from_config(cfg: Config, events_sender: EventsSender) -> Self { Self { + events_sender, tx_hashes: ArrayQueue::new(cfg.capacity.get()), accepted_txs: DashMap::new(), txs_per_user: DashMap::new(), @@ -121,13 +127,19 @@ impl Queue { |tx_time_to_live| core::cmp::min(self.tx_time_to_live, tx_time_to_live), ); - iroha_data_model::current_time().saturating_sub(tx_creation_time) > time_limit + let curr_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time"); + curr_time.saturating_sub(tx_creation_time) > time_limit } /// If `true`, this transaction is regarded to have been tampered to have a future timestamp. fn is_in_future(&self, tx: &AcceptedTransaction) -> bool { let tx_timestamp = tx.as_ref().creation_time(); - tx_timestamp.saturating_sub(iroha_data_model::current_time()) > self.future_threshold + let curr_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time"); + tx_timestamp.saturating_sub(curr_time) > self.future_threshold } /// Returns all pending transactions. @@ -226,6 +238,14 @@ impl Queue { err: Error::Full, } })?; + let _ = self.events_sender.send( + TransactionEvent { + hash, + block_height: None, + status: TransactionStatus::Queued, + } + .into(), + ); trace!("Transaction queue length = {}", self.tx_hashes.len(),); Ok(()) } @@ -281,12 +301,7 @@ impl Queue { max_txs_in_block: usize, ) -> Vec { let mut transactions = Vec::with_capacity(max_txs_in_block); - self.get_transactions_for_block( - state_view, - max_txs_in_block, - &mut transactions, - &mut Vec::new(), - ); + self.get_transactions_for_block(state_view, max_txs_in_block, &mut transactions); transactions } @@ -298,17 +313,16 @@ impl Queue { state_view: &StateView, max_txs_in_block: usize, transactions: &mut Vec, - expired_transactions: &mut Vec, ) { if transactions.len() >= max_txs_in_block { return; } let mut seen_queue = Vec::new(); - let mut expired_transactions_queue = Vec::new(); + let mut expired_transactions = Vec::new(); let txs_from_queue = core::iter::from_fn(|| { - self.pop_from_queue(&mut seen_queue, state_view, &mut expired_transactions_queue) + self.pop_from_queue(&mut seen_queue, state_view, &mut expired_transactions) }); let transactions_hashes: IndexSet> = @@ -322,7 +336,17 @@ impl Queue { .into_iter() .try_for_each(|hash| self.tx_hashes.push(hash)) .expect("Exceeded the number of transactions pending"); - expired_transactions.extend(expired_transactions_queue); + + expired_transactions + .into_iter() + .map(|tx| TransactionEvent { + hash: tx.as_ref().hash(), + block_height: None, + status: TransactionStatus::Expired, + }) + .for_each(|e| { + let _ = self.events_sender.send(e.into()); + }); } /// Check that the user adhered to the maximum transaction per user limit and increment their transaction count. @@ -381,6 +405,21 @@ pub mod tests { PeersIds, }; + impl Queue { + pub fn test(cfg: Config) -> Self { + Self { + events_sender: tokio::sync::broadcast::Sender::new(1), + tx_hashes: ArrayQueue::new(cfg.capacity.get()), + accepted_txs: DashMap::new(), + txs_per_user: DashMap::new(), + capacity: cfg.capacity, + capacity_per_user: cfg.capacity_per_user, + tx_time_to_live: cfg.transaction_time_to_live, + future_threshold: cfg.future_threshold, + } + } + } + fn accepted_tx(account_id: &str, key: &KeyPair) -> AcceptedTransaction { let chain_id = ChainId::from("0"); @@ -437,7 +476,7 @@ pub mod tests { )); let state_view = state.view(); - let queue = Queue::from_config(config_factory()); + let queue = Queue::test(config_factory()); queue .push(accepted_tx("alice@wonderland", &key_pair), &state_view) @@ -458,7 +497,7 @@ pub mod tests { )); let state_view = state.view(); - let queue = Queue::from_config(Config { + let queue = Queue::test(Config { transaction_time_to_live: Duration::from_secs(100), capacity, ..Config::default() @@ -504,7 +543,7 @@ pub mod tests { }; let state_view = state.view(); - let queue = Queue::from_config(config_factory()); + let queue = Queue::test(config_factory()); let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new(chain_id.clone(), "alice@wonderland".parse().expect("Valid")) @@ -560,7 +599,7 @@ pub mod tests { query_handle, )); let state_view = state.view(); - let queue = Queue::from_config(Config { + let queue = Queue::test(Config { transaction_time_to_live: Duration::from_secs(100), ..config_factory() }); @@ -590,7 +629,7 @@ pub mod tests { state_block.transactions.insert(tx.as_ref().hash(), 1); state_block.commit(); let state_view = state.view(); - let queue = Queue::from_config(config_factory()); + let queue = Queue::test(config_factory()); assert!(matches!( queue.push(tx, &state_view), Err(Failure { @@ -613,7 +652,7 @@ pub mod tests { query_handle, ); let tx = accepted_tx("alice@wonderland", &alice_key); - let queue = Queue::from_config(config_factory()); + let queue = Queue::test(config_factory()); queue.push(tx.clone(), &state.view()).unwrap(); let mut state_block = state.block(); state_block.transactions.insert(tx.as_ref().hash(), 1); @@ -639,7 +678,7 @@ pub mod tests { query_handle, )); let state_view = state.view(); - let queue = Queue::from_config(Config { + let queue = Queue::test(Config { transaction_time_to_live: Duration::from_millis(300), ..config_factory() }); @@ -687,7 +726,7 @@ pub mod tests { query_handle, )); let state_view = state.view(); - let queue = Queue::from_config(config_factory()); + let queue = Queue::test(config_factory()); queue .push(accepted_tx("alice@wonderland", &alice_key), &state_view) .expect("Failed to push tx into queue"); @@ -722,7 +761,9 @@ pub mod tests { query_handle, )); let state_view = state.view(); - let queue = Queue::from_config(config_factory()); + let mut queue = Queue::test(config_factory()); + let (event_sender, mut event_receiver) = tokio::sync::broadcast::channel(1); + queue.events_sender = event_sender; let instructions = [Fail { message: "expired".to_owned(), }]; @@ -737,18 +778,26 @@ pub mod tests { max_instruction_number: 4096, max_wasm_size_bytes: 0, }; + let tx_hash = tx.hash(); let tx = AcceptedTransaction::accept(tx, &chain_id, &limits) .expect("Failed to accept Transaction."); queue .push(tx.clone(), &state_view) .expect("Failed to push tx into queue"); let mut txs = Vec::new(); - let mut expired_txs = Vec::new(); thread::sleep(Duration::from_millis(TTL_MS)); - queue.get_transactions_for_block(&state_view, max_txs_in_block, &mut txs, &mut expired_txs); + queue.get_transactions_for_block(&state_view, max_txs_in_block, &mut txs); assert!(txs.is_empty()); - assert_eq!(expired_txs.len(), 1); - assert_eq!(expired_txs[0], tx); + + assert_eq!( + event_receiver.recv().await.unwrap(), + TransactionEvent { + hash: tx_hash, + block_height: None, + status: TransactionStatus::Expired, + } + .into() + ) } #[test] @@ -763,7 +812,7 @@ pub mod tests { query_handle, )); - let queue = Arc::new(Queue::from_config(Config { + let queue = Arc::new(Queue::test(Config { transaction_time_to_live: Duration::from_secs(100), capacity: 100_000_000.try_into().unwrap(), ..Config::default() @@ -837,7 +886,7 @@ pub mod tests { )); let state_view = state.view(); - let queue = Queue::from_config(Config { + let queue = Queue::test(Config { future_threshold, ..Config::default() }); @@ -898,7 +947,7 @@ pub mod tests { let query_handle = LiveQueryStore::test().start(); let state = State::new(world, kura, query_handle); - let queue = Queue::from_config(Config { + let queue = Queue::test(Config { transaction_time_to_live: Duration::from_secs(100), capacity: 100.try_into().unwrap(), capacity_per_user: 1.try_into().unwrap(), diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 1b8f8715ad8..e74c18ee217 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -316,7 +316,9 @@ mod tests { let first_block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new()) .chain(0, &mut state_block) .sign(&ALICE_KEYS) + .unpack(|_| {}) .commit(&topology) + .unpack(|_| {}) .expect("Block is valid"); state_block.apply(&first_block)?; @@ -326,7 +328,9 @@ mod tests { let block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new()) .chain(0, &mut state_block) .sign(&ALICE_KEYS) + .unpack(|_| {}) .commit(&topology) + .unpack(|_| {}) .expect("Block is valid"); state_block.apply(&block)?; @@ -466,7 +470,9 @@ mod tests { let vcb = BlockBuilder::new(vec![va_tx.clone()], topology.clone(), Vec::new()) .chain(0, &mut state_block) .sign(&ALICE_KEYS) + .unpack(|_| {}) .commit(&topology) + .unpack(|_| {}) .expect("Block is valid"); state_block.apply(&vcb)?; diff --git a/core/src/smartcontracts/isi/triggers/set.rs b/core/src/smartcontracts/isi/triggers/set.rs index d7bfca0b769..63d7732e92b 100644 --- a/core/src/smartcontracts/isi/triggers/set.rs +++ b/core/src/smartcontracts/isi/triggers/set.rs @@ -58,8 +58,8 @@ type WasmSmartContractMap = IndexMap, (WasmSmartContra pub struct Set { /// Triggers using [`DataEventFilter`] data_triggers: IndexMap>, - /// Triggers using [`PipelineEventFilter`] - pipeline_triggers: IndexMap>, + /// Triggers using [`PipelineEventFilterBox`] + pipeline_triggers: IndexMap>, /// Triggers using [`TimeEventFilter`] time_triggers: IndexMap>, /// Triggers using [`ExecuteTriggerEventFilter`] @@ -70,7 +70,7 @@ pub struct Set { original_contracts: WasmSmartContractMap, /// List of actions that should be triggered by events provided by `handle_*` methods. /// Vector is used to save the exact triggers order. - matched_ids: Vec<(Event, TriggerId)>, + matched_ids: Vec<(EventBox, TriggerId)>, } /// Helper struct for serializing triggers. @@ -177,7 +177,7 @@ impl<'de> DeserializeSeed<'de> for WasmSeed<'_, Set> { "pipeline_triggers" => { let triggers: IndexMap< TriggerId, - SpecializedAction, + SpecializedAction, > = map.next_value()?; for (id, action) in triggers { set.add_pipeline_trigger( @@ -259,7 +259,7 @@ impl Set { }) } - /// Add trigger with [`PipelineEventFilter`] + /// Add trigger with [`PipelineEventFilterBox`] /// /// Return `false` if a trigger with given id already exists /// @@ -270,7 +270,7 @@ impl Set { pub fn add_pipeline_trigger( &mut self, engine: &wasmtime::Engine, - trigger: SpecializedTrigger, + trigger: SpecializedTrigger, ) -> Result { self.add_to(engine, trigger, TriggeringEventType::Pipeline, |me| { &mut me.pipeline_triggers @@ -721,18 +721,6 @@ impl Set { }; } - /// Handle [`PipelineEvent`]. - /// - /// Find all actions that are triggered by `event` and store them. - /// These actions are inspected in the next [`Set::inspect_matched()`] call. - // Passing by value to follow other `handle_` methods interface - #[allow(clippy::needless_pass_by_value)] - pub fn handle_pipeline_event(&mut self, event: PipelineEvent) { - self.pipeline_triggers.iter().for_each(|entry| { - Self::match_and_insert_trigger(&mut self.matched_ids, event.clone(), entry) - }); - } - /// Handle [`TimeEvent`]. /// /// Find all actions that are triggered by `event` and store them. @@ -747,7 +735,7 @@ impl Set { continue; } - let ids = core::iter::repeat_with(|| (Event::Time(event), id.clone())).take( + let ids = core::iter::repeat_with(|| (EventBox::Time(event), id.clone())).take( count .try_into() .expect("`u32` should always fit in `usize`"), @@ -761,8 +749,8 @@ impl Set { /// Skips insertion: /// - If the action's filter doesn't match an event /// - If the action's repeats count equals to 0 - fn match_and_insert_trigger, F: EventFilter>( - matched_ids: &mut Vec<(Event, TriggerId)>, + fn match_and_insert_trigger, F: EventFilter>( + matched_ids: &mut Vec<(EventBox, TriggerId)>, event: E, (id, action): (&TriggerId, &LoadedAction), ) { @@ -825,7 +813,7 @@ impl Set { } /// Extract `matched_id` - pub fn extract_matched_ids(&mut self) -> Vec<(Event, TriggerId)> { + pub fn extract_matched_ids(&mut self) -> Vec<(EventBox, TriggerId)> { core::mem::take(&mut self.matched_ids) } } diff --git a/core/src/smartcontracts/isi/triggers/specialized.rs b/core/src/smartcontracts/isi/triggers/specialized.rs index 09e898b126d..24aa7b34500 100644 --- a/core/src/smartcontracts/isi/triggers/specialized.rs +++ b/core/src/smartcontracts/isi/triggers/specialized.rs @@ -103,7 +103,7 @@ macro_rules! impl_try_from_box { impl_try_from_box! { Data => DataEventFilter, - Pipeline => PipelineEventFilter, + Pipeline => PipelineEventFilterBox, Time => TimeEventFilter, ExecuteTrigger => ExecuteTriggerEventFilter, } @@ -228,7 +228,7 @@ mod tests { .unwrap() } TriggeringEventFilterBox::Pipeline(_) => { - SpecializedTrigger::::try_from(boxed) + SpecializedTrigger::::try_from(boxed) .map(|_| ()) .unwrap() } diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index dd8df4bd163..25f27e25675 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -465,7 +465,7 @@ pub mod state { #[derive(Constructor)] pub struct Trigger { /// Event which activated this trigger - pub(in super::super) triggering_event: Event, + pub(in super::super) triggering_event: EventBox, } pub mod executor { @@ -977,7 +977,7 @@ impl<'wrld, 'block: 'wrld, 'state: 'block> Runtime Result<()> { let span = wasm_log_span!("Trigger execution", %id, %authority); let state = state::Trigger::new( diff --git a/core/src/state.rs b/core/src/state.rs index b9291530cbf..ece289c0524 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -7,7 +7,12 @@ use iroha_crypto::HashOf; use iroha_data_model::{ account::AccountId, block::SignedBlock, - events::trigger_completed::{TriggerCompletedEvent, TriggerCompletedOutcome}, + events::{ + pipeline::BlockEvent, + time::TimeEvent, + trigger_completed::{TriggerCompletedEvent, TriggerCompletedOutcome}, + EventBox, + }, isi::error::{InstructionExecutionError as Error, MathError}, parameter::{Parameter, ParameterValueBox}, permission::{PermissionTokenSchema, Permissions}, @@ -95,7 +100,7 @@ pub struct WorldBlock<'world> { /// Runtime Executor pub(crate) executor: CellBlock<'world, Executor>, /// Events produced during execution of block - pub(crate) events_buffer: Vec, + events_buffer: Vec, } /// Struct for single transaction's aggregated changes @@ -126,7 +131,7 @@ pub struct WorldTransaction<'block, 'world> { /// Wrapper for event's buffer to apply transaction rollback struct TransactionEventBuffer<'block> { /// Events produced during execution of block - events_buffer: &'block mut Vec, + events_buffer: &'block mut Vec, /// Number of events produced during execution current transaction events_created_in_transaction: usize, } @@ -285,7 +290,7 @@ impl World { } } - /// Create struct to apply block's changes while reverting changes made in the latest block + /// Create struct to apply block's changes while reverting changes made in the latest block pub fn block_and_revert(&self) -> WorldBlock { WorldBlock { parameters: self.parameters.block_and_revert(), @@ -895,14 +900,14 @@ impl WorldTransaction<'_, '_> { } impl TransactionEventBuffer<'_> { - fn push(&mut self, event: Event) { + fn push(&mut self, event: EventBox) { self.events_created_in_transaction += 1; self.events_buffer.push(event); } } -impl Extend for TransactionEventBuffer<'_> { - fn extend>(&mut self, iter: T) { +impl Extend for TransactionEventBuffer<'_> { + fn extend>(&mut self, iter: T) { let len_before = self.events_buffer.len(); self.events_buffer.extend(iter); let len_after = self.events_buffer.len(); @@ -1024,7 +1029,7 @@ pub trait StateReadOnly { } /// Return the hash of the block one before the latest block - fn previous_block_hash(&self) -> Option> { + fn prev_block_hash(&self) -> Option> { self.block_hashes().iter().nth_back(1).copied() } @@ -1087,7 +1092,7 @@ pub trait StateReadOnly { let opt = self .kura() .get_block_by_height(1) - .map(|genesis_block| genesis_block.header().timestamp()); + .map(|genesis_block| genesis_block.header().creation_time()); if opt.is_none() { error!("Failed to get genesis block from Kura."); @@ -1183,13 +1188,10 @@ impl<'state> StateBlock<'state> { deprecated(note = "This function is to be used in testing only. ") )] #[iroha_logger::log(skip_all, fields(block_height))] - pub fn apply(&mut self, block: &CommittedBlock) -> Result<()> { + pub fn apply(&mut self, block: &CommittedBlock) -> Result> { self.execute_transactions(block)?; debug!("All block transactions successfully executed"); - - self.apply_without_execution(block)?; - - Ok(()) + Ok(self.apply_without_execution(block)) } /// Execute `block` transactions and store their hashes as well as @@ -1217,12 +1219,12 @@ impl<'state> StateBlock<'state> { /// Apply transactions without actually executing them. /// It's assumed that block's transaction was already executed (as part of validation for example). #[iroha_logger::log(skip_all, fields(block_height = block.as_ref().header().height))] - pub fn apply_without_execution(&mut self, block: &CommittedBlock) -> Result<()> { + pub fn apply_without_execution(&mut self, block: &CommittedBlock) -> Vec { let block_hash = block.as_ref().hash(); trace!(%block_hash, "Applying block"); let time_event = self.create_time_event(block); - self.world.events_buffer.push(Event::Time(time_event)); + self.world.events_buffer.push(time_event.into()); let block_height = block.as_ref().header().height; block @@ -1248,24 +1250,43 @@ impl<'state> StateBlock<'state> { self.block_hashes.push(block_hash); self.apply_parameters(); - - Ok(()) + self.world.events_buffer.push( + BlockEvent { + header: block.as_ref().header().clone(), + status: BlockStatus::Applied, + } + .into(), + ); + core::mem::take(&mut self.world.events_buffer) } /// Create time event using previous and current blocks fn create_time_event(&self, block: &CommittedBlock) -> TimeEvent { + use iroha_config::parameters::defaults::chain_wide::{ + DEFAULT_BLOCK_TIME, DEFAULT_COMMIT_TIME, + }; + + const DEFAULT_CONSENSUS_ESTIMATION: Duration = + match DEFAULT_BLOCK_TIME.checked_add(match DEFAULT_COMMIT_TIME.checked_div(2) { + Some(x) => x, + None => unreachable!(), + }) { + Some(x) => x, + None => unreachable!(), + }; + let prev_interval = self.latest_block_ref().map(|latest_block| { let header = &latest_block.as_ref().header(); TimeInterval { - since: header.timestamp(), - length: header.consensus_estimation(), + since: header.creation_time(), + length: DEFAULT_CONSENSUS_ESTIMATION, } }); let interval = TimeInterval { - since: block.as_ref().header().timestamp(), - length: block.as_ref().header().consensus_estimation(), + since: block.as_ref().header().creation_time(), + length: DEFAULT_CONSENSUS_ESTIMATION, }; TimeEvent { @@ -1388,7 +1409,7 @@ impl StateTransaction<'_, '_> { &mut self, id: &TriggerId, action: &dyn LoadedActionTrait, - event: Event, + event: EventBox, ) -> Result<()> { use triggers::set::LoadedExecutable::*; let authority = action.authority(); @@ -1751,7 +1772,7 @@ mod tests { /// Used to inject faulty payload for testing fn payload_mut(block: &mut CommittedBlock) -> &mut BlockPayload { - let SignedBlock::V1(signed) = &mut block.0 .0; + let SignedBlock::V1(signed) = block.as_mut(); &mut signed.payload } @@ -1760,7 +1781,10 @@ mod tests { const BLOCK_CNT: usize = 10; let topology = Topology::new(UniqueVec::new()); - let block = ValidBlock::new_dummy().commit(&topology).unwrap(); + let block = ValidBlock::new_dummy() + .commit(&topology) + .unpack(|_| {}) + .unwrap(); let kura = Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(World::default(), kura, query_handle); @@ -1771,7 +1795,7 @@ mod tests { let mut block = block.clone(); payload_mut(&mut block).header.height = i as u64; - payload_mut(&mut block).header.previous_block_hash = block_hashes.last().copied(); + payload_mut(&mut block).header.prev_block_hash = block_hashes.last().copied(); block_hashes.push(block.as_ref().hash()); state_block.apply(&block).unwrap(); @@ -1788,7 +1812,10 @@ mod tests { const BLOCK_CNT: usize = 10; let topology = Topology::new(UniqueVec::new()); - let block = ValidBlock::new_dummy().commit(&topology).unwrap(); + let block = ValidBlock::new_dummy() + .commit(&topology) + .unpack(|_| {}) + .unwrap(); let kura = Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(World::default(), kura.clone(), query_handle); @@ -1806,7 +1833,7 @@ mod tests { &state_block .all_blocks() .skip(7) - .map(|block| *block.header().height()) + .map(|block| block.header().height()) .collect::>(), &[8, 9, 10] ); diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 13bb94bb01a..8e8fcde1b3c 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -2,10 +2,7 @@ use std::sync::mpsc; use iroha_crypto::HashOf; -use iroha_data_model::{ - block::*, events::pipeline::PipelineEvent, peer::PeerId, - transaction::error::TransactionRejectionReason, -}; +use iroha_data_model::{block::*, events::pipeline::PipelineEventBox, peer::PeerId}; use iroha_p2p::UpdateTopology; use tracing::{span, Level}; @@ -82,17 +79,19 @@ impl Sumeragi { #[allow(clippy::needless_pass_by_value, single_use_lifetimes)] // TODO: uncomment when anonymous lifetimes are stable fn broadcast_packet_to<'peer_id>( &self, - msg: BlockMessage, + msg: impl Into, ids: impl IntoIterator + Send, ) { + let msg = msg.into(); + for peer_id in ids { self.post_packet_to(msg.clone(), peer_id); } } - fn broadcast_packet(&self, msg: BlockMessage) { + fn broadcast_packet(&self, msg: impl Into) { let broadcast = iroha_p2p::Broadcast { - data: NetworkMessage::SumeragiBlock(Box::new(msg)), + data: NetworkMessage::SumeragiBlock(Box::new(msg.into())), }; self.network.broadcast(broadcast); } @@ -116,17 +115,8 @@ impl Sumeragi { self.block_time + self.commit_time } - fn send_events(&self, events: impl IntoIterator>) { - let addr = &self.peer_id.address; - - if self.events_sender.receiver_count() > 0 { - for event in events { - self.events_sender - .send(event.into()) - .map_err(|err| warn!(%addr, ?err, "Event not sent")) - .unwrap_or(0); - } - } + fn send_event(&self, event: impl Into) { + let _ = self.events_sender.send(event.into()); } fn receive_network_packet( @@ -239,13 +229,15 @@ impl Sumeragi { &self.chain_id, &mut state_block, ) + .unpack(|e| self.send_event(e)) .and_then(|block| { block .commit(&self.current_topology) + .unpack(|e| self.send_event(e)) .map_err(|(block, error)| (block.into(), error)) }) { Ok(block) => block, - Err((_, error)) => { + Err(error) => { error!(?error, "Received invalid genesis block"); continue; } @@ -280,12 +272,14 @@ impl Sumeragi { let mut state_block = state.block(); let genesis = BlockBuilder::new(transactions, self.current_topology.clone(), vec![]) .chain(0, &mut state_block) - .sign(&self.key_pair); + .sign(&self.key_pair) + .unpack(|e| self.send_event(e)); - let genesis_msg = BlockCreated::from(genesis.clone()).into(); + let genesis_msg = BlockCreated::from(genesis.clone()); let genesis = genesis .commit(&self.current_topology) + .unpack(|e| self.send_event(e)) .expect("Genesis invalid"); assert!( @@ -324,19 +318,13 @@ impl Sumeragi { "{}", Strategy::LOG_MESSAGE, ); - state_block - .apply_without_execution(&block) - .expect("Failed to apply block on state. Bailing."); - - let state_events = core::mem::take(&mut state_block.world.events_buffer); - self.send_events(state_events); + let state_events = state_block.apply_without_execution(&block); let new_topology = Topology::recreate_topology( block.as_ref(), 0, state_block.world.peers().cloned().collect(), ); - let events = block.produce_events(); // https://github.com/hyperledger/iroha/issues/3396 // Kura should store the block only upon successful application to the internal state to avoid storing a corrupted block. @@ -346,6 +334,7 @@ impl Sumeragi { // Parameters are updated before updating public copy of sumeragi self.update_params(&state_block); self.cache_transaction(&state_block); + self.current_topology = new_topology; self.connect_peers(&self.current_topology); @@ -353,7 +342,7 @@ impl Sumeragi { state_block.commit(); // NOTE: This sends "Block committed" event, // so it should be done AFTER public facing state update - self.send_events(events); + state_events.into_iter().for_each(|e| self.send_event(e)); } fn update_params(&mut self, state_block: &StateBlock<'_>) { @@ -391,9 +380,11 @@ impl Sumeragi { trace!(%addr, %role, block_hash=%block_hash, "Block received, voting..."); let mut state_block = state.block(); - let block = match ValidBlock::validate(block, topology, &self.chain_id, &mut state_block) { + let block = match ValidBlock::validate(block, topology, &self.chain_id, &mut state_block) + .unpack(|e| self.send_event(e)) + { Ok(block) => block, - Err((_, error)) => { + Err(error) => { warn!(%addr, %role, ?error, "Block validation failed"); return None; } @@ -438,9 +429,9 @@ impl Sumeragi { // Release writer before handling block sync let _ = voting_block.take(); - match handle_block_sync(&self.chain_id, block, state) { + match handle_block_sync(&self.chain_id, block, state, &|e| self.send_event(e)) { Ok(BlockSyncOk::CommitBlock(block, state_block)) => { - self.commit_block(block, state_block) + self.commit_block(block, state_block); } Ok(BlockSyncOk::ReplaceTopBlock(block, state_block)) => { warn!( @@ -502,6 +493,7 @@ impl Sumeragi { match voted_block .block .commit_with_signatures(current_topology, signatures) + .unpack(|e| self.send_event(e)) { Ok(committed_block) => { self.commit_block(committed_block, voted_block.state_block) @@ -509,7 +501,7 @@ impl Sumeragi { Err((_, error)) => { error!(%addr, %role, %hash, ?error, "Block failed to be committed") } - }; + } } else { error!( %addr, %role, committed_block_hash=%hash, %voting_block_hash, @@ -533,8 +525,7 @@ impl Sumeragi { { let block_hash = v_block.block.as_ref().hash_of_payload(); - let msg = BlockSigned::from(v_block.block.clone()).into(); - + let msg = BlockSigned::from(v_block.block.clone()); self.broadcast_packet_to(msg, [current_topology.proxy_tail()]); info!(%addr, %block_hash, "Block validated, signed and forwarded"); @@ -554,7 +545,7 @@ impl Sumeragi { let block_hash = v_block.block.as_ref().hash(); self.broadcast_packet_to( - BlockSigned::from(v_block.block.clone()).into(), + BlockSigned::from(v_block.block.clone()), [current_topology.proxy_tail()], ); info!(%addr, %block_hash, "Block validated, signed and forwarded"); @@ -641,7 +632,8 @@ impl Sumeragi { event_recommendations, ) .chain(current_view_change_index, &mut state_block) - .sign(&self.key_pair); + .sign(&self.key_pair) + .unpack(|e| self.send_event(e)); let created_in = create_block_start_time.elapsed(); if let Some(current_topology) = current_topology.is_consensus_required() { @@ -652,22 +644,23 @@ impl Sumeragi { } *voting_block = Some(VotingBlock::new(new_block.clone(), state_block)); - let msg = BlockCreated::from(new_block).into(); + let msg = BlockCreated::from(new_block); if current_view_change_index >= 1 { self.broadcast_packet(msg); } else { self.broadcast_packet_to(msg, current_topology.voting_peers()); } } else { - match new_block.commit(current_topology) { + match new_block + .commit(current_topology) + .unpack(|e| self.send_event(e)) + { Ok(committed_block) => { - self.broadcast_packet( - BlockCommitted::from(committed_block.clone()).into(), - ); + self.broadcast_packet(BlockCommitted::from(&committed_block)); self.commit_block(committed_block, state_block); } - Err((_, error)) => error!(%addr, role=%Role::Leader, ?error), - } + Err(error) => error!(%addr, role=%Role::Leader, ?error), + }; } } } @@ -677,12 +670,15 @@ impl Sumeragi { let voted_at = voted_block.voted_at; let state_block = voted_block.state_block; - match voted_block.block.commit(current_topology) { + match voted_block + .block + .commit(current_topology) + .unpack(|e| self.send_event(e)) + { Ok(committed_block) => { info!(voting_block_hash = %committed_block.as_ref().hash(), "Block reached required number of votes"); - let msg = BlockCommitted::from(committed_block.clone()).into(); - + let msg = BlockCommitted::from(&committed_block); let current_topology = current_topology .is_consensus_required() .expect("Peer has `ProxyTail` role, which mean that current topology require consensus"); @@ -863,14 +859,11 @@ pub(crate) fn run( expired }); - let mut expired_transactions = Vec::new(); sumeragi.queue.get_transactions_for_block( &state_view, sumeragi.max_txs_in_block, &mut sumeragi.transaction_cache, - &mut expired_transactions, ); - sumeragi.send_events(expired_transactions.iter().map(expired_event)); let current_view_change_index = sumeragi .prune_view_change_proofs_and_calculate_current_index( @@ -1001,18 +994,6 @@ fn add_signatures( } } -/// Create expired pipeline event for the given transaction. -fn expired_event(txn: &AcceptedTransaction) -> Event { - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Rejected(PipelineRejectionReason::Transaction( - TransactionRejectionReason::Expired, - )), - hash: txn.as_ref().hash().into(), - } - .into() -} - /// Type enumerating early return types to reduce cyclomatic /// complexity of the main loop items and allow direct short /// circuiting with the `?` operator. Candidate for `impl @@ -1092,10 +1073,11 @@ enum BlockSyncError { }, } -fn handle_block_sync<'state>( +fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( chain_id: &ChainId, block: SignedBlock, state: &'state State, + handle_events: &F, ) -> Result, (SignedBlock, BlockSyncError)> { let block_height = block.header().height; let state_height = state.view().height(); @@ -1111,9 +1093,11 @@ fn handle_block_sync<'state>( Topology::recreate_topology(&last_committed_block, view_change_index, new_peers) }; ValidBlock::validate(block, &topology, chain_id, &mut state_block) + .unpack(handle_events) .and_then(|block| { block .commit(&topology) + .unpack(handle_events) .map_err(|(block, err)| (block.into(), err)) }) .map(|block| BlockSyncOk::CommitBlock(block, state_block)) @@ -1144,9 +1128,11 @@ fn handle_block_sync<'state>( Topology::recreate_topology(&last_committed_block, view_change_index, new_peers) }; ValidBlock::validate(block, &topology, chain_id, &mut state_block) + .unpack(handle_events) .and_then(|block| { block .commit(&topology) + .unpack(handle_events) .map_err(|(block, err)| (block.into(), err)) }) .map_err(|(block, error)| (block, BlockSyncError::SoftForkBlockNotValid(error))) @@ -1214,9 +1200,13 @@ mod tests { // Creating a block of two identical transactions and validating it let block = BlockBuilder::new(vec![tx.clone(), tx], topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(leader_key_pair); + .sign(leader_key_pair) + .unpack(|_| {}); - let genesis = block.commit(topology).expect("Block is valid"); + let genesis = block + .commit(topology) + .unpack(|_| {}) + .expect("Block is valid"); state_block.apply(&genesis).expect("Failed to apply block"); state_block.commit(); kura.store_block(genesis); @@ -1256,6 +1246,7 @@ mod tests { BlockBuilder::new(vec![tx1, tx2], topology.clone(), Vec::new()) .chain(0, &mut state_block) .sign(leader_key_pair) + .unpack(|_| {}) }; (state, kura, block.into()) @@ -1276,7 +1267,7 @@ mod tests { // Malform block to make it invalid payload_mut(&mut block).commit_topology.clear(); - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!(result, Err((_, BlockSyncError::BlockNotValid(_))))) } @@ -1292,12 +1283,14 @@ mod tests { let (state, kura, mut block) = create_data_for_test(&chain_id, &topology, &leader_key_pair); let mut state_block = state.block(); - let validated_block = - ValidBlock::validate(block.clone(), &topology, &chain_id, &mut state_block).unwrap(); - let committed_block = validated_block.commit(&topology).expect("Block is valid"); - state_block - .apply_without_execution(&committed_block) - .expect("Failed to apply block"); + let committed_block = + ValidBlock::validate(block.clone(), &topology, &chain_id, &mut state_block) + .unpack(|_| {}) + .unwrap() + .commit(&topology) + .unpack(|_| {}) + .expect("Block is valid"); + let _wsv_events = state_block.apply_without_execution(&committed_block); state_block.commit(); kura.store_block(committed_block); @@ -1305,7 +1298,7 @@ mod tests { payload_mut(&mut block).commit_topology.clear(); payload_mut(&mut block).header.view_change_index = 1; - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!( result, Err((_, BlockSyncError::SoftForkBlockNotValid(_))) @@ -1324,7 +1317,7 @@ mod tests { // Change block height payload_mut(&mut block).header.height = 42; - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!( result, Err(( @@ -1348,7 +1341,7 @@ mod tests { leader_key_pair.public_key().clone(), )]); let (state, _, block) = create_data_for_test(&chain_id, &topology, &leader_key_pair); - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!(result, Ok(BlockSyncOk::CommitBlock(_, _)))) } @@ -1364,12 +1357,14 @@ mod tests { let (state, kura, mut block) = create_data_for_test(&chain_id, &topology, &leader_key_pair); let mut state_block = state.block(); - let validated_block = - ValidBlock::validate(block.clone(), &topology, &chain_id, &mut state_block).unwrap(); - let committed_block = validated_block.commit(&topology).expect("Block is valid"); - state_block - .apply_without_execution(&committed_block) - .expect("Failed to apply block"); + let committed_block = + ValidBlock::validate(block.clone(), &topology, &chain_id, &mut state_block) + .unpack(|_| {}) + .unwrap() + .commit(&topology) + .unpack(|_| {}) + .expect("Block is valid"); + let _wsv_events = state_block.apply_without_execution(&committed_block); state_block.commit(); kura.store_block(committed_block); @@ -1378,7 +1373,7 @@ mod tests { // Increase block view change index payload_mut(&mut block).header.view_change_index = 42; - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!(result, Ok(BlockSyncOk::ReplaceTopBlock(_, _)))) } @@ -1397,12 +1392,14 @@ mod tests { payload_mut(&mut block).header.view_change_index = 42; let mut state_block = state.block(); - let validated_block = - ValidBlock::validate(block.clone(), &topology, &chain_id, &mut state_block).unwrap(); - let committed_block = validated_block.commit(&topology).expect("Block is valid"); - state_block - .apply_without_execution(&committed_block) - .expect("Failed to apply block"); + let committed_block = + ValidBlock::validate(block.clone(), &topology, &chain_id, &mut state_block) + .unpack(|_| {}) + .unwrap() + .commit(&topology) + .unpack(|_| {}) + .expect("Block is valid"); + let _wsv_events = state_block.apply_without_execution(&committed_block); state_block.commit(); kura.store_block(committed_block); assert_eq!(state.view().latest_block_view_change_index(), 42); @@ -1410,7 +1407,7 @@ mod tests { // Decrease block view change index back payload_mut(&mut block).header.view_change_index = 0; - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!( result, Err(( @@ -1437,7 +1434,7 @@ mod tests { payload_mut(&mut block).header.view_change_index = 42; payload_mut(&mut block).header.height = 1; - let result = handle_block_sync(&chain_id, block, &state); + let result = handle_block_sync(&chain_id, block, &state, &|_| {}); assert!(matches!( result, Err(( diff --git a/core/src/sumeragi/message.rs b/core/src/sumeragi/message.rs index b0a80207072..95caaf5c0f5 100644 --- a/core/src/sumeragi/message.rs +++ b/core/src/sumeragi/message.rs @@ -84,8 +84,8 @@ pub struct BlockCommitted { pub signatures: SignaturesOf, } -impl From for BlockCommitted { - fn from(block: CommittedBlock) -> Self { +impl From<&CommittedBlock> for BlockCommitted { + fn from(block: &CommittedBlock) -> Self { let block_hash = block.as_ref().hash_of_payload(); let block_signatures = block.as_ref().signatures().clone(); diff --git a/core/src/sumeragi/mod.rs b/core/src/sumeragi/mod.rs index 1e10895b992..f59a7ee6259 100644 --- a/core/src/sumeragi/mod.rs +++ b/core/src/sumeragi/mod.rs @@ -4,7 +4,7 @@ use std::{ fmt::{self, Debug, Formatter}, sync::{mpsc, Arc}, - time::{Duration, Instant}, + time::{Duration, Instant, SystemTime}, }; use eyre::{Result, WrapErr as _}; @@ -129,9 +129,13 @@ impl SumeragiHandle { #[allow(clippy::cast_possible_truncation)] if let Some(timestamp) = state_view.genesis_timestamp() { + let curr_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time"); + // this will overflow in 584942417years. self.metrics.uptime_since_genesis_ms.set( - (current_time() - timestamp) + (curr_time - timestamp) .as_millis() .try_into() .expect("Timestamp should fit into u64"), @@ -193,24 +197,33 @@ impl SumeragiHandle { chain_id: &ChainId, block: &SignedBlock, state_block: &mut StateBlock<'_>, + events_sender: &EventsSender, mut current_topology: Topology, ) -> Topology { // NOTE: topology need to be updated up to block's view_change_index current_topology.rotate_all_n(block.header().view_change_index); let block = ValidBlock::validate(block.clone(), ¤t_topology, chain_id, state_block) - .expect("Kura blocks should be valid") + .unpack(|e| { + let _ = events_sender.send(e.into()); + }) + .expect("Kura: Invalid block") .commit(¤t_topology) - .expect("Kura blocks should be valid"); + .unpack(|e| { + let _ = events_sender.send(e.into()); + }) + .expect("Kura: Invalid block"); if block.as_ref().header().is_genesis() { *state_block.world.trusted_peers_ids = block.as_ref().commit_topology().clone(); } - state_block.apply_without_execution(&block).expect( - "Block application in init should not fail. \ - Blocks loaded from kura assumed to be valid", - ); + state_block + .apply_without_execution(&block) + .into_iter() + .for_each(|e| { + let _ = events_sender.send(e); + }); Topology::recreate_topology( block.as_ref(), @@ -278,6 +291,7 @@ impl SumeragiHandle { &common_config.chain_id, &block, &mut state_block, + &events_sender, current_topology, ); state_block.commit(); @@ -356,16 +370,21 @@ pub const PEERS_CONNECT_INTERVAL: Duration = Duration::from_secs(1); pub const TELEMETRY_INTERVAL: Duration = Duration::from_secs(5); /// Structure represents a block that is currently in discussion. -#[non_exhaustive] pub struct VotingBlock<'state> { + /// Valid Block + block: ValidBlock, /// At what time has this peer voted for this block pub voted_at: Instant, - /// Valid Block - pub block: ValidBlock, /// [`WorldState`] after applying transactions to it but before it was committed pub state_block: StateBlock<'state>, } +impl AsRef for VotingBlock<'_> { + fn as_ref(&self) -> &ValidBlock { + &self.block + } +} + impl VotingBlock<'_> { /// Construct new `VotingBlock` with current time. pub fn new(block: ValidBlock, state_block: StateBlock<'_>) -> VotingBlock { @@ -382,8 +401,8 @@ impl VotingBlock<'_> { voted_at: Instant, ) -> VotingBlock { VotingBlock { - voted_at, block, + voted_at, state_block, } } diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 012f475eda0..b7dc0e49552 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -14,7 +14,7 @@ use iroha_client::{ }; use iroha_config::parameters::actual::Root as Config; pub use iroha_core::state::StateReadOnly; -use iroha_crypto::prelude::*; +use iroha_crypto::KeyPair; use iroha_data_model::{query::QueryOutputBox, ChainId}; use iroha_genesis::{GenesisNetwork, RawGenesisBlockFile}; use iroha_logger::InstrumentFutures; @@ -54,11 +54,11 @@ pub fn get_chain_id() -> ChainId { /// Get a standardised key-pair from the hard-coded literals. pub fn get_key_pair() -> KeyPair { KeyPair::new( - PublicKey::from_str( + iroha_crypto::PublicKey::from_str( "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0", ).unwrap(), - PrivateKey::from_hex( - Algorithm::Ed25519, + iroha_crypto::PrivateKey::from_hex( + iroha_crypto::Algorithm::Ed25519, "9AC47ABF59B356E0BD7DCBBBB4DEC080E302156A48CA907E47CB6AEA1D32719E7233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0" ).unwrap() ).unwrap() @@ -689,7 +689,7 @@ pub trait TestClient: Sized { fn test_with_account(api_url: &SocketAddr, keys: KeyPair, account_id: &AccountId) -> Self; /// Loop for events with filter and handler function - fn for_each_event(self, event_filter: impl Into, f: impl Fn(Result)); + fn for_each_event(self, event_filter: impl Into, f: impl Fn(Result)); /// Submit instruction with polling /// @@ -828,7 +828,7 @@ impl TestClient for Client { Client::new(config) } - fn for_each_event(self, event_filter: impl Into, f: impl Fn(Result)) { + fn for_each_event(self, event_filter: impl Into, f: impl Fn(Result)) { for event_result in self .listen_for_events(event_filter) .expect("Failed to create event iterator.") diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index f1662780479..aafd7868459 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -27,8 +27,6 @@ use alloc::{ }; use core::{borrow::Borrow, fmt, str::FromStr}; -#[cfg(feature = "base64")] -pub use base64; #[cfg(not(feature = "ffi_import"))] pub use blake2; use derive_more::Display; @@ -857,11 +855,6 @@ mod ffi { pub(crate) use ffi_item; } -/// The prelude re-exports most commonly used items from this crate. -pub mod prelude { - pub use super::{Algorithm, Hash, KeyPair, PrivateKey, PublicKey, Signature}; -} - #[cfg(test)] mod tests { use parity_scale_codec::{Decode, Encode}; diff --git a/data_model/derive/src/model.rs b/data_model/derive/src/model.rs index a5fdb7a7510..0547fc99ab7 100644 --- a/data_model/derive/src/model.rs +++ b/data_model/derive/src/model.rs @@ -7,7 +7,6 @@ use syn::{parse_quote, Attribute}; pub fn impl_model(emitter: &mut Emitter, input: &syn::ItemMod) -> TokenStream { let syn::ItemMod { attrs, - vis, mod_token, ident, content, @@ -15,14 +14,6 @@ pub fn impl_model(emitter: &mut Emitter, input: &syn::ItemMod) -> TokenStream { .. } = input; - let syn::Visibility::Public(vis_public) = vis else { - emit!( - emitter, - input, - "The `model` attribute can only be used on public modules" - ); - return quote!(); - }; if ident != "model" { emit!( emitter, @@ -38,7 +29,7 @@ pub fn impl_model(emitter: &mut Emitter, input: &syn::ItemMod) -> TokenStream { quote! { #(#attrs)* #[allow(missing_docs)] - #vis_public #mod_token #ident { + #mod_token #ident { #(#items_code)* }#semi } diff --git a/data_model/src/account.rs b/data_model/src/account.rs index 2383bdc21ac..ce3f9582770 100644 --- a/data_model/src/account.rs +++ b/data_model/src/account.rs @@ -431,6 +431,8 @@ pub mod prelude { #[cfg(test)] mod tests { + #[cfg(not(feature = "std"))] + use alloc::{vec, vec::Vec}; use core::cmp::Ordering; use iroha_crypto::{KeyPair, PublicKey}; diff --git a/data_model/src/block.rs b/data_model/src/block.rs index 93ce5bec045..a536cffb6a0 100644 --- a/data_model/src/block.rs +++ b/data_model/src/block.rs @@ -9,7 +9,6 @@ use alloc::{boxed::Box, format, string::String, vec::Vec}; use core::{fmt::Display, time::Duration}; use derive_more::Display; -use getset::Getters; #[cfg(all(feature = "std", feature = "transparent_api"))] use iroha_crypto::KeyPair; use iroha_crypto::{HashOf, MerkleTree, SignaturesOf}; @@ -26,6 +25,8 @@ use crate::{events::prelude::*, peer, transaction::prelude::*}; #[model] pub mod model { + use getset::{CopyGetters, Getters}; + use super::*; #[derive( @@ -37,6 +38,7 @@ pub mod model { PartialOrd, Ord, Getters, + CopyGetters, Decode, Encode, Deserialize, @@ -48,24 +50,24 @@ pub mod model { display(fmt = "Block №{height} (hash: {});", "HashOf::new(&self)") )] #[cfg_attr(not(feature = "std"), display(fmt = "Block №{height}"))] - #[getset(get = "pub")] #[allow(missing_docs)] #[ffi_type] pub struct BlockHeader { /// Number of blocks in the chain including this block. + #[getset(get_copy = "pub")] pub height: u64, /// Creation timestamp (unix time in milliseconds). #[getset(skip)] - pub timestamp_ms: u64, + pub creation_time_ms: u64, /// Hash of the previous block in the chain. - pub previous_block_hash: Option>, + #[getset(get = "pub")] + pub prev_block_hash: Option>, /// Hash of merkle tree root of transactions' hashes. + #[getset(get = "pub")] pub transactions_hash: Option>>, /// Value of view change index. Used to resolve soft forks. - pub view_change_index: u64, #[getset(skip)] - /// Estimation of consensus duration (in milliseconds). - pub consensus_estimation_ms: u64, + pub view_change_index: u64, } #[derive( @@ -76,7 +78,6 @@ pub mod model { Eq, PartialOrd, Ord, - Getters, Decode, Encode, Deserialize, @@ -84,45 +85,28 @@ pub mod model { IntoSchema, )] #[display(fmt = "({header})")] - #[getset(get = "pub")] #[allow(missing_docs)] - #[ffi_type] - pub struct BlockPayload { + pub(crate) struct BlockPayload { /// Block header pub header: BlockHeader, /// Topology of the network at the time of block commit. - #[getset(skip)] // FIXME: Because ffi related issues pub commit_topology: UniqueVec, /// array of transactions, which successfully passed validation and consensus step. - #[getset(skip)] // FIXME: Because ffi related issues pub transactions: Vec, /// Event recommendations. - #[getset(skip)] // NOTE: Unused ATM - pub event_recommendations: Vec, + pub event_recommendations: Vec, } /// Signed block #[version_with_scale(version = 1, versioned_alias = "SignedBlock")] #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Getters, - Encode, - Serialize, - IntoSchema, + Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Serialize, IntoSchema, )] #[cfg_attr(not(feature = "std"), display(fmt = "Signed block"))] #[cfg_attr(feature = "std", display(fmt = "{}", "self.hash()"))] - #[getset(get = "pub")] #[ffi_type] pub struct SignedBlockV1 { /// Signatures of peers which approved this block. - #[getset(skip)] pub signatures: SignaturesOf, /// Block payload pub payload: BlockPayload, @@ -134,13 +118,6 @@ declare_versioned!(SignedBlock 1..2, Debug, Clone, PartialEq, Eq, PartialOrd, Or #[cfg(all(not(feature = "ffi_export"), not(feature = "ffi_import")))] declare_versioned!(SignedBlock 1..2, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, FromVariant, IntoSchema); -impl BlockPayload { - /// Calculate block payload [`Hash`](`iroha_crypto::HashOf`). - pub fn hash(&self) -> iroha_crypto::HashOf { - iroha_crypto::HashOf::new(self) - } -} - impl BlockHeader { /// Checks if it's a header of a genesis block. #[inline] @@ -150,13 +127,8 @@ impl BlockHeader { } /// Creation timestamp - pub fn timestamp(&self) -> Duration { - Duration::from_millis(self.timestamp_ms) - } - - /// Consensus estimation - pub fn consensus_estimation(&self) -> Duration { - Duration::from_millis(self.consensus_estimation_ms) + pub fn creation_time(&self) -> Duration { + Duration::from_millis(self.creation_time_ms) } } @@ -168,21 +140,21 @@ impl SignedBlockV1 { } impl SignedBlock { - /// Block transactions + /// Block header #[inline] - pub fn transactions(&self) -> impl ExactSizeIterator { + pub fn header(&self) -> &BlockHeader { let SignedBlock::V1(block) = self; - block.payload.transactions.iter() + &block.payload.header } - /// Block header + /// Block transactions #[inline] - pub fn header(&self) -> &BlockHeader { + pub fn transactions(&self) -> impl ExactSizeIterator { let SignedBlock::V1(block) = self; - block.payload.header() + block.payload.transactions.iter() } - /// Block commit topology + /// Topology of the network at the time of block commit. #[inline] #[cfg(feature = "transparent_api")] pub fn commit_topology(&self) -> &UniqueVec { @@ -202,18 +174,19 @@ impl SignedBlock { pub fn hash(&self) -> HashOf { iroha_crypto::HashOf::new(self) } +} +#[cfg(feature = "transparent_api")] +impl SignedBlock { /// Calculate block payload [`Hash`](`iroha_crypto::HashOf`). #[inline] #[cfg(feature = "std")] - #[cfg(feature = "transparent_api")] pub fn hash_of_payload(&self) -> iroha_crypto::HashOf { let SignedBlock::V1(block) = self; iroha_crypto::HashOf::new(&block.payload) } /// Add additional signatures to this block - #[cfg(feature = "transparent_api")] #[must_use] pub fn sign(mut self, key_pair: &KeyPair) -> Self { let SignedBlock::V1(block) = &mut self; @@ -227,7 +200,6 @@ impl SignedBlock { /// # Errors /// /// If given signature doesn't match block hash - #[cfg(feature = "transparent_api")] pub fn add_signature( &mut self, signature: iroha_crypto::SignatureOf, @@ -242,7 +214,6 @@ impl SignedBlock { } /// Add additional signatures to this block - #[cfg(feature = "transparent_api")] pub fn replace_signatures( &mut self, signatures: iroha_crypto::SignaturesOf, @@ -292,7 +263,7 @@ mod candidate { } fn validate_header(&self) -> Result<(), &'static str> { - let actual_txs_hash = self.payload.header().transactions_hash; + let actual_txs_hash = self.payload.header.transactions_hash; let expected_txs_hash = self .payload diff --git a/data_model/src/events/data/filters.rs b/data_model/src/events/data/filters.rs index 4edc08c828e..92743725aaf 100644 --- a/data_model/src/events/data/filters.rs +++ b/data_model/src/events/data/filters.rs @@ -705,7 +705,6 @@ impl EventFilter for DataEventFilter { (DataEvent::Peer(event), Peer(filter)) => filter.matches(event), (DataEvent::Trigger(event), Trigger(filter)) => filter.matches(event), (DataEvent::Role(event), Role(filter)) => filter.matches(event), - (DataEvent::PermissionToken(_), PermissionTokenSchemaUpdate) => true, (DataEvent::Configuration(event), Configuration(filter)) => filter.matches(event), (DataEvent::Executor(event), Executor(filter)) => filter.matches(event), diff --git a/data_model/src/events/mod.rs b/data_model/src/events/mod.rs index 94c003526bc..b7e9c2abbe0 100644 --- a/data_model/src/events/mod.rs +++ b/data_model/src/events/mod.rs @@ -7,9 +7,11 @@ use iroha_data_model_derive::model; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; +use pipeline::{BlockEvent, TransactionEvent}; use serde::{Deserialize, Serialize}; pub use self::model::*; +use self::pipeline::{BlockEventFilter, TransactionEventFilter}; pub mod data; pub mod execute_trigger; @@ -37,9 +39,9 @@ pub mod model { IntoSchema, )] #[ffi_type] - pub enum Event { + pub enum EventBox { /// Pipeline event. - Pipeline(pipeline::PipelineEvent), + Pipeline(pipeline::PipelineEventBox), /// Data event. Data(data::DataEvent), /// Time event. @@ -85,7 +87,7 @@ pub mod model { #[ffi_type(opaque)] pub enum EventFilterBox { /// Listen to pipeline events with filter. - Pipeline(pipeline::PipelineEventFilter), + Pipeline(pipeline::PipelineEventFilterBox), /// Listen to data events with filter. Data(data::DataEventFilter), /// Listen to time events with filter. @@ -116,7 +118,7 @@ pub mod model { #[ffi_type(opaque)] pub enum TriggeringEventFilterBox { /// Listen to pipeline events with filter. - Pipeline(pipeline::PipelineEventFilter), + Pipeline(pipeline::PipelineEventFilterBox), /// Listen to data events with filter. Data(data::DataEventFilter), /// Listen to time events with filter. @@ -126,6 +128,62 @@ pub mod model { } } +impl From for EventBox { + fn from(source: TransactionEvent) -> Self { + Self::Pipeline(source.into()) + } +} + +impl From for EventBox { + fn from(source: BlockEvent) -> Self { + Self::Pipeline(source.into()) + } +} + +impl From for EventFilterBox { + fn from(source: TransactionEventFilter) -> Self { + Self::Pipeline(source.into()) + } +} + +impl From for EventFilterBox { + fn from(source: BlockEventFilter) -> Self { + Self::Pipeline(source.into()) + } +} + +impl TryFrom for TransactionEvent { + type Error = iroha_macro::error::ErrorTryFromEnum; + + fn try_from(event: EventBox) -> Result { + use iroha_macro::error::ErrorTryFromEnum; + + let EventBox::Pipeline(pipeline_event) = event else { + return Err(ErrorTryFromEnum::default()); + }; + + pipeline_event + .try_into() + .map_err(|_| ErrorTryFromEnum::default()) + } +} + +impl TryFrom for BlockEvent { + type Error = iroha_macro::error::ErrorTryFromEnum; + + fn try_from(event: EventBox) -> Result { + use iroha_macro::error::ErrorTryFromEnum; + + let EventBox::Pipeline(pipeline_event) = event else { + return Err(ErrorTryFromEnum::default()); + }; + + pipeline_event + .try_into() + .map_err(|_| ErrorTryFromEnum::default()) + } +} + /// Trait for filters #[cfg(feature = "transparent_api")] pub trait EventFilter { @@ -156,25 +214,27 @@ pub trait EventFilter { #[cfg(feature = "transparent_api")] impl EventFilter for EventFilterBox { - type Event = Event; + type Event = EventBox; /// Apply filter to event. - fn matches(&self, event: &Event) -> bool { + fn matches(&self, event: &EventBox) -> bool { match (event, self) { - (Event::Pipeline(event), Self::Pipeline(filter)) => filter.matches(event), - (Event::Data(event), Self::Data(filter)) => filter.matches(event), - (Event::Time(event), Self::Time(filter)) => filter.matches(event), - (Event::ExecuteTrigger(event), Self::ExecuteTrigger(filter)) => filter.matches(event), - (Event::TriggerCompleted(event), Self::TriggerCompleted(filter)) => { + (EventBox::Pipeline(event), Self::Pipeline(filter)) => filter.matches(event), + (EventBox::Data(event), Self::Data(filter)) => filter.matches(event), + (EventBox::Time(event), Self::Time(filter)) => filter.matches(event), + (EventBox::ExecuteTrigger(event), Self::ExecuteTrigger(filter)) => { + filter.matches(event) + } + (EventBox::TriggerCompleted(event), Self::TriggerCompleted(filter)) => { filter.matches(event) } // Fail to compile in case when new variant to event or filter is added ( - Event::Pipeline(_) - | Event::Data(_) - | Event::Time(_) - | Event::ExecuteTrigger(_) - | Event::TriggerCompleted(_), + EventBox::Pipeline(_) + | EventBox::Data(_) + | EventBox::Time(_) + | EventBox::ExecuteTrigger(_) + | EventBox::TriggerCompleted(_), Self::Pipeline(_) | Self::Data(_) | Self::Time(_) @@ -187,22 +247,24 @@ impl EventFilter for EventFilterBox { #[cfg(feature = "transparent_api")] impl EventFilter for TriggeringEventFilterBox { - type Event = Event; + type Event = EventBox; /// Apply filter to event. - fn matches(&self, event: &Event) -> bool { + fn matches(&self, event: &EventBox) -> bool { match (event, self) { - (Event::Pipeline(event), Self::Pipeline(filter)) => filter.matches(event), - (Event::Data(event), Self::Data(filter)) => filter.matches(event), - (Event::Time(event), Self::Time(filter)) => filter.matches(event), - (Event::ExecuteTrigger(event), Self::ExecuteTrigger(filter)) => filter.matches(event), + (EventBox::Pipeline(event), Self::Pipeline(filter)) => filter.matches(event), + (EventBox::Data(event), Self::Data(filter)) => filter.matches(event), + (EventBox::Time(event), Self::Time(filter)) => filter.matches(event), + (EventBox::ExecuteTrigger(event), Self::ExecuteTrigger(filter)) => { + filter.matches(event) + } // Fail to compile in case when new variant to event or filter is added ( - Event::Pipeline(_) - | Event::Data(_) - | Event::Time(_) - | Event::ExecuteTrigger(_) - | Event::TriggerCompleted(_), + EventBox::Pipeline(_) + | EventBox::Data(_) + | EventBox::Time(_) + | EventBox::ExecuteTrigger(_) + | EventBox::TriggerCompleted(_), Self::Pipeline(_) | Self::Data(_) | Self::Time(_) | Self::ExecuteTrigger(_), ) => false, } @@ -279,7 +341,7 @@ pub mod stream { /// Event sent by the peer. #[derive(Debug, Clone, Decode, Encode, IntoSchema)] #[repr(transparent)] - pub struct EventMessage(pub Event); + pub struct EventMessage(pub EventBox); /// Message sent by the stream consumer. /// Request sent by the client to subscribe to events. @@ -288,7 +350,7 @@ pub mod stream { pub struct EventSubscriptionRequest(pub EventFilterBox); } - impl From for Event { + impl From for EventBox { fn from(source: EventMessage) -> Self { source.0 } @@ -303,7 +365,7 @@ pub mod prelude { pub use super::EventFilter; pub use super::{ data::prelude::*, execute_trigger::prelude::*, pipeline::prelude::*, time::prelude::*, - trigger_completed::prelude::*, Event, EventFilterBox, TriggeringEventFilterBox, + trigger_completed::prelude::*, EventBox, EventFilterBox, TriggeringEventFilterBox, TriggeringEventType, }; } diff --git a/data_model/src/events/pipeline.rs b/data_model/src/events/pipeline.rs index 5d3b962144f..23adc2b436f 100644 --- a/data_model/src/events/pipeline.rs +++ b/data_model/src/events/pipeline.rs @@ -1,59 +1,52 @@ //! Pipeline events. #[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; +use alloc::{boxed::Box, format, string::String, vec::Vec}; -use getset::Getters; -use iroha_crypto::Hash; +use iroha_crypto::HashOf; use iroha_data_model_derive::model; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; -use strum::EnumDiscriminants; pub use self::model::*; +use crate::{block::BlockHeader, transaction::SignedTransaction}; #[model] pub mod model { + use getset::Getters; + use super::*; - /// [`Event`] filter. #[derive( Debug, Clone, - Copy, PartialEq, Eq, PartialOrd, Ord, - Default, - Getters, + FromVariant, Decode, Encode, - Serialize, Deserialize, + Serialize, IntoSchema, )] - pub struct PipelineEventFilter { - /// If `Some::`, filter by the [`EntityKind`]. If `None`, accept all the [`EntityKind`]. - pub(super) entity_kind: Option, - /// If `Some::`, filter by the [`StatusKind`]. If `None`, accept all the [`StatusKind`]. - pub(super) status_kind: Option, - /// If `Some::`, filter by the [`struct@Hash`]. If `None`, accept all the [`struct@Hash`]. - // TODO: Can we make hash typed like HashOf? - pub(super) hash: Option, + #[ffi_type(opaque)] + pub enum PipelineEventBox { + Transaction(TransactionEvent), + Block(BlockEvent), } - /// The kind of the pipeline entity. #[derive( Debug, Clone, - Copy, PartialEq, Eq, PartialOrd, Ord, + Getters, Decode, Encode, Deserialize, @@ -61,15 +54,12 @@ pub mod model { IntoSchema, )] #[ffi_type] - #[repr(u8)] - pub enum PipelineEntityKind { - /// Block - Block, - /// Transaction - Transaction, + #[getset(get = "pub")] + pub struct BlockEvent { + pub header: BlockHeader, + pub status: BlockStatus, } - /// Strongly-typed [`Event`] that tells the receiver the kind and the hash of the changed entity as well as its [`Status`]. #[derive( Debug, Clone, @@ -84,18 +74,15 @@ pub mod model { Serialize, IntoSchema, )] - #[getset(get = "pub")] #[ffi_type] - pub struct PipelineEvent { - /// [`EntityKind`] of the entity that caused this [`Event`]. - pub entity_kind: PipelineEntityKind, - /// [`Status`] of the entity that caused this [`Event`]. - pub status: PipelineStatus, - /// [`struct@Hash`] of the entity that caused this [`Event`]. - pub hash: Hash, + #[getset(get = "pub")] + pub struct TransactionEvent { + pub hash: HashOf, + pub block_height: Option, + pub status: TransactionStatus, } - /// [`Status`] of the entity. + /// Report of block's status in the pipeline #[derive( Debug, Clone, @@ -103,129 +90,221 @@ pub mod model { Eq, PartialOrd, Ord, - FromVariant, - EnumDiscriminants, Decode, Encode, + Deserialize, Serialize, + IntoSchema, + )] + #[ffi_type(opaque)] + pub enum BlockStatus { + /// Block was approved to participate in consensus + Approved, + /// Block was rejected by consensus + Rejected(crate::block::error::BlockRejectionReason), + /// Block has passed consensus successfully + Committed, + /// Changes have been reflected in the WSV + Applied, + } + + /// Report of transaction's status in the pipeline + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, Deserialize, + Serialize, IntoSchema, )] - #[strum_discriminants( - name(PipelineStatusKind), - derive(PartialOrd, Ord, Decode, Encode, Deserialize, Serialize, IntoSchema,) + #[ffi_type(opaque)] + pub enum TransactionStatus { + /// Transaction was received and enqueued + Queued, + /// Transaction was dropped(not stored in a block) + Expired, + /// Transaction was stored in the block as valid + Approved, + /// Transaction was stored in the block as invalid + Rejected(Box), + } + + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + FromVariant, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, )] #[ffi_type] - pub enum PipelineStatus { - /// Entity has been seen in the blockchain but has not passed validation. - Validating, - /// Entity was rejected during validation. - Rejected(PipelineRejectionReason), - /// Entity has passed validation. - Committed, + pub enum PipelineEventFilterBox { + Transaction(TransactionEventFilter), + Block(BlockEventFilter), } - /// The reason for rejecting pipeline entity such as transaction or block. #[derive( Debug, - displaydoc::Display, Clone, PartialEq, Eq, PartialOrd, Ord, - FromVariant, + Default, + Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[cfg_attr(feature = "std", derive(thiserror::Error))] #[ffi_type] - pub enum PipelineRejectionReason { - /// Block was rejected - Block(#[cfg_attr(feature = "std", source)] crate::block::error::BlockRejectionReason), - /// Transaction was rejected - Transaction( - #[cfg_attr(feature = "std", source)] - crate::transaction::error::TransactionRejectionReason, - ), + #[getset(get = "pub")] + pub struct BlockEventFilter { + pub height: Option, + pub status: Option, + } + + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Default, + Getters, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type] + #[getset(get = "pub")] + pub struct TransactionEventFilter { + pub hash: Option>, + #[getset(skip)] + pub block_height: Option>, + pub status: Option, } } -impl PipelineEventFilter { - /// Creates a new [`PipelineEventFilter`] accepting all [`PipelineEvent`]s +impl BlockEventFilter { + /// Match only block with the given height #[must_use] - #[inline] - pub const fn new() -> Self { - Self { - status_kind: None, - entity_kind: None, - hash: None, - } + pub fn for_height(mut self, height: u64) -> Self { + self.height = Some(height); + self } - /// Modifies a [`PipelineEventFilter`] to accept only [`PipelineEvent`]s originating from a specific entity kind (block/transaction). + /// Match only block with the given status #[must_use] - #[inline] - pub const fn for_entity(mut self, entity_kind: PipelineEntityKind) -> Self { - self.entity_kind = Some(entity_kind); + pub fn for_status(mut self, status: BlockStatus) -> Self { + self.status = Some(status); self } +} + +impl TransactionEventFilter { + /// Get height of the block filter is set to track + pub fn block_height(&self) -> Option> { + self.block_height + } - /// Modifies a [`PipelineEventFilter`] to accept only [`PipelineEvent`]s with a specific status. + /// Match only transactions with the given block height #[must_use] - #[inline] - pub const fn for_status(mut self, status_kind: PipelineStatusKind) -> Self { - self.status_kind = Some(status_kind); + pub fn for_block_height(mut self, block_height: Option) -> Self { + self.block_height = Some(block_height); self } - /// Modifies a [`PipelineEventFilter`] to accept only [`PipelineEvent`]s originating from an entity with specified hash. + /// Match only transactions with the given hash #[must_use] - #[inline] - pub const fn for_hash(mut self, hash: Hash) -> Self { + pub fn for_hash(mut self, hash: HashOf) -> Self { self.hash = Some(hash); self } - #[inline] - #[cfg(feature = "transparent_api")] + /// Match only transactions with the given status + #[must_use] + pub fn for_status(mut self, status: TransactionStatus) -> Self { + self.status = Some(status); + self + } +} + +#[cfg(feature = "transparent_api")] +impl TransactionEventFilter { fn field_matches(filter: Option<&T>, event: &T) -> bool { filter.map_or(true, |field| field == event) } } #[cfg(feature = "transparent_api")] -impl super::EventFilter for PipelineEventFilter { - type Event = PipelineEvent; - - /// Check if `self` accepts the `event`. - #[inline] - fn matches(&self, event: &PipelineEvent) -> bool { - [ - Self::field_matches(self.entity_kind.as_ref(), &event.entity_kind), - Self::field_matches(self.status_kind.as_ref(), &event.status.kind()), - Self::field_matches(self.hash.as_ref(), &event.hash), - ] - .into_iter() - .all(core::convert::identity) +impl BlockEventFilter { + fn field_matches(filter: Option<&T>, event: &T) -> bool { + filter.map_or(true, |field| field == event) } } #[cfg(feature = "transparent_api")] -impl PipelineStatus { - fn kind(&self) -> PipelineStatusKind { - PipelineStatusKind::from(self) +impl super::EventFilter for PipelineEventFilterBox { + type Event = PipelineEventBox; + + /// Check if `self` accepts the `event`. + #[inline] + fn matches(&self, event: &PipelineEventBox) -> bool { + match (self, event) { + (Self::Block(block_filter), PipelineEventBox::Block(block_event)) => [ + BlockEventFilter::field_matches( + block_filter.height.as_ref(), + &block_event.header.height, + ), + BlockEventFilter::field_matches(block_filter.status.as_ref(), &block_event.status), + ] + .into_iter() + .all(core::convert::identity), + ( + Self::Transaction(transaction_filter), + PipelineEventBox::Transaction(transaction_event), + ) => [ + TransactionEventFilter::field_matches( + transaction_filter.hash.as_ref(), + &transaction_event.hash, + ), + TransactionEventFilter::field_matches( + transaction_filter.block_height.as_ref(), + &transaction_event.block_height, + ), + TransactionEventFilter::field_matches( + transaction_filter.status.as_ref(), + &transaction_event.status, + ), + ] + .into_iter() + .all(core::convert::identity), + _ => false, + } } } /// Exports common structs and enums from this module. pub mod prelude { pub use super::{ - PipelineEntityKind, PipelineEvent, PipelineEventFilter, PipelineRejectionReason, - PipelineStatus, PipelineStatusKind, + BlockEvent, BlockStatus, PipelineEventBox, PipelineEventFilterBox, TransactionEvent, + TransactionStatus, }; } @@ -235,94 +314,120 @@ mod tests { #[cfg(not(feature = "std"))] use alloc::{string::ToString as _, vec, vec::Vec}; - use super::{super::EventFilter, PipelineRejectionReason::*, *}; + use iroha_crypto::Hash; + + use super::{super::EventFilter, *}; use crate::{transaction::error::TransactionRejectionReason::*, ValidationFail}; + impl BlockHeader { + fn dummy(height: u64) -> Self { + Self { + height, + creation_time_ms: 0, + prev_block_hash: None, + transactions_hash: None, + view_change_index: 0, + } + } + } + #[test] fn events_are_correctly_filtered() { let events = vec![ - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Validating, - hash: Hash::prehashed([0_u8; Hash::LENGTH]), - }, - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Rejected(Transaction(Validation( + TransactionEvent { + hash: HashOf::from_untyped_unchecked(Hash::prehashed([0_u8; Hash::LENGTH])), + block_height: None, + status: TransactionStatus::Queued, + } + .into(), + TransactionEvent { + hash: HashOf::from_untyped_unchecked(Hash::prehashed([0_u8; Hash::LENGTH])), + block_height: Some(3), + status: TransactionStatus::Rejected(Box::new(Validation( ValidationFail::TooComplex, ))), - hash: Hash::prehashed([0_u8; Hash::LENGTH]), - }, - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Committed, - hash: Hash::prehashed([2_u8; Hash::LENGTH]), - }, - PipelineEvent { - entity_kind: PipelineEntityKind::Block, - status: PipelineStatus::Committed, - hash: Hash::prehashed([2_u8; Hash::LENGTH]), - }, + } + .into(), + TransactionEvent { + hash: HashOf::from_untyped_unchecked(Hash::prehashed([2_u8; Hash::LENGTH])), + block_height: None, + status: TransactionStatus::Approved, + } + .into(), + BlockEvent { + header: BlockHeader::dummy(7), + status: BlockStatus::Committed, + } + .into(), ]; + assert_eq!( + events + .iter() + .filter(|&event| { + let filter: PipelineEventFilterBox = TransactionEventFilter::default() + .for_hash(HashOf::from_untyped_unchecked(Hash::prehashed( + [0_u8; Hash::LENGTH], + ))) + .into(); + + filter.matches(event) + }) + .cloned() + .collect::>(), vec![ - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Validating, - hash: Hash::prehashed([0_u8; Hash::LENGTH]), - }, - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Rejected(Transaction(Validation( + TransactionEvent { + hash: HashOf::from_untyped_unchecked(Hash::prehashed([0_u8; Hash::LENGTH])), + block_height: None, + status: TransactionStatus::Queued, + } + .into(), + TransactionEvent { + hash: HashOf::from_untyped_unchecked(Hash::prehashed([0_u8; Hash::LENGTH])), + block_height: Some(3), + status: TransactionStatus::Rejected(Box::new(Validation( ValidationFail::TooComplex, ))), - hash: Hash::prehashed([0_u8; Hash::LENGTH]), - }, + } + .into(), ], - events - .iter() - .filter(|&event| PipelineEventFilter::new() - .for_hash(Hash::prehashed([0_u8; Hash::LENGTH])) - .matches(event)) - .cloned() - .collect::>() ); + assert_eq!( - vec![PipelineEvent { - entity_kind: PipelineEntityKind::Block, - status: PipelineStatus::Committed, - hash: Hash::prehashed([2_u8; Hash::LENGTH]), - }], events .iter() - .filter(|&event| PipelineEventFilter::new() - .for_entity(PipelineEntityKind::Block) - .matches(event)) + .filter(|&event| { + let filter: PipelineEventFilterBox = BlockEventFilter::default().into(); + filter.matches(event) + }) .cloned() - .collect::>() + .collect::>(), + vec![BlockEvent { + status: BlockStatus::Committed, + header: BlockHeader::dummy(2), + } + .into()], ); assert_eq!( - vec![PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Committed, - hash: Hash::prehashed([2_u8; Hash::LENGTH]), - }], events .iter() - .filter(|&event| PipelineEventFilter::new() - .for_entity(PipelineEntityKind::Transaction) - .for_hash(Hash::prehashed([2_u8; Hash::LENGTH])) - .matches(event)) + .filter(|&event| { + let filter: PipelineEventFilterBox = TransactionEventFilter::default() + .for_hash(HashOf::from_untyped_unchecked(Hash::prehashed( + [2_u8; Hash::LENGTH], + ))) + .into(); + + filter.matches(event) + }) .cloned() - .collect::>() + .collect::>(), + vec![TransactionEvent { + hash: HashOf::from_untyped_unchecked(Hash::prehashed([0_u8; Hash::LENGTH])), + block_height: None, + status: TransactionStatus::Approved, + } + .into()], ); - assert_eq!( - events, - events - .iter() - .filter(|&event| PipelineEventFilter::new().matches(event)) - .cloned() - .collect::>() - ) } } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 87352bead15..db5bd809e12 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -1022,16 +1022,6 @@ impl From for RangeInclusive { } } -/// Get the current system time as `Duration` since the unix epoch. -#[cfg(feature = "std")] -pub fn current_time() -> core::time::Duration { - use std::time::SystemTime; - - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Failed to get the current system time") -} - declare_versioned_with_scale!(BatchedResponse 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); impl From> for (T, crate::query::cursor::ForwardCursor) { @@ -1086,8 +1076,6 @@ pub mod prelude { pub use iroha_crypto::PublicKey; pub use iroha_primitives::numeric::{numeric, Numeric, NumericSpec}; - #[cfg(feature = "std")] - pub use super::current_time; pub use super::{ account::prelude::*, asset::prelude::*, domain::prelude::*, events::prelude::*, executor::prelude::*, isi::prelude::*, metadata::prelude::*, name::prelude::*, diff --git a/data_model/src/query/predicate.rs b/data_model/src/query/predicate.rs index 6421c164457..7892ed6acea 100644 --- a/data_model/src/query/predicate.rs +++ b/data_model/src/query/predicate.rs @@ -1163,7 +1163,7 @@ pub mod value { QueryOutputPredicate::Display(pred) => pred.applies(&input.to_string()), QueryOutputPredicate::TimeStamp(pred) => match input { QueryOutputBox::Block(block) => { - pred.applies(block.header().timestamp().as_millis()) + pred.applies(block.header().creation_time().as_millis()) } _ => false, }, diff --git a/data_model/src/smart_contract.rs b/data_model/src/smart_contract.rs index 379da0585d9..5d51c2f89d3 100644 --- a/data_model/src/smart_contract.rs +++ b/data_model/src/smart_contract.rs @@ -20,7 +20,7 @@ pub mod payloads { /// Trigger owner who registered the trigger pub owner: AccountId, /// Event which triggered the execution - pub event: Event, + pub event: EventBox, } /// Payload for migrate entrypoint diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index fbfded831ab..20891c9c064 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -9,7 +9,6 @@ use core::{ }; use derive_more::{DebugCustom, Display}; -use getset::Getters; use iroha_crypto::SignaturesOf; use iroha_data_model_derive::model; use iroha_macro::FromVariant; @@ -28,6 +27,8 @@ use crate::{ #[model] pub mod model { + use getset::{CopyGetters, Getters}; + use super::*; /// Either ISI or Wasm binary @@ -89,35 +90,26 @@ pub mod model { Eq, PartialOrd, Ord, - Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[getset(get = "pub")] - #[ffi_type] - pub struct TransactionPayload { + pub(crate) struct TransactionPayload { /// Unique id of the blockchain. Used for simple replay attack protection. - #[getset(skip)] // FIXME: ffi error pub chain_id: ChainId, /// Creation timestamp (unix time in milliseconds). - #[getset(skip)] pub creation_time_ms: u64, /// Account ID of transaction creator. pub authority: AccountId, /// ISI or a `WebAssembly` smart contract. pub instructions: Executable, /// If transaction is not committed by this time it will be dropped. - #[getset(skip)] pub time_to_live_ms: Option, /// Random value to make different hashes for transactions which occur repeatedly and simultaneously. - // TODO: Only temporary - #[getset(skip)] pub nonce: Option, /// Store for additional information. - #[getset(skip)] // FIXME: ffi error pub metadata: UnlimitedMetadata, } @@ -131,7 +123,7 @@ pub mod model { Eq, PartialOrd, Ord, - Getters, + CopyGetters, Decode, Encode, Deserialize, @@ -139,7 +131,7 @@ pub mod model { IntoSchema, )] #[display(fmt = "{max_instruction_number},{max_wasm_size_bytes}_TL")] - #[getset(get = "pub")] + #[getset(get_copy = "pub")] #[ffi_type] pub struct TransactionLimits { /// Maximum number of instructions per transaction @@ -251,14 +243,14 @@ impl SignedTransaction { #[inline] pub fn instructions(&self) -> &Executable { let SignedTransaction::V1(tx) = self; - tx.payload.instructions() + &tx.payload.instructions } /// Return transaction authority #[inline] pub fn authority(&self) -> &AccountId { let SignedTransaction::V1(tx) = self; - tx.payload.authority() + &tx.payload.authority } /// Return transaction metadata. @@ -449,6 +441,8 @@ pub mod error { #[model] pub mod model { + use getset::Getters; + use super::*; /// Error which indicates max instruction count was reached @@ -565,8 +559,6 @@ pub mod error { InstructionExecution(#[cfg_attr(feature = "std", source)] InstructionExecutionFail), /// Failure in WebAssembly execution WasmExecution(#[cfg_attr(feature = "std", source)] WasmExecutionFail), - /// Transaction rejected due to being expired - Expired, } } @@ -638,7 +630,11 @@ mod http { #[inline] #[cfg(feature = "std")] pub fn new(chain_id: ChainId, authority: AccountId) -> Self { - let creation_time_ms = crate::current_time() + use std::time::SystemTime; + + let creation_time_ms = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time") .as_millis() .try_into() .expect("Unix timestamp exceedes u64::MAX"); @@ -745,6 +741,9 @@ pub mod prelude { #[cfg(test)] mod tests { + #[cfg(not(feature = "std"))] + use alloc::vec; + use super::*; #[test] diff --git a/data_model/src/trigger.rs b/data_model/src/trigger.rs index e65cf5f4a96..9a739cfead1 100644 --- a/data_model/src/trigger.rs +++ b/data_model/src/trigger.rs @@ -237,21 +237,21 @@ pub mod action { impl PartialOrd for Action { fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for Action { + fn cmp(&self, other: &Self) -> cmp::Ordering { // Exclude the executable. When debugging and replacing // the trigger, its position in Hash and Tree maps should // not change depending on the content. match self.repeats.cmp(&other.repeats) { cmp::Ordering::Equal => {} - ord => return Some(ord), + ord => return ord, } - Some(self.authority.cmp(&other.authority)) - } - } - impl Ord for Action { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.partial_cmp(other) - .expect("`PartialCmp::partial_cmp()` for `Action` should never return `None`") + self.authority.cmp(&other.authority) } } diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index b6d93a29fdc..e399423a703 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -480,6 +480,30 @@ } ] }, + "BlockEvent": { + "Struct": [ + { + "name": "header", + "type": "BlockHeader" + }, + { + "name": "status", + "type": "BlockStatus" + } + ] + }, + "BlockEventFilter": { + "Struct": [ + { + "name": "height", + "type": "Option" + }, + { + "name": "status", + "type": "Option" + } + ] + }, "BlockHeader": { "Struct": [ { @@ -487,11 +511,11 @@ "type": "u64" }, { - "name": "timestamp_ms", + "name": "creation_time_ms", "type": "u64" }, { - "name": "previous_block_hash", + "name": "prev_block_hash", "type": "Option>" }, { @@ -501,10 +525,6 @@ { "name": "view_change_index", "type": "u64" - }, - { - "name": "consensus_estimation_ms", - "type": "u64" } ] }, @@ -525,7 +545,7 @@ }, { "name": "event_recommendations", - "type": "Vec" + "type": "Vec" } ] }, @@ -537,6 +557,27 @@ } ] }, + "BlockStatus": { + "Enum": [ + { + "tag": "Approved", + "discriminant": 0 + }, + { + "tag": "Rejected", + "discriminant": 1, + "type": "BlockRejectionReason" + }, + { + "tag": "Committed", + "discriminant": 2 + }, + { + "tag": "Applied", + "discriminant": 3 + } + ] + }, "BlockSubscriptionRequest": "NonZero", "Burn": { "Struct": [ @@ -857,12 +898,12 @@ "u32" ] }, - "Event": { + "EventBox": { "Enum": [ { "tag": "Pipeline", "discriminant": 0, - "type": "PipelineEvent" + "type": "PipelineEventBox" }, { "tag": "Data", @@ -891,7 +932,7 @@ { "tag": "Pipeline", "discriminant": 0, - "type": "PipelineEventFilter" + "type": "PipelineEventFilterBox" }, { "tag": "Data", @@ -915,7 +956,7 @@ } ] }, - "EventMessage": "Event", + "EventMessage": "EventBox", "EventSubscriptionRequest": "EventFilterBox", "Executable": { "Enum": [ @@ -2229,21 +2270,24 @@ "Option": { "Option": "AssetId" }, + "Option": { + "Option": "BlockStatus" + }, "Option": { "Option": "DomainId" }, "Option": { "Option": "Duration" }, - "Option": { - "Option": "Hash" - }, "Option>>": { "Option": "HashOf>" }, "Option>": { "Option": "HashOf" }, + "Option>": { + "Option": "HashOf" + }, "Option": { "Option": "IpfsPath" }, @@ -2253,18 +2297,15 @@ "Option>": { "Option": "NonZero" }, + "Option>": { + "Option": "Option" + }, "Option": { "Option": "ParameterId" }, "Option": { "Option": "PeerId" }, - "Option": { - "Option": "PipelineEntityKind" - }, - "Option": { - "Option": "PipelineStatusKind" - }, "Option": { "Option": "RoleId" }, @@ -2277,6 +2318,9 @@ "Option": { "Option": "TransactionRejectionReason" }, + "Option": { + "Option": "TransactionStatus" + }, "Option": { "Option": "TriggerCompletedOutcomeType" }, @@ -2286,6 +2330,9 @@ "Option": { "Option": "u32" }, + "Option": { + "Option": "u64" + }, "Parameter": { "Struct": [ { @@ -2413,94 +2460,31 @@ } ] }, - "PipelineEntityKind": { + "PipelineEventBox": { "Enum": [ - { - "tag": "Block", - "discriminant": 0 - }, { "tag": "Transaction", - "discriminant": 1 - } - ] - }, - "PipelineEvent": { - "Struct": [ - { - "name": "entity_kind", - "type": "PipelineEntityKind" - }, - { - "name": "status", - "type": "PipelineStatus" - }, - { - "name": "hash", - "type": "Hash" - } - ] - }, - "PipelineEventFilter": { - "Struct": [ - { - "name": "entity_kind", - "type": "Option" - }, - { - "name": "status_kind", - "type": "Option" - }, - { - "name": "hash", - "type": "Option" - } - ] - }, - "PipelineRejectionReason": { - "Enum": [ - { - "tag": "Block", "discriminant": 0, - "type": "BlockRejectionReason" + "type": "TransactionEvent" }, { - "tag": "Transaction", + "tag": "Block", "discriminant": 1, - "type": "TransactionRejectionReason" + "type": "BlockEvent" } ] }, - "PipelineStatus": { + "PipelineEventFilterBox": { "Enum": [ { - "tag": "Validating", - "discriminant": 0 + "tag": "Transaction", + "discriminant": 0, + "type": "TransactionEventFilter" }, { - "tag": "Rejected", + "tag": "Block", "discriminant": 1, - "type": "PipelineRejectionReason" - }, - { - "tag": "Committed", - "discriminant": 2 - } - ] - }, - "PipelineStatusKind": { - "Enum": [ - { - "tag": "Validating", - "discriminant": 0 - }, - { - "tag": "Rejected", - "discriminant": 1 - }, - { - "tag": "Committed", - "discriminant": 2 + "type": "BlockEventFilter" } ] }, @@ -3574,6 +3558,38 @@ } ] }, + "TransactionEvent": { + "Struct": [ + { + "name": "hash", + "type": "HashOf" + }, + { + "name": "block_height", + "type": "Option" + }, + { + "name": "status", + "type": "TransactionStatus" + } + ] + }, + "TransactionEventFilter": { + "Struct": [ + { + "name": "hash", + "type": "Option>" + }, + { + "name": "block_height", + "type": "Option>" + }, + { + "name": "status", + "type": "Option" + } + ] + }, "TransactionLimitError": { "Struct": [ { @@ -3664,10 +3680,27 @@ "tag": "WasmExecution", "discriminant": 4, "type": "WasmExecutionFail" + } + ] + }, + "TransactionStatus": { + "Enum": [ + { + "tag": "Queued", + "discriminant": 0 }, { "tag": "Expired", - "discriminant": 5 + "discriminant": 1 + }, + { + "tag": "Approved", + "discriminant": 2 + }, + { + "tag": "Rejected", + "discriminant": 3, + "type": "TransactionRejectionReason" } ] }, @@ -3893,7 +3926,7 @@ { "tag": "Pipeline", "discriminant": 0, - "type": "PipelineEventFilter" + "type": "PipelineEventFilterBox" }, { "tag": "Data", @@ -4061,8 +4094,8 @@ } ] }, - "Vec": { - "Vec": "Event" + "Vec": { + "Vec": "EventBox" }, "Vec>": { "Vec": "GenericPredicateBox" diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index ebfb956f436..9d8969dbc87 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -96,13 +96,17 @@ types!( BTreeSet>, BatchedResponse, BatchedResponseV1, + BlockEvent, + BlockEventFilter, BlockHeader, BlockMessage, BlockPayload, BlockRejectionReason, + BlockStatus, BlockSubscriptionRequest, Box>, Box, + Box, Burn, Burn, Burn, @@ -124,7 +128,7 @@ types!( DomainId, DomainOwnerChanged, Duration, - Event, + EventBox, EventMessage, EventSubscriptionRequest, Executable, @@ -232,25 +236,27 @@ types!( Numeric, NumericSpec, Option, + Option, Option, Option, Option, + Option, Option, Option, - Option, Option>>, Option>, + Option>, Option, Option, Option, + Option>, Option, Option, - Option, - Option, Option, Option, Option, Option, + Option, Option, Option, Parameter, @@ -265,12 +271,8 @@ types!( PermissionToken, PermissionTokenSchema, PermissionTokenSchemaUpdateEvent, - PipelineEntityKind, - PipelineEvent, - PipelineEventFilter, - PipelineRejectionReason, - PipelineStatus, - PipelineStatusKind, + PipelineEventBox, + PipelineEventFilterBox, PredicateBox, PublicKey, QueryBox, @@ -338,12 +340,15 @@ types!( TimeEventFilter, TimeInterval, TimeSchedule, + TransactionEvent, + TransactionEventFilter, TransactionLimitError, TransactionLimits, TransactionPayload, TransactionQueryOutput, TransactionRejectionReason, TransactionValue, + TransactionStatus, Transfer, Transfer, Transfer, @@ -372,7 +377,7 @@ types!( UnregisterBox, Upgrade, ValidationFail, - Vec, + Vec, Vec, Vec, Vec, @@ -412,6 +417,7 @@ mod tests { BlockHeader, BlockPayload, SignedBlock, SignedBlockV1, }, domain::NewDomain, + events::pipeline::{BlockEventFilter, TransactionEventFilter}, executor::Executor, ipfs::IpfsPath, isi::{ diff --git a/telemetry/derive/src/lib.rs b/telemetry/derive/src/lib.rs index 471260e9ee3..21f1d5bcbaf 100644 --- a/telemetry/derive/src/lib.rs +++ b/telemetry/derive/src/lib.rs @@ -242,11 +242,16 @@ fn impl_metrics(emitter: &mut Emitter, _specs: &MetricSpecs, func: &syn::ItemFn) quote!( #(#attrs)* #vis #sig { let _closure = || #block; + let start_time = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time"); - let start_time = #_metric_arg_ident.metrics.current_time(); #totals let res = _closure(); - let end_time = #_metric_arg_ident.metrics.current_time(); + let end_time = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("Failed to get the current system time"); + #times if let Ok(_) = res { #successes diff --git a/telemetry/src/metrics.rs b/telemetry/src/metrics.rs index 404e32c3916..7e93b02f94f 100644 --- a/telemetry/src/metrics.rs +++ b/telemetry/src/metrics.rs @@ -1,9 +1,6 @@ //! [`Metrics`] and [`Status`]-related logic and functions. -use std::{ - ops::Deref, - time::{Duration, SystemTime}, -}; +use std::{ops::Deref, time::Duration}; use parity_scale_codec::{Compact, Decode, Encode}; use prometheus::{ @@ -218,17 +215,6 @@ impl Metrics { Encoder::encode(&encoder, &metric_families, &mut buffer)?; Ok(String::from_utf8(buffer)?) } - - /// Get time elapsed since Unix epoch. - /// - /// # Panics - /// Never - #[allow(clippy::unused_self)] - pub fn current_time(&self) -> Duration { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Failed to get the current system time") - } } #[cfg(test)] diff --git a/tools/parity_scale_decoder/src/main.rs b/tools/parity_scale_decoder/src/main.rs index 9bf3a824693..705d9e9f43c 100644 --- a/tools/parity_scale_decoder/src/main.rs +++ b/tools/parity_scale_decoder/src/main.rs @@ -21,6 +21,7 @@ use iroha_data_model::{ BlockHeader, BlockPayload, SignedBlock, SignedBlockV1, }, domain::NewDomain, + events::pipeline::{BlockEventFilter, TransactionEventFilter}, executor::Executor, ipfs::IpfsPath, isi::{ diff --git a/torii/src/event.rs b/torii/src/event.rs index 873f81d91ec..226ca8cd4a1 100644 --- a/torii/src/event.rs +++ b/torii/src/event.rs @@ -63,7 +63,7 @@ impl Consumer { /// # Errors /// Can fail due to timeout or sending event. Also receiving might fail #[iroha_futures::telemetry_future] - pub async fn consume(&mut self, event: Event) -> Result<()> { + pub async fn consume(&mut self, event: EventBox) -> Result<()> { if !self.filter.matches(&event) { return Ok(()); }