From 8d55b9e8c2349f39c7364b0ee6fa1954659c2491 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 21 Feb 2024 00:36:27 +0300 Subject: [PATCH] [feature] #4212: Prevent account registration without signatures Signed-off-by: Daniil Polyakov --- cli/src/lib.rs | 2 +- client/benches/torii.rs | 4 +- client/benches/tps/utils.rs | 3 +- client/examples/million_accounts_genesis.rs | 7 +- client/examples/tutorial.rs | 2 +- client/tests/integration/add_account.rs | 10 +- client/tests/integration/asset.rs | 14 +- client/tests/integration/asset_propagation.rs | 2 +- client/tests/integration/burn_public_keys.rs | 2 +- .../integration/domain_owner_permissions.rs | 16 +- .../multiple_blocks_created.rs | 2 +- .../extra_functional/unregister_peer.rs | 2 +- client/tests/integration/mod.rs | 7 + client/tests/integration/permissions.rs | 4 +- client/tests/integration/queries/account.rs | 4 +- client/tests/integration/queries/asset.rs | 2 +- client/tests/integration/roles.rs | 6 +- client/tests/integration/sorting.rs | 10 +- client/tests/integration/transfer_asset.rs | 2 +- .../integration/triggers/data_trigger.rs | 14 +- .../integration/triggers/time_trigger.rs | 4 +- client/tests/integration/upgrade.rs | 2 +- client_cli/src/main.rs | 2 +- configs/swarm/executor.wasm | Bin 617513 -> 617891 bytes core/benches/blocks/common.rs | 7 +- core/benches/validation.rs | 4 +- core/src/block.rs | 6 +- core/src/queue.rs | 25 +-- core/src/smartcontracts/isi/account.rs | 24 +-- core/src/smartcontracts/isi/mod.rs | 5 +- core/src/smartcontracts/isi/query.rs | 10 +- core/src/smartcontracts/wasm.rs | 17 +- core/src/sumeragi/main_loop.rs | 2 +- crypto/src/lib.rs | 8 +- data_model/src/account.rs | 171 +++++++++++++++--- data_model/src/events/data/filters.rs | 19 +- data_model/src/predicate.rs | 6 +- genesis/src/lib.rs | 30 +-- smart_contract/src/lib.rs | 4 +- tools/parity_scale_decoder/Cargo.toml | 3 +- tools/parity_scale_decoder/build.rs | 3 +- .../parity_scale_decoder/samples/account.bin | Bin 30 -> 64 bytes .../parity_scale_decoder/samples/account.json | 4 +- tools/parity_scale_decoder/src/main.rs | 10 +- 44 files changed, 319 insertions(+), 162 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 5dbf5318efd..95030eecf89 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -472,7 +472,7 @@ enum TelemetryStartStatus { } fn genesis_account(public_key: PublicKey) -> Account { - Account::new(iroha_genesis::GENESIS_ACCOUNT_ID.clone(), [public_key]) + Account::new(iroha_genesis::GENESIS_ACCOUNT_ID.clone(), public_key) .build(&iroha_genesis::GENESIS_ACCOUNT_ID) } diff --git a/client/benches/torii.rs b/client/benches/torii.rs index 669fcc0c917..e9bb7f48008 100644 --- a/client/benches/torii.rs +++ b/client/benches/torii.rs @@ -64,7 +64,7 @@ fn query_requests(criterion: &mut Criterion) { let create_domain = Register::domain(Domain::new(domain_id.clone())); let account_id = AccountId::new(domain_id.clone(), "account".parse().expect("Valid")); let (public_key, _) = KeyPair::generate().into(); - let create_account = Register::account(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), public_key)); let asset_definition_id = AssetDefinitionId::new(domain_id, "xor".parse().expect("Valid")); let create_asset = Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); @@ -164,7 +164,7 @@ fn instruction_submits(criterion: &mut Criterion) { let create_domain: InstructionBox = Register::domain(Domain::new(domain_id.clone())).into(); let account_id = AccountId::new(domain_id.clone(), "account".parse().expect("Valid")); let (public_key, _) = KeyPair::generate().into(); - let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into(); + let create_account = Register::account(Account::new(account_id.clone(), public_key)).into(); let asset_definition_id = AssetDefinitionId::new(domain_id, "xor".parse().expect("Valid")); let client_config = iroha_client::samples::get_client_config( get_chain_id(), diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index f078481269b..6d417b2e9e4 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -158,8 +158,7 @@ impl MeasurerUnit { let account_id = account_id(self.name); let asset_id = asset_id(self.name); - let register_me = - Register::account(Account::new(account_id, [keypair.public_key().clone()])); + let register_me = Register::account(Account::new(account_id, keypair.public_key().clone())); self.client.submit_blocking(register_me)?; let mint_a_rose = Mint::asset_quantity(1_u32, asset_id); diff --git a/client/examples/million_accounts_genesis.rs b/client/examples/million_accounts_genesis.rs index c618caf700b..7b9f805de7a 100644 --- a/client/examples/million_accounts_genesis.rs +++ b/client/examples/million_accounts_genesis.rs @@ -3,6 +3,7 @@ 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_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder}; use iroha_primitives::unique_vec; @@ -77,7 +78,11 @@ fn create_million_accounts_directly() { format!("bob-{i}").parse().expect("Valid"), ); let create_domain: InstructionBox = Register::domain(Domain::new(domain_id)).into(); - let create_account = Register::account(Account::new(normal_account_id.clone(), [])).into(); + let create_account = Register::account(Account::new( + normal_account_id.clone(), + KeyPair::generate().into_raw_parts().0, + )) + .into(); if test_client .submit_all([create_domain, create_account]) .is_err() diff --git a/client/examples/tutorial.rs b/client/examples/tutorial.rs index bec8227f6a5..b58dbdc95df 100644 --- a/client/examples/tutorial.rs +++ b/client/examples/tutorial.rs @@ -120,7 +120,7 @@ fn account_registration_test(config: Config) -> Result<(), Error> { // #region register_account_generate // Generate a new account - let create_account = Register::account(Account::new(account_id, [public_key])); + let create_account = Register::account(Account::new(account_id, public_key)); // #endregion register_account_generate // #region register_account_prepare_tx diff --git a/client/tests/integration/add_account.rs b/client/tests/integration/add_account.rs index 49d15f1ebd9..58c5e35e3c9 100644 --- a/client/tests/integration/add_account.rs +++ b/client/tests/integration/add_account.rs @@ -5,6 +5,8 @@ use iroha_client::{client, data_model::prelude::*}; use iroha_config::parameters::actual::Root as Config; use test_network::*; +use crate::integration::new_account_with_random_public_key; + #[test] // This test suite is also covered at the UI level in the iroha_client_cli tests // in test_register_accounts.py @@ -16,14 +18,18 @@ fn client_add_account_with_name_length_more_than_limit_should_not_commit_transac let pipeline_time = Config::pipeline_time(); let normal_account_id: AccountId = "bob@wonderland".parse().expect("Valid"); - let create_account = Register::account(Account::new(normal_account_id.clone(), [])); + let create_account = Register::account(new_account_with_random_public_key( + normal_account_id.clone(), + )); test_client.submit(create_account)?; let too_long_account_name = "0".repeat(2_usize.pow(14)); let incorrect_account_id: AccountId = (too_long_account_name + "@wonderland") .parse() .expect("Valid"); - let create_account = Register::account(Account::new(incorrect_account_id.clone(), [])); + let create_account = Register::account(new_account_with_random_public_key( + incorrect_account_id.clone(), + )); test_client.submit(create_account)?; thread::sleep(pipeline_time * 2); diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 3c3d719d694..0d97a8b4f61 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -271,7 +271,7 @@ fn find_rate_and_make_exchange_isi_should_succeed() { let buyer_keypair = KeyPair::generate(); let register_account = |account_id: AccountId, signature: PublicKey| { - Register::account(Account::new(account_id, [signature])) + Register::account(Account::new(account_id, signature)) }; let grant_alice_asset_transfer_permission = |asset_id: AssetId, owner_keypair: KeyPair| { @@ -448,19 +448,17 @@ fn asset_id_new(definition_name: &str, definition_domain: &str, account_id: Acco mod register { use super::*; + use crate::integration::new_account_with_random_public_key; pub fn domain(name: &str) -> Register { Register::domain(Domain::new(DomainId::from_str(name).expect("Valid"))) } pub fn account(account_name: &str, domain_name: &str) -> Register { - Register::account(Account::new( - AccountId::new( - domain_name.parse().expect("Valid"), - account_name.parse().expect("Valid"), - ), - [], - )) + Register::account(new_account_with_random_public_key(AccountId::new( + domain_name.parse().expect("Valid"), + account_name.parse().expect("Valid"), + ))) } pub fn asset_definition(asset_name: &str, domain_name: &str) -> Register { diff --git a/client/tests/integration/asset_propagation.rs b/client/tests/integration/asset_propagation.rs index 29122fc9560..c26d142e67e 100644 --- a/client/tests/integration/asset_propagation.rs +++ b/client/tests/integration/asset_propagation.rs @@ -32,7 +32,7 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount_on_a Register::domain(Domain::new(DomainId::from_str("domain")?)).into(); let account_id = AccountId::from_str("account@domain")?; let (public_key, _) = KeyPair::generate().into(); - let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into(); + let create_account = Register::account(Account::new(account_id.clone(), public_key)).into(); let asset_definition_id = AssetDefinitionId::from_str("xor#domain")?; let create_asset = Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); diff --git a/client/tests/integration/burn_public_keys.rs b/client/tests/integration/burn_public_keys.rs index a50bb6cec0e..b28c9637eae 100644 --- a/client/tests/integration/burn_public_keys.rs +++ b/client/tests/integration/burn_public_keys.rs @@ -52,7 +52,7 @@ fn public_keys_cannot_be_burned_to_nothing() { let charlie_initial_keypair = KeyPair::generate(); let register_charlie = Register::account(Account::new( charlie_id.clone(), - [charlie_initial_keypair.public_key().clone()], + charlie_initial_keypair.public_key().clone(), )); let (tx_hash, res) = submit(&client, [register_charlie], None); diff --git a/client/tests/integration/domain_owner_permissions.rs b/client/tests/integration/domain_owner_permissions.rs index d2901928317..611d0e5fbd7 100644 --- a/client/tests/integration/domain_owner_permissions.rs +++ b/client/tests/integration/domain_owner_permissions.rs @@ -6,6 +6,8 @@ use iroha_client::{ use serde_json::json; use test_network::*; +use super::new_account_with_random_public_key; + #[test] fn domain_owner_domain_permissions() -> Result<()> { let chain_id = ChainId::from("0"); @@ -23,7 +25,7 @@ fn domain_owner_domain_permissions() -> Result<()> { test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate(); - let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); + let bob = Account::new(bob_id.clone(), bob_keypair.public_key().clone()); test_client.submit_blocking(Register::account(bob))?; // Asset definitions can't be registered by "bob@kingdom" by default @@ -96,7 +98,7 @@ fn domain_owner_account_permissions() -> Result<()> { let mad_hatter_keypair = KeyPair::generate(); let mad_hatter = Account::new( mad_hatter_id.clone(), - [mad_hatter_keypair.public_key().clone()], + mad_hatter_keypair.public_key().clone(), ); test_client.submit_blocking(Register::account(mad_hatter))?; @@ -158,10 +160,10 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate(); - let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); + let bob = Account::new(bob_id.clone(), bob_keypair.public_key().clone()); test_client.submit_blocking(Register::account(bob))?; - let rabbit = Account::new(rabbit_id.clone(), []); + let rabbit = new_account_with_random_public_key(rabbit_id.clone()); test_client.submit_blocking(Register::account(rabbit))?; // Grant permission to register asset definitions to "bob@kingdom" @@ -228,7 +230,7 @@ fn domain_owner_asset_permissions() -> Result<()> { test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate(); - let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); + let bob = Account::new(bob_id.clone(), bob_keypair.public_key().clone()); test_client.submit_blocking(Register::account(bob))?; // Grant permission to register asset definitions to "bob@kingdom" @@ -293,7 +295,7 @@ fn domain_owner_trigger_permissions() -> Result<()> { test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate(); - let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); + let bob = Account::new(bob_id.clone(), bob_keypair.public_key().clone()); test_client.submit_blocking(Register::account(bob))?; let asset_definition_id = "rose#wonderland".parse()?; @@ -354,7 +356,7 @@ fn domain_owner_transfer() -> Result<()> { test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate(); - let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); + let bob = Account::new(bob_id.clone(), bob_keypair.public_key().clone()); test_client.submit_blocking(Register::account(bob))?; let domain = test_client.request(FindDomainById::new(kingdom_id.clone()))?; diff --git a/client/tests/integration/extra_functional/multiple_blocks_created.rs b/client/tests/integration/extra_functional/multiple_blocks_created.rs index 1b56abe5590..081c30e91a3 100644 --- a/client/tests/integration/extra_functional/multiple_blocks_created.rs +++ b/client/tests/integration/extra_functional/multiple_blocks_created.rs @@ -31,7 +31,7 @@ fn long_multiple_blocks_created() -> Result<()> { let create_domain: InstructionBox = Register::domain(Domain::new("domain".parse()?)).into(); let account_id: AccountId = "account@domain".parse()?; let (public_key, _) = KeyPair::generate().into(); - let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into(); + let create_account = Register::account(Account::new(account_id.clone(), public_key)).into(); let asset_definition_id: AssetDefinitionId = "xor#domain".parse()?; let create_asset = Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); diff --git a/client/tests/integration/extra_functional/unregister_peer.rs b/client/tests/integration/extra_functional/unregister_peer.rs index e73112ae920..b83ac7c1f54 100644 --- a/client/tests/integration/extra_functional/unregister_peer.rs +++ b/client/tests/integration/extra_functional/unregister_peer.rs @@ -108,7 +108,7 @@ fn init() -> Result<( let create_domain = Register::domain(Domain::new("domain".parse()?)); let account_id: AccountId = "account@domain".parse()?; let (public_key, _) = KeyPair::generate().into(); - let create_account = Register::account(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), public_key)); let asset_definition_id: AssetDefinitionId = "xor#domain".parse()?; let create_asset = Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); diff --git a/client/tests/integration/mod.rs b/client/tests/integration/mod.rs index 4340f7de2bd..c9ca0bc1052 100644 --- a/client/tests/integration/mod.rs +++ b/client/tests/integration/mod.rs @@ -1,3 +1,6 @@ +use iroha_crypto::KeyPair; +use iroha_data_model::account::{Account, AccountId, NewAccount}; + mod add_account; mod add_domain; mod asset; @@ -20,3 +23,7 @@ mod triggers; mod tx_history; mod tx_rollback; mod upgrade; + +fn new_account_with_random_public_key(account_id: AccountId) -> NewAccount { + Account::new(account_id, KeyPair::generate().into_raw_parts().0) +} diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 0d95e964396..1ceb1569123 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -206,7 +206,7 @@ fn permissions_differ_not_only_by_names() { // Registering mouse let outfit_domain: DomainId = "outfit".parse().unwrap(); let create_outfit_domain = Register::domain(Domain::new(outfit_domain.clone())); - let new_mouse_account = Account::new(mouse_id.clone(), [mouse_keypair.public_key().clone()]); + let new_mouse_account = Account::new(mouse_id.clone(), mouse_keypair.public_key().clone()); client .submit_all_blocking([ InstructionBox::from(create_outfit_domain), @@ -306,7 +306,7 @@ fn stored_vs_granted_token_payload() -> Result<()> { Register::asset_definition(AssetDefinition::store(asset_definition_id.clone())); let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); let mouse_keypair = KeyPair::generate(); - let new_mouse_account = Account::new(mouse_id.clone(), [mouse_keypair.public_key().clone()]); + let new_mouse_account = Account::new(mouse_id.clone(), mouse_keypair.public_key().clone()); let instructions: [InstructionBox; 2] = [ Register::account(new_mouse_account).into(), create_asset.into(), diff --git a/client/tests/integration/queries/account.rs b/client/tests/integration/queries/account.rs index 69d28c66e6f..2123cbb311b 100644 --- a/client/tests/integration/queries/account.rs +++ b/client/tests/integration/queries/account.rs @@ -7,6 +7,8 @@ use iroha_client::{ }; use test_network::*; +use crate::integration::new_account_with_random_public_key; + #[test] fn find_accounts_with_asset() -> Result<()> { let (_rt, _peer, test_client) = ::new().with_port(10_760).start_with_runtime(); @@ -40,7 +42,7 @@ fn find_accounts_with_asset() -> Result<()> { .iter() .skip(1) // Alice has already been registered in genesis .cloned() - .map(|account_id| Register::account(Account::new(account_id, []))) + .map(|account_id| Register::account(new_account_with_random_public_key(account_id))) .collect::>(); test_client.submit_all_blocking(register_accounts)?; diff --git a/client/tests/integration/queries/asset.rs b/client/tests/integration/queries/asset.rs index d987395f76c..982d9873dfe 100644 --- a/client/tests/integration/queries/asset.rs +++ b/client/tests/integration/queries/asset.rs @@ -41,7 +41,7 @@ fn find_asset_total_quantity() -> Result<()> { .skip(1) // Alice has already been registered in genesis .cloned() .zip(keys.iter().map(KeyPair::public_key).cloned()) - .map(|(account_id, public_key)| Register::account(Account::new(account_id, [public_key]))) + .map(|(account_id, public_key)| Register::account(Account::new(account_id, public_key))) .collect::>(); test_client.submit_all_blocking(register_accounts)?; diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index f7cc75fdaa4..a0a33516473 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -9,6 +9,8 @@ use iroha_client::{ use serde_json::json; use test_network::*; +use crate::integration::new_account_with_random_public_key; + #[test] fn register_empty_role() -> Result<()> { let (_rt, _peer, test_client) = ::new().with_port(10_695).start_with_runtime(); @@ -58,7 +60,7 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { let mouse_key_pair = KeyPair::generate(); let register_mouse = Register::account(Account::new( mouse_id.clone(), - [mouse_key_pair.public_key().clone()], + mouse_key_pair.public_key().clone(), )); test_client.submit_blocking(register_mouse)?; @@ -110,7 +112,7 @@ fn unregistered_role_removed_from_account() -> Result<()> { let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); // Registering Mouse - let register_mouse = Register::account(Account::new(mouse_id.clone(), [])); + let register_mouse = Register::account(new_account_with_random_public_key(mouse_id.clone())); test_client.submit_blocking(register_mouse)?; // Register root role diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index 19f69f3b86e..3d50a1a419f 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -17,6 +17,8 @@ use iroha_client::{ use iroha_data_model::isi::InstructionBox; use test_network::*; +use crate::integration::new_account_with_random_public_key; + #[test] fn correct_pagination_assets_after_creating_new_one() { let (_rt, _peer, test_client) = ::new().with_port(10_635).start_with_runtime(); @@ -201,7 +203,8 @@ fn correct_sorting_of_entities() { MetadataLimits::new(10, 28), ) .expect("Valid"); - let account = Account::new(account_id.clone(), []).with_metadata(account_metadata.clone()); + let account = new_account_with_random_public_key(account_id.clone()) + .with_metadata(account_metadata.clone()); accounts.push(account_id); metadata_of_accounts.push(account_metadata); @@ -342,7 +345,7 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { for i in 0..n { let account_id = AccountId::from_str(&format!("charlie{i}@wonderland")).expect("Valid"); let account = if skip_set.contains(&i) { - let account = Account::new(account_id.clone(), []); + let account = new_account_with_random_public_key(account_id.clone()); accounts_b.push(account_id); account } else { @@ -354,7 +357,8 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { MetadataLimits::new(10, 28), ) .expect("Valid"); - let account = Account::new(account_id.clone(), []).with_metadata(account_metadata); + let account = new_account_with_random_public_key(account_id.clone()) + .with_metadata(account_metadata); accounts_a.push(account_id); account }; diff --git a/client/tests/integration/transfer_asset.rs b/client/tests/integration/transfer_asset.rs index 6b3ee6540dc..2c2122f2927 100644 --- a/client/tests/integration/transfer_asset.rs +++ b/client/tests/integration/transfer_asset.rs @@ -184,5 +184,5 @@ fn generate_two_ids() -> (AccountId, AccountId) { fn create_mouse(mouse_id: AccountId) -> Register { let (mouse_public_key, _) = KeyPair::generate().into(); - Register::account(Account::new(mouse_id, [mouse_public_key])) + Register::account(Account::new(mouse_id, mouse_public_key)) } diff --git a/client/tests/integration/triggers/data_trigger.rs b/client/tests/integration/triggers/data_trigger.rs index 2c197c9da18..b9db571de16 100644 --- a/client/tests/integration/triggers/data_trigger.rs +++ b/client/tests/integration/triggers/data_trigger.rs @@ -2,6 +2,8 @@ use eyre::Result; use iroha_client::{client, data_model::prelude::*}; use test_network::*; +use crate::integration::new_account_with_random_public_key; + #[test] fn must_execute_both_triggers() -> Result<()> { let (_rt, _peer, test_client) = ::new().with_port(10_650).start_with_runtime(); @@ -47,9 +49,8 @@ fn must_execute_both_triggers() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - test_client.submit_blocking(Register::account(Account::new( + test_client.submit_blocking(Register::account(new_account_with_random_public_key( "bunny@wonderland".parse()?, - [], )))?; test_client.submit_blocking(Register::domain(Domain::new("neverland".parse()?)))?; @@ -68,7 +69,8 @@ fn domain_scoped_trigger_must_be_executed_only_on_events_in_its_domain() -> Resu Register::domain(Domain::new("neverland".parse()?)).into(); let account_id: AccountId = "sapporo@neverland".parse()?; - let create_sapporo_account = Register::account(Account::new(account_id.clone(), [])).into(); + let create_sapporo_account = + Register::account(new_account_with_random_public_key(account_id.clone())).into(); let asset_definition_id: AssetDefinitionId = "sakura#neverland".parse()?; let create_sakura_asset_definition = @@ -107,14 +109,12 @@ fn domain_scoped_trigger_must_be_executed_only_on_events_in_its_domain() -> Resu )); test_client.submit_blocking(register_trigger)?; - test_client.submit_blocking(Register::account(Account::new( + test_client.submit_blocking(Register::account(new_account_with_random_public_key( "asahi@wonderland".parse()?, - [], )))?; - test_client.submit_blocking(Register::account(Account::new( + test_client.submit_blocking(Register::account(new_account_with_random_public_key( "asahi@neverland".parse()?, - [], )))?; let new_value = get_asset_value(&test_client, asset_id)?; diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index 319467fc24a..d8ab1722a1a 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -9,6 +9,8 @@ use iroha_config::parameters::defaults::chain_wide::DEFAULT_CONSENSUS_ESTIMATION use iroha_logger::info; use test_network::*; +use crate::integration::new_account_with_random_public_key; + /// Macro to abort compilation, if `e` isn't `true` macro_rules! const_assert { ($e:expr) => { @@ -195,7 +197,7 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { .iter() .skip(1) // Alice has already been registered in genesis .cloned() - .map(|account_id| Register::account(Account::new(account_id, []))) + .map(|account_id| Register::account(new_account_with_random_public_key(account_id))) .collect::>(); test_client.submit_all_blocking(register_accounts)?; diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index b7d0f9a1c04..60a0447a095 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -24,7 +24,7 @@ fn executor_upgrade_should_work() -> Result<()> { let admin_id: AccountId = "admin@admin".parse()?; let admin_keypair = KeyPair::generate(); - let admin_account = Account::new(admin_id.clone(), [admin_keypair.public_key().clone()]); + let admin_account = Account::new(admin_id.clone(), admin_keypair.public_key().clone()); let register_admin_account = Register::account(admin_account); client.submit_blocking(register_admin_account)?; diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index 25e8eaf93c0..8e89a729d9a 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -586,7 +586,7 @@ mod account { fn run(self, context: &mut dyn RunContext) -> Result<()> { let Self { id, key, metadata } = self; let create_account = - iroha_client::data_model::isi::Register::account(Account::new(id, [key])); + iroha_client::data_model::isi::Register::account(Account::new(id, key)); submit([create_account], metadata.load()?, context) .wrap_err("Failed to register account") } diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 135f1021cc04aca6bbe8a28995ceb0a9d336812a..df2831647bf413e5981b6aadad32ec4cb456cfc6 100644 GIT binary patch delta 26156 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 I$LuftKPfTv!~g&Q diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index bfe24efe223..8b394faba0c 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -74,7 +74,7 @@ pub fn populate_wsv( instructions.push(can_unregister_domain.into()); for j in 0..accounts_per_domain { let account_id = construct_account_id(j, domain_id.clone()); - let account = Account::new(account_id.clone(), []); + let account = Account::new(account_id.clone(), KeyPair::generate().into_raw_parts().0); instructions.push(Register::account(account).into()); let can_unregister_account = Grant::permission( PermissionToken::new( @@ -150,7 +150,8 @@ pub fn restore_every_nth( for j in 0..accounts_per_domain { if j % nth == 0 || i % nth == 0 { let account_id = construct_account_id(j, domain_id.clone()); - let account = Account::new(account_id.clone(), []); + let account = + Account::new(account_id.clone(), KeyPair::generate().into_raw_parts().0); instructions.push(Register::account(account).into()); } } @@ -181,7 +182,7 @@ pub fn build_wsv( let mut domain = Domain::new(account_id.domain_id.clone()).build(account_id); domain.accounts.insert( account_id.clone(), - Account::new(account_id.clone(), [key_pair.public_key().clone()]).build(account_id), + Account::new(account_id.clone(), key_pair.public_key().clone()).build(account_id), ); let mut wsv = WorldStateView::new(World::with([domain], UniqueVec::new()), kura, query_handle); wsv.config.transaction_limits = TransactionLimits::new(u64::MAX, u64::MAX); diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 037e031cd12..04097530119 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -34,7 +34,7 @@ fn build_test_transaction(keys: &KeyPair, chain_id: ChainId) -> SignedTransactio domain_name.parse().expect("Valid"), account_name.parse().expect("Valid"), ), - [public_key], + public_key, )) .into(); let asset_definition_id = AssetDefinitionId::new( @@ -69,7 +69,7 @@ fn build_test_and_transient_wsv(keys: KeyPair) -> WorldStateView { Name::from_str(START_ACCOUNT).expect("Valid"), ); let mut domain = Domain::new(domain_id).build(&account_id); - let account = Account::new(account_id.clone(), [public_key]).build(&account_id); + let account = Account::new(account_id.clone(), public_key).build(&account_id); assert!(domain.add_account(account).is_none()); World::with([domain], UniqueVec::new()) }, diff --git a/core/src/block.rs b/core/src/block.rs index b2b0a6bc82d..8fa62f2fdb3 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -699,7 +699,7 @@ mod tests { let alice_id = AccountId::from_str("alice@wonderland").expect("Valid"); let alice_keys = KeyPair::generate(); let account = - Account::new(alice_id.clone(), [alice_keys.public_key().clone()]).build(&alice_id); + Account::new(alice_id.clone(), alice_keys.public_key().clone()).build(&alice_id); let domain_id = DomainId::from_str("wonderland").expect("Valid"); let mut domain = Domain::new(domain_id).build(&alice_id); assert!(domain.add_account(account).is_none()); @@ -742,7 +742,7 @@ mod tests { let alice_id = AccountId::from_str("alice@wonderland").expect("Valid"); let alice_keys = KeyPair::generate(); let account = - Account::new(alice_id.clone(), [alice_keys.public_key().clone()]).build(&alice_id); + Account::new(alice_id.clone(), alice_keys.public_key().clone()).build(&alice_id); let domain_id = DomainId::from_str("wonderland").expect("Valid"); let mut domain = Domain::new(domain_id).build(&alice_id); assert!(domain.add_account(account).is_none()); @@ -808,7 +808,7 @@ mod tests { let alice_id = AccountId::from_str("alice@wonderland").expect("Valid"); let alice_keys = KeyPair::generate(); let account = - Account::new(alice_id.clone(), [alice_keys.public_key().clone()]).build(&alice_id); + Account::new(alice_id.clone(), alice_keys.public_key().clone()).build(&alice_id); let domain_id = DomainId::from_str("wonderland").expect("Valid"); let mut domain = Domain::new(domain_id).build(&alice_id); assert!( diff --git a/core/src/queue.rs b/core/src/queue.rs index 3beab0a9546..4256cca4584 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -30,9 +30,7 @@ impl AcceptedTransaction { .collect(); wsv.map_account(authority, |account| { - Ok(account - .signature_check_condition - .check(&account.signatories, &transaction_signatories)) + Ok(account.check_signature_check_condition(&transaction_signatories)) })? } @@ -411,12 +409,17 @@ mod tests { } pub fn world_with_test_domains( - signatures: impl IntoIterator, + signatories: impl IntoIterator, ) -> World { let domain_id = DomainId::from_str("wonderland").expect("Valid"); let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let mut domain = Domain::new(domain_id).build(&account_id); - let account = Account::new(account_id.clone(), signatures).build(&account_id); + let mut signatories = signatories.into_iter(); + let mut account = Account::new(account_id.clone(), signatories.next().unwrap()); + for signatory in signatories { + account = account.add_signatory(signatory); + } + let account = account.build(&account_id); assert!(domain.add_account(account).is_none()); World::with([domain], PeersIds::new()) } @@ -493,11 +496,9 @@ mod tests { let domain_id = DomainId::from_str("wonderland").expect("Valid"); let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let mut domain = Domain::new(domain_id).build(&account_id); - let mut account = Account::new( - account_id.clone(), - key_pairs.iter().map(KeyPair::public_key).cloned(), - ) - .build(&account_id); + let mut account = Account::new(account_id.clone(), key_pairs[0].public_key().clone()) + .add_signatory(key_pairs[1].public_key().clone()) + .build(&account_id); account.signature_check_condition = SignatureCheckCondition::all_account_signatures(); assert!(domain.add_account(account).is_none()); let query_handle = LiveQueryStore::test().start(); @@ -887,11 +888,11 @@ mod tests { let mut domain = Domain::new(domain_id).build(&alice_account_id); let alice_account = Account::new( alice_account_id.clone(), - [alice_key_pair.public_key().clone()], + alice_key_pair.public_key().clone(), ) .build(&alice_account_id); let bob_account = - Account::new(bob_account_id.clone(), [bob_key_pair.public_key().clone()]) + Account::new(bob_account_id.clone(), bob_key_pair.public_key().clone()) .build(&bob_account_id); assert!(domain.add_account(alice_account).is_none()); assert!(domain.add_account(bob_account).is_none()); diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 7c803c76b43..c6b977e4aad 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -1,7 +1,7 @@ //! This module contains implementations of smart-contract traits and instructions for [`Account`] structure //! and implementations of [`Query`]'s to [`WorldStateView`] about [`Account`]. -use iroha_data_model::{asset::AssetsMap, prelude::*, query::error::FindError}; +use iroha_data_model::{prelude::*, query::error::FindError}; use iroha_telemetry::metrics; use super::prelude::*; @@ -13,13 +13,7 @@ impl Registrable for iroha_data_model::account::NewAccount { #[must_use] #[inline] fn build(self, _authority: &AccountId) -> Self::Target { - Self::Target { - id: self.id, - signatories: self.signatories, - assets: AssetsMap::default(), - signature_check_condition: SignatureCheckCondition::default(), - metadata: self.metadata, - } + self.into_account() } } @@ -137,7 +131,7 @@ pub mod isi { wsv.account_mut(&account_id) .map_err(Error::from) .and_then(|account| { - if account.signatories.contains(&public_key) { + if account.contains_signatory(&public_key) { return Err(RepetitionError { instruction_type: InstructionType::Mint, id: account_id.clone().into(), @@ -164,16 +158,14 @@ pub mod isi { wsv.account_mut(&account_id) .map_err(Error::from) .and_then(|account| { - if account.signatories.len() < 2 { - return Err(Error::InvariantViolation(String::from( + match account.remove_signatory(&public_key) { + None => Err(Error::InvariantViolation(String::from( "Public keys cannot be burned to nothing, \ if you want to delete the account, please use an unregister instruction", - ))); - } - if !account.remove_signatory(&public_key) { - return Err(FindError::PublicKey(public_key).into()); + ))), + Some(false) => Err(FindError::PublicKey(public_key).into()), + Some(true) => Ok(()) } - Ok(()) })?; wsv.emit_events(Some(AccountEvent::AuthenticationRemoved(account_id))); diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index ccd65f6d8cb..437ef7c1c91 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -248,7 +248,7 @@ mod tests { let asset_definition_id = AssetDefinitionId::from_str("rose#wonderland")?; Register::domain(Domain::new(DomainId::from_str("wonderland")?)) .execute(&genesis_account_id, &mut wsv)?; - Register::account(Account::new(account_id, [public_key])) + Register::account(Account::new(account_id, public_key)) .execute(&genesis_account_id, &mut wsv)?; Register::asset_definition(AssetDefinition::store(asset_definition_id)) .execute(&genesis_account_id, &mut wsv)?; @@ -395,8 +395,7 @@ mod tests { // register fake account let (public_key, _) = KeyPair::generate().into(); - let register_account = - Register::account(Account::new(fake_account_id.clone(), [public_key])); + let register_account = Register::account(Account::new(fake_account_id.clone(), public_key)); register_account.execute(&account_id, &mut wsv)?; // register the trigger diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index f7695a93c1c..90527abeb88 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -65,7 +65,7 @@ impl ValidQueryRequest { pub fn validate(query: SignedQuery, wsv: &WorldStateView) -> Result { let account_has_public_key = wsv .map_account(query.authority(), |account| { - account.signatories.contains(query.signature().public_key()) + account.contains_signatory(query.signature().public_key()) }) .map_err(Error::from)?; if !account_has_public_key { @@ -187,7 +187,7 @@ mod tests { let domain_id = DomainId::from_str("wonderland").expect("Valid"); let mut domain = Domain::new(domain_id).build(&ALICE_ID); let account = - Account::new(ALICE_ID.clone(), [ALICE_KEYS.public_key().clone()]).build(&ALICE_ID); + Account::new(ALICE_ID.clone(), ALICE_KEYS.public_key().clone()).build(&ALICE_ID); assert!(domain.add_account(account).is_none()); let asset_definition_id = AssetDefinitionId::from_str("rose#wonderland").expect("Valid"); assert!(domain @@ -201,7 +201,7 @@ mod tests { let mut domain = Domain::new(DomainId::from_str("wonderland").expect("Valid")).build(&ALICE_ID); let mut account = - Account::new(ALICE_ID.clone(), [ALICE_KEYS.public_key().clone()]).build(&ALICE_ID); + Account::new(ALICE_ID.clone(), ALICE_KEYS.public_key().clone()).build(&ALICE_ID); assert!(domain .add_asset_definition( AssetDefinition::quantity(asset_definition_id.clone()).build(&ALICE_ID) @@ -233,7 +233,7 @@ mod tests { )?; let mut domain = Domain::new(DomainId::from_str("wonderland")?).build(&ALICE_ID); - let account = Account::new(ALICE_ID.clone(), [ALICE_KEYS.public_key().clone()]) + let account = Account::new(ALICE_ID.clone(), ALICE_KEYS.public_key().clone()) .with_metadata(metadata) .build(&ALICE_ID); assert!(domain.add_account(account).is_none()); @@ -466,7 +466,7 @@ mod tests { .with_metadata(metadata) .build(&ALICE_ID); let account = - Account::new(ALICE_ID.clone(), [ALICE_KEYS.public_key().clone()]).build(&ALICE_ID); + Account::new(ALICE_ID.clone(), ALICE_KEYS.public_key().clone()).build(&ALICE_ID); assert!(domain.add_account(account).is_none()); let asset_definition_id = AssetDefinitionId::from_str("rose#wonderland")?; assert!(domain diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index ec431e8e458..5d5db7b02af 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -1649,7 +1649,7 @@ mod tests { fn world_with_test_account(authority: &AccountId) -> World { let domain_id = authority.domain_id.clone(); let (public_key, _) = KeyPair::generate().into(); - let account = Account::new(authority.clone(), [public_key]).build(authority); + let account = Account::new(authority.clone(), public_key).build(authority); let mut domain = Domain::new(domain_id).build(authority); assert!(domain.add_account(account).is_none()); @@ -1709,7 +1709,10 @@ mod tests { let isi_hex = { let new_authority = AccountId::from_str("mad_hatter@wonderland").expect("Valid"); - let register_isi = Register::account(Account::new(new_authority, [])); + let register_isi = Register::account(Account::new( + new_authority, + KeyPair::generate().into_raw_parts().0, + )); encode_hex(InstructionBox::from(register_isi)) }; @@ -1795,7 +1798,10 @@ mod tests { let isi_hex = { let new_authority = AccountId::from_str("mad_hatter@wonderland").expect("Valid"); - let register_isi = Register::account(Account::new(new_authority, [])); + let register_isi = Register::account(Account::new( + new_authority, + KeyPair::generate().into_raw_parts().0, + )); encode_hex(InstructionBox::from(register_isi)) }; @@ -1844,7 +1850,10 @@ mod tests { let isi_hex = { let new_authority = AccountId::from_str("mad_hatter@wonderland").expect("Valid"); - let register_isi = Register::account(Account::new(new_authority, [])); + let register_isi = Register::account(Account::new( + new_authority, + KeyPair::generate().into_raw_parts().0, + )); encode_hex(InstructionBox::from(register_isi)) }; diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index b545e281338..2959afa1151 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -1198,7 +1198,7 @@ mod tests { let alice_id: AccountId = "alice@wonderland".parse().expect("Valid"); let alice_keys = KeyPair::generate(); let account = - Account::new(alice_id.clone(), [alice_keys.public_key().clone()]).build(&alice_id); + Account::new(alice_id.clone(), alice_keys.public_key().clone()).build(&alice_id); let domain_id = "wonderland".parse().expect("Valid"); let mut domain = Domain::new(domain_id).build(&alice_id); assert!(domain.add_account(account).is_none()); diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 913bc1ae6aa..c985ab60dca 100755 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -204,6 +204,7 @@ impl KeyPair { } /// Construct a [`KeyPair`] + /// /// # Errors /// If public and private key don't match, i.e. if they don't make a pair pub fn new(public_key: PublicKey, private_key: PrivateKey) -> Result { @@ -239,6 +240,11 @@ impl KeyPair { Algorithm::BlsSmall => signature::bls::BlsSmall::keypair(key_gen_option).into(), } } + + /// Get [`PublicKey`] and [`PrivateKey`] contained in the [`KeyPair`]. + pub fn into_raw_parts(self) -> (PublicKey, PrivateKey) { + (self.public_key, self.private_key) + } } impl From<(ed25519::PublicKey, ed25519::PrivateKey)> for KeyPair { @@ -303,7 +309,7 @@ impl<'de> Deserialize<'de> for KeyPair { #[cfg(not(feature = "ffi_import"))] impl From for (PublicKey, PrivateKey) { fn from(key_pair: KeyPair) -> Self { - (key_pair.public_key, key_pair.private_key) + key_pair.into_raw_parts() } } diff --git a/data_model/src/account.rs b/data_model/src/account.rs index 59ad7b0a4d9..9226d54952e 100644 --- a/data_model/src/account.rs +++ b/data_model/src/account.rs @@ -13,7 +13,9 @@ use std::collections::{btree_map, btree_set}; use derive_more::{Constructor, DebugCustom, Display}; use getset::Getters; use iroha_data_model_derive::{model, IdEqOrdHash}; -use iroha_primitives::{const_vec::ConstVec, must_use::MustUse}; +use iroha_primitives::const_vec::ConstVec; +#[cfg(feature = "transparent_api")] +use iroha_primitives::must_use::MustUse; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -79,27 +81,19 @@ pub mod model { /// Account entity is an authority which is used to execute `Iroha Special Instructions`. #[derive( - Debug, - Display, - Clone, - IdEqOrdHash, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, + Debug, Display, Clone, IdEqOrdHash, Getters, Encode, Deserialize, Serialize, IntoSchema, )] #[allow(clippy::multiple_inherent_impl)] #[display(fmt = "({id})")] // TODO: Add more? #[ffi_type] + #[serde(try_from = "candidate::Account")] pub struct Account { /// An Identification of the [`Account`]. pub id: AccountId, /// Assets in this [`Account`]. pub assets: AssetsMap, /// [`Account`]'s signatories. - pub signatories: Signatories, + pub(super) signatories: Signatories, /// Condition which checks if the account has the right signatures. #[getset(get = "pub")] pub signature_check_condition: SignatureCheckCondition, @@ -109,16 +103,18 @@ pub mod model { /// Builder which should be submitted in a transaction to create a new [`Account`] #[derive( - DebugCustom, Display, Clone, IdEqOrdHash, Decode, Encode, Serialize, Deserialize, IntoSchema, + DebugCustom, Display, Clone, IdEqOrdHash, Encode, Serialize, Deserialize, IntoSchema, )] #[display(fmt = "[{id}]")] #[debug(fmt = "[{id:?}] {{ signatories: {signatories:?}, metadata: {metadata} }}")] #[ffi_type] + #[serde(try_from = "candidate::NewAccount")] pub struct NewAccount { /// Identification pub id: AccountId, /// Signatories, i.e. signatures attached to this message. - pub signatories: Signatories, + /// Cannot be empty, guaranteed by constructors. + pub(super) signatories: Signatories, /// Metadata that should be submitted with the builder pub metadata: Metadata, } @@ -148,15 +144,73 @@ pub mod model { } } +mod candidate { + //! Contains structs for deserialization checks + + use super::*; + + #[derive(Decode, Deserialize)] + /// [`Account`] candidate used for deserialization checks + pub struct Account { + id: AccountId, + assets: AssetsMap, + signatories: Signatories, + signature_check_condition: SignatureCheckCondition, + metadata: Metadata, + } + + impl TryFrom for super::Account { + type Error = &'static str; + + fn try_from(candidate: Account) -> Result { + check_signatories(&candidate.signatories)?; + + Ok(Self { + id: candidate.id, + assets: candidate.assets, + signatories: candidate.signatories, + signature_check_condition: candidate.signature_check_condition, + metadata: candidate.metadata, + }) + } + } + + /// [`NewAccount`] candidate used for deserialization checks + #[derive(Decode, Deserialize)] + pub struct NewAccount { + id: AccountId, + signatories: Signatories, + metadata: Metadata, + } + + impl TryFrom for super::NewAccount { + type Error = &'static str; + + fn try_from(candidate: NewAccount) -> Result { + check_signatories(&candidate.signatories)?; + + Ok(Self { + id: candidate.id, + signatories: candidate.signatories, + metadata: candidate.metadata, + }) + } + } + + fn check_signatories(signatories: &Signatories) -> Result<(), &'static str> { + if signatories.is_empty() { + return Err("Signatories cannot be empty"); + } + Ok(()) + } +} + impl Account { - /// Construct builder for [`Account`] identifiable by [`Id`] containing the given signatories. + /// Construct builder for [`Account`] identifiable by [`Id`] containing the given signatory. #[inline] #[must_use] - pub fn new( - id: AccountId, - signatories: impl IntoIterator, - ) -> ::With { - ::With::new(id, signatories) + pub fn new(id: AccountId, signatory: PublicKey) -> ::With { + ::With::new(id, signatory) } /// Get an iterator over [`signatories`](PublicKey) of the `Account` @@ -197,32 +251,68 @@ impl Account { pub fn remove_asset(&mut self, asset_id: &AssetId) -> Option { self.assets.remove(asset_id) } + /// Add [`signatory`](PublicKey) into the [`Account`]. /// - /// If `Account` did not have this signatory present, `true` is returned. - /// If `Account` did have this signatory present, `false` is returned. + /// If [`Account`] did not have this signatory present, `true` is returned. + /// If [`Account`] did have this signatory present, `false` is returned. #[inline] pub fn add_signatory(&mut self, signatory: PublicKey) -> bool { self.signatories.insert(signatory) } - /// Remove a signatory from the `Account` and return whether the signatory was present in the `Account` + /// Remove a signatory from the [`Account`]. + /// + /// Does nothing and returns [`None`] if only one signature is left. + /// Otherwise returns whether the signatory was presented in the Account. #[inline] - pub fn remove_signatory(&mut self, signatory: &PublicKey) -> bool { - self.signatories.remove(signatory) + pub fn remove_signatory(&mut self, signatory: &PublicKey) -> Option { + if self.signatories.len() < 2 { + return None; + } + + Some(self.signatories.remove(signatory)) + } + + /// Checks whether the transaction contains all the signatures required by the + /// [`SignatureCheckCondition`] stored in this account. + pub fn check_signature_check_condition( + &self, + transaction_signatories: &btree_set::BTreeSet, + ) -> MustUse { + self.signature_check_condition + .check(&self.signatories, transaction_signatories) + } +} + +impl Decode for Account { + fn decode( + input: &mut I, + ) -> Result { + let candidate = candidate::Account::decode(input)?; + Self::try_from(candidate).map_err(Into::into) } } impl NewAccount { - fn new(id: AccountId, signatories: impl IntoIterator) -> Self { + fn new(id: AccountId, signatory: PublicKey) -> Self { Self { id, - signatories: signatories.into_iter().collect(), + signatories: Signatories::from([signatory]), metadata: Metadata::default(), } } + /// Add signatory to account. + #[inline] + #[must_use] + pub fn add_signatory(mut self, signatory: PublicKey) -> Self { + self.signatories.insert(signatory); + self + } + /// Add [`Metadata`] to the account replacing any previously defined metadata + #[inline] #[must_use] pub fn with_metadata(mut self, metadata: Metadata) -> Self { self.metadata = metadata; @@ -230,12 +320,35 @@ impl NewAccount { } } +#[cfg(feature = "transparent_api")] +impl NewAccount { + /// Convert into [`Account`]. + pub fn into_account(self) -> Account { + Account { + id: self.id, + signatories: self.signatories, + assets: AssetsMap::default(), + signature_check_condition: SignatureCheckCondition::default(), + metadata: self.metadata, + } + } +} + impl HasMetadata for NewAccount { fn metadata(&self) -> &Metadata { &self.metadata } } +impl Decode for NewAccount { + fn decode( + input: &mut I, + ) -> Result { + let candidate = candidate::NewAccount::decode(input)?; + Self::try_from(candidate).map_err(Into::into) + } +} + impl HasMetadata for Account { fn metadata(&self) -> &Metadata { &self.metadata @@ -296,8 +409,8 @@ impl SignatureCheckCondition { Self::AllAccountSignaturesAnd(ConstVec::new_empty()) } - /// Checks whether the transaction contains all the signatures required by the `SignatureCheckCondition`. - pub fn check( + #[cfg(feature = "transparent_api")] + fn check( &self, account_signatories: &btree_set::BTreeSet, transaction_signatories: &btree_set::BTreeSet, diff --git a/data_model/src/events/data/filters.rs b/data_model/src/events/data/filters.rs index 9a4d760ea5b..71a1dda04b0 100644 --- a/data_model/src/events/data/filters.rs +++ b/data_model/src/events/data/filters.rs @@ -193,15 +193,10 @@ pub mod prelude { #[cfg(test)] #[cfg(feature = "transparent_api")] mod tests { - #[cfg(not(feature = "std"))] - use alloc::collections::BTreeSet; - #[cfg(feature = "std")] - use std::collections::BTreeSet; - use super::*; use crate::{ account::AccountsMap, - asset::{AssetDefinitionsMap, AssetTotalQuantityMap, AssetsMap}, + asset::{AssetDefinitionsMap, AssetTotalQuantityMap}, }; #[test] @@ -223,13 +218,11 @@ mod tests { owned_by: domain_owner_id, }; let account_id = AccountId::new(domain_id.clone(), account_name); - let account = Account { - id: account_id.clone(), - assets: AssetsMap::default(), - signatories: BTreeSet::default(), - signature_check_condition: SignatureCheckCondition::default(), - metadata: Metadata::default(), - }; + let account = Account::new( + account_id.clone(), + iroha_crypto::KeyPair::generate().into_raw_parts().0, + ) + .into_account(); let asset_id = AssetId::new( AssetDefinitionId::new(domain_id.clone(), asset_name), account_id.clone(), diff --git a/data_model/src/predicate.rs b/data_model/src/predicate.rs index e50157fdaf7..704c619c14b 100644 --- a/data_model/src/predicate.rs +++ b/data_model/src/predicate.rs @@ -1150,6 +1150,7 @@ pub mod value { #[cfg(test)] mod test { + use iroha_crypto::KeyPair; use iroha_primitives::addr::socket_addr; use peer::Peer; use prelude::Metadata; @@ -1169,7 +1170,10 @@ pub mod value { )))); assert!( pred.applies(&Value::Identifiable(IdentifiableBox::NewAccount( - Account::new("alice@wonderland".parse().expect("Valid"), []) + Account::new( + "alice@wonderland".parse().expect("Valid"), + KeyPair::generate().into_raw_parts().0, + ) ))) ); assert!(!pred.applies(&Value::Name("alice".parse().expect("Valid")))); diff --git a/genesis/src/lib.rs b/genesis/src/lib.rs index b9eab5b2b2e..44143ad08fb 100644 --- a/genesis/src/lib.rs +++ b/genesis/src/lib.rs @@ -302,13 +302,17 @@ impl RawGenesisDomainBuilder { } } - /// Add an account to this domain without a public key. + /// Add an account to this domain with random public key. #[cfg(test)] - fn account_without_public_key(mut self, account_name: Name) -> Self { + fn account_with_random_public_key(mut self, account_name: Name) -> Self { let account_id = AccountId::new(self.domain_id.clone(), account_name); - self.transaction - .isi - .push(Register::account(Account::new(account_id, [])).into()); + self.transaction.isi.push( + Register::account(Account::new( + account_id, + KeyPair::generate().into_raw_parts().0, + )) + .into(), + ); self } @@ -326,7 +330,7 @@ impl RawGenesisDomainBuilder { ) -> Self { let account_id = AccountId::new(self.domain_id.clone(), account_name); let register = - Register::account(Account::new(account_id, [public_key]).with_metadata(metadata)); + Register::account(Account::new(account_id, public_key).with_metadata(metadata)); self.transaction.isi.push(register.into()); self } @@ -385,11 +389,11 @@ mod tests { genesis_builder = genesis_builder .domain("wonderland".parse().unwrap()) - .account_without_public_key("alice".parse().unwrap()) - .account_without_public_key("bob".parse().unwrap()) + .account_with_random_public_key("alice".parse().unwrap()) + .account_with_random_public_key("bob".parse().unwrap()) .finish_domain() .domain("tulgey_wood".parse().unwrap()) - .account_without_public_key("Cheshire_Cat".parse().unwrap()) + .account_with_random_public_key("Cheshire_Cat".parse().unwrap()) .finish_domain() .domain("meadow".parse().unwrap()) .account("Mad_Hatter".parse().unwrap(), public_key.parse().unwrap()) @@ -408,7 +412,7 @@ mod tests { finished_genesis_block.transactions[0].isi[1], Register::account(Account::new( AccountId::new(domain_id.clone(), "alice".parse().unwrap()), - [] + KeyPair::generate().into_raw_parts().0, )) .into() ); @@ -416,7 +420,7 @@ mod tests { finished_genesis_block.transactions[0].isi[2], Register::account(Account::new( AccountId::new(domain_id, "bob".parse().unwrap()), - [] + KeyPair::generate().into_raw_parts().0, )) .into() ); @@ -431,7 +435,7 @@ mod tests { finished_genesis_block.transactions[0].isi[4], Register::account(Account::new( AccountId::new(domain_id, "Cheshire_Cat".parse().unwrap()), - [] + KeyPair::generate().into_raw_parts().0, )) .into() ); @@ -446,7 +450,7 @@ mod tests { finished_genesis_block.transactions[0].isi[6], Register::account(Account::new( AccountId::new(domain_id, "Mad_Hatter".parse().unwrap()), - [public_key.parse().unwrap()], + public_key.parse().unwrap(), )) .into() ); diff --git a/smart_contract/src/lib.rs b/smart_contract/src/lib.rs index 005b2c69338..017fb5f867a 100644 --- a/smart_contract/src/lib.rs +++ b/smart_contract/src/lib.rs @@ -471,8 +471,8 @@ mod tests { const ISI_RESULT: Result<(), ValidationFail> = Ok(()); fn get_test_instruction() -> InstructionBox { - let new_account_id = "mad_hatter@wonderland".parse().expect("Valid"); - let register_isi = Register::account(Account::new(new_account_id, [])); + let new_asset_id = "tulip##alice@wonderland".parse().unwrap(); + let register_isi = Register::asset(Asset::new(new_asset_id, 1_u32)); register_isi.into() } diff --git a/tools/parity_scale_decoder/Cargo.toml b/tools/parity_scale_decoder/Cargo.toml index e2cb948ff7a..37087501892 100644 --- a/tools/parity_scale_decoder/Cargo.toml +++ b/tools/parity_scale_decoder/Cargo.toml @@ -33,5 +33,6 @@ colored = "2.0.4" iroha_data_model = { workspace = true } parity-scale-codec = { workspace = true } -serde_json = { workspace = true } +serde_json = { workspace = true, features = ["std"]} serde = { workspace = true } +eyre = { workspace = true } diff --git a/tools/parity_scale_decoder/build.rs b/tools/parity_scale_decoder/build.rs index 49223203f69..00391f5e58e 100644 --- a/tools/parity_scale_decoder/build.rs +++ b/tools/parity_scale_decoder/build.rs @@ -1,7 +1,8 @@ //! Build script that auto-updates sample binaries from sources. -use std::{fs, io::Result, path::PathBuf}; +use std::{fs, path::PathBuf}; +use eyre::Result; use iroha_data_model::{account::NewAccount, domain::NewDomain, prelude::*}; use parity_scale_codec::Encode; use serde::de::DeserializeOwned; diff --git a/tools/parity_scale_decoder/samples/account.bin b/tools/parity_scale_decoder/samples/account.bin index 04aea960c0ea34cdacbcb9a926a181e98007dd34..c0ac2716337241b4188de397c53069ecccff7fff 100644 GIT binary patch literal 64 zcmdNW&(BLqEy_vEOA$%T$xKdVVQ6^!?fO=o0;3C7Z!+^0GWG@wh-!R3zKuirKkLFY U-TFhWEIb*BB}^ja8JQ)i09qXxvH$=8 literal 30 lcmdNW&(BLqEy_vEOA$%T$xKdVVByI~EMXEU&&VuE1puGN3GDy? diff --git a/tools/parity_scale_decoder/samples/account.json b/tools/parity_scale_decoder/samples/account.json index 08c2536561c..7bb4321521b 100644 --- a/tools/parity_scale_decoder/samples/account.json +++ b/tools/parity_scale_decoder/samples/account.json @@ -1,6 +1,8 @@ { "id": "alice@wonderland", - "signatories": [], + "signatories": [ + "ed0120EDF6D7B52C7032D03AEC696F2068BD53101528F3C7B6081BFF05A1662D7FC245" + ], "metadata": { "hat": { "Name": "white" diff --git a/tools/parity_scale_decoder/src/main.rs b/tools/parity_scale_decoder/src/main.rs index 6a60e0396ad..b01d9e9b3af 100644 --- a/tools/parity_scale_decoder/src/main.rs +++ b/tools/parity_scale_decoder/src/main.rs @@ -219,13 +219,17 @@ mod tests { let mut metadata = Metadata::new(); metadata .insert_with_limits( - "hat".parse().expect("Valid"), - Value::Name("white".parse().expect("Valid")), + "hat".parse().unwrap(), + Value::Name("white".parse().unwrap()), limits, ) .expect("Valid"); + let signature = PublicKey::from_str( + "ed0120EDF6D7B52C7032D03AEC696F2068BD53101528F3C7B6081BFF05A1662D7FC245", + ) + .unwrap(); let account = - Account::new("alice@wonderland".parse().expect("Valid"), []).with_metadata(metadata); + Account::new("alice@wonderland".parse().unwrap(), signature).with_metadata(metadata); decode_sample("account.bin", String::from("NewAccount"), &account); }