From c9a4fa85f2a29ddc8ffc896d0fa010d0cb8a6c0e Mon Sep 17 00:00:00 2001 From: Alexey Kalita Date: Wed, 20 Oct 2021 17:40:30 +0300 Subject: [PATCH] Metadata for domains Signed-off-by: Alexey Kalita --- client/tests/genesis.json | 3 +- core/benches/validation.rs | 1 + core/src/smartcontracts/isi/domain.rs | 59 +++++++++++++ core/src/smartcontracts/isi/mod.rs | 26 ++++++ core/src/smartcontracts/isi/query.rs | 22 +++++ core/src/wsv.rs | 27 ++++++ core/test_network/tests/genesis.json | 3 +- core/tests/genesis.json | 3 +- data_model/src/lib.rs | 6 ++ data_model/src/query.rs | 43 +++++++++- docs/source/references/config.md | 21 +++++ dsl/tests/genesis.json | 3 +- permissions_validators/src/lib.rs | 117 ++++++++++++++------------ 13 files changed, 276 insertions(+), 58 deletions(-) diff --git a/client/tests/genesis.json b/client/tests/genesis.json index be20b1e4ea6..fc1b7049ff6 100644 --- a/client/tests/genesis.json +++ b/client/tests/genesis.json @@ -10,7 +10,8 @@ "Domain": { "name": "wonderland", "accounts": {}, - "asset_definitions": {} + "asset_definitions": {}, + "metadata": {} } } } diff --git a/core/benches/validation.rs b/core/benches/validation.rs index d4bbf8bbae8..666c0e83821 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -178,6 +178,7 @@ fn validate_blocks(criterion: &mut Criterion) { name: domain_name.clone(), accounts, asset_definitions, + metadata: Metadata::new(), }; let mut domains = BTreeMap::new(); domains.insert(domain_name, domain); diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index 9de58021cae..0719de797c8 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -10,6 +10,7 @@ use crate::prelude::*; /// ISI module contains all instructions related to domains: /// - creating/changing assets /// - registering/unregistering accounts +/// - update metadata /// - transfer, etc. pub mod isi { use super::*; @@ -156,6 +157,48 @@ pub mod isi { Ok(()) } } + + impl Execute for SetKeyValue { + type Error = Error; + + fn execute( + self, + _authority: ::Id, + wsv: &WorldStateView, + ) -> Result<(), Error> { + let Self { + object_id, + key, + value, + } = self; + let limits = wsv.config.domain_metadata_limits; + wsv.modify_domain(&object_id, |domain| { + domain.metadata.insert_with_limits(key, value, limits)?; + Ok(()) + })?; + Ok(()) + } + } + + impl Execute for RemoveKeyValue { + type Error = Error; + + fn execute( + self, + _authority: ::Id, + wsv: &WorldStateView, + ) -> Result<(), Error> { + let Self { object_id, key } = self; + wsv.modify_domain(&object_id, |domain| { + domain + .metadata + .remove(&key) + .ok_or(FindError::MetadataKey(key))?; + Ok(()) + })?; + Ok(()) + } + } } /// Query module provides [`Query`] Domain related implementations. @@ -186,6 +229,22 @@ pub mod query { } } + impl Query for FindDomainKeyValueByIdAndKey { + #[log] + fn execute(&self, wsv: &WorldStateView) -> Result { + let name = self + .name + .evaluate(wsv, &Context::default()) + .wrap_err("Failed to get domain name")?; + let key = self + .key + .evaluate(wsv, &Context::default()) + .wrap_err("Failed to get key")?; + wsv.map_domain(&name, |domain| domain.metadata.get(&key).map(Clone::clone))? + .ok_or_else(|| eyre!("No metadata entry with this key.")) + } + } + impl Query for FindAssetDefinitionKeyValueByIdAndKey { fn execute(&self, wsv: &WorldStateView) -> Result { let id = self diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index a05a7dff0ce..08d989a2fa1 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -327,6 +327,9 @@ impl Execute for SetKeyValueBox { SetKeyValue::::new(account_id, key, value) .execute(authority, wsv) } + IdBox::DomainName(name) => { + SetKeyValue::::new(name, key, value).execute(authority, wsv) + } _ => Err(eyre!("Unsupported set key-value instruction.").into()), } } @@ -558,4 +561,27 @@ mod tests { ); Ok(()) } + + #[test] + fn domain_metadata() -> Result<()> { + let wsv = WorldStateView::new(world_with_test_domains()?); + let domain_name = "wonderland".to_owned(); + let account_id = AccountId::new("alice", "wonderland"); + SetKeyValueBox::new( + IdBox::from(domain_name.clone()), + "Bytes".to_owned(), + vec![1_u32, 2_u32, 3_u32], + ) + .execute(account_id, &wsv)?; + let bytes = wsv.domain(&domain_name)?.metadata.get("Bytes").cloned(); + assert_eq!( + bytes, + Some(Value::Vec(vec![ + Value::U32(1), + Value::U32(2), + Value::U32(3) + ])) + ); + Ok(()) + } } diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 416ab51ec36..980217db880 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -178,6 +178,7 @@ impl Query for QueryBox { FindAssetQuantityById(query) => query.execute_into_value(wsv), FindAllDomains(query) => query.execute_into_value(wsv), FindDomainByName(query) => query.execute_into_value(wsv), + FindDomainKeyValueByIdAndKey(query) => query.execute_into_value(wsv), FindAllPeers(query) => query.execute_into_value(wsv), FindAssetKeyValueByIdAndKey(query) => query.execute_into_value(wsv), FindAccountKeyValueByIdAndKey(query) => query.execute_into_value(wsv), @@ -267,4 +268,25 @@ mod tests { ); Ok(()) } + + #[test] + fn domain_metadata() -> Result<()> { + let wsv = WorldStateView::new(world_with_test_domains()?); + let domain_name = "wonderland".to_owned(); + let key = "Bytes".to_owned(); + wsv.modify_domain(&domain_name, |domain| { + domain.metadata.insert_with_limits( + key.clone(), + Value::Vec(vec![Value::U32(1), Value::U32(2), Value::U32(3)]), + MetadataLimits::new(10, 100), + )?; + Ok(()) + })?; + let bytes = FindDomainKeyValueByIdAndKey::new(domain_name, key).execute(&wsv)?; + assert_eq!( + bytes, + Value::Vec(vec![Value::U32(1), Value::U32(2), Value::U32(3)]) + ); + Ok(()) + } } diff --git a/core/src/wsv.rs b/core/src/wsv.rs index 1d1772d0d8d..688d15bcfab 100644 --- a/core/src/wsv.rs +++ b/core/src/wsv.rs @@ -232,6 +232,30 @@ impl WorldStateView { Ok(domain) } + /// Get `Domain` and pass it to closure. + /// # Errors + /// Fails if there is no domain or account + pub fn map_domain( + &self, + id: &::Id, + f: impl FnOnce(&Domain) -> T, + ) -> Result { + let domain = self.domain(id)?; + Ok(f(domain.value())) + } + + /// Get `Domain` and pass it to closure to modify it + /// # Errors + /// Fails if there is no domain + pub fn modify_domain( + &self, + name: &str, + f: impl FnOnce(&mut Domain) -> Result<()>, + ) -> Result<()> { + let mut domain = self.domain_mut(name)?; + f(domain.value_mut()) + } + /// Get `Account` and pass it to closure. /// # Errors /// Fails if there is no domain or account @@ -466,6 +490,8 @@ pub mod config { pub asset_definition_metadata_limits: MetadataLimits, /// [`MetadataLimits`] of any account's metadata. pub account_metadata_limits: MetadataLimits, + /// [`MetadataLimits`] of any domain's metadata. + pub domain_metadata_limits: MetadataLimits, /// [`LengthLimits`]for the number of chars in identifiers that can be stored in the WSV. pub ident_length_limits: LengthLimits, } @@ -476,6 +502,7 @@ pub mod config { asset_metadata_limits: DEFAULT_METADATA_LIMITS, asset_definition_metadata_limits: DEFAULT_METADATA_LIMITS, account_metadata_limits: DEFAULT_METADATA_LIMITS, + domain_metadata_limits: DEFAULT_METADATA_LIMITS, ident_length_limits: DEFAULT_IDENT_LENGTH_LIMITS, } } diff --git a/core/test_network/tests/genesis.json b/core/test_network/tests/genesis.json index 9f7a0a830bf..ec91a3184bf 100644 --- a/core/test_network/tests/genesis.json +++ b/core/test_network/tests/genesis.json @@ -10,7 +10,8 @@ "Domain": { "name": "wonderland", "accounts": {}, - "asset_definitions": {} + "asset_definitions": {}, + "metadata": {} } } } diff --git a/core/tests/genesis.json b/core/tests/genesis.json index 8795abf14a5..d4f16fdf732 100644 --- a/core/tests/genesis.json +++ b/core/tests/genesis.json @@ -10,7 +10,8 @@ "Domain": { "name": "wonderland", "accounts": {}, - "asset_definitions": {} + "asset_definitions": {}, + "metadata": {} } } } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 082cac40cc2..eb973899128 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -1737,6 +1737,7 @@ pub mod domain { use crate::{ account::{Account, AccountsMap, GenesisAccount}, asset::AssetDefinitionsMap, + metadata::Metadata, Identifiable, Name, Value, }; @@ -1770,6 +1771,7 @@ pub mod domain { )) .collect(), asset_definitions: BTreeMap::default(), + metadata: Metadata::new(), } } } @@ -1785,6 +1787,8 @@ pub mod domain { pub accounts: AccountsMap, /// Assets of the domain. pub asset_definitions: AssetDefinitionsMap, + /// Metadata of this domain as a key-value store. + pub metadata: Metadata, } impl FromStr for Domain { @@ -1813,6 +1817,7 @@ pub mod domain { name: name.to_owned(), accounts: AccountsMap::new(), asset_definitions: AssetDefinitionsMap::new(), + metadata: Metadata::new(), } } @@ -1843,6 +1848,7 @@ pub mod domain { name: name.to_owned(), accounts: accounts_map, asset_definitions: AssetDefinitionsMap::new(), + metadata: Metadata::new(), } } } diff --git a/data_model/src/query.rs b/data_model/src/query.rs index a7ac44485a9..bfb56af45cd 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query.rs @@ -73,6 +73,8 @@ pub enum QueryBox { FindAllDomains(FindAllDomains), /// `FindDomainByName` variant. FindDomainByName(FindDomainByName), + /// `FindDomainKeyValueByIdAndKey` variant. + FindDomainKeyValueByIdAndKey(FindDomainKeyValueByIdAndKey), /// `FindAllPeers` variant. FindAllPeers(FindAllPeers), /// `FindTransactionsByAccountId` variant. @@ -951,9 +953,48 @@ pub mod domain { } } + /// `FindDomainKeyValueByIdAndKey` Iroha Query will find a [`Value`] of the key-value metadata pair + /// in the specified domain. + #[derive( + Clone, + Debug, + Io, + Serialize, + Deserialize, + Encode, + Decode, + PartialEq, + Eq, + PartialOrd, + Ord, + IntoSchema, + )] + pub struct FindDomainKeyValueByIdAndKey { + /// `Name` of an domain to find. + pub name: EvaluatesTo, + /// Key of the specific key-value in the domain's metadata. + pub key: EvaluatesTo, + } + + impl FindDomainKeyValueByIdAndKey { + /// Default `FindDomainKeyValueByIdAndKey` constructor. + pub fn new( + name: impl Into>, + key: impl Into>, + ) -> Self { + let name = name.into(); + let key = key.into(); + FindDomainKeyValueByIdAndKey { name, key } + } + } + + impl QueryOutput for FindDomainKeyValueByIdAndKey { + type Output = Value; + } + /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { - pub use super::{FindAllDomains, FindDomainByName}; + pub use super::{FindAllDomains, FindDomainByName, FindDomainKeyValueByIdAndKey}; } } diff --git a/docs/source/references/config.md b/docs/source/references/config.md index 0aa95c3430d..c48eb0b46a0 100644 --- a/docs/source/references/config.md +++ b/docs/source/references/config.md @@ -70,6 +70,10 @@ Configuration of iroha is done via options in the following document. Here is de "max_len": 1048576, "max_entry_byte_size": 4096 }, + "DOMAIN_METADATA_LIMITS": { + "max_len": 1048576, + "max_entry_byte_size": 4096 + }, "IDENT_LENGTH_LIMITS": { "min": 1, "max": 128 @@ -609,6 +613,10 @@ Has type `WorldStateViewConfiguration`. Can be configured via environment variab "max_entry_byte_size": 4096, "max_len": 1048576 }, + "DOMAIN_METADATA_LIMITS": { + "max_entry_byte_size": 4096, + "max_len": 1048576 + }, "IDENT_LENGTH_LIMITS": { "max": 128, "min": 1 @@ -655,6 +663,19 @@ Has type `MetadataLimits`. Can be configured via environment variable `WSV_ASSET } ``` +### `wsv_configuration.domain_metadata_limits` + +[`MetadataLimits`] of any domain's metadata. + +Has type `MetadataLimits`. Can be configured via environment variable `WSV_DOMAIN_METADATA_LIMITS` + +```json +{ + "max_entry_byte_size": 4096, + "max_len": 1048576 +} +``` + ### `wsv_configuration.ident_length_limits` [`LengthLimits`]for the number of chars in identifiers that can be stored in the WSV. diff --git a/dsl/tests/genesis.json b/dsl/tests/genesis.json index 7423df47578..888591b6b68 100644 --- a/dsl/tests/genesis.json +++ b/dsl/tests/genesis.json @@ -10,7 +10,8 @@ "Domain": { "name": "wonderland", "accounts": {}, - "asset_definitions": {} + "asset_definitions": {}, + "metadata": {} } } } diff --git a/permissions_validators/src/lib.rs b/permissions_validators/src/lib.rs index 2e70164944d..4d69ea75780 100644 --- a/permissions_validators/src/lib.rs +++ b/permissions_validators/src/lib.rs @@ -348,9 +348,12 @@ pub mod private_blockchain { )) } } - FindDomainByName(query) => { - let domain_name = query - .name + FindDomainByName(query::FindDomainByName { name }) + | FindDomainKeyValueByIdAndKey(query::FindDomainKeyValueByIdAndKey { + name, + .. + }) => { + let domain_name = name .evaluate(wsv, &context) .map_err(|err| err.to_string())?; if domain_name == authority.domain_name { @@ -437,6 +440,7 @@ pub mod private_blockchain { | FindAssetsByName(_) | FindAllDomains(_) | FindDomainByName(_) + | FindDomainKeyValueByIdAndKey(_) | FindAssetsByDomainNameAndAssetDefinitionId(_) | FindAssetDefinitionKeyValueByIdAndKey(_) | FindAllAssets(_) => { @@ -1985,16 +1989,18 @@ pub mod public_blockchain { let wsv = WorldStateView::::new(World::with( btreemap! { "test".to_string() => Domain { - accounts: BTreeMap::new(), - name: "test".to_string(), - asset_definitions: btreemap! { - xor_id.clone() => - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone() - } - }.into_iter().collect(), - }}, + accounts: BTreeMap::new(), + name: "test".to_string(), + asset_definitions: btreemap! { + xor_id.clone() => + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone() + } + }.into_iter().collect(), + metadata: Metadata::new(), + } + }, btreeset! {}, )); let unregister = @@ -2081,18 +2087,20 @@ pub mod public_blockchain { let wsv = WorldStateView::::new(World::with( btreemap! { "test".to_string() => Domain { - accounts: BTreeMap::new(), - name: "test".to_string(), - asset_definitions: btreemap! { - xor_id => - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone() + accounts: BTreeMap::new(), + name: "test".to_string(), + asset_definitions: btreemap! { + xor_id => + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone() + } } + .into_iter() + .collect(), + metadata: Metadata::new(), } - .into_iter() - .collect(), - }}, + }, btreeset! {}, )); let mint = Instruction::Mint(MintBox { @@ -2185,17 +2193,18 @@ pub mod public_blockchain { let wsv = WorldStateView::::new(World::with( btreemap! { "test".to_string() => Domain { - accounts: BTreeMap::new(), - name: "test".to_string(), - asset_definitions: btreemap! { - xor_id => - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone() + accounts: BTreeMap::new(), + name: "test".to_string(), + asset_definitions: btreemap! { + xor_id => + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone() + } } - } - .into_iter() - .collect(), + .into_iter() + .collect(), + metadata: Metadata::new(), } }, vec![], @@ -2427,17 +2436,18 @@ pub mod public_blockchain { let wsv = WorldStateView::::new(World::with( btreemap! { "test".to_string() => Domain { - accounts: BTreeMap::new(), - name: "test".to_string(), - asset_definitions: btreemap! { - xor_id.clone() => - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone() + accounts: BTreeMap::new(), + name: "test".to_string(), + asset_definitions: btreemap! { + xor_id.clone() => + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone() + } } - } - .into_iter() - .collect(), + .into_iter() + .collect(), + metadata: Metadata::new(), } }, vec![], @@ -2464,17 +2474,18 @@ pub mod public_blockchain { let wsv = WorldStateView::::new(World::with( btreemap! { "test".to_string() => Domain { - accounts: BTreeMap::new(), - name: "test".to_string(), - asset_definitions: btreemap! { - xor_id.clone() => - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone() + accounts: BTreeMap::new(), + name: "test".to_string(), + asset_definitions: btreemap! { + xor_id.clone() => + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone() + } } - } - .into_iter() - .collect(), + .into_iter() + .collect(), + metadata: Metadata::new(), } }, vec![],