From 52db4df9ab941aff31dd2ec4dea8cffc596f3936 Mon Sep 17 00:00:00 2001 From: Dmitry Balashov <43530070+0x009922@users.noreply.github.com> Date: Thu, 16 May 2024 11:57:45 +0900 Subject: [PATCH 1/4] feat!: unify executor data model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marin Veršić Co-authored-by: Nikita Strygin Co-authored-by: Shanin Roman Co-authored-by: Dmitry Murzin Signed-off-by: Dmitry Balashov <43530070+0x009922@users.noreply.github.com> --- client/benches/tps/utils.rs | 20 +- client/src/client.rs | 17 +- client/tests/integration/asset_propagation.rs | 18 +- client/tests/integration/events/data.rs | 8 +- client/tests/integration/events/pipeline.rs | 22 +- .../extra_functional/connected_peers.rs | 19 +- .../multiple_blocks_created.rs | 18 +- .../extra_functional/offline_peers.rs | 13 +- .../extra_functional/restart_peer.rs | 4 +- .../extra_functional/unregister_peer.rs | 21 +- .../extra_functional/unstable_network.rs | 15 +- client/tests/integration/mod.rs | 2 +- client/tests/integration/parameters.rs | 83 +++ client/tests/integration/permissions.rs | 18 +- client/tests/integration/roles.rs | 13 +- client/tests/integration/set_parameter.rs | 62 --- .../integration/smartcontracts/Cargo.toml | 1 + .../executor_remove_permission/src/lib.rs | 15 +- .../executor_with_custom_parameter/Cargo.toml | 24 + .../executor_with_custom_parameter/src/lib.rs | 68 +++ .../src/lib.rs | 42 +- client/tests/integration/status_response.rs | 3 +- client/tests/integration/upgrade.rs | 61 ++- client_cli/src/main.rs | 8 +- configs/swarm/executor.wasm | Bin 522309 -> 526886 bytes configs/swarm/genesis.json | 48 +- core/src/smartcontracts/isi/account.rs | 16 +- core/src/smartcontracts/isi/mod.rs | 1 - core/src/smartcontracts/isi/query.rs | 4 +- core/src/smartcontracts/isi/world.rs | 117 ++--- core/src/smartcontracts/wasm.rs | 48 +- core/src/state.rs | 120 ++--- core/src/sumeragi/main_loop.rs | 20 +- core/test_network/src/lib.rs | 113 +++-- data_model/src/events/data/events.rs | 86 ++-- data_model/src/events/data/filters.rs | 22 - data_model/src/executor.rs | 71 ++- data_model/src/isi.rs | 25 +- data_model/src/lib.rs | 471 ++++++------------ data_model/src/permission.rs | 184 ++----- data_model/src/query/mod.rs | 58 ++- data_model/src/role.rs | 2 +- data_model/src/transaction.rs | 1 - data_model/src/visit.rs | 11 +- default_executor/src/lib.rs | 8 +- docs/source/references/schema.json | 243 +++------ schema/gen/src/lib.rs | 34 +- smart_contract/executor/derive/src/default.rs | 9 +- .../executor/derive/src/entrypoint.rs | 1 + smart_contract/executor/derive/src/lib.rs | 16 +- .../executor/derive/src/permission.rs | 34 ++ smart_contract/executor/derive/src/token.rs | 80 --- smart_contract/executor/src/default.rs | 206 ++++---- .../src/default/{tokens.rs => permissions.rs} | 204 ++++---- smart_contract/executor/src/lib.rs | 134 +++-- smart_contract/executor/src/parameter.rs | 59 +++ smart_contract/executor/src/permission.rs | 94 +++- tools/kagami/Cargo.toml | 2 +- tools/kagami/src/genesis.rs | 59 --- tools/parity_scale_cli/src/main.rs | 46 +- 60 files changed, 1434 insertions(+), 1788 deletions(-) create mode 100644 client/tests/integration/parameters.rs delete mode 100644 client/tests/integration/set_parameter.rs create mode 100644 client/tests/integration/smartcontracts/executor_with_custom_parameter/Cargo.toml create mode 100644 client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs create mode 100644 smart_contract/executor/derive/src/permission.rs delete mode 100644 smart_contract/executor/derive/src/token.rs rename smart_contract/executor/src/default/{tokens.rs => permissions.rs} (77%) create mode 100644 smart_contract/executor/src/parameter.rs diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index 913aca11688..94a4e5a95de 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -1,14 +1,7 @@ use std::{fmt, fs::File, io::BufReader, path::Path, sync::mpsc, thread, time}; use eyre::{Result, WrapErr}; -use iroha_client::{ - client::Client, - crypto::KeyPair, - data_model::{ - parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, - prelude::*, - }, -}; +use iroha_client::{client::Client, crypto::KeyPair, data_model::prelude::*}; use iroha_data_model::events::pipeline::{BlockEventFilter, BlockStatus}; use nonzero_ext::nonzero; use serde::Deserialize; @@ -51,16 +44,13 @@ impl Config { pub fn measure(self) -> Result { // READY - let (_rt, network, client) = Network::start_test_with_runtime(self.peers, None); + let (_rt, network, _client) = Network::start_test_with_runtime( + NetworkOptions::with_n_peers(self.peers) + .with_max_txs_in_block(self.max_txs_per_block.try_into().unwrap()), + ); let clients = network.clients(); wait_for_genesis_committed_with_max_retries(&clients, 0, self.genesis_max_retries); - client.submit_all_blocking( - ParametersBuilder::new() - .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, self.max_txs_per_block)? - .into_set_parameters(), - )?; - let unit_names = (UnitName::MIN..).take(self.peers as usize); let units = clients .into_iter() diff --git a/client/src/client.rs b/client/src/client.rs index f1079c1138a..4840144e195 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -328,7 +328,7 @@ impl_query_output! { crate::data_model::block::BlockHeader, crate::data_model::metadata::MetadataValueBox, crate::data_model::query::TransactionQueryOutput, - crate::data_model::permission::PermissionSchema, + crate::data_model::executor::ExecutorDataModel, crate::data_model::trigger::Trigger, crate::data_model::prelude::Numeric, } @@ -1517,11 +1517,6 @@ pub mod permission { //! Module with queries for permission tokens use super::*; - /// Construct a query to get all registered [`PermissionDefinition`]s - pub const fn permission_schema() -> FindPermissionSchema { - FindPermissionSchema {} - } - /// Construct a query to get all [`Permission`] granted /// to account with given [`Id`][AccountId] pub fn by_account_id(account_id: AccountId) -> FindPermissionsByAccountId { @@ -1564,6 +1559,16 @@ pub mod parameter { } } +pub mod executor { + //! Queries for executor entities + use super::*; + + /// Retrieve executor data model + pub const fn data_model() -> FindExecutorDataModel { + FindExecutorDataModel + } +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/client/tests/integration/asset_propagation.rs b/client/tests/integration/asset_propagation.rs index 84396976c85..ac46b31174d 100644 --- a/client/tests/integration/asset_propagation.rs +++ b/client/tests/integration/asset_propagation.rs @@ -3,12 +3,10 @@ use std::{str::FromStr as _, thread}; use eyre::Result; use iroha_client::{ client::{self, QueryResult}, - data_model::{ - parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, - prelude::*, - }, + data_model::prelude::*, }; use iroha_config::parameters::actual::Root as Config; +use nonzero_ext::nonzero; use test_network::*; use test_samples::gen_account_in; @@ -18,16 +16,14 @@ use test_samples::gen_account_in; fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount_on_another_peer( ) -> Result<()> { // Given - let (_rt, network, client) = Network::start_test_with_runtime(4, Some(10_450)); + let (_rt, network, client) = Network::start_test_with_runtime( + NetworkOptions::with_n_peers(4) + .with_start_port(10_450) + .with_max_txs_in_block(nonzero!(1u32)), + ); wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); - client.submit_all_blocking( - ParametersBuilder::new() - .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? - .into_set_parameters(), - )?; - let create_domain: InstructionBox = Register::domain(Domain::new(DomainId::from_str("domain")?)).into(); let (account_id, _account_keypair) = gen_account_in("domain"); diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index 9d29901409f..7dcbe278d2c 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -240,13 +240,13 @@ fn produce_multiple_events() -> Result<()> { DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdded( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_1.definition_id.clone(), + permission_id: token_1.id.clone(), }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdded( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_2.definition_id.clone(), + permission_id: token_2.id.clone(), }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleGranted( @@ -258,13 +258,13 @@ fn produce_multiple_events() -> Result<()> { DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemoved( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_1.definition_id, + permission_id: token_1.id, }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemoved( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_2.definition_id, + permission_id: token_2.id, }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleRevoked( diff --git a/client/tests/integration/events/pipeline.rs b/client/tests/integration/events/pipeline.rs index 778cb29615f..67ece93cf73 100644 --- a/client/tests/integration/events/pipeline.rs +++ b/client/tests/integration/events/pipeline.rs @@ -1,13 +1,7 @@ use std::thread::{self, JoinHandle}; use eyre::Result; -use iroha_client::{ - crypto::HashOf, - data_model::{ - parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, - prelude::*, - }, -}; +use iroha_client::{crypto::HashOf, data_model::prelude::*}; use iroha_config::parameters::actual::Root as Config; use iroha_data_model::{ events::pipeline::{ @@ -17,6 +11,7 @@ use iroha_data_model::{ transaction::error::TransactionRejectionReason, ValidationFail, }; +use nonzero_ext::nonzero; use test_network::*; // Needed to re-enable ignored tests. @@ -49,18 +44,15 @@ fn test_with_instruction_and_status_and_port( should_be: &TransactionStatus, port: u16, ) -> Result<()> { - let (_rt, network, client) = - Network::start_test_with_runtime(PEER_COUNT.try_into().unwrap(), Some(port)); + let (_rt, network, client) = Network::start_test_with_runtime( + NetworkOptions::with_n_peers(PEER_COUNT.try_into().unwrap()) + .with_start_port(port) + .with_max_txs_in_block(nonzero!(1u32)), + ); let clients = network.clients(); wait_for_genesis_committed(&clients, 0); let pipeline_time = Config::pipeline_time(); - client.submit_all_blocking( - ParametersBuilder::new() - .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? - .into_set_parameters(), - )?; - // Given let submitter = client; let transaction = submitter.build_transaction(instruction, UnlimitedMetadata::new()); diff --git a/client/tests/integration/extra_functional/connected_peers.rs b/client/tests/integration/extra_functional/connected_peers.rs index 000a352ce3d..a8b248b4f8e 100644 --- a/client/tests/integration/extra_functional/connected_peers.rs +++ b/client/tests/integration/extra_functional/connected_peers.rs @@ -17,17 +17,18 @@ use tokio::runtime::Runtime; #[ignore = "ignore, more in #2851"] #[test] fn connected_peers_with_f_2_1_2() -> Result<()> { - connected_peers_with_f(2, Some(11_020)) + connected_peers_with_f(2, 11_020) } #[test] fn connected_peers_with_f_1_0_1() -> Result<()> { - connected_peers_with_f(1, Some(11_000)) + connected_peers_with_f(1, 11_000) } #[test] fn register_new_peer() -> Result<()> { - let (_rt, network, _) = Network::start_test_with_runtime(4, Some(11_180)); + let (_rt, network, _) = + Network::start_test_with_runtime(NetworkOptions::with_n_peers(4).with_start_port(11_180)); wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); @@ -65,14 +66,16 @@ fn register_new_peer() -> Result<()> { } /// Test the number of connected peers, changing the number of faults tolerated down and up -fn connected_peers_with_f(faults: u64, start_port: Option) -> Result<()> { +fn connected_peers_with_f(faults: u64, start_port: u16) -> Result<()> { let n_peers = 3 * faults + 1; let (_rt, network, _) = Network::start_test_with_runtime( - (n_peers) - .try_into() - .wrap_err("`faults` argument `u64` value too high, cannot convert to `u32`")?, - start_port, + NetworkOptions::with_n_peers( + (n_peers) + .try_into() + .wrap_err("`faults` argument `u64` value too high, cannot convert to `u32`")?, + ) + .with_start_port(start_port), ); wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); diff --git a/client/tests/integration/extra_functional/multiple_blocks_created.rs b/client/tests/integration/extra_functional/multiple_blocks_created.rs index 0c96b849824..175cc809d10 100644 --- a/client/tests/integration/extra_functional/multiple_blocks_created.rs +++ b/client/tests/integration/extra_functional/multiple_blocks_created.rs @@ -3,12 +3,10 @@ use std::thread; use eyre::Result; use iroha_client::{ client::{self, Client, QueryResult}, - data_model::{ - parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, - prelude::*, - }, + data_model::prelude::*, }; use iroha_config::parameters::actual::Root as Config; +use nonzero_ext::nonzero; use test_network::*; use test_samples::gen_account_in; @@ -18,16 +16,14 @@ const N_BLOCKS: usize = 510; #[test] fn long_multiple_blocks_created() -> Result<()> { // Given - let (_rt, network, client) = Network::start_test_with_runtime(4, Some(10_965)); + let (_rt, network, client) = Network::start_test_with_runtime( + NetworkOptions::with_n_peers(4) + .with_start_port(10_965) + .with_max_txs_in_block(nonzero!(1u32)), + ); wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); - client.submit_all_blocking( - ParametersBuilder::new() - .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? - .into_set_parameters(), - )?; - let create_domain: InstructionBox = Register::domain(Domain::new("domain".parse()?)).into(); let (account_id, _account_keypair) = gen_account_in("domain"); let create_account = Register::account(Account::new(account_id.clone())).into(); diff --git a/client/tests/integration/extra_functional/offline_peers.rs b/client/tests/integration/extra_functional/offline_peers.rs index b6455eed100..1b5fd66535a 100644 --- a/client/tests/integration/extra_functional/offline_peers.rs +++ b/client/tests/integration/extra_functional/offline_peers.rs @@ -18,10 +18,10 @@ fn genesis_block_is_committed_with_some_offline_peers() -> Result<()> { // Given let rt = Runtime::test(); - let (network, client) = rt.block_on(Network::start_test_with_offline_and_set_n_shifts( - 4, - 1, - Some(10_560), + let (network, client) = rt.block_on(Network::start_test( + NetworkOptions::with_n_peers(4) + .with_offline_peers(1) + .with_start_port(10_560), )); wait_for_genesis_committed(&network.clients(), 1); @@ -44,9 +44,8 @@ fn genesis_block_is_committed_with_some_offline_peers() -> Result<()> { #[test] fn register_offline_peer() -> Result<()> { - let n_peers = 4; - - let (_rt, network, client) = Network::start_test_with_runtime(n_peers, Some(11_160)); + let (_rt, network, client) = + Network::start_test_with_runtime(NetworkOptions::with_n_peers(4).with_start_port(11_160)); wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); let peer_clients = Network::clients(&network); diff --git a/client/tests/integration/extra_functional/restart_peer.rs b/client/tests/integration/extra_functional/restart_peer.rs index 5b1995ad2d2..c16aa8d63b2 100644 --- a/client/tests/integration/extra_functional/restart_peer.rs +++ b/client/tests/integration/extra_functional/restart_peer.rs @@ -20,7 +20,9 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> { let mut removed_peer = { let n_peers = 4; - let (_rt, network, _) = Network::start_test_with_runtime(n_peers, Some(11_205)); + let (_rt, network, _) = Network::start_test_with_runtime( + NetworkOptions::with_n_peers(n_peers).with_start_port(11_205), + ); wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); let peer_clients = Network::clients(&network); diff --git a/client/tests/integration/extra_functional/unregister_peer.rs b/client/tests/integration/extra_functional/unregister_peer.rs index 5742025cc01..0e79c8fe55d 100644 --- a/client/tests/integration/extra_functional/unregister_peer.rs +++ b/client/tests/integration/extra_functional/unregister_peer.rs @@ -3,12 +3,10 @@ use std::thread; use eyre::Result; use iroha_client::{ client::{self, QueryResult}, - data_model::{ - parameter::{default::MAX_TRANSACTIONS_IN_BLOCK, ParametersBuilder}, - prelude::*, - }, + data_model::prelude::*, }; use iroha_config::parameters::actual::Root as Config; +use nonzero_ext::nonzero; use test_network::*; use test_samples::gen_account_in; @@ -114,24 +112,25 @@ fn init() -> Result<( AccountId, AssetDefinitionId, )> { - let (rt, network, client) = Network::start_test_with_runtime(4, Some(10_925)); + let (rt, network, client) = Network::start_test_with_runtime( + NetworkOptions::with_n_peers(4) + .with_start_port(10_925) + .with_max_txs_in_block(nonzero!(1u32)), + ); let pipeline_time = Config::pipeline_time(); iroha_logger::info!("Started"); - let parameters = ParametersBuilder::new() - .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? - .into_set_parameters(); let create_domain = Register::domain(Domain::new("domain".parse()?)); let (account_id, _account_keypair) = gen_account_in("domain"); let create_account = Register::account(Account::new(account_id.clone())); let asset_definition_id: AssetDefinitionId = "xor#domain".parse()?; let create_asset = Register::asset_definition(AssetDefinition::numeric(asset_definition_id.clone())); - let instructions = parameters.into_iter().chain([ + let isi: [InstructionBox; 3] = [ create_domain.into(), create_account.into(), create_asset.into(), - ]); - client.submit_all_blocking(instructions)?; + ]; + client.submit_all_blocking(isi)?; iroha_logger::info!("Init"); Ok(( rt, diff --git a/client/tests/integration/extra_functional/unstable_network.rs b/client/tests/integration/extra_functional/unstable_network.rs index 52b2b9ad851..d0b1fd2c90b 100644 --- a/client/tests/integration/extra_functional/unstable_network.rs +++ b/client/tests/integration/extra_functional/unstable_network.rs @@ -61,11 +61,16 @@ fn unstable_network( { configuration.sumeragi.debug_force_soft_fork = force_soft_fork; } - let network = Network::new_with_offline_peers( - Some(configuration), - n_peers + n_offline_peers, - 0, - Some(port), + let network = Network::new( + NetworkOptions::with_n_peers(n_peers + n_offline_peers) + .with_start_port(port) + .with_max_txs_in_block(MAX_TRANSACTIONS_IN_BLOCK.try_into().unwrap()) + .with_config_mut(|cfg| { + #[cfg(debug_assertions)] + { + cfg.sumeragi.debug_force_soft_fork = force_soft_fork; + } + }), ) .await .expect("Failed to init peers"); diff --git a/client/tests/integration/mod.rs b/client/tests/integration/mod.rs index 37299969665..1dc38c9fc08 100644 --- a/client/tests/integration/mod.rs +++ b/client/tests/integration/mod.rs @@ -6,10 +6,10 @@ mod events; mod extra_functional; mod non_mintable; mod pagination; +mod parameters; mod permissions; mod queries; mod roles; -mod set_parameter; mod sorting; mod status_response; mod transfer_asset; diff --git a/client/tests/integration/parameters.rs b/client/tests/integration/parameters.rs new file mode 100644 index 00000000000..290babbb2b9 --- /dev/null +++ b/client/tests/integration/parameters.rs @@ -0,0 +1,83 @@ +use std::collections::BTreeSet; + +use eyre::Result; +use iroha_client::{client, data_model::prelude::*}; +use serde_json::json; +use test_network::*; + +#[test] +fn playing_with_custom_parameter() -> Result<()> { + let (_rt, _peer, client) = ::new().with_port(11_135).start_with_runtime(); + wait_for_genesis_committed(&vec![client.clone()], 0); + + assert!( + client + .request(client::executor::data_model())? + .parameters() + .is_empty(), + "existing parameters should be empty" + ); + + // Registering some domain while there is no prefix requirement + client + .submit_blocking(Register::domain(Domain::new("axolotl".parse().unwrap()))) + .expect("should be fine"); + + let _err = client + .submit_blocking(SetParameter::new(Parameter::new( + "Whatever".parse().unwrap(), + json!({ "foo": "bar" }), + ))) + .expect_err("should not allow setting unknown parameters"); + + super::upgrade::upgrade_executor( + &client, + "tests/integration/smartcontracts/executor_with_custom_parameter", + )?; + + assert_eq!( + *client.request(client::executor::data_model())?.parameters(), + ["EnforceDomainPrefix"] + .into_iter() + .map(|x| ParameterId::new(x.parse().unwrap())) + .collect::>(), + "data model should have this set of parameters after upgrade" + ); + + let parameter = Parameter::new( + "EnforceDomainPrefix".parse().unwrap(), + json!({ "prefix": "disney_" }), + ); + + client + .submit_blocking(SetParameter::new(parameter.clone())) + .expect("should work, since this parameter is now registered"); + + let _err = client + .submit_blocking(SetParameter::new(Parameter::new( + "WrongNonExistingParameter".parse().unwrap(), + json!({ "prefix": "whatever" }), + ))) + .expect_err("should still not work"); + + assert_eq!( + client + .request(client::parameter::all())? + .map(Result::unwrap) + .collect::>(), + [parameter.clone()].into_iter().collect(), + "we should find set parameter in the parameters list" + ); + + let _err = client + .submit_blocking(Register::domain(Domain::new("land".parse().unwrap()))) + .expect_err("should fail since `land` is not prefixed with `disney_`"); + + client + .submit_blocking(Register::domain(Domain::new( + "disney_land".parse().unwrap(), + ))) + .expect("should be fine, since we used prefix according to the parameter"); + + Ok(()) +} diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 678d7b71b1a..7aebf8f5e35 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -315,10 +315,9 @@ fn stored_vs_granted_token_payload() -> Result<()> { // Allow alice to mint mouse asset and mint initial value let mouse_asset = AssetId::new(asset_definition_id, mouse_id.clone()); let allow_alice_to_set_key_value_in_mouse_asset = Grant::permission( - Permission::from_str_unchecked( + Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - &*format!(r###"{{ "asset_id" : "xor#wonderland#{mouse_id}" }}"###), + json!({ "asset_id": format!("xor#wonderland#{mouse_id}") }), ), alice_id, ); @@ -349,19 +348,18 @@ fn permissions_are_unified() { let alice_id = ALICE_ID.clone(); let allow_alice_to_transfer_rose_1 = Grant::permission( - Permission::from_str_unchecked( + Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - &*format!(r###"{{ "asset_id" : "rose#wonderland#{alice_id}" }}"###), + json!({ "asset_id": format!("rose#wonderland#{alice_id}") }), ), alice_id.clone(), ); let allow_alice_to_transfer_rose_2 = Grant::permission( - Permission::from_str_unchecked( + Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - &*format!(r###"{{ "asset_id" : "rose##{alice_id}" }}"###), + // different content, but same meaning + json!({ "asset_id": format!("rose##{alice_id}") }), ), alice_id, ); @@ -372,7 +370,7 @@ fn permissions_are_unified() { let _ = iroha_client .submit_blocking(allow_alice_to_transfer_rose_2) - .expect_err("permission tokens are not unified"); + .expect_err("should reject due to duplication"); } #[test] diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 6efab725cd5..a27875fff2a 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -172,20 +172,19 @@ fn role_with_invalid_permissions_is_not_accepted() -> Result<()> { #[test] #[allow(deprecated)] -fn role_permissions_unified() { +fn role_permissions_are_deduplicated() { let (_rt, _peer, test_client) = ::new().with_port(11_235).start_with_runtime(); wait_for_genesis_committed(&vec![test_client.clone()], 0); - let allow_alice_to_transfer_rose_1 = Permission::from_str_unchecked( + let allow_alice_to_transfer_rose_1 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - "{ \"asset_id\" : \"rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland\" }", + json!({ "asset_id": "rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), ); - let allow_alice_to_transfer_rose_2 = Permission::from_str_unchecked( + // Different content, but same meaning + let allow_alice_to_transfer_rose_2 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - "{ \"asset_id\" : \"rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland\" }", + json!({ "asset_id": "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), ); let role_id: RoleId = "role_id".parse().expect("Valid"); diff --git a/client/tests/integration/set_parameter.rs b/client/tests/integration/set_parameter.rs deleted file mode 100644 index e35209b0101..00000000000 --- a/client/tests/integration/set_parameter.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::str::FromStr; - -use eyre::Result; -use iroha_client::{ - client::{self, QueryResult}, - data_model::prelude::*, -}; -use test_network::*; - -#[test] -fn can_change_parameter_value() -> Result<()> { - let (_rt, _peer, test_client) = ::new().with_port(11_135).start_with_runtime(); - wait_for_genesis_committed(&vec![test_client.clone()], 0); - - let parameter = Parameter::from_str("?BlockTime=4000")?; - let parameter_id = ParameterId::from_str("BlockTime")?; - let param_box = SetParameter::new(parameter); - - let old_params = test_client - .request(client::parameter::all())? - .collect::>>()?; - let param_val_old = old_params - .iter() - .find(|param| param.id() == ¶meter_id) - .expect("Parameter should exist") - .val(); - - test_client.submit_blocking(param_box)?; - - let new_params = test_client - .request(client::parameter::all())? - .collect::>>()?; - let param_val_new = new_params - .iter() - .find(|param| param.id() == ¶meter_id) - .expect("Parameter should exist") - .val(); - - assert_ne!(param_val_old, param_val_new); - Ok(()) -} - -#[test] -fn parameter_propagated() -> Result<()> { - let (_rt, _peer, test_client) = ::new().with_port(10_985).start_with_runtime(); - wait_for_genesis_committed(&vec![test_client.clone()], 0); - - let too_long_domain_name: DomainId = "0".repeat(2_usize.pow(8)).parse()?; - let create_domain = Register::domain(Domain::new(too_long_domain_name)); - let _ = test_client - .submit_blocking(create_domain.clone()) - .expect_err("Should fail before ident length limits update"); - - let parameter = Parameter::from_str("?WSVIdentLengthLimits=1,256_LL")?; - let param_box = SetParameter::new(parameter); - test_client.submit_blocking(param_box)?; - - test_client - .submit_blocking(create_domain) - .expect("Should work after ident length limits update"); - Ok(()) -} diff --git a/client/tests/integration/smartcontracts/Cargo.toml b/client/tests/integration/smartcontracts/Cargo.toml index a03698db1b2..a9433d0a026 100644 --- a/client/tests/integration/smartcontracts/Cargo.toml +++ b/client/tests/integration/smartcontracts/Cargo.toml @@ -12,6 +12,7 @@ members = [ "create_nft_for_every_user_trigger", "mint_rose_trigger", "executor_with_admin", + "executor_with_custom_parameter", "executor_with_custom_permission", "executor_remove_permission", "executor_with_migration_fail", diff --git a/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs index 9ed9fba3b5d..a81d80355f4 100644 --- a/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs @@ -3,11 +3,12 @@ #![no_std] -extern crate alloc; #[cfg(not(test))] extern crate panic_halt; -use iroha_executor::{default::default_permission_schema, prelude::*}; +use iroha_executor::{ + default::permissions::domain::CanUnregisterDomain, prelude::*, DataModelBuilder, +}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] @@ -25,13 +26,9 @@ struct Executor { pub fn migrate(_block_height: u64) -> MigrationResult { // Note that actually migration will reset token schema to default (minus `CanUnregisterDomain`) // So any added custom permission tokens will be also removed - let mut schema = default_permission_schema(); - schema.remove::(); - - let (token_ids, schema_str) = schema.serialize(); - iroha_executor::set_permission_schema( - &iroha_executor::data_model::permission::PermissionSchema::new(token_ids, schema_str), - ); + DataModelBuilder::with_default_permissions() + .remove_permission::() + .set(); Ok(()) } diff --git a/client/tests/integration/smartcontracts/executor_with_custom_parameter/Cargo.toml b/client/tests/integration/smartcontracts/executor_with_custom_parameter/Cargo.toml new file mode 100644 index 00000000000..9287d2caff4 --- /dev/null +++ b/client/tests/integration/smartcontracts/executor_with_custom_parameter/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "executor_with_custom_parameter" + +edition.workspace = true +version.workspace = true +authors.workspace = true + +license.workspace = true + +[lib] +crate-type = ['cdylib'] + +[dependencies] +iroha_executor.workspace = true +iroha_schema.workspace = true + +parity-scale-codec.workspace = true +anyhow.workspace = true +serde_json.workspace = true +serde.workspace = true + +panic-halt.workspace = true +lol_alloc.workspace = true +getrandom.workspace = true diff --git a/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs new file mode 100644 index 00000000000..4f7a9f391d8 --- /dev/null +++ b/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs @@ -0,0 +1,68 @@ +//! Runtime Executor which defines a custom configuration parameter + +#![no_std] + +extern crate alloc; +#[cfg(not(test))] +extern crate panic_halt; + +use alloc::{format, string::String}; + +use iroha_executor::{parameter::Parameter, prelude::*, DataModelBuilder}; +use iroha_schema::IntoSchema; +use lol_alloc::{FreeListAllocator, LockedAllocator}; +use serde::{Deserialize, Serialize}; + +#[global_allocator] +static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + +getrandom::register_custom_getrandom!(iroha_executor::stub_getrandom); + +#[derive(Constructor, ValidateEntrypoints, Validate, Visit)] +#[visit(custom(visit_register_domain, visit_set_parameter))] +struct Executor { + verdict: Result, + block_height: u64, +} + +fn visit_set_parameter(executor: &mut Executor, _authority: &AccountId, isi: &SetParameter) { + execute!(executor, isi); +} + +fn visit_register_domain(executor: &mut Executor, _authority: &AccountId, isi: &Register) { + // FIXME: unwrap is ok here? + let required_prefix = FindAllParameters + .execute() + .unwrap() + .into_iter() + .map(Result::unwrap) + .find_map(|parameter| EnforceDomainPrefix::try_from_object(¶meter).ok()); + + if let Some(EnforceDomainPrefix { prefix }) = required_prefix { + let domain_id = isi.object().id().name().as_ref(); + if domain_id.strip_prefix(&prefix).is_none() { + deny!( + executor, + "Domain `{domain_id}` must be prefixed with `{prefix}`" + ); + } + } + + execute!(executor, isi); +} + +#[derive(IntoSchema, Serialize, Deserialize)] +struct EnforceDomainPrefix { + prefix: String, +} + +impl Parameter for EnforceDomainPrefix {} + +#[entrypoint] +pub fn migrate(_block_height: u64) -> MigrationResult { + DataModelBuilder::new() + .add_parameter::() + .set(); + + Ok(()) +} diff --git a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs index 3c4033465c1..af0c6f04392 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs @@ -3,9 +3,9 @@ //! //! This executor should be applied on top of the blockchain with default validation. //! -//! It also doesn't have [`iroha_executor::default::tokens::domain::CanUnregisterDomain`]. +//! It also doesn't have [`iroha_executor::default::permissions::domain::CanUnregisterDomain`]. //! -//! In migration it replaces [`iroha_executor::default::tokens::domain::CanUnregisterDomain`] +//! In migration it replaces [`iroha_executor::default::permissions::domain::CanUnregisterDomain`] //! with [`token::CanControlDomainLives`] for all accounts. //! So it doesn't matter which domain user was able to unregister before migration, they will //! get access to control all domains. Remember that this is just a test example. @@ -16,10 +16,10 @@ extern crate alloc; #[cfg(not(test))] extern crate panic_halt; -use alloc::{borrow::ToOwned, string::String}; +use alloc::string::String; use anyhow::anyhow; -use iroha_executor::{default::default_permission_schema, permission::Token as _, prelude::*}; +use iroha_executor::{permission::Permission as _, prelude::*, DataModelBuilder}; use iroha_schema::IntoSchema; use lol_alloc::{FreeListAllocator, LockedAllocator}; use parity_scale_codec::{Decode, Encode}; @@ -42,7 +42,7 @@ mod token { #[derive( PartialEq, Eq, - Token, + Permission, ValidateGrantRevoke, Decode, Encode, @@ -95,7 +95,9 @@ impl Executor { })?; if let Ok(can_unregister_domain_token) = - iroha_executor::default::tokens::domain::CanUnregisterDomain::try_from(&token) + iroha_executor::default::permissions::domain::CanUnregisterDomain::try_from_object( + &token, + ) { found_accounts.push((account, can_unregister_domain_token.domain_id)); break; @@ -107,19 +109,16 @@ impl Executor { } fn replace_token(accounts: &[(Account, DomainId)]) -> MigrationResult { - let can_unregister_domain_definition_id = PermissionId::try_from( - iroha_executor::default::tokens::domain::CanUnregisterDomain::type_name(), - ) - .unwrap(); + let can_unregister_domain_definition_id = + iroha_executor::default::permissions::domain::CanUnregisterDomain::id(); - let can_control_domain_lives_definition_id = - PermissionId::try_from(token::CanControlDomainLives::type_name()).unwrap(); + let can_control_domain_lives_definition_id = token::CanControlDomainLives::id(); accounts .iter() .try_for_each(|(account, domain_id)| { Revoke::permission( - Permission::new( + PermissionObject::new( can_unregister_domain_definition_id.clone(), &json!({ "domain_id": domain_id }), ), @@ -138,7 +137,10 @@ impl Executor { })?; Grant::permission( - Permission::new(can_control_domain_lives_definition_id.clone(), &json!(null)), + PermissionObject::new( + can_control_domain_lives_definition_id.clone(), + &json!(null), + ), account.id().clone(), ) .execute() @@ -199,14 +201,10 @@ fn visit_unregister_domain( pub fn migrate(_block_height: u64) -> MigrationResult { let accounts = Executor::get_all_accounts_with_can_unregister_domain_permission()?; - let mut schema = default_permission_schema(); - schema.remove::(); - schema.insert::(); - - let (token_ids, schema_str) = schema.serialize(); - iroha_executor::set_permission_schema( - &iroha_executor::data_model::permission::PermissionSchema::new(token_ids, schema_str), - ); + DataModelBuilder::with_default_permissions() + .remove_permission::() + .add_permission::() + .set(); Executor::replace_token(&accounts) } diff --git a/client/tests/integration/status_response.rs b/client/tests/integration/status_response.rs index 8209d46b5dd..726500cf39e 100644 --- a/client/tests/integration/status_response.rs +++ b/client/tests/integration/status_response.rs @@ -16,7 +16,8 @@ fn status_eq_excluding_uptime_and_queue(lhs: &Status, rhs: &Status) -> bool { #[test] fn json_and_scale_statuses_equality() -> Result<()> { - let (_rt, network, client) = Network::start_test_with_runtime(2, Some(11_200)); + let (_rt, network, client) = + Network::start_test_with_runtime(NetworkOptions::with_n_peers(4).with_start_port(11_200)); wait_for_genesis_committed(&network.clients(), 0); let json_status_zero = get_status_json(&client).unwrap(); diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 8910e7f4abe..637a6ea879a 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -1,6 +1,7 @@ use std::{path::Path, str::FromStr as _}; use eyre::Result; +use futures_util::TryStreamExt as _; use iroha_client::{ client::{self, Client, QueryResult}, crypto::KeyPair, @@ -10,6 +11,7 @@ use iroha_logger::info; use serde_json::json; use test_network::*; use test_samples::ALICE_ID; +use tokio::sync::mpsc; const ADMIN_PUBLIC_KEY_MULTIHASH: &str = "ed012076E5CA9698296AF9BE2CA45F525CB3BCFDEB7EE068BA56F973E9DD90564EF4FC"; @@ -76,7 +78,7 @@ fn executor_upgrade_should_run_migration() -> Result<()> { let can_unregister_domain_token_id = "CanUnregisterDomain".parse().unwrap(); // Check that `CanUnregisterDomain` exists - let definitions = client.request(FindPermissionSchema)?; + let definitions = client.request(FindExecutorDataModel)?; assert!(definitions .permissions() .iter() @@ -99,15 +101,15 @@ fn executor_upgrade_should_run_migration() -> Result<()> { )?; // Check that `CanUnregisterDomain` doesn't exist - let definitions = client.request(FindPermissionSchema)?; - assert!(!definitions + let data_model = client.request(FindExecutorDataModel)?; + assert!(!data_model .permissions() .iter() .any(|id| id == &can_unregister_domain_token_id)); let can_control_domain_lives_token_id = "CanControlDomainLives".parse().unwrap(); - assert!(definitions + assert!(data_model .permissions() .iter() .any(|id| id == &can_control_domain_lives_token_id)); @@ -144,9 +146,9 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { // Check that permission exists assert!(client - .request(FindPermissionSchema)? + .request(FindExecutorDataModel)? .permissions() - .contains(&can_unregister_domain_token.definition_id)); + .contains(&can_unregister_domain_token.id)); // Check that `TEST_ROLE` has permission assert!(client @@ -172,9 +174,9 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { // Check that permission doesn't exist assert!(!client - .request(FindPermissionSchema)? + .request(FindExecutorDataModel)? .permissions() - .contains(&can_unregister_domain_token.definition_id)); + .contains(&can_unregister_domain_token.id)); // Check that `TEST_ROLE` doesn't have permission assert!(!client @@ -225,7 +227,48 @@ fn migration_fail_should_not_cause_any_effects() { // been changed, because `executor_with_migration_fail` does not allow any queries } -fn upgrade_executor(client: &Client, executor: impl AsRef) -> Result<()> { +#[test] +fn migration_should_cause_upgrade_event() { + let (rt, _peer, client) = ::new().with_port(10_996).start_with_runtime(); + wait_for_genesis_committed(&vec![client.clone()], 0); + + let (sender, mut receiver) = mpsc::channel(1); + let events_client = client.clone(); + + let _handle = rt.spawn(async move { + let mut stream = events_client + .listen_for_events_async([ExecutorEventFilter::new()]) + .await + .unwrap(); + while let Some(event) = stream.try_next().await.unwrap() { + if let EventBox::Data(DataEvent::Executor(ExecutorEvent::Upgraded(ExecutorUpgrade { + new_data_model, + }))) = event + { + let _ = sender.send(new_data_model).await; + } + } + }); + + upgrade_executor( + &client, + "tests/integration/smartcontracts/executor_with_custom_permission", + ) + .unwrap(); + + let data_model = rt + .block_on(async { + tokio::time::timeout(std::time::Duration::from_secs(60), receiver.recv()).await + }) + .ok() + .flatten() + .expect("should receive upgraded event immediately after upgrade"); + + assert!(!data_model.permissions.is_empty()); + assert!(data_model.parameters.is_empty()); +} + +pub fn upgrade_executor(client: &Client, executor: impl AsRef) -> Result<()> { info!("Building executor"); let wasm = iroha_wasm_builder::Builder::new(executor.as_ref()) diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index d8d96386c88..6e93284efc9 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -522,7 +522,7 @@ mod account { use iroha_client::client::{self}; - use super::{Permission as DataModelPermission, *}; + use super::{Permission as PermissionObject, *}; /// subcommands for account subcommand #[derive(clap::Subcommand, Debug)] @@ -608,9 +608,9 @@ mod account { pub metadata: MetadataArgs, } - /// [`DataModelPermission`] wrapper implementing [`FromStr`] + /// [`PermissionObject`] wrapper implementing [`FromStr`] #[derive(Debug, Clone)] - pub struct Permission(DataModelPermission); + pub struct Permission(PermissionObject); impl FromStr for Permission { type Err = Error; @@ -618,7 +618,7 @@ mod account { fn from_str(s: &str) -> Result { let content = fs::read_to_string(s) .wrap_err(format!("Failed to read the permission token file {}", &s))?; - let permission: DataModelPermission = json5::from_str(&content).wrap_err(format!( + let permission: PermissionObject = json5::from_str(&content).wrap_err(format!( "Failed to deserialize the permission token from file {}", &s ))?; diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 8c552ccf2056fc4ab83600ba6fae647b7db903ad..57349508d97d53808e2d4086357cc13e61f8c2bc 100644 GIT binary patch delta 211185 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 z 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

LBykbuMT(ADp`l<8g&YG?K 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�%$7p z-#f9e&}6DrxdKxsnL+(kzu0O46P87q(Y6#p#_G=MP16*wXuvP4z*^v@T@yr0#ZjieRb$b;$OB#hUyWJ^8y~xIv zSwsgWX-|~DmauV#Luw$T(qWBM3-Pz)&qBij8E;add;;|Fec^K*$nA2lln-FqW5C2F zDr|`oV`9R(FPWL zuwMZ(nVs|?B(6g$*~C^5gkPF}4pV#9-7Si|dA0>uAGl444HXKziPhA^*a|l$Rpuv( zr&W%h7ePlP5SpL$&Qfe1;Z{GKFN0cUeGca%H{WB&Xtj%NMKyA-N}Jt=s}sDt&8@@L z1IKP(#}OgEjWs6@rs<_H+SK&IrkZ`(Ud6P{d^=ajkES)fsAx*l3;XWdX8s7ZTb%>u z7&Tf}VYS~lC~V|t_jvsD7M4D|pMK)E8}Nw~nG|AHZ5COXxG*EcgbxVgUr!S}v5lle z5VFOpeo>>WbXVhi6&Bkn=Oc;UZ%)}wopbzy0u$a{L*au;Ld|S9B!!|j1ATDWB!gu+ zDFTVi_5{5|+!HZKNCi`9Vy+7BGoweU&UFt-D~*=2ZDKk&vooFw>R9kDB3X`4vZE!l z%w<#RGOI?L>8J-YV1tC?SI#Yx$`|YkFC){Ydnc^%ppC)3iMfnvhJNlbFo?=FTK z6)uvO`MeY{j;BR2UgXlC-B2!heMYjZvRQ&~aV|^V$L)L+LlPhB02%Vo5-B5dP$JXh zs*qU-d5vpqb{)A;A-S>7Cum3r zs<|jqyC3bgchRyidlTFQ#(k6qOM53cyw~=cIgxQbX?tSPqd5NPfe3Oc0M4k*gk7}R zOnI&9SCK0!yaPqqI&D#3T$*cD>$)dhu%ksmyYVg)^%nE8R2r4Jl=l1>0NHezMp+?} zZq*dR-6dTanM>B7gH=F#ak@PvrCz{yMooyh@El~hTFKR z1bd)J*~ZII@z^G0?KC4j2351%up!pXNsmX@9MQmyP8gqns2J%HN8|~;{&`QeI?bz? zPv?LV&0TMsh0n=kpjm=mJZHA-rP{WCUg$|#_=3z%Gq<}6bq~T9O=b)gax&PSW7J6a zi25y_~;PzY+>QG#lm%m*ET$OjA~Ix*`UmAj|-Z1QRhsyyP$Hxp;_p z!W?H_8CH`V<6AL{tNOF(a|5YSgxO}mZbo>?uJ=k%`bn#|l4mZtbzwO^m> zi@_mxt7FXXfQ?X1-#T&#(<>1Kf+X_UTzsx-2SVO*E>kJ&J(8xlsctbZovXg(!;IB`KoV< zK4vpETz!4sp_%is<1u0r887=}d=cL7Y%% zn`xWr7%hxbkitc4)hSvOC$Nx2f7Ll!5+~p=L=amh9X%f>pmeDAt23EsS)5QR3B59v z(egNHt%+0c3aa*pWx7Udy*k}1 zVipAU;g$w{iR$H7H2SG_Oq$#|{_tDRCL-HZTX!q?*vnM+!sjHlSvbY~`!cns_p(`h znHrKNq?};(upaV4v*IyS?T@SEj2rglbD~XL0Y6ForH%VMh`pZA!zx8s0CR9mCKRR6 z$j;Ca1^2>9pxj%ca}aKV128A4-ADdc=sMJuNE<|5`|LlQdVM|i`(V%A-*4y$-Y#E~R`^$?IDr=mae?cKnyn0=$)jg$@&KFUMW_5k_%TOwXBjr>F|=TJ!xB)uGyQ zaqiG2Vl3fWh~5)|PDH4fH{?aqZZx}Jp?bHH+#Vs%aTWp?yJ60;Rxo+dXHNM5!P0h|CSd{7{O zvjBf77_%nvnP$3%+of89Ytx@6TChE}o{!i(e73MRrvD1E#_7VRD+LI(8G3RlO72HT&`k zB`UITxrAF=NJrjTvKfcVO=h~v45f=Jm1wtAw2_qLiQ_&Vs~`+=3*Okv<-?b5-n4a0|Mc0S=d-7 zF)Q)H{#&;OmW6any3TB_avXy>>v}aDqwoFKt7?1f5WB3z(D_O9*3k|cHQ(ttHDExAFJ zb9%n0`Z$2YkM6QLqoiqmgX;L}YI~I1QCM(Wb|G3LY}LUtR8@!Pq3s!&t`&;0Ms;Vq zxk$^5n5i>V8*9=Cq#f3=6nh*4U*R(G>nLG+dv^w``fH}|jp}G`xw+y-teLCK{2SG5 znJA=k5ESM+9SikqVaGfWIy)DPEi1)JNFrK+cn(>qvD7OO#3FpVxn;Vtk}a;d64Msg zSq8DQl5Lz`m-`UQcu;H5Li7oOB+O`5vbB2z3E7IWk}c_y-wL*pKNf6}>>z5n{DnEW zR`wUrH*G~^gy~ea#6mvoQX=l^M!O;9hM=8PE8vRTERF&vpFRv21qin9D7APMJ)Iqy zD7&okgo18C1gK}c0>NcYmp8A>RIg-^S$T`sOSS*7u52Dpw12iNz55y(Zek)k+k9cX zK3b$(#tVSpF}na7y5FJ(`06@tKQ({4Rjuu?;3m05ivtvD)lhr5M0`YcH~~KTklR!n zgx$a2rh2CG(`S=@=*F49U(%jIVz)agPYZn*;+OL??SE-4S!yooRM6Tq&Q_(S=WJD8 zbcbNqd@;xFJ6lcg9yX87R$cT`UyV+fx=sbHBx@%1hV06$T`zpYbeN;YbMx?-bJWPz zFRN5vC$@%7=Oe#io|~iYi9-+$g!nDG9frNZY`h)8bDimR2i^v6o0IQQ`*H-&BX_7< z@NqcsPPHFD=1z4I1$Nx28opnG^O2DqGBX;@NnTy`h{o z<3}ybo6oB5(#f6As`lQyru}oOYQTSOzO^C+mhRmqq{pt0kodVoYyJ}5p@rvLM*SGx5s z{h4#iFY0Y$E_~kA`@pYr|FZXtUUG97MHX!6M*y4M72ouK2V`w?Aba2iQ067G`UO?d zT+Z2o6Hm(_Qi6q5rs!_fA%#DiykVJn-t@a$wf3Gj``oP_0dKy)8%LB$rq?_?k6tiW z&Qsm@bIe{kIU=!Mw$hEF=8(dumE4x)NB3LC;xf?;p;%{bvDJ(5FRjAog!y9^MnpD0 z%tH$^)pVP$4y5vP=Bugpel@4%DY-|rt(z=oc6n!wNOWdtrRB^kxZot1)(mS0M=Jpa zXv_jHjYPUH)3Jr2A?e7UalAIl>|l^msZqJ5Wi>(ofOSZ1qC}-QKq;)Wun~jxPMbhk zbLTy(`6=6(c3TgKzCWNxgq1%at!+TG4_^2c*=v$*dHUKly3Hn}N%)2*cR12oI?8cG zqTV%@CMlsPX-4mAb?C3Xr>{L?ITh?dHRK)%F(j zihVjNKV?F+9&IHDotZE0Q=MD7RRH?C`hl@;qPeP7h2tBnJP2z_vLIL@HKoY+%&IYd z#w;13TbPUPSIs(=jjuo#QC7xHib@9sg+;}wlF~A46}E@w-usbLWb}e5=>BwGmtE4u zF3GoZ?~*QYNy)sVOI?!6OS;S@5zn5hev(THY!b~)w(qjHEilJEpn6MBWUc3SL*Bnr zwp+Fj^8S^3w;ho8%kx^7_bGWz=askZ%KH`WyBiZzp+=XK0v6aVya8?JTw+P8q(mUa zuuRp1DsyPuzpGt&yB?&zYx3%2vJyE2u%o#)FSi`x!%vnt6xTQV_xO*Zt`lD z_nGd!fNJl0NL7}m65y2FIe-G&CiSW-%A52U*u^B36iKd>JmKRbH-5^%1-to!8i*xi zC%QDR_^qzBvfs&SiAs#USGufTIIanvSGlzOY&T`IHRZb5CB;Z?fp?1}#TCyoOEJJg zEIxQx?O~S-_|dfNMPY%5iR*MsXG99hL#DjU2tpR4WL4PAKUsUThlRs-$foU3q7?Ap z5*>sJ5hNX`DhEKDgeaAtl1U^-rxW76E^4G7ZK4n=NQoR+FLawK7`j>gh-yXm|NDq~ z0p0HdkE#)<^*23=qUdLndQ8=D6y)H?RAoEcfsi_+Vz3yy1ui7>sNsyq)X)Qk)X3RY zmTGr!WIPR567O=B(sgpQwAHCML$5e(E0W;C`I}-=m_Rtl1 z2qYZh)@OlgSN=oZN(V=z<{!)-7GQ6utF#-TR#CPGQ82-Q%;I(YDRiG&Oc1H}ms3RP zk_oVaQt8ijV~~wzi?SD?xpgM{1!-?eb>*_~cBquQV5`pVLoOGI{Ta2uN@$U-m!~;A z^-c`p>2|9JcGcKDj1sa>W^0`yWjKMu_W?Vj#CO6?oFj)Ii0yuE#Zo+zy1{cnk(^i~ zJFWDS?$T4160-VsbJOGMos zQs-E*{{ZgBYX9&n^M@zY0GWnlZSv4uhozR@iJ`w;i-XV|Iz_Hzt$#?aR9;g_+1*hs zg?F-oWGs?@~ciULj?3wYYk? z!sdkcy*6g!_@L0tP<{(W=7_(vCZp=>l|)H=8{?VD>vBw#>s^PdET9}g%qJ*(2 zzgc=CXkF)p^K75u&z8nP^Zzdueu=!X%DgGE8636<$0##C-;OE#e`PM@5>T7lO>_Q6D^Q6#$m+RU;1{!O zIl^FhOvfiN0wETTNC;h^%vV_g;;4b0!E8QT=}(U2OsUhhu;YGzaEVuFOPg#p)NARD zkSNyzRm&BQyL@e;MonKWwXtMK+Hynr^;hP9$1guS8faUzeT~WEk1@%#6jIo8EWq^Q zx4zo`RQ=vp+p~V|s}qgms;SMAD%)$@zHN)I@pXr`)qG%5!nS1*Jq9powuQLw*|N4~ zTVGSY*?Kg?hzFE6i}9G6W^uM)L$>f__LAJRn4XJN2g@utdM46}qfB4BP0b;u6%vJ= zAnY%n3z2lea=~}`xXVtMWgJo!Ot+A^EGL!AjxZu2VsdT}b+C`{Be`Q-k#Xd;Ozx3< zLq=<&G&V!it7XixUC!%}B&FE&>4rR|>{BFMfe1tGtu*9kr6DvFvV^5OUz_gu5&@rF z#r8p#vwg_&;R1zwbgHwf*IvWDH+mCCv!evy7k)AY+rxnJx}U2yyX?BS9Pxn=agPp& zE3%nbUi-RJ;7|-D*;7%%ieE;Fn9}5wC8khZ3jinvcPq3pH)4f`B*D-$e_B=5Ra{u< z&rAEY`WP#CDG|J~d>FMj%5ZN!3;JvRH{Lc1WhFeG4-Vc%AmxC3s;c&<0Lm=S>=^&* zR30;J7C)^119uskyG@{ApSH7m;R zg`em9&)^9CfGJ$8&MJIRGb0J^ZM$T#I>OxftjdU&&N(lj#zehT@~Y}(cD{gaYk_J1 zBAoBbX0I33WP(Irc~OnwknZOHQC&H4cIbc9gm&}m@n~aFME;p@+3uhx!NB?E$^WSC z6|ZP~Me6IHPjK8*bWX;HX~qu_A<1EMRg$>^Inbzedyj63C>Z=T{!Cdz$L7NNA1uN9CX4ne& z`7P$46{@%QxFns;02)_dvTHDBtW>9YYt4q0>QHY(!+=#P@IN@u%l z5p}IeEl11roEf^DWirVewOk!#2EGQQ8_YqkQT%PQ=ryZ8)vxoj&K&f*n&Mq%-g=!u zG#I@`y~438tJe@3v(^;6q0Zx0uevwX__CFDuI=jOdT#Un8yJMwnjLSbgS|!Oz&A1H zZZLm)Q`Pgf=}mQ>4EBV#)b%w_W5pX&iq<}AwO=VB7Z2XKQHN71+oEx;Y(A!xdyYgue&?B=))7p!#PoYx-2)ijep}7q=il$B+5A+jXD%n3+V!eh(?Gb?oW5Rl z^qy_FW<7zt0PnkZ)%YUxt!N%SbcEyIQ-`*G0lQqaKVJTf4PO-dH2wL zQDq;h16zS>@j?!VWglKPXMD)qbN26tIC*VpP#+Rj>gX@_%+7=TN^|uV2HjvD z-C`{xZ_3*`^V1gSOoKVN5zny(Gqn*S`iOa>QFZPr>PvfZtRPP{7B_x+afnXeaE;)w zo?5tTUDF(pFv8Ih(2>?AVNRC%qE!WTUE>6 zUu9n*z(USMzLnOU!AmH!fYYW^ytjk;ZjC7t|=x}@D5lG%A?TIkQm zo{737Tbd!?5TLf!ocIk}!`tS*Z_t=*P?af28+6H!mY^M3E|%T3->@3qHWlBhe+qfL z@mow)lg)~6RhJ&`K*WSf7H6p>_u!mMshCW*$Rm>#-{Bwyf!pspimx{pf2aEMbNgo9 z(!A)Nw|}S3f__fiuKqUc-F*64lEIf^SNd5b^s^`}XRO8alSiSS#ot@{xySeF0oLT_ z-?KfeH|>8=H68H3U(Urbv1*Gj+X-edM)T~k$&&W_7!gQGk&nE zd*u(7_I~?=s;;7Q;d`834v;WeUCo}IP7V=%3R5HyPQo5;XsG>BUF!j-)jz4rB$8M_ z%2&>b+GtMPp^lKSULQ?{oVb<1Pq=ybzIHCP0G`!Y8h&8kli`Qv!yN?aPBKGLG`0K4 zqQ^%xMgS9&DXT%2+eUN7&+5){p~nTdw99oMxW(`bG4H-p_2~mK=ZH6PJhvaYj_Z`l z)?)gBK8ycRWe2|DLx~{)S0QuhPW3qFm9mzS=?;=IV=sMxH)+N+y(fRy=*pP%OMOE6 zm*bLAO1Z@^>sBW1>A7_HMNbcI`HAhYT%$sd3o`+*GiAPhl?Lz{QV(p`U~8M^*;N7b6MSa5@_BJmpCy0>QAk@x zTH1_KdVG(sprAmS)}NPRa4Srb3YXiOHhS>r9E9evFfK4pE8IZvT~WG@pJTN?hM)h@ zGGwzu>wWkc6Xy!ui}AY zmy}nad$e-sMwxw3!UAUZX>@_^Ni(Mu=q`X}T7f>b?j2g|WLu-w|0*&3TJp{xUKvKN zGs1AOFT|Ow%9B6gr`l$+yW#~D?l2+TBrU#YfIaeoLZGtDut!dB&o|s2MU)Mxo)+!w(2tkRtRg+Q&vnThS1!H# ztVugAL%7?06+Td2uQof2bmrhCLd0>|aXZg5I4aji4g^D20VVPPY+4r*r3Hp zM~QcW$fMX6`GfPWIIWystdB{qQ7LmqdtGie7VEAlx*aQ=w={$)eVGUSo>8Lr!qevM z68(LrZ5TCWm5WXkO}Sli;%`7tRk+P;E!Bey-V~-i{JNocnf|NieQRd-FKAo#9dOJF z$mmj^Y^Ja9-z+wxma5%MeW=fom&VSzx9QScr_0t9M~Mt7N}!I`-DKMMT{^G9y%!bD zFS!-;lTll_{^0uNdK`N9kDBXA{7h&;=SP?=Ep&U6xjblNYFp})Ano&8>bd@pzIm{v zf2!GMBa2U5^1YZJ1F(o|XB7;d&-f znpmU})~Up@P6*h)M5Uhvl}fS$khKE8%*huUOtM0sm;71B{MI5#y1hb6_=DxQ{0{nh zw$mFs=%e`A(m~JWE|RGo^^tN6jvd0w`&P_@c~2oV?h-O6{f5cgTKNI>7)k^_&y0Wz}k)Q?KBiKK%FB9pI*-^1(8 zEuHioIAEVSSdTRS>a5SYE>lhH+2{h0#Uk{k|$cMn(>P9x9*-)U>BN!Iw)L(xZy z-;WNbo98onu(#O!kkJ``>lD+mlD+@cDFOhQAN)tG(_SL#ZUt*JeZ(b5BEP_ zX`*U9%6rdTT&;Vi-;?D4zs!K6!u^o5woqS$(g=5QjD+jV@@ic{=1tXlP^%5aOxb7@ zF7g?i0EXcgrhkp@81l)c+L9%t9$TaTl@j0Knq(r=nho>A8hs#9CR2N`3teH3m!E~% zw`Y3j?j2?V8G&!SFhj9uMuO4=;Q`D(%L{8~W+v(=-2H{V(sY z|BipmMg8@l-g72r@tgi9M3s~q)7A9DjR~hD8B<5Ip+9T?Q&TWN4=J1_jW(yzF$46G z$V|5mV4H;z8KD10r)vg6PG2(*4b&NLx>-F?cdz;6^8BXQe^&}8DkK6=nMAae#$-W% zGudPY>D|J)tHD)J4EqHnqN=! z#fa$Wm*uxD+)tJ?tto&sZ5h%;nhqJN_b#8EZ9h$R_?M+#>I(C8Gkv@{eVEoJ90m+- zcNFimVNkr=r6+w_$D2jk_Na;uO`$_eP50q?ZzS{6hQrBiFxL-nqUYO&>)|JKwA>3@ zVkMn}9ENuVc5?xWbA#X5J?`f7Z)|@;(PaCB6=}xE!+Exra^}R0+C#T)-hxC-vXQ-t zBoW>nm`ts1S+c`6$_5es?3sVG^+%N?&%p`Vw^Eg7mbCTTc}eqjTfeKwH(on`NIPMD z!dK#QvX_U;z0{3C)`-CBMbgX$C~jZ3A8dytc~`Q)JXNi zM6YWTq#krh!<^&~PZ@V4r+2w+q^@dpqBVrt>R=B=nZuyWyCZcw?*jAPNL}LC$wtFHTr~m;?qJ?HJZCr-6z!Rr;gUA zj9BUL3b?pQ^NUvuX(yVNkv;?s)=7~b+27%Dyc#HPB0?RW#!Q{~B_sQcKxh-bdron< z%T;8XJV%VIX-tIh#bk!Ns7;GcgFrWx>IKxTG%6f%wZFe}N)#VSw~EPTF%rxKi+C*SKp=w1^JZIwC}NAwneB+U!%U)MTHY55%`B`zRv}UkJqC z!;W;Ag_Gdli-A+Ux<^^bQsdp$XTb@^=9V$KgPd4yg`e;qJ0~SFonhD~)kodnI{!6q;r)C`C=a8%4DB!D^`~9PAUI6 z4^PPhajY0M?%iKs2WzzZ0lH1y%K_(}TZRZrO+ThvEaCvqrVBV=B)kbCMkP2=5@v<# zTY^f(DRf}QuS*Ej@=Dy7R|2^^s`q#Ec4_2~v@Q_NEAya~QLYuyawL9Hgc5?Wxe`fw zmI#;AAz4Y*I|x3WA-rn(j)g^EVU8WE2kmYL#AGF5o?xfpy5cgSD}w|fWdt0O3x>=+ zku_XtR*ogm<1;j0nAbB3S)k%TKUlw<*;Xvzkiak>1Ca{9vL%|{zczEm>7^wv%Y<1o;9Ujg)I)S% zGxrePv*!oJvXqg+#PV2K7(M(tvKWt6&_&!3!GWRC@s!zdi0(6x0$_!W218~`>xts@ zCNX7#B!GZHK^Z?R_DB0xf+;UeIA5oK9I6jI?xW%osIVVZhHoWHn#0IZO}kLL-n0tZff3CJt%m)J1g% zmTDmY(m8^CyzmpV@i5&dB%{UrTxzHBx~8rvRjNcN?CX`IvvD{f&?2EVf_FXo{+&CM zXGZnglZc(*V?9blnizI&(9|)Nw*HLtEidNQCM`KZRqq% z<8?=Ipp+kN^x-a|%t43iPMsrAkb7a76I&C(9M)9;@lCkVb>`v2 zb#39#LB<208_f@g!+0Z)AEEbawbf&)P+C>Xr2D8x821zA!Xxy0Og6_HsVmd#tm^ZR z@?AqZfrW%;@CQyP;&@v#>qy-Vg}Iz35M<2?NRC3I=#J!g??^p9BQ3eM7Fzv=A1z{| z1al=~7W2z0bJ$UOAoA7aN9oc0EIUdM;%CQEbas-dJeoB%$sBUD&h!=9`fDZ9Y@u=e zvRiMgM4G8YV|27Wg;SBsj?o8q{@kkVV?C;b|4F1P>SkbPHQ5&&qq~*wl1X4HI_@Wq z(Ov1m`eSspy;Zw;3Ts6BKfo+(Fa!RePc6BU4ZMVL1Ht#reSgr0bI-T;M}03p_y18p z(NDVlYnI_A={8osm}ThdlwWs=7F`Be|uhT)UM7AvcOY5jhEd9=e1qz_E3MxGe z8PIN_8)nk+x-C$;?Redpt?8NL^(l2PfviFg$V69jyHL}ff&s5U$iy!)G;JVc2!wj< zk|7m-PSeGpGhZFTg$-MbQ-9u=3o4@Ewy)Voyf6)UN zauTBOOXj7Mko7m4O($6Z6#f-o0O==Yt2p!`_wC5P>V9o3aG(l))ZF#J@=q_B+y1JD zP|X{E#TfgNWmG(K++Q)VwEUYcryUu&|w_ji3J zJ|p8Mpz(RhY&??=JU9V0+-CFg1gd|@G)~Zii?;7vk_eXpOVjvImiP*D*hI7lH=2J; z)Ri5tBb`Hhgy_h9a~9mCvKfMHE6sw5djC>z$f{&zy=^h6Q_v)BHp5P_4IF!l9#ph2 zZa_%`?NfriZmU>fmYkw%Mp@f12#)2~k~nJ$maSi)jyGR6y_|R_QR|CQ3TDryM?YR` zs!!D&gU^&29W6HpoXP}FGZRkLL;1PmRFpE)&C91k1Fkda(-_`LGv+i)5pFq6cPo?$ z1pAX_)cFACJ(7TmK3$Js2%}F2dRxt-r|a<~7M!6oy9{-!8GMHBK|UrzN&)}jX2}`4 zh-W4tuAztNdnOjBZ)6fWnCs8fWBJTX45VQBS+?MAX91&S`IFE-CF?p1qg&6SnW<*k zS)lEW=0^&JZ~1t)pl^-jwB_ihD^35i!K?So-e>E9{^wVkf1j;Kwfa;@I#d9VWsj%e z{Fs$z>ozJK{dkr6>}*iJ-t<2Q7+q(MJ_p2DY5sAJKDaD@M!yNnYvPwy<|>cs?DY6T6!|;T-~*pbs$?7^JkXwt-LI% z6gL~`WM3{Gx8pZk{;4Z=8GPsSz{P7#be=x7;5uRM!8*X#-mE-N7wpnen*QiK+tKFd zXFDnjfY|E2fI zZ@rbd`CqzZm)5Ct!M}8ku#g-6r4LX2H|A24aH-+G1-;C~f9pfhwLSB1eO>_udnPbt zZe5pbYsO!oZ(?Wo@&b%Mlg;=G^-R%ceR-jlv#xtw1g=gt^%rS-yL~EkHdM4mq+L7{ z{n0}AJ6kRh<{NK20PMUZA%|>bJ8XvAJAQ<-WE`dBOl^h-9)NmN37cqJ!rV={;J*{8H1x|~E9 z;^-Rr#oukJ{HjzmU4E0P=z95uXsKxZXG!9jc^sdMv^ z%IucHWo`kTmU^yQ-R{{$ibxh-*_H|5Wn6;6u;1pji}fuX=TRRrjT@UkBEc3W^j)%C zg~n_|(AHl&)jV`W&cA1{W<(PX-LN`9m%$lSpS2lY7X!$40ZM00j zt!A%EJv3R(IK?MuugO|`@Xng7FTfDBWip$`$7aCguqO@X*vmD35$3;_!|H7{t*5Y9 zR-1#S=v}>wP)pdFu9-r%2D502J|%W58YI$5&aH^sE5gMlx`I*OYi3-bCw6?Qou6`E zF4J_@%Y{Q%Dh0W=nT}WDvT&>U`;}UpmTtO|#k|fexso0EW3%x}ELaVu#Z|g`fB1az zHvrq-iG5488*lH#er0~b1Ghx&k^5d<>Wo5Ut*I<3^Sy-VF``DcRFK_Vc9lM$&aMVm zUAi==^)qGJDtU?v*a^6eZ3z6YEUUffQbJlv?Q7eN6PlXZEG)&BGLvgJ*D5W}wi{Uj z&!~&eBvwUgsP=tj4!c_aq1FcHxHg8-1`U7>toe*wiz|wOc+CAu@k`7=1!SIkC;Ce# z88=Z#rjdF8)7WctXLH7DDln&CqxD{?gi})%lol41rPx0#^Tk0id4N2sl#rLEN{^OG ztqkZuHO)MHjsDjl#46fZhdqo~U_mcAY!#)a6%woc`P{H##jeUC869Ec_%fPfuhm^T zpna&n;LLQ6W92?9V*TIY3dE@oCo7`c>T(6@Rw_8Xz!hKSoOy8?!=Jomr?;Nw`dVOa% z=lO(-B)SRTo-FmKXl|la93ABx_IP;2EWAOV$9CUu2K>^AX1^Kw8s6TTq0g?nH0dYn z)UN*GauPc6@ep|#(%2kx0S=hLt7+{JGPtNE(h^0JDJYiIprrv6Il& zQJS=6ykW4>(bVw)ZQDHeC1k>NZ9V^;aVgWT=OG(fP`e)8Vqr1XL3*69r_K>)yyg5H z?Q%q;pMXOSg=112op$Nf%z>Ps#J66YxL4xU39wx4PuNXyY^n6fA77c*576(A^o&9@ zYJ0_sXM&L=#pwcuVW_YxX_ROv62g&>~5nWT3bAlk2FX(Yt02l z?~WqmNuvi<{MSw%P7bvdV%qQ{S<%Jr9LA!VxHrLQ-Y`@D8C?5rCj0Asrp--ApEsMa zH|aB|^68s!I{CzWc@r#IgXwp(9%59;)8?bwP^)I0P;%-5(p zaR9Rg`((>>yNfb$U5DSLTfo1be3#yXna7X3P4{L|Ghodriw9TWPm`LJ#NT??C>h|h z%bfdjQOpwOK35-WE}E;`jGPNH_D=MRN=2*`1p%0t6t$<-h}jcIkCt}kOn=X-qLc^{a8OZF@Gu^xXgJMY8R|AVQyUk~Hw&-Y{Uc*eYX zzdmW$_tx1vNS6t?3%U75v*v!UNR2#N76PmSCBE zAJo;+{)jas9zL7e|JO*?o znX@0$cV=8P495*dK30N{pphjp=|ahZ@b{G&xv?#Swt51;xNFVfPe3-;81n=Z@VVLWgl=!orvs*ve|MS@3xV{V=EQ}1kGhpfJR5AtLsOudCHiZv z$l{M3Q-`}(6{XS7|;ZlP{rB9SG8fY$g&xjld1v<(1v-sAVeMZyvZ zPb%C1+p3t{lIP%8KU6k&UZA@yt8t=dmRR&mXt$%J%RtlHM5YG2@Ie9>r-MO@|YVfRY7u28p~;oKWpFkDNHn1jKg zVB@Rk__mp!Ue#mRMr&7~M?TR!w1Q23o7uPmBDckKUx_|olDTUo%ElATTPv}KHcDV( zCsVNs3bNLWT&4Hz`I-gqk|eZ>=}n2Qg_9m}3bv6ba`>hnEy0yh%r(>2>x@|?gG=C<^y8DwoYlIr<~B~a6YhA|XcWR6+DOP9RHT5q>m_Dy^J?_?5Rf*n;XM1f zIq)?){<#ZAneLvSeGQfEGiK21(B^gK=GS%mF4RHT3HL!n-Z>d=xF&d{EK87hgdnBN zstvj_B{D79(#6>3)fyWKaqk+q{m;!CYtWEl09%9i(~<})D(`MU26*8 z(jA77!fJ|7X$OL!Q9&-GWOiWptZIJ=+%4rAVOvU~UcmSm%C>vMPRjFO6=IGbuHiMV zdg@#H4jVSe9b#ZVP>65>OouFiDv2~XV67#F|6HppxCSlLnu16&m2JtPB&Teh=Ki(% z@AR16Dw%F;Q!?wUG3&r}(B(1xOenk6^u?oOx6fO_f9NvVPnxY+;`@f zXWqfDeywS?ULTA<=yB_H$2ca6i@X_&V4^+k5>y;RhBw#iO9Ts#c~=kZB*b6rc4DpL z7{v*h=8hs*(9JXty^Af+ZINtj4iDdcSN9Voaa+UR zX4K`=-qT(E&DxB63RnHN-a|qDjH%gx_v$BR`Uc&h!XYUaiP^Z#c)OI#Luh5$2D@GU zv_VU3)V&*l%x1H8qqeb8Yno!CSalOJ?LcqYW+$1B@9WBP*SQ!?D=Him4tpPldy={4 zeci1}M$gKam?^Vo2f;!R3nrmW_96TP{Qc0p^*%P0j~c#xA4wS0E&NcQn2n7BbvbtW zWAo1s^}x;!c@f~G0t3$0x2A`f8eS0AxS5m)=M?${oDY!zc`8?q?pZu`7!jfZ}$|5_U3 zYa7x@df{W3+)3uEk6G40=#yU|>|LDCogWUR&1s+L$>ja!6A<=R+v#3z*Qw4F3T$@& zub#>GH~)(c^dqA;fv~rl-kU&JhS&^*ecvJM*Letgvmk7>S+I$5tTpd$(qr4LwpC9N zJDMe^SamG2Z7{W;;=#Y#to~GABshD>XZmW+b9(PHLQue(GJ@2zXbCFz5wsk+8HCwv z&e#lj#PGaXkItc9@^co-GiLnf(8rDD$Th#0Nh2_*%egW7vn;*W= z=X2v8N10~|*`2wK{a|+cmIt3@10Cf0Q#tZb+ZnBQWcDh}peO2;zI@)~))Dme*LoNGu^lqA*h?6-@d{FXo)e zF|c{wHRi8h>rU<7vDp?_e7fHc-{#dWU~2hV&$01!L4;BYeBvgJX6X<-I%FH+Tp&oX z#Vz*{zZrPHhH2Y~F~~(phn6qg1hrKl(O2K-2^F-5`i6EaHQE+WF7bHbRpz{J(U-q& z9{LvGHJA^-MPvPOL#yu)%Xfd-nrNJ6Tqu+1i%_RoeS^dhOBz}+{LC!fg1FsmpXeN9 zZt8ZBtkFEPopB?HZP%+{gCG8$`nQ-5zPE(+@E;JZKQ<5i0A=1{PCL%uuYJsb;MA6O z4M~Dj-_Kn80}DIO-Dq>C@^YKrKhoaE=8r$Z3v4k{e?&A)n?L--e12sv`AHv5;`*N~ zz5eqK#(IIdaR*AWPt21$5I`Et+8yxnUz;CypbcvvQ`!ZBtsPL`||< z=GIv@Tzcrz+cGi2D}Q3z?bL7gda3|%&Kk=THMr6sh*XL!#k%_l0ImOmwV*;f-(;O{~Pp!8fE1}}JC8YfWq!EIl z$mBl2l(Ruf2YfovGyVpKVnCzC#RZk%v$cis-WDN#dU4RIg>+@tQAm1A(=Qbir6@y@ z8bVlX|}`U`HXT9ZDP=YVcuvP|tWIXj$@PT&_c$kXn>i(QT=qT?f}_jW0Q5 zK=8K*R7hjtbp>X6QP8nNww3hn0g5|DwtM~lk7dGa z_p5(dAnHSP*Ksy@cHT$l8WI~@j1~@vZ7FP;7X_{R<|E3O|D}MGPlr#LuqbFVGQUul zcyi;BAh)}$`FZxtRm{N!Cl&?0s`9_K&DOxs&?|~w0yH|HV#Z&BUfzQNU>5&%FN5jpm&|C6yZshysZ?WlQQ816ey4e z+hM>*QuzeQ^^|P{-(=fDwxCGxpqs6^R8Ib2kP`A`yGT}P87GWzmCI6+%~J9Ec~WM& z3NrndyLQ4?r4lM;?@EXi8?K0JC3I+|t<^SOoH8RDy>4dFgQ{#O4gMzv50A@Y@bLJO z1CIdh<)+TP;yPDk4${GZo(xoE>F~LDz|Y4WdBGO&!xvrdLONw;W0W$D4=HU{>7cmH zlDK+WTG~|ov*vak3?KVDA6;$P30~wTSg4>UA}n+9Ocr_alFVO&;8tUUaW?Fs3py3Y zX2~Lsszsi)hwh0^$@*5WHhl|%Hg#g&5{e@8jEh1TLft`FSxOeOF%{64{0!@Ul>r9< zm%)Z&Gt4Ioz$tcs)Gw>Tg`g>?csh)w&9-3C@8n1PCBLA`+rMfRw}D;ig1R>^3|g04 z&z;};k`jIZCQz`?3@8i+*-B;TLU@uKvt`aL4Ek~Q(Y(T7@1iwgCy3G+&%EE=YiIT; z2|Ct2SpYmF0*IZ`(UGSlMW8EQbtD(EN=U)xCwVNH5ii`P7v@w*Rk&ITvTW_kB6$J7 znzFHUn_fiAEVY#h)sodCts&HHEeYB~WlC__;J}{lrNIG*u9ccxjuM)&gO#mVwr9r+ z#K*32U}3kIe7Zf)(RWMk3z?A|(KN@E5kmbAu7(4b-?)_S%4&Aud zL0tNxWs{aB!lzC9vY@K{)t6E*6&d+K&m*5_@f>dQ%~54R{{fS4se1kE|H7YDtXs&G*bns`aHupCRdbgS>)~+yj3ck3paZc5je5#WJ$cdemKh^g9 z@X-@F-g#e!jt!{a#7ZSDG=l5lwFLs#M5X)*KNN1;+E$X`NA5#f@j?D5+)(&tPXU!^ zsOa9+S?84LI2)j%j>Wi%#84yoaoGfGS)ibiGLTw0&27#3LhSOq(ii3C6iqo&S00MDtr15lS6gx^QuMp zpDjzB|M}rt_*|4NZPkK%<$u3nDc@V}@;(3avKRPVnEjkryP4f48039t*0c$35n1uf zw!w8=;Gx>#!TPD`-Yytb@VO`@z99CXUC_15cS)kdbSV5SLeL;!As@0CPkH7oK_-Ip>?I&NoIUSFu=RD z;qr=rlge*3(>n$?6~>jnW(IT$CitHhnA)DdZ;H&h zXq~CUWP*$QZ;MQu%3u_)M^*-+uBfjJTHzkWz0&DsaUJ#4QJ8LNyYXOUu$#S|g&;h+ z><7hUk=b4uh_8M3F2N!G_9FAIE`fO9&+ZcJwkI`#+TtvSg9Np3wKGuwZfF1o*CM>G z1w>pd7JgfVVjjKv9D2^0Ld7#ds_$48oJ4(>Rt3Y_Z7-6QVr%1X8YQ$vct*9RicV}Z z$*#fPWZu7PaNf~O6-%NTzHDoOxQ0XXgeFvA^V!K%Z`#Fw-963CaEd?e!J zK4wMhU{68*({5Lz%=Z^yxt!33q2H#=9{q#1W_9^gAJz z%cq0)3cUXZ8T{`G%s;DH;|t6!)qws}v!ps0EvvzoHB?X&q$|G@9k}Hf(SuC|^@P69 z^7w@+fTPhyvtJEWd}=1v1mjx7WkKZcWxEYG7noOTg5mV2phwW9FxUKm9zmau^vF5^ z3->q#;!3SBBSRBtV7bHc!XCk1B~A57O+A9$sHv!DP*s#2n`h3i3A&i0dj_R$gj1$5 z!Xid^cFzDqUxB%$Co(|2Su8)Fnh$#hdrGZbd(QmOOEaKXP}M3X6`XhzZii}s9Xyzc zy@GVz)p*QuBqUdzdSakFpFZPGX+cme`fDA&CEMQhl8turvUxUJF2}52=fH!KigG#T zN{+%^>PYUDpM@#RhgJ2^1Q#dBaTYUM8M7P2lWp2*e5q^ zSv9apRF&~%myywan;o`&5c!0#jUVF)A4&Dhw{7Kn^Lp=~H=y+T1c&C3<(NKPCMHm1 z?tUyVm-Y#&2E>!cjns+o4$iEk5;+tjS|z74I8cevlHG5eqyS^Hu1_$T%WsbF8+4$B z3;PDUccvC0=_0E@vtSzRUjK{`a34^a-TEUZ#u&c{riI9@Rc2=YpufY1c&}Pz-t6x% zenBTVuv8d54KVK%4l?>*}Z?z&TQ-Z8_R20 zzu#S6(wM(}s!0#9YlNmow`*$Udue5Jt`+lSYro3hSztyF40_dNF0AzCF`30gITyu_ zjRl2vZ%0D<*-{tOF-QzyeWB1E5u6t00-i6nY2h=ugdy2H&*hQ`2(^jRpqD>kj+Yw# z6Qpc3ak*Xfq)qW%IPrrp;^vV1RXfvmP|y``()bKS%KYyK2<)A0xaq`!7jj)#>cYaQ zWL7S1lMTxbn~o~jJ2f!jkCuNHY0Huc&)h#CXy;vGUK|kgMC1F_fS{WVh5a8n$j&-e ztV)1uuslhqJt$`Rh-9spKts9x38U$V#9s1sh(CxQyL6J#wECqyjdTfeQU`BUtk5J2 zVu(M?K2`CAQI<4}l+Kzf$7Iz_2q#HwA{I_rrInU#A{PoWaZF~e(wzQ*e20UB=yPNv zba}+Q4vsxulnihi_AY=Wj(hJ$o&78B@CjJGGVMf4)&vbnTl|*SMGwHKM)Ub28NJFx z1*&tV6&jJ_+S?=SxatIy93MInPfpR7+Ou_%;;UxmPnhj&-Q8izlTEFRUyS?pj#z!i?<1u;36Kgd1VO3)A*%ePxlU zYnNOF`0{LslqYbMHwviFb+lYsNC_}LZ0Eb(QgaXh2NIH@bACo zx8ZsZs-u^AMShV>*a-P5$5c{bmf8VXz; z7Xf^mC+iY2N$EsSV37?MYOxOf8{I3`Psy}rs4l9i3O|&Ssz_al4;#gS*Bn2b* z!y!gCeoG!f1W6WBA0sBO$i zDUaq`=@9|7E>|&~V$yQG{8G+<5+#5P5tN6YvS8rI3XU)mia^jcBqbfw5=ThrRbn5B zZx-S~28fMhkN|alATi?oRLyXc!j>{E(2;G_&H+rE?*<6$0e<2^US#5PHFulzTqO;4~qRp=ur|~^9h54RybvyH#oQv-EG+rGIt#bf3n%EIWp+8JK=aXP{)h7wPqX_P)1I?1Z5(Kx|d^S9g*v4=~P~%_rtPQrQ`qYav%7Nlmry?7O^WE)X?>vm| zH5ZNu1~$tG3G$=Z>1VxJJR%rXNAW~h!T*cd3bJ=a&l54di%fisMyX-0>YU3i_*+7| z_lvrzIC?^UWYq!ZMLeTc%EDQql?4I0^UkxO%kE`HVd^~(Tfp^~4IEW>_01G7amAOw z#EbGtbiJ}zq?MWSoh##|`ulm7?_y15eP#7tizEw4l4V((MKtcRyw}P%i}mqn1ju=X zTSgy9j?(-Nn8QZ~$JTAUn{=_^sR>cGW6(p}F;V+|D&J*GD4Y;A^4!$%T*vC?*~V?h zvQ>yxKst7%e6yE~XFHbdvXL*%s`6$aMvU_4Zu!)%>5DmHcY4||n^ZO$8UF&~dLoAz z|Rvg6gvb-W$VlCiynMVLEp&ofzUXv{GRIiD%6rtqG&$CwYP|UV8 z(^Vre7*r#tILMTKC|TnEZsJ*5f*a1yvOTYs%jUQ&a2@##-9WO1e;s359*@aQP;LTd zN}hO3o8l}ExpG-%XRD!>`nbQpz^^+cOH4^-#aWoFY-fhcm>!Tkf-O|DFwV0prbSz4 zG-Y}ICK;2NFe<2Ok)Qq(c}Sl#Dj3jlS=MID6}8#N+^RHNMg?73zU1(a_Lm?^g$sR7 z4eZ6EVik!^N{4ElS z`l~9RH1*?x_M=$Z@s3#@{r}QbJ47=5#s!(a0N@;rArx zFa-mPDjjK?zsB4iAslouPennWmfYAWbA-2G6la)beu_}lSK03);~PDxyo5$&L9dbe z%c2kDhy7C4{U&+d?h@=m|A}X8p%>Q%od$Jv?GBm{#ZNR9Ke4q7Nn6e=NZt14Svjn= zt9iXPs3iio^`5~V@tviRWgMPu{%+>4dj_3)+z@viDM99UJ~FRuDwj=876toIES

9actH_Xmj0!NJote!n^V)!bM5kDM}J|^UJ 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 J%Mbtf{{c7cVaEUf diff --git a/configs/swarm/genesis.json b/configs/swarm/genesis.json index 5a3633d398d..b1d6e12d309 100644 --- a/configs/swarm/genesis.json +++ b/configs/swarm/genesis.json @@ -115,68 +115,26 @@ "Grant": { "Permission": { "object": { - "definition_id": "CanSetParameters", + "id": "CanSetParameters", "payload": null }, "destination_id": "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" } } }, - { - "NewParameter": "?MaxTransactionsInBlock=512" - }, - { - "NewParameter": "?BlockTime=2000" - }, - { - "NewParameter": "?CommitTimeLimit=4000" - }, - { - "NewParameter": "?TransactionLimits=4096,4194304_TL" - }, - { - "NewParameter": "?WSVDomainMetadataLimits=1048576,4096_ML" - }, - { - "NewParameter": "?WSVAssetDefinitionMetadataLimits=1048576,4096_ML" - }, - { - "NewParameter": "?WSVAccountMetadataLimits=1048576,4096_ML" - }, - { - "NewParameter": "?WSVAssetMetadataLimits=1048576,4096_ML" - }, - { - "NewParameter": "?WSVTriggerMetadataLimits=1048576,4096_ML" - }, - { - "NewParameter": "?WSVIdentLengthLimits=1,128_LL" - }, - { - "NewParameter": "?ExecutorFuelLimit=55000000" - }, - { - "NewParameter": "?ExecutorMaxMemory=524288000" - }, - { - "NewParameter": "?WASMFuelLimit=55000000" - }, - { - "NewParameter": "?WASMMaxMemory=524288000" - }, { "Register": { "Role": { "id": "ALICE_METADATA_ACCESS", "permissions": [ { - "definition_id": "CanRemoveKeyValueInAccount", + "id": "CanRemoveKeyValueInAccount", "payload": { "account_id": "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" } }, { - "definition_id": "CanSetKeyValueInAccount", + "id": "CanSetKeyValueInAccount", "payload": { "account_id": "ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" } diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 0604680ba00..2d2667c259f 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -245,15 +245,15 @@ pub mod isi { ) -> Result<(), Error> { let account_id = self.destination_id; let permission = self.object; - let permission_id = permission.definition_id.clone(); + let permission_id = permission.id.clone(); // Check if account exists state_transaction.world.account_mut(&account_id)?; if !state_transaction .world - .permission_schema - .token_ids + .executor_data_model + .permissions .contains(&permission_id) { return Err(FindError::Permission(permission_id).into()); @@ -265,7 +265,7 @@ pub mod isi { { return Err(RepetitionError { instruction_type: InstructionType::Grant, - id: permission.definition_id.into(), + id: permission_id.into(), } .into()); } @@ -304,7 +304,7 @@ pub mod isi { .world .remove_account_permission(&account_id, &permission) { - return Err(FindError::Permission(permission.definition_id).into()); + return Err(FindError::Permission(permission.id).into()); } state_transaction @@ -312,7 +312,7 @@ pub mod isi { .emit_events(Some(AccountEvent::PermissionRemoved( AccountPermissionChanged { account_id, - permission_id: permission.definition_id, + permission_id: permission.id, }, ))); @@ -338,7 +338,7 @@ pub mod isi { .clone() .permissions .into_iter() - .map(|token| token.definition_id); + .map(|token| token.id); state_transaction.world.account(&account_id)?; @@ -397,7 +397,7 @@ pub mod isi { .clone() .permissions .into_iter() - .map(|token| token.definition_id); + .map(|token| token.id); if state_transaction .world diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index 7eecbd9b406..e1c2bf27848 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -55,7 +55,6 @@ impl Execute for InstructionBox { Self::Revoke(isi) => isi.execute(authority, state_transaction), Self::ExecuteTrigger(isi) => isi.execute(authority, state_transaction), Self::SetParameter(isi) => isi.execute(authority, state_transaction), - Self::NewParameter(isi) => isi.execute(authority, state_transaction), Self::Upgrade(isi) => isi.execute(authority, state_transaction), Self::Log(isi) => isi.execute(authority, state_transaction), } diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 4bb37a6e8d0..2de8aec3d7b 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -162,8 +162,8 @@ impl_lazy! { iroha_data_model::block::BlockHeader, iroha_data_model::metadata::MetadataValueBox, iroha_data_model::query::TransactionQueryOutput, - iroha_data_model::permission::PermissionSchema, iroha_data_model::trigger::Trigger, + iroha_data_model::executor::ExecutorDataModel } /// Query Request statefully validated on the Iroha node side. @@ -259,7 +259,7 @@ impl ValidQuery for QueryBox { FindAccountKeyValueByIdAndKey, FindAssetDefinitionKeyValueByIdAndKey, FindTriggerKeyValueByIdAndKey, - FindPermissionSchema, + FindExecutorDataModel, } FindAllAccounts, diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index ec89111cf2c..fc1b00484a6 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -17,8 +17,9 @@ impl Registrable for NewRole { /// Iroha Special Instructions that have `World` as their target. pub mod isi { + use std::collections::BTreeSet; + use eyre::Result; - use indexmap::IndexSet; use iroha_data_model::{ isi::error::{InstructionExecutionError, InvalidParameterError, RepetitionError}, prelude::*, @@ -162,11 +163,11 @@ pub mod isi { for permission in &role.permissions { if !state_transaction .world - .permission_schema - .token_ids - .contains(&permission.definition_id) + .executor_data_model + .permissions + .contains(&permission.id) { - return Err(FindError::Permission(permission.definition_id.clone()).into()); + return Err(FindError::Permission(permission.id.clone()).into()); } } @@ -227,15 +228,14 @@ pub mod isi { ) -> Result<(), Error> { let role_id = self.destination_id; let permission = self.object; - let permission_id = permission.definition_id.clone(); if !state_transaction .world - .permission_schema - .token_ids - .contains(&permission_id) + .executor_data_model + .permissions + .contains(&permission.id) { - return Err(FindError::Permission(permission_id).into()); + return Err(FindError::Permission(permission.id).into()); } let Some(role) = state_transaction.world.roles.get_mut(&role_id) else { @@ -245,7 +245,7 @@ pub mod isi { if !role.permissions.insert(permission.clone()) { return Err(RepetitionError { instruction_type: InstructionType::Grant, - id: permission.definition_id.into(), + id: permission.id.into(), } .into()); } @@ -254,7 +254,7 @@ pub mod isi { .world .emit_events(Some(RoleEvent::PermissionAdded(RolePermissionChanged { role_id, - permission_id, + permission_id: permission.id, }))); Ok(()) @@ -270,21 +270,20 @@ pub mod isi { ) -> Result<(), Error> { let role_id = self.destination_id; let permission = self.object; - let permission_id = permission.definition_id.clone(); let Some(role) = state_transaction.world.roles.get_mut(&role_id) else { return Err(FindError::Role(role_id).into()); }; if !role.permissions.remove(&permission) { - return Err(FindError::Permission(permission_id).into()); + return Err(FindError::Permission(permission.id).into()); } state_transaction .world .emit_events(Some(RoleEvent::PermissionRemoved(RolePermissionChanged { role_id, - permission_id, + permission_id: permission.id, }))); Ok(()) @@ -299,41 +298,14 @@ pub mod isi { state_transaction: &mut StateTransaction<'_, '_>, ) -> Result<(), Error> { let parameter = self.parameter; - let parameter_id = parameter.id.clone(); - let world = &mut state_transaction.world; - if !world.parameters.remove(¶meter) { - return Err(FindError::Parameter(parameter_id).into()); - } - - world.parameters.insert(parameter); - - world.emit_events(Some(ConfigurationEvent::Changed(parameter_id))); - - Ok(()) - } - } - - impl Execute for NewParameter { - #[metrics(+"new_parameter")] - fn execute( - self, - _authority: &AccountId, - state_transaction: &mut StateTransaction<'_, '_>, - ) -> Result<(), Error> { - let parameter = self.parameter; - let parameter_id = parameter.id.clone(); - let world = &mut state_transaction.world; - if !world.parameters.insert(parameter) { - return Err(RepetitionError { - instruction_type: InstructionType::NewParameter, - id: IdBox::ParameterId(parameter_id), - } - .into()); + if !world.executor_data_model.parameters.contains(¶meter.id) { + return Err(FindError::Parameter(parameter.id).into()); } - world.emit_events(Some(ConfigurationEvent::Created(parameter_id))); + let _maybe_previous = world.parameters.replace(parameter.clone()); + world.emit_events(Some(ConfigurationEvent::Changed(parameter))); Ok(()) } @@ -348,7 +320,11 @@ pub mod isi { ) -> Result<(), Error> { let raw_executor = self.executor; - let permissions_before = state_transaction.world.permission_schema.token_ids.clone(); + let permissions_before = state_transaction + .world + .executor_data_model + .permissions + .clone(); // Cloning executor to avoid multiple mutable borrows of `state_transaction`. // Also it's a cheap operation. @@ -365,10 +341,13 @@ pub mod isi { *state_transaction.world.executor.get_mut() = upgraded_executor; revoke_removed_permissions(authority, state_transaction, permissions_before)?; + prune_unknown_parameters(state_transaction); state_transaction .world - .emit_events(std::iter::once(ExecutorEvent::Upgraded)); + .emit_events(std::iter::once(ExecutorEvent::Upgraded(ExecutorUpgrade { + new_data_model: state_transaction.world.executor_data_model.clone(), + }))); Ok(()) } @@ -377,18 +356,14 @@ pub mod isi { fn revoke_removed_permissions( authority: &AccountId, state_transaction: &mut StateTransaction, - permissions_before: Vec, + permissions_before: BTreeSet, ) -> Result<(), Error> { let world = state_transaction.world(); - let permissions_after = world - .permission_schema() - .token_ids - .iter() - .collect::>(); + let permissions_after = &world.executor_data_model().permissions; let permissions_removed = permissions_before .into_iter() .filter(|permission| !permissions_after.contains(permission)) - .collect::>(); + .collect::>(); if permissions_removed.is_empty() { return Ok(()); } @@ -407,9 +382,23 @@ pub mod isi { Ok(()) } + fn prune_unknown_parameters(state_transaction: &mut StateTransaction) { + state_transaction + .world + .parameters + .get_mut() + .retain(|parameter| { + state_transaction + .world + .executor_data_model + .parameters + .contains(¶meter.id) + }); + } + fn find_related_accounts( world: &impl WorldReadOnly, - permissions: &IndexSet, + permissions: &BTreeSet, ) -> Vec<(AccountId, Permission)> { world .account_permissions() @@ -417,7 +406,7 @@ pub mod isi { .flat_map(|(account_id, account_permissions)| { account_permissions .iter() - .filter(|permission| permissions.contains(&permission.definition_id)) + .filter(|permission| permissions.contains(&permission.id)) .map(|permission| (account_id.clone(), permission.clone())) }) .collect() @@ -425,7 +414,7 @@ pub mod isi { fn find_related_roles( world: &impl WorldReadOnly, - permissions: &IndexSet, + permissions: &BTreeSet, ) -> Vec<(RoleId, Permission)> { world .roles() @@ -433,7 +422,7 @@ pub mod isi { .flat_map(|(role_id, role)| { role.permissions .iter() - .filter(|permission| permissions.contains(&permission.definition_id)) + .filter(|permission| permissions.contains(&permission.id)) .map(|permission| (role_id.clone(), permission.clone())) }) .collect() @@ -464,9 +453,9 @@ pub mod isi { pub mod query { use eyre::Result; use iroha_data_model::{ + executor::ExecutorDataModel, parameter::Parameter, peer::Peer, - permission::PermissionSchema, prelude::*, query::error::{FindError, QueryExecutionFail as Error}, role::{Role, RoleId}, @@ -534,10 +523,10 @@ pub mod query { } } - impl ValidQuery for FindPermissionSchema { - #[metrics("find_permission_schema")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - Ok(state_ro.world().permission_schema().clone()) + impl ValidQuery for FindExecutorDataModel { + #[metrics("find_executor_data_model")] + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + Ok(state_ro.world().executor_data_model().clone()) } } diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index 955134976da..4663558f009 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -5,13 +5,12 @@ use std::borrow::Borrow; use error::*; -use import::traits::{ExecuteOperations as _, GetExecutorPayloads as _, SetPermissionSchema as _}; +use import::traits::{ExecuteOperations as _, GetExecutorPayloads as _, SetDataModel as _}; use iroha_config::parameters::actual::WasmRuntime as Config; use iroha_data_model::{ account::AccountId, - executor::{self, MigrationResult}, + executor::{self, ExecutorDataModel, MigrationResult}, isi::InstructionBox, - permission::PermissionSchema, prelude::*, query::{QueryBox, QueryId, QueryOutputBox, QueryRequest, SmartContractQuery}, smart_contract::payloads::{self, Validate}, @@ -47,7 +46,7 @@ mod export { pub const GET_VALIDATE_TRANSACTION_PAYLOAD: &str = "get_validate_transaction_payload"; pub const GET_VALIDATE_INSTRUCTION_PAYLOAD: &str = "get_validate_instruction_payload"; pub const GET_VALIDATE_QUERY_PAYLOAD: &str = "get_validate_query_payload"; - pub const SET_PERMISSION_SCHEMA: &str = "set_permission_schema"; + pub const SET_DATA_MODEL: &str = "set_data_model"; pub const DBG: &str = "dbg"; pub const LOG: &str = "log"; @@ -102,9 +101,9 @@ mod import { fn get_validate_query_payload(state: &S) -> Validate; } - pub trait SetPermissionSchema { + pub trait SetDataModel { #[codec::wrap_trait_fn] - fn set_permission_schema(schema: PermissionSchema, state: &mut S); + fn set_data_model(data_model: ExecutorDataModel, state: &mut S); } } } @@ -1076,23 +1075,23 @@ where } } -/// Marker trait to auto-implement [`import_traits::SetPermissionSchema`] for a concrete [`Runtime`]. +/// Marker trait to auto-implement [`import_traits::SetExecutorDataModel`] for a concrete [`Runtime`]. /// /// Useful because *Executor* exposes more entrypoints than just `migrate()` which is the /// only entrypoint allowed to execute operations on permission tokens. -trait FakeSetPermissionSchema { +trait FakeSetExecutorDataModel { /// Entrypoint function name for panic message const ENTRYPOINT_FN_NAME: &'static str; } -impl import::traits::SetPermissionSchema for R +impl import::traits::SetDataModel for R where - R: FakeSetPermissionSchema, + R: FakeSetExecutorDataModel, { #[codec::wrap] - fn set_permission_schema(_schema: PermissionSchema, _state: &mut S) { + fn set_data_model(_model: ExecutorDataModel, _state: &mut S) { panic!( - "Executor `{}()` entrypoint should not set permission token schema", + "Executor `{}()` entrypoint should not set data model", Self::ENTRYPOINT_FN_NAME ) } @@ -1172,7 +1171,7 @@ impl<'wrld, 'block, 'state> } } -impl<'wrld> FakeSetPermissionSchema> +impl<'wrld> FakeSetExecutorDataModel> for Runtime> { const ENTRYPOINT_FN_NAME: &'static str = "validate_transaction"; @@ -1252,7 +1251,7 @@ impl<'wrld, 'block, 'state> } } -impl<'wrld> FakeSetPermissionSchema> +impl<'wrld> FakeSetExecutorDataModel> for Runtime> { const ENTRYPOINT_FN_NAME: &'static str = "validate_instruction"; @@ -1348,7 +1347,7 @@ impl<'wrld, S: StateReadOnly> } } -impl<'wrld, S: StateReadOnly> FakeSetPermissionSchema> +impl<'wrld, S: StateReadOnly> FakeSetExecutorDataModel> for Runtime> { const ENTRYPOINT_FN_NAME: &'static str = "validate_query"; @@ -1447,17 +1446,17 @@ impl<'wrld, 'block, 'state> } impl<'wrld, 'block, 'state> - import::traits::SetPermissionSchema> + import::traits::SetDataModel> for Runtime> { #[codec::wrap] - fn set_permission_schema( - schema: PermissionSchema, + fn set_data_model( + data_model: ExecutorDataModel, state: &mut state::executor::Migrate<'wrld, 'block, 'state>, ) { - debug!(%schema, "Setting permission token schema"); + debug!(%data_model, "Setting executor data model"); - state.state.0.world.set_permission_schema(schema) + state.state.0.world.set_executor_data_model(data_model) } } @@ -1602,7 +1601,7 @@ impl<'wrld, 'block, 'state> export::GET_VALIDATE_TRANSACTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) @@ -1630,7 +1629,7 @@ impl<'wrld, 'block, 'state> export::GET_VALIDATE_TRANSACTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) @@ -1655,7 +1654,7 @@ impl<'wrld, S: StateReadOnly> RuntimeBuilder |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) @@ -1663,6 +1662,7 @@ impl<'wrld, S: StateReadOnly> RuntimeBuilder RuntimeBuilder> { + // FIXME: is this accurate doc? /// Builds the [`Runtime`] to execute `permissions()` entrypoint of *Executor* /// /// # Errors @@ -1679,7 +1679,7 @@ impl<'wrld, 'block, 'state> RuntimeBuilder |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) diff --git a/core/src/state.rs b/core/src/state.rs index 49c2a12fa4d..0af2d8abb32 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -1,5 +1,5 @@ //! This module provides the [`State`] — an in-memory representation of the current blockchain state. -use std::{borrow::Borrow, collections::BTreeSet, marker::PhantomData, sync::Arc, time::Duration}; +use std::{collections::BTreeSet, marker::PhantomData, sync::Arc, time::Duration}; use eyre::Result; use iroha_config::parameters::actual::ChainWide as Config; @@ -13,9 +13,10 @@ use iroha_data_model::{ trigger_completed::{TriggerCompletedEvent, TriggerCompletedOutcome}, EventBox, }, + executor::ExecutorDataModel, isi::error::{InstructionExecutionError as Error, MathError}, - parameter::{Parameter, ParameterValueBox}, - permission::{PermissionSchema, Permissions}, + parameter::Parameter, + permission::Permissions, prelude::*, query::error::{FindError, QueryExecutionFail}, role::RoleId, @@ -61,7 +62,7 @@ use crate::{ /// For example registration of domain, will have this as an ISI target. #[derive(Default, Serialize)] pub struct World { - /// Iroha config parameters. + /// Executor config parameters. pub(crate) parameters: Cell, /// Identifications of discovered trusted peers. pub(crate) trusted_peers_ids: Cell, @@ -73,12 +74,12 @@ pub struct World { pub(crate) account_permissions: Storage, /// Roles of an account. pub(crate) account_roles: Storage, - /// Registered permission token ids. - pub(crate) permission_schema: Cell, /// Triggers pub(crate) triggers: TriggerSet, /// Runtime Executor pub(crate) executor: Cell, + /// Executor-defined data model + pub(crate) executor_data_model: Cell, } /// Struct for block's aggregated changes @@ -95,12 +96,12 @@ pub struct WorldBlock<'world> { pub(crate) account_permissions: StorageBlock<'world, AccountId, Permissions>, /// Roles of an account. pub(crate) account_roles: StorageBlock<'world, RoleIdWithOwner, ()>, - /// Registered permission token ids. - pub(crate) permission_schema: CellBlock<'world, PermissionSchema>, /// Triggers pub(crate) triggers: TriggerSetBlock<'world>, /// Runtime Executor pub(crate) executor: CellBlock<'world, Executor>, + /// Executor-defined data model + pub(crate) executor_data_model: CellBlock<'world, ExecutorDataModel>, /// Events produced during execution of block events_buffer: Vec, } @@ -119,12 +120,12 @@ pub struct WorldTransaction<'block, 'world> { pub(crate) account_permissions: StorageTransaction<'block, 'world, AccountId, Permissions>, /// Roles of an account. pub(crate) account_roles: StorageTransaction<'block, 'world, RoleIdWithOwner, ()>, - /// Registered permission token ids. - pub(crate) permission_schema: CellTransaction<'block, 'world, PermissionSchema>, /// Triggers pub(crate) triggers: TriggerSetTransaction<'block, 'world>, /// Runtime Executor pub(crate) executor: CellTransaction<'block, 'world, Executor>, + /// Executor-defined data model + pub(crate) executor_data_model: CellTransaction<'block, 'world, ExecutorDataModel>, /// Events produced during execution of a transaction events_buffer: TransactionEventBuffer<'block>, } @@ -151,12 +152,12 @@ pub struct WorldView<'world> { pub(crate) account_permissions: StorageView<'world, AccountId, Permissions>, /// Roles of an account. pub(crate) account_roles: StorageView<'world, RoleIdWithOwner, ()>, - /// Registered permission token ids. - pub(crate) permission_schema: CellView<'world, PermissionSchema>, /// Triggers pub(crate) triggers: TriggerSetView<'world>, /// Runtime Executor pub(crate) executor: CellView<'world, Executor>, + /// Executor-defined data model + pub(crate) executor_data_model: CellView<'world, ExecutorDataModel>, } /// Current state of the blockchain @@ -284,9 +285,9 @@ impl World { roles: self.roles.block(), account_permissions: self.account_permissions.block(), account_roles: self.account_roles.block(), - permission_schema: self.permission_schema.block(), triggers: self.triggers.block(), executor: self.executor.block(), + executor_data_model: self.executor_data_model.block(), events_buffer: Vec::new(), } } @@ -300,9 +301,9 @@ impl World { roles: self.roles.block_and_revert(), account_permissions: self.account_permissions.block_and_revert(), account_roles: self.account_roles.block_and_revert(), - permission_schema: self.permission_schema.block_and_revert(), triggers: self.triggers.block_and_revert(), executor: self.executor.block_and_revert(), + executor_data_model: self.executor_data_model.block_and_revert(), events_buffer: Vec::new(), } } @@ -316,9 +317,9 @@ impl World { roles: self.roles.view(), account_permissions: self.account_permissions.view(), account_roles: self.account_roles.view(), - permission_schema: self.permission_schema.view(), triggers: self.triggers.view(), executor: self.executor.view(), + executor_data_model: self.executor_data_model.view(), } } } @@ -332,9 +333,9 @@ pub trait WorldReadOnly { fn roles(&self) -> &impl StorageReadOnly; fn account_permissions(&self) -> &impl StorageReadOnly; fn account_roles(&self) -> &impl StorageReadOnly; - fn permission_schema(&self) -> &PermissionSchema; fn triggers(&self) -> &impl TriggerSetReadOnly; fn executor(&self) -> &Executor; + fn executor_data_model(&self) -> &ExecutorDataModel; // Domain-related methods @@ -535,21 +536,6 @@ pub trait WorldReadOnly { self.parameters().iter() } - /// Query parameter and convert it to a proper type - fn query_param, P: core::hash::Hash + Eq + ?Sized>( - &self, - param: &P, - ) -> Option - where - Parameter: Borrow

, - { - Parameters::get(self.parameters(), param) - .as_ref() - .map(|param| ¶m.val) - .cloned() - .and_then(|param_val| param_val.try_into().ok()) - } - /// Returns reference for trusted peer ids #[inline] fn peers_ids(&self) -> &PeersIds { @@ -578,15 +564,15 @@ macro_rules! impl_world_ro { fn account_roles(&self) -> &impl StorageReadOnly { &self.account_roles } - fn permission_schema(&self) -> &PermissionSchema { - &self.permission_schema - } fn triggers(&self) -> &impl TriggerSetReadOnly { &self.triggers } fn executor(&self) -> &Executor { &self.executor } + fn executor_data_model(&self) -> &ExecutorDataModel { + &self.executor_data_model + } } )*}; } @@ -605,9 +591,9 @@ impl<'world> WorldBlock<'world> { roles: self.roles.transaction(), account_permissions: self.account_permissions.transaction(), account_roles: self.account_roles.transaction(), - permission_schema: self.permission_schema.transaction(), triggers: self.triggers.transaction(), executor: self.executor.transaction(), + executor_data_model: self.executor_data_model.transaction(), events_buffer: TransactionEventBuffer { events_buffer: &mut self.events_buffer, events_created_in_transaction: 0, @@ -619,8 +605,8 @@ impl<'world> WorldBlock<'world> { pub fn commit(self) { // IMPORTANT!!! Commit fields in reverse order, this way consistent results are insured self.executor.commit(); + self.executor_data_model.commit(); self.triggers.commit(); - self.permission_schema.commit(); self.account_roles.commit(); self.account_permissions.commit(); self.roles.commit(); @@ -634,8 +620,8 @@ impl WorldTransaction<'_, '_> { /// Apply transaction's changes pub fn apply(mut self) { self.executor.apply(); + self.executor_data_model.apply(); self.triggers.apply(); - self.permission_schema.apply(); self.account_roles.apply(); self.account_permissions.apply(); self.roles.apply(); @@ -835,18 +821,9 @@ impl WorldTransaction<'_, '_> { Ok(()) } - /// Set new permission token schema. - /// - /// Produces [`PermissionSchemaUpdateEvent`]. - pub fn set_permission_schema(&mut self, schema: PermissionSchema) { - let old_schema: PermissionSchema = - std::mem::replace(&mut self.permission_schema, schema.clone()); - self.emit_events(std::iter::once(DataEvent::Permission( - PermissionSchemaUpdateEvent { - old_schema, - new_schema: schema, - }, - ))) + /// Set executor data model. + pub fn set_executor_data_model(&mut self, executor_data_model: ExecutorDataModel) { + *self.executor_data_model.get_mut() = executor_data_model; } /// Execute trigger with `trigger_id` as id and `authority` as owner @@ -1247,7 +1224,8 @@ impl<'state> StateBlock<'state> { self.block_hashes.push(block_hash); - self.apply_parameters(); + // TODO: apply "core" chain-wide parameters here + self.world.events_buffer.push( BlockEvent { header: block.as_ref().header().clone(), @@ -1331,32 +1309,6 @@ impl<'state> StateBlock<'state> { errors.is_empty().then_some(()).ok_or(errors) } - - fn apply_parameters(&mut self) { - use iroha_data_model::parameter::default::*; - - macro_rules! update_params { - ($($param:expr => $config:expr),+ $(,)?) => { - $(if let Some(param) = self.world.query_param($param) { - $config = param; - })+ - }; - } - - update_params! { - WSV_DOMAIN_METADATA_LIMITS => self.config.domain_metadata_limits, - WSV_ASSET_DEFINITION_METADATA_LIMITS => self.config.asset_definition_metadata_limits, - WSV_ACCOUNT_METADATA_LIMITS => self.config.account_metadata_limits, - WSV_ASSET_METADATA_LIMITS => self.config.asset_metadata_limits, - WSV_TRIGGER_METADATA_LIMITS => self.config.trigger_metadata_limits, - WSV_IDENT_LENGTH_LIMITS => self.config.ident_length_limits, - EXECUTOR_FUEL_LIMIT => self.config.executor_runtime.fuel_limit, - EXECUTOR_MAX_MEMORY => self.config.executor_runtime.max_memory_bytes, - WASM_FUEL_LIMIT => self.config.wasm_runtime.fuel_limit, - WASM_MAX_MEMORY => self.config.wasm_runtime.max_memory_bytes, - TRANSACTION_LIMITS => self.config.transaction_limits, - } - } } impl StateTransaction<'_, '_> { @@ -1589,9 +1541,9 @@ pub(crate) mod deserialize { let mut roles = None; let mut account_permissions = None; let mut account_roles = None; - let mut permission_schema = None; let mut triggers = None; let mut executor = None; + let mut executor_data_model = None; while let Some(key) = map.next_key::()? { match key.as_str() { @@ -1613,9 +1565,6 @@ pub(crate) mod deserialize { "account_roles" => { account_roles = Some(map.next_value()?); } - "permission_schema" => { - permission_schema = Some(map.next_value()?); - } "triggers" => { triggers = Some(map.next_value_seed(self.loader.cast::())?); @@ -1625,6 +1574,10 @@ pub(crate) mod deserialize { seed: self.loader.cast::(), })?); } + "executor_data_model" => { + executor_data_model = Some(map.next_value()?); + } + _ => { /* Skip unknown fields */ } } } @@ -1642,12 +1595,13 @@ pub(crate) mod deserialize { })?, account_roles: account_roles .ok_or_else(|| serde::de::Error::missing_field("account_roles"))?, - permission_schema: permission_schema - .ok_or_else(|| serde::de::Error::missing_field("permission_schema"))?, triggers: triggers .ok_or_else(|| serde::de::Error::missing_field("triggers"))?, executor: executor .ok_or_else(|| serde::de::Error::missing_field("executor"))?, + executor_data_model: executor_data_model.ok_or_else(|| { + serde::de::Error::missing_field("executor_data_model") + })?, }) } } @@ -1661,9 +1615,9 @@ pub(crate) mod deserialize { "roles", "account_permissions", "account_roles", - "permission_schema", "triggers", "executor", + "executor_data_model", ], WorldVisitor { loader: &self }, ) diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 9c0c051b047..462fb7c7f9d 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -342,7 +342,8 @@ impl Sumeragi { Strategy::kura_store_block(&self.kura, block); // Parameters are updated before updating public copy of sumeragi - self.update_params(&state_block); + // TODO: update chain-wide static parameters here, later + self.cache_transaction(&state_block); self.current_topology = new_topology; @@ -355,23 +356,6 @@ impl Sumeragi { state_events.into_iter().for_each(|e| self.send_event(e)); } - fn update_params(&mut self, state_block: &StateBlock<'_>) { - use iroha_data_model::parameter::default::*; - - if let Some(block_time) = state_block.world.query_param(BLOCK_TIME) { - self.block_time = Duration::from_millis(block_time); - } - if let Some(commit_time) = state_block.world.query_param(COMMIT_TIME_LIMIT) { - self.commit_time = Duration::from_millis(commit_time); - } - if let Some(max_txs_in_block) = state_block - .world - .query_param::(MAX_TRANSACTIONS_IN_BLOCK) - { - self.max_txs_in_block = max_txs_in_block as usize; - } - } - fn cache_transaction(&mut self, state_block: &StateBlock<'_>) { self.transaction_cache.retain(|tx| { !state_block.has_transaction(tx.as_ref().hash()) && !self.queue.is_expired(tx) diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 363d4be93bb..c0cb6eb20c1 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -2,7 +2,7 @@ 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 std::{collections::BTreeMap, num::NonZeroU32, ops::Deref, path::Path, sync::Arc, thread}; use eyre::Result; use futures::{prelude::*, stream::FuturesUnordered}; @@ -92,22 +92,22 @@ impl TestGenesis for GenesisNetwork { let mint_rose_permission = Permission::new( "CanMintAssetWithDefinition".parse().unwrap(), - &json!({ "asset_definition_id": rose_definition_id }), + json!({ "asset_definition_id": rose_definition_id }), ); let burn_rose_permission = Permission::new( "CanBurnAssetWithDefinition".parse().unwrap(), - &json!({ "asset_definition_id": rose_definition_id }), + json!({ "asset_definition_id": rose_definition_id }), ); let unregister_any_peer_permission = - Permission::new("CanUnregisterAnyPeer".parse().unwrap(), &json!(null)); + Permission::new("CanUnregisterAnyPeer".parse().unwrap(), json!(null)); let unregister_any_role_permission = - Permission::new("CanUnregisterAnyRole".parse().unwrap(), &json!(null)); + Permission::new("CanUnregisterAnyRole".parse().unwrap(), json!(null)); let unregister_wonderland_domain = Permission::new( "CanUnregisterDomain".parse().unwrap(), - &json!({ "domain_id": DomainId::from_str("wonderland").unwrap() } ), + json!({ "domain_id": DomainId::from_str("wonderland").unwrap() } ), ); let upgrade_executor_permission = - Permission::new("CanUpgradeExecutor".parse().unwrap(), &json!(null)); + Permission::new("CanUpgradeExecutor".parse().unwrap(), json!(null)); let first_transaction = genesis .first_transaction_mut() @@ -143,6 +143,47 @@ impl TestGenesis for GenesisNetwork { } } +pub struct NetworkOptions { + n_peers: u32, + config: Config, + start_port: Option, + offline_peers: u32, +} + +impl NetworkOptions { + pub fn with_n_peers(n_peers: u32) -> Self { + let mut config = Config::test(); + config.logger.level = Level::INFO; + + Self { + n_peers, + start_port: None, + offline_peers: 0, + config, + } + } + + pub fn with_start_port(mut self, value: u16) -> Self { + self.start_port = Some(value); + self + } + + pub fn with_offline_peers(mut self, value: u32) -> Self { + self.offline_peers = value; + self + } + + pub fn with_max_txs_in_block(mut self, value: NonZeroU32) -> Self { + self.config.chain_wide.max_transactions_in_block = value; + self + } + + pub fn with_config_mut(mut self, fun: impl Fn(&mut Config)) -> Self { + fun(&mut self.config); + self + } +} + impl Network { /// Collect the freeze handles from all the peers in the network. #[cfg(debug_assertions)] @@ -156,38 +197,20 @@ impl Network { /// Starts network with peers with default configuration and /// specified options in a new async runtime. Returns its info /// and client for connecting to it. - pub fn start_test_with_runtime( - n_peers: u32, - start_port: Option, - ) -> (Runtime, Self, Client) { + pub fn start_test_with_runtime(options: NetworkOptions) -> (Runtime, Self, Client) { let rt = Runtime::test(); - let (network, client) = rt.block_on(Self::start_test(n_peers, start_port)); + let (network, client) = rt.block_on(Self::start_test(options)); (rt, network, client) } /// Starts network with peers with default configuration and /// specified options. Returns its info and client for connecting /// to it. - pub async fn start_test(n_peers: u32, start_port: Option) -> (Self, Client) { - Self::start_test_with_offline(n_peers, 0, start_port).await - } - - /// Starts network with peers with default configuration and - /// specified options. Returns its info and client for connecting - /// to it. - pub async fn start_test_with_offline_and_set_n_shifts( - n_peers: u32, - offline_peers: u32, - start_port: Option, - ) -> (Self, Client) { - let mut config = Config::test(); - config.logger.level = Level::INFO; - let network = - Network::new_with_offline_peers(Some(config), n_peers, offline_peers, start_port) - .await - .expect("Failed to init peers"); + pub async fn start_test(options: NetworkOptions) -> (Self, Client) { + let network = Network::new(options).await.expect("Failed to init peers"); let client = Client::test( - &Network::peers(&network) + &network + .peers() .choose(&mut thread_rng()) .unwrap() .api_address, @@ -195,17 +218,6 @@ impl Network { (network, client) } - /// Starts network with peers with default configuration and - /// specified options. Returns its info and client for connecting - /// to it. - pub async fn start_test_with_offline( - n_peers: u32, - offline_peers: u32, - start_port: Option, - ) -> (Self, Client) { - Self::start_test_with_offline_and_set_n_shifts(n_peers, offline_peers, start_port).await - } - /// Adds peer to network and waits for it to start block /// synchronization. pub async fn add_peer(&self) -> (Peer, Client) { @@ -245,16 +257,11 @@ impl Network { /// # Errors /// - (RARE) Creating new peers and collecting into a [`HashMap`] fails. /// - Creating new [`Peer`] instance fails. - pub async fn new_with_offline_peers( - default_config: Option, - n_peers: u32, - offline_peers: u32, - start_port: Option, - ) -> Result { + pub async fn new(options: NetworkOptions) -> Result { let mut builders = core::iter::repeat_with(PeerBuilder::new) .enumerate() .map(|(n, builder)| { - if let Some(port) = start_port { + if let Some(port) = options.start_port { let offset: u16 = (n * 5) .try_into() .expect("The `n_peers` is too large to fit into `u16`"); @@ -264,14 +271,14 @@ impl Network { } }) .map(|(n, builder)| builder.with_into_genesis((n == 0).then(GenesisNetwork::test))) - .take(n_peers as usize) + .take(options.n_peers as usize) .collect::>(); let mut peers = builders .iter_mut() .map(PeerBuilder::build) .collect::>>()?; - let mut config = default_config.unwrap_or_else(Config::test); + let mut config = options.config; config.sumeragi.trusted_peers.value_mut().others = UniqueVec::from_iter(peers.iter().map(|peer| peer.id.clone())); @@ -279,7 +286,7 @@ impl Network { let genesis_builder = builders.remove(0).with_config(config.clone()); // Offset by one to account for genesis - let online_peers = n_peers - offline_peers - 1; + let online_peers = options.n_peers - options.offline_peers - 1; let rng = &mut rand::thread_rng(); let futures = FuturesUnordered::new(); @@ -294,7 +301,7 @@ impl Network { } futures.collect::<()>().await; - time::sleep(Duration::from_millis(500) * (n_peers + 1)).await; + time::sleep(Duration::from_millis(500) * (options.n_peers + 1)).await; Ok(Self { genesis: genesis_peer, diff --git a/data_model/src/events/data/events.rs b/data_model/src/events/data/events.rs index 6584e22b9d5..bb6286cc0be 100644 --- a/data_model/src/events/data/events.rs +++ b/data_model/src/events/data/events.rs @@ -89,8 +89,6 @@ mod model { Trigger(trigger::TriggerEvent), /// Role event Role(role::RoleEvent), - /// Permission token event - Permission(permission::PermissionSchemaUpdateEvent), /// Configuration event Configuration(config::ConfigurationEvent), /// Executor event @@ -287,44 +285,6 @@ mod role { } } -mod permission { - //! This module contains [`PermissionSchemaUpdateEvent`] - - pub use self::model::*; - use super::*; - use crate::permission::PermissionSchema; - - #[model] - mod model { - use super::*; - - /// Information about permission tokens update. - /// Only happens when registering new executor - #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[getset(get = "pub")] - #[ffi_type] - pub struct PermissionSchemaUpdateEvent { - /// Previous set of permission tokens - pub old_schema: PermissionSchema, - /// New set of permission tokens - pub new_schema: PermissionSchema, - } - } -} - mod account { //! This module contains `AccountEvent` and its impls @@ -332,7 +292,6 @@ mod account { pub use self::model::*; use super::*; - use crate::name::Name; type AccountMetadataChanged = MetadataChanged; @@ -414,7 +373,7 @@ mod account { impl AccountPermissionChanged { /// Get permission id - pub fn permission_id(&self) -> &Name { + pub fn permission_id(&self) -> &PermissionId { &self.permission_id } } @@ -535,9 +494,8 @@ mod config { data_event! { #[has_origin(origin = Parameter)] pub enum ConfigurationEvent { - Changed(ParameterId), - Created(ParameterId), - Deleted(ParameterId), + #[has_origin(parameter => ¶meter.id)] + Changed(Parameter), } } } @@ -558,10 +516,10 @@ mod executor { // this is used in no_std #[allow(unused)] use super::*; + use crate::executor::ExecutorDataModel; #[derive( Debug, - Copy, Clone, PartialEq, Eq, @@ -575,11 +533,34 @@ mod executor { EventSet, )] #[non_exhaustive] - #[ffi_type] + #[ffi_type(opaque)] #[serde(untagged)] // Unaffected by #3330, as single unit variant #[repr(transparent)] pub enum ExecutorEvent { - Upgraded, + Upgraded(ExecutorUpgrade), + } + + /// Information about the updated executor data model. + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + Getters, + )] + #[ffi_type] + #[repr(transparent)] + #[getset(get = "pub")] + pub struct ExecutorUpgrade { + /// Updated data model + pub new_data_model: ExecutorDataModel, } } } @@ -616,11 +597,7 @@ impl DataEvent { match self { Self::Domain(event) => Some(event.origin_id()), Self::Trigger(event) => event.origin_id().domain_id.as_ref(), - Self::Peer(_) - | Self::Configuration(_) - | Self::Role(_) - | Self::Permission(_) - | Self::Executor(_) => None, + Self::Peer(_) | Self::Configuration(_) | Self::Role(_) | Self::Executor(_) => None, } } } @@ -635,9 +612,8 @@ pub mod prelude { }, config::{ConfigurationEvent, ConfigurationEventSet}, domain::{DomainEvent, DomainEventSet, DomainOwnerChanged}, - executor::{ExecutorEvent, ExecutorEventSet}, + executor::{ExecutorEvent, ExecutorEventSet, ExecutorUpgrade}, peer::{PeerEvent, PeerEventSet}, - permission::PermissionSchemaUpdateEvent, role::{RoleEvent, RoleEventSet, RolePermissionChanged}, trigger::{TriggerEvent, TriggerEventSet, TriggerNumberOfExecutionsChanged}, DataEvent, HasOrigin, MetadataChanged, diff --git a/data_model/src/events/data/filters.rs b/data_model/src/events/data/filters.rs index 03a2048250b..ab43b561fbd 100644 --- a/data_model/src/events/data/filters.rs +++ b/data_model/src/events/data/filters.rs @@ -47,9 +47,6 @@ mod model { Trigger(TriggerEventFilter), /// Matches [`RoleEvent`]s Role(RoleEventFilter), - /// Matches [`PermissionSchemaUpdateEvent`]s - // nothing to filter for, really - PermissionSchemaUpdate, /// Matches [`ConfigurationEvent`]s Configuration(ConfigurationEventFilter), /// Matches [`ExecutorEvent`]s @@ -226,8 +223,6 @@ mod model { IntoSchema, )] pub struct ConfigurationEventFilter { - /// If specified matches only events originating from this configuration - pub(super) id_matcher: Option, /// Matches only event from this set pub(super) event_set: ConfigurationEventSet, } @@ -601,18 +596,10 @@ impl ConfigurationEventFilter { /// Creates a new [`ConfigurationEventFilter`] accepting all [`ConfigurationEvent`]s. pub const fn new() -> Self { Self { - id_matcher: None, event_set: ConfigurationEventSet::all(), } } - /// Modifies a [`ConfigurationEventFilter`] to accept only [`ConfigurationEvent`]s originating from ids matching `id_matcher`. - #[must_use] - pub fn for_parameter(mut self, id_matcher: ParameterId) -> Self { - self.id_matcher = Some(id_matcher); - self - } - /// Modifies a [`ConfigurationEventFilter`] to accept only [`ConfigurationEvent`]s of types matching `event_set`. #[must_use] pub const fn for_events(mut self, event_set: ConfigurationEventSet) -> Self { @@ -632,12 +619,6 @@ impl super::EventFilter for ConfigurationEventFilter { type Event = super::ConfigurationEvent; fn matches(&self, event: &Self::Event) -> bool { - if let Some(id_matcher) = &self.id_matcher { - if id_matcher != event.origin_id() { - return false; - } - } - if !self.event_set.matches(event) { return false; } @@ -706,7 +687,6 @@ impl EventFilter for DataEventFilter { (DataEvent::Trigger(event), Trigger(filter)) => filter.matches(event), (DataEvent::Role(event), Role(filter)) => filter.matches(event), (DataEvent::Configuration(event), Configuration(filter)) => filter.matches(event), - (DataEvent::Permission(_), PermissionSchemaUpdate) => true, (DataEvent::Executor(event), Executor(filter)) => filter.matches(event), ( @@ -714,7 +694,6 @@ impl EventFilter for DataEventFilter { | DataEvent::Domain(_) | DataEvent::Trigger(_) | DataEvent::Role(_) - | DataEvent::Permission(_) | DataEvent::Configuration(_) | DataEvent::Executor(_), Any, @@ -724,7 +703,6 @@ impl EventFilter for DataEventFilter { | DataEvent::Domain(_) | DataEvent::Trigger(_) | DataEvent::Role(_) - | DataEvent::Permission(_) | DataEvent::Configuration(_) | DataEvent::Executor(_), _, diff --git a/data_model/src/executor.rs b/data_model/src/executor.rs index 8c147cc477b..a97aff64c26 100644 --- a/data_model/src/executor.rs +++ b/data_model/src/executor.rs @@ -1,9 +1,11 @@ //! Structures, traits and impls related to *runtime* `Executor`s. #[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; +use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; +#[cfg(feature = "std")] +use std::collections::BTreeSet; -use derive_more::Constructor; +use derive_more::{Constructor, Display}; use getset::Getters; use iroha_data_model_derive::model; use iroha_schema::IntoSchema; @@ -11,7 +13,9 @@ use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::transaction::WasmSmartContract; +use crate::{ + parameter::ParameterId, permission::PermissionId, transaction::WasmSmartContract, JsonString, +}; #[model] mod model { @@ -47,10 +51,69 @@ mod model { pub wasm: WasmSmartContract, } + /// Executor data model. + /// + /// Defined from within the executor, it describes certain structures the executor + /// works with. + /// + /// Executor can define: + /// + /// - Permission tokens (see [`crate::permission::Permission`]) + /// - Configuration parameters (see [`crate::parameter::Parameter`]) + #[derive( + Default, + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Constructor, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + Display, + )] + #[ffi_type] + #[display(fmt = "{self:?}")] + pub struct ExecutorDataModel { + /// Permission tokens supported by the executor. + /// + /// These IDs refer to the types in the schema. + pub permissions: BTreeSet, + /// Configuration parameters supported by the executor. + /// + /// These IDs refer to the types in the schema. + pub parameters: BTreeSet, + /// Data model JSON schema, typically produced by [`IntoSchema`]. + pub schema: JsonString, + } + // TODO: Client doesn't need structures defined inside this macro. When dynamic linking is // implemented use: #[cfg(any(feature = "transparent_api", feature = "ffi_import"))] } +// TODO: derive `Getters` once FFI impl is fixed +// currently it fails for all fields +impl ExecutorDataModel { + /// Getter + pub fn permissions(&self) -> &BTreeSet { + &self.permissions + } + + /// Getter + pub fn parameters(&self) -> &BTreeSet { + &self.parameters + } + + /// Getter + pub fn schema(&self) -> &JsonString { + &self.schema + } +} + /// Result type that every executor should return. pub type Result = core::result::Result; @@ -62,5 +125,5 @@ pub type MigrationResult = Result<(), MigrationError>; pub mod prelude { //! The prelude re-exports most commonly used traits, structs and macros from this crate. - pub use super::Executor; + pub use super::{Executor, ExecutorDataModel}; } diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index 7ca016b30b2..d272941459a 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -111,8 +111,6 @@ mod model { #[debug(fmt = "{_0:?}")] SetParameter(SetParameter), #[debug(fmt = "{_0:?}")] - NewParameter(NewParameter), - #[debug(fmt = "{_0:?}")] Upgrade(Upgrade), #[debug(fmt = "{_0:?}")] Log(Log), @@ -172,7 +170,6 @@ impl_instruction! { Revoke, Revoke, SetParameter, - NewParameter, Upgrade, ExecuteTrigger, Log, @@ -246,7 +243,7 @@ mod transparent { } isi! { - /// Generic instruction for setting a chain-wide config parameter. + /// Setting an executor configuration parameter #[derive(Constructor, Display)] #[display(fmt = "SET `{parameter}`")] #[serde(transparent)] @@ -258,20 +255,6 @@ mod transparent { } } - isi! { - /// Sized structure for all possible on-chain configuration parameters when they are first created. - /// Generic instruction for setting a chain-wide config parameter. - #[derive(Constructor, Display)] - #[display(fmt = "SET `{parameter}`")] - #[serde(transparent)] - #[repr(transparent)] - pub struct NewParameter { - /// Parameter to be changed. - #[serde(flatten)] - pub parameter: Parameter, - } - } - isi! { /// Generic instruction to set key value at the object. #[schema(bounds = "O: Identifiable, O::Id: IntoSchema")] @@ -1519,8 +1502,8 @@ pub mod error { pub mod prelude { pub use super::{ AssetTransferBox, Burn, BurnBox, ExecuteTrigger, Fail, Grant, GrantBox, InstructionBox, - Log, Mint, MintBox, NewParameter, Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, - Revoke, RevokeBox, SetKeyValue, SetKeyValueBox, SetParameter, Transfer, TransferBox, - Unregister, UnregisterBox, Upgrade, + Log, Mint, MintBox, Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, Revoke, + RevokeBox, SetKeyValue, SetKeyValueBox, SetParameter, Transfer, TransferBox, Unregister, + UnregisterBox, Upgrade, }; } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 301b01ba84e..7a376b3be6a 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -17,17 +17,16 @@ use alloc::{ }; use core::{fmt, fmt::Debug, ops::RangeInclusive, str::FromStr}; -use derive_more::{Constructor, Display, From, FromStr}; +use derive_more::{Constructor, Display, FromStr}; use getset::Getters; use iroha_crypto::PublicKey; -use iroha_data_model_derive::{model, EnumRef, IdEqOrdHash}; +use iroha_data_model_derive::{model, EnumRef}; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; use iroha_version::{declare_versioned, version_with_scale}; use parity_scale_codec::{Decode, Encode}; use prelude::Executable; use serde::{Deserialize, Serialize}; -use serde_with::{DeserializeFromStr, SerializeDisplay}; use strum::FromRepr; pub use self::model::*; @@ -117,7 +116,6 @@ mod seal { Revoke, SetParameter, - NewParameter, Upgrade, ExecuteTrigger, Log, @@ -154,7 +152,6 @@ mod seal { FindTransactionsByAccountId, FindTransactionByHash, FindPermissionsByAccountId, - FindPermissionSchema, FindAllActiveTriggerIds, FindTriggerById, FindTriggerKeyValueByIdAndKey, @@ -164,6 +161,7 @@ mod seal { FindRoleByRoleId, FindRolesByAccountId, FindAllParameters, + FindExecutorDataModel } } @@ -212,66 +210,21 @@ impl EnumTryAsError { impl std::error::Error for EnumTryAsError {} pub mod parameter { - //! Structures, traits and impls related to `Paramater`s. + //! Executor configuration parameters use core::borrow::Borrow; - use iroha_primitives::numeric::Numeric; + use iroha_data_model_derive::IdEqOrdHash; pub use self::model::*; use super::*; - use crate::isi::InstructionBox; - - /// Set of parameter names currently used by iroha - #[allow(missing_docs)] - pub mod default { - pub const MAX_TRANSACTIONS_IN_BLOCK: &str = "MaxTransactionsInBlock"; - pub const BLOCK_TIME: &str = "BlockTime"; - pub const COMMIT_TIME_LIMIT: &str = "CommitTimeLimit"; - pub const TRANSACTION_LIMITS: &str = "TransactionLimits"; - pub const WSV_DOMAIN_METADATA_LIMITS: &str = "WSVDomainMetadataLimits"; - pub const WSV_ASSET_DEFINITION_METADATA_LIMITS: &str = "WSVAssetDefinitionMetadataLimits"; - pub const WSV_ACCOUNT_METADATA_LIMITS: &str = "WSVAccountMetadataLimits"; - pub const WSV_ASSET_METADATA_LIMITS: &str = "WSVAssetMetadataLimits"; - pub const WSV_TRIGGER_METADATA_LIMITS: &str = "WSVTriggerMetadataLimits"; - pub const WSV_IDENT_LENGTH_LIMITS: &str = "WSVIdentLengthLimits"; - pub const EXECUTOR_FUEL_LIMIT: &str = "ExecutorFuelLimit"; - pub const EXECUTOR_MAX_MEMORY: &str = "ExecutorMaxMemory"; - pub const WASM_FUEL_LIMIT: &str = "WASMFuelLimit"; - pub const WASM_MAX_MEMORY: &str = "WASMMaxMemory"; - } #[model] mod model { use super::*; - #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - FromVariant, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[ffi_type(local)] - pub enum ParameterValueBox { - TransactionLimits(transaction::TransactionLimits), - MetadataLimits(metadata::Limits), - LengthLimits(LengthLimits), - Numeric( - #[skip_from] - #[skip_try_from] - Numeric, - ), - } - - /// Identification of a [`Parameter`]. + /// Identifies a global on-chain configuration [`Parameter`]. + /// The executor defines available parameter names. #[derive( Debug, Display, @@ -281,97 +234,61 @@ pub mod parameter { PartialOrd, Ord, Hash, - Getters, + Constructor, FromStr, + Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[display(fmt = "{name}")] #[getset(get = "pub")] #[serde(transparent)] #[repr(transparent)] #[ffi_type(opaque)] pub struct ParameterId { - /// [`Name`] unique to a [`Parameter`]. + /// Should be unique. pub name: Name, } + /// Executor configuration parameter. + /// + /// Must be defined in [`crate::executor::ExecutorDataModel`]. #[derive( Debug, Display, Clone, - Constructor, IdEqOrdHash, Decode, Encode, - DeserializeFromStr, - SerializeDisplay, + Deserialize, + Serialize, IntoSchema, + Getters, )] - #[display(fmt = "?{id}={val}")] - /// A chain-wide configuration parameter and its value. + #[display(fmt = "PARAMETER `{id}` = `{payload}`")] #[ffi_type] + #[getset(get = "pub")] pub struct Parameter { - /// Unique [`Id`] of the [`Parameter`]. + /// Refers to a type defined in [`crate::executor::ExecutorDataModel`]. pub id: ParameterId, - /// Current value of the [`Parameter`]. - pub val: ParameterValueBox, - } - } - - // TODO: Maybe derive - impl core::fmt::Display for ParameterValueBox { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::MetadataLimits(v) => core::fmt::Display::fmt(&v, f), - Self::TransactionLimits(v) => core::fmt::Display::fmt(&v, f), - Self::LengthLimits(v) => core::fmt::Display::fmt(&v, f), - Self::Numeric(v) => core::fmt::Display::fmt(&v, f), - } - } - } - - impl> From for ParameterValueBox { - fn from(value: T) -> Self { - Self::Numeric(value.into()) - } - } - - impl TryFrom for u32 { - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: ParameterValueBox) -> Result { - use iroha_macro::error::ErrorTryFromEnum; - - let ParameterValueBox::Numeric(numeric) = value else { - return Err(ErrorTryFromEnum::default()); - }; - - numeric.try_into().map_err(|_| ErrorTryFromEnum::default()) - } - } - - impl TryFrom for u64 { - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: ParameterValueBox) -> Result { - use iroha_macro::error::ErrorTryFromEnum; - - let ParameterValueBox::Numeric(numeric) = value else { - return Err(ErrorTryFromEnum::default()); - }; - - numeric.try_into().map_err(|_| ErrorTryFromEnum::default()) + /// Payload containing actual value. + /// + /// It is JSON-encoded, and its structure must correspond to the structure of + /// the type defined in [`crate::executor::ExecutorDataModel`]. + #[getset(skip)] + pub payload: JsonString, } } impl Parameter { - /// Current value of the [`Parameter`]. - pub fn val(&self) -> &ParameterValueBox { - &self.val + /// Constructor + pub fn new(id: ParameterId, payload: impl IntoJsonString) -> Self { + Self { + id, + payload: payload.into_json_string(), + } } } @@ -387,152 +304,11 @@ pub mod parameter { } } - impl FromStr for Parameter { - type Err = ParseError; - - fn from_str(string: &str) -> Result { - if let Some((parameter_id_candidate, val_candidate)) = string.rsplit_once('=') { - if let Some(parameter_id_candidate) = parameter_id_candidate.strip_prefix('?') { - let param_id: ParameterId = - parameter_id_candidate.parse().map_err(|_| ParseError { - reason: "Failed to parse the `param_id` part of the `Parameter`.", - })?; - if let Some((val, ty)) = val_candidate.rsplit_once('_') { - let val = match ty { - // Shorthand for `LengthLimits` - "LL" => { - let (lower, upper) = val.rsplit_once(',').ok_or( ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `LengthLimits`. Two comma-separated values are expected.", - })?; - let lower = lower.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `LengthLimits`. Invalid lower `u32` bound.", - })?; - let upper = upper.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `LengthLimits`. Invalid upper `u32` bound.", - })?; - LengthLimits::new(lower, upper).into() - } - // Shorthand for `TransactionLimits` - "TL" => { - let (max_instr, max_wasm_size) = val.rsplit_once(',').ok_or( ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `TransactionLimits`. Two comma-separated values are expected.", - })?; - let max_instr = max_instr.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `TransactionLimits`. `max_instruction_number` field should be a valid `u64`.", - })?; - let max_wasm_size = max_wasm_size.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `TransactionLimits`. `max_wasm_size_bytes` field should be a valid `u64`.", - })?; - transaction::TransactionLimits::new( - max_instr, - max_wasm_size, - ).into() - } - // Shorthand for `MetadataLimits` - "ML" => { - let (lower, upper) = val.rsplit_once(',').ok_or( ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `MetadataLimits`. Two comma-separated values are expected.", - })?; - let lower = lower.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `MetadataLimits`. Invalid `u32` in `capacity` field.", - })?; - let upper = upper.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `MetadataLimits`. Invalid `u32` in `max_entry_len` field.", - })?; - metadata::Limits::new(lower, upper).into() - } - _ => return Err(ParseError { - reason: - "Unsupported type provided for the `val` part of the `Parameter`.", - }), - }; - Ok(Self::new(param_id, val)) - } else { - let val = val_candidate.parse::().map_err(|_| ParseError { - reason: - "Failed to parse the `val` part of the `Parameter` as `Numeric`.", - })?; - - Ok(Self::new(param_id, val.into())) - } - } else { - Err(ParseError { - reason: "`param_id` part of `Parameter` must start with `?`", - }) - } - } else { - Err(ParseError { - reason: "The `Parameter` string did not contain the `=` character.", - }) - } - } - } - - /// Convenience tool for setting parameters - #[derive(Default)] - #[must_use] - pub struct ParametersBuilder { - parameters: Vec, - } - - /// Error associated with parameters builder - #[derive(From, Debug, Display, Copy, Clone)] - pub enum ParametersBuilderError { - /// Error emerged during parsing of parameter id - Parse(ParseError), - } - - #[cfg(feature = "std")] - impl std::error::Error for ParametersBuilderError {} - - impl ParametersBuilder { - /// Construct [`Self`] - pub fn new() -> Self { - Self::default() - } - - /// Add [`Parameter`] to self - /// - /// # Errors - /// - [`ParameterId`] parsing failed - pub fn add_parameter( - mut self, - parameter_id: &str, - val: impl Into, - ) -> Result { - let parameter = Parameter { - id: parameter_id.parse()?, - val: val.into(), - }; - self.parameters.push(parameter); - Ok(self) - } - - /// Create sequence isi for setting parameters - pub fn into_set_parameters(self) -> Vec { - self.parameters - .into_iter() - .map(isi::SetParameter::new) - .map(Into::into) - .collect() - } - - /// Create sequence isi for creating parameters - pub fn into_create_parameters(self) -> Vec { - self.parameters - .into_iter() - .map(isi::NewParameter::new) - .map(Into::into) - .collect() + impl Parameter { + /// Getter + // TODO: derive with getset once FFI impl is fixed + pub fn payload(&self) -> &JsonString { + &self.payload } } @@ -541,82 +317,13 @@ pub mod parameter { pub use super::{Parameter, ParameterId}; } - - #[cfg(test)] - mod tests { - use super::*; - use crate::{ - prelude::{numeric, MetadataLimits}, - transaction::TransactionLimits, - }; - - const INVALID_PARAM: [&str; 4] = [ - "", - "Block?SyncGossipPeriod=20000", - "?BlockSyncGossipPeriod20000", - "?BlockSyncGossipPeriod=20000_u32", - ]; - - #[test] - fn test_invalid_parameter_str() { - assert!(matches!( - parameter::Parameter::from_str(INVALID_PARAM[0]), - Err(err) if err.reason == "The `Parameter` string did not contain the `=` character." - )); - assert!(matches!( - parameter::Parameter::from_str(INVALID_PARAM[1]), - Err(err) if err.reason == "`param_id` part of `Parameter` must start with `?`" - )); - assert!(matches!( - parameter::Parameter::from_str(INVALID_PARAM[2]), - Err(err) if err.to_string() == "The `Parameter` string did not contain the `=` character." - )); - assert!(matches!( - parameter::Parameter::from_str(INVALID_PARAM[3]), - Err(err) if err.to_string() == "Unsupported type provided for the `val` part of the `Parameter`." - )); - } - - #[test] - fn test_parameter_serialize_deserialize_consistent() { - let parameters = [ - Parameter::new( - ParameterId::from_str("TransactionLimits") - .expect("Failed to parse `ParameterId`"), - TransactionLimits::new(42, 24).into(), - ), - Parameter::new( - ParameterId::from_str("MetadataLimits").expect("Failed to parse `ParameterId`"), - MetadataLimits::new(42, 24).into(), - ), - Parameter::new( - ParameterId::from_str("LengthLimits").expect("Failed to parse `ParameterId`"), - LengthLimits::new(24, 42).into(), - ), - Parameter::new( - ParameterId::from_str("Int").expect("Failed to parse `ParameterId`"), - numeric!(42).into(), - ), - ]; - - for parameter in parameters { - assert_eq!( - parameter, - serde_json::to_string(¶meter) - .and_then(|parameter| serde_json::from_str(¶meter)) - .unwrap_or_else(|_| panic!( - "Failed to de/serialize parameter {:?}", - ¶meter - )) - ); - } - } - } } #[model] #[allow(clippy::redundant_pub_crate)] mod model { + use iroha_schema::TypeId; + use super::*; /// Unique id of blockchain @@ -675,7 +382,7 @@ mod model { TriggerId(trigger::TriggerId), /// [`RoleId`](`role::RoleId`) variant. RoleId(role::RoleId), - /// [`Permission`](`permission::Permission`) variant. + /// [`PermissionId`](`permission::PermissionId`) variant. PermissionId(permission::PermissionId), /// [`ParameterId`](`parameter::ParameterId`) variant. ParameterId(parameter::ParameterId), @@ -864,6 +571,106 @@ mod model { /// in the next request to continue fetching results of the original query pub cursor: crate::query::cursor::ForwardCursor, } + + /// String containing serialized valid JSON. + /// + /// This string is guaranteed to be parsed as JSON. + #[derive(Display, Default, Debug, Clone, Eq, Encode, Decode, TypeId, Ord, PartialOrd)] + #[ffi_type(unsafe {robust})] + #[repr(transparent)] + #[display(fmt = "{}", "0")] + pub struct JsonString(pub(super) String); +} + +/// A helper trait for polymorphism, implemented for various types +pub trait IntoJsonString { + /// Converts self into [`JsonString`] + fn into_json_string(self) -> JsonString; +} + +impl IntoJsonString for JsonString { + fn into_json_string(self) -> JsonString { + self + } +} + +impl IntoJsonString for &serde_json::Value { + fn into_json_string(self) -> JsonString { + JsonString::from(self) + } +} + +impl IntoJsonString for serde_json::Value { + fn into_json_string(self) -> JsonString { + (&self).into_json_string() + } +} + +impl JsonString { + /// Deserialize JSON into something + /// # Errors + /// See [`serde_json::from_str`]. + pub fn deserialize<'a, T>(&'a self) -> serde_json::Result + where + T: Deserialize<'a>, + { + serde_json::from_str(&self.0) + } + + /// Serializes a value into [`JsonString`] + /// # Errors + /// See [`serde_json::to_string`]. + pub fn serialize(value: &T) -> serde_json::Result { + let serialized = serde_json::to_string(value)?; + // the string was obtained from serde_json serialization, + // so it should be a valid JSON string + Ok(Self(serialized)) + } +} + +impl From<&serde_json::Value> for JsonString { + fn from(value: &serde_json::Value) -> Self { + Self(value.to_string()) + } +} + +impl PartialEq for JsonString { + fn eq(&self, other: &Self) -> bool { + serde_json::from_str::(&self.0).unwrap() + == serde_json::from_str::(&other.0).unwrap() + } +} + +impl<'de> serde::de::Deserialize<'de> for JsonString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let json = serde_json::Value::deserialize(deserializer)?; + Ok(Self::from(&json)) + } +} + +impl serde::ser::Serialize for JsonString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let json = serde_json::Value::from_str(&self.0).map_err(serde::ser::Error::custom)?; + json.serialize(serializer) + } +} + +impl IntoSchema for JsonString { + fn type_name() -> iroha_schema::Ident { + ::id() + } + + fn update_schema_map(map: &mut iroha_schema::MetaMap) { + if !map.contains_key::() { + map.insert::(iroha_schema::Metadata::String); + } + } } macro_rules! impl_encode_as_id_box { diff --git a/data_model/src/permission.rs b/data_model/src/permission.rs index e5c24fe2ea5..4651554585c 100644 --- a/data_model/src/permission.rs +++ b/data_model/src/permission.rs @@ -1,6 +1,7 @@ //! Permission Token and related impls #[cfg(not(feature = "std"))] -use alloc::{borrow::ToOwned as _, collections::BTreeSet, format, string::String, vec::Vec}; +use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; +use core::borrow::Borrow; #[cfg(feature = "std")] use std::collections::BTreeSet; @@ -17,196 +18,101 @@ pub type Permissions = BTreeSet; use super::*; -/// Unique id of [`Permission`] -pub type PermissionId = Name; - #[model] mod model { use super::*; - /// Stored proof of the account having a permission for a certain action. - /// - /// Since permission token is represented opaque to core - /// either executor or client should make sure that tokens are represented uniformly. - /// - /// So that: - /// - payload A is equal to payload B then token A must be equal to token B - /// - and if payload A is't equal to B then token A mustn't be equal to token B + /// Identifies a [`Permission`]. + /// The executor defines available permission names. #[derive( Debug, + Display, Clone, PartialEq, Eq, PartialOrd, Ord, + Hash, + Constructor, + FromStr, + Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[ffi_type] - pub struct Permission { - /// Token identifier - pub definition_id: PermissionId, - /// JSON encoded token payload - pub payload: JsonString, + #[getset(get = "pub")] + #[serde(transparent)] + #[repr(transparent)] + #[ffi_type(opaque)] + pub struct PermissionId { + /// Should be unique. + pub name: Name, } - /// Description of tokens defined in the executor + /// Stored proof of the account having a permission for a certain action. #[derive( Debug, - Display, Clone, PartialEq, Eq, PartialOrd, Ord, - Default, Decode, Encode, Deserialize, Serialize, IntoSchema, + Display, + Getters, )] - #[display(fmt = "{token_ids:#?}")] #[ffi_type] - pub struct PermissionSchema { - /// Ids of all permission tokens, sorted. - pub token_ids: Vec, - /// Type schema of permission tokens + #[display(fmt = "PERMISSION `{id}` = `{payload}`")] + #[getset(get = "pub")] + pub struct Permission { + /// Refers to a type defined in [`crate::executor::ExecutorDataModel`]. + pub id: PermissionId, + /// Payload containing actual value. /// - /// At the time of writing this doc a complete schema is returned but it's - /// possible that in the future this field will contain references to types - /// defined in the Iroha schema without defining them itself - pub schema: String, - } - - /// String containing serialized valid JSON. - /// This string is guaranteed to parse as JSON - #[derive(Debug, Clone, Eq, Encode, Decode)] - pub struct JsonString(pub(super) String); -} - -// TODO: Use getset to derive this -impl PermissionSchema { - /// Construct new [`PermissionSchema`] - pub fn new(token_ids: Vec, schema: String) -> Self { - Self { token_ids, schema } - } - - /// Return id of this token - pub fn permissions(&self) -> &[PermissionId] { - &self.token_ids + /// It is JSON-encoded, and its structure must correspond to the structure of + /// the type defined in [`crate::executor::ExecutorDataModel`]. + #[getset(skip)] + pub payload: JsonString, } } impl Permission { - /// Construct [`Self`] from a raw string slice. The caller of the function - /// must make sure that the given string slice can be parsed as valid JSON. - /// - /// Only used in tests - #[cfg(debug_assertions)] - // TODO: Remove after integration tests have been moved to python tests - #[deprecated(note = "Will be removed after integration tests are removed from iroha_client")] - pub fn from_str_unchecked(definition_id: PermissionId, payload: &str) -> Self { + /// Constructor + pub fn new(id: PermissionId, payload: impl IntoJsonString) -> Self { Self { - definition_id, - payload: JsonString(payload.to_owned()), + id, + payload: payload.into_json_string(), } } - - /// Construct [`Self`] - pub fn new(definition_id: PermissionId, payload: &serde_json::Value) -> Self { - Self { - definition_id, - payload: JsonString::new(payload), - } - } - - /// Return id of this token - // TODO: Use getset to derive this after fixes in FFI - pub fn definition_id(&self) -> &Name { - &self.definition_id - } - - /// Payload of this token - // TODO: Use getset to derive this after fixes in FFI - pub fn payload(&self) -> &String { - &self.payload.0 - } -} - -impl core::fmt::Display for Permission { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.definition_id) - } -} - -impl JsonString { - /// Construct [`JsonString`] - pub fn new(payload: &serde_json::Value) -> Self { - Self(payload.to_string()) - } -} -impl PartialEq for JsonString { - fn eq(&self, other: &Self) -> bool { - serde_json::from_str::(&self.0).unwrap() - == serde_json::from_str::(&other.0).unwrap() - } } -impl<'de> serde::de::Deserialize<'de> for JsonString { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let json = serde_json::Value::deserialize(deserializer)?; - Ok(Self::new(&json)) +impl Borrow for PermissionId { + fn borrow(&self) -> &str { + self.name.borrow() } } -impl serde::ser::Serialize for JsonString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let json = serde_json::Value::from_str(&self.0).map_err(serde::ser::Error::custom)?; - json.serialize(serializer) +impl Borrow for Permission { + fn borrow(&self) -> &str { + self.id.borrow() } } -impl iroha_schema::TypeId for JsonString { - fn id() -> iroha_schema::Ident { - "JsonString".to_owned() - } -} - -impl IntoSchema for JsonString { - fn type_name() -> iroha_schema::Ident { - ::id() - } - - fn update_schema_map(map: &mut iroha_schema::MetaMap) { - if !map.contains_key::() { - map.insert::(iroha_schema::Metadata::String); - } - } -} - -impl PartialOrd for JsonString { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for JsonString { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.0.cmp(&other.0) +impl Permission { + /// Getter + // TODO: derive with getset once FFI impl is fixed + pub fn payload(&self) -> &JsonString { + &self.payload } } pub mod prelude { //! The prelude re-exports most commonly used traits, structs and macros from this crate. - pub use super::{Permission, PermissionId, PermissionSchema}; + pub use super::{Permission, PermissionId}; } diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index ae99fc9fa2e..ca450c6b9c4 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -26,8 +26,8 @@ pub use sorting::Sorting; pub use self::model::*; use self::{ - account::*, asset::*, block::*, domain::*, peer::*, permission::*, predicate::*, role::*, - transaction::*, trigger::*, + account::*, asset::*, block::*, domain::*, executor::*, peer::*, permission::*, predicate::*, + role::*, transaction::*, trigger::*, }; use crate::{ account::{Account, AccountId}, @@ -188,7 +188,6 @@ mod model { FindTransactionsByAccountId(FindTransactionsByAccountId), FindTransactionByHash(FindTransactionByHash), FindPermissionsByAccountId(FindPermissionsByAccountId), - FindPermissionSchema(FindPermissionSchema), FindAllActiveTriggerIds(FindAllActiveTriggerIds), FindTriggerById(FindTriggerById), FindTriggerKeyValueByIdAndKey(FindTriggerKeyValueByIdAndKey), @@ -198,6 +197,7 @@ mod model { FindRoleByRoleId(FindRoleByRoleId), FindRolesByAccountId(FindRolesByAccountId), FindAllParameters(FindAllParameters), + FindExecutorDataModel(FindExecutorDataModel), } /// Sized container for all possible [`Query::Output`]s @@ -221,11 +221,11 @@ mod model { Identifiable(IdentifiableBox), Transaction(TransactionQueryOutput), Permission(crate::permission::Permission), - PermissionSchema(crate::permission::PermissionSchema), LimitedMetadata(MetadataValueBox), Numeric(Numeric), BlockHeader(BlockHeader), Block(crate::block::SignedBlock), + ExecutorDataModel(crate::executor::ExecutorDataModel), Vec( #[skip_from] @@ -359,7 +359,6 @@ impl_query! { FindAllRoleIds => Vec, FindRolesByAccountId => Vec, FindRoleByRoleId => crate::role::Role, - FindPermissionSchema => crate::permission::PermissionSchema, FindPermissionsByAccountId => Vec, FindAllAccounts => Vec, FindAccountById => crate::account::Account, @@ -394,6 +393,7 @@ impl_query! { FindAllBlocks => Vec, FindAllBlockHeaders => Vec, FindBlockHeaderByHash => crate::block::BlockHeader, + FindExecutorDataModel => crate::executor::ExecutorDataModel } impl Query for QueryBox { @@ -412,11 +412,11 @@ impl core::fmt::Display for QueryOutputBox { QueryOutputBox::Identifiable(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Transaction(_) => write!(f, "TransactionQueryOutput"), QueryOutputBox::Permission(v) => core::fmt::Display::fmt(&v, f), - QueryOutputBox::PermissionSchema(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Block(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::BlockHeader(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Numeric(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::LimitedMetadata(v) => core::fmt::Display::fmt(&v, f), + QueryOutputBox::ExecutorDataModel(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Vec(v) => { // TODO: Remove so we can derive. @@ -621,17 +621,9 @@ pub mod permission { use parity_scale_codec::Encode; use super::{Query, QueryType}; - use crate::{ - permission::{self, PermissionSchema}, - prelude::*, - }; + use crate::prelude::*; queries! { - /// Finds all registered permission tokens - #[derive(Copy, Display)] - #[ffi_type] - pub struct FindPermissionSchema; - /// [`FindPermissionsByAccountId`] Iroha Query finds all [`Permission`]s /// for a specified account. #[derive(Display)] @@ -647,7 +639,7 @@ pub mod permission { /// The prelude re-exports most commonly used traits, structs and macros from this module. pub mod prelude { - pub use super::{FindPermissionSchema, FindPermissionsByAccountId}; + pub use super::FindPermissionsByAccountId; } } @@ -954,7 +946,7 @@ pub mod domain { } pub mod peer { - //! Queries related to [`Domain`](crate::domain::Domain). + //! Queries related to [`crate::peer`]. #[cfg(not(feature = "std"))] use alloc::{format, string::String, vec::Vec}; @@ -970,18 +962,39 @@ pub mod peer { #[display(fmt = "Find all peers")] #[ffi_type] pub struct FindAllPeers; + } + + /// The prelude re-exports most commonly used traits, structs and macros from this crate. + pub mod prelude { + pub use super::FindAllPeers; + } +} + +pub mod executor { + //! Queries related to [`crate::executor`]. + + #[cfg(not(feature = "std"))] + use alloc::{format, string::String, vec::Vec}; + + use derive_more::Display; + + queries! { + /// [`FindExecutorDataModel`] Iroha Query finds the data model of the current executor. + #[derive(Copy, Display)] + #[display(fmt = "Find executor data model")] + #[ffi_type] + pub struct FindExecutorDataModel; - /// [`FindAllParameters`] Iroha Query finds all [`Peer`]s parameters. + /// [`FindAllParameters`] Iroha Query finds all defined executor configuration parameters. #[derive(Copy, Display)] #[display(fmt = "Find all peers parameters")] - // TODO: Unused query. Remove? #[ffi_type] pub struct FindAllParameters; } /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { - pub use super::{FindAllParameters, FindAllPeers}; + pub use super::{FindAllParameters, FindExecutorDataModel}; } } @@ -1495,7 +1508,8 @@ pub mod prelude { pub use super::http::*; pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, - peer::prelude::*, permission::prelude::*, predicate::PredicateTrait, role::prelude::*, - transaction::*, trigger::prelude::*, FetchSize, QueryBox, QueryId, TransactionQueryOutput, + executor::prelude::*, peer::prelude::*, permission::prelude::*, predicate::PredicateTrait, + role::prelude::*, transaction::prelude::*, trigger::prelude::*, FetchSize, QueryBox, + QueryId, TransactionQueryOutput, }; } diff --git a/data_model/src/role.rs b/data_model/src/role.rs index 45c6b53732a..d7479db5959 100644 --- a/data_model/src/role.rs +++ b/data_model/src/role.rs @@ -44,7 +44,7 @@ mod model { #[repr(transparent)] #[ffi_type(opaque)] pub struct RoleId { - /// Role name, should be unique . + /// Role name. Should be unique. pub name: Name, } diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 081e0a514f9..ace8cd328d4 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -580,7 +580,6 @@ pub mod error { Revoke(_) => "revoke", ExecuteTrigger(_) => "execute trigger", SetParameter(_) => "set parameter", - NewParameter(_) => "new parameter", Upgrade(_) => "upgrade", Log(_) => "log", }; diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index 754ee8368c6..4d5be970008 100644 --- a/data_model/src/visit.rs +++ b/data_model/src/visit.rs @@ -40,7 +40,6 @@ pub trait Visit { visit_upgrade(&Upgrade), visit_execute_trigger(&ExecuteTrigger), - visit_new_parameter(&NewParameter), visit_set_parameter(&SetParameter), visit_log(&Log), @@ -58,7 +57,6 @@ pub trait Visit { visit_find_all_domains(&FindAllDomains), visit_find_all_parameters(&FindAllParameters), visit_find_all_peers(&FindAllPeers), - visit_find_permission_schema(&FindPermissionSchema), visit_find_all_role_ids(&FindAllRoleIds), visit_find_all_roles(&FindAllRoles), visit_find_all_transactions(&FindAllTransactions), @@ -84,6 +82,7 @@ pub trait Visit { visit_find_trigger_by_id(&FindTriggerById), visit_find_trigger_key_value_by_id_and_key(&FindTriggerKeyValueByIdAndKey), visit_find_triggers_by_domain_id(&FindTriggersByDomainId), + visit_find_executor_data_model(&FindExecutorDataModel), // Visit RegisterBox visit_register_peer(&Register), @@ -183,7 +182,6 @@ pub fn visit_query(visitor: &mut V, authority: &AccountId, qu visit_find_all_domains(FindAllDomains), visit_find_all_parameters(FindAllParameters), visit_find_all_peers(FindAllPeers), - visit_find_permission_schema(FindPermissionSchema), visit_find_all_role_ids(FindAllRoleIds), visit_find_all_roles(FindAllRoles), visit_find_all_transactions(FindAllTransactions), @@ -209,6 +207,7 @@ pub fn visit_query(visitor: &mut V, authority: &AccountId, qu visit_find_trigger_by_id(FindTriggerById), visit_find_trigger_key_value_by_id_and_key(FindTriggerKeyValueByIdAndKey), visit_find_triggers_by_domain_id(FindTriggersByDomainId), + visit_find_executor_data_model(FindExecutorDataModel), } } @@ -230,9 +229,6 @@ pub fn visit_instruction( isi: &InstructionBox, ) { match isi { - InstructionBox::NewParameter(variant_value) => { - visitor.visit_new_parameter(authority, variant_value) - } InstructionBox::SetParameter(variant_value) => { visitor.visit_set_parameter(authority, variant_value) } @@ -424,7 +420,6 @@ leaf_visitors! { visit_mint_trigger_repetitions(&Mint), visit_burn_trigger_repetitions(&Burn), visit_upgrade(&Upgrade), - visit_new_parameter(&NewParameter), visit_set_parameter(&SetParameter), visit_execute_trigger(&ExecuteTrigger), visit_fail(&Fail), @@ -444,7 +439,6 @@ leaf_visitors! { visit_find_all_domains(&FindAllDomains), visit_find_all_parameters(&FindAllParameters), visit_find_all_peers(&FindAllPeers), - visit_find_permission_schema(&FindPermissionSchema), visit_find_all_role_ids(&FindAllRoleIds), visit_find_all_roles(&FindAllRoles), visit_find_all_transactions(&FindAllTransactions), @@ -470,4 +464,5 @@ leaf_visitors! { visit_find_trigger_by_id(&FindTriggerById), visit_find_trigger_key_value_by_id_and_key(&FindTriggerKeyValueByIdAndKey), visit_find_triggers_by_domain_id(&FindTriggersByDomainId), + visit_find_executor_data_model(&FindExecutorDataModel), } diff --git a/default_executor/src/lib.rs b/default_executor/src/lib.rs index 26b3593f753..546a6cab359 100644 --- a/default_executor/src/lib.rs +++ b/default_executor/src/lib.rs @@ -8,7 +8,7 @@ extern crate panic_halt; use alloc::borrow::ToOwned as _; -use iroha_executor::{default::default_permission_schema, prelude::*}; +use iroha_executor::{prelude::*, DataModelBuilder}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] @@ -52,11 +52,7 @@ impl Executor { pub fn migrate(block_height: u64) -> MigrationResult { Executor::ensure_genesis(block_height)?; - let schema = default_permission_schema(); - let (token_ids, schema_str) = schema.serialize(); - iroha_executor::set_permission_schema( - &iroha_executor::data_model::permission::PermissionSchema::new(token_ids, schema_str), - ); + DataModelBuilder::with_default_permissions().set(); Ok(()) } diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 3428213f02e..81fd7f6e7e8 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -157,7 +157,7 @@ }, { "name": "permission_id", - "type": "Name" + "type": "PermissionId" } ] }, @@ -769,26 +769,12 @@ { "tag": "Changed", "discriminant": 0, - "type": "ParameterId" - }, - { - "tag": "Created", - "discriminant": 1, - "type": "ParameterId" - }, - { - "tag": "Deleted", - "discriminant": 2, - "type": "ParameterId" + "type": "Parameter" } ] }, "ConfigurationEventFilter": { "Struct": [ - { - "name": "id_matcher", - "type": "Option" - }, { "name": "event_set", "type": "ConfigurationEventSet" @@ -802,14 +788,6 @@ { "name": "Changed", "mask": 1 - }, - { - "name": "Created", - "mask": 2 - }, - { - "name": "Deleted", - "mask": 4 } ] } @@ -855,19 +833,14 @@ "discriminant": 3, "type": "RoleEvent" }, - { - "tag": "Permission", - "discriminant": 4, - "type": "PermissionSchemaUpdateEvent" - }, { "tag": "Configuration", - "discriminant": 5, + "discriminant": 4, "type": "ConfigurationEvent" }, { "tag": "Executor", - "discriminant": 6, + "discriminant": 5, "type": "ExecutorEvent" } ] @@ -913,18 +886,14 @@ "discriminant": 7, "type": "RoleEventFilter" }, - { - "tag": "PermissionSchemaUpdate", - "discriminant": 8 - }, { "tag": "Configuration", - "discriminant": 9, + "discriminant": 8, "type": "ConfigurationEventFilter" }, { "tag": "Executor", - "discriminant": 10, + "discriminant": 9, "type": "ExecutorEventFilter" } ] @@ -1200,11 +1169,28 @@ } ] }, + "ExecutorDataModel": { + "Struct": [ + { + "name": "permissions", + "type": "SortedVec" + }, + { + "name": "parameters", + "type": "SortedVec" + }, + { + "name": "schema", + "type": "JsonString" + } + ] + }, "ExecutorEvent": { "Enum": [ { "tag": "Upgraded", - "discriminant": 0 + "discriminant": 0, + "type": "ExecutorUpgrade" } ] }, @@ -1227,6 +1213,14 @@ ] } }, + "ExecutorUpgrade": { + "Struct": [ + { + "name": "new_data_model", + "type": "ExecutorDataModel" + } + ] + }, "Fail": { "Struct": [ { @@ -1466,7 +1460,7 @@ { "tag": "Permission", "discriminant": 10, - "type": "Name" + "type": "PermissionId" }, { "tag": "Parameter", @@ -1480,7 +1474,7 @@ } ] }, - "FindPermissionSchema": null, + "FindExecutorDataModel": null, "FindPermissionsByAccountId": { "Struct": [ { @@ -1692,7 +1686,7 @@ { "tag": "PermissionId", "discriminant": 7, - "type": "Name" + "type": "PermissionId" }, { "tag": "ParameterId", @@ -1822,24 +1816,19 @@ "discriminant": 10, "type": "SetParameter" }, - { - "tag": "NewParameter", - "discriminant": 11, - "type": "NewParameter" - }, { "tag": "Upgrade", - "discriminant": 12, + "discriminant": 11, "type": "Upgrade" }, { "tag": "Log", - "discriminant": 13, + "discriminant": 12, "type": "Log" }, { "tag": "Fail", - "discriminant": 14, + "discriminant": 13, "type": "Fail" } ] @@ -1980,21 +1969,17 @@ "tag": "SetParameter", "discriminant": 10 }, - { - "tag": "NewParameter", - "discriminant": 11 - }, { "tag": "Upgrade", - "discriminant": 12 + "discriminant": 11 }, { "tag": "Log", - "discriminant": 13 + "discriminant": 12 }, { "tag": "Fail", - "discriminant": 14 + "discriminant": 13 } ] }, @@ -2019,18 +2004,6 @@ "Ipv4Addr": "Array", "Ipv6Addr": "Array", "JsonString": "String", - "LengthLimits": { - "Struct": [ - { - "name": "min", - "type": "u32" - }, - { - "name": "max", - "type": "u32" - } - ] - }, "Level": { "Enum": [ { @@ -2394,14 +2367,6 @@ } ] }, - "NewParameter": { - "Struct": [ - { - "name": "parameter", - "type": "Parameter" - } - ] - }, "NewRole": { "Struct": [ { @@ -2475,9 +2440,6 @@ "Option>": { "Option": "Option" }, - "Option": { - "Option": "ParameterId" - }, "Option": { "Option": "PeerId" }, @@ -2527,8 +2489,8 @@ "type": "ParameterId" }, { - "name": "val", - "type": "ParameterValueBox" + "name": "payload", + "type": "JsonString" } ] }, @@ -2540,30 +2502,6 @@ } ] }, - "ParameterValueBox": { - "Enum": [ - { - "tag": "TransactionLimits", - "discriminant": 0, - "type": "TransactionLimits" - }, - { - "tag": "MetadataLimits", - "discriminant": 1, - "type": "Limits" - }, - { - "tag": "LengthLimits", - "discriminant": 2, - "type": "LengthLimits" - }, - { - "tag": "Numeric", - "discriminant": 3, - "type": "Numeric" - } - ] - }, "Peer": { "Struct": [ { @@ -2628,8 +2566,8 @@ "Permission": { "Struct": [ { - "name": "definition_id", - "type": "Name" + "name": "id", + "type": "PermissionId" }, { "name": "payload", @@ -2637,27 +2575,11 @@ } ] }, - "PermissionSchema": { - "Struct": [ - { - "name": "token_ids", - "type": "Vec" - }, - { - "name": "schema", - "type": "String" - } - ] - }, - "PermissionSchemaUpdateEvent": { + "PermissionId": { "Struct": [ { - "name": "old_schema", - "type": "PermissionSchema" - }, - { - "name": "new_schema", - "type": "PermissionSchema" + "name": "name", + "type": "Name" } ] }, @@ -2848,55 +2770,55 @@ "discriminant": 28, "type": "FindPermissionsByAccountId" }, - { - "tag": "FindPermissionSchema", - "discriminant": 29, - "type": "FindPermissionSchema" - }, { "tag": "FindAllActiveTriggerIds", - "discriminant": 30, + "discriminant": 29, "type": "FindAllActiveTriggerIds" }, { "tag": "FindTriggerById", - "discriminant": 31, + "discriminant": 30, "type": "FindTriggerById" }, { "tag": "FindTriggerKeyValueByIdAndKey", - "discriminant": 32, + "discriminant": 31, "type": "FindTriggerKeyValueByIdAndKey" }, { "tag": "FindTriggersByDomainId", - "discriminant": 33, + "discriminant": 32, "type": "FindTriggersByDomainId" }, { "tag": "FindAllRoles", - "discriminant": 34, + "discriminant": 33, "type": "FindAllRoles" }, { "tag": "FindAllRoleIds", - "discriminant": 35, + "discriminant": 34, "type": "FindAllRoleIds" }, { "tag": "FindRoleByRoleId", - "discriminant": 36, + "discriminant": 35, "type": "FindRoleByRoleId" }, { "tag": "FindRolesByAccountId", - "discriminant": 37, + "discriminant": 36, "type": "FindRolesByAccountId" }, { "tag": "FindAllParameters", - "discriminant": 38, + "discriminant": 37, "type": "FindAllParameters" + }, + { + "tag": "FindExecutorDataModel", + "discriminant": 38, + "type": "FindExecutorDataModel" } ] }, @@ -2953,31 +2875,31 @@ "discriminant": 3, "type": "Permission" }, - { - "tag": "PermissionSchema", - "discriminant": 4, - "type": "PermissionSchema" - }, { "tag": "LimitedMetadata", - "discriminant": 5, + "discriminant": 4, "type": "MetadataValueBox" }, { "tag": "Numeric", - "discriminant": 6, + "discriminant": 5, "type": "Numeric" }, { "tag": "BlockHeader", - "discriminant": 7, + "discriminant": 6, "type": "BlockHeader" }, { "tag": "Block", - "discriminant": 8, + "discriminant": 7, "type": "SignedBlock" }, + { + "tag": "ExecutorDataModel", + "discriminant": 8, + "type": "ExecutorDataModel" + }, { "tag": "Vec", "discriminant": 9, @@ -3369,7 +3291,7 @@ }, { "name": "permission_id", - "type": "Name" + "type": "PermissionId" } ] }, @@ -3718,9 +3640,15 @@ "value": "MetadataValueBox" } }, + "SortedVec": { + "Vec": "ParameterId" + }, "SortedVec": { "Vec": "Permission" }, + "SortedVec": { + "Vec": "PermissionId" + }, "SortedVec>": { "Vec": "SignatureOf" }, @@ -3822,18 +3750,6 @@ } ] }, - "TransactionLimits": { - "Struct": [ - { - "name": "max_instruction_number", - "type": "u64" - }, - { - "name": "max_wasm_size_bytes", - "type": "u64" - } - ] - }, "TransactionPayload": { "Struct": [ { @@ -4364,9 +4280,6 @@ "Vec": { "Vec": "MetadataValueBox" }, - "Vec": { - "Vec": "Name" - }, "Vec": { "Vec": "PeerId" }, diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index ff6b9888da7..bfae67733b1 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -137,6 +137,8 @@ types!( ExecutorEvent, ExecutorEventFilter, ExecutorEventSet, + ExecutorUpgrade, + ExecutorDataModel, Fail, EventFilterBox, FetchSize, @@ -170,7 +172,6 @@ types!( FindDomainById, FindDomainKeyValueByIdAndKey, FindError, - FindPermissionSchema, FindPermissionsByAccountId, FindRoleByRoleId, FindRolesByAccountId, @@ -224,7 +225,6 @@ types!( NewAccount, NewAssetDefinition, NewDomain, - NewParameter, NewRole, NonTrivial, NonZeroU32, @@ -247,7 +247,6 @@ types!( Option, Option, Option>, - Option, Option, Option, Option, @@ -259,7 +258,6 @@ types!( Pagination, Parameter, ParameterId, - ParameterValueBox, Peer, PeerEvent, PeerEventFilter, @@ -267,8 +265,6 @@ types!( PeerId, RolePermissionChanged, Permission, - PermissionSchema, - PermissionSchemaUpdateEvent, PipelineEventBox, PipelineEventFilterBox, PredicateBox, @@ -396,16 +392,17 @@ types!( u8, ); -#[cfg(test)] -mod tests { - use core::num::{NonZeroU32, NonZeroU64}; - use std::{ +pub mod complete_data_model { + //! Complete set of types participating in the schema + + pub use core::num::{NonZeroU32, NonZeroU64}; + pub use std::{ collections::{BTreeMap, BTreeSet, HashMap, HashSet}, time::Duration, }; - use iroha_crypto::*; - use iroha_data_model::{ + pub use iroha_crypto::*; + pub use iroha_data_model::{ account::NewAccount, asset::NewAssetDefinition, block::{ @@ -425,8 +422,6 @@ mod tests { InstructionType, }, metadata::{MetadataError, MetadataValueBox, SizeError}, - parameter::ParameterValueBox, - permission::JsonString, prelude::*, query::{ error::{FindError, QueryExecutionFail}, @@ -442,17 +437,20 @@ mod tests { error::TransactionLimitError, SignedTransactionV1, TransactionLimits, TransactionPayload, }, - BatchedResponse, BatchedResponseV1, Level, + BatchedResponse, BatchedResponseV1, JsonString, Level, }; - use iroha_primitives::{ + pub use iroha_primitives::{ addr::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrHost, SocketAddrV4, SocketAddrV6}, const_vec::ConstVec, conststr::ConstString, unique_vec::UniqueVec, }; - use iroha_schema::Compact; + pub use iroha_schema::Compact; +} - use super::IntoSchema; +#[cfg(test)] +mod tests { + use super::{complete_data_model::*, IntoSchema}; fn is_const_generic(generic: &str) -> bool { generic.parse::().is_ok() diff --git a/smart_contract/executor/derive/src/default.rs b/smart_contract/executor/derive/src/default.rs index 14b58d0bd73..492fe0787f2 100644 --- a/smart_contract/executor/derive/src/default.rs +++ b/smart_contract/executor/derive/src/default.rs @@ -142,21 +142,20 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn::DeriveInput) -> Tok "fn visit_transfer_asset_definition(operation: &Transfer)", "fn visit_set_asset_definition_key_value(operation: &SetKeyValue)", "fn visit_remove_asset_definition_key_value(operation: &RemoveKeyValue)", - "fn visit_grant_account_permission(operation: &Grant)", - "fn visit_revoke_account_permission(operation: &Revoke)", + "fn visit_grant_account_permission(operation: &Grant)", + "fn visit_revoke_account_permission(operation: &Revoke)", "fn visit_register_role(operation: &Register)", "fn visit_unregister_role(operation: &Unregister)", "fn visit_grant_account_role(operation: &Grant)", "fn visit_revoke_account_role(operation: &Revoke)", - "fn visit_grant_role_permission(operation: &Grant)", - "fn visit_revoke_role_permission(operation: &Revoke)", + "fn visit_grant_role_permission(operation: &Grant)", + "fn visit_revoke_role_permission(operation: &Revoke)", "fn visit_register_trigger(operation: &Register)", "fn visit_unregister_trigger(operation: &Unregister)", "fn visit_mint_trigger_repetitions(operation: &Mint)", "fn visit_burn_trigger_repetitions(operation: &Burn)", "fn visit_execute_trigger(operation: &ExecuteTrigger)", "fn visit_set_parameter(operation: &SetParameter)", - "fn visit_new_parameter(operation: &NewParameter)", "fn visit_upgrade(operation: &Upgrade)", "fn visit_log(operation: &Log)", "fn visit_fail(operation: &Fail)", diff --git a/smart_contract/executor/derive/src/entrypoint.rs b/smart_contract/executor/derive/src/entrypoint.rs index 84b685bd64e..646fb0c281f 100644 --- a/smart_contract/executor/derive/src/entrypoint.rs +++ b/smart_contract/executor/derive/src/entrypoint.rs @@ -144,6 +144,7 @@ fn impl_migrate_entrypoint(fn_item: syn::ItemFn) -> TokenStream { let migrate_fn_name = syn::Ident::new(export::EXECUTOR_MIGRATE, proc_macro2::Span::call_site()); + // FIXME: is doc accurate? quote! { /// Executor `permission_schema` entrypoint /// diff --git a/smart_contract/executor/derive/src/lib.rs b/smart_contract/executor/derive/src/lib.rs index fa4a4318c8b..7a431e3d15b 100644 --- a/smart_contract/executor/derive/src/lib.rs +++ b/smart_contract/executor/derive/src/lib.rs @@ -7,7 +7,7 @@ use proc_macro2::TokenStream; mod conversion; mod default; mod entrypoint; -mod token; +mod permission; mod validate; /// Annotate the user-defined function that starts the execution of a executor. @@ -64,14 +64,14 @@ pub fn entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { emitter.finish_token_stream_with(result) } -/// Derive macro for `Token` trait. +/// Derive macro for `Permission` trait. /// /// # Example /// /// ```ignore /// use iroha_executor::{permission, prelude::*}; /// -/// #[derive(Token, ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] +/// #[derive(Permission, ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] /// #[validate(permission::asset::Owner)] /// struct CanDoSomethingWithAsset { /// some_data: String, @@ -93,11 +93,11 @@ pub fn entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[manyhow] -#[proc_macro_derive(Token)] -pub fn derive_token(input: TokenStream) -> Result { +#[proc_macro_derive(Permission)] +pub fn derive_permission(input: TokenStream) -> Result { let input = syn::parse2(input)?; - Ok(token::impl_derive_token(&input)) + Ok(permission::impl_derive_permission(&input)) } /// Derive macro for `ValidateGrantRevoke` trait. @@ -149,13 +149,13 @@ pub fn derive_token(input: TokenStream) -> Result { /// /// # Example /// -/// See [`Token`] derive macro example. +/// See [`Permission`] derive macro example. // // TODO: Add combinators (#3255). // Example: // // ``` -// #[derive(Token, ValidateGrantRevoke)] +// #[derive(Permission, ValidateGrantRevoke)] // #[validate(Creator || Admin)] // pub struct CanDoSomethingWithAsset { // ... diff --git a/smart_contract/executor/derive/src/permission.rs b/smart_contract/executor/derive/src/permission.rs new file mode 100644 index 00000000000..82c6a51fb14 --- /dev/null +++ b/smart_contract/executor/derive/src/permission.rs @@ -0,0 +1,34 @@ +//! Module with [`derive_permission`](crate::derive_permission) macro implementation + +use proc_macro2::TokenStream; +use quote::quote; + +/// [`derive_permission`](crate::derive_permission()) macro implementation +pub fn impl_derive_permission(input: &syn::DeriveInput) -> TokenStream { + let generics = &input.generics; + let ident = &input.ident; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics ::iroha_executor::permission::Permission for #ident #ty_generics #where_clause { + fn is_owned_by(&self, account_id: &::iroha_executor::data_model::account::AccountId) -> bool { + let account_tokens_cursor = + ::iroha_executor::smart_contract::ExecuteQueryOnHost::execute( + &::iroha_executor::data_model::query::permission::FindPermissionsByAccountId::new( + account_id.clone(), + ) + ) + .expect("`FindPermissionsByAccountId` query should never fail, it's a bug"); + + account_tokens_cursor + .into_iter() + .map(|res| ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( + res, + "Failed to get permission token from cursor" + )) + .filter_map(|token| Self::try_from_object(&token).ok()) + .any(|token| self == &token) + } + } + } +} diff --git a/smart_contract/executor/derive/src/token.rs b/smart_contract/executor/derive/src/token.rs deleted file mode 100644 index c2ca5adea29..00000000000 --- a/smart_contract/executor/derive/src/token.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Module with [`derive_token`](crate::derive_token) macro implementation - -use proc_macro2::TokenStream; -use quote::quote; - -/// [`derive_token`](crate::derive_token()) macro implementation -pub fn impl_derive_token(input: &syn::DeriveInput) -> TokenStream { - let generics = &input.generics; - let ident = &input.ident; - - let impl_token = impl_token(ident, generics); - let impl_try_from_permission = impl_try_from_permission(ident, generics); - - quote! { - #impl_token - #impl_try_from_permission - } -} - -fn impl_token(ident: &syn::Ident, generics: &syn::Generics) -> proc_macro2::TokenStream { - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - quote! { - impl #impl_generics ::iroha_executor::permission::Token for #ident #ty_generics #where_clause { - fn is_owned_by(&self, account_id: &::iroha_executor::data_model::account::AccountId) -> bool { - let account_tokens_cursor = ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( - ::iroha_executor::smart_contract::ExecuteQueryOnHost::execute( - &::iroha_executor::data_model::query::permission::FindPermissionsByAccountId::new( - account_id.clone(), - ) - ), - "Failed to execute `FindPermissionsByAccountId` query" - ); - - account_tokens_cursor - .into_iter() - .map(|res| ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( - res, - "Failed to get permission token from cursor" - )) - .filter_map(|token| Self::try_from(&token).ok()) - .any(|token| self == &token) - } - } - } -} - -fn impl_try_from_permission(ident: &syn::Ident, generics: &syn::Generics) -> TokenStream { - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let token_id = quote! { <#ident #ty_generics as ::iroha_executor::permission::Token>::name() }; - - quote! { - impl #impl_generics ::core::convert::TryFrom<&::iroha_executor::data_model::permission::Permission> for #ident #ty_generics #where_clause { - type Error = ::iroha_executor::permission::PermissionConversionError; - - fn try_from(token: &::iroha_executor::data_model::permission::Permission) -> ::core::result::Result { - if #token_id != *token.definition_id() { - return Err(::iroha_executor::permission::PermissionConversionError::Id( - ToOwned::to_owned(token.definition_id()) - )); - } - ::serde_json::from_str::(token.payload()) - .map_err(::iroha_executor::permission::PermissionConversionError::Deserialize) - } - } - - impl #impl_generics ::core::convert::From<#ident #ty_generics> for ::iroha_executor::data_model::permission::Permission #where_clause { - fn from(token: #ident #ty_generics) -> Self { - let definition_id = #token_id; - - let payload = ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( - ::serde_json::to_value::<#ident #ty_generics>(token), - "failed to serialize concrete permission token type. This is a bug." - ); - - ::iroha_executor::data_model::permission::Permission::new(definition_id, &payload) - } - } - } -} diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index e2a4e827b9e..5d583ce5a00 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -1,7 +1,7 @@ //! Definition of Iroha default executor and accompanying validation functions #![allow(missing_docs, clippy::missing_errors_doc)] -pub mod tokens; +pub mod permissions; use alloc::format; @@ -27,9 +27,10 @@ pub use executor::visit_upgrade; pub use fail::visit_fail; use iroha_smart_contract::data_model::isi::InstructionBox; pub use log::visit_log; -pub use parameter::{visit_new_parameter, visit_set_parameter}; +pub use parameter::visit_set_parameter; pub use peer::{visit_register_peer, visit_unregister_peer}; pub use permission::{visit_grant_account_permission, visit_revoke_account_permission}; +use permissions::AnyPermission; pub use role::{ visit_grant_account_role, visit_grant_role_permission, visit_register_role, visit_revoke_account_role, visit_revoke_role_permission, visit_unregister_role, @@ -40,21 +41,7 @@ pub use trigger::{ visit_unregister_trigger, }; -use crate::{permission::Token as _, prelude::*}; - -pub fn default_permission_schema() -> PermissionSchema { - let mut schema = iroha_executor::PermissionSchema::default(); - - macro_rules! add_to_schema { - ($token_ty:ty) => { - schema.insert::<$token_ty>(); - }; - } - - tokens::map_token_type!(add_to_schema); - - schema -} +use crate::{permission::Permission as _, prelude::*}; // NOTE: If any new `visit_..` functions are introduced in this module, one should // not forget to update the default executor boilerplate too, specifically the @@ -95,9 +82,6 @@ pub fn visit_instruction( isi: &InstructionBox, ) { match isi { - InstructionBox::NewParameter(isi) => { - executor.visit_new_parameter(authority, isi); - } InstructionBox::SetParameter(isi) => { executor.visit_set_parameter(authority, isi); } @@ -163,7 +147,7 @@ pub mod peer { if is_genesis(executor) { execute!(executor, isi); } - if tokens::peer::CanUnregisterAnyPeer.is_owned_by(authority) { + if permissions::peer::CanUnregisterAnyPeer.is_owned_by(authority) { execute!(executor, isi); } @@ -172,8 +156,7 @@ pub mod peer { } pub mod domain { - use iroha_smart_contract::data_model::{domain::DomainId, permission::Permission}; - use tokens::AnyPermission; + use iroha_smart_contract::data_model::domain::DomainId; use super::*; use crate::permission::{ @@ -201,7 +184,7 @@ pub mod domain { Ok(is_domain_owner) => is_domain_owner, } || { - let can_unregister_domain_token = tokens::domain::CanUnregisterDomain { + let can_unregister_domain_token = permissions::domain::CanUnregisterDomain { domain_id: domain_id.clone(), }; can_unregister_domain_token.is_owned_by(authority) @@ -268,7 +251,7 @@ pub mod domain { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_domain_token = tokens::domain::CanSetKeyValueInDomain { + let can_set_key_value_in_domain_token = permissions::domain::CanSetKeyValueInDomain { domain_id: domain_id.clone(), }; if can_set_key_value_in_domain_token.is_owned_by(authority) { @@ -293,7 +276,7 @@ pub mod domain { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_remove_key_value_in_domain_token = tokens::domain::CanRemoveKeyValueInDomain { + let can_remove_key_value_in_domain_token = permissions::domain::CanRemoveKeyValueInDomain { domain_id: domain_id.clone(), }; if can_remove_key_value_in_domain_token.is_owned_by(authority) { @@ -304,7 +287,7 @@ pub mod domain { } #[allow(clippy::too_many_lines)] - fn is_token_domain_associated(permission: &Permission, domain_id: &DomainId) -> bool { + fn is_token_domain_associated(permission: &PermissionObject, domain_id: &DomainId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -421,9 +404,6 @@ pub mod domain { } pub mod account { - use iroha_smart_contract::data_model::permission::Permission; - use tokens::AnyPermission; - use super::*; use crate::permission::{account::is_account_owner, accounts_permissions, roles_permissions}; @@ -440,7 +420,7 @@ pub mod account { Ok(false) => {} } - let can_register_account_in_domain = tokens::domain::CanRegisterAccountInDomain { + let can_register_account_in_domain = permissions::domain::CanRegisterAccountInDomain { domain_id: domain_id.clone(), }; if can_register_account_in_domain.is_owned_by(authority) { @@ -466,7 +446,7 @@ pub mod account { Ok(is_account_owner) => is_account_owner, } || { - let can_unregister_user_account = tokens::account::CanUnregisterAccount { + let can_unregister_user_account = permissions::account::CanUnregisterAccount { account_id: account_id.clone(), }; can_unregister_user_account.is_owned_by(authority) @@ -508,9 +488,10 @@ pub mod account { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_user_account_token = tokens::account::CanSetKeyValueInAccount { - account_id: account_id.clone(), - }; + let can_set_key_value_in_user_account_token = + permissions::account::CanSetKeyValueInAccount { + account_id: account_id.clone(), + }; if can_set_key_value_in_user_account_token.is_owned_by(authority) { execute!(executor, isi); } @@ -537,7 +518,7 @@ pub mod account { Ok(false) => {} } let can_remove_key_value_in_user_account_token = - tokens::account::CanRemoveKeyValueInAccount { + permissions::account::CanRemoveKeyValueInAccount { account_id: account_id.clone(), }; if can_remove_key_value_in_user_account_token.is_owned_by(authority) { @@ -550,7 +531,7 @@ pub mod account { ); } - fn is_token_account_associated(permission: &Permission, account_id: &AccountId) -> bool { + fn is_token_account_associated(permission: &PermissionObject, account_id: &AccountId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -627,8 +608,7 @@ pub mod account { } pub mod asset_definition { - use iroha_smart_contract::data_model::{asset::AssetDefinitionId, permission::Permission}; - use tokens::AnyPermission; + use iroha_smart_contract::data_model::asset::AssetDefinitionId; use super::*; use crate::permission::{ @@ -650,7 +630,7 @@ pub mod asset_definition { } let can_register_asset_definition_in_domain_token = - tokens::domain::CanRegisterAssetDefinitionInDomain { + permissions::domain::CanRegisterAssetDefinitionInDomain { domain_id: domain_id.clone(), }; if can_register_asset_definition_in_domain_token.is_owned_by(authority) { @@ -677,7 +657,7 @@ pub mod asset_definition { } || { let can_unregister_asset_definition_token = - tokens::asset_definition::CanUnregisterAssetDefinition { + permissions::asset_definition::CanUnregisterAssetDefinition { asset_definition_id: asset_definition_id.clone(), }; can_unregister_asset_definition_token.is_owned_by(authority) @@ -751,7 +731,7 @@ pub mod asset_definition { Ok(false) => {} } let can_set_key_value_in_asset_definition_token = - tokens::asset_definition::CanSetKeyValueInAssetDefinition { + permissions::asset_definition::CanSetKeyValueInAssetDefinition { asset_definition_id: asset_definition_id.clone(), }; if can_set_key_value_in_asset_definition_token.is_owned_by(authority) { @@ -780,7 +760,7 @@ pub mod asset_definition { Ok(false) => {} } let can_remove_key_value_in_asset_definition_token = - tokens::asset_definition::CanRemoveKeyValueInAssetDefinition { + permissions::asset_definition::CanRemoveKeyValueInAssetDefinition { asset_definition_id: asset_definition_id.clone(), }; if can_remove_key_value_in_asset_definition_token.is_owned_by(authority) { @@ -794,7 +774,7 @@ pub mod asset_definition { } fn is_token_asset_definition_associated( - permission: &Permission, + permission: &PermissionObject, asset_definition_id: &AssetDefinitionId, ) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { @@ -899,7 +879,7 @@ pub mod asset { Ok(false) => {} } let can_register_assets_with_definition_token = - tokens::asset::CanRegisterAssetWithDefinition { + permissions::asset::CanRegisterAssetWithDefinition { asset_definition_id: asset.id().definition_id().clone(), }; if can_register_assets_with_definition_token.is_owned_by(authority) { @@ -933,13 +913,13 @@ pub mod asset { Ok(false) => {} } let can_unregister_assets_with_definition_token = - tokens::asset::CanUnregisterAssetWithDefinition { + permissions::asset::CanUnregisterAssetWithDefinition { asset_definition_id: asset_id.definition_id().clone(), }; if can_unregister_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_unregister_user_asset_token = tokens::asset::CanUnregisterUserAsset { + let can_unregister_user_asset_token = permissions::asset::CanUnregisterUserAsset { asset_id: asset_id.clone(), }; if can_unregister_user_asset_token.is_owned_by(authority) { @@ -964,13 +944,14 @@ pub mod asset { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_assets_with_definition_token = tokens::asset::CanMintAssetWithDefinition { - asset_definition_id: asset_id.definition_id().clone(), - }; + let can_mint_assets_with_definition_token = + permissions::asset::CanMintAssetWithDefinition { + asset_definition_id: asset_id.definition_id().clone(), + }; if can_mint_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_mint_user_asset_token = tokens::asset::CanMintUserAsset { + let can_mint_user_asset_token = permissions::asset::CanMintUserAsset { asset_id: asset_id.clone(), }; if can_mint_user_asset_token.is_owned_by(authority) { @@ -1011,13 +992,14 @@ pub mod asset { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_burn_assets_with_definition_token = tokens::asset::CanBurnAssetWithDefinition { - asset_definition_id: asset_id.definition_id().clone(), - }; + let can_burn_assets_with_definition_token = + permissions::asset::CanBurnAssetWithDefinition { + asset_definition_id: asset_id.definition_id().clone(), + }; if can_burn_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_burn_user_asset_token = tokens::asset::CanBurnUserAsset { + let can_burn_user_asset_token = permissions::asset::CanBurnUserAsset { asset_id: asset_id.clone(), }; if can_burn_user_asset_token.is_owned_by(authority) { @@ -1059,13 +1041,13 @@ pub mod asset { Ok(false) => {} } let can_transfer_assets_with_definition_token = - tokens::asset::CanTransferAssetWithDefinition { + permissions::asset::CanTransferAssetWithDefinition { asset_definition_id: asset_id.definition_id().clone(), }; if can_transfer_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_transfer_user_asset_token = tokens::asset::CanTransferUserAsset { + let can_transfer_user_asset_token = permissions::asset::CanTransferUserAsset { asset_id: asset_id.clone(), }; if can_transfer_user_asset_token.is_owned_by(authority) { @@ -1107,7 +1089,7 @@ pub mod asset { Ok(false) => {} } - let can_set_key_value_in_user_asset_token = tokens::asset::CanSetKeyValueInUserAsset { + let can_set_key_value_in_user_asset_token = permissions::asset::CanSetKeyValueInUserAsset { asset_id: asset_id.clone(), }; if can_set_key_value_in_user_asset_token.is_owned_by(authority) { @@ -1136,7 +1118,7 @@ pub mod asset { Ok(false) => {} } let can_remove_key_value_in_user_asset_token = - tokens::asset::CanRemoveKeyValueInUserAsset { + permissions::asset::CanRemoveKeyValueInUserAsset { asset_id: asset_id.clone(), }; if can_remove_key_value_in_user_asset_token.is_owned_by(authority) { @@ -1153,25 +1135,6 @@ pub mod asset { pub mod parameter { use super::*; - #[allow(clippy::needless_pass_by_value)] - pub fn visit_new_parameter( - executor: &mut V, - authority: &AccountId, - isi: &NewParameter, - ) { - if is_genesis(executor) { - execute!(executor, isi); - } - if tokens::parameter::CanCreateParameters.is_owned_by(authority) { - execute!(executor, isi); - } - - deny!( - executor, - "Can't create new configuration parameters outside genesis without permission" - ); - } - #[allow(clippy::needless_pass_by_value)] pub fn visit_set_parameter( executor: &mut V, @@ -1181,20 +1144,20 @@ pub mod parameter { if is_genesis(executor) { execute!(executor, isi); } - if tokens::parameter::CanSetParameters.is_owned_by(authority) { + if permissions::parameter::CanSetParameters.is_owned_by(authority) { execute!(executor, isi); } deny!( executor, - "Can't set configuration parameters without permission" + "Can't set executor configuration parameters without permission" ); } } pub mod role { use iroha_smart_contract::data_model::role::Role; - use role::tokens::AnyPermission; + use role::permissions::AnyPermission; use super::*; @@ -1242,7 +1205,7 @@ pub mod role { let token = $isi.object(); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = Permission::from(any_token.clone()); + let token = PermissionObject::from(any_token.clone()); let isi = <$isi_type>::role_permission(token, role_id); if is_genesis($executor) { execute!($executor, isi); @@ -1280,7 +1243,7 @@ pub mod role { iroha_smart_contract::debug!(&format!("Checking `{token:?}`")); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = Permission::from(any_token); + let token = PermissionObject::from(any_token); new_role = new_role.add_permission(token); continue; } @@ -1310,7 +1273,7 @@ pub mod role { if is_genesis(executor) { execute!(executor, isi); } - if tokens::role::CanUnregisterAnyRole.is_owned_by(authority) { + if permissions::role::CanUnregisterAnyRole.is_owned_by(authority) { execute!(executor, isi); } @@ -1336,28 +1299,29 @@ pub mod role { pub fn visit_grant_role_permission( executor: &mut V, authority: &AccountId, - isi: &Grant, + isi: &Grant, ) { - impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_grant, Grant); + impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_grant, Grant); } pub fn visit_revoke_role_permission( executor: &mut V, authority: &AccountId, - isi: &Revoke, + isi: &Revoke, ) { - impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_revoke, Revoke); + impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_revoke, Revoke); } } pub mod trigger { - use iroha_executor::permission::trigger::find_trigger; - use iroha_smart_contract::data_model::{permission::Permission, trigger::Trigger}; - use tokens::AnyPermission; + use iroha_smart_contract::data_model::trigger::Trigger; use super::*; use crate::permission::{ - accounts_permissions, domain::is_domain_owner, roles_permissions, trigger::is_trigger_owner, + accounts_permissions, + domain::is_domain_owner, + roles_permissions, + trigger::{find_trigger, is_trigger_owner}, }; pub fn visit_register_trigger( @@ -1375,9 +1339,10 @@ pub mod trigger { } } || { - let can_register_user_trigger_token = tokens::trigger::CanRegisterUserTrigger { - account_id: isi.object().action().authority().clone(), - }; + let can_register_user_trigger_token = + permissions::trigger::CanRegisterUserTrigger { + account_id: isi.object().action().authority().clone(), + }; can_register_user_trigger_token.is_owned_by(authority) } { @@ -1399,13 +1364,14 @@ pub mod trigger { Ok(is_trigger_owner) => is_trigger_owner, } || { - let can_unregister_user_trigger_token = tokens::trigger::CanUnregisterUserTrigger { - account_id: find_trigger(trigger_id) - .unwrap() - .action() - .authority() - .clone(), - }; + let can_unregister_user_trigger_token = + permissions::trigger::CanUnregisterUserTrigger { + account_id: find_trigger(trigger_id) + .unwrap() + .action() + .authority() + .clone(), + }; can_unregister_user_trigger_token.is_owned_by(authority) } { @@ -1448,7 +1414,7 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_trigger_token = tokens::trigger::CanMintUserTrigger { + let can_mint_user_trigger_token = permissions::trigger::CanMintUserTrigger { trigger_id: trigger_id.clone(), }; if can_mint_user_trigger_token.is_owned_by(authority) { @@ -1476,7 +1442,7 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_trigger_token = tokens::trigger::CanBurnUserTrigger { + let can_mint_user_trigger_token = permissions::trigger::CanBurnUserTrigger { trigger_id: trigger_id.clone(), }; if can_mint_user_trigger_token.is_owned_by(authority) { @@ -1504,7 +1470,7 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_execute_trigger_token = tokens::trigger::CanExecuteUserTrigger { + let can_execute_trigger_token = permissions::trigger::CanExecuteUserTrigger { trigger_id: trigger_id.clone(), }; if can_execute_trigger_token.is_owned_by(authority) { @@ -1529,9 +1495,10 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_user_trigger_token = tokens::trigger::CanSetKeyValueInTrigger { - trigger_id: trigger_id.clone(), - }; + let can_set_key_value_in_user_trigger_token = + permissions::trigger::CanSetKeyValueInTrigger { + trigger_id: trigger_id.clone(), + }; if can_set_key_value_in_user_trigger_token.is_owned_by(authority) { execute!(executor, isi); } @@ -1557,9 +1524,10 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_remove_key_value_in_trigger_token = tokens::trigger::CanRemoveKeyValueInTrigger { - trigger_id: trigger_id.clone(), - }; + let can_remove_key_value_in_trigger_token = + permissions::trigger::CanRemoveKeyValueInTrigger { + trigger_id: trigger_id.clone(), + }; if can_remove_key_value_in_trigger_token.is_owned_by(authority) { execute!(executor, isi); } @@ -1570,7 +1538,7 @@ pub mod trigger { ); } - fn is_token_trigger_associated(permission: &Permission, trigger_id: &TriggerId) -> bool { + fn is_token_trigger_associated(permission: &PermissionObject, trigger_id: &TriggerId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -1627,8 +1595,6 @@ pub mod trigger { } pub mod permission { - use tokens::AnyPermission; - use super::*; macro_rules! impl_validate { @@ -1637,7 +1603,7 @@ pub mod permission { let token = $isi.object(); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = Permission::from(any_token.clone()); + let token = PermissionObject::from(any_token.clone()); let isi = <$isi_type>::permission(token, account_id); if is_genesis($executor) { execute!($executor, isi); @@ -1663,28 +1629,28 @@ pub mod permission { pub fn visit_grant_account_permission( executor: &mut V, authority: &AccountId, - isi: &Grant, + isi: &Grant, ) { impl_validate!( executor, authority, isi, validate_grant, - Grant + Grant ); } pub fn visit_revoke_account_permission( executor: &mut V, authority: &AccountId, - isi: &Revoke, + isi: &Revoke, ) { impl_validate!( executor, authority, isi, validate_revoke, - Revoke + Revoke ); } } @@ -1701,7 +1667,7 @@ pub mod executor { if is_genesis(executor) { execute!(executor, isi); } - if tokens::executor::CanUpgradeExecutor.is_owned_by(authority) { + if permissions::executor::CanUpgradeExecutor.is_owned_by(authority) { execute!(executor, isi); } diff --git a/smart_contract/executor/src/default/tokens.rs b/smart_contract/executor/src/default/permissions.rs similarity index 77% rename from smart_contract/executor/src/default/tokens.rs rename to smart_contract/executor/src/default/permissions.rs index 283b48858ae..8406a9afe2c 100644 --- a/smart_contract/executor/src/default/tokens.rs +++ b/smart_contract/executor/src/default/permissions.rs @@ -6,7 +6,7 @@ use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; use iroha_executor_derive::ValidateGrantRevoke; use iroha_smart_contract::data_model::{executor::Result, prelude::*}; -use crate::permission::{self, Token as _}; +use crate::permission::{self, Permission as _}; /// Declare token types of current module. Use it with a full path to the token. /// Used to iterate over tokens to validate `Grant` and `Revoke` instructions. @@ -27,9 +27,9 @@ use crate::permission::{self, Token as _}; /// pub struct MyToken; /// } /// ``` -macro_rules! declare_tokens { +macro_rules! declare_permissions { ($($($token_path:ident ::)+ { $token_ty:ident }),+ $(,)?) => { - macro_rules! map_token_type { + macro_rules! map_default_permissions { ($callback:ident) => { $( $callback!($($token_path::)+$token_ty); )+ }; @@ -43,15 +43,15 @@ macro_rules! declare_tokens { } impl TryFrom<&$crate::data_model::permission::Permission> for AnyPermission { - type Error = $crate::permission::PermissionConversionError; + type Error = $crate::TryFromDataModelObjectError; fn try_from(token: &$crate::data_model::permission::Permission) -> Result { - match token.definition_id().as_ref() { $( + match token.id().name().as_ref() { $( stringify!($token_ty) => { - let token = <$($token_path::)+$token_ty>::try_from(token)?; + let token = <$($token_path::)+$token_ty>::try_from_object(token)?; Ok(Self::$token_ty(token)) } )+ - _ => Err(Self::Error::Id(token.definition_id().clone())) + _ => Err(Self::Error::Id(token.id().name().clone())) } } } @@ -59,7 +59,7 @@ macro_rules! declare_tokens { impl From for $crate::data_model::permission::Permission { fn from(token: AnyPermission) -> Self { match token { $( - AnyPermission::$token_ty(token) => Self::from(token), )* + AnyPermission::$token_ty(token) => token.to_object(), )* } } } @@ -78,76 +78,76 @@ macro_rules! declare_tokens { } } - pub(crate) use map_token_type; + pub(crate) use map_default_permissions; }; } -macro_rules! token { +macro_rules! permission { ($($meta:meta)* $item:item) => { #[derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)] - #[derive(Clone, iroha_executor_derive::Token)] + #[derive(Clone, iroha_executor_derive::Permission)] #[derive(iroha_schema::IntoSchema)] $($meta)* $item }; } -declare_tokens! { - crate::default::tokens::peer::{CanUnregisterAnyPeer}, - - crate::default::tokens::domain::{CanUnregisterDomain}, - crate::default::tokens::domain::{CanSetKeyValueInDomain}, - crate::default::tokens::domain::{CanRemoveKeyValueInDomain}, - crate::default::tokens::domain::{CanRegisterAccountInDomain}, - crate::default::tokens::domain::{CanRegisterAssetDefinitionInDomain}, - - crate::default::tokens::account::{CanUnregisterAccount}, - crate::default::tokens::account::{CanMintUserPublicKeys}, - crate::default::tokens::account::{CanBurnUserPublicKeys}, - crate::default::tokens::account::{CanMintUserSignatureCheckConditions}, - crate::default::tokens::account::{CanSetKeyValueInAccount}, - crate::default::tokens::account::{CanRemoveKeyValueInAccount}, - - crate::default::tokens::asset_definition::{CanUnregisterAssetDefinition}, - crate::default::tokens::asset_definition::{CanSetKeyValueInAssetDefinition}, - crate::default::tokens::asset_definition::{CanRemoveKeyValueInAssetDefinition}, - - crate::default::tokens::asset::{CanRegisterAssetWithDefinition}, - crate::default::tokens::asset::{CanUnregisterAssetWithDefinition}, - crate::default::tokens::asset::{CanUnregisterUserAsset}, - crate::default::tokens::asset::{CanBurnAssetWithDefinition}, - crate::default::tokens::asset::{CanMintAssetWithDefinition}, - crate::default::tokens::asset::{CanMintUserAsset}, - crate::default::tokens::asset::{CanBurnUserAsset}, - crate::default::tokens::asset::{CanTransferAssetWithDefinition}, - crate::default::tokens::asset::{CanTransferUserAsset}, - crate::default::tokens::asset::{CanSetKeyValueInUserAsset}, - crate::default::tokens::asset::{CanRemoveKeyValueInUserAsset}, - - crate::default::tokens::parameter::{CanGrantPermissionToCreateParameters}, - crate::default::tokens::parameter::{CanRevokePermissionToCreateParameters}, - crate::default::tokens::parameter::{CanCreateParameters}, - crate::default::tokens::parameter::{CanGrantPermissionToSetParameters}, - crate::default::tokens::parameter::{CanRevokePermissionToSetParameters}, - crate::default::tokens::parameter::{CanSetParameters}, - - crate::default::tokens::role::{CanUnregisterAnyRole}, - - crate::default::tokens::trigger::{CanRegisterUserTrigger}, - crate::default::tokens::trigger::{CanExecuteUserTrigger}, - crate::default::tokens::trigger::{CanUnregisterUserTrigger}, - crate::default::tokens::trigger::{CanMintUserTrigger}, - crate::default::tokens::trigger::{CanBurnUserTrigger}, - crate::default::tokens::trigger::{CanSetKeyValueInTrigger}, - crate::default::tokens::trigger::{CanRemoveKeyValueInTrigger}, - - crate::default::tokens::executor::{CanUpgradeExecutor}, +declare_permissions! { + crate::default::permissions::peer::{CanUnregisterAnyPeer}, + + crate::default::permissions::domain::{CanUnregisterDomain}, + crate::default::permissions::domain::{CanSetKeyValueInDomain}, + crate::default::permissions::domain::{CanRemoveKeyValueInDomain}, + crate::default::permissions::domain::{CanRegisterAccountInDomain}, + crate::default::permissions::domain::{CanRegisterAssetDefinitionInDomain}, + + crate::default::permissions::account::{CanUnregisterAccount}, + crate::default::permissions::account::{CanMintUserPublicKeys}, + crate::default::permissions::account::{CanBurnUserPublicKeys}, + crate::default::permissions::account::{CanMintUserSignatureCheckConditions}, + crate::default::permissions::account::{CanSetKeyValueInAccount}, + crate::default::permissions::account::{CanRemoveKeyValueInAccount}, + + crate::default::permissions::asset_definition::{CanUnregisterAssetDefinition}, + crate::default::permissions::asset_definition::{CanSetKeyValueInAssetDefinition}, + crate::default::permissions::asset_definition::{CanRemoveKeyValueInAssetDefinition}, + + crate::default::permissions::asset::{CanRegisterAssetWithDefinition}, + crate::default::permissions::asset::{CanUnregisterAssetWithDefinition}, + crate::default::permissions::asset::{CanUnregisterUserAsset}, + crate::default::permissions::asset::{CanBurnAssetWithDefinition}, + crate::default::permissions::asset::{CanMintAssetWithDefinition}, + crate::default::permissions::asset::{CanMintUserAsset}, + crate::default::permissions::asset::{CanBurnUserAsset}, + crate::default::permissions::asset::{CanTransferAssetWithDefinition}, + crate::default::permissions::asset::{CanTransferUserAsset}, + crate::default::permissions::asset::{CanSetKeyValueInUserAsset}, + crate::default::permissions::asset::{CanRemoveKeyValueInUserAsset}, + + crate::default::permissions::parameter::{CanGrantPermissionToCreateParameters}, + crate::default::permissions::parameter::{CanRevokePermissionToCreateParameters}, + crate::default::permissions::parameter::{CanCreateParameters}, + crate::default::permissions::parameter::{CanGrantPermissionToSetParameters}, + crate::default::permissions::parameter::{CanRevokePermissionToSetParameters}, + crate::default::permissions::parameter::{CanSetParameters}, + + crate::default::permissions::role::{CanUnregisterAnyRole}, + + crate::default::permissions::trigger::{CanRegisterUserTrigger}, + crate::default::permissions::trigger::{CanExecuteUserTrigger}, + crate::default::permissions::trigger::{CanUnregisterUserTrigger}, + crate::default::permissions::trigger::{CanMintUserTrigger}, + crate::default::permissions::trigger::{CanBurnUserTrigger}, + crate::default::permissions::trigger::{CanSetKeyValueInTrigger}, + crate::default::permissions::trigger::{CanRemoveKeyValueInTrigger}, + + crate::default::permissions::executor::{CanUpgradeExecutor}, } pub mod peer { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanUnregisterAnyPeer; @@ -157,7 +157,7 @@ pub mod peer { pub mod domain { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanUnregisterDomain { @@ -165,7 +165,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanSetKeyValueInDomain { @@ -173,7 +173,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanRemoveKeyValueInDomain { @@ -181,7 +181,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanRegisterAccountInDomain { @@ -189,7 +189,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanRegisterAssetDefinitionInDomain { @@ -201,42 +201,42 @@ pub mod domain { pub mod account { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanUnregisterAccount { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanMintUserPublicKeys { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanBurnUserPublicKeys { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanMintUserSignatureCheckConditions { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanSetKeyValueInAccount { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanRemoveKeyValueInAccount { @@ -248,7 +248,7 @@ pub mod account { pub mod asset_definition { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanUnregisterAssetDefinition { @@ -256,7 +256,7 @@ pub mod asset_definition { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanSetKeyValueInAssetDefinition { @@ -264,7 +264,7 @@ pub mod asset_definition { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanRemoveKeyValueInAssetDefinition { @@ -276,7 +276,7 @@ pub mod asset_definition { pub mod asset { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanRegisterAssetWithDefinition { @@ -284,7 +284,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanUnregisterAssetWithDefinition { @@ -292,7 +292,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanUnregisterUserAsset { @@ -300,7 +300,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanBurnAssetWithDefinition { @@ -308,7 +308,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanBurnUserAsset { @@ -316,7 +316,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanMintAssetWithDefinition { @@ -324,7 +324,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanMintUserAsset { @@ -332,7 +332,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanTransferAssetWithDefinition { @@ -340,7 +340,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanTransferUserAsset { @@ -348,7 +348,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanSetKeyValueInUserAsset { @@ -356,7 +356,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanRemoveKeyValueInUserAsset { @@ -370,36 +370,36 @@ pub mod parameter { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanGrantPermissionToCreateParameters; } - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanRevokePermissionToCreateParameters; } - token! { + permission! { #[derive(Copy)] pub struct CanCreateParameters; } - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanGrantPermissionToSetParameters; } - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanRevokePermissionToSetParameters; } - token! { + permission! { #[derive(Copy)] pub struct CanSetParameters; } @@ -456,7 +456,7 @@ pub mod parameter { pub mod role { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanUnregisterAnyRole; @@ -478,7 +478,7 @@ pub mod trigger { )+}; } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanRegisterUserTrigger { @@ -486,7 +486,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanExecuteUserTrigger { @@ -494,7 +494,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanUnregisterUserTrigger { @@ -502,7 +502,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanMintUserTrigger { @@ -510,7 +510,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanBurnUserTrigger { @@ -518,7 +518,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanSetKeyValueInTrigger { @@ -526,7 +526,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanRemoveKeyValueInTrigger { @@ -546,7 +546,7 @@ pub mod trigger { pub mod executor { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanUpgradeExecutor; diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index 1e26b7b5c9e..fac1d112b8f 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -5,9 +5,9 @@ extern crate alloc; extern crate self as iroha_executor; -use alloc::vec::Vec; +use alloc::collections::BTreeSet; -use data_model::{executor::Result, permission::PermissionId, ValidationFail}; +use data_model::{executor::Result, ValidationFail}; #[cfg(not(test))] use data_model::{prelude::*, smart_contract::payloads}; pub use iroha_schema::MetaMap; @@ -18,6 +18,7 @@ use iroha_smart_contract_utils::{decode_with_length_prefix_from_raw, encode_and_ pub use smart_contract::{data_model, parse, stub_getrandom}; pub mod default; +pub mod parameter; pub mod permission; pub mod utils { @@ -78,7 +79,11 @@ pub fn get_migrate_payload() -> payloads::Migrate { unsafe { decode_with_length_prefix_from_raw(host::get_migrate_payload()) } } -/// Set new [`PermissionSchema`]. +// pub trait IntoDataModel { +// fn into_data_model(&self) -> &ExecutorDataModel; +// } + +/// Set new [`ExecutorDataModel`]. /// /// # Errors /// @@ -89,9 +94,9 @@ pub fn get_migrate_payload() -> payloads::Migrate { /// Host side will generate a trap if this function was not called from a /// executor's `migrate()` entrypoint. #[cfg(not(test))] -pub fn set_permission_schema(schema: &data_model::permission::PermissionSchema) { +pub fn set_data_model(data_model: &ExecutorDataModel) { // Safety: - ownership of the returned result is transferred into `_decode_from_raw` - unsafe { encode_and_execute(&schema, host::set_permission_schema) } + unsafe { encode_and_execute(&data_model, host::set_data_model) } } #[cfg(not(test))] @@ -126,8 +131,8 @@ mod host { /// This function does transfer ownership of the result to the caller pub(super) fn get_migrate_payload() -> *const u8; - /// Set new [`PermissionSchema`]. - pub(super) fn set_permission_schema(ptr: *const u8, len: usize); + /// Set new [`ExecutorDataModel`]. + pub(super) fn set_data_model(ptr: *const u8, len: usize); } } @@ -172,35 +177,96 @@ macro_rules! deny { }}; } -/// Collection of all permission tokens defined by the executor -#[derive(Debug, Clone, Default)] -pub struct PermissionSchema(Vec, MetaMap); +/// An error that might occur while converting a data model object (with id and payload) +/// into a native executor type. +/// +/// Such objects are [`data_model::prelude::Permission`] and [`data_model::prelude::Parameter`]. +#[derive(Debug)] +pub enum TryFromDataModelObjectError { + /// Unexpected object id + Id(data_model::prelude::Name), + /// Failed to deserialize object payload + Deserialize(serde_json::Error), +} + +/// A convenience to build [`ExecutorDataModel`] from within the executor +#[derive(Debug, Clone)] +pub struct DataModelBuilder { + schema: MetaMap, + permissions: BTreeSet, + parameters: BTreeSet, +} + +impl DataModelBuilder { + /// Constructor + // we don't need to confuse with `with_default_permissions` + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + schema: <_>::default(), + permissions: <_>::default(), + parameters: <_>::default(), + } + } -impl PermissionSchema { - /// Remove permission token from this collection - pub fn remove(&mut self) { - let to_remove = ::name(); + /// Creates a data model with default permissions preset (defined in [`default::permissions`]) + #[must_use] + pub fn with_default_permissions() -> Self { + let mut builder = Self::new(); - if let Some(pos) = self.0.iter().position(|token_id| *token_id == to_remove) { - self.0.remove(pos); - ::remove_from_schema(&mut self.1); + macro_rules! add_to_schema { + ($token_ty:ty) => { + builder = builder.add_permission::<$token_ty>(); + }; } + + default::permissions::map_default_permissions!(add_to_schema); + + builder + } + + /// Define a permission in the data model + #[must_use] + pub fn add_permission(mut self) -> Self { + ::update_schema_map(&mut self.schema); + self.permissions.insert(::id()); + self } - /// Insert new permission token into this collection - pub fn insert(&mut self) { - ::update_schema_map(&mut self.1); - self.0.push(::name()); + /// Remove a permission from the data model + #[must_use] + pub fn remove_permission(mut self) -> Self { + ::remove_from_schema(&mut self.schema); + self.permissions + .remove(&::id()); + self } - /// Serializes schema into a JSON string representation - pub fn serialize(mut self) -> (Vec, alloc::string::String) { - self.0.sort(); + /// Define a configuration parameter in the data model + #[must_use] + pub fn add_parameter(mut self) -> Self { + ::update_schema_map(&mut self.schema); + self.parameters.insert(::id()); + self + } + + /// Remove a configuration parameter from the data model + #[must_use] + pub fn remove_parameter(mut self) -> Self { + ::remove_from_schema(&mut self.schema); + self.parameters.remove(&::id()); + self + } - ( - self.0, - serde_json::to_string(&self.1).expect("schema serialization must not fail"), - ) + /// Set the data model of the executor via [`set_data_model`] + #[cfg(not(test))] + pub fn set(self) { + set_data_model(&ExecutorDataModel::new( + self.permissions, + self.parameters, + data_model::JsonString::serialize(&self.schema) + .expect("schema serialization must not fail"), + )) } } @@ -222,9 +288,12 @@ pub mod prelude { pub use alloc::vec::Vec; pub use iroha_executor_derive::{ - entrypoint, Constructor, Token, Validate, ValidateEntrypoints, ValidateGrantRevoke, Visit, + entrypoint, Constructor, Permission, Validate, ValidateEntrypoints, ValidateGrantRevoke, + Visit, + }; + pub use iroha_smart_contract::prelude::{ + Parameter as ParameterObject, Permission as PermissionObject, *, }; - pub use iroha_smart_contract::prelude::*; pub use super::{ data_model::{ @@ -232,6 +301,9 @@ pub mod prelude { visit::Visit, ValidationFail, }, - deny, execute, PermissionSchema, Validate, + deny, execute, + parameter::Parameter, + permission::Permission, + DataModelBuilder, Validate, }; } diff --git a/smart_contract/executor/src/parameter.rs b/smart_contract/executor/src/parameter.rs new file mode 100644 index 00000000000..805868de2a0 --- /dev/null +++ b/smart_contract/executor/src/parameter.rs @@ -0,0 +1,59 @@ +//! Executor-defined configuration parameters + +use iroha_executor::prelude::{ParameterId, ParameterObject}; +use iroha_schema::IntoSchema; +use iroha_smart_contract_utils::debug::DebugExpectExt; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{data_model::JsonString, TryFromDataModelObjectError}; + +/// Marker trait for parameters. +/// +/// A parameter could be defined in the following way: +/// +/// ``` +/// use iroha_executor::parameter::Parameter; +/// use iroha_schema::IntoSchema; +/// use serde::{Deserialize, Serialize}; +/// +/// #[derive(IntoSchema, Serialize, Deserialize)] +/// struct DomainPrefix { +/// prefix: String, +/// } +/// +/// impl Parameter for DomainPrefix {} +/// ``` +pub trait Parameter: Serialize + DeserializeOwned + IntoSchema { + /// Parameter id, according to [`IntoSchema`]. + fn id() -> ParameterId { + ParameterId::new( + ::type_name() + .parse() + .dbg_expect("Failed to parse parameter id as `Name`"), + ) + } + + /// Try to convert from [`ParameterObject`] + /// # Errors + /// See [`TryFromDataModelObjectError`] + fn try_from_object( + object: &ParameterObject, + ) -> crate::prelude::Result { + if *object.id() != ::id() { + return Err(TryFromDataModelObjectError::Id(object.id().name().clone())); + } + object + .payload() + .deserialize() + .map_err(TryFromDataModelObjectError::Deserialize) + } + + /// Convert into [`ParameterObject`] + fn to_object(&self) -> ParameterObject { + ParameterObject::new( + ::id(), + JsonString::serialize(&self) + .expect("failed to serialize concrete data model entity; this is a bug"), + ) + } +} diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index 67f2f6a689a..313fe38dd48 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -3,27 +3,49 @@ use alloc::borrow::ToOwned as _; use iroha_schema::IntoSchema; -use iroha_smart_contract::{data_model::permission::Permission, QueryOutputCursor}; +use iroha_smart_contract::{data_model::JsonString, QueryOutputCursor}; use iroha_smart_contract_utils::debug::DebugExpectExt as _; use serde::{de::DeserializeOwned, Serialize}; -use crate::{data_model::prelude::*, prelude::*}; +use crate::{prelude::*, TryFromDataModelObjectError}; -/// [`Token`] trait is used to check if the token is owned by the account. -pub trait Token: +/// Is used to check if the permission token is owned by the account. +pub trait Permission: Serialize + DeserializeOwned + IntoSchema + PartialEq + ValidateGrantRevoke -where - for<'a> Self: TryFrom<&'a Permission, Error = PermissionConversionError>, { - /// Return name of this permission token - fn name() -> Name { - ::type_name() - .parse() - .dbg_expect("Failed to parse permission token as `Name`") + /// Check if the account owns this token + fn is_owned_by(&self, account_id: &AccountId) -> bool; + + /// Permission id, according to [`IntoSchema`]. + fn id() -> PermissionId { + PermissionId::new( + ::type_name() + .parse() + .dbg_expect("Failed to parse permission id as `Name`"), + ) } - /// Check if token is owned by the account - fn is_owned_by(&self, account_id: &AccountId) -> bool; + /// Try to convert from [`PermissionObject`] + /// # Errors + /// See [`TryFromDataModelObjectError`] + fn try_from_object(object: &PermissionObject) -> Result { + if *object.id() != ::id() { + return Err(TryFromDataModelObjectError::Id(object.id().name().clone())); + } + object + .payload() + .deserialize() + .map_err(TryFromDataModelObjectError::Deserialize) + } + + /// Convert into [`PermissionObject`] + fn to_object(&self) -> PermissionObject { + PermissionObject::new( + ::id(), + JsonString::serialize(&self) + .expect("failed to serialize concrete data model entity; this is a bug"), + ) + } } /// Trait that should be implemented for all permission tokens. @@ -43,15 +65,6 @@ pub trait PassCondition { fn validate(&self, authority: &AccountId, block_height: u64) -> Result; } -/// Error type for `TryFrom` implementations. -#[derive(Debug)] -pub enum PermissionConversionError { - /// Unexpected token id. - Id(PermissionId), - /// Failed to deserialize JSON - Deserialize(serde_json::Error), -} - pub mod derive_conversions { //! Module with derive macros to generate conversion from custom strongly-typed token //! to some pass condition to successfully derive [`ValidateGrantRevoke`](iroha_executor_derive::ValidateGrantRevoke) @@ -307,7 +320,7 @@ impl PassCondition for AlwaysPass { } } -impl From<&T> for AlwaysPass { +impl From<&T> for AlwaysPass { fn from(_: &T) -> Self { Self } @@ -331,14 +344,14 @@ impl PassCondition for OnlyGenesis { } } -impl From<&T> for OnlyGenesis { +impl From<&T> for OnlyGenesis { fn from(_: &T) -> Self { Self } } /// Iterator over all accounts and theirs permission tokens -pub(crate) fn accounts_permissions() -> impl Iterator { +pub(crate) fn accounts_permissions() -> impl Iterator { FindAllAccounts .execute() .dbg_expect("failed to query all accounts") @@ -355,7 +368,7 @@ pub(crate) fn accounts_permissions() -> impl Iterator impl Iterator { +pub(crate) fn roles_permissions() -> impl Iterator { FindAllRoles .execute() .dbg_expect("failed to query all accounts") @@ -369,3 +382,32 @@ pub(crate) fn roles_permissions() -> impl Iterator .map(move |token| (role.id().clone(), token)) }) } + +#[cfg(test)] +mod tests { + use alloc::{format, string::String}; + + use serde::Deserialize; + use serde_json::json; + + use super::*; + + #[test] + fn convert_token() { + #[derive( + Serialize, Deserialize, IntoSchema, PartialEq, ValidateGrantRevoke, Permission, + )] + #[validate(AlwaysPass)] + struct SampleToken { + can_do_whatever: bool, + } + + let object = PermissionObject::new( + "SampleToken".parse().unwrap(), + json!({ "can_do_whatever": false }), + ); + let parsed = SampleToken::try_from_object(&object).expect("valid"); + + assert!(!parsed.can_do_whatever); + } +} diff --git a/tools/kagami/Cargo.toml b/tools/kagami/Cargo.toml index 1f076c54b85..c35098ccb09 100644 --- a/tools/kagami/Cargo.toml +++ b/tools/kagami/Cargo.toml @@ -13,7 +13,7 @@ license.workspace = true workspace = true [dependencies] -iroha_crypto = { workspace = true } +iroha_crypto = { workspace = true, features = ["rand"] } iroha_config = { workspace = true } iroha_data_model = { workspace = true } iroha_schema_gen = { workspace = true } diff --git a/tools/kagami/src/genesis.rs b/tools/kagami/src/genesis.rs index 4f5b6203c66..da43396a715 100644 --- a/tools/kagami/src/genesis.rs +++ b/tools/kagami/src/genesis.rs @@ -1,11 +1,9 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; -use iroha_config::parameters::defaults::chain_wide as chain_wide_defaults; use iroha_data_model::{ asset::{AssetDefinitionId, AssetValueType}, metadata::Limits, - parameter::{default::*, ParametersBuilder}, prelude::AssetId, }; use iroha_genesis::{ @@ -142,62 +140,6 @@ pub fn generate_default( ) .into(); - let parameter_defaults = ParametersBuilder::new() - .add_parameter( - MAX_TRANSACTIONS_IN_BLOCK, - Numeric::new(chain_wide_defaults::MAX_TXS.get().into(), 0), - )? - .add_parameter( - BLOCK_TIME, - Numeric::new(chain_wide_defaults::BLOCK_TIME.as_millis(), 0), - )? - .add_parameter( - COMMIT_TIME_LIMIT, - Numeric::new(chain_wide_defaults::COMMIT_TIME.as_millis(), 0), - )? - .add_parameter(TRANSACTION_LIMITS, chain_wide_defaults::TRANSACTION_LIMITS)? - .add_parameter( - WSV_DOMAIN_METADATA_LIMITS, - chain_wide_defaults::METADATA_LIMITS, - )? - .add_parameter( - WSV_ASSET_DEFINITION_METADATA_LIMITS, - chain_wide_defaults::METADATA_LIMITS, - )? - .add_parameter( - WSV_ACCOUNT_METADATA_LIMITS, - chain_wide_defaults::METADATA_LIMITS, - )? - .add_parameter( - WSV_ASSET_METADATA_LIMITS, - chain_wide_defaults::METADATA_LIMITS, - )? - .add_parameter( - WSV_TRIGGER_METADATA_LIMITS, - chain_wide_defaults::METADATA_LIMITS, - )? - .add_parameter( - WSV_IDENT_LENGTH_LIMITS, - chain_wide_defaults::IDENT_LENGTH_LIMITS, - )? - .add_parameter( - EXECUTOR_FUEL_LIMIT, - Numeric::new(chain_wide_defaults::WASM_FUEL_LIMIT.into(), 0), - )? - .add_parameter( - EXECUTOR_MAX_MEMORY, - Numeric::new(chain_wide_defaults::WASM_MAX_MEMORY_BYTES.into(), 0), - )? - .add_parameter( - WASM_FUEL_LIMIT, - Numeric::new(chain_wide_defaults::WASM_FUEL_LIMIT.into(), 0), - )? - .add_parameter( - WASM_MAX_MEMORY, - Numeric::new(chain_wide_defaults::WASM_MAX_MEMORY_BYTES.into(), 0), - )? - .into_create_parameters(); - let first_tx = genesis .first_transaction_mut() .expect("At least one transaction is expected"); @@ -209,7 +151,6 @@ pub fn generate_default( grant_permission_to_set_parameters.into(), ] .into_iter() - .chain(parameter_defaults.into_iter()) .chain(std::iter::once(register_user_metadata_access)) { first_tx.append_instruction(isi); diff --git a/tools/parity_scale_cli/src/main.rs b/tools/parity_scale_cli/src/main.rs index 9d7f8457a4d..b47530cb08c 100644 --- a/tools/parity_scale_cli/src/main.rs +++ b/tools/parity_scale_cli/src/main.rs @@ -15,51 +15,7 @@ use std::{ use clap::Parser; use colored::*; use eyre::{eyre, Result}; -use iroha_crypto::*; -use iroha_data_model::{ - account::NewAccount, - asset::NewAssetDefinition, - block::{ - error::BlockRejectionReason, - stream::{BlockMessage, BlockSubscriptionRequest}, - BlockHeader, BlockPayload, SignedBlock, SignedBlockV1, - }, - domain::NewDomain, - events::pipeline::{BlockEventFilter, TransactionEventFilter}, - executor::Executor, - ipfs::IpfsPath, - isi::{ - error::{ - InstructionEvaluationError, InstructionExecutionError, InvalidParameterError, - MathError, MintabilityError, Mismatch, RepetitionError, TypeError, - }, - InstructionType, - }, - metadata::{MetadataError, MetadataValueBox, SizeError}, - parameter::ParameterValueBox, - permission::JsonString, - prelude::*, - query::{ - error::{FindError, QueryExecutionFail}, - predicate::{ - numerical::{SemiInterval, SemiRange}, - string::StringPredicate, - value::{AtIndex, Container, QueryOutputPredicate}, - GenericPredicateBox, NonTrivial, PredicateBox, - }, - ForwardCursor, Pagination, QueryOutputBox, Sorting, - }, - transaction::{ - error::TransactionLimitError, SignedTransactionV1, TransactionLimits, TransactionPayload, - }, - BatchedResponse, BatchedResponseV1, Level, -}; -use iroha_primitives::{ - addr::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrHost, SocketAddrV4, SocketAddrV6}, - const_vec::ConstVec, - conststr::ConstString, - unique_vec::UniqueVec, -}; +use iroha_schema_gen::complete_data_model::*; use parity_scale_codec::{DecodeAll, Encode}; use serde::{de::DeserializeOwned, Serialize}; From 79e49166f385927ab47e764a106365628c485f62 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Thu, 23 May 2024 19:09:44 +0900 Subject: [PATCH 2/4] test: expand messages Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- .../executor_with_custom_parameter/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs index 4f7a9f391d8..b1f607f50f8 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs @@ -30,12 +30,13 @@ fn visit_set_parameter(executor: &mut Executor, _authority: &AccountId, isi: &Se } fn visit_register_domain(executor: &mut Executor, _authority: &AccountId, isi: &Register) { - // FIXME: unwrap is ok here? let required_prefix = FindAllParameters .execute() - .unwrap() + .expect("Iroha should not fail to provide parameters, it is a bug") .into_iter() - .map(Result::unwrap) + .map(|result| { + result.expect("each parameter retrieval should not fail as well, it is a bug") + }) .find_map(|parameter| EnforceDomainPrefix::try_from_object(¶meter).ok()); if let Some(EnforceDomainPrefix { prefix }) = required_prefix { From c1fe4aba9478f264dfe1ba9d4c1d215c30ee096b Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Fri, 24 May 2024 17:31:51 +0900 Subject: [PATCH 3/4] refactor: apply suggestions from code review Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/permissions.rs | 8 +++++++- .../executor_with_custom_permission/src/lib.rs | 9 +++------ client_cli/src/main.rs | 12 ++++++------ data_model/src/lib.rs | 7 +++++++ smart_contract/executor/derive/src/default.rs | 8 ++++---- smart_contract/executor/derive/src/entrypoint.rs | 3 +-- smart_contract/executor/src/default.rs | 5 ++++- smart_contract/executor/src/lib.rs | 12 +++--------- smart_contract/executor/src/parameter.rs | 11 ++++++----- smart_contract/executor/src/permission.rs | 5 ++++- 10 files changed, 45 insertions(+), 35 deletions(-) diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 7aebf8f5e35..d2bc825b8c3 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -8,6 +8,7 @@ use iroha_client::{ }; use iroha_data_model::{ permission::Permission, role::RoleId, transaction::error::TransactionRejectionReason, + JsonString, }; use iroha_genesis::GenesisNetwork; use serde_json::json; @@ -317,7 +318,12 @@ fn stored_vs_granted_token_payload() -> Result<()> { let allow_alice_to_set_key_value_in_mouse_asset = Grant::permission( Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - json!({ "asset_id": format!("xor#wonderland#{mouse_id}") }), + JsonString::from_json_string_unchecked(format!( + // Introducing some whitespaces + // This way, if the executor compares just JSON strings, this test would fail + r##"{{ "asset_id" : "xor#wonderland#{}" }}"##, + mouse_id + )), ), alice_id, ); diff --git a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs index af0c6f04392..1fb8612595f 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs @@ -19,7 +19,7 @@ extern crate panic_halt; use alloc::string::String; use anyhow::anyhow; -use iroha_executor::{permission::Permission as _, prelude::*, DataModelBuilder}; +use iroha_executor::{prelude::*, DataModelBuilder}; use iroha_schema::IntoSchema; use lol_alloc::{FreeListAllocator, LockedAllocator}; use parity_scale_codec::{Decode, Encode}; @@ -118,7 +118,7 @@ impl Executor { .iter() .try_for_each(|(account, domain_id)| { Revoke::permission( - PermissionObject::new( + Permission::new( can_unregister_domain_definition_id.clone(), &json!({ "domain_id": domain_id }), ), @@ -137,10 +137,7 @@ impl Executor { })?; Grant::permission( - PermissionObject::new( - can_control_domain_lives_definition_id.clone(), - &json!(null), - ), + Permission::new(can_control_domain_lives_definition_id.clone(), &json!(null)), account.id().clone(), ) .execute() diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index 6e93284efc9..d2753792d2e 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -522,7 +522,7 @@ mod account { use iroha_client::client::{self}; - use super::{Permission as PermissionObject, *}; + use super::*; /// subcommands for account subcommand #[derive(clap::Subcommand, Debug)] @@ -603,22 +603,22 @@ mod account { pub id: AccountId, /// The JSON/JSON5 file with a permission token #[arg(short, long)] - pub permission: Permission, + pub permission: PermissionWrap, #[command(flatten)] pub metadata: MetadataArgs, } - /// [`PermissionObject`] wrapper implementing [`FromStr`] + /// [`Permission`] wrapper implementing [`FromStr`] #[derive(Debug, Clone)] - pub struct Permission(PermissionObject); + pub struct PermissionWrap(Permission); - impl FromStr for Permission { + impl FromStr for PermissionWrap { type Err = Error; fn from_str(s: &str) -> Result { let content = fs::read_to_string(s) .wrap_err(format!("Failed to read the permission token file {}", &s))?; - let permission: PermissionObject = json5::from_str(&content).wrap_err(format!( + let permission: Permission = json5::from_str(&content).wrap_err(format!( "Failed to deserialize the permission token from file {}", &s ))?; diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 7a376b3be6a..0e763c42dd2 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -626,6 +626,13 @@ impl JsonString { // so it should be a valid JSON string Ok(Self(serialized)) } + + /// Create without checking whether the input is a valid JSON string. + /// + /// The caller must guarantee that the value is valid. + pub fn from_json_string_unchecked(value: String) -> Self { + Self(value) + } } impl From<&serde_json::Value> for JsonString { diff --git a/smart_contract/executor/derive/src/default.rs b/smart_contract/executor/derive/src/default.rs index 492fe0787f2..d85b25c720a 100644 --- a/smart_contract/executor/derive/src/default.rs +++ b/smart_contract/executor/derive/src/default.rs @@ -142,14 +142,14 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn::DeriveInput) -> Tok "fn visit_transfer_asset_definition(operation: &Transfer)", "fn visit_set_asset_definition_key_value(operation: &SetKeyValue)", "fn visit_remove_asset_definition_key_value(operation: &RemoveKeyValue)", - "fn visit_grant_account_permission(operation: &Grant)", - "fn visit_revoke_account_permission(operation: &Revoke)", + "fn visit_grant_account_permission(operation: &Grant)", + "fn visit_revoke_account_permission(operation: &Revoke)", "fn visit_register_role(operation: &Register)", "fn visit_unregister_role(operation: &Unregister)", "fn visit_grant_account_role(operation: &Grant)", "fn visit_revoke_account_role(operation: &Revoke)", - "fn visit_grant_role_permission(operation: &Grant)", - "fn visit_revoke_role_permission(operation: &Revoke)", + "fn visit_grant_role_permission(operation: &Grant)", + "fn visit_revoke_role_permission(operation: &Revoke)", "fn visit_register_trigger(operation: &Register)", "fn visit_unregister_trigger(operation: &Unregister)", "fn visit_mint_trigger_repetitions(operation: &Mint)", diff --git a/smart_contract/executor/derive/src/entrypoint.rs b/smart_contract/executor/derive/src/entrypoint.rs index 646fb0c281f..0b914e825fd 100644 --- a/smart_contract/executor/derive/src/entrypoint.rs +++ b/smart_contract/executor/derive/src/entrypoint.rs @@ -144,9 +144,8 @@ fn impl_migrate_entrypoint(fn_item: syn::ItemFn) -> TokenStream { let migrate_fn_name = syn::Ident::new(export::EXECUTOR_MIGRATE, proc_macro2::Span::call_site()); - // FIXME: is doc accurate? quote! { - /// Executor `permission_schema` entrypoint + /// Executor `data_model_schema` entrypoint /// /// # Memory safety /// diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index 5d583ce5a00..c90a4cec724 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -41,7 +41,10 @@ pub use trigger::{ visit_unregister_trigger, }; -use crate::{permission::Permission as _, prelude::*}; +use crate::{ + permission::Permission as _, + prelude::{Permission as PermissionObject, *}, +}; // NOTE: If any new `visit_..` functions are introduced in this module, one should // not forget to update the default executor boilerplate too, specifically the diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index fac1d112b8f..dde70e5690a 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -79,10 +79,6 @@ pub fn get_migrate_payload() -> payloads::Migrate { unsafe { decode_with_length_prefix_from_raw(host::get_migrate_payload()) } } -// pub trait IntoDataModel { -// fn into_data_model(&self) -> &ExecutorDataModel; -// } - /// Set new [`ExecutorDataModel`]. /// /// # Errors @@ -291,9 +287,7 @@ pub mod prelude { entrypoint, Constructor, Permission, Validate, ValidateEntrypoints, ValidateGrantRevoke, Visit, }; - pub use iroha_smart_contract::prelude::{ - Parameter as ParameterObject, Permission as PermissionObject, *, - }; + pub use iroha_smart_contract::prelude::*; pub use super::{ data_model::{ @@ -302,8 +296,8 @@ pub mod prelude { ValidationFail, }, deny, execute, - parameter::Parameter, - permission::Permission, + parameter::Parameter as ParameterTrait, + permission::Permission as PermissionTrait, DataModelBuilder, Validate, }; } diff --git a/smart_contract/executor/src/parameter.rs b/smart_contract/executor/src/parameter.rs index 805868de2a0..b6e887054cb 100644 --- a/smart_contract/executor/src/parameter.rs +++ b/smart_contract/executor/src/parameter.rs @@ -1,11 +1,14 @@ //! Executor-defined configuration parameters -use iroha_executor::prelude::{ParameterId, ParameterObject}; use iroha_schema::IntoSchema; use iroha_smart_contract_utils::debug::DebugExpectExt; use serde::{de::DeserializeOwned, Serialize}; -use crate::{data_model::JsonString, TryFromDataModelObjectError}; +use crate::{ + data_model::JsonString, + prelude::{Parameter as ParameterObject, *}, + TryFromDataModelObjectError, +}; /// Marker trait for parameters. /// @@ -36,9 +39,7 @@ pub trait Parameter: Serialize + DeserializeOwned + IntoSchema { /// Try to convert from [`ParameterObject`] /// # Errors /// See [`TryFromDataModelObjectError`] - fn try_from_object( - object: &ParameterObject, - ) -> crate::prelude::Result { + fn try_from_object(object: &ParameterObject) -> Result { if *object.id() != ::id() { return Err(TryFromDataModelObjectError::Id(object.id().name().clone())); } diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index 313fe38dd48..5672b4a7114 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -7,7 +7,10 @@ use iroha_smart_contract::{data_model::JsonString, QueryOutputCursor}; use iroha_smart_contract_utils::debug::DebugExpectExt as _; use serde::{de::DeserializeOwned, Serialize}; -use crate::{prelude::*, TryFromDataModelObjectError}; +use crate::{ + prelude::{Permission as PermissionObject, *}, + TryFromDataModelObjectError, +}; /// Is used to check if the permission token is owned by the account. pub trait Permission: From 48363e92b22bf6cb1f3668af646d303cef6a5bba Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Mon, 27 May 2024 08:52:51 +0900 Subject: [PATCH 4/4] test: change port Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 637a6ea879a..2d1f2b5c1ac 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -199,7 +199,7 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { #[test] fn migration_fail_should_not_cause_any_effects() { - let (_rt, _peer, client) = ::new().with_port(10_995).start_with_runtime(); + let (_rt, _peer, client) = ::new().with_port(10_999).start_with_runtime(); wait_for_genesis_committed(&vec![client.clone()], 0); let assert_domain_does_not_exist = |client: &Client, domain_id: &DomainId| {