From 1ebda34ac8821ff49eddc4bc0f9b4294ec284a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Ver=C5=A1i=C4=87?= Date: Wed, 5 Apr 2023 10:06:51 +0300 Subject: [PATCH] [refactor]: Update block and sumeragi code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marin Veršić --- Cargo.lock | 3 + cli/src/event.rs | 3 +- cli/src/lib.rs | 28 +- cli/src/torii/mod.rs | 2 +- cli/src/torii/routing.rs | 35 +- client/Cargo.toml | 2 +- client/benches/tps/utils.rs | 11 +- client/examples/tutorial.rs | 4 +- client/src/client.rs | 101 +- client/tests/integration/asset.rs | 8 +- client/tests/integration/burn_public_keys.rs | 5 +- client/tests/integration/events/data.rs | 2 +- client/tests/integration/events/pipeline.rs | 6 +- .../integration/multisignature_transaction.rs | 4 +- client/tests/integration/non_mintable.rs | 2 +- client/tests/integration/permissions.rs | 4 +- client/tests/integration/roles.rs | 2 +- .../integration/triggers/by_call_trigger.rs | 2 +- client/tests/integration/tx_history.rs | 6 +- client/tests/integration/unstable_network.rs | 1 + client/tests/integration/upgrade.rs | 4 +- client_cli/src/main.rs | 4 +- config/src/client.rs | 12 +- config/src/sumeragi.rs | 32 +- configs/peer/config.json | 4 +- configs/peer/validator.wasm | Bin 765273 -> 730994 bytes core/Cargo.toml | 2 +- core/benches/kura.rs | 53 +- core/benches/validation.rs | 88 +- core/src/block.rs | 1184 +++++++++-------- core/src/block_sync.rs | 53 +- core/src/kura.rs | 50 +- core/src/lib.rs | 9 +- core/src/queue.rs | 86 +- core/src/smartcontracts/isi/block.rs | 22 +- core/src/smartcontracts/isi/query.rs | 101 +- core/src/smartcontracts/isi/tx.rs | 2 +- core/src/smartcontracts/wasm.rs | 27 +- core/src/sumeragi/main_loop.rs | 630 +++++---- core/src/sumeragi/message.rs | 132 +- core/src/sumeragi/mod.rs | 73 +- core/src/sumeragi/network_topology.rs | 79 +- core/src/sumeragi/view_change.rs | 105 +- core/src/tx.rs | 131 +- core/src/validator.rs | 8 +- core/src/wsv.rs | 140 +- core/test_network/src/lib.rs | 4 +- crypto/src/hash.rs | 18 +- crypto/src/lib.rs | 13 +- crypto/src/merkle.rs | 4 +- crypto/src/signature.rs | 153 +-- data_model/Cargo.toml | 2 +- data_model/derive/src/filter.rs | 4 +- data_model/derive/src/lib.rs | 12 +- data_model/derive/src/model.rs | 16 +- data_model/src/account.rs | 18 +- data_model/src/block.rs | 521 ++++---- data_model/src/domain.rs | 17 +- data_model/src/events/data/events.rs | 2 +- data_model/src/events/data/filters.rs | 10 +- data_model/src/events/data/mod.rs | 2 +- data_model/src/events/execute_trigger.rs | 2 +- data_model/src/events/mod.rs | 52 +- data_model/src/events/pipeline.rs | 8 +- data_model/src/events/time.rs | 8 +- data_model/src/expression.rs | 2 +- data_model/src/lib.rs | 362 +++-- data_model/src/metadata.rs | 8 +- data_model/src/predicate.rs | 6 +- data_model/src/query.rs | 44 +- data_model/src/transaction.rs | 557 +------- data_model/src/trigger.rs | 10 +- data_model/src/validator.rs | 3 +- default_validator/src/lib.rs | 2 +- docs/source/references/config.md | 10 +- docs/source/references/schema.json | 274 ++-- genesis/Cargo.toml | 7 +- genesis/src/lib.rs | 102 +- schema/gen/Cargo.toml | 2 +- schema/gen/src/lib.rs | 49 +- schema/src/lib.rs | 22 + tools/kura_inspector/src/main.rs | 3 +- .../parity_scale_decoder/samples/trigger.bin | Bin 80 -> 80 bytes tools/parity_scale_decoder/src/main.rs | 9 +- 84 files changed, 2629 insertions(+), 2971 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a4d604311d..90b0f3fbe40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2347,10 +2347,13 @@ dependencies = [ "iroha_crypto", "iroha_data_model", "iroha_logger", + "iroha_macro", "iroha_primitives", "iroha_schema", + "once_cell", "serde", "serde_json", + "thiserror", "tracing", ] diff --git a/cli/src/event.rs b/cli/src/event.rs index 601ab41bb6f..79d085f4a83 100644 --- a/cli/src/event.rs +++ b/cli/src/event.rs @@ -58,7 +58,8 @@ impl Consumer { #[iroha_futures::telemetry_future] pub async fn new(mut stream: WebSocket) -> Result { let subscription_request: VersionedEventSubscriptionRequest = stream.recv().await?; - let EventSubscriptionRequest(filter) = subscription_request.into_v1(); + let VersionedEventSubscriptionRequest::V1(EventSubscriptionRequest(filter)) = + subscription_request; Ok(Consumer { stream, filter }) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ba5b2a4eb58..d944b7bfeae 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -9,7 +9,7 @@ clippy::std_instead_of_core, clippy::std_instead_of_alloc )] -use core::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::AtomicBool; use std::sync::Arc; use color_eyre::eyre::{eyre, Result, WrapErr}; @@ -30,7 +30,7 @@ use iroha_core::{ IrohaNetwork, }; use iroha_data_model::prelude::*; -use iroha_genesis::GenesisNetwork; +use iroha_genesis::{GenesisNetwork, GENESIS_ACCOUNT_ID, GENESIS_DOMAIN_ID}; use iroha_logger::prelude::span; use tokio::{ signal, @@ -149,15 +149,22 @@ impl NetworkRelay { use iroha_core::NetworkMessage::*; #[cfg(debug_assertions)] - if self.freeze_status.load(Ordering::SeqCst) { + if self + .freeze_status + .load(core::sync::atomic::Ordering::SeqCst) + { return; } match msg { SumeragiPacket(data) => { - self.sumeragi.incoming_message(data.into_v1()); + let iroha_core::sumeragi::message::VersionedPacket::V1(data) = *data; + self.sumeragi.incoming_message(data); + } + BlockSync(data) => { + let iroha_core::block_sync::message::VersionedMessage::V1(data) = *data; + self.block_sync.message(data).await } - BlockSync(data) => self.block_sync.message(data.into_v1()).await, Health => {} } } @@ -250,7 +257,7 @@ impl Iroha { let wsv_clone = wsv.clone(); transaction_validator - .validate_every(genesis.iter().cloned(), &wsv_clone) + .validate_every::(genesis.iter().cloned(), &wsv_clone) .wrap_err("Transaction validation failed in genesis block")?; span.exit(); } @@ -374,6 +381,7 @@ impl Iroha { if let Some((substrate_telemetry, telemetry_future)) = telemetry { #[cfg(feature = "dev-telemetry")] { + // TODO: JoinHandle is just dropped here. It should be joined iroha_telemetry::dev::start(&config.telemetry, telemetry_future) .await .wrap_err("Failed to setup telemetry for futures")?; @@ -426,17 +434,15 @@ impl Iroha { } fn genesis_account(public_key: iroha_crypto::PublicKey) -> Account { - let genesis_account_id = AccountId::genesis(); - Account::new(genesis_account_id.clone(), [public_key]).build(genesis_account_id) + Account::new(GENESIS_ACCOUNT_ID.clone(), [public_key]).build(GENESIS_ACCOUNT_ID.clone()) } fn genesis_domain(configuration: &Configuration) -> Domain { - let genesis_account_id = AccountId::genesis(); let account_public_key = &configuration.genesis.account_public_key; - let mut domain = Domain::new(DomainId::genesis()).build(genesis_account_id); + let mut domain = Domain::new(GENESIS_DOMAIN_ID.clone()).build(GENESIS_ACCOUNT_ID.clone()); domain.accounts.insert( - ::Id::genesis(), + GENESIS_ACCOUNT_ID.clone(), genesis_account(account_public_key.clone()), ); diff --git a/cli/src/torii/mod.rs b/cli/src/torii/mod.rs index 777a6aa59f3..4b12a455c39 100644 --- a/cli/src/torii/mod.rs +++ b/cli/src/torii/mod.rs @@ -50,7 +50,7 @@ pub enum Error { Query(#[from] iroha_data_model::query::error::QueryExecutionFailure), /// Failed to accept transaction #[error("Failed to accept transaction: {0}")] - AcceptTransaction(#[from] iroha_data_model::transaction::error::AcceptTransactionFailure), + AcceptTransaction(#[from] iroha_genesis::AcceptTransactionFailure), /// Error while getting or setting configuration #[error("Configuration error: {0}")] Config(#[source] eyre::Report), diff --git a/cli/src/torii/routing.rs b/cli/src/torii/routing.rs index 89c75260eef..1287bf4a077 100644 --- a/cli/src/torii/routing.rs +++ b/cli/src/torii/routing.rs @@ -19,18 +19,16 @@ use iroha_config::{ use iroha_core::smartcontracts::isi::query::ValidQueryRequest; use iroha_crypto::SignatureOf; use iroha_data_model::{ - block::{ - stream::{ - BlockMessage, BlockSubscriptionRequest, VersionedBlockMessage, - VersionedBlockSubscriptionRequest, - }, - VersionedCommittedBlock, + block::stream::{ + BlockMessage, BlockSubscriptionRequest, VersionedBlockMessage, + VersionedBlockSubscriptionRequest, }, predicate::PredicateBox, prelude::*, query, query::error::QueryExecutionFailure, }; +use iroha_genesis::AcceptedTransaction; #[cfg(feature = "telemetry")] use iroha_telemetry::metrics::Status; use pagination::{paginate, Paginate}; @@ -75,7 +73,11 @@ impl VerifiedQuery { ))); } wsv.validator_view() - .validate(wsv, &self.payload.account_id, self.payload.query.clone()) + .validate( + wsv, + &self.payload.account_id, + self.payload.query.clone().into(), + ) .map_err(|err| QueryExecutionFailure::Permission(err.to_string()))?; Ok(( ValidQueryRequest::new(self.payload.query), @@ -106,12 +108,9 @@ pub(crate) async fn handle_instructions( sumeragi: Arc, transaction: VersionedSignedTransaction, ) -> Result { - let transaction: SignedTransaction = transaction.into_v1(); let transaction = AcceptedTransaction::accept::(transaction, &iroha_cfg.sumeragi.transaction_limits) - .map_err(Error::AcceptTransaction)? - .into(); - #[allow(clippy::map_err_ignore)] + .map_err(|(_tx, error)| Error::AcceptTransaction(error))?; queue .push(transaction, &sumeragi.wsv_mutex_access()) .map_err(|queue::Failure { tx, err }| { @@ -233,11 +232,9 @@ async fn handle_pending_transactions( queue .all_transactions(&sumeragi.wsv_mutex_access()) .into_iter() - .map(VersionedAcceptedTransaction::into_v1) - .map(SignedTransaction::from) + .map(VersionedSignedTransaction::from) .paginate(pagination) - .collect::() - .into(), + .collect::(), )) } @@ -283,7 +280,8 @@ async fn handle_post_configuration( #[iroha_futures::telemetry_future] async fn handle_blocks_stream(kura: Arc, mut stream: WebSocket) -> eyre::Result<()> { let subscription_request: VersionedBlockSubscriptionRequest = stream.recv().await?; - let BlockSubscriptionRequest(mut from_height) = subscription_request.into_v1(); + let VersionedBlockSubscriptionRequest::V1(BlockSubscriptionRequest(mut from_height)) = + subscription_request; let mut interval = tokio::time::interval(std::time::Duration::from_millis(10)); loop { @@ -313,7 +311,7 @@ async fn handle_blocks_stream(kura: Arc, mut stream: WebSocket) -> eyre::R stream // TODO: to avoid clone `VersionedBlockMessage` could be split into sending and receiving parts .send(VersionedBlockMessage::from( - BlockMessage(VersionedCommittedBlock::clone(&block)), + BlockMessage(VersionedSignedBlock::clone(&block)), )) .await?; from_height += 1; @@ -415,7 +413,6 @@ mod subscription { async fn handle_version(sumeragi: Arc) -> Json { use iroha_version::Version; - #[allow(clippy::expect_used)] let string = sumeragi .wsv_mutex_access() .latest_block_ref() @@ -490,7 +487,6 @@ impl Torii { } } - #[allow(opaque_hidden_inferred_bound)] #[cfg(feature = "telemetry")] /// Helper function to create router. This router can tested without starting up an HTTP server fn create_telemetry_router( @@ -528,7 +524,6 @@ impl Torii { } /// Helper function to create router. This router can tested without starting up an HTTP server - #[allow(opaque_hidden_inferred_bound)] pub(crate) fn create_api_router( &self, ) -> impl warp::Filter + Clone + Send { diff --git a/client/Cargo.toml b/client/Cargo.toml index 7d0ab4d6f96..9111770a7ae 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -46,7 +46,7 @@ futures-util = "0.3.25" [dev-dependencies] iroha_wasm_builder = { version = "=2.0.0-pre-rc.13", path = "../wasm_builder" } -# TODO: These three activate `transparent_api` but client should never activate this feature. +# TODO: These three activate `transparent-api` but client should never activate this feature. # Additionally there is a dependency on iroha_core in dev-dependencies in telemetry/derive # Hopefully, once the integration tests migration is finished these can be removed iroha = { path = "../cli", features = ["dev-telemetry", "telemetry"] } diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index 59aeaff31ca..a206227d036 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -126,8 +126,11 @@ impl Config { let block = blocks .next() .expect("The block is not yet in WSV. Need more sleep?"); - let block = block.as_v1(); - (block.transactions.len(), block.rejected_transactions.len()) + let payload = block.payload(); + ( + payload.transactions().len(), + payload.rejected_transactions.len(), + ) }) .fold((0, 0), |acc, pair| (acc.0 + pair.0, acc.1 + pair.1)); #[allow(clippy::float_arithmetic, clippy::cast_precision_loss)] @@ -182,7 +185,7 @@ impl MeasurerUnit { 100_000, ) .sign(keypair)?; - self.client.submit_transaction_blocking(grant_tx)?; + self.client.submit_transaction_blocking(&grant_tx)?; let mint_a_rose = MintBox::new(1_u32, asset_id); self.client.submit_blocking(mint_a_rose)?; @@ -236,7 +239,7 @@ impl MeasurerUnit { let transaction = submitter .sign_transaction(transaction) .expect("Failed to sign transaction"); - if let Err(error) = submitter.submit_transaction(transaction) { + if let Err(error) = submitter.submit_transaction(&transaction) { iroha_logger::error!(?error, "Failed to submit transaction"); } nonce = nonce.wrapping_add(1); diff --git a/client/examples/tutorial.rs b/client/examples/tutorial.rs index 2c66f458165..ee4d7ce79c1 100644 --- a/client/examples/tutorial.rs +++ b/client/examples/tutorial.rs @@ -83,7 +83,7 @@ fn domain_registration_test(config: &Configuration) -> Result<(), Error> { // #region domain_register_example_submit_tx // Submit a prepared domain registration transaction iroha_client - .submit_transaction(tx) + .submit_transaction(&tx) .wrap_err("Failed to submit transaction")?; // #endregion domain_register_example_submit_tx @@ -152,7 +152,7 @@ fn account_registration_test(config: &Configuration) -> Result<(), Error> { // #region register_account_submit_tx // Submit a prepared account registration transaction - iroha_client.submit_transaction(tx)?; + iroha_client.submit_transaction(&tx)?; // #endregion register_account_submit_tx // Finish the test successfully diff --git a/client/src/client.rs b/client/src/client.rs index eccce391a73..12580e5f53a 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -14,7 +14,7 @@ use http_default::{AsyncWebSocketStream, WebSocketStream}; use iroha_config::{client::Configuration, torii::uri, GetConfiguration, PostConfiguration}; use iroha_crypto::{HashOf, KeyPair}; use iroha_data_model::{ - block::VersionedCommittedBlock, predicate::PredicateBox, prelude::*, + block::VersionedSignedBlock, predicate::PredicateBox, prelude::*, query::error::QueryExecutionFailure, transaction::TransactionPayload, }; use iroha_logger::prelude::*; @@ -328,7 +328,7 @@ impl Client { &self, instructions: impl Into, metadata: UnlimitedMetadata, - ) -> Result { + ) -> Result { let transaction = TransactionBuilder::new( self.account_id.clone(), instructions, @@ -352,7 +352,10 @@ impl Client { /// /// # Errors /// Fails if signature generation fails - pub fn sign_transaction(&self, transaction: Tx) -> Result { + pub fn sign_transaction( + &self, + transaction: Tx, + ) -> Result { transaction .sign(self.key_pair.clone()) .wrap_err("Failed to sign transaction") @@ -376,7 +379,7 @@ impl Client { pub fn submit( &self, instruction: impl Into + Debug, - ) -> Result> { + ) -> Result> { let isi = instruction.into(); self.submit_all([isi]) } @@ -389,7 +392,7 @@ impl Client { pub fn submit_all( &self, instructions: impl IntoIterator, - ) -> Result> { + ) -> Result> { self.submit_all_with_metadata(instructions, UnlimitedMetadata::new()) } @@ -403,7 +406,7 @@ impl Client { &self, instruction: InstructionBox, metadata: UnlimitedMetadata, - ) -> Result> { + ) -> Result> { self.submit_all_with_metadata([instruction], metadata) } @@ -417,8 +420,8 @@ impl Client { &self, instructions: impl IntoIterator, metadata: UnlimitedMetadata, - ) -> Result> { - self.submit_transaction(self.build_transaction(instructions, metadata)?) + ) -> Result> { + self.submit_transaction(&self.build_transaction(instructions, metadata)?) } /// Submit a prebuilt transaction. @@ -428,8 +431,8 @@ impl Client { /// Fails if sending transaction to peer fails or if it response with error pub fn submit_transaction( &self, - transaction: SignedTransaction, - ) -> Result> { + transaction: &VersionedSignedTransaction, + ) -> Result> { iroha_logger::trace!(tx=?transaction, "Submitting"); let (req, hash, resp_handler) = self.prepare_transaction_request::(transaction)?; @@ -448,8 +451,8 @@ impl Client { /// Fails if sending a transaction to a peer fails or there is an error in the response pub fn submit_transaction_blocking( &self, - transaction: SignedTransaction, - ) -> Result> { + transaction: &VersionedSignedTransaction, + ) -> Result> { let (init_sender, init_receiver) = tokio::sync::oneshot::channel(); let hash = transaction.hash(); @@ -478,8 +481,8 @@ impl Client { fn listen_for_tx_confirmation( &self, init_sender: tokio::sync::oneshot::Sender, - hash: HashOf, - ) -> Result> { + hash: HashOf, + ) -> Result> { let deadline = tokio::time::Instant::now() + self.transaction_status_timeout; let rt = tokio::runtime::Builder::new_current_thread() .enable_all() @@ -501,22 +504,19 @@ impl Client { event_iterator_result? }; - let result = tokio::time::timeout_at( + tokio::time::timeout_at( deadline, - Self::listen_for_tx_confirmation_loop(&mut event_iterator, hash), + Self::listen_for_tx_confirmation_loop(&mut event_iterator), ) .await .map_err(Into::into) - .and_then(std::convert::identity); + .and_then(std::convert::identity)?; event_iterator.close().await; - result + Ok(hash) }) } - async fn listen_for_tx_confirmation_loop( - event_iterator: &mut AsyncEventStream, - hash: HashOf, - ) -> Result> { + async fn listen_for_tx_confirmation_loop(event_iterator: &mut AsyncEventStream) -> Result<()> { while let Some(event) = event_iterator.next().await { if let Event::Pipeline(this_event) = event? { match this_event.status() { @@ -524,7 +524,7 @@ impl Client { PipelineStatus::Rejected(reason) => { return Err(reason.clone()).wrap_err("Transaction rejected") } - PipelineStatus::Committed => return Ok(hash.transmute()), + PipelineStatus::Committed => return Ok(()), } } } @@ -545,14 +545,9 @@ impl Client { /// Fails if transaction check fails pub fn prepare_transaction_request( &self, - transaction: SignedTransaction, - ) -> Result<( - B, - HashOf, - TransactionResponseHandler, - )> { + transaction: &VersionedSignedTransaction, + ) -> Result<(B, HashOf, TransactionResponseHandler)> { transaction.check_limits(&self.transaction_limits)?; - let transaction: VersionedSignedTransaction = transaction.into(); let hash = transaction.hash(); let transaction_bytes: Vec = transaction.encode_versioned(); @@ -576,7 +571,7 @@ impl Client { pub fn submit_blocking( &self, instruction: impl Into, - ) -> Result> { + ) -> Result> { self.submit_all_blocking(vec![instruction.into()]) } @@ -588,7 +583,7 @@ impl Client { pub fn submit_all_blocking( &self, instructions: impl IntoIterator, - ) -> Result> { + ) -> Result> { self.submit_all_blocking_with_metadata(instructions, UnlimitedMetadata::new()) } @@ -602,7 +597,7 @@ impl Client { &self, instruction: impl Into, metadata: UnlimitedMetadata, - ) -> Result> { + ) -> Result> { self.submit_all_blocking_with_metadata(vec![instruction.into()], metadata) } @@ -616,9 +611,9 @@ impl Client { &self, instructions: impl IntoIterator, metadata: UnlimitedMetadata, - ) -> Result> { + ) -> Result> { let transaction = self.build_transaction(instructions, metadata)?; - self.submit_transaction_blocking(transaction) + self.submit_transaction_blocking(&transaction) } /// Lower-level Query API entry point. Prepares an http-request and returns it with an http-response handler. @@ -914,7 +909,7 @@ impl Client { pub fn listen_for_blocks( &self, height: u64, - ) -> Result>> { + ) -> Result>> { blocks_api::BlockIterator::new(self.blocks_handler(height)?) } @@ -959,11 +954,11 @@ impl Client { /// - if subscribing to websocket fails pub fn get_original_transaction_with_pagination( &self, - transaction: &SignedTransaction, + transaction: &VersionedSignedTransaction, retry_count: u32, retry_in: Duration, pagination: Pagination, - ) -> Result> { + ) -> Result> { let pagination: Vec<_> = pagination.into(); for _ in 0..retry_count { let response = DefaultRequestBuilder::new( @@ -1009,10 +1004,10 @@ impl Client { /// - if sending request fails pub fn get_original_transaction( &self, - transaction: &SignedTransaction, + transaction: &VersionedSignedTransaction, retry_count: u32, retry_in: Duration, - ) -> Result> { + ) -> Result> { self.get_original_transaction_with_pagination( transaction, retry_count, @@ -1347,8 +1342,8 @@ pub mod events_api { type Event = iroha_data_model::prelude::Event; fn message(&self, message: Vec) -> Result { - let event_socket_message = - VersionedEventMessage::decode_all_versioned(&message)?.into_v1(); + let VersionedEventMessage::V1(event_socket_message) = + VersionedEventMessage::decode_all_versioned(&message)?; Ok(event_socket_message.into()) } } @@ -1426,10 +1421,11 @@ mod blocks_api { pub struct Events; impl FlowEvents for Events { - type Event = iroha_data_model::block::VersionedCommittedBlock; + type Event = iroha_data_model::block::VersionedSignedBlock; fn message(&self, message: Vec) -> Result { - let block_msg = VersionedBlockMessage::decode_all_versioned(&message)?.into_v1(); + let VersionedBlockMessage::V1(block_msg) = + VersionedBlockMessage::decode_all_versioned(&message)?; Ok(block_msg.into()) } } @@ -1498,7 +1494,7 @@ pub mod asset { pub mod block { //! Module with queries related to blocks - use iroha_crypto::Hash; + use iroha_data_model::block::BlockPayload; use super::*; @@ -1513,7 +1509,9 @@ pub mod block { } /// Construct a query to find block header by hash - pub fn header_by_hash(hash: impl Into>) -> FindBlockHeaderByHash { + pub fn header_by_hash( + hash: impl Into>>, + ) -> FindBlockHeaderByHash { FindBlockHeaderByHash::new(hash) } } @@ -1535,7 +1533,6 @@ pub mod domain { pub mod transaction { //! Module with queries for transactions - use iroha_crypto::Hash; use super::*; @@ -1552,7 +1549,9 @@ pub mod transaction { } /// Construct a query to retrieve transaction by hash - pub fn by_hash(hash: impl Into>) -> FindTransactionByHash { + pub fn by_hash( + hash: impl Into>>, + ) -> FindTransactionByHash { FindTransactionByHash::new(hash) } } @@ -1664,11 +1663,11 @@ mod tests { .unwrap() }; let tx1 = build_transaction(); - let mut tx2 = build_transaction(); + let VersionedSignedTransaction::V1(mut tx2) = build_transaction(); - tx2.payload.creation_time = tx1.payload.creation_time; + tx2.payload.creation_time_ms = tx1.payload().creation_time_ms; assert_ne!(tx1.hash(), tx2.hash()); - tx2.payload.nonce = tx1.payload.nonce; + tx2.payload.nonce = tx1.payload().nonce; assert_eq!(tx1.hash(), tx2.hash()); } diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 6c8c8f858d1..c9a085c02df 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -99,7 +99,7 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount() -> ); let instructions: Vec = vec![create_asset.into(), mint.into()]; let tx = test_client.build_transaction(instructions, metadata)?; - test_client.submit_transaction(tx)?; + test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { result.iter().any(|asset| { asset.id().definition_id == asset_definition_id @@ -130,7 +130,7 @@ fn client_add_big_asset_quantity_to_existing_asset_should_increase_asset_amount( ); let instructions: Vec = vec![create_asset.into(), mint.into()]; let tx = test_client.build_transaction(instructions, metadata)?; - test_client.submit_transaction(tx)?; + test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { result.iter().any(|asset| { asset.id().definition_id == asset_definition_id @@ -162,7 +162,7 @@ fn client_add_asset_with_decimal_should_increase_asset_amount() -> Result<()> { ); let instructions: Vec = vec![create_asset.into(), mint.into()]; let tx = test_client.build_transaction(instructions, metadata)?; - test_client.submit_transaction(tx)?; + test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id.clone()), |result| { result.iter().any(|asset| { asset.id().definition_id == asset_definition_id @@ -271,7 +271,7 @@ fn find_rate_and_make_exchange_isi_should_succeed() { .expect("Failed to sign seller transaction"); test_client - .submit_transaction_blocking(grant_asset_transfer_tx) + .submit_transaction_blocking(&grant_asset_transfer_tx) .expect(&format!( "Failed to grant permission alice to transfer {}.", asset_id diff --git a/client/tests/integration/burn_public_keys.rs b/client/tests/integration/burn_public_keys.rs index 6a1ce05546b..508213d6167 100644 --- a/client/tests/integration/burn_public_keys.rs +++ b/client/tests/integration/burn_public_keys.rs @@ -21,9 +21,8 @@ fn submit_and_get( }; let hash = tx.hash(); - let _ = client.submit_transaction_blocking(tx); - - client.request(transaction::by_hash(*hash)).unwrap() + let _ = client.submit_transaction_blocking(&tx); + client.request(transaction::by_hash(hash)).unwrap().tx_value } fn account_keys_count(client: &mut Client, account_id: AccountId) -> usize { diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index 03b2a43e7ac..516ef4f38aa 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -117,7 +117,7 @@ fn transaction_execution_should_produce_events( let transaction = client .build_transaction(executable, UnlimitedMetadata::new()) .unwrap(); - client.submit_transaction_blocking(transaction)?; + client.submit_transaction_blocking(&transaction)?; // assertion for i in 0..4_usize { diff --git a/client/tests/integration/events/pipeline.rs b/client/tests/integration/events/pipeline.rs index 6024150765b..e76409c8c25 100644 --- a/client/tests/integration/events/pipeline.rs +++ b/client/tests/integration/events/pipeline.rs @@ -54,7 +54,7 @@ fn test_with_instruction_and_status_and_port( handles.push(handle_validated); } // When - submitter.submit_transaction(transaction)?; + submitter.submit_transaction(&transaction)?; thread::sleep(pipeline_time * 2); // Then for handle in handles { @@ -66,7 +66,7 @@ fn test_with_instruction_and_status_and_port( #[derive(Clone)] struct Checker { listener: iroha_client::client::Client, - hash: iroha_crypto::HashOf, + hash: iroha_crypto::HashOf, } impl Checker { @@ -78,7 +78,7 @@ impl Checker { PipelineEventFilter::new() .entity_kind(PipelineEntityKind::Transaction) .status_kind(status_kind) - .hash(*self.hash), + .hash(self.hash.into()), )) .expect("Failed to create event iterator."); let event_result = event_iterator.next().expect("Stream closed"); diff --git a/client/tests/integration/multisignature_transaction.rs b/client/tests/integration/multisignature_transaction.rs index 317a3bd2e82..4cc21bb990d 100644 --- a/client/tests/integration/multisignature_transaction.rs +++ b/client/tests/integration/multisignature_transaction.rs @@ -67,7 +67,7 @@ fn multisignature_transactions_should_wait_for_all_signatures() { .expect("Failed to create transaction."); iroha_client .submit_transaction( - iroha_client + &iroha_client .sign_transaction(transaction) .expect("Failed to sign transaction."), ) @@ -101,7 +101,7 @@ fn multisignature_transactions_should_wait_for_all_signatures() { .expect("Found no pending transaction for this account."); iroha_client_2 .submit_transaction( - iroha_client_2 + &iroha_client_2 .sign_transaction(transaction) .expect("Failed to sign transaction."), ) diff --git a/client/tests/integration/non_mintable.rs b/client/tests/integration/non_mintable.rs index b74a18b831b..15a47b9178a 100644 --- a/client/tests/integration/non_mintable.rs +++ b/client/tests/integration/non_mintable.rs @@ -32,7 +32,7 @@ fn non_mintable_asset_can_be_minted_once_but_not_twice() -> Result<()> { let tx = test_client.build_transaction(instructions, metadata)?; // We can register and mint the non-mintable token - test_client.submit_transaction(tx)?; + test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id.clone()), |result| { result.iter().any(|asset| { asset.id().definition_id == asset_definition_id diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index b9c504c71c0..9c5cbe0dd5c 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -238,7 +238,7 @@ fn permissions_differ_not_only_by_names() { .sign(mouse_keypair.clone()) .expect("Failed to sign mouse transaction"); client - .submit_transaction_blocking(grant_hats_access_tx) + .submit_transaction_blocking(&grant_hats_access_tx) .expect("Failed grant permission to modify Mouse's hats"); // Checking that Alice can modify Mouse's hats ... @@ -278,7 +278,7 @@ fn permissions_differ_not_only_by_names() { .expect("Failed to sign mouse transaction"); client - .submit_transaction_blocking(grant_shoes_access_tx) + .submit_transaction_blocking(&grant_shoes_access_tx) .expect("Failed grant permission to modify Mouse's shoes"); // Checking that Alice can modify Mouse's shoes diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 45ba5c1d2fa..205bcc0618d 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -76,7 +76,7 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { let grant_role = GrantBox::new(role_id.clone(), alice_id.clone()); let grant_role_tx = TransactionBuilder::new(mouse_id.clone(), vec![grant_role.into()], 100_000) .sign(mouse_key_pair)?; - test_client.submit_transaction_blocking(grant_role_tx)?; + test_client.submit_transaction_blocking(&grant_role_tx)?; // Alice modifies Mouse's metadata let set_key_value = SetKeyValueBox::new( diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index 768d287b98f..5407eabc8c1 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -357,7 +357,7 @@ fn trigger_in_genesis_using_base64() -> Result<()> { // Registering trigger in genesis let mut genesis = GenesisNetwork::test(true).expect("Expected genesis"); - match &mut genesis.transactions[0].as_mut_v1().payload.instructions { + match &mut genesis.transactions[0].payload.instructions { Executable::Instructions(instructions) => { instructions.push(RegisterBox::new(trigger).into()); } diff --git a/client/tests/integration/tx_history.rs b/client/tests/integration/tx_history.rs index fe840d55520..7d018183f16 100644 --- a/client/tests/integration/tx_history.rs +++ b/client/tests/integration/tx_history.rs @@ -51,7 +51,7 @@ fn client_has_rejected_and_acepted_txs_should_return_tx_history() { .build_transaction(instructions, UnlimitedMetadata::new()) .expect("Failed to create transaction"); iroha_client - .submit_transaction(transaction) + .submit_transaction(&transaction) .expect("Failed to submit transaction"); } thread::sleep(pipeline_time * 5); @@ -69,7 +69,7 @@ fn client_has_rejected_and_acepted_txs_should_return_tx_history() { for tx in &transactions { assert_eq!(&tx.payload().account_id, &account_id); //check sorted - assert!(tx.payload().creation_time >= prev_creation_time); - prev_creation_time = tx.payload().creation_time; + assert!(tx.payload().creation_time_ms >= prev_creation_time); + prev_creation_time = tx.payload().creation_time_ms; } } diff --git a/client/tests/integration/unstable_network.rs b/client/tests/integration/unstable_network.rs index df1ebaecd44..e11e087f4d4 100644 --- a/client/tests/integration/unstable_network.rs +++ b/client/tests/integration/unstable_network.rs @@ -1,4 +1,5 @@ #![allow(clippy::restriction)] +#![cfg(debug_assertions)] use core::sync::atomic::Ordering; use std::thread; diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index ec124b77a0b..c21890dc1bc 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -34,7 +34,7 @@ fn validator_upgrade_should_work() -> Result<()> { ) .sign(admin_keypair.clone())?; let _ = client - .submit_transaction_blocking(transfer_rose_tx) + .submit_transaction_blocking(&transfer_rose_tx) .expect_err("Should fail"); // Upgrade Validator @@ -66,7 +66,7 @@ fn validator_upgrade_should_work() -> Result<()> { TransactionBuilder::new(admin_id, vec![transfer_alice_rose.into()], 100_000) .sign(admin_keypair)?; client - .submit_transaction_blocking(transfer_rose_tx) + .submit_transaction_blocking(&transfer_rose_tx) .expect("Should succeed"); Ok(()) diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index db315ebea33..628b909def1 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -217,7 +217,7 @@ pub fn submit( #[cfg(not(debug_assertions))] let err_msg = "Failed to submit transaction."; let hash = iroha_client - .submit_transaction_blocking(tx) + .submit_transaction_blocking(&tx) .wrap_err(err_msg)?; Ok(Box::new(hash)) } @@ -542,7 +542,7 @@ mod account { impl RunArgs for ListPermissions { fn run(self, cfg: &ClientConfiguration) -> Result> { let client = Client::new(cfg)?; - let find_all_permissions = FindPermissionTokensByAccountId { id: self.id.into() }; + let find_all_permissions = FindPermissionTokensByAccountId::new(self.id); let permissions = client .request(find_all_permissions) .wrap_err("Failed to get all account permissions")?; diff --git a/config/src/client.rs b/config/src/client.rs index 6e95fec8c50..31ff6ef36ca 100644 --- a/config/src/client.rs +++ b/config/src/client.rs @@ -6,10 +6,12 @@ use derive_more::Display; use eyre::{Result, WrapErr}; use iroha_config_base::derive::{Documented, Error as ConfigError, Proxy}; use iroha_crypto::prelude::*; -use iroha_data_model::{prelude::*, transaction}; +use iroha_data_model::prelude::*; use iroha_primitives::small::SmallStr; use serde::{Deserialize, Serialize}; +use crate::sumeragi::{DEFAULT_MAX_INSTRUCTION_NUMBER, DEFAULT_MAX_WASM_SIZE_BYTES}; + const DEFAULT_TRANSACTION_TIME_TO_LIVE_MS: u64 = 100_000; const DEFAULT_TRANSACTION_STATUS_TIMEOUT_MS: u64 = 15_000; const DEFAULT_ADD_TRANSACTION_NONCE: bool = false; @@ -99,8 +101,8 @@ impl Default for ConfigurationProxy { transaction_time_to_live_ms: Some(DEFAULT_TRANSACTION_TIME_TO_LIVE_MS), transaction_status_timeout_ms: Some(DEFAULT_TRANSACTION_STATUS_TIMEOUT_MS), transaction_limits: Some(TransactionLimits::new( - transaction::DEFAULT_MAX_INSTRUCTION_NUMBER, - transaction::DEFAULT_MAX_WASM_SIZE_BYTES, + DEFAULT_MAX_INSTRUCTION_NUMBER, + DEFAULT_MAX_WASM_SIZE_BYTES, )), add_transaction_nonce: Some(DEFAULT_ADD_TRANSACTION_NONCE), } @@ -271,8 +273,8 @@ mod tests { transaction_time_to_live_ms in prop::option::of(Just(DEFAULT_TRANSACTION_TIME_TO_LIVE_MS)), transaction_status_timeout_ms in prop::option::of(Just(DEFAULT_TRANSACTION_STATUS_TIMEOUT_MS)), transaction_limits in prop::option::of(Just(TransactionLimits::new( - transaction::DEFAULT_MAX_INSTRUCTION_NUMBER, - transaction::DEFAULT_MAX_WASM_SIZE_BYTES, + DEFAULT_MAX_INSTRUCTION_NUMBER, + DEFAULT_MAX_WASM_SIZE_BYTES, ))), add_transaction_nonce in prop::option::of(Just(DEFAULT_ADD_TRANSACTION_NONCE)), ) diff --git a/config/src/sumeragi.rs b/config/src/sumeragi.rs index b33f87f7d20..f712e53e7f3 100644 --- a/config/src/sumeragi.rs +++ b/config/src/sumeragi.rs @@ -5,7 +5,7 @@ use std::{collections::HashSet, fmt::Debug, fs::File, io::BufReader, path::Path} use eyre::{Result, WrapErr}; use iroha_config_base::derive::{view, Documented, Proxy}; use iroha_crypto::prelude::*; -use iroha_data_model::{prelude::*, transaction}; +use iroha_data_model::prelude::*; use serde::{Deserialize, Serialize}; /// Default Amount of time peer waits for transactions before creating a block. @@ -15,6 +15,10 @@ pub const DEFAULT_COMMIT_TIME_LIMIT_MS: u64 = 4000; const DEFAULT_ACTOR_CHANNEL_CAPACITY: u32 = 100; const DEFAULT_GOSSIP_PERIOD_MS: u64 = 1000; const DEFAULT_GOSSIP_BATCH_SIZE: u32 = 500; +/// Default maximum number of instructions and expressions per transaction +pub const DEFAULT_MAX_INSTRUCTION_NUMBER: u64 = 2_u64.pow(12); +/// Default maximum number of instructions and expressions per transaction +pub const DEFAULT_MAX_WASM_SIZE_BYTES: u64 = 2_u64.pow(22); // 4 MiB /// Default estimation of consensus duration #[allow(clippy::integer_division)] @@ -30,21 +34,24 @@ view! { #[serde(rename_all = "UPPERCASE")] #[config(env_prefix = "SUMERAGI_")] pub struct Configuration { + /// Current Peer Identification. + pub peer_id: PeerId, /// The key pair consisting of a private and a public key. //TODO: consider putting a `#[serde(skip)]` on the proxy struct here #[view(ignore)] pub key_pair: KeyPair, - /// Current Peer Identification. - pub peer_id: PeerId, - /// The period of time a peer waits for the `CreatedBlock` message after getting a `TransactionReceipt` - pub block_time_ms: u64, - /// Optional list of predefined trusted peers. + /// List of predefined trusted peers. pub trusted_peers: TrustedPeers, - /// The period of time a peer waits for `CommitMessage` from the proxy tail. + /// Time a peer waits to produce a new block since the beginning of the voting round + /// if it cannot satisfy `max_transactions_in_block`. Unless there is no transactions, + /// after this time has elapsed the block will be committed regardless. + pub block_time_ms: u64, + /// Time a peer waits for the block to be committed since the beginning of the voting round pub commit_time_limit_ms: u64, /// The limits to which transactions must adhere pub transaction_limits: TransactionLimits, /// Buffer capacity of actor's MPSC channel + #[deprecated(since = "2.0.0-pre-rc.13", note = "Will be removed in future versions")] pub actor_channel_capacity: u32, /// Maximum number of transactions in tx gossip batch message. While configuring this, pay attention to `p2p` max message size. pub gossip_batch_size: u32, @@ -66,8 +73,8 @@ impl Default for ConfigurationProxy { block_time_ms: Some(DEFAULT_BLOCK_TIME_MS), commit_time_limit_ms: Some(DEFAULT_COMMIT_TIME_LIMIT_MS), transaction_limits: Some(TransactionLimits::new( - transaction::DEFAULT_MAX_INSTRUCTION_NUMBER, - transaction::DEFAULT_MAX_WASM_SIZE_BYTES, + DEFAULT_MAX_INSTRUCTION_NUMBER, + DEFAULT_MAX_WASM_SIZE_BYTES, )), actor_channel_capacity: Some(DEFAULT_ACTOR_CHANNEL_CAPACITY), gossip_batch_size: Some(DEFAULT_GOSSIP_BATCH_SIZE), @@ -106,8 +113,7 @@ impl Configuration { } } -/// `SumeragiConfiguration` provides an ability to define parameters -/// such as `BLOCK_TIME_MS` and a list of `TRUSTED_PEERS`. +/// Trusted peers #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] #[serde(transparent)] @@ -197,8 +203,8 @@ pub mod tests { trusted_peers in Just(None), commit_time_limit_ms in prop::option::of(Just(DEFAULT_COMMIT_TIME_LIMIT_MS)), transaction_limits in prop::option::of(Just(TransactionLimits::new( - transaction::DEFAULT_MAX_INSTRUCTION_NUMBER, - transaction::DEFAULT_MAX_WASM_SIZE_BYTES, + DEFAULT_MAX_INSTRUCTION_NUMBER, + DEFAULT_MAX_WASM_SIZE_BYTES, ))), actor_channel_capacity in prop::option::of(Just(DEFAULT_ACTOR_CHANNEL_CAPACITY)), gossip_batch_size in prop::option::of(Just(DEFAULT_GOSSIP_BATCH_SIZE)), diff --git a/configs/peer/config.json b/configs/peer/config.json index 2798e25cdba..8cbabfab005 100644 --- a/configs/peer/config.json +++ b/configs/peer/config.json @@ -10,10 +10,10 @@ "DEBUG_OUTPUT_NEW_BLOCKS": false }, "SUMERAGI": { - "KEY_PAIR": null, "PEER_ID": null, - "BLOCK_TIME_MS": 2000, + "KEY_PAIR": null, "TRUSTED_PEERS": null, + "BLOCK_TIME_MS": 2000, "COMMIT_TIME_LIMIT_MS": 4000, "TRANSACTION_LIMITS": { "max_instruction_number": 4096, diff --git a/configs/peer/validator.wasm b/configs/peer/validator.wasm index d806ab45480c1e9dd5fa998c67eeb489044dd3ca..298e86353579523abe7f00a4eb02381caaffd0d1 100644 GIT binary patch literal 730994 zcmeFa4ZK}dbuYR;_TJ}X?{l*90Y*SwXA|3#7OrU@O;XFdJu9yv^l5MLqy6#vyY~XO z@_Tr&3Wz@Y9>P%rq>36PV3e<@2vIDr&TT&|7ICo+%Ba(Q%a^@__czg)6J=hm2;`K#tvinEMO!gGAN{<$0gF9!gC#a~mD zurDWs3V^b<0f8isb@Rv{lMg>qKLAt!?20J)|EksJU-F_0&P$*7KmPA1(&v@uzwG?K ze95KfpY!||zWCBhUhc!wo`%+ajg++LqJ#1bSRF4ukH~v2LUQl zClc9R?pvpa%BY}?Z1og>q8MZy8e(NbL&<2ICUH8NR1#!LI{wqr_=}3||Aqt!_hlXr z?_@ox9gj%&W$N#*3fx4?jm1H=o`wGG*^nq<{={HHBXy^D3EZ8kaErPoD|0Q9S zy*)#@clsy&4#1^~olZ{@^sC{wQV#nPgYWQ+E>^i=4@yIa*joAKepp6E8$M`Hus8Uh zFc=fdY8WGk07EXV2RT?B|KIV+f0P{Ue_Ati>yzaJ(rm4$1STxuo-O(2?qdTB83h%k z8j3?0lNFRs2s?%cs7ZyDCXh#}%&6jsgcl(ksz)OK>#0l6!cO1g4YeB5$Aa{R8u4UAOv%JEMaJ-$*O#&{2X zgIbWcUa!Q*uLhn^JfZSL{CC0=pNQh}Up2lcKJ&~fm}hzIiRl$_F*P+E)vt((YxaCx zez)^?;QU(cug?Fg7hUq#NtB%D>o0^GH~m_I|e>EaNofG z?4tu88`wAS>49&I-ZA)^;a3koG;q_v9Rt$?_YT}O@RjVt1Gf*{Gw{xV_YPdO;kpyZ&?1})>3Fg)9vk5AbUZegOqBmI{b1Z2Dwb^-F+lRTs7yMz0&duVs~d2!0he?HR#9Ms zqZMfIN?gwYcy01fydgO$kBU_hZ4zyev}`gT-jL#JUf+-o z;-|7cS&g4~eNw?sf|8AqdY%-GGe(9Rbv$hzN%^{EWVndN1{*{Cn4B>J8ZmLGNRT*K zPbTy|DpGmJTe@Xr5J+tYL2KF7O3v|Qd)!Ql+7Eq4knB}=_MOO27 z>nKZ%H|v|&_wwHrm*w^6-gVig=-E=+>dCyGj{?SssiuMAt4;Bj=csCJy1LnT?p=It zyu3MFf;0?BXKfxCK&8XPemEZ{Dvc|VbyIwmMQ!-XeE8+h8HqMUo37Xtp>ff(Puvva zH-65E&DzV+-l{%sik<^>YL_)@dHp#rMhDc!&_DHT=ii`ZT=dLF4ZR3t>-q3j;0=VvU`4a&AgNyEhitnc zcSib-)i+58u1yEWs>}!k4uXrd&!vP2RJIM&r5wx%4&aCkINKD6mlLiFRJNX{qIK>V z7-&3s88`;)$QJI8CXrnsf#<>MO>izj9}v)}0BjF#Z-Tv%j2=oZYl7oJ7m79uSLKVm zK|<=33;hNeQedRuezb!WVtev|^$>Ub4z7o|<9B!i1TQz`vRAV$A_JgaESwV(LV>T# zt|jSCP>MFzC%Xa25-cCc;}@<+Hz+3AYY349^#r?S{se=%I+h7Guy7S7T?koG6*iC@pkas2!OOOa-=R25{Itp@@o*L~;K+% zi;S~|wA$UQPPCnV64aO`qZQJ?mw<}l`%$p03?|uz^e~=T6aF4fCyPT<(^FCQjW?iH zGPdCGB@$*N(TWeJI@AkvmKd}uKPpDi&+Je3#J8mhyFfCdo9yoiL}IF9x@H+g<+9kB zOWB3ZL^}RpjyQQnccbT*g);N=T?v8ZjyBs~GM1`%-E=SxqyAwZ^CwR+!K_`)BqVc$_5# zQtSZN*Zan<^NnSjT&8B6eTYqBibeAYtT1AQ^*?5X+4aKN_1|ej1l*i`c@_lzvndhL zP%@|wW)HChaV=o8nN*>$NUCWV=uy8KGXyJ3zPF^CC`g7!rRjz&N=3+f+`^t7Dtn1t z8D;@NF!W1dNauH<63ZQTUA3atc5=C~wGTw0Xy<_DOyac2DI9^NJ+8*U8Z`#;(9EW^ zZS?jv0d+R7$H&0TmZj>)8>MkAeM=o!M;OI zn1g=~TWeVUJnKXxrYH{k5Jtr_@FqH?txdG_+uEcGo<`H@G@vj)V4F&#E!!kzrA;t| z$cZqb*rpBxF;Sra#`$3J%lP6V+CpnIvI9=?bokT6#)M^O$Cq{C@G_w!7{?C=m(%fs z+Bf0{!~Gik3_d*qT>yRcOP?N{EKh?k{ex%>=&%RXIAgEEKs5SfB~gyESHA-MDoDj= z)VE_=WhRD(Ri~4XRkw2#3V0RoAWgumI}_#B9f`8*cFcWxHv7g36y*&gs}%&dK)@ml+JJ= zwvIy}3Y(!k4sw!WIvSZT%IIKjMJBm3X@VDKVr2_k2R^O^j%tg6$vGW`VGTY9oyjf& z+HpL(k7gP=lQuKt7Ti&5VEPbm2A7D5RFPL2OX&2G3R;=SbYyr_EK`mKI2^N+5xim! zr!%s#I0dc2Z1FRoGSlK9_-SO5%%|KOk=9ieX`{is@lmAVo%1e}52c>XY|p-!4LcVP zm+;{teJG}4nOb5tvNk=Ne=fKTGmO0U+)Il1#dnQiinum?*;pNtspwp+Qz=dz$JrOrN42KTXoX%}Qs;beB&Md=LYse6qR=pN$)+5;yLMR7m9 z8{k`-keF&yd#YrY=Bm2{JVC02d9{0gOuf6)Amo&7c_}!=TkV zkAd$A>oJ&TPf!;e(>=lAPzQN8Dh4xIRka*}b(n;?ZXFnVWlFGJ)RC%aXlly{=y2#x z5tw#{fG}@^zTVTq6l96%#Ez0btft8o@#St-wySz!vtkVYikMs3wRS$1V-5@TK-m`0 zW-YJeRrpNOyi)8>C-HY5{?ho&cCZXS1J_K{Mdj3Je`+UkmEyIM9fdHF3ua&iIkLi8 ziJyj%B9&r?6gYqaR@#x6O=kZ@`c4Db0Ws}7K^RwW9!)pxwOzMc$&4FSWrirR9izpu`UNYGn%m?#yQ8Zd7#Om;HZD0_~ z=)ebsg?#k(%G;YrKMvROwo~5VzX{%eP*4sC1#dt|-rg+bU=qsPTjULG4RUfEaJBP! zO;O42dlf4et?gPxGsaA?!01)i_p~jCNPF~w>lFwXX7==U)0SZ$Ma3#4kUMr&EyGXx zSyS$(eHd`EA3|qJcjd`=QsIn@6W2ik;xv^*DyK&19U4(~>YJfHe$KwihB9kztNKtd zL)%`mGhq+3Iy-W>IyPDXNej`;@NkFX5y9mR4nQjYx63jk(3H&JTIb^sN*EFVZQ~w6 zDNGPr-2f=R681TH3j>5Pp7&MYs0sOK_GeCr;iKiBr1&5%s6!a;~x~HZo7CMxZ=orX)TxWPw#{MDSpmBv+)?rBe2m?l$w(n+c7g$q{Ed zf%DTWMMBO+?vW7>;(V5wA1(adm~6s~Ed&T85%XOct_kJT2=mv(x?6uFiiI#jH6&9p z(M0@GQbqD9#zd+>QLm0*v_3srjZ{oQfGFlC<4Km?LgX>qbp{dL1d_=xGSyc0R$*lH z06#q{=0TeV3o=scqmjwJgi}<(M{HlBDwaLxwpvR_Axxy{-XSBw{ubJ()$XRZnkEK* zK*{F7h7>+YfHwXU0#!gcKCyqf^h)IlqsH{?)m^>d(j|}~bV-XB9Np651=nstXF08J z)cj1(UU1AKU_j341y`>$>dU^Y{Ko#fkrP0nHN6+)Wa|SL#zPs90!;z2bWq3BA;`xj zfS7{6dxoXuQ8lCNp)1-JP0-@BCsgL3c)JF)dWXRse z3h?}hWEOi$a^SnQ?xfyFgL;)dZ+8uXD!5g^L@<4@5|`aiDgeM|5W_+4yHip>Yf>MO z->BFtsh=~c2TbaItW96dXQ7A%t>e{$nkV}*f9u;b6G~F?p(JHn4!j6P@u4K-&63aX zkntisY`h3FF^8G*KT6c2vG*JbP}R+79@^(aNdOM)H=Y0-IF-v$WZv+f0p@+)CX_^d z6rv7^WXbCdI)SH|-0BYeMNE|m@1$tCSv=qB3WOf)=J{USh!gq@f{2qnVZ@0#Zs=@9 zVpwykl;JMb2zi zMaNOkO(E2z8ibl))I^xcrm07Ey#2CGSGdZ=FbDX&aTy_OY&P;?dK(dQa#`Ln+ys-% zWI`fI;P8`35(H^H=fw8d5_;N*$l$Ba0;Kq=W_{ByuSHDFpIr>?jhGl>@@FFuNw@}| z1LP#|q-p|BU_MsMfhQNhz`^i#Q3GaaTEKLe1Z@WL%>#)(;~o^gxcQ2Qtis z(UA^1=_-0-Sw>y586LzXo9ly*=utl_9$;rnrG9>YfiVhrSFff2ka$`)F8fYehWo-m2 zX7t$65}6U0ft!!RB+WwcT*6EAFyG)s=*#bURxKE9Xm4mcW zS1YpK6V7T=E)MP|(Aw?m6RtlH7w;cMleDb!k0ND0q+?%nC*5I+SV54#D!K_1tEW-h zZu-`^M_YBG?^x(Wbgb2hVo^HpUTNh_x)?!WU02TN;UVMhg{ST!K8`2%z@hg7Jc@KB zb(xyal7;<9MJJ?oAuKbl?`#MQQaCR85SC8kk{LQ&2n%~h28?THRMK29AuQVj{#K-p z(sSHOnyDC;rO;(m**uJ45&lLad**Mk{-JibQaN`F%QhI5>d7t+1535{fkiq+Zz;go z`XNW%*r$O~9Zf47%)O2>@6JO91Pn+Ob5gWC_T~$e^e;OW;ni1kO5gLqMWS z8SBSk#D|{NiU?7q7<7`thn`j?^wcb~F~&nePvOmwrE|vYFecC#0#tucfS|=$m>5Y| z1!U`NuGvbqz!;DWn6V25pG#AUGIqACp{7*ETMc(i#n6=smi7P!sXt?!5D4YCHJL#Vh_oLEah5s0M7!s0Kzz3#>2f zMh5NVy#5y$--qSTvrfE=F%d6k0>ft!(fe}bV{sVnQxxFbQo=4OW1T6EY)7y<|AmSdDgn1l+;XkRJKWv;6| z*s9|E=%s9IRNRq_>+>73Xla-sYZo5cc~W197cdfuh4=0A^-fL(~E9Hm!)-@}OHhOp6`j zHA23!TaxV$77kJ0LC_LQhwueCA6cb%t0dx+OKhy7H%6>@CSy@g>d*&P)4porWDEtl zdO{H5EWe#7H!GkthZIv;P#3zdQudivvfV7h1&>nW-+6?M*LH@DXBDQMH#icux9tSm zCdCNkGTremVq}m6s^GOd79U3}@5~2BWEWH{j(08vj#z1$501!Q!tti1z|nZNP;*ax zjqJ*OTnT;0RuS-4xub9_gE@elhtkRMC|Vy0J0KZr4D=*_J)K+|xv;kAL4@iaq!_5g zJ_1qN=awy{I>19zfrBKnW&(!+nwHT)OpFfFBB3Czw4xb;A*1VOC*?N(hG3nvfz`gW z_@-oj71%pNXpqyw$-Np;5cl4u~=Muml7fsTYtaV7c$E#g_*- z&E~^v$c`p>nSJ+C@ETS{=Yu1%mvDT~Qs4-m{d{l~7k@yPF-x2ejxwnX=y>H);D{;Pd~lQ*UV!7%I7)W0Ng#i2MgkBLU=aj)F}6*DwD!1AwmpfD zWt%R=mtihLJMK2xaoI=NY@Mu$->F#1&=xFUcUX`=EU3%?mGXjooPEp??^evFj3eYu z|A>{@DZsmjM&O|z@reWUAy6rpF=9wsG$i zkXk1OE8Kx!h5Dfd3jYr_8E{dus4#kF9lT36wn%JQ9bl5pOuOG zAq1Jo+Sm&**Fg5IP_q2vQD!nou{LfKW$+fkI-Q?TI-qv=r~Bv{RyqC|cr0;^YL7R>Kj^ z01`|c5Y$A^A_AP~a2`)iq9-?<$l-uzR7pjmN`N%o$0quTdmo@h!FvN5SK<~pDhMzk z%~`1iLE*6_9%^m!Up2}Jm3EE(^j~l5h{fmoC-?)e$Ecbe#WL}XVdv$w?xEuGN1d&pMyS>t%MBq`+O)NDKR8Y+v5CY0n^TbAS?OJe> zS1tjIhRr8wbfDrsMbV8x+p%?^)OHE^Y}JQO7lO#T@!W1>wKQJbN!7@CYYO;pBf!XVH26tGdNw7Y*;seCh^`_w$|ibjVJ z;BwZl2}yW-Mu)7$w3}+()IZ~ZYxQOdu&8Go_zn8MjL*-q8v3%CSRe?hDyV^v1c}q8esX&qir+-M27`$$>hFP$k z^kFE#gyW-}D~f2*)Mz)2k<0CP`#R1LmGc$cit(aFMsesrYH?Bi$OKGBgn@j_fUrH( zCMKL3+M$eP*66`lh#9~t(Yb182*2^D3<*|)Q8izw;c*CzsYO^6lsS)0s1^~zq7uX) z>3bv*QIG+INp0b|3HT74=2m52aYn{zCO>3v;ASKwY3vJ+SCA;Q!7*{z;vgPI6UZTC z2x-cPwV5u8W*yr-0|$hSqJeWlAqkX?I@di-BiYtz?ChDCM57@DyV?*Gr8LaUIFDdY z(o!^0G+s=f$yh>8DcJ+)3Z=7+qOm*U@eQ<9P%b_K3Ve25SVXc$gLzBV#F)8s-{^eJnzzk#SVdD06(>QF^HdPy%@qzrCto< zC#@HC{2)BOfgeE;zJ)m3E8bIVRoy5k7OUVQh1UW2yJa;t2Pue)kMWe*WkpHkdJz(X zDUlvqxe>If5Y(z*c@C_kZc*gfxFO+k2@F9w-A$&;L`c^xStZOPC42q9co&pEy-`+e zACkp6)(>i90`?WNSSLt`2Nq9*8xkJ6=}ynw9{Z(D9>3VqyHTlkIh||oT`VF=2V-0E zhgv##oQotpT@edDs1-TQ@}&Lc>hu~m5W3bh@FcJj`a*aGM1d(UhepVI{Q=x?gJXwq zACf)b0Diay;0#mP;v6z1X_s*7p}RIKT>2uhG*NoHDc)>hUo7D<=5V#10DzXRVoRA_ zJ>G;?kre>g=+XgbEdqWM@9BPy?@cZ&o0dp--b-2vwG(qn1PKFHuwFJYt}L)34vEQ@ zfY0+Vd3e2pNp#4xSPUm?jc1tN`5Llfl}fRkTtU#tiiy9gj+J1U-}KTmeI;-lR5dlP z^3(zxt1i8UR`BedhSZj2H;QmYd39~Figc~|>Hmpnr|u$`)d7Z}f&*kNy?$oG?iM=4 zjJ-^--xS&=;puQVBqPD;a3aB*Hg3BeYrroC!w%wrRXW2(#O!H;^@jpZ_e@h@y`aEt z2vbqf%sx&OPtbN_9W{Fs0}Akp$N|&WSkBClkb!;h{Rfz_0aHoHKB3g)PWRO~b0Nh+ zgG2(atB5BjojHaNY6ut;mEr&^&F(W*AW3)+klhlza2Jfc9Hm((yGt?yBk{BlGw1EU zE%4+_LyrAfTu?I&Y^Z^99!w_k2(+s}7gJgD=`<6SQ7Nvu3iv>SBN0D2exN^?aym*g z_I!P^PM!wwv=J%b^9r%rCwfLs)w*FT2_la-GnE9WaIMzxM_m8yh)C!;Nij%xm|?N# zaO@t?=bT;tGsZX6zZ!d&^%UY%JSW3Ei?fd?!2u5fGOp1n5pl9z4yy`+EtYK<{99U?(-SUuMi1EMx2^=?r+h#b1EcqV>rtGWGg|JU|?ZQ>@3E znI$pXN3=bZBA}F`IL07Q+;$XBWdjOu=?UH2ZtgEU)P%Is-P%Y6I}M1~Cud!C)coa2l-Ga&xE<8H+FPV7;Q{(o>`D z!C5+3rQqCzAyb#YM)HdHwt7w*MTa2BJNjq9;uQEYKxTHAA zKAB;29ZI0fIkK(6dDGeH*Gy~p87@Nd8#~|9FshSJ_26Z54}>t zfse(YctCl89tHusbRlLIBmxK9=~68O@(>ngj2=&CxGuy-DhmeMT-N$o%|8fBabN*U z&F|>1U2}~#zZ%Z@P34@aR~$L#T$B3YlIQiN#&k5n?j5xe?xk@-85$pNsme(od)1M1(vJfjR36uG{$7`H?btYp@L|J4 z7}a(r;^ZJxvHg9~T!-iFst1a7Rr z9SyS@=lL3YU!5t~A0dYK{bmw?po#J%0A5@_2{^@00<7tO8cG|}AJ#kv4DaDPQ_TDQ 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 YCA#zXzIM~QeCS(O-sL6-zJHe=*8btH2Uan& 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

wO&qfoYAamaFZCE;; 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 S^gI9Iw}0y{J4by9MErjWSjzSQ literal 765273 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 z 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�fGdU;KpZJr{%4XM96kmI{xV|BOuf`+^5Pjq;Z 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 diff --git a/core/Cargo.toml b/core/Cargo.toml index c03d31667ca..195cc90d029 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -37,7 +37,7 @@ is-it-maintained-open-issues = { repository = "https://github.com/hyperledger/ir maintenance = { status = "actively-developed" } [dependencies] -iroha_data_model = { version = "=2.0.0-pre-rc.13", path = "../data_model", features = ["transparent_api"] } +iroha_data_model = { version = "=2.0.0-pre-rc.13", path = "../data_model", features = ["transparent-api"] } iroha_macro = { version = "=2.0.0-pre-rc.13", path = "../macro" } iroha_p2p = { version = "=2.0.0-pre-rc.13", path = "../p2p" } iroha_logger = { version = "=2.0.0-pre-rc.13", path = "../logger"} diff --git a/core/benches/kura.rs b/core/benches/kura.rs index 97402e59592..371c19e011c 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -4,9 +4,13 @@ use std::str::FromStr as _; use byte_unit::Byte; use criterion::{criterion_group, criterion_main, Criterion}; -use iroha_core::{block::*, kura::BlockStore, prelude::*, tx::TransactionValidator, wsv::World}; +use iroha_core::{ + block::*, kura::BlockStore, prelude::*, sumeragi::network_topology::Topology, + tx::TransactionValidator, wsv::World, +}; use iroha_crypto::KeyPair; -use iroha_data_model::{block::VersionedCommittedBlock, prelude::*}; +use iroha_data_model::prelude::*; +use iroha_genesis::AcceptedTransaction; use iroha_version::scale::EncodeVersioned; use tokio::{fs, runtime::Runtime}; @@ -35,43 +39,40 @@ async fn measure_block_size_for_n_validators(n_validators: u32) { max_wasm_size_bytes: 0, }; let tx = AcceptedTransaction::accept::(tx, &transaction_limits) - .expect("Failed to accept Transaction.") - .into(); + .expect("Failed to accept Transaction."); let dir = tempfile::tempdir().expect("Could not create tempfile."); - let kura = - iroha_core::kura::Kura::new(iroha_config::kura::Mode::Strict, dir.path(), false).unwrap(); + let kura = iroha_core::kura::Kura::new(iroha_config::kura::Mode::Strict, dir.path(), false) + .expect("Valid"); let _thread_handle = iroha_core::kura::Kura::start(kura.clone()); - let mut block = BlockBuilder { - transactions: vec![tx], - event_recommendations: Vec::new(), - height: 1, - previous_block_hash: None, - view_change_index: 0, - committed_with_topology: iroha_core::sumeragi::network_topology::Topology::new(Vec::new()), - key_pair: KeyPair::generate().expect("Failed to generate KeyPair"), - transaction_validator: &TransactionValidator::new(transaction_limits), - wsv: WorldStateView::new(World::new(), kura), - } - .build(); + let topology = Topology::new(Vec::new()); + let wsv = WorldStateView::new(World::new(), kura); + let transaction_validator = TransactionValidator::new(transaction_limits); + let mut block = BlockBuilder::new(vec![tx], topology.clone(), Vec::new()) + .chain_first(&transaction_validator, wsv) + .sign(KeyPair::generate().expect("Failed to generate KeyPair")) + .expect("Valid"); for _ in 1..n_validators { block = block .sign(KeyPair::generate().expect("Failed to generate KeyPair.")) - .unwrap(); + .expect("Valid"); } - let block: VersionedCommittedBlock = block.commit_unchecked().into(); let mut block_store = BlockStore::new(dir.path()) .lock() .expect("Failed to lock store"); - block_store.create_files_if_they_do_not_exist().unwrap(); + block_store + .create_files_if_they_do_not_exist() + .expect("Valid"); - let serialized_block: Vec = block.encode_versioned(); + let serialized_block: Vec = VersionedSignedBlock::from(block).encode_versioned(); block_store .append_block_to_chain(&serialized_block) - .unwrap(); + .expect("Valid"); - let metadata = fs::metadata(dir.path().join("blocks.data")).await.unwrap(); + let metadata = fs::metadata(dir.path().join("blocks.data")) + .await + .expect("Valid"); let file_size = Byte::from_bytes(u128::from(metadata.len())).get_appropriate_unit(false); println!("For {n_validators} validators: {file_size}"); } @@ -85,7 +86,9 @@ async fn measure_block_size_async() { } fn measure_block_size(_criterion: &mut Criterion) { - Runtime::new().unwrap().block_on(measure_block_size_async()); + Runtime::new() + .expect("Valid") + .block_on(measure_block_size_async()); } criterion_group!(kura, measure_block_size); diff --git a/core/benches/validation.rs b/core/benches/validation.rs index a47ad11d6d0..4aa4ac7b658 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -8,6 +8,7 @@ use iroha_core::{ sumeragi::network_topology::Topology, tx::TransactionValidator, wsv::World, }; use iroha_data_model::prelude::*; +use iroha_genesis::AcceptedTransaction; const TRANSACTION_TIME_TO_LIVE_MS: u64 = 100_000; @@ -19,7 +20,7 @@ const TRANSACTION_LIMITS: TransactionLimits = TransactionLimits { max_wasm_size_bytes: 0, }; -fn build_test_transaction(keys: KeyPair) -> SignedTransaction { +fn build_test_transaction(keys: KeyPair) -> VersionedSignedTransaction { let domain_name = "domain"; let domain_id = DomainId::from_str(domain_name).expect("does not panic"); let create_domain = RegisterBox::new(Domain::new(domain_id)); @@ -119,9 +120,8 @@ fn validate_transaction(criterion: &mut Criterion) { let _ = criterion.bench_function("validate", move |b| { let transaction_validator = TransactionValidator::new(TRANSACTION_LIMITS); b.iter(|| { - match transaction_validator.validate( + match transaction_validator.validate::( transaction.clone(), - false, &Arc::new(build_test_and_transient_wsv(keys.clone())), ) { Ok(_) => success_count += 1, @@ -132,46 +132,74 @@ fn validate_transaction(criterion: &mut Criterion) { println!("Success count: {success_count}, Failure count: {failure_count}"); } +fn chain_blocks(criterion: &mut Criterion) { + let keys = KeyPair::generate().expect("Failed to generate keys"); + let transaction = AcceptedTransaction::accept::( + build_test_transaction(keys.clone()), + &TRANSACTION_LIMITS, + ) + .expect("Failed to accept transaction."); + let transaction_validator = TransactionValidator::new(TRANSACTION_LIMITS); + let wsv = build_test_and_transient_wsv(keys); + let topology = Topology::new(Vec::new()); + let block = BlockBuilder::new(vec![transaction], topology, Vec::new()); + let previous_block = block + .clone() + .chain_first(&transaction_validator, wsv.clone()); + let mut previous_block_hash = previous_block.hash(); + + let mut success_count = 0; + let _ = criterion.bench_function("chain_block", |b| { + b.iter(|| { + success_count += 1; + + let new_block = block.clone().chain( + success_count, + Some(previous_block_hash), + 0, + &transaction_validator, + wsv.clone(), + ); + + previous_block_hash = new_block.hash(); + }); + }); + println!("Total count: {success_count}"); +} + fn sign_blocks(criterion: &mut Criterion) { let keys = KeyPair::generate().expect("Failed to generate keys"); - let transaction = - AcceptedTransaction::accept::(build_test_transaction(keys), &TRANSACTION_LIMITS) - .expect("Failed to accept transaction."); + let transaction = AcceptedTransaction::accept::( + build_test_transaction(keys.clone()), + &TRANSACTION_LIMITS, + ) + .expect("Failed to accept transaction."); let transaction_validator = TransactionValidator::new(TRANSACTION_LIMITS); + let wsv = build_test_and_transient_wsv(keys); + let topology = Topology::new(Vec::new()); + let block = BlockBuilder::new(vec![transaction], topology, Vec::new()) + .chain_first(&transaction_validator, wsv); let key_pair = KeyPair::generate().expect("Failed to generate KeyPair."); - let kura = iroha_core::kura::Kura::blank_kura_for_testing(); - let mut success_count = 0; let mut failures_count = 0; let _ = criterion.bench_function("sign_block", |b| { - b.iter(|| { - let block = BlockBuilder { - transactions: vec![transaction.clone().into()], - event_recommendations: Vec::new(), - height: 1, - previous_block_hash: None, - view_change_index: 0, - committed_with_topology: Topology::new(Vec::new()), - key_pair: key_pair.clone(), - transaction_validator: &transaction_validator, - wsv: WorldStateView::new(World::new(), kura.clone()), - } - .build(); - - match block.sign(key_pair.clone()) { - Ok(_) => success_count += 1, - Err(_) => failures_count += 1, - } + b.iter(|| match block.clone().sign(key_pair.clone()) { + Ok(_) => success_count += 1, + Err(_) => failures_count += 1, }); }); println!("Success count: {success_count}, Failures count: {failures_count}"); } -criterion_group!( +criterion_group! { transactions, accept_transaction, sign_transaction, - validate_transaction -); -criterion_group!(blocks, sign_blocks); + validate_transaction, +} +criterion_group! { + blocks, + chain_blocks, + sign_blocks, +} criterion_main!(transactions, blocks); diff --git a/core/src/block.rs b/core/src/block.rs index 325b4562b8a..bad4c9e7184 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -1,8 +1,7 @@ //! This module contains [`Block`] structures for each state, it's -//! transitions, implementations and related traits -//! implementations. [`Block`]s are organised into a linear sequence -//! over time (also known as the block chain). A Block's life-cycle -//! starts from [`PendingBlock`]. +//! transitions, implementations and related trait implementations. +//! [`Block`]s are organised into a linear sequence over time (also known as the block chain). + #![allow( clippy::module_name_repetitions, clippy::std_instead_of_core, @@ -10,596 +9,696 @@ clippy::arithmetic_side_effects )] -use std::error::Error; +use std::error::Error as _; -use eyre::{bail, eyre, Context, Result}; +use eyre::{eyre, Context, Result}; use iroha_config::sumeragi::DEFAULT_CONSENSUS_ESTIMATION_MS; use iroha_crypto::{HashOf, KeyPair, MerkleTree, SignatureOf, SignaturesOf}; use iroha_data_model::{block::*, events::prelude::*, transaction::prelude::*}; -use parity_scale_codec::{Decode, Encode}; +use iroha_genesis::AcceptedTransaction; +pub use self::{chained::Chained, commit::CommittedBlock, pending::Pending, valid::ValidBlock}; use crate::{ prelude::*, sumeragi::network_topology::{Role, Topology}, tx::TransactionValidator, }; -/// Transaction data is permanently recorded in chunks called -/// blocks. -#[derive(Debug, Clone, Decode, Encode)] -pub struct PendingBlock { - /// Block header - pub header: BlockHeader, - /// Array of rejected transactions. - pub rejected_transactions: Vec, - /// Array of transactions, which successfully passed validation and consensus step. - pub transactions: Vec, - /// Event recommendations. - pub event_recommendations: Vec, - /// Signatures of peers which approved this block - pub signatures: SignaturesOf, -} +/// Buidler for blocks +#[derive(Debug, Clone)] +pub struct BlockBuilder(B); -/// Builder for `PendingBlock` -pub struct BlockBuilder<'a> { - /// Block's transactions. - pub transactions: Vec, - /// Block's event recommendations. - pub event_recommendations: Vec, - /// The height of the block. - pub height: u64, - /// The hash of the previous block if there is one. - pub previous_block_hash: Option>, - /// The view change index this block was committed with. Produced by consensus. - pub view_change_index: u64, - /// The topology thihs block was committed with. Produced by consensus. - pub committed_with_topology: Topology, - /// The keypair used to sign this block. - pub key_pair: KeyPair, - /// The transaction validator to be used when validating the block. - pub transaction_validator: &'a TransactionValidator, - /// The world state to be used when validating the block. - pub wsv: WorldStateView, -} +mod pending { + use super::*; -impl BlockBuilder<'_> { - /// Create a new [`PendingBlock`] from transactions. - pub fn build(self) -> PendingBlock { - let timestamp = crate::current_time().as_millis(); - // TODO: Need to check if the `transactions` vector is empty. It shouldn't be allowed. - - let mut header = BlockHeader { - timestamp, - consensus_estimation: DEFAULT_CONSENSUS_ESTIMATION_MS, - height: self.height, - view_change_index: self.view_change_index, - previous_block_hash: self.previous_block_hash, - transactions_hash: None, - rejected_transactions_hash: None, - committed_with_topology: self.committed_with_topology.sorted_peers, - }; + /// First stage in the life-cycle of a [`Block`]. + /// In the beginning the block is assumed to be verified and to contain only accepted transactions. + /// Additionally the block must retain events emitted during the execution of on-chain logic during + /// the previous round, which might then be processed by the trigger system. + #[derive(Debug, Clone)] + pub struct Pending { + /// Unix timestamp + timestamp_ms: u128, + /// Collection of transactions which have been accepted. + /// Transaction will be validated when block is chained. + transactions: Vec, + /// The topology at the time of block commit. + commit_topology: Topology, + /// Event recommendations for use in triggers and off-chain work + event_recommendations: Vec, + } - let mut txs = Vec::new(); - let mut rejected = Vec::new(); + impl BlockBuilder { + /// Create [`Self`] + /// + /// # Panics + /// + /// if the given list of transaction is empty + #[inline] + pub fn new( + transactions: Vec, + commit_topology: Topology, + event_recommendations: Vec, + ) -> Self { + assert!(!transactions.is_empty(), "Empty block created"); + + Self(Pending { + timestamp_ms: iroha_data_model::current_time().as_millis(), + transactions, + commit_topology, + event_recommendations, + }) + } - for tx in self.transactions { - match self - .transaction_validator - .validate(tx.into_v1(), header.is_genesis(), &self.wsv) - { - Ok(tx) => txs.push(tx), - Err(tx) => { - iroha_logger::warn!( - reason = %tx.as_v1().rejection_reason, - caused_by = ?tx.as_v1().rejection_reason.source(), - "Transaction validation failed", - ); - rejected.push(tx) + fn make_header( + timestamp_ms: u128, + height: u64, + previous_block_hash: Option>, + view_change_index: u64, + transactions: &[VersionedSignedTransaction], + rejected_transactions: &[(VersionedSignedTransaction, TransactionRejectionReason)], + commit_topology: Topology, + ) -> BlockHeader { + BlockHeader { + timestamp_ms, + consensus_estimation_ms: DEFAULT_CONSENSUS_ESTIMATION_MS, + height: height + 1, + view_change_index, + previous_block_hash, + transactions_hash: transactions + .iter() + .map(VersionedSignedTransaction::hash) + .collect::>() + .hash(), + rejected_transactions_hash: rejected_transactions + .iter() + .map(|(tx, _error)| tx.hash()) + .collect::>() + .hash(), + commit_topology: commit_topology.ordered_peers, + } + } + + // NOTE: Transactions are applied to WSV clone + #[allow(clippy::needless_pass_by_value)] + fn categorize_transactions( + transactions: Vec, + transaction_validator: &TransactionValidator, + wsv: WorldStateView, + ) -> ( + Vec, + Vec<(VersionedSignedTransaction, TransactionRejectionReason)>, + ) { + let (mut valid, mut rejected) = (Vec::new(), Vec::new()); + + for tx in transactions { + match transaction_validator.validate::(tx, &wsv) { + Ok(tx) => valid.push(tx), + Err(tx) => { + iroha_logger::warn!( + reason = %tx.1, + caused_by = ?tx.1.source(), + "Transaction validation failed", + ); + rejected.push(tx) + } } } + + (valid, rejected) + } + + /// Chain the block with existing blockchain. + pub fn chain( + self, + height: u64, + previous_block_hash: Option>, + view_change_index: u64, + transaction_validator: &TransactionValidator, + wsv: WorldStateView, + ) -> BlockBuilder { + let (transactions, rejected_transactions) = Self::categorize_transactions::( + self.0.transactions, + transaction_validator, + wsv, + ); + + BlockBuilder(Chained(BlockPayload { + header: Self::make_header( + self.0.timestamp_ms, + height, + previous_block_hash, + view_change_index, + &transactions, + &rejected_transactions, + self.0.commit_topology, + ), + transactions: transactions.into_iter().map(Into::into).collect(), + rejected_transactions: rejected_transactions.into_iter().map(Into::into).collect(), + event_recommendations: self.0.event_recommendations, + })) } - header.transactions_hash = txs - .iter() - .map(VersionedValidTransaction::hash) - .collect::>() - .hash(); - header.rejected_transactions_hash = rejected - .iter() - .map(VersionedRejectedTransaction::hash) - .collect::>() - .hash(); - // TODO: Validate Event recommendations somehow? - - let signature = SignatureOf::from_hash(self.key_pair, &HashOf::new(&header).transmute()) - .expect("Signing of new block failed."); - let signatures = SignaturesOf::from(signature); - - PendingBlock { - header, - rejected_transactions: rejected, - transactions: txs, - event_recommendations: self.event_recommendations, - signatures, + + /// Create a new blockchain with current block as the first block. + pub fn chain_first( + self, + transaction_validator: &TransactionValidator, + wsv: WorldStateView, + ) -> BlockBuilder { + let (transactions, rejected_transactions) = Self::categorize_transactions::( + self.0.transactions, + transaction_validator, + wsv, + ); + + BlockBuilder(Chained(BlockPayload { + header: Self::make_header( + self.0.timestamp_ms, + 0, + None, + 0, + &transactions, + &rejected_transactions, + self.0.commit_topology, + ), + transactions: transactions.into_iter().map(Into::into).collect(), + rejected_transactions: rejected_transactions.into_iter().map(Into::into).collect(), + event_recommendations: self.0.event_recommendations, + })) } } } -impl PendingBlock { - /// Calculate the hash of the current block. - pub fn hash(&self) -> HashOf { - HashOf::new(&self.header).transmute() - } +mod chained { + use super::*; + + /// When a [`Pending`] block is chained with the blockchain it becomes [`Chained`] block. + #[derive(Debug, Clone)] + pub struct Chained(pub(crate) BlockPayload); - /// Return signatures that are verified with the `hash` of this block, - /// removing all other signatures. - #[inline] - pub fn retain_verified_signatures(&mut self) -> impl Iterator> { - self.signatures.retain_verified_by_hash(self.hash()) + impl BlockBuilder { + /// Hash of the block being built + pub fn hash(&self) -> HashOf { + HashOf::new(&self.0 .0.header).transmute() + } + + /// Sign this block and get [`SignedBlock`]. + /// + /// # Errors + /// + /// Fails if signature generation fails + pub fn sign(self, key_pair: KeyPair) -> Result { + let hash = self.hash(); + + let signature = SignatureOf::from_hash(key_pair, &hash) + .wrap_err(format!("Failed to sign block with hash {}", hash))?; + let signatures = SignaturesOf::from(signature); + Ok(ValidBlock( + SignedBlock { + payload: self.0 .0, + signatures, + } + .into(), + )) + } } +} + +mod valid { + use eyre::bail; + + use super::*; + + /// Block that was validated and accepted + #[derive(Debug, Clone)] + #[repr(transparent)] + pub struct ValidBlock(pub(crate) VersionedSignedBlock); - /// Commit block to the store. - /// When calling this function, the user is responsible for the validity of the block signatures. - /// Preference should be given to [`Self::commit`], where signature verification is built in. - #[inline] - pub fn commit_unchecked(self) -> CommittedBlock { - let Self { - header, - rejected_transactions, - transactions, - event_recommendations, - signatures, - } = self; - - CommittedBlock { - event_recommendations, - header, - rejected_transactions, - transactions, - signatures: signatures.transmute(), + impl Block for ValidBlock { + fn payload(&self) -> &BlockPayload { + self.0.payload() + } + fn signatures(&self) -> &SignaturesOf { + self.0.signatures() } } - /// Verify signatures and commit block to the store. - /// - /// # Errors - /// - /// Not enough signatures - #[inline] - pub fn commit(mut self, topology: &Topology) -> Result { - let verified_signatures = self.retain_verified_signatures(); - - if topology - .filter_signatures_by_roles( - &[ - Role::ValidatingPeer, - Role::Leader, - Role::ProxyTail, - Role::ObservingPeer, - ], - verified_signatures, + impl ValidBlock { + /// Validate a block against the current state of the world. + /// + /// # Errors + /// + /// - Block is empty + /// - There is a mismatch between candidate block height and actual blockchain height + /// - There is a mismatch between candidate block previous block hash and actual latest block hash + /// - Block has committed transactions + /// - Block header transaction hashes don't match with computed transaction hashes + /// - Error during validation of individual transactions + /// - Topology field is incorrect + pub fn validate( + block: VersionedSignedBlock, + expected_block_height: u64, + expected_previous_block_hash: Option>, + topology: &Topology, + transaction_validator: &TransactionValidator, + wsv: WorldStateView, + ) -> Result { + let actual_commit_topology = &block.header().commit_topology; + let expected_commit_topology = &topology.ordered_peers; + + if actual_commit_topology != expected_commit_topology { + let msg = eyre!("Block topology incorrect. Expected: {expected_commit_topology:#?}, actual: {actual_commit_topology:#?}"); + return Err((block, msg)); + } + + if !block.header().is_genesis() + && topology + .filter_signatures_by_roles(&[Role::Leader], block.signatures()) + .is_empty() + { + return Err((block, eyre!("Block is not signed by the leader"))); + } + + Self::validate_without_validating_topology( + block, + expected_block_height, + expected_previous_block_hash, + transaction_validator, + wsv, ) - .len() - .lt(&topology.min_votes_for_commit()) - { - return Err(( - self, - eyre!("The block doesn't have enough valid signatures to be committed."), - )); } - Ok(self.commit_unchecked()) - } + /// Validate a block against the current state of the world. + /// + /// # Errors + /// + /// - Block is empty + /// - There is a mismatch between candidate block height and actual blockchain height + /// - There is a mismatch between candidate block previous block hash and actual latest block hash + /// - Block has committed transactions + /// - Block header transaction hashes don't match with computed transaction hashes + /// - Error during validation of individual transactions + #[deprecated( + since = "2.0.0-pre-rc.13", + note = "This method exists only because some tests are failing, but it shouldn't" + )] + // TODO: a committed block should always contains Leader and Proxy tail signatures + // TODO: Is it ok to not validate topology filed of the header in block_sync? + // NOTE: Transactions are applied to WSV clone + #[allow(clippy::needless_pass_by_value)] + pub fn validate_without_validating_topology( + block: VersionedSignedBlock, + expected_block_height: u64, + expected_previous_block_hash: Option>, + transaction_validator: &TransactionValidator, + wsv: WorldStateView, + ) -> Result { + let actual_height = block.header().height; + if expected_block_height != actual_height { + return Err((block, eyre!("Mismatch between the actual and expected heights of the block. Expected: {expected_block_height}, actual: {actual_height}"))); + } + let actual_block_hash = block.header().previous_block_hash; + if expected_previous_block_hash != actual_block_hash { + return Err((block, eyre!("Mismatch between the actual and expected hashes of the latest block. Expected: {expected_previous_block_hash:?}, actual: {actual_block_hash:?}"))); + } - /// Add additional signatures for [`SignedBlock`]. - /// - /// # Errors - /// Fails if signature generation fails - pub fn sign(mut self, key_pair: KeyPair) -> Result { - SignatureOf::from_hash(key_pair, &self.hash()) - .wrap_err(format!("Failed to sign block with hash {}", self.hash())) - .map(|signature| { - self.signatures.insert(signature); - self - }) - } + if let Err(error) = Self::validate_transactions(&block, transaction_validator, &wsv) { + return Err((block, error)); + } + if let Err(error) = + Self::validate_rejected_transactions(&block, transaction_validator, &wsv) + { + return Err((block, error)); + } - /// Add additional signature for [`SignedBlock`] - /// - /// # Errors - /// Fails if given signature doesn't match block hash - pub fn add_signature(&mut self, signature: SignatureOf) -> Result<()> { - signature - .verify_hash(&self.hash()) - .map(|_| { - self.signatures.insert(signature); - }) - .wrap_err(format!( - "Provided signature doesn't match block with hash {}", - self.hash() + let VersionedSignedBlock::V1(block) = block; + Ok(ValidBlock( + SignedBlock { + payload: block.payload, + signatures: block.signatures, + } + .into(), )) - } + } - /// Create dummy [`ValidBlock`]. Used in tests - /// - /// # Panics - /// If generating keys or block signing fails. - #[allow(clippy::restriction)] - #[cfg(test)] - pub fn new_dummy() -> Self { - let timestamp = crate::current_time().as_millis(); - - let header = BlockHeader { - timestamp, - consensus_estimation: DEFAULT_CONSENSUS_ESTIMATION_MS, - height: 1, - view_change_index: 0, - previous_block_hash: None, - transactions_hash: None, - rejected_transactions_hash: None, - committed_with_topology: Vec::new(), - }; + fn validate_transactions( + block: &VersionedSignedBlock, + transaction_validator: &TransactionValidator, + wsv: &WorldStateView, + ) -> Result<(), eyre::Report> { + let committed_txns: Vec<_> = block + .payload() + .transactions + .iter() + .filter(|transaction| transaction.is_in_blockchain(wsv)) + .collect(); + if !committed_txns.is_empty() { + bail!("Found committed transactions: {committed_txns:?}"); + } + + // Check that valid transactions are still valid + block.payload() + .transactions + .iter() + // TODO: Unnecessary clone? + .cloned() + .map(|tx| { + let limits = &transaction_validator.transaction_limits; + + let tx_result = if block.header().is_genesis() { + AcceptedTransaction::accept::(tx, limits) + } else { + AcceptedTransaction::accept::(tx, limits) + }; + + match tx_result { + Ok(tx) => Ok(tx), + Err((_tx, err)) => Err(err).wrap_err("Failed to accept transaction") + } + }) + .map(|accepted_tx| { + accepted_tx.and_then(|tx| { + let tx_result = if block.header().is_genesis() { + transaction_validator.validate::(tx, wsv) + } else { + transaction_validator.validate::(tx, wsv) + }; + + tx_result + .map_err(|tx| tx.1) + .wrap_err("Failed to validate transaction") + }) + }) + .try_fold(Vec::new(), |mut acc, tx| { + tx.map(|valid_tx| { + acc.push(valid_tx); + acc + }) + }) + .wrap_err("Error during transaction validation")?; - let key_pair = KeyPair::generate().unwrap(); - let signature = SignatureOf::from_hash(key_pair, &HashOf::new(&header).transmute()) - .expect("Signing of new block failed."); - let signatures = SignaturesOf::from(signature); - - Self { - header, - rejected_transactions: Vec::new(), - transactions: Vec::new(), - event_recommendations: Vec::new(), - signatures, + Ok(()) } - } -} -/// This trait represents the ability to revalidate a block. Should be -/// implemented for both `PendingBlock` and `VersionedCommittedBlock`. -pub trait Revalidate: Sized { - /// # Errors - /// - When the block is deemed invalid. - fn revalidate( - &self, - transaction_validator: &TransactionValidator, - wsv: WorldStateView, - latest_block: Option>, - block_height: u64, - ) -> Result<(), eyre::Report>; - - /// Return whether or not the block contains transactions already committed. - fn has_committed_transactions(&self, wsv: &WorldStateView) -> bool; -} + fn validate_rejected_transactions( + block: &VersionedSignedBlock, + transaction_validator: &TransactionValidator, + wsv: &WorldStateView, + ) -> Result<(), eyre::Report> { + let committed_rejected_txns: Vec<_> = block + .payload() + .rejected_transactions + .iter() + .filter(|(transaction, _)| transaction.is_in_blockchain(wsv)) + .collect(); + + if !committed_rejected_txns.is_empty() { + bail!("Found committed rejected transactions: {committed_rejected_txns:?}"); + } + + // Check that rejected transactions are indeed rejected + block.payload() + .rejected_transactions + .iter() + // TODO: Unnecessary clone? + .cloned() + .map(|tx| { + let limits = &transaction_validator.transaction_limits; + + let tx_result = if block.header().is_genesis() { + AcceptedTransaction::accept::(tx.0, limits) + } else { + AcceptedTransaction::accept::(tx.0, limits) + }; + + match tx_result { + Ok(tx) => Ok(tx), + Err((_tx, err)) => Err(err).wrap_err("Failed to accept transaction") + } + }) + .map(|accepted_tx| { + accepted_tx.and_then(|tx| { + let tx_result = if block.header().is_genesis() { + transaction_validator.validate::(tx, wsv) + } else { + transaction_validator.validate::(tx, wsv) + }; + + match tx_result { + Err(rejected_transaction) => Ok(rejected_transaction), + Ok(_) => Err(eyre!("Transactions which supposed to be rejected is valid")), + + } + }) + }) + .try_fold(Vec::new(), |mut acc, rejected_tx| { + rejected_tx.map(|tx| { + acc.push(tx); + acc + }) + }) + .wrap_err("Error during transaction validation")?; -impl Revalidate for PendingBlock { - /// Revalidate a block against the current state of the world. - /// - /// # Errors - /// - Block is empty - /// - Block has committed transactions - /// - There is a mismatch between candidate block height and actual blockchain height - /// - There is a mismatch between candidate block previous block hash and actual latest block hash - /// - Block header transaction hashes don't match with computed transaction hashes - /// - Error during revalidation of individual transactions - #[allow(clippy::too_many_lines)] - fn revalidate( - &self, - transaction_validator: &TransactionValidator, - wsv: WorldStateView, - latest_block: Option>, - block_height: u64, - ) -> Result<(), eyre::Report> { - if self.transactions.is_empty() && self.rejected_transactions.is_empty() { - bail!("Block is empty"); + Ok(()) } - if self.has_committed_transactions(&wsv) { - bail!("Block has committed transactions"); + /// Verify signatures and commit block to the store. + /// + /// # Errors + /// + /// - Not enough signatures + /// - Not signed by proxy tail + pub fn commit( + self, + topology: &Topology, + ) -> Result<(CommittedBlock, Vec), (Self, eyre::Report)> { + // TODO: Should the peer that serves genesis have a fixed role of ProxyTail in topology? + if !self.header().is_genesis() + && topology.is_consensus_required() + && topology + .filter_signatures_by_roles(&[Role::ProxyTail], self.signatures()) + .is_empty() + { + return Err((self, eyre!("Block is not signed by the proxy tail"))); + } + + self.commit_without_proxy_tail_signature(topology) } - if latest_block != self.header.previous_block_hash { - bail!( - "Mismatch between the actual and expected hashes of the latest block. Expected: {:?}, actual: {:?}", - latest_block, - &self.header.previous_block_hash - ); + /// Verify signatures and commit block to the store. + /// The block doesn't have to be signed by the proxy tail. + /// + /// # Errors + /// + /// - Not enough signatures + // TODO: a committed block should always contains Leader and Proxy tail signatures + #[deprecated( + since = "2.0.0-pre-rc.13", + note = "This method exists only because some tests are failing, but it shouldn't" + )] + pub(crate) fn commit_without_proxy_tail_signature( + self, + topology: &Topology, + ) -> Result<(CommittedBlock, Vec), (Self, eyre::Report)> { + #[allow(clippy::collapsible_else_if)] + if self.header().is_genesis() { + // If we receive a committed genesis block that is valid, use it without question. + // At genesis round we blindly take on the network topology from the genesis block. + } else { + // TODO: What is the point of filtering by all roles? + let roles = [ + Role::ValidatingPeer, + Role::Leader, + Role::ProxyTail, + Role::ObservingPeer, + ]; + + if topology + .filter_signatures_by_roles(&roles, self.signatures()) + .len() + .lt(&topology.min_votes_for_commit()) + { + return Err(( + self, + eyre!("The block doesn't have enough valid signatures to be committed."), + )); + } + } + + let block = CommittedBlock(self.0); + let events = block.produce_events(); + + Ok((block, events)) } - if block_height + 1 != self.header.height { - bail!( - "Mismatch between the actual and expected heights of the block. Expected: {}, actual: {}", - block_height + 1, - self.header.height - ); + /// Replace signatures in this block with the given + pub fn replace_signatures(&mut self, signatures: SignaturesOf) { + let VersionedSignedBlock::V1(block) = &mut self.0; + + block.signatures.clear(); + for signature in signatures { + if let Err(err) = self.add_signature(signature) { + // TODO: Is this something to be tolerated or should the block be rejected? + iroha_logger::warn!(?err, "Signature not valid"); + } + } } - // Validate that header transactions hashes are matched with actual hashes - self.transactions - .iter() - .map(VersionedValidTransaction::hash) - .collect::>() - .hash() - .eq(&self.header.transactions_hash) - .then_some(()) - .ok_or_else(|| { - eyre!("The transaction hash stored in the block header does not match the actual transaction hash.") - })?; - - self.rejected_transactions - .iter() - .map(VersionedRejectedTransaction::hash) - .collect::>() - .hash() - .eq(&self.header.rejected_transactions_hash) - .then_some(()) - .ok_or_else(|| eyre!("The hash of a rejected transaction stored in the block header does not match the actual hash or this transaction."))?; - - // Check that valid transactions are still valid - let _transactions = self - .transactions - .iter() - .cloned() - .map(VersionedValidTransaction::into_v1) - .map(|tx_v| { - let tx = SignedTransaction { - payload: tx_v.payload, - signatures: tx_v.signatures.into(), - }; - AcceptedTransaction::accept::( - tx, - &transaction_validator.transaction_limits, - ) - .wrap_err("Failed to accept transaction") - }) - .map(|accepted_tx| { - accepted_tx.and_then(|tx| { - transaction_validator - .validate(tx, self.header.is_genesis(), &wsv) - .map_err(|rejected_tx| rejected_tx.into_v1().rejection_reason) - .wrap_err("Failed to validate transaction") - }) - }) - .try_fold(Vec::new(), |mut acc, tx| { - tx.map(|valid_tx| { - acc.push(valid_tx); - acc - }) - }) - .wrap_err("Error during transaction revalidation")?; - - // Check that rejected transactions are indeed rejected - let _rejected_transactions = self - .rejected_transactions - .iter() - .cloned() - .map(VersionedRejectedTransaction::into_v1) - .map(|tx_r| { - let tx = SignedTransaction { - payload: tx_r.payload, - signatures: tx_r.signatures.into(), - }; - AcceptedTransaction::accept::( - tx, - &transaction_validator.transaction_limits, - ) - .wrap_err("Failed to accept transaction") - }) - .map(|accepted_tx| { - accepted_tx.and_then(|tx| { - match transaction_validator.validate(tx, self.header.is_genesis(), &wsv) { - Err(rejected_transaction) => Ok(rejected_transaction), - Ok(_) => Err(eyre!("Transactions which supposed to be rejected is valid")), - } - }) - }) - .try_fold(Vec::new(), |mut acc, rejected_tx| { - rejected_tx.map(|tx| { - acc.push(tx); - acc - }) - }) - .wrap_err("Error during transaction revalidation")?; - Ok(()) - } + #[cfg(test)] + #[deprecated( + since = "2.0.0-pre-rc.13", + note = "This method exists only because some tests are failing, but it shouldn't" + )] + pub(crate) fn commit_unchecked(self) -> (CommittedBlock, Vec) { + let block = CommittedBlock(self.0); + let events = block.produce_events(); - /// Check if a block has transactions that are already in the blockchain. - fn has_committed_transactions(&self, wsv: &WorldStateView) -> bool { - self.transactions - .iter() - .any(|transaction| transaction.is_in_blockchain(wsv)) - || self - .rejected_transactions - .iter() - .any(|transaction| transaction.is_in_blockchain(wsv)) + (block, events) + } + + /// Add additional signatures for [`Self`]. + /// + /// # Errors + /// + /// If signature generation fails + pub fn sign(self, key_pair: KeyPair) -> Result { + Ok(ValidBlock( + SignatureOf::from_hash(key_pair, &self.hash()) + .wrap_err(format!("Failed to sign block with hash {}", self.hash())) + .map(|signature| { + let VersionedSignedBlock::V1(mut block) = self.0; + block.signatures.insert(signature); + VersionedSignedBlock::from(block) + })?, + )) + } + + /// Add additional signature for [`Self`] + /// + /// # Errors + /// + /// If given signature doesn't match block hash + pub fn add_signature(&mut self, signature: SignatureOf) -> Result<()> { + let VersionedSignedBlock::V1(block) = &mut self.0; + + signature + .verify_hash(&block.hash()) + .map(|_| block.signatures.insert(signature)) + .wrap_err(format!( + "Provided signature doesn't match block with hash {}", + self.hash() + )) + } + + #[cfg(test)] + pub(crate) fn new_dummy() -> Self { + BlockBuilder(Chained(BlockPayload { + header: BlockHeader { + timestamp_ms: 0, + consensus_estimation_ms: DEFAULT_CONSENSUS_ESTIMATION_MS, + height: 1, + view_change_index: 0, + previous_block_hash: None, + transactions_hash: None, + rejected_transactions_hash: None, + commit_topology: Vec::new(), + }, + transactions: Vec::new(), + rejected_transactions: Vec::new(), + event_recommendations: Vec::new(), + })) + .sign(KeyPair::generate().unwrap()) + .unwrap() + } } -} -impl Revalidate for VersionedCommittedBlock { - /// Revalidate a block against the current state of the world. - /// - /// # Errors - /// - Block is empty - /// - Block has committed transactions - /// - There is a mismatch between candidate block height and actual blockchain height - /// - There is a mismatch between candidate block previous block hash and actual latest block hash - /// - Block header transaction hashes don't match with computed transaction hashes - /// - Error during revalidation of individual transactions - #[allow(clippy::too_many_lines)] - fn revalidate( - &self, - transaction_validator: &TransactionValidator, - wsv: WorldStateView, - latest_block: Option>, - block_height: u64, - ) -> Result<(), eyre::Report> { - if self.has_committed_transactions(&wsv) { - bail!("Block has committed transactions"); + impl From for VersionedSignedBlock { + fn from(source: ValidBlock) -> Self { + source.0 } - match self { - VersionedCommittedBlock::V1(block) => { - if block.transactions.is_empty() && block.rejected_transactions.is_empty() { - bail!("Block is empty"); - } + } +} - if latest_block != block.header.previous_block_hash { - bail!( - "Mismatch between the actual and expected hashes of the latest block. Expected: {:?}, actual: {:?}", - latest_block, - &block.header.previous_block_hash - ); - } +mod commit { + use iroha_data_model::block::Block; - if block_height + 1 != block.header.height { - bail!( - "Mismatch between the actual and expected heights of the block. Expected: {}, actual: {}", - block_height + 1, - block.header.height - ); - } + use super::*; - // Validate that header transactions hashes are matched with actual hashes - block.transactions - .iter() - .map(VersionedValidTransaction::hash) - .collect::>() - .hash() - .eq(&block.header.transactions_hash) - .then_some(()) - .ok_or_else(|| { - eyre!("The transaction hash stored in the block header does not match the actual transaction hash.") - })?; - - block.rejected_transactions - .iter() - .map(VersionedRejectedTransaction::hash) - .collect::>() - .hash() - .eq(&block.header.rejected_transactions_hash) - .then_some(()) - .ok_or_else(|| eyre!("The hash of a rejected transaction stored in the block header does not match the actual hash or this transaction."))?; - - // Check that valid transactions are still valid - let _transactions = block - .transactions - .iter() - .cloned() - .map(VersionedValidTransaction::into_v1) - .map(|tx_v| { - let tx = SignedTransaction { - payload: tx_v.payload, - signatures: tx_v.signatures.into(), - }; - AcceptedTransaction::accept::( - tx, - &transaction_validator.transaction_limits, - ) - .wrap_err("Failed to accept transaction") - }) - .map(|accepted_tx| { - accepted_tx.and_then(|tx| { - transaction_validator - .validate(tx, block.header.is_genesis(), &wsv) - .map_err(|rejected_tx| rejected_tx.into_v1().rejection_reason) - .wrap_err("Failed to validate transaction") - }) - }) - .try_fold(Vec::new(), |mut acc, tx| { - tx.map(|valid_tx| { - acc.push(valid_tx); - acc - }) - }) - .wrap_err("Error during transaction revalidation")?; + /// Represents a block accepted by consensus. + /// Every [`Self`] will have a different height. + #[derive(Debug, Clone)] + pub struct CommittedBlock(pub(super) VersionedSignedBlock); - // Check that rejected transactions are indeed rejected - let _rejected_transactions = block - .rejected_transactions - .iter() - .cloned() - .map(VersionedRejectedTransaction::into_v1) - .map(|tx_r| { - let tx = SignedTransaction { - payload: tx_r.payload, - signatures: tx_r.signatures.into(), - }; - AcceptedTransaction::accept::( - tx, - &transaction_validator.transaction_limits, - ) - .wrap_err("Failed to accept transaction") - }) - .map(|accepted_tx| { - accepted_tx.and_then(|tx| { - match transaction_validator.validate( - tx, - block.header.is_genesis(), - &wsv, - ) { - Err(rejected_transaction) => Ok(rejected_transaction), - Ok(_) => Err(eyre!( - "Transactions which supposed to be rejected is valid" - )), - } - }) - }) - .try_fold(Vec::new(), |mut acc, rejected_tx| { - rejected_tx.map(|tx| { - acc.push(tx); - acc - }) - }) - .wrap_err("Error during transaction revalidation")?; + impl Block for CommittedBlock { + fn payload(&self) -> &BlockPayload { + self.0.payload() + } + fn signatures(&self) -> &SignaturesOf { + self.0.signatures() + } + } - Ok(()) - } + impl CommittedBlock { + #[deprecated( + since = "2.0.0-pre-rc.13", + note = "This method exists only because some tests are failing, but it shouldn't" + )] + pub(crate) fn commit_without_validation( + block: VersionedSignedBlock, + ) -> (CommittedBlock, Vec) { + let VersionedSignedBlock::V1(block) = block; + let block = CommittedBlock(block.into()); + let events = block.produce_events(); + + (block, events) } } - /// Check if a block has transactions that are already in the blockchain. - fn has_committed_transactions(&self, wsv: &WorldStateView) -> bool { - match self { - VersionedCommittedBlock::V1(block) => { - block - .transactions - .iter() - .any(|transaction| transaction.is_in_blockchain(wsv)) - || block - .rejected_transactions - .iter() - .any(|transaction| transaction.is_in_blockchain(wsv)) + impl From for VersionedSignedBlock { + fn from(source: CommittedBlock) -> Self { + let VersionedSignedBlock::V1(block) = source.0; + + SignedBlock { + payload: block.payload, + signatures: block.signatures, } + .into() } } -} -impl From<&PendingBlock> for Vec { - fn from(block: &PendingBlock) -> Self { - block - .transactions - .iter() - .map(|transaction| -> Event { + impl CommittedBlock { + pub(super) fn produce_events(&self) -> Vec { + let rejected_tx = self + .payload() + .rejected_transactions + .iter() + .map(|transaction| { + PipelineEvent { + entity_kind: PipelineEntityKind::Transaction, + status: PipelineStatus::Rejected(transaction.1.clone().into()), + hash: transaction.0.hash().into(), + } + .into() + }); + let tx = self.payload().transactions.iter().map(|transaction| { PipelineEvent { entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Validating, + status: PipelineStatus::Committed, hash: transaction.hash().into(), } .into() - }) - .chain(block.rejected_transactions.iter().map(|transaction| { + }); + let current_block = core::iter::once( PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Validating, - hash: transaction.hash().into(), + entity_kind: PipelineEntityKind::Block, + status: PipelineStatus::Committed, + hash: self.hash().into(), } - .into() - })) - .chain([PipelineEvent { - entity_kind: PipelineEntityKind::Block, - status: PipelineStatus::Validating, - hash: block.hash().into(), - } - .into()]) - .collect() + .into(), + ); + + tx.chain(rejected_tx).chain(current_block).collect() + } } } @@ -609,17 +708,18 @@ mod tests { use std::str::FromStr; - use iroha_data_model::prelude::*; + use iroha_data_model::{block::Block, prelude::*}; use super::*; use crate::{kura::Kura, smartcontracts::isi::Registrable as _}; #[test] pub fn committed_and_valid_block_hashes_are_equal() { - let valid_block = PendingBlock::new_dummy(); - let committed_block = valid_block.clone().commit_unchecked(); + let topology = Topology::new(Vec::new()); + let valid_block = ValidBlock::new_dummy(); + let committed_block = valid_block.clone().commit(&topology).expect("Valid"); - assert_eq!(*valid_block.hash(), *committed_block.hash()) + assert_eq!(valid_block.hash(), committed_block.0.hash()) } #[test] @@ -648,32 +748,22 @@ mod tests { }; let transaction_validator = TransactionValidator::new(transaction_limits); let tx = TransactionBuilder::new(alice_id, [create_asset_definition], 4000) - .sign(alice_keys.clone()) + .sign(alice_keys) + .expect("Valid"); + let tx: AcceptedTransaction = AcceptedTransaction::accept::(tx, &transaction_limits) + .map(Into::into) .expect("Valid"); - let tx: VersionedAcceptedTransaction = - AcceptedTransaction::accept::(tx, &transaction_limits) - .map(Into::into) - .expect("Valid"); // Creating a block of two identical transactions and validating it let transactions = vec![tx.clone(), tx]; - let valid_block = BlockBuilder { - transactions, - event_recommendations: Vec::new(), - height: 1, - previous_block_hash: None, - view_change_index: 0, - committed_with_topology: Topology::new(Vec::new()), - key_pair: alice_keys, - transaction_validator: &transaction_validator, - wsv, - } - .build(); + let topology = Topology::new(Vec::new()); + let block = BlockBuilder::new(transactions, topology, Vec::new()) + .chain_first(&transaction_validator, wsv); // The first transaction should be confirmed - assert_eq!(valid_block.transactions.len(), 1); + assert_eq!(block.0 .0.transactions.len(), 1); // The second transaction should be rejected - assert_eq!(valid_block.rejected_transactions.len(), 1); + assert_eq!(block.0 .0.rejected_transactions.len(), 1); } } diff --git a/core/src/block_sync.rs b/core/src/block_sync.rs index 10f8e0c43a1..fec1da7f32d 100644 --- a/core/src/block_sync.rs +++ b/core/src/block_sync.rs @@ -8,7 +8,7 @@ use std::{fmt::Debug, sync::Arc, time::Duration}; use iroha_config::block_sync::Configuration; use iroha_crypto::*; -use iroha_data_model::{block::VersionedCommittedBlock, prelude::*}; +use iroha_data_model::prelude::*; use iroha_logger::prelude::*; use iroha_macro::*; use iroha_p2p::Post; @@ -125,41 +125,20 @@ impl BlockSynchronizer { pub mod message { //! Module containing messages for [`BlockSynchronizer`](super::BlockSynchronizer). + use iroha_data_model::block::{Block, BlockPayload, VersionedSignedBlock}; + use super::*; use crate::sumeragi::view_change::ProofChain; declare_versioned_with_scale!(VersionedMessage 1..2, Debug, Clone, iroha_macro::FromVariant); - impl VersionedMessage { - /// Convert from `&VersionedMessage` to V1 reference - pub const fn as_v1(&self) -> &Message { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedMessage` to V1 mutable reference - pub fn as_mut_v1(&mut self) -> &mut Message { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedMessage` to V1 - pub fn into_v1(self) -> Message { - match self { - Self::V1(v1) => v1, - } - } - } - /// Get blocks after some block #[derive(Debug, Clone, Decode, Encode)] pub struct GetBlocksAfter { /// Hash of latest available block - pub latest_hash: Option>, + pub latest_hash: Option>, /// Hash of second to latest block - pub previous_hash: Option>, + pub previous_hash: Option>, /// Peer id pub peer_id: PeerId, } @@ -167,8 +146,8 @@ pub mod message { impl GetBlocksAfter { /// Construct [`GetBlocksAfter`]. pub const fn new( - latest_hash: Option>, - previous_hash: Option>, + latest_hash: Option>, + previous_hash: Option>, peer_id: PeerId, ) -> Self { Self { @@ -183,15 +162,20 @@ pub mod message { #[derive(Debug, Clone, Decode, Encode)] pub struct ShareBlocks { /// Blocks - pub blocks: Vec, + pub blocks: Vec, /// Peer id pub peer_id: PeerId, } impl ShareBlocks { /// Construct [`ShareBlocks`]. - pub const fn new(blocks: Vec, peer_id: PeerId) -> Self { - Self { blocks, peer_id } + fn new(blocks: Vec, peer_id: PeerId) -> Self { + Self { + // Converting into non-validated block because it's not possible + // to guarantee that the sending peer sent a valid committed block + blocks: blocks.into_iter().map(Into::into).collect(), + peer_id, + } } } @@ -242,7 +226,7 @@ pub mod message { .take(1 + block_sync.block_batch_size as usize) .map_while(|height| block_sync.kura.get_block_by_height(height)) .skip_while(|block| Some(block.hash()) == *latest_hash) - .map(|block| VersionedCommittedBlock::clone(&block)) + .map(|block| VersionedSignedBlock::clone(&block)) .collect::>(); if blocks.is_empty() { @@ -258,11 +242,12 @@ pub mod message { } } Message::ShareBlocks(ShareBlocks { blocks, .. }) => { - use crate::sumeragi::message::{Message, MessagePacket}; + use crate::sumeragi::message::{BlockSyncUpdate, Message, MessagePacket}; + for block in blocks.clone() { block_sync.sumeragi.incoming_message(MessagePacket::new( ProofChain::default(), - Message::BlockSyncUpdate(block.into()), + Message::BlockSyncUpdate(BlockSyncUpdate { block }), )); } } diff --git a/core/src/kura.rs b/core/src/kura.rs index c21514d7b04..68a015cc18b 100644 --- a/core/src/kura.rs +++ b/core/src/kura.rs @@ -1,6 +1,6 @@ //! Translates to warehouse. File-system and persistence-related //! logic. [`Kura`] is the main entity which should be used to store -//! new [`Block`](`crate::block::VersionedCommittedBlock`)s on the +//! new [`Block`](`crate::block::VersionedSignedBlock`)s on the //! blockchain. #![allow(clippy::std_instead_of_alloc, clippy::arithmetic_side_effects)] use std::{ @@ -14,12 +14,12 @@ use std::{ use derive_more::Deref; use iroha_config::kura::Mode; use iroha_crypto::HashOf; -use iroha_data_model::block::VersionedCommittedBlock; +use iroha_data_model::block::{Block, BlockPayload, VersionedSignedBlock}; use iroha_logger::prelude::*; use iroha_version::scale::{DecodeVersioned, EncodeVersioned}; use parking_lot::Mutex; -use crate::handler::ThreadHandler; +use crate::{block::CommittedBlock, handler::ThreadHandler}; const INDEX_FILE_NAME: &str = "blocks.index"; const DATA_FILE_NAME: &str = "blocks.data"; @@ -36,12 +36,7 @@ pub struct Kura { block_store: Mutex>, /// The array of block hashes and a slot for an arc of the block. This is normally recovered from the index file. #[allow(clippy::type_complexity)] - block_data: Mutex< - Vec<( - HashOf, - Option>, - )>, - >, + block_data: Mutex, Option>)>>, /// Path to file for plain text blocks. block_plain_text_path: Option, } @@ -100,7 +95,12 @@ impl Kura { }); let shutdown = move || { - let _result = shutdown_sender.send(()); + if let Err(error) = shutdown_sender.send(()) { + iroha_logger::error!( + ?error, + "Failed to send shut down signal to kura. Thead might already be shut down." + ); + } }; ThreadHandler::new(Box::new(shutdown), thread_handle) @@ -113,7 +113,7 @@ impl Kura { /// - file storage is unavailable /// - data in file storage is invalid or corrupted #[iroha_logger::log(skip_all, name = "kura_init")] - pub fn init(&self) -> Result>> { + pub fn init(&self) -> Result>> { let block_store = self.block_store.lock(); let block_index_count: usize = block_store @@ -123,15 +123,15 @@ impl Kura { let mut block_indices = vec![BlockIndex::default(); block_index_count]; block_store.read_block_indices(0, &mut block_indices)?; - let mut block_hashes: Vec> = Vec::new(); + let mut block_hashes = Vec::new(); 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()?]; match block_store.read_block_data(block.start, &mut block_data_buffer) { - Ok(_) => match VersionedCommittedBlock::decode_all_versioned(&block_data_buffer) { + Ok(_) => match VersionedSignedBlock::decode_all_versioned(&block_data_buffer) { Ok(decoded_block) => { - block_hashes.push(decoded_block.hash()); + block_hashes.push(Block::hash(&decoded_block)); } Err(error) => { error!(?error, "Encountered malformed block. Not reading any blocks beyond this height."); @@ -243,7 +243,7 @@ impl Kura { /// Get the hash of the block at the provided height. #[allow(clippy::expect_used)] - pub fn get_block_hash(&self, block_height: u64) -> Option> { + pub fn get_block_hash(&self, block_height: u64) -> Option> { let hash_data_guard = self.block_data.lock(); if block_height == 0 || block_height > hash_data_guard.len() as u64 { return None; @@ -255,7 +255,7 @@ impl Kura { } /// Search through blocks for the height of the block with the given hash. - pub fn get_block_height_by_hash(&self, hash: &HashOf) -> Option { + pub fn get_block_height_by_hash(&self, hash: &HashOf) -> Option { self.block_data .lock() .iter() @@ -267,7 +267,7 @@ impl Kura { #[allow(clippy::expect_used)] // The below lint suggests changing the code into something that does not compile due // to the borrow checker. - pub fn get_block_by_height(&self, block_height: u64) -> Option> { + pub fn get_block_by_height(&self, block_height: u64) -> Option> { let mut data_array_guard = self.block_data.lock(); if block_height == 0 || block_height > data_array_guard.len() as u64 { return None; @@ -290,8 +290,8 @@ impl Kura { block_store .read_block_data(start, &mut block_buf) .expect("Failed to read block data."); - let block = VersionedCommittedBlock::decode_all_versioned(&block_buf) - .expect("Failed to decode block"); + let block = + VersionedSignedBlock::decode_all_versioned(&block_buf).expect("Failed to decode block"); let block_arc = Arc::new(block); data_array_guard[block_number].1 = Some(Arc::clone(&block_arc)); @@ -305,8 +305,8 @@ impl Kura { /// call `get_block_by_height` directly. pub fn get_block_by_hash( &self, - block_hash: &HashOf, - ) -> Option> { + block_hash: &HashOf, + ) -> Option> { let index = self .block_data .lock() @@ -317,15 +317,19 @@ impl Kura { } /// Put a block in kura's in memory block store. - pub fn store_block(&self, block: VersionedCommittedBlock) { + pub fn store_block(&self, block: CommittedBlock) { + let block = VersionedSignedBlock::from(block); + self.block_data .lock() .push((block.hash(), Some(Arc::new(block)))); } /// Replace the block in `Kura`'s in memory block store. - pub fn replace_top_block(&self, block: VersionedCommittedBlock) { + pub fn replace_top_block(&self, block: CommittedBlock) { + let block = VersionedSignedBlock::from(block); let mut data = self.block_data.lock(); + data.pop(); data.push((block.hash(), Some(Arc::new(block)))); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 616de19fda3..f42c2f9b8b2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -97,7 +97,10 @@ pub mod handler { fn drop(&mut self) { (self.shutdown.take().expect("Always some after init"))(); let handle = self.handle.take().expect("Always some after init"); - let _joined = handle.join(); + + if let Err(error) = handle.join() { + iroha_logger::error!(?error, "Fatal error: thread panicked"); + } } } } @@ -111,10 +114,6 @@ pub mod prelude { #[doc(inline)] pub use crate::{ smartcontracts::ValidQuery, - tx::{ - AcceptedTransaction, ValidTransaction, VersionedAcceptedTransaction, - VersionedValidTransaction, - }, wsv::{World, WorldStateView}, IsInBlockchain, }; diff --git a/core/src/queue.rs b/core/src/queue.rs index e592cc48469..9e497fa6ea7 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -15,6 +15,7 @@ use eyre::{Report, Result}; use iroha_config::queue::Configuration; use iroha_crypto::HashOf; use iroha_data_model::transaction::prelude::*; +use iroha_genesis::AcceptedTransaction; use iroha_logger::{debug, info, trace, warn}; use iroha_primitives::{must_use::MustUse, riffle_iter::RiffleIter}; use rand::seq::IteratorRandom; @@ -28,13 +29,13 @@ use crate::{prelude::*, tx::CheckSignatureCondition as _}; #[derive(Debug)] pub struct Queue { /// The queue for transactions that passed signature check - queue: ArrayQueue>, + queue: ArrayQueue>, /// The queue for transactions that didn't pass signature check and are waiting for additional signatures /// /// Second queue is needed to prevent situation when multisig transactions prevent ordinary transactions from being added into the queue - signature_buffer: ArrayQueue>, - /// [`VersionedAcceptedTransaction`]s addressed by `Hash`. - txs: DashMap, VersionedAcceptedTransaction>, + signature_buffer: ArrayQueue>, + /// [`AcceptedTransaction`]s addressed by `Hash`. + txs: DashMap, AcceptedTransaction>, /// The maximum number of transactions in the block pub txs_in_block: usize, /// The maximum number of transactions in the queue @@ -71,7 +72,7 @@ pub enum Error { #[error("Failure during signature condition execution, tx hash: {tx_hash}, reason: {reason}")] SignatureCondition { /// Transaction hash - tx_hash: HashOf, + tx_hash: HashOf, /// Failure reason reason: Report, }, @@ -81,7 +82,7 @@ pub enum Error { #[derive(Debug)] pub struct Failure { /// Transaction failed to be pushed into the queue - pub tx: VersionedAcceptedTransaction, + pub tx: AcceptedTransaction, /// Push failure reason pub err: Error, } @@ -103,12 +104,12 @@ impl Queue { } } - fn is_pending(&self, tx: &VersionedAcceptedTransaction, wsv: &WorldStateView) -> bool { + fn is_pending(&self, tx: &AcceptedTransaction, wsv: &WorldStateView) -> bool { !tx.is_expired(self.tx_time_to_live) && !tx.is_in_blockchain(wsv) } /// Returns all pending transactions. - pub fn all_transactions(&self, wsv: &WorldStateView) -> Vec { + pub fn all_transactions(&self, wsv: &WorldStateView) -> Vec { self.txs .iter() .filter(|e| self.is_pending(e.value(), wsv)) @@ -117,11 +118,7 @@ impl Queue { } /// Returns `n` randomly selected transaction from the queue. - pub fn n_random_transactions( - &self, - n: u32, - wsv: &WorldStateView, - ) -> Vec { + pub fn n_random_transactions(&self, n: u32, wsv: &WorldStateView) -> Vec { self.txs .iter() .filter(|e| self.is_pending(e.value(), wsv)) @@ -134,14 +131,14 @@ impl Queue { fn check_tx( &self, - tx: &VersionedAcceptedTransaction, + tx: &AcceptedTransaction, wsv: &WorldStateView, ) -> Result, Error> { if tx.is_in_future(self.future_threshold) { Err(Error::InFuture) } else if tx.is_expired(self.tx_time_to_live) { Err(Error::Expired { - time_to_live_ms: tx.payload().time_to_live_ms, + time_to_live_ms: tx.payload.time_to_live_ms, }) } else if tx.is_in_blockchain(wsv) { Err(Error::InBlockchain) @@ -158,11 +155,7 @@ impl Queue { /// /// # Errors /// See [`enum@Error`] - pub fn push( - &self, - tx: VersionedAcceptedTransaction, - wsv: &WorldStateView, - ) -> Result<(), Failure> { + pub fn push(&self, tx: AcceptedTransaction, wsv: &WorldStateView) -> Result<(), Failure> { trace!(?tx, "Pushing to the queue"); let signature_check_succeed = match self.check_tx(&tx, wsv) { Err(err) => { @@ -178,11 +171,7 @@ impl Queue { let entry = match self.txs.entry(hash) { Entry::Occupied(mut old_tx) => { // MST case - old_tx - .get_mut() - .as_mut_v1() - .signatures - .extend(tx.as_v1().signatures.clone()); + old_tx.get_mut().signatures.extend(tx.signatures); info!("Signature added to existing multisignature transaction"); return Ok(()); } @@ -229,10 +218,10 @@ impl Queue { /// Pop single transaction from the signature buffer. Record all visited and not removed transactions in `seen`. fn pop_from_signature_buffer( &self, - seen: &mut Vec>, + seen: &mut Vec>, wsv: &WorldStateView, - expired_transactions: &mut Vec, - ) -> Option { + expired_transactions: &mut Vec, + ) -> Option { // NOTE: `SKIP_SIGNATURE_CHECK=false` because `signature_buffer` contains transaction which signature check can be either `true` or `false`. self.pop_from::(&self.signature_buffer, seen, wsv, expired_transactions) } @@ -240,10 +229,10 @@ impl Queue { /// Pop single transaction from the queue. Record all visited and not removed transactions in `seen`. fn pop_from_queue( &self, - seen: &mut Vec>, + seen: &mut Vec>, wsv: &WorldStateView, - expired_transactions: &mut Vec, - ) -> Option { + expired_transactions: &mut Vec, + ) -> Option { // NOTE: `SKIP_SIGNATURE_CHECK=true` because `queue` contains only transactions for which signature check is `true`. self.pop_from::(&self.queue, seen, wsv, expired_transactions) } @@ -252,11 +241,11 @@ impl Queue { #[inline] fn pop_from( &self, - queue: &ArrayQueue>, - seen: &mut Vec>, + queue: &ArrayQueue>, + seen: &mut Vec>, wsv: &WorldStateView, - expired_transactions: &mut Vec, - ) -> Option { + expired_transactions: &mut Vec, + ) -> Option { loop { let Some(hash) = queue.pop() else { trace!("Queue is empty"); @@ -303,10 +292,7 @@ impl Queue { /// /// BEWARE: Shouldn't be called in parallel with itself. #[cfg(test)] - fn collect_transactions_for_block( - &self, - wsv: &WorldStateView, - ) -> Vec { + fn collect_transactions_for_block(&self, wsv: &WorldStateView) -> Vec { let mut transactions = Vec::with_capacity(self.txs_in_block); self.get_transactions_for_block(wsv, &mut transactions, &mut Vec::new()); transactions @@ -318,8 +304,8 @@ impl Queue { pub fn get_transactions_for_block( &self, wsv: &WorldStateView, - transactions: &mut Vec, - expired_transactions: &mut Vec, + transactions: &mut Vec, + expired_transactions: &mut Vec, ) { if transactions.len() >= self.txs_in_block { return; @@ -341,10 +327,8 @@ impl Queue { ) }); - let transactions_hashes: HashSet> = transactions - .iter() - .map(VersionedAcceptedTransaction::hash) - .collect(); + let transactions_hashes: HashSet<_> = + transactions.iter().map(AcceptedTransaction::hash).collect(); let txs = txs_from_queue .riffle(txs_from_waiting_buffer) .filter(|tx| !transactions_hashes.contains(&tx.hash())) @@ -386,11 +370,7 @@ mod tests { use super::*; use crate::{kura::Kura, smartcontracts::isi::Registrable as _, wsv::World, PeersIds}; - fn accepted_tx( - account_id: &str, - proposed_ttl_ms: u64, - key: KeyPair, - ) -> VersionedAcceptedTransaction { + fn accepted_tx(account_id: &str, proposed_ttl_ms: u64, key: KeyPair) -> AcceptedTransaction { let message = std::iter::repeat_with(rand::random::) .take(16) .collect(); @@ -755,7 +735,7 @@ mod tests { max_instruction_number: 4096, max_wasm_size_bytes: 0, }; - let fully_signed_tx: VersionedAcceptedTransaction = { + let fully_signed_tx: AcceptedTransaction = { let mut signed_tx = tx .clone() .sign((&key_pairs[0]).clone()) @@ -782,7 +762,7 @@ mod tests { .into() }; for key_pair in key_pairs { - let partially_signed_tx: VersionedAcceptedTransaction = get_tx(key_pair); + let partially_signed_tx: AcceptedTransaction = get_tx(key_pair); // Check that non of partially signed pass signature check assert!(matches!( partially_signed_tx.check_signature_condition(&wsv), @@ -1059,7 +1039,7 @@ mod tests { let mut tx = accepted_tx("alice@wonderland", 100_000, alice_key); assert!(queue.push(tx.clone(), &wsv).is_ok()); // tamper timestamp - tx.as_mut_v1().payload.creation_time += 2 * future_threshold_ms; + tx.payload.creation_time_ms += 2 * future_threshold_ms; assert!(matches!( queue.push(tx, &wsv), Err(Failure { diff --git a/core/src/smartcontracts/isi/block.rs b/core/src/smartcontracts/isi/block.rs index 49301e4bed1..a4eeaafc402 100644 --- a/core/src/smartcontracts/isi/block.rs +++ b/core/src/smartcontracts/isi/block.rs @@ -1,8 +1,11 @@ //! This module contains trait implementations related to block queries use eyre::{Result, WrapErr}; -use iroha_data_model::query::{ - block::FindBlockHeaderByHash, - error::{FindError, QueryExecutionFailure}, +use iroha_data_model::{ + block::Block, + query::{ + block::FindBlockHeaderByHash, + error::{FindError, QueryExecutionFailure}, + }, }; use iroha_telemetry::metrics; @@ -22,7 +25,7 @@ impl ValidQuery for FindAllBlockHeaders { let block_headers = wsv .all_blocks_by_value() .rev() - .map(|block| block.into_v1().header) + .map(|VersionedSignedBlock::V1(block)| block.payload.header) .collect(); Ok(block_headers) } @@ -35,14 +38,11 @@ impl ValidQuery for FindBlockHeaderByHash { .hash .evaluate(&Context::new(wsv)) .wrap_err("Failed to evaluate hash") - .map_err(|e| QueryExecutionFailure::Evaluate(e.to_string()))? - .typed(); + .map_err(|e| QueryExecutionFailure::Evaluate(e.to_string()))?; - let block = wsv - .all_blocks_by_value() + wsv.all_blocks_by_value() .find(|block| block.hash() == hash) - .ok_or_else(|| QueryExecutionFailure::Find(Box::new(FindError::Block(hash))))?; - - Ok(block.into_v1().header) + .ok_or_else(|| QueryExecutionFailure::Find(Box::new(FindError::Block(hash)))) + .map(|VersionedSignedBlock::V1(block)| block.payload.header) } } diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index e7b77844965..b32a123539d 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -91,14 +91,15 @@ mod tests { use std::str::FromStr; - use iroha_crypto::{Hash, HashOf, KeyPair}; - use iroha_data_model::{block::VersionedCommittedBlock, transaction::TransactionLimits}; + use iroha_crypto::{HashOf, KeyPair}; + use iroha_data_model::{block::Block, transaction::TransactionLimits}; + use iroha_genesis::AcceptedTransaction; use once_cell::sync::Lazy; use super::*; use crate::{ - block::*, kura::Kura, smartcontracts::isi::Registrable as _, tx::TransactionValidator, - wsv::World, PeersIds, + block::*, kura::Kura, smartcontracts::isi::Registrable as _, + sumeragi::network_topology::Topology, tx::TransactionValidator, wsv::World, PeersIds, }; static ALICE_KEYS: Lazy = Lazy::new(|| KeyPair::generate().unwrap()); @@ -190,32 +191,25 @@ mod tests { let valid_tx = { let tx = TransactionBuilder::new(ALICE_ID.clone(), vec![], 4000).sign(ALICE_KEYS.clone())?; - VersionedAcceptedTransaction::from(AcceptedTransaction::accept::(tx, &limits)?) + AcceptedTransaction::accept::(tx, &limits).expect("Valid") }; let invalid_tx = { let isi: InstructionBox = FailBox::new("fail").into(); let tx = TransactionBuilder::new(ALICE_ID.clone(), vec![isi.clone(), isi], 4000) .sign(ALICE_KEYS.clone())?; - AcceptedTransaction::accept::(tx, &huge_limits)?.into() + AcceptedTransaction::accept::(tx, &huge_limits).expect("Valid") }; let mut transactions = vec![valid_tx; valid_tx_per_block]; transactions.append(&mut vec![invalid_tx; invalid_tx_per_block]); - let first_block: VersionedCommittedBlock = BlockBuilder { - transactions: transactions.clone(), - event_recommendations: Vec::new(), - height: 1, - previous_block_hash: None, - view_change_index: 0, - committed_with_topology: crate::sumeragi::network_topology::Topology::new(vec![]), - key_pair: ALICE_KEYS.clone(), - transaction_validator: &TransactionValidator::new(limits), - wsv: wsv.clone(), - } - .build() - .commit_unchecked() - .into(); + let topology = Topology::new(Vec::new()); + let (first_block, _): (CommittedBlock, _) = + BlockBuilder::new(transactions.clone(), topology.clone(), vec![]) + .chain_first(&TransactionValidator::new(limits), wsv.clone()) + .sign(ALICE_KEYS.clone()) + .expect("Failed to sign block") + .commit_unchecked(); let mut curr_hash = first_block.hash(); @@ -223,21 +217,18 @@ mod tests { kura.store_block(first_block); for height in 1u64..blocks { - let block: VersionedCommittedBlock = BlockBuilder { - transactions: transactions.clone(), - event_recommendations: Vec::new(), - height, - previous_block_hash: Some(curr_hash), - view_change_index: 0, - committed_with_topology: crate::sumeragi::network_topology::Topology::new(vec![]), - key_pair: ALICE_KEYS.clone(), - transaction_validator: &TransactionValidator::new(limits), - wsv: wsv.clone(), - } - .build() - .commit_unchecked() - .into(); - + let (block, _): (CommittedBlock, _) = + BlockBuilder::new(transactions.clone(), topology.clone(), vec![]) + .chain( + height, + Some(curr_hash), + 0, + &TransactionValidator::new(limits), + wsv.clone(), + ) + .sign(ALICE_KEYS.clone()) + .expect("Failed to sign block") + .commit_unchecked(); curr_hash = block.hash(); wsv.apply(&block)?; kura.store_block(block); @@ -315,11 +306,11 @@ mod tests { .expect("WSV is empty"); assert_eq!( - &FindBlockHeaderByHash::new(*block.hash()).execute(&wsv)?, + &FindBlockHeaderByHash::new(block.hash()).execute(&wsv)?, block.header() ); - assert!(FindBlockHeaderByHash::new(Hash::new([42])) + assert!(FindBlockHeaderByHash::new(HashOf::new(&42).transmute()) .execute(&wsv) .is_err()); @@ -365,33 +356,25 @@ mod tests { max_wasm_size_bytes: 0, }; - let va_tx: VersionedAcceptedTransaction = - AcceptedTransaction::accept::(signed_tx, &tx_limits)?.into(); - - let vcb: VersionedCommittedBlock = BlockBuilder { - transactions: vec![va_tx.clone()], - event_recommendations: Vec::new(), - height: 1, - previous_block_hash: None, - view_change_index: 0, - committed_with_topology: crate::sumeragi::network_topology::Topology::new(vec![]), - key_pair: ALICE_KEYS.clone(), - transaction_validator: &TransactionValidator::new(tx_limits), - wsv: wsv.clone(), - } - .build() - .commit_unchecked() - .into(); + let va_tx: AcceptedTransaction = + AcceptedTransaction::accept::(signed_tx, &tx_limits).expect("Valid"); - wsv.apply(&vcb)?; - kura.store_block(vcb); + let topology = Topology::new(Vec::new()); + let (block, _) = BlockBuilder::new(vec![va_tx.clone()], topology.clone(), Vec::new()) + .chain_first(&TransactionValidator::new(tx_limits), wsv.clone()) + .sign(ALICE_KEYS.clone()) + .expect("Failed to sign blocks.") + .commit(&topology) + .expect("Valid"); + wsv.apply(&block)?; + kura.store_block(block); - let wrong_hash: Hash = HashOf::new(&2_u8).into(); + let wrong_hash = HashOf::new(&2_u8).transmute(); let not_found = FindTransactionByHash::new(wrong_hash).execute(&wsv); assert!(matches!(not_found, Err(_))); - let found_accepted = FindTransactionByHash::new(Hash::from(va_tx.hash())).execute(&wsv)?; - match found_accepted { + let found_accepted = FindTransactionByHash::new(va_tx.hash()).execute(&wsv)?; + match found_accepted.tx_value { TransactionValue::Transaction(tx) => { assert_eq!(va_tx.hash().transmute(), tx.hash()) } diff --git a/core/src/smartcontracts/isi/tx.rs b/core/src/smartcontracts/isi/tx.rs index 3e89bcd94d6..7c8b6266070 100644 --- a/core/src/smartcontracts/isi/tx.rs +++ b/core/src/smartcontracts/isi/tx.rs @@ -39,8 +39,8 @@ impl ValidQuery for FindTransactionByHash { .evaluate(&Context::new(wsv)) .wrap_err("Failed to get hash") .map_err(|e| QueryExecutionFailure::Evaluate(e.to_string()))?; + iroha_logger::trace!(%hash); - let hash = hash.typed(); if !wsv.has_transaction(&hash) { return Err(FindError::Transaction(hash).into()); }; diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index d3cde4871a2..dbb75dbcfbf 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -98,10 +98,10 @@ pub type Result = core::result::Result; /// Panics if something is wrong with the configuration. /// Configuration is hardcoded and tested, so this function should never panic. pub fn create_engine() -> Engine { - create_config() - .and_then(|config| { - Engine::new(&config).map_err(|err| Error::Initialization(eyre!(Box::new(err)))) - }) + let config = create_config(); + + Engine::new(&config) + .map_err(|err| Error::Initialization(eyre!(Box::new(err)))) .expect("Failed to create WASM engine with a predefined configuration. This is a bug") } @@ -116,13 +116,18 @@ pub fn load_module(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result Result { +fn create_config() -> Config { let mut config = Config::new(); + config.consume_fuel(true); + + if let Err(error) = config.cache_config_load_default() { + iroha_logger::warn!( + ?error, + "Setting up Wasmtime cache failed. Performance might degrade" + ); + } + config - .consume_fuel(true) - .cache_config_load_default() - .map_err(|err| Error::Initialization(eyre!(Box::new(err))))?; - Ok(config) } #[derive(Clone)] @@ -269,7 +274,7 @@ impl<'wrld> Runtime<'wrld> { let wsv = caller.data().wsv; wsv.validator_view() - .validate(wsv, &caller.data().account_id, query.clone()) + .validate(wsv, &caller.data().account_id, query.clone().into()) .map_err(|error| NotPermittedFail { reason: error.to_string(), }) @@ -334,7 +339,7 @@ impl<'wrld> Runtime<'wrld> { .map_err(|error| Trap::new(error.to_string()))?; } wsv.validator_view() - .validate(wsv, account_id, instruction) + .validate(wsv, account_id, instruction.into()) .map_err(|error| NotPermittedFail { reason: error.to_string(), }) diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index f0ca7933b2c..49fc88e3823 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -1,18 +1,17 @@ //! The main event loop that powers sumeragi. -#![allow(clippy::cognitive_complexity)] + use iroha_crypto::HashOf; use iroha_data_model::{block::*, transaction::error::TransactionExpired}; +use iroha_genesis::AcceptedTransaction; use iroha_p2p::UpdateTopology; use parking_lot::Mutex; use tracing::{span, Level}; -use super::*; +use super::{view_change::ProofBuilder, *}; use crate::{block::*, sumeragi::tracing::instrument}; /// `Sumeragi` is the implementation of the consensus. /// -/// TODO: paraphrase -/// /// `sumeragi_state_data` is a [`Mutex`] instead of a `RWLock` /// because it communicates more clearly the correct use of the /// lock. The most frequent action on this lock is the main loop @@ -72,12 +71,12 @@ impl Debug for Sumeragi { /// Internal structure that retains the state. pub struct State { - /// The view change index of latest [`VersionedCommittedBlock`] + /// The view change index of latest [`CommittedBlock`] pub latest_block_view_change_index: u64, - /// The hash of the latest [`VersionedCommittedBlock`] - pub latest_block_hash: Option>, - /// Hash of the previous [`VersionedCommittedBlock`] - pub previous_block_hash: Option>, + /// The hash of the latest [`CommittedBlock`] + pub latest_block_hash: Option>, + /// Hash of the previous [`CommittedBlock`] + pub previous_block_hash: Option>, /// Current block height pub latest_block_height: u64, /// The current network topology. @@ -98,7 +97,7 @@ pub struct State { /// other subsystems where we can. This way the performance of /// sumeragi is more dependent on the code that is internal to the /// subsystem. - pub transaction_cache: Vec, + pub transaction_cache: Vec, } impl Sumeragi { @@ -163,7 +162,7 @@ impl Sumeragi { #[allow(clippy::needless_pass_by_value)] fn broadcast_packet(&self, msg: MessagePacket, topology: &Topology) { - self.broadcast_packet_to(msg, &topology.sorted_peers); + self.broadcast_packet_to(msg, &topology.ordered_peers); } fn gossip_transactions(&self, state: &State, view_change_proof_chain: &ProofChain) { @@ -188,7 +187,7 @@ impl Sumeragi { /// Connect or disconnect peers according to the current network topology. fn connect_peers(&self, topology: &Topology) { - let peers = topology.sorted_peers.clone().into_iter().collect(); + let peers = topology.ordered_peers.clone().into_iter().collect(); self.network.update_topology(UpdateTopology(peers)); } @@ -198,11 +197,11 @@ impl Sumeragi { self.block_time + self.commit_time } - fn send_events(&self, events: impl Into>) { + fn send_events(&self, events: Vec) { let addr = &self.peer_id.address; if self.events_sender.receiver_count() > 0 { - for event in events.into() { + for event in events { self.events_sender .send(event) .map_err(|err| warn!(%addr, ?err, "Event not sent")) @@ -222,7 +221,7 @@ impl Sumeragi { Ok(packet) => { if let Err(error) = view_change_proof_chain.merge( packet.view_change_proofs, - ¤t_topology.sorted_peers, + ¤t_topology.ordered_peers, current_topology.max_faults(), state.latest_block_hash, ) { @@ -247,64 +246,22 @@ impl Sumeragi { shutdown_receiver: &mut tokio::sync::oneshot::Receiver<()>, ) -> Result<(), EarlyReturn> { trace!("Listen for genesis"); - assert!( - state.current_topology.is_consensus_required(), - "Only peer in network, yet required to receive genesis topology. This is a configuration error." - ); + loop { + let addr = &self.peer_id.address; + std::thread::sleep(Duration::from_millis(50)); early_return(shutdown_receiver).map_err(|e| { debug!(?e, "Early return."); e })?; - // we must connect to peers so that our block_sync can find us - // the genesis block. + + // Connect to peers so that block_sync can find the genesis block. match self.message_receiver.lock().try_recv() { Ok(packet) => { let block = match packet.message { - Message::BlockCreated(block_created) => { - // If we receive a committed genesis block that is - // valid, use it without question. During the - // genesis round we blindly take on the network - // topology described in the provided genesis - // block. - let block = { - let span = span!( - Level::TRACE, - "Genesis Round Peer is revalidating the block." - ); - let _enter = span.enter(); - match block_created.validate_and_extract_block::( - &self.transaction_validator, - state.wsv.clone(), - state.latest_block_hash, - state.latest_block_height, - ) { - Ok(block) => block, - Err(error) => { - error!(?error); - continue; - } - } - }; - // Omit signature verification during genesis round - block.commit_unchecked().into() - } - Message::BlockSyncUpdate(block_sync_update) => { - // Omit signature verification during genesis round - match block_sync_update.validate_and_extract_block::( - &self.transaction_validator, - state.wsv.clone(), - state.latest_block_hash, - state.latest_block_height, - ) { - Ok(block) => block, - Err(error) => { - error!(?error); - continue; - } - } - } + Message::BlockCreated(BlockCreated { block }) + | Message::BlockSyncUpdate(BlockSyncUpdate { block }) => block, msg => { trace!(?msg, "Not handling the message, waiting for genesis..."); continue; @@ -312,9 +269,31 @@ impl Sumeragi { }; if block.header().is_genesis() { + let block = match ValidBlock::validate( + block, + state.latest_block_height + 1, + state.latest_block_hash, + &state.current_topology, + &self.transaction_validator, + state.wsv.clone(), + ) { + Ok(block) => match block.commit(&state.current_topology) { + Ok(block) => block, + Err((block, err)) => { + warn!(%addr, hash=%block.hash(), ?err, "Received invalid genesis block"); + continue; + } + }, + Err((block, err)) => { + warn!(%addr, hash=%block.hash(), ?err, "Failed to commit genesis block"); + continue; + } + }; + commit_block(self, state, block); return Err(EarlyReturn::GenesisBlockReceivedAndCommitted); } + debug!("Received a block that was not genesis."); } Err(mpsc::TryRecvError::Disconnected) => return Err(EarlyReturn::Disconnected), @@ -324,37 +303,56 @@ impl Sumeragi { } } -fn commit_block(sumeragi: &Sumeragi, state: &mut State, block: impl Into) { - let committed_block = block.into(); - +fn commit_block( + sumeragi: &Sumeragi, + state: &mut State, + (committed_block, events): (CommittedBlock, Vec), +) { + let block_hash = committed_block.hash(); state.finalized_wsv = state.wsv.clone(); - update_state(state, sumeragi, &committed_block); + update_state(state, sumeragi, &committed_block, events); state.previous_block_hash = state.latest_block_hash; info!( addr=%sumeragi.peer_id.address, role=%state.current_topology.role(&sumeragi.peer_id), block_height=%state.latest_block_height, - block_hash=%committed_block.hash(), + %block_hash, "Committing block" ); update_topology(state, sumeragi, &committed_block); - sumeragi.kura.store_block(committed_block); - - cache_transaction(state, sumeragi); + cache_transactions(state, sumeragi); } -fn replace_top_block( - sumeragi: &Sumeragi, - state: &mut State, - block: impl Into, -) { - let committed_block = block.into(); +fn replace_top_block(sumeragi: &Sumeragi, state: &mut State, block: ValidBlock) { + let role = state.current_topology.role(&sumeragi.peer_id); + let addr = &sumeragi.peer_id.address; + + let header = block.header(); + let block_hash = block.hash(); + + warn!( + %addr, %role, %block_hash, + peer_latest_block_hash=?state.latest_block_hash, + peer_latest_block_view_change_index=?state.latest_block_view_change_index, + consensus_latest_block_view_change_index=%header.view_change_index, + "Soft fork occurred: peer in inconsistent state. Rolling back and replacing top block." + ); + + let (committed_block, events) = match block + .commit_without_proxy_tail_signature(&state.current_topology) + { + Ok(block) => block, + Err((_, err)) => { + error!(?err, %block_hash, "Failed to commit replacement block. Unable to resolve soft fork."); + return; + } + }; state.wsv = state.finalized_wsv.clone(); - update_state(state, sumeragi, &committed_block); + update_state(state, sumeragi, &committed_block, events); // state.previous_block_hash stays the same. info!( @@ -366,20 +364,12 @@ fn replace_top_block( ); update_topology(state, sumeragi, &committed_block); - sumeragi.kura.replace_top_block(committed_block); - - cache_transaction(state, sumeragi) + cache_transactions(state, sumeragi) } -fn update_topology( - state: &mut State, - sumeragi: &Sumeragi, - committed_block: &VersionedCommittedBlock, -) { - let mut topology = Topology { - sorted_peers: committed_block.header().committed_with_topology.clone(), - }; +fn update_topology(state: &mut State, sumeragi: &Sumeragi, committed_block: &CommittedBlock) { + let mut topology = Topology::new(committed_block.header().commit_topology.clone()); topology.lift_up_peers( &committed_block .signatures() @@ -400,7 +390,12 @@ fn update_topology( sumeragi.connect_peers(&state.current_topology); } -fn update_state(state: &mut State, sumeragi: &Sumeragi, committed_block: &VersionedCommittedBlock) { +fn update_state( + state: &mut State, + sumeragi: &Sumeragi, + committed_block: &CommittedBlock, + events: Vec, +) { state .wsv .apply(committed_block) @@ -413,14 +408,14 @@ fn update_state(state: &mut State, sumeragi: &Sumeragi, committed_block: &Versio // This sends "Block committed" event, so it should be done // AFTER public facing WSV update - sumeragi.send_events(committed_block); + sumeragi.send_events(events); state.latest_block_height = committed_block.header().height; state.latest_block_hash = Some(committed_block.hash()); state.latest_block_view_change_index = committed_block.header().view_change_index; } -fn cache_transaction(state: &mut State, sumeragi: &Sumeragi) { +fn cache_transactions(state: &mut State, sumeragi: &Sumeragi) { state.transaction_cache.retain(|tx| { !tx.is_in_blockchain(&state.wsv) && !tx.is_expired(sumeragi.queue.tx_time_to_live) }); @@ -432,32 +427,35 @@ fn suggest_view_change( view_change_proof_chain: &mut ProofChain, current_view_change_index: u64, ) { - let suspect_proof = { - let mut proof = Proof { - latest_block_hash: state.latest_block_hash, - view_change_index: current_view_change_index, - signatures: Vec::new(), - }; - proof - .sign(sumeragi.key_pair.clone()) - .expect("Proof signing failed"); - proof + let view_change_proof = { + let mut proof = ProofBuilder::new(current_view_change_index); + + if let Some(latest_block_hash) = state.latest_block_hash { + proof = proof.with_latest_block_hash(latest_block_hash); + } + + proof.sign(sumeragi.key_pair.clone()) }; - view_change_proof_chain - .insert_proof( - &state.current_topology.sorted_peers, - state.current_topology.max_faults(), - state.latest_block_hash, - suspect_proof, - ) - .unwrap_or_else(|err| error!("{err}")); + match view_change_proof { + Ok(proof) => { + view_change_proof_chain + .insert_proof( + &state.current_topology.ordered_peers, + state.current_topology.max_faults(), + state.latest_block_hash, + proof, + ) + .unwrap_or_else(|error| error!(?error, "View change proof incorrect")); - let msg = MessagePacket::new( - view_change_proof_chain.clone(), - Message::ViewChangeSuggested, - ); - sumeragi.broadcast_packet(msg, &state.current_topology); + let msg = MessagePacket::new( + view_change_proof_chain.clone(), + Message::ViewChangeSuggested, + ); + sumeragi.broadcast_packet(msg, &state.current_topology); + } + Err(error) => error!(?error, "Failed to sign view change proof"), + } } fn prune_view_change_proofs_and_calculate_current_index( @@ -466,18 +464,17 @@ fn prune_view_change_proofs_and_calculate_current_index( ) -> u64 { view_change_proof_chain.prune(state.latest_block_hash); view_change_proof_chain.verify_with_state( - &state.current_topology.sorted_peers, + &state.current_topology.ordered_peers, state.current_topology.max_faults(), state.latest_block_hash, ) as u64 } fn enqueue_transaction(sumeragi: &Sumeragi, wsv: &WorldStateView, tx: VersionedSignedTransaction) { - let tx = tx.into_v1(); - let addr = &sumeragi.peer_id.address; + match AcceptedTransaction::accept::(tx, &sumeragi.transaction_limits) { - Ok(tx) => match sumeragi.queue.push(tx.into(), wsv) { + Ok(tx) => match sumeragi.queue.push(tx, wsv) { Ok(_) => {} Err(crate::queue::Failure { tx, @@ -489,19 +486,19 @@ fn enqueue_transaction(sumeragi: &Sumeragi, wsv: &WorldStateView, tx: VersionedS error!(%addr, ?err, tx_hash = %tx.hash(), "Failed to enqueue transaction.") } }, - Err(err) => error!(%addr, %err, "Transaction rejected"), + Err((_tx, err)) => error!(%addr, %err, "Transaction rejected"), } } #[allow(clippy::too_many_lines)] -fn handle_message( +fn handle_message( message: Message, sumeragi: &Sumeragi, state: &mut State, voting_block: &mut Option, current_view_change_index: u64, view_change_proof_chain: &mut ProofChain, - voting_signatures: &mut Vec>, + voting_signatures: &mut Vec>, ) { let current_topology = &state.current_topology; let role = current_topology.role(&sumeragi.peer_id); @@ -516,90 +513,83 @@ fn handle_message( (Message::ViewChangeSuggested, _) => { trace!("Received view change suggestion."); } - (Message::BlockSyncUpdate(block_sync_update), _) => { - let block_hash = block_sync_update.hash(); - info!(%addr, %role, hash=%block_hash, "Block sync update received"); - - let block = match block_sync_update - .clone() - .validate_and_extract_block::( - &sumeragi.transaction_validator, - state.wsv.clone(), - state.latest_block_hash, - state.latest_block_height, - ) - .or_else(|_| - /* If the block fails validation we must check again using the finaziled wsv. - When a soft-fork occurs the consensus-block may be valid on the previous - wsv but not the current one. */ - block_sync_update.validate_and_extract_block::( - &sumeragi.transaction_validator, - state.finalized_wsv.clone(), - state.previous_block_hash, - state.latest_block_height.saturating_sub(1), - )) { + (Message::BlockSyncUpdate(BlockSyncUpdate { block }), _) => { + let block_hash = block.hash(); + debug!(%addr, %role, %block_hash, "Block sync update received"); + + let block = match ValidBlock::validate_without_validating_topology( + block, + state.latest_block_height + 1, + state.latest_block_hash, + &sumeragi.transaction_validator, + state.wsv.clone(), + ) { Ok(block) => block, - Err(error) => { - error!(%addr, %role, %block_hash, ?error, "Block not valid."); - return; + Err((block, error)) => { + let header = block.header(); + + // TODO: What is the correct condition for soft-fork? + #[allow(clippy::redundant_else)] + if state.latest_block_height == header.height + && state.previous_block_hash == header.previous_block_hash + && state.latest_block_hash != Some(block_hash) + && state.latest_block_view_change_index < header.view_change_index + { + debug!(%addr, %role, %block_hash, ?error, "Block appears to be invalid. Revalidating..."); + + /* If the block fails validation we must check again using the finaziled wsv. + When a soft-fork occurs the consensus-block may be valid on the previous + wsv but not the current one. */ + match ValidBlock::validate_without_validating_topology( + block, + state.latest_block_height, + state.previous_block_hash, + &sumeragi.transaction_validator, + state.finalized_wsv.clone(), + ) { + Ok(block) => { + replace_top_block(sumeragi, state, block); + return; + } + Err((_, error)) => { + warn!(%addr, %role, %block_hash, ?error, "Block rejected"); + return; + } + } + } else { + warn!(%addr, %role, %block_hash, ?error, "Block rejected"); + return; + } } }; - if state.previous_block_hash == block.header().previous_block_hash - && state.latest_block_height == block.header().height - && state.latest_block_hash != Some(block.hash()) - && state.latest_block_view_change_index < block.header().view_change_index - { - error!( - %addr, %role, - peer_latest_block_hash=?state.latest_block_hash, - peer_latest_block_view_change_index=?state.latest_block_view_change_index, - consensus_latest_block_hash=%block.hash(), - consensus_latest_block_view_change_index=%block.header().view_change_index, - "Soft fork occurred: peer in inconsistent state. Rolling back and replacing top block." - ); - replace_top_block(sumeragi, state, block); - return; - } - if state.latest_block_hash != block.header().previous_block_hash { - error!( - %addr, %role, - actual = ?block.header().previous_block_hash, - expected = ?state.latest_block_hash, - "Mismatch between the actual and expected hashes of the latest block." - ); - return; - } - if state.latest_block_height + 1 != block.header().height { - error!( - %addr, %role, - actual = block.header().height, - expected = state.latest_block_height + 1, - "Mismatch between the actual and expected height of the block." - ); - return; - } - - commit_block(sumeragi, state, block); + match block.commit_without_proxy_tail_signature(current_topology) { + Ok(committed_block) => commit_block(sumeragi, state, committed_block), + Err((_, err)) => { + warn!(%addr, %role, %block_hash, ?err, "Failed to commit block") + } + }; } (Message::BlockCommitted(BlockCommitted { hash, signatures }), _) => { if role == Role::ProxyTail && current_topology.is_consensus_required() || role == Role::Leader && !current_topology.is_consensus_required() { - error!(%addr, %role, "Received BlockCommitted message, but shouldn't"); + error!(%addr, %role, "Received BlockCommitted message, but shouldn't have"); } else if let Some(mut voted_block) = voting_block.take() { let voting_block_hash = voted_block.block.hash(); - if hash == voting_block_hash.transmute() { + if hash == voting_block_hash { // The manipulation of the topology relies upon all peers seeing the same signature set. // Therefore we must clear the signatures and accept what the proxy tail giveth. - voted_block.block.signatures.clear(); - add_signatures::(&mut voted_block, signatures.transmute()); + voted_block.block.replace_signatures(signatures); - match voted_block.block.commit(current_topology) { + match voted_block + .block + .commit_without_proxy_tail_signature(current_topology) + { Ok(committed_block) => commit_block(sumeragi, state, committed_block), Err((_, err)) => { - error!(%addr, %role, %hash, ?err, "Block failed to be committed") + warn!(%addr, %role, %hash, ?err, "Failed to commit block") } }; } else { @@ -624,30 +614,33 @@ fn handle_message( ); sumeragi.broadcast_packet_to(msg, [current_topology.proxy_tail()]); - info!(%addr, %block_hash, "Block validated, signed and forwarded"); + debug!(%addr, %role, %block_hash, "Block validated, signed and forwarded"); *voting_block = Some(block); } } (Message::BlockCreated(block_created), Role::ObservingPeer) => { if let Some(block) = vote_for_block(sumeragi, state, block_created) { - if current_view_change_index >= 1 { - let block_hash = block.block.hash(); + let block_hash = block.block.hash(); + if current_view_change_index >= 1 { let msg = MessagePacket::new( view_change_proof_chain.clone(), BlockSigned::from(block.block.clone()), ); sumeragi.broadcast_packet_to(msg, [current_topology.proxy_tail()]); - info!(%addr, %block_hash, "Block validated, signed and forwarded"); + debug!(%addr, %role, %block_hash, current_view_change_index, "Block validated signed and forwarded"); + } else { + // NOTE: It is ok to sign the block because signatures will be replaced from + // the one in the proxy tail + debug!(%addr, %role, %block_hash, "Block validated and signed"); } + *voting_block = Some(block); } } (Message::BlockCreated(block_created), Role::ProxyTail) => { - // NOTE: False positive from nursery - #[allow(clippy::iter_with_drain)] if let Some(mut new_block) = vote_for_block(sumeragi, state, block_created) { // NOTE: Up until this point it was unknown which block is expected to be received, // therefore all the signatures (of any hash) were collected and will now be pruned @@ -692,13 +685,11 @@ fn process_message_independent( current_view_change_index: u64, view_change_proof_chain: &mut ProofChain, round_start_time: &Instant, - is_genesis_peer: bool, + #[cfg(debug_assertions)] is_soft_fork_peer: bool, ) { let current_topology = &state.current_topology; - let role = current_topology.role(&sumeragi.peer_id); - let addr = &sumeragi.peer_id.address; - match role { + match current_topology.role(&sumeragi.peer_id) { Role::Leader => { if voting_block.is_none() { let cache_full = state.transaction_cache.len() >= sumeragi.queue.txs_in_block; @@ -707,49 +698,30 @@ fn process_message_independent( if cache_full || (deadline_reached && cache_non_empty) { let transactions = state.transaction_cache.clone(); - info!(txns=%transactions.len(), "Creating block..."); + debug!(txns=%transactions.len(), "Creating block..."); // TODO: properly process triggers! let event_recommendations = Vec::new(); - let new_block = BlockBuilder { + match BlockBuilder::new( transactions, + state.current_topology.clone(), event_recommendations, - height: state.latest_block_height + 1, - previous_block_hash: state.latest_block_hash, - view_change_index: current_view_change_index, - committed_with_topology: state.current_topology.clone(), - key_pair: sumeragi.key_pair.clone(), - transaction_validator: &sumeragi.transaction_validator, - wsv: state.wsv.clone(), - } - .build(); - - sumeragi.send_events(&new_block); - if current_topology.is_consensus_required() { - info!(%addr, hash=%new_block.hash(), "Block created"); - *voting_block = Some(VotingBlock::new(new_block.clone())); - - let msg = MessagePacket::new( - view_change_proof_chain.clone(), - BlockCreated::from(new_block), - ); - sumeragi.broadcast_packet(msg, current_topology); - } else { - match new_block.commit(current_topology) { - Ok(committed_block) => { - let msg = MessagePacket::new( - view_change_proof_chain.clone(), - BlockCommitted::from(Into::::into( - committed_block.clone(), - )), - ); - - sumeragi.broadcast_packet(msg, current_topology); - commit_block(sumeragi, state, committed_block); - } - Err(err) => error!(%addr, role=%Role::Leader, ?err), + ) + .chain( + state.latest_block_height, + state.latest_block_hash, + current_view_change_index, + &sumeragi.transaction_validator, + state.wsv.clone(), + ) + .sign(sumeragi.key_pair.clone()) + { + Ok(block) => { + *voting_block = + create_block(sumeragi, state, block, view_change_proof_chain); } - } + Err(error) => error!(?error, "Failed to sign block"), + }; } } } @@ -757,19 +729,20 @@ fn process_message_independent( if let Some(voted_block) = voting_block.take() { let voted_at = voted_block.voted_at; - match voted_block.block.commit(current_topology) { + match voted_block + .block + .commit_without_proxy_tail_signature(current_topology) + { Ok(committed_block) => { - info!(voting_block_hash = %committed_block.hash(), "Block reached required number of votes"); + debug!(block_hash=%committed_block.0.hash(), "Block reached required number of votes"); let msg = MessagePacket::new( view_change_proof_chain.clone(), - BlockCommitted::from(Into::::into( - committed_block.clone(), - )), + BlockCommitted::from(committed_block.0.clone()), ); #[cfg(debug_assertions)] - if is_genesis_peer && sumeragi.debug_force_soft_fork { + if is_soft_fork_peer && sumeragi.debug_force_soft_fork { std::thread::sleep(sumeragi.pipeline_time() * 2); } else { sumeragi.broadcast_packet(msg, current_topology); @@ -784,7 +757,7 @@ fn process_message_independent( Err((block, err)) => { // Restore the current voting block and continue the round *voting_block = Some(VotingBlock::voted_at(block, voted_at)); - trace!(?err, "Not enough signatures, waiting for more..."); + debug!(?err, "Not enough signatures, waiting for more..."); } } } @@ -793,8 +766,49 @@ fn process_message_independent( } } +fn create_block( + sumeragi: &Sumeragi, + state: &mut State, + block: ValidBlock, + view_change_proof_chain: &mut ProofChain, +) -> Option { + let current_topology = &state.current_topology; + let addr = &sumeragi.peer_id.address; + + if current_topology.is_consensus_required() { + debug!(%addr, hash=%block.hash(), "Block created"); + let msg = MessagePacket::new( + view_change_proof_chain.clone(), + BlockCreated::from(VersionedSignedBlock::from(block.clone())), + ); + + sumeragi.broadcast_packet(msg, current_topology); + Some(VotingBlock::new(block)) + } else { + // TODO: Should we stop producing blocks if we cannot guarantee BFT anymore? Should we + // make the limit for number of peers after which blocks will not be produced configurable? + // TODO: validator could also deny Unregister that lowers the number of peers below some limit + error!(%addr, ?current_topology, "Insufficient number of nodes to guarantee BFT"); + + match block.commit(current_topology) { + Ok(committed_block) => { + let msg = MessagePacket::new( + view_change_proof_chain.clone(), + BlockCommitted::from(Into::::into(committed_block.0.clone())), + ); + + commit_block(sumeragi, state, committed_block); + sumeragi.broadcast_packet(msg, &state.current_topology); + } + Err(err) => error!(%addr, role=%Role::Leader, ?err), + } + + None + } +} + // NOTE: False positive useless_let_if_seq from nursery -#[allow(clippy::too_many_arguments, clippy::useless_let_if_seq)] +#[allow(clippy::too_many_arguments)] fn reset_state( peer_id: &PeerId, pipeline_time: Duration, @@ -805,7 +819,7 @@ fn reset_state( // below is the state that gets reset. current_topology: &mut Topology, voting_block: &mut Option, - voting_signatures: &mut Vec>, + voting_signatures: &mut Vec>, round_start_time: &mut Instant, last_view_change_time: &mut Instant, view_change_time: &mut Duration, @@ -862,7 +876,7 @@ pub(crate) fn run( sumeragi.connect_peers(&state.current_topology); let span = span!(tracing::Level::TRACE, "genesis").entered(); - let is_genesis_peer = if state.latest_block_height == 0 || state.latest_block_hash.is_none() { + let is_soft_fork_peer = if state.latest_block_height == 0 || state.latest_block_hash.is_none() { if let Some(genesis_network) = genesis_network { sumeragi_init_commit_genesis(sumeragi, &mut state, genesis_network); true @@ -992,7 +1006,7 @@ pub(crate) fn run( should_sleep = true; }, |message| { - handle_message( + handle_message::( message, sumeragi, &mut state, @@ -1011,21 +1025,22 @@ pub(crate) fn run( current_view_change_index, &mut view_change_proof_chain, &round_start_time, - is_genesis_peer, + #[cfg(debug_assertions)] + is_soft_fork_peer, ); } } fn add_signatures( block: &mut VotingBlock, - signatures: impl IntoIterator>, + signatures: impl IntoIterator>, ) { for signature in signatures { if let Err(err) = block.block.add_signature(signature) { let err_msg = "Signature not valid"; if EXPECT_VALID { - error!(?err, err_msg); + warn!(?err, err_msg); } else { debug!(?err, err_msg); } @@ -1050,59 +1065,30 @@ fn expired_event(txn: &impl Transaction) -> Event { fn vote_for_block( sumeragi: &Sumeragi, state: &State, - block_created: BlockCreated, + BlockCreated { block }: BlockCreated, ) -> Option { - let block_hash = block_created.hash(); let addr = &sumeragi.peer_id.address; let role = state.current_topology.role(&sumeragi.peer_id); - trace!(%addr, %role, block_hash=%block_hash, "Block received, voting..."); - - let mut block = { - let span = span!(Level::TRACE, "block revalidation"); - let _enter = span.enter(); + trace!(%addr, %role, block_hash=%block.hash(), "Block received, voting..."); - match block_created.validate_and_extract_block::( - &sumeragi.transaction_validator, - state.wsv.clone(), - state.latest_block_hash, - state.latest_block_height, - ) { - Ok(block) => block, - Err(err) => { - warn!(%addr, %role, ?err); - return None; - } + let block = match ValidBlock::validate( + block, + state.latest_block_height + 1, + state.latest_block_hash, + &state.current_topology, + &sumeragi.transaction_validator, + state.wsv.clone(), + ) { + Ok(block) => block, + Err((block, err)) => { + warn!(%addr, %role, hash=%block.hash(), ?err, "Received invalid block"); + return None; } - }; - - if state - .current_topology - .filter_signatures_by_roles(&[Role::Leader], block.retain_verified_signatures()) - .is_empty() - { - error!( - %addr, %role, leader=%state.current_topology.leader().address, hash=%block.hash(), - "The block is rejected as it is not signed by the leader." - ); - - return None; } + .sign(sumeragi.key_pair.clone()) + .expect("Block signing failed"); - if block.header.committed_with_topology != state.current_topology.sorted_peers { - error!( - %addr, %role, block_topology=?block.header.committed_with_topology, my_topology=?state.current_topology, hash=%block.hash(), - "The block is rejected as because the topology field is incorrect." - ); - - return None; - } - - let signed_block = block - .sign(sumeragi.key_pair.clone()) - .expect("Block signing failed"); - - sumeragi.send_events(&signed_block); - Some(VotingBlock::new(signed_block)) + Some(VotingBlock::new(block)) } fn sumeragi_init_commit_genesis( @@ -1117,41 +1103,31 @@ fn sumeragi_init_commit_genesis( assert_eq!(state.latest_block_height, 0); assert_eq!(state.latest_block_hash, None); - let transactions = genesis_network.transactions; + let transactions: Vec<_> = genesis_network + .transactions + .into_iter() + .map(Into::into) + .collect(); // Don't start genesis round. Instead just commit the genesis block. - assert!( - !transactions.is_empty(), - "Genesis transaction set contains no valid transactions" - ); - let block = BlockBuilder { - transactions, - event_recommendations: Vec::new(), - height: 1, - previous_block_hash: None, - view_change_index: 0, - committed_with_topology: state.current_topology.clone(), - key_pair: sumeragi.key_pair.clone(), - transaction_validator: &sumeragi.transaction_validator, - wsv: state.wsv.clone(), - } - .build(); + let addr = &sumeragi.peer_id.address; + let (block, events): (CommittedBlock, _) = + BlockBuilder::new(transactions, state.current_topology.clone(), Vec::new()) + .chain_first(&sumeragi.transaction_validator, state.wsv.clone()) + .sign(sumeragi.key_pair.clone()) + .expect("Failed to sign genesis block") + .commit(&state.current_topology) + .expect("Topology should not be validated for genesis"); - { - info!(block_hash = %block.hash(), "Publishing genesis block."); + info!(%addr, hash=%block.hash(), "Genesis block created"); - info!( - role = ?state.current_topology.role(&sumeragi.peer_id), - block_hash = %block.hash(), - "Created a block to commit.", - ); + let msg = MessagePacket::new( + ProofChain::default(), + BlockCreated::from(VersionedSignedBlock::from(block.clone())), + ); - sumeragi.send_events(&block); - let msg = MessagePacket::new(ProofChain::default(), BlockCreated::from(block.clone())); - sumeragi.broadcast_packet(msg, &state.current_topology); - // Omit signature verification during genesis round - commit_block(sumeragi, state, block.commit_unchecked()); - } + commit_block(sumeragi, state, (block, events)); + sumeragi.broadcast_packet(msg, &state.current_topology); } /// Type enumerating early return types to reduce cyclomatic diff --git a/core/src/sumeragi/message.rs b/core/src/sumeragi/message.rs index 4b33d8131ed..bb5c91d88ef 100644 --- a/core/src/sumeragi/message.rs +++ b/core/src/sumeragi/message.rs @@ -7,43 +7,20 @@ )] use iroha_crypto::{HashOf, SignaturesOf}; -use iroha_data_model::{block::VersionedCommittedBlock, prelude::*}; +use iroha_data_model::{ + block::{Block, BlockPayload}, + prelude::*, +}; +use iroha_genesis::AcceptedTransaction; use iroha_macro::*; use iroha_version::prelude::*; use parity_scale_codec::{Decode, Encode}; use super::view_change; -use crate::{ - block::{PendingBlock, Revalidate}, - tx::TransactionValidator, - VersionedAcceptedTransaction, WorldStateView, -}; +use crate::block::{CommittedBlock, ValidBlock}; declare_versioned_with_scale!(VersionedPacket 1..2, Debug, Clone, iroha_macro::FromVariant); -impl VersionedPacket { - /// Convert `&`[`Self`] to V1 reference - pub const fn as_v1(&self) -> &MessagePacket { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert `&mut` [`Self`] to V1 mutable reference - pub fn as_mut_v1(&mut self) -> &mut MessagePacket { - match self { - Self::V1(v1) => v1, - } - } - - /// Perform the conversion from [`Self`] to V1 - pub fn into_v1(self) -> MessagePacket { - match self { - Self::V1(v1) => v1, - } - } -} - /// Helper structure, wrapping messages and view change proofs. #[version_with_scale(n = 1, versioned = "VersionedPacket")] #[derive(Debug, Clone, Decode, Encode)] @@ -87,53 +64,29 @@ pub enum Message { #[non_exhaustive] pub struct BlockCreated { /// The corresponding block. - pub block: PendingBlock, + pub block: VersionedSignedBlock, } -impl From for BlockCreated { - fn from(block: PendingBlock) -> Self { +impl From for BlockCreated { + fn from(block: VersionedSignedBlock) -> Self { Self { block } } } -impl BlockCreated { - /// Extract block from block created message. - /// - /// # Errors - /// - When the block is invalid. - pub fn validate_and_extract_block( - self, - transaction_validator: &TransactionValidator, - wsv: WorldStateView, - latest_block: Option>, - block_height: u64, - ) -> Result { - self.block.revalidate::( - transaction_validator, - wsv, - latest_block, - block_height, - )?; - Ok(self.block) - } - /// Get hash of block. - pub fn hash(&self) -> HashOf { - self.block.hash() - } -} - /// `BlockSigned` message structure. #[derive(Debug, Clone, Decode, Encode)] #[non_exhaustive] pub struct BlockSigned { /// Hash of the block being signed. - pub hash: HashOf, + pub hash: HashOf, /// Set of signatures. - pub signatures: SignaturesOf, + pub signatures: SignaturesOf, } -impl From for BlockSigned { - fn from(block: PendingBlock) -> Self { +impl From for BlockSigned { + fn from(block: ValidBlock) -> Self { + let VersionedSignedBlock::V1(block) = block.into(); + Self { hash: block.hash(), signatures: block.signatures, @@ -146,16 +99,18 @@ impl From for BlockSigned { #[non_exhaustive] pub struct BlockCommitted { /// Hash of the block being signed. - pub hash: HashOf, + pub hash: HashOf, /// Set of signatures. - pub signatures: SignaturesOf, + pub signatures: SignaturesOf, } -impl From for BlockCommitted { - fn from(block: VersionedCommittedBlock) -> Self { +impl From for BlockCommitted { + fn from(block: CommittedBlock) -> Self { + let VersionedSignedBlock::V1(block) = block.into(); + Self { - hash: block.hash().transmute(), - signatures: block.as_v1().signatures.clone().transmute(), + hash: block.hash(), + signatures: block.signatures, } } } @@ -165,39 +120,7 @@ impl From for BlockCommitted { #[non_exhaustive] pub struct BlockSyncUpdate { /// The corresponding block. - block: VersionedCommittedBlock, -} - -impl From for BlockSyncUpdate { - fn from(block: VersionedCommittedBlock) -> Self { - Self { block } - } -} - -impl BlockSyncUpdate { - /// Extract block from block sync update message. - /// - /// # Errors - /// - When the block is invalid. - pub fn validate_and_extract_block( - self, - transaction_validator: &TransactionValidator, - wsv: WorldStateView, - latest_block: Option>, - block_height: u64, - ) -> Result { - self.block.revalidate::( - transaction_validator, - wsv, - latest_block, - block_height, - )?; - Ok(self.block) - } - /// Get hash of block. - pub fn hash(&self) -> HashOf { - self.block.hash() - } + pub block: VersionedSignedBlock, } /// Message for gossiping batches of transactions. @@ -210,11 +133,14 @@ pub struct TransactionGossip { impl TransactionGossip { #![allow(clippy::unused_async)] /// Constructor. - pub fn new(txs: Vec) -> Self { + pub fn new(txs: Vec) -> Self { Self { // Converting into non-accepted transaction because it's not possible // to guarantee that the sending peer checked transaction limits - txs: txs.into_iter().map(Into::into).collect(), + txs: txs + .into_iter() + .map(VersionedSignedTransaction::from) + .collect(), } } } diff --git a/core/src/sumeragi/mod.rs b/core/src/sumeragi/mod.rs index fb6bbbdc223..978d5806a4c 100644 --- a/core/src/sumeragi/mod.rs +++ b/core/src/sumeragi/mod.rs @@ -1,6 +1,4 @@ //! Translates to Emperor. Consensus-related logic of Iroha. -//! -//! `Consensus` trait is now implemented only by `Sumeragi` for now. #![allow( clippy::arithmetic_side_effects, clippy::std_instead_of_core, @@ -19,27 +17,29 @@ use iroha_data_model::{block::*, prelude::*}; use iroha_genesis::GenesisNetwork; use iroha_logger::prelude::*; use iroha_telemetry::metrics::Metrics; -use network_topology::{Role, Topology}; - -use crate::handler::ThreadHandler; - -pub mod main_loop; -pub mod message; -pub mod network_topology; -pub mod view_change; - use main_loop::State; +use network_topology::{Role, Topology}; use parking_lot::{Mutex, MutexGuard}; use self::{ message::{Message, *}, - view_change::{Proof, ProofChain}, + view_change::ProofChain, }; use crate::{ - block::*, kura::Kura, prelude::*, queue::Queue, tx::TransactionValidator, EventsSender, - IrohaNetwork, NetworkMessage, + block::{CommittedBlock, ValidBlock}, + handler::ThreadHandler, + kura::Kura, + prelude::*, + queue::Queue, + tx::TransactionValidator, + EventsSender, IrohaNetwork, NetworkMessage, }; +pub mod main_loop; +pub mod message; +pub mod network_topology; +pub mod view_change; + /* The values in the following struct are not atomics because the code that operates on them assumes their values does not change during the course of @@ -132,8 +132,8 @@ impl Sumeragi { break; }; block_index += 1; - let block_txs_accepted = block.as_v1().transactions.len() as u64; - let block_txs_rejected = block.as_v1().rejected_transactions.len() as u64; + let block_txs_accepted = block.payload().transactions.len() as u64; + let block_txs_rejected = block.payload().rejected_transactions.len() as u64; self.metrics .txs @@ -220,7 +220,7 @@ impl Sumeragi { pub fn initialize_and_start_thread( sumeragi: Arc, genesis_network: Option, - block_hashes: &[HashOf], + block_hashes: &[HashOf], ) -> ThreadHandler { let wsv = sumeragi.wsv_mutex_access().clone(); @@ -230,22 +230,26 @@ impl Sumeragi { .zip(1u64..) { let block_height: u64 = i; - let block_ref = sumeragi.internal.kura.get_block_by_height(block_height).expect("Sumeragi could not load block that was reported as present. Please check that the block storage was not disconnected."); + let (block, _) = CommittedBlock::commit_without_validation(VersionedSignedBlock::clone( + &sumeragi.internal.kura.get_block_by_height(block_height).expect("Sumeragi could not load block that was reported as present. Please check that the block storage was not disconnected."), + )); assert_eq!( - block_ref.hash(), + block.hash(), *block_hash, "Kura init correctly reported the block hash." ); - wsv.apply(&block_ref) + wsv.apply(&block) .expect("Failed to apply block to wsv in init."); } let finalized_wsv = wsv.clone(); if !block_hashes.is_empty() { - let block_ref = sumeragi.internal.kura.get_block_by_height(block_hashes.len() as u64).expect("Sumeragi could not load block that was reported as present. Please check that the block storage was not disconnected."); + let (block, _) = CommittedBlock::commit_without_validation(VersionedSignedBlock::clone( + &sumeragi.internal.kura.get_block_by_height(block_hashes.len() as u64).expect("Sumeragi could not load block that was reported as present. Please check that the block storage was not disconnected."), + )); - wsv.apply(&block_ref) + wsv.apply(&block) .expect("Failed to apply block to wsv in init."); } *sumeragi.wsv_mutex_access() = wsv.clone(); @@ -258,13 +262,18 @@ impl Sumeragi { let previous_block_hash = wsv.previous_block_hash(); let current_topology = if latest_block_height == 0 { - assert!(!sumeragi.config.trusted_peers.peers.is_empty()); - Topology::new(sumeragi.config.trusted_peers.peers.clone()) + Topology::new( + sumeragi + .config + .trusted_peers + .peers + .iter() + .cloned() + .collect(), + ) } else { let block_ref = sumeragi.internal.kura.get_block_by_height(latest_block_height).expect("Sumeragi could not load block that was reported as present. Please check that the block storage was not disconnected."); - let mut topology = Topology { - sorted_peers: block_ref.header().committed_with_topology.clone(), - }; + let mut topology = Topology::new(block_ref.header().commit_topology.clone()); topology.rotate_set_a(); topology }; @@ -296,7 +305,9 @@ impl Sumeragi { .expect("Sumeragi thread spawn should not fail."); let shutdown = move || { - let _result = shutdown_sender.send(()); + if let Err(error) = shutdown_sender.send(()) { + iroha_logger::error!(?error, "Failed to send shut down signal to sumeragi. Thead might already be shut down."); + } }; ThreadHandler::new(Box::new(shutdown), thread_handle) @@ -328,13 +339,13 @@ pub struct VotingBlock { /// At what time has this peer voted for this block pub voted_at: Instant, /// Valid Block - pub block: PendingBlock, + pub block: ValidBlock, } impl VotingBlock { /// Construct new `VotingBlock` with current time. #[allow(clippy::expect_used)] - pub fn new(block: PendingBlock) -> VotingBlock { + pub fn new(block: ValidBlock) -> VotingBlock { VotingBlock { block, voted_at: Instant::now(), @@ -342,7 +353,7 @@ impl VotingBlock { } /// Construct new `VotingBlock` with the given time. #[allow(clippy::expect_used)] - pub(crate) fn voted_at(block: PendingBlock, voted_at: Instant) -> VotingBlock { + pub(crate) fn voted_at(block: ValidBlock, voted_at: Instant) -> VotingBlock { VotingBlock { block, voted_at } } } diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index decd07c88d0..c78784a0eb2 100644 --- a/core/src/sumeragi/network_topology.rs +++ b/core/src/sumeragi/network_topology.rs @@ -26,14 +26,21 @@ use iroha_logger::trace; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Topology { /// Current order of peers. The roles of peers are defined based on this order. - pub(crate) sorted_peers: Vec, + pub(crate) ordered_peers: Vec, } impl Topology { /// Create a new topology. - pub fn new(peers: impl IntoIterator) -> Self { + /// + /// # Panics + /// + /// if the given list of peers is empty + pub fn new(peers: Vec) -> Self { + // TODO: This assertion should be applied in tests as well + #[cfg(not(test))] + assert!(!peers.is_empty(), "Empty topology"); Topology { - sorted_peers: peers.into_iter().collect(), + ordered_peers: peers, } } /// Is consensus required, aka are there more than 1 peer. @@ -42,7 +49,7 @@ impl Topology { } /// How many faulty peers can this topology tolerate. pub fn max_faults(&self) -> usize { - (self.sorted_peers.len().saturating_sub(1)) / 3 + (self.ordered_peers.len().saturating_sub(1)) / 3 } /// The required amount of votes to commit a block with this topology. pub fn min_votes_for_commit(&self) -> usize { @@ -58,39 +65,39 @@ impl Topology { let mut public_keys = Vec::new(); for role in roles { let role_public_keys = match (role, self.max_faults()) { - (Role::Leader, _) => vec![self.sorted_peers[0].public_key.clone()], + (Role::Leader, _) => vec![self.ordered_peers[0].public_key.clone()], (Role::ValidatingPeer, 0) => { - if self.sorted_peers.len() > 2 { - vec![self.sorted_peers[1].public_key.clone()] + if self.ordered_peers.len() > 2 { + vec![self.ordered_peers[1].public_key.clone()] } else { vec![] } } (Role::ProxyTail, 0) => { - if self.sorted_peers.len() == 2 { - vec![self.sorted_peers[1].public_key.clone()] - } else if self.sorted_peers.len() > 2 { - vec![self.sorted_peers[2].public_key.clone()] + if self.ordered_peers.len() == 2 { + vec![self.ordered_peers[1].public_key.clone()] + } else if self.ordered_peers.len() > 2 { + vec![self.ordered_peers[2].public_key.clone()] } else { vec![] } } (Role::ObservingPeer, 0) => { - if self.sorted_peers.len() == 4 { - vec![self.sorted_peers[3].public_key.clone()] + if self.ordered_peers.len() == 4 { + vec![self.ordered_peers[3].public_key.clone()] } else { vec![] } } - (Role::ValidatingPeer, _) => self.sorted_peers + (Role::ValidatingPeer, _) => self.ordered_peers [1..(self.min_votes_for_commit() - 1)] .iter() .map(|peer_id| peer_id.public_key.clone()) .collect(), - (Role::ProxyTail, _) => vec![self.sorted_peers[self.min_votes_for_commit() - 1] + (Role::ProxyTail, _) => vec![self.ordered_peers[self.min_votes_for_commit() - 1] .public_key .clone()], - (Role::ObservingPeer, _) => self.sorted_peers[self.min_votes_for_commit()..] + (Role::ObservingPeer, _) => self.ordered_peers[self.min_votes_for_commit()..] .iter() .map(|peer_id| peer_id.public_key.clone()) .collect(), @@ -108,7 +115,7 @@ impl Topology { // This lint is a bad suggestion. #[allow(clippy::option_if_let_else)] pub fn role(&self, peer_id: &PeerId) -> Role { - match self.sorted_peers.iter().position(|p| p == peer_id) { + match self.ordered_peers.iter().position(|p| p == peer_id) { Some(index) if index == 0 => Role::Leader, Some(index) if index < self.min_votes_for_commit() => Role::ValidatingPeer, Some(index) if index == self.min_votes_for_commit() => Role::ProxyTail, @@ -121,39 +128,40 @@ impl Topology { } /// Get leader's peer id. pub fn leader(&self) -> &PeerId { - &self.sorted_peers[0] + &self.ordered_peers[0] } /// Get proxy tail's peer id. pub fn proxy_tail(&self) -> &PeerId { - &self.sorted_peers[self.min_votes_for_commit()] + &self.ordered_peers[self.min_votes_for_commit()] } - /// Add or remove peers from the topology. + /// Add or remove peers from the topology preserving the order pub fn update_peer_list(&mut self, new_peer_list: &[PeerId]) { let mut i = 0; - while i < self.sorted_peers.len() { - if new_peer_list.iter().any(|p| p == &self.sorted_peers[i]) { + while i < self.ordered_peers.len() { + if new_peer_list.contains(&self.ordered_peers[i]) { i += 1; } else { - self.sorted_peers.remove(i); + let p = self.ordered_peers.remove(i); + iroha_logger::debug!(%p, "Peer removed"); } } - self.sorted_peers.extend( + self.ordered_peers.extend( new_peer_list .iter() - .filter(|p| !self.sorted_peers.contains(p)) + .filter(|p| !self.ordered_peers.contains(p)) .cloned() .collect::>(), ); } /// Rotate peers after each failed attempt to create a block. pub fn rotate_all(&mut self) { - self.sorted_peers.rotate_left(1); + self.ordered_peers.rotate_left(1); } /// Re-arrange the set of peers after each successful block commit. pub fn rotate_set_a(&mut self) { - let top = self.sorted_peers.remove(0); - self.sorted_peers.insert( - self.min_votes_for_commit().min(self.sorted_peers.len()), + let top = self.ordered_peers.remove(0); + self.ordered_peers.insert( + self.min_votes_for_commit().min(self.ordered_peers.len()), top, ); } @@ -161,14 +169,19 @@ impl Topology { pub fn lift_up_peers(&mut self, to_lift_up: &[PublicKey]) { let mut observing = Vec::new(); let mut i = 0; - while i < self.sorted_peers.len() { - if to_lift_up.contains(&self.sorted_peers[i].public_key) { + while i < self.ordered_peers.len() { + if to_lift_up.contains(&self.ordered_peers[i].public_key) { i += 1; } else { - observing.insert(0, self.sorted_peers.remove(i)); // This has to be insert(0) and not push in order to preserve order. + // Has to be insert(0) and not push to preserve order. + observing.insert(0, self.ordered_peers.remove(i)); } } - self.sorted_peers.extend(observing); + + iroha_logger::debug!("Voting peers: {:#?}", self.ordered_peers); + iroha_logger::debug!("Observing peers: {observing:#?}"); + + self.ordered_peers.extend(observing); } } diff --git a/core/src/sumeragi/view_change.rs b/core/src/sumeragi/view_change.rs index a4f62c8de34..fb5bb308880 100644 --- a/core/src/sumeragi/view_change.rs +++ b/core/src/sumeragi/view_change.rs @@ -6,12 +6,12 @@ clippy::std_instead_of_alloc, single_use_lifetimes )] -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use derive_more::{Deref, DerefMut}; use eyre::Result; -use iroha_crypto::{Hash, HashOf, KeyPair, PublicKey, Signature}; -use iroha_data_model::{block::VersionedCommittedBlock, prelude::PeerId}; +use iroha_crypto::{HashOf, KeyPair, PublicKey, SignatureOf}; +use iroha_data_model::{block::BlockPayload, prelude::PeerId}; use parity_scale_codec::{Decode, Encode}; use thiserror::Error; @@ -25,28 +25,46 @@ pub enum Error { ViewChangeNotFound, } -/// The proof of a view change. It needs to be signed by f+1 peers for proof to be valid and view change to happen. #[derive(Debug, Clone, Decode, Encode)] -pub struct Proof { +struct ProofPayload { /// Hash of the latest committed block. - pub latest_block_hash: Option>, + latest_block_hash: Option>, /// Within a round, what is the index of the view change this proof is trying to prove. - pub view_change_index: u64, + view_change_index: u64, +} + +/// The proof of a view change. It needs to be signed by f+1 peers for proof to be valid and view change to happen. +#[derive(Debug, Clone, Decode, Encode)] +pub struct SignedProof { + payload: ProofPayload, /// Collection of signatures from the different peers. - pub signatures: Vec, + signatures: BTreeSet>, } -impl Proof { - /// Produce a signature payload in the form of a [`Hash`] - pub fn signature_payload(&self) -> Hash { - let mut buf = [0_u8; Hash::LENGTH + std::mem::size_of::()]; - if let Some(hash) = self.latest_block_hash { - buf[..Hash::LENGTH].copy_from_slice(hash.as_ref()); - } - buf[Hash::LENGTH..].copy_from_slice(&self.view_change_index.to_le_bytes()); - // Now we hash the buffer to produce a payload that is completely - // different between view change proofs in the same sumeragi round. - Hash::new(buf) +/// Builder for proofs +#[repr(transparent)] +pub struct ProofBuilder(SignedProof); + +impl ProofBuilder { + /// Constructor from index. + pub fn new(view_change_index: u64) -> Self { + let proof = SignedProof { + payload: ProofPayload { + latest_block_hash: None, + view_change_index, + }, + signatures: BTreeSet::new(), + }; + + Self(proof) + } + + /// Add latest block hash to the proof. This function can be skipped + /// only if the genesis block has not yet been committed + #[must_use] + pub fn with_latest_block_hash(mut self, latest_block_hash: HashOf) -> Self { + self.0.payload.latest_block_hash = Some(latest_block_hash); + self } /// Sign this message with the peer's public and private key. @@ -54,35 +72,33 @@ impl Proof { /// /// # Errors /// Can fail during creation of signature - pub fn sign(&mut self, key_pair: KeyPair) -> Result<()> { - let signature = Signature::new(key_pair, self.signature_payload().as_ref())?; - self.signatures.push(signature); - Ok(()) + pub fn sign(mut self, key_pair: KeyPair) -> Result { + let signature = SignatureOf::from_hash(key_pair, &HashOf::new(&self.0.payload))?; + self.0.signatures.insert(signature); + Ok(self.0) } +} +impl SignedProof { /// Verify the signatures of `other` and add them to this proof. - pub fn merge_signatures(&mut self, other: Vec) { - let signature_payload = self.signature_payload(); + fn merge_signatures(&mut self, other: BTreeSet>) { for signature in other { - if signature.verify(signature_payload.as_ref()).is_ok() - && !self.signatures.contains(&signature) - { - self.signatures.push(signature); + if signature.verify(&self.payload).is_ok() && !self.signatures.contains(&signature) { + self.signatures.insert(signature); } } } /// Verify if the proof is valid, given the peers in `topology`. - pub fn verify(&self, peers: &[PeerId], max_faults: usize) -> bool { + fn verify(&self, peers: &[PeerId], max_faults: usize) -> bool { let peer_public_keys: HashSet<&PublicKey> = peers.iter().map(|peer_id| &peer_id.public_key).collect(); - let signature_payload = self.signature_payload(); let valid_count = self .signatures .iter() .filter(|signature| { - signature.verify(signature_payload.as_ref()).is_ok() + signature.verify(&self.payload).is_ok() && peer_public_keys.contains(signature.public_key()) }) .count(); @@ -97,7 +113,7 @@ impl Proof { /// Structure representing sequence of view change proofs. #[derive(Debug, Clone, Encode, Decode, Deref, DerefMut, Default)] -pub struct ProofChain(Vec); +pub struct ProofChain(Vec); impl ProofChain { /// Verify the view change proof chain. @@ -105,31 +121,32 @@ impl ProofChain { &self, peers: &[PeerId], max_faults: usize, - latest_block: Option>, + latest_block: Option>, ) -> usize { self.iter() .enumerate() .take_while(|(i, proof)| { - proof.latest_block_hash == latest_block - && proof.view_change_index == (*i as u64) + proof.payload.latest_block_hash == latest_block + && proof.payload.view_change_index == (*i as u64) && proof.verify(peers, max_faults) }) .count() } /// Remove invalid proofs from the chain. - pub fn prune(&mut self, latest_block: Option>) { + pub fn prune(&mut self, latest_block: Option>) { let valid_count = self .iter() .enumerate() .take_while(|(i, proof)| { - proof.latest_block_hash == latest_block && proof.view_change_index == (*i as u64) + proof.payload.latest_block_hash == latest_block + && proof.payload.view_change_index == (*i as u64) }) .count(); self.truncate(valid_count); } - /// Attempt to insert a view chain proof into this `ProofChain`. + /// Attempt to insert a view change proof into [`Self`]. /// /// # Errors /// - If proof latest block hash doesn't match peer latest block hash @@ -139,14 +156,14 @@ impl ProofChain { &mut self, peers: &[PeerId], max_faults: usize, - latest_block: Option>, - new_proof: Proof, + latest_block: Option>, + new_proof: SignedProof, ) -> Result<(), Error> { - if new_proof.latest_block_hash != latest_block { + if new_proof.payload.latest_block_hash != latest_block { return Err(Error::BlockHashMismatch); } let next_unfinished_view_change = self.verify_with_state(peers, max_faults, latest_block); - if new_proof.view_change_index != (next_unfinished_view_change as u64) { + if new_proof.payload.view_change_index != (next_unfinished_view_change as u64) { return Err(Error::ViewChangeNotFound); // We only care about the current view change that may or may not happen. } @@ -169,7 +186,7 @@ impl ProofChain { mut other: Self, peers: &[PeerId], max_faults: usize, - latest_block_hash: Option>, + latest_block_hash: Option>, ) -> Result<(), Error> { // Prune to exclude invalid proofs other.prune(latest_block_hash); diff --git a/core/src/tx.rs b/core/src/tx.rs index 96e9d86b375..b9dc3fb2b14 100644 --- a/core/src/tx.rs +++ b/core/src/tx.rs @@ -18,6 +18,11 @@ use std::str::FromStr; use eyre::Result; pub use iroha_data_model::prelude::*; +use iroha_data_model::{ + transaction::{error::TransactionRejectionReason, SignedTransaction}, + validator::NeedsValidationBox, +}; +use iroha_genesis::AcceptedTransaction; use iroha_logger::debug; use iroha_primitives::must_use::MustUse; @@ -48,22 +53,17 @@ impl TransactionValidator { /// /// # Errors /// Fails if validation of instruction fails (e.g. permissions mismatch). - pub fn validate( + pub fn validate( &self, tx: AcceptedTransaction, - is_genesis: bool, wsv: &WorldStateView, - ) -> Result { - if let Err(rejection_reason) = self.validate_internal(tx.clone(), is_genesis, wsv) { - return Err(RejectedTransaction { - payload: tx.payload, - signatures: tx.signatures, - rejection_reason, - } - .into()); + ) -> Result + { + if let Err(rejection_reason) = self.validate_internal::(tx.clone(), wsv) { + return Err((tx.into(), rejection_reason)); } - Ok(ValidTransaction { + Ok(SignedTransaction { payload: tx.payload, signatures: tx.signatures, } @@ -77,27 +77,24 @@ impl TransactionValidator { /// /// # Errors /// Fails if validation of any transaction fails - // - // TODO (#2742): Accept `txs` by reference, not by value - pub fn validate_every( + pub fn validate_every( &self, - txs: impl IntoIterator, + txs: impl IntoIterator, wsv: &WorldStateView, ) -> Result<(), TransactionRejectionReason> { for tx in txs { - self.validate_internal(tx.into_v1(), true, wsv)?; + self.validate_internal::(tx, wsv)?; } Ok(()) } - fn validate_internal( + fn validate_internal( &self, tx: AcceptedTransaction, - is_genesis: bool, wsv: &WorldStateView, ) -> Result<(), TransactionRejectionReason> { let account_id = &tx.payload.account_id; - Self::validate_signatures(&tx, is_genesis, wsv)?; + Self::validate_signatures::(&tx, wsv)?; if !wsv .domain(&account_id.domain_id) @@ -114,7 +111,7 @@ impl TransactionValidator { })); } - if !is_genesis { + if !IS_GENESIS { debug!("Validating transaction: {:?}", tx); Self::validate_with_runtime_validator(account_id, tx.clone(), wsv)?; } @@ -122,7 +119,7 @@ impl TransactionValidator { match tx.payload.instructions { Executable::Instructions(instructions) => { // Non-genesis instructions have been executed in `validate_with_runtime_validators()`. - if is_genesis { + if IS_GENESIS { for instruction in instructions { instruction .clone() @@ -138,17 +135,16 @@ impl TransactionValidator { Executable::Wasm(bytes) => self.validate_wasm(account_id.clone(), wsv, bytes)?, } - (!is_genesis).then(|| debug!("Validation successful")); + (!IS_GENESIS).then(|| debug!("Validation successful")); Ok(()) } /// Validate signatures for the given transaction - fn validate_signatures( + fn validate_signatures( tx: &AcceptedTransaction, - is_genesis: bool, wsv: &WorldStateView, ) -> Result<(), TransactionRejectionReason> { - if !is_genesis && tx.payload().account_id == AccountId::genesis() { + if !IS_GENESIS && tx.payload.account_id == *iroha_genesis::GENESIS_ACCOUNT_ID { return Err(TransactionRejectionReason::UnexpectedGenesisAccountSignature); } @@ -205,7 +201,6 @@ impl TransactionValidator { payload, signatures, } = tx; - let signatures = signatures.into_iter().collect(); let signed_tx = SignedTransaction { payload, @@ -213,7 +208,7 @@ impl TransactionValidator { }; wsv.validator_view() - .validate(wsv, authority, signed_tx) + .validate(wsv, authority, NeedsValidationBox::from(signed_tx)) .map_err(|err| { TransactionRejectionReason::NotPermitted(NotPermittedFail { reason: err.to_string(), @@ -236,10 +231,10 @@ pub trait CheckSignatureCondition: Sized { impl CheckSignatureCondition for AcceptedTransaction { fn check_signature_condition(&self, wsv: &WorldStateView) -> Result> { - let account_id = &self.payload.account_id; + let account_id = &self.payload().account_id; let signatories = self - .signatures + .signatures() .iter() .map(|signature| signature.public_key()) .cloned(); @@ -253,12 +248,6 @@ impl CheckSignatureCondition for AcceptedTransaction { } } -impl CheckSignatureCondition for VersionedAcceptedTransaction { - fn check_signature_condition(&self, wsv: &WorldStateView) -> Result> { - self.as_v1().check_signature_condition(wsv) - } -} - /// Returns a prebuilt expression that when executed /// returns if the needed signatures are gathered. fn check_signature_condition( @@ -287,21 +276,73 @@ impl IsInBlockchain for VersionedSignedTransaction { wsv.has_transaction(&self.hash()) } } -impl IsInBlockchain for VersionedAcceptedTransaction { +impl IsInBlockchain for AcceptedTransaction { #[inline] fn is_in_blockchain(&self, wsv: &WorldStateView) -> bool { wsv.has_transaction(&self.hash()) } } -impl IsInBlockchain for VersionedValidTransaction { - #[inline] - fn is_in_blockchain(&self, wsv: &WorldStateView) -> bool { - wsv.has_transaction(&self.hash()) + +#[cfg(test)] +mod tests { + #![allow(clippy::pedantic)] + + use iroha_config::sumeragi::DEFAULT_MAX_INSTRUCTION_NUMBER; + use iroha_data_model::prelude::*; + + use super::*; + + #[test] + fn transaction_not_accepted_max_instruction_number() { + let key_pair = iroha_crypto::KeyPair::generate().expect("Failed to generate key pair."); + let inst: InstructionBox = FailBox { + message: "Will fail".to_owned(), + } + .into(); + let tx = TransactionBuilder::new( + "root@global".parse().expect("Valid"), + vec![inst; DEFAULT_MAX_INSTRUCTION_NUMBER as usize + 1], + 1000, + ) + .sign(key_pair) + .expect("Valid"); + let tx_limits = TransactionLimits { + max_instruction_number: 4096, + max_wasm_size_bytes: 0, + }; + let result = AcceptedTransaction::accept::(tx, &tx_limits); + assert!(result.is_err()); + + let err = result.unwrap_err(); + assert_eq!( + err.1.to_string(), + format!( + "Too many instructions in payload, max number is {}, but got {}", + tx_limits.max_instruction_number, + DEFAULT_MAX_INSTRUCTION_NUMBER + 1 + ) + ); } -} -impl IsInBlockchain for VersionedRejectedTransaction { - #[inline] - fn is_in_blockchain(&self, wsv: &WorldStateView) -> bool { - wsv.has_transaction(&self.hash()) + + #[test] + fn genesis_transaction_ignore_limits() { + let key_pair = iroha_crypto::KeyPair::generate().expect("Failed to generate key pair."); + let inst: InstructionBox = FailBox { + message: "Will fail".to_owned(), + } + .into(); + let tx = TransactionBuilder::new( + "root@global".parse().expect("Valid"), + vec![inst; DEFAULT_MAX_INSTRUCTION_NUMBER as usize + 1], + 1000, + ) + .sign(key_pair) + .expect("Valid"); + let tx_limits = TransactionLimits { + max_instruction_number: 4096, + max_wasm_size_bytes: 0, + }; + + assert!(AcceptedTransaction::accept::(tx, &tx_limits).is_ok()); } } diff --git a/core/src/validator.rs b/core/src/validator.rs index d667afc32ea..c7fd1c8b6bc 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -71,9 +71,9 @@ impl Validator { &self, wsv: &WorldStateView, authority: &::Id, - operation: impl Into, + operation: data_model_validator::NeedsValidationBox, ) -> Result<()> { - let operation = operation.into(); + let operation = operation; let runtime = wasm::RuntimeBuilder::new() .with_engine(self.engine.clone()) // Cloning engine is cheap, see [`wasmtime::Engine`] docs @@ -140,9 +140,9 @@ impl MockValidator { &self, wsv: &WorldStateView, authority: &::Id, - operation: impl Into, + operation: data_model_validator::NeedsValidationBox, ) -> Result<()> { - match operation.into() { + match operation { NeedsValidationBox::Instruction(isi) => { Self::execute_instruction(wsv, authority.clone(), isi) } diff --git a/core/src/wsv.rs b/core/src/wsv.rs index c59915c6b7c..17175b52183 100644 --- a/core/src/wsv.rs +++ b/core/src/wsv.rs @@ -20,7 +20,7 @@ use iroha_config::{ }; use iroha_crypto::HashOf; use iroha_data_model::{ - block::{CommittedBlock, VersionedCommittedBlock}, + block::{Block, BlockPayload, VersionedSignedBlock}, isi::error::{InstructionExecutionFailure as Error, MathError}, prelude::*, query::error::{FindError, QueryExecutionFailure}, @@ -35,8 +35,8 @@ use crate::validator::MockValidator as Validator; #[cfg(not(test))] use crate::validator::Validator; use crate::{ + block::CommittedBlock, kura::Kura, - prelude::*, smartcontracts::{ triggers::{ self, @@ -121,9 +121,9 @@ pub struct WorldStateView { /// Configuration of World State View. pub config: Configuration, /// Blockchain. - pub block_hashes: std::cell::RefCell>>, + pub block_hashes: std::cell::RefCell>>, /// Hashes of transactions - pub transactions: DashSet>, + pub transactions: DashSet>, /// Buffer containing events generated during `WorldStateView::apply`. Renewed on every block commit. pub events_buffer: std::cell::RefCell>, /// Accumulated amount of any asset that has been transacted. @@ -318,10 +318,9 @@ impl WorldStateView { /// - If trigger execution fails /// - If timestamp conversion to `u64` fails #[iroha_logger::log(skip_all, fields(block_height))] - pub fn apply(&self, block: &VersionedCommittedBlock) -> Result<()> { + pub fn apply(&self, block: &CommittedBlock) -> Result<()> { let hash = block.hash(); - let block = block.as_v1(); - iroha_logger::prelude::Span::current().record("block_height", block.header.height); + iroha_logger::prelude::Span::current().record("block_height", block.header().height); trace!("Applying block"); let time_event = self.create_time_event(block)?; self.events_buffer @@ -354,27 +353,29 @@ impl WorldStateView { /// Get a reference to the latest block. Returns none if genesis is not committed. #[inline] - pub fn latest_block_ref(&self) -> Option> { + pub fn latest_block_ref(&self) -> Option> { self.kura .get_block_by_height(self.block_hashes.borrow().len() as u64) } /// Create time event using previous and current blocks fn create_time_event(&self, block: &CommittedBlock) -> Result { + let block_header = block.header(); + let prev_interval = self .latest_block_ref() .map(|latest_block| { let header = latest_block.header(); - header.timestamp.try_into().map(|since| TimeInterval { + header.timestamp_ms.try_into().map(|since| TimeInterval { since: Duration::from_millis(since), - length: Duration::from_millis(header.consensus_estimation), + length: Duration::from_millis(header.consensus_estimation_ms), }) }) .transpose()?; let interval = TimeInterval { - since: Duration::from_millis(block.header.timestamp.try_into()?), - length: Duration::from_millis(block.header.consensus_estimation), + since: Duration::from_millis(block_header.timestamp_ms.try_into()?), + length: Duration::from_millis(block_header.consensus_estimation_ms), }; Ok(TimeEvent { @@ -389,16 +390,15 @@ impl WorldStateView { /// # Errors /// Fails if transaction instruction execution fails fn execute_transactions(&self, block: &CommittedBlock) -> Result<()> { + let payload = block.payload(); + // TODO: Should this block panic instead? - for tx in &block.transactions { - self.process_executable( - &tx.as_v1().payload.instructions, - tx.payload().account_id.clone(), - )?; + for tx in &payload.transactions { + self.process_executable(&tx.payload().instructions, tx.payload().account_id.clone())?; self.transactions.insert(tx.hash()); } - for tx in &block.rejected_transactions { - self.transactions.insert(tx.hash()); + for tx in &payload.rejected_transactions { + self.transactions.insert(tx.0.hash()); } Ok(()) @@ -429,7 +429,9 @@ impl WorldStateView { /// /// # Errors /// - There is no account with such name. - #[allow(clippy::missing_panics_doc)] + /// + /// # Panics + /// - if [`Account::add_asset`] fails. pub fn asset_or_insert( &self, id: &::Id, @@ -458,7 +460,7 @@ impl WorldStateView { #[allow(clippy::expect_used)] pub fn all_blocks_by_value( &self, - ) -> impl DoubleEndedIterator + '_ { + ) -> impl DoubleEndedIterator + '_ { let block_count = self.block_hashes.borrow().len() as u64; (1..=block_count) .map(|height| { @@ -466,14 +468,14 @@ impl WorldStateView { .get_block_by_height(height) .expect("Failed to load block.") }) - .map(|block| VersionedCommittedBlock::clone(&block)) + .map(|block| VersionedSignedBlock::clone(&block)) } /// Return a vector of blockchain blocks after the block with the given `hash` pub fn block_hashes_after_hash( &self, - hash: Option>, - ) -> Vec> { + hash: Option>, + ) -> Vec> { hash.map_or_else( || self.block_hashes.borrow().clone(), |block_hash| { @@ -534,7 +536,7 @@ impl WorldStateView { } /// Return an iterator over blockchain block hashes starting with the block of the given `height` - pub fn block_hashes_from_height(&self, height: usize) -> Vec> { + pub fn block_hashes_from_height(&self, height: usize) -> Vec> { self.block_hashes .borrow() .iter() @@ -667,7 +669,7 @@ impl WorldStateView { let opt = self .kura .get_block_by_height(1) - .map(|genesis_block| genesis_block.as_v1().header.timestamp); + .map(|genesis_block| genesis_block.header().timestamp_ms); if opt.is_none() { error!("Failed to get genesis block from Kura."); @@ -678,7 +680,7 @@ impl WorldStateView { /// Check if this [`VersionedSignedTransaction`] is already committed or rejected. #[inline] - pub fn has_transaction(&self, hash: &HashOf) -> bool { + pub fn has_transaction(&self, hash: &HashOf) -> bool { self.transactions.contains(hash) } @@ -689,7 +691,7 @@ impl WorldStateView { } /// Return the hash of the latest block - pub fn latest_block_hash(&self) -> Option> { + pub fn latest_block_hash(&self) -> Option> { self.block_hashes.borrow().iter().nth_back(0).copied() } @@ -701,7 +703,7 @@ impl WorldStateView { } /// Return the hash of the block one before the latest block - pub fn previous_block_hash(&self) -> Option> { + pub fn previous_block_hash(&self) -> Option> { self.block_hashes.borrow().iter().nth_back(1).copied() } @@ -963,18 +965,19 @@ impl WorldStateView { let mut txs = self .all_blocks_by_value() .flat_map(|block| { - let block = block.as_v1(); - block + let payload = block.payload(); + + payload .rejected_transactions .iter() .cloned() .map(Box::new) .map(|versioned_rejected_tx| TransactionQueryResult { tx_value: TransactionValue::RejectedTransaction(versioned_rejected_tx), - block_hash: Hash::from(block.hash()), + block_hash: block.hash(), }) .chain( - block + payload .transactions .iter() .cloned() @@ -982,7 +985,7 @@ impl WorldStateView { .map(Box::new) .map(|versioned_tx| TransactionQueryResult { tx_value: TransactionValue::Transaction(versioned_tx), - block_hash: Hash::from(block.hash()), + block_hash: block.hash(), }), ) .collect::>() @@ -995,25 +998,33 @@ impl WorldStateView { /// Find a [`VersionedSignedTransaction`] by hash. pub fn transaction_value_by_hash( &self, - hash: &HashOf, - ) -> Option { + hash: &HashOf, + ) -> Option { self.all_blocks_by_value().find_map(|b| { - b.as_v1() + let payload = b.payload(); + + payload .rejected_transactions .iter() - .find(|e| e.hash() == *hash) + .find(|e| e.0.hash() == *hash) .cloned() .map(Box::new) - .map(TransactionValue::RejectedTransaction) + .map(|versioned_rejected_tx| TransactionQueryResult { + tx_value: TransactionValue::RejectedTransaction(versioned_rejected_tx), + block_hash: b.hash(), + }) .or_else(|| { - b.as_v1() + payload .transactions .iter() .find(|e| e.hash() == *hash) .cloned() .map(VersionedSignedTransaction::from) .map(Box::new) - .map(TransactionValue::Transaction) + .map(|versioned_tx| TransactionQueryResult { + tx_value: TransactionValue::Transaction(versioned_tx), + block_hash: b.hash(), + }) }) }) } @@ -1022,27 +1033,34 @@ impl WorldStateView { pub fn transactions_values_by_account_id( &self, account_id: &AccountId, - ) -> Vec { + ) -> Vec { let mut transactions = self .all_blocks_by_value() - .flat_map(|block_entry| { - let block = block_entry.as_v1(); - block + .flat_map(|b| { + let payload = b.payload(); + + payload .rejected_transactions .iter() - .filter(|transaction| &transaction.payload().account_id == account_id) + .filter(|transaction| &transaction.0.payload().account_id == account_id) .cloned() .map(Box::new) - .map(TransactionValue::RejectedTransaction) + .map(|versioned_rejected_tx| TransactionQueryResult { + tx_value: TransactionValue::RejectedTransaction(versioned_rejected_tx), + block_hash: b.hash(), + }) .chain( - block + payload .transactions .iter() .filter(|transaction| &transaction.payload().account_id == account_id) .cloned() .map(VersionedSignedTransaction::from) .map(Box::new) - .map(TransactionValue::Transaction), + .map(|versioned_tx| TransactionQueryResult { + tx_value: TransactionValue::Transaction(versioned_tx), + block_hash: b.hash(), + }), ) .collect::>() }) @@ -1145,21 +1163,24 @@ mod tests { #![allow(clippy::restriction)] use super::*; - use crate::block::PendingBlock; + use crate::block::ValidBlock; #[test] fn get_block_hashes_after_hash() { const BLOCK_CNT: usize = 10; - let mut block = PendingBlock::new_dummy().commit_unchecked(); let kura = Kura::blank_kura_for_testing(); let wsv = WorldStateView::new(World::default(), kura); + let mut valid_block = ValidBlock::new_dummy(); let mut block_hashes = vec![]; for i in 1..=BLOCK_CNT { - block.header.height = i as u64; - block.header.previous_block_hash = block_hashes.last().copied(); - let block: VersionedCommittedBlock = block.clone().into(); + let VersionedSignedBlock::V1(v1_block) = &mut valid_block.0; + v1_block.payload.header.height = i as u64; + v1_block.payload.header.previous_block_hash = block_hashes.last().copied(); + + let block = valid_block.clone().commit_unchecked(); + let block: CommittedBlock = block.0.clone(); block_hashes.push(block.hash()); wsv.apply(&block).unwrap(); } @@ -1174,15 +1195,16 @@ mod tests { fn get_blocks_from_height() { const BLOCK_CNT: usize = 10; - let mut block = PendingBlock::new_dummy().commit_unchecked(); let kura = Kura::blank_kura_for_testing(); let wsv = WorldStateView::new(World::default(), kura.clone()); + let mut valid_block = ValidBlock::new_dummy(); for i in 1..=BLOCK_CNT { - block.header.height = i as u64; - let block: VersionedCommittedBlock = block.clone().into(); - wsv.apply(&block).unwrap(); - kura.store_block(block); + let VersionedSignedBlock::V1(v1_block) = &mut valid_block.0; + v1_block.payload.header.height = i as u64; + let block = valid_block.clone().commit_unchecked(); + wsv.apply(&block.0).unwrap(); + kura.store_block(block.0); } assert_eq!( diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 26f7260cb3a..8b317038583 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -177,7 +177,6 @@ impl Network { ) -> (Self, Client) { let mut configuration = Configuration::test(); configuration.queue.maximum_transactions_in_block = max_txs_in_block; - configuration.logger.max_log_level = iroha_logger::Level::INFO.into(); let network = Network::new_with_offline_peers( Some(configuration), n_peers, @@ -826,6 +825,9 @@ impl TestConfiguration for Configuration { iroha::samples::get_config_proxy(HashSet::new(), Some(get_key_pair())); let env_proxy = ConfigurationProxy::from_env(); let (public_key, private_key) = KeyPair::generate().unwrap().into(); + if let Some(logger) = sample_proxy.logger.as_mut() { + logger.max_log_level = Some(iroha_logger::Level::DEBUG.into()); + } sample_proxy.public_key = Some(public_key); sample_proxy.private_key = Some(private_key); sample_proxy.override_with(env_proxy) diff --git a/crypto/src/hash.rs b/crypto/src/hash.rs index 941159512b9..52010e9b2d5 100644 --- a/crypto/src/hash.rs +++ b/crypto/src/hash.rs @@ -2,7 +2,7 @@ use alloc::{borrow::ToOwned as _, format, string::String, vec, vec::Vec}; use core::{hash, marker::PhantomData, num::NonZeroU8, str::FromStr}; -use derive_more::{DebugCustom, Deref, DerefMut, Display}; +use derive_more::{DebugCustom, Display}; use iroha_ffi::FfiType; use iroha_schema::{IntoSchema, TypeId}; use parity_scale_codec::{Decode, Encode}; @@ -74,7 +74,7 @@ impl Hash { /// since it is not possible to validate the correctness of the conversion. /// Prefer creating new hashes with [`HashOf::new`] whenever possible #[must_use] - pub const fn typed(self) -> HashOf { + pub(crate) const fn typed_unchecked(self) -> HashOf { HashOf(self, PhantomData) } @@ -199,23 +199,15 @@ impl From> for Hash { } /// Represents hash of Iroha entities like `Block` or `Transaction`. Currently supports only blake2b-32. -// Lint triggers when expanding #[codec(skip)] -#[allow(clippy::default_trait_access)] -#[derive( - DebugCustom, Deref, DerefMut, Display, Decode, Encode, Deserialize, Serialize, FfiType, TypeId, -)] +#[allow(clippy::default_trait_access)] // NOTE: Caused by #[codec(skip)] +#[derive(DebugCustom, Display, Decode, Encode, Deserialize, Serialize, FfiType, TypeId)] #[debug(fmt = "{{ {} {_0} }}", "core::any::type_name::()")] #[display(fmt = "{_0}")] #[serde(transparent)] #[repr(transparent)] // TODO: Temporary until PRs are resolved #[ffi_type(opaque)] -pub struct HashOf( - #[deref] - #[deref_mut] - Hash, - #[codec(skip)] PhantomData, -); +pub struct HashOf(Hash, #[codec(skip)] PhantomData); impl Clone for HashOf { fn clone(&self) -> Self { diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index f5e42fabaff..93b3decd948 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -336,7 +336,7 @@ impl From for (PublicKey, PrivateKey) { ffi::ffi_item! { /// Public Key used in signatures. #[derive(DebugCustom, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, DeserializeFromStr, SerializeDisplay, Decode, Encode, FfiType, IntoSchema)] - #[debug(fmt = "{{digest: {digest_function}, payload: {payload:X?}}}")] + #[debug(fmt = "digest: {digest_function}, payload: {}", "hex::encode_upper(&self.payload)")] pub struct PublicKey { /// Digest function digest_function: Algorithm, @@ -421,8 +421,9 @@ impl From for PublicKey { ffi::ffi_item! { /// Private Key used in signatures. - #[derive(DebugCustom, Clone, PartialEq, Eq, Serialize, FfiType)] - #[debug(fmt = "{{digest: {digest_function}, payload: {payload:X?}}}")] + #[derive(DebugCustom, Display, Clone, PartialEq, Eq, Serialize, FfiType)] + #[debug(fmt = "digest: {digest_function}, payload: {}", "hex::encode_upper(&self.payload)")] + #[display(fmt = "payload: {}", "hex::encode_upper(&self.payload)")] pub struct PrivateKey { /// Digest function digest_function: Algorithm, @@ -432,12 +433,6 @@ ffi::ffi_item! { } } -impl fmt::Display for PrivateKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode_upper(&self.payload)) - } -} - #[cfg_attr( all(feature = "ffi_export", not(feature = "ffi_import")), iroha_ffi::ffi_export diff --git a/crypto/src/merkle.rs b/crypto/src/merkle.rs index ac9bbe0566e..d5081733d76 100644 --- a/crypto/src/merkle.rs +++ b/crypto/src/merkle.rs @@ -228,7 +228,7 @@ impl MerkleTree { .zip(r_hash.as_ref().iter()) .map(|(l, r)| l.wrapping_add(*r)) .collect(); - Some(crate::Hash::new(sum).typed()) + Some(crate::Hash::new(sum).typed_unchecked()) } } @@ -261,7 +261,7 @@ mod tests { fn test_hashes(n_hashes: u8) -> Vec> { (1..=n_hashes) - .map(|i| Hash::prehashed([i; Hash::LENGTH]).typed()) + .map(|i| Hash::prehashed([i; Hash::LENGTH]).typed_unchecked()) .collect() } diff --git a/crypto/src/signature.rs b/crypto/src/signature.rs index cd71b879869..a632008816b 100644 --- a/crypto/src/signature.rs +++ b/crypto/src/signature.rs @@ -8,7 +8,7 @@ use std::collections::btree_set; use derive_more::{DebugCustom, Deref, DerefMut}; use getset::Getters; use iroha_schema::{IntoSchema, TypeId}; -use parity_scale_codec::{Decode, Encode, Input}; +use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use ursa::{ @@ -35,12 +35,12 @@ pub type Payload = Vec; Eq, PartialOrd, Ord, + Hash, Getters, Decode, Encode, Deserialize, Serialize, - Hash, IntoSchema, )] #[getset(get = "pub")] @@ -62,7 +62,7 @@ impl Signature { /// # Errors /// Fails if signing fails #[cfg(feature = "std")] - pub fn new(key_pair: KeyPair, payload: &[u8]) -> Result { + fn new(key_pair: KeyPair, payload: &[u8]) -> Result { let (public_key, private_key) = key_pair.into(); let algorithm: Algorithm = private_key.digest_function(); @@ -86,7 +86,7 @@ impl Signature { /// Prefer creating new signatures with [`SignatureOf::new`] whenever possible #[inline] #[cfg_attr(not(feature = "std"), allow(dead_code))] - const fn typed(self) -> SignatureOf { + const fn typed_unchecked(self) -> SignatureOf { SignatureOf(self, PhantomData) } @@ -95,7 +95,7 @@ impl Signature { /// # Errors /// Fails if message didn't pass verification #[cfg(feature = "std")] - pub fn verify(&self, payload: &[u8]) -> Result<(), Error> { + fn verify(&self, payload: &[u8]) -> Result<(), Error> { let algorithm: Algorithm = self.public_key.digest_function(); let public_key = UrsaPublicKey(self.public_key.payload().to_owned()); @@ -130,13 +130,11 @@ impl From> for Signature { } /// Represents signature of the data (`Block` or `Transaction` for example). -// Lint triggers when expanding #[codec(skip)] #[allow( + // Caused by #[codec(skip)] clippy::default_trait_access, - clippy::unsafe_derive_deserialize, - clippy::derive_hash_xor_eq )] -#[derive(Deref, DerefMut, Hash, Decode, Encode, Deserialize, Serialize, TypeId)] +#[derive(Deref, DerefMut, Decode, Encode, Deserialize, Serialize, TypeId)] #[serde(transparent)] // Transmute guard #[repr(transparent)] @@ -179,6 +177,12 @@ impl Ord for SignatureOf { } } +impl core::hash::Hash for SignatureOf { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + impl IntoSchema for SignatureOf { fn type_name() -> String { format!("SignatureOf<{}>", T::type_name()) @@ -203,28 +207,7 @@ impl SignatureOf { /// Fails if signing fails #[cfg(feature = "std")] pub fn from_hash(key_pair: KeyPair, hash: &HashOf) -> Result { - Ok(Signature::new(key_pair, hash.as_ref())?.typed()) - } - - /// Transmutes signature to some specific type - pub fn transmute(self) -> SignatureOf { - SignatureOf(self.0, PhantomData) - } - - /// Transmutes signature to some specific type - /// - /// # Warning: - /// - /// This method uses [`core::mem::transmute`] internally - pub const fn transmute_ref(&self) -> &SignatureOf { - #[allow(unsafe_code, trivial_casts)] - // SAFETY: transmuting is safe, because we're casting a - // pointer of type `SignatureOf` into a pointer of type - // `SignatureOf`, where `` and `` type parameters are - // normally related types that have the exact same alignment. - unsafe { - &*((self as *const Self).cast::>()) - } + Signature::new(key_pair, hash.as_ref()).map(Signature::typed_unchecked) } /// Verify signature for this hash @@ -287,7 +270,7 @@ impl Eq for SignatureWrapperOf {} impl PartialOrd for SignatureWrapperOf { fn partial_cmp(&self, other: &Self) -> Option { - self.0.public_key.partial_cmp(&other.0.public_key) + Some(self.cmp(other)) } } impl Ord for SignatureWrapperOf { @@ -319,10 +302,7 @@ impl core::hash::Hash for SignatureWrapperOf { /// /// If the public key of the added signature is already in the set, /// the associated signature will be replaced with the new one. -/// -/// GUARANTEE 1: Each signature corresponds to a different public key -#[allow(clippy::derive_hash_xor_eq)] -#[derive(Hash, Encode, Serialize, IntoSchema)] +#[derive(Default, Decode, Encode, Deserialize, Serialize, IntoSchema)] #[serde(transparent)] // Transmute guard #[repr(transparent)] @@ -350,34 +330,20 @@ impl PartialEq for SignaturesOf { } } impl Eq for SignaturesOf {} - -impl<'de, T> Deserialize<'de> for SignaturesOf { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error as _; - - let signatures = >>::deserialize(deserializer)?; - - if signatures.is_empty() { - return Err(D::Error::custom( - "Could not deserialize SignaturesOf. Input contains 0 signatures", - )); - } - - Ok(Self { signatures }) +impl PartialOrd for SignaturesOf { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for SignaturesOf { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.signatures.cmp(&other.signatures) } } -impl Decode for SignaturesOf { - fn decode(input: &mut I) -> Result { - let signatures = >>::decode(input)?; - - if signatures.is_empty() { - return Err("Could not decode SignaturesOf. Input contains 0 signatures".into()); - } - Ok(Self { signatures }) +impl core::hash::Hash for SignaturesOf { + fn hash(&self, state: &mut H) { + self.signatures.hash(state); } } @@ -420,10 +386,8 @@ impl From> for btree_set::BTreeSet> { } } -impl TryFrom>> for SignaturesOf { - type Error = Error; - - fn try_from(signatures: btree_set::BTreeSet>) -> Result { +impl From>> for SignaturesOf { + fn from(signatures: btree_set::BTreeSet>) -> Self { signatures.into_iter().collect() } } @@ -436,31 +400,15 @@ impl From> for SignaturesOf { } } -impl FromIterator> for Result, Error> { +impl FromIterator> for SignaturesOf { fn from_iter>>(iter: T) -> Self { - let mut iter = iter.into_iter(); - iter.next() - .ok_or(Error::EmptySignatureIter) - .map(move |first_signature| core::iter::once(first_signature).chain(iter)) - .map(|signatures| signatures.map(SignatureWrapperOf).collect()) - .map(|signatures| SignaturesOf { signatures }) + Self { + signatures: iter.into_iter().map(SignatureWrapperOf).collect(), + } } } impl SignaturesOf { - /// Transmutes signature generic type - /// - /// # Warning: - /// - /// This method uses [`core::mem::transmute`] internally - #[allow(unsafe_code, clippy::transmute_undefined_repr)] - pub fn transmute(self) -> SignaturesOf { - // SAFETY: Safe because we are transmuting to a pointer of - // type `` which is related to type ``. - let signatures = unsafe { core::mem::transmute(self.signatures) }; - SignaturesOf { signatures } - } - /// Adds a signature. If the signature with this key was present, replaces it. pub fn insert(&mut self, signature: SignatureOf) { self.signatures.insert(SignatureWrapperOf(signature)); @@ -581,8 +529,6 @@ impl std::error::Error for SignatureVerificationFail {} mod tests { #![allow(clippy::restriction)] - use parity_scale_codec::DecodeAll; - #[cfg(feature = "std")] use super::*; #[cfg(feature = "std")] @@ -644,32 +590,6 @@ mod tests { assert!(signature.verify(message).is_ok()) } - #[test] - #[cfg(feature = "std")] - fn decode_signatures_of() { - let no_signatures: SignaturesOf = SignaturesOf { - signatures: btree_set::BTreeSet::new(), - }; - let bytes = no_signatures.encode(); - - let signatures = SignaturesOf::::decode_all(&mut &bytes[..]); - assert!(signatures.is_err()); - } - - #[test] - #[cfg(feature = "std")] - fn deserialize_signatures_of() -> Result<(), serde_json::Error> { - let no_signatures: SignaturesOf = SignaturesOf { - signatures: btree_set::BTreeSet::new(), - }; - let serialized = serde_json::to_string(&no_signatures)?; - - let signatures = serde_json::from_str::>(serialized.as_str()); - assert!(signatures.is_err()); - - Ok(()) - } - #[test] #[cfg(feature = "std")] fn signatures_of_deduplication_by_public_key() { @@ -679,10 +599,7 @@ mod tests { SignatureOf::new(key_pair.clone(), &2).expect("Failed to sign"), SignatureOf::new(key_pair, &3).expect("Failed to sign"), ]; - let signatures = signatures - .into_iter() - .collect::, Error>>() - .expect("One signature must stay"); + let signatures: SignaturesOf<_> = signatures.into_iter().collect(); // Signatures with the same public key was deduplicated assert_eq!(signatures.len(), 1); } @@ -707,6 +624,8 @@ mod tests { .collect::>(); let hash_set: HashSet<_> = signatures.clone().into_iter().collect(); let btree_set: BTreeSet<_> = signatures.into_iter().collect(); + println!("{:#?}", &hash_set); + println!("{:#?}", &btree_set); // Check that `hash_set` is subset of `btree_set` for signature in &hash_set { diff --git a/data_model/Cargo.toml b/data_model/Cargo.toml index d67d17de9ae..1ded40aebfb 100644 --- a/data_model/Cargo.toml +++ b/data_model/Cargo.toml @@ -27,7 +27,7 @@ http = ["std", "warp", "iroha_version/http"] # Expose FFI API for dynamic linking (Internal use only) ffi_export = ["std", "iroha_ffi", "iroha_primitives/ffi_export", "iroha_crypto/ffi_export"] # Expose API for mutating structures (Internal use only) -transparent_api = [] +transparent-api = [] [dependencies] iroha_primitives = { path = "../primitives", version = "=2.0.0-pre-rc.13", default-features = false } diff --git a/data_model/derive/src/filter.rs b/data_model/derive/src/filter.rs index 7ee0d6b40f2..a4058300ad9 100644 --- a/data_model/derive/src/filter.rs +++ b/data_model/derive/src/filter.rs @@ -209,7 +209,7 @@ pub fn impl_filter(event: &EventEnum) -> TokenStream { } } - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] impl #import_path::Filter for #filter_ident { type Event = #imp_event; @@ -256,7 +256,7 @@ fn impl_event_filter(event: &EventEnum) -> proc_macro2::TokenStream { } } - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] impl #import_path::Filter for #event_filter_ident { type Event = #imp_event; diff --git a/data_model/derive/src/lib.rs b/data_model/derive/src/lib.rs index 74ded26ca9a..62f5b3dbcb1 100644 --- a/data_model/derive/src/lib.rs +++ b/data_model/derive/src/lib.rs @@ -10,13 +10,13 @@ mod partially_tagged; use proc_macro::TokenStream; use syn::parse_macro_input; -/// Macro which controls how to export item's API. The behaviour is controlled with `transparent_api` +/// Macro which controls how to export item's API. The behaviour is controlled with `transparent-api` /// feature flag. If the flag is active, item's public fields will be exposed as public, however, if /// it's not active, item will be exposed as opaque, i.e. no fields will be visible. This enables /// internal libraries of Iroha to see and destructure data model items. On the other hand, /// client libraries will only see opaque items and can be dynamically linked. /// -/// Additionally, this macro will rewrite private items as public when `transparent_api` is active. +/// Additionally, this macro will rewrite private items as public when `transparent-api` is active. /// If an item should remain private regardless of consumer library, just don't wrap it in this macro. /// /// Should be used only on public module named `model`. @@ -43,20 +43,20 @@ use syn::parse_macro_input; /// /* will produce: /// pub mod model { /// pub struct DataModel1 { -/// #[cfg(feature = "transparent_api")] +/// #[cfg(feature = "transparent-api")] /// pub item1: u32, -/// #[cfg(not(feature = "transparent_api"))] +/// #[cfg(not(feature = "transparent-api"))] /// pub(crate) item1: u32, /// pub(super) item2: u64 /// } /// -/// #[cfg(not(feature = "transparent_api"))] +/// #[cfg(not(feature = "transparent-api"))] /// pub struct DataModel2 { /// pub item1: u32, /// pub(super) item2: u64 /// } /// -/// #[cfg(feature = "transparent_api")] +/// #[cfg(feature = "transparent-api")] /// struct DataModel2 { /// pub item1: u32, /// pub(super) item2: u64 diff --git a/data_model/derive/src/model.rs b/data_model/derive/src/model.rs index db879181b41..6139021c29f 100644 --- a/data_model/derive/src/model.rs +++ b/data_model/derive/src/model.rs @@ -51,13 +51,13 @@ pub fn process_item(item: syn::Item) -> TokenStream { } let non_transparent_item = quote! { - #[cfg(not(feature = "transparent_api"))] + #[cfg(not(feature = "transparent-api"))] #input }; input.vis = parse_quote! {pub}; let transparent_item = quote! { - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] #input }; @@ -86,11 +86,11 @@ fn process_pub_item(input: syn::DeriveInput) -> TokenStream { } quote! { - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] #(#field_attrs)* pub #field_name: #field_ty, - #[cfg(not(feature = "transparent_api"))] + #[cfg(not(feature = "transparent-api"))] #(#field_attrs)* pub(crate) #field_name: #field_ty, } @@ -114,11 +114,11 @@ fn process_pub_item(input: syn::DeriveInput) -> TokenStream { } quote! { - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] #(#field_attrs)* pub #field_ty, - #[cfg(not(feature = "transparent_api"))] + #[cfg(not(feature = "transparent-api"))] #(#field_attrs)* pub(crate) #field_ty, } @@ -163,11 +163,11 @@ fn process_pub_item(input: syn::DeriveInput) -> TokenStream { quote! { #(#field_attrs)* - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] pub #field_name: #field_ty, #(#field_attrs)* - #[cfg(not(feature = "transparent_api"))] + #[cfg(not(feature = "transparent-api"))] pub(crate) #field_name: #field_ty, } }); diff --git a/data_model/src/account.rs b/data_model/src/account.rs index 789f0845351..0e435983d9e 100644 --- a/data_model/src/account.rs +++ b/data_model/src/account.rs @@ -163,22 +163,6 @@ pub mod model { pub struct SignatureCheckCondition(pub EvaluatesTo); } -impl AccountId { - #[cfg(feature = "transparent_api")] - const GENESIS_ACCOUNT_NAME: &str = "genesis"; - - /// Construct [`Id`] of the genesis account. - #[inline] - #[must_use] - #[cfg(feature = "transparent_api")] - pub fn genesis() -> Self { - Self { - name: Self::GENESIS_ACCOUNT_NAME.parse().expect("Valid"), - domain_id: DomainId::genesis(), - } - } -} - impl Account { /// Construct builder for [`Account`] identifiable by [`Id`] containing the given signatories. #[inline] @@ -227,7 +211,7 @@ impl Account { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Account { /// Add [`Asset`] into the [`Account`] returning previous asset stored under the same id #[inline] diff --git a/data_model/src/block.rs b/data_model/src/block.rs index b871d2baeb9..e220c71d303 100644 --- a/data_model/src/block.rs +++ b/data_model/src/block.rs @@ -10,274 +10,281 @@ use core::{cmp::Ordering, fmt::Display}; use derive_more::Display; use getset::Getters; -#[cfg(feature = "std")] -use iroha_crypto::SignatureOf; -use iroha_crypto::{HashOf, MerkleTree, SignaturesOf}; +use iroha_crypto::{HashOf, MerkleTree, SignatureOf, SignaturesOf}; +use iroha_data_model_derive::model; +use iroha_macro::FromVariant; use iroha_schema::IntoSchema; -use iroha_version::{declare_versioned_with_scale, version_with_scale}; -use parity_scale_codec::{Decode, Encode}; +use iroha_version::{declare_versioned, version_with_scale}; +pub use model::*; +use parity_scale_codec::{Decode, Encode, Input}; use serde::{Deserialize, Serialize}; -pub use self::{ - committed::{CommittedBlock, VersionedCommittedBlock}, - header::BlockHeader, -}; -use crate::{events::prelude::*, model, peer, transaction::prelude::*}; +use crate::{events::prelude::*, peer, transaction::prelude::*}; -mod header { - pub use self::model::*; +/// Trait for basic block operations +pub trait Block { + /// Calculate block hash + #[cfg(feature = "std")] + fn hash(&self) -> HashOf { + HashOf::new(self.header()).transmute() + } + /// Return block header + fn header(&self) -> &BlockHeader { + &self.payload().header + } + + /// Return block payload + fn payload(&self) -> &BlockPayload; + /// Return block signatures + fn signatures(&self) -> &SignaturesOf; +} + +#[model] +pub mod model { use super::*; + use crate::transaction::error::TransactionRejectionReason; + + #[derive( + Debug, + Display, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Getters, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[cfg_attr( + feature = "std", + 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] + // TODO: Do we need both BlockPayload and BlockHeader? + // If yes, what data goes into which structure? + pub struct BlockHeader { + /// Number of blocks in the chain including this block. + pub height: u64, + /// Creation timestamp (unix time in milliseconds). + pub timestamp_ms: u128, + /// Hash of the previous block in the chain. + pub previous_block_hash: Option>, + /// Hash of merkle tree root of valid transactions' hashes. + pub transactions_hash: Option>>, + /// Hash of merkle tree root of rejected transactions' hashes. + pub rejected_transactions_hash: Option>>, + /// Topology of the network at the time of block commit. + pub commit_topology: Vec, + /// Value of view change index. Used to resolve soft forks. + // NOTE: This field used to be required to rotate topology. After merging + // https://github.com/hyperledger/iroha/pull/3250 only commit_topology is used + #[deprecated(since = "2.0.0-pre-rc.13", note = "Will be removed in future versions")] + pub view_change_index: u64, + /// Estimation of consensus duration (in milliseconds). + pub consensus_estimation_ms: u64, + } - #[model] - pub mod model { - use super::*; + #[derive( + Debug, Display, Clone, Eq, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] + #[display(fmt = "({header})")] + #[getset(get = "pub")] + #[allow(missing_docs)] + #[ffi_type] + pub struct BlockPayload { + /// Block header + pub header: BlockHeader, + /// array of transactions, which successfully passed validation and consensus step. + pub transactions: Vec, + /// Array of rejected transactions. + pub rejected_transactions: Vec<(VersionedSignedTransaction, TransactionRejectionReason)>, + /// Event recommendations. + #[getset(skip)] // NOTE: Unused ATM + pub event_recommendations: Vec, + } - /// Header of the block. The hash should be taken from its byte representation. - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - Hash, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[cfg_attr( - feature = "std", - display(fmt = "Block №{height} (hash: {});", "HashOf::new(&self)") - )] - #[cfg_attr(not(feature = "std"), display(fmt = "Block №{height}"))] - #[getset(get = "pub")] - #[ffi_type] - pub struct BlockHeader { - /// Unix time (in milliseconds) of block forming by a peer. - pub timestamp: u128, - /// Estimation of consensus duration in milliseconds - pub consensus_estimation: u64, - /// A number of blocks in the chain up to the block. - pub height: u64, - /// Value of view change index used to resolve soft forks - pub view_change_index: u64, - /// Hash of a previous block in the chain. - /// Is an array of zeros for the first block. - pub previous_block_hash: Option>, - /// Hash of merkle tree root of the tree of valid transactions' hashes. - pub transactions_hash: Option>>, - /// Hash of merkle tree root of the tree of rejected transactions' hashes. - pub rejected_transactions_hash: Option>>, - /// Network topology when the block was committed. - pub committed_with_topology: Vec, - } + /// Signed block + #[version_with_scale(n = 1, versioned = "VersionedSignedBlock")] + #[derive( + Debug, + Display, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Getters, + Encode, + Serialize, + IntoSchema, + )] + #[display(fmt = "({payload})")] + #[getset(get = "pub")] + #[ffi_type] + pub struct SignedBlock { + /// Block payload + pub payload: BlockPayload, + /// Signatures of peers which approved this block. + pub signatures: SignaturesOf, } +} - impl BlockHeader { - /// Checks if it's a header of a genesis block. - #[inline] - pub const fn is_genesis(&self) -> bool { - self.height == 1 - } +#[cfg(any(feature = "ffi_import", feature = "ffi_export"))] +declare_versioned!(VersionedSignedBlock 1..2, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, FromVariant, iroha_ffi::FfiType, IntoSchema); +#[cfg(all(not(feature = "ffi_import"), not(feature = "ffi_export")))] +declare_versioned!(VersionedSignedBlock 1..2, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, FromVariant, IntoSchema); + +// TODO: Think about how should BlockPayload implement Eq, Ord, Hash? +impl PartialEq for BlockPayload { + fn eq(&self, other: &Self) -> bool { + self.header == other.header } +} +impl PartialOrd for BlockPayload { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for BlockPayload { + fn cmp(&self, other: &Self) -> Ordering { + self.header.cmp(&other.header) + } +} +impl core::hash::Hash for BlockPayload { + fn hash(&self, state: &mut H) { + self.header.hash(state) + } +} - impl PartialOrd for BlockHeader { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } +impl BlockHeader { + /// Checks if it's a header of a genesis block. + #[inline] + pub const fn is_genesis(&self) -> bool { + self.height == 1 } +} - impl Ord for BlockHeader { - fn cmp(&self, other: &Self) -> Ordering { - self.timestamp.cmp(&other.timestamp) - } +impl Block for SignedBlock { + fn payload(&self) -> &BlockPayload { + &self.payload + } + fn signatures(&self) -> &SignaturesOf { + &self.signatures } } -mod committed { - use iroha_macro::FromVariant; +impl Block for VersionedSignedBlock { + fn payload(&self) -> &BlockPayload { + let VersionedSignedBlock::V1(block) = self; + block.payload() + } + fn signatures(&self) -> &SignaturesOf { + let VersionedSignedBlock::V1(block) = self; + block.signatures() + } +} - pub use self::model::*; +mod candidate { use super::*; - #[cfg(any(feature = "ffi_import", feature = "ffi_export"))] - declare_versioned_with_scale!(VersionedCommittedBlock 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, Deserialize, Serialize, iroha_ffi::FfiType, IntoSchema); - #[cfg(all(not(feature = "ffi_import"), not(feature = "ffi_export")))] - declare_versioned_with_scale!(VersionedCommittedBlock 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, Deserialize, Serialize, IntoSchema); + #[derive(Decode, Deserialize)] + #[serde(transparent)] + struct SignedBlockCandidate(SignedBlock); - #[model] - pub mod model { - use super::*; + impl SignedBlockCandidate { + fn validate(mut self) -> Result { + #[cfg(feature = "std")] + self.validate_header()?; - /// The `CommittedBlock` struct represents a block accepted by consensus - #[version_with_scale(n = 1, versioned = "VersionedCommittedBlock")] - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - Hash, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[display(fmt = "({header})")] - #[getset(get = "pub")] - #[ffi_type] - pub struct CommittedBlock { - /// Block header - pub header: BlockHeader, - /// Array of rejected transactions. - pub rejected_transactions: Vec, - /// array of transactions, which successfully passed validation and consensus step. - pub transactions: Vec, - /// Event recommendations. - pub event_recommendations: Vec, - /// Signatures of peers which approved this block - pub signatures: SignaturesOf, - } - } - - impl VersionedCommittedBlock { - /// Convert from `&VersionedCommittedBlock` to V1 reference - #[inline] - pub const fn as_v1(&self) -> &CommittedBlock { - match self { - Self::V1(v1) => v1, + #[cfg(feature = "std")] + if self.retain_verified_signatures().is_empty() { + return Err("Block contains no signatures"); } - } - /// Convert from `&mut VersionedCommittedBlock` to V1 mutable reference - #[inline] - pub fn as_mut_v1(&mut self) -> &mut CommittedBlock { - match self { - Self::V1(v1) => v1, + let payload = &self.0.payload; + if payload.transactions.is_empty() && payload.rejected_transactions.is_empty() { + return Err("Block is empty"); } - } - /// Performs the conversion from `VersionedCommittedBlock` to V1 - #[inline] - pub fn into_v1(self) -> CommittedBlock { - match self { - Self::V1(v1) => v1, - } + Ok(self.0) } - /// Calculate the hash of the current block. - /// `VersionedCommitedBlock` should have the same hash as `VersionedCommitedBlock`. #[cfg(feature = "std")] - #[inline] - pub fn hash(&self) -> HashOf { - self.as_v1().hash().transmute() - } - - /// Returns the header of a valid block - #[inline] - pub const fn header(&self) -> &BlockHeader { - &self.as_v1().header + fn retain_verified_signatures(&mut self) -> Vec<&SignatureOf> { + self.0 + .signatures + .retain_verified_by_hash(self.0.hash()) + .collect() } - /// Return signatures that are verified with the `hash` of this block #[cfg(feature = "std")] - #[inline] - pub fn signatures(&self) -> impl IntoIterator> { - self.as_v1() - .signatures + fn validate_header(&self) -> Result<(), &'static str> { + let actual_txs_hash = self.0.header().transactions_hash; + let actual_rejected_txs_hash = self.0.header().rejected_transactions_hash; + + let expected_txs_hash = self + .0 + .payload + .transactions .iter() - .map(SignatureOf::transmute_ref) - } - } - - impl Display for VersionedCommittedBlock { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.as_v1().fmt(f) - } - } - - impl PartialOrd for VersionedCommittedBlock { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - impl Ord for VersionedCommittedBlock { - fn cmp(&self, other: &Self) -> Ordering { - self.as_v1().cmp(other.as_v1()) - } - } + .map(VersionedSignedTransaction::hash) + .collect::>() + .hash(); + let expected_rejected_txs_hash = self + .0 + .payload + .rejected_transactions + .iter() + .map(|(rejected_transaction, _)| rejected_transaction.hash()) + .collect::>() + .hash(); - impl CommittedBlock { - /// Calculate the hash of the current block. - /// `CommitedBlock` should have the same hash as `ValidBlock`. - #[cfg(feature = "std")] - #[inline] - pub fn hash(&self) -> HashOf { - HashOf::new(&self.header).transmute() - } - } + if expected_txs_hash != actual_txs_hash { + return Err("Transactions' hash incorrect. Expected: {expected_txs_hash:?}, actual: {actual_txs_hash:?}"); + } + if expected_rejected_txs_hash != actual_rejected_txs_hash { + return Err("Rejected transactions' hash incorrect. Expected: {expected_rejected_txs_hash:?}, actual: {actual_rejected_txs_hash:?}"); + } + // TODO: Validate Event recommendations somehow? - impl PartialOrd for CommittedBlock { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + Ok(()) } } - impl Ord for CommittedBlock { - fn cmp(&self, other: &Self) -> Ordering { - self.header.cmp(&other.header) + impl Decode for SignedBlock { + fn decode(input: &mut I) -> Result { + SignedBlockCandidate::decode(input)? + .validate() + .map_err(Into::into) } } - - #[cfg(feature = "std")] - impl From<&CommittedBlock> for Vec { - fn from(block: &CommittedBlock) -> Self { - let rejected_tx = block - .rejected_transactions - .iter() - .cloned() - .map(|transaction| { - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Rejected( - transaction.as_v1().rejection_reason.clone().into(), - ), - hash: transaction.hash().into(), - } - .into() - }); - let tx = block.transactions.iter().cloned().map(|transaction| { - PipelineEvent { - entity_kind: PipelineEntityKind::Transaction, - status: PipelineStatus::Committed, - hash: transaction.hash().into(), - } - .into() - }); - let current_block = core::iter::once( - PipelineEvent { - entity_kind: PipelineEntityKind::Block, - status: PipelineStatus::Committed, - hash: block.hash().into(), - } - .into(), - ); - - tx.chain(rejected_tx).chain(current_block).collect() + impl<'de> Deserialize<'de> for SignedBlock { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error as _; + + SignedBlockCandidate::deserialize(deserializer)? + .validate() + .map_err(D::Error::custom) } } +} - #[cfg(feature = "std")] - impl From<&VersionedCommittedBlock> for Vec { - #[inline] - fn from(block: &VersionedCommittedBlock) -> Self { - block.as_v1().into() - } +impl Display for VersionedSignedBlock { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let VersionedSignedBlock::V1(block) = self; + block.fmt(f) } } @@ -296,29 +303,6 @@ pub mod stream { declare_versioned_with_scale!(VersionedBlockMessage 1..2, Debug, Clone, FromVariant, IntoSchema); - impl VersionedBlockMessage { - /// Convert from `&VersionedBlockPublisherMessage` to V1 reference - pub const fn as_v1(&self) -> &BlockMessage { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedBlockPublisherMessage` to V1 mutable reference - pub fn as_mut_v1(&mut self) -> &mut BlockMessage { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedBlockPublisherMessage` to V1 - pub fn into_v1(self) -> BlockMessage { - match self { - Self::V1(v1) => v1, - } - } - } - #[model] pub mod model { use super::*; @@ -334,10 +318,10 @@ pub mod stream { #[version_with_scale(n = 1, versioned = "VersionedBlockMessage")] #[derive(Debug, Clone, Decode, Encode, IntoSchema)] #[repr(transparent)] - pub struct BlockMessage(pub VersionedCommittedBlock); + pub struct BlockMessage(pub VersionedSignedBlock); } - impl From for VersionedCommittedBlock { + impl From for VersionedSignedBlock { fn from(source: BlockMessage) -> Self { source.0 } @@ -345,38 +329,19 @@ pub mod stream { declare_versioned_with_scale!(VersionedBlockSubscriptionRequest 1..2, Debug, Clone, FromVariant, IntoSchema); - impl VersionedBlockSubscriptionRequest { - /// Convert from `&VersionedBlockSubscriberMessage` to V1 reference - pub const fn as_v1(&self) -> &BlockSubscriptionRequest { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedBlockSubscriberMessage` to V1 mutable reference - pub fn as_mut_v1(&mut self) -> &mut BlockSubscriptionRequest { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedBlockSubscriberMessage` to V1 - pub fn into_v1(self) -> BlockSubscriptionRequest { - match self { - Self::V1(v1) => v1, - } - } - } - /// Exports common structs and enums from this module. pub mod prelude { - pub use super::{ - BlockMessage, BlockSubscriptionRequest, VersionedBlockMessage, - VersionedBlockSubscriptionRequest, - }; + pub use super::{VersionedBlockMessage, VersionedBlockSubscriptionRequest}; } } +/// Exports common structs and enums from this module. +pub mod prelude { + #[cfg(feature = "http")] + pub use super::stream::prelude::*; + pub use super::{Block, VersionedSignedBlock}; +} + pub mod error { //! Module containing errors that can occur during instruction evaluation diff --git a/data_model/src/domain.rs b/data_model/src/domain.rs index 89a51fc1d07..c4337e321d9 100644 --- a/data_model/src/domain.rs +++ b/data_model/src/domain.rs @@ -198,7 +198,7 @@ impl Domain { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Domain { /// Add [`Account`] into the [`Domain`] returning previous account stored under the same id #[inline] @@ -266,21 +266,6 @@ impl FromIterator for crate::Value { } } -impl DomainId { - #[cfg(feature = "transparent_api")] - const GENESIS_DOMAIN_NAME: &str = "genesis"; - - /// Construct [`Id`] of the genesis domain. - #[inline] - #[must_use] - #[cfg(feature = "transparent_api")] - pub fn genesis() -> Self { - Self { - name: Self::GENESIS_DOMAIN_NAME.parse().expect("Valid"), - } - } -} - /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{Domain, DomainId}; diff --git a/data_model/src/events/data/events.rs b/data_model/src/events/data/events.rs index 03d44955524..6ee1173e313 100644 --- a/data_model/src/events/data/events.rs +++ b/data_model/src/events/data/events.rs @@ -526,7 +526,7 @@ mod validator { } } - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] impl super::Filter for ValidatorFilter { type Event = ValidatorEvent; diff --git a/data_model/src/events/data/filters.rs b/data_model/src/events/data/filters.rs index 49776ea76fb..58e20c582c9 100644 --- a/data_model/src/events/data/filters.rs +++ b/data_model/src/events/data/filters.rs @@ -136,7 +136,7 @@ mod accept_all_as_string { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Filter for FilterOpt { type Event = F::Event; @@ -148,7 +148,7 @@ impl Filter for FilterOpt { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Filter for DataEntityFilter { type Event = DataEvent; @@ -180,7 +180,7 @@ where } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Filter for OriginFilter where ::Id: @@ -222,7 +222,7 @@ pub mod prelude { } #[cfg(test)] -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] mod tests { #[cfg(not(feature = "std"))] use alloc::collections::BTreeSet; @@ -237,7 +237,7 @@ mod tests { }; #[test] - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] fn entity_scope() { let domain_name = "wonderland".parse().expect("Valid"); let account_name = "alice".parse().expect("Valid"); diff --git a/data_model/src/events/data/mod.rs b/data_model/src/events/data/mod.rs index 1c1ab8494f8..0153d217ef6 100644 --- a/data_model/src/events/data/mod.rs +++ b/data_model/src/events/data/mod.rs @@ -10,7 +10,7 @@ use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] use super::Filter; use crate::prelude::*; pub use crate::Registered; diff --git a/data_model/src/events/execute_trigger.rs b/data_model/src/events/execute_trigger.rs index b9222c16387..0f95fccb991 100644 --- a/data_model/src/events/execute_trigger.rs +++ b/data_model/src/events/execute_trigger.rs @@ -59,7 +59,7 @@ pub mod model { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Filter for ExecuteTriggerEventFilter { type Event = ExecuteTriggerEvent; diff --git a/data_model/src/events/mod.rs b/data_model/src/events/mod.rs index dd94c4d1a20..e4425dab704 100644 --- a/data_model/src/events/mod.rs +++ b/data_model/src/events/mod.rs @@ -87,7 +87,7 @@ pub mod model { } /// Trait for filters -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] pub trait Filter { /// Type of event that can be filtered type Event; @@ -114,7 +114,7 @@ pub trait Filter { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Filter for FilterBox { type Event = Event; @@ -145,29 +145,6 @@ pub mod stream { declare_versioned_with_scale!(VersionedEventMessage 1..2, Debug, Clone, FromVariant, IntoSchema); - impl VersionedEventMessage { - #[allow(missing_docs)] - pub const fn as_v1(&self) -> &EventMessage { - match self { - Self::V1(v1) => v1, - } - } - - #[allow(missing_docs)] - pub fn as_mut_v1(&mut self) -> &mut EventMessage { - match self { - Self::V1(v1) => v1, - } - } - - #[allow(missing_docs)] - pub fn into_v1(self) -> EventMessage { - match self { - Self::V1(v1) => v1, - } - } - } - #[model] pub mod model { use super::*; @@ -194,29 +171,6 @@ pub mod stream { } declare_versioned_with_scale!(VersionedEventSubscriptionRequest 1..2, Debug, Clone, FromVariant, IntoSchema); - - impl VersionedEventSubscriptionRequest { - #[allow(missing_docs)] - pub const fn as_v1(&self) -> &EventSubscriptionRequest { - match self { - Self::V1(v1) => v1, - } - } - - #[allow(missing_docs)] - pub fn as_mut_v1(&mut self) -> &mut EventSubscriptionRequest { - match self { - Self::V1(v1) => v1, - } - } - - #[allow(missing_docs)] - pub fn into_v1(self) -> EventSubscriptionRequest { - match self { - Self::V1(v1) => v1, - } - } - } } /// Exports common structs and enums from this module. @@ -226,7 +180,7 @@ pub mod prelude { EventMessage, EventSubscriptionRequest, VersionedEventMessage, VersionedEventSubscriptionRequest, }; - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] pub use super::Filter; pub use super::{ data::prelude::*, execute_trigger::prelude::*, pipeline::prelude::*, time::prelude::*, diff --git a/data_model/src/events/pipeline.rs b/data_model/src/events/pipeline.rs index 67fe381af4c..a821db663ac 100644 --- a/data_model/src/events/pipeline.rs +++ b/data_model/src/events/pipeline.rs @@ -196,13 +196,13 @@ impl PipelineEventFilter { } #[inline] - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] fn field_matches(filter: Option<&T>, event: &T) -> bool { filter.map_or(true, |field| field == event) } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl super::Filter for PipelineEventFilter { type Event = PipelineEvent; @@ -219,7 +219,7 @@ impl super::Filter for PipelineEventFilter { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl PipelineStatus { fn kind(&self) -> PipelineStatusKind { PipelineStatusKind::from(self) @@ -235,7 +235,7 @@ pub mod prelude { } #[cfg(test)] -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] mod tests { #![allow(clippy::restriction)] diff --git a/data_model/src/events/time.rs b/data_model/src/events/time.rs index 654a100921c..7a52134a53e 100644 --- a/data_model/src/events/time.rs +++ b/data_model/src/events/time.rs @@ -135,7 +135,7 @@ pub mod model { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Filter for TimeEventFilter { type Event = TimeEvent; @@ -172,7 +172,7 @@ impl Filter for TimeEventFilter { } /// Count something with the `schedule` within the `interval` -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] fn count_matches_in_interval(schedule: &Schedule, interval: &TimeInterval) -> u32 { schedule.period.map_or_else( || u32::from(Range::from(*interval).contains(&schedule.start)), @@ -199,7 +199,7 @@ fn count_matches_in_interval(schedule: &Schedule, interval: &TimeInterval) -> u3 /// /// # Panics /// Panics if resulting number in seconds can't be represented as `u64` -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] fn multiply_duration_by_u128(duration: Duration, n: u128) -> Duration { if let Ok(n) = u32::try_from(n) { return duration * n; @@ -251,7 +251,7 @@ pub mod prelude { } #[cfg(test)] -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] mod tests { use super::*; diff --git a/data_model/src/expression.rs b/data_model/src/expression.rs index 9bd41133c72..fe475ac0d6b 100644 --- a/data_model/src/expression.rs +++ b/data_model/src/expression.rs @@ -1,7 +1,7 @@ //! Expressions to use inside of ISIs. #![allow( - // Because of `codec(skip)` + // Caused by #[codec(skip)] clippy::default_trait_access, // Because of length on instructions and expressions (can't be 0) clippy::len_without_is_empty, diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index c87ccad4b16..98577684de9 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -32,14 +32,14 @@ use core::{ #[cfg(feature = "std")] use std::borrow::Cow; -use block::VersionedCommittedBlock; +use block::{BlockPayload, VersionedSignedBlock}; #[cfg(not(target_arch = "aarch64"))] use derive_more::Into; use derive_more::{AsRef, DebugCustom, Deref, Display, From, FromStr}; use events::FilterBox; use getset::Getters; pub use iroha_crypto::SignatureOf; -use iroha_crypto::{Hash, PublicKey}; +use iroha_crypto::{HashOf, PublicKey}; use iroha_data_model_derive::{ model, IdEqOrdHash, PartiallyTaggedDeserialize, PartiallyTaggedSerialize, }; @@ -49,14 +49,14 @@ use iroha_primitives::{ small::{Array as SmallArray, SmallVec}, }; use iroha_schema::IntoSchema; +pub use model::*; use parity_scale_codec::{Decode, Encode}; -use prelude::{Executable, TransactionQueryResult}; +use prelude::{Executable, TransactionPayload, TransactionQueryResult}; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use strum::EnumDiscriminants; -pub use self::model::*; -use crate::{account::SignatureCheckCondition, name::Name, transaction::TransactionValue}; +use crate::{account::SignatureCheckCondition, name::Name}; pub mod account; pub mod asset; @@ -303,7 +303,7 @@ pub mod parameter { reason: "Failed to parse the `val` part of the `Parameter` as `LengthLimits`. Invalid upper `u32` bound.", })?; - Value::LengthLimits(LengthLimits::new(lower, upper)) + Value::Limits(LimitsValue::Length(LengthLimits::new(lower, upper))) } // Shorthand for `TransactionLimits` "TL" => { @@ -319,10 +319,10 @@ pub mod parameter { reason: "Failed to parse the `val` part of the `Parameter` as `TransactionLimits`. `max_wasm_size_bytes` field should be a valid `u64`.", })?; - Value::TransactionLimits(transaction::TransactionLimits::new( + Value::Limits(LimitsValue::Transaction(transaction::TransactionLimits::new( max_instr, max_wasm_size, - )) + ))) } // Shorthand for `MetadataLimits` "ML" => { @@ -338,7 +338,7 @@ pub mod parameter { reason: "Failed to parse the `val` part of the `Parameter` as `MetadataLimits`. Invalid `u32` in `max_entry_byte_size` field.", })?; - Value::MetadataLimits(metadata::Limits::new(lower, upper)) + Value::Limits(LimitsValue::Metadata(metadata::Limits::new(lower, upper))) } _ => return Err(ParseError { reason: @@ -407,19 +407,19 @@ pub mod parameter { ParameterId { name: Name::from_str("TransactionLimits").expect("Failed to parse `Name`"), }, - Value::TransactionLimits(TransactionLimits::new(42, 24)), + Value::Limits(LimitsValue::Transaction(TransactionLimits::new(42, 24))), ), Parameter::new( ParameterId { name: Name::from_str("MetadataLimits").expect("Failed to parse `Name`"), }, - Value::MetadataLimits(MetadataLimits::new(42, 24)), + Value::Limits(LimitsValue::Metadata(MetadataLimits::new(42, 24))), ), Parameter::new( ParameterId { name: Name::from_str("LengthLimits").expect("Failed to parse `Name`"), }, - Value::LengthLimits(LengthLimits::new(24, 42)), + Value::Limits(LimitsValue::Length(LengthLimits::new(24, 42))), ), Parameter::new( ParameterId { @@ -677,20 +677,17 @@ pub mod model { Vec, ), LimitedMetadata(metadata::Metadata), - MetadataLimits(metadata::Limits), - TransactionLimits(transaction::TransactionLimits), - LengthLimits(LengthLimits), + Limits(LimitsValue), #[serde_partially_tagged(untagged)] Id(IdBox), #[serde_partially_tagged(untagged)] Identifiable(IdentifiableBox), PublicKey(PublicKey), SignatureCheckCondition(SignatureCheckCondition), - TransactionValue(TransactionValue), TransactionQueryResult(TransactionQueryResult), PermissionToken(permission::PermissionToken), - Hash(Hash), - Block(VersionedCommittedBlockWrapper), + Hash(HashValue), + Block(VersionedSignedBlockWrapper), BlockHeader(block::BlockHeader), Ipv4Addr(iroha_primitives::addr::Ipv4Addr), Ipv6Addr(iroha_primitives::addr::Ipv6Addr), @@ -734,7 +731,61 @@ pub mod model { Fixed(fixed::Fixed), } - /// Cross-platform wrapper for [`VersionedCommittedBlock`]. + /// Enum for all supported hash types + #[derive( + DebugCustom, + Display, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + FromVariant, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type] + pub enum HashValue { + /// Block hash + Block(HashOf), + /// Transaction hash + Transaction(HashOf), + } + + /// Enum for all supported limit types + #[derive( + DebugCustom, + Display, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + FromVariant, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type] + pub enum LimitsValue { + /// Metadata limits + Metadata(metadata::Limits), + /// Transaction limits + Transaction(transaction::TransactionLimits), + /// Length limits + Length(LengthLimits), + } + + /// Cross-platform wrapper for [`VersionedSignedBlock`]. #[cfg(not(target_arch = "aarch64"))] #[derive( Debug, @@ -754,12 +805,12 @@ pub mod model { Serialize, IntoSchema, )] - // SAFETY: VersionedCommittedBlockWrapper has no trap representations in VersionedCommittedBlock - #[schema(transparent = "VersionedCommittedBlock")] + // SAFETY: VersionedSignedBlockWrapper has no trap representations in VersionedSignedBlock + #[schema(transparent = "VersionedSignedBlock")] #[ffi_type(unsafe {robust})] #[serde(transparent)] #[repr(transparent)] - pub struct VersionedCommittedBlockWrapper(VersionedCommittedBlock); + pub struct VersionedSignedBlockWrapper(pub VersionedSignedBlock); /// Cross-platform wrapper for `BlockValue`. #[cfg(target_arch = "aarch64")] @@ -780,15 +831,15 @@ pub mod model { Serialize, IntoSchema, )] - #[schema(transparent = "Box")] + #[schema(transparent = "Box")] #[as_ref(forward)] #[deref(forward)] #[from(forward)] - // SAFETY: VersionedCommittedBlockWrapper has no trap representations in Box + // SAFETY: VersionedSignedBlockWrapper has no trap representations in Box #[ffi_type(unsafe {robust})] #[serde(transparent)] #[repr(transparent)] - pub struct VersionedCommittedBlockWrapper(pub(super) Box); + pub struct VersionedSignedBlockWrapper(pub Box); /// Limits of length of the identifiers (e.g. in [`domain::Domain`], [`account::Account`], [`asset::AssetDefinition`]) in number of chars #[derive( @@ -907,8 +958,8 @@ impl TryFrom for NumericValue { } #[cfg(target_arch = "aarch64")] -impl From for VersionedCommittedBlock { - fn from(block_value: VersionedCommittedBlockWrapper) -> Self { +impl From for VersionedSignedBlock { + fn from(block_value: VersionedSignedBlockWrapper) -> Self { *block_value.0 } } @@ -934,7 +985,7 @@ impl fmt::Display for Value { Value::Identifiable(v) => fmt::Display::fmt(&v, f), Value::PublicKey(v) => fmt::Display::fmt(&v, f), Value::SignatureCheckCondition(v) => fmt::Display::fmt(&v, f), - Value::TransactionValue(_) => write!(f, "TransactionValue"), + // TODO: display hash of the transaction Value::TransactionQueryResult(_) => write!(f, "TransactionQueryResult"), Value::PermissionToken(v) => fmt::Display::fmt(&v, f), Value::Hash(v) => fmt::Display::fmt(&v, f), @@ -942,10 +993,8 @@ impl fmt::Display for Value { Value::BlockHeader(v) => fmt::Display::fmt(&v, f), Value::Ipv4Addr(v) => fmt::Display::fmt(&v, f), Value::Ipv6Addr(v) => fmt::Display::fmt(&v, f), + Value::Limits(v) => fmt::Display::fmt(&v, f), Value::Numeric(v) => fmt::Display::fmt(&v, f), - Value::MetadataLimits(v) => fmt::Display::fmt(&v, f), - Value::TransactionLimits(v) => fmt::Display::fmt(&v, f), - Value::LengthLimits(v) => fmt::Display::fmt(&v, f), Value::Validator(v) => write!(f, "Validator({} bytes)", v.wasm.as_ref().len()), } } @@ -964,7 +1013,6 @@ impl Value { | Identifiable(_) | String(_) | Name(_) - | TransactionValue(_) | TransactionQueryResult(_) | PermissionToken(_) | Hash(_) @@ -972,9 +1020,7 @@ impl Value { | Ipv4Addr(_) | Ipv6Addr(_) | BlockHeader(_) - | MetadataLimits(_) - | TransactionLimits(_) - | LengthLimits(_) + | Limits(_) | Numeric(_) | Validator(_) => 1_usize, Vec(v) => v.iter().map(Self::len).sum::() + 1_usize, @@ -984,8 +1030,8 @@ impl Value { } } -impl From for Value { - fn from(block_value: VersionedCommittedBlock) -> Self { +impl From for Value { + fn from(block_value: VersionedSignedBlock) -> Self { Value::Block(block_value.into()) } } @@ -1003,11 +1049,9 @@ where } } -// TODO: This macro looks very similar to `from_and_try_from_value_identifiable` -// and `from_and_try_from_value_identifiablebox` macros. It should be possible to -// generalize them under one macro +// TODO: The following macros looks very similar. Try to generalize them under one macro macro_rules! from_and_try_from_value_idbox { - ( $($variant:ident( $ty:ty ),)* $(,)? ) => { + ( $($variant:ident( $ty:ty )),+ $(,)? ) => { $( impl TryFrom for $ty { type Error = ErrorTryFromEnum; @@ -1026,26 +1070,12 @@ macro_rules! from_and_try_from_value_idbox { Value::Id(IdBox::$variant(id)) } } - )* + )+ }; } -from_and_try_from_value_idbox!( - PeerId(peer::PeerId), - DomainId(domain::DomainId), - AccountId(account::AccountId), - AssetId(asset::AssetId), - AssetDefinitionId(asset::AssetDefinitionId), - TriggerId(trigger::TriggerId), - RoleId(role::RoleId), - ParameterId(parameter::ParameterId), -); - -// TODO: Should we wrap String with new type in order to convert like here? -//from_and_try_from_value_idbox!((DomainName(Name), ErrorValueTryFromDomainName),); - macro_rules! from_and_try_from_value_identifiablebox { - ( $( $variant:ident( Box< $ty:ty > ),)* $(,)? ) => { + ( $( $variant:ident( Box< $ty:ty > )),+ $(,)? ) => { $( impl TryFrom for $ty { type Error = ErrorTryFromEnum; @@ -1064,11 +1094,11 @@ macro_rules! from_and_try_from_value_identifiablebox { Value::Identifiable(IdentifiableBox::$variant(Box::new(id))) } } - )* + )+ }; } macro_rules! from_and_try_from_value_identifiable { - ( $( $variant:ident( $ty:ty ), )* $(,)? ) => { + ( $( $variant:ident( $ty:ty ) ),+ $(,)? ) => { $( impl TryFrom for $ty { type Error = ErrorTryFromEnum; @@ -1087,11 +1117,124 @@ macro_rules! from_and_try_from_value_identifiable { Value::Identifiable(IdentifiableBox::$variant(id)) } } - )* + )+ }; } -from_and_try_from_value_identifiablebox!( +macro_rules! from_and_try_from_and_try_as_value_hash { + ( $( $variant:ident($ty:ty)),+ $(,)? ) => { $( + impl TryFrom for $ty { + type Error = ErrorTryFromEnum; + + #[inline] + fn try_from(value: Value) -> Result { + if let Value::Hash(HashValue::$variant(value)) = value { + Ok(value) + } else { + Err(Self::Error::default()) + } + } + } + + impl From<$ty> for Value { + #[inline] + fn from(value: $ty) -> Self { + Value::Hash(HashValue::$variant(value)) + } + } + + impl TryAsMut<$ty> for HashValue { + type Error = crate::EnumTryAsError<$ty, HashValue>; + + #[inline] + fn try_as_mut(&mut self) -> Result<&mut $ty, Self::Error> { + if let HashValue::$variant (value) = self { + Ok(value) + } else { + Err(crate::EnumTryAsError::got(*self)) + } + } + } + + impl TryAsRef<$ty> for HashValue { + type Error = crate::EnumTryAsError<$ty, HashValue>; + + #[inline] + fn try_as_ref(&self) -> Result<& $ty, Self::Error> { + if let HashValue::$variant (value) = self { + Ok(value) + } else { + Err(crate::EnumTryAsError::got(*self)) + } + } + })+ + }; +} + +macro_rules! from_and_try_from_and_try_as_value_numeric { + ( $( $variant:ident($ty:ty)),+ $(,)? ) => { $( + impl TryFrom for $ty { + type Error = ErrorTryFromEnum; + + #[inline] + fn try_from(value: Value) -> Result { + if let Value::Numeric(NumericValue::$variant(value)) = value { + Ok(value) + } else { + Err(Self::Error::default()) + } + } + } + + impl From<$ty> for Value { + #[inline] + fn from(value: $ty) -> Self { + Value::Numeric(NumericValue::$variant(value)) + } + } + + impl TryAsMut<$ty> for NumericValue { + type Error = crate::EnumTryAsError<$ty, NumericValue>; + + #[inline] + fn try_as_mut(&mut self) -> Result<&mut $ty, Self::Error> { + if let NumericValue:: $variant (value) = self { + Ok(value) + } else { + Err(crate::EnumTryAsError::got(*self)) + } + } + } + + impl TryAsRef<$ty> for NumericValue { + type Error = crate::EnumTryAsError<$ty, NumericValue>; + + #[inline] + fn try_as_ref(&self) -> Result<& $ty, Self::Error> { + if let NumericValue:: $variant (value) = self { + Ok(value) + } else { + Err(crate::EnumTryAsError::got(*self)) + } + } + })+ + }; +} + +from_and_try_from_value_idbox!( + PeerId(peer::PeerId), + DomainId(domain::DomainId), + AccountId(account::AccountId), + AssetId(asset::AssetId), + AssetDefinitionId(asset::AssetDefinitionId), + TriggerId(trigger::TriggerId), + RoleId(role::RoleId), + ParameterId(parameter::ParameterId), + // TODO: Should we wrap String with new type in order to convert like here? + //from_and_try_from_value_idbox!((DomainName(Name), ErrorValueTryFromDomainName),); +); + +from_and_try_from_value_identifiablebox! { NewDomain(Box), NewAccount(Box), NewAssetDefinition(Box), @@ -1103,10 +1246,10 @@ from_and_try_from_value_identifiablebox!( Asset(Box), Role(Box), PermissionTokenDefinition(Box), - Parameter(Box), -); + Parameter(Box) +} -from_and_try_from_value_identifiable!( +from_and_try_from_value_identifiable! { NewDomain(Box), NewAccount(Box), NewAssetDefinition(Box), @@ -1118,8 +1261,20 @@ from_and_try_from_value_identifiable!( Trigger(TriggerBox), Role(Box), PermissionTokenDefinition(Box), - Parameter(Box), -); + Parameter(Box) +} + +from_and_try_from_and_try_as_value_numeric! { + U32(u32), + U64(u64), + U128(u128), + Fixed(fixed::Fixed) +} + +from_and_try_from_and_try_as_value_hash! { + Block(HashOf), + Transaction(HashOf) +} impl TryFrom for RegistrableBox { type Error = ErrorTryFromEnum; @@ -1217,7 +1372,7 @@ where } } -impl TryFrom for VersionedCommittedBlock { +impl TryFrom for VersionedSignedBlock { type Error = ErrorTryFromEnum; fn try_from(value: Value) -> Result { @@ -1247,65 +1402,6 @@ where } } -macro_rules! from_and_try_from_and_try_as_value_numeric { - ( $( $variant:ident($ty:ty),)+ $(,)? ) => { - $( - impl TryFrom for $ty { - type Error = ErrorTryFromEnum; - - #[inline] - fn try_from(value: Value) -> Result { - if let Value::Numeric(NumericValue::$variant(value)) = value { - Ok(value) - } else { - Err(Self::Error::default()) - } - } - } - - impl From<$ty> for Value { - #[inline] - fn from(value: $ty) -> Self { - Value::Numeric(NumericValue::$variant(value)) - } - } - - impl TryAsMut<$ty> for NumericValue { - type Error = crate::EnumTryAsError<$ty, NumericValue>; - - #[inline] - fn try_as_mut(&mut self) -> Result<&mut $ty, Self::Error> { - if let NumericValue:: $variant (value) = self { - Ok(value) - } else { - Err(crate::EnumTryAsError::got(*self)) - } - } - } - - impl TryAsRef<$ty> for NumericValue { - type Error = crate::EnumTryAsError<$ty, NumericValue>; - - #[inline] - fn try_as_ref(&self) -> Result<& $ty, Self::Error> { - if let NumericValue:: $variant (value) = self { - Ok(value) - } else { - Err(crate::EnumTryAsError::got(*self)) - } - } - } - )* - }; -} - -from_and_try_from_and_try_as_value_numeric! { - U32(u32), - U64(u64), - U128(u128), - Fixed(fixed::Fixed), -} - impl TryFrom for Value { type Error = >::Error; fn try_from(value: f64) -> Result { @@ -1771,13 +1867,13 @@ pub mod prelude { #[cfg(feature = "std")] pub use super::current_time; pub use super::{ - account::prelude::*, asset::prelude::*, domain::prelude::*, evaluate::prelude::*, - events::prelude::*, expression::prelude::*, isi::prelude::*, metadata::prelude::*, - name::prelude::*, parameter::prelude::*, peer::prelude::*, permission::prelude::*, - query::prelude::*, role::prelude::*, transaction::prelude::*, trigger::prelude::*, - validator::prelude::*, EnumTryAsError, HasMetadata, IdBox, Identifiable, IdentifiableBox, - LengthLimits, NumericValue, PredicateTrait, RegistrableBox, ToValue, TriggerBox, TryAsMut, - TryAsRef, TryToValue, UpgradableBox, ValidationError, Value, + account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, + evaluate::prelude::*, events::prelude::*, expression::prelude::*, isi::prelude::*, + metadata::prelude::*, name::prelude::*, parameter::prelude::*, peer::prelude::*, + permission::prelude::*, query::prelude::*, role::prelude::*, transaction::prelude::*, + trigger::prelude::*, validator::prelude::*, EnumTryAsError, HasMetadata, IdBox, + Identifiable, IdentifiableBox, LengthLimits, NumericValue, PredicateTrait, RegistrableBox, + ToValue, TriggerBox, TryAsMut, TryAsRef, TryToValue, UpgradableBox, ValidationError, Value, }; #[cfg(feature = "http")] pub use super::{pagination::prelude::*, sorting::prelude::*}; diff --git a/data_model/src/metadata.rs b/data_model/src/metadata.rs index da0e3311c9f..4662542650e 100644 --- a/data_model/src/metadata.rs +++ b/data_model/src/metadata.rs @@ -228,7 +228,7 @@ impl Metadata { } } -#[cfg(feature = "transparent_api")] +#[cfg(feature = "transparent-api")] impl Metadata { /// Removes a key from the map, returning the owned /// `Some(value)` at the key if the key was previously in the @@ -296,7 +296,7 @@ mod tests { } #[test] - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] fn nested_fns_ignore_empty_path() { let mut metadata = Metadata::new(); let empty_path = vec![]; @@ -308,7 +308,7 @@ mod tests { } #[test] - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] fn nesting_inserts_removes() -> Result<(), TestError> { let mut metadata = Metadata::new(); let limits = Limits::new(1024, 1024); @@ -342,7 +342,7 @@ mod tests { } #[test] - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] fn non_existent_path_segment_fails() -> Result<(), TestError> { let mut metadata = Metadata::new(); let limits = Limits::new(10, 15); diff --git a/data_model/src/predicate.rs b/data_model/src/predicate.rs index 5ff434f5840..52096fa73b1 100644 --- a/data_model/src/predicate.rs +++ b/data_model/src/predicate.rs @@ -5,7 +5,7 @@ use alloc::vec; use core::{fmt::Display, ops::Not}; use super::*; -use crate::{IdBox, Name, Value}; +use crate::{block::Block, IdBox, Name, Value}; mod nontrivial { use super::*; @@ -1000,7 +1000,7 @@ pub mod value { Display(string::StringPredicate), /// Apply predicate to the numerical value. Numerical(numerical::SemiRange), - /// Timestamp (currently for [`VersionedCommittedBlock`] only). + /// Timestamp (currently for [`VersionedSignedBlock`] only). TimeStamp(numerical::SemiInterval), /// IpAddress enumerable by `u32` Ipv4Addr(ip_addr::Ipv4Predicate), @@ -1066,7 +1066,7 @@ pub mod value { ValuePredicate::Numerical(pred) => pred.applies(input), ValuePredicate::Display(pred) => pred.applies(&input.to_string()), ValuePredicate::TimeStamp(pred) => match input { - Value::Block(block) => pred.applies(block.header().timestamp), + Value::Block(block) => pred.applies(block.header().timestamp_ms), _ => false, }, ValuePredicate::Ipv4Addr(pred) => match input { diff --git a/data_model/src/query.rs b/data_model/src/query.rs index d10ce12c782..72e2dab453c 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query.rs @@ -1085,13 +1085,13 @@ pub mod transaction { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use iroha_crypto::Hash; + use iroha_crypto::HashOf; use super::Query; use crate::{ expression::EvaluatesTo, prelude::Account, - transaction::{TransactionQueryResult, TransactionValue}, + transaction::{TransactionPayload, TransactionQueryResult}, Identifiable, }; @@ -1119,11 +1119,11 @@ pub mod transaction { #[derive(Display)] #[display(fmt = "Find transaction with `{hash}` hash")] #[repr(transparent)] - // SAFETY: `FindTransactionByHash` has no trap representation in `EvaluatesTo` + // SAFETY: `FindTransactionByHash` has no trap representation in `EvaluatesTo>` #[ffi_type(unsafe {robust})] pub struct FindTransactionByHash { /// Transaction hash. - pub hash: EvaluatesTo, + pub hash: EvaluatesTo>, } } @@ -1132,11 +1132,11 @@ pub mod transaction { } impl Query for FindTransactionsByAccountId { - type Output = Vec; + type Output = Vec; } impl Query for FindTransactionByHash { - type Output = TransactionValue; + type Output = TransactionQueryResult; } impl FindTransactionsByAccountId { @@ -1150,7 +1150,7 @@ pub mod transaction { impl FindTransactionByHash { ///Construct [`FindTransactionByHash`]. - pub fn new(hash: impl Into>) -> Self { + pub fn new(hash: impl Into>>) -> Self { Self { hash: hash.into() } } } @@ -1170,11 +1170,11 @@ pub mod block { use alloc::{boxed::Box, format, string::String, vec::Vec}; use derive_more::Display; - use iroha_crypto::Hash; + use iroha_crypto::HashOf; use super::Query; use crate::{ - block::{BlockHeader, VersionedCommittedBlock}, + block::{BlockHeader, BlockPayload, VersionedSignedBlock}, prelude::EvaluatesTo, }; @@ -1197,16 +1197,16 @@ pub mod block { #[derive(Display)] #[display(fmt = "Find block header with `{hash}` hash")] #[repr(transparent)] - // SAFETY: `FindBlockHeaderByHash` has no trap representation in `EvaluatesTo` + // SAFETY: `FindBlockHeaderByHash` has no trap representation in `EvaluatesTo>` #[ffi_type(unsafe {robust})] pub struct FindBlockHeaderByHash { /// Block hash. - pub hash: EvaluatesTo, + pub hash: EvaluatesTo>, } } impl Query for FindAllBlocks { - type Output = Vec; + type Output = Vec; } impl Query for FindAllBlockHeaders { @@ -1219,7 +1219,7 @@ pub mod block { impl FindBlockHeaderByHash { /// Construct [`FindBlockHeaderByHash`]. - pub fn new(hash: impl Into>) -> Self { + pub fn new(hash: impl Into>>) -> Self { Self { hash: hash.into() } } } @@ -1287,9 +1287,6 @@ pub mod http { #[version_with_scale(n = 1, versioned = "VersionedQueryResult")] #[serde(transparent)] #[repr(transparent)] - // TODO: This should be a separate type, not just wrap Value because it infects Value - // with variants that can only ever be returned, i.e. can't be used in instructions - // enum QueryResult { ... } pub struct QueryResult(pub Value); } @@ -1377,7 +1374,12 @@ pub mod error { pub use self::model::*; use super::*; - use crate::{block::VersionedCommittedBlock, permission, prelude::*, validator}; + use crate::{ + block::{BlockPayload, VersionedSignedBlock}, + permission, + prelude::*, + validator, + }; #[model] pub mod model { @@ -1441,10 +1443,10 @@ pub mod error { MetadataKey(Name), /// Block with supplied parent hash not found. More description in a string. #[display(fmt = "Block with hash {_0} not found")] - Block(HashOf), + Block(HashOf), /// Transaction with given hash not found. #[display(fmt = "Transaction not found")] - Transaction(HashOf), + Transaction(HashOf), /// Peer not found. #[display(fmt = "Peer {_0} not found")] Peer(PeerId), @@ -1473,7 +1475,7 @@ pub mod prelude { pub use super::http::*; pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, - peer::prelude::*, permission::prelude::*, role::prelude::*, transaction::*, - trigger::prelude::*, Query, QueryBox, + error::QueryExecutionFailure, peer::prelude::*, permission::prelude::*, role::prelude::*, + transaction::*, trigger::prelude::*, Query, QueryBox, }; } diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 8d429f15514..cd6531bf4a5 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -1,56 +1,46 @@ //! [`Transaction`] structures and related implementations. #![allow(clippy::std_instead_of_core)] -// TODO: Remove when a proper `Display` will be implemented for `Transaction` -#![allow(clippy::use_debug)] #[cfg(not(feature = "std"))] -use alloc::{boxed::Box, collections::btree_set, format, string::String, vec::Vec}; +use alloc::{boxed::Box, format, string::String, vec::Vec}; use core::{ cmp::Ordering, fmt::{Display, Formatter, Result as FmtResult}, iter::IntoIterator, }; #[cfg(feature = "std")] -use std::{collections::btree_set, time::Duration}; +use std::time::Duration; use derive_more::{Constructor, DebugCustom, Display}; use getset::Getters; -use iroha_crypto::{Hash, SignatureOf, SignatureVerificationFail, SignaturesOf}; +use iroha_crypto::{HashOf, SignaturesOf}; use iroha_data_model_derive::model; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; -#[cfg(feature = "transparent_api")] -use iroha_version::declare_versioned_with_scale; use iroha_version::{declare_versioned, version, version_with_scale}; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::{account::Account, isi::InstructionBox, metadata::UnlimitedMetadata, Identifiable}; - -/// Default maximum number of instructions and expressions per transaction -pub const DEFAULT_MAX_INSTRUCTION_NUMBER: u64 = 2_u64.pow(12); - -/// Default maximum number of instructions and expressions per transaction -pub const DEFAULT_MAX_WASM_SIZE_BYTES: u64 = 2_u64.pow(22); // 4 MiB +use crate::{ + account::Account, block::BlockPayload, isi::InstructionBox, metadata::UnlimitedMetadata, + Identifiable, +}; /// Trait for basic transaction operations pub trait Transaction { - /// Result of hashing - type HashOf: Transaction; - /// Returns payload of a transaction fn payload(&self) -> &TransactionPayload; /// Calculate transaction [`Hash`](`iroha_crypto::Hash`). #[inline] #[cfg(feature = "std")] - fn hash(&self) -> iroha_crypto::HashOf - where - Self: Sized, - { - iroha_crypto::HashOf::new(self.payload()).transmute() + fn hash(&self) -> iroha_crypto::HashOf { + iroha_crypto::HashOf::new(self.payload()) } + /// Return signatures + fn signatures(&self) -> &SignaturesOf; + /// Checks if number of instructions in payload or wasm size exceeds maximum /// /// # Errors @@ -103,7 +93,7 @@ pub trait Transaction { /// specified TTLs was reached. #[cfg(feature = "std")] fn is_expired(&self, transaction_time_to_live: Duration) -> bool { - let tx_timestamp = Duration::from_millis(self.payload().creation_time); + let tx_timestamp = Duration::from_millis(self.payload().creation_time_ms); crate::current_time().saturating_sub(tx_timestamp) > core::cmp::min( transaction_time_to_live, @@ -115,7 +105,7 @@ pub trait Transaction { /// to have a future timestamp. #[cfg(feature = "std")] fn is_in_future(&self, threshold: Duration) -> bool { - let tx_timestamp = Duration::from_millis(self.payload().creation_time); + let tx_timestamp = Duration::from_millis(self.payload().creation_time_ms); tx_timestamp.saturating_sub(crate::current_time()) > threshold } } @@ -131,12 +121,12 @@ pub trait Sign { fn sign( self, key_pair: iroha_crypto::KeyPair, - ) -> Result; + ) -> Result; } #[model] pub mod model { - use super::*; + use super::{error::TransactionRejectionReason, *}; /// Either ISI or Wasm binary #[derive( @@ -205,17 +195,17 @@ pub mod model { #[getset(get = "pub")] #[ffi_type] pub struct TransactionPayload { + /// Creation timestamp (unix time in milliseconds). + pub creation_time_ms: u64, + /// Random value to make different hashes for transactions which occur repeatedly and simultaneously. + pub nonce: Option, /// Account ID of transaction creator. pub account_id: ::Id, - /// Instructions or WebAssembly smartcontract + /// ISI or a `WebAssembly` smartcontract. pub instructions: Executable, - /// Time of creation (unix time, in milliseconds). - pub creation_time: u64, - /// The transaction will be dropped after this time if it is still in a `Queue`. + /// If transaction is not committed by this time it will be dropped. pub time_to_live_ms: u64, - /// Random value to make different hashes for transactions which occur repeatedly and simultaneously - pub nonce: Option, - /// Metadata. + /// Store for additional information. pub metadata: UnlimitedMetadata, } @@ -256,9 +246,9 @@ pub mod model { pub payload: TransactionPayload, } - /// Structure that represents the second state of the transaction after receiving at least one signature. + /// Transaction that contains at least one signature /// - /// `Iroha` and its clients use [`Transaction`] to send transactions over the network. + /// `Iroha` and its clients use [`Self`] to send transactions over the network. /// After a transaction is signed and before it can be processed any further, /// the transaction must be accepted by the `Iroha` peer. /// The peer verifies the signatures and checks the limits. @@ -276,13 +266,14 @@ pub mod model { Serialize, IntoSchema, )] - #[display(fmt = "{self:?}")] // TODO ? + #[cfg_attr(not(feature = "std"), display(fmt = "Signed transaction"))] + #[cfg_attr(feature = "std", display(fmt = "{}", "self.hash()"))] #[ffi_type] pub struct SignedTransaction { /// [`Transaction`] payload. pub payload: TransactionPayload, - /// [`SignatureOf`]<[`TransactionPayload`]>. - pub signatures: btree_set::BTreeSet>, + /// [`iroha_crypto::SignatureOf`]<[`TransactionPayload`]>. + pub signatures: SignaturesOf, } /// Transaction Value used in Instructions and Queries @@ -294,7 +285,7 @@ pub mod model { /// Committed transaction Transaction(Box), /// Rejected transaction with reason of rejection - RejectedTransaction(Box), + RejectedTransaction(Box<(VersionedSignedTransaction, TransactionRejectionReason)>), } /// `TransactionQueryResult` is used in `FindAllTransactions` query @@ -317,56 +308,7 @@ pub mod model { /// Transaction pub tx_value: TransactionValue, /// The hash of the block to which `tx` belongs to - pub block_hash: Hash, - } - - /// `ValidTransaction` represents trustfull Transaction state. - #[version_with_scale(n = 1, versioned = "VersionedValidTransaction")] - #[derive( - Debug, Clone, PartialEq, Eq, Hash, Decode, Encode, Deserialize, Serialize, IntoSchema, - )] - #[ffi_type] - pub struct ValidTransaction { - /// The [`Transaction`]'s payload. - pub payload: TransactionPayload, - /// [`SignatureOf`]<[`TransactionPayload`]>. - pub signatures: SignaturesOf, - } - - /// [`RejectedTransaction`] represents transaction rejected by some validator at some stage of the pipeline. - #[version(n = 1, versioned = "VersionedRejectedTransaction")] - #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[ffi_type] - pub struct RejectedTransaction { - /// The [`Transaction`]'s payload. - pub payload: TransactionPayload, - /// [`SignatureOf`] [`Transaction`]. - pub signatures: SignaturesOf, - /// The reason for rejecting this transaction during the validation pipeline. - #[getset(get = "pub")] - pub rejection_reason: error::TransactionRejectionReason, - } - - /// `AcceptedTransaction` — a transaction accepted by iroha peer. - #[version_with_scale(n = 1, versioned = "VersionedAcceptedTransaction")] - #[derive(Debug, Clone, Decode, Encode, Serialize)] - pub(crate) struct AcceptedTransaction { - /// Payload of this transaction. - pub payload: TransactionPayload, - /// Signatures for this transaction. - pub signatures: SignaturesOf, + pub block_hash: HashOf, } } @@ -419,7 +361,7 @@ impl TransactionBuilder { payload: TransactionPayload { account_id, instructions: instructions.into(), - creation_time, + creation_time_ms: creation_time, time_to_live_ms: proposed_ttl_ms, nonce: None, metadata: UnlimitedMetadata::new(), @@ -449,14 +391,14 @@ impl Sign for TransactionBuilder { fn sign( self, key_pair: iroha_crypto::KeyPair, - ) -> Result { - let signature = SignatureOf::new(key_pair, &self.payload)?; - let signatures = btree_set::BTreeSet::from([signature]); + ) -> Result { + let signatures = SignaturesOf::new(key_pair, &self.payload)?; Ok(SignedTransaction { payload: self.payload, signatures, - }) + } + .into()) } } @@ -465,87 +407,48 @@ declare_versioned!(VersionedSignedTransaction 1..2, Debug, Clone, PartialEq, Eq, #[cfg(all(not(feature = "ffi_import"), not(feature = "ffi_export")))] declare_versioned!(VersionedSignedTransaction 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, IntoSchema); -impl VersionedSignedTransaction { - /// Convert from `&VersionedSignedTransaction` to V1 reference - pub const fn as_v1(&self) -> &SignedTransaction { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedSignedTransaction` to V1 mutable reference - #[inline] - pub fn as_mut_v1(&mut self) -> &mut SignedTransaction { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedSignedTransaction` to V1 - #[inline] - pub fn into_v1(self) -> SignedTransaction { - match self { - Self::V1(v1) => v1, - } - } -} - impl Transaction for VersionedSignedTransaction { - type HashOf = Self; - #[inline] fn payload(&self) -> &TransactionPayload { match self { Self::V1(v1) => &v1.payload, } } -} - -impl From for VersionedSignedTransaction { - fn from(transaction: VersionedValidTransaction) -> Self { - match transaction { - VersionedValidTransaction::V1(transaction) => { - let signatures = transaction.signatures.into(); - - SignedTransaction { - payload: transaction.payload, - signatures, - } - .into() - } - } - } -} -impl SignedTransaction { - /// Return signatures - pub fn signatures(&self) -> impl ExactSizeIterator> { - self.signatures.iter() + #[inline] + fn signatures(&self) -> &SignaturesOf { + let VersionedSignedTransaction::V1(tx) = self; + tx.signatures() } } impl Transaction for SignedTransaction { - type HashOf = Self; - #[inline] fn payload(&self) -> &TransactionPayload { &self.payload } + + #[inline] + fn signatures(&self) -> &SignaturesOf { + &self.signatures + } } #[cfg(feature = "std")] -impl Sign for SignedTransaction { +impl Sign for VersionedSignedTransaction { fn sign( - mut self, + self, key_pair: iroha_crypto::KeyPair, - ) -> Result { - let signature = SignatureOf::new(key_pair, &self.payload)?; - self.signatures.insert(signature); + ) -> Result { + let VersionedSignedTransaction::V1(mut tx) = self; + let signature = iroha_crypto::SignatureOf::new(key_pair, &tx.payload)?; + tx.signatures.insert(signature); Ok(SignedTransaction { - payload: self.payload, - signatures: self.signatures, - }) + payload: tx.payload, + signatures: tx.signatures, + } + .into()) } } @@ -555,7 +458,7 @@ impl TransactionValue { pub fn payload(&self) -> &TransactionPayload { match self { TransactionValue::Transaction(tx) => tx.payload(), - TransactionValue::RejectedTransaction(tx) => tx.payload(), + TransactionValue::RejectedTransaction(tx) => tx.0.payload(), } } } @@ -571,8 +474,8 @@ impl Ord for TransactionValue { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.payload() - .creation_time - .cmp(&other.payload().creation_time) + .creation_time_ms + .cmp(&other.payload().creation_time_ms) } } @@ -595,233 +498,8 @@ impl Ord for TransactionQueryResult { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.payload() - .creation_time - .cmp(&other.payload().creation_time) - } -} - -#[cfg(any(feature = "ffi_import", feature = "ffi_export"))] -declare_versioned!(VersionedValidTransaction 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, iroha_ffi::FfiType, IntoSchema); -#[cfg(all(not(feature = "ffi_import"), not(feature = "ffi_export")))] -declare_versioned!(VersionedValidTransaction 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, IntoSchema); - -impl VersionedValidTransaction { - /// Convert from `&VersionedValidTransaction` to V1 reference - #[inline] - pub const fn as_v1(&self) -> &ValidTransaction { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedValidTransaction` to V1 mutable reference - #[inline] - pub fn as_mut_v1(&mut self) -> &mut ValidTransaction { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedValidTransaction` to V1 - #[inline] - pub fn into_v1(self) -> ValidTransaction { - match self { - Self::V1(v1) => v1, - } - } -} - -impl Transaction for VersionedValidTransaction { - type HashOf = VersionedSignedTransaction; - - #[inline] - fn payload(&self) -> &TransactionPayload { - &self.as_v1().payload - } -} - -impl ValidTransaction { - /// Return signatures - pub fn signatures(&self) -> impl ExactSizeIterator> { - self.signatures.iter() - } -} - -impl Transaction for ValidTransaction { - type HashOf = SignedTransaction; - - #[inline] - fn payload(&self) -> &TransactionPayload { - &self.payload - } -} - -#[cfg(any(feature = "ffi_import", feature = "ffi_export"))] -declare_versioned!(VersionedRejectedTransaction 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, iroha_ffi::FfiType, IntoSchema); -#[cfg(all(not(feature = "ffi_import"), not(feature = "ffi_export")))] -declare_versioned!(VersionedRejectedTransaction 1..2, Debug, Clone, PartialEq, Eq, Hash, FromVariant, IntoSchema); - -impl VersionedRejectedTransaction { - /// Convert from `&VersionedRejectedTransaction` to V1 reference - #[inline] - pub const fn as_v1(&self) -> &RejectedTransaction { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedRejectedTransaction` to V1 mutable reference - #[inline] - pub fn as_mut_v1(&mut self) -> &mut RejectedTransaction { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedRejectedTransaction` to V1 - #[inline] - pub fn into_v1(self) -> RejectedTransaction { - match self { - Self::V1(v1) => v1, - } - } -} - -impl Transaction for VersionedRejectedTransaction { - type HashOf = VersionedSignedTransaction; - - #[inline] - fn payload(&self) -> &TransactionPayload { - match self { - Self::V1(v1) => &v1.payload, - } - } -} - -impl RejectedTransaction { - /// Return signatures - pub fn signatures(&self) -> impl ExactSizeIterator> { - self.signatures.iter() - } -} - -impl Transaction for RejectedTransaction { - type HashOf = SignedTransaction; - - #[inline] - fn payload(&self) -> &TransactionPayload { - &self.payload - } -} - -impl From for VersionedSignedTransaction { - fn from(transaction: VersionedRejectedTransaction) -> Self { - match transaction { - VersionedRejectedTransaction::V1(transaction) => { - let signatures = transaction.signatures.into(); - - SignedTransaction { - payload: transaction.payload, - signatures, - } - .into() - } - } - } -} - -#[cfg(feature = "transparent_api")] -declare_versioned_with_scale!(VersionedAcceptedTransaction 1..2, Debug, Clone, iroha_macro::FromVariant, Serialize); - -#[cfg(feature = "transparent_api")] -impl VersionedAcceptedTransaction { - /// Convert from `&VersionedAcceptedTransaction` to V1 reference - pub const fn as_v1(&self) -> &AcceptedTransaction { - match self { - VersionedAcceptedTransaction::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedAcceptedTransaction` to V1 mutable reference - pub fn as_mut_v1(&mut self) -> &mut AcceptedTransaction { - match self { - VersionedAcceptedTransaction::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedAcceptedTransaction` to V1 - pub fn into_v1(self) -> AcceptedTransaction { - match self { - VersionedAcceptedTransaction::V1(v1) => v1, - } - } -} - -#[cfg(feature = "transparent_api")] -impl Transaction for VersionedAcceptedTransaction { - type HashOf = VersionedSignedTransaction; - - #[inline] - fn payload(&self) -> &TransactionPayload { - &self.as_v1().payload - } -} - -#[cfg(feature = "transparent_api")] -impl Transaction for AcceptedTransaction { - type HashOf = SignedTransaction; - - #[inline] - fn payload(&self) -> &TransactionPayload { - &self.payload - } -} - -#[cfg(feature = "transparent_api")] -impl AcceptedTransaction { - /// Accept transaction. Transition from [`Transaction`] to [`AcceptedTransaction`]. - /// - /// # Errors - /// - /// - if it does not adhere to limits - /// - if signature verification fails - #[cfg(feature = "std")] - pub fn accept( - transaction: SignedTransaction, - limits: &TransactionLimits, - ) -> Result { - if !IS_GENESIS { - transaction.check_limits(limits)? - } - let signatures: SignaturesOf<_> = transaction - .signatures - .try_into() - .expect("Transaction should have at least one signature"); - signatures.verify(&transaction.payload)?; - - Ok(Self { - payload: transaction.payload, - signatures, - }) - } -} - -#[cfg(feature = "transparent_api")] -impl From for VersionedSignedTransaction { - fn from(tx: VersionedAcceptedTransaction) -> Self { - let tx: AcceptedTransaction = tx.into_v1(); - let tx: SignedTransaction = tx.into(); - tx.into() - } -} - -#[cfg(feature = "transparent_api")] -impl From for SignedTransaction { - fn from(transaction: AcceptedTransaction) -> Self { - SignedTransaction { - payload: transaction.payload, - signatures: transaction.signatures.into_iter().collect(), - } + .creation_time_ms + .cmp(&other.payload().creation_time_ms) } } @@ -872,18 +550,6 @@ pub mod error { pub mod model { use super::*; - /// Error type for transaction from [`Transaction`] to [`AcceptedTransaction`] - #[derive(Debug, Display, FromVariant)] - #[cfg_attr(feature = "std", derive(thiserror::Error))] - pub(crate) enum AcceptTransactionFailure { - /// Failure during limits check - TransactionLimit(#[cfg_attr(feature = "std", source)] TransactionLimitError), - /// Failure during signature verification - SignatureVerification( - #[cfg_attr(feature = "std", source)] SignatureVerificationFail, - ), - } - /// Error which indicates max instruction count was reached #[derive( Debug, @@ -1159,43 +825,17 @@ mod http { #[repr(transparent)] // SAFETY: `PendingTransactions` has no trap representation in `Vec` #[ffi_type(unsafe {robust})] - pub struct PendingTransactions(pub(super) Vec); - } - - impl VersionedPendingTransactions { - /// Convert from `&VersionedPendingTransactions` to V1 reference - #[inline] - pub const fn as_v1(&self) -> &PendingTransactions { - match self { - Self::V1(v1) => v1, - } - } - - /// Convert from `&mut VersionedPendingTransactions` to V1 mutable reference - #[inline] - pub fn as_mut_v1(&mut self) -> &mut PendingTransactions { - match self { - Self::V1(v1) => v1, - } - } - - /// Performs the conversion from `VersionedPendingTransactions` to V1 - #[inline] - pub fn into_v1(self) -> PendingTransactions { - match self { - Self::V1(v1) => v1, - } - } + pub struct PendingTransactions(pub(super) Vec); } - impl FromIterator for PendingTransactions { - fn from_iter>(iter: T) -> Self { - Self(iter.into_iter().collect()) + impl FromIterator for VersionedPendingTransactions { + fn from_iter>(iter: T) -> Self { + PendingTransactions(iter.into_iter().collect::>()).into() } } impl IntoIterator for PendingTransactions { - type Item = SignedTransaction; + type Item = VersionedSignedTransaction; type IntoIter = vec::IntoIter; @@ -1221,13 +861,10 @@ pub mod prelude { #[cfg(feature = "std")] pub use super::Sign; pub use super::{ - error::prelude::*, Executable, RejectedTransaction, SignedTransaction, Transaction, - TransactionBuilder, TransactionLimits, TransactionPayload, TransactionQueryResult, - TransactionValue, ValidTransaction, VersionedRejectedTransaction, - VersionedSignedTransaction, VersionedValidTransaction, WasmSmartContract, + error::prelude::*, Executable, Transaction, TransactionBuilder, TransactionLimits, + TransactionPayload, TransactionQueryResult, TransactionValue, VersionedSignedTransaction, + WasmSmartContract, }; - #[cfg(feature = "transparent_api")] - pub use super::{AcceptedTransaction, VersionedAcceptedTransaction}; } #[cfg(test)] @@ -1235,64 +872,6 @@ mod tests { #![allow(clippy::pedantic, clippy::restriction)] use super::*; - #[cfg(feature = "transparent_api")] - use crate::prelude::FailBox; - - #[test] - #[cfg(feature = "transparent_api")] - fn transaction_not_accepted_max_instruction_number() { - let key_pair = iroha_crypto::KeyPair::generate().expect("Failed to generate key pair."); - let inst: InstructionBox = FailBox { - message: "Will fail".to_owned(), - } - .into(); - let tx = TransactionBuilder::new( - "root@global".parse().expect("Valid"), - vec![inst; DEFAULT_MAX_INSTRUCTION_NUMBER as usize + 1], - 1000, - ) - .sign(key_pair) - .expect("Valid"); - let tx_limits = TransactionLimits { - max_instruction_number: 4096, - max_wasm_size_bytes: 0, - }; - let result = AcceptedTransaction::accept::(tx, &tx_limits); - assert!(result.is_err()); - - let err = result.unwrap_err(); - assert_eq!( - err.to_string(), - format!( - "Too many instructions in payload, max number is {}, but got {}", - tx_limits.max_instruction_number, - DEFAULT_MAX_INSTRUCTION_NUMBER + 1 - ) - ); - } - - #[test] - #[cfg(feature = "transparent_api")] - fn genesis_transaction_ignore_limits() { - let key_pair = iroha_crypto::KeyPair::generate().expect("Failed to generate key pair."); - let inst: InstructionBox = FailBox { - message: "Will fail".to_owned(), - } - .into(); - let tx = TransactionBuilder::new( - "root@global".parse().expect("Valid"), - vec![inst; DEFAULT_MAX_INSTRUCTION_NUMBER as usize + 1], - 1000, - ) - .sign(key_pair) - .expect("Valid"); - let tx_limits = TransactionLimits { - max_instruction_number: 4096, - max_wasm_size_bytes: 0, - }; - - assert!(AcceptedTransaction::accept::(tx, &tx_limits).is_ok()); - } #[test] fn wasm_smart_contract_debug_repr_should_contain_just_len() { diff --git a/data_model/src/trigger.rs b/data_model/src/trigger.rs index d0e26ce567e..a704488b9c7 100644 --- a/data_model/src/trigger.rs +++ b/data_model/src/trigger.rs @@ -180,7 +180,7 @@ pub mod action { pub use self::model::*; use super::*; - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] use crate::prelude::Account; #[model] @@ -232,7 +232,7 @@ pub mod action { } } - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] impl crate::HasMetadata for Action { fn metadata(&self) -> &crate::metadata::Metadata { &self.metadata @@ -287,7 +287,7 @@ pub mod action { } /// Trait for common methods for all [`Action`]'s - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] pub trait ActionTrait { /// Type of action executable type Executable; @@ -317,7 +317,7 @@ pub mod action { fn clone_and_box(&self) -> Action; } - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] impl + Clone, E: Clone> ActionTrait for Action { type Executable = E; @@ -398,7 +398,7 @@ pub mod action { pub mod prelude { //! Re-exports of commonly used types. - #[cfg(feature = "transparent_api")] + #[cfg(feature = "transparent-api")] pub use super::action::ActionTrait; pub use super::{action::prelude::*, Trigger, TriggerId}; } diff --git a/data_model/src/validator.rs b/data_model/src/validator.rs index 63d00d4111a..3a737ddb4c1 100644 --- a/data_model/src/validator.rs +++ b/data_model/src/validator.rs @@ -53,7 +53,7 @@ pub mod model { } // TODO: Client doesn't need structures defined inside this macro. When dynamic linking is - // implemented use: #[cfg(any(feature = "transparent_api", feature = "ffi_import"))] + // implemented use: #[cfg(any(feature = "transparent-api", feature = "ffi_import"))] /// Boxed version of [`NeedsPermission`] #[derive( @@ -62,6 +62,7 @@ pub mod model { #[ffi_type] pub enum NeedsValidationBox { /// [`Transaction`] application operation + // TODO: Should it not be `VersionedSignedTransaction`? Transaction(SignedTransaction), /// [`InstructionBox`] execution operation Instruction(InstructionBox), diff --git a/default_validator/src/lib.rs b/default_validator/src/lib.rs index f3543f81f21..86783753b4b 100644 --- a/default_validator/src/lib.rs +++ b/default_validator/src/lib.rs @@ -9,7 +9,7 @@ extern crate panic_halt; pub mod isi; -use iroha_validator::{pass_conditions, prelude::*}; +use iroha_validator::{data_model::transaction::SignedTransaction, pass_conditions, prelude::*}; /// Apply `callback` macro for all token types from this crate. /// diff --git a/docs/source/references/config.md b/docs/source/references/config.md index c11cad1447b..9416bbde2ff 100644 --- a/docs/source/references/config.md +++ b/docs/source/references/config.md @@ -41,10 +41,10 @@ The following is the default configuration used by Iroha. "DEBUG_OUTPUT_NEW_BLOCKS": false }, "SUMERAGI": { - "KEY_PAIR": null, "PEER_ID": null, - "BLOCK_TIME_MS": 2000, + "KEY_PAIR": null, "TRUSTED_PEERS": null, + "BLOCK_TIME_MS": 2000, "COMMIT_TIME_LIMIT_MS": 4000, "TRANSACTION_LIMITS": { "max_instruction_number": 4096, @@ -485,7 +485,7 @@ Has type `Option`[^1]. Can be configured via environment variable `SUMERAGI ### `sumeragi.block_time_ms` -The period of time a peer waits for the `CreatedBlock` message after getting a `TransactionReceipt` +Time a peer waits to produce a new block since the beginning of the voting round Has type `Option`[^1]. Can be configured via environment variable `SUMERAGI_BLOCK_TIME_MS` @@ -495,7 +495,7 @@ Has type `Option`[^1]. Can be configured via environment variable `SUMERAGI ### `sumeragi.commit_time_limit_ms` -The period of time a peer waits for `CommitMessage` from the proxy tail. +Time a peer waits for the block to be committed since the beginning of the voting round Has type `Option`[^1]. Can be configured via environment variable `SUMERAGI_COMMIT_TIME_LIMIT_MS` @@ -558,7 +558,7 @@ Has type `Option`[^1]. Can be configured via environment vari ### `sumeragi.trusted_peers` -Optional list of predefined trusted peers. +List of predefined trusted peers. Has type `Option`[^1]. Can be configured via environment variable `SUMERAGI_TRUSTED_PEERS` diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 8bd35ff7fec..ca1779383c6 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -561,40 +561,60 @@ "BlockHeader": { "Struct": [ { - "name": "timestamp", + "name": "height", + "type": "u64" + }, + { + "name": "timestamp_ms", "type": "u128" }, { - "name": "consensus_estimation", - "type": "u64" + "name": "previous_block_hash", + "type": "Option>" }, { - "name": "height", - "type": "u64" + "name": "transactions_hash", + "type": "Option>>" + }, + { + "name": "rejected_transactions_hash", + "type": "Option>>" + }, + { + "name": "commit_topology", + "type": "Vec" }, { "name": "view_change_index", "type": "u64" }, { - "name": "previous_block_hash", - "type": "Option>" + "name": "consensus_estimation_ms", + "type": "u64" + } + ] + }, + "BlockMessage": "VersionedSignedBlock", + "BlockPayload": { + "Struct": [ + { + "name": "header", + "type": "BlockHeader" }, { - "name": "transactions_hash", - "type": "Option>>" + "name": "transactions", + "type": "Vec" }, { - "name": "rejected_transactions_hash", - "type": "Option>>" + "name": "rejected_transactions", + "type": "Vec>" }, { - "name": "committed_with_topology", - "type": "Vec" + "name": "event_recommendations", + "type": "Vec" } ] }, - "BlockMessage": "VersionedCommittedBlock", "BlockRejectionReason": { "Enum": [ { @@ -616,30 +636,6 @@ } ] }, - "CommittedBlock": { - "Struct": [ - { - "name": "header", - "type": "BlockHeader" - }, - { - "name": "rejected_transactions", - "type": "Vec" - }, - { - "name": "transactions", - "type": "Vec" - }, - { - "name": "event_recommendations", - "type": "Vec" - }, - { - "name": "signatures", - "type": "SignaturesOf" - } - ] - }, "Compact": { "Int": "Compact" }, @@ -1001,7 +997,15 @@ } ] }, - "EvaluatesTo": { + "EvaluatesTo>": { + "Struct": [ + { + "name": "expression", + "type": "Expression" + } + ] + }, + "EvaluatesTo>": { "Struct": [ { "name": "expression", @@ -1673,7 +1677,7 @@ "Struct": [ { "name": "hash", - "type": "EvaluatesTo" + "type": "EvaluatesTo>" } ] }, @@ -1721,11 +1725,11 @@ }, { "name": "Block", - "type": "HashOf" + "type": "HashOf" }, { "name": "Transaction", - "type": "HashOf" + "type": "HashOf" }, { "name": "Peer", @@ -1785,7 +1789,7 @@ "Struct": [ { "name": "hash", - "type": "EvaluatesTo" + "type": "EvaluatesTo>" } ] }, @@ -1885,9 +1889,21 @@ ] }, "Hash": "Array", - "HashOf>": "Hash", - "HashOf": "Hash", - "HashOf": "Hash", + "HashOf": "Hash", + "HashOf>": "Hash", + "HashOf": "Hash", + "HashValue": { + "Enum": [ + { + "name": "Block", + "type": "HashOf" + }, + { + "name": "Transaction", + "type": "HashOf" + } + ] + }, "IdBox": { "Enum": [ { @@ -2161,6 +2177,22 @@ } ] }, + "LimitsValue": { + "Enum": [ + { + "name": "Metadata", + "type": "Limits" + }, + { + "name": "Transaction", + "type": "TransactionLimits" + }, + { + "name": "Length", + "type": "LengthLimits" + } + ] + }, "Metadata": { "Struct": [ { @@ -2413,11 +2445,11 @@ "Option": { "Option": "Hash" }, - "Option>>": { - "Option": "HashOf>" + "Option>": { + "Option": "HashOf" }, - "Option>": { - "Option": "HashOf" + "Option>>": { + "Option": "HashOf>" }, "Option": { "Option": "InstructionBox" @@ -2581,7 +2613,7 @@ } ] }, - "PendingTransactions": "Vec", + "PendingTransactions": "Vec", "PermissionRemoved": { "Struct": [ { @@ -2983,22 +3015,6 @@ } ] }, - "RejectedTransaction": { - "Struct": [ - { - "name": "payload", - "type": "TransactionPayload" - }, - { - "name": "signatures", - "type": "SignaturesOf" - }, - { - "name": "rejection_reason", - "type": "TransactionRejectionReason" - } - ] - }, "RemoveKeyValueBox": { "Struct": [ { @@ -3204,14 +3220,14 @@ ] }, "SignatureCheckCondition": "EvaluatesTo", - "SignatureOf": "Signature", + "SignatureOf": "Signature", "SignatureOf": "Signature", "SignatureOf": "Signature", - "SignaturesOf": { + "SignaturesOf": { "Struct": [ { "name": "signatures", - "type": "SortedVec>" + "type": "SortedVec>" } ] }, @@ -3223,6 +3239,18 @@ } ] }, + "SignedBlock": { + "Struct": [ + { + "name": "payload", + "type": "BlockPayload" + }, + { + "name": "signatures", + "type": "SignaturesOf" + } + ] + }, "SignedQuery": { "Struct": [ { @@ -3243,7 +3271,7 @@ }, { "name": "signatures", - "type": "SortedVec>" + "type": "SignaturesOf" } ] }, @@ -3298,8 +3326,8 @@ "SortedVec": { "Vec": "RoleId" }, - "SortedVec>": { - "Vec": "SignatureOf" + "SortedVec>": { + "Vec": "SignatureOf" }, "SortedVec>": { "Vec": "SignatureOf" @@ -3400,6 +3428,14 @@ }, "TransactionPayload": { "Struct": [ + { + "name": "creation_time_ms", + "type": "u64" + }, + { + "name": "nonce", + "type": "Option" + }, { "name": "account_id", "type": "AccountId" @@ -3408,18 +3444,10 @@ "name": "instructions", "type": "Executable" }, - { - "name": "creation_time", - "type": "u64" - }, { "name": "time_to_live_ms", "type": "u64" }, - { - "name": "nonce", - "type": "Option" - }, { "name": "metadata", "type": "SortedMap" @@ -3434,7 +3462,7 @@ }, { "name": "block_hash", - "type": "Hash" + "type": "HashOf" } ] }, @@ -3477,7 +3505,7 @@ }, { "name": "RejectedTransaction", - "type": "VersionedRejectedTransaction" + "type": "Tuple2" } ] }, @@ -3605,6 +3633,12 @@ } ] }, + "Tuple2": { + "Tuple": [ + "VersionedSignedTransaction", + "TransactionRejectionReason" + ] + }, "UnregisterBox": { "Struct": [ { @@ -3637,18 +3671,6 @@ } ] }, - "ValidTransaction": { - "Struct": [ - { - "name": "payload", - "type": "TransactionPayload" - }, - { - "name": "signatures", - "type": "SignaturesOf" - } - ] - }, "Validator": { "Struct": [ { @@ -3707,16 +3729,8 @@ "type": "Metadata" }, { - "name": "MetadataLimits", - "type": "Limits" - }, - { - "name": "TransactionLimits", - "type": "TransactionLimits" - }, - { - "name": "LengthLimits", - "type": "LengthLimits" + "name": "Limits", + "type": "LimitsValue" }, { "name": "Id", @@ -3734,10 +3748,6 @@ "name": "SignatureCheckCondition", "type": "SignatureCheckCondition" }, - { - "name": "TransactionValue", - "type": "TransactionValue" - }, { "name": "TransactionQueryResult", "type": "TransactionQueryResult" @@ -3748,11 +3758,11 @@ }, { "name": "Hash", - "type": "Hash" + "type": "HashValue" }, { "name": "Block", - "type": "VersionedCommittedBlock" + "type": "VersionedSignedBlock" }, { "name": "BlockHeader", @@ -3794,13 +3804,7 @@ "name": "LimitedMetadata" }, { - "name": "MetadataLimits" - }, - { - "name": "TransactionLimits" - }, - { - "name": "LengthLimits" + "name": "Limits" }, { "name": "Id" @@ -3814,9 +3818,6 @@ { "name": "SignatureCheckCondition" }, - { - "name": "TransactionValue" - }, { "name": "TransactionQueryResult" }, @@ -3908,17 +3909,14 @@ "Vec": { "Vec": "PeerId" }, - "Vec": { - "Vec": "SignedTransaction" + "Vec>": { + "Vec": "Tuple2" }, "Vec": { "Vec": "Value" }, - "Vec": { - "Vec": "VersionedRejectedTransaction" - }, - "Vec": { - "Vec": "VersionedValidTransaction" + "Vec": { + "Vec": "VersionedSignedTransaction" }, "Vec": { "Vec": "u8" @@ -3939,14 +3937,6 @@ } ] }, - "VersionedCommittedBlock": { - "Enum": [ - { - "name": "V1", - "type": "CommittedBlock" - } - ] - }, "VersionedEventMessage": { "Enum": [ { @@ -3979,11 +3969,11 @@ } ] }, - "VersionedRejectedTransaction": { + "VersionedSignedBlock": { "Enum": [ { "name": "V1", - "type": "RejectedTransaction" + "type": "SignedBlock" } ] }, @@ -4003,14 +3993,6 @@ } ] }, - "VersionedValidTransaction": { - "Enum": [ - { - "name": "V1", - "type": "ValidTransaction" - } - ] - }, "WasmExecutionFail": { "Struct": [ { diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index 76e6999fb30..9b2acc7b4eb 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -6,8 +6,9 @@ edition = "2021" [dependencies] iroha_config = { version = "=2.0.0-pre-rc.13", path = "../config" } +iroha_macro = { version = "2.0.0-pre-rc.13", path = "../macro", default-features = false } iroha_crypto = { version = "=2.0.0-pre-rc.13", path = "../crypto" } -iroha_data_model = { version = "=2.0.0-pre-rc.13", path = "../data_model", features = ["transparent_api"] } +iroha_data_model = { version = "=2.0.0-pre-rc.13", path = "../data_model", features = ["transparent-api"] } iroha_logger = { version = "=2.0.0-pre-rc.13", path = "../logger" } iroha_primitives = { version = "=2.0.0-pre-rc.13", path = "../primitives" } iroha_schema = { version = "=2.0.0-pre-rc.13", path = "../schema" } @@ -15,5 +16,7 @@ iroha_schema = { version = "=2.0.0-pre-rc.13", path = "../schema" } derive_more = { version = "0.99.17", default-features = false, features = ["deref"]} serde = { version = "1.0.151", features = ["derive"] } serde_json = "1.0.91" -eyre = "0.6.8" +once_cell = "1.16.0" +thiserror = "1.0.38" tracing = "0.1.37" +eyre = "0.6.8" diff --git a/genesis/src/lib.rs b/genesis/src/lib.rs index d5f1a6ffa4f..7d2154ed9d1 100644 --- a/genesis/src/lib.rs +++ b/genesis/src/lib.rs @@ -16,15 +16,17 @@ use std::{ path::{Path, PathBuf}, }; -use derive_more::{Deref, From}; +use derive_more::{Deref, Display, From}; use eyre::{bail, eyre, ErrReport, Result, WrapErr}; use iroha_config::genesis::Configuration; -use iroha_crypto::{KeyPair, PublicKey}; +use iroha_crypto::{KeyPair, PublicKey, SignatureVerificationFail, SignaturesOf}; use iroha_data_model::{ asset::AssetDefinition, prelude::{Metadata, *}, + transaction::{error::TransactionLimitError, SignedTransaction}, validator::Validator, }; +use iroha_macro::FromVariant; use iroha_primitives::small::{smallvec, SmallVec}; use iroha_schema::IntoSchema; use serde::{Deserialize, Serialize}; @@ -32,9 +34,85 @@ use serde::{Deserialize, Serialize}; /// Time to live for genesis transactions. const GENESIS_TRANSACTIONS_TTL_MS: u64 = 100_000; +/// `AcceptedTransaction` — a transaction accepted by iroha peer. +#[derive(Debug, Clone)] +pub struct AcceptedTransaction { + /// Transaction payload. + pub payload: TransactionPayload, + /// Transaction signatures. + pub signatures: SignaturesOf, +} + +/// Error type for transaction from [`Transaction`] to [`AcceptedTransaction`] +#[derive(Debug, Display, FromVariant, thiserror::Error)] +pub enum AcceptTransactionFailure { + /// Failure during limits check + TransactionLimit(#[source] TransactionLimitError), + /// Failure during signature verification + SignatureVerification(#[source] SignatureVerificationFail), +} + +/// [`Id`] of the genesis domain. +pub static GENESIS_DOMAIN_ID: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| "genesis".parse().expect("Valid")); +/// [`Id`] of the genesis account. +pub static GENESIS_ACCOUNT_ID: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| "genesis@genesis".parse().expect("Valid")); + +impl Transaction for AcceptedTransaction { + #[inline] + fn payload(&self) -> &TransactionPayload { + &self.payload + } + + #[inline] + fn signatures(&self) -> &SignaturesOf { + &self.signatures + } +} + +impl AcceptedTransaction { + /// Accept transaction. Transition from [`Transaction`] to [`AcceptedTransaction`]. + /// + /// # Errors + /// + /// - if it does not adhere to limits + /// - if signature verification fails + pub fn accept( + transaction: VersionedSignedTransaction, + limits: &TransactionLimits, + ) -> Result { + if let Err(error) = transaction.signatures().verify(transaction.payload()) { + return Err((transaction, error.into())); + } + + if !IS_GENESIS { + if let Err(error) = transaction.check_limits(limits) { + return Err((transaction, error.into())); + } + } + + let VersionedSignedTransaction::V1(v1_transaction) = transaction; + Ok(Self { + payload: v1_transaction.payload, + signatures: v1_transaction.signatures, + }) + } +} + +impl From for VersionedSignedTransaction { + fn from(source: AcceptedTransaction) -> Self { + SignedTransaction { + payload: source.payload, + signatures: source.signatures, + } + .into() + } +} + /// Genesis network trait for mocking pub trait GenesisNetworkTrait: - Deref> + Sync + Send + 'static + Sized + Debug + Deref> + Sync + Send + 'static + Sized + Debug { /// Construct [`GenesisNetwork`] from configuration. /// @@ -53,7 +131,7 @@ pub trait GenesisNetworkTrait: pub struct GenesisNetwork { /// transactions from `GenesisBlock`, any transaction is accepted #[deref] - pub transactions: Vec, + pub transactions: Vec, } impl GenesisNetworkTrait for GenesisNetwork { @@ -240,14 +318,18 @@ impl GenesisTransaction { self, genesis_key_pair: KeyPair, limits: &TransactionLimits, - ) -> Result { - let transaction = - TransactionBuilder::new(AccountId::genesis(), self.isi, GENESIS_TRANSACTIONS_TTL_MS) - .sign(genesis_key_pair)?; + ) -> Result { + let transaction = TransactionBuilder::new( + GENESIS_ACCOUNT_ID.clone(), + self.isi, + GENESIS_TRANSACTIONS_TTL_MS, + ) + .sign(genesis_key_pair)?; AcceptedTransaction::accept::(transaction, limits) - .wrap_err("Failed to accept transaction") .map(Into::into) + .map_err(|(_block, error)| error) + .wrap_err("Failed to accept transaction") } } @@ -357,7 +439,7 @@ impl RawGenesisDomainBuilder { /// Add an account to this domain without a public key. #[cfg(test)] - pub fn account_without_public_key(mut self, account_name: Name) -> Self { + fn account_without_public_key(mut self, account_name: Name) -> Self { let account_id = AccountId::new(account_name, self.domain_id.clone()); self.transaction .isi diff --git a/schema/gen/Cargo.toml b/schema/gen/Cargo.toml index 930f8780c97..244b8ab1b2d 100644 --- a/schema/gen/Cargo.toml +++ b/schema/gen/Cargo.toml @@ -11,6 +11,6 @@ license.workspace = true # TODO: Should genesis belong to schema? #3284 iroha_genesis = { version = "=2.0.0-pre-rc.13", path = "../../genesis"} iroha_primitives = { version = "=2.0.0-pre-rc.13", path = "../../primitives" } -iroha_data_model = { version = "=2.0.0-pre-rc.13", path = "../../data_model", features = ["http"] } +iroha_data_model = { version = "=2.0.0-pre-rc.13", path = "../../data_model", features = ["http", "transparent-api"] } iroha_crypto = { version = "=2.0.0-pre-rc.13", path = "../../crypto" } iroha_schema = { version = "=2.0.0-pre-rc.13", path = "../../schema" } diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index 0514434a071..92238387e97 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -3,11 +3,11 @@ //! types are included in the schema. #![allow(clippy::arithmetic_side_effects)] -use iroha_data_model::{block::stream::prelude::*, query::error::QueryExecutionFailure}; use iroha_genesis::RawGenesisBlock; use iroha_schema::prelude::*; macro_rules! types { + ($($t:ty),+ $(,)?) => { /// Generate map holding all schema types #[macro_export] @@ -17,7 +17,7 @@ macro_rules! types { $( $insert_entry!(map, $t); )+ #[cfg(target_arch = "aarch64")] - $insert_entry!(map, Box); + $insert_entry!(map, Box); map }} @@ -95,7 +95,7 @@ types!( BTreeSet, BTreeSet, BTreeSet, - BTreeSet>, + BTreeSet>, BlockHeader, BlockMessage, BlockRejectionReason, @@ -119,10 +119,8 @@ types!( Box>, Box, Box, - Box, Box, BurnBox, - CommittedBlock, Conditional, ConfigurationEvent, ConstString, @@ -147,7 +145,8 @@ types!( EvaluatesTo, EvaluatesTo, EvaluatesTo, - EvaluatesTo, + EvaluatesTo>, + EvaluatesTo>, EvaluatesTo, EvaluatesTo, EvaluatesTo, @@ -237,9 +236,9 @@ types!( GrantBox, Greater, Hash, - HashOf>, - HashOf, - HashOf, + HashOf, + HashOf>, + HashOf, IdBox, IdentifiableBox, If, @@ -278,8 +277,8 @@ types!( Option, Option, Option, - Option>>, - Option>, + Option>, + Option>>, Option, Option, Option, @@ -325,7 +324,6 @@ types!( QueryResult, RaiseTo, RegisterBox, - RejectedTransaction, RemoveKeyValueBox, Repeats, RevokeBox, @@ -343,12 +341,12 @@ types!( SetParameterBox, Signature, SignatureCheckCondition, - SignatureOf, + SignatureOf, SignatureOf, SignatureOf, - SignatureWrapperOf, + SignatureWrapperOf, SignatureWrapperOf, - SignaturesOf, + SignaturesOf, SignaturesOf, SignedQuery, SignedTransaction, @@ -377,7 +375,6 @@ types!( UnregisterBox, UnsatisfiedSignatureConditionFail, UpgradableBox, - ValidTransaction, Validator, ValidatorEvent, Value, @@ -390,21 +387,17 @@ types!( Vec, Vec, Vec, - Vec, - Vec, Vec, VersionedBlockMessage, VersionedBlockSubscriptionRequest, - VersionedCommittedBlock, - VersionedCommittedBlockWrapper, VersionedEventMessage, VersionedEventSubscriptionRequest, VersionedPaginatedQueryResult, VersionedPendingTransactions, - VersionedRejectedTransaction, + VersionedSignedBlock, + VersionedSignedBlockWrapper, VersionedSignedQuery, VersionedSignedTransaction, - VersionedValidTransaction, WasmExecutionFail, WasmSmartContract, Where, @@ -439,7 +432,7 @@ mod tests { BlockMessage, BlockSubscriptionRequest, VersionedBlockMessage, VersionedBlockSubscriptionRequest, }, - BlockHeader, CommittedBlock, VersionedCommittedBlock, + BlockHeader, BlockPayload, }, domain::NewDomain, ipfs::IpfsPath, @@ -452,9 +445,12 @@ mod tests { }, prelude::*, query::error::{FindError, QueryExecutionFailure}, - transaction::error::{TransactionExpired, TransactionLimitError}, + transaction::{ + error::{TransactionExpired, TransactionLimitError}, + SignedTransaction, + }, validator::Validator, - ValueKind, VersionedCommittedBlockWrapper, + ValueKind, VersionedSignedBlockWrapper, }; use iroha_genesis::RawGenesisBlock; use iroha_primitives::{ @@ -468,8 +464,7 @@ mod tests { // NOTE: These type parameters should not be have their schema exposed // By default `PhantomData` wrapped types schema will not be included - const SCHEMALESS_TYPES: [&str; 2] = - ["MerkleTree", "RegistrableBox"]; + const SCHEMALESS_TYPES: [&str; 2] = ["MerkleTree", "RegistrableBox"]; fn is_const_generic(generic: &str) -> bool { generic.parse::().is_ok() diff --git a/schema/src/lib.rs b/schema/src/lib.rs index cb01440e0ac..38171fefc84 100644 --- a/schema/src/lib.rs +++ b/schema/src/lib.rs @@ -489,6 +489,28 @@ impl IntoSchema for [T; L] { } } +// TODO: Implement for all tuples? +impl TypeId for (F, S) { + fn id() -> String { + format!("Tuple2<{}, {}>", F::id(), S::id()) + } +} +impl IntoSchema for (F, S) { + fn type_name() -> String { + format!("Tuple2<{}, {}>", F::type_name(), S::type_name()) + } + fn update_schema_map(map: &mut MetaMap) { + if !map.contains_key::() { + map.insert::(Metadata::Tuple(UnnamedFieldsMeta { + types: vec![core::any::TypeId::of::(), core::any::TypeId::of::()], + })); + + F::update_schema_map(map); + S::update_schema_map(map); + } + } +} + pub mod prelude { //! Exports common types. diff --git a/tools/kura_inspector/src/main.rs b/tools/kura_inspector/src/main.rs index 0ff0a89f3c7..9516cb6d478 100644 --- a/tools/kura_inspector/src/main.rs +++ b/tools/kura_inspector/src/main.rs @@ -8,7 +8,6 @@ use std::path::{Path, PathBuf}; use clap::{Parser, Subcommand}; use iroha_core::kura::{BlockIndex, BlockStore}; -use iroha_data_model::block::VersionedCommittedBlock; use iroha_version::scale::DecodeVersioned; /// Kura inspector @@ -136,7 +135,7 @@ fn print_blockchain(block_store_path: &Path, from_height: u64, block_count: u64) block_store .read_block_data(idx.start, &mut block_buf) .expect(&format!("Failed to read block № {} data.", meta_index + 1)); - let block = VersionedCommittedBlock::decode_all_versioned(&block_buf) + let block = iroha_data_model::block::VersionedSignedBlock::decode_all_versioned(&block_buf) .expect(&format!("Failed to decode block № {}", meta_index + 1)); println!("Block#{} :", meta_index + 1); println!("{block:#?}"); diff --git a/tools/parity_scale_decoder/samples/trigger.bin b/tools/parity_scale_decoder/samples/trigger.bin index 99b44b464041e5d82e98cce35e3d10a5bc67e3b0..bdbf97f7b8860c8992585fbb22f5904317f67193 100644 GIT binary patch delta 29 kcmWFt5Ld~~%qxj6$}di3U|?b56=Yy!U|`^7n<#Dx0Bku0#Q*>R delta 29 kcmWFt5Ld~~%qxj6$}di3U|?b56=7gxU|`_om?&-u0Bm9f$^ZZW diff --git a/tools/parity_scale_decoder/src/main.rs b/tools/parity_scale_decoder/src/main.rs index 626f1519c72..78ae8c51852 100644 --- a/tools/parity_scale_decoder/src/main.rs +++ b/tools/parity_scale_decoder/src/main.rs @@ -27,7 +27,7 @@ use iroha_data_model::{ BlockMessage, BlockSubscriptionRequest, VersionedBlockMessage, VersionedBlockSubscriptionRequest, }, - BlockHeader, CommittedBlock, VersionedCommittedBlock, + BlockHeader, BlockPayload, }, domain::NewDomain, ipfs::IpfsPath, @@ -40,9 +40,12 @@ use iroha_data_model::{ }, prelude::*, query::error::{FindError, QueryExecutionFailure}, - transaction::error::{TransactionExpired, TransactionLimitError}, + transaction::{ + error::{TransactionExpired, TransactionLimitError}, + SignedTransaction, + }, validator::Validator, - ValueKind, VersionedCommittedBlockWrapper, + ValueKind, VersionedSignedBlockWrapper, }; use iroha_primitives::{ addr::{Ipv4Addr, Ipv6Addr},