diff --git a/Cargo.lock b/Cargo.lock index dff2ee23c08..727fff239be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3172,7 +3172,6 @@ dependencies = [ "eyre", "iroha_crypto", "iroha_data_model", - "iroha_schema", "once_cell", "serde", "serde_json", diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 947f0a96321..78d70135689 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -292,7 +292,7 @@ impl Iroha { &config.block_sync, sumeragi.clone(), Arc::clone(&kura), - config.common.peer_id(), + config.common.peer_id.clone(), network.clone(), Arc::clone(&state), ) diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index ca63d75d89b..6a91fde39ba 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -4,11 +4,11 @@ use eyre::{Result, WrapErr}; use iroha_client::{ client::Client, data_model::{ + events::pipeline::{BlockEventFilter, BlockStatus}, parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, prelude::*, }, }; -use iroha_data_model::events::pipeline::{BlockEventFilter, BlockStatus}; use nonzero_ext::nonzero; use serde::Deserialize; use test_network::*; diff --git a/client/examples/million_accounts_genesis.rs b/client/examples/million_accounts_genesis.rs index 5a6b1dc6692..126354bbabb 100644 --- a/client/examples/million_accounts_genesis.rs +++ b/client/examples/million_accounts_genesis.rs @@ -2,9 +2,10 @@ use std::{thread, time::Duration}; use iroha::samples::{construct_executor, get_config}; -use iroha_client::data_model::prelude::*; -use iroha_crypto::KeyPair; -use iroha_data_model::isi::InstructionBox; +use iroha_client::{ + crypto::KeyPair, + data_model::{isi::InstructionBox, prelude::*}, +}; use iroha_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder}; use iroha_primitives::unique_vec; use test_network::{ diff --git a/client/examples/register_1000_triggers.rs b/client/examples/register_1000_triggers.rs index 6de1efd77bc..3dde24a5cb9 100644 --- a/client/examples/register_1000_triggers.rs +++ b/client/examples/register_1000_triggers.rs @@ -3,8 +3,10 @@ use std::str::FromStr; use iroha::samples::{construct_executor, get_config}; -use iroha_client::{client::Client, data_model::prelude::*}; -use iroha_data_model::trigger::TriggerId; +use iroha_client::{ + client::Client, + data_model::{prelude::*, trigger::TriggerId}, +}; use iroha_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder}; use iroha_primitives::unique_vec; use test_network::{ diff --git a/client/src/client.rs b/client/src/client.rs index 2a4eed202d7..15be1ce3b5a 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -14,13 +14,6 @@ use eyre::{eyre, Result, WrapErr}; use futures_util::StreamExt; use http_default::{AsyncWebSocketStream, WebSocketStream}; pub use iroha_config::client_api::ConfigDTO; -use iroha_data_model::{ - events::pipeline::{ - BlockEventFilter, BlockStatus, PipelineEventBox, PipelineEventFilterBox, - TransactionEventFilter, TransactionStatus, - }, - query::QueryOutputBox, -}; use iroha_logger::prelude::*; use iroha_telemetry::metrics::Status; use iroha_torii_const::uri as torii_uri; @@ -35,9 +28,13 @@ use crate::{ crypto::{HashOf, KeyPair}, data_model::{ block::SignedBlock, + events::pipeline::{ + BlockEventFilter, BlockStatus, PipelineEventBox, PipelineEventFilterBox, + TransactionEventFilter, TransactionStatus, + }, isi::Instruction, prelude::*, - query::{predicate::PredicateBox, Pagination, Query, Sorting}, + query::{predicate::PredicateBox, Pagination, Query, QueryOutputBox, Sorting}, BatchedResponse, ChainId, ValidationFail, }, http::{Method as HttpMethod, RequestBuilder, Response, StatusCode}, @@ -70,17 +67,17 @@ pub type QueryResult = core::result::Result; /// Trait for signing transactions pub trait Sign { /// Sign transaction with provided key pair. - fn sign(self, key_pair: &crate::crypto::KeyPair) -> SignedTransaction; + fn sign(self, key_pair: &KeyPair) -> SignedTransaction; } impl Sign for TransactionBuilder { - fn sign(self, key_pair: &crate::crypto::KeyPair) -> SignedTransaction { + fn sign(self, key_pair: &KeyPair) -> SignedTransaction { self.sign(key_pair) } } impl Sign for SignedTransaction { - fn sign(self, key_pair: &crate::crypto::KeyPair) -> SignedTransaction { + fn sign(self, key_pair: &KeyPair) -> SignedTransaction { self.sign(key_pair) } } diff --git a/client/src/config.rs b/client/src/config.rs index 72bb909d8c7..d2c44603b25 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -9,14 +9,16 @@ use iroha_config::{ base, base::{FromEnv, StdEnv, UnwrapPartial}, }; -use iroha_crypto::KeyPair; -use iroha_data_model::{prelude::*, ChainId}; use iroha_primitives::small::SmallStr; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use url::Url; -use crate::config::user::RootPartial; +use crate::{ + config::user::RootPartial, + crypto::KeyPair, + data_model::{prelude::*, ChainId}, +}; mod user; diff --git a/client/src/config/user.rs b/client/src/config/user.rs index 30a684e5bac..62869f563bf 100644 --- a/client/src/config/user.rs +++ b/client/src/config/user.rs @@ -7,13 +7,15 @@ use std::{fs::File, io::Read, path::Path, str::FromStr, time::Duration}; pub use boilerplate::*; use eyre::{eyre, Context, Report}; use iroha_config::base::{Emitter, ErrorsCollection}; -use iroha_crypto::{KeyPair, PrivateKey, PublicKey}; -use iroha_data_model::{account::AccountId, ChainId}; use merge::Merge; use serde_with::DeserializeFromStr; use url::Url; -use crate::config::BasicAuth; +use crate::{ + config::BasicAuth, + crypto::{KeyPair, PrivateKey, PublicKey}, + data_model::{account::AccountId, ChainId}, +}; impl RootPartial { /// Reads the partial layer from TOML diff --git a/client/src/config/user/boilerplate.rs b/client/src/config/user/boilerplate.rs index 500b13afecb..a1a71dd01a8 100644 --- a/client/src/config/user/boilerplate.rs +++ b/client/src/config/user/boilerplate.rs @@ -8,15 +8,17 @@ use iroha_config::base::{ Emitter, FromEnv, HumanDuration, Merge, ParseEnvResult, UnwrapPartial, UnwrapPartialResult, UserField, }; -use iroha_crypto::{PrivateKey, PublicKey}; -use iroha_data_model::{account::AccountId, ChainId}; use serde::Deserialize; -use crate::config::{ - base::{FromEnvResult, ReadEnv}, - user::{Account, OnlyHttpUrl, Root, Transaction}, - BasicAuth, DEFAULT_TRANSACTION_NONCE, DEFAULT_TRANSACTION_STATUS_TIMEOUT, - DEFAULT_TRANSACTION_TIME_TO_LIVE, +use crate::{ + config::{ + base::{FromEnvResult, ReadEnv}, + user::{Account, OnlyHttpUrl, Root, Transaction}, + BasicAuth, DEFAULT_TRANSACTION_NONCE, DEFAULT_TRANSACTION_STATUS_TIMEOUT, + DEFAULT_TRANSACTION_TIME_TO_LIVE, + }, + crypto::{PrivateKey, PublicKey}, + data_model::{account::AccountId, ChainId}, }; #[derive(Debug, Clone, Deserialize, Eq, PartialEq, Default, Merge)] diff --git a/client/src/query_builder.rs b/client/src/query_builder.rs index 4ccfe7c99db..5e4cefa6a97 100644 --- a/client/src/query_builder.rs +++ b/client/src/query_builder.rs @@ -1,10 +1,10 @@ use std::fmt::Debug; -use iroha_data_model::query::QueryOutputBox; - use crate::{ client::{Client, QueryOutput, QueryResult}, - data_model::query::{predicate::PredicateBox, sorting::Sorting, FetchSize, Pagination, Query}, + data_model::query::{ + predicate::PredicateBox, sorting::Sorting, FetchSize, Pagination, Query, QueryOutputBox, + }, }; pub struct QueryRequestBuilder<'a, R> { diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 34a102afd93..fb9ab077fb2 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -4,14 +4,14 @@ use eyre::Result; use iroha_client::{ client::{self, QueryResult}, crypto::{KeyPair, PublicKey}, - data_model::prelude::*, + data_model::{ + asset::{AssetId, AssetValue, AssetValueType}, + isi::error::{InstructionEvaluationError, InstructionExecutionError, Mismatch, TypeError}, + prelude::*, + transaction::error::TransactionRejectionReason, + }, }; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::{ - asset::{AssetId, AssetValue, AssetValueType}, - isi::error::{InstructionEvaluationError, InstructionExecutionError, Mismatch, TypeError}, - transaction::error::TransactionRejectionReason, -}; use serde_json::json; use test_network::*; diff --git a/client/tests/integration/burn_public_keys.rs b/client/tests/integration/burn_public_keys.rs index 8d245051aea..b606855131d 100644 --- a/client/tests/integration/burn_public_keys.rs +++ b/client/tests/integration/burn_public_keys.rs @@ -1,9 +1,8 @@ use iroha_client::{ client::{account, transaction, Client}, crypto::{HashOf, KeyPair, PublicKey}, - data_model::{isi::Instruction, prelude::*}, + data_model::{isi::Instruction, prelude::*, query::TransactionQueryOutput}, }; -use iroha_data_model::query::TransactionQueryOutput; use test_network::*; fn submit( diff --git a/client/tests/integration/domain_owner_permissions.rs b/client/tests/integration/domain_owner_permissions.rs index af78eff12ac..beda25b3f47 100644 --- a/client/tests/integration/domain_owner_permissions.rs +++ b/client/tests/integration/domain_owner_permissions.rs @@ -1,9 +1,11 @@ use eyre::Result; use iroha_client::{ crypto::KeyPair, - data_model::{account::SignatureCheckCondition, prelude::*}, + data_model::{ + account::SignatureCheckCondition, prelude::*, + transaction::error::TransactionRejectionReason, + }, }; -use iroha_data_model::transaction::error::TransactionRejectionReason; use serde_json::json; use test_network::*; diff --git a/client/tests/integration/events/pipeline.rs b/client/tests/integration/events/pipeline.rs index cd8288e0f05..ba7046d32fd 100644 --- a/client/tests/integration/events/pipeline.rs +++ b/client/tests/integration/events/pipeline.rs @@ -4,19 +4,17 @@ use eyre::Result; use iroha_client::{ crypto::HashOf, data_model::{ + events::pipeline::{ + BlockEvent, BlockEventFilter, BlockStatus, TransactionEventFilter, TransactionStatus, + }, + isi::error::InstructionExecutionError, parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, prelude::*, + transaction::error::TransactionRejectionReason, + ValidationFail, }, }; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::{ - events::pipeline::{ - BlockEvent, BlockEventFilter, BlockStatus, TransactionEventFilter, TransactionStatus, - }, - isi::error::InstructionExecutionError, - transaction::error::TransactionRejectionReason, - ValidationFail, -}; use test_network::*; // Needed to re-enable ignored tests. diff --git a/client/tests/integration/extra_functional/offline_peers.rs b/client/tests/integration/extra_functional/offline_peers.rs index 988b0271acb..14ad9479574 100644 --- a/client/tests/integration/extra_functional/offline_peers.rs +++ b/client/tests/integration/extra_functional/offline_peers.rs @@ -1,13 +1,13 @@ use eyre::Result; use iroha_client::{ client::{self, Client, QueryResult}, + crypto::KeyPair, data_model::{ peer::{Peer as DataModelPeer, PeerId}, prelude::*, }, }; use iroha_config::parameters::actual::Root as Config; -use iroha_crypto::KeyPair; use iroha_primitives::addr::socket_addr; use test_network::*; use tokio::runtime::Runtime; diff --git a/client/tests/integration/mod.rs b/client/tests/integration/mod.rs index 98b17659895..eb462f66174 100644 --- a/client/tests/integration/mod.rs +++ b/client/tests/integration/mod.rs @@ -1,5 +1,7 @@ -use iroha_crypto::KeyPair; -use iroha_data_model::account::{Account, AccountId, NewAccount}; +use iroha_client::{ + crypto::KeyPair, + data_model::account::{Account, AccountId, NewAccount}, +}; mod add_account; mod add_domain; diff --git a/client/tests/integration/non_mintable.rs b/client/tests/integration/non_mintable.rs index e9652f29390..0c595c612ac 100644 --- a/client/tests/integration/non_mintable.rs +++ b/client/tests/integration/non_mintable.rs @@ -3,9 +3,8 @@ use std::str::FromStr as _; use eyre::Result; use iroha_client::{ client::{self, QueryResult}, - data_model::{metadata::UnlimitedMetadata, prelude::*}, + data_model::{isi::InstructionBox, metadata::UnlimitedMetadata, prelude::*}, }; -use iroha_data_model::isi::InstructionBox; use test_network::*; #[test] diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 16a4c85140a..3896773bc67 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -4,10 +4,10 @@ use eyre::Result; use iroha_client::{ client::{self, Client, QueryResult}, crypto::KeyPair, - data_model::prelude::*, -}; -use iroha_data_model::{ - permission::PermissionToken, role::RoleId, transaction::error::TransactionRejectionReason, + data_model::{ + permission::PermissionToken, prelude::*, role::RoleId, + transaction::error::TransactionRejectionReason, + }, }; use iroha_genesis::GenesisNetwork; use serde_json::json; diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 3a3bcd7aff6..514685d28a2 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -4,9 +4,8 @@ use eyre::Result; use iroha_client::{ client::{self, QueryResult}, crypto::KeyPair, - data_model::prelude::*, + data_model::{prelude::*, transaction::error::TransactionRejectionReason}, }; -use iroha_data_model::transaction::error::TransactionRejectionReason; use serde_json::json; use test_network::*; diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index bddfcd7ee39..0d1907c8268 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -5,6 +5,7 @@ use iroha_client::{ client::{self, QueryResult}, data_model::{ account::Account, + isi::InstructionBox, prelude::*, query::{ predicate::{string, value, PredicateBox}, @@ -12,7 +13,6 @@ use iroha_client::{ }, }, }; -use iroha_data_model::isi::InstructionBox; use nonzero_ext::nonzero; use test_network::*; diff --git a/client/tests/integration/transfer_asset.rs b/client/tests/integration/transfer_asset.rs index 31c2750068f..e822644c059 100644 --- a/client/tests/integration/transfer_asset.rs +++ b/client/tests/integration/transfer_asset.rs @@ -3,13 +3,14 @@ use std::str::FromStr; use iroha_client::{ client::{self, QueryResult}, crypto::KeyPair, - data_model::{isi::Instruction, prelude::*, Registered}, -}; -use iroha_data_model::{ - account::{Account, AccountId}, - asset::{Asset, AssetDefinition}, - isi::InstructionBox, - name::Name, + data_model::{ + account::{Account, AccountId}, + asset::{Asset, AssetDefinition}, + isi::{Instruction, InstructionBox}, + name::Name, + prelude::*, + Registered, + }, }; use test_network::*; diff --git a/client/tests/integration/triggers/data_trigger.rs b/client/tests/integration/triggers/data_trigger.rs index 46f505a9f9f..70f47902402 100644 --- a/client/tests/integration/triggers/data_trigger.rs +++ b/client/tests/integration/triggers/data_trigger.rs @@ -1,6 +1,8 @@ use eyre::Result; -use iroha_client::{client, data_model::prelude::*}; -use iroha_data_model::asset::AssetValue; +use iroha_client::{ + client, + data_model::{asset::AssetValue, prelude::*}, +}; use test_network::*; use crate::integration::new_account_with_random_public_key; diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index 8a9bb9fb034..dbed2f51b00 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -3,10 +3,13 @@ use std::{str::FromStr as _, time::Duration}; use eyre::Result; use iroha_client::{ client::{self, Client, QueryResult}, - data_model::{prelude::*, transaction::WasmSmartContract}, + data_model::{ + events::pipeline::{BlockEventFilter, BlockStatus}, + prelude::*, + transaction::WasmSmartContract, + }, }; use iroha_config::parameters::defaults::chain_wide::DEFAULT_CONSENSUS_ESTIMATION; -use iroha_data_model::events::pipeline::{BlockEventFilter, BlockStatus}; use iroha_logger::info; use test_network::*; diff --git a/client/tests/integration/tx_chain_id.rs b/client/tests/integration/tx_chain_id.rs index 9e16a90a898..5ad88365582 100644 --- a/client/tests/integration/tx_chain_id.rs +++ b/client/tests/integration/tx_chain_id.rs @@ -1,7 +1,6 @@ use std::str::FromStr; -use iroha_crypto::KeyPair; -use iroha_data_model::prelude::*; +use iroha_client::{crypto::KeyPair, data_model::prelude::*}; use iroha_primitives::numeric::numeric; use test_network::*; diff --git a/config/src/parameters/actual.rs b/config/src/parameters/actual.rs index c9dc0973ab6..51b7a141cd4 100644 --- a/config/src/parameters/actual.rs +++ b/config/src/parameters/actual.rs @@ -75,13 +75,6 @@ pub struct Common { pub peer_id: PeerId, } -impl Common { - /// Construct an id of this peer - pub fn peer_id(&self) -> PeerId { - self.peer_id.clone() - } -} - /// Network options #[allow(missing_docs)] #[derive(Debug, Clone)] diff --git a/config/src/parameters/user.rs b/config/src/parameters/user.rs index 35774e882a6..73491c1bd34 100644 --- a/config/src/parameters/user.rs +++ b/config/src/parameters/user.rs @@ -236,7 +236,7 @@ impl Root { let genesis = genesis.unwrap(); let sumeragi = { let mut x = sumeragi.unwrap(); - x.trusted_peers.push(peer.peer_id()); + x.trusted_peers.push(peer.peer_id.clone()); x }; diff --git a/config/tests/fixtures.rs b/config/tests/fixtures.rs index e88b2fdf155..88ce5be4a99 100644 --- a/config/tests/fixtures.rs +++ b/config/tests/fixtures.rs @@ -241,7 +241,7 @@ fn self_is_presented_in_trusted_peers() -> Result<()> { assert!(config .sumeragi .trusted_peers - .contains(&config.common.peer_id())); + .contains(&config.common.peer_id)); Ok(()) } diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 55348c7b4cb..bdce8736f47 100644 Binary files a/configs/swarm/executor.wasm and b/configs/swarm/executor.wasm differ diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index d88514f7c9f..5b061477532 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -34,14 +34,16 @@ pub fn create_block( .sign(key_pair); let limits = state.transaction_executor().transaction_limits; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let block = BlockBuilder::new( vec![AcceptedTransaction::accept(transaction, &chain_id, &limits).unwrap()], topology.clone(), Vec::new(), ) .chain(0, state) - .sign(key_pair) + .sign(key_pair.private_key()) .unpack(|_| {}) .commit(&topology) .unpack(|_| {}) diff --git a/core/benches/kura.rs b/core/benches/kura.rs index f9b53e25190..7bf60f757c1 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -15,7 +15,6 @@ use iroha_core::{ }; use iroha_crypto::KeyPair; use iroha_data_model::{prelude::*, transaction::TransactionLimits}; -use iroha_primitives::unique_vec::UniqueVec; use tokio::{fs, runtime::Runtime}; async fn measure_block_size_for_n_executors(n_executors: u32) { @@ -26,13 +25,13 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let xor_id = AssetDefinitionId::from_str("xor#test").expect("tested"); let alice_xor_id = AssetId::new(xor_id, alice_id); let transfer = Transfer::asset_numeric(alice_xor_id, 10u32, bob_id); - let keypair = KeyPair::random(); + let key_pair = KeyPair::random(); let tx = TransactionBuilder::new( chain_id.clone(), AccountId::from_str("alice@wonderland").expect("checked"), ) .with_instructions([transfer]) - .sign(&keypair); + .sign(&key_pair); let transaction_limits = TransactionLimits { max_instruction_number: 4096, max_wasm_size_bytes: 0, @@ -50,17 +49,19 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let query_handle = LiveQueryStore::test().start(); let state = State::new(World::new(), kura, query_handle); - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, peer_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let mut block = { let mut state_block = state.block(); - BlockBuilder::new(vec![tx], topology, Vec::new()) + BlockBuilder::new(vec![tx], topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(&KeyPair::random()) + .sign(&peer_private_key) .unpack(|_| {}) }; for _ in 1..n_executors { - block = block.sign(&KeyPair::random()); + block.sign(&key_pair, &topology); } let mut block_store = BlockStore::new(dir.path(), LockStatus::Unlocked); block_store.create_files_if_they_do_not_exist().unwrap(); diff --git a/core/benches/validation.rs b/core/benches/validation.rs index d7e5459f090..128340a3ad5 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -170,11 +170,12 @@ fn sign_blocks(criterion: &mut Criterion) { &TRANSACTION_LIMITS, ) .expect("Failed to accept transaction."); - let key_pair = KeyPair::random(); let kura = iroha_core::kura::Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(World::new(), kura, query_handle); - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, peer_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let mut count = 0; @@ -186,7 +187,7 @@ fn sign_blocks(criterion: &mut Criterion) { b.iter_batched( || block.clone(), |block| { - let _: ValidBlock = block.sign(&key_pair).unpack(|_| {}); + let _: ValidBlock = block.sign(&peer_private_key).unpack(|_| {}); count += 1; }, BatchSize::SmallInput, diff --git a/core/src/block.rs b/core/src/block.rs index 68df8771241..bbee8f0240a 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -7,7 +7,7 @@ use std::error::Error as _; use iroha_config::parameters::defaults::chain_wide::DEFAULT_CONSENSUS_ESTIMATION; -use iroha_crypto::{HashOf, KeyPair, MerkleTree, SignatureOf, SignaturesOf}; +use iroha_crypto::{HashOf, MerkleTree, SignatureOf}; use iroha_data_model::{ block::*, events::prelude::*, @@ -15,7 +15,6 @@ use iroha_data_model::{ transaction::{error::TransactionRejectionReason, prelude::*}, }; use iroha_genesis::GenesisTransaction; -use iroha_primitives::unique_vec::UniqueVec; use thiserror::Error; pub(crate) use self::event::WithEvents; @@ -23,7 +22,7 @@ pub use self::{chained::Chained, commit::CommittedBlock, valid::ValidBlock}; use crate::{prelude::*, sumeragi::network_topology::Topology, tx::AcceptTransactionFail}; /// Error during transaction validation -#[derive(Debug, displaydoc::Display, Error)] +#[derive(Debug, displaydoc::Display, PartialEq, Eq, Error)] pub enum TransactionValidationError { /// Failed to accept transaction Accept(#[from] AcceptTransactionFail), @@ -34,31 +33,24 @@ pub enum TransactionValidationError { } /// Errors occurred on block validation -#[derive(Debug, displaydoc::Display, Error)] +#[derive(Debug, displaydoc::Display, PartialEq, Eq, Error)] pub enum BlockValidationError { /// Block has committed transactions HasCommittedTransactions, - /// Mismatch between the actual and expected hashes of the latest block. Expected: {expected:?}, actual: {actual:?} - LatestBlockHashMismatch { + /// Mismatch between the actual and expected hashes of the previous block. Expected: {expected:?}, actual: {actual:?} + PrevBlockHashMismatch { /// Expected value expected: Option>, /// Actual value actual: Option>, }, - /// Mismatch between the actual and expected height of the latest block. Expected: {expected}, actual: {actual} - LatestBlockHeightMismatch { + /// Mismatch between the actual and expected height of the previous block. Expected: {expected}, actual: {actual} + PrevBlockHeightMismatch { /// Expected value expected: u64, /// Actual value actual: u64, }, - /// Mismatch between the actual and expected hashes of the current block. Expected: {expected:?}, actual: {actual:?} - IncorrectHash { - /// Expected value - expected: HashOf, - /// Actual value - actual: HashOf, - }, /// The transaction hash stored in the block header does not match the actual transaction hash TransactionHashMismatch, /// Error during transaction validation @@ -66,9 +58,9 @@ pub enum BlockValidationError { /// Mismatch between the actual and expected topology. Expected: {expected:?}, actual: {actual:?} TopologyMismatch { /// Expected value - expected: UniqueVec, + expected: Vec, /// Actual value - actual: UniqueVec, + actual: Vec, }, /// Error during block signatures check SignatureVerification(#[from] SignatureVerificationError), @@ -77,7 +69,7 @@ pub enum BlockValidationError { } /// Error during signature verification -#[derive(thiserror::Error, displaydoc::Display, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, displaydoc::Display, Clone, Copy, PartialEq, Eq, Error)] pub enum SignatureVerificationError { /// The block doesn't have enough valid signatures to be committed (`votes_count` out of `min_votes_for_commit`) NotEnoughSignatures { @@ -86,9 +78,14 @@ pub enum SignatureVerificationError { /// Minimal required number of signatures min_votes_for_commit: usize, }, - /// The block doesn't contain an expected signature. Expected signature can be leader or the current peer - SignatureMissing, - /// Found signature that does not correspond to block payload + /// Block was signed by the same node multiple times + DuplicateSignatures { + /// Index of the faulty node in the topology + signatory: usize, + }, + /// Block signatory doesn't correspond to any in topology + UnknownSignatory, + /// Block signature doesn't correspond to block payload UnknownSignature, /// The block doesn't have proxy tail signature ProxyTailMissing, @@ -147,7 +144,7 @@ mod pending { fn make_header( previous_height: u64, prev_block_hash: Option>, - view_change_index: u64, + view_change_index: usize, transactions: &[TransactionValue], ) -> BlockHeader { BlockHeader { @@ -164,7 +161,7 @@ mod pending { .as_millis() .try_into() .expect("Time should fit into u64"), - view_change_index, + view_change_index: view_change_index as u32, consensus_estimation_ms: DEFAULT_CONSENSUS_ESTIMATION .as_millis() .try_into() @@ -205,7 +202,7 @@ mod pending { /// Upon executing this method current timestamp is stored in the block header. pub fn chain( self, - view_change_index: u64, + view_change_index: usize, state: &mut StateBlock<'_>, ) -> BlockBuilder { let transactions = Self::categorize_transactions(self.0.transactions, state); @@ -218,7 +215,7 @@ mod pending { &transactions, ), transactions, - commit_topology: self.0.commit_topology.ordered_peers, + commit_topology: self.0.commit_topology.into_iter().collect(), event_recommendations: self.0.event_recommendations, })) } @@ -234,13 +231,13 @@ mod chained { impl BlockBuilder { /// Sign this block and get [`SignedBlock`]. - pub fn sign(self, key_pair: &KeyPair) -> WithEvents { - let signature = SignatureOf::new(key_pair, &self.0 .0); + pub fn sign(self, private_key: &PrivateKey) -> WithEvents { + let signature = BlockSignature(0, SignatureOf::new(private_key, &self.0 .0)); WithEvents::new(ValidBlock( SignedBlockV1 { + signatures: vec![signature], payload: self.0 .0, - signatures: SignaturesOf::from(signature), } .into(), )) @@ -249,6 +246,7 @@ mod chained { } mod valid { + use indexmap::IndexMap; use iroha_data_model::ChainId; use super::*; @@ -260,17 +258,152 @@ mod valid { pub struct ValidBlock(pub(super) SignedBlock); impl ValidBlock { + fn verify_leader_signature( + block: &SignedBlock, + topology: &Topology, + ) -> Result<(), SignatureVerificationError> { + let leader_index = topology.leader_index(); + let mut signatures = block.signatures(); + + match signatures.next() { + Some(BlockSignature(signatory, signature)) + if usize::try_from(*signatory) + .map_err(|_err| SignatureVerificationError::LeaderMissing)? + == leader_index => + { + let leader = &topology.as_ref()[leader_index]; + + let mut additional_leader_signatures = + topology.filter_signatures_by_roles(&[Role::Leader], signatures); + + if additional_leader_signatures.next().is_some() { + return Err(SignatureVerificationError::DuplicateSignatures { + signatory: leader_index, + }); + } + + let SignedBlock::V1(block) = block; + signature + .verify(leader.public_key(), &block.payload) + .map_err(|_err| SignatureVerificationError::LeaderMissing)?; + } + _ => { + return Err(SignatureVerificationError::LeaderMissing); + } + } + + Ok(()) + } + + fn verify_validator_signatures( + block: &SignedBlock, + topology: &Topology, + ) -> Result<(), SignatureVerificationError> { + //let roles: &[Role] = if topology.view_change_index() >= 1 { + // &[Role::ValidatingPeer, Role::ObservingPeer] + //} else { + // if topology + // .filter_signatures_by_roles(&[Role::ObservingPeer], block.signatures()) + // .next() + // .is_some() + // { + // return Err(SignatureVerificationError::UnknownSignatory); + // } + + // &[Role::ValidatingPeer] + //}; + let roles = &[Role::ValidatingPeer, Role::ObservingPeer]; + + topology + .filter_signatures_by_roles(roles, block.signatures()) + .try_fold(IndexMap::::default(), |mut acc, signature| { + let signatory_idx = usize::try_from(signature.0) + .map_err(|_err| SignatureVerificationError::UnknownSignatory)?; + + if acc.insert(signatory_idx, signature.1).is_some() { + return Err(SignatureVerificationError::DuplicateSignatures { + signatory: signatory_idx, + }); + } + + Ok(acc) + })? + .into_iter() + .try_for_each(|(signatory_idx, signature)| { + let signatory: &PeerId = topology + .as_ref() + .get(signatory_idx) + .ok_or(SignatureVerificationError::UnknownSignatory)?; + + let SignedBlock::V1(block) = block; + signature + .verify(signatory.public_key(), &block.payload) + .map_err(|_err| SignatureVerificationError::UnknownSignature)?; + + Ok(()) + })?; + + if topology + .filter_signatures_by_roles(&[Role::Undefined], block.signatures()) + .next() + .is_some() + { + return Err(SignatureVerificationError::UnknownSignatory); + } + + Ok(()) + } + + fn verify_proxy_tail_signature( + block: &SignedBlock, + topology: &Topology, + ) -> Result<(), SignatureVerificationError> { + let proxy_tail_index = topology.proxy_tail_index(); + let mut signatures = block.signatures().rev(); + + match signatures.next() { + Some(BlockSignature(signatory, signature)) + if usize::try_from(*signatory) + .map_err(|_err| SignatureVerificationError::ProxyTailMissing)? + == proxy_tail_index => + { + let proxy_tail = &topology.as_ref()[proxy_tail_index]; + + let mut additional_proxy_tail_signatures = + topology.filter_signatures_by_roles(&[Role::ProxyTail], signatures); + + if additional_proxy_tail_signatures.next().is_some() { + return Err(SignatureVerificationError::DuplicateSignatures { + signatory: proxy_tail_index, + }); + } + + let SignedBlock::V1(block) = block; + signature + .verify(proxy_tail.public_key(), &block.payload) + .map_err(|_err| SignatureVerificationError::ProxyTailMissing)?; + } + _ => { + return Err(SignatureVerificationError::ProxyTailMissing); + } + } + + Ok(()) + } + /// 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 + /// - There is a mismatch between candidate block previous block hash and actual previous block hash + /// - Block is not signed by the leader + /// - Block has duplicate signatures + /// - Block has unknown signatories + /// - Block has incorrect signatures + /// - Topology field is incorrect /// - 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 /// - Transaction in the genesis block is not signed by the genesis public key pub fn validate( block: SignedBlock, @@ -285,7 +418,7 @@ mod valid { if expected_block_height != actual_height { return WithEvents::new(Err(( block, - BlockValidationError::LatestBlockHeightMismatch { + BlockValidationError::PrevBlockHeightMismatch { expected: expected_block_height, actual: actual_height, }, @@ -298,39 +431,34 @@ mod valid { if expected_prev_block_hash != actual_prev_block_hash { return WithEvents::new(Err(( block, - BlockValidationError::LatestBlockHashMismatch { + BlockValidationError::PrevBlockHashMismatch { expected: expected_prev_block_hash, actual: actual_prev_block_hash, }, ))); } - // NOTE: should be checked AFTER height and hash, both this issues lead to topology mismatch if !block.header().is_genesis() { - let actual_commit_topology = block.commit_topology(); - let expected_commit_topology = &topology.ordered_peers; + if let Err(err) = Self::verify_leader_signature(&block, topology) + .map(|()| Self::verify_validator_signatures(&block, topology)) + { + return WithEvents::new(Err((block, err.into()))); + } - if actual_commit_topology != expected_commit_topology { - let actual_commit_topology = actual_commit_topology.clone(); + let actual_commit_topology = block.commit_topology().cloned().collect(); + let expected_commit_topology = topology.as_ref(); + // NOTE: checked AFTER height and hash because + // both of them can lead to a topology mismatch + if actual_commit_topology != expected_commit_topology { return WithEvents::new(Err(( block, BlockValidationError::TopologyMismatch { - expected: expected_commit_topology.clone(), + expected: expected_commit_topology.to_owned(), actual: actual_commit_topology, }, ))); } - - if topology - .filter_signatures_by_roles(&[Role::Leader], block.signatures()) - .is_empty() - { - return WithEvents::new(Err(( - block, - SignatureVerificationError::LeaderMissing.into(), - ))); - } } if block @@ -403,91 +531,93 @@ mod valid { }) } - /// 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. + /// Add additional signature for [`Self`] /// /// # Errors /// - /// - Not enough signatures - /// - Not signed by proxy tail - pub fn commit_with_signatures( - mut self, + /// If given signature doesn't match block hash + pub fn add_signature( + &mut self, + signature: BlockSignature, topology: &Topology, - signatures: SignaturesOf, - expected_hash: HashOf, - ) -> WithEvents> { - if topology - .filter_signatures_by_roles(&[Role::Leader], &signatures) - .is_empty() - { - return WithEvents::new(Err(( - self, - SignatureVerificationError::LeaderMissing.into(), - ))); - } + ) -> Result<(), iroha_crypto::Error> { + let signatory = &topology.as_ref()[signature.0 as usize]; - if !self.as_ref().signatures().is_subset(&signatures) { - return WithEvents::new(Err(( - self, - SignatureVerificationError::SignatureMissing.into(), - ))); - } + let SignedBlock::V1(block) = &mut self.0; + signature.1.verify(signatory.public_key(), &block.payload)?; + block.signatures.push(signature); - if !self.0.replace_signatures(signatures) { - return WithEvents::new(Err(( - self, - SignatureVerificationError::UnknownSignature.into(), - ))); - } + Ok(()) + } - let actual_block_hash = self.as_ref().hash(); - if actual_block_hash != expected_hash { - let err = BlockValidationError::IncorrectHash { - expected: expected_hash, - actual: actual_block_hash, - }; + /// Replace block's signatures. Returns previous block signatures + /// + /// # Errors + /// + /// - Replacement signatures don't contain the leader signature + /// - Replacement signatures contain duplicate signatures + /// - Replacement signatures contain unknown signatories + /// - Replacement signatures contain incorrect signatures + pub fn replace_signatures( + &mut self, + mut signatures: Vec, + topology: &Topology, + ) -> WithEvents, SignatureVerificationError>> { + if !self.as_ref().header().is_genesis() { + let SignedBlock::V1(block) = &mut self.0; - return WithEvents::new(Err((self, err))); + core::mem::swap(&mut block.signatures, &mut signatures); + if let Err(err) = Self::verify_leader_signature(self.as_ref(), topology) + .map(|()| Self::verify_validator_signatures(self.as_ref(), topology)) + { + let SignedBlock::V1(block) = &mut self.0; + core::mem::swap(&mut block.signatures, &mut signatures); + return WithEvents::new(Err(err)); + } } - self.commit(topology) + WithEvents::new(Ok(signatures)) } - /// Verify signatures and commit block to the store. + /// commit block to the store. /// /// # Errors /// - /// - Not enough signatures - /// - Not signed by proxy tail + /// - Block has duplicate proxy tail signatures + /// - Block is not signed by the proxy tail + /// - Block doesn't have enough signatures pub fn commit( self, topology: &Topology, ) -> WithEvents> { - if !self.0.header().is_genesis() { - if let Err(err) = self.verify_signatures(topology) { + if !self.as_ref().header().is_genesis() { + if let Err(err) = Self::verify_proxy_tail_signature(self.as_ref(), topology) { return WithEvents::new(Err((self, err.into()))); } + + let votes_count = self.as_ref().signatures().len(); + if votes_count < topology.min_votes_for_commit() { + return WithEvents::new(Err(( + self, + SignatureVerificationError::NotEnoughSignatures { + votes_count, + min_votes_for_commit: topology.min_votes_for_commit(), + } + .into(), + ))); + } } WithEvents::new(Ok(CommittedBlock(self))) } /// Add additional signatures for [`Self`]. - #[must_use] - pub fn sign(self, key_pair: &KeyPair) -> ValidBlock { - ValidBlock(self.0.sign(key_pair)) - } + pub fn sign(&mut self, key_pair: &KeyPair, topology: &Topology) { + let signatory_idx = topology + .position(key_pair.public_key()) + .expect("BUG: Node is not in topology"); - /// Add additional signature for [`Self`] - /// - /// # Errors - /// - /// If given signature doesn't match block hash - pub fn add_signature( - &mut self, - signature: SignatureOf, - ) -> Result<(), iroha_crypto::error::Error> { - self.0.add_signature(signature) + self.0.sign(key_pair.private_key(), signatory_idx); } #[cfg(test)] @@ -505,56 +635,12 @@ mod valid { .expect("Time should fit into u64"), }, transactions: Vec::new(), - commit_topology: UniqueVec::new(), + commit_topology: Vec::new(), event_recommendations: Vec::new(), })) - .sign(&KeyPair::random()) + .sign(iroha_crypto::KeyPair::random().private_key()) .unpack(|_| {}) } - - /// Check if block's signatures meet requirements for given topology. - /// - /// In order for block to be considered valid there should be at least $2f + 1$ signatures (including proxy tail and leader signature) where f is maximum number of faulty nodes. - /// For further information please refer to the [whitepaper](docs/source/iroha_2_whitepaper.md) section 2.8 consensus. - /// - /// # Errors - /// - Not enough signatures - /// - Missing proxy tail signature - fn verify_signatures(&self, topology: &Topology) -> Result<(), SignatureVerificationError> { - // TODO: Should the peer that serves genesis have a fixed role of ProxyTail in topology? - if !self.as_ref().header().is_genesis() - && topology.is_consensus_required().is_some() - && topology - .filter_signatures_by_roles(&[Role::ProxyTail], self.as_ref().signatures()) - .is_empty() - { - return Err(SignatureVerificationError::ProxyTailMissing); - } - - #[allow(clippy::collapsible_else_if)] - if self.as_ref().header().is_genesis() { - // At genesis round we blindly take on the network topology from the genesis block. - } else { - let roles = [ - Role::ValidatingPeer, - Role::Leader, - Role::ProxyTail, - Role::ObservingPeer, - ]; - - let votes_count = topology - .filter_signatures_by_roles(&roles, self.as_ref().signatures()) - .len(); - if votes_count < topology.min_votes_for_commit() { - return Err(SignatureVerificationError::NotEnoughSignatures { - votes_count, - min_votes_for_commit: topology.min_votes_for_commit(), - }); - } - } - - Ok(()) - } } impl From for SignedBlock { @@ -592,11 +678,13 @@ mod valid { let payload = payload(&block).clone(); key_pairs .iter() - .map(|key_pair| SignatureOf::new(key_pair, &payload)) - .try_for_each(|signature| block.add_signature(signature)) + .map(|key_pair| { + BlockSignature(0, SignatureOf::new(key_pair.private_key(), &payload)) + }) + .try_for_each(|signature| block.add_signature(signature, &topology)) .expect("Failed to add signatures"); - assert_eq!(block.verify_signatures(&topology), Ok(())); + assert!(block.commit(&topology).unpack(|_| {}).is_ok()); } #[test] @@ -612,11 +700,13 @@ mod valid { let payload = payload(&block).clone(); key_pairs .iter() - .map(|key_pair| SignatureOf::new(key_pair, &payload)) - .try_for_each(|signature| block.add_signature(signature)) + .map(|key_pair| { + BlockSignature(0, SignatureOf::new(key_pair.private_key(), &payload)) + }) + .try_for_each(|signature| block.add_signature(signature, &topology)) .expect("Failed to add signatures"); - assert_eq!(block.verify_signatures(&topology), Ok(())); + assert!(block.commit(&topology).unpack(|_| {}).is_ok()); } /// Check requirement of having at least $2f + 1$ signatures in $3f + 1$ network @@ -631,17 +721,19 @@ mod valid { let mut block = ValidBlock::new_dummy(); let payload = payload(&block).clone(); - let proxy_tail_signature = SignatureOf::new(&key_pairs[4], &payload); + let proxy_tail_signature = + BlockSignature(0, SignatureOf::new(key_pairs[4].private_key(), &payload)); block - .add_signature(proxy_tail_signature) + .add_signature(proxy_tail_signature, &topology) .expect("Failed to add signature"); assert_eq!( - block.verify_signatures(&topology), - Err(SignatureVerificationError::NotEnoughSignatures { + block.commit(&topology).unpack(|_| {}).unwrap_err().1, + SignatureVerificationError::NotEnoughSignatures { votes_count: 1, min_votes_for_commit: topology.min_votes_for_commit(), - }) + } + .into() ) } @@ -661,13 +753,15 @@ mod valid { .iter() .enumerate() .filter(|(i, _)| *i != 4) // Skip proxy tail - .map(|(_, key_pair)| SignatureOf::new(key_pair, &payload)) - .try_for_each(|signature| block.add_signature(signature)) + .map(|(_, key_pair)| { + BlockSignature(0, SignatureOf::new(key_pair.private_key(), &payload)) + }) + .try_for_each(|signature| block.add_signature(signature, &topology)) .expect("Failed to add signatures"); assert_eq!( - block.verify_signatures(&topology), - Err(SignatureVerificationError::ProxyTailMissing) + block.commit(&topology).unpack(|_| {}).unwrap_err().1, + SignatureVerificationError::ProxyTailMissing.into() ) } } @@ -732,6 +826,17 @@ mod event { } } } + impl WithEvents, SignatureVerificationError>> { + pub fn unpack( + self, + f: F, + ) -> Result, SignatureVerificationError> { + match self.0 { + Ok(ok) => Ok(ok), + Err(err) => Err(WithEvents(err).unpack(f)), + } + } + } impl WithEvents { pub fn unpack(self, f: F) -> B { self.0.produce_events().for_each(f); @@ -789,6 +894,14 @@ mod event { impl EventProducer for BlockValidationError { fn produce_events(&self) -> impl Iterator { + // TODO: + core::iter::empty() + } + } + + impl EventProducer for SignatureVerificationError { + fn produce_events(&self) -> impl Iterator { + // TODO: core::iter::empty() } } @@ -798,20 +911,22 @@ mod event { mod tests { use std::str::FromStr as _; - use iroha_crypto::SignatureVerificationFail; use iroha_data_model::prelude::*; use iroha_genesis::{GENESIS_ACCOUNT_ID, GENESIS_DOMAIN_ID}; + use iroha_primitives::unique_vec::UniqueVec; use super::*; use crate::{ kura::Kura, query::store::LiveQueryStore, smartcontracts::isi::Registrable as _, - state::State, + state::State, tx::SignatureVerificationFail, }; #[test] pub fn committed_and_valid_block_hashes_are_equal() { let valid_block = ValidBlock::new_dummy(); - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let committed_block = valid_block .clone() .commit(&topology) @@ -853,10 +968,12 @@ mod tests { // Creating a block of two identical transactions and validating it let transactions = vec![tx.clone(), tx]; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let valid_block = BlockBuilder::new(transactions, topology, Vec::new()) .chain(0, &mut state_block) - .sign(&alice_keys) + .sign(alice_keys.private_key()) .unpack(|_| {}); // The first transaction should be confirmed @@ -928,10 +1045,12 @@ mod tests { // Creating a block of two identical transactions and validating it let transactions = vec![tx0, tx, tx2]; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let valid_block = BlockBuilder::new(transactions, topology, Vec::new()) .chain(0, &mut state_block) - .sign(&alice_keys) + .sign(alice_keys.private_key()) .unpack(|_| {}); // The first transaction should fail @@ -998,10 +1117,12 @@ mod tests { // Creating a block of where first transaction must fail and second one fully executed let transactions = vec![tx_fail, tx_accept]; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let valid_block = BlockBuilder::new(transactions, topology, Vec::new()) .chain(0, &mut state_block) - .sign(&alice_keys) + .sign(alice_keys.private_key()) .unpack(|_| {}); // The first transaction should be rejected @@ -1069,10 +1190,12 @@ mod tests { // Create genesis block let transactions = vec![tx]; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let valid_block = BlockBuilder::new(transactions, topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(&KeyPair::random()) + .sign(KeyPair::random().private_key()) .unpack(|_| {}); // Validate genesis block diff --git a/core/src/kura.rs b/core/src/kura.rs index ef36bbdf19e..94cf045b428 100644 --- a/core/src/kura.rs +++ b/core/src/kura.rs @@ -291,8 +291,6 @@ impl Kura { } /// Get a reference to block by height, loading it from disk if needed. - // 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> { let mut data_array_guard = self.block_data.lock(); if block_height == 0 || block_height > data_array_guard.len() as u64 { diff --git a/core/src/queue.rs b/core/src/queue.rs index 51e3ad38a5b..09b62b9e180 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -29,8 +29,7 @@ impl AcceptedTransaction { let transaction_signatories = self .as_ref() .signatures() - .iter() - .map(|signature| signature.public_key()) + .map(|signature| &signature.0) .cloned() .collect(); diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index daf7faae917..9eb38215fae 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -75,7 +75,7 @@ impl ValidQueryRequest { let account_has_public_key = state_ro .world() .map_account(query.authority(), |account| { - account.contains_signatory(query.signature().public_key()) + account.contains_signatory(&query.signature().0) }) .map_err(Error::from)?; if !account_has_public_key { @@ -190,7 +190,6 @@ mod tests { use iroha_data_model::{ metadata::MetadataValueBox, query::error::FindError, transaction::TransactionLimits, }; - use iroha_primitives::unique_vec::UniqueVec; use once_cell::sync::Lazy; use tokio::test; @@ -312,10 +311,12 @@ mod tests { let mut transactions = vec![valid_tx; valid_tx_per_block]; transactions.append(&mut vec![invalid_tx; invalid_tx_per_block]); - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let first_block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(&ALICE_KEYS) + .sign(ALICE_KEYS.private_key()) .unpack(|_| {}) .commit(&topology) .unpack(|_| {}) @@ -327,7 +328,7 @@ mod tests { for _ in 1u64..blocks { let block = BlockBuilder::new(transactions.clone(), topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(&ALICE_KEYS) + .sign(ALICE_KEYS.private_key()) .unpack(|_| {}) .commit(&topology) .unpack(|_| {}) @@ -466,10 +467,12 @@ mod tests { let tx_limits = &state_block.transaction_executor().transaction_limits; let va_tx = AcceptedTransaction::accept(tx, &chain_id, tx_limits)?; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let vcb = BlockBuilder::new(vec![va_tx.clone()], topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(&ALICE_KEYS) + .sign(ALICE_KEYS.private_key()) .unpack(|_| {}) .commit(&topology) .unpack(|_| {}) diff --git a/core/src/state.rs b/core/src/state.rs index 81ec0c79dec..f51484ea7ef 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -1022,7 +1022,7 @@ pub trait StateReadOnly { } /// Return the view change index of the latest block - fn latest_block_view_change_index(&self) -> u64 { + fn latest_block_view_change_index(&self) -> u32 { self.kura() .get_block_by_height(self.height()) .map_or(0, |block| block.header().view_change_index) @@ -1765,7 +1765,6 @@ pub(crate) mod deserialize { #[cfg(test)] mod tests { use iroha_data_model::block::BlockPayload; - use iroha_primitives::unique_vec::UniqueVec; use super::*; use crate::{ @@ -1783,7 +1782,9 @@ mod tests { async fn get_block_hashes_after_hash() { const BLOCK_CNT: usize = 10; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = iroha_crypto::KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let block = ValidBlock::new_dummy() .commit(&topology) .unpack(|_| {}) @@ -1814,7 +1815,9 @@ mod tests { async fn get_blocks_from_height() { const BLOCK_CNT: usize = 10; - let topology = Topology::new(UniqueVec::new()); + let (peer_public_key, _) = iroha_crypto::KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); + let topology = Topology::new(vec![peer_id]); let block = ValidBlock::new_dummy() .commit(&topology) .unpack(|_| {}) diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 49e1c1db8b6..8d4c8ceecea 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -1,7 +1,7 @@ //! The main event loop that powers sumeragi. use std::sync::mpsc; -use iroha_crypto::HashOf; +use iroha_crypto::{HashOf, KeyPair}; use iroha_data_model::{block::*, events::pipeline::PipelineEventBox, peer::PeerId}; use iroha_p2p::UpdateTopology; use tracing::{span, Level}; @@ -41,7 +41,7 @@ pub struct Sumeragi { /// is the proxy tail. pub debug_force_soft_fork: bool, /// The current network topology. - pub current_topology: Topology, + pub topology: Topology, /// In order to *be fast*, we must minimize communication with /// other subsystems where we can. This way the performance of /// sumeragi is more dependent on the code that is internal to the @@ -78,11 +78,11 @@ impl Sumeragi { self.network.post(post); } - #[allow(clippy::needless_pass_by_value, single_use_lifetimes)] // TODO: uncomment when anonymous lifetimes are stable - fn broadcast_packet_to<'peer_id>( + #[allow(clippy::needless_pass_by_value)] + fn broadcast_packet_to<'peer_id, I: IntoIterator + Send>( &self, msg: impl Into, - ids: impl IntoIterator + Send, + ids: I, ) { let msg = msg.into(); @@ -107,7 +107,7 @@ impl Sumeragi { /// Connect or disconnect peers according to the current network topology. fn connect_peers(&self, topology: &Topology) { - let peers = topology.ordered_peers.clone().into_iter().collect(); + let peers = topology.iter().cloned().collect(); self.network.update_topology(UpdateTopology(peers)); } @@ -155,30 +155,28 @@ impl Sumeragi { should_sleep = false; if let Err(error) = view_change_proof_chain.merge( msg.view_change_proofs, - &self.current_topology.ordered_peers, - self.current_topology.max_faults(), + &self.topology, state_view.latest_block_hash(), ) { trace!(%error, "Failed to add proofs into view change proof chain") } - let current_view_change_index = view_change_proof_chain.verify_with_state( - &self.current_topology.ordered_peers, - self.current_topology.max_faults(), + let curr_view_change_index = view_change_proof_chain.verify_with_state( + &self.topology, state_view.latest_block_hash(), - ) as u64; + ); let mut should_prune = false; if let Some(msg) = block_msg.as_ref() { - let vc_index : Option = match msg { + let vc_index = match msg { BlockMessage::BlockCreated(bc) => Some(bc.block.header().view_change_index), // Signed and Committed contain no block. // Block sync updates are exempt from early pruning. BlockMessage::BlockSigned(_) | BlockMessage::BlockCommitted(_) | BlockMessage::BlockSyncUpdate(_) => None, }; if let Some(vc_index) = vc_index { - if vc_index < current_view_change_index { + if (vc_index as usize) < curr_view_change_index { should_prune = true; } } @@ -228,7 +226,7 @@ impl Sumeragi { let mut state_block = state.block(); let block = match ValidBlock::validate( block, - &self.current_topology, + &self.topology, &self.chain_id, genesis_public_key, &mut state_block, @@ -236,7 +234,7 @@ impl Sumeragi { .unpack(|e| self.send_event(e)) .and_then(|block| { block - .commit(&self.current_topology) + .commit(&self.topology) .unpack(|e| self.send_event(e)) .map_err(|(block, error)| (block.into(), error)) }) { @@ -247,7 +245,8 @@ impl Sumeragi { } }; - *state_block.world.trusted_peers_ids = block.as_ref().commit_topology().clone(); + *state_block.world.trusted_peers_ids = + block.as_ref().commit_topology().cloned().collect(); self.commit_block(block, state_block); return Err(EarlyReturn::GenesisBlockReceivedAndCommitted); } @@ -279,15 +278,15 @@ impl Sumeragi { .expect("Genesis invalid"); let mut state_block = state.block(); - let genesis = BlockBuilder::new(transactions, self.current_topology.clone(), vec![]) + let genesis = BlockBuilder::new(transactions, self.topology.clone(), vec![]) .chain(0, &mut state_block) - .sign(&self.key_pair) + .sign(self.key_pair.private_key()) .unpack(|e| self.send_event(e)); let genesis_msg = BlockCreated::from(genesis.clone()); let genesis = genesis - .commit(&self.current_topology) + .commit(&self.topology) .unpack(|e| self.send_event(e)) .expect("Genesis invalid"); @@ -297,7 +296,7 @@ impl Sumeragi { ); info!( - role = ?self.current_topology.role(&self.peer_id), + role = ?self.topology.role(&self.peer_id), block_hash = %genesis.as_ref().hash(), "Genesis block created", ); @@ -321,7 +320,7 @@ impl Sumeragi { ) { info!( addr=%self.peer_id.address, - role=%self.current_topology.role(&self.peer_id), + role=%self.topology.role(&self.peer_id), block_height=%block.as_ref().header().height, block_hash=%block.as_ref().hash(), "{}", Strategy::LOG_MESSAGE, @@ -329,26 +328,18 @@ impl Sumeragi { let state_events = state_block.apply_without_execution(&block); - let new_topology = Topology::recreate_topology( - block.as_ref(), - 0, - state_block.world.peers().cloned().collect(), - ); - - // https://github.com/hyperledger/iroha/issues/3396 - // Kura should store the block only upon successful application to the internal state to avoid storing a corrupted block. - // Public-facing state update should happen after that and be followed by `BlockCommited` event to prevent client access to uncommitted data. - Strategy::kura_store_block(&self.kura, block); - // Parameters are updated before updating public copy of sumeragi self.update_params(&state_block); self.cache_transaction(&state_block); - self.current_topology = new_topology; - self.connect_peers(&self.current_topology); + self.topology + .block_committed(block.as_ref(), state_block.world.peers().cloned()); + self.connect_peers(&self.topology); // Commit new block making it's effect visible for the rest of application state_block.commit(); + Strategy::kura_store_block(&self.kura, block); + // NOTE: This sends "Block committed" event, // so it should be done AFTER public facing state update state_events.into_iter().for_each(|e| self.send_event(e)); @@ -386,11 +377,11 @@ impl Sumeragi { ) -> Option> { let block_hash = block.hash(); let addr = &self.peer_id.address; - let role = self.current_topology.role(&self.peer_id); + let role = self.topology.role(&self.peer_id); trace!(%addr, %role, block=%block_hash, "Block received, voting..."); let mut state_block = state.block(); - let block = match ValidBlock::validate( + let mut block = match ValidBlock::validate( block, topology, &self.chain_id, @@ -406,21 +397,21 @@ impl Sumeragi { } }; - let signed_block = block.sign(&self.key_pair); - Some(VotingBlock::new(signed_block, state_block)) + // NOTE: Proxy tail has to be the last to sign the block + if self.topology.role(&self.peer_id) == Role::ProxyTail { + block.sign(&self.key_pair, topology); + }; + + Some(VotingBlock::new(block, state_block)) } fn prune_view_change_proofs_and_calculate_current_index( &self, state_view: &StateView<'_>, view_change_proof_chain: &mut ProofChain, - ) -> u64 { + ) -> usize { view_change_proof_chain.prune(state_view.latest_block_hash()); - view_change_proof_chain.verify_with_state( - &self.current_topology.ordered_peers, - self.current_topology.max_faults(), - state_view.latest_block_hash(), - ) as u64 + view_change_proof_chain.verify_with_state(&self.topology, state_view.latest_block_hash()) } #[allow(clippy::too_many_lines)] @@ -429,13 +420,14 @@ impl Sumeragi { message: BlockMessage, state: &'state State, voting_block: &mut Option>, - current_view_change_index: u64, + curr_view_change_index: usize, genesis_public_key: &PublicKey, - voting_signatures: &mut Vec>, + voting_signatures: &mut Vec, + #[cfg_attr(not(debug_assertions), allow(unused_variables))] is_genesis_peer: bool, ) { - let current_topology = &self.current_topology; - let role = current_topology.role(&self.peer_id); let addr = &self.peer_id.address; + let topology = &self.topology; + let role = topology.role(&self.peer_id); #[allow(clippy::suspicious_operation_groupings)] match (message, role) { @@ -444,8 +436,9 @@ impl Sumeragi { info!(%addr, %role, block=%block_hash, "Block sync update received"); // Release writer before handling block sync - let _ = voting_block.take(); - match handle_block_sync(&self.chain_id, genesis_public_key, block, state, &|e| { + let curr_block = voting_block.take(); + + match handle_block_sync(&self.chain_id, block, state, genesis_public_key, &|e| { self.send_event(e) }) { Ok(BlockSyncOk::CommitBlock(block, state_block)) => { @@ -494,147 +487,151 @@ impl Sumeragi { warn!(%addr, %role, %block_hash, %block_height, %peer_height, "Other peer send irrelevant or outdated block to the peer (it's neither `peer_height` nor `peer_height + 1`).") } } + + *voting_block = curr_block; } ( - BlockMessage::BlockCommitted(BlockCommitted { hash, signatures }), - Role::Leader | Role::ValidatingPeer | Role::ProxyTail | Role::ObservingPeer, + BlockMessage::BlockCommitted(BlockCommitted { signatures }), + Role::Leader | Role::ValidatingPeer | Role::ObservingPeer | Role::ProxyTail, ) => { - let is_consensus_required = current_topology.is_consensus_required().is_some(); - if role == Role::ProxyTail && is_consensus_required - || role == Role::Leader && !is_consensus_required - { + if role == Role::Leader && topology.is_consensus_required().is_none() { error!(%addr, %role, "Received BlockCommitted message, but shouldn't"); - } else if let Some(voted_block) = voting_block.take() { + } else if let Some(mut voted_block) = voting_block.take() { match voted_block .block - .commit_with_signatures(current_topology, signatures, hash) + // NOTE: 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. + .replace_signatures(signatures, topology) .unpack(|e| self.send_event(e)) { - Ok(committed_block) => { - self.commit_block(committed_block, voted_block.state_block) - } - Err(( - valid_block, - BlockValidationError::IncorrectHash { expected, actual }, - )) => { - error!(%addr, %role, %expected, %actual, "The hash of the committed block does not match the hash of the block stored by the peer."); - - *voting_block = Some(VotingBlock { - voted_at: voted_block.voted_at, - block: valid_block, - state_block: voted_block.state_block, - }); + Ok(prev_signatures) => { + match voted_block + .block + .commit(topology) + .unpack(|e| self.send_event(e)) + { + Ok(committed_block) => { + self.commit_block(committed_block, voted_block.state_block) + } + Err((mut block, error)) => { + error!(%addr, %role, ?error, "Block failed to be committed"); + + block + .replace_signatures(prev_signatures, topology) + .unpack(|e| self.send_event(e)) + .expect("Can't fail"); + voted_block.block = block; + } + } } - Err((_, error)) => { - error!(%addr, %role, %hash, ?error, "Block failed to be committed") + Err(error) => { + error!(%addr, %role, ?error, "Received incorrect signatures"); } } } else { - error!(%addr, %role, %hash, "Peer missing voting block") + error!(%addr, %role, "Peer missing voting block") } } (BlockMessage::BlockCreated(block_created), Role::ValidatingPeer) => { - let current_topology = current_topology + let topology = topology .is_consensus_required() .expect("Peer has `ValidatingPeer` role, which mean that current topology require consensus"); // Release block writer before creating a new one - let _ = voting_block.take(); - if let Some(v_block) = - self.vote_for_block(state, ¤t_topology, genesis_public_key, block_created) + let curr_block = voting_block.take(); + + *voting_block = if let Some(v_block) = + self.vote_for_block(state, &topology, genesis_public_key, block_created) { let block_hash = v_block.block.as_ref().hash(); let msg = BlockSigned::from(&v_block.block); - self.broadcast_packet_to(msg, [current_topology.proxy_tail()]); + self.broadcast_packet_to(msg, [topology.proxy_tail()]); info!(%addr, block=%block_hash, "Block validated, signed and forwarded"); - *voting_block = Some(v_block); - } + Some(v_block) + } else { + curr_block + }; } (BlockMessage::BlockCreated(BlockCreated { block }), Role::ObservingPeer) => { - let current_topology = current_topology.is_consensus_required().expect( + let current_topology = topology.is_consensus_required().expect( "Peer has `ObservingPeer` role, which mean that current topology require consensus" ); - // Release block writer before creating new one - let _ = voting_block.take(); + let block_hash = block.hash_of_payload(); + let role = self.topology.role(&self.peer_id); + trace!(%addr, %role, %block_hash, "Block received, voting..."); - let v_block = { - let block_hash = block.hash_of_payload(); - let role = self.current_topology.role(&self.peer_id); - trace!(%addr, %role, %block_hash, "Block received, voting..."); + let mut state_block = state.block(); - let mut state_block = state.block(); - match ValidBlock::validate( - block, - ¤t_topology, - &self.chain_id, - genesis_public_key, - &mut state_block, - ) - .unpack(|e| self.send_event(e)) - { - Ok(block) => { - let block = if current_view_change_index >= 1 { - block.sign(&self.key_pair) - } else { - block - }; - - Some(VotingBlock::new(block, state_block)) - } - Err((_, error)) => { - warn!(%addr, %role, ?error, "Block validation failed"); - None + // Release block writer before creating new one + let curr_block = voting_block.take(); + *voting_block = match ValidBlock::validate( + block, + topology, + &self.chain_id, + genesis_public_key, + &mut state_block, + ) + .unpack(|e| self.send_event(e)) + { + Ok(mut block) => { + if curr_view_change_index >= 1 { + block.sign(&self.key_pair, topology); + }; + + let new_block = VotingBlock::new(block, state_block); + let block_hash = new_block.block.as_ref().hash(); + info!(%addr, %block_hash, "Block validated"); + if curr_view_change_index >= 1 { + self.broadcast_packet_to( + BlockSigned::from(&new_block.block), + [current_topology.proxy_tail()], + ); + info!(%addr, block=%block_hash, "Block signed and forwarded"); } + Some(new_block) } - }; - - if let Some(v_block) = v_block { - let block_hash = v_block.block.as_ref().hash(); - info!(%addr, %block_hash, "Block validated"); - if current_view_change_index >= 1 { - self.broadcast_packet_to( - BlockSigned::from(&v_block.block), - [current_topology.proxy_tail()], - ); - info!(%addr, block=%block_hash, "Block signed and forwarded"); + Err((_, error)) => { + warn!(%addr, %role, ?error, "Block validation failed"); + curr_block } - *voting_block = Some(v_block); - } + }; } (BlockMessage::BlockCreated(block_created), Role::ProxyTail) => { + info!(%addr, %role, block=%block_created.block.hash(), "Block received"); + // Release block writer before creating new one - let _ = voting_block.take(); - if let Some(mut new_block) = - self.vote_for_block(state, current_topology, genesis_public_key, 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 - add_signatures::(&mut new_block, voting_signatures.drain(..)); - *voting_block = Some(new_block); - } + let prev_voting_block = voting_block.take(); + + *voting_block = self + .vote_for_block(state, topology, genesis_public_key, block_created) + .map_or(prev_voting_block, |mut new_block| { + // 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 + add_signatures::( + &mut new_block, + voting_signatures.drain(..), + &self.topology, + ); + + self.try_commit_block(new_block, is_genesis_peer) + }); } - (BlockMessage::BlockSigned(BlockSigned { hash, signatures }), Role::ProxyTail) => { - trace!(block_hash=%hash, "Received block signatures"); + (BlockMessage::BlockSigned(BlockSigned { signatures }), Role::ProxyTail) => { + info!(%addr, %role, "Received block signatures"); - let roles: &[Role] = if current_view_change_index >= 1 { + let roles: &[Role] = if curr_view_change_index >= 1 { &[Role::ValidatingPeer, Role::ObservingPeer] } else { &[Role::ValidatingPeer] }; - let valid_signatures = - current_topology.filter_signatures_by_roles(roles, &signatures); + let valid_signatures = topology.filter_signatures_by_roles(roles, &signatures); - if let Some(voted_block) = voting_block.as_mut() { - let voting_block_hash = voted_block.block.as_ref().hash_of_payload(); - - if hash == voting_block_hash { - add_signatures::(voted_block, valid_signatures); - } else { - debug!(%voting_block_hash, "Received signatures are not for the current block"); - } + if let Some(mut voted_block) = voting_block.take() { + add_signatures::(&mut voted_block, valid_signatures, topology); + *voting_block = self.try_commit_block(voted_block, is_genesis_peer); } else { // NOTE: Due to the nature of distributed systems, signatures can sometimes be received before // the block (sent by the leader). Collect the signatures and wait for the block to be received @@ -647,107 +644,100 @@ impl Sumeragi { } } + /// Commits block if there are enough votes + fn try_commit_block<'state>( + &mut self, + mut voted_block: VotingBlock<'state>, + #[cfg_attr(not(debug_assertions), allow(unused_variables))] is_genesis_peer: bool, + ) -> Option> { + let topology = &self.topology; + assert_eq!(topology.role(&self.peer_id), Role::ProxyTail); + let votes_count = voted_block.block.as_ref().signatures().len(); + if votes_count + 1 >= topology.min_votes_for_commit() { + info!(block=%voted_block.block.as_ref().hash(), "Block reached required number of votes"); + voted_block.block.sign(&self.key_pair, topology); + + let committed_block = voted_block + .block + .commit(topology) + .unpack(|e| self.send_event(e)) + .unwrap(); + + let msg = BlockCommitted::from(&committed_block); + + #[cfg(debug_assertions)] + if is_genesis_peer && self.debug_force_soft_fork { + std::thread::sleep(self.pipeline_time() * 2); + } else { + self.broadcast_packet(msg); + } + + #[cfg(not(debug_assertions))] + { + self.broadcast_packet(msg); + } + + let state_block = voted_block.state_block; + self.commit_block(committed_block, state_block); + + return None; + } + + Some(voted_block) + } + #[allow(clippy::too_many_lines)] fn process_message_independent<'state>( &mut self, state: &'state State, voting_block: &mut Option>, - current_view_change_index: u64, + view_change_index: usize, round_start_time: &Instant, - #[cfg_attr(not(debug_assertions), allow(unused_variables))] is_genesis_peer: bool, ) { - let current_topology = &self.current_topology; - let role = current_topology.role(&self.peer_id); let addr = &self.peer_id.address; - - match role { - Role::Leader => { - if voting_block.is_none() { - let cache_full = self.transaction_cache.len() >= self.max_txs_in_block; - let deadline_reached = round_start_time.elapsed() > self.block_time; - let cache_non_empty = !self.transaction_cache.is_empty(); - - if cache_full || (deadline_reached && cache_non_empty) { - let transactions = self.transaction_cache.clone(); - info!(%addr, txns=%transactions.len(), "Creating block..."); - let create_block_start_time = Instant::now(); - - // TODO: properly process triggers! - let mut state_block = state.block(); - let event_recommendations = Vec::new(); - let new_block = BlockBuilder::new( - transactions, - self.current_topology.clone(), - event_recommendations, - ) - .chain(current_view_change_index, &mut state_block) - .sign(&self.key_pair) + let topology = &self.topology; + let role = topology.role(&self.peer_id); + + if role == Role::Leader && voting_block.is_none() { + let cache_full = self.transaction_cache.len() >= self.max_txs_in_block; + let deadline_reached = round_start_time.elapsed() > self.block_time; + let cache_non_empty = !self.transaction_cache.is_empty(); + + if cache_full || (deadline_reached && cache_non_empty) { + let transactions = self.transaction_cache.clone(); + info!(%addr, txns=%transactions.len(), "Creating block..."); + let create_block_start_time = Instant::now(); + + // TODO: properly process triggers! + let mut state_block = state.block(); + let event_recommendations = Vec::new(); + let new_block = + BlockBuilder::new(transactions, self.topology.clone(), event_recommendations) + .chain(view_change_index, &mut state_block) + .sign(self.key_pair.private_key()) .unpack(|e| self.send_event(e)); - let created_in = create_block_start_time.elapsed(); - if current_topology.is_consensus_required().is_some() { - info!(%addr, created_in_ms=%created_in.as_millis(), block=%new_block.as_ref().hash(), "Block created"); - - if created_in > self.pipeline_time() / 2 { - warn!("Creating block takes too much time. This might prevent consensus from operating. Consider increasing `commit_time` or decreasing `max_transactions_in_block`"); - } - *voting_block = Some(VotingBlock::new(new_block.clone(), state_block)); + let created_in = create_block_start_time.elapsed(); + if topology.is_consensus_required().is_some() { + info!(%addr, created_in_ms=%created_in.as_millis(), block=%new_block.as_ref().hash(), "Block created"); - let msg = BlockCreated::from(new_block); - self.broadcast_packet(msg); - } else { - match new_block - .commit(current_topology) - .unpack(|e| self.send_event(e)) - { - Ok(committed_block) => { - self.broadcast_packet(BlockCommitted::from(&committed_block)); - self.commit_block(committed_block, state_block); - } - Err(error) => error!(%addr, role=%Role::Leader, ?error), - }; - } + if created_in > self.pipeline_time() / 2 { + warn!("Creating block takes too much time. This might prevent consensus from operating. Consider increasing `commit_time` or decreasing `max_transactions_in_block`"); } - } - } - Role::ProxyTail => { - if let Some(voted_block) = voting_block.take() { - let voted_at = voted_block.voted_at; - let state_block = voted_block.state_block; + *voting_block = Some(VotingBlock::new(new_block.clone(), state_block)); - match voted_block - .block - .commit(current_topology) - .unpack(|e| self.send_event(e)) - { + let msg = BlockCreated::from(new_block); + self.broadcast_packet(msg); + } else { + match new_block.commit(topology).unpack(|e| self.send_event(e)) { Ok(committed_block) => { - info!(block=%committed_block.as_ref().hash(), "Block reached required number of votes"); - - let msg = BlockCommitted::from(&committed_block); - - #[cfg(debug_assertions)] - if is_genesis_peer && self.debug_force_soft_fork { - std::thread::sleep(self.pipeline_time() * 2); - } else { - self.broadcast_packet(msg); - } - - #[cfg(not(debug_assertions))] - { - self.broadcast_packet(msg); - } + self.broadcast_packet(BlockCommitted::from(&committed_block)); self.commit_block(committed_block, state_block); } - Err((block, error)) => { - // Restore the current voting block and continue the round - *voting_block = - Some(VotingBlock::voted_at(block, state_block, voted_at)); - trace!(?error, "Not enough signatures, waiting for more..."); - } - } + Err(error) => error!(%addr, role=%Role::Leader, ?error), + }; } } - _ => {} } } } @@ -756,49 +746,43 @@ impl Sumeragi { fn reset_state( peer_id: &PeerId, pipeline_time: Duration, - current_view_change_index: u64, - old_view_change_index: &mut u64, + curr_view_change_index: usize, + prev_view_change_index: &mut usize, old_latest_block_hash: &mut HashOf, latest_block: &SignedBlock, - // below is the state that gets reset. - current_topology: &mut Topology, + 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, ) { let mut was_commit_or_view_change = false; - let current_latest_block_hash = latest_block.hash(); - if current_latest_block_hash != *old_latest_block_hash { + let curr_latest_block_hash = latest_block.hash(); + if curr_latest_block_hash != *old_latest_block_hash { // Round is only restarted on a block commit, so that in the case of // a view change a new block is immediately created by the leader *round_start_time = Instant::now(); was_commit_or_view_change = true; - *old_view_change_index = 0; + *prev_view_change_index = 0; } - if *old_view_change_index < current_view_change_index { + if *prev_view_change_index < curr_view_change_index { error!(addr=%peer_id.address, "Rotating the entire topology."); - *old_view_change_index = current_view_change_index; + topology.rotate_all_n(curr_view_change_index - *prev_view_change_index); + *prev_view_change_index = curr_view_change_index; was_commit_or_view_change = true; } // Reset state for the next round. if was_commit_or_view_change { - *old_latest_block_hash = current_latest_block_hash; - - *current_topology = Topology::recreate_topology( - latest_block, - current_view_change_index, - current_topology.ordered_peers.iter().cloned().collect(), - ); + *old_latest_block_hash = curr_latest_block_hash; *voting_block = None; voting_signatures.clear(); *last_view_change_time = Instant::now(); *view_change_time = pipeline_time; - info!(addr=%peer_id.address, role=%current_topology.role(peer_id), %current_view_change_index, "View change updated"); + info!(addr=%peer_id.address, role=%topology.role(peer_id), %curr_view_change_index, "View change updated"); } } @@ -823,7 +807,7 @@ pub(crate) fn run( state: Arc, ) { // Connect peers with initial topology - sumeragi.connect_peers(&sumeragi.current_topology); + sumeragi.connect_peers(&sumeragi.topology); let span = span!(tracing::Level::TRACE, "genesis").entered(); let is_genesis_peer = if state.view().height() == 0 @@ -849,7 +833,7 @@ pub(crate) fn run( info!( addr=%sumeragi.peer_id.address, - role_in_next_round=%sumeragi.current_topology.role(&sumeragi.peer_id), + role_in_next_round=%sumeragi.topology.role(&sumeragi.peer_id), "Sumeragi initialized", ); @@ -858,7 +842,7 @@ pub(crate) fn run( let mut voting_signatures = Vec::new(); let mut should_sleep = false; let mut view_change_proof_chain = ProofChain::default(); - let mut old_view_change_index = 0; + let mut prev_view_change_index = 0; let mut old_latest_block_hash = state .view() .latest_block_ref() @@ -899,29 +883,30 @@ pub(crate) fn run( &mut sumeragi.transaction_cache, ); - let current_view_change_index = sumeragi - .prune_view_change_proofs_and_calculate_current_index( - &state_view, - &mut view_change_proof_chain, - ); + let curr_view_change_index = sumeragi.prune_view_change_proofs_and_calculate_current_index( + &state_view, + &mut view_change_proof_chain, + ); reset_state( &sumeragi.peer_id, sumeragi.pipeline_time(), - current_view_change_index, - &mut old_view_change_index, + curr_view_change_index, + &mut prev_view_change_index, &mut old_latest_block_hash, &state_view .latest_block_ref() .expect("state must have blocks"), - &mut sumeragi.current_topology, + &mut sumeragi.topology, &mut voting_block, &mut voting_signatures, &mut round_start_time, &mut last_view_change_time, &mut view_change_time, ); - sumeragi.view_changes_metric.set(old_view_change_index); + sumeragi + .view_changes_metric + .set(prev_view_change_index as u64); if let Some(message) = { let (msg, sleep) = @@ -933,26 +918,26 @@ pub(crate) fn run( message, &state, &mut voting_block, - current_view_change_index, + curr_view_change_index, &genesis_network.public_key, &mut voting_signatures, + is_genesis_peer, ); } // State could be changed after handling message so it is necessary to reset state before handling message independent step let state_view = state.view(); - let current_view_change_index = sumeragi - .prune_view_change_proofs_and_calculate_current_index( - &state_view, - &mut view_change_proof_chain, - ); + let curr_view_change_index = sumeragi.prune_view_change_proofs_and_calculate_current_index( + &state_view, + &mut view_change_proof_chain, + ); // We broadcast our view change suggestion after having processed the latest from others inside `receive_network_packet` let node_expects_block = !sumeragi.transaction_cache.is_empty(); - if (node_expects_block || current_view_change_index > 0) + if (node_expects_block || curr_view_change_index > 0) && last_view_change_time.elapsed() > view_change_time { - let role = sumeragi.current_topology.role(&sumeragi.peer_id); + let role = sumeragi.topology.role(&sumeragi.peer_id); if node_expects_block { if let Some(VotingBlock { block, .. }) = voting_block.as_ref() { @@ -964,16 +949,21 @@ pub(crate) fn run( warn!(peer_public_key=%sumeragi.peer_id.public_key, %role, "No block produced in due time, requesting view change..."); } - let suspect_proof = - ProofBuilder::new(state_view.latest_block_hash(), current_view_change_index) - .sign(&sumeragi.key_pair); + let suspect_proof = { + let signatory_idx = sumeragi + .topology + .position(sumeragi.peer_id.public_key()) + .expect("BUG: Node is not part of consensus"); + + ProofBuilder::new(state_view.latest_block_hash(), curr_view_change_index) + .sign(signatory_idx, sumeragi.key_pair.private_key()) + }; view_change_proof_chain .insert_proof( - &sumeragi.current_topology.ordered_peers, - sumeragi.current_topology.max_faults(), - state_view.latest_block_hash(), suspect_proof, + &sumeragi.topology, + state_view.latest_block_hash(), ) .unwrap_or_else(|err| error!("{err}")); } @@ -989,37 +979,39 @@ pub(crate) fn run( reset_state( &sumeragi.peer_id, sumeragi.pipeline_time(), - current_view_change_index, - &mut old_view_change_index, + curr_view_change_index, + &mut prev_view_change_index, &mut old_latest_block_hash, &state_view .latest_block_ref() .expect("state must have blocks"), - &mut sumeragi.current_topology, + &mut sumeragi.topology, &mut voting_block, &mut voting_signatures, &mut round_start_time, &mut last_view_change_time, &mut view_change_time, ); - sumeragi.view_changes_metric.set(old_view_change_index); + sumeragi + .view_changes_metric + .set(prev_view_change_index as u64); sumeragi.process_message_independent( &state, &mut voting_block, - current_view_change_index, + curr_view_change_index, &round_start_time, - is_genesis_peer, ); } } fn add_signatures( block: &mut VotingBlock, - signatures: impl IntoIterator>, + signatures: impl IntoIterator, + topology: &Topology, ) { for signature in signatures { - if let Err(error) = block.block.add_signature(signature) { + if let Err(error) = block.block.add_signature(signature, topology) { let err_msg = "Signature not valid"; if EXPECT_VALID { @@ -1101,8 +1093,8 @@ enum BlockSyncError { BlockNotValid(BlockValidationError), SoftForkBlockNotValid(BlockValidationError), SoftForkBlockSmallViewChangeIndex { - peer_view_change_index: u64, - block_view_change_index: u64, + peer_view_change_index: u32, + block_view_change_index: u32, }, BlockNotProperHeight { peer_height: u64, @@ -1112,9 +1104,9 @@ enum BlockSyncError { fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( chain_id: &ChainId, - genesis_public_key: &PublicKey, block: SignedBlock, state: &'state State, + genesis_public_key: &PublicKey, handle_events: &F, ) -> Result, (SignedBlock, BlockSyncError)> { let block_height = block.header().height; @@ -1126,9 +1118,10 @@ fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( let last_committed_block = state_block .latest_block_ref() .expect("Not in genesis round so must have at least genesis block"); - let new_peers = state_block.world.peers().cloned().collect(); - let view_change_index = block.header().view_change_index; - Topology::recreate_topology(&last_committed_block, view_change_index, new_peers) + let mut topology = Topology::new(last_committed_block.commit_topology().cloned()); + topology.block_committed(&last_committed_block, state_block.world.peers().cloned()); + topology.rotate_all_n(block.header().view_change_index as usize); + topology }; ValidBlock::validate( block, @@ -1167,9 +1160,10 @@ fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( let last_committed_block = state_block .latest_block_ref() .expect("Not in genesis round so must have at least genesis block"); - let new_peers = state_block.world.peers().cloned().collect(); - let view_change_index = block.header().view_change_index; - Topology::recreate_topology(&last_committed_block, view_change_index, new_peers) + let mut topology = Topology::new(last_committed_block.commit_topology().cloned()); + topology.block_committed(&last_committed_block, state_block.world.peers().cloned()); + topology.rotate_all_n(block.header().view_change_index as usize); + topology }; ValidBlock::validate( block, @@ -1201,7 +1195,6 @@ fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( #[cfg(test)] mod tests { - use iroha_primitives::{unique_vec, unique_vec::UniqueVec}; use tokio::test; use super::*; @@ -1216,7 +1209,7 @@ mod tests { fn create_data_for_test( chain_id: &ChainId, topology: &Topology, - leader_key_pair: &KeyPair, + leader_private_key: &PrivateKey, tx_signer_key_pair: &KeyPair, ) -> (State, Arc, SignedBlock) { // Predefined world state @@ -1226,7 +1219,7 @@ mod tests { let domain_id = "wonderland".parse().expect("Valid"); let mut domain = Domain::new(domain_id).build(&alice_id); assert!(domain.add_account(account).is_none()); - let world = World::with([domain], topology.ordered_peers.clone()); + let world = World::with([domain], topology.iter().cloned().collect()); let kura = Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(world, Arc::clone(&kura), query_handle); @@ -1250,7 +1243,7 @@ mod tests { // Creating a block of two identical transactions and validating it let block = BlockBuilder::new(vec![tx.clone(), tx], topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(leader_key_pair) + .sign(leader_private_key) .unpack(|_| {}); let genesis = block @@ -1295,7 +1288,7 @@ mod tests { // Creating a block of two identical transactions and validating it BlockBuilder::new(vec![tx1, tx2], topology.clone(), Vec::new()) .chain(0, &mut state_block) - .sign(leader_key_pair) + .sign(leader_private_key) .unpack(|_| {}) }; @@ -1308,22 +1301,24 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let leader_key_pair = KeyPair::random(); - let topology = Topology::new(unique_vec![PeerId::new( - "127.0.0.1:8080".parse().unwrap(), - leader_key_pair.public_key().clone(), - )]); - let (state, _, mut block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, _, mut block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); // Malform block to make it invalid payload_mut(&mut block).commit_topology.clear(); let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!(result, Err((_, BlockSyncError::BlockNotValid(_))))) @@ -1334,13 +1329,15 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let leader_key_pair = KeyPair::random(); - let topology = Topology::new(unique_vec![PeerId::new( - "127.0.0.1:8080".parse().unwrap(), - leader_key_pair.public_key().clone(), - )]); - let (state, kura, mut block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, kura, mut block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); let mut state_block = state.block(); let committed_block = ValidBlock::validate( @@ -1365,9 +1362,9 @@ mod tests { let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!( @@ -1382,19 +1379,24 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let topology = Topology::new(UniqueVec::new()); - let leader_key_pair = KeyPair::random(); - let (state, _, mut block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, _, mut block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); // Change block height payload_mut(&mut block).header.height = 42; let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!( @@ -1415,18 +1417,20 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let leader_key_pair = KeyPair::random(); - let topology = Topology::new(unique_vec![PeerId::new( - "127.0.0.1:8080".parse().unwrap(), - leader_key_pair.public_key().clone(), - )]); - let (state, _, block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, _, block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!(result, Ok(BlockSyncOk::CommitBlock(_, _)))) @@ -1437,13 +1441,15 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let leader_key_pair = KeyPair::random(); - let topology = Topology::new(unique_vec![PeerId::new( - "127.0.0.1:8080".parse().unwrap(), - leader_key_pair.public_key().clone(), - )]); - let (state, kura, mut block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, kura, mut block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); let mut state_block = state.block(); let committed_block = ValidBlock::validate( @@ -1469,9 +1475,9 @@ mod tests { let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!(result, Ok(BlockSyncOk::ReplaceTopBlock(_, _)))) @@ -1482,13 +1488,15 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let leader_key_pair = KeyPair::random(); - let topology = Topology::new(unique_vec![PeerId::new( - "127.0.0.1:8080".parse().unwrap(), - leader_key_pair.public_key().clone(), - )]); - let (state, kura, mut block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, kura, mut block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); // Increase block view change index payload_mut(&mut block).header.view_change_index = 42; @@ -1516,9 +1524,9 @@ mod tests { let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!( @@ -1539,10 +1547,15 @@ mod tests { let chain_id = ChainId::from("0"); let tx_signer_key_pair = KeyPair::random(); - let topology = Topology::new(UniqueVec::new()); - let leader_key_pair = KeyPair::random(); - let (state, _, mut block) = - create_data_for_test(&chain_id, &topology, &leader_key_pair, &tx_signer_key_pair); + let (leader_public_key, leader_private_key) = KeyPair::random().into_parts(); + let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), leader_public_key); + let topology = Topology::new(vec![peer_id]); + let (state, _, mut block) = create_data_for_test( + &chain_id, + &topology, + &leader_private_key, + &tx_signer_key_pair, + ); // Change block height and view change index // Soft-fork on genesis block is not possible @@ -1551,9 +1564,9 @@ mod tests { let result = handle_block_sync( &chain_id, - tx_signer_key_pair.public_key(), block, &state, + tx_signer_key_pair.public_key(), &|_| {}, ); assert!(matches!( diff --git a/core/src/sumeragi/message.rs b/core/src/sumeragi/message.rs index c5d4fa27fa7..bcbe44fbd85 100644 --- a/core/src/sumeragi/message.rs +++ b/core/src/sumeragi/message.rs @@ -1,6 +1,5 @@ //! Contains message structures for p2p communication during consensus. -use iroha_crypto::{HashOf, SignaturesOf}; -use iroha_data_model::block::{BlockPayload, SignedBlock}; +use iroha_data_model::block::{BlockSignature, SignedBlock}; use iroha_macro::*; use parity_scale_codec::{Decode, Encode}; @@ -56,20 +55,14 @@ impl From for BlockCreated { #[derive(Debug, Clone, Decode, Encode)] #[non_exhaustive] pub struct BlockSigned { - /// Hash of the block being signed. - pub hash: HashOf, /// Set of signatures. - pub signatures: SignaturesOf, + pub signatures: Vec, } impl From<&ValidBlock> for BlockSigned { fn from(block: &ValidBlock) -> Self { - let block_hash = block.as_ref().hash_of_payload(); - let block_signatures = block.as_ref().signatures().clone(); - Self { - hash: block_hash, - signatures: block_signatures, + signatures: block.as_ref().signatures().cloned().collect(), } } } @@ -78,20 +71,14 @@ impl From<&ValidBlock> for BlockSigned { #[derive(Debug, Clone, Decode, Encode)] #[non_exhaustive] pub struct BlockCommitted { - /// Hash of the block being signed. - pub hash: HashOf, /// Set of signatures. - pub signatures: SignaturesOf, + pub signatures: Vec, } impl From<&CommittedBlock> for BlockCommitted { fn from(block: &CommittedBlock) -> Self { - let block_hash = block.as_ref().hash(); - let block_signatures = block.as_ref().signatures().clone(); - Self { - hash: block_hash, - signatures: block_signatures, + signatures: block.as_ref().signatures().cloned().collect(), } } } diff --git a/core/src/sumeragi/mod.rs b/core/src/sumeragi/mod.rs index 92c441f17dd..644fc3b3272 100644 --- a/core/src/sumeragi/mod.rs +++ b/core/src/sumeragi/mod.rs @@ -9,7 +9,6 @@ use std::{ use eyre::Result; use iroha_config::parameters::actual::{Common as CommonConfig, Sumeragi as SumeragiConfig}; -use iroha_crypto::{KeyPair, SignatureOf}; use iroha_data_model::{block::SignedBlock, prelude::*}; use iroha_genesis::GenesisNetwork; use iroha_logger::prelude::*; @@ -72,14 +71,14 @@ impl SumeragiHandle { block: &SignedBlock, state_block: &mut StateBlock<'_>, events_sender: &EventsSender, - mut current_topology: Topology, - ) -> Topology { + topology: &mut Topology, + ) { // NOTE: topology need to be updated up to block's view_change_index - current_topology.rotate_all_n(block.header().view_change_index); + topology.rotate_all_n(block.header().view_change_index as usize); let block = ValidBlock::validate( block.clone(), - ¤t_topology, + topology, chain_id, genesis_public_key, state_block, @@ -88,14 +87,15 @@ impl SumeragiHandle { let _ = events_sender.send(e.into()); }) .expect("Kura: Invalid block") - .commit(¤t_topology) + .commit(topology) .unpack(|e| { let _ = events_sender.send(e.into()); }) .expect("Kura: Invalid block"); if block.as_ref().header().is_genesis() { - *state_block.world.trusted_peers_ids = block.as_ref().commit_topology().clone(); + *state_block.world.trusted_peers_ids = + block.as_ref().commit_topology().cloned().collect(); } state_block @@ -105,11 +105,7 @@ impl SumeragiHandle { let _ = events_sender.send(e); }); - Topology::recreate_topology( - block.as_ref(), - 0, - state_block.world.peers().cloned().collect(), - ) + topology.block_committed(block.as_ref(), state_block.world.peers().cloned()); } /// Start [`Sumeragi`] actor and return handle to it. @@ -139,7 +135,7 @@ impl SumeragiHandle { let (message_sender, message_receiver) = mpsc::sync_channel(100); let blocks_iter; - let mut current_topology; + let mut topology; { let state_view = state.view(); @@ -151,7 +147,7 @@ impl SumeragiHandle { ) }); - current_topology = match state_view.height() { + topology = match state_view.height() { 0 => { assert!(!sumeragi_config.trusted_peers.is_empty()); Topology::new(sumeragi_config.trusted_peers.clone()) @@ -161,24 +157,23 @@ impl SumeragiHandle { "Sumeragi could not load block that was reported as present. \ Please check that the block storage was not disconnected.", ); - Topology::recreate_topology( - &block_ref, - 0, - state_view.world.peers_ids().iter().cloned().collect(), - ) + let mut topology = Topology::new(block_ref.commit_topology().cloned()); + topology + .block_committed(&block_ref, state_view.world.peers_ids().iter().cloned()); + topology } }; } for block in blocks_iter { let mut state_block = state.block(); - current_topology = Self::replay_block( + Self::replay_block( &common_config.chain_id, &genesis_network.public_key, &block, &mut state_block, &events_sender, - current_topology, + &mut topology, ); state_block.commit(); } @@ -190,7 +185,7 @@ impl SumeragiHandle { #[cfg(not(debug_assertions))] let debug_force_soft_fork = false; - let peer_id = common_config.peer_id(); + let peer_id = common_config.peer_id.clone(); let sumeragi = main_loop::Sumeragi { chain_id: common_config.chain_id, @@ -206,7 +201,7 @@ impl SumeragiHandle { control_message_receiver, message_receiver, debug_force_soft_fork, - current_topology, + topology, transaction_cache: Vec::new(), view_changes_metric: view_changes, }; @@ -274,18 +269,6 @@ impl VotingBlock<'_> { state_block, } } - /// Construct new `VotingBlock` with the given time. - pub(crate) fn voted_at( - block: ValidBlock, - state_block: StateBlock<'_>, - voted_at: Instant, - ) -> VotingBlock { - VotingBlock { - block, - voted_at, - state_block, - } - } } /// Arguments for [`SumeragiHandle::start`] function diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index dfa22fd9cc5..5ea4e1646ef 100644 --- a/core/src/sumeragi/network_topology.rs +++ b/core/src/sumeragi/network_topology.rs @@ -2,10 +2,11 @@ use derive_more::Display; use indexmap::IndexSet; -use iroha_crypto::{PublicKey, SignatureOf}; -use iroha_data_model::{block::SignedBlock, prelude::PeerId}; -use iroha_logger::trace; -use iroha_primitives::unique_vec::UniqueVec; +use iroha_crypto::PublicKey; +use iroha_data_model::{ + block::{BlockSignature, SignedBlock}, + prelude::PeerId, +}; /// The ordering of the peers which defines their roles in the current round of consensus. /// @@ -19,10 +20,7 @@ use iroha_primitives::unique_vec::UniqueVec; /// /// Above is an illustration of how the various operations work for a f = 2 topology. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Topology { - /// Current order of peers. The roles of peers are defined based on this order. - pub(crate) ordered_peers: UniqueVec, -} +pub struct Topology(Vec); /// Topology with at least one peer #[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)] @@ -36,32 +34,61 @@ pub struct ConsensusTopology<'topology> { topology: &'topology Topology, } +impl AsRef<[PeerId]> for Topology { + fn as_ref(&self) -> &[PeerId] { + &self.0 + } +} + +impl IntoIterator for Topology { + type Item = PeerId; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + impl Topology { /// Create a new topology. - pub fn new(peers: UniqueVec) -> Self { - Topology { - ordered_peers: peers, - } + pub fn new(peers: impl IntoIterator) -> Self { + let topology = peers.into_iter().collect::>(); + + assert!( + !topology.is_empty(), + "Topology must contain at least one peer" + ); + + Topology(topology.into_iter().collect()) + } + + pub(crate) fn position(&self, peer: &PublicKey) -> Option { + self.0.iter().position(|p| p.public_key() == peer) + } + + pub(crate) fn iter(&self) -> impl ExactSizeIterator { + self.0.iter() } /// True, if the topology contains at least one peer and thus requires consensus pub fn is_non_empty(&self) -> Option { - (!self.ordered_peers.is_empty()).then_some(NonEmptyTopology { topology: self }) + (!self.0.is_empty()).then_some(NonEmptyTopology { topology: self }) } /// Is consensus required, aka are there more than 1 peer. pub fn is_consensus_required(&self) -> Option { - (self.ordered_peers.len() > 1).then_some(ConsensusTopology { topology: self }) + (self.0.len() > 1).then_some(ConsensusTopology { topology: self }) } /// How many faulty peers can this topology tolerate. pub fn max_faults(&self) -> usize { - (self.ordered_peers.len().saturating_sub(1)) / 3 + (self.0.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 { - let len = self.ordered_peers.len(); + let len = self.0.len(); + if len > 3 { self.max_faults() * 2 + 1 } else { @@ -69,196 +96,144 @@ impl Topology { } } - /// Index of leader among `ordered_peers` + /// Index of leader #[allow(clippy::unused_self)] // In order to be consistent with `proxy_tail_index` method - fn leader_index(&self) -> usize { + pub const fn leader_index(&self) -> usize { 0 } - /// Index of leader among `ordered_peers` - fn proxy_tail_index(&self) -> usize { - // NOTE: proxy tail is the last element from the set A so that's why it's `min_votes_for_commit - 1` + /// Index of proxy tail + pub fn proxy_tail_index(&self) -> usize { + // NOTE: last element of set A self.min_votes_for_commit() - 1 } /// Filter signatures by roles in the topology. - #[allow(clippy::comparison_chain)] - pub fn filter_signatures_by_roles<'a, T: 'a, I: IntoIterator>>( + pub fn filter_signatures_by_roles<'a, I: IntoIterator>( &self, roles: &[Role], signatures: I, - ) -> Vec> { - let mut public_keys = IndexSet::with_capacity(self.ordered_peers.len()); + ) -> impl Iterator + 'a + where + ::IntoIter: 'a, + { + let mut filtered = IndexSet::new(); + for role in roles { match (role, self.is_non_empty(), self.is_consensus_required()) { (Role::Leader, Some(topology), _) => { - public_keys.insert(topology.leader().public_key()); + filtered.insert(topology.leader_index()); } (Role::ProxyTail, _, Some(topology)) => { - public_keys.insert(&topology.proxy_tail().public_key); + filtered.insert(topology.proxy_tail_index()); } (Role::ValidatingPeer, _, Some(topology)) => { - for peer in topology.validating_peers() { - public_keys.insert(peer.public_key()); - } + filtered.extend(topology.leader_index() + 1..topology.proxy_tail_index()); } (Role::ObservingPeer, _, Some(topology)) => { - for peer in topology.observing_peers() { - public_keys.insert(peer.public_key()); - } + filtered.extend(topology.proxy_tail_index() + 1..topology.0.len()); } _ => {} }; } + signatures .into_iter() - .filter(|signature| public_keys.contains(signature.public_key())) + .filter(move |signature| filtered.contains(&(signature.0 as usize))) .cloned() - .collect() } /// What role does this peer have in the topology. - pub fn role(&self, peer_id: &PeerId) -> Role { - match self.ordered_peers.iter().position(|p| p == peer_id) { - Some(index) if index == self.leader_index() => Role::Leader, - Some(index) if index < self.proxy_tail_index() => Role::ValidatingPeer, - Some(index) if index == self.proxy_tail_index() => Role::ProxyTail, + pub fn role(&self, peer: &PeerId) -> Role { + match self.position(peer.public_key()) { + Some(x) if x == self.leader_index() => Role::Leader, + Some(x) if x < self.proxy_tail_index() => Role::ValidatingPeer, + Some(x) if x == self.proxy_tail_index() => Role::ProxyTail, Some(_) => Role::ObservingPeer, - None => { - trace!(%peer_id, "Peer is not in topology."); - Role::Undefined - } + None => Role::Undefined, } } /// Add or remove peers from the topology. - pub fn update_peer_list(&mut self, new_peers: UniqueVec) { - self.modify_peers_directly(|peers| peers.retain(|peer| new_peers.contains(peer))); - self.ordered_peers.extend(new_peers); + fn update_peer_list(&mut self, new_peers: impl IntoIterator) { + let (old_peers, new_peers): (IndexSet<_>, IndexSet<_>) = new_peers + .into_iter() + .partition(|peer| self.0.contains(peer)); + self.0.retain(|peer| old_peers.contains(peer)); + self.0.extend(new_peers); } /// Rotate peers n times where n is a number of failed attempt to create a block. - pub fn rotate_all_n(&mut self, n: u64) { - let len = self - .ordered_peers - .len() - .try_into() - .expect("`usize` should fit into `u64`"); - if let Some(rem) = n.checked_rem(len) { - let rem = rem.try_into().expect( - "`rem` is smaller than `usize::MAX`, because remainder is always smaller than divisor", - ); + pub fn rotate_all_n(&mut self, n: usize) { + let len = self.0.len(); - self.modify_peers_directly(|peers| peers.rotate_left(rem)); + if let Some(rem) = n.checked_rem(len) { + self.0.rotate_left(rem); + unimplemented!() } } /// Re-arrange the set of peers after each successful block commit. pub fn rotate_set_a(&mut self) { let rotate_at = self.min_votes_for_commit(); - if rotate_at > 0 { - self.modify_peers_directly(|peers| peers[..rotate_at].rotate_left(1)); - } + self.0[..rotate_at].rotate_left(1); } /// Pull peers up in the topology to the top of the a set while preserving local order. - pub fn lift_up_peers(&mut self, to_lift_up: &[PublicKey]) { - self.modify_peers_directly(|peers| { - peers.sort_by_cached_key(|peer| !to_lift_up.contains(&peer.public_key)); + pub fn lift_up_peers(&mut self, to_lift_up: impl IntoIterator) { + let to_lift_up: IndexSet<_> = to_lift_up.into_iter().collect(); + + let mut signatory_idx = 0; + self.0.sort_by_cached_key(|_| { + let res = !to_lift_up.contains(&signatory_idx); + signatory_idx += 1; + res }); } - /// Perform sequence of actions after block committed. - pub fn update_topology(&mut self, block_signees: &[PublicKey], new_peers: UniqueVec) { - self.lift_up_peers(block_signees); + /// Recreate topology for given block + pub fn block_committed( + &mut self, + block: &SignedBlock, + new_peers: impl IntoIterator, + ) { + self.lift_up_peers(block.signatures().map(|s| s.0 as usize)); self.rotate_set_a(); self.update_peer_list(new_peers); } - - /// Recreate topology for given block and view change index - pub fn recreate_topology( - block: &SignedBlock, - view_change_index: u64, - new_peers: UniqueVec, - ) -> Self { - let mut topology = Topology::new(block.commit_topology().clone()); - let block_signees = block - .signatures() - .into_iter() - .map(|s| s.public_key()) - .cloned() - .collect::>(); - - topology.update_topology(&block_signees, new_peers); - - // Rotate all once for every view_change - topology.rotate_all_n(view_change_index); - - { - // FIXME: This is a hack to prevent consensus from running amock due to - // a bug in the implementation by reverting to predictable ordering - - let view_change_limit: usize = view_change_index - .saturating_sub(10) - .try_into() - .expect("u64 must fit into usize"); - - if view_change_limit > 1 { - iroha_logger::error!("Restarting consensus(internal bug). Report to developers"); - let mut peers: Vec<_> = topology.ordered_peers.iter().cloned().collect(); - - peers.sort(); - let peers_count = peers.len(); - peers.rotate_right(view_change_limit % peers_count); - topology = Topology::new(peers.into_iter().collect()); - } - } - - topology - } - - /// Modify [`ordered_peers`](Self::ordered_peers) directly as [`Vec`]. - fn modify_peers_directly(&mut self, f: impl FnOnce(&mut Vec)) { - let unique_peers = std::mem::take(&mut self.ordered_peers); - - let mut peers_vec = Vec::from(unique_peers); - f(&mut peers_vec); - - self.ordered_peers = UniqueVec::from_iter(peers_vec); - } } impl<'topology> NonEmptyTopology<'topology> { /// Get leader's [`PeerId`]. pub fn leader(&self) -> &'topology PeerId { - &self.topology.ordered_peers[self.topology.leader_index()] + &self.topology.0[self.topology.leader_index()] } } impl<'topology> ConsensusTopology<'topology> { /// Get proxy tail's peer id. pub fn proxy_tail(&self) -> &'topology PeerId { - &self.topology.ordered_peers[self.topology.proxy_tail_index()] + &self.topology.0[self.topology.proxy_tail_index()] } /// Get leader's [`PeerId`] pub fn leader(&self) -> &'topology PeerId { - &self.topology.ordered_peers[self.topology.leader_index()] + &self.topology.0[self.topology.leader_index()] } /// Get validating [`PeerId`]s. pub fn validating_peers(&self) -> &'topology [PeerId] { - &self.ordered_peers[self.leader_index() + 1..self.proxy_tail_index()] + &self.0[self.leader_index() + 1..self.proxy_tail_index()] } /// Get observing [`PeerId`]s. pub fn observing_peers(&self) -> &'topology [PeerId] { - &self.ordered_peers[self.proxy_tail_index() + 1..] + &self.0[self.proxy_tail_index() + 1..] } /// Get voting [`PeerId`]s. pub fn voting_peers(&self) -> &'topology [PeerId] { - &self.ordered_peers[self.leader_index()..=self.proxy_tail_index()] + &self.0[self.leader_index()..=self.proxy_tail_index()] } } @@ -280,7 +255,10 @@ pub enum Role { #[cfg(test)] macro_rules! test_peers { ($($id:literal),+$(,)?) => {{ - let mut iter = ::core::iter::repeat_with(|| KeyPair::random()); + let mut iter = ::core::iter::repeat_with( + || iroha_crypto::KeyPair::random() + ); + test_peers![$($id),*: iter] }}; ($($id:literal),+$(,)?: $key_pair_iter:expr) => { @@ -299,6 +277,7 @@ mod tests { use iroha_primitives::unique_vec; use super::*; + use crate::block::ValidBlock; fn topology() -> Topology { let peers = test_peers![0, 1, 2, 3, 4, 5, 6]; @@ -306,11 +285,7 @@ mod tests { } fn extract_ports(topology: &Topology) -> Vec { - topology - .ordered_peers - .iter() - .map(|peer| peer.address.port()) - .collect() + topology.0.iter().map(|peer| peer.address.port()).collect() } #[test] @@ -324,12 +299,7 @@ mod tests { fn lift_up_peers() { let mut topology = topology(); // Will lift up 1, 2, 4, 6 - let to_lift_up = &[ - topology.ordered_peers[1].public_key().clone(), - topology.ordered_peers[2].public_key().clone(), - topology.ordered_peers[4].public_key().clone(), - topology.ordered_peers[6].public_key().clone(), - ]; + let to_lift_up = [1, 2, 4, 6]; topology.lift_up_peers(to_lift_up); assert_eq!(extract_ports(&topology), vec![1, 2, 4, 6, 0, 3, 5]) } @@ -340,9 +310,9 @@ mod tests { // New peers will be 0, 2, 5, 7 let new_peers = { let mut peers = unique_vec![ - topology.ordered_peers[5].clone(), - topology.ordered_peers[0].clone(), - topology.ordered_peers[2].clone(), + topology.0[5].clone(), + topology.0[0].clone(), + topology.0[2].clone(), ]; peers.extend(test_peers![7]); peers @@ -358,70 +328,37 @@ mod tests { .collect::>(); let mut key_pairs_iter = key_pairs.iter(); let peers = test_peers![0, 1, 2, 3, 4, 5, 6: key_pairs_iter]; - let topology = Topology::new(peers.clone()); + let topology = Topology::new(peers); - let dummy = "value to sign"; - let signatures = key_pairs - .iter() - .map(|key_pair| SignatureOf::new(key_pair, &dummy)) - .collect::>>(); + let dummy_block = ValidBlock::new_dummy(); + let dummy_signature = &dummy_block.as_ref().signatures().next().unwrap().1; + let dummy_signatures = (0..key_pairs.len()) + .map(|i| BlockSignature(i as u64, dummy_signature.clone())) + .collect::>(); - let leader_signatures = - topology.filter_signatures_by_roles(&[Role::Leader], signatures.iter()); + let leader_signatures = topology + .filter_signatures_by_roles(&[Role::Leader], dummy_signatures.iter()) + .collect::>(); assert_eq!(leader_signatures.len(), 1); - assert_eq!(leader_signatures[0].public_key(), peers[0].public_key()); + assert_eq!(leader_signatures[0].0, 0); - let proxy_tail_signatures = - topology.filter_signatures_by_roles(&[Role::ProxyTail], signatures.iter()); + let proxy_tail_signatures = topology + .filter_signatures_by_roles(&[Role::ProxyTail], dummy_signatures.iter()) + .collect::>(); assert_eq!(proxy_tail_signatures.len(), 1); - assert_eq!(proxy_tail_signatures[0].public_key(), peers[4].public_key()); + assert_eq!(proxy_tail_signatures[0].0, 4); - let validating_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ValidatingPeer], signatures.iter()); + let validating_peers_signatures = topology + .filter_signatures_by_roles(&[Role::ValidatingPeer], dummy_signatures.iter()) + .collect::>(); assert_eq!(validating_peers_signatures.len(), 3); - assert!(validating_peers_signatures - .iter() - .map(|s| s.public_key()) - .eq(peers[1..4].iter().map(PeerId::public_key))); - - let observing_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ObservingPeer], signatures.iter()); - assert_eq!(observing_peers_signatures.len(), 2); - assert!(observing_peers_signatures - .iter() - .map(|s| s.public_key()) - .eq(peers[5..].iter().map(PeerId::public_key))); - } + assert!(validating_peers_signatures.iter().map(|s| s.0).eq(1..4)); - #[test] - fn filter_by_role_empty() { - let key_pairs = core::iter::repeat_with(KeyPair::random) - .take(7) + let observing_peers_signatures = topology + .filter_signatures_by_roles(&[Role::ObservingPeer], dummy_signatures.iter()) .collect::>(); - let peers = UniqueVec::new(); - let topology = Topology::new(peers); - - let dummy = "value to sign"; - let signatures = key_pairs - .iter() - .map(|key_pair| SignatureOf::new(key_pair, &dummy)) - .collect::>>(); - - let leader_signatures = - topology.filter_signatures_by_roles(&[Role::Leader], signatures.iter()); - assert!(leader_signatures.is_empty()); - - let proxy_tail_signatures = - topology.filter_signatures_by_roles(&[Role::ProxyTail], signatures.iter()); - assert!(proxy_tail_signatures.is_empty()); - - let validating_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ValidatingPeer], signatures.iter()); - assert!(validating_peers_signatures.is_empty()); - - let observing_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ObservingPeer], signatures.iter()); - assert!(observing_peers_signatures.is_empty()); + assert_eq!(observing_peers_signatures.len(), 2); + assert!(observing_peers_signatures.iter().map(|s| s.0).eq(5..7)); } #[test] @@ -431,30 +368,31 @@ mod tests { .collect::>(); let mut key_pairs_iter = key_pairs.iter(); let peers = test_peers![0: key_pairs_iter]; - let topology = Topology::new(peers.clone()); + let topology = Topology::new(peers); - let dummy = "value to sign"; - let signatures = key_pairs - .iter() - .map(|key_pair| SignatureOf::new(key_pair, &dummy)) - .collect::>>(); + let dummy_block = ValidBlock::new_dummy(); + let dummy_signature = &dummy_block.as_ref().signatures().next().unwrap().1; + let dummy_signatures = (0..key_pairs.len()) + .map(|i| BlockSignature(i as u64, dummy_signature.clone())) + .collect::>(); - let leader_signatures = - topology.filter_signatures_by_roles(&[Role::Leader], signatures.iter()); + let leader_signatures = topology + .filter_signatures_by_roles(&[Role::Leader], dummy_signatures.iter()) + .collect::>(); assert_eq!(leader_signatures.len(), 1); - assert_eq!(leader_signatures[0].public_key(), peers[0].public_key()); + assert_eq!(leader_signatures[0].0, 0); - let proxy_tail_signatures = - topology.filter_signatures_by_roles(&[Role::ProxyTail], signatures.iter()); - assert!(proxy_tail_signatures.is_empty()); + let mut proxy_tail_signatures = + topology.filter_signatures_by_roles(&[Role::ProxyTail], dummy_signatures.iter()); + assert!(proxy_tail_signatures.next().is_none()); - let validating_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ValidatingPeer], signatures.iter()); - assert!(validating_peers_signatures.is_empty()); + let mut validating_peers_signatures = + topology.filter_signatures_by_roles(&[Role::ValidatingPeer], dummy_signatures.iter()); + assert!(validating_peers_signatures.next().is_none()); - let observing_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ObservingPeer], signatures.iter()); - assert!(observing_peers_signatures.is_empty()); + let mut observing_peers_signatures = + topology.filter_signatures_by_roles(&[Role::ObservingPeer], dummy_signatures.iter()); + assert!(observing_peers_signatures.next().is_none()); } #[test] @@ -464,31 +402,33 @@ mod tests { .collect::>(); let mut key_pairs_iter = key_pairs.iter(); let peers = test_peers![0, 1: key_pairs_iter]; - let topology = Topology::new(peers.clone()); + let topology = Topology::new(peers); - let dummy = "value to sign"; - let signatures = key_pairs - .iter() - .map(|key_pair| SignatureOf::new(key_pair, &dummy)) - .collect::>>(); + let dummy_block = ValidBlock::new_dummy(); + let dummy_signature = &dummy_block.as_ref().signatures().next().unwrap().1; + let dummy_signatures = (0..key_pairs.len()) + .map(|i| BlockSignature(i as u64, dummy_signature.clone())) + .collect::>(); - let leader_signatures = - topology.filter_signatures_by_roles(&[Role::Leader], signatures.iter()); + let leader_signatures = topology + .filter_signatures_by_roles(&[Role::Leader], dummy_signatures.iter()) + .collect::>(); assert_eq!(leader_signatures.len(), 1); - assert_eq!(leader_signatures[0].public_key(), peers[0].public_key()); + assert_eq!(leader_signatures[0].0, 0); - let proxy_tail_signatures = - topology.filter_signatures_by_roles(&[Role::ProxyTail], signatures.iter()); + let proxy_tail_signatures = topology + .filter_signatures_by_roles(&[Role::ProxyTail], dummy_signatures.iter()) + .collect::>(); assert_eq!(proxy_tail_signatures.len(), 1); - assert_eq!(proxy_tail_signatures[0].public_key(), peers[1].public_key()); + assert_eq!(proxy_tail_signatures[0].0, 1); - let validating_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ValidatingPeer], signatures.iter()); - assert!(validating_peers_signatures.is_empty()); + let mut validating_peers_signatures = + topology.filter_signatures_by_roles(&[Role::ValidatingPeer], dummy_signatures.iter()); + assert!(validating_peers_signatures.next().is_none()); - let observing_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ObservingPeer], signatures.iter()); - assert!(observing_peers_signatures.is_empty()); + let mut observing_peers_signatures = + topology.filter_signatures_by_roles(&[Role::ObservingPeer], dummy_signatures.iter()); + assert!(observing_peers_signatures.next().is_none()); } #[test] @@ -498,65 +438,35 @@ mod tests { .collect::>(); let mut key_pairs_iter = key_pairs.iter(); let peers = test_peers![0, 1, 2: key_pairs_iter]; - let topology = Topology::new(peers.clone()); + let topology = Topology::new(peers); - let dummy = "value to sign"; - let signatures = key_pairs - .iter() - .map(|key_pair| SignatureOf::new(key_pair, &dummy)) - .collect::>>(); + let dummy_block = ValidBlock::new_dummy(); + let dummy_signature = &dummy_block.as_ref().signatures().next().unwrap().1; + let dummy_signatures = (0..key_pairs.len()) + .map(|i| BlockSignature(i as u64, dummy_signature.clone())) + .collect::>(); - let leader_signatures = - topology.filter_signatures_by_roles(&[Role::Leader], signatures.iter()); + let leader_signatures = topology + .filter_signatures_by_roles(&[Role::Leader], dummy_signatures.iter()) + .collect::>(); assert_eq!(leader_signatures.len(), 1); - assert_eq!(leader_signatures[0].public_key(), peers[0].public_key()); + assert_eq!(leader_signatures[0].0, 0); - let proxy_tail_signatures = - topology.filter_signatures_by_roles(&[Role::ProxyTail], signatures.iter()); + let proxy_tail_signatures = topology + .filter_signatures_by_roles(&[Role::ProxyTail], dummy_signatures.iter()) + .collect::>(); assert_eq!(proxy_tail_signatures.len(), 1); - assert_eq!(proxy_tail_signatures[0].public_key(), peers[2].public_key()); + assert_eq!(proxy_tail_signatures[0].0, 2); - let validating_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ValidatingPeer], signatures.iter()); + let validating_peers_signatures = topology + .filter_signatures_by_roles(&[Role::ValidatingPeer], dummy_signatures.iter()) + .collect::>(); assert_eq!(validating_peers_signatures.len(), 1); - assert_eq!( - validating_peers_signatures[0].public_key(), - peers[1].public_key() - ); + assert_eq!(validating_peers_signatures[0].0, 1); - let observing_peers_signatures = - topology.filter_signatures_by_roles(&[Role::ObservingPeer], signatures.iter()); - assert!(observing_peers_signatures.is_empty()); - } - - #[test] - fn roles() { - let peers = test_peers![0, 1, 2, 3, 4, 5, 6]; - let not_in_topology_peers = test_peers![7, 8, 9]; - let topology = Topology::new(peers.clone()); - let expected_roles = [ - Role::Leader, - Role::ValidatingPeer, - Role::ValidatingPeer, - Role::ValidatingPeer, - Role::ProxyTail, - Role::ObservingPeer, - Role::ObservingPeer, - Role::Undefined, - Role::Undefined, - Role::Undefined, - ]; - - for ((i, peer), expected_role) in (0..) - .zip(peers.into_iter().chain(not_in_topology_peers)) - .zip(expected_roles) - { - let actual_role = topology.role(&peer); - assert_eq!( - actual_role, expected_role, - "Role detection failed for peer with index: {i}" - ); - } + let mut observing_peers_signatures = + topology.filter_signatures_by_roles(&[Role::ObservingPeer], dummy_signatures.iter()); + assert!(observing_peers_signatures.next().is_none()); } #[test] @@ -574,17 +484,9 @@ mod tests { } #[test] - fn proxy_tail_empty() { - let peers = UniqueVec::new(); - let topology = Topology::new(peers); - - assert_eq!( - topology - .is_consensus_required() - .as_ref() - .map(ConsensusTopology::proxy_tail), - None, - ); + #[should_panic(expected = "Topology must contain at least one peer")] + fn topology_empty() { + let _topology = Topology::new(Vec::new()); } #[test] @@ -643,20 +545,6 @@ mod tests { ); } - #[test] - fn leader_empty() { - let peers = UniqueVec::new(); - let topology = Topology::new(peers); - - assert_eq!( - topology - .is_non_empty() - .as_ref() - .map(NonEmptyTopology::leader), - None, - ); - } - #[test] fn leader_1() { let peers = test_peers![0]; @@ -713,20 +601,6 @@ mod tests { ); } - #[test] - fn validating_peers_empty() { - let peers = UniqueVec::new(); - let topology = Topology::new(peers); - - assert_eq!( - topology - .is_consensus_required() - .as_ref() - .map(ConsensusTopology::validating_peers), - None, - ); - } - #[test] fn validating_peers_1() { let peers = test_peers![0]; @@ -784,20 +658,6 @@ mod tests { ); } - #[test] - fn observing_peers_empty() { - let peers = UniqueVec::new(); - let topology = Topology::new(peers); - - assert_eq!( - topology - .is_consensus_required() - .as_ref() - .map(ConsensusTopology::observing_peers), - None, - ); - } - #[test] fn observing_peers_1() { let peers = test_peers![0]; diff --git a/core/src/sumeragi/view_change.rs b/core/src/sumeragi/view_change.rs index 9a24f0ece33..23aa04c5381 100644 --- a/core/src/sumeragi/view_change.rs +++ b/core/src/sumeragi/view_change.rs @@ -1,14 +1,16 @@ //! Structures related to proofs and reasons of view changes. //! Where view change is a process of changing topology due to some faulty network behavior. -use derive_more::{Deref, DerefMut}; use eyre::Result; -use indexmap::IndexSet; -use iroha_crypto::{HashOf, KeyPair, SignatureOf, SignaturesOf}; -use iroha_data_model::{block::SignedBlock, prelude::PeerId}; +use iroha_crypto::{HashOf, PrivateKey, SignatureOf}; +use iroha_data_model::block::SignedBlock; use parity_scale_codec::{Decode, Encode}; use thiserror::Error; +use super::network_topology::Topology; + +type ViewChangeProofSignature = (u64, SignatureOf); + /// Error emerge during insertion of `Proof` into `ProofChain` #[derive(Error, displaydoc::Display, Debug, Clone, Copy)] #[allow(missing_docs)] @@ -30,8 +32,7 @@ struct ProofPayload { /// 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 { - signatures: SignaturesOf, - /// Collection of signatures from the different peers. + signatures: Vec, payload: ProofPayload, } @@ -41,90 +42,92 @@ pub struct ProofBuilder(SignedProof); impl ProofBuilder { /// Constructor from index. - pub fn new(latest_block_hash: Option>, view_change_index: u64) -> Self { + pub fn new(latest_block_hash: Option>, view_change_index: usize) -> Self { + let view_change_index = view_change_index as u64; + let proof = SignedProof { payload: ProofPayload { latest_block_hash, view_change_index, }, - signatures: [].into_iter().collect(), + signatures: Vec::new(), }; Self(proof) } /// Sign this message with the peer's public and private key. - pub fn sign(mut self, key_pair: &KeyPair) -> SignedProof { - let signature = SignatureOf::new(key_pair, &self.0.payload); - self.0.signatures.insert(signature); + pub fn sign(mut self, signatory_idx: usize, private_key: &PrivateKey) -> SignedProof { + let signature = SignatureOf::new(private_key, &self.0.payload); + self.0.signatures.push((signatory_idx as u64, signature)); self.0 } } impl SignedProof { /// Verify the signatures of `other` and add them to this proof. - fn merge_signatures(&mut self, other: SignaturesOf) { - for signature in other { - if signature.verify(&self.payload).is_ok() { - self.signatures.insert(signature); + fn merge_signatures(&mut self, other: Vec, topology: &Topology) { + for (signatory_idx, signature) in other { + let public_key = topology.as_ref()[signatory_idx as usize].public_key(); + + if signature.verify(public_key, &self.payload).is_ok() { + self.signatures.push((signatory_idx, signature)); } } } /// Verify if the proof is valid, given the peers in `topology`. - fn verify(&self, peers: &[PeerId], max_faults: usize) -> bool { - let peer_public_keys: IndexSet<_> = peers.iter().map(PeerId::public_key).collect(); - + fn verify(&self, topology: &Topology) -> bool { let valid_count = self .signatures .iter() - .filter(|signature| { - signature.verify(&self.payload).is_ok() - && peer_public_keys.contains(signature.public_key()) + .filter(|&(signatory_idx, signature)| { + let public_key = topology.as_ref()[*signatory_idx as usize].public_key(); + signature.verify(public_key, &self.payload).is_ok() }) .count(); // See Whitepaper for the information on this limit. #[allow(clippy::int_plus_one)] { - valid_count >= max_faults + 1 + valid_count >= topology.max_faults() + 1 } } } /// Structure representing sequence of view change proofs. -#[derive(Debug, Clone, Encode, Decode, Deref, DerefMut, Default)] +#[derive(Debug, Clone, Encode, Decode, Default)] pub struct ProofChain(Vec); impl ProofChain { /// Verify the view change proof chain. pub fn verify_with_state( &self, - peers: &[PeerId], - max_faults: usize, + topology: &Topology, latest_block_hash: Option>, ) -> usize { - self.iter() + self.0 + .iter() .enumerate() .take_while(|(i, proof)| { proof.payload.latest_block_hash == latest_block_hash && proof.payload.view_change_index == (*i as u64) - && proof.verify(peers, max_faults) + && proof.verify(topology) }) .count() } /// Remove invalid proofs from the chain. pub fn prune(&mut self, latest_block_hash: Option>) { - let valid_count = self - .iter() + self.0 = core::mem::take(&mut self.0) + .into_iter() .enumerate() .take_while(|(i, proof)| { proof.payload.latest_block_hash == latest_block_hash && proof.payload.view_change_index == (*i as u64) }) - .count(); - self.truncate(valid_count); + .map(|(_, proof)| proof) + .collect(); } /// Attempt to insert a view chain proof into this `ProofChain`. @@ -134,25 +137,23 @@ impl ProofChain { /// - If proof view change number differs from view change number pub fn insert_proof( &mut self, - peers: &[PeerId], - max_faults: usize, - latest_block_hash: Option>, new_proof: SignedProof, + topology: &Topology, + latest_block_hash: Option>, ) -> Result<(), Error> { if new_proof.payload.latest_block_hash != latest_block_hash { return Err(Error::BlockHashMismatch); } - let next_unfinished_view_change = - self.verify_with_state(peers, max_faults, latest_block_hash); + let next_unfinished_view_change = self.verify_with_state(topology, latest_block_hash); 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. } - let is_proof_chain_incomplete = next_unfinished_view_change < self.len(); + let is_proof_chain_incomplete = next_unfinished_view_change < self.0.len(); if is_proof_chain_incomplete { - self[next_unfinished_view_change].merge_signatures(new_proof.signatures); + self.0[next_unfinished_view_change].merge_signatures(new_proof.signatures, topology); } else { - self.push(new_proof); + self.0.push(new_proof); } Ok(()) } @@ -165,31 +166,30 @@ impl ProofChain { pub fn merge( &mut self, mut other: Self, - peers: &[PeerId], - max_faults: usize, + topology: &Topology, latest_block_hash: Option>, ) -> Result<(), Error> { - // Prune to exclude invalid proofs other.prune(latest_block_hash); - if other.is_empty() { + + if other.0.is_empty() { return Err(Error::BlockHashMismatch); } - let next_unfinished_view_change = - self.verify_with_state(peers, max_faults, latest_block_hash); - let is_proof_chain_incomplete = next_unfinished_view_change < self.len(); - let other_contain_additional_proofs = next_unfinished_view_change < other.len(); + let next_unfinished_view_change = self.verify_with_state(topology, latest_block_hash); + let is_proof_chain_incomplete = next_unfinished_view_change < self.0.len(); + let other_contain_additional_proofs = next_unfinished_view_change < other.0.len(); match (is_proof_chain_incomplete, other_contain_additional_proofs) { // Case 1: proof chain is incomplete and other have corresponding proof. (true, true) => { - let new_proof = other.swap_remove(next_unfinished_view_change); - self[next_unfinished_view_change].merge_signatures(new_proof.signatures); + let new_proof = other.0.swap_remove(next_unfinished_view_change); + self.0[next_unfinished_view_change] + .merge_signatures(new_proof.signatures, topology); } // Case 2: proof chain is complete, but other have additional proof. (false, true) => { - let new_proof = other.swap_remove(next_unfinished_view_change); - self.push(new_proof); + let new_proof = other.0.swap_remove(next_unfinished_view_change); + self.0.push(new_proof); } // Case 3: proof chain is incomplete, but other doesn't contain corresponding proof. // Usually this mean that sender peer is behind receiver peer. diff --git a/core/src/tx.rs b/core/src/tx.rs index 070ff3e7d03..935b3f96259 100644 --- a/core/src/tx.rs +++ b/core/src/tx.rs @@ -9,12 +9,14 @@ //! as various forms of validation are performed. use eyre::Result; -use iroha_crypto::SignatureVerificationFail; +use iroha_crypto::SignatureOf; pub use iroha_data_model::prelude::*; use iroha_data_model::{ isi::error::Mismatch, query::error::FindError, - transaction::{error::TransactionLimitError, TransactionLimits, TransactionPayload}, + transaction::{ + error::TransactionLimitError, TransactionLimits, TransactionPayload, TransactionSignature, + }, }; use iroha_genesis::GenesisTransaction; use iroha_logger::{debug, error}; @@ -27,16 +29,42 @@ use crate::{ /// `AcceptedTransaction` — a transaction accepted by iroha peer. #[derive(Debug, Clone, PartialEq, Eq)] -// TODO: Inner field should be private to maintain invariants +// FIX: Inner field should be private to maintain invariants pub struct AcceptedTransaction(pub(crate) SignedTransaction); +/// Verification failed of some signature due to following reason +#[derive(Clone, PartialEq, Eq)] +pub struct SignatureVerificationFail { + /// Signature which verification has failed + pub signature: SignatureOf, + /// Error which happened during verification + pub reason: String, +} + +impl core::fmt::Debug for SignatureVerificationFail { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SignatureVerificationFail") + .field("signature", &self.signature) + .field("reason", &self.reason) + .finish() + } +} + +impl core::fmt::Display for SignatureVerificationFail { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Failed to verify signatures: {}", self.reason,) + } +} + +impl std::error::Error for SignatureVerificationFail {} + /// Error type for transaction from [`SignedTransaction`] to [`AcceptedTransaction`] -#[derive(Debug, FromVariant, thiserror::Error, displaydoc::Display)] +#[derive(Debug, displaydoc::Display, PartialEq, Eq, FromVariant, thiserror::Error)] pub enum AcceptTransactionFail { /// Failure during limits check TransactionLimit(#[source] TransactionLimitError), /// Failure during signature verification - SignatureVerification(#[source] SignatureVerificationFail), + SignatureVerification(#[source] SignatureVerificationFail), /// The genesis account can only sign transactions in the genesis block UnexpectedGenesisAccountSignature, /// Transaction's `chain_id` doesn't correspond to the id of current blockchain @@ -63,10 +91,10 @@ impl AcceptedTransaction { })); } - for signature in tx.0.signatures() { - if signature.public_key() != genesis_public_key { + for TransactionSignature(public_key, signature) in tx.0.signatures() { + if public_key != genesis_public_key { return Err(SignatureVerificationFail { - signature: signature.clone().into(), + signature: signature.clone(), reason: "Signature doesn't correspond to genesis public key".to_string(), } .into()); diff --git a/crypto/src/signature/mod.rs b/crypto/src/signature/mod.rs index 29c22cc6844..90a5c75eb51 100644 --- a/crypto/src/signature/mod.rs +++ b/crypto/src/signature/mod.rs @@ -11,13 +11,8 @@ pub(crate) mod ed25519; pub(crate) mod secp256k1; #[cfg(not(feature = "std"))] -use alloc::{ - boxed::Box, collections::btree_set, format, string::String, string::ToString as _, vec, - vec::Vec, -}; +use alloc::{format, string::String, vec, vec::Vec}; use core::{borrow::Borrow as _, marker::PhantomData}; -#[cfg(feature = "std")] -use std::collections::btree_set; use arrayref::array_ref; use derive_more::{Deref, DerefMut}; @@ -30,7 +25,7 @@ use serde::{Deserialize, Serialize}; use sha2::Digest as _; use zeroize::Zeroize as _; -use crate::{error::ParseError, ffi, hex_decode, Error, HashOf, KeyPair, PublicKey}; +use crate::{error::ParseError, ffi, hex_decode, Error, HashOf, PrivateKey, PublicKey}; /// Construct cryptographic RNG from seed. fn rng_from_seed(mut seed: Vec) -> impl CryptoRngCore { @@ -44,31 +39,19 @@ ffi::ffi_item! { #[serde_with::serde_as] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, getset::Getters)] #[cfg_attr(not(feature="ffi_import"), derive(derive_more::DebugCustom, Hash, Decode, Encode, Deserialize, Serialize, IntoSchema))] - #[cfg_attr(not(feature="ffi_import"), debug( - fmt = "{{ pub_key: {public_key}, payload: {} }}", - "hex::encode_upper(payload)" - ))] + #[cfg_attr(not(feature="ffi_import"), debug(fmt = "{{ {} }}", "hex::encode_upper(payload)"))] pub struct Signature { - /// Public key that is used for verification. Payload is verified by algorithm - /// that corresponds with the public key's digest function. - #[getset(get = "pub")] - public_key: PublicKey, - /// Signature payload #[serde_as(as = "serde_with::hex::Hex")] - payload: ConstVec, + payload: ConstVec } } impl Signature { - /// Access the signature's payload - pub fn payload(&self) -> &[u8] { - self.payload.as_ref() - } - /// Creates new signature by signing payload via [`KeyPair::private_key`]. - pub fn new(key_pair: &KeyPair, payload: &[u8]) -> Self { + pub fn new(private_key: &PrivateKey, payload: &[u8]) -> Self { use crate::secrecy::ExposeSecret; - let signature = match key_pair.private_key.0.expose_secret() { + + let signature = match private_key.0.expose_secret() { crate::PrivateKeyInner::Ed25519(sk) => ed25519::Ed25519Sha512::sign(payload, sk), crate::PrivateKeyInner::Secp256k1(sk) => { secp256k1::EcdsaSecp256k1Sha256::sign(payload, sk) @@ -76,8 +59,8 @@ impl Signature { crate::PrivateKeyInner::BlsSmall(sk) => bls::BlsSmall::sign(payload, sk), crate::PrivateKeyInner::BlsNormal(sk) => bls::BlsNormal::sign(payload, sk), }; + Self { - public_key: key_pair.public_key.clone(), payload: ConstVec::new(signature), } } @@ -88,9 +71,8 @@ impl Signature { /// /// This method exists to allow reproducing the signature in a more efficient way than through /// deserialization. - pub fn from_bytes(public_key: PublicKey, payload: &[u8]) -> Self { + pub fn from_bytes(payload: &[u8]) -> Self { Self { - public_key, payload: ConstVec::new(payload), } } @@ -99,28 +81,28 @@ impl Signature { /// /// # Errors /// If passed string is not a valid hex. - pub fn from_hex(public_key: PublicKey, payload: impl AsRef) -> Result { + pub fn from_hex(payload: impl AsRef) -> Result { let payload: Vec = hex_decode(payload.as_ref())?; - Ok(Self::from_bytes(public_key, &payload)) + Ok(Self::from_bytes(&payload)) } /// Verify `payload` using signed data and [`KeyPair::public_key`]. /// /// # Errors /// Fails if the message doesn't pass verification - pub fn verify(&self, payload: &[u8]) -> Result<(), Error> { - match self.public_key.0.borrow() { + pub fn verify(&self, public_key: &PublicKey, payload: &[u8]) -> Result<(), Error> { + match public_key.0.borrow() { crate::PublicKeyInner::Ed25519(pk) => { - ed25519::Ed25519Sha512::verify(payload, self.payload(), pk) + ed25519::Ed25519Sha512::verify(payload, &self.payload, pk) } crate::PublicKeyInner::Secp256k1(pk) => { - secp256k1::EcdsaSecp256k1Sha256::verify(payload, self.payload(), pk) + secp256k1::EcdsaSecp256k1Sha256::verify(payload, &self.payload, pk) } crate::PublicKeyInner::BlsSmall(pk) => { - bls::BlsSmall::verify(payload, self.payload(), pk) + bls::BlsSmall::verify(payload, &self.payload, pk) } crate::PublicKeyInner::BlsNormal(pk) => { - bls::BlsNormal::verify(payload, self.payload(), pk) + bls::BlsNormal::verify(payload, &self.payload, pk) } }?; @@ -128,19 +110,6 @@ impl Signature { } } -// TODO: Enable in ffi_import -#[cfg(not(feature = "ffi_import"))] -impl From for (PublicKey, Vec) { - fn from( - Signature { - public_key, - payload: signature, - }: Signature, - ) -> Self { - (public_key, signature.into_vec()) - } -} - // TODO: Enable in ffi_import #[cfg(not(feature = "ffi_import"))] impl From> for Signature { @@ -234,8 +203,8 @@ impl SignatureOf { /// # Errors /// Fails if signing fails #[inline] - fn from_hash(key_pair: &KeyPair, hash: HashOf) -> Self { - Self(Signature::new(key_pair, hash.as_ref()), PhantomData) + fn from_hash(private_key: &PrivateKey, hash: HashOf) -> Self { + Self(Signature::new(private_key, hash.as_ref()), PhantomData) } /// Verify signature for this hash @@ -243,8 +212,8 @@ impl SignatureOf { /// # Errors /// /// Fails if the given hash didn't pass verification - fn verify_hash(&self, hash: HashOf) -> Result<(), Error> { - self.0.verify(hash.as_ref()) + pub fn verify_hash(&self, public_key: &PublicKey, hash: HashOf) -> Result<(), Error> { + self.0.verify(public_key, hash.as_ref()) } } @@ -256,324 +225,33 @@ impl SignatureOf { /// # Errors /// Fails if signing fails #[inline] - pub fn new(key_pair: &KeyPair, value: &T) -> Self { - Self::from_hash(key_pair, HashOf::new(value)) + pub fn new(private_key: &PrivateKey, value: &T) -> Self { + Self::from_hash(private_key, HashOf::new(value)) } /// Verifies signature for this item /// /// # Errors /// Fails if verification fails - pub fn verify(&self, value: &T) -> Result<(), Error> { - self.verify_hash(HashOf::new(value)) - } -} - -/// Wrapper around [`SignatureOf`] used to reimplement [`Eq`], [`Ord`], [`Hash`] -/// to compare signatures only by their [`PublicKey`]. -#[derive(Deref, DerefMut, Decode, Encode, Deserialize, Serialize, IntoSchema)] -#[serde(transparent, bound(deserialize = ""))] -#[schema(transparent)] -#[repr(transparent)] -#[cfg(not(feature = "ffi_import"))] -pub struct SignatureWrapperOf( - #[deref] - #[deref_mut] - SignatureOf, -); - -#[cfg(not(feature = "ffi_import"))] -impl SignatureWrapperOf { - #[inline] - fn inner(self) -> SignatureOf { - self.0 - } -} - -#[cfg(not(feature = "ffi_import"))] -impl core::fmt::Debug for SignatureWrapperOf { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.0.fmt(f) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl Clone for SignatureWrapperOf { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -#[allow(clippy::unconditional_recursion)] // False-positive -#[cfg(not(feature = "ffi_import"))] -impl PartialEq for SignatureWrapperOf { - fn eq(&self, other: &Self) -> bool { - self.0.public_key().eq(other.0.public_key()) - } -} -#[cfg(not(feature = "ffi_import"))] -impl Eq for SignatureWrapperOf {} - -#[cfg(not(feature = "ffi_import"))] -impl PartialOrd for SignatureWrapperOf { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -#[cfg(not(feature = "ffi_import"))] -impl Ord for SignatureWrapperOf { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.public_key().cmp(other.0.public_key()) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl core::hash::Hash for SignatureWrapperOf { - // Implement `Hash` manually to be consistent with `Ord` - fn hash(&self, state: &mut H) { - self.0.public_key().hash(state); - } -} - -/// Container for multiple signatures, each corresponding to a different public key. -/// -/// 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::derived_hash_with_manual_eq)] -#[derive(Hash, Decode, Encode, Deserialize, Serialize, IntoSchema)] -#[serde(transparent)] -// Transmute guard -#[repr(transparent)] -#[cfg(not(feature = "ffi_import"))] -pub struct SignaturesOf { - signatures: btree_set::BTreeSet>, -} - -#[cfg(not(feature = "ffi_import"))] -impl core::fmt::Debug for SignaturesOf { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct(core::any::type_name::()) - .field("signatures", &self.signatures) - .finish() - } -} - -#[cfg(not(feature = "ffi_import"))] -impl Clone for SignaturesOf { - fn clone(&self) -> Self { - let signatures = self.signatures.clone(); - Self { signatures } - } -} - -#[allow(clippy::unconditional_recursion)] // False-positive -#[cfg(not(feature = "ffi_import"))] -impl PartialEq for SignaturesOf { - fn eq(&self, other: &Self) -> bool { - self.signatures.eq(&other.signatures) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl Eq for SignaturesOf {} - -#[cfg(not(feature = "ffi_import"))] -impl PartialOrd for SignaturesOf { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl Ord for SignaturesOf { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.signatures.cmp(&other.signatures) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl IntoIterator for SignaturesOf { - type Item = SignatureOf; - type IntoIter = core::iter::Map< - btree_set::IntoIter>, - fn(SignatureWrapperOf) -> SignatureOf, - >; - fn into_iter(self) -> Self::IntoIter { - self.signatures.into_iter().map(SignatureWrapperOf::inner) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl<'itm, T> IntoIterator for &'itm SignaturesOf { - type Item = &'itm SignatureOf; - type IntoIter = core::iter::Map< - btree_set::Iter<'itm, SignatureWrapperOf>, - fn(&'itm SignatureWrapperOf) -> &'itm SignatureOf, - >; - fn into_iter(self) -> Self::IntoIter { - self.signatures.iter().map(core::ops::Deref::deref) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl Extend> for SignaturesOf { - fn extend(&mut self, iter: T) - where - T: IntoIterator>, - { - for signature in iter { - self.insert(signature); - } - } -} - -#[cfg(not(feature = "ffi_import"))] -impl From> for btree_set::BTreeSet> { - fn from(source: SignaturesOf) -> Self { - source.into_iter().collect() - } -} - -#[cfg(not(feature = "ffi_import"))] -impl From>> for SignaturesOf { - fn from(source: btree_set::BTreeSet>) -> Self { - source.into_iter().collect() - } -} - -#[cfg(not(feature = "ffi_import"))] -impl From> for SignaturesOf { - fn from(signature: SignatureOf) -> Self { - Self { - signatures: [SignatureWrapperOf(signature)].into(), - } + pub fn verify(&self, public_key: &PublicKey, value: &T) -> Result<(), Error> { + self.verify_hash(public_key, HashOf::new(value)) } } -#[cfg(not(feature = "ffi_import"))] -impl FromIterator> for SignaturesOf { - fn from_iter>>(signatures: T) -> Self { - Self { - signatures: signatures.into_iter().map(SignatureWrapperOf).collect(), - } - } -} - -#[cfg(not(feature = "ffi_import"))] -impl SignaturesOf { - /// 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)); - } - - /// Return all signatures. - #[inline] - pub fn iter(&self) -> impl ExactSizeIterator> { - self.into_iter() - } - - /// Number of signatures. - #[inline] - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.signatures.len() - } - - /// Verify signatures for this hash - /// - /// # Errors - /// Fails if verificatoin of any signature fails - pub fn verify_hash(&self, hash: HashOf) -> Result<(), SignatureVerificationFail> { - self.iter().try_for_each(|signature| { - signature - .verify_hash(hash) - .map_err(|error| SignatureVerificationFail { - signature: Box::new(signature.clone()), - reason: error.to_string(), - }) - }) - } - - /// Returns true if the set is a subset of another, i.e., other contains at least all the elements in self. - pub fn is_subset(&self, other: &Self) -> bool { - self.signatures.is_subset(&other.signatures) - } -} - -#[cfg(not(feature = "ffi_import"))] -impl SignaturesOf { - /// Create new signatures container - /// - /// # Errors - /// Forwards [`SignatureOf::new`] errors - #[inline] - pub fn new(key_pair: &KeyPair, value: &T) -> Self { - SignatureOf::new(key_pair, value).into() - } - - /// Verifies all signatures - /// - /// # Errors - /// Fails if validation of any signature fails - pub fn verify(&self, item: &T) -> Result<(), SignatureVerificationFail> { - self.verify_hash(HashOf::new(item)) - } -} - -/// Verification failed of some signature due to following reason -#[derive(Clone, PartialEq, Eq)] -pub struct SignatureVerificationFail { - /// Signature which verification has failed - pub signature: Box>, - /// Error which happened during verification - pub reason: String, -} - -#[cfg(not(feature = "ffi_import"))] -impl core::fmt::Debug for SignatureVerificationFail { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("SignatureVerificationFail") - .field("signature", &self.signature.0) - .field("reason", &self.reason) - .finish() - } -} - -#[cfg(not(feature = "ffi_import"))] -impl core::fmt::Display for SignatureVerificationFail { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Failed to verify signatures because of signature {}: {}", - self.signature.public_key(), - self.reason, - ) - } -} - -#[cfg(feature = "std")] -#[cfg(not(feature = "ffi_import"))] -impl std::error::Error for SignatureVerificationFail {} - #[cfg(test)] mod tests { - use core::str::FromStr; - use serde_json::json; use super::*; - use crate::Algorithm; + use crate::{Algorithm, KeyPair}; #[test] #[cfg(feature = "rand")] fn create_signature_ed25519() { let key_pair = KeyPair::random_with_algorithm(crate::Algorithm::Ed25519); let message = b"Test message to sign."; - let signature = Signature::new(&key_pair, message); - assert_eq!(*signature.public_key(), *key_pair.public_key()); - signature.verify(message).unwrap(); + let signature = Signature::new(key_pair.private_key(), message); + signature.verify(key_pair.public_key(), message).unwrap(); } #[test] @@ -581,9 +259,8 @@ mod tests { fn create_signature_secp256k1() { let key_pair = KeyPair::random_with_algorithm(Algorithm::Secp256k1); let message = b"Test message to sign."; - let signature = Signature::new(&key_pair, message); - assert_eq!(*signature.public_key(), *key_pair.public_key()); - signature.verify(message).unwrap(); + let signature = Signature::new(key_pair.private_key(), message); + signature.verify(key_pair.public_key(), message).unwrap(); } #[test] @@ -591,9 +268,8 @@ mod tests { fn create_signature_bls_normal() { let key_pair = KeyPair::random_with_algorithm(Algorithm::BlsNormal); let message = b"Test message to sign."; - let signature = Signature::new(&key_pair, message); - assert_eq!(*signature.public_key(), *key_pair.public_key()); - signature.verify(message).unwrap(); + let signature = Signature::new(key_pair.private_key(), message); + signature.verify(key_pair.public_key(), message).unwrap(); } #[test] @@ -601,55 +277,8 @@ mod tests { fn create_signature_bls_small() { let key_pair = KeyPair::random_with_algorithm(Algorithm::BlsSmall); let message = b"Test message to sign."; - let signature = Signature::new(&key_pair, message); - assert_eq!(*signature.public_key(), *key_pair.public_key()); - signature.verify(message).unwrap(); - } - - #[test] - #[cfg(all(feature = "rand", not(feature = "ffi_import")))] - fn signatures_of_deduplication_by_public_key() { - let key_pair = KeyPair::random(); - let signatures = [ - SignatureOf::new(&key_pair, &1), - SignatureOf::new(&key_pair, &2), - SignatureOf::new(&key_pair, &3), - ] - .into_iter() - .collect::>(); - // Signatures with the same public key was deduplicated - assert_eq!(signatures.len(), 1); - } - - #[test] - #[cfg(not(feature = "ffi_import"))] - fn signature_wrapper_btree_and_hash_sets_consistent_results() { - use std::collections::{BTreeSet, HashSet}; - - let keys = 5; - let signatures_per_key = 10; - let signatures = core::iter::repeat_with(KeyPair::random) - .take(keys) - .flat_map(|key| { - core::iter::repeat_with(move || key.clone()) - .zip(0..) - .map(|(key, i)| SignatureOf::new(&key, &i)) - .take(signatures_per_key) - }) - .map(SignatureWrapperOf) - .collect::>(); - let hash_set: HashSet<_> = signatures.clone().into_iter().collect(); - let btree_set: BTreeSet<_> = signatures.into_iter().collect(); - - // Check that `hash_set` is subset of `btree_set` - for signature in &hash_set { - assert!(btree_set.contains(signature)); - } - // Check that `btree_set` is subset `hash_set` - for signature in &btree_set { - assert!(hash_set.contains(signature)); - } - // From the above we can conclude that `SignatureWrapperOf` have consistent behavior for `HashSet` and `BTreeSet` + let signature = Signature::new(key_pair.private_key(), message); + signature.verify(key_pair.public_key(), message).unwrap(); } #[test] @@ -666,12 +295,9 @@ mod tests { #[test] fn signature_from_hex_simply_reproduces_the_data() { - let public_key = "e701210312273E8810581E58948D3FB8F9E8AD53AAA21492EBB8703915BBB565A21B7FCC"; let payload = "3a7991af1abb77f3fd27cc148404a6ae4439d095a63591b77c788d53f708a02a1509a611ad6d97b01d871e58ed00c8fd7c3917b6ca61a8c2833a19e000aac2e4"; - let value = Signature::from_hex(PublicKey::from_str(public_key).unwrap(), payload).unwrap(); - - assert_eq!(value.public_key().to_string(), public_key); - assert_eq!(value.payload(), hex::decode(payload).unwrap()); + let value = Signature::from_hex(payload).unwrap(); + assert_eq!(value.payload.as_ref(), &hex::decode(payload).unwrap()); } } diff --git a/data_model/src/block.rs b/data_model/src/block.rs index b0f861757b9..b6dd6be1616 100644 --- a/data_model/src/block.rs +++ b/data_model/src/block.rs @@ -10,11 +10,10 @@ use core::{fmt::Display, time::Duration}; use derive_more::Display; #[cfg(all(feature = "std", feature = "transparent_api"))] -use iroha_crypto::KeyPair; -use iroha_crypto::{HashOf, MerkleTree, SignaturesOf}; +use iroha_crypto::PrivateKey; +use iroha_crypto::{HashOf, MerkleTree, SignatureOf}; use iroha_data_model_derive::model; use iroha_macro::FromVariant; -use iroha_primitives::unique_vec::UniqueVec; use iroha_schema::IntoSchema; use iroha_version::{declare_versioned, version_with_scale}; use parity_scale_codec::{Decode, Encode}; @@ -67,7 +66,7 @@ mod model { pub timestamp_ms: u64, /// Value of view change index. Used to resolve soft forks. #[getset(skip)] - pub view_change_index: u64, + pub view_change_index: u32, /// Estimation of consensus duration (in milliseconds). pub consensus_estimation_ms: u64, } @@ -92,13 +91,34 @@ mod model { /// Block header pub header: BlockHeader, /// Topology of the network at the time of block commit. - pub commit_topology: UniqueVec, + pub commit_topology: Vec, /// array of transactions, which successfully passed validation and consensus step. pub transactions: Vec, /// Event recommendations. pub event_recommendations: Vec, } + /// Signature of a block + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct BlockSignature( + /// Index of the block in the topology + pub u64, + /// Payload + pub SignatureOf, + ); + /// Signed block #[version_with_scale(version = 1, versioned_alias = "SignedBlock")] #[derive( @@ -109,8 +129,9 @@ mod model { #[ffi_type] pub struct SignedBlockV1 { /// Signatures of peers which approved this block. - pub signatures: SignaturesOf, + pub signatures: Vec, /// Block payload + #[serde(flatten)] pub payload: BlockPayload, } } @@ -159,16 +180,16 @@ impl SignedBlock { /// Topology of the network at the time of block commit. #[inline] #[cfg(feature = "transparent_api")] - pub fn commit_topology(&self) -> &UniqueVec { + pub fn commit_topology(&self) -> impl ExactSizeIterator { let SignedBlock::V1(block) = self; - &block.payload.commit_topology + block.payload.commit_topology.iter() } /// Signatures of peers which approved this block. #[inline] - pub fn signatures(&self) -> &SignaturesOf { + pub fn signatures(&self) -> impl ExactSizeIterator + DoubleEndedIterator { let SignedBlock::V1(block) = self; - &block.signatures + block.signatures.iter() } /// Calculate block hash @@ -186,56 +207,15 @@ impl SignedBlock { iroha_crypto::HashOf::new(&block.payload) } - /// Add additional signatures to this block - #[must_use] - #[cfg(feature = "transparent_api")] - pub fn sign(mut self, key_pair: &KeyPair) -> Self { - let SignedBlock::V1(block) = &mut self; - let signature = iroha_crypto::SignatureOf::new(key_pair, &block.payload); - block.signatures.insert(signature); - self - } - - /// Add additional signatures to this block - /// - /// # Errors - /// - /// If given signature doesn't match block hash - #[cfg(feature = "transparent_api")] - pub fn add_signature( - &mut self, - signature: iroha_crypto::SignatureOf, - ) -> Result<(), iroha_crypto::error::Error> { - let SignedBlock::V1(block) = self; - signature.verify(&block.payload)?; - - let SignedBlock::V1(block) = self; - block.signatures.insert(signature); - - Ok(()) - } - /// Add additional signatures to this block #[cfg(feature = "transparent_api")] - pub fn replace_signatures( - &mut self, - signatures: iroha_crypto::SignaturesOf, - ) -> bool { - #[cfg(not(feature = "std"))] - use alloc::collections::BTreeSet; - #[cfg(feature = "std")] - use std::collections::BTreeSet; - + pub fn sign(&mut self, private_key: &PrivateKey, node_pos: usize) { let SignedBlock::V1(block) = self; - block.signatures = BTreeSet::new().into(); - - for signature in signatures { - if self.add_signature(signature).is_err() { - return false; - } - } - true + block.signatures.push(BlockSignature( + node_pos as u64, + SignatureOf::new(private_key, &block.payload), + )); } } @@ -246,13 +226,13 @@ mod candidate { #[derive(Decode, Deserialize)] struct SignedBlockCandidate { - signatures: SignaturesOf, + signatures: Vec, + #[serde(flatten)] payload: BlockPayload, } impl SignedBlockCandidate { fn validate(self) -> Result { - self.validate_signatures()?; self.validate_header()?; if self.payload.transactions.is_empty() { @@ -283,12 +263,6 @@ mod candidate { Ok(()) } - - fn validate_signatures(&self) -> Result<(), &'static str> { - self.signatures - .verify(&self.payload) - .map_err(|_| "Transaction contains invalid signatures") - } } impl Decode for SignedBlockV1 { diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index 9837f00905d..d5b99bead19 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -1184,12 +1184,28 @@ pub mod http { pub filter: PredicateBox, } + /// Signature of query + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct QuerySignature(pub PublicKey, pub SignatureOf); + /// I/O ready structure to send queries. #[derive(Debug, Clone, Encode, Serialize, IntoSchema)] #[version_with_scale(version = 1, versioned_alias = "SignedQuery")] pub struct SignedQueryV1 { /// Signature of the client who sends this query. - pub signature: SignatureOf, + pub signature: QuerySignature, /// Payload pub payload: QueryPayload, } @@ -1225,13 +1241,15 @@ pub mod http { #[derive(Decode, Deserialize)] struct SignedQueryCandidate { - signature: SignatureOf, + signature: QuerySignature, payload: QueryPayload, } impl SignedQueryCandidate { fn validate(self) -> Result { - if self.signature.verify(&self.payload).is_err() { + let QuerySignature(public_key, signature) = &self.signature; + + if signature.verify(public_key, &self.payload).is_err() { return Err("Query signature not valid"); } @@ -1267,7 +1285,7 @@ pub mod http { #[cfg(feature = "transparent_api")] impl SignedQuery { /// Return query signature - pub fn signature(&self) -> &SignatureOf { + pub fn signature(&self) -> &QuerySignature { let SignedQuery::V1(query) = self; &query.signature } @@ -1314,8 +1332,10 @@ pub mod http { #[inline] #[must_use] pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedQuery { + let signature = SignatureOf::new(key_pair.private_key(), &self.payload); + SignedQueryV1 { - signature: SignatureOf::new(key_pair, &self.payload), + signature: QuerySignature(key_pair.public_key().clone(), signature), payload: self.payload, } .into() diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 0a541f70d04..41439df5833 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -9,7 +9,7 @@ use core::{ }; use derive_more::{DebugCustom, Display}; -use iroha_crypto::SignaturesOf; +use iroha_crypto::{KeyPair, PublicKey, SignatureOf}; use iroha_data_model_derive::model; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; @@ -30,6 +30,7 @@ mod model { use getset::{CopyGetters, Getters}; use super::*; + use crate::account::AccountId; /// Either ISI or Wasm binary #[derive( @@ -140,6 +141,22 @@ mod model { pub max_wasm_size_bytes: u64, } + /// Signature of transaction + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct TransactionSignature(pub PublicKey, pub SignatureOf); + /// Transaction that contains at least one signature /// /// `Iroha` and its clients use [`Self`] to send transactions over the network. @@ -155,8 +172,9 @@ mod model { #[ffi_type] pub struct SignedTransactionV1 { /// [`iroha_crypto::SignatureOf`]<[`TransactionPayload`]>. - pub(super) signatures: SignaturesOf, + pub(super) signatures: Vec, /// [`Transaction`] payload. + #[serde(flatten)] pub(super) payload: TransactionPayload, } @@ -292,9 +310,9 @@ impl SignedTransaction { /// Return transaction signatures #[inline] - pub fn signatures(&self) -> &SignaturesOf { + pub fn signatures(&self) -> impl ExactSizeIterator { let SignedTransaction::V1(tx) = self; - &tx.signatures + tx.signatures.iter() } /// Calculate transaction [`Hash`](`iroha_crypto::HashOf`). @@ -305,10 +323,13 @@ impl SignedTransaction { /// Sign transaction with provided key pair. #[must_use] - pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedTransaction { + pub fn sign(self, key_pair: &KeyPair) -> SignedTransaction { let SignedTransaction::V1(mut tx) = self; - let signature = iroha_crypto::SignatureOf::new(key_pair, &tx.payload); - tx.signatures.insert(signature); + let signature = SignatureOf::new(key_pair.private_key(), &tx.payload); + tx.signatures.push(TransactionSignature( + key_pair.public_key().clone(), + signature, + )); SignedTransactionV1 { payload: tx.payload, @@ -346,33 +367,40 @@ mod candidate { #[derive(Decode, Deserialize)] struct SignedTransactionCandidate { - signatures: SignaturesOf, + signatures: Vec, + #[serde(flatten)] payload: TransactionPayload, } impl SignedTransactionCandidate { fn validate(self) -> Result { + self.validate_instructions()?; self.validate_signatures()?; - self.validate_instructions() + + Ok(SignedTransactionV1 { + signatures: self.signatures, + payload: self.payload, + }) } - fn validate_instructions(self) -> Result { + fn validate_instructions(&self) -> Result<(), &'static str> { if let Executable::Instructions(instructions) = &self.payload.instructions { if instructions.is_empty() { return Err("Transaction is empty"); } } - Ok(SignedTransactionV1 { - payload: self.payload, - signatures: self.signatures, - }) + Ok(()) } fn validate_signatures(&self) -> Result<(), &'static str> { - self.signatures - .verify(&self.payload) - .map_err(|_| "Transaction contains invalid signatures") + for TransactionSignature(public_key, signature) in &self.signatures { + signature + .verify(public_key, &self.payload) + .map_err(|_| "Transaction contains invalid signatures")?; + } + + Ok(()) } } @@ -740,8 +768,12 @@ mod http { /// Sign transaction with provided key pair. #[must_use] - pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedTransaction { - let signatures = SignaturesOf::new(key_pair, &self.payload); + pub fn sign(self, key_pair: &KeyPair) -> SignedTransaction { + let signature = SignatureOf::new(key_pair.private_key(), &self.payload); + let signatures = vec![TransactionSignature( + key_pair.public_key().clone(), + signature, + )]; SignedTransactionV1 { payload: self.payload, diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index e9196b814df..7b73f45f0b8 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -642,7 +642,7 @@ }, { "name": "view_change_index", - "type": "u64" + "type": "u32" }, { "name": "consensus_estimation_ms", @@ -679,6 +679,12 @@ } ] }, + "BlockSignature": { + "Tuple": [ + "u64", + "SignatureOf" + ] + }, "BlockStatus": { "Enum": [ { @@ -3048,6 +3054,12 @@ } ] }, + "QuerySignature": { + "Tuple": [ + "PublicKey", + "SignatureOf" + ] + }, "Register": { "Struct": [ { @@ -3567,10 +3579,6 @@ }, "Signature": { "Struct": [ - { - "name": "public_key", - "type": "PublicKey" - }, { "name": "payload", "type": "Vec" @@ -3594,22 +3602,6 @@ "SignatureOf": "Signature", "SignatureOf": "Signature", "SignatureOf": "Signature", - "SignaturesOf": { - "Struct": [ - { - "name": "signatures", - "type": "SortedVec>" - } - ] - }, - "SignaturesOf": { - "Struct": [ - { - "name": "signatures", - "type": "SortedVec>" - } - ] - }, "SignedBlock": { "Enum": [ { @@ -3623,7 +3615,7 @@ "Struct": [ { "name": "signatures", - "type": "SignaturesOf" + "type": "Vec" }, { "name": "payload", @@ -3644,7 +3636,7 @@ "Struct": [ { "name": "signature", - "type": "SignatureOf" + "type": "QuerySignature" }, { "name": "payload", @@ -3665,7 +3657,7 @@ "Struct": [ { "name": "signatures", - "type": "SignaturesOf" + "type": "Vec" }, { "name": "payload", @@ -3776,12 +3768,6 @@ "SortedVec": { "Vec": "PublicKey" }, - "SortedVec>": { - "Vec": "SignatureOf" - }, - "SortedVec>": { - "Vec": "SignatureOf" - }, "String": "String", "StringPredicate": { "Enum": [ @@ -3957,6 +3943,12 @@ } ] }, + "TransactionSignature": { + "Tuple": [ + "PublicKey", + "SignatureOf" + ] + }, "TransactionStatus": { "Enum": [ { @@ -4408,6 +4400,9 @@ } ] }, + "Vec": { + "Vec": "BlockSignature" + }, "Vec": { "Vec": "EventBox" }, @@ -4435,6 +4430,9 @@ "Vec": { "Vec": "QueryOutputBox" }, + "Vec": { + "Vec": "TransactionSignature" + }, "Vec": { "Vec": "TransactionValue" }, diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index 75b8186cc7e..e30bd125b2b 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -13,7 +13,6 @@ workspace = true [dependencies] iroha_crypto = { workspace = true } iroha_data_model = { workspace = true, features = ["http"] } -iroha_schema = { workspace = true } derive_more = { workspace = true, features = ["deref"] } serde = { workspace = true, features = ["derive"] } diff --git a/p2p/src/peer.rs b/p2p/src/peer.rs index bfa8af93b80..801a688dbb6 100644 --- a/p2p/src/peer.rs +++ b/p2p/src/peer.rs @@ -258,7 +258,7 @@ mod run { } } }; - // Reset idle and ping timeout as peer received message from another peer + // Reset idle and ping timeout as peer received message from another peer idle_interval.reset(); ping_interval.reset(); } @@ -428,7 +428,7 @@ mod run { mod state { //! Module for peer stages. - use iroha_crypto::{KeyGenOption, KeyPair, Signature}; + use iroha_crypto::{KeyGenOption, KeyPair, PublicKey, Signature}; use iroha_primitives::addr::SocketAddr; use super::{cryptographer::Cryptographer, *}; @@ -572,8 +572,8 @@ mod state { let write_half = &mut connection.write; let payload = create_payload::(&kx_local_pk, &kx_remote_pk); - let signature = Signature::new(&key_pair, &payload); - let data = signature.encode(); + let signature = Signature::new(key_pair.private_key(), &payload); + let data = (key_pair.public_key(), signature).encode(); let data = &cryptographer.encrypt(data.as_slice())?; @@ -621,13 +621,12 @@ mod state { let data = cryptographer.decrypt(data.as_slice())?; - let signature: Signature = DecodeAll::decode_all(&mut data.as_slice())?; + let (remote_pub_key, signature): (PublicKey, Signature) = + DecodeAll::decode_all(&mut data.as_slice())?; // Swap order of keys since we are verifying for other peer order remote/local keys is reversed let payload = create_payload::(&kx_remote_pk, &kx_local_pk); - signature.verify(&payload)?; - - let (remote_pub_key, _) = signature.into(); + signature.verify(&remote_pub_key, &payload)?; let peer_id = PeerId::new(peer_addr, remote_pub_key); diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index e3c56afd90c..81274c989db 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -92,8 +92,6 @@ types!( BTreeMap, BTreeSet, BTreeSet, - BTreeSet>, - BTreeSet>, BatchedResponse, BatchedResponseV1, BlockEvent, @@ -321,10 +319,6 @@ types!( SignatureOf, SignatureOf, SignatureOf, - SignatureWrapperOf, - SignatureWrapperOf, - SignaturesOf, - SignaturesOf, SignedBlock, SignedBlockV1, SignedQuery,