From f682e7c90b35b2c32d7b61a2dd4632eb62d61a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Ver=C5=A1i=C4=87?= Date: Mon, 17 Jun 2024 22:23:33 +0200 Subject: [PATCH] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marin Veršić --- client/benches/tps/utils.rs | 6 +- client/src/config/user.rs | 8 +- client/tests/integration/asset_propagation.rs | 12 +- client/tests/integration/events/data.rs | 22 +- client/tests/integration/events/pipeline.rs | 14 +- .../multiple_blocks_created.rs | 12 +- .../integration/extra_functional/normal.rs | 15 +- .../extra_functional/unregister_peer.rs | 12 +- client/tests/integration/set_parameter.rs | 22 +- .../integration/smartcontracts/Cargo.toml | 1 + .../executor_with_custom_parameter/Cargo.toml | 24 ++ .../executor_with_custom_parameter/src/lib.rs | 68 ++++ client/tests/integration/sorting.rs | 24 +- .../integration/triggers/data_trigger.rs | 4 +- .../integration/triggers/event_trigger.rs | 2 +- .../integration/triggers/time_trigger.rs | 7 +- client/tests/integration/upgrade.rs | 42 +- configs/swarm/executor.wasm | Bin 512882 -> 521750 bytes core/benches/blocks/common.rs | 10 +- core/benches/kura.rs | 4 +- core/benches/validation.rs | 6 +- core/src/block.rs | 10 +- core/src/executor.rs | 8 +- core/src/gossiper.rs | 2 +- core/src/queue.rs | 6 +- core/src/smartcontracts/isi/account.rs | 22 +- core/src/smartcontracts/isi/asset.rs | 24 +- core/src/smartcontracts/isi/domain.rs | 18 +- core/src/smartcontracts/isi/mod.rs | 2 +- core/src/smartcontracts/isi/query.rs | 10 +- core/src/smartcontracts/isi/triggers/mod.rs | 12 +- core/src/smartcontracts/isi/world.rs | 169 ++++---- core/src/smartcontracts/wasm.rs | 2 +- core/src/state.rs | 32 +- core/src/sumeragi/main_loop.rs | 49 ++- core/src/tx.rs | 17 +- core/test_network/src/lib.rs | 4 +- data_model/derive/src/id.rs | 22 +- .../derive/tests/has_origin_generics.rs | 6 - data_model/src/account.rs | 6 +- data_model/src/asset.rs | 6 +- data_model/src/domain.rs | 3 +- data_model/src/events/data/events.rs | 104 +++-- data_model/src/events/data/filters.rs | 6 +- data_model/src/executor.rs | 28 +- data_model/src/ipfs.rs | 7 +- data_model/src/lib.rs | 12 +- data_model/src/metadata.rs | 15 +- data_model/src/name.rs | 30 +- data_model/src/param.rs | 378 ++++++++++++++---- data_model/src/peer.rs | 11 +- data_model/src/permission.rs | 15 +- data_model/src/query/predicate.rs | 1 + data_model/src/role.rs | 16 +- data_model/src/trigger.rs | 6 +- docs/source/references/schema.json | 370 ++++++++++------- smart_contract/executor/derive/src/lib.rs | 11 + .../executor/derive/src/parameter.rs | 37 ++ smart_contract/executor/src/lib.rs | 23 +- smart_contract/executor/src/parameter.rs | 17 + smart_contract/executor/src/permission.rs | 4 +- torii/src/routing.rs | 2 +- 62 files changed, 1170 insertions(+), 668 deletions(-) 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/parameter.rs create mode 100644 smart_contract/executor/src/parameter.rs diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index 0c376b9347f..7ad85776692 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -6,10 +6,10 @@ use iroha::{ crypto::KeyPair, data_model::{ events::pipeline::{BlockEventFilter, BlockStatus}, + param::BlockParameter, prelude::*, }, }; -use iroha_data_model::param::BlockLimits; use nonzero_ext::nonzero; use serde::Deserialize; use test_network::*; @@ -22,7 +22,7 @@ pub struct Config { pub peers: u32, /// Interval in microseconds between transactions to reduce load pub interval_us_per_tx: u64, - pub block_limits: BlockLimits, + pub block_limits: BlockParameter, pub blocks: u32, pub sample_size: u32, pub genesis_max_retries: u32, @@ -51,7 +51,7 @@ impl Config { let clients = network.clients(); wait_for_genesis_committed_with_max_retries(&clients, 0, self.genesis_max_retries); - client.submit_blocking(SetParameter::new(Parameter::BlockLimits(self.block_limits)))?; + client.submit_blocking(SetParameter::new(Parameter::Block(self.block_limits)))?; let unit_names = (UnitName::MIN..).take(self.peers as usize); let units = clients diff --git a/client/src/config/user.rs b/client/src/config/user.rs index 000ab2a2dd8..71bf826d4d3 100644 --- a/client/src/config/user.rs +++ b/client/src/config/user.rs @@ -6,11 +6,13 @@ use iroha_config_base::{ util::{DurationMs, Emitter, EmitterResultExt}, ReadConfig, WithOrigin, }; -use iroha_crypto::{KeyPair, PrivateKey, PublicKey}; -use iroha_data_model::prelude::{AccountId, ChainId, DomainId}; use url::Url; -use crate::config::BasicAuth; +use crate::{ + config::BasicAuth, + crypto::{KeyPair, PrivateKey, PublicKey}, + data_model::prelude::{AccountId, ChainId, DomainId}, +}; /// Root of the user configuration #[derive(Clone, Debug, ReadConfig)] diff --git a/client/tests/integration/asset_propagation.rs b/client/tests/integration/asset_propagation.rs index 54f4df0034f..df38e50fbca 100644 --- a/client/tests/integration/asset_propagation.rs +++ b/client/tests/integration/asset_propagation.rs @@ -1,12 +1,12 @@ -use std::{num::NonZeroU64, str::FromStr as _, thread}; +use std::{str::FromStr as _, thread}; use eyre::Result; use iroha::{ client::{self, QueryResult}, - data_model::prelude::*, + data_model::{param::BlockParameter, prelude::*}, }; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::param::BlockLimits; +use nonzero_ext::nonzero; use test_network::*; use test_samples::gen_account_in; @@ -20,9 +20,9 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount_on_a wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); - client.submit_blocking(SetParameter::new(Parameter::BlockLimits(BlockLimits { - max_transactions: NonZeroU64::new(1).unwrap(), - })))?; + client.submit_blocking(SetParameter::new(Parameter::Block( + BlockParameter::MaxTransactions(nonzero!(1_u64)), + )))?; let create_domain: InstructionBox = Register::domain(Domain::new(DomainId::from_str("domain")?)).into(); diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index 5c3db17605f..e1c8d12b987 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -161,9 +161,9 @@ fn transaction_execution_should_produce_events( iroha_logger::info!("Event: {:?}", event); assert!(matches!(event, DataEvent::Domain(_))); if let DataEvent::Domain(domain_event) = event { - assert!(matches!(domain_event, DomainEvent::Create(_))); + assert!(matches!(domain_event, DomainEvent::Created(_))); - if let DomainEvent::Create(created_domain) = domain_event { + if let DomainEvent::Created(created_domain) = domain_event { let domain_id = DomainId::new(i.to_string().parse().expect("Valid")); assert_eq!(domain_id, *created_domain.id()); } @@ -226,9 +226,9 @@ fn produce_multiple_events() -> Result<()> { let event: DataEvent = event_receiver.recv()??.try_into()?; assert!(matches!(event, DataEvent::Role(_))); if let DataEvent::Role(role_event) = event { - assert!(matches!(role_event, RoleEvent::Create(_))); + assert!(matches!(role_event, RoleEvent::Created(_))); - if let RoleEvent::Create(created_role) = role_event { + if let RoleEvent::Created(created_role) = role_event { assert_eq!(created_role.id(), role.id()); assert!(created_role .permissions() @@ -237,43 +237,43 @@ fn produce_multiple_events() -> Result<()> { } let expected_domain_events: Vec = [ - DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdd( + DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdded( AccountPermissionChanged { account: bob_id.clone(), permission: token_1.id.clone(), }, ))), - DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdd( + DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdded( AccountPermissionChanged { account: bob_id.clone(), permission: token_2.id.clone(), }, ))), - DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleGrant( + DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleGranted( AccountRoleChanged { account: bob_id.clone(), role: role_id.clone(), }, ))), - DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemove( + DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemoved( AccountPermissionChanged { account: bob_id.clone(), permission: token_1.id, }, ))), - DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemove( + DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemoved( AccountPermissionChanged { account: bob_id.clone(), permission: token_2.id, }, ))), - DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleRevoke( + DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleRevoked( AccountRoleChanged { account: bob_id, role: role_id.clone(), }, ))), - DataEvent::Role(RoleEvent::Delete(role_id)), + DataEvent::Role(RoleEvent::Deleted(role_id)), ] .into_iter() .map(Into::into) diff --git a/client/tests/integration/events/pipeline.rs b/client/tests/integration/events/pipeline.rs index d3c87533f0b..cc89ddf8476 100644 --- a/client/tests/integration/events/pipeline.rs +++ b/client/tests/integration/events/pipeline.rs @@ -1,7 +1,4 @@ -use std::{ - num::NonZeroU64, - thread::{self, JoinHandle}, -}; +use std::thread::{self, JoinHandle}; use eyre::Result; use iroha::{ @@ -11,13 +8,14 @@ use iroha::{ BlockEvent, BlockEventFilter, BlockStatus, TransactionEventFilter, TransactionStatus, }, isi::error::InstructionExecutionError, + param::BlockParameter, prelude::*, transaction::error::TransactionRejectionReason, ValidationFail, }, }; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::param::BlockLimits; +use nonzero_ext::nonzero; use test_network::*; // Needed to re-enable ignored tests. @@ -56,9 +54,9 @@ fn test_with_instruction_and_status_and_port( wait_for_genesis_committed(&clients, 0); let pipeline_time = Config::pipeline_time(); - client.submit_blocking(SetParameter::new(Parameter::BlockLimits(BlockLimits { - max_transactions: NonZeroU64::new(1).unwrap(), - })))?; + client.submit_blocking(SetParameter::new(Parameter::Block( + BlockParameter::MaxTransactions(nonzero!(1_u64)), + )))?; // Given let submitter = client; diff --git a/client/tests/integration/extra_functional/multiple_blocks_created.rs b/client/tests/integration/extra_functional/multiple_blocks_created.rs index 7646e457f54..484723300ee 100644 --- a/client/tests/integration/extra_functional/multiple_blocks_created.rs +++ b/client/tests/integration/extra_functional/multiple_blocks_created.rs @@ -1,12 +1,12 @@ -use std::{num::NonZeroU64, thread}; +use std::thread; use eyre::Result; use iroha::{ client::{self, Client, QueryResult}, - data_model::prelude::*, + data_model::{param::BlockParameter, prelude::*}, }; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::param::BlockLimits; +use nonzero_ext::nonzero; use test_network::*; use test_samples::gen_account_in; @@ -20,9 +20,9 @@ fn long_multiple_blocks_created() -> Result<()> { wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Config::pipeline_time(); - client.submit_blocking(SetParameter::new(Parameter::BlockLimits(BlockLimits { - max_transactions: NonZeroU64::new(1).unwrap(), - })))?; + client.submit_blocking(SetParameter::new(Parameter::Block( + BlockParameter::MaxTransactions(nonzero!(1_u64)), + )))?; let create_domain: InstructionBox = Register::domain(Domain::new("domain".parse()?)).into(); let (account_id, _account_keypair) = gen_account_in("domain"); diff --git a/client/tests/integration/extra_functional/normal.rs b/client/tests/integration/extra_functional/normal.rs index 6c94ea2a02f..ea474a54d71 100644 --- a/client/tests/integration/extra_functional/normal.rs +++ b/client/tests/integration/extra_functional/normal.rs @@ -1,8 +1,9 @@ -use std::num::NonZeroU64; - -use iroha::client::{self, Client}; +use iroha::{ + client::{self, Client}, + data_model::{asset::AssetDefinitionId, param::BlockParameter, prelude::*}, +}; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::{asset::AssetDefinitionId, param::BlockLimits, prelude::*}; +use nonzero_ext::nonzero; use test_network::*; use tokio::runtime::Runtime; @@ -20,9 +21,9 @@ fn tranasctions_should_be_applied() { }); wait_for_genesis_committed(&network.clients(), 0); iroha - .submit_blocking(SetParameter::new(Parameter::BlockLimits(BlockLimits { - max_transactions: NonZeroU64::new(1).unwrap(), - }))) + .submit_blocking(SetParameter::new(Parameter::Block( + BlockParameter::MaxTransactions(nonzero!(1_u64)), + ))) .unwrap(); let domain_id = "and".parse::().unwrap(); diff --git a/client/tests/integration/extra_functional/unregister_peer.rs b/client/tests/integration/extra_functional/unregister_peer.rs index 0b3719a849d..07f6f6e7c3c 100644 --- a/client/tests/integration/extra_functional/unregister_peer.rs +++ b/client/tests/integration/extra_functional/unregister_peer.rs @@ -1,12 +1,12 @@ -use std::{num::NonZeroU64, thread}; +use std::thread; use eyre::Result; use iroha::{ client::{self, QueryResult}, - data_model::prelude::*, + data_model::{param::BlockParameter, prelude::*}, }; use iroha_config::parameters::actual::Root as Config; -use iroha_data_model::param::BlockLimits; +use nonzero_ext::nonzero; use test_network::*; use test_samples::gen_account_in; @@ -116,9 +116,9 @@ fn init() -> Result<( let pipeline_time = Config::pipeline_time(); iroha_logger::info!("Started"); - let set_max_txns_in_block = SetParameter::new(Parameter::BlockLimits(BlockLimits { - max_transactions: NonZeroU64::new(1).unwrap(), - })); + let set_max_txns_in_block = SetParameter::new(Parameter::Block( + BlockParameter::MaxTransactions(nonzero!(1_u64)), + )); let create_domain = Register::domain(Domain::new("domain".parse()?)); let (account_id, _account_keypair) = gen_account_in("domain"); diff --git a/client/tests/integration/set_parameter.rs b/client/tests/integration/set_parameter.rs index 20615c4d3c6..a863966fa76 100644 --- a/client/tests/integration/set_parameter.rs +++ b/client/tests/integration/set_parameter.rs @@ -3,9 +3,11 @@ use std::time::Duration; use eyre::Result; use iroha::{ client, - data_model::{param::Parameter, prelude::*}, + data_model::{ + param::{Parameter, Parameters, SumeragiParameter, SumeragiParameters}, + prelude::*, + }, }; -use iroha_data_model::param::Parameters; use test_network::*; #[test] @@ -14,15 +16,21 @@ fn can_change_parameter_value() -> Result<()> { wait_for_genesis_committed(&vec![test_client.clone()], 0); let old_params: Parameters = test_client.request(client::parameter::all())?; - assert_eq!(old_params.block_time, Parameters::default().block_time); + assert_eq!( + old_params.sumeragi().block_time(), + SumeragiParameters::default().block_time() + ); - let block_time = Duration::from_millis(40_000); - let parameter = Parameter::BlockTime(block_time); + let block_time = 40_000; + let parameter = Parameter::Sumeragi(SumeragiParameter::BlockTimeMs(block_time)); let set_param_isi = SetParameter::new(parameter); test_client.submit_blocking(set_param_isi)?; - let new_params = test_client.request(client::parameter::all())?; - assert_eq!(new_params.block_time, block_time); + let sumeragi_params = test_client.request(client::parameter::all())?.sumeragi; + assert_eq!( + sumeragi_params.block_time(), + Duration::from_millis(block_time) + ); Ok(()) } diff --git a/client/tests/integration/smartcontracts/Cargo.toml b/client/tests/integration/smartcontracts/Cargo.toml index 5004748a0c0..e6fb9bcaf40 100644 --- a/client/tests/integration/smartcontracts/Cargo.toml +++ b/client/tests/integration/smartcontracts/Cargo.toml @@ -13,6 +13,7 @@ members = [ "mint_rose_trigger", "executor_with_admin", "executor_with_custom_permission", + "executor_with_custom_parameter", "executor_remove_permission", "executor_with_migration_fail", "executor_custom_instructions_simple", 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..3533fe2718c --- /dev/null +++ b/client/tests/integration/smartcontracts/executor_with_custom_parameter/src/lib.rs @@ -0,0 +1,68 @@ +//! Runtime Executor which allows domains whose id satisfies the length limit +#![no_std] + +extern crate alloc; +#[cfg(not(test))] +extern crate panic_halt; + +use iroha_executor::{prelude::*, DataModelBuilder}; +use lol_alloc::{FreeListAllocator, LockedAllocator}; + +#[global_allocator] +static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + +getrandom::register_custom_getrandom!(iroha_executor::stub_getrandom); + +use alloc::format; + +mod parameter { + //! Module with custom parameters + use alloc::string::String; + + use iroha_schema::IntoSchema; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; + + use super::*; + + /// Parameter that controls domain limits + #[derive(PartialEq, Eq, Parameter, Decode, Encode, Serialize, Deserialize, IntoSchema)] + pub struct DomainLimits { + /// Length of domain id in bytes + pub id_len: u32, + } +} + +#[derive(Constructor, ValidateEntrypoints, Validate, Visit)] +#[visit(custom(visit_register_domain))] +struct Executor { + verdict: Result, + block_height: u64, +} + +fn visit_register_domain(executor: &mut Executor, _authority: &AccountId, isi: &Register) { + let parameters = FindAllParameters.execute().unwrap().into_inner(); + + let domain_limits_json = parameters + .custom() + .get(¶meter::DomainLimits::id()) + .unwrap(); + + let domain_limits: parameter::DomainLimits = serde_json::from_str(domain_limits_json.as_ref()) + .expect("INTERNAL BUG: Domain limits not a valid JSON"); + + if isi.object().id().name().as_ref().len() < domain_limits.id_len as usize { + deny!(executor, "Domain id exceeds the limit"); + } + + execute!(executor, isi); +} + +#[entrypoint] +pub fn migrate(_block_height: u64) -> MigrationResult { + DataModelBuilder::with_default_permissions() + .add_parameter::() + .build_and_set(); + + Ok(()) +} diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index 7f5fec19ec8..490a371ebef 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -52,9 +52,7 @@ fn correct_pagination_assets_after_creating_new_one() { AssetDefinitionId::from_str(&format!("xor{i}#wonderland")).expect("Valid"); let asset_definition = AssetDefinition::store(asset_definition_id.clone()); let mut asset_metadata = Metadata::default(); - asset_metadata - .insert(sort_by_metadata_key.clone(), i as u32) - .expect("Valid"); + asset_metadata.insert(sort_by_metadata_key.clone(), i as u32); let asset = Asset::new( AssetId::new(asset_definition_id, account_id.clone()), AssetValue::Store(asset_metadata), @@ -143,9 +141,7 @@ fn correct_sorting_of_entities() { let asset_definition_id = AssetDefinitionId::from_str(&format!("xor_{i}#wonderland")).expect("Valid"); let mut asset_metadata = Metadata::default(); - asset_metadata - .insert(sort_by_metadata_key.clone(), n - i - 1) - .expect("Valid"); + asset_metadata.insert(sort_by_metadata_key.clone(), n - i - 1); let asset_definition = AssetDefinition::numeric(asset_definition_id.clone()) .with_metadata(asset_metadata.clone()); @@ -200,9 +196,7 @@ fn correct_sorting_of_entities() { for i in 0..n { let account_id = AccountId::new(domain_id.clone(), public_keys[i as usize].clone()); let mut account_metadata = Metadata::default(); - account_metadata - .insert(sort_by_metadata_key.clone(), n - i - 1) - .expect("Valid"); + account_metadata.insert(sort_by_metadata_key.clone(), n - i - 1); let account = Account::new(account_id.clone()).with_metadata(account_metadata.clone()); accounts.push(account_id); @@ -244,9 +238,7 @@ fn correct_sorting_of_entities() { for i in 0..n { let domain_id = DomainId::from_str(&format!("neverland{i}")).expect("Valid"); let mut domain_metadata = Metadata::default(); - domain_metadata - .insert(sort_by_metadata_key.clone(), n - i - 1) - .expect("Valid"); + domain_metadata.insert(sort_by_metadata_key.clone(), n - i - 1); let domain = Domain::new(domain_id.clone()).with_metadata(domain_metadata.clone()); domains.push(domain_id); @@ -287,9 +279,7 @@ fn correct_sorting_of_entities() { for (idx, val) in input { let domain_id = DomainId::from_str(&format!("neverland_{idx}")).expect("Valid"); let mut domain_metadata = Metadata::default(); - domain_metadata - .insert(sort_by_metadata_key.clone(), val) - .expect("Valid"); + domain_metadata.insert(sort_by_metadata_key.clone(), val); let domain = Domain::new(domain_id.clone()).with_metadata(domain_metadata.clone()); domains.push(domain_id); @@ -357,9 +347,7 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { account } else { let mut account_metadata = Metadata::default(); - account_metadata - .insert(sort_by_metadata_key.clone(), n - i - 1) - .expect("Valid"); + account_metadata.insert(sort_by_metadata_key.clone(), n - i - 1); let account = Account::new(account_id.clone()).with_metadata(account_metadata); accounts_a.push(account_id); account diff --git a/client/tests/integration/triggers/data_trigger.rs b/client/tests/integration/triggers/data_trigger.rs index bfbc48fd188..06ce5a2400e 100644 --- a/client/tests/integration/triggers/data_trigger.rs +++ b/client/tests/integration/triggers/data_trigger.rs @@ -32,7 +32,7 @@ fn must_execute_both_triggers() -> Result<()> { [instruction.clone()], Repeats::Indefinitely, account_id.clone(), - AccountEventFilter::new().for_events(AccountEventSet::Create), + AccountEventFilter::new().for_events(AccountEventSet::Created), ), )); test_client.submit_blocking(register_trigger)?; @@ -43,7 +43,7 @@ fn must_execute_both_triggers() -> Result<()> { [instruction], Repeats::Indefinitely, account_id, - DomainEventFilter::new().for_events(DomainEventSet::Create), + DomainEventFilter::new().for_events(DomainEventSet::Created), ), )); test_client.submit_blocking(register_trigger)?; diff --git a/client/tests/integration/triggers/event_trigger.rs b/client/tests/integration/triggers/event_trigger.rs index 190d507ff5d..0221d770139 100644 --- a/client/tests/integration/triggers/event_trigger.rs +++ b/client/tests/integration/triggers/event_trigger.rs @@ -23,7 +23,7 @@ fn test_mint_asset_when_new_asset_definition_created() -> Result<()> { vec![instruction], Repeats::Indefinitely, account_id, - AssetDefinitionEventFilter::new().for_events(AssetDefinitionEventSet::Create), + AssetDefinitionEventFilter::new().for_events(AssetDefinitionEventSet::Created), ), )); test_client.submit(register_trigger)?; diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index 075a0f1bd7b..a7a3b4d70c5 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -6,6 +6,7 @@ use iroha::{ data_model::{ asset::AssetId, events::pipeline::{BlockEventFilter, BlockStatus}, + param::SumeragiParameters, prelude::*, transaction::WasmSmartContract, Level, @@ -17,13 +18,13 @@ use test_samples::{gen_account_in, ALICE_ID}; /// Default estimation of consensus duration. pub fn default_consensus_estimation() -> Duration { - let default_parameters = iroha_data_model::param::Parameters::default(); + let default_parameters = SumeragiParameters::default(); default_parameters - .block_time + .block_time() .checked_add( default_parameters - .commit_time + .commit_time() .checked_div(2) .map_or_else(|| unreachable!(), |x| x), ) diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 9dac5fb46ef..5235d32fa13 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -338,7 +338,7 @@ fn migration_should_cause_upgrade_event() { .await .unwrap(); while let Some(event) = stream.try_next().await.unwrap() { - if let EventBox::Data(DataEvent::Executor(ExecutorEvent::Upgrade(ExecutorUpgrade { + if let EventBox::Data(DataEvent::Executor(ExecutorEvent::Upgraded(ExecutorUpgrade { new_data_model, }))) = event { @@ -364,6 +364,46 @@ fn migration_should_cause_upgrade_event() { assert!(!data_model.permissions.is_empty()); } +#[test] +fn define_custom_parameter() { + 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_parameter", + ) + .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.parameters.is_empty()); +} + fn upgrade_executor(client: &Client, executor: impl AsRef) -> Result<()> { info!("Building executor"); diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 13353c399545a99675cc96c93429eb72c5f63f81..68d1a1f0e1680c5aeb81b782aae069caaf7d77bc 100644 GIT binary patch delta 127678 zcmeFa2Y6J)`aixiXKQvhIY1IZ62k5#K&XO9Q9#%zA}R`Ay9n5>8f;kZwQrCjC?#-! zK}84(Qlta|1gW72NDT-G(n3)XDFL|{kn;PyGrOA&Bwq2WkN5vPfA1xGX6Bulx6eCs zX6BswyvE3*HF9s^+K#AijShT)gP9mJo6O8=xV3u5_1Yz~iJO?w%KVm*YqkU`n=CHw z=JcPLGfh5l?!d3fWHFgcR+HJpOlGsyW@Zd&3;m!yDx?1xH=~48kr`bf5u-=jOvpom z@~mdGlxVUgS&_DI3+Idw;@pA~v)hbbWd>y$eoLHi+-%chOwlpA73gT1Sp$UerX?v5 z#E5C=jQWfCFN6)Uk`>;;Le!!E;w+Zx7K_PZaakh%bBEF{U`2iXHG^ z6=OyfTOZGGHk?^NM>Q8u_+O?0aK?-NSw`rr4_oCZRV! za~dq6T8*uao7-=-&1DB|3$XxZY}#&`1Fd^?}UOKjiU4%muq+gNW)U&~B>fn8+3 zvT1xe&*QWB96mL`m-CIhkbkAk;=?VoZTonht+zePevuuv?YEt_{l|93_KWQ&+Y#H3 zwmz2kEFW2q+78)H+D_Pd*=N~)wwysA zpRj*v|Bt=IzSf>=|G{?2cHZ`@?VRnbZF*GHrcbJWn|Io=nX#{pRnAU4i;M2BTYa_i zII{+{w*Glwv;5P*Wb&E)%wtZv!DRB7&26dBc+}N_Ei}GxO=0tl``kC+d8m7}RaG*& zMOTfdM^ts2oG#{cdQ2XsNBigH4UC?|;&(?Sdg45l{OLM9#d>sq0n>LIx9KB2l;QBO zU=j0Jx7=P~9n)zM5It`{0rs}2iXG<%t=n$yGm=1kqNA(4PY9d6dMEJ##U2^70B>u{4k zoFL+Z;3FszR~|%cWE)kGNatp>VV7;lE-NdDK-rcnMb^`iAgo4GH!;D4>@!+{m96Uz zLX0ACS1|D*CBW}eilWNYO$d_YKyDcvjU@?5Pf1Lzk+m&lY6mNuFRx5I)gr6$$OO9_ z-0)Xh$krPdt0g2JH@j*&J(@o))5rXpK7&aXKv_;0jS>^sexpO;wd{cLR$@ZpezU7a z7}QY)bUKRHn_bnz#mc%l>9cz5Avo?oYsrN1hKlEzGXX3O{<#`$V64+>q{OOTig+ga0Dvmq zTca(I@Oa|Vzj45cpeUX2&EnJ%GBWCWNxA*J8J|t$j4Cu9@%Y#zBg@kvidtkIo4$`3 z-*}c?JHo-+lEx>uGq?4a^}cq$*=zQjyc*i^XES|@!)I>iHhaiwlQaEka7U}Wx3LjM zW#8Lut�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

5GFhm+dedbnL;1 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

t0NLtp5D7u)gn(G(uA6C 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 diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index f166e5c0565..28dc00f007f 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -13,7 +13,7 @@ use iroha_data_model::{ asset::{AssetDefinition, AssetDefinitionId}, domain::Domain, isi::InstructionBox, - param::TransactionLimits, + param::TransactionParameters, prelude::*, ChainId, JsonString, }; @@ -32,7 +32,7 @@ pub fn create_block( let transaction = TransactionBuilder::new(chain_id.clone(), account_id) .with_instructions(instructions) .sign(private_key); - let limits = state.transaction_executor().transaction_limits; + let limits = state.transaction_executor().limits; let (peer_public_key, _) = KeyPair::random().into_parts(); let peer_id = PeerId::new("127.0.0.1:8080".parse().unwrap(), peer_public_key); @@ -182,12 +182,12 @@ pub fn build_state(rt: &tokio::runtime::Handle, account_id: &AccountId) -> State { let mut state_block = state.block(); - state_block.world.parameters.transaction_limits = TransactionLimits::new( + state_block.world.parameters.transaction = TransactionParameters::new( NonZeroU64::new(u64::MAX).unwrap(), NonZeroU64::new(u64::MAX).unwrap(), ); - state_block.world.parameters.executor_limits.fuel = NonZeroU64::new(u64::MAX).unwrap(); - state_block.world.parameters.executor_limits.memory = NonZeroU64::new(u64::MAX).unwrap(); + state_block.world.parameters.executor.fuel = NonZeroU64::new(u64::MAX).unwrap(); + state_block.world.parameters.executor.memory = NonZeroU64::new(u64::MAX).unwrap(); let mut state_transaction = state_block.transaction(); let path_to_executor = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) diff --git a/core/benches/kura.rs b/core/benches/kura.rs index 32904aec6d7..9b5e148a201 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -14,7 +14,7 @@ use iroha_core::{ sumeragi::network_topology::Topology, }; use iroha_crypto::KeyPair; -use iroha_data_model::{param::TransactionLimits, prelude::*}; +use iroha_data_model::{param::TransactionParameters, prelude::*}; use nonzero_ext::nonzero; use test_samples::gen_account_in; use tokio::{fs, runtime::Runtime}; @@ -30,7 +30,7 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let tx = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([transfer]) .sign(alice_keypair.private_key()); - let txn_limits = TransactionLimits { + let txn_limits = TransactionParameters { max_instructions: nonzero!(4096_u64), smart_contract_size: nonzero!(1_u64), }; diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 05066c46e0b..a5cf3ad2b1d 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -11,7 +11,7 @@ use iroha_core::{ tx::TransactionExecutor, }; use iroha_data_model::{ - account::AccountId, isi::InstructionBox, param::TransactionLimits, prelude::*, + account::AccountId, isi::InstructionBox, param::TransactionParameters, prelude::*, transaction::TransactionBuilder, }; use iroha_primitives::unique_vec::UniqueVec; @@ -24,8 +24,8 @@ static STARTER_KEYPAIR: Lazy = Lazy::new(KeyPair::random); static STARTER_ID: Lazy = Lazy::new(|| AccountId::new(STARTER_DOMAIN.clone(), STARTER_KEYPAIR.public_key().clone())); -const TRANSACTION_LIMITS: TransactionLimits = - TransactionLimits::new(nonzero!(5096_u64), nonzero!(1_u64)); +const TRANSACTION_LIMITS: TransactionParameters = + TransactionParameters::new(nonzero!(4096_u64), nonzero!(1_u64)); fn build_test_transaction(chain_id: ChainId) -> TransactionBuilder { let domain_id: DomainId = "domain".parse().unwrap(); diff --git a/core/src/block.rs b/core/src/block.rs index 145a5fa6084..218cd18b183 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -224,7 +224,7 @@ mod pending { state.latest_block_hash(), view_change_index, &transactions, - state.consensus_estimation(), + state.world.parameters().sumeragi.consensus_estimation(), ), transactions, commit_topology: self.0.commit_topology.into_iter().collect(), @@ -512,7 +512,7 @@ mod valid { AcceptedTransaction::accept( value, expected_chain_id, - transaction_executor.transaction_limits, + transaction_executor.limits, ) }?; @@ -968,7 +968,7 @@ mod tests { Register::asset_definition(AssetDefinition::numeric(asset_definition_id)); // Making two transactions that have the same instruction - let transaction_limits = state_block.transaction_executor().transaction_limits; + let transaction_limits = state_block.transaction_executor().limits; let tx = TransactionBuilder::new(chain_id.clone(), alice_id) .with_instructions([create_asset_definition]) .sign(alice_keypair.private_key()); @@ -1025,7 +1025,7 @@ mod tests { Register::asset_definition(AssetDefinition::numeric(asset_definition_id.clone())); // Making two transactions that have the same instruction - let transaction_limits = state_block.transaction_executor().transaction_limits; + let transaction_limits = state_block.transaction_executor().limits; let tx = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([create_asset_definition]) .sign(alice_keypair.private_key()); @@ -1096,7 +1096,7 @@ mod tests { let query_handle = LiveQueryStore::test().start(); let state = State::new(world, kura, query_handle); let mut state_block = state.block(); - let transaction_limits = state_block.transaction_executor().transaction_limits; + let transaction_limits = state_block.transaction_executor().limits; let domain_id = DomainId::from_str("domain").expect("Valid"); let create_domain = Register::domain(Domain::new(domain_id)); diff --git a/core/src/executor.rs b/core/src/executor.rs index 178a3935731..167c634a3f6 100644 --- a/core/src/executor.rs +++ b/core/src/executor.rs @@ -158,7 +158,7 @@ impl Executor { let runtime = wasm::RuntimeBuilder::::new() .with_engine(state_transaction.engine.clone()) // Cloning engine is cheap, see [`wasmtime::Engine`] docs - .with_config(state_transaction.world.parameters().executor_limits) + .with_config(state_transaction.world.parameters().executor) .build()?; runtime.execute_executor_validate_transaction( @@ -194,7 +194,7 @@ impl Executor { let runtime = wasm::RuntimeBuilder::::new() .with_engine(state_transaction.engine.clone()) // Cloning engine is cheap, see [`wasmtime::Engine`] docs - .with_config(state_transaction.world.parameters().executor_limits) + .with_config(state_transaction.world.parameters().executor) .build()?; runtime.execute_executor_validate_instruction( @@ -228,7 +228,7 @@ impl Executor { let runtime = wasm::RuntimeBuilder::>::new() .with_engine(state_ro.engine().clone()) // Cloning engine is cheap, see [`wasmtime::Engine`] docs - .with_config(state_ro.world().parameters().executor_limits) + .with_config(state_ro.world().parameters().executor) .build()?; runtime.execute_executor_validate_query( @@ -263,7 +263,7 @@ impl Executor { let runtime = wasm::RuntimeBuilder::::new() .with_engine(state_transaction.engine.clone()) // Cloning engine is cheap, see [`wasmtime::Engine`] docs - .with_config(state_transaction.world().parameters().executor_limits) + .with_config(state_transaction.world().parameters().executor) .build()?; runtime diff --git a/core/src/gossiper.rs b/core/src/gossiper.rs index 9b6ca31881e..4a08606108e 100644 --- a/core/src/gossiper.rs +++ b/core/src/gossiper.rs @@ -110,7 +110,7 @@ impl TransactionGossiper { let state_view = self.state.view(); for tx in txs { - let transaction_limits = state_view.world().parameters().transaction_limits; + let transaction_limits = state_view.world().parameters().transaction; match AcceptedTransaction::accept(tx, &self.chain_id, transaction_limits) { Ok(tx) => match self.queue.push(tx, &state_view) { diff --git a/core/src/queue.rs b/core/src/queue.rs index 3cd0ca344d7..81e44f6fdb4 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -377,7 +377,7 @@ impl Queue { pub mod tests { use std::{str::FromStr, sync::Arc, thread, time::Duration}; - use iroha_data_model::{param::TransactionLimits, prelude::*}; + use iroha_data_model::{param::TransactionParameters, prelude::*}; use nonzero_ext::nonzero; use rand::Rng as _; use test_samples::gen_account_in; @@ -427,7 +427,7 @@ pub mod tests { TransactionBuilder::new_with_time_source(chain_id.clone(), account_id, time_source) .with_instructions(instructions) .sign(key_pair.private_key()); - let limits = TransactionLimits { + let limits = TransactionParameters { max_instructions: nonzero!(4096_u64), smart_contract_size: nonzero!(1024_u64), }; @@ -682,7 +682,7 @@ pub mod tests { .with_instructions(instructions); tx.set_ttl(Duration::from_millis(TTL_MS)); let tx = tx.sign(alice_keypair.private_key()); - let limits = TransactionLimits { + let limits = TransactionParameters { max_instructions: nonzero!(4096_u64), smart_contract_size: nonzero!(1024_u64), }; diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index d2d6aed507b..03190f0c30c 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -119,7 +119,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AccountEvent::Asset(AssetEvent::Remove( + .emit_events(Some(AccountEvent::Asset(AssetEvent::Removed( AssetChanged { asset: asset.id, amount: asset.value, @@ -154,7 +154,7 @@ pub mod isi { asset_definition.owned_by = destination.clone(); state_transaction .world - .emit_events(Some(AssetDefinitionEvent::OwnerChange( + .emit_events(Some(AssetDefinitionEvent::OwnerChanged( AssetDefinitionOwnerChanged { asset_definition: object, new_owner: destination, @@ -186,7 +186,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AccountEvent::MetadataInsert(MetadataChanged { + .emit_events(Some(AccountEvent::MetadataInserted(MetadataChanged { target: account_id, key: self.key, value: self.value, @@ -217,7 +217,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AccountEvent::MetadataRemove(MetadataChanged { + .emit_events(Some(AccountEvent::MetadataRemoved(MetadataChanged { target: account_id, key: self.key, value, @@ -267,7 +267,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AccountEvent::PermissionAdd( + .emit_events(Some(AccountEvent::PermissionAdded( AccountPermissionChanged { account: account_id, permission: permission_id, @@ -300,7 +300,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AccountEvent::PermissionRemove( + .emit_events(Some(AccountEvent::PermissionRemoved( AccountPermissionChanged { account: account_id, permission: permission.id, @@ -357,8 +357,8 @@ pub mod isi { account: account_id, permission: permission_id, }) - .map(AccountEvent::PermissionAdd) - .chain(std::iter::once(AccountEvent::RoleGrant( + .map(AccountEvent::PermissionAdded) + .chain(std::iter::once(AccountEvent::RoleGranted( AccountRoleChanged { account: account_id_clone, role: role_id, @@ -410,8 +410,8 @@ pub mod isi { account: account_id, permission: permission_id, }) - .map(AccountEvent::PermissionRemove) - .chain(std::iter::once(AccountEvent::RoleRevoke( + .map(AccountEvent::PermissionRemoved) + .chain(std::iter::once(AccountEvent::RoleRevoked( AccountRoleChanged { account: account_id_clone, role: role_id, @@ -452,7 +452,7 @@ pub mod isi { .asset_definition_mut(definition_id)?; forbid_minting(asset_definition)?; state_transaction.world.emit_events(Some( - AssetDefinitionEvent::MintabilityChange(definition_id.clone()), + AssetDefinitionEvent::MintabilityChanged(definition_id.clone()), )); } Ok(()) diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index fd6bc862e50..a93ab940866 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -76,7 +76,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AssetEvent::MetadataInsert(MetadataChanged { + .emit_events(Some(AssetEvent::MetadataInserted(MetadataChanged { target: asset_id, key: self.key, value: self.value, @@ -115,7 +115,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AssetEvent::MetadataRemove(MetadataChanged { + .emit_events(Some(AssetEvent::MetadataRemoved(MetadataChanged { target: asset_id, key: self.key, value, @@ -159,8 +159,8 @@ pub mod isi { }; state_transaction.world.emit_events([ - AssetEvent::Delete(asset_id), - AssetEvent::Create(destination_store), + AssetEvent::Deleted(asset_id), + AssetEvent::Created(destination_store), ]); Ok(()) @@ -206,7 +206,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AssetEvent::Add(AssetChanged { + .emit_events(Some(AssetEvent::Added(AssetChanged { asset: asset_id, amount: self.object.into(), }))); @@ -259,7 +259,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AssetEvent::Remove(AssetChanged { + .emit_events(Some(AssetEvent::Removed(AssetChanged { asset: asset_id.clone(), amount: self.object.into(), }))); @@ -323,11 +323,11 @@ pub mod isi { } state_transaction.world.emit_events([ - AssetEvent::Remove(AssetChanged { + AssetEvent::Removed(AssetChanged { asset: source_id, amount: self.object.into(), }), - AssetEvent::Add(AssetChanged { + AssetEvent::Added(AssetChanged { asset: destination_id, amount: self.object.into(), }), @@ -389,11 +389,9 @@ pub mod isi { .world .asset_definition_mut(&asset_definition_id)?; forbid_minting(asset_definition)?; - state_transaction - .world - .emit_events(Some(AssetDefinitionEvent::MintabilityChange( - asset_definition_id, - ))); + state_transaction.world.emit_events(Some( + AssetDefinitionEvent::MintabilityChanged(asset_definition_id), + )); Ok(()) } } diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index 376c06da702..a4f2da1a918 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -68,7 +68,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(DomainEvent::Account(AccountEvent::Create(account)))); + .emit_events(Some(DomainEvent::Account(AccountEvent::Created(account)))); Ok(()) } @@ -112,7 +112,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AccountEvent::Delete(account_id))); + .emit_events(Some(AccountEvent::Deleted(account_id))); Ok(()) } @@ -146,7 +146,7 @@ pub mod isi { state_transaction .world .emit_events(Some(DomainEvent::AssetDefinition( - AssetDefinitionEvent::Create(asset_definition), + AssetDefinitionEvent::Created(asset_definition), ))); Ok(()) @@ -192,7 +192,7 @@ pub mod isi { error!(%asset_id, "asset not found. This is a bug"); } - events.push(AccountEvent::Asset(AssetEvent::Delete(asset_id)).into()); + events.push(AccountEvent::Asset(AssetEvent::Deleted(asset_id)).into()); } let domain = state_transaction @@ -238,7 +238,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AssetDefinitionEvent::MetadataInsert( + .emit_events(Some(AssetDefinitionEvent::MetadataInserted( MetadataChanged { target: asset_definition_id, key: self.key, @@ -271,7 +271,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(AssetDefinitionEvent::MetadataRemove( + .emit_events(Some(AssetDefinitionEvent::MetadataRemoved( MetadataChanged { target: asset_definition_id, key: self.key, @@ -297,7 +297,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(DomainEvent::MetadataInsert(MetadataChanged { + .emit_events(Some(DomainEvent::MetadataInserted(MetadataChanged { target: domain_id, key: self.key, value: self.value, @@ -324,7 +324,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(DomainEvent::MetadataRemove(MetadataChanged { + .emit_events(Some(DomainEvent::MetadataRemoved(MetadataChanged { target: domain_id, key: self.key, value, @@ -358,7 +358,7 @@ pub mod isi { domain.owned_by = destination.clone(); state_transaction .world - .emit_events(Some(DomainEvent::OwnerChange(DomainOwnerChanged { + .emit_events(Some(DomainEvent::OwnerChanged(DomainOwnerChanged { domain: object, new_owner: destination, }))); diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index 2c08c32957c..e16b72d701f 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -496,7 +496,7 @@ mod tests { let tx = TransactionBuilder::new(chain_id.clone(), SAMPLE_GENESIS_ACCOUNT_ID.clone()) .with_instructions(instructions) .sign(SAMPLE_GENESIS_ACCOUNT_KEYPAIR.private_key()); - let tx_limits = state_block.transaction_executor().transaction_limits; + let tx_limits = state_block.transaction_executor().limits; assert!(matches!( AcceptedTransaction::accept(tx, &chain_id, tx_limits), Err(AcceptTransactionFail::UnexpectedGenesisAccountSignature) diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index afe15a95697..25760c539de 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -293,7 +293,7 @@ mod tests { use iroha_crypto::{Hash, HashOf, KeyPair}; use iroha_data_model::{ - metadata::MetadataValueBox, param::TransactionLimits, query::error::FindError, + metadata::MetadataValueBox, param::TransactionParameters, query::error::FindError, }; use nonzero_ext::nonzero; use test_samples::{gen_account_in, ALICE_ID, ALICE_KEYPAIR}; @@ -380,16 +380,16 @@ mod tests { let state = State::new(world_with_test_domains(), kura.clone(), query_handle); { let mut state_block = state.block(); - let limits = TransactionLimits { + let limits = TransactionParameters { max_instructions: nonzero!(1000_u64), smart_contract_size: nonzero!(1024_u64), }; - let huge_limits = TransactionLimits { + let huge_limits = TransactionParameters { max_instructions: nonzero!(1000_u64), smart_contract_size: nonzero!(1024_u64), }; - state_block.world.parameters.transaction_limits = limits; + state_block.world.parameters.transaction = limits; let valid_tx = { let instructions: [InstructionBox; 0] = []; @@ -562,7 +562,7 @@ mod tests { .with_instructions(instructions) .sign(ALICE_KEYPAIR.private_key()); - let tx_limits = state_block.transaction_executor().transaction_limits; + let tx_limits = state_block.transaction_executor().limits; let va_tx = AcceptedTransaction::accept(tx, &chain_id, tx_limits)?; let (peer_public_key, _) = KeyPair::random().into_parts(); diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index f92f2df3438..29a590f8e78 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -107,7 +107,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(TriggerEvent::Create(trigger_id))); + .emit_events(Some(TriggerEvent::Created(trigger_id))); Ok(()) } @@ -126,7 +126,7 @@ pub mod isi { if triggers.remove(trigger_id.clone()) { state_transaction .world - .emit_events(Some(TriggerEvent::Delete(trigger_id))); + .emit_events(Some(TriggerEvent::Deleted(trigger_id))); Ok(()) } else { Err(RepetitionError { @@ -165,7 +165,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(TriggerEvent::Extend( + .emit_events(Some(TriggerEvent::Extended( TriggerNumberOfExecutionsChanged { trigger: id, by: self.object, @@ -193,7 +193,7 @@ pub mod isi { // when they will match some of the events? state_transaction .world - .emit_events(Some(TriggerEvent::Shorten( + .emit_events(Some(TriggerEvent::Shortened( TriggerNumberOfExecutionsChanged { trigger, by: self.object, @@ -225,7 +225,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(TriggerEvent::MetadataInsert(MetadataChanged { + .emit_events(Some(TriggerEvent::MetadataInserted(MetadataChanged { target: trigger_id, key: self.key, value: self.value, @@ -257,7 +257,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(TriggerEvent::MetadataRemove(MetadataChanged { + .emit_events(Some(TriggerEvent::MetadataRemoved(MetadataChanged { target: trigger_id, key: self.key, value, diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index 5a231c57d2f..668dbc3f8d7 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -21,11 +21,11 @@ pub mod isi { use eyre::Result; use iroha_data_model::{ - isi::error::{InvalidParameterError, RepetitionError}, - param::Parameter, + isi::error::{InstructionExecutionError, InvalidParameterError, RepetitionError}, + param::{CustomParameter, Parameter}, prelude::*, query::error::FindError, - Level, + JsonString, Level, }; use iroha_primitives::unique_vec::PushResult; @@ -50,7 +50,7 @@ pub mod isi { .into()); } - world.emit_events(Some(PeerEvent::Add(peer_id))); + world.emit_events(Some(PeerEvent::Added(peer_id))); Ok(()) } @@ -71,7 +71,7 @@ pub mod isi { world.trusted_peers_ids.remove(index); - world.emit_events(Some(PeerEvent::Remove(peer_id))); + world.emit_events(Some(PeerEvent::Removed(peer_id))); Ok(()) } @@ -87,6 +87,12 @@ pub mod isi { let domain: Domain = self.object.build(authority); let domain_id = domain.id().clone(); + if domain_id == *iroha_genesis::GENESIS_DOMAIN_ID { + return Err(InstructionExecutionError::InvariantViolation( + "Not allowed to register genesis domain".to_owned(), + )); + } + let world = &mut state_transaction.world; if world.domains.get(&domain_id).is_some() { return Err(RepetitionError { @@ -97,8 +103,7 @@ pub mod isi { } world.domains.insert(domain_id, domain.clone()); - - world.emit_events(Some(DomainEvent::Create(domain))); + world.emit_events(Some(DomainEvent::Created(domain))); Ok(()) } @@ -142,7 +147,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(DomainEvent::Delete(domain_id))); + .emit_events(Some(DomainEvent::Deleted(domain_id))); Ok(()) } @@ -180,7 +185,7 @@ pub mod isi { let role_id = role.id().clone(); world.roles.insert(role_id, role.clone()); - world.emit_events(Some(RoleEvent::Create(role))); + world.emit_events(Some(RoleEvent::Created(role))); Ok(()) } @@ -218,7 +223,7 @@ pub mod isi { return Err(FindError::Role(role_id).into()); } - world.emit_events(Some(RoleEvent::Delete(role_id))); + world.emit_events(Some(RoleEvent::Deleted(role_id))); Ok(()) } @@ -258,7 +263,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(RoleEvent::PermissionAdd(RolePermissionChanged { + .emit_events(Some(RoleEvent::PermissionAdded(RolePermissionChanged { role: role_id, permission: permission_id, }))); @@ -288,7 +293,7 @@ pub mod isi { state_transaction .world - .emit_events(Some(RoleEvent::PermissionRemove(RolePermissionChanged { + .emit_events(Some(RoleEvent::PermissionRemoved(RolePermissionChanged { role: role_id, permission: permission_id, }))); @@ -304,90 +309,70 @@ pub mod isi { _authority: &AccountId, state_transaction: &mut StateTransaction<'_, '_>, ) -> Result<(), Error> { - match self.0 { - Parameter::BlockTime(duration) => { - let old_param = core::mem::replace( - &mut state_transaction.world.parameters.block_time, - duration, - ); + macro_rules! set_parameter { + ($($container:ident($param:ident.$field:ident) => $single:ident::$variant:ident),* $(,)?) => { + match self.0 { $( + Parameter::$container(iroha_data_model::param::$single::$variant(next)) => { + let prev = core::mem::replace( + &mut state_transaction.world.parameters.$param.$field, + next, + ); + + state_transaction.world.emit_events( + Some(ConfigurationEvent::Changed(ParameterChanged { + prev: Parameter::$container(iroha_data_model::param::$single::$variant( + prev, + )), + next: Parameter::$container(iroha_data_model::param::$single::$variant( + next, + )), + })) + ); + })* + Parameter::Custom(next) => { + let prev = state_transaction + .world + .parameters + .custom + .insert(next.id.clone(), next.payload.clone()) + .unwrap_or_else(|| { + iroha_logger::error!( + "{}: Initial parameter value not set during executor migration", + next.id + ); + + JsonString::default() + }); + + state_transaction + .world + .emit_events(Some(ConfigurationEvent::Changed(ParameterChanged { + prev: Parameter::Custom(CustomParameter { + id: next.id.clone(), + payload: prev, + }), + next: Parameter::Custom(next), + }))); + } + } + }; + } - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change(Parameter::BlockTime( - old_param, - )))); - } - Parameter::CommitTime(duration) => { - let old_param = core::mem::replace( - &mut state_transaction.world.parameters.commit_time, - duration, - ); + set_parameter!( + Sumeragi(sumeragi.block_time_ms) => SumeragiParameter::BlockTimeMs, + Sumeragi(sumeragi.commit_time_ms) => SumeragiParameter::CommitTimeMs, - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change(Parameter::CommitTime( - old_param, - )))); - } - Parameter::BlockLimits(block_limits) => { - let old_param = core::mem::replace( - &mut state_transaction.world.parameters.block_limits, - block_limits, - ); + Block(block.max_transactions) => BlockParameter::MaxTransactions, - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change(Parameter::BlockLimits( - old_param, - )))); - } - Parameter::TransactionLimits(txn_limits) => { - let old_param = core::mem::replace( - &mut state_transaction.world.parameters.transaction_limits, - txn_limits, - ); + Transaction(transaction.max_instructions) => TransactionParameter::MaxInstructions, + Transaction(transaction.smart_contract_size) => TransactionParameter::SmartContractSize, - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change( - Parameter::TransactionLimits(old_param), - ))); - } - Parameter::SmartContractLimits(limits) => { - let old_param = core::mem::replace( - &mut state_transaction.world.parameters.smart_contract_limits, - limits, - ); - - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change( - Parameter::SmartContractLimits(old_param), - ))); - } - Parameter::ExecutorLimits(limits) => { - let old_param = core::mem::replace( - &mut state_transaction.world.parameters.executor_limits, - limits, - ); - - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change(Parameter::ExecutorLimits( - old_param, - )))); - } - Parameter::Custom(custom) => { - let old_param = - core::mem::replace(&mut state_transaction.world.parameters.custom, custom); + SmartContract(smart_contract.fuel) => SmartContractParameter::Fuel, + SmartContract(smart_contract.memory) => SmartContractParameter::Memory, - state_transaction - .world - .emit_events(Some(ConfigurationEvent::Change(Parameter::Custom( - old_param, - )))); - } - } + Executor(executor.fuel) => SmartContractParameter::Fuel, + Executor(executor.memory) => SmartContractParameter::Memory, + ); Ok(()) } @@ -426,7 +411,7 @@ pub mod isi { state_transaction .world - .emit_events(std::iter::once(ExecutorEvent::Upgrade(ExecutorUpgrade { + .emit_events(std::iter::once(ExecutorEvent::Upgraded(ExecutorUpgrade { new_data_model: state_transaction.world.executor_data_model.clone(), }))); diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index 981ff5b197f..79b97a00398 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -10,7 +10,7 @@ use iroha_data_model::{ account::AccountId, executor::{self, ExecutorDataModel, MigrationResult}, isi::InstructionBox, - param::SmartContractLimits as Config, + param::SmartContractParameters as Config, prelude::*, query::{QueryBox, QueryId, QueryOutputBox, QueryRequest, SmartContractQuery}, smart_contract::payloads::{self, Validate}, diff --git a/core/src/state.rs b/core/src/state.rs index c5ef21a01b4..9fda39afd2f 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -63,7 +63,7 @@ use crate::{ /// For example registration of domain, will have this as an ISI target. #[derive(Default, Serialize)] pub struct World { - /// Iroha on-chain built-in parameters. + /// Iroha on-chain parameters. pub(crate) parameters: Cell, /// Identifications of discovered trusted peers. pub(crate) trusted_peers_ids: Cell, @@ -85,7 +85,7 @@ pub struct World { /// Struct for block's aggregated changes pub struct WorldBlock<'world> { - /// Iroha on-chain built-in parameters. + /// Iroha on-chain parameters. pub parameters: CellBlock<'world, Parameters>, /// Identifications of discovered trusted peers. pub(crate) trusted_peers_ids: CellBlock<'world, PeersIds>, @@ -109,7 +109,7 @@ pub struct WorldBlock<'world> { /// Struct for single transaction's aggregated changes pub struct WorldTransaction<'block, 'world> { - /// Iroha on-chain built-in parameters. + /// Iroha on-chain parameters. pub(crate) parameters: CellTransaction<'block, 'world, Parameters>, /// Identifications of discovered trusted peers. pub(crate) trusted_peers_ids: CellTransaction<'block, 'world, PeersIds>, @@ -141,7 +141,7 @@ struct TransactionEventBuffer<'block> { /// Consistent point in time view of the [`World`] pub struct WorldView<'world> { - /// Iroha on-chain built-in parameters. + /// Iroha on-chain parameters. pub(crate) parameters: CellView<'world, Parameters>, /// Identifications of discovered trusted peers. pub(crate) trusted_peers_ids: CellView<'world, PeersIds>, @@ -729,7 +729,7 @@ impl WorldTransaction<'_, '_> { Self::emit_events_impl( &mut self.triggers, &mut self.events_buffer, - Some(AccountEvent::Asset(AssetEvent::Create(asset.clone()))), + Some(AccountEvent::Asset(AssetEvent::Created(asset.clone()))), ); asset })) @@ -774,7 +774,7 @@ impl WorldTransaction<'_, '_> { self.emit_events({ Some(DomainEvent::AssetDefinition( - AssetDefinitionEvent::TotalQuantityChange(AssetDefinitionTotalQuantityChanged { + AssetDefinitionEvent::TotalQuantityChanged(AssetDefinitionTotalQuantityChanged { asset_definition: definition_id.clone(), total_amount: asset_total_amount, }), @@ -807,7 +807,7 @@ impl WorldTransaction<'_, '_> { self.emit_events({ Some(DomainEvent::AssetDefinition( - AssetDefinitionEvent::TotalQuantityChange(AssetDefinitionTotalQuantityChanged { + AssetDefinitionEvent::TotalQuantityChanged(AssetDefinitionTotalQuantityChanged { asset_definition: definition_id.clone(), total_amount: asset_total_amount, }), @@ -893,18 +893,6 @@ impl Drop for TransactionEventBuffer<'_> { } } -impl StateBlock<'_> { - pub(crate) fn consensus_estimation(&self) -> Duration { - let Parameters { - block_time, - commit_time, - .. - } = self.world.parameters(); - - *block_time + (*commit_time / 2) - } -} - impl State { /// Construct [`State`] with given [`World`]. #[must_use] @@ -1067,7 +1055,7 @@ pub trait StateReadOnly { /// Get transaction executor fn transaction_executor(&self) -> TransactionExecutor { - TransactionExecutor::new(self.world().parameters().transaction_limits) + TransactionExecutor::new(self.world().parameters().transaction) } } @@ -1306,7 +1294,7 @@ impl StateTransaction<'_, '_> { } Executable::Wasm(bytes) => { let mut wasm_runtime = wasm::RuntimeBuilder::::new() - .with_config(self.world().parameters().smart_contract_limits) + .with_config(self.world().parameters().smart_contract) .with_engine(self.engine.clone()) // Cloning engine is cheap .build()?; wasm_runtime @@ -1348,7 +1336,7 @@ impl StateTransaction<'_, '_> { .expect("INTERNAL BUG: contract is not present") .clone(); let mut wasm_runtime = wasm::RuntimeBuilder::::new() - .with_config(self.world().parameters().smart_contract_limits) + .with_config(self.world().parameters().smart_contract) .with_engine(self.engine.clone()) // Cloning engine is cheap .build()?; wasm_runtime diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 4510f7503af..500f5f99971 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -2,9 +2,7 @@ use std::{collections::BTreeSet, sync::mpsc}; use iroha_crypto::{HashOf, KeyPair}; -use iroha_data_model::{ - block::*, events::pipeline::PipelineEventBox, param::Parameters, peer::PeerId, -}; +use iroha_data_model::{block::*, events::pipeline::PipelineEventBox, peer::PeerId}; use iroha_p2p::UpdateTopology; use tracing::{span, Level}; @@ -63,10 +61,6 @@ impl Debug for Sumeragi { } impl Sumeragi { - fn pipeline_time(parameters: &Parameters) -> Duration { - parameters.block_time + parameters.commit_time - } - fn role(&self) -> Role { self.topology.role(&self.peer_id) } @@ -794,9 +788,14 @@ impl Sumeragi { #[cfg(debug_assertions)] if is_genesis_peer && self.debug_force_soft_fork { - std::thread::sleep( - Sumeragi::pipeline_time(voting_block.state_block.world.parameters()) * 2, - ); + let pipeline_time = voting_block + .state_block + .world + .parameters() + .sumeragi + .pipeline_time(); + + std::thread::sleep(pipeline_time * 2); } else { let msg = BlockCommitted::from(&committed_block); self.broadcast_packet(msg); @@ -828,11 +827,11 @@ impl Sumeragi { .world .view() .parameters - .block_limits + .block .max_transactions .try_into() .expect("INTERNAL BUG: transactions in block exceed usize::MAX"); - let block_time = state.world.view().parameters.block_time; + let block_time = state.world.view().parameters.sumeragi.block_time(); let tx_cache_full = self.transaction_cache.len() >= max_transactions.get(); let deadline_reached = self.round_start_time.elapsed() > block_time; let tx_cache_non_empty = !self.transaction_cache.is_empty(); @@ -851,7 +850,8 @@ impl Sumeragi { .unpack(|e| self.send_event(e)); let created_in = create_block_start_time.elapsed(); - if created_in > Sumeragi::pipeline_time(state.world.view().parameters()) / 2 { + let pipeline_time = state.world.view().parameters().sumeragi.pipeline_time(); + if created_in > pipeline_time / 2 { warn!( role=%self.role(), peer_id=%self.peer_id, @@ -997,7 +997,7 @@ pub(crate) fn run( let mut should_sleep = false; let mut view_change_proof_chain = ProofChain::default(); // Duration after which a view change is suggested - let mut view_change_time = Sumeragi::pipeline_time(state.world.view().parameters()); + let mut view_change_time = state.world.view().parameters().sumeragi.pipeline_time(); // Instant when the previous view change or round happened. let mut last_view_change_time = Instant::now(); @@ -1031,7 +1031,7 @@ pub(crate) fn run( .world .view() .parameters - .block_limits + .block .max_transactions .try_into() .expect("INTERNAL BUG: transactions in block exceed usize::MAX"), @@ -1047,7 +1047,7 @@ pub(crate) fn run( reset_state( &sumeragi.peer_id, - Sumeragi::pipeline_time(state.world.view().parameters()), + state.world.view().parameters().sumeragi.pipeline_time(), view_change_index, &mut sumeragi.was_commit, &mut sumeragi.topology, @@ -1132,12 +1132,12 @@ pub(crate) fn run( // NOTE: View change must be periodically suggested until it is accepted. // Must be initialized to pipeline time but can increase by chosen amount - view_change_time += Sumeragi::pipeline_time(state.world.view().parameters()); + view_change_time += state.world.view().parameters().sumeragi.pipeline_time(); } reset_state( &sumeragi.peer_id, - Sumeragi::pipeline_time(state.world.view().parameters()), + state.world.view().parameters().sumeragi.pipeline_time(), view_change_index, &mut sumeragi.was_commit, &mut sumeragi.topology, @@ -1372,12 +1372,9 @@ mod tests { let tx = TransactionBuilder::new(chain_id.clone(), alice_id.clone()) .with_instructions([fail_box]) .sign(alice_keypair.private_key()); - let tx = AcceptedTransaction::accept( - tx, - chain_id, - state_block.transaction_executor().transaction_limits, - ) - .expect("Valid"); + let tx = + AcceptedTransaction::accept(tx, chain_id, state_block.transaction_executor().limits) + .expect("Valid"); // Creating a block of two identical transactions and validating it let block = BlockBuilder::new(vec![tx.clone(), tx], topology.clone(), Vec::new()) @@ -1409,7 +1406,7 @@ mod tests { let tx1 = AcceptedTransaction::accept( tx1, chain_id, - state_block.transaction_executor().transaction_limits, + state_block.transaction_executor().limits, ) .map(Into::into) .expect("Valid"); @@ -1419,7 +1416,7 @@ mod tests { let tx2 = AcceptedTransaction::accept( tx2, chain_id, - state_block.transaction_executor().transaction_limits, + state_block.transaction_executor().limits, ) .map(Into::into) .expect("Valid"); diff --git a/core/src/tx.rs b/core/src/tx.rs index 68ac2bf9f5b..8401052d795 100644 --- a/core/src/tx.rs +++ b/core/src/tx.rs @@ -95,7 +95,7 @@ impl AcceptedTransaction { pub fn accept( tx: SignedTransaction, expected_chain_id: &ChainId, - limits: TransactionLimits, + limits: TransactionParameters, ) -> Result { let actual_chain_id = tx.chain(); @@ -131,7 +131,8 @@ impl AcceptedTransaction { return Err(AcceptTransactionFail::TransactionLimit( TransactionLimitError { reason: format!( - "Wasm binary too large, max size is {}, but got {}", + "WASM binary size is too large: max {}, got {} \ + (configured by \"Parameter::SmartContractLimits\")", limits.smart_contract_size, smart_contract_size ), }, @@ -172,14 +173,16 @@ impl AsRef for AcceptedTransaction { /// Validation is skipped for genesis. #[derive(Clone, Copy)] pub struct TransactionExecutor { - /// [`TransactionLimits`] field - pub transaction_limits: TransactionLimits, + /// [`TransactionParameters`] field + pub limits: TransactionParameters, } impl TransactionExecutor { /// Construct [`TransactionExecutor`] - pub fn new(transaction_limits: TransactionLimits) -> Self { - Self { transaction_limits } + pub fn new(transaction_limits: TransactionParameters) -> Self { + Self { + limits: transaction_limits, + } } /// Move transaction lifecycle forward by checking if the @@ -252,7 +255,7 @@ impl TransactionExecutor { state_transaction, authority, wasm, - self.transaction_limits.max_instructions, + self.limits.max_instructions, ) }) .map_err(|error| WasmExecutionFail { diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 32958c340c4..559daf0c20e 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -770,8 +770,8 @@ impl TestConfig for Config { } fn pipeline_time() -> Duration { - let defaults = iroha_data_model::param::Parameters::default(); - defaults.block_time + defaults.commit_time + let defaults = iroha_data_model::param::SumeragiParameters::default(); + defaults.block_time() + defaults.commit_time() } fn block_sync_gossip_time() -> Duration { diff --git a/data_model/derive/src/id.rs b/data_model/derive/src/id.rs index c9a64c64537..baaa45daa31 100644 --- a/data_model/derive/src/id.rs +++ b/data_model/derive/src/id.rs @@ -85,29 +85,29 @@ pub fn impl_id_eq_ord_hash(emitter: &mut Emitter, input: &syn::DeriveInput) -> T quote! { #identifiable_derive - impl #impl_generics ::core::cmp::PartialOrd for #name #ty_generics #where_clause where Self: Identifiable { + impl #impl_generics ::core::cmp::PartialOrd for #name #ty_generics #where_clause where Self: crate::Identifiable { #[inline] fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { Some(self.cmp(other)) } } - impl #impl_generics ::core::cmp::Ord for #name #ty_generics #where_clause where Self: Identifiable { + impl #impl_generics ::core::cmp::Ord for #name #ty_generics #where_clause where Self: crate::Identifiable { fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { - self.id().cmp(other.id()) + ::id(self).cmp(::id(other)) } } - impl #impl_generics ::core::cmp::Eq for #name #ty_generics #where_clause where Self: Identifiable {} - impl #impl_generics ::core::cmp::PartialEq for #name #ty_generics #where_clause where Self: Identifiable { + impl #impl_generics ::core::cmp::Eq for #name #ty_generics #where_clause where Self: crate::Identifiable {} + impl #impl_generics ::core::cmp::PartialEq for #name #ty_generics #where_clause where Self: crate::Identifiable { fn eq(&self, other: &Self) -> bool { - self.id() == other.id() + ::id(self) == ::id(other) } } - impl #impl_generics ::core::hash::Hash for #name #ty_generics #where_clause where Self: Identifiable { + impl #impl_generics ::core::hash::Hash for #name #ty_generics #where_clause where Self: crate::Identifiable { fn hash(&self, state: &mut H) { - self.id().hash(state); + ::id(self).hash(state) } } } @@ -119,7 +119,7 @@ fn derive_identifiable(emitter: &mut Emitter, input: &IdDeriveInput) -> TokenStr let (id_type, id_expr) = get_id_type(emitter, input); quote! { - impl #impl_generics Identifiable for #name #ty_generics #where_clause { + impl #impl_generics crate::Identifiable for #name #ty_generics #where_clause { type Id = #id_type; #[inline] @@ -142,8 +142,8 @@ fn get_id_type(emitter: &mut Emitter, input: &IdDeriveInput) -> (syn::Type, syn: } IdAttr::Transparent => { return ( - parse_quote! {<#ty as Identifiable>::Id}, - parse_quote! {Identifiable::id(&self.#field_name)}, + parse_quote! {<#ty as crate::Identifiable>::Id}, + parse_quote! {crate::Identifiable::id(&self.#field_name)}, ); } IdAttr::Missing => { diff --git a/data_model/derive/tests/has_origin_generics.rs b/data_model/derive/tests/has_origin_generics.rs index a1090a312cc..69724714bb1 100644 --- a/data_model/derive/tests/has_origin_generics.rs +++ b/data_model/derive/tests/has_origin_generics.rs @@ -16,12 +16,6 @@ struct Object { id: ObjectId, } -impl Object { - fn id(&self) -> &ObjectId { - &self.id - } -} - #[allow(clippy::enum_variant_names)] // it's a test, duh #[derive(Debug, HasOrigin)] #[has_origin(origin = Object)] diff --git a/data_model/src/account.rs b/data_model/src/account.rs index 60a839db856..893ca23aca4 100644 --- a/data_model/src/account.rs +++ b/data_model/src/account.rs @@ -6,7 +6,6 @@ use core::str::FromStr; use std::collections::btree_map; use derive_more::{Constructor, DebugCustom, Display}; -use getset::Getters; use iroha_data_model_derive::{model, IdEqOrdHash}; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; @@ -18,7 +17,7 @@ use crate::{ asset::{Asset, AssetDefinitionId, AssetsMap}, domain::prelude::*, metadata::Metadata, - HasMetadata, Identifiable, ParseError, PublicKey, Registered, + HasMetadata, ParseError, PublicKey, Registered, }; /// API to work with collections of [`Id`] : [`Account`] mappings. @@ -26,6 +25,8 @@ pub type AccountsMap = btree_map::BTreeMap; #[model] mod model { + use getset::Getters; + use super::*; /// Identification of [`Account`] by the combination of the [`PublicKey`] as its sole signatory and the [`Domain`](crate::domain::Domain) it belongs to. @@ -75,7 +76,6 @@ mod model { Display, Clone, IdEqOrdHash, - Getters, Decode, Encode, Deserialize, diff --git a/data_model/src/asset.rs b/data_model/src/asset.rs index 6b3972c1e49..750f261f577 100644 --- a/data_model/src/asset.rs +++ b/data_model/src/asset.rs @@ -7,7 +7,6 @@ use core::{fmt, str::FromStr}; use std::collections::btree_map; use derive_more::{Constructor, DebugCustom, Display}; -use getset::{CopyGetters, Getters}; use iroha_data_model_derive::{model, IdEqOrdHash}; use iroha_primitives::numeric::{Numeric, NumericSpec, NumericSpecParseError}; use iroha_schema::IntoSchema; @@ -17,8 +16,8 @@ use serde_with::{DeserializeFromStr, SerializeDisplay}; pub use self::model::*; use crate::{ - account::prelude::*, domain::prelude::*, ipfs::IpfsPath, metadata::Metadata, HasMetadata, - Identifiable, Name, ParseError, Registered, + account::prelude::*, domain::prelude::*, ipfs::IpfsPath, metadata::Metadata, HasMetadata, Name, + ParseError, Registered, }; /// API to work with collections of [`Id`] : [`Asset`] mappings. @@ -34,6 +33,7 @@ pub type AssetTotalQuantityMap = btree_map::BTreeMap #[model] mod model { + use getset::{CopyGetters, Getters}; use iroha_macro::FromVariant; use super::*; diff --git a/data_model/src/domain.rs b/data_model/src/domain.rs index 89df2d7bcfd..428b223c579 100644 --- a/data_model/src/domain.rs +++ b/data_model/src/domain.rs @@ -4,7 +4,6 @@ use alloc::{format, string::String, vec::Vec}; use derive_more::{Constructor, Display, FromStr}; -use getset::Getters; use iroha_data_model_derive::{model, IdEqOrdHash}; use iroha_primitives::numeric::Numeric; use iroha_schema::IntoSchema; @@ -24,6 +23,8 @@ use crate::{ #[model] mod model { + use getset::Getters; + use super::*; /// Identification of a [`Domain`]. diff --git a/data_model/src/events/data/events.rs b/data_model/src/events/data/events.rs index efe00d42fae..c041fdb6bff 100644 --- a/data_model/src/events/data/events.rs +++ b/data_model/src/events/data/events.rs @@ -111,16 +111,16 @@ mod asset { #[has_origin(origin = Asset)] pub enum AssetEvent { #[has_origin(asset => asset.id())] - Create(Asset), - Delete(AssetId), + Created(Asset), + Deleted(AssetId), #[has_origin(asset_changed => &asset_changed.asset)] - Add(AssetChanged), + Added(AssetChanged), #[has_origin(asset_changed => &asset_changed.asset)] - Remove(AssetChanged), + Removed(AssetChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataInsert(AssetMetadataChanged), + MetadataInserted(AssetMetadataChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataRemove(AssetMetadataChanged), + MetadataRemoved(AssetMetadataChanged), } } @@ -128,17 +128,17 @@ mod asset { #[has_origin(origin = AssetDefinition)] pub enum AssetDefinitionEvent { #[has_origin(asset_definition => asset_definition.id())] - Create(AssetDefinition), + Created(AssetDefinition), Delete(AssetDefinitionId), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataInsert(AssetDefinitionMetadataChanged), + MetadataInserted(AssetDefinitionMetadataChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataRemove(AssetDefinitionMetadataChanged), - MintabilityChange(AssetDefinitionId), + MetadataRemoved(AssetDefinitionMetadataChanged), + MintabilityChanged(AssetDefinitionId), #[has_origin(total_quantity_changed => &total_quantity_changed.asset_definition)] - TotalQuantityChange(AssetDefinitionTotalQuantityChanged), + TotalQuantityChanged(AssetDefinitionTotalQuantityChanged), #[has_origin(ownership_changed => &ownership_changed.asset_definition)] - OwnerChange(AssetDefinitionOwnerChanged), + OwnerChanged(AssetDefinitionOwnerChanged), } } @@ -224,8 +224,8 @@ mod peer { data_event! { #[has_origin(origin = Peer)] pub enum PeerEvent { - Add(PeerId), - Remove(PeerId), + Added(PeerId), + Removed(PeerId), } } } @@ -242,16 +242,16 @@ mod role { #[has_origin(origin = Role)] pub enum RoleEvent { #[has_origin(role => role.id())] - Create(Role), - Delete(RoleId), + Created(Role), + Deleted(RoleId), /// [`Permission`]s with particular [`PermissionId`] /// were removed added to the role. #[has_origin(permission_added => &permission_added.role)] - PermissionAdd(RolePermissionChanged), + PermissionAdded(RolePermissionChanged), /// [`Permission`]s with particular [`PermissionId`] /// were removed from the role. #[has_origin(permission_removed => &permission_removed.role)] - PermissionRemove(RolePermissionChanged), + PermissionRemoved(RolePermissionChanged), } } @@ -299,24 +299,22 @@ mod account { #[has_origin(origin = Account)] pub enum AccountEvent { #[has_origin(account => account.id())] - Create(Account), - Delete(AccountId), - AuthenticationAdd(AccountId), - AuthenticationRemove(AccountId), + Created(Account), + Deleted(AccountId), #[has_origin(asset_event => &asset_event.origin().account)] Asset(AssetEvent), #[has_origin(permission_changed => &permission_changed.account)] - PermissionAdd(AccountPermissionChanged), + PermissionAdded(AccountPermissionChanged), #[has_origin(permission_changed => &permission_changed.account)] - PermissionRemove(AccountPermissionChanged), + PermissionRemoved(AccountPermissionChanged), #[has_origin(role_changed => &role_changed.account)] - RoleGrant(AccountRoleChanged), + RoleGranted(AccountRoleChanged), #[has_origin(role_changed => &role_changed.account)] - RoleRevoke(AccountRoleChanged), + RoleRevoked(AccountRoleChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataInsert(AccountMetadataChanged), + MetadataInserted(AccountMetadataChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataRemove(AccountMetadataChanged), + MetadataRemoved(AccountMetadataChanged), } } @@ -391,18 +389,18 @@ mod domain { #[has_origin(origin = Domain)] pub enum DomainEvent { #[has_origin(domain => domain.id())] - Create(Domain), - Delete(DomainId), + Created(Domain), + Deleted(DomainId), #[has_origin(asset_definition_event => &asset_definition_event.origin().domain)] AssetDefinition(AssetDefinitionEvent), #[has_origin(account_event => &account_event.origin().domain)] Account(AccountEvent), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataInsert(DomainMetadataChanged), + MetadataInserted(DomainMetadataChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataRemove(DomainMetadataChanged), + MetadataRemoved(DomainMetadataChanged), #[has_origin(owner_changed => &owner_changed.domain)] - OwnerChange(DomainOwnerChanged), + OwnerChanged(DomainOwnerChanged), } } @@ -447,16 +445,16 @@ mod trigger { data_event! { #[has_origin(origin = Trigger)] pub enum TriggerEvent { - Create(TriggerId), - Delete(TriggerId), + Created(TriggerId), + Deleted(TriggerId), #[has_origin(number_of_executions_changed => &number_of_executions_changed.trigger)] - Extend(TriggerNumberOfExecutionsChanged), + Extended(TriggerNumberOfExecutionsChanged), #[has_origin(number_of_executions_changed => &number_of_executions_changed.trigger)] - Shorten(TriggerNumberOfExecutionsChanged), + Shortened(TriggerNumberOfExecutionsChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataInsert(TriggerMetadataChanged), + MetadataInserted(TriggerMetadataChanged), #[has_origin(metadata_changed => &metadata_changed.target)] - MetadataRemove(TriggerMetadataChanged), + MetadataRemoved(TriggerMetadataChanged), } } @@ -497,6 +495,28 @@ mod config { mod model { use super::*; + /// Changed parameter event + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type] + pub struct ParameterChanged { + /// Previous value for the parameter + pub prev: Parameter, + /// Next value for the parameter + pub next: Parameter, + } + #[derive( Debug, Clone, @@ -514,7 +534,7 @@ mod config { )] #[ffi_type] pub enum ConfigurationEvent { - Change(Parameter), + Changed(ParameterChanged), } } } @@ -556,7 +576,7 @@ mod executor { #[serde(untagged)] // Unaffected by #3330, as single unit variant #[repr(transparent)] pub enum ExecutorEvent { - Upgrade(ExecutorUpgrade), + Upgraded(ExecutorUpgrade), } /// Information about the updated executor data model. @@ -649,7 +669,7 @@ pub mod prelude { AssetDefinitionOwnerChanged, AssetDefinitionTotalQuantityChanged, AssetEvent, AssetEventSet, }, - config::{ConfigurationEvent, ConfigurationEventSet}, + config::{ConfigurationEvent, ConfigurationEventSet, ParameterChanged}, domain::{DomainEvent, DomainEventSet, DomainOwnerChanged}, executor::{ExecutorEvent, ExecutorEventSet, ExecutorUpgrade}, peer::{PeerEvent, PeerEventSet}, diff --git a/data_model/src/events/data/filters.rs b/data_model/src/events/data/filters.rs index 0a112a4fbb8..d25bd69ba24 100644 --- a/data_model/src/events/data/filters.rs +++ b/data_model/src/events/data/filters.rs @@ -753,10 +753,10 @@ mod tests { // the first one is just a domain event // the second one is an account event with a domain event inside // the third one is an asset event with an account event with a domain event inside - let domain_created = DomainEvent::Create(domain).into(); - let account_created = DomainEvent::Account(AccountEvent::Create(account)).into(); + let domain_created = DomainEvent::Created(domain).into(); + let account_created = DomainEvent::Account(AccountEvent::Created(account)).into(); let asset_created = - DomainEvent::Account(AccountEvent::Asset(AssetEvent::Create(asset))).into(); + DomainEvent::Account(AccountEvent::Asset(AssetEvent::Created(asset))).into(); // test how the differently nested filters with with the events let domain_filter = DataEventFilter::Domain(DomainEventFilter::new().for_domain(domain_id)); diff --git a/data_model/src/executor.rs b/data_model/src/executor.rs index 8836c40219e..c9c874e663e 100644 --- a/data_model/src/executor.rs +++ b/data_model/src/executor.rs @@ -5,21 +5,22 @@ use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; #[cfg(feature = "std")] use std::collections::BTreeSet; -use derive_more::{Constructor, Display}; -use getset::Getters; use iroha_data_model_derive::model; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; pub use self::model::*; use crate::{permission::PermissionId, transaction::WasmSmartContract, JsonString}; #[model] mod model { + use derive_more::{Constructor, Display}; + use getset::Getters; use iroha_schema::Ident; + use iroha_schema::IntoSchema; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; use super::*; + use crate::param::CustomParameterId; /// executor that checks if an operation satisfies some conditions. /// @@ -79,15 +80,22 @@ mod model { #[ffi_type] #[display(fmt = "{self:?}")] pub struct ExecutorDataModel { + /// Corresponds to the id of [`InstructionBox::Custom`]. + /// + /// It should be set during executor migration, + /// so it can be retrieved through Iroha API. + pub custom_instruction: Option, + /// Corresponds to the id of [`Parameter::Custom`]. + /// + /// It should be set during executor migration, + /// so it can be retrieved through Iroha API. + pub parameters: BTreeSet, /// Permission tokens supported by the executor. /// /// These IDs refer to the types in the schema. pub permissions: BTreeSet, - /// Corresponds to the id of [`InstructionBox::Custom`]. - /// - /// It is recommended to set it, so clients can retrieve it through Iroha API. - pub custom_instruction: Option, - /// Data model JSON schema, typically produced by [`IntoSchema`]. + /// Data model JSON schema. Includes description of all executor defined types + /// (permissions, parameters, instructions) pub schema: JsonString, } diff --git a/data_model/src/ipfs.rs b/data_model/src/ipfs.rs index 635900ba5c2..e6dbaca5c76 100644 --- a/data_model/src/ipfs.rs +++ b/data_model/src/ipfs.rs @@ -4,18 +4,19 @@ use alloc::{format, string::String, vec::Vec}; use core::str::FromStr; -use derive_more::Display; use iroha_data_model_derive::model; use iroha_primitives::conststr::ConstString; -use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode, Input}; -use serde_with::{DeserializeFromStr, SerializeDisplay}; pub use self::model::*; use crate::ParseError; #[model] mod model { + use derive_more::Display; + use iroha_schema::IntoSchema; + use serde_with::{DeserializeFromStr, SerializeDisplay}; + use super::*; /// Represents path in IPFS. Performs checks to ensure path validity. diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 829f82d679a..b241bac89cb 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -17,8 +17,7 @@ use alloc::{ }; use core::{fmt, fmt::Debug, str::FromStr}; -use derive_more::{Constructor, Display, FromStr}; -use getset::Getters; +use derive_more::{Constructor, Display}; use iroha_crypto::PublicKey; use iroha_data_model_derive::{model, EnumRef}; use iroha_macro::FromVariant; @@ -215,6 +214,8 @@ impl std::error::Error for EnumTryAsError a.id().clone().into(), IdentifiableBox::Trigger(a) => a.id().clone().into(), IdentifiableBox::Role(a) => a.id().clone().into(), + IdentifiableBox::CustomParameter(a) => a.id().clone().into(), } } } diff --git a/data_model/src/metadata.rs b/data_model/src/metadata.rs index bf3d1be835d..801f2ce8561 100644 --- a/data_model/src/metadata.rs +++ b/data_model/src/metadata.rs @@ -11,13 +11,8 @@ use core::borrow::Borrow; #[cfg(feature = "std")] use std::collections::btree_map; -use derive_more::Display; use iroha_data_model_derive::model; -use iroha_macro::FromVariant; use iroha_primitives::numeric::Numeric; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; pub use self::model::*; use crate::Name; @@ -27,6 +22,12 @@ pub type Path = [Name]; #[model] mod model { + use derive_more::Display; + use iroha_macro::FromVariant; + use iroha_schema::IntoSchema; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; + use super::*; /// Collection of parameters by their names with checked insertion. @@ -216,9 +217,7 @@ impl Metadata { key: Name, value: impl Into, ) -> Option { - let value = value.into(); - - self.0.insert(key, value) + self.0.insert(key, value.into()) } } diff --git a/data_model/src/name.rs b/data_model/src/name.rs index 6094cb7acf4..bf87b77a275 100644 --- a/data_model/src/name.rs +++ b/data_model/src/name.rs @@ -2,20 +2,21 @@ //! and related implementations and trait implementations. #[cfg(not(feature = "std"))] use alloc::{format, string::String, vec::Vec}; -use core::{borrow::Borrow, ops::RangeInclusive, str::FromStr}; +use core::{borrow::Borrow, str::FromStr}; -use derive_more::{DebugCustom, Display}; use iroha_data_model_derive::model; use iroha_primitives::conststr::ConstString; -use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode, Input}; use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::{isi::error::InvalidParameterError, ParseError}; +use crate::ParseError; #[model] mod model { + use derive_more::{DebugCustom, Display}; + use iroha_schema::IntoSchema; + use super::*; /// `Name` struct represents the type of Iroha Entities names, such as @@ -41,27 +42,6 @@ mod model { } impl Name { - /// Check if `range` contains the number of chars in the inner `ConstString` of this [`Name`]. - /// - /// # Errors - /// Fails if `range` does not - pub fn validate_len( - &self, - range: impl Into>, - ) -> Result<(), InvalidParameterError> { - let range = range.into(); - let Ok(true) = &self - .0 - .chars() - .count() - .try_into() - .map(|len| range.contains(&len)) - else { - return Err(InvalidParameterError::NameLength); - }; - Ok(()) - } - /// Check if `candidate` string would be valid [`Name`]. /// /// # Errors diff --git a/data_model/src/param.rs b/data_model/src/param.rs index b1a9358c604..208483151af 100644 --- a/data_model/src/param.rs +++ b/data_model/src/param.rs @@ -1,19 +1,52 @@ //! Structures, traits and impls related to `Paramater`s. +#[cfg(not(feature = "std"))] +use alloc::{collections::btree_map, format, string::String, vec::Vec}; use core::{num::NonZeroU64, time::Duration}; +#[cfg(feature = "std")] +use std::collections::btree_map; +use iroha_data_model_derive::model; use nonzero_ext::nonzero; -use strum::EnumDiscriminants; pub use self::model::*; -use super::*; +use crate::{name::Name, JsonString}; + +/// Collection of [`CustomParameter`]s +type CustomParameters = btree_map::BTreeMap; #[model] mod model { - use getset::CopyGetters; + use derive_more::{Constructor, Display}; + use getset::{CopyGetters, Getters}; + use iroha_data_model_derive::IdEqOrdHash; + use iroha_schema::IntoSchema; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; + use strum::EnumDiscriminants; use super::*; - /// Limits that a transaction must obey to be accepted. + /// Id of a custom parameter + #[derive( + Debug, + Display, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Constructor, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type] + pub struct CustomParameterId(pub Name); + + /// Limits that govern consensus operation #[derive( Debug, Display, @@ -23,20 +56,46 @@ mod model { Eq, PartialOrd, Ord, - CopyGetters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[display(fmt = "{max_instructions},{smart_contract_size}_TL")] - #[getset(get_copy = "pub")] - pub struct TransactionLimits { - /// Maximum number of instructions per transaction - pub max_instructions: NonZeroU64, - /// Maximum size of wasm binary in bytes - pub smart_contract_size: NonZeroU64, + #[display(fmt = "{block_time_ms},{commit_time_ms}_SL")] + pub struct SumeragiParameters { + /// Maximal amount of time (in milliseconds) a peer will wait before forcing creation of a new block. + /// + /// A block is created if this limit or [`BlockParameters::max_transactions`] limit is reached, + /// whichever comes first. Regardless of the limits, an empty block is never created. + pub block_time_ms: u64, + /// Time (in milliseconds) a peer will wait for a block to be committed. + /// + /// If this period expires the block will request a view change + pub commit_time_ms: u64, + } + + /// Single Sumeragi parameter + /// + /// Check [`SumeragiParameters`] for more details + #[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Serialize, + Deserialize, + IntoSchema, + )] + pub enum SumeragiParameter { + BlockTimeMs(u64), + CommitTimeMs(u64), } /// Limits that a block must obey to be accepted. @@ -58,7 +117,7 @@ mod model { )] #[display(fmt = "{max_transactions}_BL")] #[getset(get_copy = "pub")] - pub struct BlockLimits { + pub struct BlockParameters { /// Maximal number of transactions in a block. /// /// A block is created if this limit is reached or `Self::BlockTime` has expired, @@ -66,7 +125,78 @@ mod model { pub max_transactions: NonZeroU64, } - /// Limits that a smart contract must obey at runtime to not be considered invalid. + /// Single block parameter + /// + /// Check [`BlockParameters`] for more details + #[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Serialize, + Deserialize, + IntoSchema, + )] + pub enum BlockParameter { + MaxTransactions(NonZeroU64), + } + + /// Limits that a transaction must obey to be accepted. + #[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + CopyGetters, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[display(fmt = "{max_instructions},{smart_contract_size}_TL")] + #[getset(get_copy = "pub")] + pub struct TransactionParameters { + /// Maximum number of instructions per transaction + pub max_instructions: NonZeroU64, + /// Maximum size of wasm binary in bytes + pub smart_contract_size: NonZeroU64, + } + + /// Single transaction parameter + /// + /// Check [`TransactionParameters`] for more details + #[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Serialize, + Deserialize, + IntoSchema, + )] + pub enum TransactionParameter { + MaxInstructions(NonZeroU64), + SmartContractSize(NonZeroU64), + } + + /// Limits that a smart contract must obey at runtime to considered valid. #[derive( Debug, Display, @@ -83,56 +213,55 @@ mod model { Deserialize, IntoSchema, )] - #[display(fmt = "{fuel},{memory}_SL")] + #[display(fmt = "{fuel},{memory}_SCL")] #[getset(get_copy = "pub")] - pub struct SmartContractLimits { + pub struct SmartContractParameters { /// Maximum amount of fuel that a smart contract can consume pub fuel: NonZeroU64, /// Maximum amount of memory that a smart contract can use pub memory: NonZeroU64, } - /// Single blockchain [`Parameter`]. Controls how blockchain works. + /// Single smart contract parameter + /// + /// Check [`SmartContractParameters`] for more details #[derive( Debug, + Display, Clone, + Copy, PartialEq, Eq, PartialOrd, Ord, - EnumDiscriminants, Decode, Encode, - Deserialize, Serialize, + Deserialize, IntoSchema, )] - #[ffi_type(opaque)] - pub enum Parameter { - /// Maximal amount of time a peer will wait before forcing creation of a new block. - /// - /// A block is created if this limit or `Self::TransactionsInBlock` limit is reached, - /// whichever comes first. Regardless of the limits, an empty block is never created. - BlockTime(Duration), - /// How long a peer will wait for a block to be committed. + pub enum SmartContractParameter { + Fuel(NonZeroU64), + Memory(NonZeroU64), + } + + /// Blockchain specific parameter defined in the executor + #[derive( + Debug, Display, Clone, IdEqOrdHash, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] + #[ffi_type] + #[display(fmt = "{id}({payload})")] + pub struct CustomParameter { + /// Unique id of the parameter. + pub id: CustomParameterId, + /// Payload containing actual value. /// - /// If this period expires the block will request a view change - CommitTime(Duration), - /// Block limits - BlockLimits(BlockLimits), - /// Transaction limits. - TransactionLimits(TransactionLimits), - /// Smart contract limits (user submitted smart contracts and triggers) - SmartContractLimits(SmartContractLimits), - /// Executor limits - ExecutorLimits(SmartContractLimits), - /// Blockchain specific parameter (defined in the executor) - Custom(JsonString), + /// It is JSON-encoded, and its structure must correspond to the structure of + /// the type defined in [`crate::executor::ExecutorDataModel`]. + pub payload: JsonString, } /// Set of all current blockchain parameter values - /// - /// See [`Parameter`] for details #[derive( Debug, Clone, @@ -140,59 +269,117 @@ mod model { Eq, PartialOrd, Ord, + Default, + Getters, + CopyGetters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[allow(missing_docs)] pub struct Parameters { - pub block_time: Duration, - pub commit_time: Duration, - pub block_limits: BlockLimits, - pub transaction_limits: TransactionLimits, - pub executor_limits: SmartContractLimits, - pub smart_contract_limits: SmartContractLimits, - /// Collection of all custom parameters - pub custom: JsonString, + /// Sumeragi parameters + #[getset(get_copy = "pub")] + pub sumeragi: SumeragiParameters, + /// Block parameters + #[getset(get_copy = "pub")] + pub block: BlockParameters, + /// Transaction parameters + #[getset(get_copy = "pub")] + pub transaction: TransactionParameters, + /// Executor parameters + #[getset(get_copy = "pub")] + pub executor: SmartContractParameters, + /// Smart contract parameters + #[getset(get_copy = "pub")] + pub smart_contract: SmartContractParameters, + /// Collection of blockchain specific parameters + #[getset(get = "pub")] + pub custom: CustomParameters, + } + + /// Single blockchain parameter. + /// + /// Check [`Parameters`] for more details + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + EnumDiscriminants, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[ffi_type(opaque)] + pub enum Parameter { + Sumeragi(SumeragiParameter), + Block(BlockParameter), + Transaction(TransactionParameter), + SmartContract(SmartContractParameter), + Executor(SmartContractParameter), + Custom(CustomParameter), } } impl core::fmt::Display for Parameter { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::BlockTime(v) | Self::CommitTime(v) => core::fmt::Debug::fmt(&v, f), - Self::BlockLimits(v) => core::fmt::Display::fmt(&v, f), - Self::TransactionLimits(v) => core::fmt::Display::fmt(&v, f), - Self::SmartContractLimits(v) | Self::ExecutorLimits(v) => { - core::fmt::Display::fmt(&v, f) - } - Self::Custom(v) => write!(f, "Custom({})", v.0), + Self::Sumeragi(v) => core::fmt::Display::fmt(&v, f), + Self::Block(v) => core::fmt::Display::fmt(&v, f), + Self::Transaction(v) => core::fmt::Display::fmt(&v, f), + Self::SmartContract(v) | Self::Executor(v) => core::fmt::Display::fmt(&v, f), + Self::Custom(v) => write!(f, "{}({})", v.id, v.payload), } } } -impl Default for Parameters { +impl SumeragiParameters { + /// Maximal amount of time (in milliseconds) a peer will wait before forcing creation of a new block. + /// + /// A block is created if this limit or [`BlockParameters::max_transactions`] limit is reached, + /// whichever comes first. Regardless of the limits, an empty block is never created. + pub fn block_time(&self) -> Duration { + Duration::from_millis(self.block_time_ms) + } + + /// Time (in milliseconds) a peer will wait for a block to be committed. + /// + /// If this period expires the block will request a view change + pub fn commit_time(&self) -> Duration { + Duration::from_millis(self.commit_time_ms) + } + + /// Maximal amount of time it takes to commit a block + #[cfg(feature = "transparent_api")] + pub fn pipeline_time(&self) -> Duration { + self.block_time() + self.commit_time() + } + + /// Estimation of consensus duration + #[cfg(feature = "transparent_api")] + pub fn consensus_estimation(&self) -> Duration { + self.block_time() + (self.commit_time() / 2) + } +} + +impl Default for SumeragiParameters { fn default() -> Self { - /// Default value for [`Parameters::BlockTime`] - pub const DEFAULT_BLOCK_TIME: Duration = Duration::from_secs(2); - /// Default value for [`Parameters::CommitTime`] - pub const DEFAULT_COMMIT_TIME: Duration = Duration::from_secs(4); + pub const DEFAULT_BLOCK_TIME: u64 = 2_000; + pub const DEFAULT_COMMIT_TIME: u64 = 4_000; Self { - block_time: DEFAULT_BLOCK_TIME, - commit_time: DEFAULT_COMMIT_TIME, - block_limits: BlockLimits::default(), - transaction_limits: TransactionLimits::default(), - executor_limits: SmartContractLimits::default(), - smart_contract_limits: SmartContractLimits::default(), - custom: JsonString::default(), + block_time_ms: DEFAULT_BLOCK_TIME, + commit_time_ms: DEFAULT_COMMIT_TIME, } } } - -impl Default for BlockLimits { +impl Default for BlockParameters { fn default() -> Self { /// Default value for [`Parameters::MaxTransactionsInBlock`] pub const DEFAULT_TRANSACTIONS_IN_BLOCK: NonZeroU64 = nonzero!(2_u64.pow(9)); @@ -201,7 +388,7 @@ impl Default for BlockLimits { } } -impl Default for TransactionLimits { +impl Default for TransactionParameters { fn default() -> Self { const DEFAULT_INSTRUCTION_NUMBER: NonZeroU64 = nonzero!(2_u64.pow(12)); const DEFAULT_SMART_CONTRACT_SIZE: NonZeroU64 = nonzero!(4 * 2_u64.pow(20)); @@ -210,7 +397,7 @@ impl Default for TransactionLimits { } } -impl Default for SmartContractLimits { +impl Default for SmartContractParameters { fn default() -> Self { const DEFAULT_FUEL: NonZeroU64 = nonzero!(55_000_000_u64); const DEFAULT_MEMORY: NonZeroU64 = nonzero!(55_000_000_u64); @@ -222,14 +409,30 @@ impl Default for SmartContractLimits { } } -impl BlockLimits { +impl SumeragiParameters { + /// Construct [`Self`] + pub fn new(block_time: Duration, commit_time: Duration) -> Self { + Self { + block_time_ms: block_time + .as_millis() + .try_into() + .expect("INTERNAL BUG: Time should fit into u64"), + commit_time_ms: commit_time + .as_millis() + .try_into() + .expect("INTERNAL BUG: Time should fit into u64"), + } + } +} + +impl BlockParameters { /// Construct [`Self`] pub const fn new(max_transactions: NonZeroU64) -> Self { Self { max_transactions } } } -impl TransactionLimits { +impl TransactionParameters { /// Construct [`Self`] pub const fn new(max_instructions: NonZeroU64, smart_contract_size: NonZeroU64) -> Self { Self { @@ -239,8 +442,31 @@ impl TransactionLimits { } } +impl CustomParameterId { + /// Getter for name + pub fn name(&self) -> &Name { + &self.0 + } +} + +impl CustomParameter { + /// Constructor + pub fn new(id: CustomParameterId, payload: impl Into) -> Self { + Self { + id, + payload: payload.into(), + } + } + + /// Getter + // TODO: derive with getset once FFI impl is fixed + pub fn payload(&self) -> &JsonString { + &self.payload + } +} + pub mod prelude { //! Prelude: re-export of most commonly used traits, structs and macros in this crate. - pub use super::{Parameter, Parameters, SmartContractLimits, TransactionLimits}; + pub use super::{Parameter, Parameters, SmartContractParameters, TransactionParameters}; } diff --git a/data_model/src/peer.rs b/data_model/src/peer.rs index ffacb39dd98..72ab6b9a8e8 100644 --- a/data_model/src/peer.rs +++ b/data_model/src/peer.rs @@ -9,18 +9,19 @@ use core::{ }; use derive_more::Display; -use iroha_data_model_derive::{model, IdEqOrdHash}; +use iroha_data_model_derive::model; use iroha_primitives::addr::SocketAddr; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::{Identifiable, PublicKey, Registered}; +use crate::{PublicKey, Registered}; #[model] mod model { use getset::Getters; + use iroha_data_model_derive::IdEqOrdHash; + use iroha_schema::IntoSchema; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; use super::*; diff --git a/data_model/src/permission.rs b/data_model/src/permission.rs index 253c1784d37..33cbe408c31 100644 --- a/data_model/src/permission.rs +++ b/data_model/src/permission.rs @@ -6,19 +6,20 @@ use std::collections::BTreeSet; use iroha_data_model_derive::model; use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::name::Name; +use crate::{name::Name, JsonString}; -/// Collection of [`Token`]s +/// Collection of [`Permission`]s pub type Permissions = BTreeSet; -use super::*; - #[model] mod model { + use derive_more::{Constructor, Display, FromStr}; + use getset::Getters; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; + use super::*; /// Identifies a [`Permission`]. @@ -67,7 +68,7 @@ mod model { Getters, )] #[ffi_type] - #[display(fmt = "PERMISSION `{id}` = `{payload}`")] + #[display(fmt = "{id}({payload})")] #[getset(get = "pub")] pub struct Permission { /// Refers to a type defined in [`crate::executor::ExecutorDataModel`]. diff --git a/data_model/src/query/predicate.rs b/data_model/src/query/predicate.rs index a0d7e152900..2eabc6f1116 100644 --- a/data_model/src/query/predicate.rs +++ b/data_model/src/query/predicate.rs @@ -602,6 +602,7 @@ pub mod string { IdBox::TriggerId(id) => self.applies(&id.to_string()), IdBox::RoleId(id) => self.applies(&id.to_string()), IdBox::PermissionId(id) => self.applies(&id.to_string()), + IdBox::CustomParameterId(id) => self.applies(&id.to_string()), } } } diff --git a/data_model/src/role.rs b/data_model/src/role.rs index 45c6b53732a..834ef75e57b 100644 --- a/data_model/src/role.rs +++ b/data_model/src/role.rs @@ -3,21 +3,23 @@ #[cfg(not(feature = "std"))] use alloc::{format, string::String, vec::Vec}; -use derive_more::{Constructor, Display, FromStr}; -use getset::Getters; -use iroha_data_model_derive::{model, IdEqOrdHash}; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; +use iroha_data_model_derive::model; pub use self::model::*; use crate::{ permission::{Permission, Permissions}, - Identifiable, Name, Registered, + Name, Registered, }; #[model] mod model { + use derive_more::{Constructor, Display, FromStr}; + use getset::Getters; + use iroha_data_model_derive::IdEqOrdHash; + use iroha_schema::IntoSchema; + use parity_scale_codec::{Decode, Encode}; + use serde::{Deserialize, Serialize}; + use super::*; /// Identification of a role. diff --git a/data_model/src/trigger.rs b/data_model/src/trigger.rs index 8893ff88084..d06af43c1fe 100644 --- a/data_model/src/trigger.rs +++ b/data_model/src/trigger.rs @@ -8,18 +8,16 @@ use alloc::{format, string::String, vec::Vec}; use core::cmp; use derive_more::{Constructor, Display, FromStr}; -use getset::Getters; use iroha_data_model_derive::{model, IdEqOrdHash}; use iroha_macro::ffi_impl_opaque; +use getset::Getters; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; pub use self::model::*; -use crate::{ - events::prelude::*, metadata::Metadata, transaction::Executable, Identifiable, Name, Registered, -}; +use crate::{events::prelude::*, metadata::Metadata, transaction::Executable, Name, Registered}; #[model] mod model { diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index d946addf7b3..2ceef0b5252 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -18,58 +18,48 @@ "AccountEvent": { "Enum": [ { - "tag": "Create", + "tag": "Created", "discriminant": 0, "type": "Account" }, { - "tag": "Delete", + "tag": "Deleted", "discriminant": 1, "type": "AccountId" }, - { - "tag": "AuthenticationAdd", - "discriminant": 2, - "type": "AccountId" - }, - { - "tag": "AuthenticationRemove", - "discriminant": 3, - "type": "AccountId" - }, { "tag": "Asset", - "discriminant": 4, + "discriminant": 2, "type": "AssetEvent" }, { - "tag": "PermissionAdd", - "discriminant": 5, + "tag": "PermissionAdded", + "discriminant": 3, "type": "AccountPermissionChanged" }, { - "tag": "PermissionRemove", - "discriminant": 6, + "tag": "PermissionRemoved", + "discriminant": 4, "type": "AccountPermissionChanged" }, { - "tag": "RoleGrant", - "discriminant": 7, + "tag": "RoleGranted", + "discriminant": 5, "type": "AccountRoleChanged" }, { - "tag": "RoleRevoke", - "discriminant": 8, + "tag": "RoleRevoked", + "discriminant": 6, "type": "AccountRoleChanged" }, { - "tag": "MetadataInsert", - "discriminant": 9, + "tag": "MetadataInserted", + "discriminant": 7, "type": "MetadataChanged" }, { - "tag": "MetadataRemove", - "discriminant": 10, + "tag": "MetadataRemoved", + "discriminant": 8, "type": "MetadataChanged" } ] @@ -91,48 +81,40 @@ "repr": "u32", "masks": [ { - "name": "Create", + "name": "Created", "mask": 1 }, { - "name": "Delete", + "name": "Deleted", "mask": 2 }, { - "name": "AuthenticationAdd", + "name": "AnyAsset", "mask": 4 }, { - "name": "AuthenticationRemove", + "name": "PermissionAdded", "mask": 8 }, { - "name": "AnyAsset", + "name": "PermissionRemoved", "mask": 16 }, { - "name": "PermissionAdd", + "name": "RoleGranted", "mask": 32 }, { - "name": "PermissionRemove", + "name": "RoleRevoked", "mask": 64 }, { - "name": "RoleGrant", + "name": "MetadataInserted", "mask": 128 }, { - "name": "RoleRevoke", + "name": "MetadataRemoved", "mask": 256 - }, - { - "name": "MetadataInsert", - "mask": 512 - }, - { - "name": "MetadataRemove", - "mask": 1024 } ] } @@ -290,7 +272,7 @@ "AssetDefinitionEvent": { "Enum": [ { - "tag": "Create", + "tag": "Created", "discriminant": 0, "type": "AssetDefinition" }, @@ -300,27 +282,27 @@ "type": "AssetDefinitionId" }, { - "tag": "MetadataInsert", + "tag": "MetadataInserted", "discriminant": 2, "type": "MetadataChanged" }, { - "tag": "MetadataRemove", + "tag": "MetadataRemoved", "discriminant": 3, "type": "MetadataChanged" }, { - "tag": "MintabilityChange", + "tag": "MintabilityChanged", "discriminant": 4, "type": "AssetDefinitionId" }, { - "tag": "TotalQuantityChange", + "tag": "TotalQuantityChanged", "discriminant": 5, "type": "AssetDefinitionTotalQuantityChanged" }, { - "tag": "OwnerChange", + "tag": "OwnerChanged", "discriminant": 6, "type": "AssetDefinitionOwnerChanged" } @@ -343,7 +325,7 @@ "repr": "u32", "masks": [ { - "name": "Create", + "name": "Created", "mask": 1 }, { @@ -351,23 +333,23 @@ "mask": 2 }, { - "name": "MetadataInsert", + "name": "MetadataInserted", "mask": 4 }, { - "name": "MetadataRemove", + "name": "MetadataRemoved", "mask": 8 }, { - "name": "MintabilityChange", + "name": "MintabilityChanged", "mask": 16 }, { - "name": "TotalQuantityChange", + "name": "TotalQuantityChanged", "mask": 32 }, { - "name": "OwnerChange", + "name": "OwnerChanged", "mask": 64 } ] @@ -412,32 +394,32 @@ "AssetEvent": { "Enum": [ { - "tag": "Create", + "tag": "Created", "discriminant": 0, "type": "Asset" }, { - "tag": "Delete", + "tag": "Deleted", "discriminant": 1, "type": "AssetId" }, { - "tag": "Add", + "tag": "Added", "discriminant": 2, "type": "AssetChanged" }, { - "tag": "Remove", + "tag": "Removed", "discriminant": 3, "type": "AssetChanged" }, { - "tag": "MetadataInsert", + "tag": "MetadataInserted", "discriminant": 4, "type": "MetadataChanged" }, { - "tag": "MetadataRemove", + "tag": "MetadataRemoved", "discriminant": 5, "type": "MetadataChanged" } @@ -460,27 +442,27 @@ "repr": "u32", "masks": [ { - "name": "Create", + "name": "Created", "mask": 1 }, { - "name": "Delete", + "name": "Deleted", "mask": 2 }, { - "name": "Add", + "name": "Added", "mask": 4 }, { - "name": "Remove", + "name": "Removed", "mask": 8 }, { - "name": "MetadataInsert", + "name": "MetadataInserted", "mask": 16 }, { - "name": "MetadataRemove", + "name": "MetadataRemoved", "mask": 32 } ] @@ -628,7 +610,17 @@ } ] }, - "BlockLimits": { + "BlockMessage": "SignedBlock", + "BlockParameter": { + "Enum": [ + { + "tag": "MaxTransactions", + "discriminant": 0, + "type": "NonZero" + } + ] + }, + "BlockParameters": { "Struct": [ { "name": "max_transactions", @@ -636,7 +628,6 @@ } ] }, - "BlockMessage": "SignedBlock", "BlockPayload": { "Struct": [ { @@ -781,9 +772,9 @@ "ConfigurationEvent": { "Enum": [ { - "tag": "Change", + "tag": "Changed", "discriminant": 0, - "type": "Parameter" + "type": "ParameterChanged" } ] }, @@ -800,7 +791,7 @@ "repr": "u32", "masks": [ { - "name": "Change", + "name": "Changed", "mask": 1 } ] @@ -833,6 +824,19 @@ } ] }, + "CustomParameter": { + "Struct": [ + { + "name": "id", + "type": "CustomParameterId" + }, + { + "name": "payload", + "type": "JsonString" + } + ] + }, + "CustomParameterId": "Name", "DataEvent": { "Enum": [ { @@ -955,12 +959,12 @@ "DomainEvent": { "Enum": [ { - "tag": "Create", + "tag": "Created", "discriminant": 0, "type": "Domain" }, { - "tag": "Delete", + "tag": "Deleted", "discriminant": 1, "type": "DomainId" }, @@ -975,17 +979,17 @@ "type": "AccountEvent" }, { - "tag": "MetadataInsert", + "tag": "MetadataInserted", "discriminant": 4, "type": "MetadataChanged" }, { - "tag": "MetadataRemove", + "tag": "MetadataRemoved", "discriminant": 5, "type": "MetadataChanged" }, { - "tag": "OwnerChange", + "tag": "OwnerChanged", "discriminant": 6, "type": "DomainOwnerChanged" } @@ -1008,11 +1012,11 @@ "repr": "u32", "masks": [ { - "name": "Create", + "name": "Created", "mask": 1 }, { - "name": "Delete", + "name": "Deleted", "mask": 2 }, { @@ -1024,15 +1028,15 @@ "mask": 8 }, { - "name": "MetadataInsert", + "name": "MetadataInserted", "mask": 16 }, { - "name": "MetadataRemove", + "name": "MetadataRemoved", "mask": 32 }, { - "name": "OwnerChange", + "name": "OwnerChanged", "mask": 64 } ] @@ -1193,14 +1197,18 @@ }, "ExecutorDataModel": { "Struct": [ - { - "name": "permissions", - "type": "SortedVec" - }, { "name": "custom_instruction", "type": "Option" }, + { + "name": "parameters", + "type": "SortedVec" + }, + { + "name": "permissions", + "type": "SortedVec" + }, { "name": "schema", "type": "JsonString" @@ -1210,7 +1218,7 @@ "ExecutorEvent": { "Enum": [ { - "tag": "Upgrade", + "tag": "Upgraded", "discriminant": 0, "type": "ExecutorUpgrade" } @@ -1229,7 +1237,7 @@ "repr": "u32", "masks": [ { - "name": "Upgrade", + "name": "Upgraded", "mask": 1 } ] @@ -1712,6 +1720,11 @@ "tag": "PermissionId", "discriminant": 7, "type": "PermissionId" + }, + { + "tag": "CustomParameterId", + "discriminant": 8, + "type": "CustomParameterId" } ] }, @@ -1771,6 +1784,11 @@ "tag": "Role", "discriminant": 10, "type": "Role" + }, + { + "tag": "CustomParameter", + "discriminant": 11, + "type": "CustomParameter" } ] }, @@ -2453,71 +2471,74 @@ "Parameter": { "Enum": [ { - "tag": "BlockTime", + "tag": "Sumeragi", "discriminant": 0, - "type": "Duration" + "type": "SumeragiParameter" }, { - "tag": "CommitTime", + "tag": "Block", "discriminant": 1, - "type": "Duration" + "type": "BlockParameter" }, { - "tag": "BlockLimits", + "tag": "Transaction", "discriminant": 2, - "type": "BlockLimits" + "type": "TransactionParameter" }, { - "tag": "TransactionLimits", + "tag": "SmartContract", "discriminant": 3, - "type": "TransactionLimits" + "type": "SmartContractParameter" }, { - "tag": "SmartContractLimits", + "tag": "Executor", "discriminant": 4, - "type": "SmartContractLimits" + "type": "SmartContractParameter" }, { - "tag": "ExecutorLimits", + "tag": "Custom", "discriminant": 5, - "type": "SmartContractLimits" + "type": "CustomParameter" + } + ] + }, + "ParameterChanged": { + "Struct": [ + { + "name": "prev", + "type": "Parameter" }, { - "tag": "Custom", - "discriminant": 6, - "type": "JsonString" + "name": "next", + "type": "Parameter" } ] }, "Parameters": { "Struct": [ { - "name": "block_time", - "type": "Duration" - }, - { - "name": "commit_time", - "type": "Duration" + "name": "sumeragi", + "type": "SumeragiParameters" }, { - "name": "block_limits", - "type": "BlockLimits" + "name": "block", + "type": "BlockParameters" }, { - "name": "transaction_limits", - "type": "TransactionLimits" + "name": "transaction", + "type": "TransactionParameters" }, { - "name": "executor_limits", - "type": "SmartContractLimits" + "name": "executor", + "type": "SmartContractParameters" }, { - "name": "smart_contract_limits", - "type": "SmartContractLimits" + "name": "smart_contract", + "type": "SmartContractParameters" }, { "name": "custom", - "type": "JsonString" + "type": "SortedMap" } ] }, @@ -2532,12 +2553,12 @@ "PeerEvent": { "Enum": [ { - "tag": "Add", + "tag": "Added", "discriminant": 0, "type": "PeerId" }, { - "tag": "Remove", + "tag": "Removed", "discriminant": 1, "type": "PeerId" } @@ -2560,11 +2581,11 @@ "repr": "u32", "masks": [ { - "name": "Add", + "name": "Added", "mask": 1 }, { - "name": "Remove", + "name": "Removed", "mask": 2 } ] @@ -3244,22 +3265,22 @@ "RoleEvent": { "Enum": [ { - "tag": "Create", + "tag": "Created", "discriminant": 0, "type": "Role" }, { - "tag": "Delete", + "tag": "Deleted", "discriminant": 1, "type": "RoleId" }, { - "tag": "PermissionAdd", + "tag": "PermissionAdded", "discriminant": 2, "type": "RolePermissionChanged" }, { - "tag": "PermissionRemove", + "tag": "PermissionRemoved", "discriminant": 3, "type": "RolePermissionChanged" } @@ -3282,19 +3303,19 @@ "repr": "u32", "masks": [ { - "name": "Create", + "name": "Created", "mask": 1 }, { - "name": "Delete", + "name": "Deleted", "mask": 2 }, { - "name": "PermissionAdd", + "name": "PermissionAdded", "mask": 4 }, { - "name": "PermissionRemove", + "name": "PermissionRemoved", "mask": 8 } ] @@ -3549,7 +3570,21 @@ } ] }, - "SmartContractLimits": { + "SmartContractParameter": { + "Enum": [ + { + "tag": "Fuel", + "discriminant": 0, + "type": "NonZero" + }, + { + "tag": "Memory", + "discriminant": 1, + "type": "NonZero" + } + ] + }, + "SmartContractParameters": { "Struct": [ { "name": "fuel", @@ -3640,12 +3675,21 @@ "value": "Numeric" } }, + "SortedMap": { + "Map": { + "key": "CustomParameterId", + "value": "JsonString" + } + }, "SortedMap": { "Map": { "key": "Name", "value": "MetadataValueBox" } }, + "SortedVec": { + "Vec": "CustomParameterId" + }, "SortedVec": { "Vec": "Permission" }, @@ -3685,6 +3729,32 @@ } ] }, + "SumeragiParameter": { + "Enum": [ + { + "tag": "BlockTimeMs", + "discriminant": 0, + "type": "u64" + }, + { + "tag": "CommitTimeMs", + "discriminant": 1, + "type": "u64" + } + ] + }, + "SumeragiParameters": { + "Struct": [ + { + "name": "block_time_ms", + "type": "u64" + }, + { + "name": "commit_time_ms", + "type": "u64" + } + ] + }, "TimeEvent": { "Struct": [ { @@ -3750,7 +3820,21 @@ } ] }, - "TransactionLimits": { + "TransactionParameter": { + "Enum": [ + { + "tag": "MaxInstructions", + "discriminant": 0, + "type": "NonZero" + }, + { + "tag": "SmartContractSize", + "discriminant": 1, + "type": "NonZero" + } + ] + }, + "TransactionParameters": { "Struct": [ { "name": "max_instructions", @@ -4004,32 +4088,32 @@ "TriggerEvent": { "Enum": [ { - "tag": "Create", + "tag": "Created", "discriminant": 0, "type": "TriggerId" }, { - "tag": "Delete", + "tag": "Deleted", "discriminant": 1, "type": "TriggerId" }, { - "tag": "Extend", + "tag": "Extended", "discriminant": 2, "type": "TriggerNumberOfExecutionsChanged" }, { - "tag": "Shorten", + "tag": "Shortened", "discriminant": 3, "type": "TriggerNumberOfExecutionsChanged" }, { - "tag": "MetadataInsert", + "tag": "MetadataInserted", "discriminant": 4, "type": "MetadataChanged" }, { - "tag": "MetadataRemove", + "tag": "MetadataRemoved", "discriminant": 5, "type": "MetadataChanged" } @@ -4052,27 +4136,27 @@ "repr": "u32", "masks": [ { - "name": "Create", + "name": "Created", "mask": 1 }, { - "name": "Delete", + "name": "Deleted", "mask": 2 }, { - "name": "Extend", + "name": "Extended", "mask": 4 }, { - "name": "Shorten", + "name": "Shortened", "mask": 8 }, { - "name": "MetadataInsert", + "name": "MetadataInserted", "mask": 16 }, { - "name": "MetadataRemove", + "name": "MetadataRemoved", "mask": 32 } ] diff --git a/smart_contract/executor/derive/src/lib.rs b/smart_contract/executor/derive/src/lib.rs index 4b11347a112..6c3360d5417 100644 --- a/smart_contract/executor/derive/src/lib.rs +++ b/smart_contract/executor/derive/src/lib.rs @@ -7,6 +7,7 @@ use proc_macro2::TokenStream; mod conversion; mod default; mod entrypoint; +mod parameter; mod permission; mod validate; @@ -100,6 +101,16 @@ pub fn derive_permission(input: TokenStream) -> Result { Ok(permission::impl_derive_permission(&input)) } +/// Derive macro for `Parameter` trait. +/// ``` +#[manyhow] +#[proc_macro_derive(Parameter)] +pub fn derive_parameter(input: TokenStream) -> Result { + let input = syn::parse2(input)?; + + Ok(parameter::impl_derive_parameter(&input)) +} + /// Derive macro for `ValidateGrantRevoke` trait. /// /// # Attributes diff --git a/smart_contract/executor/derive/src/parameter.rs b/smart_contract/executor/derive/src/parameter.rs new file mode 100644 index 00000000000..7fcfdbf1176 --- /dev/null +++ b/smart_contract/executor/derive/src/parameter.rs @@ -0,0 +1,37 @@ +//! Module with [`derive_parameter`](crate::derive_parameter) macro implementation + +use proc_macro2::TokenStream; +use quote::quote; + +/// [`derive_parameter`](crate::derive_parameter()) macro implementation +pub fn impl_derive_parameter(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::parameter::Parameter for #ident #ty_generics #where_clause {} + + impl #impl_generics TryFrom<&::iroha_executor::data_model::param::CustomParameter> for #ident #ty_generics #where_clause { + type Error = ::iroha_executor::TryFromDataModelObjectError; + + fn try_from(value: &::iroha_executor::data_model::param::CustomParameter) -> core::result::Result { + if *value.id() != ::id() { + return Err(Self::Error::Id(value.id().name().clone())); + } + + serde_json::from_str::(value.payload().as_ref()).map_err(Self::Error::Deserialize) + } + } + + impl #impl_generics From<#ident #ty_generics> for ::iroha_executor::data_model::param::CustomParameter #where_clause { + fn from(value: #ident #ty_generics) -> Self { + ::iroha_executor::data_model::param::CustomParameter::new( + <#ident as ::iroha_executor::parameter::Parameter>::id(), + ::serde_json::to_value::<#ident #ty_generics>(value) + .expect("INTERNAL BUG: Failed to serialize Executor data model entity"), + ) + } + } + } +} diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index 78b34b0f198..747aeeaed78 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -19,6 +19,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 { @@ -188,8 +189,9 @@ pub enum TryFromDataModelObjectError { /// A convenience to build [`ExecutorDataModel`] from within the executor #[derive(Debug, Clone)] pub struct DataModelBuilder { - permissions: BTreeSet, custom_instruction: Option, + parameters: BTreeSet, + permissions: BTreeSet, schema: MetaMap, } @@ -199,8 +201,9 @@ impl DataModelBuilder { #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { - permissions: <_>::default(), custom_instruction: None, + parameters: <_>::default(), + permissions: <_>::default(), schema: <_>::default(), } } @@ -229,6 +232,14 @@ impl DataModelBuilder { self } + /// Define a permission in the data model + #[must_use] + pub fn add_parameter(mut self) -> Self { + ::update_schema_map(&mut self.schema); + self.parameters.insert(::id()); + self + } + /// Define a type of custom instruction in the data model. /// Corresponds to payload of `InstructionBox::Custom`. #[must_use] @@ -251,8 +262,9 @@ impl DataModelBuilder { #[cfg(not(test))] pub fn build_and_set(self) { set_data_model(&ExecutorDataModel::new( - self.permissions, self.custom_instruction, + self.parameters, + self.permissions, serde_json::to_value(&self.schema) .expect("INTERNAL BUG: Failed to serialize Executor data model entity") .into(), @@ -278,8 +290,8 @@ pub mod prelude { pub use alloc::vec::Vec; pub use iroha_executor_derive::{ - entrypoint, Constructor, Permission, Validate, ValidateEntrypoints, ValidateGrantRevoke, - Visit, + entrypoint, Constructor, Parameter, Permission, Validate, ValidateEntrypoints, + ValidateGrantRevoke, Visit, }; pub use iroha_smart_contract::prelude::*; @@ -290,6 +302,7 @@ pub mod prelude { ValidationFail, }, deny, execute, + 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 new file mode 100644 index 00000000000..41565e96faa --- /dev/null +++ b/smart_contract/executor/src/parameter.rs @@ -0,0 +1,17 @@ +//! Module with parameter related functionality. + +use iroha_schema::IntoSchema; +use iroha_smart_contract::{data_model::param::CustomParameterId, debug::DebugExpectExt}; +use serde::{de::DeserializeOwned, Serialize}; + +/// Blockchain specific parameter +pub trait Parameter: Serialize + DeserializeOwned + IntoSchema { + /// Parameter id, according to [`IntoSchema`]. + fn id() -> CustomParameterId { + CustomParameterId::new( + ::type_name() + .parse() + .dbg_expect("Failed to parse parameter id as `Name`"), + ) + } +} diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index a6ead932324..42874c6285a 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -1,4 +1,4 @@ -//! Module with permission tokens and permission related functionality. +//! Module with permission related functionality. use alloc::borrow::ToOwned as _; @@ -9,7 +9,7 @@ use serde::{de::DeserializeOwned, Serialize}; use crate::prelude::{Permission as PermissionObject, *}; -/// Is used to check if the permission token is owned by the account. +/// Used to check if the permission token is owned by the account. pub trait Permission: Serialize + DeserializeOwned + IntoSchema + PartialEq + ValidateGrantRevoke { diff --git a/torii/src/routing.rs b/torii/src/routing.rs index 10ba3dde66c..3f8dbe997ef 100644 --- a/torii/src/routing.rs +++ b/torii/src/routing.rs @@ -52,7 +52,7 @@ pub async fn handle_transaction( transaction: SignedTransaction, ) -> Result { let state_view = state.view(); - let transaction_limits = state_view.world().parameters().transaction_limits; + let transaction_limits = state_view.world().parameters().transaction; let transaction = AcceptedTransaction::accept(transaction, &chain_id, transaction_limits) .map_err(Error::AcceptTransaction)?; queue