From 7d05f4b342bc97ecfd5b332e0e0330c3f3cb0046 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 11 Feb 2025 19:35:42 +0700 Subject: [PATCH 01/19] feat: more granular number types --- .../class_methods/try_from_schema/mod.rs | 14 + .../class_methods/try_from_schema/v1/mod.rs | 943 ++++++++++++++++++ .../document_type/property/mod.rs | 99 ++ .../dpp_versions/dpp_contract_versions/v2.rs | 2 +- 4 files changed, 1057 insertions(+), 1 deletion(-) create mode 100644 packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs index d56fad8f84e..011fda33c7a 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs @@ -7,6 +7,7 @@ use platform_version::version::PlatformVersion; use std::collections::BTreeMap; mod v0; +mod v1; impl DocumentType { pub fn try_from_schema( @@ -41,6 +42,19 @@ impl DocumentType { platform_version, ) .map(|document_type| document_type.into()), + 1 => DocumentTypeV0::try_from_schema_v1( + data_contract_id, + name, + schema, + schema_defs, + default_keeps_history, + default_mutability, + default_can_be_deleted, + full_validation, + validation_operations, + platform_version, + ) + .map(|document_type| document_type.into()), version => Err(ProtocolError::UnknownVersionMismatch { method: "try_from_schema".to_string(), known_versions: vec![0], diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs new file mode 100644 index 00000000000..9a38f1b8297 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs @@ -0,0 +1,943 @@ +#[cfg(feature = "validation")] +use crate::consensus::basic::data_contract::{ + DuplicateIndexNameError, InvalidIndexPropertyTypeError, InvalidIndexedPropertyConstraintError, + SystemPropertyIndexAlreadyPresentError, UndefinedIndexPropertyError, + UniqueIndicesLimitReachedError, +}; +#[cfg(feature = "validation")] +use crate::consensus::ConsensusError; +use crate::data_contract::document_type::index::Index; +use crate::data_contract::document_type::index_level::IndexLevel; +use crate::data_contract::document_type::property::{DocumentProperty, DocumentPropertyType}; +#[cfg(feature = "validation")] +use crate::data_contract::document_type::schema::validate_max_depth; +use crate::data_contract::document_type::v0::DocumentTypeV0; +#[cfg(feature = "validation")] +use crate::data_contract::document_type::v0::StatelessJsonSchemaLazyValidator; +use indexmap::IndexMap; +#[cfg(feature = "validation")] +use std::collections::HashSet; +use std::collections::{BTreeMap, BTreeSet}; +use std::convert::TryInto; + +#[cfg(feature = "validation")] +use crate::consensus::basic::data_contract::ContestedUniqueIndexOnMutableDocumentTypeError; +#[cfg(feature = "validation")] +use crate::consensus::basic::data_contract::ContestedUniqueIndexWithUniqueIndexError; +#[cfg(any(test, feature = "validation"))] +use crate::consensus::basic::data_contract::InvalidDocumentTypeNameError; +#[cfg(feature = "validation")] +use crate::consensus::basic::document::MissingPositionsInDocumentTypePropertiesError; +#[cfg(feature = "validation")] +use crate::consensus::basic::BasicError; +use crate::data_contract::document_type::class_methods::{ + consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, +}; +use crate::data_contract::document_type::property_names::{ + CAN_BE_DELETED, CREATION_RESTRICTION_MODE, DOCUMENTS_KEEP_HISTORY, DOCUMENTS_MUTABLE, + TRADE_MODE, TRANSFERABLE, +}; +use crate::data_contract::document_type::{property_names, DocumentType}; +use crate::data_contract::errors::DataContractError; +use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; +use crate::identity::SecurityLevel; +use crate::util::json_schema::resolve_uri; +#[cfg(feature = "validation")] +use crate::validation::meta_validators::DOCUMENT_META_SCHEMA_V0; +use crate::validation::operations::ProtocolValidationOperation; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use platform_value::btreemap_extensions::BTreeValueMapHelper; +use platform_value::{Identifier, Value}; + +const NOT_ALLOWED_SYSTEM_PROPERTIES: [&str; 1] = ["$id"]; + +const SYSTEM_PROPERTIES: [&str; 11] = [ + "$id", + "$ownerId", + "$createdAt", + "$updatedAt", + "$transferredAt", + "$createdAtBlockHeight", + "$updatedAtBlockHeight", + "$transferredAtBlockHeight", + "$createdAtCoreBlockHeight", + "$updatedAtCoreBlockHeight", + "$transferredAtCoreBlockHeight", +]; + +const MAX_INDEXED_STRING_PROPERTY_LENGTH: u16 = 63; +const MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH: u16 = 255; +const MAX_INDEXED_ARRAY_ITEMS: usize = 1024; + +impl DocumentTypeV0 { + // TODO: Split into multiple functions + #[allow(unused_variables)] + pub(super) fn try_from_schema_v1( + data_contract_id: Identifier, + name: &str, + schema: Value, + schema_defs: Option<&BTreeMap>, + default_keeps_history: bool, + default_mutability: bool, + default_can_be_deleted: bool, + full_validation: bool, // we don't need to validate if loaded from state + validation_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result { + // Create a full root JSON Schema from shorten contract document type schema + let root_schema = DocumentType::enrich_with_base_schema( + schema.clone(), + schema_defs.map(|defs| Value::from(defs.clone())), + platform_version, + )?; + + #[cfg(not(feature = "validation"))] + if full_validation { + // TODO we are silently dropping this error when we shouldn't be + // but returning this error causes tests to fail; investigate more. + ProtocolError::CorruptedCodeExecution( + "validation is not enabled but is being called on try_from_schema_v0".to_string(), + ); + } + + #[cfg(feature = "validation")] + let json_schema_validator = StatelessJsonSchemaLazyValidator::new(); + + #[cfg(feature = "validation")] + if full_validation { + // Make sure a document type name is compliant + if !name + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + || name.is_empty() + || name.len() > 64 + { + return Err(ProtocolError::ConsensusError(Box::new( + InvalidDocumentTypeNameError::new(name.to_string()).into(), + ))); + } + + // Validate document schema depth + let mut result = validate_max_depth(&root_schema, platform_version)?; + + if !result.is_valid() { + let error = result.errors.remove(0); + + let schema_size = result.into_data()?.size; + + validation_operations.push( + ProtocolValidationOperation::DocumentTypeSchemaValidationForSize(schema_size), + ); + + return Err(ProtocolError::ConsensusError(Box::new(error))); + } + + let schema_size = result.into_data()?.size; + + validation_operations.push( + ProtocolValidationOperation::DocumentTypeSchemaValidationForSize(schema_size), + ); + + // Make sure JSON Schema is compilable + let root_json_schema = root_schema.try_to_validating_json().map_err(|e| { + ProtocolError::ConsensusError( + ConsensusError::BasicError(BasicError::ValueError(e.into())).into(), + ) + })?; + + // Validate against JSON Schema + DOCUMENT_META_SCHEMA_V0 + .validate(&root_json_schema) + .map_err(|mut errs| ConsensusError::from(errs.next().unwrap()))?; + + json_schema_validator.compile(&root_json_schema, platform_version)?; + } + + // This has already been validated, but we leave the map_err here for consistency + let schema_map = schema.to_map().map_err(|err| { + consensus_or_protocol_data_contract_error(DataContractError::InvalidContractStructure( + format!("document schema must be an object: {err}"), + )) + })?; + + // Do documents of this type keep history? (Overrides contract value) + let documents_keep_history: bool = + Value::inner_optional_bool_value(schema_map, DOCUMENTS_KEEP_HISTORY) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or(default_keeps_history); + + // Are documents of this type mutable? (Overrides contract value) + let documents_mutable: bool = + Value::inner_optional_bool_value(schema_map, DOCUMENTS_MUTABLE) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or(default_mutability); + + // Can documents of this type be deleted? (Overrides contract value) + let documents_can_be_deleted: bool = + Value::inner_optional_bool_value(schema_map, CAN_BE_DELETED) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or(default_can_be_deleted); + + // Are documents of this type transferable? + let documents_transferable_u8: u8 = + Value::inner_optional_integer_value(schema_map, TRANSFERABLE) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or_default(); + + let documents_transferable = documents_transferable_u8.try_into()?; + + // What is the trade mode of these documents + let documents_trade_mode_u8: u8 = + Value::inner_optional_integer_value(schema_map, TRADE_MODE) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or_default(); + + let trade_mode = documents_trade_mode_u8.try_into()?; + + // What is the creation restriction mode of this document type? + let documents_creation_restriction_mode_u8: u8 = + Value::inner_optional_integer_value(schema_map, CREATION_RESTRICTION_MODE) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or_default(); + + let creation_restriction_mode = documents_creation_restriction_mode_u8.try_into()?; + + // Extract the properties + let property_values = Value::inner_optional_index_map::( + schema_map, + property_names::PROPERTIES, + property_names::POSITION, + ) + .map_err(consensus_or_protocol_value_error)? + .unwrap_or_default(); + + #[cfg(feature = "validation")] + if full_validation { + validation_operations.push( + ProtocolValidationOperation::DocumentTypeSchemaPropertyValidation( + property_values.values().len() as u64, + ), + ); + + // We should validate that the positions are continuous + for (pos, value) in property_values.values().enumerate() { + if value.get_integer::(property_names::POSITION)? != pos as u32 { + return Err(ConsensusError::BasicError( + BasicError::MissingPositionsInDocumentTypePropertiesError( + MissingPositionsInDocumentTypePropertiesError::new( + pos as u32, + data_contract_id, + name.to_string(), + ), + ), + ) + .into()); + } + } + } + + // Prepare internal data for efficient querying + let mut flattened_document_properties: IndexMap = IndexMap::new(); + let mut document_properties: IndexMap = IndexMap::new(); + + let required_fields = Value::inner_recursive_optional_array_of_strings( + schema_map, + "".to_string(), + property_names::PROPERTIES, + property_names::REQUIRED, + ); + + let transient_fields = Value::inner_recursive_optional_array_of_strings( + schema_map, + "".to_string(), + property_names::PROPERTIES, + property_names::TRANSIENT, + ); + + // Based on the property name, determine the type + for (property_key, property_value) in property_values { + // TODO: It's very inefficient. It must be done in one iteration and flattened properties + // must keep a reference? We even could keep only one collection + insert_values( + &mut flattened_document_properties, + &required_fields, + &transient_fields, + None, + property_key.clone(), + property_value, + &root_schema, + ) + .map_err(consensus_or_protocol_data_contract_error)?; + + insert_values_nested( + &mut document_properties, + &required_fields, + &transient_fields, + property_key, + property_value, + &root_schema, + ) + .map_err(consensus_or_protocol_data_contract_error)?; + } + + // Initialize indices + let index_values = + Value::inner_optional_array_slice_value(schema_map, property_names::INDICES) + .map_err(consensus_or_protocol_value_error)?; + + #[cfg(feature = "validation")] + let mut index_names: HashSet = HashSet::new(); + #[cfg(feature = "validation")] + let mut unique_indices_count = 0; + + #[cfg(feature = "validation")] + let mut last_non_contested_unique_index_name: Option = None; + + #[cfg(feature = "validation")] + let mut last_contested_unique_index_name: Option = None; + + #[cfg(feature = "validation")] + let mut contested_indices_count = 0; + + let indices: BTreeMap = index_values + .map(|index_values| { + index_values + .iter() + .map(|index_value| { + let index: Index = index_value + .to_map() + .map_err(consensus_or_protocol_value_error)? + .as_slice() + .try_into() + .map_err(consensus_or_protocol_data_contract_error)?; + + #[cfg(feature = "validation")] + if full_validation { + validation_operations.push( + ProtocolValidationOperation::DocumentTypeSchemaIndexValidation( + index.properties.len() as u64, + index.unique, + ), + ); + + // Unique indices produces significant load on the system during state validation + // so we need to limit their number to prevent of spikes and DoS attacks + if index.unique { + unique_indices_count += 1; + if unique_indices_count + > platform_version + .dpp + .validation + .document_type + .unique_index_limit + { + return Err(ProtocolError::ConsensusError(Box::new( + UniqueIndicesLimitReachedError::new( + name.to_string(), + platform_version + .dpp + .validation + .document_type + .unique_index_limit, + false, + ) + .into(), + ))); + } + + if let Some(last_contested_unique_index_name) = + last_contested_unique_index_name.as_ref() + { + return Err(ProtocolError::ConsensusError(Box::new( + ContestedUniqueIndexWithUniqueIndexError::new( + name.to_string(), + last_contested_unique_index_name.clone(), + index.name, + ) + .into(), + ))); + } + + if index.contested_index.is_none() { + last_non_contested_unique_index_name = Some(index.name.clone()); + } + } + + if index.contested_index.is_some() { + contested_indices_count += 1; + if contested_indices_count + > platform_version + .dpp + .validation + .document_type + .contested_index_limit + { + return Err(ProtocolError::ConsensusError(Box::new( + UniqueIndicesLimitReachedError::new( + name.to_string(), + platform_version + .dpp + .validation + .document_type + .contested_index_limit, + true, + ) + .into(), + ))); + } + + if let Some(last_unique_index_name) = + last_non_contested_unique_index_name.as_ref() + { + return Err(ProtocolError::ConsensusError(Box::new( + ContestedUniqueIndexWithUniqueIndexError::new( + name.to_string(), + index.name, + last_unique_index_name.clone(), + ) + .into(), + ))); + } + + if documents_mutable { + return Err(ProtocolError::ConsensusError(Box::new( + ContestedUniqueIndexOnMutableDocumentTypeError::new( + name.to_string(), + index.name, + ) + .into(), + ))); + } + + last_contested_unique_index_name = Some(index.name.clone()); + } + + // Index names must be unique for the document type + if !index_names.insert(index.name.to_owned()) { + return Err(ProtocolError::ConsensusError(Box::new( + DuplicateIndexNameError::new(name.to_string(), index.name) + .into(), + ))); + } + + // Validate indexed properties + index.properties.iter().try_for_each(|index_property| { + // Do not allow to index already indexed system properties + if NOT_ALLOWED_SYSTEM_PROPERTIES + .contains(&index_property.name.as_str()) + { + return Err(ProtocolError::ConsensusError(Box::new( + SystemPropertyIndexAlreadyPresentError::new( + name.to_owned(), + index.name.to_owned(), + index_property.name.to_owned(), + ) + .into(), + ))); + } + + // Indexed property must be defined in user schema if it's not a system one + if !SYSTEM_PROPERTIES.contains(&index_property.name.as_str()) { + let property_definition = flattened_document_properties + .get(&index_property.name) + .ok_or_else(|| { + ProtocolError::ConsensusError(Box::new( + UndefinedIndexPropertyError::new( + name.to_owned(), + index.name.to_owned(), + index_property.name.to_owned(), + ) + .into(), + )) + })?; + + // Validate indexed property type + match &property_definition.property_type { + // Array and objects aren't supported for indexing yet + DocumentPropertyType::Array(_) + | DocumentPropertyType::Object(_) + | DocumentPropertyType::VariableTypeArray(_) => { + Err(ProtocolError::ConsensusError(Box::new( + InvalidIndexPropertyTypeError::new( + name.to_owned(), + index.name.to_owned(), + index_property.name.to_owned(), + property_definition.property_type.name(), + ) + .into(), + ))) + } + // Indexed byte array size must be limited + DocumentPropertyType::ByteArray(sizes) + if sizes.max_size.is_none() + || sizes.max_size.unwrap() + > MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH => + { + Err(ProtocolError::ConsensusError(Box::new( + InvalidIndexedPropertyConstraintError::new( + name.to_owned(), + index.name.to_owned(), + index_property.name.to_owned(), + "maxItems".to_string(), + format!( + "should be less or equal {}", + MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH + ), + ) + .into(), + ))) + } + // Indexed string length must be limited + DocumentPropertyType::String(sizes) + if sizes.max_length.is_none() + || sizes.max_length.unwrap() + > MAX_INDEXED_STRING_PROPERTY_LENGTH => + { + Err(ProtocolError::ConsensusError(Box::new( + InvalidIndexedPropertyConstraintError::new( + name.to_owned(), + index.name.to_owned(), + index_property.name.to_owned(), + "maxLength".to_string(), + format!( + "should be less or equal {}", + MAX_INDEXED_STRING_PROPERTY_LENGTH + ), + ) + .into(), + ))) + } + _ => Ok(()), + } + } else { + Ok(()) + } + })?; + } + + Ok((index.name.clone(), index)) + }) + .collect::, ProtocolError>>() + }) + .transpose()? + .unwrap_or_default(); + + let index_structure = + IndexLevel::try_from_indices(indices.values(), name, platform_version)?; + + // Collect binary and identifier properties + let (identifier_paths, binary_paths) = DocumentType::find_identifier_and_binary_paths( + &document_properties, + &platform_version + .dpp + .contract_versions + .document_type_versions, + )?; + + let security_level_requirement = schema + .get_optional_integer::(property_names::SECURITY_LEVEL_REQUIREMENT) + .map_err(consensus_or_protocol_value_error)? + .map(SecurityLevel::try_from) + .transpose()? + .unwrap_or(SecurityLevel::HIGH); + + let requires_identity_encryption_bounded_key = schema + .get_optional_integer::(property_names::REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY) + .map_err(consensus_or_protocol_value_error)? + .map(StorageKeyRequirements::try_from) + .transpose()?; + + let requires_identity_decryption_bounded_key = schema + .get_optional_integer::(property_names::REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY) + .map_err(consensus_or_protocol_value_error)? + .map(StorageKeyRequirements::try_from) + .transpose()?; + + Ok(DocumentTypeV0 { + name: String::from(name), + schema, + indices, + index_structure, + flattened_properties: flattened_document_properties, + properties: document_properties, + identifier_paths, + binary_paths, + required_fields, + transient_fields, + documents_keep_history, + documents_mutable, + documents_can_be_deleted, + documents_transferable, + trade_mode, + creation_restriction_mode, + data_contract_id, + requires_identity_encryption_bounded_key, + requires_identity_decryption_bounded_key, + security_level_requirement, + #[cfg(feature = "validation")] + json_schema_validator, + }) + } +} + +fn insert_values( + document_properties: &mut IndexMap, + known_required: &BTreeSet, + known_transient: &BTreeSet, + prefix: Option, + property_key: String, + property_value: &Value, + root_schema: &Value, +) -> Result<(), DataContractError> { + let mut to_visit: Vec<(Option, String, &Value)> = + vec![(prefix, property_key, property_value)]; + + while let Some((prefix, property_key, property_value)) = to_visit.pop() { + let prefixed_property_key = match prefix { + None => property_key, + Some(prefix) => [prefix, property_key].join(".").to_owned(), + }; + + let mut inner_properties = property_value.to_btree_ref_string_map()?; + + if let Some(schema_ref) = inner_properties.get_optional_str(property_names::REF)? { + let referenced_sub_schema = resolve_uri(root_schema, schema_ref)?; + + inner_properties = referenced_sub_schema.to_btree_ref_string_map()? + } + + let is_required = known_required.contains(&prefixed_property_key); + let is_transient = known_transient.contains(&prefixed_property_key); + + match DocumentPropertyType::try_from(&inner_properties)? { + DocumentPropertyType::Object(_) => { + if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) + { + let properties = + properties_as_value + .as_map() + .ok_or(DataContractError::ValueWrongType( + "properties must be a map".to_string(), + ))?; + + for (object_property_key, object_property_value) in properties.iter() { + let object_property_string = object_property_key + .as_text() + .ok_or(DataContractError::KeyWrongType( + "property key must be a string".to_string(), + ))? + .to_string(); + to_visit.push(( + Some(prefixed_property_key.clone()), + object_property_string, + object_property_value, + )); + } + } + } + property_type => { + document_properties.insert( + prefixed_property_key, + DocumentProperty { + property_type, + required: is_required, + transient: is_transient, + }, + ); + } + }; + } + + Ok(()) +} +fn insert_values_nested( + document_properties: &mut IndexMap, + known_required: &BTreeSet, + known_transient: &BTreeSet, + property_key: String, + property_value: &Value, + root_schema: &Value, +) -> Result<(), DataContractError> { + let mut inner_properties = property_value.to_btree_ref_string_map()?; + + if let Some(schema_ref) = inner_properties.get_optional_str(property_names::REF)? { + let referenced_sub_schema = resolve_uri(root_schema, schema_ref)?; + + inner_properties = referenced_sub_schema.to_btree_ref_string_map()?; + } + + let is_required = known_required.contains(&property_key); + + let is_transient = known_transient.contains(&property_key); + + let property_type = match DocumentPropertyType::try_from(&inner_properties)? { + DocumentPropertyType::Object(_) => { + let mut nested_properties = IndexMap::new(); + if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { + let properties = + properties_as_value + .as_map() + .ok_or(DataContractError::ValueWrongType( + "properties must be a map".to_string(), + ))?; + + let mut sorted_properties: Vec<_> = properties.iter().collect(); + + sorted_properties.sort_by(|(_, value_1), (_, value_2)| { + let pos_1: u64 = value_1 + .get_integer(property_names::POSITION) + .expect("expected a position"); + let pos_2: u64 = value_2 + .get_integer(property_names::POSITION) + .expect("expected a position"); + pos_1.cmp(&pos_2) + }); + + // Create a new set with the prefix removed from the keys + let stripped_required: BTreeSet = known_required + .iter() + .filter_map(|key| { + if key.starts_with(&property_key) && key.len() > property_key.len() { + Some(key[property_key.len() + 1..].to_string()) + } else { + None + } + }) + .collect(); + + let stripped_transient: BTreeSet = known_transient + .iter() + .filter_map(|key| { + if key.starts_with(&property_key) && key.len() > property_key.len() { + Some(key[property_key.len() + 1..].to_string()) + } else { + None + } + }) + .collect(); + + for (object_property_key, object_property_value) in properties.iter() { + let object_property_string = object_property_key + .as_text() + .ok_or(DataContractError::KeyWrongType( + "property key must be a string".to_string(), + ))? + .to_string(); + + insert_values_nested( + &mut nested_properties, + &stripped_required, + &stripped_transient, + object_property_string, + object_property_value, + root_schema, + )?; + } + } + + DocumentPropertyType::Object(nested_properties) + } + property_type => property_type, + }; + + document_properties.insert( + property_key, + DocumentProperty { + property_type, + required: is_required, + transient: is_transient, + }, + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use platform_value::platform_value; + + mod document_type_name { + use super::*; + + #[test] + fn should_be_valid() { + let platform_version = PlatformVersion::latest(); + + let schema = platform_value!({ + "type": "object", + "properties": { + "valid_name": { + "type": "string", + "position": 0 + } + }, + "additionalProperties": false + }); + + let _result = DocumentTypeV0::try_from_schema_v0( + Identifier::new([1; 32]), + "valid_name-a-b-123", + schema, + None, + false, + false, + false, + true, + &mut vec![], + platform_version, + ) + .expect("should be valid"); + } + + #[test] + fn should_no_be_empty() { + let platform_version = PlatformVersion::latest(); + + let schema = platform_value!({ + "type": "object", + "properties": { + "valid_name": { + "type": "string", + "position": 0 + } + }, + "additionalProperties": false + }); + + let result = DocumentTypeV0::try_from_schema_v0( + Identifier::new([1; 32]), + "", + schema, + None, + false, + false, + false, + true, + &mut vec![], + platform_version, + ); + + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) + ) + ) + } + ); + } + + #[test] + fn should_no_be_longer_than_64_chars() { + let platform_version = PlatformVersion::latest(); + + let schema = platform_value!({ + "type": "object", + "properties": { + "valid_name": { + "type": "string", + "position": 0 + } + }, + "additionalProperties": false + }); + + let result = DocumentTypeV0::try_from_schema_v0( + Identifier::new([1; 32]), + &"a".repeat(65), + schema, + None, + false, + false, + false, + true, + &mut vec![], + platform_version, + ); + + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) + ) + ) + } + ); + } + + #[test] + fn should_no_be_alphanumeric() { + let platform_version = PlatformVersion::latest(); + + let schema = platform_value!({ + "type": "object", + "properties": { + "valid_name": { + "type": "string", + "position": 0 + } + }, + "additionalProperties": false + }); + + let result = DocumentTypeV0::try_from_schema_v0( + Identifier::new([1; 32]), + "invalid name", + schema.clone(), + None, + false, + false, + false, + true, + &mut vec![], + platform_version, + ); + + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) + ) + ) + } + ); + + let result = DocumentTypeV0::try_from_schema_v0( + Identifier::new([1; 32]), + "invalid&name", + schema, + None, + false, + false, + false, + true, + &mut vec![], + platform_version, + ); + + assert_matches!( + result, + Err(ProtocolError::ConsensusError(boxed)) => { + assert_matches!( + boxed.as_ref(), + ConsensusError::BasicError( + BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) + ) + ) + } + ); + } + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index fd144701bcf..96606979bf6 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::convert::TryInto; use std::io::{BufReader, Cursor, Read}; @@ -5,12 +6,14 @@ use std::io::{BufReader, Cursor, Read}; use crate::data_contract::errors::DataContractError; use crate::consensus::basic::decode::DecodingError; +use crate::data_contract::document_type::property_names; use crate::prelude::TimestampMillis; use crate::ProtocolError; use array::ArrayItemType; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use indexmap::IndexMap; use integer_encoding::{VarInt, VarIntReader}; +use platform_value::btreemap_extensions::BTreeValueMapHelper; use platform_value::{Identifier, Value}; use platform_version::version::PlatformVersion; use rand::distributions::{Alphanumeric, Standard}; @@ -67,6 +70,7 @@ pub enum DocumentPropertyType { } impl DocumentPropertyType { + #[deprecated = "this method is missing required information to create a type. Use TryFrom<&Value> instead."] pub fn try_from_name(name: &str) -> Result { match name { "u128" => Ok(DocumentPropertyType::U128), @@ -2014,3 +2018,98 @@ fn get_field_type_matching_error(value: &Value) -> DataContractError { value )) } + +impl TryFrom<&BTreeMap> for DocumentPropertyType { + type Error = DataContractError; + + fn try_from(value: &BTreeMap) -> Result { + let type_value = value.get_str(property_names::TYPE)?; + + let property_type = match type_value { + "integer" => { + let minimum = value.get_optional_integer::(property_names::MINIMUM)?; + let maximum = value.get_optional_integer::(property_names::MAXIMUM)?; + + match (minimum, maximum) { + (Some(min), Some(max)) => { + if min >= 0 { + if max <= u8::MAX as i64 { + Self::U8 + } else if max <= u16::MAX as i64 { + Self::U16 + } else if max <= u32::MAX as i64 { + Self::U32 + } else { + Self::U64 + } + } else { + if min >= i8::MIN as i64 && max <= i8::MAX as i64 { + Self::I8 + } else if min >= i16::MIN as i64 && max <= i16::MAX as i64 { + Self::I16 + } else if min >= i32::MIN as i64 && max <= i32::MAX as i64 { + Self::I32 + } else { + Self::I64 + } + } + } + (Some(min), None) => { + if min >= 0 { + Self::U64 + } else { + Self::I64 + } + } + (None, Some(max)) => { + if max >= 0 { + Self::U64 + } else { + Self::I64 + } + } + (None, None) => Self::I64, + } + } + "string" => DocumentPropertyType::String(StringPropertySizes { + min_length: value.get_optional_integer(property_names::MIN_LENGTH)?, + max_length: value.get_optional_integer(property_names::MAX_LENGTH)?, + }), + "array" => { + // Only handling bytearrays for v1 + // Return an error if it is not a byte array + let Some(is_byte_array) = value.get_optional_bool(property_names::BYTE_ARRAY)? + else { + return Err(DataContractError::InvalidContractStructure( + "only byte arrays are supported now".to_string(), + )); + }; + + if !is_byte_array { + return Err(DataContractError::InvalidContractStructure( + "byteArray should always be true if defined".to_string(), + )); + } + + match value.get_optional_str(property_names::CONTENT_MEDIA_TYPE)? { + Some("application/x.dash.dpp.identifier") => DocumentPropertyType::Identifier, + Some(_) | None => DocumentPropertyType::ByteArray(ByteArrayPropertySizes { + min_size: value.get_optional_integer(property_names::MIN_ITEMS)?, + max_size: value.get_optional_integer(property_names::MAX_ITEMS)?, + }), + } + } + "object" => Self::Object(Default::default()), + "boolean" => DocumentPropertyType::Boolean, + "number" => DocumentPropertyType::F64, + _ => { + return Err(DataContractError::InvalidContractStructure(format!( + "unsupported property type: {}", + type_value + ))); + } + }; + + Ok(property_type) + } +} diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs index 854a3dc331b..476ed6bd010 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs @@ -26,7 +26,7 @@ pub const CONTRACT_VERSIONS_V2: DPPContractVersions = DPPContractVersions { index_levels_from_indices: 0, }, class_method_versions: DocumentTypeClassMethodVersions { - try_from_schema: 0, + try_from_schema: 1, //changed to detect more granular number types based on `type`, `min` and `max` create_document_types_from_document_schemas: 1, //changed to allow contracts with only tokens }, structure_version: 0, From 2a6c7325ea603ef8fd6025141d44679d2871a2e3 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 11 Feb 2025 21:15:49 +0700 Subject: [PATCH 02/19] feat: detect type using enum keyword --- .../src/data_contract/document_type/mod.rs | 1 + .../document_type/property/mod.rs | 82 +++++++++++++------ 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/mod.rs b/packages/rs-dpp/src/data_contract/document_type/mod.rs index 23382dab535..fdded490e5e 100644 --- a/packages/rs-dpp/src/data_contract/document_type/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/mod.rs @@ -54,6 +54,7 @@ mod property_names { pub const UPDATED_AT: &str = "$updatedAt"; pub const TRANSFERRED_AT: &str = "$transferredAt"; pub const MINIMUM: &str = "minimum"; + pub const ENUM: &str = "enum"; pub const MAXIMUM: &str = "maximum"; pub const MIN_ITEMS: &str = "minItems"; pub const MAX_ITEMS: &str = "maxItems"; diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 96606979bf6..801628a2b42 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -13,6 +13,7 @@ use array::ArrayItemType; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use indexmap::IndexMap; use integer_encoding::{VarInt, VarIntReader}; +use itertools::Itertools; use platform_value::btreemap_extensions::BTreeValueMapHelper; use platform_value::{Identifier, Value}; use platform_version::version::PlatformVersion; @@ -2031,29 +2032,7 @@ impl TryFrom<&BTreeMap> for DocumentPropertyType { let maximum = value.get_optional_integer::(property_names::MAXIMUM)?; match (minimum, maximum) { - (Some(min), Some(max)) => { - if min >= 0 { - if max <= u8::MAX as i64 { - Self::U8 - } else if max <= u16::MAX as i64 { - Self::U16 - } else if max <= u32::MAX as i64 { - Self::U32 - } else { - Self::U64 - } - } else { - if min >= i8::MIN as i64 && max <= i8::MAX as i64 { - Self::I8 - } else if min >= i16::MIN as i64 && max <= i16::MAX as i64 { - Self::I16 - } else if min >= i32::MIN as i64 && max <= i32::MAX as i64 { - Self::I32 - } else { - Self::I64 - } - } - } + (Some(min), Some(max)) => find_integer_type_for_min_and_max_values(min, max), (Some(min), None) => { if min >= 0 { Self::U64 @@ -2061,14 +2040,35 @@ impl TryFrom<&BTreeMap> for DocumentPropertyType { Self::I64 } } - (None, Some(max)) => { - if max >= 0 { - Self::U64 + (None, Some(max)) => find_unsigned_integer_type_for_max_value(max), + (None, None) => { + // If enum is defined, we can try to figure out type based on minimal and maximal values + let enum_type = if let Some(enum_values) = + value.get_optional_inner_value_array::>(property_names::ENUM)? + { + match enum_values + .into_iter() + .filter_map(|v| v.as_integer()) + .minmax() + { + itertools::MinMaxResult::MinMax(min, max) => { + Some(find_integer_type_for_min_and_max_values(min, max)) + } + itertools::MinMaxResult::OneElement(val) => { + Some(find_unsigned_integer_type_for_max_value(val)) + } + _ => None, + } + } else { + None + }; + + if let Some(enum_type) = enum_type { + enum_type } else { Self::I64 } } - (None, None) => Self::I64, } } "string" => DocumentPropertyType::String(StringPropertySizes { @@ -2113,3 +2113,31 @@ impl TryFrom<&BTreeMap> for DocumentPropertyType { Ok(property_type) } } + +fn find_unsigned_integer_type_for_max_value(max_value: i64) -> DocumentPropertyType { + if max_value <= u8::MAX as i64 { + DocumentPropertyType::U8 + } else if max_value <= u16::MAX as i64 { + DocumentPropertyType::U16 + } else if max_value <= u32::MAX as i64 { + DocumentPropertyType::U32 + } else { + DocumentPropertyType::U64 + } +} + +fn find_integer_type_for_min_and_max_values(min: i64, max: i64) -> DocumentPropertyType { + if min >= 0 { + find_unsigned_integer_type_for_max_value(max) + } else { + if min >= i8::MIN as i64 && max <= i8::MAX as i64 { + DocumentPropertyType::I8 + } else if min >= i16::MIN as i64 && max <= i16::MAX as i64 { + DocumentPropertyType::I16 + } else if min >= i32::MIN as i64 && max <= i32::MAX as i64 { + DocumentPropertyType::I32 + } else { + DocumentPropertyType::I64 + } + } +} From 5d4930ee27371fa291f567927cfcddb37e28e8cb Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Wed, 12 Feb 2025 13:11:10 +0700 Subject: [PATCH 03/19] refactor: accept entire contract config to `try_from_schema` --- .../mod.rs | 13 +- .../v0/mod.rs | 16 +- .../v1/mod.rs | 9 +- .../class_methods/try_from_schema/mod.rs | 23 +- .../class_methods/try_from_schema/v0/mod.rs | 236 +---- .../class_methods/try_from_schema/v1/mod.rs | 943 ------------------ .../methods/validate_update/v0/mod.rs | 143 +-- .../src/data_contract/v0/methods/schema.rs | 8 +- .../src/data_contract/v0/serialization/mod.rs | 8 +- .../src/data_contract/v1/methods/schema.rs | 8 +- .../src/data_contract/v1/serialization/mod.rs | 8 +- packages/rs-dpp/src/util/json_schema.rs | 8 +- .../dpp_versions/dpp_contract_versions/v2.rs | 2 +- 13 files changed, 146 insertions(+), 1279 deletions(-) delete mode 100644 packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/mod.rs index cdd1666e026..6eda29a77a7 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/mod.rs @@ -1,6 +1,7 @@ mod v0; mod v1; +use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::DocumentType; use crate::data_contract::DocumentName; @@ -38,9 +39,7 @@ impl DocumentType { data_contract_id: Identifier, document_schemas: BTreeMap, schema_defs: Option<&BTreeMap>, - documents_keep_history_contract_default: bool, - documents_mutable_contract_default: bool, - documents_can_be_deleted_contract_default: bool, + data_contact_config: &DataContractConfig, full_validation: bool, has_tokens: bool, validation_operations: &mut Vec, @@ -57,9 +56,7 @@ impl DocumentType { data_contract_id, document_schemas, schema_defs, - documents_keep_history_contract_default, - documents_mutable_contract_default, - documents_can_be_deleted_contract_default, + data_contact_config, full_validation, validation_operations, platform_version, @@ -69,9 +66,7 @@ impl DocumentType { data_contract_id, document_schemas, schema_defs, - documents_keep_history_contract_default, - documents_mutable_contract_default, - documents_can_be_deleted_contract_default, + data_contact_config, full_validation, has_tokens, validation_operations, diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v0/mod.rs index 69b497eeeae..45817e8dad2 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v0/mod.rs @@ -1,4 +1,5 @@ use crate::consensus::basic::data_contract::DocumentTypesAreMissingError; +use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::class_methods::consensus_or_protocol_data_contract_error; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::DocumentType; @@ -14,9 +15,7 @@ impl DocumentTypeV0 { data_contract_id: Identifier, document_schemas: BTreeMap, schema_defs: Option<&BTreeMap>, - documents_keep_history_contract_default: bool, - documents_mutable_contract_default: bool, - documents_can_be_deleted_contract_default: bool, + data_contact_config: &DataContractConfig, full_validation: bool, validation_operations: &mut Vec, platform_version: &PlatformVersion, @@ -41,9 +40,7 @@ impl DocumentTypeV0 { &name, schema, schema_defs, - documents_keep_history_contract_default, - documents_mutable_contract_default, - documents_can_be_deleted_contract_default, + data_contact_config, full_validation, validation_operations, platform_version, @@ -80,13 +77,14 @@ mod tests { pub fn should_not_allow_creating_document_types_with_empty_schema() { let id = Identifier::random(); + let config = DataContractConfig::default_for_version(PlatformVersion::latest()) + .expect("should create a default config"); + let result = DocumentType::create_document_types_from_document_schemas( id, Default::default(), None, - false, - false, - false, + &config, false, false, &mut vec![], diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs index 720dc9fe835..6b0a27eaa7d 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs @@ -1,4 +1,5 @@ use crate::consensus::basic::data_contract::DocumentTypesAreMissingError; +use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::class_methods::consensus_or_protocol_data_contract_error; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::DocumentType; @@ -14,9 +15,7 @@ impl DocumentTypeV0 { data_contract_id: Identifier, document_schemas: BTreeMap, schema_defs: Option<&BTreeMap>, - documents_keep_history_contract_default: bool, - documents_mutable_contract_default: bool, - documents_can_be_deleted_contract_default: bool, + data_contact_config: &DataContractConfig, full_validation: bool, has_tokens: bool, validation_operations: &mut Vec, @@ -42,9 +41,7 @@ impl DocumentTypeV0 { &name, schema, schema_defs, - documents_keep_history_contract_default, - documents_mutable_contract_default, - documents_can_be_deleted_contract_default, + data_contact_config, full_validation, validation_operations, platform_version, diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs index 011fda33c7a..41a031aa18d 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs @@ -1,3 +1,4 @@ +use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::DocumentType; use crate::validation::operations::ProtocolValidationOperation; @@ -7,7 +8,6 @@ use platform_version::version::PlatformVersion; use std::collections::BTreeMap; mod v0; -mod v1; impl DocumentType { pub fn try_from_schema( @@ -15,9 +15,7 @@ impl DocumentType { name: &str, schema: Value, schema_defs: Option<&BTreeMap>, - default_keeps_history: bool, - default_mutability: bool, - default_can_be_deleted: bool, + data_contact_config: &DataContractConfig, full_validation: bool, validation_operations: &mut Vec, platform_version: &PlatformVersion, @@ -34,22 +32,7 @@ impl DocumentType { name, schema, schema_defs, - default_keeps_history, - default_mutability, - default_can_be_deleted, - full_validation, - validation_operations, - platform_version, - ) - .map(|document_type| document_type.into()), - 1 => DocumentTypeV0::try_from_schema_v1( - data_contract_id, - name, - schema, - schema_defs, - default_keeps_history, - default_mutability, - default_can_be_deleted, + data_contact_config, full_validation, validation_operations, platform_version, diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs index 3394b0f6fc7..d9bc4659512 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs @@ -6,7 +6,6 @@ use crate::consensus::basic::data_contract::{ }; #[cfg(feature = "validation")] use crate::consensus::ConsensusError; -use crate::data_contract::document_type::array::ArrayItemType; use crate::data_contract::document_type::index::Index; use crate::data_contract::document_type::index_level::IndexLevel; use crate::data_contract::document_type::property::{DocumentProperty, DocumentPropertyType}; @@ -31,6 +30,8 @@ use crate::consensus::basic::data_contract::InvalidDocumentTypeNameError; use crate::consensus::basic::document::MissingPositionsInDocumentTypePropertiesError; #[cfg(feature = "validation")] use crate::consensus::basic::BasicError; +use crate::data_contract::config::v0::DataContractConfigGettersV0; +use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::class_methods::{ consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, }; @@ -38,9 +39,7 @@ use crate::data_contract::document_type::property_names::{ CAN_BE_DELETED, CREATION_RESTRICTION_MODE, DOCUMENTS_KEEP_HISTORY, DOCUMENTS_MUTABLE, TRADE_MODE, TRANSFERABLE, }; -use crate::data_contract::document_type::{ - property_names, ByteArrayPropertySizes, DocumentType, StringPropertySizes, -}; +use crate::data_contract::document_type::{property_names, DocumentType}; use crate::data_contract::errors::DataContractError; use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; use crate::identity::SecurityLevel; @@ -81,9 +80,7 @@ impl DocumentTypeV0 { name: &str, schema: Value, schema_defs: Option<&BTreeMap>, - default_keeps_history: bool, - default_mutability: bool, - default_can_be_deleted: bool, + data_contact_config: &DataContractConfig, full_validation: bool, // we don't need to validate if loaded from state validation_operations: &mut Vec, platform_version: &PlatformVersion, @@ -168,19 +165,19 @@ impl DocumentTypeV0 { let documents_keep_history: bool = Value::inner_optional_bool_value(schema_map, DOCUMENTS_KEEP_HISTORY) .map_err(consensus_or_protocol_value_error)? - .unwrap_or(default_keeps_history); + .unwrap_or(data_contact_config.documents_keep_history_contract_default()); // Are documents of this type mutable? (Overrides contract value) let documents_mutable: bool = Value::inner_optional_bool_value(schema_map, DOCUMENTS_MUTABLE) .map_err(consensus_or_protocol_value_error)? - .unwrap_or(default_mutability); + .unwrap_or(data_contact_config.documents_mutable_contract_default()); // Can documents of this type be deleted? (Overrides contract value) let documents_can_be_deleted: bool = Value::inner_optional_bool_value(schema_map, CAN_BE_DELETED) .map_err(consensus_or_protocol_value_error)? - .unwrap_or(default_can_be_deleted); + .unwrap_or(data_contact_config.documents_can_be_deleted_contract_default()); // Are documents of this type transferable? let documents_transferable_u8: u8 = @@ -610,58 +607,11 @@ fn insert_values( inner_properties = referenced_sub_schema.to_btree_ref_string_map()? } - let type_value = inner_properties.get_str(property_names::TYPE)?; - let is_required = known_required.contains(&prefixed_property_key); let is_transient = known_transient.contains(&prefixed_property_key); - let field_type: DocumentPropertyType; - - match type_value { - "array" => { - // Only handling bytearrays for v1 - // Return an error if it is not a byte array - field_type = match inner_properties.get_optional_bool(property_names::BYTE_ARRAY)? { - Some(inner_bool) => { - if inner_bool { - match inner_properties - .get_optional_str(property_names::CONTENT_MEDIA_TYPE)? - { - Some("application/x.dash.dpp.identifier") => { - DocumentPropertyType::Identifier - } - Some(_) | None => { - DocumentPropertyType::ByteArray(ByteArrayPropertySizes { - min_size: inner_properties - .get_optional_integer(property_names::MIN_ITEMS)?, - max_size: inner_properties - .get_optional_integer(property_names::MAX_ITEMS)?, - }) - } - } - } else { - return Err(DataContractError::InvalidContractStructure( - "byteArray should always be true if defined".to_string(), - )); - } - } - // TODO: Update when arrays are implemented - None => { - return Err(DataContractError::InvalidContractStructure( - "only byte arrays are supported now".to_string(), - )); - } - }; - document_properties.insert( - prefixed_property_key, - DocumentProperty { - property_type: field_type, - required: is_required, - transient: is_transient, - }, - ); - } - "object" => { + match DocumentPropertyType::try_from(&inner_properties)? { + DocumentPropertyType::Object(_) => { if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { let properties = @@ -686,37 +636,17 @@ fn insert_values( } } } - - "string" => { - field_type = DocumentPropertyType::String(StringPropertySizes { - min_length: inner_properties - .get_optional_integer(property_names::MIN_LENGTH)?, - max_length: inner_properties - .get_optional_integer(property_names::MAX_LENGTH)?, - }); + property_type => { document_properties.insert( prefixed_property_key, DocumentProperty { - property_type: field_type, + property_type, required: is_required, transient: is_transient, }, ); } - - _ => { - field_type = DocumentPropertyType::try_from_name(type_value)?; - - document_properties.insert( - prefixed_property_key, - DocumentProperty { - property_type: field_type, - required: is_required, - transient: is_transient, - }, - ); - } - } + }; } Ok(()) @@ -737,100 +667,12 @@ fn insert_values_nested( inner_properties = referenced_sub_schema.to_btree_ref_string_map()?; } - let type_value = inner_properties.get_str(property_names::TYPE)?; - let is_required = known_required.contains(&property_key); let is_transient = known_transient.contains(&property_key); - let field_type = match type_value { - "integer" => { - DocumentPropertyType::I64 - // todo: we might want to do the following in the future (must be versioned) - // let minimum = inner_properties.get_optional_integer::(property_names::MINIMUM)?; - // let maximum = inner_properties.get_optional_integer::(property_names::MAXIMUM)?; - // - // match (minimum, maximum) { - // (Some(min), Some(max)) => { - // if min >= 0 { - // if max <= u8::MAX as i64 { - // DocumentPropertyType::U8 - // } else if max <= u16::MAX as i64 { - // DocumentPropertyType::U16 - // } else if max <= u32::MAX as i64 { - // DocumentPropertyType::U32 - // } else { - // DocumentPropertyType::U64 - // } - // } else { - // if min >= i8::MIN as i64 && max <= i8::MAX as i64 { - // DocumentPropertyType::I8 - // } else if min >= i16::MIN as i64 && max <= i16::MAX as i64 { - // DocumentPropertyType::I16 - // } else if min >= i32::MIN as i64 && max <= i32::MAX as i64 { - // DocumentPropertyType::I32 - // } else { - // DocumentPropertyType::I64 - // } - // } - // } - // (Some(min), None) => { - // if min >= 0 { - // DocumentPropertyType::U64 - // } else { - // DocumentPropertyType::I64 - // } - // } - // (None, Some(max)) => { - // if max >= 0 { - // DocumentPropertyType::U64 - // } else { - // DocumentPropertyType::I64 - // } - // } - // (None, None) => DocumentPropertyType::I64, - // } - } - "number" => DocumentPropertyType::F64, - "string" => DocumentPropertyType::String(StringPropertySizes { - min_length: inner_properties.get_optional_integer(property_names::MIN_LENGTH)?, - max_length: inner_properties.get_optional_integer(property_names::MAX_LENGTH)?, - }), - "array" => { - // Only handling bytearrays for v1 - // Return an error if it is not a byte array - match inner_properties.get_optional_bool(property_names::BYTE_ARRAY)? { - Some(inner_bool) => { - if inner_bool { - match inner_properties - .get_optional_str(property_names::CONTENT_MEDIA_TYPE)? - { - Some("application/x.dash.dpp.identifier") => { - DocumentPropertyType::Identifier - } - Some(_) | None => { - DocumentPropertyType::ByteArray(ByteArrayPropertySizes { - min_size: inner_properties - .get_optional_integer(property_names::MIN_ITEMS)?, - max_size: inner_properties - .get_optional_integer(property_names::MAX_ITEMS)?, - }) - } - } - } else { - return Err(DataContractError::InvalidContractStructure( - "byteArray should always be true if defined".to_string(), - )); - } - } - // TODO: Contract indices and new encoding format don't support arrays - // but we still can use them as document fields with current cbor encoding - // This is a temporary workaround to bring back v0.22 behavior and should be - // replaced with a proper array support in future versions - None => DocumentPropertyType::Array(ArrayItemType::Boolean), - } - } - "object" => { + let property_type = match DocumentPropertyType::try_from(&inner_properties)? { + DocumentPropertyType::Object(_) => { let mut nested_properties = IndexMap::new(); if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { let properties = @@ -893,23 +735,16 @@ fn insert_values_nested( )?; } } - document_properties.insert( - property_key, - DocumentProperty { - property_type: DocumentPropertyType::Object(nested_properties), - required: is_required, - transient: is_transient, - }, - ); - return Ok(()); + + DocumentPropertyType::Object(nested_properties) } - _ => DocumentPropertyType::try_from_name(type_value)?, + property_type => property_type, }; document_properties.insert( property_key, DocumentProperty { - property_type: field_type, + property_type, required: is_required, transient: is_transient, }, @@ -942,14 +777,15 @@ mod tests { "additionalProperties": false }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let _result = DocumentTypeV0::try_from_schema_v0( Identifier::new([1; 32]), "valid_name-a-b-123", schema, None, - false, - false, - false, + &config, true, &mut vec![], platform_version, @@ -972,14 +808,15 @@ mod tests { "additionalProperties": false }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let result = DocumentTypeV0::try_from_schema_v0( Identifier::new([1; 32]), "", schema, None, - false, - false, - false, + &config, true, &mut vec![], platform_version, @@ -1013,14 +850,15 @@ mod tests { "additionalProperties": false }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let result = DocumentTypeV0::try_from_schema_v0( Identifier::new([1; 32]), &"a".repeat(65), schema, None, - false, - false, - false, + &config, true, &mut vec![], platform_version, @@ -1054,14 +892,15 @@ mod tests { "additionalProperties": false }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let result = DocumentTypeV0::try_from_schema_v0( Identifier::new([1; 32]), "invalid name", schema.clone(), None, - false, - false, - false, + &config, true, &mut vec![], platform_version, @@ -1079,14 +918,15 @@ mod tests { } ); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let result = DocumentTypeV0::try_from_schema_v0( Identifier::new([1; 32]), "invalid&name", schema, None, - false, - false, - false, + &config, true, &mut vec![], platform_version, diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs deleted file mode 100644 index 9a38f1b8297..00000000000 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs +++ /dev/null @@ -1,943 +0,0 @@ -#[cfg(feature = "validation")] -use crate::consensus::basic::data_contract::{ - DuplicateIndexNameError, InvalidIndexPropertyTypeError, InvalidIndexedPropertyConstraintError, - SystemPropertyIndexAlreadyPresentError, UndefinedIndexPropertyError, - UniqueIndicesLimitReachedError, -}; -#[cfg(feature = "validation")] -use crate::consensus::ConsensusError; -use crate::data_contract::document_type::index::Index; -use crate::data_contract::document_type::index_level::IndexLevel; -use crate::data_contract::document_type::property::{DocumentProperty, DocumentPropertyType}; -#[cfg(feature = "validation")] -use crate::data_contract::document_type::schema::validate_max_depth; -use crate::data_contract::document_type::v0::DocumentTypeV0; -#[cfg(feature = "validation")] -use crate::data_contract::document_type::v0::StatelessJsonSchemaLazyValidator; -use indexmap::IndexMap; -#[cfg(feature = "validation")] -use std::collections::HashSet; -use std::collections::{BTreeMap, BTreeSet}; -use std::convert::TryInto; - -#[cfg(feature = "validation")] -use crate::consensus::basic::data_contract::ContestedUniqueIndexOnMutableDocumentTypeError; -#[cfg(feature = "validation")] -use crate::consensus::basic::data_contract::ContestedUniqueIndexWithUniqueIndexError; -#[cfg(any(test, feature = "validation"))] -use crate::consensus::basic::data_contract::InvalidDocumentTypeNameError; -#[cfg(feature = "validation")] -use crate::consensus::basic::document::MissingPositionsInDocumentTypePropertiesError; -#[cfg(feature = "validation")] -use crate::consensus::basic::BasicError; -use crate::data_contract::document_type::class_methods::{ - consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, -}; -use crate::data_contract::document_type::property_names::{ - CAN_BE_DELETED, CREATION_RESTRICTION_MODE, DOCUMENTS_KEEP_HISTORY, DOCUMENTS_MUTABLE, - TRADE_MODE, TRANSFERABLE, -}; -use crate::data_contract::document_type::{property_names, DocumentType}; -use crate::data_contract::errors::DataContractError; -use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; -use crate::identity::SecurityLevel; -use crate::util::json_schema::resolve_uri; -#[cfg(feature = "validation")] -use crate::validation::meta_validators::DOCUMENT_META_SCHEMA_V0; -use crate::validation::operations::ProtocolValidationOperation; -use crate::version::PlatformVersion; -use crate::ProtocolError; -use platform_value::btreemap_extensions::BTreeValueMapHelper; -use platform_value::{Identifier, Value}; - -const NOT_ALLOWED_SYSTEM_PROPERTIES: [&str; 1] = ["$id"]; - -const SYSTEM_PROPERTIES: [&str; 11] = [ - "$id", - "$ownerId", - "$createdAt", - "$updatedAt", - "$transferredAt", - "$createdAtBlockHeight", - "$updatedAtBlockHeight", - "$transferredAtBlockHeight", - "$createdAtCoreBlockHeight", - "$updatedAtCoreBlockHeight", - "$transferredAtCoreBlockHeight", -]; - -const MAX_INDEXED_STRING_PROPERTY_LENGTH: u16 = 63; -const MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH: u16 = 255; -const MAX_INDEXED_ARRAY_ITEMS: usize = 1024; - -impl DocumentTypeV0 { - // TODO: Split into multiple functions - #[allow(unused_variables)] - pub(super) fn try_from_schema_v1( - data_contract_id: Identifier, - name: &str, - schema: Value, - schema_defs: Option<&BTreeMap>, - default_keeps_history: bool, - default_mutability: bool, - default_can_be_deleted: bool, - full_validation: bool, // we don't need to validate if loaded from state - validation_operations: &mut Vec, - platform_version: &PlatformVersion, - ) -> Result { - // Create a full root JSON Schema from shorten contract document type schema - let root_schema = DocumentType::enrich_with_base_schema( - schema.clone(), - schema_defs.map(|defs| Value::from(defs.clone())), - platform_version, - )?; - - #[cfg(not(feature = "validation"))] - if full_validation { - // TODO we are silently dropping this error when we shouldn't be - // but returning this error causes tests to fail; investigate more. - ProtocolError::CorruptedCodeExecution( - "validation is not enabled but is being called on try_from_schema_v0".to_string(), - ); - } - - #[cfg(feature = "validation")] - let json_schema_validator = StatelessJsonSchemaLazyValidator::new(); - - #[cfg(feature = "validation")] - if full_validation { - // Make sure a document type name is compliant - if !name - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') - || name.is_empty() - || name.len() > 64 - { - return Err(ProtocolError::ConsensusError(Box::new( - InvalidDocumentTypeNameError::new(name.to_string()).into(), - ))); - } - - // Validate document schema depth - let mut result = validate_max_depth(&root_schema, platform_version)?; - - if !result.is_valid() { - let error = result.errors.remove(0); - - let schema_size = result.into_data()?.size; - - validation_operations.push( - ProtocolValidationOperation::DocumentTypeSchemaValidationForSize(schema_size), - ); - - return Err(ProtocolError::ConsensusError(Box::new(error))); - } - - let schema_size = result.into_data()?.size; - - validation_operations.push( - ProtocolValidationOperation::DocumentTypeSchemaValidationForSize(schema_size), - ); - - // Make sure JSON Schema is compilable - let root_json_schema = root_schema.try_to_validating_json().map_err(|e| { - ProtocolError::ConsensusError( - ConsensusError::BasicError(BasicError::ValueError(e.into())).into(), - ) - })?; - - // Validate against JSON Schema - DOCUMENT_META_SCHEMA_V0 - .validate(&root_json_schema) - .map_err(|mut errs| ConsensusError::from(errs.next().unwrap()))?; - - json_schema_validator.compile(&root_json_schema, platform_version)?; - } - - // This has already been validated, but we leave the map_err here for consistency - let schema_map = schema.to_map().map_err(|err| { - consensus_or_protocol_data_contract_error(DataContractError::InvalidContractStructure( - format!("document schema must be an object: {err}"), - )) - })?; - - // Do documents of this type keep history? (Overrides contract value) - let documents_keep_history: bool = - Value::inner_optional_bool_value(schema_map, DOCUMENTS_KEEP_HISTORY) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or(default_keeps_history); - - // Are documents of this type mutable? (Overrides contract value) - let documents_mutable: bool = - Value::inner_optional_bool_value(schema_map, DOCUMENTS_MUTABLE) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or(default_mutability); - - // Can documents of this type be deleted? (Overrides contract value) - let documents_can_be_deleted: bool = - Value::inner_optional_bool_value(schema_map, CAN_BE_DELETED) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or(default_can_be_deleted); - - // Are documents of this type transferable? - let documents_transferable_u8: u8 = - Value::inner_optional_integer_value(schema_map, TRANSFERABLE) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or_default(); - - let documents_transferable = documents_transferable_u8.try_into()?; - - // What is the trade mode of these documents - let documents_trade_mode_u8: u8 = - Value::inner_optional_integer_value(schema_map, TRADE_MODE) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or_default(); - - let trade_mode = documents_trade_mode_u8.try_into()?; - - // What is the creation restriction mode of this document type? - let documents_creation_restriction_mode_u8: u8 = - Value::inner_optional_integer_value(schema_map, CREATION_RESTRICTION_MODE) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or_default(); - - let creation_restriction_mode = documents_creation_restriction_mode_u8.try_into()?; - - // Extract the properties - let property_values = Value::inner_optional_index_map::( - schema_map, - property_names::PROPERTIES, - property_names::POSITION, - ) - .map_err(consensus_or_protocol_value_error)? - .unwrap_or_default(); - - #[cfg(feature = "validation")] - if full_validation { - validation_operations.push( - ProtocolValidationOperation::DocumentTypeSchemaPropertyValidation( - property_values.values().len() as u64, - ), - ); - - // We should validate that the positions are continuous - for (pos, value) in property_values.values().enumerate() { - if value.get_integer::(property_names::POSITION)? != pos as u32 { - return Err(ConsensusError::BasicError( - BasicError::MissingPositionsInDocumentTypePropertiesError( - MissingPositionsInDocumentTypePropertiesError::new( - pos as u32, - data_contract_id, - name.to_string(), - ), - ), - ) - .into()); - } - } - } - - // Prepare internal data for efficient querying - let mut flattened_document_properties: IndexMap = IndexMap::new(); - let mut document_properties: IndexMap = IndexMap::new(); - - let required_fields = Value::inner_recursive_optional_array_of_strings( - schema_map, - "".to_string(), - property_names::PROPERTIES, - property_names::REQUIRED, - ); - - let transient_fields = Value::inner_recursive_optional_array_of_strings( - schema_map, - "".to_string(), - property_names::PROPERTIES, - property_names::TRANSIENT, - ); - - // Based on the property name, determine the type - for (property_key, property_value) in property_values { - // TODO: It's very inefficient. It must be done in one iteration and flattened properties - // must keep a reference? We even could keep only one collection - insert_values( - &mut flattened_document_properties, - &required_fields, - &transient_fields, - None, - property_key.clone(), - property_value, - &root_schema, - ) - .map_err(consensus_or_protocol_data_contract_error)?; - - insert_values_nested( - &mut document_properties, - &required_fields, - &transient_fields, - property_key, - property_value, - &root_schema, - ) - .map_err(consensus_or_protocol_data_contract_error)?; - } - - // Initialize indices - let index_values = - Value::inner_optional_array_slice_value(schema_map, property_names::INDICES) - .map_err(consensus_or_protocol_value_error)?; - - #[cfg(feature = "validation")] - let mut index_names: HashSet = HashSet::new(); - #[cfg(feature = "validation")] - let mut unique_indices_count = 0; - - #[cfg(feature = "validation")] - let mut last_non_contested_unique_index_name: Option = None; - - #[cfg(feature = "validation")] - let mut last_contested_unique_index_name: Option = None; - - #[cfg(feature = "validation")] - let mut contested_indices_count = 0; - - let indices: BTreeMap = index_values - .map(|index_values| { - index_values - .iter() - .map(|index_value| { - let index: Index = index_value - .to_map() - .map_err(consensus_or_protocol_value_error)? - .as_slice() - .try_into() - .map_err(consensus_or_protocol_data_contract_error)?; - - #[cfg(feature = "validation")] - if full_validation { - validation_operations.push( - ProtocolValidationOperation::DocumentTypeSchemaIndexValidation( - index.properties.len() as u64, - index.unique, - ), - ); - - // Unique indices produces significant load on the system during state validation - // so we need to limit their number to prevent of spikes and DoS attacks - if index.unique { - unique_indices_count += 1; - if unique_indices_count - > platform_version - .dpp - .validation - .document_type - .unique_index_limit - { - return Err(ProtocolError::ConsensusError(Box::new( - UniqueIndicesLimitReachedError::new( - name.to_string(), - platform_version - .dpp - .validation - .document_type - .unique_index_limit, - false, - ) - .into(), - ))); - } - - if let Some(last_contested_unique_index_name) = - last_contested_unique_index_name.as_ref() - { - return Err(ProtocolError::ConsensusError(Box::new( - ContestedUniqueIndexWithUniqueIndexError::new( - name.to_string(), - last_contested_unique_index_name.clone(), - index.name, - ) - .into(), - ))); - } - - if index.contested_index.is_none() { - last_non_contested_unique_index_name = Some(index.name.clone()); - } - } - - if index.contested_index.is_some() { - contested_indices_count += 1; - if contested_indices_count - > platform_version - .dpp - .validation - .document_type - .contested_index_limit - { - return Err(ProtocolError::ConsensusError(Box::new( - UniqueIndicesLimitReachedError::new( - name.to_string(), - platform_version - .dpp - .validation - .document_type - .contested_index_limit, - true, - ) - .into(), - ))); - } - - if let Some(last_unique_index_name) = - last_non_contested_unique_index_name.as_ref() - { - return Err(ProtocolError::ConsensusError(Box::new( - ContestedUniqueIndexWithUniqueIndexError::new( - name.to_string(), - index.name, - last_unique_index_name.clone(), - ) - .into(), - ))); - } - - if documents_mutable { - return Err(ProtocolError::ConsensusError(Box::new( - ContestedUniqueIndexOnMutableDocumentTypeError::new( - name.to_string(), - index.name, - ) - .into(), - ))); - } - - last_contested_unique_index_name = Some(index.name.clone()); - } - - // Index names must be unique for the document type - if !index_names.insert(index.name.to_owned()) { - return Err(ProtocolError::ConsensusError(Box::new( - DuplicateIndexNameError::new(name.to_string(), index.name) - .into(), - ))); - } - - // Validate indexed properties - index.properties.iter().try_for_each(|index_property| { - // Do not allow to index already indexed system properties - if NOT_ALLOWED_SYSTEM_PROPERTIES - .contains(&index_property.name.as_str()) - { - return Err(ProtocolError::ConsensusError(Box::new( - SystemPropertyIndexAlreadyPresentError::new( - name.to_owned(), - index.name.to_owned(), - index_property.name.to_owned(), - ) - .into(), - ))); - } - - // Indexed property must be defined in user schema if it's not a system one - if !SYSTEM_PROPERTIES.contains(&index_property.name.as_str()) { - let property_definition = flattened_document_properties - .get(&index_property.name) - .ok_or_else(|| { - ProtocolError::ConsensusError(Box::new( - UndefinedIndexPropertyError::new( - name.to_owned(), - index.name.to_owned(), - index_property.name.to_owned(), - ) - .into(), - )) - })?; - - // Validate indexed property type - match &property_definition.property_type { - // Array and objects aren't supported for indexing yet - DocumentPropertyType::Array(_) - | DocumentPropertyType::Object(_) - | DocumentPropertyType::VariableTypeArray(_) => { - Err(ProtocolError::ConsensusError(Box::new( - InvalidIndexPropertyTypeError::new( - name.to_owned(), - index.name.to_owned(), - index_property.name.to_owned(), - property_definition.property_type.name(), - ) - .into(), - ))) - } - // Indexed byte array size must be limited - DocumentPropertyType::ByteArray(sizes) - if sizes.max_size.is_none() - || sizes.max_size.unwrap() - > MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH => - { - Err(ProtocolError::ConsensusError(Box::new( - InvalidIndexedPropertyConstraintError::new( - name.to_owned(), - index.name.to_owned(), - index_property.name.to_owned(), - "maxItems".to_string(), - format!( - "should be less or equal {}", - MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH - ), - ) - .into(), - ))) - } - // Indexed string length must be limited - DocumentPropertyType::String(sizes) - if sizes.max_length.is_none() - || sizes.max_length.unwrap() - > MAX_INDEXED_STRING_PROPERTY_LENGTH => - { - Err(ProtocolError::ConsensusError(Box::new( - InvalidIndexedPropertyConstraintError::new( - name.to_owned(), - index.name.to_owned(), - index_property.name.to_owned(), - "maxLength".to_string(), - format!( - "should be less or equal {}", - MAX_INDEXED_STRING_PROPERTY_LENGTH - ), - ) - .into(), - ))) - } - _ => Ok(()), - } - } else { - Ok(()) - } - })?; - } - - Ok((index.name.clone(), index)) - }) - .collect::, ProtocolError>>() - }) - .transpose()? - .unwrap_or_default(); - - let index_structure = - IndexLevel::try_from_indices(indices.values(), name, platform_version)?; - - // Collect binary and identifier properties - let (identifier_paths, binary_paths) = DocumentType::find_identifier_and_binary_paths( - &document_properties, - &platform_version - .dpp - .contract_versions - .document_type_versions, - )?; - - let security_level_requirement = schema - .get_optional_integer::(property_names::SECURITY_LEVEL_REQUIREMENT) - .map_err(consensus_or_protocol_value_error)? - .map(SecurityLevel::try_from) - .transpose()? - .unwrap_or(SecurityLevel::HIGH); - - let requires_identity_encryption_bounded_key = schema - .get_optional_integer::(property_names::REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY) - .map_err(consensus_or_protocol_value_error)? - .map(StorageKeyRequirements::try_from) - .transpose()?; - - let requires_identity_decryption_bounded_key = schema - .get_optional_integer::(property_names::REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY) - .map_err(consensus_or_protocol_value_error)? - .map(StorageKeyRequirements::try_from) - .transpose()?; - - Ok(DocumentTypeV0 { - name: String::from(name), - schema, - indices, - index_structure, - flattened_properties: flattened_document_properties, - properties: document_properties, - identifier_paths, - binary_paths, - required_fields, - transient_fields, - documents_keep_history, - documents_mutable, - documents_can_be_deleted, - documents_transferable, - trade_mode, - creation_restriction_mode, - data_contract_id, - requires_identity_encryption_bounded_key, - requires_identity_decryption_bounded_key, - security_level_requirement, - #[cfg(feature = "validation")] - json_schema_validator, - }) - } -} - -fn insert_values( - document_properties: &mut IndexMap, - known_required: &BTreeSet, - known_transient: &BTreeSet, - prefix: Option, - property_key: String, - property_value: &Value, - root_schema: &Value, -) -> Result<(), DataContractError> { - let mut to_visit: Vec<(Option, String, &Value)> = - vec![(prefix, property_key, property_value)]; - - while let Some((prefix, property_key, property_value)) = to_visit.pop() { - let prefixed_property_key = match prefix { - None => property_key, - Some(prefix) => [prefix, property_key].join(".").to_owned(), - }; - - let mut inner_properties = property_value.to_btree_ref_string_map()?; - - if let Some(schema_ref) = inner_properties.get_optional_str(property_names::REF)? { - let referenced_sub_schema = resolve_uri(root_schema, schema_ref)?; - - inner_properties = referenced_sub_schema.to_btree_ref_string_map()? - } - - let is_required = known_required.contains(&prefixed_property_key); - let is_transient = known_transient.contains(&prefixed_property_key); - - match DocumentPropertyType::try_from(&inner_properties)? { - DocumentPropertyType::Object(_) => { - if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) - { - let properties = - properties_as_value - .as_map() - .ok_or(DataContractError::ValueWrongType( - "properties must be a map".to_string(), - ))?; - - for (object_property_key, object_property_value) in properties.iter() { - let object_property_string = object_property_key - .as_text() - .ok_or(DataContractError::KeyWrongType( - "property key must be a string".to_string(), - ))? - .to_string(); - to_visit.push(( - Some(prefixed_property_key.clone()), - object_property_string, - object_property_value, - )); - } - } - } - property_type => { - document_properties.insert( - prefixed_property_key, - DocumentProperty { - property_type, - required: is_required, - transient: is_transient, - }, - ); - } - }; - } - - Ok(()) -} -fn insert_values_nested( - document_properties: &mut IndexMap, - known_required: &BTreeSet, - known_transient: &BTreeSet, - property_key: String, - property_value: &Value, - root_schema: &Value, -) -> Result<(), DataContractError> { - let mut inner_properties = property_value.to_btree_ref_string_map()?; - - if let Some(schema_ref) = inner_properties.get_optional_str(property_names::REF)? { - let referenced_sub_schema = resolve_uri(root_schema, schema_ref)?; - - inner_properties = referenced_sub_schema.to_btree_ref_string_map()?; - } - - let is_required = known_required.contains(&property_key); - - let is_transient = known_transient.contains(&property_key); - - let property_type = match DocumentPropertyType::try_from(&inner_properties)? { - DocumentPropertyType::Object(_) => { - let mut nested_properties = IndexMap::new(); - if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { - let properties = - properties_as_value - .as_map() - .ok_or(DataContractError::ValueWrongType( - "properties must be a map".to_string(), - ))?; - - let mut sorted_properties: Vec<_> = properties.iter().collect(); - - sorted_properties.sort_by(|(_, value_1), (_, value_2)| { - let pos_1: u64 = value_1 - .get_integer(property_names::POSITION) - .expect("expected a position"); - let pos_2: u64 = value_2 - .get_integer(property_names::POSITION) - .expect("expected a position"); - pos_1.cmp(&pos_2) - }); - - // Create a new set with the prefix removed from the keys - let stripped_required: BTreeSet = known_required - .iter() - .filter_map(|key| { - if key.starts_with(&property_key) && key.len() > property_key.len() { - Some(key[property_key.len() + 1..].to_string()) - } else { - None - } - }) - .collect(); - - let stripped_transient: BTreeSet = known_transient - .iter() - .filter_map(|key| { - if key.starts_with(&property_key) && key.len() > property_key.len() { - Some(key[property_key.len() + 1..].to_string()) - } else { - None - } - }) - .collect(); - - for (object_property_key, object_property_value) in properties.iter() { - let object_property_string = object_property_key - .as_text() - .ok_or(DataContractError::KeyWrongType( - "property key must be a string".to_string(), - ))? - .to_string(); - - insert_values_nested( - &mut nested_properties, - &stripped_required, - &stripped_transient, - object_property_string, - object_property_value, - root_schema, - )?; - } - } - - DocumentPropertyType::Object(nested_properties) - } - property_type => property_type, - }; - - document_properties.insert( - property_key, - DocumentProperty { - property_type, - required: is_required, - transient: is_transient, - }, - ); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use assert_matches::assert_matches; - use platform_value::platform_value; - - mod document_type_name { - use super::*; - - #[test] - fn should_be_valid() { - let platform_version = PlatformVersion::latest(); - - let schema = platform_value!({ - "type": "object", - "properties": { - "valid_name": { - "type": "string", - "position": 0 - } - }, - "additionalProperties": false - }); - - let _result = DocumentTypeV0::try_from_schema_v0( - Identifier::new([1; 32]), - "valid_name-a-b-123", - schema, - None, - false, - false, - false, - true, - &mut vec![], - platform_version, - ) - .expect("should be valid"); - } - - #[test] - fn should_no_be_empty() { - let platform_version = PlatformVersion::latest(); - - let schema = platform_value!({ - "type": "object", - "properties": { - "valid_name": { - "type": "string", - "position": 0 - } - }, - "additionalProperties": false - }); - - let result = DocumentTypeV0::try_from_schema_v0( - Identifier::new([1; 32]), - "", - schema, - None, - false, - false, - false, - true, - &mut vec![], - platform_version, - ); - - assert_matches!( - result, - Err(ProtocolError::ConsensusError(boxed)) => { - assert_matches!( - boxed.as_ref(), - ConsensusError::BasicError( - BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) - ) - ) - } - ); - } - - #[test] - fn should_no_be_longer_than_64_chars() { - let platform_version = PlatformVersion::latest(); - - let schema = platform_value!({ - "type": "object", - "properties": { - "valid_name": { - "type": "string", - "position": 0 - } - }, - "additionalProperties": false - }); - - let result = DocumentTypeV0::try_from_schema_v0( - Identifier::new([1; 32]), - &"a".repeat(65), - schema, - None, - false, - false, - false, - true, - &mut vec![], - platform_version, - ); - - assert_matches!( - result, - Err(ProtocolError::ConsensusError(boxed)) => { - assert_matches!( - boxed.as_ref(), - ConsensusError::BasicError( - BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) - ) - ) - } - ); - } - - #[test] - fn should_no_be_alphanumeric() { - let platform_version = PlatformVersion::latest(); - - let schema = platform_value!({ - "type": "object", - "properties": { - "valid_name": { - "type": "string", - "position": 0 - } - }, - "additionalProperties": false - }); - - let result = DocumentTypeV0::try_from_schema_v0( - Identifier::new([1; 32]), - "invalid name", - schema.clone(), - None, - false, - false, - false, - true, - &mut vec![], - platform_version, - ); - - assert_matches!( - result, - Err(ProtocolError::ConsensusError(boxed)) => { - assert_matches!( - boxed.as_ref(), - ConsensusError::BasicError( - BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) - ) - ) - } - ); - - let result = DocumentTypeV0::try_from_schema_v0( - Identifier::new([1; 32]), - "invalid&name", - schema, - None, - false, - false, - false, - true, - &mut vec![], - platform_version, - ); - - assert_matches!( - result, - Err(ProtocolError::ConsensusError(boxed)) => { - assert_matches!( - boxed.as_ref(), - ConsensusError::BasicError( - BasicError::InvalidDocumentTypeNameError(InvalidDocumentTypeNameError { .. }) - ) - ) - } - ); - } - } -} diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs index fe3980e0aa7..1d1613a2443 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs @@ -247,6 +247,7 @@ mod tests { use crate::consensus::state::state_error::StateError; use crate::consensus::ConsensusError; + use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::DocumentType; use assert_matches::assert_matches; use platform_value::platform_value; @@ -273,14 +274,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -299,14 +301,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -343,14 +346,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -369,14 +373,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -413,14 +418,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -439,14 +445,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -483,14 +490,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -509,14 +517,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -553,14 +562,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -579,14 +589,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -623,14 +634,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -649,14 +661,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -693,14 +706,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -724,9 +738,7 @@ mod tests { document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -763,14 +775,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -794,9 +807,7 @@ mod tests { document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -833,14 +844,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -864,9 +876,7 @@ mod tests { document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -908,28 +918,30 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema.clone(), None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, ) .expect("failed to create old document type"); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let new_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -962,14 +974,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let old_document_type = DocumentType::try_from_schema( data_contract_id, document_type_name, schema.clone(), None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, @@ -993,9 +1006,7 @@ mod tests { document_type_name, schema, None, - false, - false, - false, + &config, false, &mut Vec::new(), platform_version, diff --git a/packages/rs-dpp/src/data_contract/v0/methods/schema.rs b/packages/rs-dpp/src/data_contract/v0/methods/schema.rs index dddd285a6c3..ea8c5649233 100644 --- a/packages/rs-dpp/src/data_contract/v0/methods/schema.rs +++ b/packages/rs-dpp/src/data_contract/v0/methods/schema.rs @@ -23,9 +23,7 @@ impl DataContractSchemaMethodsV0 for DataContractV0 { self.id, schemas, defs.as_ref(), - self.config.documents_keep_history_contract_default(), - self.config.documents_mutable_contract_default(), - self.config.documents_can_be_deleted_contract_default(), + &self.config, full_validation, false, validation_operations, @@ -48,9 +46,7 @@ impl DataContractSchemaMethodsV0 for DataContractV0 { name, schema, self.schema_defs.as_ref(), - self.config.documents_keep_history_contract_default(), - self.config.documents_mutable_contract_default(), - self.config.documents_mutable_contract_default(), + &self.config, full_validation, validation_operations, platform_version, diff --git a/packages/rs-dpp/src/data_contract/v0/serialization/mod.rs b/packages/rs-dpp/src/data_contract/v0/serialization/mod.rs index 2a79edd3627..eb773037191 100644 --- a/packages/rs-dpp/src/data_contract/v0/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/v0/serialization/mod.rs @@ -91,9 +91,7 @@ impl DataContractV0 { id, document_schemas, schema_defs.as_ref(), - config.documents_keep_history_contract_default(), - config.documents_mutable_contract_default(), - config.documents_can_be_deleted_contract_default(), + &config, full_validation, false, validation_operations, @@ -133,9 +131,7 @@ impl DataContractV0 { id, document_schemas, schema_defs.as_ref(), - config.documents_keep_history_contract_default(), - config.documents_mutable_contract_default(), - config.documents_can_be_deleted_contract_default(), + &config, full_validation, false, validation_operations, diff --git a/packages/rs-dpp/src/data_contract/v1/methods/schema.rs b/packages/rs-dpp/src/data_contract/v1/methods/schema.rs index 901813af168..ae44171cb39 100644 --- a/packages/rs-dpp/src/data_contract/v1/methods/schema.rs +++ b/packages/rs-dpp/src/data_contract/v1/methods/schema.rs @@ -22,9 +22,7 @@ impl DataContractSchemaMethodsV0 for DataContractV1 { self.id, schemas, defs.as_ref(), - self.config.documents_keep_history_contract_default(), - self.config.documents_mutable_contract_default(), - self.config.documents_can_be_deleted_contract_default(), + &self.config, full_validation, !self.tokens.is_empty(), validation_operations, @@ -47,9 +45,7 @@ impl DataContractSchemaMethodsV0 for DataContractV1 { name, schema, self.schema_defs.as_ref(), - self.config.documents_keep_history_contract_default(), - self.config.documents_mutable_contract_default(), - self.config.documents_mutable_contract_default(), + &self.config, full_validation, validation_operations, platform_version, diff --git a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs index 87825f6c692..9c1ff5fe51f 100644 --- a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs @@ -90,9 +90,7 @@ impl DataContractV1 { id, document_schemas, schema_defs.as_ref(), - config.documents_keep_history_contract_default(), - config.documents_mutable_contract_default(), - config.documents_can_be_deleted_contract_default(), + &config, full_validation, false, validation_operations, @@ -135,9 +133,7 @@ impl DataContractV1 { id, document_schemas, schema_defs.as_ref(), - config.documents_keep_history_contract_default(), - config.documents_mutable_contract_default(), - config.documents_can_be_deleted_contract_default(), + &config, full_validation, !tokens.is_empty(), validation_operations, diff --git a/packages/rs-dpp/src/util/json_schema.rs b/packages/rs-dpp/src/util/json_schema.rs index eaf8fb0d909..936b459aa6b 100644 --- a/packages/rs-dpp/src/util/json_schema.rs +++ b/packages/rs-dpp/src/util/json_schema.rs @@ -145,6 +145,7 @@ impl JsonSchemaExt for JsonValue { #[cfg(test)] mod test { + use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::DocumentType; @@ -206,14 +207,15 @@ mod test { let platform_value = platform_value::to_value(input).unwrap(); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + let document_type = DocumentType::try_from_schema( Identifier::random(), "doc", platform_value, None, - false, - false, - false, + &config, false, &mut vec![], platform_version, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs index 476ed6bd010..854a3dc331b 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs @@ -26,7 +26,7 @@ pub const CONTRACT_VERSIONS_V2: DPPContractVersions = DPPContractVersions { index_levels_from_indices: 0, }, class_method_versions: DocumentTypeClassMethodVersions { - try_from_schema: 1, //changed to detect more granular number types based on `type`, `min` and `max` + try_from_schema: 0, create_document_types_from_document_schemas: 1, //changed to allow contracts with only tokens }, structure_version: 0, From fc8dc72d5f4c85d4be698265c66ee68040f02c51 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Wed, 12 Feb 2025 13:11:35 +0700 Subject: [PATCH 04/19] test: fix tests --- .../src/query/document_query/v0/mod.rs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/rs-drive-abci/src/query/document_query/v0/mod.rs b/packages/rs-drive-abci/src/query/document_query/v0/mod.rs index d4177878bce..0b1e8be12b9 100644 --- a/packages/rs-drive-abci/src/query/document_query/v0/mod.rs +++ b/packages/rs-drive-abci/src/query/document_query/v0/mod.rs @@ -773,10 +773,10 @@ mod tests { owner_id: Identifier::random_with_rng(&mut std_rng), properties: { let mut properties = BTreeMap::new(); - properties.insert("status".to_string(), Value::I64(0)); // Always queued - properties.insert("pooling".to_string(), Value::I64(0)); // Always 0 - properties.insert("coreFeePerByte".to_string(), Value::I64(1)); // Always 1 - properties.insert("amount".to_string(), Value::I64(1000)); // Set a minimum amount of 1000 + properties.insert("status".to_string(), Value::U8(0)); // Always queued + properties.insert("pooling".to_string(), Value::U8(0)); // Always 0 + properties.insert("coreFeePerByte".to_string(), Value::U32(1)); // Always 1 + properties.insert("amount".to_string(), Value::U64(1000)); // Set a minimum amount of 1000 properties.insert("outputScript".to_string(), Value::Bytes(vec![])); // Set an empty output script properties }, @@ -937,10 +937,10 @@ mod tests { owner_id: Identifier::random_with_rng(&mut std_rng), properties: { let mut properties = BTreeMap::new(); - properties.insert("status".to_string(), Value::I64(i as i64 % 4)); // Always queued - properties.insert("pooling".to_string(), Value::I64(0)); // Always 0 - properties.insert("coreFeePerByte".to_string(), Value::I64(1)); // Always 1 - properties.insert("amount".to_string(), Value::I64(1000)); // Set a minimum amount of 1000 + properties.insert("status".to_string(), Value::U8(i as u8 % 4)); // Always queued + properties.insert("pooling".to_string(), Value::U8(0)); // Always 0 + properties.insert("coreFeePerByte".to_string(), Value::U32(1)); // Always 1 + properties.insert("amount".to_string(), Value::U64(1000)); // Set a minimum amount of 1000 properties.insert("outputScript".to_string(), Value::Bytes(vec![])); // Set an empty output script properties }, @@ -1101,10 +1101,10 @@ mod tests { owner_id: Identifier::random_with_rng(&mut std_rng), properties: { let mut properties = BTreeMap::new(); - properties.insert("status".to_string(), Value::I64(i as i64 % 4)); // Always queued - properties.insert("pooling".to_string(), Value::I64(0)); // Always 0 - properties.insert("coreFeePerByte".to_string(), Value::I64(1)); // Always 1 - properties.insert("amount".to_string(), Value::I64(1000)); // Set a minimum amount of 1000 + properties.insert("status".to_string(), Value::U8(i as u8 % 4)); // Always queued + properties.insert("pooling".to_string(), Value::U8(0)); // Always 0 + properties.insert("coreFeePerByte".to_string(), Value::U32(1)); // Always 1 + properties.insert("amount".to_string(), Value::U64(1000)); // Set a minimum amount of 1000 properties.insert("outputScript".to_string(), Value::Bytes(vec![])); // Set an empty output script properties }, @@ -1257,10 +1257,10 @@ mod tests { owner_id: Identifier::random_with_rng(&mut std_rng), properties: { let mut properties = BTreeMap::new(); - properties.insert("status".to_string(), Value::I64(i as i64 % 4)); // Always queued - properties.insert("pooling".to_string(), Value::I64(0)); // Always 0 - properties.insert("coreFeePerByte".to_string(), Value::I64(1)); // Always 1 - properties.insert("amount".to_string(), Value::I64(1000)); // Set a minimum amount of 1000 + properties.insert("status".to_string(), Value::U8(i as u8 % 4)); // Always queued + properties.insert("pooling".to_string(), Value::U8(0)); // Always 0 + properties.insert("coreFeePerByte".to_string(), Value::U32(1)); // Always 1 + properties.insert("amount".to_string(), Value::U64(1000)); // Set a minimum amount of 1000 properties.insert("outputScript".to_string(), Value::Bytes(vec![])); // Set an empty output script properties }, @@ -1428,10 +1428,10 @@ mod tests { owner_id: Identifier::random_with_rng(&mut std_rng), properties: { let mut properties = BTreeMap::new(); - properties.insert("status".to_string(), Value::I64(i as i64 % 4)); // Always queued - properties.insert("pooling".to_string(), Value::I64(0)); // Always 0 - properties.insert("coreFeePerByte".to_string(), Value::I64(1)); // Always 1 - properties.insert("amount".to_string(), Value::I64(1000)); // Set a minimum amount of 1000 + properties.insert("status".to_string(), Value::U8(i as u8 % 4)); // Always queued + properties.insert("pooling".to_string(), Value::U8(0)); // Always 0 + properties.insert("coreFeePerByte".to_string(), Value::U32(1)); // Always 1 + properties.insert("amount".to_string(), Value::U64(1000)); // Set a minimum amount of 1000 properties.insert("outputScript".to_string(), Value::Bytes(vec![])); // Set an empty output script properties }, From b776185bae1b6bee6ae235aad71bcc3049349ed6 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 13:47:22 +0700 Subject: [PATCH 05/19] feat: parse numeric types according to config --- .../rs-dpp/src/data_contract/config/fields.rs | 2 + .../rs-dpp/src/data_contract/config/mod.rs | 55 +++++- .../rs-dpp/src/data_contract/config/v1/mod.rs | 157 ++++++++++++++++ .../class_methods/try_from_schema/v0/mod.rs | 144 ++++++++------- .../document_type/property/mod.rs | 169 +++++++++++------- packages/rs-drive/src/query/test_index.rs | 17 +- .../rs-drive/tests/deterministic_root_hash.rs | 3 +- packages/rs-drive/tests/query_tests.rs | 12 +- .../dpp_versions/dpp_contract_versions/v2.rs | 2 +- 9 files changed, 417 insertions(+), 144 deletions(-) create mode 100644 packages/rs-dpp/src/data_contract/config/v1/mod.rs diff --git a/packages/rs-dpp/src/data_contract/config/fields.rs b/packages/rs-dpp/src/data_contract/config/fields.rs index 200c0402e07..f322d467a10 100644 --- a/packages/rs-dpp/src/data_contract/config/fields.rs +++ b/packages/rs-dpp/src/data_contract/config/fields.rs @@ -4,6 +4,7 @@ pub const DEFAULT_CONTRACT_MUTABILITY: bool = true; pub const DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY: bool = false; pub const DEFAULT_CONTRACT_DOCUMENT_MUTABILITY: bool = true; pub const DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED: bool = true; +pub const DEFAULT_GRANULAR_NUMERIC_TYPES: bool = true; pub mod property { pub const CAN_BE_DELETED: &str = "canBeDeleted"; @@ -17,4 +18,5 @@ pub mod property { "requiresIdentityEncryptionBoundedKey"; pub const REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY: &str = "requiresIdentityDecryptionBoundedKey"; + pub const GRANULAR_NUMERIC_TYPES: &str = "granular_numeric_types"; } diff --git a/packages/rs-dpp/src/data_contract/config/mod.rs b/packages/rs-dpp/src/data_contract/config/mod.rs index 2f2a2c825a2..9c79173539b 100644 --- a/packages/rs-dpp/src/data_contract/config/mod.rs +++ b/packages/rs-dpp/src/data_contract/config/mod.rs @@ -1,7 +1,11 @@ mod fields; mod methods; pub mod v0; +pub mod v1; +use crate::data_contract::config::v1::{ + DataContractConfigGettersV1, DataContractConfigSettersV1, DataContractConfigV1, +}; use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; use crate::version::PlatformVersion; use crate::ProtocolError; @@ -18,6 +22,8 @@ use v0::{DataContractConfigGettersV0, DataContractConfigSettersV0, DataContractC pub enum DataContractConfig { #[serde(rename = "0")] V0(DataContractConfigV0), + #[serde(rename = "1")] + V1(DataContractConfigV1), } impl DataContractConfig { @@ -26,9 +32,10 @@ impl DataContractConfig { ) -> Result { match platform_version.dpp.contract_versions.config { 0 => Ok(DataContractConfigV0::default().into()), + 1 => Ok(DataContractConfigV1::default().into()), version => Err(ProtocolError::UnknownVersionMismatch { method: "DataContractConfig::default_for_version".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, }), } @@ -43,14 +50,19 @@ impl DataContractConfig { let config: DataContractConfigV0 = platform_value::from_value(value)?; Ok(config.into()) } + 1 => { + let config: DataContractConfigV1 = platform_value::from_value(value)?; + Ok(config.into()) + } version => Err(ProtocolError::UnknownVersionMismatch { method: "DataContractConfig::from_value".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, }), } } + // TODO: Remove, it's not using /// Retrieve contract configuration properties. /// /// This method takes a BTreeMap representing a contract and retrieves @@ -77,9 +89,12 @@ impl DataContractConfig { 0 => Ok( DataContractConfigV0::get_contract_configuration_properties_v0(contract)?.into(), ), + 1 => Ok( + DataContractConfigV1::get_contract_configuration_properties_v1(contract)?.into(), + ), version => Err(ProtocolError::UnknownVersionMismatch { method: "DataContractConfig::get_contract_configuration_properties".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, }), } @@ -90,36 +105,42 @@ impl DataContractConfigGettersV0 for DataContractConfig { fn can_be_deleted(&self) -> bool { match self { DataContractConfig::V0(v0) => v0.can_be_deleted, + DataContractConfig::V1(v1) => v1.can_be_deleted, } } fn readonly(&self) -> bool { match self { DataContractConfig::V0(v0) => v0.readonly, + DataContractConfig::V1(v1) => v1.readonly, } } fn keeps_history(&self) -> bool { match self { DataContractConfig::V0(v0) => v0.keeps_history, + DataContractConfig::V1(v1) => v1.keeps_history, } } fn documents_keep_history_contract_default(&self) -> bool { match self { DataContractConfig::V0(v0) => v0.documents_keep_history_contract_default, + DataContractConfig::V1(v1) => v1.documents_keep_history_contract_default, } } fn documents_mutable_contract_default(&self) -> bool { match self { DataContractConfig::V0(v0) => v0.documents_mutable_contract_default, + DataContractConfig::V1(v1) => v1.documents_mutable_contract_default, } } fn documents_can_be_deleted_contract_default(&self) -> bool { match self { DataContractConfig::V0(v0) => v0.documents_can_be_deleted_contract_default, + DataContractConfig::V1(v1) => v1.documents_can_be_deleted_contract_default, } } @@ -127,6 +148,7 @@ impl DataContractConfigGettersV0 for DataContractConfig { fn requires_identity_encryption_bounded_key(&self) -> Option { match self { DataContractConfig::V0(v0) => v0.requires_identity_encryption_bounded_key, + DataContractConfig::V1(v1) => v1.requires_identity_encryption_bounded_key, } } @@ -134,6 +156,7 @@ impl DataContractConfigGettersV0 for DataContractConfig { fn requires_identity_decryption_bounded_key(&self) -> Option { match self { DataContractConfig::V0(v0) => v0.requires_identity_decryption_bounded_key, + DataContractConfig::V1(v1) => v1.requires_identity_decryption_bounded_key, } } } @@ -142,36 +165,42 @@ impl DataContractConfigSettersV0 for DataContractConfig { fn set_can_be_deleted(&mut self, value: bool) { match self { DataContractConfig::V0(v0) => v0.can_be_deleted = value, + DataContractConfig::V1(v1) => v1.can_be_deleted = value, } } fn set_readonly(&mut self, value: bool) { match self { DataContractConfig::V0(v0) => v0.readonly = value, + DataContractConfig::V1(v1) => v1.readonly = value, } } fn set_keeps_history(&mut self, value: bool) { match self { DataContractConfig::V0(v0) => v0.keeps_history = value, + DataContractConfig::V1(v1) => v1.keeps_history = value, } } fn set_documents_keep_history_contract_default(&mut self, value: bool) { match self { DataContractConfig::V0(v0) => v0.documents_keep_history_contract_default = value, + DataContractConfig::V1(v1) => v1.documents_keep_history_contract_default = value, } } fn set_documents_can_be_deleted_contract_default(&mut self, value: bool) { match self { DataContractConfig::V0(v0) => v0.documents_can_be_deleted_contract_default = value, + DataContractConfig::V1(v1) => v1.documents_can_be_deleted_contract_default = value, } } fn set_documents_mutable_contract_default(&mut self, value: bool) { match self { DataContractConfig::V0(v0) => v0.documents_mutable_contract_default = value, + DataContractConfig::V1(v1) => v1.documents_mutable_contract_default = value, } } @@ -181,6 +210,7 @@ impl DataContractConfigSettersV0 for DataContractConfig { ) { match self { DataContractConfig::V0(v0) => v0.requires_identity_encryption_bounded_key = value, + DataContractConfig::V1(v1) => v1.requires_identity_encryption_bounded_key = value, } } @@ -190,6 +220,25 @@ impl DataContractConfigSettersV0 for DataContractConfig { ) { match self { DataContractConfig::V0(v0) => v0.requires_identity_decryption_bounded_key = value, + DataContractConfig::V1(v1) => v1.requires_identity_decryption_bounded_key = value, + } + } +} + +impl DataContractConfigGettersV1 for DataContractConfig { + fn granular_integer_types(&self) -> bool { + match self { + DataContractConfig::V0(v0) => false, + DataContractConfig::V1(v1) => v1.granular_integer_types, + } + } +} + +impl DataContractConfigSettersV1 for DataContractConfig { + fn set_granular_integer_types_enabled(&mut self, enable: bool) { + match self { + DataContractConfig::V0(_) => {} + DataContractConfig::V1(v1) => v1.granular_integer_types = enable, } } } diff --git a/packages/rs-dpp/src/data_contract/config/v1/mod.rs b/packages/rs-dpp/src/data_contract/config/v1/mod.rs new file mode 100644 index 00000000000..c517b1ea2dc --- /dev/null +++ b/packages/rs-dpp/src/data_contract/config/v1/mod.rs @@ -0,0 +1,157 @@ +use crate::data_contract::config; +use crate::data_contract::config::v0::{DataContractConfigGettersV0, DataContractConfigSettersV0}; +use crate::data_contract::config::{ + DataContractConfig, DEFAULT_CONTRACT_CAN_BE_DELETED, DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED, + DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY, DEFAULT_CONTRACT_DOCUMENT_MUTABILITY, + DEFAULT_CONTRACT_KEEPS_HISTORY, DEFAULT_CONTRACT_MUTABILITY, DEFAULT_GRANULAR_NUMERIC_TYPES, +}; +use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_value::btreemap_extensions::BTreeValueMapHelper; +use platform_value::Value; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Serialize, Deserialize, Decode, Encode, Debug, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "camelCase", default)] +pub struct DataContractConfigV1 { + /// Can the contract ever be deleted. If the contract is deleted, so should be all + /// documents associated with it. TODO: There should also be a way to "stop" the contract - + /// contract and documents are kept in the system, but no new documents can be added to it + pub can_be_deleted: bool, + /// Is the contract mutable. Means that the document definitions can be changed or new + /// document definitions can be added to the contract + pub readonly: bool, + /// Does the contract keep history when the contract itself changes + pub keeps_history: bool, + /// Do documents in the contract keep history. This is a default for all documents in + /// the contract, but can be overridden by the document itself + pub documents_keep_history_contract_default: bool, + /// Are documents in the contract mutable? This specifies whether the documents can be + /// changed. This is a default for all document types in the contract, but can be + /// overridden by the document type config. + pub documents_mutable_contract_default: bool, + /// Can documents in the contract be deleted? This specifies whether the documents can be + /// deleted. This is a default for all document types in the contract, but can be + /// overridden by the document types itself. + pub documents_can_be_deleted_contract_default: bool, + /// Encryption key storage requirements + pub requires_identity_encryption_bounded_key: Option, + /// Decryption key storage requirements + pub requires_identity_decryption_bounded_key: Option, + /// Use granular integer Rust types for `integer` property type + pub granular_integer_types: bool, +} + +/// Trait representing getters for `DataContractConfigV1` +pub trait DataContractConfigGettersV1: DataContractConfigGettersV0 { + /// Use granular integer Rust types for `integer` property type + fn granular_integer_types(&self) -> bool; +} + +/// Trait representing setters for `DataContractConfigV1` +pub trait DataContractConfigSettersV1: DataContractConfigSettersV0 { + /// Enable/disable granular integer Rust types for `integer` property type + fn set_granular_integer_types_enabled(&mut self, enable: bool); +} + +impl Default for DataContractConfigV1 { + fn default() -> Self { + DataContractConfigV1 { + can_be_deleted: DEFAULT_CONTRACT_CAN_BE_DELETED, + readonly: !DEFAULT_CONTRACT_MUTABILITY, + keeps_history: DEFAULT_CONTRACT_KEEPS_HISTORY, + documents_keep_history_contract_default: DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY, + documents_mutable_contract_default: DEFAULT_CONTRACT_DOCUMENT_MUTABILITY, + documents_can_be_deleted_contract_default: DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + granular_integer_types: true, + } + } +} + +impl DataContractConfigV1 { + pub fn from_value(value: Value) -> Result { + platform_value::from_value(value).map_err(ProtocolError::ValueError) + } + + pub fn default_with_version() -> DataContractConfig { + Self::default().into() + } +} + +impl DataContractConfigV1 { + /// Retrieve contract configuration properties. + /// + /// This method takes a BTreeMap representing a contract and retrieves + /// the configuration properties based on the values found in the map. + /// + /// The process of retrieving contract configuration properties is versioned, + /// and the version is determined by the platform version parameter. + /// If the version is not supported, an error is returned. + /// + /// # Parameters + /// + /// * `contract`: BTreeMap representing the contract. + /// * `platform_version`: The platform version being used. + /// + /// # Returns + /// + /// * `Result`: On success, a ContractConfig. + /// On failure, a ProtocolError. + #[inline(always)] + pub(super) fn get_contract_configuration_properties_v1( + contract: &BTreeMap, + ) -> Result { + let keeps_history = contract + .get_optional_bool(config::property::KEEPS_HISTORY)? + .unwrap_or(DEFAULT_CONTRACT_KEEPS_HISTORY); + let can_be_deleted = contract + .get_optional_bool(config::property::CAN_BE_DELETED)? + .unwrap_or(DEFAULT_CONTRACT_CAN_BE_DELETED); + + let readonly = contract + .get_optional_bool(config::property::READONLY)? + .unwrap_or(!DEFAULT_CONTRACT_MUTABILITY); + + let documents_keep_history_contract_default = contract + .get_optional_bool(config::property::DOCUMENTS_KEEP_HISTORY_CONTRACT_DEFAULT)? + .unwrap_or(DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY); + + let documents_mutable_contract_default = contract + .get_optional_bool(config::property::DOCUMENTS_MUTABLE_CONTRACT_DEFAULT)? + .unwrap_or(DEFAULT_CONTRACT_DOCUMENT_MUTABILITY); + + let documents_can_be_deleted_contract_default = contract + .get_optional_bool(config::property::DOCUMENTS_CAN_BE_DELETED_CONTRACT_DEFAULT)? + .unwrap_or(DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED); + + let requires_identity_encryption_bounded_key = contract + .get_optional_integer::(config::property::REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY)? + .map(|int| int.try_into()) + .transpose()?; + + let requires_identity_decryption_bounded_key = contract + .get_optional_integer::(config::property::REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY)? + .map(|int| int.try_into()) + .transpose()?; + + let granular_numeric_types = contract + .get_optional_bool(config::property::GRANULAR_NUMERIC_TYPES)? + .unwrap_or(DEFAULT_GRANULAR_NUMERIC_TYPES); + + Ok(DataContractConfigV1 { + can_be_deleted, + readonly, + keeps_history, + documents_keep_history_contract_default, + documents_mutable_contract_default, + documents_can_be_deleted_contract_default, + requires_identity_encryption_bounded_key, + requires_identity_decryption_bounded_key, + granular_integer_types: granular_numeric_types, + }) + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs index d9bc4659512..7ea6e3942e7 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs @@ -31,6 +31,7 @@ use crate::consensus::basic::document::MissingPositionsInDocumentTypePropertiesE #[cfg(feature = "validation")] use crate::consensus::basic::BasicError; use crate::data_contract::config::v0::DataContractConfigGettersV0; +use crate::data_contract::config::v1::DataContractConfigGettersV1; use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::class_methods::{ consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, @@ -267,6 +268,7 @@ impl DocumentTypeV0 { property_key.clone(), property_value, &root_schema, + data_contact_config, ) .map_err(consensus_or_protocol_data_contract_error)?; @@ -277,6 +279,7 @@ impl DocumentTypeV0 { property_key, property_value, &root_schema, + data_contact_config, ) .map_err(consensus_or_protocol_data_contract_error)?; } @@ -589,6 +592,7 @@ fn insert_values( property_key: String, property_value: &Value, root_schema: &Value, + config: &DataContractConfig, ) -> Result<(), DataContractError> { let mut to_visit: Vec<(Option, String, &Value)> = vec![(prefix, property_key, property_value)]; @@ -610,7 +614,7 @@ fn insert_values( let is_required = known_required.contains(&prefixed_property_key); let is_transient = known_transient.contains(&prefixed_property_key); - match DocumentPropertyType::try_from(&inner_properties)? { + match DocumentPropertyType::try_from_value_map(&inner_properties, &config.into())? { DocumentPropertyType::Object(_) => { if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { @@ -637,6 +641,8 @@ fn insert_values( } } property_type => { + if !config.granular_integer_types() && property_type.is_integer() {} + document_properties.insert( prefixed_property_key, DocumentProperty { @@ -658,6 +664,7 @@ fn insert_values_nested( property_key: String, property_value: &Value, root_schema: &Value, + config: &DataContractConfig, ) -> Result<(), DataContractError> { let mut inner_properties = property_value.to_btree_ref_string_map()?; @@ -671,75 +678,78 @@ fn insert_values_nested( let is_transient = known_transient.contains(&property_key); - let property_type = match DocumentPropertyType::try_from(&inner_properties)? { - DocumentPropertyType::Object(_) => { - let mut nested_properties = IndexMap::new(); - if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { - let properties = - properties_as_value - .as_map() - .ok_or(DataContractError::ValueWrongType( - "properties must be a map".to_string(), - ))?; - - let mut sorted_properties: Vec<_> = properties.iter().collect(); - - sorted_properties.sort_by(|(_, value_1), (_, value_2)| { - let pos_1: u64 = value_1 - .get_integer(property_names::POSITION) - .expect("expected a position"); - let pos_2: u64 = value_2 - .get_integer(property_names::POSITION) - .expect("expected a position"); - pos_1.cmp(&pos_2) - }); - - // Create a new set with the prefix removed from the keys - let stripped_required: BTreeSet = known_required - .iter() - .filter_map(|key| { - if key.starts_with(&property_key) && key.len() > property_key.len() { - Some(key[property_key.len() + 1..].to_string()) - } else { - None - } - }) - .collect(); + let property_type = + match DocumentPropertyType::try_from_value_map(&inner_properties, &config.into())? { + DocumentPropertyType::Object(_) => { + let mut nested_properties = IndexMap::new(); + if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) + { + let properties = + properties_as_value + .as_map() + .ok_or(DataContractError::ValueWrongType( + "properties must be a map".to_string(), + ))?; - let stripped_transient: BTreeSet = known_transient - .iter() - .filter_map(|key| { - if key.starts_with(&property_key) && key.len() > property_key.len() { - Some(key[property_key.len() + 1..].to_string()) - } else { - None - } - }) - .collect(); - - for (object_property_key, object_property_value) in properties.iter() { - let object_property_string = object_property_key - .as_text() - .ok_or(DataContractError::KeyWrongType( - "property key must be a string".to_string(), - ))? - .to_string(); - - insert_values_nested( - &mut nested_properties, - &stripped_required, - &stripped_transient, - object_property_string, - object_property_value, - root_schema, - )?; + let mut sorted_properties: Vec<_> = properties.iter().collect(); + + sorted_properties.sort_by(|(_, value_1), (_, value_2)| { + let pos_1: u64 = value_1 + .get_integer(property_names::POSITION) + .expect("expected a position"); + let pos_2: u64 = value_2 + .get_integer(property_names::POSITION) + .expect("expected a position"); + pos_1.cmp(&pos_2) + }); + + // Create a new set with the prefix removed from the keys + let stripped_required: BTreeSet = known_required + .iter() + .filter_map(|key| { + if key.starts_with(&property_key) && key.len() > property_key.len() { + Some(key[property_key.len() + 1..].to_string()) + } else { + None + } + }) + .collect(); + + let stripped_transient: BTreeSet = known_transient + .iter() + .filter_map(|key| { + if key.starts_with(&property_key) && key.len() > property_key.len() { + Some(key[property_key.len() + 1..].to_string()) + } else { + None + } + }) + .collect(); + + for (object_property_key, object_property_value) in properties.iter() { + let object_property_string = object_property_key + .as_text() + .ok_or(DataContractError::KeyWrongType( + "property key must be a string".to_string(), + ))? + .to_string(); + + insert_values_nested( + &mut nested_properties, + &stripped_required, + &stripped_transient, + object_property_string, + object_property_value, + root_schema, + config, + )?; + } } - } - DocumentPropertyType::Object(nested_properties) - } - property_type => property_type, - }; + DocumentPropertyType::Object(nested_properties) + } + property_type => property_type, + }; document_properties.insert( property_key, diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 801628a2b42..31c9b0a4cae 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -6,6 +6,8 @@ use std::io::{BufReader, Cursor, Read}; use crate::data_contract::errors::DataContractError; use crate::consensus::basic::decode::DecodingError; +use crate::data_contract::config::v1::DataContractConfigGettersV1; +use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::property_names; use crate::prelude::TimestampMillis; use crate::ProtocolError; @@ -14,7 +16,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use indexmap::IndexMap; use integer_encoding::{VarInt, VarIntReader}; use itertools::Itertools; -use platform_value::btreemap_extensions::BTreeValueMapHelper; +use platform_value::btreemap_extensions::{BTreeValueMapHelper, BTreeValueMapPathHelper}; use platform_value::{Identifier, Value}; use platform_version::version::PlatformVersion; use rand::distributions::{Alphanumeric, Standard}; @@ -2011,74 +2013,44 @@ impl DocumentPropertyType { let mut cursor = Cursor::new(wtr); cursor.read_f64::().ok() } -} -fn get_field_type_matching_error(value: &Value) -> DataContractError { - DataContractError::ValueWrongType(format!( - "document field type doesn't match \"{}\" document value", - value - )) -} - -impl TryFrom<&BTreeMap> for DocumentPropertyType { - type Error = DataContractError; + pub fn is_integer(&self) -> bool { + matches!( + self, + DocumentPropertyType::I8 + | DocumentPropertyType::I16 + | DocumentPropertyType::I32 + | DocumentPropertyType::I64 + | DocumentPropertyType::U8 + | DocumentPropertyType::U16 + | DocumentPropertyType::U32 + | DocumentPropertyType::U64 + ) + } - fn try_from(value: &BTreeMap) -> Result { - let type_value = value.get_str(property_names::TYPE)?; + pub fn try_from_value_map( + value_map: &BTreeMap, + options: &DocumentPropertyTypeParsingOptions, + ) -> Result { + let type_value = value_map.get_str(property_names::TYPE)?; let property_type = match type_value { "integer" => { - let minimum = value.get_optional_integer::(property_names::MINIMUM)?; - let maximum = value.get_optional_integer::(property_names::MAXIMUM)?; - - match (minimum, maximum) { - (Some(min), Some(max)) => find_integer_type_for_min_and_max_values(min, max), - (Some(min), None) => { - if min >= 0 { - Self::U64 - } else { - Self::I64 - } - } - (None, Some(max)) => find_unsigned_integer_type_for_max_value(max), - (None, None) => { - // If enum is defined, we can try to figure out type based on minimal and maximal values - let enum_type = if let Some(enum_values) = - value.get_optional_inner_value_array::>(property_names::ENUM)? - { - match enum_values - .into_iter() - .filter_map(|v| v.as_integer()) - .minmax() - { - itertools::MinMaxResult::MinMax(min, max) => { - Some(find_integer_type_for_min_and_max_values(min, max)) - } - itertools::MinMaxResult::OneElement(val) => { - Some(find_unsigned_integer_type_for_max_value(val)) - } - _ => None, - } - } else { - None - }; - - if let Some(enum_type) = enum_type { - enum_type - } else { - Self::I64 - } - } + if options.granular_integer_types { + find_integer_type_for_subschema_value(value_map)? + } else { + DocumentPropertyType::I64 } } "string" => DocumentPropertyType::String(StringPropertySizes { - min_length: value.get_optional_integer(property_names::MIN_LENGTH)?, - max_length: value.get_optional_integer(property_names::MAX_LENGTH)?, + min_length: value_map.get_optional_integer(property_names::MIN_LENGTH)?, + max_length: value_map.get_optional_integer(property_names::MAX_LENGTH)?, }), "array" => { // Only handling bytearrays for v1 // Return an error if it is not a byte array - let Some(is_byte_array) = value.get_optional_bool(property_names::BYTE_ARRAY)? + let Some(is_byte_array) = + value_map.get_optional_bool(property_names::BYTE_ARRAY)? else { return Err(DataContractError::InvalidContractStructure( "only byte arrays are supported now".to_string(), @@ -2091,11 +2063,11 @@ impl TryFrom<&BTreeMap> for DocumentPropertyType { )); } - match value.get_optional_str(property_names::CONTENT_MEDIA_TYPE)? { + match value_map.get_optional_str(property_names::CONTENT_MEDIA_TYPE)? { Some("application/x.dash.dpp.identifier") => DocumentPropertyType::Identifier, Some(_) | None => DocumentPropertyType::ByteArray(ByteArrayPropertySizes { - min_size: value.get_optional_integer(property_names::MIN_ITEMS)?, - max_size: value.get_optional_integer(property_names::MAX_ITEMS)?, + min_size: value_map.get_optional_integer(property_names::MIN_ITEMS)?, + max_size: value_map.get_optional_integer(property_names::MAX_ITEMS)?, }), } } @@ -2114,6 +2086,83 @@ impl TryFrom<&BTreeMap> for DocumentPropertyType { } } +#[derive(Debug, Clone)] +pub struct DocumentPropertyTypeParsingOptions { + pub granular_integer_types: bool, +} + +impl Default for DocumentPropertyTypeParsingOptions { + fn default() -> Self { + Self { + granular_integer_types: true, + } + } +} + +impl From<&DataContractConfig> for DocumentPropertyTypeParsingOptions { + fn from(config: &DataContractConfig) -> Self { + Self { + granular_integer_types: config.granular_integer_types(), + } + } +} + +fn get_field_type_matching_error(value: &Value) -> DataContractError { + DataContractError::ValueWrongType(format!( + "document field type doesn't match \"{}\" document value", + value + )) +} + +fn find_integer_type_for_subschema_value( + value: &BTreeMap, +) -> Result { + let minimum = value.get_optional_integer::(property_names::MINIMUM)?; + let maximum = value.get_optional_integer::(property_names::MAXIMUM)?; + + let property_type = match (minimum, maximum) { + (Some(min), Some(max)) => find_integer_type_for_min_and_max_values(min, max), + (Some(min), None) => { + if min >= 0 { + DocumentPropertyType::U64 + } else { + DocumentPropertyType::I64 + } + } + (None, Some(max)) => find_unsigned_integer_type_for_max_value(max), + (None, None) => { + // If enum is defined, we can try to figure out type based on minimal and maximal values + let enum_type = if let Some(enum_values) = + value.get_optional_inner_value_array::>(property_names::ENUM)? + { + match enum_values + .into_iter() + .filter_map(|v| v.as_integer()) + .minmax() + { + itertools::MinMaxResult::MinMax(min, max) => { + Some(find_integer_type_for_min_and_max_values(min, max)) + } + itertools::MinMaxResult::OneElement(val) => { + Some(find_unsigned_integer_type_for_max_value(val)) + } + _ => None, + } + } else { + None + }; + + if let Some(enum_type) = enum_type { + enum_type + } else { + DocumentPropertyType::I64 + } + } + }; + + Ok(property_type) +} + fn find_unsigned_integer_type_for_max_value(max_value: i64) -> DocumentPropertyType { if max_value <= u8::MAX as i64 { DocumentPropertyType::U8 diff --git a/packages/rs-drive/src/query/test_index.rs b/packages/rs-drive/src/query/test_index.rs index df368eb5c8e..6e2576d19d7 100644 --- a/packages/rs-drive/src/query/test_index.rs +++ b/packages/rs-drive/src/query/test_index.rs @@ -1,15 +1,15 @@ #[cfg(feature = "server")] #[cfg(test)] mod tests { - use dpp::data_contract::document_type::DocumentType; - use dpp::platform_value::{platform_value, Identifier}; - use dpp::util::cbor_serializer; - use serde_json::json; - use crate::config::DriveConfig; use crate::error::{query::QuerySyntaxError, Error}; use crate::query::DriveDocumentQuery; + use dpp::data_contract::config::DataContractConfig; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; + use dpp::data_contract::document_type::DocumentType; + use dpp::platform_value::{platform_value, Identifier}; + use dpp::util::cbor_serializer; + use serde_json::json; use dpp::tests::fixtures::get_dpns_data_contract_fixture; use dpp::version::PlatformVersion; @@ -77,14 +77,15 @@ mod tests { "additionalProperties": false, }); + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + DocumentType::try_from_schema( Identifier::random(), "indexed_type", schema, None, - false, - false, - false, + &config, true, &mut vec![], platform_version, diff --git a/packages/rs-drive/tests/deterministic_root_hash.rs b/packages/rs-drive/tests/deterministic_root_hash.rs index 690fce96091..17cc57f0f90 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -302,7 +302,8 @@ mod tests { // We expect a different app hash because data contract is not serialized the same way let expected_app_hash = match platform_version.protocol_version { 0..7 => "1b80f4a9f00597b3f1ddca904b3cee67576868adcdd802c0a3f91e14209bb402", - _ => "387fe8e2298bb33e0ff79fd377eccb14109fb2534c7338c535bd74b5b8580580", + 7..9 => "387fe8e2298bb33e0ff79fd377eccb14109fb2534c7338c535bd74b5b8580580", + _ => "bd6560b17460a653077af58097e0c111d84696f25fd1c8f252b2a1af3fc5f122", }; assert_eq!(hex::encode(app_hash), expected_app_hash); diff --git a/packages/rs-drive/tests/query_tests.rs b/packages/rs-drive/tests/query_tests.rs index 8451908d687..537be44938d 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -59,6 +59,7 @@ use base64::Engine; #[cfg(feature = "server")] use dpp::block::block_info::BlockInfo; use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::config::v1::DataContractConfigSettersV1; use dpp::data_contract::conversion::value::v0::DataContractValueConversionMethodsV0; use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::document::serialization_traits::{ @@ -76,12 +77,11 @@ use dpp::tests::json_document::json_document_to_contract; use dpp::util::cbor_serializer; use dpp::version::fee::FeeVersion; use dpp::version::PlatformVersion; -use once_cell::sync::Lazy; -use rand::prelude::StdRng; - #[cfg(feature = "server")] use drive::drive::contract::test_helpers::add_init_contracts_structure_operations; use drive::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; +use once_cell::sync::Lazy; +use rand::prelude::StdRng; use drive::drive::document::query::QueryDocumentsOutcomeV0Methods; #[cfg(feature = "server")] @@ -880,7 +880,7 @@ pub fn setup_withdrawal_tests( .expect("expected to create contracts tree successfully"); // setup code - let contract = setup_contract( + let mut contract = setup_contract( &drive, "tests/supporting_files/contract/withdrawals/withdrawals-contract.json", None, @@ -890,6 +890,10 @@ pub fn setup_withdrawal_tests( None, ); + contract + .config_mut() + .set_granular_integer_types_enabled(false); + add_withdrawals_to_contract( &drive, &contract, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs index 854a3dc331b..04de060d85b 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs @@ -14,7 +14,7 @@ pub const CONTRACT_VERSIONS_V2: DPPContractVersions = DPPContractVersions { }, contract_structure_version: 1, //changed created_data_contract_structure: 0, - config: 0, + config: 1, // changed to enable granular number types methods: DataContractMethodVersions { validate_document: 0, validate_update: 0, From 380948cdf0be547f3bd3dc06142d571da7198c61 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 14:18:31 +0700 Subject: [PATCH 06/19] fix: missing known version --- .../document_type/class_methods/try_from_schema/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs index 41a031aa18d..fcc71ce022b 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs @@ -40,7 +40,7 @@ impl DocumentType { .map(|document_type| document_type.into()), version => Err(ProtocolError::UnknownVersionMismatch { method: "try_from_schema".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, }), } From a3fb073d725b432b1b82f9125a478b64d85d94a0 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 16:59:05 +0700 Subject: [PATCH 07/19] chore: kick off CI From e76f575bcb7e923f1a0a2b822d17d3cedb5cf6af Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 18:00:49 +0700 Subject: [PATCH 08/19] fix: metadata deserialization in JS client --- .../platform/waitForStateTransitionResult/ErrorResult.js | 4 ++-- .../WaitForStateTransitionResultResponse.js | 8 +++----- .../src/SDK/Client/Platform/IStateTransitionResult.ts | 2 +- .../src/SDK/Client/Platform/broadcastStateTransition.ts | 6 +++++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js index 8bc98691f89..55072f849b6 100644 --- a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js +++ b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js @@ -2,7 +2,7 @@ class ErrorResult { /** * @param {number} code * @param {string} message - * @param {*} data + * @param {Buffer|undefined} data */ constructor(code, message, data) { this.code = code; @@ -25,7 +25,7 @@ class ErrorResult { } /** - * @returns {*} + * @returns {Buffer|undefined} */ getData() { return this.data; diff --git a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js index da357d7d9d7..836987a2376 100644 --- a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js +++ b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js @@ -1,5 +1,3 @@ -const cbor = require('cbor'); - const AbstractResponse = require('../response/AbstractResponse'); const Metadata = require('../response/Metadata'); const Proof = require('../response/Proof'); @@ -38,9 +36,9 @@ class WaitForStateTransitionResultResponse extends AbstractResponse { if (proto.getV0().getError()) { let data; - const rawData = proto.getV0().getError().getData(); - if (rawData) { - data = cbor.decode(Buffer.from(rawData)); + + if (proto.getV0().getError().getData()) { + data = Buffer.from(proto.getV0().getError().getData()); } error = new ErrorResult( diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts index 2b679814453..08e562082b4 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts @@ -7,6 +7,6 @@ export interface IStateTransitionResult { error?: { code: number, message: string, - data: any, + data?: Buffer, } } diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts index a12b67872f4..2dce38a116e 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts @@ -89,7 +89,11 @@ export default async function broadcastStateTransition( // instead of passing it as GrpcError constructor argument // Otherwise it will be converted to grpc-js metadata // Which is not compatible with web - grpcError.metadata = error.data; + if (error.data) { + grpcError.metadata = { + 'dash-serialized-consensus-error-bin': error.data.toString('base64'), + }; + } let cause = await createGrpcTransportError(grpcError); From 959dcfc7f70abca0fb149e442df4ab06810b1a16 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 13 Feb 2025 18:59:13 +0700 Subject: [PATCH 09/19] fix: decryption bounded key option read --- packages/rs-dpp/src/data_contract/config/mod.rs | 2 +- packages/rs-dpp/src/data_contract/config/v1/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/config/mod.rs b/packages/rs-dpp/src/data_contract/config/mod.rs index 9c79173539b..9c30b8d21e2 100644 --- a/packages/rs-dpp/src/data_contract/config/mod.rs +++ b/packages/rs-dpp/src/data_contract/config/mod.rs @@ -228,7 +228,7 @@ impl DataContractConfigSettersV0 for DataContractConfig { impl DataContractConfigGettersV1 for DataContractConfig { fn granular_integer_types(&self) -> bool { match self { - DataContractConfig::V0(v0) => false, + DataContractConfig::V0(_) => false, DataContractConfig::V1(v1) => v1.granular_integer_types, } } diff --git a/packages/rs-dpp/src/data_contract/config/v1/mod.rs b/packages/rs-dpp/src/data_contract/config/v1/mod.rs index c517b1ea2dc..209bb07ac98 100644 --- a/packages/rs-dpp/src/data_contract/config/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/config/v1/mod.rs @@ -134,7 +134,7 @@ impl DataContractConfigV1 { .transpose()?; let requires_identity_decryption_bounded_key = contract - .get_optional_integer::(config::property::REQUIRES_IDENTITY_ENCRYPTION_BOUNDED_KEY)? + .get_optional_integer::(config::property::REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY)? .map(|int| int.try_into()) .transpose()?; From b83c12f29a008ccb049a24741fc382e06fecab19 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 14:45:57 +0700 Subject: [PATCH 10/19] revert: changes from another branch --- .../platform/waitForStateTransitionResult/ErrorResult.js | 4 ++-- .../WaitForStateTransitionResultResponse.js | 8 +++++--- .../src/SDK/Client/Platform/IStateTransitionResult.ts | 2 +- .../src/SDK/Client/Platform/broadcastStateTransition.ts | 6 +----- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js index 55072f849b6..8bc98691f89 100644 --- a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js +++ b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/ErrorResult.js @@ -2,7 +2,7 @@ class ErrorResult { /** * @param {number} code * @param {string} message - * @param {Buffer|undefined} data + * @param {*} data */ constructor(code, message, data) { this.code = code; @@ -25,7 +25,7 @@ class ErrorResult { } /** - * @returns {Buffer|undefined} + * @returns {*} */ getData() { return this.data; diff --git a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js index 836987a2376..da357d7d9d7 100644 --- a/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js +++ b/packages/js-dapi-client/lib/methods/platform/waitForStateTransitionResult/WaitForStateTransitionResultResponse.js @@ -1,3 +1,5 @@ +const cbor = require('cbor'); + const AbstractResponse = require('../response/AbstractResponse'); const Metadata = require('../response/Metadata'); const Proof = require('../response/Proof'); @@ -36,9 +38,9 @@ class WaitForStateTransitionResultResponse extends AbstractResponse { if (proto.getV0().getError()) { let data; - - if (proto.getV0().getError().getData()) { - data = Buffer.from(proto.getV0().getError().getData()); + const rawData = proto.getV0().getError().getData(); + if (rawData) { + data = cbor.decode(Buffer.from(rawData)); } error = new ErrorResult( diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts index 08e562082b4..2b679814453 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/IStateTransitionResult.ts @@ -7,6 +7,6 @@ export interface IStateTransitionResult { error?: { code: number, message: string, - data?: Buffer, + data: any, } } diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts index 2dce38a116e..a12b67872f4 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/broadcastStateTransition.ts @@ -89,11 +89,7 @@ export default async function broadcastStateTransition( // instead of passing it as GrpcError constructor argument // Otherwise it will be converted to grpc-js metadata // Which is not compatible with web - if (error.data) { - grpcError.metadata = { - 'dash-serialized-consensus-error-bin': error.data.toString('base64'), - }; - } + grpcError.metadata = error.data; let cause = await createGrpcTransportError(grpcError); From 722a63203009d5ac6186739fe7cdf49ae7720bc7 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 14:58:28 +0700 Subject: [PATCH 11/19] test: fix tests --- .../src/query/document_query/v0/mod.rs | 46 +++++++++---------- packages/strategy-tests/src/operations.rs | 7 +-- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/rs-drive-abci/src/query/document_query/v0/mod.rs b/packages/rs-drive-abci/src/query/document_query/v0/mod.rs index 0b1e8be12b9..26c37964d39 100644 --- a/packages/rs-drive-abci/src/query/document_query/v0/mod.rs +++ b/packages/rs-drive-abci/src/query/document_query/v0/mod.rs @@ -822,7 +822,7 @@ mod tests { WhereClause { field: "status".to_string(), operator: WhereOperator::Equal, - value: Value::I64(0), + value: Value::U8(0), }, ), ( @@ -830,7 +830,7 @@ mod tests { WhereClause { field: "pooling".to_string(), operator: WhereOperator::Equal, - value: Value::I64(0), + value: Value::U8(0), }, ), ( @@ -838,7 +838,7 @@ mod tests { WhereClause { field: "coreFeePerByte".to_string(), operator: WhereOperator::Equal, - value: Value::I64(1), + value: Value::U32(1), }, ), ]), @@ -986,7 +986,7 @@ mod tests { WhereClause { field: "status".to_string(), operator: WhereOperator::Equal, - value: Value::I64(0), + value: Value::U8(0), }, ), ( @@ -994,7 +994,7 @@ mod tests { WhereClause { field: "pooling".to_string(), operator: WhereOperator::Equal, - value: Value::I64(0), + value: Value::U8(0), }, ), ( @@ -1002,7 +1002,7 @@ mod tests { WhereClause { field: "coreFeePerByte".to_string(), operator: WhereOperator::Equal, - value: Value::I64(1), + value: Value::U32(1), }, ), ]), @@ -1140,11 +1140,11 @@ mod tests { field: "status".to_string(), operator: WhereOperator::In, value: Value::Array(vec![ - Value::I64(0), - Value::I64(1), - Value::I64(2), - Value::I64(3), - Value::I64(4), + Value::U8(0), + Value::U8(1), + Value::U8(2), + Value::U8(3), + Value::U8(4), ]), }), range_clause: None, @@ -1302,11 +1302,11 @@ mod tests { field: "status".to_string(), operator: WhereOperator::In, value: Value::Array(vec![ - Value::I64(0), - Value::I64(1), - Value::I64(2), - Value::I64(3), - Value::I64(4), + Value::U8(0), + Value::U8(1), + Value::U8(2), + Value::U8(3), + Value::U8(4), ]), }), range_clause: None, @@ -1473,11 +1473,11 @@ mod tests { field: "status".to_string(), operator: WhereOperator::In, value: Value::Array(vec![ - Value::I64(0), - Value::I64(1), - Value::I64(2), - Value::I64(3), - Value::I64(4), + Value::U8(0), + Value::U8(1), + Value::U8(2), + Value::U8(3), + Value::U8(4), ]), }), range_clause: None, @@ -1487,7 +1487,7 @@ mod tests { WhereClause { field: "pooling".to_string(), operator: WhereOperator::Equal, - value: Value::I64(0), + value: Value::U8(0), }, ), ( @@ -1495,7 +1495,7 @@ mod tests { WhereClause { field: "coreFeePerByte".to_string(), operator: WhereOperator::Equal, - value: Value::I64(1), + value: Value::U32(1), }, ), ]), diff --git a/packages/strategy-tests/src/operations.rs b/packages/strategy-tests/src/operations.rs index 4dcebb9f62a..ca22237f2a8 100644 --- a/packages/strategy-tests/src/operations.rs +++ b/packages/strategy-tests/src/operations.rs @@ -424,16 +424,13 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Dat let name_str = name.to_str().expect( "Couldn't convert document type name to str in deserialization", ); - let schema = Value::try_from(schema_json).unwrap(); let owner_id = contract.owner_id(); // Assuming you have a method to get the owner_id from the contract DocumentType::try_from_schema( owner_id, name_str, - schema, + schema_json, None, - true, - true, - true, + contract.config(), full_validation, &mut vec![], platform_version, From 5c64fc5cae3985872656cf6a9aab2347c5a1c450 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 15:54:29 +0700 Subject: [PATCH 12/19] test: update wasm dpp tests --- .../DataContractCreateTransition.spec.js | 2 +- .../DataContractUpdateTransition.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractCreateTransition/DataContractCreateTransition.spec.js b/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractCreateTransition/DataContractCreateTransition.spec.js index dc394aee3c6..59cc00c8852 100644 --- a/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractCreateTransition/DataContractCreateTransition.spec.js +++ b/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractCreateTransition/DataContractCreateTransition.spec.js @@ -66,7 +66,7 @@ describe('DataContractCreateTransition', () => { it('should return serialized State Transition', () => { const result = stateTransition.toBuffer(); expect(result).to.be.instanceOf(Buffer); - expect(result).to.have.lengthOf(2359); + expect(result).to.have.lengthOf(2360); }); it('should be able to restore contract config from bytes', () => { diff --git a/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/DataContractUpdateTransition.spec.js b/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/DataContractUpdateTransition.spec.js index 444f695113c..0931b62a1fe 100644 --- a/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/DataContractUpdateTransition.spec.js +++ b/packages/wasm-dpp/test/unit/dataContract/stateTransition/DataContractUpdateTransition/DataContractUpdateTransition.spec.js @@ -65,7 +65,7 @@ describe('DataContractUpdateTransition', () => { it('should return serialized State Transition', () => { const result = stateTransition.toBuffer(); expect(result).to.be.instanceOf(Buffer); - expect(result).to.have.lengthOf(2359); + expect(result).to.have.lengthOf(2360); }); it('should be able to restore contract config from bytes', () => { From ca5f335bcb6ac6b5b923dbe98e062674be2e4bcd Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 16:11:21 +0700 Subject: [PATCH 13/19] test: fix rs sdk tests --- packages/rs-sdk/tests/fetch/common.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/rs-sdk/tests/fetch/common.rs b/packages/rs-sdk/tests/fetch/common.rs index da109c4da0b..676f7e2c7c4 100644 --- a/packages/rs-sdk/tests/fetch/common.rs +++ b/packages/rs-sdk/tests/fetch/common.rs @@ -1,4 +1,5 @@ use dash_sdk::{mock::Mockable, platform::Query, Sdk}; +use dpp::data_contract::config::DataContractConfig; use dpp::{data_contract::DataContractFactory, prelude::Identifier}; use hex::ToHex; use rs_dapi_client::transport::TransportRequest; @@ -30,14 +31,15 @@ pub fn mock_document_type() -> dpp::data_contract::document_type::DocumentType { "additionalProperties": false, }); + let config = + DataContractConfig::default_for_version(platform_version).expect("create a default config"); + DocumentType::try_from_schema( Identifier::random(), "document_type_name", schema, None, - false, - false, - false, + &config, true, &mut vec![], platform_version, From e946b2f80ca2b1d7e95581cc0cbbb8c2d37b5b63 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 16:29:42 +0700 Subject: [PATCH 14/19] fix: invalid feature flag usage --- .../token_transfer_transition/v0/mod.rs | 11 +---------- .../token_transfer_transition/v0/v0_methods.rs | 2 +- .../token_transfer_transition/v0_methods.rs | 2 +- .../document/batch_transition/methods/mod.rs | 4 +--- .../document/batch_transition/methods/v1/mod.rs | 4 +--- .../document/batch_transition/v1/v0_methods.rs | 3 +-- packages/rs-dpp/src/tokens/mod.rs | 10 ++++++++++ packages/rs-dpp/src/tokens/token_event.rs | 2 +- .../rs-drive-abci/src/execution/check_tx/v0/mod.rs | 8 ++++---- .../token_transfer_transition_action/mod.rs | 2 +- .../token_transfer_transition_action/v0/mod.rs | 2 +- .../platform/transition/fungible_tokens/transfer.rs | 5 +---- 12 files changed, 24 insertions(+), 31 deletions(-) diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/mod.rs index 54fc9f55ff6..de433c3510f 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/mod.rs @@ -4,21 +4,12 @@ use bincode::{Decode, Encode}; use derive_more::Display; pub use super::super::token_base_transition::IDENTIFIER_FIELDS; -use crate::prelude::{ - DerivationEncryptionKeyIndex, RecipientKeyIndex, RootEncryptionKeyIndex, SenderKeyIndex, -}; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; +use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; use platform_value::Identifier; #[cfg(feature = "state-transition-serde-conversion")] use serde::{Deserialize, Serialize}; -pub type SharedEncryptedNote = (SenderKeyIndex, RecipientKeyIndex, Vec); -pub type PrivateEncryptedNote = ( - RootEncryptionKeyIndex, - DerivationEncryptionKeyIndex, - Vec, -); - mod property_names { pub const AMOUNT: &str = "$amount"; pub const RECIPIENT_OWNER_ID: &str = "recipientOwnerId"; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs index 5f8875f3df3..710f4cfdb04 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs @@ -3,7 +3,7 @@ use crate::prelude::{DerivationEncryptionKeyIndex, RecipientKeyIndex, RootEncryp use crate::state_transition::batch_transition::batched_transition::token_transfer_transition::TokenTransferTransitionV0; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; -use crate::state_transition::batch_transition::token_transfer_transition::{PrivateEncryptedNote, SharedEncryptedNote}; +use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; impl TokenBaseTransitionAccessors for TokenTransferTransitionV0 { fn base(&self) -> &TokenBaseTransition { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs index 81cd4a1e46d..aff9670182f 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs @@ -4,7 +4,7 @@ use crate::state_transition::batch_transition::batched_transition::token_transfe use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::TokenTransferTransition; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; -use crate::state_transition::batch_transition::token_transfer_transition::{PrivateEncryptedNote, SharedEncryptedNote}; +use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; impl TokenBaseTransitionAccessors for TokenTransferTransition { fn base(&self) -> &TokenBaseTransition { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs index 9895e738b9e..d243861648c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs @@ -20,9 +20,6 @@ use crate::prelude::UserFeeIncrease; use crate::state_transition::batch_transition::batched_transition::BatchedTransition; use crate::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use crate::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1; -use crate::state_transition::batch_transition::token_transfer_transition::{ - PrivateEncryptedNote, SharedEncryptedNote, -}; use crate::state_transition::batch_transition::BatchTransition; #[cfg(feature = "state-transition-signing")] use crate::state_transition::batch_transition::{BatchTransitionV0, BatchTransitionV1}; @@ -30,6 +27,7 @@ use crate::state_transition::batch_transition::{BatchTransitionV0, BatchTransiti use crate::state_transition::StateTransition; #[cfg(feature = "state-transition-signing")] use crate::tokens::emergency_action::TokenEmergencyAction; +use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; use crate::ProtocolError; #[cfg(feature = "state-transition-signing")] use platform_value::Identifier; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs index e68792f3316..62617d914ce 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs @@ -11,13 +11,11 @@ use crate::identity::IdentityPublicKey; #[cfg(feature = "state-transition-signing")] use crate::prelude::{IdentityNonce, UserFeeIncrease}; use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0; -use crate::state_transition::batch_transition::token_transfer_transition::{ - PrivateEncryptedNote, SharedEncryptedNote, -}; #[cfg(feature = "state-transition-signing")] use crate::state_transition::StateTransition; #[cfg(feature = "state-transition-signing")] use crate::tokens::emergency_action::TokenEmergencyAction; +use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; #[cfg(feature = "state-transition-signing")] use crate::version::FeatureVersion; #[cfg(feature = "state-transition-signing")] diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs index eff8b16880a..3a7454eecbc 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs @@ -71,13 +71,12 @@ use crate::state_transition::batch_transition::token_freeze_transition::TokenFre #[cfg(feature = "state-transition-signing")] use crate::state_transition::batch_transition::token_mint_transition::TokenMintTransitionV0; #[cfg(feature = "state-transition-signing")] -use crate::state_transition::batch_transition::token_transfer_transition::{PrivateEncryptedNote, SharedEncryptedNote}; -#[cfg(feature = "state-transition-signing")] use crate::state_transition::batch_transition::token_transfer_transition::TokenTransferTransitionV0; #[cfg(feature = "state-transition-signing")] use crate::state_transition::batch_transition::token_unfreeze_transition::TokenUnfreezeTransitionV0; #[cfg(feature = "state-transition-signing")] use crate::tokens::emergency_action::TokenEmergencyAction; +use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; impl DocumentsBatchTransitionAccessorsV0 for BatchTransitionV1 { type IterType<'a> diff --git a/packages/rs-dpp/src/tokens/mod.rs b/packages/rs-dpp/src/tokens/mod.rs index aa8703acbf3..b05c9bdbd95 100644 --- a/packages/rs-dpp/src/tokens/mod.rs +++ b/packages/rs-dpp/src/tokens/mod.rs @@ -1,4 +1,7 @@ use crate::data_contract::TokenContractPosition; +use crate::prelude::{ + DerivationEncryptionKeyIndex, RecipientKeyIndex, RootEncryptionKeyIndex, SenderKeyIndex, +}; use crate::util::hash::hash_double; pub mod allowed_currency; @@ -8,6 +11,13 @@ pub mod info; pub mod status; pub mod token_event; +pub type SharedEncryptedNote = (SenderKeyIndex, RecipientKeyIndex, Vec); +pub type PrivateEncryptedNote = ( + RootEncryptionKeyIndex, + DerivationEncryptionKeyIndex, + Vec, +); + pub fn calculate_token_id(contract_id: &[u8; 32], token_pos: TokenContractPosition) -> [u8; 32] { let mut bytes = b"dash_token".to_vec(); bytes.extend_from_slice(contract_id); diff --git a/packages/rs-dpp/src/tokens/token_event.rs b/packages/rs-dpp/src/tokens/token_event.rs index fa5675dc5af..82c72825d70 100644 --- a/packages/rs-dpp/src/tokens/token_event.rs +++ b/packages/rs-dpp/src/tokens/token_event.rs @@ -21,8 +21,8 @@ pub type TokenEventPersonalEncryptedNote = Option<( Vec, )>; use crate::serialization::PlatformSerializableWithPlatformVersion; -use crate::state_transition::batch_transition::token_transfer_transition::SharedEncryptedNote; use crate::tokens::emergency_action::TokenEmergencyAction; +use crate::tokens::SharedEncryptedNote; use crate::ProtocolError; pub type RecipientIdentifier = Identifier; diff --git a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs index 06292c23421..08989a82f77 100644 --- a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs @@ -630,7 +630,7 @@ mod tests { ) .expect("expected to process state transition"); - assert_eq!(processing_result.aggregated_fees().processing_fee, 2484410); + assert_eq!(processing_result.aggregated_fees().processing_fee, 2484810); let check_result = platform .check_tx( @@ -1141,7 +1141,7 @@ mod tests { // The processing fees should be twice as much as a fee multiplier of 0, // since a fee multiplier of 100 means 100% more of 1 (gives 2) - assert_eq!(processing_result.aggregated_fees().processing_fee, 4968820); + assert_eq!(processing_result.aggregated_fees().processing_fee, 4969620); let check_result = platform .check_tx( @@ -1613,7 +1613,7 @@ mod tests { ) .expect("expected to process state transition"); - assert_eq!(processing_result.aggregated_fees().processing_fee, 2484410); + assert_eq!(processing_result.aggregated_fees().processing_fee, 2484810); platform .drive @@ -2069,7 +2069,7 @@ mod tests { ) .expect("expected to process state transition"); - assert_eq!(processing_result.aggregated_fees().processing_fee, 2484410); + assert_eq!(processing_result.aggregated_fees().processing_fee, 2484810); platform .drive diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/mod.rs index 6fd5f24a8eb..eb9fc965198 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/mod.rs @@ -3,7 +3,7 @@ use derive_more::From; use crate::state_transition_action::batch::batched_transition::token_transition::token_base_transition_action::TokenBaseTransitionAction; use dpp::identifier::Identifier; use dpp::prelude::{DerivationEncryptionKeyIndex, RootEncryptionKeyIndex}; -use dpp::state_transition::batch_transition::token_transfer_transition::{PrivateEncryptedNote, SharedEncryptedNote}; +use dpp::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; /// transformer module pub mod transformer; diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/v0/mod.rs index 7cec7fc2be0..4df8b2e93ad 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/token_transition/token_transfer_transition_action/v0/mod.rs @@ -6,7 +6,7 @@ use dpp::identifier::Identifier; use dpp::prelude::{ DerivationEncryptionKeyIndex, IdentityNonce, RootEncryptionKeyIndex, }; -use dpp::state_transition::batch_transition::token_transfer_transition::{PrivateEncryptedNote, SharedEncryptedNote}; +use dpp::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; use crate::drive::contract::DataContractFetchInfo; use crate::state_transition_action::batch::batched_transition::token_transition::token_base_transition_action::{ TokenBaseTransitionAction, TokenBaseTransitionActionAccessorsV0, diff --git a/packages/rs-sdk/src/platform/transition/fungible_tokens/transfer.rs b/packages/rs-sdk/src/platform/transition/fungible_tokens/transfer.rs index 6908c8864d0..281907b0799 100644 --- a/packages/rs-sdk/src/platform/transition/fungible_tokens/transfer.rs +++ b/packages/rs-sdk/src/platform/transition/fungible_tokens/transfer.rs @@ -9,12 +9,9 @@ use dpp::identity::signer::Signer; use dpp::identity::IdentityPublicKey; use dpp::prelude::UserFeeIncrease; use dpp::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1; -use dpp::state_transition::batch_transition::token_transfer_transition::{ - PrivateEncryptedNote, SharedEncryptedNote, -}; use dpp::state_transition::batch_transition::BatchTransition; use dpp::state_transition::StateTransition; -use dpp::tokens::calculate_token_id; +use dpp::tokens::{calculate_token_id, PrivateEncryptedNote, SharedEncryptedNote}; use dpp::version::PlatformVersion; /// A builder to configure and broadcast token transfer transitions From 0dbfeec0849cb3120b7f0d37d68bd6da7d316ebe Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Mon, 17 Feb 2025 17:08:22 +0700 Subject: [PATCH 15/19] test: update drive abci tests --- packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs index 08989a82f77..546316bd9df 100644 --- a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs @@ -1699,7 +1699,7 @@ mod tests { assert_eq!( update_processing_result.aggregated_fees().processing_fee, - 2496910 + 2497370 ); let check_result = platform From 565fba5469dc0887707f87fa85591f88c2c8a74f Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Wed, 19 Feb 2025 22:05:04 +0700 Subject: [PATCH 16/19] refactor: add imports only for state-transition-signing flag --- .../document/batch_transition/methods/v1/mod.rs | 1 + .../state_transitions/document/batch_transition/v1/v0_methods.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs index 62617d914ce..f17e54aa2bc 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/v1/mod.rs @@ -15,6 +15,7 @@ use crate::state_transition::batch_transition::accessors::DocumentsBatchTransiti use crate::state_transition::StateTransition; #[cfg(feature = "state-transition-signing")] use crate::tokens::emergency_action::TokenEmergencyAction; +#[cfg(feature = "state-transition-signing")] use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; #[cfg(feature = "state-transition-signing")] use crate::version::FeatureVersion; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs index 3a7454eecbc..c6eb96ed24c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v0_methods.rs @@ -76,6 +76,7 @@ use crate::state_transition::batch_transition::token_transfer_transition::TokenT use crate::state_transition::batch_transition::token_unfreeze_transition::TokenUnfreezeTransitionV0; #[cfg(feature = "state-transition-signing")] use crate::tokens::emergency_action::TokenEmergencyAction; +#[cfg(feature = "state-transition-signing")] use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; impl DocumentsBatchTransitionAccessorsV0 for BatchTransitionV1 { From 67231d1cfbf7646de244461d9c2272a9f3c08ae1 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Wed, 19 Feb 2025 22:06:06 +0700 Subject: [PATCH 17/19] refactor: add more imports only for state-transition-signing flag --- .../state_transitions/document/batch_transition/methods/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs index d243861648c..420f84b8cdd 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/methods/mod.rs @@ -27,6 +27,7 @@ use crate::state_transition::batch_transition::{BatchTransitionV0, BatchTransiti use crate::state_transition::StateTransition; #[cfg(feature = "state-transition-signing")] use crate::tokens::emergency_action::TokenEmergencyAction; +#[cfg(feature = "state-transition-signing")] use crate::tokens::{PrivateEncryptedNote, SharedEncryptedNote}; use crate::ProtocolError; #[cfg(feature = "state-transition-signing")] From b162f856f9d344a7872c2a0de34bb913f07c7f11 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Wed, 19 Feb 2025 22:08:11 +0700 Subject: [PATCH 18/19] refactor: remove unnecessary if condition --- .../document_type/class_methods/try_from_schema/v0/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs index 7ea6e3942e7..0362a20e57d 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v0/mod.rs @@ -641,8 +641,6 @@ fn insert_values( } } property_type => { - if !config.granular_integer_types() && property_type.is_integer() {} - document_properties.insert( prefixed_property_key, DocumentProperty { From 77558f2c6f53db6fc9ee68e68f3e4eb567c341f5 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Tue, 11 Mar 2025 17:42:02 +0700 Subject: [PATCH 19/19] refactor: rename granular types to sized types --- .../rs-dpp/src/data_contract/config/fields.rs | 4 ++-- .../rs-dpp/src/data_contract/config/mod.rs | 8 +++---- .../rs-dpp/src/data_contract/config/v1/mod.rs | 24 +++++++++---------- .../document_type/property/mod.rs | 8 +++---- packages/rs-drive/tests/query_tests.rs | 4 +--- .../dpp_versions/dpp_contract_versions/v2.rs | 2 +- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/config/fields.rs b/packages/rs-dpp/src/data_contract/config/fields.rs index f322d467a10..dd6692ad2c6 100644 --- a/packages/rs-dpp/src/data_contract/config/fields.rs +++ b/packages/rs-dpp/src/data_contract/config/fields.rs @@ -4,7 +4,7 @@ pub const DEFAULT_CONTRACT_MUTABILITY: bool = true; pub const DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY: bool = false; pub const DEFAULT_CONTRACT_DOCUMENT_MUTABILITY: bool = true; pub const DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED: bool = true; -pub const DEFAULT_GRANULAR_NUMERIC_TYPES: bool = true; +pub const DEFAULT_SIZED_INTEGER_TYPES: bool = true; pub mod property { pub const CAN_BE_DELETED: &str = "canBeDeleted"; @@ -18,5 +18,5 @@ pub mod property { "requiresIdentityEncryptionBoundedKey"; pub const REQUIRES_IDENTITY_DECRYPTION_BOUNDED_KEY: &str = "requiresIdentityDecryptionBoundedKey"; - pub const GRANULAR_NUMERIC_TYPES: &str = "granular_numeric_types"; + pub const SIZED_INTEGER_TYPES: &str = "sized_integer_types"; } diff --git a/packages/rs-dpp/src/data_contract/config/mod.rs b/packages/rs-dpp/src/data_contract/config/mod.rs index 9c30b8d21e2..eeaab84f94f 100644 --- a/packages/rs-dpp/src/data_contract/config/mod.rs +++ b/packages/rs-dpp/src/data_contract/config/mod.rs @@ -226,19 +226,19 @@ impl DataContractConfigSettersV0 for DataContractConfig { } impl DataContractConfigGettersV1 for DataContractConfig { - fn granular_integer_types(&self) -> bool { + fn sized_integer_types(&self) -> bool { match self { DataContractConfig::V0(_) => false, - DataContractConfig::V1(v1) => v1.granular_integer_types, + DataContractConfig::V1(v1) => v1.sized_integer_types, } } } impl DataContractConfigSettersV1 for DataContractConfig { - fn set_granular_integer_types_enabled(&mut self, enable: bool) { + fn set_sized_integer_types_enabled(&mut self, enable: bool) { match self { DataContractConfig::V0(_) => {} - DataContractConfig::V1(v1) => v1.granular_integer_types = enable, + DataContractConfig::V1(v1) => v1.sized_integer_types = enable, } } } diff --git a/packages/rs-dpp/src/data_contract/config/v1/mod.rs b/packages/rs-dpp/src/data_contract/config/v1/mod.rs index 209bb07ac98..c3faf947a21 100644 --- a/packages/rs-dpp/src/data_contract/config/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/config/v1/mod.rs @@ -3,7 +3,7 @@ use crate::data_contract::config::v0::{DataContractConfigGettersV0, DataContract use crate::data_contract::config::{ DataContractConfig, DEFAULT_CONTRACT_CAN_BE_DELETED, DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED, DEFAULT_CONTRACT_DOCUMENTS_KEEPS_HISTORY, DEFAULT_CONTRACT_DOCUMENT_MUTABILITY, - DEFAULT_CONTRACT_KEEPS_HISTORY, DEFAULT_CONTRACT_MUTABILITY, DEFAULT_GRANULAR_NUMERIC_TYPES, + DEFAULT_CONTRACT_KEEPS_HISTORY, DEFAULT_CONTRACT_MUTABILITY, DEFAULT_SIZED_INTEGER_TYPES, }; use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; use crate::ProtocolError; @@ -40,20 +40,20 @@ pub struct DataContractConfigV1 { pub requires_identity_encryption_bounded_key: Option, /// Decryption key storage requirements pub requires_identity_decryption_bounded_key: Option, - /// Use granular integer Rust types for `integer` property type - pub granular_integer_types: bool, + /// Use sized integer Rust types for `integer` property type based on validation rules + pub sized_integer_types: bool, } /// Trait representing getters for `DataContractConfigV1` pub trait DataContractConfigGettersV1: DataContractConfigGettersV0 { - /// Use granular integer Rust types for `integer` property type - fn granular_integer_types(&self) -> bool; + /// Use sized integer Rust types for `integer` property type based on validation rules + fn sized_integer_types(&self) -> bool; } /// Trait representing setters for `DataContractConfigV1` pub trait DataContractConfigSettersV1: DataContractConfigSettersV0 { - /// Enable/disable granular integer Rust types for `integer` property type - fn set_granular_integer_types_enabled(&mut self, enable: bool); + /// Enable/disable sized integer Rust types for `integer` property type + fn set_sized_integer_types_enabled(&mut self, enable: bool); } impl Default for DataContractConfigV1 { @@ -67,7 +67,7 @@ impl Default for DataContractConfigV1 { documents_can_be_deleted_contract_default: DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED, requires_identity_encryption_bounded_key: None, requires_identity_decryption_bounded_key: None, - granular_integer_types: true, + sized_integer_types: true, } } } @@ -138,9 +138,9 @@ impl DataContractConfigV1 { .map(|int| int.try_into()) .transpose()?; - let granular_numeric_types = contract - .get_optional_bool(config::property::GRANULAR_NUMERIC_TYPES)? - .unwrap_or(DEFAULT_GRANULAR_NUMERIC_TYPES); + let sized_integer_types = contract + .get_optional_bool(config::property::SIZED_INTEGER_TYPES)? + .unwrap_or(DEFAULT_SIZED_INTEGER_TYPES); Ok(DataContractConfigV1 { can_be_deleted, @@ -151,7 +151,7 @@ impl DataContractConfigV1 { documents_can_be_deleted_contract_default, requires_identity_encryption_bounded_key, requires_identity_decryption_bounded_key, - granular_integer_types: granular_numeric_types, + sized_integer_types, }) } } diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 31c9b0a4cae..22595b09d87 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -2036,7 +2036,7 @@ impl DocumentPropertyType { let property_type = match type_value { "integer" => { - if options.granular_integer_types { + if options.sized_integer_types { find_integer_type_for_subschema_value(value_map)? } else { DocumentPropertyType::I64 @@ -2088,13 +2088,13 @@ impl DocumentPropertyType { #[derive(Debug, Clone)] pub struct DocumentPropertyTypeParsingOptions { - pub granular_integer_types: bool, + pub sized_integer_types: bool, } impl Default for DocumentPropertyTypeParsingOptions { fn default() -> Self { Self { - granular_integer_types: true, + sized_integer_types: true, } } } @@ -2102,7 +2102,7 @@ impl Default for DocumentPropertyTypeParsingOptions { impl From<&DataContractConfig> for DocumentPropertyTypeParsingOptions { fn from(config: &DataContractConfig) -> Self { Self { - granular_integer_types: config.granular_integer_types(), + sized_integer_types: config.sized_integer_types(), } } } diff --git a/packages/rs-drive/tests/query_tests.rs b/packages/rs-drive/tests/query_tests.rs index 80b7e6a2911..c0980cbad1e 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -890,9 +890,7 @@ pub fn setup_withdrawal_tests( None, ); - contract - .config_mut() - .set_granular_integer_types_enabled(false); + contract.config_mut().set_sized_integer_types_enabled(false); add_withdrawals_to_contract( &drive, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs index 1d28eb01dd3..e83ec50b2fe 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs @@ -14,7 +14,7 @@ pub const CONTRACT_VERSIONS_V2: DPPContractVersions = DPPContractVersions { }, contract_structure_version: 1, //changed created_data_contract_structure: 0, - config: 1, // changed to enable granular number types + config: 1, // changed to enable sized integer types methods: DataContractMethodVersions { validate_document: 0, validate_update: 0,