From e9a2788a8b1fc6e625fabbca2487c55cdfc882eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Ver=C5=A1i=C4=87?= Date: Mon, 27 May 2024 09:40:24 +0300 Subject: [PATCH] refactor: remove public key from transaction and query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marin Veršić --- client/examples/million_accounts_genesis.rs | 2 +- client/src/client.rs | 18 +- client/src/query_builder.rs | 5 +- client/tests/integration/asset.rs | 2 +- .../integration/domain_owner_permissions.rs | 8 +- client/tests/integration/permissions.rs | 10 +- client/tests/integration/roles.rs | 8 +- client/tests/integration/tx_chain_id.rs | 6 +- client/tests/integration/upgrade.rs | 13 +- configs/swarm/executor.wasm | Bin 509453 -> 509091 bytes core/benches/blocks/apply_blocks.rs | 2 +- core/benches/blocks/common.rs | 8 +- core/benches/blocks/validate_blocks.rs | 8 +- core/benches/kura.rs | 4 +- core/benches/validation.rs | 16 +- core/src/block.rs | 118 ++++--- core/src/gossiper.rs | 2 +- core/src/queue.rs | 18 +- core/src/smartcontracts/isi/mod.rs | 4 +- core/src/smartcontracts/isi/query.rs | 20 +- core/src/snapshot.rs | 5 +- core/src/sumeragi/main_loop.rs | 323 +++++++++++------- core/src/sumeragi/message.rs | 18 +- core/src/sumeragi/mod.rs | 17 +- core/src/sumeragi/network_topology.rs | 23 +- core/src/sumeragi/view_change.rs | 54 ++- core/src/tx.rs | 21 +- core/test_network/src/lib.rs | 4 +- crypto/src/lib.rs | 6 - data_model/src/block.rs | 31 +- data_model/src/events/pipeline.rs | 4 +- data_model/src/query/mod.rs | 18 +- data_model/src/transaction.rs | 26 +- docs/source/references/schema.json | 34 +- genesis/src/lib.rs | 2 +- torii/src/lib.rs | 2 - torii/src/routing.rs | 2 +- 37 files changed, 466 insertions(+), 396 deletions(-) diff --git a/client/examples/million_accounts_genesis.rs b/client/examples/million_accounts_genesis.rs index c0e1f92c78a..0afd61d1805 100644 --- a/client/examples/million_accounts_genesis.rs +++ b/client/examples/million_accounts_genesis.rs @@ -3,7 +3,7 @@ use std::{thread, time::Duration}; use iroha::{ crypto::KeyPair, - data_model::{::isi::InstructionBox, prelude::*}, + data_model::{isi::InstructionBox, prelude::*}, }; use iroha_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder}; use iroha_primitives::unique_vec; diff --git a/client/src/client.rs b/client/src/client.rs index 87cab9796df..d3a180951f3 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -450,7 +450,9 @@ impl Client { tx_builder.set_nonce(nonce); }; - tx_builder.with_metadata(metadata).sign(&self.key_pair) + tx_builder + .with_metadata(metadata) + .sign(self.key_pair.private_key()) } /// Signs transaction @@ -458,7 +460,7 @@ impl Client { /// # Errors /// Fails if signature generation fails pub fn sign_transaction(&self, transaction: TransactionBuilder) -> SignedTransaction { - transaction.sign(&self.key_pair) + transaction.sign(self.key_pair.private_key()) } /// Signs query @@ -1634,20 +1636,12 @@ mod tests { use http::Response; use super::*; - use crate::data_model::{asset::Asset, query::error::QueryExecutionFail, ValidationFail}; + use crate::data_model::{asset::Asset, ValidationFail}; #[test] fn certain_errors() -> Result<()> { let mut sut = QueryResponseHandler::>::new(QueryRequest::dummy()); - let responses = vec![ - ( - StatusCode::UNAUTHORIZED, - ValidationFail::QueryFailed(QueryExecutionFail::Signature( - "whatever".to_owned(), - )), - ), - (StatusCode::UNPROCESSABLE_ENTITY, ValidationFail::TooComplex), - ]; + let responses = vec![(StatusCode::UNPROCESSABLE_ENTITY, ValidationFail::TooComplex)]; for (status_code, err) in responses { let resp = Response::builder().status(status_code).body(err.encode())?; diff --git a/client/src/query_builder.rs b/client/src/query_builder.rs index 7a59c893907..d77321f9542 100644 --- a/client/src/query_builder.rs +++ b/client/src/query_builder.rs @@ -1,11 +1,10 @@ use std::fmt::Debug; -use iroha_data_model::query::{IterableQuery, QueryOutputBox}; - use crate::{ client::{Client, QueryOutput, QueryResult}, data_model::query::{ - predicate::PredicateBox, sorting::Sorting, FetchSize, Pagination, Query, QueryOutputBox, + predicate::PredicateBox, sorting::Sorting, FetchSize, IterableQuery, Pagination, Query, + QueryOutputBox, }, }; diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 67e1a801b50..3d0e7c16a62 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -305,7 +305,7 @@ fn find_rate_and_make_exchange_isi_should_succeed() { let transaction = TransactionBuilder::new(ChainId::from("0"), asset_id.account_id().clone()) .with_instructions([instruction]) - .sign(&owner_key_pair); + .sign(owner_key_pair.private_key()); test_client .submit_transaction_blocking(&transaction) diff --git a/client/tests/integration/domain_owner_permissions.rs b/client/tests/integration/domain_owner_permissions.rs index 5ea9177f6a6..07982b282ee 100644 --- a/client/tests/integration/domain_owner_permissions.rs +++ b/client/tests/integration/domain_owner_permissions.rs @@ -26,7 +26,7 @@ fn domain_owner_domain_permissions() -> Result<()> { // Asset definitions can't be registered by "bob@kingdom" by default let transaction = TransactionBuilder::new(chain_id.clone(), bob_id.clone()) .with_instructions([Register::asset_definition(coin.clone())]) - .sign(&bob_keypair); + .sign(bob_keypair.private_key()); let err = test_client .submit_transaction_blocking(&transaction) .expect_err("Tx should fail due to permissions"); @@ -52,7 +52,7 @@ fn domain_owner_domain_permissions() -> Result<()> { test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; let transaction = TransactionBuilder::new(chain_id, bob_id.clone()) .with_instructions([Register::asset_definition(coin)]) - .sign(&bob_keypair); + .sign(bob_keypair.private_key()); test_client.submit_transaction_blocking(&transaction)?; test_client.submit_blocking(Revoke::permission(token, bob_id.clone()))?; @@ -148,7 +148,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { let coin = AssetDefinition::numeric(coin_id.clone()); let transaction = TransactionBuilder::new(chain_id, bob_id.clone()) .with_instructions([Register::asset_definition(coin)]) - .sign(&bob_keypair); + .sign(bob_keypair.private_key()); test_client.submit_transaction_blocking(&transaction)?; // check that "alice@wonderland" as owner of domain can transfer asset definitions in her domain @@ -217,7 +217,7 @@ fn domain_owner_asset_permissions() -> Result<()> { Register::asset_definition(coin), Register::asset_definition(store), ]) - .sign(&bob_keypair); + .sign(bob_keypair.private_key()); test_client.submit_transaction_blocking(&transaction)?; // check that "alice@wonderland" as owner of domain can register and unregister assets in her domain diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 9ac1d322833..ce1d007dd2d 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -102,7 +102,7 @@ fn permissions_disallow_asset_transfer() { ); let transfer_tx = TransactionBuilder::new(chain_id, mouse_id) .with_instructions([transfer_asset]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); let err = iroha .submit_transaction_blocking(&transfer_tx) .expect_err("Transaction was not rejected."); @@ -151,7 +151,7 @@ fn permissions_disallow_asset_burn() { ); let burn_tx = TransactionBuilder::new(chain_id, mouse_id) .with_instructions([burn_asset]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); let err = iroha .submit_transaction_blocking(&burn_tx) @@ -239,7 +239,7 @@ fn permissions_differ_not_only_by_names() { let grant_hats_access_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone()) .with_instructions([allow_alice_to_set_key_value_in_hats]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); client .submit_transaction_blocking(&grant_hats_access_tx) .expect("Failed grant permission to modify Mouse's hats"); @@ -275,7 +275,7 @@ fn permissions_differ_not_only_by_names() { let grant_shoes_access_tx = TransactionBuilder::new(chain_id, mouse_id) .with_instructions([allow_alice_to_set_key_value_in_shoes]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); client .submit_transaction_blocking(&grant_shoes_access_tx) @@ -328,7 +328,7 @@ fn stored_vs_granted_token_payload() -> Result<()> { let transaction = TransactionBuilder::new(chain_id, mouse_id) .with_instructions([allow_alice_to_set_key_value_in_mouse_asset]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); iroha .submit_transaction_blocking(&transaction) .expect("Failed to grant permission to alice."); diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 32080824fb1..f0590335081 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -76,7 +76,7 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { let grant_role = Grant::role(role_id.clone(), alice_id.clone()); let grant_role_tx = TransactionBuilder::new(chain_id, mouse_id.clone()) .with_instructions([grant_role]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); test_client.submit_transaction_blocking(&grant_role_tx)?; // Alice modifies Mouse's metadata @@ -236,7 +236,7 @@ fn grant_revoke_role_permissions() -> Result<()> { let grant_role = Grant::role(role_id.clone(), alice_id.clone()); let grant_role_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone()) .with_instructions([grant_role]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); test_client.submit_transaction_blocking(&grant_role_tx)?; let set_key_value = SetKeyValue::account( @@ -263,7 +263,7 @@ fn grant_revoke_role_permissions() -> Result<()> { // Alice can modify Mouse's metadata after permission token is granted to role let grant_role_permission_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone()) .with_instructions([grant_role_permission]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); test_client.submit_transaction_blocking(&grant_role_permission_tx)?; let found_permissions = test_client .request(FindPermissionsByAccountId::new(alice_id.clone()))? @@ -274,7 +274,7 @@ fn grant_revoke_role_permissions() -> Result<()> { // Alice can't modify Mouse's metadata after permission token is removed from role let revoke_role_permission_tx = TransactionBuilder::new(chain_id.clone(), mouse_id.clone()) .with_instructions([revoke_role_permission]) - .sign(&mouse_keypair); + .sign(mouse_keypair.private_key()); test_client.submit_transaction_blocking(&revoke_role_permission_tx)?; let found_permissions = test_client .request(FindPermissionsByAccountId::new(alice_id.clone()))? diff --git a/client/tests/integration/tx_chain_id.rs b/client/tests/integration/tx_chain_id.rs index 1963eec7861..02420e973ff 100644 --- a/client/tests/integration/tx_chain_id.rs +++ b/client/tests/integration/tx_chain_id.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use iroha_client::data_model::prelude::*; +use iroha::data_model::prelude::*; use iroha_primitives::numeric::numeric; use test_network::*; use test_samples::gen_account_in; @@ -44,10 +44,10 @@ fn send_tx_with_different_chain_id() { ); let asset_transfer_tx_0 = TransactionBuilder::new(chain_id_0, sender_id.clone()) .with_instructions([transfer_instruction.clone()]) - .sign(&sender_keypair); + .sign(sender_keypair.private_key()); let asset_transfer_tx_1 = TransactionBuilder::new(chain_id_1, sender_id.clone()) .with_instructions([transfer_instruction]) - .sign(&sender_keypair); + .sign(sender_keypair.private_key()); test_client .submit_transaction_blocking(&asset_transfer_tx_0) .unwrap(); diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 1208ced8a7a..fa3edd4587f 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -4,7 +4,6 @@ use eyre::Result; use futures_util::TryStreamExt as _; use iroha::{ client::{self, Client, QueryResult}, - crypto::KeyPair, data_model::prelude::*, }; use iroha_logger::info; @@ -23,11 +22,9 @@ fn executor_upgrade_should_work() -> Result<()> { let admin_id: AccountId = format!("{ADMIN_PUBLIC_KEY_MULTIHASH}@admin") .parse() .unwrap(); - let admin_keypair = KeyPair::new( - admin_id.signatory().clone(), - ADMIN_PRIVATE_KEY_MULTIHASH.parse().unwrap(), - ) - .unwrap(); + let admin_private_key = ADMIN_PRIVATE_KEY_MULTIHASH + .parse::() + .unwrap(); let (_rt, _peer, client) = ::new().with_port(10_795).start_with_runtime(); wait_for_genesis_committed(&vec![client.clone()], 0); @@ -48,7 +45,7 @@ fn executor_upgrade_should_work() -> Result<()> { let transfer_alice_rose = Transfer::asset_numeric(alice_rose, 1u32, admin_id.clone()); let transfer_rose_tx = TransactionBuilder::new(chain_id.clone(), admin_id.clone()) .with_instructions([transfer_alice_rose.clone()]) - .sign(&admin_keypair); + .sign(&admin_private_key); let _ = client .submit_transaction_blocking(&transfer_rose_tx) .expect_err("Should fail"); @@ -62,7 +59,7 @@ fn executor_upgrade_should_work() -> Result<()> { // Creating new transaction instead of cloning, because we need to update it's creation time let transfer_rose_tx = TransactionBuilder::new(chain_id, admin_id.clone()) .with_instructions([transfer_alice_rose]) - .sign(&admin_keypair); + .sign(&admin_private_key); client .submit_transaction_blocking(&transfer_rose_tx) .expect("Should succeed"); diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 48d80950691278dd11ec4964ae5b447f8d9da398..d4618d58c84963ae60c9bbb408bb5c83737e2742 100644 GIT binary patch delta 77015 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 delta 78178 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, instructions: Vec, account_id: AccountId, - key_pair: &KeyPair, + private_key: &PrivateKey, ) -> CommittedBlock { let chain_id = ChainId::from("0"); let transaction = TransactionBuilder::new(chain_id.clone(), account_id) .with_instructions(instructions) - .sign(key_pair); + .sign(private_key); let limits = state.transaction_executor().transaction_limits; 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()], + vec![AcceptedTransaction::accept(transaction, &chain_id, limits).unwrap()], topology.clone(), Vec::new(), ) .chain(0, state) - .sign(key_pair.private_key()) + .sign(private_key) .unpack(|_| {}) .commit(&topology) .unpack(|_| {}) diff --git a/core/benches/blocks/validate_blocks.rs b/core/benches/blocks/validate_blocks.rs index 0d26e9ed407..c9e7e082ab4 100644 --- a/core/benches/blocks/validate_blocks.rs +++ b/core/benches/blocks/validate_blocks.rs @@ -10,7 +10,7 @@ use common::*; pub struct StateValidateBlocks { state: State, instructions: Vec>, - key_pair: KeyPair, + private_key: PrivateKey, account_id: AccountId, } @@ -41,7 +41,7 @@ impl StateValidateBlocks { Self { state, instructions, - key_pair: alice_keypair, + private_key: alice_keypair.private_key().clone(), account_id: alice_id, } } @@ -58,7 +58,7 @@ impl StateValidateBlocks { Self { state, instructions, - key_pair, + private_key, account_id, }: Self, ) { @@ -68,7 +68,7 @@ impl StateValidateBlocks { &mut state_block, instructions, account_id.clone(), - &key_pair, + &private_key, ); let _events = state_block.apply_without_execution(&block); assert_eq!(state_block.height(), i); diff --git a/core/benches/kura.rs b/core/benches/kura.rs index 93705817fd2..63e72ae8dd1 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -28,12 +28,12 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let transfer = Transfer::asset_numeric(alice_xor_id, 10u32, bob_id); let tx = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([transfer]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let transaction_limits = TransactionLimits { max_instruction_number: 4096, max_wasm_size_bytes: 0, }; - let tx = AcceptedTransaction::accept(tx, &chain_id, &transaction_limits) + let tx = AcceptedTransaction::accept(tx, &chain_id, transaction_limits) .expect("Failed to accept Transaction."); let dir = tempfile::tempdir().expect("Could not create tempfile."); let cfg = Config { diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 2e6e702d4b6..914f25e1e0f 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -80,12 +80,12 @@ fn build_test_and_transient_state() -> State { fn accept_transaction(criterion: &mut Criterion) { let chain_id = ChainId::from("0"); - let transaction = build_test_transaction(chain_id.clone()).sign(&STARTER_KEYPAIR); + let transaction = build_test_transaction(chain_id.clone()).sign(STARTER_KEYPAIR.private_key()); let mut success_count = 0; let mut failures_count = 0; let _ = criterion.bench_function("accept", |b| { b.iter(|| { - match AcceptedTransaction::accept(transaction.clone(), &chain_id, &TRANSACTION_LIMITS) { + match AcceptedTransaction::accept(transaction.clone(), &chain_id, TRANSACTION_LIMITS) { Ok(_) => success_count += 1, Err(_) => failures_count += 1, } @@ -98,13 +98,13 @@ fn sign_transaction(criterion: &mut Criterion) { let chain_id = ChainId::from("0"); let transaction = build_test_transaction(chain_id); - let key_pair = KeyPair::random(); + let (_, private_key) = KeyPair::random().into_parts(); let mut count = 0; let _ = criterion.bench_function("sign", |b| { b.iter_batched( || transaction.clone(), |transaction| { - let _: SignedTransaction = transaction.sign(&key_pair); + let _: SignedTransaction = transaction.sign(&private_key); count += 1; }, BatchSize::SmallInput, @@ -117,9 +117,9 @@ fn validate_transaction(criterion: &mut Criterion) { let chain_id = ChainId::from("0"); let transaction = AcceptedTransaction::accept( - build_test_transaction(chain_id.clone()).sign(&STARTER_KEYPAIR), + build_test_transaction(chain_id.clone()).sign(STARTER_KEYPAIR.private_key()), &chain_id, - &TRANSACTION_LIMITS, + TRANSACTION_LIMITS, ) .expect("Failed to accept transaction."); let mut success_count = 0; @@ -142,9 +142,9 @@ fn sign_blocks(criterion: &mut Criterion) { let chain_id = ChainId::from("0"); let transaction = AcceptedTransaction::accept( - build_test_transaction(chain_id.clone()).sign(&STARTER_KEYPAIR), + build_test_transaction(chain_id.clone()).sign(STARTER_KEYPAIR.private_key()), &chain_id, - &TRANSACTION_LIMITS, + TRANSACTION_LIMITS, ) .expect("Failed to accept transaction."); let kura = iroha_core::kura::Kura::blank_kura_for_testing(); diff --git a/core/src/block.rs b/core/src/block.rs index d3dcfc00594..9cccfe87fa1 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -132,8 +132,6 @@ mod pending { commit_topology: Topology, event_recommendations: Vec, ) -> Self { - assert!(!transactions.is_empty(), "Empty block created"); - Self(Pending { commit_topology, transactions, @@ -154,14 +152,17 @@ mod pending { .iter() .map(|value| value.as_ref().hash()) .collect::>() - .hash(), + .hash() + .expect("INTERNAL BUG: Empty block created"), timestamp_ms: SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .expect("Failed to get the current system time") .as_millis() .try_into() .expect("Time should fit into u64"), - view_change_index: view_change_index as u32, + view_change_index: view_change_index + .try_into() + .expect("View change index should fit into u32"), consensus_estimation_ms: DEFAULT_CONSENSUS_ESTIMATION .as_millis() .try_into() @@ -239,7 +240,7 @@ mod chained { mod valid { use indexmap::IndexMap; - use iroha_data_model::ChainId; + use iroha_data_model::{account::AccountId, ChainId}; use super::*; use crate::{state::StateBlock, sumeragi::network_topology::Role}; @@ -289,24 +290,14 @@ mod valid { block: &SignedBlock, topology: &Topology, ) -> Result<(), SignatureVerificationError> { - // TODO: ? - //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]; + let valid_roles: &[Role] = if topology.view_change_index() >= 1 { + &[Role::ValidatingPeer, Role::ObservingPeer] + } else { + &[Role::ValidatingPeer] + }; topology - .filter_signatures_by_roles(roles, block.signatures()) + .filter_signatures_by_roles(valid_roles, block.signatures()) .try_fold(IndexMap::::default(), |mut acc, signature| { let signatory_idx = usize::try_from(signature.0) .map_err(|_err| SignatureVerificationError::UnknownSignatory)?; @@ -333,6 +324,13 @@ mod valid { Ok(()) })?; + Ok(()) + } + + fn verify_no_undefined_signatures( + block: &SignedBlock, + topology: &Topology, + ) -> Result<(), SignatureVerificationError> { if topology .filter_signatures_by_roles(&[Role::Undefined], block.signatures()) .next() @@ -398,7 +396,7 @@ mod valid { block: SignedBlock, topology: &Topology, expected_chain_id: &ChainId, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, state_block: &mut StateBlock<'_>, ) -> WithEvents> { let expected_block_height = state_block.height() + 1; @@ -430,6 +428,7 @@ mod valid { if !block.header().is_genesis() { if let Err(err) = Self::verify_leader_signature(&block, topology) .and_then(|()| Self::verify_validator_signatures(&block, topology)) + .and_then(|()| Self::verify_no_undefined_signatures(&block, topology)) { return WithEvents::new(Err((block, err.into()))); } @@ -460,12 +459,9 @@ mod valid { ))); } - if let Err(error) = Self::validate_transactions( - &block, - expected_chain_id, - genesis_public_key, - state_block, - ) { + if let Err(error) = + Self::validate_transactions(&block, expected_chain_id, genesis_account, state_block) + { return WithEvents::new(Err((block, error.into()))); } @@ -475,7 +471,7 @@ mod valid { fn validate_transactions( block: &SignedBlock, expected_chain_id: &ChainId, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, state_block: &mut StateBlock<'_>, ) -> Result<(), TransactionValidationError> { let is_genesis = block.header().is_genesis(); @@ -486,16 +482,19 @@ mod valid { .cloned() .try_for_each(|CommittedTransaction { value, error }| { let transaction_executor = state_block.transaction_executor(); - let limits = &transaction_executor.transaction_limits; let tx = if is_genesis { AcceptedTransaction::accept_genesis( GenesisTransaction(value), expected_chain_id, - genesis_public_key, + genesis_account, ) } else { - AcceptedTransaction::accept(value, expected_chain_id, limits) + AcceptedTransaction::accept( + value, + expected_chain_id, + transaction_executor.transaction_limits, + ) }?; if error.is_some() { @@ -522,9 +521,21 @@ mod valid { &mut self, signature: BlockSignature, topology: &Topology, - ) -> Result<(), iroha_crypto::Error> { - let signatory = &topology.as_ref()[signature.0 as usize]; - self.0.add_signature(signature, signatory.public_key()) + ) -> Result<(), SignatureVerificationError> { + let signatory_idx = usize::try_from(signature.0) + .expect("INTERNAL BUG: Number of peers exceeds usize::MAX"); + let signatory = &topology.as_ref()[signatory_idx]; + + assert_ne!(Role::Leader, topology.role(signatory)); + if topology.view_change_index() == 0 { + assert_ne!(Role::ObservingPeer, topology.role(signatory),); + } + assert_ne!(Role::Undefined, topology.role(signatory)); + assert_ne!(Role::ProxyTail, topology.role(signatory)); + + self.0 + .add_signature(signature, signatory.public_key()) + .map_err(|_err| SignatureVerificationError::UnknownSignature) } /// Replace block's signatures. Returns previous block signatures @@ -544,6 +555,7 @@ mod valid { if let Err(err) = Self::verify_leader_signature(self.as_ref(), topology) .and_then(|()| Self::verify_validator_signatures(self.as_ref(), topology)) + .and_then(|()| Self::verify_no_undefined_signatures(self.as_ref(), topology)) { self.0.replace_signatures_unchecked(prev_signatures); WithEvents::new(Err(err)) @@ -604,7 +616,9 @@ mod valid { header: BlockHeader { height: 2, previous_block_hash: None, - transactions_hash: None, + transactions_hash: HashOf::from_untyped_unchecked(Hash::prehashed( + [1; Hash::LENGTH], + )), timestamp_ms: 0, view_change_index: 0, consensus_estimation_ms: 0, @@ -635,13 +649,9 @@ mod valid { #[cfg(test)] mod tests { use iroha_crypto::SignatureOf; - use iroha_primitives::unique_vec::UniqueVec; use super::*; - use crate::{ - kura::Kura, query::store::LiveQueryStore, state::State, - sumeragi::network_topology::test_peers, - }; + use crate::sumeragi::network_topology::test_peers; #[test] fn signature_verification_ok() { @@ -892,7 +902,7 @@ mod tests { use iroha_data_model::prelude::*; use iroha_genesis::GENESIS_DOMAIN_ID; use iroha_primitives::unique_vec::UniqueVec; - use test_samples::gen_account_in; + use test_samples::{gen_account_in, SAMPLE_GENESIS_ACCOUNT_ID}; use super::*; use crate::{ @@ -937,10 +947,10 @@ mod tests { Register::asset_definition(AssetDefinition::numeric(asset_definition_id)); // Making two transactions that have the same instruction - let transaction_limits = &state_block.transaction_executor().transaction_limits; + let transaction_limits = state_block.transaction_executor().transaction_limits; let tx = TransactionBuilder::new(chain_id.clone(), alice_id) .with_instructions([create_asset_definition]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx = AcceptedTransaction::accept(tx, &chain_id, transaction_limits).expect("Valid"); // Creating a block of two identical transactions and validating it @@ -994,10 +1004,10 @@ mod tests { Register::asset_definition(AssetDefinition::numeric(asset_definition_id.clone())); // Making two transactions that have the same instruction - let transaction_limits = &state_block.transaction_executor().transaction_limits; + let transaction_limits = state_block.transaction_executor().transaction_limits; let tx = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([create_asset_definition]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx = AcceptedTransaction::accept(tx, &chain_id, transaction_limits).expect("Valid"); let fail_mint = Mint::asset_numeric( @@ -1010,12 +1020,12 @@ mod tests { let tx0 = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([fail_mint]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx0 = AcceptedTransaction::accept(tx0, &chain_id, transaction_limits).expect("Valid"); let tx2 = TransactionBuilder::new(chain_id.clone(), alice_id) .with_instructions([succeed_mint]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx2 = AcceptedTransaction::accept(tx2, &chain_id, transaction_limits).expect("Valid"); // Creating a block of two identical transactions and validating it @@ -1065,7 +1075,7 @@ mod tests { let query_handle = LiveQueryStore::test().start(); let state = State::new(world, kura, query_handle); let mut state_block = state.block(); - let transaction_limits = &state_block.transaction_executor().transaction_limits; + let transaction_limits = state_block.transaction_executor().transaction_limits; let domain_id = DomainId::from_str("domain").expect("Valid"); let create_domain = Register::domain(Domain::new(domain_id)); @@ -1079,12 +1089,12 @@ mod tests { let instructions_accept: [InstructionBox; 2] = [create_domain.into(), create_asset.into()]; let tx_fail = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions(instructions_fail) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx_fail = AcceptedTransaction::accept(tx_fail, &chain_id, transaction_limits).expect("Valid"); let tx_accept = TransactionBuilder::new(chain_id.clone(), alice_id) .with_instructions(instructions_accept) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx_accept = AcceptedTransaction::accept(tx_accept, &chain_id, transaction_limits).expect("Valid"); @@ -1159,11 +1169,11 @@ mod tests { // Sign with `genesis_wrong_key` as peer which has incorrect genesis key pair let tx = TransactionBuilder::new(chain_id.clone(), genesis_wrong_account_id.clone()) .with_instructions([isi]) - .sign(&genesis_wrong_key); + .sign(genesis_wrong_key.private_key()); let tx = AcceptedTransaction::accept_genesis( iroha_genesis::GenesisTransaction(tx), &chain_id, - genesis_wrong_key.public_key(), + &SAMPLE_GENESIS_ACCOUNT_ID, ) .expect("Valid"); @@ -1184,7 +1194,7 @@ mod tests { block, &topology, &chain_id, - genesis_correct_key.public_key(), + &SAMPLE_GENESIS_ACCOUNT_ID, &mut state_block, ) .unpack(|_| {}) diff --git a/core/src/gossiper.rs b/core/src/gossiper.rs index c964bbb151f..4f5018aa9f6 100644 --- a/core/src/gossiper.rs +++ b/core/src/gossiper.rs @@ -110,7 +110,7 @@ impl TransactionGossiper { let state_view = self.state.view(); for tx in txs { - let transaction_limits = &state_view.config.transaction_limits; + let transaction_limits = state_view.config.transaction_limits; match AcceptedTransaction::accept(tx, &self.chain_id, transaction_limits) { Ok(tx) => match self.queue.push(tx, &state_view) { diff --git a/core/src/queue.rs b/core/src/queue.rs index 3dd5f889dda..56e2c3cde56 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -22,12 +22,6 @@ use crate::{prelude::*, EventsSender}; impl AcceptedTransaction { // TODO: We should have another type of transaction like `CheckedTransaction` in the type system? - fn is_signatory_consistent(&self) -> bool { - let authority = self.as_ref().authority(); - let signatory = &self.as_ref().signature().0; - authority.signatory_matches(signatory) - } - /// Check if [`self`] is committed or rejected. fn is_in_blockchain(&self, state_view: &StateView<'_>) -> bool { state_view.has_transaction(self.as_ref().hash()) @@ -77,8 +71,6 @@ pub enum Error { MaximumTransactionsPerUser, /// The transaction is already in the queue IsInQueue, - /// Signatories in signature and payload mismatch - SignatoryInconsistent, } /// Failure that can pop up when pushing transaction into the queue @@ -175,8 +167,6 @@ impl Queue { Err(Error::Expired) } else if tx.is_in_blockchain(state_view) { Err(Error::InBlockchain) - } else if !tx.is_signatory_consistent() { - Err(Error::SignatoryInconsistent) } else { Ok(()) } @@ -436,12 +426,12 @@ pub mod tests { let tx = TransactionBuilder::new_with_time_source(chain_id.clone(), account_id, time_source) .with_instructions(instructions) - .sign(key_pair); + .sign(key_pair.private_key()); let limits = TransactionLimits { max_instruction_number: 4096, max_wasm_size_bytes: 0, }; - AcceptedTransaction::accept(tx, &chain_id, &limits).expect("Failed to accept Transaction.") + AcceptedTransaction::accept(tx, &chain_id, limits).expect("Failed to accept Transaction.") } pub fn world_with_test_domains() -> World { @@ -687,13 +677,13 @@ pub mod tests { TransactionBuilder::new_with_time_source(chain_id.clone(), alice_id, &time_source) .with_instructions(instructions); tx.set_ttl(Duration::from_millis(TTL_MS)); - let tx = tx.sign(&alice_keypair); + let tx = tx.sign(alice_keypair.private_key()); let limits = TransactionLimits { max_instruction_number: 4096, max_wasm_size_bytes: 0, }; let tx_hash = tx.hash(); - let tx = AcceptedTransaction::accept(tx, &chain_id, &limits) + let tx = AcceptedTransaction::accept(tx, &chain_id, limits) .expect("Failed to accept Transaction."); queue .push(tx.clone(), &state_view) diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index 7eecbd9b406..ecf5b39d5f4 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -493,8 +493,8 @@ mod tests { let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new(chain_id.clone(), SAMPLE_GENESIS_ACCOUNT_ID.clone()) .with_instructions(instructions) - .sign(&SAMPLE_GENESIS_ACCOUNT_KEYPAIR); - let tx_limits = &state_block.transaction_executor().transaction_limits; + .sign(SAMPLE_GENESIS_ACCOUNT_KEYPAIR.private_key()); + let tx_limits = state_block.transaction_executor().transaction_limits; assert!(matches!( AcceptedTransaction::accept(tx, &chain_id, tx_limits), Err(AcceptTransactionFail::UnexpectedGenesisAccountSignature) diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 3c1dad7b032..210d07e51a5 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -182,12 +182,6 @@ impl ValidQueryRequest { query: SignedQuery, state_ro: &impl StateReadOnly, ) -> Result { - if !query.authority().signatory_matches(&query.signature().0) { - return Err(Error::Signature(String::from( - "Signature public key doesn't correspond to the account.", - )) - .into()); - } state_ro.world().executor().validate_query( state_ro, query.authority(), @@ -395,15 +389,15 @@ mod tests { let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new(chain_id.clone(), ALICE_ID.clone()) .with_instructions(instructions) - .sign(&ALICE_KEYPAIR); - AcceptedTransaction::accept(tx, &chain_id, &limits)? + .sign(ALICE_KEYPAIR.private_key()); + AcceptedTransaction::accept(tx, &chain_id, limits)? }; let invalid_tx = { let isi = Fail::new("fail".to_owned()); let tx = TransactionBuilder::new(chain_id.clone(), ALICE_ID.clone()) .with_instructions([isi.clone(), isi]) - .sign(&ALICE_KEYPAIR); - AcceptedTransaction::accept(tx, &chain_id, &huge_limits)? + .sign(ALICE_KEYPAIR.private_key()); + AcceptedTransaction::accept(tx, &chain_id, huge_limits)? }; let mut transactions = vec![valid_tx; valid_tx_per_block]; @@ -560,9 +554,9 @@ mod tests { let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new(chain_id.clone(), ALICE_ID.clone()) .with_instructions(instructions) - .sign(&ALICE_KEYPAIR); + .sign(ALICE_KEYPAIR.private_key()); - let tx_limits = &state_block.transaction_executor().transaction_limits; + let tx_limits = state_block.transaction_executor().transaction_limits; let va_tx = AcceptedTransaction::accept(tx, &chain_id, tx_limits)?; let (peer_public_key, _) = KeyPair::random().into_parts(); @@ -584,7 +578,7 @@ mod tests { let unapplied_tx = TransactionBuilder::new(chain_id, ALICE_ID.clone()) .with_instructions([Unregister::account(gen_account_in("domain").0)]) - .sign(&ALICE_KEYPAIR); + .sign(ALICE_KEYPAIR.private_key()); let wrong_hash = unapplied_tx.hash(); let not_found = FindTransactionByHash::new(wrong_hash).execute(&state_view); assert!(matches!( diff --git a/core/src/snapshot.rs b/core/src/snapshot.rs index 08c908e0b73..a2d100e86bb 100644 --- a/core/src/snapshot.rs +++ b/core/src/snapshot.rs @@ -161,7 +161,10 @@ pub fn try_read_snapshot( }; let state = seed.deserialize(&mut deserializer)?; let state_view = state.view(); - let snapshot_height = state_view.height() as usize; + let snapshot_height = state_view + .height() + .try_into() + .expect("Blockchain height should fit into usize"); if snapshot_height > block_count { return Err(TryReadError::MismatchedHeight { snapshot_height, diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index cdd1874a5c4..dffa745ea44 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -1,5 +1,5 @@ //! The main event loop that powers sumeragi. -use std::sync::mpsc; +use std::{collections::BTreeSet, sync::mpsc}; use iroha_crypto::{HashOf, KeyPair}; use iroha_data_model::{block::*, events::pipeline::PipelineEventBox, peer::PeerId}; @@ -209,7 +209,7 @@ impl Sumeragi { fn init_listen_for_genesis( &mut self, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, state: &State, shutdown_receiver: &mut tokio::sync::oneshot::Receiver<()>, ) -> Result<(), EarlyReturn> { @@ -242,7 +242,7 @@ impl Sumeragi { block, &self.topology, &self.chain_id, - genesis_public_key, + genesis_account, &mut state_block, ) .unpack(|e| self.send_event(e)) @@ -286,10 +286,10 @@ impl Sumeragi { } } - fn sumeragi_init_commit_genesis( + fn init_commit_genesis( &mut self, genesis_network: GenesisNetwork, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, state: &State, ) { std::thread::sleep(Duration::from_millis(250)); // TODO: Why this sleep? @@ -303,7 +303,7 @@ impl Sumeragi { let transactions: Vec<_> = genesis_network .into_transactions() .into_iter() - .map(|tx| AcceptedTransaction::accept_genesis(tx, &self.chain_id, genesis_public_key)) + .map(|tx| AcceptedTransaction::accept_genesis(tx, &self.chain_id, genesis_account)) .collect::>() .expect("Genesis invalid"); @@ -353,14 +353,18 @@ impl Sumeragi { .block_committed(block.as_ref(), state_block.world.peers().cloned()); self.connect_peers(&self.topology); + let block_hash = block.as_ref().hash(); + let block_height = block.as_ref().header().height(); + Strategy::kura_store_block(&self.kura, block); + // Commit new block making it's effect visible for the rest of application state_block.commit(); info!( peer_id=%self.peer_id, %prev_role, next_role=%self.role(), - block_hash=%block.as_ref().hash(), - new_height=%block.as_ref().header().height, + block_hash=%block_hash, + new_height=%block_height, "{}", Strategy::LOG_MESSAGE, ); #[cfg(debug_assertions)] @@ -371,8 +375,6 @@ impl Sumeragi { "Topology after commit" ); - Strategy::kura_store_block(&self.kura, block); - // NOTE: This sends `BlockStatus::Applied` event, // so it should be done AFTER public facing state update state_events.into_iter().for_each(|e| self.send_event(e)); @@ -408,7 +410,7 @@ impl Sumeragi { &self, state: &'state State, topology: &Topology, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, BlockCreated { block }: BlockCreated, ) -> Option> { let mut state_block = state.block(); @@ -417,7 +419,7 @@ impl Sumeragi { block, topology, &self.chain_id, - genesis_public_key, + genesis_account, &mut state_block, ) .unpack(|e| self.send_event(e)) @@ -444,14 +446,15 @@ impl Sumeragi { } #[allow(clippy::too_many_lines)] + #[allow(clippy::too_many_arguments)] fn handle_message<'state>( &mut self, message: BlockMessage, state: &'state State, voting_block: &mut Option>, view_change_index: usize, - genesis_public_key: &PublicKey, - voting_signatures: &mut Vec, + genesis_account: &AccountId, + voting_signatures: &mut BTreeSet, #[cfg_attr(not(debug_assertions), allow(unused_variables))] is_genesis_peer: bool, ) { #[allow(clippy::suspicious_operation_groupings)] @@ -467,9 +470,10 @@ impl Sumeragi { // Release block writer before creating new one // FIX: Restore `voting_block` if `handle_block_sync` returns Err // Currently it's not possible because block writer needs to be released + // Look at https://github.com/hyperledger/iroha/issues/4643 let _ = voting_block.take(); - match handle_block_sync(&self.chain_id, block, state, genesis_public_key, &|e| { + match handle_block_sync(&self.chain_id, block, state, genesis_account, &|e| { self.send_event(e) }) { Ok(BlockSyncOk::CommitBlock(block, state_block, topology)) => { @@ -556,7 +560,7 @@ impl Sumeragi { let _ = voting_block.take(); if let Some(mut v_block) = - self.validate_block(state, topology, genesis_public_key, block_created) + self.validate_block(state, topology, genesis_account, block_created) { v_block.block.sign(&self.key_pair, topology); @@ -567,6 +571,7 @@ impl Sumeragi { } else { // FIX: Restore `voting_block` // Currently it's not possible because block writer needs to be released + // Look at https://github.com/hyperledger/iroha/issues/4643 } } (BlockMessage::BlockCreated(block_created), Role::ObservingPeer) => { @@ -579,7 +584,7 @@ impl Sumeragi { let _ = voting_block.take(); if let Some(mut v_block) = - self.validate_block(state, topology, genesis_public_key, block_created) + self.validate_block(state, topology, genesis_account, block_created) { if view_change_index >= 1 { v_block.block.sign(&self.key_pair, topology); @@ -599,6 +604,7 @@ impl Sumeragi { } else { // FIX: Restore `voting_block` // Currently it's not possible because block writer needs to be released + // Look at https://github.com/hyperledger/iroha/issues/4643 } } (BlockMessage::BlockCreated(block_created), Role::ProxyTail) => { @@ -613,98 +619,165 @@ impl Sumeragi { let _ = voting_block.take(); if let Some(mut valid_block) = - self.validate_block(state, &self.topology, genesis_public_key, block_created) + self.validate_block(state, &self.topology, genesis_account, 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 valid_block, - voting_signatures.drain(..), - &self.topology, - ); + + for signature in core::mem::take(voting_signatures) { + if let Err(error) = + valid_block.block.add_signature(signature, &self.topology) + { + debug!(?error, "Signature not valid"); + } + } *voting_block = self.try_commit_block(valid_block, is_genesis_peer); } else { // FIX: Restore `voting_block` // Currently it's not possible because block writer needs to be released + // Look at https://github.com/hyperledger/iroha/issues/4643 } } - (BlockMessage::BlockSigned(BlockSigned { signatures }), Role::ProxyTail) => { + (BlockMessage::BlockSigned(BlockSigned { hash, signature }), Role::ProxyTail) => { info!( peer_id=%self.peer_id, role=%self.role(), "Received block signatures" ); - let roles: &[Role] = if view_change_index >= 1 { - &[Role::ValidatingPeer, Role::ObservingPeer] - } else { - &[Role::ValidatingPeer] - }; - let valid_signatures = self - .topology - .filter_signatures_by_roles(roles, &signatures) - .cloned(); + if let Ok(signatory_idx) = usize::try_from(signature.0) { + let signatory = &self.topology.as_ref()[signatory_idx]; - if let Some(mut voted_block) = voting_block.take() { - add_signatures::(&mut voted_block, valid_signatures, &self.topology); - *voting_block = self.try_commit_block(voted_block, is_genesis_peer); + match self.topology.role(signatory) { + Role::Leader => error!( + peer_id=%self.peer_id, + role=%self.role(), + "Signatory is leader" + ), + Role::Undefined => error!( + peer_id=%self.peer_id, + role=%self.role(), + "Unknown signatory" + ), + Role::ObservingPeer if view_change_index == 0 => error!( + peer_id=%self.peer_id, + role=%self.role(), + "Signatory is observing peer" + ), + Role::ProxyTail => error!( + peer_id=%self.peer_id, + role=%self.role(), + "Signatory is proxy tail" + ), + _ => { + if let Some(mut voted_block) = voting_block.take() { + let actual_hash = voted_block.block.as_ref().hash(); + + if hash != actual_hash { + error!( + peer_id=%self.peer_id, + role=%self.role(), + expected_hash=?hash, + ?actual_hash, + "Block hash mismatch" + ); + } else if let Err(err) = + voted_block.block.add_signature(signature, &self.topology) + { + error!( + peer_id=%self.peer_id, + role=%self.role(), + ?err, + "Signature not valid" + ); + } else { + *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 + if !voting_signatures.insert(signature) { + error!( + peer_id=%self.peer_id, + role=%self.role(), + "Duplicate signature" + ); + } + } + } + } } 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 - voting_signatures.extend(valid_signatures); + error!( + peer_id=%self.peer_id, + role=%self.role(), + "Signatory index exceeds usize::MAX" + ); } } (BlockMessage::BlockCommitted(BlockCommitted { .. }), Role::Leader) if self.topology.is_consensus_required().is_none() => {} ( - BlockMessage::BlockCommitted(BlockCommitted { signatures }), + BlockMessage::BlockCommitted(BlockCommitted { hash, signatures }), Role::Leader | Role::ValidatingPeer | Role::ObservingPeer, ) => { if let Some(mut voted_block) = voting_block.take() { - match voted_block - .block - // 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, &self.topology) - .unpack(|e| self.send_event(e)) - { - Ok(prev_signatures) => { - match voted_block - .block - .commit(&self.topology) - .unpack(|e| self.send_event(e)) - { - Ok(committed_block) => { - self.commit_block(committed_block, voted_block.block_state) - } - Err((mut block, error)) => { - error!( - peer_id=%self.peer_id, - role=%self.role(), - ?error, - "Block failed to be committed" - ); - - block - .replace_signatures(prev_signatures, &self.topology) - .unpack(|e| self.send_event(e)) - .expect("INTERNAL BUG: Failed to replace signatures"); - voted_block.block = block; - *voting_block = Some(voted_block); + let actual_hash = voted_block.block.as_ref().hash(); + + if actual_hash == hash { + match voted_block + .block + // 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 has giveth. + .replace_signatures(signatures, &self.topology) + .unpack(|e| self.send_event(e)) + { + Ok(prev_signatures) => { + match voted_block + .block + .commit(&self.topology) + .unpack(|e| self.send_event(e)) + { + Ok(committed_block) => { + self.commit_block(committed_block, voted_block.state_block) + } + Err((mut block, error)) => { + error!( + peer_id=%self.peer_id, + role=%self.role(), + ?error, + "Block failed to be committed" + ); + + block + .replace_signatures(prev_signatures, &self.topology) + .unpack(|e| self.send_event(e)) + .expect("INTERNAL BUG: Failed to replace signatures"); + voted_block.block = block; + *voting_block = Some(voted_block); + } } } + Err(error) => { + error!( + peer_id=%self.peer_id, + role=%self.role(), + ?error, + "Received incorrect signatures" + ); + + *voting_block = Some(voted_block); + } } - Err(error) => { - error!( - peer_id=%self.peer_id, - role=%self.role(), - ?error, - "Received incorrect signatures" - ); - - *voting_block = Some(voted_block); - } + } else { + error!( + peer_id=%self.peer_id, + role=%self.role(), + expected_hash=?hash, + ?actual_hash, + "Block hash mismatch" + ); } } else { error!( @@ -757,7 +830,7 @@ impl Sumeragi { self.broadcast_packet(msg); } - self.commit_block(committed_block, voting_block.block_state); + self.commit_block(committed_block, voting_block.state_block); return None; } @@ -836,7 +909,7 @@ fn reset_state( was_commit: &mut bool, topology: &mut Topology, voting_block: &mut Option, - voting_signatures: &mut Vec, + voting_signatures: &mut BTreeSet, last_view_change_time: &mut Instant, view_change_time: &mut Duration, ) { @@ -899,26 +972,30 @@ pub(crate) fn run( // Connect peers with initial topology sumeragi.connect_peers(&sumeragi.topology); + let genesis_account = AccountId::new( + iroha_genesis::GENESIS_DOMAIN_ID.clone(), + genesis_network.public_key.clone(), + ); + let span = span!(tracing::Level::TRACE, "genesis").entered(); - let is_genesis_peer = - if state.view().height() == 0 || state.view().latest_block_hash().is_none() { - if let Some(genesis) = genesis_network.genesis { - sumeragi.sumeragi_init_commit_genesis(genesis, &genesis_network.public_key, &state); - true - } else { - if let Err(err) = sumeragi.init_listen_for_genesis( - &genesis_network.public_key, - &state, - &mut shutdown_receiver, - ) { - info!(?err, "Sumeragi Thread is being shut down."); - return; - } - false - } + let is_genesis_peer = if state.view().height() == 0 + || state.view().latest_block_hash().is_none() + { + if let Some(genesis) = genesis_network.genesis { + sumeragi.init_commit_genesis(genesis, &genesis_account, &state); + true } else { + if let Err(err) = + sumeragi.init_listen_for_genesis(&genesis_account, &state, &mut shutdown_receiver) + { + info!(?err, "Sumeragi Thread is being shut down."); + return; + } false - }; + } + } else { + false + }; span.exit(); info!( @@ -929,7 +1006,7 @@ pub(crate) fn run( let mut voting_block = None; // Proxy tail collection of voting block signatures - let mut voting_signatures = Vec::new(); + let mut voting_signatures = BTreeSet::new(); let mut should_sleep = false; let mut view_change_proof_chain = ProofChain::default(); // Duration after which a view change is suggested @@ -1004,7 +1081,7 @@ pub(crate) fn run( &state, &mut voting_block, view_change_index, - &genesis_network.public_key, + &genesis_account, &mut voting_signatures, is_genesis_peer, ); @@ -1085,24 +1162,6 @@ pub(crate) fn run( } } -fn add_signatures( - block: &mut VotingBlock, - signatures: impl IntoIterator, - topology: &Topology, -) { - for signature in signatures { - if let Err(error) = block.block.add_signature(signature, topology) { - let err_msg = "Signature not valid"; - - if EXPECT_VALID { - error!(?error, err_msg); - } else { - debug!(?error, err_msg); - } - } - } -} - /// Type enumerating early return types to reduce cyclomatic /// complexity of the main loop items and allow direct short /// circuiting with the `?` operator. Candidate for `impl @@ -1184,7 +1243,7 @@ fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( chain_id: &ChainId, block: SignedBlock, state: &'state State, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, handle_events: &F, ) -> Result, (SignedBlock, BlockSyncError)> { let block_height = block.header().height; @@ -1234,7 +1293,7 @@ fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( block, &topology, chain_id, - genesis_public_key, + genesis_account, &mut state_block, ) .unpack(handle_events) @@ -1265,6 +1324,7 @@ fn handle_block_sync<'state, F: Fn(PipelineEventBox)>( #[cfg(test)] mod tests { + use iroha_genesis::GENESIS_DOMAIN_ID; use test_samples::gen_account_in; use tokio::test; @@ -1287,10 +1347,13 @@ mod tests { chain_id: &ChainId, topology: &Topology, leader_private_key: &PrivateKey, - ) -> (State, Arc, SignedBlock, PublicKey) { + ) -> (State, Arc, SignedBlock, AccountId) { // Predefined world state let (alice_id, alice_keypair) = gen_account_in("wonderland"); - let genesis_public_key = alice_keypair.public_key().clone(); + let genesis_account = AccountId::new( + GENESIS_DOMAIN_ID.clone(), + alice_keypair.public_key().clone(), + ); let account = Account::new(alice_id.clone()).build(&alice_id); let domain_id = "wonderland".parse().expect("Valid"); let mut domain = Domain::new(domain_id).build(&alice_id); @@ -1308,11 +1371,11 @@ mod tests { // Making two transactions that have the same instruction let tx = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([fail_box]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx = AcceptedTransaction::accept( tx, chain_id, - &state_block.transaction_executor().transaction_limits, + state_block.transaction_executor().transaction_limits, ) .expect("Valid"); @@ -1342,21 +1405,21 @@ mod tests { let tx1 = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([create_asset_definition1]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx1 = AcceptedTransaction::accept( tx1, chain_id, - &state_block.transaction_executor().transaction_limits, + state_block.transaction_executor().transaction_limits, ) .map(Into::into) .expect("Valid"); let tx2 = TransactionBuilder::new(chain_id.clone(), alice_id) .with_instructions([create_asset_definition2]) - .sign(&alice_keypair); + .sign(alice_keypair.private_key()); let tx2 = AcceptedTransaction::accept( tx2, chain_id, - &state_block.transaction_executor().transaction_limits, + state_block.transaction_executor().transaction_limits, ) .map(Into::into) .expect("Valid"); @@ -1368,7 +1431,7 @@ mod tests { .unpack(|_| {}) }; - (state, kura, block.into(), genesis_public_key) + (state, kura, block.into(), genesis_account) } #[test] diff --git a/core/src/sumeragi/message.rs b/core/src/sumeragi/message.rs index 390763094f7..bf4d1bf5d49 100644 --- a/core/src/sumeragi/message.rs +++ b/core/src/sumeragi/message.rs @@ -1,4 +1,5 @@ //! Contains message structures for p2p communication during consensus. +use iroha_crypto::HashOf; use iroha_data_model::block::{BlockSignature, SignedBlock}; use iroha_macro::*; use parity_scale_codec::{Decode, Encode}; @@ -56,14 +57,22 @@ impl From<&ValidBlock> for BlockCreated { #[derive(Debug, Clone, Decode, Encode)] #[non_exhaustive] pub struct BlockSigned { - /// Set of signatures. - pub signatures: Vec, + /// Hash of the block being signed. + pub hash: HashOf, + /// Signature of the block + pub signature: BlockSignature, } impl From<&ValidBlock> for BlockSigned { fn from(block: &ValidBlock) -> Self { Self { - signatures: block.as_ref().signatures().cloned().collect(), + hash: block.as_ref().hash(), + signature: block + .as_ref() + .signatures() + .last() + .cloned() + .expect("INTERNAL BUG: Block must have at least one signature"), } } } @@ -72,6 +81,8 @@ 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: Vec, } @@ -79,6 +90,7 @@ pub struct BlockCommitted { impl From<&CommittedBlock> for BlockCommitted { fn from(block: &CommittedBlock) -> Self { Self { + hash: block.as_ref().hash(), signatures: block.as_ref().signatures().cloned().collect(), } } diff --git a/core/src/sumeragi/mod.rs b/core/src/sumeragi/mod.rs index 6c0fe4d93ae..a0df438d211 100644 --- a/core/src/sumeragi/mod.rs +++ b/core/src/sumeragi/mod.rs @@ -9,7 +9,7 @@ use std::{ use eyre::Result; use iroha_config::parameters::actual::{Common as CommonConfig, Sumeragi as SumeragiConfig}; -use iroha_data_model::{block::SignedBlock, prelude::*}; +use iroha_data_model::{account::AccountId, block::SignedBlock, prelude::*}; use iroha_genesis::GenesisNetwork; use iroha_logger::prelude::*; use network_topology::{Role, Topology}; @@ -72,7 +72,7 @@ impl SumeragiHandle { fn replay_block( chain_id: &ChainId, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, block: &SignedBlock, state_block: &mut StateBlock<'_>, events_sender: &EventsSender, @@ -85,7 +85,7 @@ impl SumeragiHandle { block.clone(), topology, chain_id, - genesis_public_key, + genesis_account, state_block, ) .unpack(|e| { @@ -178,11 +178,16 @@ impl SumeragiHandle { }; } + let genesis_account = AccountId::new( + iroha_genesis::GENESIS_DOMAIN_ID.clone(), + genesis_network.public_key.clone(), + ); + for block in blocks_iter { let mut state_block = state.block(); Self::replay_block( &common_config.chain_id, - &genesis_network.public_key, + &genesis_account, &block, &mut state_block, &events_sender, @@ -268,7 +273,7 @@ pub struct VotingBlock<'state> { /// At what time has this peer voted for this block pub voted_at: Instant, /// [`WorldState`] after applying transactions to it but before it was committed - pub block_state: StateBlock<'state>, + pub state_block: StateBlock<'state>, } impl AsRef for VotingBlock<'_> { @@ -283,7 +288,7 @@ impl VotingBlock<'_> { VotingBlock { block, voted_at: Instant::now(), - block_state: state_block, + state_block, } } } diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index 0b7ec997c4b..4b58d52a5b3 100644 --- a/core/src/sumeragi/network_topology.rs +++ b/core/src/sumeragi/network_topology.rs @@ -20,7 +20,12 @@ use iroha_data_model::{ /// /// Above is an illustration of how the various operations work for a f = 2 topology. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Topology(Vec, usize); +pub struct Topology( + /// Ordered set of peers + Vec, + /// Current view change index. Reset to 0 after every block commit + usize, +); /// Topology with at least one peer #[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)] @@ -147,9 +152,11 @@ impl Topology { }; } - signatures - .into_iter() - .filter(move |signature| filtered.contains(&(signature.0 as usize))) + signatures.into_iter().filter(move |signature| { + filtered.contains( + &(usize::try_from(signature.0).expect("Peer index should fit into usize")), + ) + }) } /// What role does this peer have in the topology. @@ -209,13 +216,17 @@ impl Topology { self.0 = topology.into_iter().map(|(_, peer)| peer).collect(); } - /// Recreate topology for given block + /// Rotate topology after a block has been committed 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.lift_up_peers( + block + .signatures() + .map(|s| s.0.try_into().expect("Peer index should fit into usize")), + ); self.rotate_set_a(); self.update_peer_list(new_peers); self.1 = 0; diff --git a/core/src/sumeragi/view_change.rs b/core/src/sumeragi/view_change.rs index c2a7b80bb46..508594c9bc9 100644 --- a/core/src/sumeragi/view_change.rs +++ b/core/src/sumeragi/view_change.rs @@ -2,6 +2,7 @@ //! Where view change is a process of changing topology due to some faulty network behavior. use eyre::Result; +use indexmap::IndexSet; use iroha_crypto::{HashOf, PublicKey, SignatureOf}; use iroha_data_model::block::SignedBlock; use parity_scale_codec::{Decode, Encode}; @@ -26,7 +27,7 @@ struct ViewChangeProofPayload { /// Hash of the latest committed block. latest_block: HashOf, /// Within a round, what is the index of the view change this proof is trying to prove. - view_change_index: u64, + view_change_index: u32, } /// 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. @@ -43,7 +44,9 @@ pub struct ProofBuilder(SignedViewChangeProof); impl ProofBuilder { /// Constructor from index. pub fn new(latest_block: HashOf, view_change_index: usize) -> Self { - let view_change_index = view_change_index as u64; + let view_change_index = view_change_index + .try_into() + .expect("INTERNAL BUG: view change index is too large"); let proof = SignedViewChangeProof { payload: ViewChangeProofPayload { @@ -67,13 +70,21 @@ impl ProofBuilder { impl SignedViewChangeProof { /// Verify the signatures of `other` and add them to this proof. fn merge_signatures(&mut self, other: Vec, topology: &Topology) { - for (public_key, signature) in other { - if topology.position(&public_key).is_none() { - continue; - } - - self.signatures.push((public_key, signature)); - } + let signatures = core::mem::take(&mut self.signatures) + .into_iter() + .collect::>(); + + self.signatures = other + .into_iter() + .fold(signatures, |mut acc, (public_key, signature)| { + if topology.position(&public_key).is_some() { + acc.insert((public_key, signature)); + } + + acc + }) + .into_iter() + .collect(); } /// Verify if the proof is valid, given the peers in `topology`. @@ -104,8 +115,10 @@ impl ProofChain { .iter() .enumerate() .take_while(|(i, proof)| { + let view_change_index = proof.payload.view_change_index as usize; + proof.payload.latest_block == latest_block - && proof.payload.view_change_index == (*i as u64) + && view_change_index == *i && proof.verify(topology) }) .count() @@ -118,8 +131,8 @@ impl ProofChain { .iter() .enumerate() .take_while(|(i, proof)| { - proof.payload.latest_block == latest_block - && proof.payload.view_change_index == (*i as u64) + let view_change_index = proof.payload.view_change_index as usize; + proof.payload.latest_block == latest_block && view_change_index == *i }) .count(); self.0.truncate(valid_count); @@ -140,7 +153,7 @@ impl ProofChain { return Err(Error::BlockHashMismatch); } let next_unfinished_view_change = self.verify_with_state(topology, latest_block); - if new_proof.payload.view_change_index != (next_unfinished_view_change as u64) { + if new_proof.payload.view_change_index as usize != next_unfinished_view_change { return Err(Error::ViewChangeNotFound); // We only care about the current view change that may or may not happen. } @@ -216,13 +229,20 @@ mod candidate { fn validate(self) -> Result { self.validate_signatures()?; - // TODO: If it is possible, we should instead reject decoding proofs that - // have duplicated signatures. This would require change in the code as well - let unique_signatures = self.signatures.into_iter().collect::>(); + self.signatures + .iter() + .map(|signature| &signature.0) + .try_fold(IndexSet::new(), |mut acc, elem| { + if !acc.insert(elem) { + return Err("Duplicate signature in proof"); + } + + Ok(acc) + })?; Ok(SignedViewChangeProof { + signatures: self.signatures, payload: self.payload, - signatures: unique_signatures.into_iter().collect(), }) } diff --git a/core/src/tx.rs b/core/src/tx.rs index efbd0170f82..624a77f9918 100644 --- a/core/src/tx.rs +++ b/core/src/tx.rs @@ -14,9 +14,7 @@ pub use iroha_data_model::prelude::*; use iroha_data_model::{ isi::error::Mismatch, query::error::FindError, - transaction::{ - error::TransactionLimitError, TransactionLimits, TransactionPayload, TransactionSignature, - }, + transaction::{error::TransactionLimitError, TransactionLimits, TransactionPayload}, }; use iroha_genesis::GenesisTransaction; use iroha_logger::{debug, error}; @@ -71,7 +69,7 @@ impl AcceptedTransaction { pub fn accept_genesis( tx: GenesisTransaction, expected_chain_id: &ChainId, - genesis_public_key: &PublicKey, + genesis_account: &AccountId, ) -> Result { let actual_chain_id = tx.0.chain_id(); @@ -82,13 +80,8 @@ impl AcceptedTransaction { })); } - let TransactionSignature(public_key, signature) = tx.0.signature(); - if public_key != genesis_public_key { - return Err(SignatureVerificationFail { - signature: signature.clone(), - reason: "Signature doesn't correspond to genesis public key".to_string(), - } - .into()); + if genesis_account != tx.0.authority() { + return Err(AcceptTransactionFail::UnexpectedGenesisAccountSignature); } Ok(Self(tx.0)) @@ -102,7 +95,7 @@ impl AcceptedTransaction { pub fn accept( tx: SignedTransaction, expected_chain_id: &ChainId, - limits: &TransactionLimits, + limits: TransactionLimits, ) -> Result { let actual_chain_id = tx.chain_id(); @@ -113,10 +106,6 @@ impl AcceptedTransaction { })); } - if *iroha_genesis::GENESIS_DOMAIN_ID == *tx.authority().domain_id() { - return Err(AcceptTransactionFail::UnexpectedGenesisAccountSignature); - } - match &tx.instructions() { Executable::Instructions(instructions) => { let instruction_count = instructions.len(); diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index a8da743c90c..1898e6ab328 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -1,7 +1,5 @@ //! Module for starting peers and networks. Used only for tests use core::{fmt::Debug, str::FromStr as _, time::Duration}; -#[cfg(debug_assertions)] -use std::sync::atomic::AtomicBool; use std::{collections::BTreeMap, ops::Deref, path::Path, sync::Arc, thread}; use eyre::Result; @@ -146,7 +144,7 @@ impl TestGenesis for GenesisNetwork { impl Network { /// Collect the freeze handles from all the peers in the network. #[cfg(debug_assertions)] - pub fn get_freeze_status_handles(&self) -> Vec { + pub fn get_freeze_status_handles(&self) -> Vec { self.peers() .filter_map(|peer| peer.irohad.as_ref()) .map(|iroha| iroha.freeze_status()) diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index aa7270b6e00..dfe5fa32471 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -839,12 +839,6 @@ pub mod error { /// Returned when an error occurs during key generation #[display(fmt = "Key generation failed. {_0}")] KeyGen(String), - /// Returned when an error occurs during digest generation - #[display(fmt = "Digest generation failed. {_0}")] - DigestGen(String), - /// Returned when an error occurs during creation of [`SignaturesOf`] - #[display(fmt = "`SignaturesOf` must contain at least one signature")] - EmptySignatureIter, /// A General purpose error message that doesn't fit in any category #[display(fmt = "General error. {_0}")] // This is going to cause a headache Other(String), diff --git a/data_model/src/block.rs b/data_model/src/block.rs index 6c24e10ba5e..a76ff2c288d 100644 --- a/data_model/src/block.rs +++ b/data_model/src/block.rs @@ -54,12 +54,11 @@ mod model { #[getset(get_copy = "pub")] pub height: u64, /// Hash of the previous block in the chain. - #[getset(get = "pub")] + #[getset(get_copy = "pub")] pub previous_block_hash: Option>, /// Hash of merkle tree root of transactions' hashes. - #[getset(get = "pub")] - // TODO: How can it be `None`??? - pub transactions_hash: Option>>, + #[getset(get_copy = "pub")] + pub transactions_hash: HashOf>, /// Creation timestamp (unix time in milliseconds). #[getset(skip)] pub timestamp_ms: u64, @@ -231,6 +230,22 @@ impl SignedBlock { signature: BlockSignature, public_key: &iroha_crypto::PublicKey, ) -> Result<(), iroha_crypto::Error> { + #[cfg(not(feature = "std"))] + use alloc::{borrow::ToOwned as _, collections::BTreeSet}; + #[cfg(feature = "std")] + use std::collections::BTreeSet; + + if !self + .signatures() + .map(|signature| signature.0) + .collect::>() + .insert(signature.0) + { + return Err(iroha_crypto::Error::Signing( + "Duplicate signature".to_owned(), + )); + } + signature.1.verify(public_key, self.payload())?; let SignedBlock::V1(block) = self; @@ -292,10 +307,6 @@ mod candidate { self.validate_signatures()?; self.validate_header()?; - if self.payload.transactions.is_empty() { - return Err("Block is empty"); - } - Ok(SignedBlockV1 { signatures: self.signatures, payload: self.payload, @@ -305,6 +316,7 @@ mod candidate { fn validate_signatures(&self) -> Result<(), &'static str> { self.signatures .iter() + .map(|signature| signature.0) .try_fold(BTreeSet::new(), |mut acc, elem| { if !acc.insert(elem) { return Err("Duplicate signature in block"); @@ -324,7 +336,8 @@ mod candidate { .iter() .map(|value| value.as_ref().hash()) .collect::>() - .hash(); + .hash() + .ok_or("Block is empty")?; if expected_txs_hash != actual_txs_hash { return Err("Transactions' hash incorrect. Expected: {expected_txs_hash:?}, actual: {actual_txs_hash:?}"); diff --git a/data_model/src/events/pipeline.rs b/data_model/src/events/pipeline.rs index 2b7bba7f745..f4708ed9c93 100644 --- a/data_model/src/events/pipeline.rs +++ b/data_model/src/events/pipeline.rs @@ -354,7 +354,9 @@ mod tests { Self { height, previous_block_hash: None, - transactions_hash: None, + transactions_hash: HashOf::from_untyped_unchecked(Hash::prehashed( + [1_u8; Hash::LENGTH], + )), timestamp_ms: 0, view_change_index: 0, consensus_estimation_ms: 0, diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index 4bcc2381c76..ea32111d21a 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -1268,7 +1268,7 @@ pub mod http { Serialize, IntoSchema, )] - pub struct QuerySignature(pub PublicKey, pub SignatureOf); + pub struct QuerySignature(pub SignatureOf); /// I/O ready structure to send queries. #[derive(Debug, Clone, Encode, Serialize, IntoSchema)] @@ -1311,11 +1311,11 @@ pub mod http { impl SignedQueryCandidate { fn validate(self) -> Result { - let QuerySignature(public_key, signature) = &self.signature; + let QuerySignature(signature) = &self.signature; - if signature.verify(public_key, &self.payload).is_err() { - return Err("Query signature not valid"); - } + signature + .verify(&self.payload.authority.signatory, &self.payload) + .map_err(|_| "Query signature is not valid")?; Ok(SignedQueryV1 { payload: self.payload, @@ -1438,7 +1438,7 @@ pub mod http { let signature = SignatureOf::new(key_pair.private_key(), &self.payload); SignedQueryV1 { - signature: QuerySignature(key_pair.public_key().clone(), signature), + signature: QuerySignature(signature), payload: self.payload, } .into() @@ -1488,12 +1488,6 @@ pub mod error { )] #[cfg_attr(feature = "std", derive(thiserror::Error))] pub enum QueryExecutionFail { - /// Query has the wrong signature: {0} - Signature( - #[skip_from] - #[skip_try_from] - String, - ), /// {0} #[cfg_attr(feature = "std", error(transparent))] Find(FindError), diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 178e06fe0ea..a86e069ced1 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -158,10 +158,7 @@ mod model { Serialize, IntoSchema, )] - pub struct TransactionSignature( - pub iroha_crypto::PublicKey, - pub SignatureOf, - ); + pub struct TransactionSignature(pub SignatureOf); /// Transaction that contains a signature /// @@ -263,6 +260,13 @@ declare_versioned!(SignedTransaction 1..2, Debug, Display, Clone, PartialEq, Eq, declare_versioned!(SignedTransaction 1..2, Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, FromVariant, IntoSchema); impl SignedTransaction { + /// Transaction payload. Used for tests + #[cfg(feature = "transparent_api")] + pub fn payload(&self) -> &TransactionPayload { + let SignedTransaction::V1(tx) = self; + &tx.payload + } + /// Return transaction instructions #[inline] pub fn instructions(&self) -> &Executable { @@ -383,10 +387,11 @@ mod candidate { } fn validate_signature(&self) -> Result<(), &'static str> { - let TransactionSignature(public_key, signature) = &self.signature; + let TransactionSignature(signature) = &self.signature; + signature - .verify(public_key, &self.payload) - .map_err(|_| "Transaction contains invalid signatures")?; + .verify(&self.payload.authority.signatory, &self.payload) + .map_err(|_| "Transaction signature is invalid")?; Ok(()) } @@ -756,11 +761,8 @@ mod http { /// Sign transaction with provided key pair. #[must_use] - pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedTransaction { - let signature = TransactionSignature( - key_pair.public_key().clone(), - SignatureOf::new(key_pair.private_key(), &self.payload), - ); + pub fn sign(self, private_key: &iroha_crypto::PrivateKey) -> SignedTransaction { + let signature = TransactionSignature(SignatureOf::new(private_key, &self.payload)); SignedTransactionV1 { signature, diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 490b88ef5e2..44ced7ddc36 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -612,7 +612,7 @@ }, { "name": "transactions_hash", - "type": "Option>>" + "type": "HashOf>" }, { "name": "timestamp_ms", @@ -2469,9 +2469,6 @@ "Option": { "Option": "Duration" }, - "Option>>": { - "Option": "HashOf>" - }, "Option>": { "Option": "HashOf" }, @@ -2904,32 +2901,27 @@ }, "QueryExecutionFail": { "Enum": [ - { - "tag": "Signature", - "discriminant": 0, - "type": "String" - }, { "tag": "Find", - "discriminant": 1, + "discriminant": 0, "type": "FindError" }, { "tag": "Conversion", - "discriminant": 2, + "discriminant": 1, "type": "String" }, { "tag": "UnknownCursor", - "discriminant": 3 + "discriminant": 2 }, { "tag": "FetchSizeTooBig", - "discriminant": 4 + "discriminant": 3 }, { "tag": "InvalidSingularParameters", - "discriminant": 5 + "discriminant": 4 } ] }, @@ -3020,12 +3012,7 @@ } ] }, - "QuerySignature": { - "Tuple": [ - "PublicKey", - "SignatureOf" - ] - }, + "QuerySignature": "SignatureOf", "Register": { "Struct": [ { @@ -3903,12 +3890,7 @@ } ] }, - "TransactionSignature": { - "Tuple": [ - "PublicKey", - "SignatureOf" - ] - }, + "TransactionSignature": "SignatureOf", "TransactionStatus": { "Enum": [ { diff --git a/genesis/src/lib.rs b/genesis/src/lib.rs index 0bbdae09468..18c920d80e5 100644 --- a/genesis/src/lib.rs +++ b/genesis/src/lib.rs @@ -170,7 +170,7 @@ impl GenesisTransactionBuilder { ); TransactionBuilder::new(chain_id, genesis_account_id) .with_instructions(self.isi) - .sign(genesis_key_pair) + .sign(genesis_key_pair.private_key()) } /// Add new instruction to the transaction. diff --git a/torii/src/lib.rs b/torii/src/lib.rs index a987608ee3f..4db82d4c7f1 100644 --- a/torii/src/lib.rs +++ b/torii/src/lib.rs @@ -338,7 +338,6 @@ impl Error { Config(_) | StatusSegmentNotFound(_) => StatusCode::NOT_FOUND, PushIntoQueue(err) => match **err { queue::Error::Full => StatusCode::INTERNAL_SERVER_ERROR, - queue::Error::SignatoryInconsistent => StatusCode::UNAUTHORIZED, _ => StatusCode::BAD_REQUEST, }, #[cfg(feature = "telemetry")] @@ -363,7 +362,6 @@ impl Error { Conversion(_) | UnknownCursor | FetchSizeTooBig | InvalidSingularParameters => { StatusCode::BAD_REQUEST } - Signature(_) => StatusCode::UNAUTHORIZED, Find(_) => StatusCode::NOT_FOUND, }, TooComplex => StatusCode::UNPROCESSABLE_ENTITY, diff --git a/torii/src/routing.rs b/torii/src/routing.rs index b75eacf6b05..1d52394630e 100644 --- a/torii/src/routing.rs +++ b/torii/src/routing.rs @@ -53,7 +53,7 @@ pub async fn handle_transaction( ) -> Result { let state_view = state.view(); let transaction_limits = state_view.config.transaction_limits; - let transaction = AcceptedTransaction::accept(transaction, &chain_id, &transaction_limits) + let transaction = AcceptedTransaction::accept(transaction, &chain_id, transaction_limits) .map_err(Error::AcceptTransaction)?; queue .push(transaction, &state_view)