From 78b846c7d084012f7476e47cf014990c918bffb4 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 29 Sep 2025 04:32:06 +0700 Subject: [PATCH 01/13] feat: creator id --- Cargo.lock | 1 + packages/rs-dpp/src/document/fields.rs | 1 + .../deserialize/v0/mod.rs | 10 + .../serialize/v0/mod.rs | 7 + packages/rs-dpp/src/document/v0/serialize.rs | 504 +++++++++++++++++- .../batch/transformer/v0/mod.rs | 2 + .../transformer.rs | 3 + .../v0/transformer.rs | 17 +- .../transformer.rs | 3 + .../v0/transformer.rs | 19 +- .../dpp_versions/dpp_document_versions/mod.rs | 1 + .../dpp_versions/dpp_document_versions/v3.rs | 31 ++ .../rs-platform-version/src/version/v10.rs | 4 +- packages/wasm-sdk/Cargo.toml | 3 + 14 files changed, 596 insertions(+), 10 deletions(-) create mode 100644 packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/v3.rs diff --git a/Cargo.lock b/Cargo.lock index 609dcf222a4..b1a85cc2cd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7143,6 +7143,7 @@ dependencies = [ "tracing-wasm", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-sdk", "web-sys", "wee_alloc", ] diff --git a/packages/rs-dpp/src/document/fields.rs b/packages/rs-dpp/src/document/fields.rs index 452b7f7c3ef..2d88483f034 100644 --- a/packages/rs-dpp/src/document/fields.rs +++ b/packages/rs-dpp/src/document/fields.rs @@ -5,6 +5,7 @@ pub mod property_names { pub const DATA_CONTRACT_ID: &str = "$dataContractId"; pub const REVISION: &str = "$revision"; pub const OWNER_ID: &str = "$ownerId"; + pub const CREATOR_ID: &str = "$creatorId"; pub const PRICE: &str = "$price"; pub const CREATED_AT: &str = "$createdAt"; pub const UPDATED_AT: &str = "$updatedAt"; diff --git a/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/deserialize/v0/mod.rs b/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/deserialize/v0/mod.rs index a6e43769b52..5db85a579fa 100644 --- a/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/deserialize/v0/mod.rs +++ b/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/deserialize/v0/mod.rs @@ -25,6 +25,16 @@ pub(in crate::document) trait DocumentPlatformDeserializationMethodsV0 { ) -> Result where Self: Sized; + + /// Reads a serialized document and creates a Document from it. + /// Version 2 has the creator id. + fn from_bytes_v2( + serialized_document: &[u8], + document_type: DocumentTypeRef, + platform_version: &PlatformVersion, + ) -> Result + where + Self: Sized; } #[cfg(feature = "extended-document")] diff --git a/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/serialize/v0/mod.rs b/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/serialize/v0/mod.rs index 4d6348011b7..5f6238667b8 100644 --- a/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/serialize/v0/mod.rs +++ b/packages/rs-dpp/src/document/serialization_traits/platform_serialization_conversion/serialize/v0/mod.rs @@ -17,6 +17,13 @@ pub(in crate::document) trait DocumentPlatformSerializationMethodsV0 { /// id 32 bytes + owner_id 32 bytes + encoded values byte arrays /// Serialize v1 will encode integers normally with their known size fn serialize_v1(&self, document_type: DocumentTypeRef) -> Result, ProtocolError>; + + /// Serializes the document. + /// + /// The serialization of a document follows the pattern: + /// id 32 bytes + owner_id 32 bytes + encoded values byte arrays + /// Serialize v2 will serialize the creator id if the document can be transferred or sold + fn serialize_v2(&self, document_type: DocumentTypeRef) -> Result, ProtocolError>; } #[cfg(feature = "extended-document")] diff --git a/packages/rs-dpp/src/document/v0/serialize.rs b/packages/rs-dpp/src/document/v0/serialize.rs index ce4792af538..eb225434943 100644 --- a/packages/rs-dpp/src/document/v0/serialize.rs +++ b/packages/rs-dpp/src/document/v0/serialize.rs @@ -2,8 +2,8 @@ use crate::data_contract::document_type::{DocumentPropertyType, DocumentTypeRef} use crate::data_contract::errors::DataContractError; use crate::document::property_names::{ - CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, PRICE, TRANSFERRED_AT, - TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, + CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, CREATOR_ID, PRICE, + TRANSFERRED_AT, TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; @@ -36,6 +36,9 @@ use crate::consensus::basic::BasicError; use crate::consensus::ConsensusError; use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::config::DataContractConfig; +use crate::document::transfer::Transferable; +use crate::nft::TradeMode; +use platform_value::btreemap_extensions::BTreeValueMapHelper; use std::io::{BufReader, Read}; impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { @@ -479,6 +482,234 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { Ok(buffer) } + + /// Serializes the document. + /// + /// The serialization of a document follows the pattern: + /// id 32 bytes + owner_id 32 bytes + encoded values byte arrays + /// Serialize v2 will encode the creator id as well. + fn serialize_v2(&self, document_type: DocumentTypeRef) -> Result, ProtocolError> { + let mut buffer: Vec = 2u64.encode_var_vec(); //version 1 + + // $id + buffer.extend(self.id.as_slice()); + + // $ownerId + buffer.extend(self.owner_id.as_slice()); + + if document_type.trade_mode() != TradeMode::None + || document_type.documents_transferable() != Transferable::Never + { + if let Some(creator_id) = self.properties.get_optional_identifier(CREATOR_ID)? { + buffer.push(1); + buffer.extend(creator_id.as_slice()); + } else { + buffer.push(0); + } + } + + // $revision + if let Some(revision) = self.revision { + buffer.extend(revision.encode_var_vec()) + } else if document_type.requires_revision() { + buffer.extend((1 as Revision).encode_var_vec()) + } + + let mut bitwise_exists_flag: u16 = 0; + + let mut time_fields_data_buffer = vec![]; + + // $createdAt + if let Some(created_at) = &self.created_at { + bitwise_exists_flag |= 1; + // dbg!("we pushed created at {}", hex::encode(created_at.to_be_bytes())); + time_fields_data_buffer.extend(created_at.to_be_bytes()); + } else if document_type.required_fields().contains(CREATED_AT) { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "created at field is not present".to_string(), + ), + )); + } + + // $updatedAt + if let Some(updated_at) = &self.updated_at { + bitwise_exists_flag |= 2; + // dbg!("we pushed updated at {}", hex::encode(updated_at.to_be_bytes())); + time_fields_data_buffer.extend(updated_at.to_be_bytes()); + } else if document_type.required_fields().contains(UPDATED_AT) { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "updated at field is not present".to_string(), + ), + )); + } + + // $transferredAt + if let Some(transferred_at) = &self.transferred_at { + bitwise_exists_flag |= 4; + // dbg!("we pushed transferred at {}", hex::encode(transferred_at.to_be_bytes())); + time_fields_data_buffer.extend(transferred_at.to_be_bytes()); + } else if document_type.required_fields().contains(TRANSFERRED_AT) { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "transferred at field is not present".to_string(), + ), + )); + } + + // $createdAtBlockHeight + if let Some(created_at_block_height) = &self.created_at_block_height { + bitwise_exists_flag |= 8; + time_fields_data_buffer.extend(created_at_block_height.to_be_bytes()); + } else if document_type + .required_fields() + .contains(CREATED_AT_BLOCK_HEIGHT) + { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "created_at_block_height field is not present".to_string(), + ), + )); + } + + // $updatedAtBlockHeight + if let Some(updated_at_block_height) = &self.updated_at_block_height { + bitwise_exists_flag |= 16; + time_fields_data_buffer.extend(updated_at_block_height.to_be_bytes()); + } else if document_type + .required_fields() + .contains(UPDATED_AT_BLOCK_HEIGHT) + { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "updated_at_block_height field is not present".to_string(), + ), + )); + } + + // $transferredAtBlockHeight + if let Some(transferred_at_block_height) = &self.transferred_at_block_height { + bitwise_exists_flag |= 32; + time_fields_data_buffer.extend(transferred_at_block_height.to_be_bytes()); + } else if document_type + .required_fields() + .contains(TRANSFERRED_AT_BLOCK_HEIGHT) + { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "transferred_at_block_height field is not present".to_string(), + ), + )); + } + + // $createdAtCoreBlockHeight + if let Some(created_at_core_block_height) = &self.created_at_core_block_height { + bitwise_exists_flag |= 64; + time_fields_data_buffer.extend(created_at_core_block_height.to_be_bytes()); + } else if document_type + .required_fields() + .contains(CREATED_AT_CORE_BLOCK_HEIGHT) + { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "created_at_core_block_height field is not present".to_string(), + ), + )); + } + + // $updatedAtCoreBlockHeight + if let Some(updated_at_core_block_height) = &self.updated_at_core_block_height { + bitwise_exists_flag |= 128; + time_fields_data_buffer.extend(updated_at_core_block_height.to_be_bytes()); + } else if document_type + .required_fields() + .contains(UPDATED_AT_CORE_BLOCK_HEIGHT) + { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "updated_at_core_block_height field is not present".to_string(), + ), + )); + } + + // $transferredAtCoreBlockHeight + if let Some(transferred_at_core_block_height) = &self.transferred_at_core_block_height { + bitwise_exists_flag |= 256; + time_fields_data_buffer.extend(transferred_at_core_block_height.to_be_bytes()); + } else if document_type + .required_fields() + .contains(TRANSFERRED_AT_CORE_BLOCK_HEIGHT) + { + return Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "transferred_at_core_block_height field is not present".to_string(), + ), + )); + } + + buffer.extend(bitwise_exists_flag.to_be_bytes().as_slice()); + buffer.append(&mut time_fields_data_buffer); + + // Now we serialize the price which might not be necessary unless called for by the document type + + if document_type.trade_mode().seller_sets_price() { + if let Some(price) = self.properties.get(PRICE) { + buffer.push(1); + let price_as_u64: u64 = price.to_integer().map_err(ProtocolError::ValueError)?; + buffer.append(&mut price_as_u64.to_be_bytes().to_vec()); + } else { + buffer.push(0); + } + } + + // User defined properties + document_type + .properties() + .iter() + .try_for_each(|(field_name, property)| { + if let Some(value) = self.properties.get(field_name) { + if value.is_null() { + if property.required && !property.transient { + Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey( + "a required field is not present".to_string(), + ), + )) + } else { + // dbg!("we pushed {} with 0", field_name); + // We don't have something that wasn't required + buffer.push(0); + Ok(()) + } + } else { + if !property.required || property.transient { + // dbg!("we added 1", field_name); + buffer.push(1); + } + let value = property + .property_type + .encode_value_ref_with_size(value, property.required)?; + // dbg!("we pushed {} with {}", field_name, hex::encode(&value)); + buffer.extend(value.as_slice()); + Ok(()) + } + } else if property.required && !property.transient { + Err(ProtocolError::DataContractError( + DataContractError::MissingRequiredKey(format!( + "a required field {field_name} is not present" + )), + )) + } else { + // dbg!("we pushed {} with 0", field_name); + // We don't have something that wasn't required + buffer.push(0); + Ok(()) + } + })?; + + Ok(buffer) + } } impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { @@ -920,6 +1151,251 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { transferred_at_core_block_height, }) } + + /// Reads a serialized document and creates a Document from it. + fn from_bytes_v2( + serialized_document: &[u8], + document_type: DocumentTypeRef, + _platform_version: &PlatformVersion, + ) -> Result { + let mut buf = BufReader::new(serialized_document); + if serialized_document.len() < 64 { + return Err(DataContractError::DecodingDocumentError( + DecodingError::new( + "serialized document is too small, must have id and owner id".to_string(), + ), + )); + } + + // $id + let mut id = [0; 32]; + buf.read_exact(&mut id).map_err(|_| { + DataContractError::DecodingDocumentError(DecodingError::new( + "error reading from serialized document for id".to_string(), + )) + })?; + + // $ownerId + let mut owner_id = [0; 32]; + buf.read_exact(&mut owner_id).map_err(|_| { + DataContractError::DecodingDocumentError(DecodingError::new( + "error reading from serialized document for owner id".to_string(), + )) + })?; + + // $creatorId + let creator_id: Option = if document_type.trade_mode() != TradeMode::None + || document_type.documents_transferable() != Transferable::Never + { + let has_creator_id = buf.read_u8().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading has creator id bool from serialized document".to_string(), + ) + })?; + if has_creator_id > 0 { + // $creatorId + let mut known_owner_id = [0; 32]; + buf.read_exact(&mut known_owner_id).map_err(|_| { + DataContractError::DecodingDocumentError(DecodingError::new( + "error reading from serialized document for creator id".to_string(), + )) + })?; + Some(known_owner_id.into()) + } else { + None + } + } else { + None + }; + + // $revision + // if the document type is mutable then we should deserialize the revision + let revision: Option = if document_type.requires_revision() { + let revision = buf.read_varint().map_err(|_| { + DataContractError::DecodingDocumentError(DecodingError::new( + "error reading revision from serialized document for revision".to_string(), + )) + })?; + Some(revision) + } else { + None + }; + + let timestamp_flags = buf.read_u16::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading timestamp flags from serialized document".to_string(), + ) + })?; + + let created_at = if timestamp_flags & 1 > 0 { + Some(buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading created_at timestamp from serialized document".to_string(), + ) + })?) + } else { + None + }; + + let updated_at = if timestamp_flags & 2 > 0 { + Some(buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading updated_at timestamp from serialized document".to_string(), + ) + })?) + } else { + None + }; + + let transferred_at = if timestamp_flags & 4 > 0 { + Some(buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading transferred_at timestamp from serialized document".to_string(), + ) + })?) + } else { + None + }; + + let created_at_block_height = if timestamp_flags & 8 > 0 { + Some(buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading created_at_block_height from serialized document".to_string(), + ) + })?) + } else { + None + }; + + let updated_at_block_height = if timestamp_flags & 16 > 0 { + Some(buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading updated_at_block_height from serialized document".to_string(), + ) + })?) + } else { + None + }; + + let transferred_at_block_height = if timestamp_flags & 32 > 0 { + Some(buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading transferred_at_block_height from serialized document" + .to_string(), + ) + })?) + } else { + None + }; + + let created_at_core_block_height = if timestamp_flags & 64 > 0 { + Some(buf.read_u32::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading created_at_core_block_height from serialized document" + .to_string(), + ) + })?) + } else { + None + }; + + let updated_at_core_block_height = if timestamp_flags & 128 > 0 { + Some(buf.read_u32::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading updated_at_core_block_height from serialized document" + .to_string(), + ) + })?) + } else { + None + }; + + let transferred_at_core_block_height = if timestamp_flags & 256 > 0 { + Some(buf.read_u32::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading updated_at_core_block_height from serialized document" + .to_string(), + ) + })?) + } else { + None + }; + + // Now we deserialize the price which might not be necessary unless called for by the document type + + let price = if document_type.trade_mode().seller_sets_price() { + let has_price = buf.read_u8().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading has price bool from serialized document".to_string(), + ) + })?; + if has_price > 0 { + let price = buf.read_u64::().map_err(|_| { + DataContractError::CorruptedSerialization( + "error reading price u64 from serialized document".to_string(), + ) + })?; + Some(price) + } else { + None + } + } else { + None + }; + + let mut finished_buffer = false; + + let mut properties = document_type + .properties() + .iter() + .filter_map(|(key, property)| { + if finished_buffer { + return if property.required && !property.transient { + Some(Err(DataContractError::CorruptedSerialization( + "required field after finished buffer".to_string(), + ))) + } else { + None + }; + } + let read_value = property + .property_type + .read_optionally_from(&mut buf, property.required & !property.transient); + + match read_value { + Ok(read_value) => { + finished_buffer |= read_value.1; + read_value.0.map(|read_value| Ok((key.clone(), read_value))) + } + Err(e) => Some(Err(e)), + } + }) + .collect::, DataContractError>>()?; + + if let Some(price) = price { + properties.insert(PRICE.to_string(), price.into()); + } + + if let Some(creator_id) = creator_id { + properties.insert(CREATOR_ID.to_string(), creator_id.into()); + } + + Ok(DocumentV0 { + id: Identifier::new(id), + properties, + owner_id: Identifier::new(owner_id), + revision, + created_at, + updated_at, + transferred_at, + created_at_block_height, + updated_at_block_height, + transferred_at_block_height, + created_at_core_block_height, + updated_at_core_block_height, + transferred_at_core_block_height, + }) + } } impl DocumentPlatformConversionMethodsV0 for DocumentV0 { @@ -954,9 +1430,10 @@ impl DocumentPlatformConversionMethodsV0 for DocumentV0 { // and most importantly different integer types. // Document types now have properties that are known to be things like u8, i32 etc. 1 => self.serialize_v1(document_type), + 2 => self.serialize_v2(document_type), version => Err(ProtocolError::UnknownVersionMismatch { method: "DocumentV0::serialize".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } @@ -983,9 +1460,10 @@ impl DocumentPlatformConversionMethodsV0 for DocumentV0 { match feature_version { 0 => self.serialize_v0(document_type), 1 => self.serialize_v1(document_type), + 2 => self.serialize_v2(document_type), version => Err(ProtocolError::UnknownVersionMismatch { method: "DocumentV0::serialize".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } @@ -1033,9 +1511,11 @@ impl DocumentPlatformConversionMethodsV0 for DocumentV0 { } 1 => DocumentV0::from_bytes_v1(serialized_document, document_type, platform_version) .map_err(ProtocolError::DataContractError), + 2 => DocumentV0::from_bytes_v2(serialized_document, document_type, platform_version) + .map_err(ProtocolError::DataContractError), version => Err(ProtocolError::UnknownVersionMismatch { method: "Document::from_bytes (deserialization)".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } @@ -1096,9 +1576,21 @@ impl DocumentPlatformConversionMethodsV0 for DocumentV0 { )), } } + 2 => { + match DocumentV0::from_bytes_v2( + serialized_document, + document_type, + platform_version, + ) { + Ok(document) => Ok(ConsensusValidationResult::new_with_data(document)), + Err(err) => Ok(ConsensusValidationResult::new_with_error( + ConsensusError::BasicError(BasicError::ContractError(err)), + )), + } + } version => Err(ProtocolError::UnknownVersionMismatch { method: "Document::from_bytes (deserialization)".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs index b76b38ab53d..03f13858a31 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs @@ -815,6 +815,7 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { block_info, user_fee_increase, |_identifier| Ok(data_contract_fetch_info.clone()), + platform_version, )?; execution_context @@ -944,6 +945,7 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { block_info, user_fee_increase, |_identifier| Ok(data_contract_fetch_info.clone()), + platform_version, )?; execution_context diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs index d6082362fc6..acd9a6fdbd5 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs @@ -6,6 +6,7 @@ use dpp::fee::fee_result::FeeResult; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::DocumentPurchaseTransition; +use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -21,6 +22,7 @@ impl DocumentPurchaseTransitionAction { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, + platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -38,6 +40,7 @@ impl DocumentPurchaseTransitionAction { block_info, user_fee_increase, get_data_contract, + platform_version, ) } } diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs index c0a706aa30c..ce259909d9f 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs @@ -1,13 +1,15 @@ use dpp::block::block_info::BlockInfo; -use dpp::document::property_names::PRICE; +use dpp::document::property_names::{CREATOR_ID, PRICE}; use dpp::document::{property_names, Document, DocumentV0Getters, DocumentV0Setters}; use dpp::platform_value::Identifier; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; use dpp::fee::fee_result::FeeResult; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::DocumentPurchaseTransitionV0; +use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -26,6 +28,7 @@ impl DocumentPurchaseTransitionActionV0 { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, + platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -67,6 +70,18 @@ impl DocumentPurchaseTransitionActionV0 { let mut modified_document = original_document; + // If we don't have a creator id that means we never had sold it before + if modified_document + .properties() + .get_optional_identifier(CREATOR_ID)? + .is_none() + && platform_version.protocol_version >= 10 + { + modified_document + .properties_mut() + .insert(CREATOR_ID.to_string(), original_owner_id.into()); + } + modified_document.bump_revision(); // We must remove the price if there is one diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs index a5035f8951e..aecbf44e9d5 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs @@ -6,6 +6,7 @@ use dpp::fee::fee_result::FeeResult; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::DocumentTransferTransition; +use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -20,6 +21,7 @@ impl DocumentTransferTransitionAction { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, + platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -36,6 +38,7 @@ impl DocumentTransferTransitionAction { block_info, user_fee_increase, get_data_contract, + platform_version, ) } } diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs index 17264725946..e1f4929626b 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs @@ -1,13 +1,15 @@ use dpp::block::block_info::BlockInfo; -use dpp::document::property_names::PRICE; +use dpp::document::property_names::{CREATOR_ID, PRICE}; use dpp::document::{property_names, Document, DocumentV0Getters, DocumentV0Setters}; use dpp::platform_value::Identifier; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; use dpp::fee::fee_result::FeeResult; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::DocumentTransferTransitionV0; +use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -25,6 +27,7 @@ impl DocumentTransferTransitionActionV0 { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, + platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -67,10 +70,24 @@ impl DocumentTransferTransitionActionV0 { } }; + let original_owner_id = original_document.owner_id(); + let mut modified_document = original_document; modified_document.set_owner_id(*recipient_owner_id); + // If we don't have a creator id that means we never had sold it before + if modified_document + .properties() + .get_optional_identifier(CREATOR_ID)? + .is_none() + && platform_version.protocol_version >= 10 + { + modified_document + .properties_mut() + .insert(CREATOR_ID.to_string(), original_owner_id.into()); + } + // We must remove the price modified_document.properties_mut().remove(PRICE); diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/mod.rs index 9ab667770f2..22148d7eeaf 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/mod.rs @@ -2,6 +2,7 @@ use versioned_feature_core::{FeatureVersion, FeatureVersionBounds}; pub mod v1; pub mod v2; +pub mod v3; #[derive(Clone, Debug, Default)] pub struct DPPDocumentVersions { diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/v3.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/v3.rs new file mode 100644 index 00000000000..67837019e8b --- /dev/null +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_document_versions/v3.rs @@ -0,0 +1,31 @@ +use crate::version::dpp_versions::dpp_document_versions::{ + DPPDocumentVersions, DocumentMethodVersions, +}; +use versioned_feature_core::FeatureVersionBounds; + +pub const DOCUMENT_VERSIONS_V3: DPPDocumentVersions = DPPDocumentVersions { + document_structure_version: 0, + document_serialization_version: FeatureVersionBounds { + min_version: 0, + max_version: 2, + default_current_version: 2, + }, + document_cbor_serialization_version: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + extended_document_structure_version: 0, + extended_document_serialization_version: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + document_method_versions: DocumentMethodVersions { + is_equal_ignoring_timestamps: 0, + hash: 0, + get_raw_for_contract: 0, + get_raw_for_document_type: 0, + try_into_asset_unlock_base_transaction_info: 0, + }, +}; diff --git a/packages/rs-platform-version/src/version/v10.rs b/packages/rs-platform-version/src/version/v10.rs index e1a27df4f5f..3b15c2f59da 100644 --- a/packages/rs-platform-version/src/version/v10.rs +++ b/packages/rs-platform-version/src/version/v10.rs @@ -2,7 +2,7 @@ use crate::version::consensus_versions::ConsensusVersions; use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1; use crate::version::dpp_versions::dpp_contract_versions::v2::CONTRACT_VERSIONS_V2; use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1; -use crate::version::dpp_versions::dpp_document_versions::v2::DOCUMENT_VERSIONS_V2; +use crate::version::dpp_versions::dpp_document_versions::v3::DOCUMENT_VERSIONS_V3; use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1; use crate::version::dpp_versions::dpp_identity_versions::v1::IDENTITY_VERSIONS_V1; use crate::version::dpp_versions::dpp_method_versions::v2::DPP_METHOD_VERSIONS_V2; @@ -48,7 +48,7 @@ pub const PLATFORM_V10: PlatformVersion = PlatformVersion { state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1, state_transitions: STATE_TRANSITION_VERSIONS_V2, contract_versions: CONTRACT_VERSIONS_V2, - document_versions: DOCUMENT_VERSIONS_V2, + document_versions: DOCUMENT_VERSIONS_V3, // changed to support serialization of creator id identity_versions: IDENTITY_VERSIONS_V1, voting_versions: VOTING_VERSION_V2, token_versions: TOKEN_VERSIONS_V1, diff --git a/packages/wasm-sdk/Cargo.toml b/packages/wasm-sdk/Cargo.toml index fdb45063964..e9d841705b4 100644 --- a/packages/wasm-sdk/Cargo.toml +++ b/packages/wasm-sdk/Cargo.toml @@ -66,6 +66,9 @@ rs-dapi-client = { path = "../rs-dapi-client" } hmac = { version = "0.12" } sha2 = { version = "0.10" } +[dev-dependencies] +wasm-sdk = { path = ".", features = ["mocks", "all-system-contracts"] } + [profile.release] opt-level = "z" panic = "abort" From 5134a401e248dc4c73dbad970dbee875e000d718 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 00:50:54 +0700 Subject: [PATCH 02/13] feat: creatorId --- .../rs-dpp/src/data_contract/accessors/mod.rs | 7 + .../src/data_contract/accessors/v0/mod.rs | 1 + .../rs-dpp/src/data_contract/config/mod.rs | 7 + .../mod.rs | 7 + .../v0/mod.rs | 6 + .../v1/mod.rs | 4 + .../document_type/class_methods/mod.rs | 2 + .../should_use_creator_id/mod.rs | 90 +++ .../class_methods/system_properties/mod.rs | 57 ++ .../class_methods/try_from_schema/mod.rs | 20 +- .../class_methods/try_from_schema/v0/mod.rs | 23 +- .../class_methods/try_from_schema/v1/mod.rs | 23 +- .../methods/validate_update/v0/mod.rs | 44 ++ .../methods/versioned_methods.rs | 4 +- .../methods/validate_document/v0/mod.rs | 29 +- .../src/data_contract/v0/accessors/mod.rs | 4 + .../src/data_contract/v0/methods/schema.rs | 5 + .../src/data_contract/v0/serialization/mod.rs | 4 + .../src/data_contract/v1/accessors/mod.rs | 4 + .../src/data_contract/v1/methods/schema.rs | 5 + .../src/data_contract/v1/serialization/mod.rs | 4 + .../src/document/document_factory/v0/mod.rs | 30 - .../src/document/extended_document/v0/mod.rs | 3 + .../specialized_document_factory/v0/mod.rs | 30 - packages/rs-dpp/src/document/v0/serialize.rs | 7 +- .../document_create_transition/mod.rs | 7 + .../document_create_transition/v0/mod.rs | 41 +- packages/rs-dpp/src/util/json_schema.rs | 33 +- .../advanced_structure_v0/mod.rs | 5 +- .../advanced_structure_v0/mod.rs | 5 +- .../batch/tests/document/transfer.rs | 522 +++++++++++++++++- .../batch/transformer/v0/mod.rs | 9 +- .../data_contract_create/mod.rs | 145 +++++ ...ame-all-transferable-format-version-0.json | 133 +++++ .../crypto-card-game-all-transferable.json | 10 +- packages/rs-drive/src/query/conditions.rs | 4 +- packages/rs-drive/src/query/test_index.rs | 2 + .../v0/mod.rs | 18 +- .../transformer.rs | 3 - .../v0/transformer.rs | 17 +- .../transformer.rs | 2 + .../v0/transformer.rs | 9 +- .../transformer.rs | 3 - .../v0/transformer.rs | 19 +- .../util/object_size_info/document_info.rs | 2 +- .../v0/mod.rs | 1 + packages/rs-drive/tests/query_tests.rs | 12 +- .../dpp_versions/dpp_contract_versions/mod.rs | 2 + .../dpp_versions/dpp_contract_versions/v1.rs | 1 + .../dpp_versions/dpp_contract_versions/v2.rs | 1 + .../dpp_versions/dpp_contract_versions/v3.rs | 66 +++ .../rs-platform-version/src/version/v10.rs | 4 +- packages/rs-sdk/tests/fetch/common.rs | 2 + packages/strategy-tests/src/operations.rs | 2 + 54 files changed, 1292 insertions(+), 208 deletions(-) create mode 100644 packages/rs-dpp/src/data_contract/document_type/class_methods/should_use_creator_id/mod.rs create mode 100644 packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-format-version-0.json create mode 100644 packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v3.rs diff --git a/packages/rs-dpp/src/data_contract/accessors/mod.rs b/packages/rs-dpp/src/data_contract/accessors/mod.rs index d58879843b4..3c1bf129981 100644 --- a/packages/rs-dpp/src/data_contract/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/accessors/mod.rs @@ -38,6 +38,13 @@ impl DataContractV0Getters for DataContract { } } + fn system_version_type(&self) -> u16 { + match self { + DataContract::V0(_) => 0, + DataContract::V1(_) => 1, + } + } + fn version(&self) -> u32 { match self { DataContract::V0(v0) => v0.version(), diff --git a/packages/rs-dpp/src/data_contract/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/accessors/v0/mod.rs index d106fa530f8..a521fc7f6c1 100644 --- a/packages/rs-dpp/src/data_contract/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/accessors/v0/mod.rs @@ -11,6 +11,7 @@ pub trait DataContractV0Getters { fn id(&self) -> Identifier; fn id_ref(&self) -> &Identifier; + fn system_version_type(&self) -> u16; /// Returns the version of this data contract. fn version(&self) -> u32; diff --git a/packages/rs-dpp/src/data_contract/config/mod.rs b/packages/rs-dpp/src/data_contract/config/mod.rs index b5d20f671af..303e59456f3 100644 --- a/packages/rs-dpp/src/data_contract/config/mod.rs +++ b/packages/rs-dpp/src/data_contract/config/mod.rs @@ -27,6 +27,13 @@ pub enum DataContractConfig { } impl DataContractConfig { + pub fn version(&self) -> u16 { + match self { + DataContractConfig::V0(_) => 0, + DataContractConfig::V1(_) => 1, + } + } + pub fn default_for_version( platform_version: &PlatformVersion, ) -> Result { 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 d5f466e5092..2ccd4283987 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 @@ -24,6 +24,7 @@ impl DocumentType { /// # Parameters /// /// * `data_contract_id`: Identifier for the data contract. + /// * `data_contract_system_version`: The version of the data contract /// * `contract_document_types_raw`: Vector representing the raw contract document types. /// * `definition_references`: BTreeMap representing the definition references. /// * `documents_keep_history_contract_default`: A boolean flag that specifies the document's keep history contract default. @@ -37,6 +38,8 @@ impl DocumentType { #[allow(clippy::too_many_arguments)] pub fn create_document_types_from_document_schemas( data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, document_schemas: BTreeMap, schema_defs: Option<&BTreeMap>, token_configurations: &BTreeMap, @@ -55,6 +58,8 @@ impl DocumentType { { 0 => DocumentType::create_document_types_from_document_schemas_v0( data_contract_id, + data_contract_system_version, + contract_config_version, document_schemas, schema_defs, token_configurations, @@ -66,6 +71,8 @@ impl DocumentType { // in v1 we add the ability to have contracts without documents and just tokens 1 => DocumentType::create_document_types_from_document_schemas_v1( data_contract_id, + data_contract_system_version, + contract_config_version, document_schemas, schema_defs, token_configurations, 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 9a187a750d7..a9fcc1f2189 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 @@ -13,6 +13,8 @@ impl DocumentType { #[allow(clippy::too_many_arguments)] pub(in crate::data_contract) fn create_document_types_from_document_schemas_v0( data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, document_schemas: BTreeMap, schema_defs: Option<&BTreeMap>, token_configurations: &BTreeMap, @@ -38,6 +40,8 @@ impl DocumentType { { 0 => DocumentType::try_from_schema( data_contract_id, + data_contract_system_version, + contract_config_version, &name, schema, schema_defs, @@ -84,6 +88,8 @@ mod tests { let result = DocumentType::create_document_types_from_document_schemas( id, + 1, + config.version(), Default::default(), None, &BTreeMap::new(), 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 1aa1755649b..6529bd47f95 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 @@ -13,6 +13,8 @@ impl DocumentType { #[allow(clippy::too_many_arguments)] pub(in crate::data_contract) fn create_document_types_from_document_schemas_v1( data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, document_schemas: BTreeMap, schema_defs: Option<&BTreeMap>, token_configurations: &BTreeMap, @@ -39,6 +41,8 @@ impl DocumentType { { 0 => DocumentType::try_from_schema( data_contract_id, + data_contract_system_version, + contract_config_version, &name, schema, schema_defs, diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/mod.rs index bfad96e4a76..48b129985a4 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/mod.rs @@ -6,6 +6,8 @@ use crate::data_contract::errors::DataContractError; use crate::ProtocolError; mod create_document_types_from_document_schemas; +mod should_use_creator_id; +mod system_properties; mod try_from_schema; #[inline] diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/should_use_creator_id/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/should_use_creator_id/mod.rs new file mode 100644 index 00000000000..f881d019a71 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/should_use_creator_id/mod.rs @@ -0,0 +1,90 @@ +use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; +use crate::data_contract::document_type::{DocumentType, DocumentTypeMutRef, DocumentTypeRef}; +use crate::document::transfer::Transferable; +use crate::nft::TradeMode; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl DocumentType { + /// A convenience method on if we should add the creator id + /// Contracts on version 0 never use creator ids + pub fn should_use_creator_id( + &self, + contract_version_type: u16, + contract_config_version_type: u16, + platform_version: &PlatformVersion, + ) -> Result { + should_use_creator_id_class_method( + contract_version_type, + contract_config_version_type, + self.documents_transferable(), + self.trade_mode(), + platform_version, + ) + } +} + +impl DocumentTypeRef<'_> { + /// A convenience method on if we should add the creator id + /// Contracts on version 0 never use creator ids + pub fn should_use_creator_id( + &self, + contract_version_type: u16, + contract_config_version_type: u16, + platform_version: &PlatformVersion, + ) -> Result { + should_use_creator_id_class_method( + contract_version_type, + contract_config_version_type, + self.documents_transferable(), + self.trade_mode(), + platform_version, + ) + } +} + +impl DocumentTypeMutRef<'_> { + /// A convenience method on if we should add the creator id + /// Contracts on version 0 never use creator ids + pub fn should_use_creator_id( + &self, + contract_version_type: u16, + contract_config_version_type: u16, + platform_version: &PlatformVersion, + ) -> Result { + should_use_creator_id_class_method( + contract_version_type, + contract_config_version_type, + self.documents_transferable(), + self.trade_mode(), + platform_version, + ) + } +} + +/// A convenience method on if we should add the creator id +fn should_use_creator_id_class_method( + contract_version_type: u16, + contract_config_version_type: u16, + transferable: Transferable, + trade_mode: TradeMode, + platform_version: &PlatformVersion, +) -> Result { + match platform_version + .dpp + .contract_versions + .document_type_versions + .schema + .should_add_creator_id + { + 0 => Ok(false), + 1 => Ok(contract_version_type > 0 + && contract_config_version_type > 0 + && (transferable.is_transferable() || trade_mode != TradeMode::None)), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "DocumentType::should_use_creator_id".to_string(), + known_versions: vec![0, 1], + received: version, + }), + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs new file mode 100644 index 00000000000..caa2bb1f892 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs @@ -0,0 +1,57 @@ +use crate::data_contract::document_type::DocumentType; +use crate::document::property_names::CREATOR_ID; +use crate::document::transfer::Transferable; +use crate::nft::TradeMode; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +const SYSTEM_PROPERTIES: [&str; 11] = [ + "$id", + "$ownerId", + "$createdAt", + "$updatedAt", + "$transferredAt", + "$createdAtBlockHeight", + "$updatedAtBlockHeight", + "$transferredAtBlockHeight", + "$createdAtCoreBlockHeight", + "$updatedAtCoreBlockHeight", + "$transferredAtCoreBlockHeight", +]; + +impl DocumentType { + pub fn system_properties_contains( + contract_system_version: u16, + contract_config_version: u16, + transferable: Transferable, + trade_mode: TradeMode, + index_name: &str, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .contract_versions + .document_type_versions + .schema + .should_add_creator_id + { + 0 => Ok(SYSTEM_PROPERTIES.contains(&index_name)), + 1 => { + if index_name == CREATOR_ID + && contract_system_version > 0 + && contract_config_version > 0 + && (transferable.is_transferable() || trade_mode != TradeMode::None) + { + Ok(true) + } else { + Ok(SYSTEM_PROPERTIES.contains(&index_name)) + } + } + version => Err(ProtocolError::UnknownVersionMismatch { + method: "DocumentType::system_properties".to_string(), + known_versions: vec![0, 1], + received: 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 33e63576f9b..37de63fe927 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 @@ -19,19 +19,7 @@ mod v0; mod v1; 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; @@ -40,6 +28,8 @@ impl DocumentType { #[allow(clippy::too_many_arguments)] pub fn try_from_schema( data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, name: &str, schema: Value, schema_defs: Option<&BTreeMap>, @@ -58,6 +48,8 @@ impl DocumentType { { 0 => DocumentTypeV0::try_from_schema( data_contract_id, + data_contract_system_version, + contract_config_version, name, schema, schema_defs, @@ -69,6 +61,8 @@ impl DocumentType { .map(|document_type| document_type.into()), 1 => DocumentTypeV1::try_from_schema( data_contract_id, + data_contract_system_version, + contract_config_version, name, schema, schema_defs, 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 5d3dbfeba04..ab0328705d1 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 @@ -37,7 +37,7 @@ use crate::data_contract::config::DataContractConfig; #[cfg(feature = "validation")] use crate::data_contract::document_type::class_methods::try_from_schema::{ MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH, MAX_INDEXED_STRING_PROPERTY_LENGTH, - NOT_ALLOWED_SYSTEM_PROPERTIES, SYSTEM_PROPERTIES, + NOT_ALLOWED_SYSTEM_PROPERTIES, }; use crate::data_contract::document_type::class_methods::{ consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, try_from_schema, @@ -63,6 +63,8 @@ impl DocumentTypeV0 { #[allow(clippy::too_many_arguments)] pub(super) fn try_from_schema( data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, name: &str, schema: Value, schema_defs: Option<&BTreeMap>, @@ -424,7 +426,14 @@ impl DocumentTypeV0 { } // Indexed property must be defined in user schema if it's not a system one - if !SYSTEM_PROPERTIES.contains(&index_property.name.as_str()) { + if !DocumentType::system_properties_contains( + data_contract_system_version, + contract_config_version, + documents_transferable, + trade_mode, + &index_property.name.as_str(), + platform_version, + )? { let property_definition = flattened_document_properties .get(&index_property.name) .ok_or_else(|| { @@ -596,6 +605,8 @@ mod tests { let _result = DocumentTypeV0::try_from_schema( Identifier::new([1; 32]), + 0, + config.version(), "valid_name-a-b-123", schema, None, @@ -627,6 +638,8 @@ mod tests { let result = DocumentTypeV0::try_from_schema( Identifier::new([1; 32]), + 0, + config.version(), "", schema, None, @@ -669,6 +682,8 @@ mod tests { let result = DocumentTypeV0::try_from_schema( Identifier::new([1; 32]), + 0, + config.version(), &"a".repeat(65), schema, None, @@ -711,6 +726,8 @@ mod tests { let result = DocumentTypeV0::try_from_schema( Identifier::new([1; 32]), + 0, + config.version(), "invalid name", schema.clone(), None, @@ -737,6 +754,8 @@ mod tests { let result = DocumentTypeV0::try_from_schema( Identifier::new([1; 32]), + 0, + config.version(), "invalid&name", schema, None, 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 index af1cc654238..3a23b1f87c9 100644 --- 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 @@ -46,7 +46,7 @@ use crate::data_contract::document_type::class_methods::try_from_schema::{ #[cfg(feature = "validation")] use crate::data_contract::document_type::class_methods::try_from_schema::{ MAX_INDEXED_BYTE_ARRAY_PROPERTY_LENGTH, MAX_INDEXED_STRING_PROPERTY_LENGTH, - NOT_ALLOWED_SYSTEM_PROPERTIES, SYSTEM_PROPERTIES, + NOT_ALLOWED_SYSTEM_PROPERTIES, }; use crate::data_contract::document_type::class_methods::{ consensus_or_protocol_data_contract_error, consensus_or_protocol_value_error, @@ -79,6 +79,8 @@ impl DocumentTypeV1 { #[allow(clippy::too_many_arguments)] pub(super) fn try_from_schema( data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, name: &str, schema: Value, schema_defs: Option<&BTreeMap>, @@ -441,7 +443,14 @@ impl DocumentTypeV1 { } // Indexed property must be defined in user schema if it's not a system one - if !SYSTEM_PROPERTIES.contains(&index_property.name.as_str()) { + if !DocumentType::system_properties_contains( + data_contract_system_version, + contract_config_version, + documents_transferable, + trade_mode, + &index_property.name.as_str(), + platform_version, + )? { let property_definition = flattened_document_properties .get(&index_property.name) .ok_or_else(|| { @@ -709,6 +718,8 @@ mod tests { let _result = DocumentTypeV1::try_from_schema( Identifier::new([1; 32]), + 1, + config.version(), "valid_name-a-b-123", schema, None, @@ -741,6 +752,8 @@ mod tests { let result = DocumentTypeV1::try_from_schema( Identifier::new([1; 32]), + 1, + config.version(), "", schema, None, @@ -784,6 +797,8 @@ mod tests { let result = DocumentTypeV1::try_from_schema( Identifier::new([1; 32]), + 1, + config.version(), &"a".repeat(65), schema, None, @@ -827,6 +842,8 @@ mod tests { let result = DocumentTypeV0::try_from_schema( Identifier::new([1; 32]), + 1, + config.version(), "invalid name", schema.clone(), None, @@ -853,6 +870,8 @@ mod tests { let result = DocumentTypeV1::try_from_schema( Identifier::new([1; 32]), + 1, + config.version(), "invalid&name", schema, None, 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 e6cc41614c4..ce0acdf7b44 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 @@ -280,6 +280,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -308,6 +310,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -354,6 +358,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -382,6 +388,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -428,6 +436,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -456,6 +466,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -502,6 +514,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -530,6 +544,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -576,6 +592,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -604,6 +622,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -650,6 +670,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -678,6 +700,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -724,6 +748,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -749,6 +775,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -795,6 +823,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -820,6 +850,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -866,6 +898,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -891,6 +925,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -943,6 +979,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema.clone(), None, @@ -959,6 +997,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, @@ -1001,6 +1041,8 @@ mod tests { let old_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema.clone(), None, @@ -1026,6 +1068,8 @@ mod tests { let new_document_type = DocumentType::try_from_schema( data_contract_id, + 1, + config.version(), document_type_name, schema, None, diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs index a9a84c2e317..d7cba722244 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs @@ -491,7 +491,7 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa value: &Value, ) -> Result, ProtocolError> { match key { - "$ownerId" | "$id" => { + "$ownerId" | "$id" | "$creatorId" => { let bytes = value .to_identifier_bytes() .map_err(ProtocolError::ValueError)?; @@ -544,7 +544,7 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa value: &[u8], ) -> Result { match key { - "$ownerId" | "$id" => { + "$ownerId" | "$id" | "$creatorId" => { let bytes = Identifier::from_bytes(value)?; Ok(Value::Identifier(bytes.to_buffer())) } diff --git a/packages/rs-dpp/src/data_contract/methods/validate_document/v0/mod.rs b/packages/rs-dpp/src/data_contract/methods/validate_document/v0/mod.rs index 32914c4e23a..c651d9d3105 100644 --- a/packages/rs-dpp/src/data_contract/methods/validate_document/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/methods/validate_document/v0/mod.rs @@ -3,13 +3,13 @@ use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::DocumentType; use crate::consensus::basic::document::{ - DocumentFieldMaxSizeExceededError, InvalidDocumentTypeError, MissingDocumentTypeError, + DocumentFieldMaxSizeExceededError, InvalidDocumentTypeError, }; use crate::consensus::basic::BasicError; use crate::consensus::ConsensusError; use crate::data_contract::schema::DataContractSchemaMethodsV0; use crate::data_contract::DataContract; -use crate::document::{property_names, Document, DocumentV0Getters}; +use crate::document::{Document, DocumentV0Getters}; use crate::validation::SimpleConsensusValidationResult; use crate::ProtocolError; use platform_value::Value; @@ -97,7 +97,6 @@ impl DataContract { } } - // TODO: Move to document #[inline(always)] pub(super) fn validate_document_v0( &self, @@ -105,30 +104,6 @@ impl DataContract { document: &Document, platform_version: &PlatformVersion, ) -> Result { - // Make sure that the document type is defined in the contract - let Some(document_type) = self.document_type_optional_for_name(name) else { - return Ok(SimpleConsensusValidationResult::new_with_error( - InvalidDocumentTypeError::new(name.to_string(), self.id()).into(), - )); - }; - - // Make sure that timestamps are present if required - let required_fields = document_type.required_fields(); - - if required_fields.contains(property_names::CREATED_AT) && document.created_at().is_none() { - // TODO: Create a special consensus error for this - return Ok(SimpleConsensusValidationResult::new_with_error( - MissingDocumentTypeError::new().into(), - )); - } - - if required_fields.contains(property_names::UPDATED_AT) && document.updated_at().is_none() { - // TODO: Create a special consensus error for this - return Ok(SimpleConsensusValidationResult::new_with_error( - MissingDocumentTypeError::new().into(), - )); - } - // Validate user defined properties self.validate_document_properties_v0(name, document.properties().into(), platform_version) } diff --git a/packages/rs-dpp/src/data_contract/v0/accessors/mod.rs b/packages/rs-dpp/src/data_contract/v0/accessors/mod.rs index c2517a338bf..7a455cfdaaf 100644 --- a/packages/rs-dpp/src/data_contract/v0/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/v0/accessors/mod.rs @@ -21,6 +21,10 @@ impl DataContractV0Getters for DataContractV0 { &self.id } + fn system_version_type(&self) -> u16 { + 0 + } + fn version(&self) -> u32 { self.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 576e98d2a89..4ca959edc63 100644 --- a/packages/rs-dpp/src/data_contract/v0/methods/schema.rs +++ b/packages/rs-dpp/src/data_contract/v0/methods/schema.rs @@ -1,3 +1,4 @@ +use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::DocumentType; use crate::data_contract::schema::DataContractSchemaMethodsV0; @@ -20,6 +21,8 @@ impl DataContractSchemaMethodsV0 for DataContractV0 { ) -> Result<(), ProtocolError> { self.document_types = DocumentType::create_document_types_from_document_schemas( self.id, + self.system_version_type(), + self.config.version(), schemas, defs.as_ref(), &BTreeMap::new(), @@ -43,6 +46,8 @@ impl DataContractSchemaMethodsV0 for DataContractV0 { ) -> Result<(), ProtocolError> { let document_type = DocumentType::try_from_schema( self.id, + self.system_version_type(), + self.config.version(), name, schema, self.schema_defs.as_ref(), 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 ca4048864e2..938a72d1143 100644 --- a/packages/rs-dpp/src/data_contract/v0/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/v0/serialization/mod.rs @@ -93,6 +93,8 @@ impl DataContractV0 { let document_types = DocumentType::create_document_types_from_document_schemas( id, + 0, + data_contract_data.config.version(), document_schemas, schema_defs.as_ref(), &BTreeMap::new(), @@ -134,6 +136,8 @@ impl DataContractV0 { let document_types = DocumentType::create_document_types_from_document_schemas( id, + 0, + data_contract_data.config.version(), document_schemas, schema_defs.as_ref(), &BTreeMap::new(), diff --git a/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs b/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs index f85c274787f..1ffd4587e20 100644 --- a/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs @@ -30,6 +30,10 @@ impl DataContractV0Getters for DataContractV1 { &self.id } + fn system_version_type(&self) -> u16 { + 1 + } + fn version(&self) -> u32 { self.version } 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 f74987abe12..00d4e0cd679 100644 --- a/packages/rs-dpp/src/data_contract/v1/methods/schema.rs +++ b/packages/rs-dpp/src/data_contract/v1/methods/schema.rs @@ -1,3 +1,4 @@ +use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::accessors::v1::DataContractV1Getters; use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::DocumentType; @@ -20,6 +21,8 @@ impl DataContractSchemaMethodsV0 for DataContractV1 { ) -> Result<(), ProtocolError> { self.document_types = DocumentType::create_document_types_from_document_schemas( self.id, + self.system_version_type(), + self.config.version(), schemas, defs.as_ref(), &self.tokens, @@ -43,6 +46,8 @@ impl DataContractSchemaMethodsV0 for DataContractV1 { ) -> Result<(), ProtocolError> { let document_type = DocumentType::try_from_schema( self.id, + self.system_version_type(), + self.config.version(), name, schema, self.schema_defs.as_ref(), 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 8ffcbe4e25d..68c8151beee 100644 --- a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs @@ -92,6 +92,8 @@ impl DataContractV1 { let document_types = DocumentType::create_document_types_from_document_schemas( id, + 1, + data_contract_data.config.version(), document_schemas, schema_defs.as_ref(), &BTreeMap::new(), @@ -151,6 +153,8 @@ impl DataContractV1 { let document_types = DocumentType::create_document_types_from_document_schemas( id, + 1, + data_contract_data.config.version(), document_schemas, schema_defs.as_ref(), &tokens, diff --git a/packages/rs-dpp/src/document/document_factory/v0/mod.rs b/packages/rs-dpp/src/document/document_factory/v0/mod.rs index 7cfbfed6757..d4618b2272a 100644 --- a/packages/rs-dpp/src/document/document_factory/v0/mod.rs +++ b/packages/rs-dpp/src/document/document_factory/v0/mod.rs @@ -36,36 +36,6 @@ use itertools::Itertools; use crate::state_transition::state_transitions::document::batch_transition::batched_transition::document_transition::DocumentTransition; use crate::tokens::token_payment_info::TokenPaymentInfo; -const PROPERTY_FEATURE_VERSION: &str = "$version"; -const PROPERTY_ENTROPY: &str = "$entropy"; -const PROPERTY_ACTION: &str = "$action"; -const PROPERTY_OWNER_ID: &str = "ownerId"; -const PROPERTY_DOCUMENT_OWNER_ID: &str = "$ownerId"; -const PROPERTY_TYPE: &str = "$type"; -const PROPERTY_ID: &str = "$id"; -const PROPERTY_TRANSITIONS: &str = "transitions"; -const PROPERTY_DATA_CONTRACT_ID: &str = "$dataContractId"; -const PROPERTY_REVISION: &str = "$revision"; -const PROPERTY_CREATED_AT: &str = "$createdAt"; -const PROPERTY_UPDATED_AT: &str = "$updatedAt"; -const PROPERTY_DOCUMENT_TYPE: &str = "$type"; - -const DOCUMENT_CREATE_KEYS_TO_STAY: [&str; 5] = [ - PROPERTY_ID, - PROPERTY_TYPE, - PROPERTY_DATA_CONTRACT_ID, - PROPERTY_CREATED_AT, - PROPERTY_UPDATED_AT, -]; - -const DOCUMENT_REPLACE_KEYS_TO_STAY: [&str; 5] = [ - PROPERTY_ID, - PROPERTY_TYPE, - PROPERTY_DATA_CONTRACT_ID, - PROPERTY_REVISION, - PROPERTY_UPDATED_AT, -]; - /// Factory for creating documents pub struct DocumentFactoryV0 { protocol_version: u32, diff --git a/packages/rs-dpp/src/document/extended_document/v0/mod.rs b/packages/rs-dpp/src/document/extended_document/v0/mod.rs index 61deac8c344..7ec130d76a9 100644 --- a/packages/rs-dpp/src/document/extended_document/v0/mod.rs +++ b/packages/rs-dpp/src/document/extended_document/v0/mod.rs @@ -382,6 +382,9 @@ impl ExtendedDocumentV0 { let binary_paths = document_type.binary_paths(); document_value.replace_at_paths(["$id", "$ownerId"], ReplacementType::Identifier)?; + if document_value.has("$creatorId")? { + document_value.replace_at_path("$creatorId", ReplacementType::Identifier)?; + } document_value.replace_at_paths( identifiers.iter().map(|s| s.as_str()), ReplacementType::Identifier, diff --git a/packages/rs-dpp/src/document/specialized_document_factory/v0/mod.rs b/packages/rs-dpp/src/document/specialized_document_factory/v0/mod.rs index 6e6ace91043..1a78bc4cbcd 100644 --- a/packages/rs-dpp/src/document/specialized_document_factory/v0/mod.rs +++ b/packages/rs-dpp/src/document/specialized_document_factory/v0/mod.rs @@ -35,36 +35,6 @@ use itertools::Itertools; use crate::state_transition::state_transitions::document::batch_transition::batched_transition::document_transition::DocumentTransition; use crate::tokens::token_payment_info::TokenPaymentInfo; -const PROPERTY_FEATURE_VERSION: &str = "$version"; -const PROPERTY_ENTROPY: &str = "$entropy"; -const PROPERTY_ACTION: &str = "$action"; -const PROPERTY_OWNER_ID: &str = "ownerId"; -const PROPERTY_DOCUMENT_OWNER_ID: &str = "$ownerId"; -const PROPERTY_TYPE: &str = "$type"; -const PROPERTY_ID: &str = "$id"; -const PROPERTY_TRANSITIONS: &str = "transitions"; -const PROPERTY_DATA_CONTRACT_ID: &str = "$dataContractId"; -const PROPERTY_REVISION: &str = "$revision"; -const PROPERTY_CREATED_AT: &str = "$createdAt"; -const PROPERTY_UPDATED_AT: &str = "$updatedAt"; -const PROPERTY_DOCUMENT_TYPE: &str = "$type"; - -const DOCUMENT_CREATE_KEYS_TO_STAY: [&str; 5] = [ - PROPERTY_ID, - PROPERTY_TYPE, - PROPERTY_DATA_CONTRACT_ID, - PROPERTY_CREATED_AT, - PROPERTY_UPDATED_AT, -]; - -const DOCUMENT_REPLACE_KEYS_TO_STAY: [&str; 5] = [ - PROPERTY_ID, - PROPERTY_TYPE, - PROPERTY_DATA_CONTRACT_ID, - PROPERTY_REVISION, - PROPERTY_UPDATED_AT, -]; - /// Factory for creating documents pub struct SpecializedDocumentFactoryV0 { protocol_version: u32, diff --git a/packages/rs-dpp/src/document/v0/serialize.rs b/packages/rs-dpp/src/document/v0/serialize.rs index eb225434943..0652b3584a5 100644 --- a/packages/rs-dpp/src/document/v0/serialize.rs +++ b/packages/rs-dpp/src/document/v0/serialize.rs @@ -36,7 +36,6 @@ use crate::consensus::basic::BasicError; use crate::consensus::ConsensusError; use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::config::DataContractConfig; -use crate::document::transfer::Transferable; use crate::nft::TradeMode; use platform_value::btreemap_extensions::BTreeValueMapHelper; use std::io::{BufReader, Read}; @@ -489,7 +488,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { /// id 32 bytes + owner_id 32 bytes + encoded values byte arrays /// Serialize v2 will encode the creator id as well. fn serialize_v2(&self, document_type: DocumentTypeRef) -> Result, ProtocolError> { - let mut buffer: Vec = 2u64.encode_var_vec(); //version 1 + let mut buffer: Vec = 2u64.encode_var_vec(); //version 2 // $id buffer.extend(self.id.as_slice()); @@ -498,7 +497,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { buffer.extend(self.owner_id.as_slice()); if document_type.trade_mode() != TradeMode::None - || document_type.documents_transferable() != Transferable::Never + || document_type.documents_transferable().is_transferable() { if let Some(creator_id) = self.properties.get_optional_identifier(CREATOR_ID)? { buffer.push(1); @@ -1185,7 +1184,7 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { // $creatorId let creator_id: Option = if document_type.trade_mode() != TradeMode::None - || document_type.documents_transferable() != Transferable::Never + || document_type.documents_transferable().is_transferable() { let has_creator_id = buf.read_u8().map_err(|_| { DataContractError::CorruptedSerialization( diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/mod.rs index dfc46828724..7d88cc44097 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/mod.rs @@ -6,6 +6,7 @@ mod v0_methods; use crate::block::block_info::BlockInfo; use crate::data_contract::document_type::DocumentTypeRef; use crate::document::Document; +use crate::prelude::DataContract; use crate::state_transition::batch_transition::document_create_transition::v0::DocumentFromCreateTransitionV0; use crate::ProtocolError; use bincode::{Decode, Encode}; @@ -51,6 +52,7 @@ pub trait DocumentFromCreateTransition { document_create_transition: &DocumentCreateTransition, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -74,6 +76,7 @@ pub trait DocumentFromCreateTransition { document_create_transition: DocumentCreateTransition, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -86,6 +89,7 @@ impl DocumentFromCreateTransition for Document { document_create_transition: &DocumentCreateTransition, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -97,6 +101,7 @@ impl DocumentFromCreateTransition for Document { v0, owner_id, block_info, + contract, document_type, platform_version, ), @@ -107,6 +112,7 @@ impl DocumentFromCreateTransition for Document { document_create_transition: DocumentCreateTransition, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -118,6 +124,7 @@ impl DocumentFromCreateTransition for Document { v0, owner_id, block_info, + contract, document_type, platform_version, ), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs index e9e2643caf4..bb9984b75ed 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs @@ -13,18 +13,22 @@ use std::collections::BTreeMap; use std::string::ToString; -#[cfg(feature = "state-transition-value-conversion")] use crate::data_contract::DataContract; use crate::{document, errors::ProtocolError}; use crate::block::block_info::BlockInfo; +use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::methods::DocumentTypeBasicMethods; use crate::data_contract::document_type::DocumentTypeRef; +use crate::document::property_names::CREATOR_ID; use crate::document::{Document, DocumentV0}; use crate::fee::Credits; #[cfg(feature = "state-transition-value-conversion")] +use crate::state_transition::batch_transition; +use crate::state_transition::batch_transition::document_base_transition::v0::v0_methods::DocumentBaseTransitionV0Methods; +#[cfg(feature = "state-transition-value-conversion")] use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0; #[cfg(feature = "state-transition-value-conversion")] use crate::state_transition::batch_transition::document_base_transition::v0::DocumentTransitionObjectLike; @@ -34,10 +38,6 @@ use derive_more::Display; use platform_value::btreemap_extensions::BTreeValueRemoveTupleFromMapHelper; use platform_version::version::PlatformVersion; -#[cfg(feature = "state-transition-value-conversion")] -use crate::state_transition::batch_transition; -use crate::state_transition::batch_transition::document_base_transition::v0::v0_methods::DocumentBaseTransitionV0Methods; - mod property_names { pub const ENTROPY: &str = "$entropy"; pub const PREFUNDED_VOTING_BALANCE: &str = "$prefundedVotingBalance"; @@ -152,6 +152,7 @@ pub trait DocumentFromCreateTransitionV0 { v0: DocumentCreateTransitionV0, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -177,6 +178,7 @@ pub trait DocumentFromCreateTransitionV0 { v0: &DocumentCreateTransitionV0, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -189,17 +191,29 @@ impl DocumentFromCreateTransitionV0 for Document { v0: DocumentCreateTransitionV0, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result where Self: Sized, { - let DocumentCreateTransitionV0 { base, data, .. } = v0; + let DocumentCreateTransitionV0 { base, mut data, .. } = v0; let requires_created_at = document_type .required_fields() .contains(document::property_names::CREATED_AT); + + let adds_creator_id = document_type.should_use_creator_id( + contract.system_version_type(), + contract.config().version(), + platform_version, + )?; + + if adds_creator_id { + data.insert(CREATOR_ID.to_string(), owner_id.into()); + } + let requires_updated_at = document_type .required_fields() .contains(document::property_names::UPDATED_AT); @@ -284,6 +298,7 @@ impl DocumentFromCreateTransitionV0 for Document { v0: &DocumentCreateTransitionV0, owner_id: Identifier, block_info: &BlockInfo, + contract: &DataContract, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, ) -> Result @@ -295,6 +310,18 @@ impl DocumentFromCreateTransitionV0 for Document { let requires_created_at = document_type .required_fields() .contains(document::property_names::CREATED_AT); + + let mut properties = data.clone(); + let adds_creator_id = document_type.should_use_creator_id( + contract.system_version_type(), + contract.config().version(), + platform_version, + )?; + + if adds_creator_id { + properties.insert(CREATOR_ID.to_string(), owner_id.into()); + } + let requires_updated_at = document_type .required_fields() .contains(document::property_names::UPDATED_AT); @@ -354,7 +381,7 @@ impl DocumentFromCreateTransitionV0 for Document { 0 => Ok(DocumentV0 { id: base.id(), owner_id, - properties: data.clone(), + properties, revision: document_type.initial_revision(), created_at, updated_at, diff --git a/packages/rs-dpp/src/util/json_schema.rs b/packages/rs-dpp/src/util/json_schema.rs index 721bbd4e614..08eb80e0727 100644 --- a/packages/rs-dpp/src/util/json_schema.rs +++ b/packages/rs-dpp/src/util/json_schema.rs @@ -102,20 +102,6 @@ impl JsonSchemaExt for JsonValue { bail!("the {:?} isn't an map", self); } - // TODO: Why we are doing this? - // fn get_indices>(&self) -> Result { - // let indices_with_raw_properties: Vec = match self.get("indices") { - // Some(raw_indices) => serde_json::from_value(raw_indices.to_owned())?, - // - // None => vec![], - // }; - // - // indices_with_raw_properties - // .into_iter() - // .map(Index::try_from) - // .collect::>() - // } - fn is_type_of_identifier(&self) -> bool { if let JsonValue::Object(ref map) = self { if let Some(JsonValue::String(media_type)) = map.get("contentMediaType") { @@ -124,23 +110,6 @@ impl JsonSchemaExt for JsonValue { } false } - - // TODO: Why do we need this? - // fn get_indices_map>(&self) -> Result { - // let indices_with_raw_properties: Vec = match self.get("indices") { - // Some(raw_indices) => serde_json::from_value(raw_indices.to_owned())?, - // - // None => vec![], - // }; - // - // indices_with_raw_properties - // .into_iter() - // .map(|r| { - // let index = Index::try_from(r)?; - // Ok((index.name().clone(), index)) - // }) - // .collect::>() - // } } #[cfg(test)] @@ -213,6 +182,8 @@ mod test { let document_type = DocumentType::try_from_schema( Identifier::random(), + 1, + config.version(), "doc", platform_value, None, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs index d3c0d57b7df..63d7d162799 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs @@ -7,6 +7,7 @@ use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::data_contract::document_type::restricted_creation::CreationRestrictionMode; use dpp::data_contract::validate_document::DataContractDocumentValidationMethodsV0; +use dpp::document::property_names::CREATOR_ID; use dpp::identifier::Identifier; use dpp::validation::{SimpleConsensusValidationResult}; use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; @@ -104,10 +105,12 @@ impl DocumentCreateTransitionActionStructureValidationV0 for DocumentCreateTrans } } + let mut data = self.data().clone(); + data.remove(CREATOR_ID); // Validate user defined properties data_contract - .validate_document_properties(document_type_name, self.data().into(), platform_version) + .validate_document_properties(document_type_name, data.into(), platform_version) .map_err(Error::Protocol) } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs index 582db214f0d..8df10703601 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs @@ -2,6 +2,7 @@ use dpp::consensus::basic::document::{InvalidDocumentTransitionActionError, Inva use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::validate_document::DataContractDocumentValidationMethodsV0; +use dpp::document::property_names::CREATOR_ID; use dpp::validation::SimpleConsensusValidationResult; use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; use drive::state_transition_action::batch::batched_transition::document_transition::document_replace_transition_action::{DocumentReplaceTransitionAction, DocumentReplaceTransitionActionAccessorsV0}; @@ -42,10 +43,12 @@ impl DocumentReplaceTransitionActionStructureValidationV0 for DocumentReplaceTra )); } + let mut data = self.data().clone(); + data.remove(CREATOR_ID); // Validate user defined properties data_contract - .validate_document_properties(document_type_name, self.data().into(), platform_version) + .validate_document_properties(document_type_name, data.into(), platform_version) .map_err(Error::Protocol) } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs index 02743269f77..721951af2a4 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs @@ -177,6 +177,250 @@ mod transfer_tests { ); } + #[test] + fn test_document_transfer_on_document_type_that_is_transferable_before_creator_id() { + let platform_version = PlatformVersion::get(9).unwrap(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-format-version-0.json"; + + // let's construct the grovedb structure for the card game data contract + let contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + assert!(contract.system_version_type() > 0); + assert_eq!(contract.config().version(), 0); // this will cause us to serialize v0 + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let (receiver, _, _) = setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a profile document type"); + + assert!(!card_document_type.documents_mutable()); + + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("attack", 4.into()); + document.set("defense", 7.into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document.clone(), + card_document_type, + entropy.0, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition = documents_batch_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let sender_documents_sql_string = + format!("select * from card where $ownerId == '{}'", identity.id()); + + let query_sender_identity_documents = DriveDocumentQuery::from_sql_expr( + sender_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let receiver_documents_sql_string = + format!("select * from card where $ownerId == '{}'", receiver.id()); + + let query_receiver_identity_documents = DriveDocumentQuery::from_sql_expr( + receiver_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_sender_results = platform + .drive + .query_documents( + query_sender_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + let query_receiver_results = platform + .drive + .query_documents( + query_receiver_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + // We expect the sender to have 1 document, and the receiver to have none + assert_eq!(query_sender_results.documents().len(), 1); + + assert_eq!(query_receiver_results.documents().len(), 0); + + document.set_revision(Some(2)); + + let documents_batch_transfer_transition = + BatchTransition::new_document_transfer_transition_from_document( + document, + card_document_type, + receiver.id(), + &key, + 3, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition = documents_batch_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + assert_eq!(processing_result.invalid_paid_count(), 0); + + assert_eq!(processing_result.invalid_unpaid_count(), 0); + + assert_eq!(processing_result.valid_count(), 1); + + assert_eq!(processing_result.aggregated_fees().storage_fee, 37341000); // 1383 bytes added + + // todo: we should expect these numbers to be closer + + assert_eq!( + processing_result + .aggregated_fees() + .fee_refunds + .calculate_refunds_amount_for_identity(identity.id()), + Some(14992395) + ); + + assert_eq!(processing_result.aggregated_fees().processing_fee, 3369260); + + let query_sender_results = platform + .drive + .query_documents(query_sender_identity_documents, None, false, None, None) + .expect("expected query result"); + + let query_receiver_results = platform + .drive + .query_documents(query_receiver_identity_documents, None, false, None, None) + .expect("expected query result"); + + // We expect the sender to have no documents, and the receiver to have 1 + assert_eq!(query_sender_results.documents().len(), 0); + + assert_eq!(query_receiver_results.documents().len(), 1); + + let issues = platform + .drive + .grove + .visualize_verify_grovedb(None, true, false, &platform_version.drive.grove_version) + .expect("expected to have no issues"); + + assert_eq!( + issues.len(), + 0, + "issues are {}", + issues + .iter() + .map(|(hash, (a, b, c))| format!("{}: {} {} {}", hash, a, b, c)) + .collect::>() + .join(" | ") + ); + } + #[test] fn test_document_transfer_on_document_type_that_is_transferable() { let platform_version = PlatformVersion::latest(); @@ -268,6 +512,16 @@ mod transfer_tests { ) .expect("expected document query"); + let creator_documents_sql_string = + format!("select * from card where $creatorId == '{}'", identity.id()); + + let query_creator_identity_documents = DriveDocumentQuery::from_sql_expr( + creator_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + let receiver_documents_sql_string = format!("select * from card where $ownerId == '{}'", receiver.id()); @@ -289,6 +543,17 @@ mod transfer_tests { ) .expect("expected query result"); + let query_creator_results = platform + .drive + .query_documents( + query_creator_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + let query_receiver_results = platform .drive .query_documents( @@ -305,6 +570,9 @@ mod transfer_tests { assert_eq!(query_receiver_results.documents().len(), 0); + // We expect 1 document by the creator id (sender) + assert_eq!(query_creator_results.documents().len(), 1); + document.set_revision(Some(2)); let documents_batch_transfer_transition = @@ -356,7 +624,257 @@ mod transfer_tests { assert_eq!(processing_result.aggregated_fees().storage_fee, 37341000); // 1383 bytes added - // todo: we should expect these numbers to be closer + assert_eq!( + processing_result + .aggregated_fees() + .fee_refunds + .calculate_refunds_amount_for_identity(identity.id()), + Some(14992395) + ); + + assert_eq!(processing_result.aggregated_fees().processing_fee, 3631040); + + let query_sender_results = platform + .drive + .query_documents(query_sender_identity_documents, None, false, None, None) + .expect("expected query result"); + + let query_creator_results = platform + .drive + .query_documents(query_creator_identity_documents, None, false, None, None) + .expect("expected query result"); + + let query_receiver_results = platform + .drive + .query_documents(query_receiver_identity_documents, None, false, None, None) + .expect("expected query result"); + + // We expect the sender to have no documents, and the receiver to have 1 + assert_eq!(query_sender_results.documents().len(), 0); + + assert_eq!(query_receiver_results.documents().len(), 1); + + assert_eq!(query_creator_results.documents().len(), 1); + + let issues = platform + .drive + .grove + .visualize_verify_grovedb(None, true, false, &platform_version.drive.grove_version) + .expect("expected to have no issues"); + + assert_eq!( + issues.len(), + 0, + "issues are {}", + issues + .iter() + .map(|(hash, (a, b, c))| format!("{}: {} {} {}", hash, a, b, c)) + .collect::>() + .join(" | ") + ); + } + + #[test] + fn test_document_transfer_on_document_type_that_is_transferable_contract_v0() { + // With a contract v0 we should not be adding the creator id + // We do this because the creator id can not be serialized in document serialization v0 + // And document serialization v0 is necessary when the data contract is v0 or the data + // contract config is v0. + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-format-version-0.json"; + + // let's construct the grovedb structure for the card game data contract + let contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + assert!(contract.system_version_type() > 0); + assert_eq!(contract.config().version(), 0); // this will cause us to serialize v0 + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let (receiver, _, _) = setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a profile document type"); + + assert!(!card_document_type.documents_mutable()); + + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("attack", 4.into()); + document.set("defense", 7.into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document.clone(), + card_document_type, + entropy.0, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition = documents_batch_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let sender_documents_sql_string = + format!("select * from card where $ownerId == '{}'", identity.id()); + + let query_sender_identity_documents = DriveDocumentQuery::from_sql_expr( + sender_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let receiver_documents_sql_string = + format!("select * from card where $ownerId == '{}'", receiver.id()); + + let query_receiver_identity_documents = DriveDocumentQuery::from_sql_expr( + receiver_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_sender_results = platform + .drive + .query_documents( + query_sender_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + let query_receiver_results = platform + .drive + .query_documents( + query_receiver_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + // We expect the sender to have 1 document, and the receiver to have none + assert_eq!(query_sender_results.documents().len(), 1); + + assert_eq!(query_receiver_results.documents().len(), 0); + + document.set_revision(Some(2)); + + let documents_batch_transfer_transition = + BatchTransition::new_document_transfer_transition_from_document( + document, + card_document_type, + receiver.id(), + &key, + 3, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition = documents_batch_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + assert_eq!(processing_result.invalid_paid_count(), 0); + + assert_eq!(processing_result.invalid_unpaid_count(), 0); + + assert_eq!(processing_result.valid_count(), 1); + + assert_eq!(processing_result.aggregated_fees().storage_fee, 37341000); // 1383 bytes added assert_eq!( processing_result @@ -922,7 +1440,7 @@ mod transfer_tests { assert_eq!(processing_result.valid_count(), 1); - assert_eq!(processing_result.aggregated_fees().processing_fee, 3730120); + assert_eq!(processing_result.aggregated_fees().processing_fee, 3991900); let query_sender_results = platform .drive diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs index 03f13858a31..d2e1c8f4d58 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs @@ -17,7 +17,7 @@ use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::block::block_info::BlockInfo; use dpp::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use dpp::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; -use dpp::document::property_names::PRICE; +use dpp::document::property_names::{CREATOR_ID, PRICE}; use dpp::document::{Document, DocumentV0Getters}; use dpp::fee::Credits; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; @@ -706,6 +706,10 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { let original_document_transferred_at_core_block_height = original_document.transferred_at_core_block_height(); + let original_creator_id = original_document + .properties() + .get_optional_identifier(CREATOR_ID)?; + let validation_result = Self::check_ownership_of_old_replaced_document_v0( document_replace_transition.base().id(), original_document, @@ -743,6 +747,7 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { original_document_transferred_at, original_document_transferred_at_block_height, original_document_transferred_at_core_block_height, + original_creator_id, block_info, user_fee_increase, |_identifier| Ok(data_contract_fetch_info.clone()), @@ -815,7 +820,6 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { block_info, user_fee_increase, |_identifier| Ok(data_contract_fetch_info.clone()), - platform_version, )?; execution_context @@ -945,7 +949,6 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { block_info, user_fee_increase, |_identifier| Ok(data_contract_fetch_info.clone()), - platform_version, )?; execution_context diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs index ca6f85a9474..04602980f20 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs @@ -4607,4 +4607,149 @@ mod tests { ); } } + + #[cfg(test)] + mod creator_id { + use super::*; + use crate::execution::validation::state_transition::tests::setup_identity; + use crate::test::helpers::setup::TestPlatformBuilder; + use assert_matches::assert_matches; + use dpp::block::block_info::BlockInfo; + use dpp::dash_to_credits; + use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; + use dpp::tests::json_document::json_document_to_contract_with_ids; + use platform_version::version::PlatformVersion; + + #[test] + fn test_data_contract_creation_with_creator_id_index() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(2.0)); + + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable.json", + None, + None, + false, //no need to validate the data contracts in tests for drive + platform_version, + ) + .expect("expected to get json based contract"); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + } + + #[test] + fn test_data_contract_creation_with_creator_id_index_not_available_on_protocol_version_9() { + let platform_version = PlatformVersion::get(9).unwrap(); + let mut platform = TestPlatformBuilder::new() + .with_initial_protocol_version(9) + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(2.0)); + + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable.json", + None, + None, + false, //no need to validate the data contracts in tests for drive + platform_version, + ) + .expect("expected to get json based contract"); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::BasicError(BasicError::UndefinedIndexPropertyError(_)), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + } + } } diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-format-version-0.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-format-version-0.json new file mode 100644 index 00000000000..88a6c22aa98 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-format-version-0.json @@ -0,0 +1,133 @@ +{ + "$format_version": "0", + "id": "86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "version": 1, + "documentSchemas": { + "card": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": false, + "transferable": 1, + "properties": { + "name": { + "type": "string", + "description": "Name of the card", + "maxLength": 63, + "position": 0 + }, + "description": { + "type": "string", + "description": "Description of the card", + "maxLength": 256, + "position": 1 + }, + "imageUrl": { + "type": "string", + "description": "URL of the image associated with the card", + "maxLength": 2048, + "format": "uri", + "position": 2 + }, + "imageHash": { + "type": "array", + "description": "SHA256 hash of the bytes of the image specified by imageUrl", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 3 + }, + "imageFingerprint": { + "type": "array", + "description": "dHash of the image specified by imageUrl", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "position": 4 + }, + "attack": { + "type": "integer", + "description": "Attack power of the card", + "minimum": 0, + "position": 5 + }, + "defense": { + "type": "integer", + "description": "Defense level of the card", + "minimum": 0, + "position": 6 + } + }, + "indices": [ + { + "name": "owner", + "properties": [ + { + "$ownerId": "asc" + } + ] + }, + { + "name": "attack", + "properties": [ + { + "attack": "asc" + } + ] + }, + { + "name": "defense", + "properties": [ + { + "defense": "asc" + } + ] + }, + { + "name": "transferredAt", + "properties": [ + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "ownerTransferredAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "transferredAtBlockHeight", + "properties": [ + { + "$transferredAtBlockHeight": "asc" + } + ] + }, + { + "name": "transferredAtCoreBlockHeight", + "properties": [ + { + "$transferredAtCoreBlockHeight": "asc" + } + ] + } + ], + "required": [ + "name", + "$transferredAt", + "$transferredAtBlockHeight", + "$transferredAtCoreBlockHeight", + "attack", + "defense" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable.json index 88a6c22aa98..e34f8370d22 100644 --- a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable.json +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable.json @@ -1,5 +1,5 @@ { - "$format_version": "0", + "$format_version": "1", "id": "86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e", "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", "version": 1, @@ -67,6 +67,14 @@ } ] }, + { + "name": "creator", + "properties": [ + { + "$creatorId": "asc" + } + ] + }, { "name": "attack", "properties": [ diff --git a/packages/rs-drive/src/query/conditions.rs b/packages/rs-drive/src/query/conditions.rs index 1061501bdd3..5f3f70e3668 100644 --- a/packages/rs-drive/src/query/conditions.rs +++ b/packages/rs-drive/src/query/conditions.rs @@ -1674,7 +1674,9 @@ fn is_numeric_value(value: &Value) -> bool { fn meta_field_property_type(field: &str) -> Option { match field { // Identifiers - "$id" | "$ownerId" | "$dataContractId" => Some(DocumentPropertyType::Identifier), + "$id" | "$ownerId" | "$dataContractId" | "$creatorId" => { + Some(DocumentPropertyType::Identifier) + } // Dates (millis since epoch) "$createdAt" | "$updatedAt" | "$transferredAt" => Some(DocumentPropertyType::Date), // Block heights and core block heights diff --git a/packages/rs-drive/src/query/test_index.rs b/packages/rs-drive/src/query/test_index.rs index 357b33075da..c3ace5a5810 100644 --- a/packages/rs-drive/src/query/test_index.rs +++ b/packages/rs-drive/src/query/test_index.rs @@ -83,6 +83,8 @@ mod tests { DocumentType::try_from_schema( Identifier::random(), + 1, + config.version(), "indexed_type", schema, None, diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs index 8f6bd372340..3c607bdaa27 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs @@ -12,7 +12,7 @@ use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::methods::DocumentTypeBasicMethods; use dpp::document::property_names::{ - CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, TRANSFERRED_AT, + CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, CREATOR_ID, TRANSFERRED_AT, TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; @@ -154,6 +154,14 @@ impl DocumentFromCreateTransitionActionV0 for Document { data.retain(|key, _| !transient_fields.contains(key)); } + if document_type.should_use_creator_id( + data_contract.contract.system_version_type(), + data_contract.contract.config().version(), + platform_version, + )? { + data.insert(CREATOR_ID.to_string(), owner_id.into()); + } + let is_created_at_required = required_fields.contains(CREATED_AT); let is_updated_at_required = required_fields.contains(UPDATED_AT); let is_transferred_at_required = required_fields.contains(TRANSFERRED_AT); @@ -275,6 +283,14 @@ impl DocumentFromCreateTransitionActionV0 for Document { data.retain(|key, _| !transient_fields.contains(key)); } + if document_type.should_use_creator_id( + data_contract.contract.system_version_type(), + data_contract.contract.config().version(), + platform_version, + )? { + data.insert(CREATOR_ID.to_string(), owner_id.into()); + } + let is_created_at_required = required_fields.contains(CREATED_AT); let is_updated_at_required = required_fields.contains(UPDATED_AT); let is_transferred_at_required = required_fields.contains(TRANSFERRED_AT); diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs index acd9a6fdbd5..d6082362fc6 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/transformer.rs @@ -6,7 +6,6 @@ use dpp::fee::fee_result::FeeResult; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::DocumentPurchaseTransition; -use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -22,7 +21,6 @@ impl DocumentPurchaseTransitionAction { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, - platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -40,7 +38,6 @@ impl DocumentPurchaseTransitionAction { block_info, user_fee_increase, get_data_contract, - platform_version, ) } } diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs index ce259909d9f..c0a706aa30c 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_purchase_transition_action/v0/transformer.rs @@ -1,15 +1,13 @@ use dpp::block::block_info::BlockInfo; -use dpp::document::property_names::{CREATOR_ID, PRICE}; +use dpp::document::property_names::PRICE; use dpp::document::{property_names, Document, DocumentV0Getters, DocumentV0Setters}; use dpp::platform_value::Identifier; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; use dpp::fee::fee_result::FeeResult; -use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::document_purchase_transition::DocumentPurchaseTransitionV0; -use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -28,7 +26,6 @@ impl DocumentPurchaseTransitionActionV0 { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, - platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -70,18 +67,6 @@ impl DocumentPurchaseTransitionActionV0 { let mut modified_document = original_document; - // If we don't have a creator id that means we never had sold it before - if modified_document - .properties() - .get_optional_identifier(CREATOR_ID)? - .is_none() - && platform_version.protocol_version >= 10 - { - modified_document - .properties_mut() - .insert(CREATOR_ID.to_string(), original_owner_id.into()); - } - modified_document.bump_revision(); // We must remove the price if there is one diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs index 7cd6cfb39bc..e068849e6ac 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs @@ -23,6 +23,7 @@ impl DocumentReplaceTransitionAction { originally_transferred_at: Option, originally_transferred_at_block_height: Option, originally_transferred_at_core_block_height: Option, + original_creator_id: Option, block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, @@ -44,6 +45,7 @@ impl DocumentReplaceTransitionAction { originally_transferred_at, originally_transferred_at_block_height, originally_transferred_at_core_block_height, + original_creator_id, block_info, user_fee_increase, get_data_contract, diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs index 8642bc34221..4dfdd0b4769 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs @@ -3,6 +3,7 @@ use dpp::document::property_names; use dpp::platform_value::Identifier; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; +use dpp::document::property_names::CREATOR_ID; use dpp::fee::fee_result::FeeResult; use dpp::identity::TimestampMillis; use dpp::prelude::{BlockHeight, ConsensusValidationResult, CoreBlockHeight, UserFeeIncrease}; @@ -28,6 +29,7 @@ impl DocumentReplaceTransitionActionV0 { originally_transferred_at: Option, originally_transferred_at_block_height: Option, originally_transferred_at_core_block_height: Option, + original_creator_id: Option, block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, @@ -94,6 +96,11 @@ impl DocumentReplaceTransitionActionV0 { None }; + let mut data = data.clone(); + if let Some(original_creator_id) = original_creator_id { + data.insert(CREATOR_ID.to_string(), original_creator_id.into()); + }; + Ok(( BatchedTransitionAction::DocumentAction(DocumentTransitionAction::ReplaceAction( DocumentReplaceTransitionActionV0 { @@ -108,7 +115,7 @@ impl DocumentReplaceTransitionActionV0 { created_at_core_block_height: originally_created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height: originally_transferred_at_core_block_height, - data: data.clone(), + data, } .into(), )) diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs index aecbf44e9d5..a5035f8951e 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/transformer.rs @@ -6,7 +6,6 @@ use dpp::fee::fee_result::FeeResult; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::DocumentTransferTransition; -use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -21,7 +20,6 @@ impl DocumentTransferTransitionAction { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, - platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -38,7 +36,6 @@ impl DocumentTransferTransitionAction { block_info, user_fee_increase, get_data_contract, - platform_version, ) } } diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs index e1f4929626b..17264725946 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_transfer_transition_action/v0/transformer.rs @@ -1,15 +1,13 @@ use dpp::block::block_info::BlockInfo; -use dpp::document::property_names::{CREATOR_ID, PRICE}; +use dpp::document::property_names::PRICE; use dpp::document::{property_names, Document, DocumentV0Getters, DocumentV0Setters}; use dpp::platform_value::Identifier; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; use dpp::fee::fee_result::FeeResult; -use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::DocumentTransferTransitionV0; -use platform_version::version::PlatformVersion; use crate::drive::contract::DataContractFetchInfo; use crate::error::Error; use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction; @@ -27,7 +25,6 @@ impl DocumentTransferTransitionActionV0 { block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, - platform_version: &PlatformVersion, ) -> Result< ( ConsensusValidationResult, @@ -70,24 +67,10 @@ impl DocumentTransferTransitionActionV0 { } }; - let original_owner_id = original_document.owner_id(); - let mut modified_document = original_document; modified_document.set_owner_id(*recipient_owner_id); - // If we don't have a creator id that means we never had sold it before - if modified_document - .properties() - .get_optional_identifier(CREATOR_ID)? - .is_none() - && platform_version.protocol_version >= 10 - { - modified_document - .properties_mut() - .insert(CREATOR_ID.to_string(), original_owner_id.into()); - } - // We must remove the price modified_document.properties_mut().remove(PRICE); diff --git a/packages/rs-drive/src/util/object_size_info/document_info.rs b/packages/rs-drive/src/util/object_size_info/document_info.rs index 9c94b1a130b..f5e8315ed9c 100644 --- a/packages/rs-drive/src/util/object_size_info/document_info.rs +++ b/packages/rs-drive/src/util/object_size_info/document_info.rs @@ -115,7 +115,7 @@ impl DocumentInfoV0Methods for DocumentInfo<'_> { platform_version: &PlatformVersion, ) -> Result { match key_path { - "$ownerId" | "$id" => Ok(DEFAULT_HASH_SIZE_U16), + "$ownerId" | "$id" | "$creatorId" => Ok(DEFAULT_HASH_SIZE_U16), "$createdAt" | "$updatedAt" | "$transferredAt" => Ok(U64_SIZE_U16), "$createdAtBlockHeight" | "$updatedAtBlockHeight" | "$transferredAtBlockHeight" => { Ok(U64_SIZE_U16) diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index f64e731988c..cde93481b81 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -175,6 +175,7 @@ impl Drive { create_transition, documents_batch_transition.owner_id(), block_info, + &contract, &document_type, platform_version, )?; diff --git a/packages/rs-drive/tests/query_tests.rs b/packages/rs-drive/tests/query_tests.rs index 5c9828abc1b..aa0e4f22771 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -6468,8 +6468,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 230, 69, 78, 132, 91, 227, 185, 56, 79, 242, 84, 4, 40, 72, 112, 127, 218, 3, 148, 82, - 208, 167, 211, 241, 75, 124, 112, 225, 10, 123, 43, 184, + 237, 198, 157, 236, 20, 182, 87, 85, 216, 64, 84, 25, 163, 231, 107, 173, 155, 152, 34, + 64, 34, 142, 234, 16, 99, 134, 153, 156, 24, 208, 150, 115, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6548,8 +6548,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 230, 69, 78, 132, 91, 227, 185, 56, 79, 242, 84, 4, 40, 72, 112, 127, 218, 3, 148, 82, - 208, 167, 211, 241, 75, 124, 112, 225, 10, 123, 43, 184, + 237, 198, 157, 236, 20, 182, 87, 85, 216, 64, 84, 25, 163, 231, 107, 173, 155, 152, 34, + 64, 34, 142, 234, 16, 99, 134, 153, 156, 24, 208, 150, 115, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6649,8 +6649,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 230, 69, 78, 132, 91, 227, 185, 56, 79, 242, 84, 4, 40, 72, 112, 127, 218, 3, 148, 82, - 208, 167, 211, 241, 75, 124, 112, 225, 10, 123, 43, 184, + 237, 198, 157, 236, 20, 182, 87, 85, 216, 64, 84, 25, 163, 231, 107, 173, 155, 152, 34, + 64, 34, 142, 234, 16, 99, 134, 153, 156, 24, 208, 150, 115, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs index d49ac02bb7e..ea970e7d982 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs @@ -1,6 +1,7 @@ use versioned_feature_core::{FeatureVersion, FeatureVersionBounds}; pub mod v1; pub mod v2; +pub mod v3; #[derive(Clone, Debug, Default)] pub struct DPPContractVersions { @@ -70,6 +71,7 @@ pub struct DocumentTypeMethodVersions { #[derive(Clone, Debug, Default)] pub struct DocumentTypeSchemaVersions { + pub should_add_creator_id: FeatureVersion, pub enrich_with_base_schema: FeatureVersion, pub find_identifier_and_binary_paths: FeatureVersion, pub validate_max_depth: FeatureVersion, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v1.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v1.rs index c8a379f7810..77619d972df 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v1.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v1.rs @@ -37,6 +37,7 @@ pub const CONTRACT_VERSIONS_V1: DPPContractVersions = DPPContractVersions { }, structure_version: 0, schema: DocumentTypeSchemaVersions { + should_add_creator_id: 0, enrich_with_base_schema: 0, find_identifier_and_binary_paths: 0, validate_max_depth: 0, 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 9fee67a6754..891323d771b 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 @@ -37,6 +37,7 @@ pub const CONTRACT_VERSIONS_V2: DPPContractVersions = DPPContractVersions { }, structure_version: 0, schema: DocumentTypeSchemaVersions { + should_add_creator_id: 0, enrich_with_base_schema: 0, find_identifier_and_binary_paths: 0, validate_max_depth: 0, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v3.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v3.rs new file mode 100644 index 00000000000..1f20fd2793a --- /dev/null +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v3.rs @@ -0,0 +1,66 @@ +use crate::version::dpp_versions::dpp_contract_versions::{ + DPPContractVersions, DataContractMethodVersions, DocumentTypeClassMethodVersions, + DocumentTypeIndexVersions, DocumentTypeMethodVersions, DocumentTypeSchemaVersions, + DocumentTypeVersions, RecursiveSchemaValidatorVersions, TokenVersions, +}; +use versioned_feature_core::FeatureVersionBounds; + +// Introduced in protocol version 10, system_properties are changed to allow to make indexes on $creatorId +pub const CONTRACT_VERSIONS_V3: DPPContractVersions = DPPContractVersions { + max_serialized_size: 65000, + contract_serialization_version: FeatureVersionBounds { + min_version: 0, + max_version: 1, + default_current_version: 1, + }, + contract_structure_version: 1, + created_data_contract_structure: 0, + config: FeatureVersionBounds { + min_version: 0, + max_version: 1, + default_current_version: 1, + }, + methods: DataContractMethodVersions { + validate_document: 0, + validate_update: 0, + schema: 0, + validate_groups: 0, + equal_ignoring_time_fields: 0, + registration_cost: 1, + }, + document_type_versions: DocumentTypeVersions { + index_versions: DocumentTypeIndexVersions { + index_levels_from_indices: 0, + }, + class_method_versions: DocumentTypeClassMethodVersions { + try_from_schema: 1, + create_document_types_from_document_schemas: 1, + }, + structure_version: 0, + schema: DocumentTypeSchemaVersions { + should_add_creator_id: 1, //changed + enrich_with_base_schema: 0, + find_identifier_and_binary_paths: 0, + validate_max_depth: 0, + max_depth: 256, + recursive_schema_validator_versions: RecursiveSchemaValidatorVersions { + traversal_validator: 0, + }, + validate_schema_compatibility: 0, + }, + methods: DocumentTypeMethodVersions { + create_document_from_data: 0, + create_document_with_prevalidated_properties: 0, + prefunded_voting_balance_for_document: 0, + contested_vote_poll_for_document: 0, + estimated_size: 0, + index_for_types: 0, + max_size: 0, + serialize_value_for_key: 0, + deserialize_value_for_key: 0, + }, + }, + token_versions: TokenVersions { + validate_structure_interval: 0, + }, +}; diff --git a/packages/rs-platform-version/src/version/v10.rs b/packages/rs-platform-version/src/version/v10.rs index 3b15c2f59da..07b3be090c1 100644 --- a/packages/rs-platform-version/src/version/v10.rs +++ b/packages/rs-platform-version/src/version/v10.rs @@ -1,6 +1,6 @@ use crate::version::consensus_versions::ConsensusVersions; use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1; -use crate::version::dpp_versions::dpp_contract_versions::v2::CONTRACT_VERSIONS_V2; +use crate::version::dpp_versions::dpp_contract_versions::v3::CONTRACT_VERSIONS_V3; use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1; use crate::version::dpp_versions::dpp_document_versions::v3::DOCUMENT_VERSIONS_V3; use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1; @@ -47,7 +47,7 @@ pub const PLATFORM_V10: PlatformVersion = PlatformVersion { state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2, state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1, state_transitions: STATE_TRANSITION_VERSIONS_V2, - contract_versions: CONTRACT_VERSIONS_V2, + contract_versions: CONTRACT_VERSIONS_V3, // changed to allow indexes on creator id document_versions: DOCUMENT_VERSIONS_V3, // changed to support serialization of creator id identity_versions: IDENTITY_VERSIONS_V1, voting_versions: VOTING_VERSION_V2, diff --git a/packages/rs-sdk/tests/fetch/common.rs b/packages/rs-sdk/tests/fetch/common.rs index bba48e12f61..ea6b71d23c5 100644 --- a/packages/rs-sdk/tests/fetch/common.rs +++ b/packages/rs-sdk/tests/fetch/common.rs @@ -37,6 +37,8 @@ pub fn mock_document_type() -> dpp::data_contract::document_type::DocumentType { DocumentType::try_from_schema( Identifier::random(), + 1, + config.version(), "document_type_name", schema, None, diff --git a/packages/strategy-tests/src/operations.rs b/packages/strategy-tests/src/operations.rs index cf3201e8459..5623781770a 100644 --- a/packages/strategy-tests/src/operations.rs +++ b/packages/strategy-tests/src/operations.rs @@ -428,6 +428,8 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Dat 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, + contract.system_version_type(), + contract.config().version(), name_str, schema_json, None, From dc3c1a7f961ecb41c438b7202de29c1906f470ec Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 10:28:15 +0700 Subject: [PATCH 03/13] another fix --- .../document_type/class_methods/try_from_schema/v0/mod.rs | 2 +- .../document_type/class_methods/try_from_schema/v1/mod.rs | 2 +- .../crypto-card-game-in-game-currency.json | 8 ++++++++ 3 files changed, 10 insertions(+), 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 ab0328705d1..ac5c6258cc1 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 @@ -431,7 +431,7 @@ impl DocumentTypeV0 { contract_config_version, documents_transferable, trade_mode, - &index_property.name.as_str(), + index_property.name.as_str(), platform_version, )? { let property_definition = flattened_document_properties 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 index 3a23b1f87c9..b07517e04a1 100644 --- 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 @@ -448,7 +448,7 @@ impl DocumentTypeV1 { contract_config_version, documents_transferable, trade_mode, - &index_property.name.as_str(), + index_property.name.as_str(), platform_version, )? { let property_definition = flattened_document_properties diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency.json index 90cb39d749c..ddb71aa5cc4 100644 --- a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency.json +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency.json @@ -76,6 +76,14 @@ } ] }, + { + "name": "creator", + "properties": [ + { + "$creatorId": "asc" + } + ] + }, { "name": "attack", "properties": [ From 6423789f04f866fe2fbe9008fc1515b3967d5f79 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 10:32:21 +0700 Subject: [PATCH 04/13] small fix --- packages/rs-drive/src/util/object_size_info/document_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-drive/src/util/object_size_info/document_info.rs b/packages/rs-drive/src/util/object_size_info/document_info.rs index f5e8315ed9c..875565e9926 100644 --- a/packages/rs-drive/src/util/object_size_info/document_info.rs +++ b/packages/rs-drive/src/util/object_size_info/document_info.rs @@ -184,7 +184,7 @@ impl DocumentInfoV0Methods for DocumentInfo<'_> { DriveError::CorruptedCodeExecution("size_info_with_base_event None but needed"), ))?; match key_path { - "$ownerId" | "$id" => Ok(Some(KeySize(KeyInfo::MaxKeySize { + "$ownerId" | "$id" | "$creatorId" => Ok(Some(KeySize(KeyInfo::MaxKeySize { unique_id: document_type .unique_id_for_document_field(index_level, base_event) .to_vec(), From a531eccc7768a19fb9a0c56b2d78959a5dd24e37 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 19:36:59 +0700 Subject: [PATCH 05/13] make creator id a new field --- .../class_methods/system_properties/mod.rs | 10 +- .../methods/versioned_methods.rs | 12 ++- .../document_type/random_document.rs | 1 + packages/rs-dpp/src/document/accessors/mod.rs | 12 +++ .../rs-dpp/src/document/accessors/v0/mod.rs | 8 ++ .../src/document/document_factory/v0/mod.rs | 1 + packages/rs-dpp/src/document/v0/accessors.rs | 19 ++++ .../rs-dpp/src/document/v0/cbor_conversion.rs | 10 ++ .../rs-dpp/src/document/v0/json_conversion.rs | 34 +++++++ packages/rs-dpp/src/document/v0/mod.rs | 33 +++++++ packages/rs-dpp/src/document/v0/serialize.rs | 14 ++- .../document_create_transition/v0/mod.rs | 32 ++++--- .../document_replace_transition/mod.rs | 14 +++ .../document_replace_transition/v0/mod.rs | 6 ++ packages/rs-dpp/src/tests/json_document.rs | 1 + packages/rs-dpp/src/tokens/token_event.rs | 1 + .../create_genesis_state/common.rs | 1 + .../advanced_structure_v0/mod.rs | 6 +- .../advanced_structure_v0/mod.rs | 5 +- .../batch/tests/document/creation.rs | 95 +++++++++++++++++++ .../src/query/document_query/v0/mod.rs | 5 + .../src/test/helpers/fee_pools.rs | 1 + .../contract/insert/add_description/v0/mod.rs | 1 + .../insert/add_new_keywords/v0/mod.rs | 1 + packages/rs-drive/src/query/mod.rs | 1 + .../v0/mod.rs | 20 ++-- .../document_replace_transition_action/mod.rs | 6 ++ .../v0/mod.rs | 9 ++ .../v0/transformer.rs | 9 +- .../v0/transformer.rs | 2 + .../v0/mod.rs | 1 + .../platform/documents/transitions/delete.rs | 1 + .../documents/transitions/purchase.rs | 1 + .../documents/transitions/set_price.rs | 1 + .../documents/transitions/transfer.rs | 1 + .../rs-sdk/src/platform/dpns_usernames/mod.rs | 2 + 36 files changed, 325 insertions(+), 52 deletions(-) diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs index caa2bb1f892..f363248346d 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/system_properties/mod.rs @@ -25,7 +25,7 @@ impl DocumentType { contract_config_version: u16, transferable: Transferable, trade_mode: TradeMode, - index_name: &str, + property_name: &str, platform_version: &PlatformVersion, ) -> Result { match platform_version @@ -35,20 +35,20 @@ impl DocumentType { .schema .should_add_creator_id { - 0 => Ok(SYSTEM_PROPERTIES.contains(&index_name)), + 0 => Ok(SYSTEM_PROPERTIES.contains(&property_name)), 1 => { - if index_name == CREATOR_ID + if property_name == CREATOR_ID && contract_system_version > 0 && contract_config_version > 0 && (transferable.is_transferable() || trade_mode != TradeMode::None) { Ok(true) } else { - Ok(SYSTEM_PROPERTIES.contains(&index_name)) + Ok(SYSTEM_PROPERTIES.contains(&property_name)) } } version => Err(ProtocolError::UnknownVersionMismatch { - method: "DocumentType::system_properties".to_string(), + method: "DocumentType::system_properties_contains".to_string(), known_versions: vec![0, 1], received: version, }), diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs index d7cba722244..686aaba0c4b 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs @@ -7,7 +7,7 @@ use crate::data_contract::document_type::{ }; use crate::data_contract::errors::DataContractError; use crate::document::property_names::{ - CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, TRANSFERRED_AT, + CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, CREATOR_ID, TRANSFERRED_AT, TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; @@ -88,6 +88,10 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa .get_optional_integer(TRANSFERRED_AT_CORE_BLOCK_HEIGHT) .map_err(ProtocolError::ValueError)?; + let creator_id: Option = data + .get_optional_identifier(CREATOR_ID) + .map_err(ProtocolError::ValueError)?; + let is_created_at_required = self.required_fields().contains(CREATED_AT); let is_updated_at_required = self.required_fields().contains(UPDATED_AT); let is_transferred_at_required = self.required_fields().contains(TRANSFERRED_AT); @@ -176,6 +180,7 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, }; document @@ -240,6 +245,10 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa .get_optional_integer(TRANSFERRED_AT_CORE_BLOCK_HEIGHT) .map_err(ProtocolError::ValueError)?; + let creator_id: Option = properties + .get_optional_identifier(CREATOR_ID) + .map_err(ProtocolError::ValueError)?; + let is_created_at_required = self.required_fields().contains(CREATED_AT); let is_updated_at_required = self.required_fields().contains(UPDATED_AT); let is_transferred_at_required = self.required_fields().contains(TRANSFERRED_AT); @@ -331,6 +340,7 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-dpp/src/data_contract/document_type/random_document.rs b/packages/rs-dpp/src/data_contract/document_type/random_document.rs index 102199904d9..e696e807b26 100644 --- a/packages/rs-dpp/src/data_contract/document_type/random_document.rs +++ b/packages/rs-dpp/src/data_contract/document_type/random_document.rs @@ -348,6 +348,7 @@ pub trait CreateRandomDocument: DocumentTypeV0Getters + DocumentTypeV0Methods { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height: None, + creator_id: None, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-dpp/src/document/accessors/mod.rs b/packages/rs-dpp/src/document/accessors/mod.rs index 2ba6d883103..b27f5800dd4 100644 --- a/packages/rs-dpp/src/document/accessors/mod.rs +++ b/packages/rs-dpp/src/document/accessors/mod.rs @@ -110,6 +110,12 @@ impl DocumentV0Getters for Document { Document::V0(v0) => v0.transferred_at_core_block_height, } } + + fn creator_id(&self) -> Option { + match self { + Document::V0(v0) => v0.creator_id, + } + } } impl DocumentV0Setters for Document { @@ -201,4 +207,10 @@ impl DocumentV0Setters for Document { } } } + + fn set_creator_id(&mut self, creator_id: Option) { + match self { + Document::V0(v0) => v0.creator_id = creator_id, + } + } } diff --git a/packages/rs-dpp/src/document/accessors/v0/mod.rs b/packages/rs-dpp/src/document/accessors/v0/mod.rs index 671ac05c4a6..b2de832bc36 100644 --- a/packages/rs-dpp/src/document/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/document/accessors/v0/mod.rs @@ -47,6 +47,7 @@ pub trait DocumentV0Getters { fn created_at_core_block_height(&self) -> Option; fn updated_at_core_block_height(&self) -> Option; fn transferred_at_core_block_height(&self) -> Option; + fn creator_id(&self) -> Option; } pub trait DocumentV0Setters: DocumentV0Getters { @@ -150,4 +151,11 @@ pub trait DocumentV0Setters: DocumentV0Getters { fn set_transferred_at_block_height(&mut self, transferred_at_block_height: Option); fn set_transferred_at(&mut self, transferred_at: Option); fn bump_revision(&mut self); + /// Sets the creator identifier of the document. This is applicable if the document's + /// schema requires this information. + /// + /// # Parameters + /// - `creator_id`: An `Option` to set as the document's creator ID. + /// `None` indicates the creator ID is not available. + fn set_creator_id(&mut self, creator_id: Option); } diff --git a/packages/rs-dpp/src/document/document_factory/v0/mod.rs b/packages/rs-dpp/src/document/document_factory/v0/mod.rs index d4618b2272a..269dc16c092 100644 --- a/packages/rs-dpp/src/document/document_factory/v0/mod.rs +++ b/packages/rs-dpp/src/document/document_factory/v0/mod.rs @@ -587,6 +587,7 @@ mod test { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }; let document = Document::V0(document_v0); diff --git a/packages/rs-dpp/src/document/v0/accessors.rs b/packages/rs-dpp/src/document/v0/accessors.rs index d62d35eab79..7d0b47f0de3 100644 --- a/packages/rs-dpp/src/document/v0/accessors.rs +++ b/packages/rs-dpp/src/document/v0/accessors.rs @@ -151,6 +151,15 @@ impl DocumentV0Getters for DocumentV0 { fn transferred_at_core_block_height(&self) -> Option { self.transferred_at_core_block_height } + + /// Returns the creator identifier of the document, if it is part + /// of the document. The document will have this field if it's schema has it set as required. + /// + /// # Returns + /// An `Option` representing the creator's ID, or `None` if not available. + fn creator_id(&self) -> Option { + self.creator_id + } } impl DocumentV0Setters for DocumentV0 { @@ -271,4 +280,14 @@ impl DocumentV0Setters for DocumentV0 { ) { self.transferred_at_core_block_height = transferred_at_core_block_height; } + + /// Sets the creator identifier of the document. This is applicable if the document's + /// schema requires this information. + /// + /// # Parameters + /// - `creator_id`: An `Option` to set as the document's creator ID. + /// `None` indicates the creator ID is not available. + fn set_creator_id(&mut self, creator_id: Option) { + self.creator_id = creator_id; + } } diff --git a/packages/rs-dpp/src/document/v0/cbor_conversion.rs b/packages/rs-dpp/src/document/v0/cbor_conversion.rs index 8fe0732ea2c..76e1f05676e 100644 --- a/packages/rs-dpp/src/document/v0/cbor_conversion.rs +++ b/packages/rs-dpp/src/document/v0/cbor_conversion.rs @@ -57,6 +57,9 @@ pub struct DocumentForCbor { pub updated_at_core_block_height: Option, #[serde(rename = "$transferredAtCoreBlockHeight")] pub transferred_at_core_block_height: Option, + + #[serde(rename = "$creatorId")] + pub creator_id: Option, } #[cfg(feature = "cbor")] @@ -78,6 +81,7 @@ impl TryFrom for DocumentForCbor { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, } = value; Ok(DocumentForCbor { id: id.to_buffer(), @@ -94,6 +98,7 @@ impl TryFrom for DocumentForCbor { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, }) } } @@ -139,6 +144,10 @@ impl DocumentV0 { let transferred_at_core_block_height = document_map .remove_optional_integer(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT)?; + let creator_id = document_map + .remove_optional_identifier(property_names::CREATOR_ID) + .map_err(ProtocolError::ValueError)?; + // dev-note: properties is everything other than the id and owner id Ok(DocumentV0 { properties: document_map, @@ -154,6 +163,7 @@ impl DocumentV0 { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, }) } } diff --git a/packages/rs-dpp/src/document/v0/json_conversion.rs b/packages/rs-dpp/src/document/v0/json_conversion.rs index fd7d9bb8daa..f05c6d72d3b 100644 --- a/packages/rs-dpp/src/document/v0/json_conversion.rs +++ b/packages/rs-dpp/src/document/v0/json_conversion.rs @@ -60,6 +60,27 @@ impl DocumentJsonMethodsV0<'_> for DocumentV0 { JsonValue::Number(updated_at_core_block_height.into()), ); } + if let Some(transferred_at) = self.transferred_at { + value_mut.insert( + property_names::TRANSFERRED_AT.to_string(), + JsonValue::Number(transferred_at.into()), + ); + } + if let Some(transferred_at_block_height) = self.transferred_at_block_height { + value_mut.insert( + property_names::TRANSFERRED_AT_BLOCK_HEIGHT.to_string(), + JsonValue::Number(transferred_at_block_height.into()), + ); + } + if let Some(transferred_at_core_block_height) = self.transferred_at_core_block_height { + value_mut.insert( + property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT.to_string(), + JsonValue::Number(transferred_at_core_block_height.into()), + ); + } + if let Some(creator_id) = self.creator_id { + value_mut.insert(property_names::CREATOR_ID.to_string(), json!(creator_id)); + } if let Some(revision) = self.revision { value_mut.insert( property_names::REVISION.to_string(), @@ -123,6 +144,19 @@ impl DocumentJsonMethodsV0<'_> for DocumentV0 { if let Ok(value) = document_value.remove(property_names::UPDATED_AT_CORE_BLOCK_HEIGHT) { document.updated_at_core_block_height = serde_json::from_value(value)?; } + if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT) { + document.transferred_at = serde_json::from_value(value)?; + } + if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT_BLOCK_HEIGHT) { + document.transferred_at_block_height = serde_json::from_value(value)?; + } + if let Ok(value) = document_value.remove(property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT) { + document.transferred_at_core_block_height = serde_json::from_value(value)?; + } + if let Ok(value) = document_value.remove(property_names::CREATOR_ID) { + let data: S = serde_json::from_value(value)?; + document.creator_id = Some(data.try_into()?); + } let platform_value: Value = document_value.into(); diff --git a/packages/rs-dpp/src/document/v0/mod.rs b/packages/rs-dpp/src/document/v0/mod.rs index c68c01a42ac..e16ba818e3a 100644 --- a/packages/rs-dpp/src/document/v0/mod.rs +++ b/packages/rs-dpp/src/document/v0/mod.rs @@ -102,6 +102,12 @@ pub struct DocumentV0 { serde(rename = "$transferredAtCoreBlockHeight", default) )] pub transferred_at_core_block_height: Option, + /// The creator id. + #[cfg_attr( + feature = "document-serde-conversion", + serde(rename = "$creatorId", default) + )] + pub creator_id: Option, } impl DocumentGetRawForContractV0 for DocumentV0 { @@ -132,6 +138,15 @@ impl fmt::Display for DocumentV0 { let datetime = DateTime::from_timestamp_millis(updated_at as i64).unwrap_or_default(); write!(f, "updated_at:{} ", datetime.format("%Y-%m-%d %H:%M:%S"))?; } + if let Some(transferred_at) = self.transferred_at { + let datetime = + DateTime::from_timestamp_millis(transferred_at as i64).unwrap_or_default(); + write!( + f, + "transferred_at:{} ", + datetime.format("%Y-%m-%d %H:%M:%S") + )?; + } if let Some(created_at_block_height) = self.created_at_block_height { write!(f, "created_at_block_height:{} ", created_at_block_height)?; @@ -139,6 +154,13 @@ impl fmt::Display for DocumentV0 { if let Some(updated_at_block_height) = self.updated_at_block_height { write!(f, "updated_at_block_height:{} ", updated_at_block_height)?; } + if let Some(transferred_at_block_height) = self.transferred_at_block_height { + write!( + f, + "transferred_at_block_height:{} ", + transferred_at_block_height + )?; + } if let Some(created_at_core_block_height) = self.created_at_core_block_height { write!( f, @@ -153,6 +175,17 @@ impl fmt::Display for DocumentV0 { updated_at_core_block_height )?; } + if let Some(transferred_at_core_block_height) = self.transferred_at_core_block_height { + write!( + f, + "transferred_at_core_block_height:{} ", + transferred_at_core_block_height + )?; + } + + if let Some(creator_id) = self.creator_id { + write!(f, "creator_id:{} ", creator_id)?; + } if self.properties.is_empty() { write!(f, "no properties")?; diff --git a/packages/rs-dpp/src/document/v0/serialize.rs b/packages/rs-dpp/src/document/v0/serialize.rs index 0652b3584a5..a64fd49878e 100644 --- a/packages/rs-dpp/src/document/v0/serialize.rs +++ b/packages/rs-dpp/src/document/v0/serialize.rs @@ -2,8 +2,8 @@ use crate::data_contract::document_type::{DocumentPropertyType, DocumentTypeRef} use crate::data_contract::errors::DataContractError; use crate::document::property_names::{ - CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, CREATOR_ID, PRICE, - TRANSFERRED_AT, TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, + CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, PRICE, TRANSFERRED_AT, + TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; @@ -37,7 +37,6 @@ use crate::consensus::ConsensusError; use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::config::DataContractConfig; use crate::nft::TradeMode; -use platform_value::btreemap_extensions::BTreeValueMapHelper; use std::io::{BufReader, Read}; impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { @@ -499,7 +498,7 @@ impl DocumentPlatformSerializationMethodsV0 for DocumentV0 { if document_type.trade_mode() != TradeMode::None || document_type.documents_transferable().is_transferable() { - if let Some(creator_id) = self.properties.get_optional_identifier(CREATOR_ID)? { + if let Some(creator_id) = self.creator_id { buffer.push(1); buffer.extend(creator_id.as_slice()); } else { @@ -932,6 +931,7 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id: None, }) } @@ -1148,6 +1148,7 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id: None, }) } @@ -1375,10 +1376,6 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { properties.insert(PRICE.to_string(), price.into()); } - if let Some(creator_id) = creator_id { - properties.insert(CREATOR_ID.to_string(), creator_id.into()); - } - Ok(DocumentV0 { id: Identifier::new(id), properties, @@ -1393,6 +1390,7 @@ impl DocumentPlatformDeserializationMethodsV0 for DocumentV0 { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, }) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs index bb9984b75ed..b7bef6f0611 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_create_transition/v0/mod.rs @@ -22,7 +22,6 @@ use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::methods::DocumentTypeBasicMethods; use crate::data_contract::document_type::DocumentTypeRef; -use crate::document::property_names::CREATOR_ID; use crate::document::{Document, DocumentV0}; use crate::fee::Credits; #[cfg(feature = "state-transition-value-conversion")] @@ -198,21 +197,21 @@ impl DocumentFromCreateTransitionV0 for Document { where Self: Sized, { - let DocumentCreateTransitionV0 { base, mut data, .. } = v0; + let DocumentCreateTransitionV0 { base, data, .. } = v0; let requires_created_at = document_type .required_fields() .contains(document::property_names::CREATED_AT); - let adds_creator_id = document_type.should_use_creator_id( + let creator_id = if document_type.should_use_creator_id( contract.system_version_type(), contract.config().version(), platform_version, - )?; - - if adds_creator_id { - data.insert(CREATOR_ID.to_string(), owner_id.into()); - } + )? { + Some(owner_id) + } else { + None + }; let requires_updated_at = document_type .required_fields() @@ -284,6 +283,7 @@ impl DocumentFromCreateTransitionV0 for Document { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height: None, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { @@ -311,16 +311,17 @@ impl DocumentFromCreateTransitionV0 for Document { .required_fields() .contains(document::property_names::CREATED_AT); - let mut properties = data.clone(); - let adds_creator_id = document_type.should_use_creator_id( + let properties = data.clone(); + + let creator_id = if document_type.should_use_creator_id( contract.system_version_type(), contract.config().version(), platform_version, - )?; - - if adds_creator_id { - properties.insert(CREATOR_ID.to_string(), owner_id.into()); - } + )? { + Some(owner_id) + } else { + None + }; let requires_updated_at = document_type .required_fields() @@ -392,6 +393,7 @@ impl DocumentFromCreateTransitionV0 for Document { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height: None, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/mod.rs index ef0e89bc455..8f889032196 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/mod.rs @@ -38,6 +38,10 @@ pub trait DocumentFromReplaceTransition { /// * `created_at` - An optional timestamp indicating when the original document was created. /// * `created_at_block_height` - An optional block height indicating when the original document was created. /// * `created_at_core_block_height` - An optional core block height indicating when the original document was created. + /// * `transferred_at` - An optional timestamp indicating when the document was last transferred. + /// * `transferred_at_block_height` - An optional block height indicating when the document was last transferred. + /// * `transferred_at_core_block_height` - An optional core block height indicating when the document was last transferred. + /// * `creator_id` - An optional `Identifier` of the document's original creator. /// * `block_info` - Current block information used for updating document metadata. /// * `document_type` - Reference to the document type to ensure compatibility and proper validation. /// * `platform_version` - Reference to the current platform version to check for compatibility and apply version-specific logic. @@ -59,6 +63,7 @@ pub trait DocumentFromReplaceTransition { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -77,6 +82,10 @@ pub trait DocumentFromReplaceTransition { /// * `created_at` - An optional timestamp indicating when the original document was created. /// * `created_at_block_height` - An optional block height indicating when the original document was created. /// * `created_at_core_block_height` - An optional core block height indicating when the original document was created. + /// * `transferred_at` - An optional timestamp indicating when the document was last transferred. + /// * `transferred_at_block_height` - An optional block height indicating when the document was last transferred. + /// * `transferred_at_core_block_height` - An optional core block height indicating when the document was last transferred. + /// * `creator_id` - An optional `Identifier` of the document's original creator. /// * `block_info` - Current block information used for updating document metadata. /// * `document_type` - Reference to the document type to ensure compatibility and proper validation. /// * `platform_version` - Reference to the current platform version to check for compatibility and apply version-specific logic. @@ -98,6 +107,7 @@ pub trait DocumentFromReplaceTransition { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -116,6 +126,7 @@ impl DocumentFromReplaceTransition for Document { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -130,6 +141,7 @@ impl DocumentFromReplaceTransition for Document { transferred_at, transferred_at_block_height, transferred_at_core_block_height, + creator_id, block_info, document_type, platform_version, @@ -146,6 +158,7 @@ impl DocumentFromReplaceTransition for Document { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -160,6 +173,7 @@ impl DocumentFromReplaceTransition for Document { transferred_at, transferred_at_block_height, transferred_at_core_block_height, + creator_id, block_info, document_type, platform_version, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/v0/mod.rs index ef045d2200a..923ccd1a596 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/document_replace_transition/v0/mod.rs @@ -76,6 +76,7 @@ pub trait DocumentFromReplaceTransitionV0 { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -112,6 +113,7 @@ pub trait DocumentFromReplaceTransitionV0 { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -130,6 +132,7 @@ impl DocumentFromReplaceTransitionV0 for Document { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -191,6 +194,7 @@ impl DocumentFromReplaceTransitionV0 for Document { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { @@ -210,6 +214,7 @@ impl DocumentFromReplaceTransitionV0 for Document { transferred_at: Option, transferred_at_block_height: Option, transferred_at_core_block_height: Option, + creator_id: Option, block_info: &BlockInfo, document_type: &DocumentTypeRef, platform_version: &PlatformVersion, @@ -270,6 +275,7 @@ impl DocumentFromReplaceTransitionV0 for Document { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-dpp/src/tests/json_document.rs b/packages/rs-dpp/src/tests/json_document.rs index d0b80a33585..a76be4dd5a2 100644 --- a/packages/rs-dpp/src/tests/json_document.rs +++ b/packages/rs-dpp/src/tests/json_document.rs @@ -148,6 +148,7 @@ pub fn json_document_to_document( updated_at_core_block_height: data.remove_optional_integer("$updatedAtCoreBlockHeight")?, transferred_at_core_block_height: data .remove_optional_integer("$transferredAtCoreBlockHeight")?, + creator_id: data.remove_optional_identifier("$creatorId")?, }; data.replace_at_paths( diff --git a/packages/rs-dpp/src/tokens/token_event.rs b/packages/rs-dpp/src/tokens/token_event.rs index 92ed079b906..b7a852ca430 100644 --- a/packages/rs-dpp/src/tokens/token_event.rs +++ b/packages/rs-dpp/src/tokens/token_event.rs @@ -453,6 +453,7 @@ impl TokenEvent { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/common.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/common.rs index 9d0a27dec46..a96ca521343 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/common.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/common.rs @@ -79,6 +79,7 @@ impl Platform { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs index 63d7d162799..15420223eb5 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v0/mod.rs @@ -7,7 +7,6 @@ use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::data_contract::document_type::restricted_creation::CreationRestrictionMode; use dpp::data_contract::validate_document::DataContractDocumentValidationMethodsV0; -use dpp::document::property_names::CREATOR_ID; use dpp::identifier::Identifier; use dpp::validation::{SimpleConsensusValidationResult}; use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; @@ -104,13 +103,10 @@ impl DocumentCreateTransitionActionStructureValidationV0 for DocumentCreateTrans )); } } - - let mut data = self.data().clone(); - data.remove(CREATOR_ID); // Validate user defined properties data_contract - .validate_document_properties(document_type_name, data.into(), platform_version) + .validate_document_properties(document_type_name, self.data().into(), platform_version) .map_err(Error::Protocol) } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs index 8df10703601..582db214f0d 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/advanced_structure_v0/mod.rs @@ -2,7 +2,6 @@ use dpp::consensus::basic::document::{InvalidDocumentTransitionActionError, Inva use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::validate_document::DataContractDocumentValidationMethodsV0; -use dpp::document::property_names::CREATOR_ID; use dpp::validation::SimpleConsensusValidationResult; use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; use drive::state_transition_action::batch::batched_transition::document_transition::document_replace_transition_action::{DocumentReplaceTransitionAction, DocumentReplaceTransitionActionAccessorsV0}; @@ -43,12 +42,10 @@ impl DocumentReplaceTransitionActionStructureValidationV0 for DocumentReplaceTra )); } - let mut data = self.data().clone(); - data.remove(CREATOR_ID); // Validate user defined properties data_contract - .validate_document_properties(document_type_name, data.into(), platform_version) + .validate_document_properties(document_type_name, self.data().into(), platform_version) .map_err(Error::Protocol) } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs index 7bf92281c42..aedac5e16c1 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs @@ -35,6 +35,7 @@ mod creation_tests { use dpp::dashcore::Network; use dpp::dashcore::Network::Testnet; use dpp::data_contract::{DataContract, TokenConfiguration}; + use dpp::document::transfer::Transferable; use dpp::identity::SecurityLevel; use dpp::state_transition::batch_transition::document_base_transition::DocumentBaseTransition; use dpp::state_transition::batch_transition::document_create_transition::DocumentCreateTransitionV0; @@ -134,6 +135,100 @@ mod creation_tests { .expect("expected to commit transaction"); } + #[test] + fn test_document_creation_should_fail_when_creator_id_is_provided() { + let platform_version = PlatformVersion::latest(); + let (mut platform, contract) = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure() + .with_crypto_card_game_transfer_only(Transferable::Always); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::FillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("attack", 4.into()); + document.set("defense", 7.into()); + document.set("imageUrl", "https://example.com/card.png".into()); + + let forged_creator_bytes = Bytes32::random_with_rng(&mut rng); + let forged_creator = Identifier::from(forged_creator_bytes.0); + document.set("$creatorId", forged_creator.into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document, + card_document_type, + entropy.0, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition = documents_batch_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let result = processing_result.into_execution_results().remove(0); + let PaidConsensusError(consensus_error, _) = result else { + panic!("expected a paid consensus error"); + }; + + assert!( + consensus_error.to_string().contains("$creatorId"), + "expected the error to mention $creatorId but got: {}", + consensus_error + ); + } + #[test] fn test_document_creation_should_fail_if_reusing_entropy() { let platform_version = PlatformVersion::latest(); 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 a620156babd..8021b1bc243 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 @@ -789,6 +789,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); store_document( @@ -953,6 +954,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); store_document( @@ -1117,6 +1119,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); store_document( @@ -1273,6 +1276,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); store_document( @@ -1444,6 +1448,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); store_document( diff --git a/packages/rs-drive-abci/src/test/helpers/fee_pools.rs b/packages/rs-drive-abci/src/test/helpers/fee_pools.rs index 443cc03b900..5dbe0cb0957 100644 --- a/packages/rs-drive-abci/src/test/helpers/fee_pools.rs +++ b/packages/rs-drive-abci/src/test/helpers/fee_pools.rs @@ -91,6 +91,7 @@ fn create_test_mn_share_document( created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs b/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs index c0b6b955fca..cb742291b16 100644 --- a/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs @@ -212,6 +212,7 @@ impl Drive { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs b/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs index b3f49fb0e2d..a105fef6a71 100644 --- a/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs @@ -173,6 +173,7 @@ impl Drive { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive/src/query/mod.rs b/packages/rs-drive/src/query/mod.rs index 6253e379a67..ef57daaa180 100644 --- a/packages/rs-drive/src/query/mod.rs +++ b/packages/rs-drive/src/query/mod.rs @@ -3220,6 +3220,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs index 3c607bdaa27..8cb8c278692 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/mod.rs @@ -12,7 +12,7 @@ use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::methods::DocumentTypeBasicMethods; use dpp::document::property_names::{ - CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, CREATOR_ID, TRANSFERRED_AT, + CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, TRANSFERRED_AT, TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; @@ -154,13 +154,15 @@ impl DocumentFromCreateTransitionActionV0 for Document { data.retain(|key, _| !transient_fields.contains(key)); } - if document_type.should_use_creator_id( + let creator_id = if document_type.should_use_creator_id( data_contract.contract.system_version_type(), data_contract.contract.config().version(), platform_version, )? { - data.insert(CREATOR_ID.to_string(), owner_id.into()); - } + Some(owner_id) + } else { + None + }; let is_created_at_required = required_fields.contains(CREATED_AT); let is_updated_at_required = required_fields.contains(UPDATED_AT); @@ -236,6 +238,7 @@ impl DocumentFromCreateTransitionActionV0 for Document { } else { None }, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { @@ -283,13 +286,15 @@ impl DocumentFromCreateTransitionActionV0 for Document { data.retain(|key, _| !transient_fields.contains(key)); } - if document_type.should_use_creator_id( + let creator_id = if document_type.should_use_creator_id( data_contract.contract.system_version_type(), data_contract.contract.config().version(), platform_version, )? { - data.insert(CREATOR_ID.to_string(), owner_id.into()); - } + Some(owner_id) + } else { + None + }; let is_created_at_required = required_fields.contains(CREATED_AT); let is_updated_at_required = required_fields.contains(UPDATED_AT); @@ -365,6 +370,7 @@ impl DocumentFromCreateTransitionActionV0 for Document { } else { None }, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs index 01d8400b382..f6b70b2e6d6 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs @@ -112,6 +112,12 @@ impl DocumentReplaceTransitionActionAccessorsV0 for DocumentReplaceTransitionAct DocumentReplaceTransitionAction::V0(v0) => v0.data, } } + + fn creator_id(&self) -> Option { + match self { + DocumentReplaceTransitionAction::V0(v0) => v0.creator_id, + } + } } /// document from replace transition diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs index f8b4daa0fd6..da4daf59ace 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs @@ -38,6 +38,8 @@ pub struct DocumentReplaceTransitionActionV0 { pub transferred_at_core_block_height: Option, /// Document properties pub data: BTreeMap, + /// Creator id + pub creator_id: Option, } /// document replace transition action accessors v0 @@ -76,6 +78,9 @@ pub trait DocumentReplaceTransitionActionAccessorsV0 { fn data(&self) -> &BTreeMap; /// data owned fn data_owned(self) -> BTreeMap; + + /// creator id + fn creator_id(&self) -> Option; } /// document from replace transition v0 @@ -135,6 +140,7 @@ impl DocumentFromReplaceTransitionActionV0 for Document { updated_at_core_block_height, transferred_at_core_block_height, data, + creator_id, } = value; let id = base.id(); @@ -158,6 +164,7 @@ impl DocumentFromReplaceTransitionActionV0 for Document { created_at_core_block_height: *created_at_core_block_height, updated_at_core_block_height: *updated_at_core_block_height, transferred_at_core_block_height: *transferred_at_core_block_height, + creator_id: *creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { @@ -186,6 +193,7 @@ impl DocumentFromReplaceTransitionActionV0 for Document { updated_at_core_block_height, transferred_at_core_block_height, data, + creator_id, } = value; let id = base.id(); @@ -209,6 +217,7 @@ impl DocumentFromReplaceTransitionActionV0 for Document { created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height, + creator_id, } .into()), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs index 4dfdd0b4769..1d52f7831d4 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs @@ -3,7 +3,6 @@ use dpp::document::property_names; use dpp::platform_value::Identifier; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; -use dpp::document::property_names::CREATOR_ID; use dpp::fee::fee_result::FeeResult; use dpp::identity::TimestampMillis; use dpp::prelude::{BlockHeight, ConsensusValidationResult, CoreBlockHeight, UserFeeIncrease}; @@ -96,11 +95,6 @@ impl DocumentReplaceTransitionActionV0 { None }; - let mut data = data.clone(); - if let Some(original_creator_id) = original_creator_id { - data.insert(CREATOR_ID.to_string(), original_creator_id.into()); - }; - Ok(( BatchedTransitionAction::DocumentAction(DocumentTransitionAction::ReplaceAction( DocumentReplaceTransitionActionV0 { @@ -115,7 +109,8 @@ impl DocumentReplaceTransitionActionV0 { created_at_core_block_height: originally_created_at_core_block_height, updated_at_core_block_height, transferred_at_core_block_height: originally_transferred_at_core_block_height, - data, + data: data.clone(), + creator_id: original_creator_id, } .into(), )) diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs index bf4f38b651a..e7f8d622d16 100644 --- a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs @@ -65,6 +65,7 @@ impl IdentityCreditWithdrawalTransitionActionV0 { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); @@ -188,6 +189,7 @@ impl IdentityCreditWithdrawalTransitionActionV0 { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, } .into(); diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index cde93481b81..86bc627fe78 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -212,6 +212,7 @@ impl Drive { document.created_at(), //we can trust the created at (as we don't care) document.created_at_block_height(), //we can trust the created at block height (as we don't care) document.created_at_core_block_height(), //we can trust the created at core block height (as we don't care) + document.creator_id(), block_info, &document_type, platform_version, diff --git a/packages/rs-sdk/src/platform/documents/transitions/delete.rs b/packages/rs-sdk/src/platform/documents/transitions/delete.rs index 55ccf4d8371..b6a3cf854b2 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/delete.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/delete.rs @@ -192,6 +192,7 @@ impl DocumentDeleteTransitionBuilder { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); let state_transition = BatchTransition::new_document_deletion_transition_from_document( diff --git a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs index 83ec7117ed8..cdc6d1a9871 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs @@ -103,6 +103,7 @@ impl DocumentPurchaseTransitionBuilder { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Self::new( diff --git a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs index 8b38a1ff24e..55a9d783657 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs @@ -97,6 +97,7 @@ impl DocumentSetPriceTransitionBuilder { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Self::new(data_contract, document_type_name, document, price) diff --git a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs index f75237927c4..533317f829e 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs @@ -96,6 +96,7 @@ impl DocumentTransferTransitionBuilder { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Self::new(data_contract, document_type_name, document, recipient_id) diff --git a/packages/rs-sdk/src/platform/dpns_usernames/mod.rs b/packages/rs-sdk/src/platform/dpns_usernames/mod.rs index b971fdc9ba0..3bc51a9cd7f 100644 --- a/packages/rs-sdk/src/platform/dpns_usernames/mod.rs +++ b/packages/rs-sdk/src/platform/dpns_usernames/mod.rs @@ -273,6 +273,7 @@ impl Sdk { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); // Create domain document @@ -319,6 +320,7 @@ impl Sdk { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); // Submit preorder document first From 7e7eb7e5ee1307c0c31edc909f2b70e9cc4b70a9 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 20:18:33 +0700 Subject: [PATCH 06/13] uniqueness of data request --- .../rs-dpp/src/document/extended_document/mod.rs | 2 +- .../state_transitions/batch/transformer/v0/mod.rs | 6 ++---- .../internal/validate_uniqueness_of_data/mod.rs | 2 ++ .../internal/validate_uniqueness_of_data/v0/mod.rs | 8 ++++++++ .../v0/mod.rs | 13 ++++++++++++- .../v0/mod.rs | 1 + .../v0/mod.rs | 1 + .../v0/mod.rs | 1 + .../validate_document_uniqueness/v0/mod.rs | 1 + .../v0/mod.rs | 1 + packages/rs-sdk-ffi/src/document/create.rs | 1 + packages/rs-sdk-ffi/src/document/delete.rs | 1 + packages/rs-sdk-ffi/src/document/price.rs | 1 + packages/rs-sdk-ffi/src/document/purchase.rs | 1 + packages/rs-sdk-ffi/src/document/put.rs | 1 + packages/rs-sdk-ffi/src/document/replace.rs | 1 + packages/rs-sdk-ffi/src/document/transfer.rs | 1 + .../wasm-sdk/src/state_transitions/documents/mod.rs | 5 +++++ 18 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/rs-dpp/src/document/extended_document/mod.rs b/packages/rs-dpp/src/document/extended_document/mod.rs index 83f1eb3c3df..7f7c1dc3e8d 100644 --- a/packages/rs-dpp/src/document/extended_document/mod.rs +++ b/packages/rs-dpp/src/document/extended_document/mod.rs @@ -550,7 +550,7 @@ mod test { let string = serde_json::to_string(&document)?; assert_eq!( - "{\"version\":0,\"$type\":\"domain\",\"$dataContractId\":\"566vcJkmebVCAb2Dkj2yVMSgGFcsshupnQqtsz1RFbcy\",\"document\":{\"$version\":\"0\",\"$id\":\"4veLBZPHDkaCPF9LfZ8fX3JZiS5q5iUVGhdBbaa9ga5E\",\"$ownerId\":\"HBNMY5QWuBVKNFLhgBTC1VmpEnscrmqKPMXpnYSHwhfn\",\"$dataContractId\":\"566vcJkmebVCAb2Dkj2yVMSgGFcsshupnQqtsz1RFbcy\",\"$protocolVersion\":0,\"$type\":\"domain\",\"label\":\"user-9999\",\"normalizedLabel\":\"user-9999\",\"normalizedParentDomainName\":\"dash\",\"preorderSalt\":\"BzQi567XVqc8wYiVHS887sJtL6MDbxLHNnp+UpTFSB0=\",\"records\":{\"identity\":\"HBNMY5QWuBVKNFLhgBTC1VmpEnscrmqKPMXpnYSHwhfn\"},\"subdomainRules\":{\"allowSubdomains\":false},\"$revision\":1,\"$createdAt\":null,\"$updatedAt\":null,\"$transferredAt\":null,\"$createdAtBlockHeight\":null,\"$updatedAtBlockHeight\":null,\"$transferredAtBlockHeight\":null,\"$createdAtCoreBlockHeight\":null,\"$updatedAtCoreBlockHeight\":null,\"$transferredAtCoreBlockHeight\":null}}", + "{\"version\":0,\"$type\":\"domain\",\"$dataContractId\":\"566vcJkmebVCAb2Dkj2yVMSgGFcsshupnQqtsz1RFbcy\",\"document\":{\"$version\":\"0\",\"$id\":\"4veLBZPHDkaCPF9LfZ8fX3JZiS5q5iUVGhdBbaa9ga5E\",\"$ownerId\":\"HBNMY5QWuBVKNFLhgBTC1VmpEnscrmqKPMXpnYSHwhfn\",\"$dataContractId\":\"566vcJkmebVCAb2Dkj2yVMSgGFcsshupnQqtsz1RFbcy\",\"$protocolVersion\":0,\"$type\":\"domain\",\"label\":\"user-9999\",\"normalizedLabel\":\"user-9999\",\"normalizedParentDomainName\":\"dash\",\"preorderSalt\":\"BzQi567XVqc8wYiVHS887sJtL6MDbxLHNnp+UpTFSB0=\",\"records\":{\"identity\":\"HBNMY5QWuBVKNFLhgBTC1VmpEnscrmqKPMXpnYSHwhfn\"},\"subdomainRules\":{\"allowSubdomains\":false},\"$revision\":1,\"$createdAt\":null,\"$updatedAt\":null,\"$transferredAt\":null,\"$createdAtBlockHeight\":null,\"$updatedAtBlockHeight\":null,\"$transferredAtBlockHeight\":null,\"$createdAtCoreBlockHeight\":null,\"$updatedAtCoreBlockHeight\":null,\"$transferredAtCoreBlockHeight\":null,\"$creatorId\":null}}", string ); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs index d2e1c8f4d58..48f49adfed6 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs @@ -17,7 +17,7 @@ use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::block::block_info::BlockInfo; use dpp::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use dpp::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; -use dpp::document::property_names::{CREATOR_ID, PRICE}; +use dpp::document::property_names::PRICE; use dpp::document::{Document, DocumentV0Getters}; use dpp::fee::Credits; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; @@ -706,9 +706,7 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { let original_document_transferred_at_core_block_height = original_document.transferred_at_core_block_height(); - let original_creator_id = original_document - .properties() - .get_optional_identifier(CREATOR_ID)?; + let original_creator_id = original_document.creator_id(); let validation_result = Self::check_ownership_of_old_replaced_document_v0( document_replace_transition.base().id(), diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs index 32823a82e9c..ac3082ff69b 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs @@ -32,6 +32,8 @@ pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequest< pub document_type: DocumentTypeRef<'a>, /// The ID representing the owner. pub owner_id: Identifier, + /// The ID representing the original creator. + pub creator_id: Option, /// The ID of the document in question. pub document_id: Identifier, /// A flag indicating if the original (existing) document is considered permissible. diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs index acc70692f62..50c7a62c3c0 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs @@ -45,6 +45,7 @@ impl Drive { contract, document_type, owner_id, + creator_id, document_id, allow_original, created_at, @@ -82,6 +83,13 @@ impl Drive { return None; } } + property_names::CREATOR_ID => { + if let Some(creator_id) = creator_id { + platform_value!(creator_id) + } else { + return None; + } + } property_names::UPDATED_AT => { if let Some(updated_at) = updated_at { platform_value!(updated_at) diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs index 33a1fdcc9de..c34f463d2bf 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs @@ -18,7 +18,7 @@ use dpp::document::property_names::{ UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; use grovedb::TransactionArg; - +use dpp::data_contract::accessors::v0::DataContractV0Getters; use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; use crate::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::{DocumentCreateTransitionAction, DocumentCreateTransitionActionAccessorsV0}; use dpp::version::PlatformVersion; @@ -55,10 +55,21 @@ impl Drive { let block_info = document_create_transition.block_info(); + let creator_id = if document_type.should_use_creator_id( + contract.system_version_type(), + contract.config().version(), + platform_version, + )? { + Some(owner_id) + } else { + None + }; + let request = UniquenessOfDataRequest { contract, document_type, owner_id, + creator_id, document_id: document_create_transition.base().id(), allow_original: false, created_at: if is_created_at_required { diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs index 05bee3b7e1e..445da09d406 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs @@ -34,6 +34,7 @@ impl Drive { contract, document_type, owner_id, + creator_id: document_purchase_transition.document().creator_id(), document_id: document_purchase_transition.base().id(), allow_original: true, created_at: document_purchase_transition.document().created_at(), diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs index 5dbea707624..8c1c9180514 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs @@ -33,6 +33,7 @@ impl Drive { contract, document_type, owner_id, + creator_id: document_replace_transition.creator_id(), document_id: document_replace_transition.base().id(), allow_original: true, created_at: document_replace_transition.created_at(), diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs index 638fcdd852a..c42dcd5feb4 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs @@ -34,6 +34,7 @@ impl Drive { contract, document_type, owner_id, + creator_id: document_transfer_transition.document().creator_id(), document_id: document_transfer_transition.base().id(), allow_original: true, created_at: document_transfer_transition.document().created_at(), diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs index 13615711650..b42bc6d560c 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs @@ -32,6 +32,7 @@ impl Drive { contract, document_type, owner_id, + creator_id: document.creator_id(), document_id: document.id(), allow_original, created_at: document.created_at(), diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs index feb615a5046..b1532523ebe 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs @@ -34,6 +34,7 @@ impl Drive { contract, document_type, owner_id, + creator_id: document_update_price_transition.document().creator_id(), document_id: document_update_price_transition.base().id(), allow_original: true, created_at: document_update_price_transition.document().created_at(), diff --git a/packages/rs-sdk-ffi/src/document/create.rs b/packages/rs-sdk-ffi/src/document/create.rs index acbabdbf53e..1412ea0d46e 100644 --- a/packages/rs-sdk-ffi/src/document/create.rs +++ b/packages/rs-sdk-ffi/src/document/create.rs @@ -354,6 +354,7 @@ pub unsafe extern "C" fn dash_sdk_document_make_handle( created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); // Box and return as handle diff --git a/packages/rs-sdk-ffi/src/document/delete.rs b/packages/rs-sdk-ffi/src/document/delete.rs index 8458631a47c..b1dbb23d439 100644 --- a/packages/rs-sdk-ffi/src/document/delete.rs +++ b/packages/rs-sdk-ffi/src/document/delete.rs @@ -413,6 +413,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Box::new(document) diff --git a/packages/rs-sdk-ffi/src/document/price.rs b/packages/rs-sdk-ffi/src/document/price.rs index bf026e3545e..18aea1485c7 100644 --- a/packages/rs-sdk-ffi/src/document/price.rs +++ b/packages/rs-sdk-ffi/src/document/price.rs @@ -349,6 +349,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Box::new(document) diff --git a/packages/rs-sdk-ffi/src/document/purchase.rs b/packages/rs-sdk-ffi/src/document/purchase.rs index 7f2c8c13482..bf55bb6fc11 100644 --- a/packages/rs-sdk-ffi/src/document/purchase.rs +++ b/packages/rs-sdk-ffi/src/document/purchase.rs @@ -395,6 +395,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Box::new(document) diff --git a/packages/rs-sdk-ffi/src/document/put.rs b/packages/rs-sdk-ffi/src/document/put.rs index 18d5b8a58a5..fb2539df325 100644 --- a/packages/rs-sdk-ffi/src/document/put.rs +++ b/packages/rs-sdk-ffi/src/document/put.rs @@ -413,6 +413,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Box::new(document) diff --git a/packages/rs-sdk-ffi/src/document/replace.rs b/packages/rs-sdk-ffi/src/document/replace.rs index 370a1078f51..6b31a179c7f 100644 --- a/packages/rs-sdk-ffi/src/document/replace.rs +++ b/packages/rs-sdk-ffi/src/document/replace.rs @@ -420,6 +420,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Box::new(document) diff --git a/packages/rs-sdk-ffi/src/document/transfer.rs b/packages/rs-sdk-ffi/src/document/transfer.rs index 1b2b3288c08..9dfeeaab883 100644 --- a/packages/rs-sdk-ffi/src/document/transfer.rs +++ b/packages/rs-sdk-ffi/src/document/transfer.rs @@ -404,6 +404,7 @@ mod tests { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); Box::new(document) diff --git a/packages/wasm-sdk/src/state_transitions/documents/mod.rs b/packages/wasm-sdk/src/state_transitions/documents/mod.rs index 7711a46ede3..62d9d475f00 100644 --- a/packages/wasm-sdk/src/state_transitions/documents/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/documents/mod.rs @@ -616,6 +616,7 @@ impl WasmSdk { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); // Fetch the identity to get the correct key @@ -940,6 +941,7 @@ impl WasmSdk { created_at_core_block_height: None, updated_at_core_block_height: None, transferred_at_core_block_height: None, + creator_id: None, }); // Create a delete transition @@ -1049,6 +1051,7 @@ impl WasmSdk { created_at_core_block_height: document.created_at_core_block_height(), updated_at_core_block_height: document.updated_at_core_block_height(), transferred_at_core_block_height: document.transferred_at_core_block_height(), + creator_id: document.creator_id(), }); // Fetch the identity to get the correct key @@ -1182,6 +1185,7 @@ impl WasmSdk { created_at_core_block_height: document.created_at_core_block_height(), updated_at_core_block_height: document.updated_at_core_block_height(), transferred_at_core_block_height: document.transferred_at_core_block_height(), + creator_id: document.creator_id(), }); // Fetch buyer identity @@ -1343,6 +1347,7 @@ impl WasmSdk { created_at_core_block_height: existing_doc.created_at_core_block_height(), updated_at_core_block_height: existing_doc.updated_at_core_block_height(), transferred_at_core_block_height: existing_doc.transferred_at_core_block_height(), + creator_id: existing_doc.creator_id(), }); // Fetch the identity to get the authentication key From b41e27ba0d0af676da53ffc0eecc0f92da6f24bb Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 20:46:35 +0700 Subject: [PATCH 07/13] another one --- .../get_raw_for_document_type/v0/mod.rs | 1 + ...-transferable-unique-creator-id-index.json | 142 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json diff --git a/packages/rs-dpp/src/document/document_methods/get_raw_for_document_type/v0/mod.rs b/packages/rs-dpp/src/document/document_methods/get_raw_for_document_type/v0/mod.rs index 2bcaa11c271..1a2918d368d 100644 --- a/packages/rs-dpp/src/document/document_methods/get_raw_for_document_type/v0/mod.rs +++ b/packages/rs-dpp/src/document/document_methods/get_raw_for_document_type/v0/mod.rs @@ -28,6 +28,7 @@ pub trait DocumentGetRawForDocumentTypeV0: DocumentV0Getters { // returns self.id or self.owner_id if key path is $id or $ownerId "$id" => return Ok(Some(self.id().to_vec())), "$ownerId" => return Ok(Some(self.owner_id().to_vec())), + "$creatorId" => return Ok(self.creator_id().map(|id| id.to_vec())), "$createdAt" => { return Ok(self .created_at() diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json new file mode 100644 index 00000000000..554477ee21b --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json @@ -0,0 +1,142 @@ +{ + "$format_version": "1", + "id": "86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "version": 1, + "documentSchemas": { + "card": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": false, + "transferable": 1, + "properties": { + "name": { + "type": "string", + "description": "Name of the card", + "maxLength": 63, + "position": 0 + }, + "description": { + "type": "string", + "description": "Description of the card", + "maxLength": 256, + "position": 1 + }, + "imageUrl": { + "type": "string", + "description": "URL of the image associated with the card", + "maxLength": 2048, + "format": "uri", + "position": 2 + }, + "imageHash": { + "type": "array", + "description": "SHA256 hash of the bytes of the image specified by imageUrl", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 3 + }, + "imageFingerprint": { + "type": "array", + "description": "dHash of the image specified by imageUrl", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "position": 4 + }, + "attack": { + "type": "integer", + "description": "Attack power of the card", + "minimum": 0, + "position": 5 + }, + "defense": { + "type": "integer", + "description": "Defense level of the card", + "minimum": 0, + "position": 6 + } + }, + "indices": [ + { + "name": "owner", + "properties": [ + { + "$ownerId": "asc" + } + ] + }, + { + "name": "creator", + "properties": [ + { + "$creatorId": "asc" + } + ], + "unique": true + }, + { + "name": "attack", + "properties": [ + { + "attack": "asc" + } + ] + }, + { + "name": "defense", + "properties": [ + { + "defense": "asc" + } + ] + }, + { + "name": "transferredAt", + "properties": [ + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "ownerTransferredAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "transferredAtBlockHeight", + "properties": [ + { + "$transferredAtBlockHeight": "asc" + } + ] + }, + { + "name": "transferredAtCoreBlockHeight", + "properties": [ + { + "$transferredAtCoreBlockHeight": "asc" + } + ] + } + ], + "required": [ + "name", + "$transferredAt", + "$transferredAtBlockHeight", + "$transferredAtCoreBlockHeight", + "attack", + "defense" + ], + "additionalProperties": false + } + } +} \ No newline at end of file From f4bd4a6af32be2697ca12649b5026c315f8cb8dd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 21:08:03 +0700 Subject: [PATCH 08/13] more tests --- .../batch/tests/document/transfer.rs | 1076 +++++++++++++++++ ...-transferable-unique-creator-id-index.json | 2 +- ...unique-creator-id-with-owner-id-index.json | 145 +++ 3 files changed, 1222 insertions(+), 1 deletion(-) create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-with-owner-id-index.json diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs index 721951af2a4..bd367a2aa7b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs @@ -1772,4 +1772,1080 @@ mod transfer_tests { // He had 5, but spent 1 assert_eq!(token_balance, Some(4)); } + + #[test] + fn test_document_creator_id_unique_index_enforcement() { + // This test verifies that a unique index on creator_id is properly enforced throughout + // the complete document lifecycle, ensuring that only one document per creator can exist + // at any time, regardless of ownership changes. + // + // ## Purpose + // The creator_id field is immutable and set once at document creation. A unique index on + // this field enforces a "one document per creator" constraint, which is useful for use + // cases like: + // - Digital collectibles where each creator can mint exactly one item + // - Unique identity documents (e.g., verified credentials, certificates) + // - Limited edition NFTs with single-creator constraints + // + // ## Why This Test Is Important + // This test ensures that: + // 1. The unique constraint prevents duplicate documents from the same creator + // 2. The creator_id remains immutable during transfers (doesn't change with ownership) + // 3. The unique constraint persists even after ownership transfers + // 4. Only document deletion frees up the creator_id for potential reuse + // + // ## Test Scenario + // This test uses a contract where the "card" document type has a unique index on $creatorId. + // The test verifies the following sequence: + // + // 1. Creator creates first document → SUCCESS (creator_id slot is claimed) + // 2. Creator tries to create second document → FAIL (creator_id already used) + // 3. Document is transferred to receiver → SUCCESS (ownership changes, creator_id stays) + // 4. Creator tries to create new document → FAIL (creator_id still claimed despite transfer) + // 5. Receiver deletes the document → SUCCESS (creator_id is freed) + // 6. Creator creates new document → SUCCESS (creator_id now available again) + // + // This proves that creator_id is truly immutable and the unique constraint works correctly + // across transfers, only releasing when the document is deleted from the system entirely. + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json"; + + // Load the contract with unique creator_id index + let contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + // Setup two identities: creator and receiver + let (creator, creator_signer, creator_key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (receiver, receiver_signer, receiver_key) = + setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + assert!(!card_document_type.documents_mutable()); + + // Step 1: Create first document by creator + let entropy1 = Bytes32::random_with_rng(&mut rng); + + let mut document1 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy1, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document1.set("attack", 5.into()); + document1.set("defense", 8.into()); + + let documents_batch_create_transition1 = + BatchTransition::new_document_creation_transition_from_document( + document1.clone(), + card_document_type, + entropy1.0, + &creator_key, + 2, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition1 = documents_batch_create_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 2: Try to create a second document by the same creator + // This should FAIL due to unique creator_id index + let entropy2 = Bytes32::random_with_rng(&mut rng); + + let mut document2 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy2, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document2.set("attack", 3.into()); + document2.set("defense", 6.into()); + + let documents_batch_create_transition2 = + BatchTransition::new_document_creation_transition_from_document( + document2.clone(), + card_document_type, + entropy2.0, + &creator_key, + 3, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition2 = documents_batch_create_transition2 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition2.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because creator already has a document (unique creator_id constraint) + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 3: Transfer the first document to the receiver + document1.set_revision(Some(2)); + + let documents_batch_transfer_transition = + BatchTransition::new_document_transfer_transition_from_document( + document1.clone(), + card_document_type, + receiver.id(), + &creator_key, + 4, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition = documents_batch_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify the document was transferred + let receiver_documents_sql_string = + format!("select * from card where $ownerId == '{}'", receiver.id()); + + let query_receiver_identity_documents = DriveDocumentQuery::from_sql_expr( + receiver_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_receiver_results = platform + .drive + .query_documents( + query_receiver_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + assert_eq!(query_receiver_results.documents().len(), 1); + + // Step 4: Try to create a new document by the creator again after transfer + // This should STILL FAIL because creator_id is immutable and still points to creator + let entropy3 = Bytes32::random_with_rng(&mut rng); + + let mut document3 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy3, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document3.set("attack", 7.into()); + document3.set("defense", 4.into()); + + let documents_batch_create_transition3 = + BatchTransition::new_document_creation_transition_from_document( + document3.clone(), + card_document_type, + entropy3.0, + &creator_key, + 5, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition3 = documents_batch_create_transition3 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition3.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should still fail because creator_id is immutable and the unique constraint still applies + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 5: Receiver deletes the document + document1.set_owner_id(receiver.id()); + document1.set_revision(Some(3)); + + let documents_batch_deletion_transition = + BatchTransition::new_document_deletion_transition_from_document( + document1, + card_document_type, + &receiver_key, + 2, + 0, + None, + &receiver_signer, + platform_version, + None, + ) + .expect("expect to create documents batch deletion transition"); + + let documents_batch_deletion_serialized_transition = documents_batch_deletion_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_deletion_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify the document was deleted + let query_receiver_results = platform + .drive + .query_documents( + query_receiver_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + assert_eq!(query_receiver_results.documents().len(), 0); + + // Step 6: Now creator should be able to create a new document + // This should SUCCEED because the previous document with this creator_id was deleted + let entropy4 = Bytes32::random_with_rng(&mut rng); + + let mut document4 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy4, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document4.set("attack", 9.into()); + document4.set("defense", 2.into()); + + let documents_batch_create_transition4 = + BatchTransition::new_document_creation_transition_from_document( + document4.clone(), + card_document_type, + entropy4.0, + &creator_key, + 6, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition4 = documents_batch_create_transition4 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition4.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should succeed now because the previous document was deleted + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify the new document was created + let creator_documents_sql_string = + format!("select * from card where $ownerId == '{}'", creator.id()); + + let query_creator_identity_documents = DriveDocumentQuery::from_sql_expr( + creator_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_creator_results = platform + .drive + .query_documents(query_creator_identity_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_creator_results.documents().len(), 1); + + // Verify via creator_id query + let creator_id_documents_sql_string = + format!("select * from card where $creatorId == '{}'", creator.id()); + + let query_creator_id_documents = DriveDocumentQuery::from_sql_expr( + creator_id_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_creator_id_results = platform + .drive + .query_documents(query_creator_id_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_creator_id_results.documents().len(), 1); + + let issues = platform + .drive + .grove + .visualize_verify_grovedb(None, true, false, &platform_version.drive.grove_version) + .expect("expected to have no issues"); + + assert_eq!( + issues.len(), + 0, + "issues are {}", + issues + .iter() + .map(|(hash, (a, b, c))| format!("{}: {} {} {}", hash, a, b, c)) + .collect::>() + .join(" | ") + ); + } + + #[test] + fn test_document_owner_and_creator_id_unique_index_enforcement() { + // This test verifies that a unique compound index on (owner_id, creator_id) is properly + // enforced throughout the document lifecycle, allowing a creator to have multiple documents + // but preventing duplicate (owner, creator) combinations. + // + // ## Purpose + // A compound unique index on (owner_id, creator_id) creates a more flexible constraint than + // a simple unique creator_id. It allows: + // - The same creator to create multiple documents (unlike single creator_id uniqueness) + // - Each owner to hold at most one document from any given creator + // - Documents to move between owners while maintaining creator attribution + // + // This is useful for use cases like: + // - Trading card games where one creator can make many cards, but each owner can only hold + // one card from that creator at a time + // - Digital art where collectors can own one piece per artist + // - Subscription or membership tokens where each user can have one active token per issuer + // + // ## Why This Test Is Important + // This test ensures that: + // 1. The compound constraint prevents duplicate (owner, creator) pairs + // 2. Creators can create multiple documents when owned by different people + // 3. Transfers can fail if they would create a duplicate (owner, creator) combination + // 4. Transfers can succeed when they free up a (owner, creator) slot + // 5. Deletion properly frees up the (owner, creator) constraint + // + // ## Test Scenario + // This test uses a contract where the "card" document type has a unique compound index on + // ($ownerId, $creatorId). The test verifies a complex sequence: + // + // 1. Creator creates document1 (owner=creator, creator=creator) → SUCCESS + // 2. Creator tries to create document2 with same owner → FAIL (duplicate (creator, creator)) + // 3. Document1 transferred to receiver → SUCCESS (now (receiver, creator) exists) + // 4. Creator creates document3 → SUCCESS ((creator, creator) is now available) + // 5. Try to transfer document3 to receiver → FAIL ((receiver, creator) already exists) + // 6. Receiver tries to transfer document1 back → FAIL ((creator, creator) occupied by document3) + // 7. Creator deletes document3 → SUCCESS (frees (creator, creator)) + // 8. Receiver transfers document1 back to creator → SUCCESS ((creator, creator) now free) + // + // This proves that the compound unique index correctly tracks the combination of owner and + // creator, allowing flexible document distribution while preventing conflicts. + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-with-owner-id-index.json"; + + // Load the contract with unique (owner_id, creator_id) compound index + let contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + // Setup two identities: creator and receiver + let (creator, creator_signer, creator_key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (receiver, receiver_signer, receiver_key) = + setup_identity(&mut platform, 450, dash_to_credits!(0.1)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + assert!(!card_document_type.documents_mutable()); + + // Step 1: Creator creates first document (owner=creator, creator=creator) + let entropy1 = Bytes32::random_with_rng(&mut rng); + + let mut document1 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy1, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document1.set("attack", 5.into()); + document1.set("defense", 8.into()); + + let documents_batch_create_transition1 = + BatchTransition::new_document_creation_transition_from_document( + document1.clone(), + card_document_type, + entropy1.0, + &creator_key, + 2, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition1 = documents_batch_create_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 2: Try to create second document by same creator with same owner + // This should FAIL due to unique (owner_id, creator_id) constraint + let entropy2 = Bytes32::random_with_rng(&mut rng); + + let mut document2 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy2, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document2.set("attack", 3.into()); + document2.set("defense", 6.into()); + + let documents_batch_create_transition2 = + BatchTransition::new_document_creation_transition_from_document( + document2.clone(), + card_document_type, + entropy2.0, + &creator_key, + 3, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition2 = documents_batch_create_transition2 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition2.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because (creator, creator) combination already exists + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 3: Transfer first document to receiver (changes owner to receiver, creator stays same) + // Now we have (owner=receiver, creator=creator) + document1.set_revision(Some(2)); + + let documents_batch_transfer_transition1 = + BatchTransition::new_document_transfer_transition_from_document( + document1.clone(), + card_document_type, + receiver.id(), + &creator_key, + 4, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition1 = documents_batch_transfer_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 4: Now creator can create another document because (owner=creator, creator=creator) is available + // This should SUCCEED + let entropy3 = Bytes32::random_with_rng(&mut rng); + + let mut document3 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy3, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document3.set("attack", 7.into()); + document3.set("defense", 4.into()); + + let documents_batch_create_transition3 = + BatchTransition::new_document_creation_transition_from_document( + document3.clone(), + card_document_type, + entropy3.0, + &creator_key, + 5, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition3 = documents_batch_create_transition3 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition3.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should succeed because (creator, creator) is now available after transfer + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 5: Try to transfer document3 to receiver + // This should FAIL because (receiver, creator) already exists from document1 + document3.set_revision(Some(2)); + + let documents_batch_transfer_transition3 = + BatchTransition::new_document_transfer_transition_from_document( + document3.clone(), + card_document_type, + receiver.id(), + &creator_key, + 6, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition3 = documents_batch_transfer_transition3 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition3.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because (receiver, creator) combination already exists + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 6: Receiver tries to transfer document1 back to creator + // This should FAIL because (creator, creator) now exists from document3 + document1.set_owner_id(receiver.id()); + document1.set_revision(Some(3)); + + let documents_batch_transfer_transition_back = + BatchTransition::new_document_transfer_transition_from_document( + document1.clone(), + card_document_type, + creator.id(), + &receiver_key, + 2, + 0, + None, + &receiver_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition_back = + documents_batch_transfer_transition_back + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition_back.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because (creator, creator) is occupied by document3 + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 7: Delete document3 (which has owner=creator, creator=creator) + document3.set_revision(Some(3)); + + let documents_batch_deletion_transition = + BatchTransition::new_document_deletion_transition_from_document( + document3, + card_document_type, + &creator_key, + 7, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch deletion transition"); + + let documents_batch_deletion_serialized_transition = documents_batch_deletion_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_deletion_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 8: Now receiver can transfer document1 back to creator + // This should SUCCEED because (creator, creator) is now available + document1.set_revision(Some(4)); + + let documents_batch_transfer_transition_back2 = + BatchTransition::new_document_transfer_transition_from_document( + document1.clone(), + card_document_type, + creator.id(), + &receiver_key, + 3, + 0, + None, + &receiver_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for transfer"); + + let documents_batch_transfer_serialized_transition_back2 = + documents_batch_transfer_transition_back2 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_transfer_serialized_transition_back2.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should succeed now because (creator, creator) is available after deletion + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify final state: creator has the document back + let creator_documents_sql_string = + format!("select * from card where $ownerId == '{}'", creator.id()); + + let query_creator_identity_documents = DriveDocumentQuery::from_sql_expr( + creator_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_creator_results = platform + .drive + .query_documents(query_creator_identity_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_creator_results.documents().len(), 1); + + let receiver_documents_sql_string = + format!("select * from card where $ownerId == '{}'", receiver.id()); + + let query_receiver_identity_documents = DriveDocumentQuery::from_sql_expr( + receiver_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_receiver_results = platform + .drive + .query_documents(query_receiver_identity_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_receiver_results.documents().len(), 0); + + let issues = platform + .drive + .grove + .visualize_verify_grovedb(None, true, false, &platform_version.drive.grove_version) + .expect("expected to have no issues"); + + assert_eq!( + issues.len(), + 0, + "issues are {}", + issues + .iter() + .map(|(hash, (a, b, c))| format!("{}: {} {} {}", hash, a, b, c)) + .collect::>() + .join(" | ") + ); + } } diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json index 554477ee21b..2ad54677b80 100644 --- a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-index.json @@ -7,7 +7,7 @@ "card": { "type": "object", "documentsMutable": false, - "canBeDeleted": false, + "canBeDeleted": true, "transferable": 1, "properties": { "name": { diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-with-owner-id-index.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-with-owner-id-index.json new file mode 100644 index 00000000000..4df4ff7a395 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-all-transferable-unique-creator-id-with-owner-id-index.json @@ -0,0 +1,145 @@ +{ + "$format_version": "1", + "id": "86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "version": 1, + "documentSchemas": { + "card": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": true, + "transferable": 1, + "properties": { + "name": { + "type": "string", + "description": "Name of the card", + "maxLength": 63, + "position": 0 + }, + "description": { + "type": "string", + "description": "Description of the card", + "maxLength": 256, + "position": 1 + }, + "imageUrl": { + "type": "string", + "description": "URL of the image associated with the card", + "maxLength": 2048, + "format": "uri", + "position": 2 + }, + "imageHash": { + "type": "array", + "description": "SHA256 hash of the bytes of the image specified by imageUrl", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 3 + }, + "imageFingerprint": { + "type": "array", + "description": "dHash of the image specified by imageUrl", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "position": 4 + }, + "attack": { + "type": "integer", + "description": "Attack power of the card", + "minimum": 0, + "position": 5 + }, + "defense": { + "type": "integer", + "description": "Defense level of the card", + "minimum": 0, + "position": 6 + } + }, + "indices": [ + { + "name": "owner", + "properties": [ + { + "$ownerId": "asc" + } + ] + }, + { + "name": "owner_creator", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$creatorId": "asc" + } + ], + "unique": true + }, + { + "name": "attack", + "properties": [ + { + "attack": "asc" + } + ] + }, + { + "name": "defense", + "properties": [ + { + "defense": "asc" + } + ] + }, + { + "name": "transferredAt", + "properties": [ + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "ownerTransferredAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "transferredAtBlockHeight", + "properties": [ + { + "$transferredAtBlockHeight": "asc" + } + ] + }, + { + "name": "transferredAtCoreBlockHeight", + "properties": [ + { + "$transferredAtCoreBlockHeight": "asc" + } + ] + } + ], + "required": [ + "name", + "$transferredAt", + "$transferredAtBlockHeight", + "$transferredAtCoreBlockHeight", + "attack", + "defense" + ], + "additionalProperties": false + } + } +} \ No newline at end of file From e3064a9482f8a1d5aa50efd257316dd5d60e4484 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 30 Sep 2025 23:54:19 +0700 Subject: [PATCH 09/13] temp --- .../validate_uniqueness_of_data/mod.rs | 110 ++++++-- .../validate_uniqueness_of_data/v0/mod.rs | 14 +- .../validate_uniqueness_of_data/v1/mod.rs | 236 ++++++++++++++++++ 3 files changed, 334 insertions(+), 26 deletions(-) create mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs index ac3082ff69b..acdb5a76325 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs @@ -1,4 +1,5 @@ mod v0; +mod v1; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -16,6 +17,11 @@ use dpp::version::PlatformVersion; use grovedb::TransactionArg; use std::collections::BTreeMap; +pub(in crate::drive::document::index_uniqueness) enum UniquenessOfDataRequest<'a> { + V0(UniquenessOfDataRequestV0<'a>), + V1(UniquenessOfDataRequestV1<'a>), +} + /// Represents a request to determine the uniqueness of data. /// This structure is defined to handle index uniqueness within a document. /// The purpose is to encapsulate all the required parameters to determine @@ -25,15 +31,13 @@ use std::collections::BTreeMap; /// with index uniqueness methods. Any change here might necessitate changes across /// all those methods. Given the likely infrequent need for changes, this design choice /// is deemed acceptable. -pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequest<'a> { +pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV0<'a> { /// Reference to the associated data contract. pub contract: &'a DataContract, /// Reference of the document type. pub document_type: DocumentTypeRef<'a>, /// The ID representing the owner. pub owner_id: Identifier, - /// The ID representing the original creator. - pub creator_id: Option, /// The ID of the document in question. pub document_id: Identifier, /// A flag indicating if the original (existing) document is considered permissible. @@ -60,6 +64,91 @@ pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequest< pub data: &'a BTreeMap, } +/// A request object used when verifying the uniqueness of document data +/// within the platform. It bundles together references to the data contract, +/// document type, identifying fields, timestamps, and the actual data map +/// being checked for uniqueness constraints. +/// +/// This structure is versioned (`V1`) to allow evolution of the +/// uniqueness-checking logic without breaking existing code. +pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV1<'a> { + /// Reference to the data contract that defines the schema + /// and uniqueness rules for this document. + pub contract: &'a DataContract, + + /// Reference to the document type within the contract + /// that this uniqueness check applies to. + pub document_type: DocumentTypeRef<'a>, + + /// Identifier of the current owner of the document. + pub owner_id: Identifier, + + /// Indicates whether the `owner_id` field was modified + /// in this update operation. + pub changed_owner_id: bool, + + /// Identifier of the original creator of the document. + /// This may differ from `owner_id` if ownership has changed. + pub creator_id: Option, + + /// Identifier of the document whose data is being checked. + pub document_id: Identifier, + + /// Timestamp (in milliseconds) when the document was first created. + pub created_at: Option, + + /// Timestamp (in milliseconds) when the document was last updated. + pub updated_at: Option, + + /// Indicates whether the `updated_at` field was modified. + pub changed_updated_at: bool, + + /// Timestamp (in milliseconds) when the document was last transferred + /// (ownership change event). + pub transferred_at: Option, + + /// Indicates whether the `transferred_at` field was modified. + pub changed_transferred_at: bool, + + /// Block height at which the document was originally created. + pub created_at_block_height: Option, + + /// Block height at which the document was last updated. + pub updated_at_block_height: Option, + + /// Indicates whether the `updated_at_block_height` field was modified. + pub changed_updated_at_block_height: bool, + + /// Block height at which the document was last transferred. + pub transferred_at_block_height: Option, + + /// Indicates whether the `transferred_at_block_height` field was modified. + pub changed_transferred_at_block_height: bool, + + /// Core chain block height at which the document was created. + pub created_at_core_block_height: Option, + + /// Core chain block height at which the document was last updated. + pub updated_at_core_block_height: Option, + + /// Indicates whether the `updated_at_core_block_height` field was modified. + pub changed_updated_at_core_block_height: bool, + + /// Core chain block height at which the document was last transferred. + pub transferred_at_core_block_height: Option, + + /// Indicates whether the `transferred_at_core_block_height` field was modified. + pub changed_transferred_at_core_block_height: bool, + + /// The map of field names to values representing the document’s data. + /// This is the primary content to be checked against uniqueness indexes. + pub data: &'a BTreeMap, + + /// A list of keys in the `data` map whose values have changed, + /// used to limit uniqueness checks only to modified fields. + pub changed_data_values: Vec<&'a String>, +} + impl Drive { /// Internal method validating uniqueness /// @@ -83,19 +172,10 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - match platform_version - .drive - .methods - .document - .index_uniqueness - .validate_uniqueness_of_data + match request { - 0 => self.validate_uniqueness_of_data_v0(request, transaction, platform_version), - version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "validate_uniqueness_of_data".to_string(), - known_versions: vec![0], - received: version, - })), + UniquenessOfDataRequest::V0(v0) => self.validate_uniqueness_of_data_v0(v0, transaction, platform_version), + UniquenessOfDataRequest::V1(v1) => self.validate_uniqueness_of_data_v1(v1, transaction, platform_version), } } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs index 50c7a62c3c0..6a8d2412997 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs @@ -1,6 +1,6 @@ use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{UniquenessOfDataRequest, UniquenessOfDataRequestV0}; use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; use crate::error::Error; use crate::query::{DriveDocumentQuery, InternalClauses, WhereClause, WhereOperator}; @@ -37,15 +37,14 @@ impl Drive { #[inline(always)] pub(super) fn validate_uniqueness_of_data_v0( &self, - request: UniquenessOfDataRequest, + request: UniquenessOfDataRequestV0, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let UniquenessOfDataRequest { + let UniquenessOfDataRequestV0 { contract, document_type, owner_id, - creator_id, document_id, allow_original, created_at, @@ -83,13 +82,6 @@ impl Drive { return None; } } - property_names::CREATOR_ID => { - if let Some(creator_id) = creator_id { - platform_value!(creator_id) - } else { - return None; - } - } property_names::UPDATED_AT => { if let Some(updated_at) = updated_at { platform_value!(updated_at) diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs new file mode 100644 index 00000000000..40367774a7b --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs @@ -0,0 +1,236 @@ +use crate::drive::Drive; + +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{UniquenessOfDataRequest, UniquenessOfDataRequestV0, UniquenessOfDataRequestV1}; +use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; +use crate::error::Error; +use crate::query::{DriveDocumentQuery, InternalClauses, WhereClause, WhereOperator}; +use dpp::consensus::state::document::duplicate_unique_index_error::DuplicateUniqueIndexError; +use dpp::consensus::state::state_error::StateError; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::document::{property_names, DocumentV0Getters}; +use dpp::platform_value::platform_value; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; +use std::collections::BTreeMap; + +impl Drive { + /// Validates the uniqueness of data for version 1. + /// + /// This method checks if a given data, within the context of its associated contract and + /// document type, is unique. If an index is not flagged as unique, it is considered non-problematic. + /// If all required fields for uniqueness are present and the data is found to be unique, + /// it returns a successful validation result. + /// + /// # Arguments + /// + /// * `request`: The data and related metadata to be checked for uniqueness. + /// * `transaction`: The transaction associated with this check. + /// * `platform_version`: The version of the platform being used. + /// + /// # Returns + /// + /// A `Result`, which either: + /// + /// * Contains a validation result indicating if the data is unique or not, or + /// * An error that occurred during the operation. + #[inline(always)] + pub(super) fn validate_uniqueness_of_data_v1( + &self, + request: UniquenessOfDataRequestV1, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let UniquenessOfDataRequestV1 { + contract, + document_type, + owner_id, + changed_owner_id, creator_id, document_id, + created_at, + updated_at, + changed_updated_at, transferred_at, + changed_transferred_at, created_at_block_height, + updated_at_block_height, + changed_updated_at_block_height, transferred_at_block_height, + changed_transferred_at_block_height, created_at_core_block_height, + updated_at_core_block_height, + changed_updated_at_core_block_height, transferred_at_core_block_height, + changed_transferred_at_core_block_height, data, changed_data_values, + } = request; + + let validation_results = document_type + .indexes() + .values() + .filter_map(|index| { + if !index.unique { + // if an index is not unique there is no issue + None + } else { + let where_queries = index + .properties + .iter() + .filter_map(|property| { + let value = match property.name.as_str() { + property_names::OWNER_ID => { + platform_value!(owner_id) + } + property_names::CREATOR_ID => { + if let Some(creator_id) = creator_id { + platform_value!(creator_id) + } else { + return None; + } + } + property_names::CREATED_AT => { + if let Some(created_at) = created_at { + platform_value!(created_at) + } else { + return None; + } + } + property_names::UPDATED_AT => { + if let Some(updated_at) = updated_at { + platform_value!(updated_at) + } else { + return None; + } + } + property_names::TRANSFERRED_AT => { + if let Some(transferred_at) = transferred_at { + platform_value!(transferred_at) + } else { + return None; + } + } + property_names::CREATED_AT_BLOCK_HEIGHT => { + if let Some(created_at_block_height) = created_at_block_height { + platform_value!(created_at_block_height) + } else { + return None; + } + } + property_names::UPDATED_AT_BLOCK_HEIGHT => { + if let Some(updated_at_block_height) = updated_at_block_height { + platform_value!(updated_at_block_height) + } else { + return None; + } + } + property_names::TRANSFERRED_AT_BLOCK_HEIGHT => { + if let Some(transferred_at_block_height) = + transferred_at_block_height + { + platform_value!(transferred_at_block_height) + } else { + return None; + } + } + property_names::CREATED_AT_CORE_BLOCK_HEIGHT => { + if let Some(created_at_core_block_height) = + created_at_core_block_height + { + platform_value!(created_at_core_block_height) + } else { + return None; + } + } + property_names::UPDATED_AT_CORE_BLOCK_HEIGHT => { + if let Some(updated_at_core_block_height) = + updated_at_core_block_height + { + platform_value!(updated_at_core_block_height) + } else { + return None; + } + } + property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT => { + if let Some(transferred_at_core_block_height) = + transferred_at_core_block_height + { + platform_value!(transferred_at_core_block_height) + } else { + return None; + } + } + _ => { + if let Some(value) = data.get(property.name.as_str()) { + value.clone() + } else { + return None; + } + } + }; + Some(( + property.name.clone(), + WhereClause { + field: property.name.clone(), + operator: WhereOperator::Equal, + value, + }, + )) + }) + .collect::>(); + + if where_queries.len() < index.properties.len() { + // there are empty fields, which means that the index is no longer unique + None + } else { + let query = DriveDocumentQuery { + contract, + document_type, + internal_clauses: InternalClauses { + primary_key_in_clause: None, + primary_key_equal_clause: None, + in_clause: None, + range_clause: None, + equal_clauses: where_queries, + }, + offset: None, + limit: Some(1), + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time_ms: None, + }; + + // todo: deal with cost of this operation + let query_result = self.query_documents( + query, + None, + false, + transaction, + Some(platform_version.protocol_version), + ); + match query_result { + Ok(query_outcome) => { + let documents = query_outcome.documents_owned(); + let would_be_unique = documents.is_empty() + || (allow_original + && documents.len() == 1 + && documents[0].id() == document_id); + if would_be_unique { + Some(Ok(SimpleConsensusValidationResult::default())) + } else { + Some(Ok(SimpleConsensusValidationResult::new_with_error( + StateError::DuplicateUniqueIndexError( + DuplicateUniqueIndexError::new( + document_id, + index.property_names(), + ), + ) + .into(), + ))) + } + } + Err(e) => Some(Err(e)), + } + } + } + }) + .collect::, Error>>()?; + + Ok(SimpleConsensusValidationResult::merge_many_errors( + validation_results, + )) + } +} From b4a429f01166113fee510ace3fbcd588abac5541 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 1 Oct 2025 15:07:23 +0700 Subject: [PATCH 10/13] final fixes --- .../batch/tests/document/transfer.rs | 2 +- .../batch/transformer/v0/mod.rs | 28 +- .../tests/strategy_tests/query.rs | 2 +- .../tests/strategy_tests/strategy.rs | 2 +- .../tests/strategy_tests/token_tests.rs | 10 +- .../validate_uniqueness_of_data/mod.rs | 86 ++-- .../validate_uniqueness_of_data/v0/mod.rs | 2 +- .../validate_uniqueness_of_data/v1/mod.rs | 401 +++++++++++++----- .../drive/document/index_uniqueness/mod.rs | 1 - .../mod.rs | 11 +- .../v0/mod.rs | 18 +- .../v1/mod.rs | 126 ++++++ .../mod.rs | 11 +- .../v0/mod.rs | 7 +- .../v1/mod.rs | 78 ++++ .../mod.rs | 11 +- .../v0/mod.rs | 7 +- .../v1/mod.rs | 69 +++ .../mod.rs | 10 +- .../v0/mod.rs | 7 +- .../v1/mod.rs | 73 ++++ .../validate_document_uniqueness/mod.rs | 72 ---- .../validate_document_uniqueness/v0/mod.rs | 51 --- .../mod.rs | 11 +- .../v0/mod.rs | 7 +- .../v1/mod.rs | 78 ++++ .../document_replace_transition_action/mod.rs | 8 +- .../transformer.rs | 20 +- .../v0/mod.rs | 9 +- .../v0/transformer.rs | 75 +++- .../drive_document_method_versions/mod.rs | 3 +- .../drive_document_method_versions/v1.rs | 2 - .../drive_document_method_versions/v2.rs | 72 ++++ .../src/version/drive_versions/mod.rs | 1 + .../src/version/drive_versions/v5.rs | 108 +++++ .../rs-platform-version/src/version/v10.rs | 4 +- 36 files changed, 1097 insertions(+), 386 deletions(-) create mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v1/mod.rs create mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs create mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v1/mod.rs create mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v1/mod.rs delete mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/mod.rs delete mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v1/mod.rs create mode 100644 packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs create mode 100644 packages/rs-platform-version/src/version/drive_versions/v5.rs diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs index bd367a2aa7b..90ac2a1018d 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs @@ -2748,7 +2748,7 @@ mod transfer_tests { // Step 8: Now receiver can transfer document1 back to creator // This should SUCCEED because (creator, creator) is now available - document1.set_revision(Some(4)); + document1.set_revision(Some(3)); let documents_batch_transfer_transition_back2 = BatchTransition::new_document_transfer_transition_from_document( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs index 48f49adfed6..f478107bc60 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/mod.rs @@ -688,26 +688,6 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { let original_document = validation_result.into_data()?; - // There is a case where we updated a just deleted document - // In this case we don't care about the created at - let original_document_created_at = original_document.created_at(); - - let original_document_created_at_block_height = - original_document.created_at_block_height(); - - let original_document_created_at_core_block_height = - original_document.created_at_core_block_height(); - - let original_document_transferred_at = original_document.transferred_at(); - - let original_document_transferred_at_block_height = - original_document.transferred_at_block_height(); - - let original_document_transferred_at_core_block_height = - original_document.transferred_at_core_block_height(); - - let original_creator_id = original_document.creator_id(); - let validation_result = Self::check_ownership_of_old_replaced_document_v0( document_replace_transition.base().id(), original_document, @@ -739,13 +719,7 @@ impl BatchTransitionInternalTransformerV0 for BatchTransition { DocumentReplaceTransitionAction::try_from_borrowed_document_replace_transition( document_replace_transition, owner_id, - original_document_created_at, - original_document_created_at_block_height, - original_document_created_at_core_block_height, - original_document_transferred_at, - original_document_transferred_at_block_height, - original_document_transferred_at_core_block_height, - original_creator_id, + original_document, block_info, user_fee_increase, |_identifier| Ok(data_contract_fetch_info.clone()), diff --git a/packages/rs-drive-abci/tests/strategy_tests/query.rs b/packages/rs-drive-abci/tests/strategy_tests/query.rs index 4e7c2386b83..c19787ba366 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/query.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/query.rs @@ -833,7 +833,7 @@ mod tests { assert!(!proof.signature.is_empty()); // Verify the proof - let (root_hash, finalized_epoch_infos) = + let (_root_hash, finalized_epoch_infos) = Drive::verify_finalized_epoch_infos( &proof.grovedb_proof, 0, diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs index 2ab8c6fdbf8..2e38db5aac4 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -1623,7 +1623,7 @@ impl NetworkStrategy { // Handle the Result returned by identity_state_transitions_for_block let (mut identities, mut state_transitions) = match identity_state_transitions_result { Ok(transitions) => transitions.into_iter().unzip(), - Err(error) => (vec![], vec![]), + Err(_) => (vec![], vec![]), }; current_identities.append(&mut identities); diff --git a/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs index a67876d2a2a..bc6e126986a 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs @@ -6,7 +6,7 @@ mod tests { use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy, GENESIS_TIME_MS}; use crate::strategy::{ChainExecutionOutcome, ChainExecutionParameters, NetworkStrategy, StrategyRandomness}; use dpp::dash_to_duffs; - use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters}; + use dpp::data_contract::accessors::v0::DataContractV0Setters; use dpp::data_contract::accessors::v1::DataContractV1Getters; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Setters; @@ -484,7 +484,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -1002,7 +1001,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -1273,7 +1271,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -1380,7 +1377,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -1599,7 +1595,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -1871,7 +1866,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -1978,7 +1972,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) @@ -2197,7 +2190,6 @@ mod tests { let state = platform.state.load(); { - let counter = platform.drive.cache.protocol_versions_counter.read(); platform .drive .fetch_versions_with_counter(None, &platform_version.drive) diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs index acdb5a76325..a5879c25f3b 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/mod.rs @@ -2,8 +2,8 @@ mod v0; mod v1; use crate::drive::Drive; -use crate::error::drive::DriveError; use crate::error::Error; +use std::borrow::Cow; use dpp::data_contract::document_type::DocumentTypeRef; use dpp::data_contract::DataContract; @@ -12,14 +12,16 @@ use dpp::identifier::Identifier; use dpp::platform_value::Value; use dpp::prelude::{BlockHeight, CoreBlockHeight, TimestampMillis}; +use derive_more::From; use dpp::validation::SimpleConsensusValidationResult; use dpp::version::PlatformVersion; use grovedb::TransactionArg; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; -pub(in crate::drive::document::index_uniqueness) enum UniquenessOfDataRequest<'a> { +#[derive(Debug, From)] +pub(in crate::drive::document::index_uniqueness) enum UniquenessOfDataRequest<'a, 'b> { V0(UniquenessOfDataRequestV0<'a>), - V1(UniquenessOfDataRequestV1<'a>), + V1(UniquenessOfDataRequestV1<'a, 'b>), } /// Represents a request to determine the uniqueness of data. @@ -31,6 +33,7 @@ pub(in crate::drive::document::index_uniqueness) enum UniquenessOfDataRequest<'a /// with index uniqueness methods. Any change here might necessitate changes across /// all those methods. Given the likely infrequent need for changes, this design choice /// is deemed acceptable. +#[derive(Debug)] pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV0<'a> { /// Reference to the associated data contract. pub contract: &'a DataContract, @@ -71,7 +74,8 @@ pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV /// /// This structure is versioned (`V1`) to allow evolution of the /// uniqueness-checking logic without breaking existing code. -pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV1<'a> { +#[derive(Debug)] +pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV1<'a, 'b> { /// Reference to the data contract that defines the schema /// and uniqueness rules for this document. pub contract: &'a DataContract, @@ -83,10 +87,6 @@ pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV /// Identifier of the current owner of the document. pub owner_id: Identifier, - /// Indicates whether the `owner_id` field was modified - /// in this update operation. - pub changed_owner_id: bool, - /// Identifier of the original creator of the document. /// This may differ from `owner_id` if ownership has changed. pub creator_id: Option, @@ -100,53 +100,68 @@ pub(in crate::drive::document::index_uniqueness) struct UniquenessOfDataRequestV /// Timestamp (in milliseconds) when the document was last updated. pub updated_at: Option, - /// Indicates whether the `updated_at` field was modified. - pub changed_updated_at: bool, - /// Timestamp (in milliseconds) when the document was last transferred /// (ownership change event). pub transferred_at: Option, - /// Indicates whether the `transferred_at` field was modified. - pub changed_transferred_at: bool, - /// Block height at which the document was originally created. pub created_at_block_height: Option, /// Block height at which the document was last updated. pub updated_at_block_height: Option, - /// Indicates whether the `updated_at_block_height` field was modified. - pub changed_updated_at_block_height: bool, - /// Block height at which the document was last transferred. pub transferred_at_block_height: Option, - /// Indicates whether the `transferred_at_block_height` field was modified. - pub changed_transferred_at_block_height: bool, - /// Core chain block height at which the document was created. pub created_at_core_block_height: Option, /// Core chain block height at which the document was last updated. pub updated_at_core_block_height: Option, - /// Indicates whether the `updated_at_core_block_height` field was modified. - pub changed_updated_at_core_block_height: bool, - /// Core chain block height at which the document was last transferred. pub transferred_at_core_block_height: Option, - /// Indicates whether the `transferred_at_core_block_height` field was modified. - pub changed_transferred_at_core_block_height: bool, - /// The map of field names to values representing the document’s data. /// This is the primary content to be checked against uniqueness indexes. pub data: &'a BTreeMap, - /// A list of keys in the `data` map whose values have changed, - /// used to limit uniqueness checks only to modified fields. - pub changed_data_values: Vec<&'a String>, + /// The type of uniqueness of data request + pub update_type: UniquenessOfDataRequestUpdateType<'b>, +} + +#[derive(Debug)] +pub enum UniquenessOfDataRequestUpdateType<'a> { + /// It's a new document, all unique index couples should not yet exist in the state + NewDocument, + /// It's a changed document, unique index couples should already exist in the state for series + /// of values that have not changed. + /// For example if you have the owner_id and a value like car_type that has a unique index, and + /// you have another unique index owner_id and updated_at. The document changes a value of car + /// color and updated at. Since the car type does not change, we expect that in the state we + /// will still have the couple however we should not have + /// . Knowing what changed allows us to know in advance if there + /// will be an issue with the unique indexes. + ChangedDocument { + /// Indicates whether the `owner_id` field was modified + /// in this update operation. + changed_owner_id: bool, + /// Indicates whether the `updated_at` field was modified. + changed_updated_at: bool, + /// Indicates whether the `transferred_at` field was modified. + changed_transferred_at: bool, + /// Indicates whether the `updated_at_block_height` field was modified. + changed_updated_at_block_height: bool, + /// Indicates whether the `transferred_at_block_height` field was modified. + changed_transferred_at_block_height: bool, + /// Indicates whether the `updated_at_core_block_height` field was modified. + changed_updated_at_core_block_height: bool, + /// Indicates whether the `transferred_at_core_block_height` field was modified. + changed_transferred_at_core_block_height: bool, + /// A list of keys in the `data` map whose values have changed, + /// used to limit uniqueness checks only to modified fields. + changed_data_values: Cow<'a, BTreeSet>, + }, } impl Drive { @@ -172,10 +187,13 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - match request - { - UniquenessOfDataRequest::V0(v0) => self.validate_uniqueness_of_data_v0(v0, transaction, platform_version), - UniquenessOfDataRequest::V1(v1) => self.validate_uniqueness_of_data_v1(v1, transaction, platform_version), + match request { + UniquenessOfDataRequest::V0(v0) => { + self.validate_uniqueness_of_data_v0(v0, transaction, platform_version) + } + UniquenessOfDataRequest::V1(v1) => { + self.validate_uniqueness_of_data_v1(v1, transaction, platform_version) + } } } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs index 6a8d2412997..cf04062ee65 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs @@ -1,6 +1,6 @@ use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{UniquenessOfDataRequest, UniquenessOfDataRequestV0}; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequestV0; use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; use crate::error::Error; use crate::query::{DriveDocumentQuery, InternalClauses, WhereClause, WhereOperator}; diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs index 40367774a7b..c9c4b80eb0f 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs @@ -1,6 +1,8 @@ use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{UniquenessOfDataRequest, UniquenessOfDataRequestV0, UniquenessOfDataRequestV1}; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{ + UniquenessOfDataRequestUpdateType, UniquenessOfDataRequestV1, +}; use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; use crate::error::Error; use crate::query::{DriveDocumentQuery, InternalClauses, WhereClause, WhereOperator}; @@ -45,17 +47,19 @@ impl Drive { contract, document_type, owner_id, - changed_owner_id, creator_id, document_id, + creator_id, + document_id, created_at, updated_at, - changed_updated_at, transferred_at, - changed_transferred_at, created_at_block_height, + transferred_at, + created_at_block_height, updated_at_block_height, - changed_updated_at_block_height, transferred_at_block_height, - changed_transferred_at_block_height, created_at_core_block_height, + transferred_at_block_height, + created_at_core_block_height, updated_at_core_block_height, - changed_updated_at_core_block_height, transferred_at_core_block_height, - changed_transferred_at_core_block_height, data, changed_data_values, + transferred_at_core_block_height, + data, + update_type, } = request; let validation_results = document_type @@ -66,110 +70,283 @@ impl Drive { // if an index is not unique there is no issue None } else { - let where_queries = index - .properties - .iter() - .filter_map(|property| { - let value = match property.name.as_str() { - property_names::OWNER_ID => { - platform_value!(owner_id) - } - property_names::CREATOR_ID => { - if let Some(creator_id) = creator_id { - platform_value!(creator_id) - } else { - return None; - } - } - property_names::CREATED_AT => { - if let Some(created_at) = created_at { - platform_value!(created_at) - } else { - return None; - } - } - property_names::UPDATED_AT => { - if let Some(updated_at) = updated_at { - platform_value!(updated_at) - } else { - return None; - } - } - property_names::TRANSFERRED_AT => { - if let Some(transferred_at) = transferred_at { - platform_value!(transferred_at) - } else { - return None; - } - } - property_names::CREATED_AT_BLOCK_HEIGHT => { - if let Some(created_at_block_height) = created_at_block_height { - platform_value!(created_at_block_height) - } else { - return None; - } - } - property_names::UPDATED_AT_BLOCK_HEIGHT => { - if let Some(updated_at_block_height) = updated_at_block_height { - platform_value!(updated_at_block_height) - } else { - return None; - } - } - property_names::TRANSFERRED_AT_BLOCK_HEIGHT => { - if let Some(transferred_at_block_height) = - transferred_at_block_height - { - platform_value!(transferred_at_block_height) - } else { - return None; - } - } - property_names::CREATED_AT_CORE_BLOCK_HEIGHT => { - if let Some(created_at_core_block_height) = - created_at_core_block_height - { - platform_value!(created_at_core_block_height) - } else { - return None; - } - } - property_names::UPDATED_AT_CORE_BLOCK_HEIGHT => { - if let Some(updated_at_core_block_height) = - updated_at_core_block_height - { - platform_value!(updated_at_core_block_height) - } else { - return None; - } - } - property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT => { - if let Some(transferred_at_core_block_height) = - transferred_at_core_block_height - { - platform_value!(transferred_at_core_block_height) - } else { - return None; - } - } - _ => { - if let Some(value) = data.get(property.name.as_str()) { - value.clone() - } else { - return None; - } - } - }; - Some(( - property.name.clone(), - WhereClause { - field: property.name.clone(), - operator: WhereOperator::Equal, - value, - }, - )) - }) - .collect::>(); + let (where_queries, allow_original) = match &update_type { + UniquenessOfDataRequestUpdateType::NewDocument => { + let where_queries = index + .properties + .iter() + .filter_map(|property| { + let value = match property.name.as_str() { + property_names::OWNER_ID => { + platform_value!(owner_id) + } + property_names::CREATOR_ID => { + if let Some(creator_id) = creator_id { + platform_value!(creator_id) + } else { + return None; + } + } + property_names::CREATED_AT => { + if let Some(created_at) = created_at { + platform_value!(created_at) + } else { + return None; + } + } + property_names::UPDATED_AT => { + if let Some(updated_at) = updated_at { + platform_value!(updated_at) + } else { + return None; + } + } + property_names::TRANSFERRED_AT => { + if let Some(transferred_at) = transferred_at { + platform_value!(transferred_at) + } else { + return None; + } + } + property_names::CREATED_AT_BLOCK_HEIGHT => { + if let Some(created_at_block_height) = + created_at_block_height + { + platform_value!(created_at_block_height) + } else { + return None; + } + } + property_names::UPDATED_AT_BLOCK_HEIGHT => { + if let Some(updated_at_block_height) = + updated_at_block_height + { + platform_value!(updated_at_block_height) + } else { + return None; + } + } + property_names::TRANSFERRED_AT_BLOCK_HEIGHT => { + if let Some(transferred_at_block_height) = + transferred_at_block_height + { + platform_value!(transferred_at_block_height) + } else { + return None; + } + } + property_names::CREATED_AT_CORE_BLOCK_HEIGHT => { + if let Some(created_at_core_block_height) = + created_at_core_block_height + { + platform_value!(created_at_core_block_height) + } else { + return None; + } + } + property_names::UPDATED_AT_CORE_BLOCK_HEIGHT => { + if let Some(updated_at_core_block_height) = + updated_at_core_block_height + { + platform_value!(updated_at_core_block_height) + } else { + return None; + } + } + property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT => { + if let Some(transferred_at_core_block_height) = + transferred_at_core_block_height + { + platform_value!(transferred_at_core_block_height) + } else { + return None; + } + } + _ => { + if let Some(value) = data.get(property.name.as_str()) { + value.clone() + } else { + return None; + } + } + }; + Some(( + property.name.clone(), + WhereClause { + field: property.name.clone(), + operator: WhereOperator::Equal, + value, + }, + )) + }) + .collect::>(); + (where_queries, false) + } + UniquenessOfDataRequestUpdateType::ChangedDocument { + changed_owner_id, + changed_updated_at, + changed_transferred_at, + changed_updated_at_block_height, + changed_transferred_at_block_height, + changed_updated_at_core_block_height, + changed_transferred_at_core_block_height, + changed_data_values, + } => { + let mut allow_original = true; + let mut exit_early = false; + let where_queries = index + .properties + .iter() + .filter_map(|property| { + let value = match property.name.as_str() { + property_names::OWNER_ID => { + if *changed_owner_id { + allow_original = false; + } + platform_value!(owner_id) + } + property_names::CREATOR_ID => { + if let Some(creator_id) = creator_id { + platform_value!(creator_id) + } else { + exit_early = true; + return None; + } + } + property_names::CREATED_AT => { + if let Some(created_at) = created_at { + platform_value!(created_at) + } else { + exit_early = true; + return None; + } + } + property_names::UPDATED_AT => { + if *changed_updated_at { + allow_original = false; + } + if let Some(updated_at) = updated_at { + platform_value!(updated_at) + } else { + exit_early = true; + return None; + } + } + property_names::TRANSFERRED_AT => { + if *changed_transferred_at { + allow_original = false; + } + if let Some(transferred_at) = transferred_at { + platform_value!(transferred_at) + } else { + exit_early = true; + return None; + } + } + property_names::CREATED_AT_BLOCK_HEIGHT => { + if let Some(created_at_block_height) = + created_at_block_height + { + platform_value!(created_at_block_height) + } else { + exit_early = true; + return None; + } + } + property_names::UPDATED_AT_BLOCK_HEIGHT => { + if *changed_updated_at_block_height { + allow_original = false; + } + if let Some(updated_at_block_height) = + updated_at_block_height + { + platform_value!(updated_at_block_height) + } else { + exit_early = true; + return None; + } + } + property_names::TRANSFERRED_AT_BLOCK_HEIGHT => { + if *changed_transferred_at_block_height { + allow_original = false; + } + if let Some(transferred_at_block_height) = + transferred_at_block_height + { + platform_value!(transferred_at_block_height) + } else { + exit_early = true; + return None; + } + } + property_names::CREATED_AT_CORE_BLOCK_HEIGHT => { + if let Some(created_at_core_block_height) = + created_at_core_block_height + { + platform_value!(created_at_core_block_height) + } else { + exit_early = true; + return None; + } + } + property_names::UPDATED_AT_CORE_BLOCK_HEIGHT => { + if *changed_updated_at_core_block_height { + allow_original = false; + } + if let Some(updated_at_core_block_height) = + updated_at_core_block_height + { + platform_value!(updated_at_core_block_height) + } else { + exit_early = true; + return None; + } + } + property_names::TRANSFERRED_AT_CORE_BLOCK_HEIGHT => { + if *changed_transferred_at_core_block_height { + allow_original = false; + } + if let Some(transferred_at_core_block_height) = + transferred_at_core_block_height + { + platform_value!(transferred_at_core_block_height) + } else { + exit_early = true; + return None; + } + } + _ => { + if let Some(value) = data.get(property.name.as_str()) { + // If the property is not none then the uniqueness should exist + if changed_data_values.get(&property.name).is_some() + { + allow_original = false; + } + value.clone() + } else { + // If any of the index is null then the uniqueness no longer exists + exit_early = true; + return None; + } + } + }; + Some(( + property.name.clone(), + WhereClause { + field: property.name.clone(), + operator: WhereOperator::Equal, + value, + }, + )) + }) + .collect::>(); + if exit_early { + return None; + } else { + (where_queries, allow_original) + } + } + }; if where_queries.len() < index.properties.len() { // there are empty fields, which means that the index is no longer unique diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/mod.rs index e5dac3fa3ea..c7930c917e8 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/mod.rs @@ -41,5 +41,4 @@ mod validate_document_replace_transition_action_uniqueness; mod validate_document_purchase_transition_action_uniqueness; mod validate_document_transfer_transition_action_uniqueness; -mod validate_document_uniqueness; mod validate_document_update_price_transition_action_uniqueness; diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/mod.rs index 8ffb92bdcc7..e39407d5011 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/mod.rs @@ -1,4 +1,5 @@ mod v0; +mod v1; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -60,9 +61,17 @@ impl Drive { transaction, platform_version, ), + 1 => self.validate_document_create_transition_action_uniqueness_v1( + contract, + document_type, + document_create_transition, + owner_id, + transaction, + platform_version, + ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "validate_document_create_transition_action_uniqueness".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, })), } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs index c34f463d2bf..33484f34d04 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v0/mod.rs @@ -2,7 +2,7 @@ use dpp::data_contract::DataContract; use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequestV0; use crate::error::Error; use dpp::data_contract::document_type::DocumentTypeRef; @@ -18,7 +18,6 @@ use dpp::document::property_names::{ UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, }; use grovedb::TransactionArg; -use dpp::data_contract::accessors::v0::DataContractV0Getters; use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; use crate::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::{DocumentCreateTransitionAction, DocumentCreateTransitionActionAccessorsV0}; use dpp::version::PlatformVersion; @@ -55,21 +54,10 @@ impl Drive { let block_info = document_create_transition.block_info(); - let creator_id = if document_type.should_use_creator_id( - contract.system_version_type(), - contract.config().version(), - platform_version, - )? { - Some(owner_id) - } else { - None - }; - - let request = UniquenessOfDataRequest { + let request = UniquenessOfDataRequestV0 { contract, document_type, owner_id, - creator_id, document_id: document_create_transition.base().id(), allow_original: false, created_at: if is_created_at_required { @@ -119,6 +107,6 @@ impl Drive { }, data: document_create_transition.data(), }; - self.validate_uniqueness_of_data(request, transaction, platform_version) + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v1/mod.rs new file mode 100644 index 00000000000..0d8f36f6a15 --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_create_transition_action_uniqueness/v1/mod.rs @@ -0,0 +1,126 @@ +use dpp::data_contract::DataContract; + +use crate::drive::Drive; + +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{ + UniquenessOfDataRequestUpdateType, UniquenessOfDataRequestV1, +}; +use crate::error::Error; + +use dpp::data_contract::document_type::DocumentTypeRef; + +use dpp::identifier::Identifier; + +use dpp::validation::SimpleConsensusValidationResult; + +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::document::property_names::{ + CREATED_AT, CREATED_AT_BLOCK_HEIGHT, CREATED_AT_CORE_BLOCK_HEIGHT, TRANSFERRED_AT, + TRANSFERRED_AT_BLOCK_HEIGHT, TRANSFERRED_AT_CORE_BLOCK_HEIGHT, UPDATED_AT, + UPDATED_AT_BLOCK_HEIGHT, UPDATED_AT_CORE_BLOCK_HEIGHT, +}; +use grovedb::TransactionArg; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; +use crate::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::{DocumentCreateTransitionAction, DocumentCreateTransitionActionAccessorsV0}; +use dpp::version::PlatformVersion; + +impl Drive { + /// Validate that a document create transition action would be unique in the state + #[inline(always)] + pub(super) fn validate_document_create_transition_action_uniqueness_v1( + &self, + contract: &DataContract, + document_type: DocumentTypeRef, + document_create_transition: &DocumentCreateTransitionAction, + owner_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let required_fields = document_type.required_fields(); + + let is_created_at_required = required_fields.contains(CREATED_AT); + let is_updated_at_required = required_fields.contains(UPDATED_AT); + let is_transferred_at_required = required_fields.contains(TRANSFERRED_AT); + + let is_created_at_block_height_required = required_fields.contains(CREATED_AT_BLOCK_HEIGHT); + let is_updated_at_block_height_required = required_fields.contains(UPDATED_AT_BLOCK_HEIGHT); + let is_transferred_at_block_height_required = + required_fields.contains(TRANSFERRED_AT_BLOCK_HEIGHT); + + let is_created_at_core_block_height_required = + required_fields.contains(CREATED_AT_CORE_BLOCK_HEIGHT); + let is_updated_at_core_block_height_required = + required_fields.contains(UPDATED_AT_CORE_BLOCK_HEIGHT); + let is_transferred_at_core_block_height_required = + required_fields.contains(TRANSFERRED_AT_CORE_BLOCK_HEIGHT); + + let block_info = document_create_transition.block_info(); + + let creator_id = if document_type.should_use_creator_id( + contract.system_version_type(), + contract.config().version(), + platform_version, + )? { + Some(owner_id) + } else { + None + }; + + let request = UniquenessOfDataRequestV1 { + contract, + document_type, + owner_id, + creator_id, + document_id: document_create_transition.base().id(), + created_at: if is_created_at_required { + Some(block_info.time_ms) + } else { + None + }, + updated_at: if is_updated_at_required { + Some(block_info.time_ms) + } else { + None + }, + transferred_at: if is_transferred_at_required { + Some(block_info.time_ms) + } else { + None + }, + created_at_block_height: if is_created_at_block_height_required { + Some(block_info.height) + } else { + None + }, + updated_at_block_height: if is_updated_at_block_height_required { + Some(block_info.height) + } else { + None + }, + transferred_at_block_height: if is_transferred_at_block_height_required { + Some(block_info.height) + } else { + None + }, + created_at_core_block_height: if is_created_at_core_block_height_required { + Some(block_info.core_height) + } else { + None + }, + updated_at_core_block_height: if is_updated_at_core_block_height_required { + Some(block_info.core_height) + } else { + None + }, + transferred_at_core_block_height: if is_transferred_at_core_block_height_required { + Some(block_info.core_height) + } else { + None + }, + data: document_create_transition.data(), + update_type: UniquenessOfDataRequestUpdateType::NewDocument, + }; + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) + } +} diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs index 75baec39ad8..f747bed59b5 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs @@ -1,4 +1,5 @@ mod v0; +mod v1; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -60,9 +61,17 @@ impl Drive { transaction, platform_version, ), + 1 => self.validate_document_purchase_transition_action_uniqueness_v1( + contract, + document_type, + document_purchase_transition, + owner_id, + transaction, + platform_version, + ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "validate_document_purchase_transition_action_uniqueness".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, })), } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs index 445da09d406..74c92d71e05 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v0/mod.rs @@ -2,7 +2,7 @@ use dpp::data_contract::DataContract; use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequestV0; use crate::error::Error; use dpp::data_contract::document_type::DocumentTypeRef; @@ -30,11 +30,10 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let request = UniquenessOfDataRequest { + let request = UniquenessOfDataRequestV0 { contract, document_type, owner_id, - creator_id: document_purchase_transition.document().creator_id(), document_id: document_purchase_transition.base().id(), allow_original: true, created_at: document_purchase_transition.document().created_at(), @@ -60,6 +59,6 @@ impl Drive { .transferred_at_core_block_height(), data: document_purchase_transition.document().properties(), }; - self.validate_uniqueness_of_data(request, transaction, platform_version) + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs new file mode 100644 index 00000000000..4053d41dc5e --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs @@ -0,0 +1,78 @@ +use dpp::data_contract::DataContract; +use std::borrow::Cow; +use std::collections::BTreeSet; + +use crate::drive::Drive; + +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{ + UniquenessOfDataRequestUpdateType, UniquenessOfDataRequestV1, +}; +use crate::error::Error; + +use dpp::data_contract::document_type::DocumentTypeRef; + +use dpp::identifier::Identifier; + +use dpp::validation::SimpleConsensusValidationResult; + +use dpp::document::DocumentV0Getters; +use grovedb::TransactionArg; + +use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; +use crate::state_transition_action::batch::batched_transition::document_transition::document_purchase_transition_action::{DocumentPurchaseTransitionAction, DocumentPurchaseTransitionActionAccessorsV0}; +use dpp::version::PlatformVersion; + +impl Drive { + /// Validate that a document purchase transition action would be unique in the state + #[inline(always)] + pub(super) fn validate_document_purchase_transition_action_uniqueness_v1( + &self, + contract: &DataContract, + document_type: DocumentTypeRef, + document_purchase_transition: &DocumentPurchaseTransitionAction, + owner_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let request = UniquenessOfDataRequestV1 { + contract, + document_type, + owner_id, + creator_id: document_purchase_transition.document().creator_id(), + document_id: document_purchase_transition.base().id(), + created_at: document_purchase_transition.document().created_at(), + updated_at: document_purchase_transition.document().updated_at(), + transferred_at: document_purchase_transition.document().transferred_at(), + created_at_block_height: document_purchase_transition + .document() + .created_at_block_height(), + updated_at_block_height: document_purchase_transition + .document() + .updated_at_block_height(), + transferred_at_block_height: document_purchase_transition + .document() + .transferred_at_block_height(), + created_at_core_block_height: document_purchase_transition + .document() + .created_at_core_block_height(), + updated_at_core_block_height: document_purchase_transition + .document() + .updated_at_core_block_height(), + transferred_at_core_block_height: document_purchase_transition + .document() + .transferred_at_core_block_height(), + data: document_purchase_transition.document().properties(), + update_type: UniquenessOfDataRequestUpdateType::ChangedDocument { + changed_owner_id: true, + changed_updated_at: false, + changed_transferred_at: true, + changed_updated_at_block_height: false, + changed_transferred_at_block_height: true, + changed_updated_at_core_block_height: false, + changed_transferred_at_core_block_height: true, + changed_data_values: Cow::Owned(BTreeSet::from(["price".to_string()])), + }, + }; + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) + } +} diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/mod.rs index 05deb0d4877..ba49c1a5aa6 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/mod.rs @@ -1,4 +1,5 @@ mod v0; +mod v1; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -60,9 +61,17 @@ impl Drive { transaction, platform_version, ), + 1 => self.validate_document_replace_transition_action_uniqueness_v1( + contract, + document_type, + document_replace_transition, + owner_id, + transaction, + platform_version, + ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "validate_document_replace_transition_action_uniqueness".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, })), } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs index 8c1c9180514..d8101fe2e2e 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v0/mod.rs @@ -2,7 +2,7 @@ use dpp::data_contract::DataContract; use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequestV0; use crate::error::Error; use dpp::data_contract::document_type::DocumentTypeRef; @@ -29,11 +29,10 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let request = UniquenessOfDataRequest { + let request = UniquenessOfDataRequestV0 { contract, document_type, owner_id, - creator_id: document_replace_transition.creator_id(), document_id: document_replace_transition.base().id(), allow_original: true, created_at: document_replace_transition.created_at(), @@ -50,6 +49,6 @@ impl Drive { .transferred_at_core_block_height(), data: document_replace_transition.data(), }; - self.validate_uniqueness_of_data(request, transaction, platform_version) + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v1/mod.rs new file mode 100644 index 00000000000..1538fa1d23c --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_replace_transition_action_uniqueness/v1/mod.rs @@ -0,0 +1,69 @@ +use dpp::data_contract::DataContract; +use std::borrow::Cow; + +use crate::drive::Drive; + +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{ + UniquenessOfDataRequestUpdateType, UniquenessOfDataRequestV1, +}; +use crate::error::Error; + +use dpp::data_contract::document_type::DocumentTypeRef; + +use dpp::identifier::Identifier; + +use dpp::validation::SimpleConsensusValidationResult; + +use grovedb::TransactionArg; + +use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; +use crate::state_transition_action::batch::batched_transition::document_transition::document_replace_transition_action::{DocumentReplaceTransitionAction, DocumentReplaceTransitionActionAccessorsV0}; +use dpp::version::PlatformVersion; + +impl Drive { + /// Validate that a document replace transition action would be unique in the state + #[inline(always)] + pub(super) fn validate_document_replace_transition_action_uniqueness_v1( + &self, + contract: &DataContract, + document_type: DocumentTypeRef, + document_replace_transition: &DocumentReplaceTransitionAction, + owner_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let request = UniquenessOfDataRequestV1 { + contract, + document_type, + owner_id, + creator_id: document_replace_transition.creator_id(), + document_id: document_replace_transition.base().id(), + created_at: document_replace_transition.created_at(), + updated_at: document_replace_transition.updated_at(), + transferred_at: document_replace_transition.transferred_at(), + created_at_block_height: document_replace_transition.created_at_block_height(), + updated_at_block_height: document_replace_transition.updated_at_block_height(), + transferred_at_block_height: document_replace_transition.transferred_at_block_height(), + created_at_core_block_height: document_replace_transition + .created_at_core_block_height(), + updated_at_core_block_height: document_replace_transition + .updated_at_core_block_height(), + transferred_at_core_block_height: document_replace_transition + .transferred_at_core_block_height(), + data: document_replace_transition.data(), + update_type: UniquenessOfDataRequestUpdateType::ChangedDocument { + changed_owner_id: false, + changed_updated_at: true, + changed_transferred_at: false, + changed_updated_at_block_height: true, + changed_transferred_at_block_height: false, + changed_updated_at_core_block_height: true, + changed_transferred_at_core_block_height: false, + changed_data_values: Cow::Borrowed( + document_replace_transition.changed_data_fields(), + ), + }, + }; + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) + } +} diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/mod.rs index 16c272b5414..ca86d7e9b98 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/mod.rs @@ -1,4 +1,5 @@ mod v0; +mod v1; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -60,9 +61,16 @@ impl Drive { transaction, platform_version, ), + 1 => self.validate_document_transfer_transition_action_uniqueness_v1( + contract, + document_type, + document_transfer_transition, + transaction, + platform_version, + ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "validate_document_transfer_transition_action_uniqueness".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, })), } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs index c42dcd5feb4..bd44360cf93 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v0/mod.rs @@ -2,7 +2,7 @@ use dpp::data_contract::DataContract; use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequestV0; use crate::error::Error; use dpp::data_contract::document_type::DocumentTypeRef; @@ -30,11 +30,10 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let request = UniquenessOfDataRequest { + let request = UniquenessOfDataRequestV0 { contract, document_type, owner_id, - creator_id: document_transfer_transition.document().creator_id(), document_id: document_transfer_transition.base().id(), allow_original: true, created_at: document_transfer_transition.document().created_at(), @@ -60,6 +59,6 @@ impl Drive { .transferred_at_core_block_height(), data: document_transfer_transition.document().properties(), }; - self.validate_uniqueness_of_data(request, transaction, platform_version) + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v1/mod.rs new file mode 100644 index 00000000000..c666f7223f4 --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_transfer_transition_action_uniqueness/v1/mod.rs @@ -0,0 +1,73 @@ +use dpp::data_contract::DataContract; + +use crate::drive::Drive; + +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{ + UniquenessOfDataRequestUpdateType, UniquenessOfDataRequestV1, +}; +use crate::error::Error; + +use dpp::data_contract::document_type::DocumentTypeRef; + +use dpp::validation::SimpleConsensusValidationResult; + +use dpp::document::DocumentV0Getters; +use grovedb::TransactionArg; + +use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; +use crate::state_transition_action::batch::batched_transition::document_transition::document_transfer_transition_action::{DocumentTransferTransitionAction, DocumentTransferTransitionActionAccessorsV0}; +use dpp::version::PlatformVersion; + +impl Drive { + /// Validate that a document transfer transition action would be unique in the state + #[inline(always)] + pub(super) fn validate_document_transfer_transition_action_uniqueness_v1( + &self, + contract: &DataContract, + document_type: DocumentTypeRef, + document_transfer_transition: &DocumentTransferTransitionAction, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let request = UniquenessOfDataRequestV1 { + contract, + document_type, + owner_id: document_transfer_transition.document().owner_id(), + creator_id: document_transfer_transition.document().creator_id(), + document_id: document_transfer_transition.base().id(), + created_at: document_transfer_transition.document().created_at(), + updated_at: document_transfer_transition.document().updated_at(), + transferred_at: document_transfer_transition.document().transferred_at(), + created_at_block_height: document_transfer_transition + .document() + .created_at_block_height(), + updated_at_block_height: document_transfer_transition + .document() + .updated_at_block_height(), + transferred_at_block_height: document_transfer_transition + .document() + .transferred_at_block_height(), + created_at_core_block_height: document_transfer_transition + .document() + .created_at_core_block_height(), + updated_at_core_block_height: document_transfer_transition + .document() + .updated_at_core_block_height(), + transferred_at_core_block_height: document_transfer_transition + .document() + .transferred_at_core_block_height(), + data: document_transfer_transition.document().properties(), + update_type: UniquenessOfDataRequestUpdateType::ChangedDocument { + changed_owner_id: true, + changed_updated_at: false, + changed_transferred_at: true, + changed_updated_at_block_height: false, + changed_transferred_at_block_height: true, + changed_updated_at_core_block_height: false, + changed_transferred_at_core_block_height: true, + changed_data_values: Default::default(), + }, + }; + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) + } +} diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/mod.rs deleted file mode 100644 index ff783d06d64..00000000000 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod v0; - -use crate::drive::Drive; -use crate::error::drive::DriveError; -use crate::error::Error; - -use dpp::data_contract::document_type::DocumentTypeRef; -use dpp::data_contract::DataContract; -use dpp::document::Document; -use dpp::identifier::Identifier; - -use dpp::validation::SimpleConsensusValidationResult; - -use dpp::version::PlatformVersion; -use grovedb::TransactionArg; - -impl Drive { - /// Validate that a document would be unique in the state. - /// - /// # Arguments - /// - /// * `contract` - A `Contract` object representing the contract. - /// * `document_type` - A `DocumentType` object representing the type of the document. - /// * `document` - A `Document` object representing the document to validate. - /// * `owner_id` - An `Identifier` object representing the owner's ID. - /// * `allow_original` - A boolean indicating whether the original document is allowed. - /// * `transaction` - A `TransactionArg` object representing the transaction. - /// * `drive_version` - A `DriveVersion` object representing the version of the Drive. - /// - /// # Returns - /// - /// * `Result` - If successful, returns a `SimpleConsensusValidationResult` object representing the result of the validation. - /// If an error occurs during the operation, returns an `Error`. - /// - /// # Errors - /// - /// This function will return an error if the version of the Drive is unknown. - #[allow(clippy::too_many_arguments)] - pub fn validate_document_uniqueness( - &self, - contract: &DataContract, - document_type: DocumentTypeRef, - document: &Document, - owner_id: Identifier, - allow_original: bool, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result { - match platform_version - .drive - .methods - .document - .index_uniqueness - .validate_document_uniqueness - { - 0 => self.validate_document_uniqueness_v0( - contract, - document_type, - document, - owner_id, - allow_original, - transaction, - platform_version, - ), - version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "validate_document_uniqueness".to_string(), - known_versions: vec![0], - received: version, - })), - } - } -} diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs deleted file mode 100644 index b42bc6d560c..00000000000 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_uniqueness/v0/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -use dpp::data_contract::DataContract; - -use crate::drive::Drive; - -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; -use crate::error::Error; - -use dpp::data_contract::document_type::DocumentTypeRef; -use dpp::document::{Document, DocumentV0Getters}; -use dpp::identifier::Identifier; - -use dpp::validation::SimpleConsensusValidationResult; - -use dpp::version::PlatformVersion; -use grovedb::TransactionArg; - -impl Drive { - /// Validate that a document would be unique in the state - #[inline(always)] - #[allow(clippy::too_many_arguments)] - pub(super) fn validate_document_uniqueness_v0( - &self, - contract: &DataContract, - document_type: DocumentTypeRef, - document: &Document, - owner_id: Identifier, - allow_original: bool, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result { - let request = UniquenessOfDataRequest { - contract, - document_type, - owner_id, - creator_id: document.creator_id(), - document_id: document.id(), - allow_original, - created_at: document.created_at(), - updated_at: document.updated_at(), - transferred_at: document.transferred_at(), - created_at_block_height: document.created_at_block_height(), - updated_at_block_height: document.updated_at_block_height(), - transferred_at_block_height: document.transferred_at_block_height(), - created_at_core_block_height: document.created_at_core_block_height(), - updated_at_core_block_height: document.updated_at_core_block_height(), - transferred_at_core_block_height: document.transferred_at_core_block_height(), - data: document.properties(), - }; - self.validate_uniqueness_of_data(request, transaction, platform_version) - } -} diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/mod.rs index f60019f474f..e12d7bca408 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/mod.rs @@ -1,4 +1,5 @@ mod v0; +mod v1; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -60,9 +61,17 @@ impl Drive { transaction, platform_version, ), + 1 => self.validate_document_update_price_transition_action_uniqueness_v1( + contract, + document_type, + document_update_price_transition, + owner_id, + transaction, + platform_version, + ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "validate_document_update_price_transition_action_uniqueness".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1], received: version, })), } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs index b1532523ebe..5500af72bd1 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v0/mod.rs @@ -2,7 +2,7 @@ use dpp::data_contract::DataContract; use crate::drive::Drive; -use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequest; +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::UniquenessOfDataRequestV0; use crate::error::Error; use dpp::data_contract::document_type::DocumentTypeRef; @@ -30,11 +30,10 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let request = UniquenessOfDataRequest { + let request = UniquenessOfDataRequestV0 { contract, document_type, owner_id, - creator_id: document_update_price_transition.document().creator_id(), document_id: document_update_price_transition.base().id(), allow_original: true, created_at: document_update_price_transition.document().created_at(), @@ -60,6 +59,6 @@ impl Drive { .transferred_at_core_block_height(), data: document_update_price_transition.document().properties(), }; - self.validate_uniqueness_of_data(request, transaction, platform_version) + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) } } diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v1/mod.rs new file mode 100644 index 00000000000..0414d2b1f24 --- /dev/null +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_update_price_transition_action_uniqueness/v1/mod.rs @@ -0,0 +1,78 @@ +use dpp::data_contract::DataContract; +use std::borrow::Cow; +use std::collections::BTreeSet; + +use crate::drive::Drive; + +use crate::drive::document::index_uniqueness::internal::validate_uniqueness_of_data::{ + UniquenessOfDataRequestUpdateType, UniquenessOfDataRequestV1, +}; +use crate::error::Error; + +use dpp::data_contract::document_type::DocumentTypeRef; + +use dpp::identifier::Identifier; + +use dpp::validation::SimpleConsensusValidationResult; + +use dpp::document::DocumentV0Getters; +use grovedb::TransactionArg; + +use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; +use crate::state_transition_action::batch::batched_transition::document_transition::document_update_price_transition_action::{DocumentUpdatePriceTransitionAction, DocumentUpdatePriceTransitionActionAccessorsV0}; +use dpp::version::PlatformVersion; + +impl Drive { + /// Validate that a document update_price transition action would be unique in the state + #[inline(always)] + pub(super) fn validate_document_update_price_transition_action_uniqueness_v1( + &self, + contract: &DataContract, + document_type: DocumentTypeRef, + document_update_price_transition: &DocumentUpdatePriceTransitionAction, + owner_id: Identifier, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let request = UniquenessOfDataRequestV1 { + contract, + document_type, + owner_id, + creator_id: document_update_price_transition.document().creator_id(), + document_id: document_update_price_transition.base().id(), + created_at: document_update_price_transition.document().created_at(), + updated_at: document_update_price_transition.document().updated_at(), + transferred_at: document_update_price_transition.document().transferred_at(), + created_at_block_height: document_update_price_transition + .document() + .created_at_block_height(), + updated_at_block_height: document_update_price_transition + .document() + .updated_at_block_height(), + transferred_at_block_height: document_update_price_transition + .document() + .transferred_at_block_height(), + created_at_core_block_height: document_update_price_transition + .document() + .created_at_core_block_height(), + updated_at_core_block_height: document_update_price_transition + .document() + .updated_at_core_block_height(), + transferred_at_core_block_height: document_update_price_transition + .document() + .transferred_at_core_block_height(), + data: document_update_price_transition.document().properties(), + update_type: UniquenessOfDataRequestUpdateType::ChangedDocument { + changed_owner_id: false, + changed_updated_at: true, + changed_transferred_at: false, + changed_updated_at_block_height: true, + changed_transferred_at_block_height: false, + changed_updated_at_core_block_height: true, + changed_transferred_at_core_block_height: false, + changed_data_values: Cow::Owned(BTreeSet::from(["price".to_string()])), + }, + }; + self.validate_uniqueness_of_data(request.into(), transaction, platform_version) + } +} diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs index f6b70b2e6d6..136c1f9e6dc 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/mod.rs @@ -1,6 +1,6 @@ mod v0; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use derive_more::From; @@ -107,6 +107,12 @@ impl DocumentReplaceTransitionActionAccessorsV0 for DocumentReplaceTransitionAct } } + fn changed_data_fields(&self) -> &BTreeSet { + match self { + DocumentReplaceTransitionAction::V0(v0) => &v0.changed_data_fields, + } + } + fn data_owned(self) -> BTreeMap { match self { DocumentReplaceTransitionAction::V0(v0) => v0.data, diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs index e068849e6ac..7cd0add02b5 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/transformer.rs @@ -1,9 +1,9 @@ use dpp::block::block_info::BlockInfo; use dpp::platform_value::Identifier; use std::sync::Arc; +use dpp::document::Document; use dpp::fee::fee_result::FeeResult; -use dpp::identity::TimestampMillis; -use dpp::prelude::{BlockHeight, ConsensusValidationResult, CoreBlockHeight, UserFeeIncrease}; +use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::DocumentReplaceTransition; use crate::drive::contract::DataContractFetchInfo; @@ -17,13 +17,7 @@ impl DocumentReplaceTransitionAction { pub fn try_from_borrowed_document_replace_transition( document_replace_transition: &DocumentReplaceTransition, owner_id: Identifier, - originally_created_at: Option, - originally_created_at_block_height: Option, - originally_created_at_core_block_height: Option, - originally_transferred_at: Option, - originally_transferred_at_block_height: Option, - originally_transferred_at_core_block_height: Option, - original_creator_id: Option, + original_document: &Document, block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, @@ -39,13 +33,7 @@ impl DocumentReplaceTransitionAction { DocumentReplaceTransitionActionV0::try_from_borrowed_document_replace_transition( v0, owner_id, - originally_created_at, - originally_created_at_block_height, - originally_created_at_core_block_height, - originally_transferred_at, - originally_transferred_at_block_height, - originally_transferred_at_core_block_height, - original_creator_id, + original_document, block_info, user_fee_increase, get_data_contract, diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs index da4daf59ace..2a458a70f2e 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/mod.rs @@ -6,7 +6,7 @@ use dpp::platform_value::{Identifier, Value}; use dpp::prelude::{BlockHeight, CoreBlockHeight, Revision}; use dpp::ProtocolError; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::{DocumentBaseTransitionAction, DocumentBaseTransitionActionAccessorsV0}; use dpp::version::PlatformVersion; @@ -38,6 +38,8 @@ pub struct DocumentReplaceTransitionActionV0 { pub transferred_at_core_block_height: Option, /// Document properties pub data: BTreeMap, + /// Updated fields + pub changed_data_fields: BTreeSet, /// Creator id pub creator_id: Option, } @@ -76,6 +78,9 @@ pub trait DocumentReplaceTransitionActionAccessorsV0 { /// data fn data(&self) -> &BTreeMap; + + /// The fields that have changed + fn changed_data_fields(&self) -> &BTreeSet; /// data owned fn data_owned(self) -> BTreeMap; @@ -141,6 +146,7 @@ impl DocumentFromReplaceTransitionActionV0 for Document { transferred_at_core_block_height, data, creator_id, + .. } = value; let id = base.id(); @@ -194,6 +200,7 @@ impl DocumentFromReplaceTransitionActionV0 for Document { transferred_at_core_block_height, data, creator_id, + .. } = value; let id = base.id(); diff --git a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs index 1d52f7831d4..21c3d4150ec 100644 --- a/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_replace_transition_action/v0/transformer.rs @@ -1,11 +1,11 @@ use dpp::block::block_info::BlockInfo; -use dpp::document::property_names; +use dpp::document::{property_names, Document, DocumentV0Getters}; use dpp::platform_value::Identifier; +use std::collections::BTreeSet; use std::sync::Arc; use dpp::data_contract::document_type::accessors::DocumentTypeV1Getters; use dpp::fee::fee_result::FeeResult; -use dpp::identity::TimestampMillis; -use dpp::prelude::{BlockHeight, ConsensusValidationResult, CoreBlockHeight, UserFeeIncrease}; +use dpp::prelude::{ConsensusValidationResult, UserFeeIncrease}; use dpp::ProtocolError; use dpp::state_transition::batch_transition::batched_transition::document_replace_transition::DocumentReplaceTransitionV0; use crate::drive::contract::DataContractFetchInfo; @@ -22,13 +22,7 @@ impl DocumentReplaceTransitionActionV0 { pub fn try_from_borrowed_document_replace_transition( document_replace_transition: &DocumentReplaceTransitionV0, owner_id: Identifier, - originally_created_at: Option, - originally_created_at_block_height: Option, - originally_created_at_core_block_height: Option, - originally_transferred_at: Option, - originally_transferred_at_block_height: Option, - originally_transferred_at_core_block_height: Option, - original_creator_id: Option, + original_document: &Document, block_info: &BlockInfo, user_fee_increase: UserFeeIncrease, get_data_contract: impl Fn(Identifier) -> Result, ProtocolError>, @@ -95,21 +89,70 @@ impl DocumentReplaceTransitionActionV0 { None }; + // There is a case where we updated a just deleted document + // In this case we don't care about the created at + let original_document_created_at = original_document.created_at(); + + let original_document_created_at_block_height = original_document.created_at_block_height(); + + let original_document_created_at_core_block_height = + original_document.created_at_core_block_height(); + + let original_document_transferred_at = original_document.transferred_at(); + + let original_document_transferred_at_block_height = + original_document.transferred_at_block_height(); + + let original_document_transferred_at_core_block_height = + original_document.transferred_at_core_block_height(); + + let original_creator_id = original_document.creator_id(); + + // Determine which fields have changed between the original document and the new data + let changed_fields: BTreeSet = data + .iter() + .filter_map(|(key, new_value)| { + let original_value = original_document.properties().get(key); + match original_value { + Some(old_value) => { + if !old_value.equal_underlying_data(new_value) { + Some(key.clone()) + } else { + None + } + } + None => Some(key.clone()), // New field that wasn't in original + } + }) + .chain( + // Check for fields that were in the original but removed in the new data + original_document.properties().keys().filter_map(|key| { + if !data.contains_key(key) { + Some(key.clone()) + } else { + None + } + }), + ) + .collect(); + Ok(( BatchedTransitionAction::DocumentAction(DocumentTransitionAction::ReplaceAction( DocumentReplaceTransitionActionV0 { base, revision: *revision, - created_at: originally_created_at, + created_at: original_document_created_at, updated_at, - transferred_at: originally_transferred_at, - created_at_block_height: originally_created_at_block_height, + transferred_at: original_document_transferred_at, + created_at_block_height: original_document_created_at_block_height, updated_at_block_height, - transferred_at_block_height: originally_transferred_at_block_height, - created_at_core_block_height: originally_created_at_core_block_height, + transferred_at_block_height: original_document_transferred_at_block_height, + created_at_core_block_height: original_document_created_at_core_block_height, updated_at_core_block_height, - transferred_at_core_block_height: originally_transferred_at_core_block_height, + transferred_at_core_block_height: + original_document_transferred_at_core_block_height, data: data.clone(), + changed_data_fields: changed_fields, creator_id: original_creator_id, } .into(), diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs index 12eebc73865..8abec60c116 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs @@ -1,6 +1,7 @@ use versioned_feature_core::FeatureVersion; pub mod v1; +pub mod v2; #[derive(Clone, Debug, Default)] pub struct DriveDocumentMethodVersions { @@ -80,11 +81,9 @@ pub struct DriveDocumentDeleteMethodVersions { #[derive(Clone, Debug, Default)] pub struct DriveDocumentIndexUniquenessMethodVersions { - pub validate_document_uniqueness: FeatureVersion, pub validate_document_create_transition_action_uniqueness: FeatureVersion, pub validate_document_replace_transition_action_uniqueness: FeatureVersion, pub validate_document_transfer_transition_action_uniqueness: FeatureVersion, pub validate_document_purchase_transition_action_uniqueness: FeatureVersion, pub validate_document_update_price_transition_action_uniqueness: FeatureVersion, - pub validate_uniqueness_of_data: FeatureVersion, } diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs index 05479b23e99..91627b4c1ad 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs @@ -61,12 +61,10 @@ pub const DRIVE_DOCUMENT_METHOD_VERSIONS_V1: DriveDocumentMethodVersions = stateless_delete_of_non_tree_for_costs: 0, }, index_uniqueness: DriveDocumentIndexUniquenessMethodVersions { - validate_document_uniqueness: 0, validate_document_create_transition_action_uniqueness: 0, validate_document_replace_transition_action_uniqueness: 0, validate_document_transfer_transition_action_uniqueness: 0, validate_document_purchase_transition_action_uniqueness: 0, validate_document_update_price_transition_action_uniqueness: 0, - validate_uniqueness_of_data: 0, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs new file mode 100644 index 00000000000..60e06ac729e --- /dev/null +++ b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs @@ -0,0 +1,72 @@ +use crate::version::drive_versions::drive_document_method_versions::{ + DriveDocumentDeleteMethodVersions, DriveDocumentEstimationCostsMethodVersions, + DriveDocumentIndexUniquenessMethodVersions, DriveDocumentInsertContestedMethodVersions, + DriveDocumentInsertMethodVersions, DriveDocumentMethodVersions, + DriveDocumentQueryMethodVersions, DriveDocumentUpdateMethodVersions, +}; + +/// This was introduced in protocol v10 to deal with changes in queries for document uniqueness +/// during validation. +pub const DRIVE_DOCUMENT_METHOD_VERSIONS_V2: DriveDocumentMethodVersions = + DriveDocumentMethodVersions { + query: DriveDocumentQueryMethodVersions { + query_documents: 0, + query_contested_documents: 0, + query_contested_documents_vote_state: 0, + query_documents_with_flags: 0, + }, + delete: DriveDocumentDeleteMethodVersions { + add_estimation_costs_for_remove_document_to_primary_storage: 0, + delete_document_for_contract: 0, + delete_document_for_contract_id: 0, + delete_document_for_contract_apply_and_add_to_operations: 0, + remove_document_from_primary_storage: 0, + remove_reference_for_index_level_for_contract_operations: 0, + remove_indices_for_index_level_for_contract_operations: 0, + remove_indices_for_top_index_level_for_contract_operations: 0, + delete_document_for_contract_id_with_named_type_operations: 0, + delete_document_for_contract_with_named_type_operations: 0, + delete_document_for_contract_operations: 0, + }, + insert: DriveDocumentInsertMethodVersions { + add_document: 0, + add_document_for_contract: 0, + add_document_for_contract_apply_and_add_to_operations: 0, + add_document_for_contract_operations: 0, + add_document_to_primary_storage: 0, + add_indices_for_index_level_for_contract_operations: 0, + add_indices_for_top_index_level_for_contract_operations: 0, + add_reference_for_index_level_for_contract_operations: 0, + }, + insert_contested: DriveDocumentInsertContestedMethodVersions { + add_contested_document: 0, + add_contested_document_for_contract: 0, + add_contested_document_for_contract_apply_and_add_to_operations: 0, + add_contested_document_for_contract_operations: 0, + add_contested_document_to_primary_storage: 0, + add_contested_indices_for_contract_operations: 0, + add_contested_reference_and_vote_subtree_to_document_operations: 0, + add_contested_vote_subtree_for_non_identities_operations: 0, + }, + update: DriveDocumentUpdateMethodVersions { + add_update_multiple_documents_operations: 0, + update_document_for_contract: 0, + update_document_for_contract_apply_and_add_to_operations: 0, + update_document_for_contract_id: 0, + update_document_for_contract_operations: 0, + update_document_with_serialization_for_contract: 0, + update_serialized_document_for_contract: 0, + }, + estimation_costs: DriveDocumentEstimationCostsMethodVersions { + add_estimation_costs_for_add_document_to_primary_storage: 0, + add_estimation_costs_for_add_contested_document_to_primary_storage: 0, + stateless_delete_of_non_tree_for_costs: 0, + }, + index_uniqueness: DriveDocumentIndexUniquenessMethodVersions { + validate_document_create_transition_action_uniqueness: 1, // Changed + validate_document_replace_transition_action_uniqueness: 1, // Changed + validate_document_transfer_transition_action_uniqueness: 1, // Changed + validate_document_purchase_transition_action_uniqueness: 1, // Changed + validate_document_update_price_transition_action_uniqueness: 1, // Changed + }, + }; diff --git a/packages/rs-platform-version/src/version/drive_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/mod.rs index 7ed30fe0a57..a5d716c37c6 100644 --- a/packages/rs-platform-version/src/version/drive_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/mod.rs @@ -27,6 +27,7 @@ pub mod v1; pub mod v2; pub mod v3; pub mod v4; +pub mod v5; #[derive(Clone, Debug, Default)] pub struct DriveVersion { diff --git a/packages/rs-platform-version/src/version/drive_versions/v5.rs b/packages/rs-platform-version/src/version/drive_versions/v5.rs new file mode 100644 index 00000000000..f982379d442 --- /dev/null +++ b/packages/rs-platform-version/src/version/drive_versions/v5.rs @@ -0,0 +1,108 @@ +use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CONTRACT_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_structure_version::v1::DRIVE_STRUCTURE_V1; +use crate::version::drive_versions::drive_token_method_versions::v1::DRIVE_TOKEN_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_verify_method_versions::v1::DRIVE_VERIFY_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_vote_method_versions::v2::DRIVE_VOTE_METHOD_VERSIONS_V2; +use crate::version::drive_versions::{ + DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion, + DriveEstimatedCostsMethodVersions, DriveFeesMethodVersions, DriveFetchMethodVersions, + DriveInitializationMethodVersions, DriveMethodVersions, DriveOperationsMethodVersion, + DrivePlatformStateMethodVersions, DrivePlatformSystemMethodVersions, + DrivePrefundedSpecializedMethodVersions, DriveProtocolUpgradeVersions, + DriveProveMethodVersions, DriveSystemEstimationCostsMethodVersions, DriveVersion, +}; +use grovedb_version::version::v2::GROVE_V2; + +/// This was introduced in protocol v10 to deal with changes in queries for document uniqueness +/// during validation. +pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { + structure: DRIVE_STRUCTURE_V1, + methods: DriveMethodVersions { + initialization: DriveInitializationMethodVersions { + create_initial_state_structure: 1, + }, + credit_pools: CREDIT_POOL_METHOD_VERSIONS_V1, + protocol_upgrade: DriveProtocolUpgradeVersions { + clear_version_information: 0, + fetch_versions_with_counter: 0, + fetch_proved_versions_with_counter: 0, + fetch_validator_version_votes: 0, + fetch_proved_validator_version_votes: 0, + remove_validators_proposed_app_versions: 0, + update_validator_proposed_app_version: 0, + }, + prove: DriveProveMethodVersions { + prove_elements: 0, + prove_multiple_state_transition_results: 0, + prove_state_transition: 0, + }, + balances: DriveBalancesMethodVersions { + add_to_system_credits: 0, + add_to_system_credits_operations: 0, + remove_from_system_credits: 0, + remove_from_system_credits_operations: 0, + calculate_total_credits_balance: 0, + }, + document: DRIVE_DOCUMENT_METHOD_VERSIONS_V2, // Changed + vote: DRIVE_VOTE_METHOD_VERSIONS_V2, + contract: DRIVE_CONTRACT_METHOD_VERSIONS_V2, + fees: DriveFeesMethodVersions { calculate_fee: 0 }, + estimated_costs: DriveEstimatedCostsMethodVersions { + add_estimation_costs_for_levels_up_to_contract: 0, + add_estimation_costs_for_levels_up_to_contract_document_type_excluded: 0, + add_estimation_costs_for_contested_document_tree_levels_up_to_contract: 0, + add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded: 0, + }, + asset_lock: DriveAssetLockMethodVersions { + add_asset_lock_outpoint: 0, + add_estimation_costs_for_adding_asset_lock: 0, + fetch_asset_lock_outpoint_info: 0, + }, + verify: DRIVE_VERIFY_METHOD_VERSIONS_V1, + identity: DRIVE_IDENTITY_METHOD_VERSIONS_V1, + token: DRIVE_TOKEN_METHOD_VERSIONS_V1, + platform_system: DrivePlatformSystemMethodVersions { + estimation_costs: DriveSystemEstimationCostsMethodVersions { + for_total_system_credits_update: 0, + }, + }, + operations: DriveOperationsMethodVersion { + rollback_transaction: 0, + drop_cache: 0, + commit_transaction: 0, + apply_partial_batch_low_level_drive_operations: 0, + apply_partial_batch_grovedb_operations: 0, + apply_batch_low_level_drive_operations: 0, + apply_batch_grovedb_operations: 0, + }, + state_transitions: DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1, + batch_operations: DriveBatchOperationsMethodVersion { + convert_drive_operations_to_grove_operations: 0, + apply_drive_operations: 0, + }, + platform_state: DrivePlatformStateMethodVersions { + fetch_platform_state_bytes: 0, + store_platform_state_bytes: 0, + }, + fetch: DriveFetchMethodVersions { fetch_elements: 0 }, + prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { + fetch_single: 0, + prove_single: 0, + add_prefunded_specialized_balance: 0, + add_prefunded_specialized_balance_operations: 1, + deduct_from_prefunded_specialized_balance: 1, + deduct_from_prefunded_specialized_balance_operations: 0, + estimated_cost_for_prefunded_specialized_balance_update: 0, + empty_prefunded_specialized_balance: 0, + }, + group: DRIVE_GROUP_METHOD_VERSIONS_V1, + }, + grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, + grove_version: GROVE_V2, +}; diff --git a/packages/rs-platform-version/src/version/v10.rs b/packages/rs-platform-version/src/version/v10.rs index 07b3be090c1..72fc56e5347 100644 --- a/packages/rs-platform-version/src/version/v10.rs +++ b/packages/rs-platform-version/src/version/v10.rs @@ -20,7 +20,7 @@ use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIV use crate::version::drive_abci_versions::drive_abci_validation_versions::v6::DRIVE_ABCI_VALIDATION_VERSIONS_V6; use crate::version::drive_abci_versions::drive_abci_withdrawal_constants::v2::DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2; use crate::version::drive_abci_versions::DriveAbciVersion; -use crate::version::drive_versions::v4::DRIVE_VERSION_V4; +use crate::version::drive_versions::v5::DRIVE_VERSION_V5; use crate::version::fee::v2::FEE_VERSION2; use crate::version::protocol_version::PlatformVersion; use crate::version::system_data_contract_versions::v1::SYSTEM_DATA_CONTRACT_VERSIONS_V1; @@ -32,7 +32,7 @@ pub const PROTOCOL_VERSION_10: ProtocolVersion = 10; /// This version was for Platform release 2.1.0 pub const PLATFORM_V10: PlatformVersion = PlatformVersion { protocol_version: PROTOCOL_VERSION_10, - drive: DRIVE_VERSION_V4, + drive: DRIVE_VERSION_V5, // Changed to deal with document uniqueness improvements drive_abci: DriveAbciVersion { structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, methods: DRIVE_ABCI_METHOD_VERSIONS_V6, From 2e3f5213933830a18c2d4155f007a9622b82b516 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 1 Oct 2025 15:19:47 +0700 Subject: [PATCH 11/13] more work --- .../batch/tests/document/nft.rs | 1164 +++++++++++++++++ .../batch/tests/document/transfer.rs | 4 +- ...rect-purchase-unique-creator-id-index.json | 143 ++ ...unique-creator-id-with-owner-id-index.json | 146 +++ 4 files changed, 1455 insertions(+), 2 deletions(-) create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-index.json create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-with-owner-id-index.json diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/nft.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/nft.rs index 6ba43deb0dc..72ffee2719b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/nft.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/nft.rs @@ -3129,4 +3129,1168 @@ mod nft_tests { // It costs 3 to purchase on top of the credits and he had 5 assert_eq!(gold_token_balance, Some(2)); } + + #[test] + fn test_document_creator_id_unique_index_enforcement_with_purchase() { + // This test verifies that a unique index on creator_id is properly enforced throughout + // the complete document lifecycle with purchase operations, ensuring that only one + // document per creator can exist at any time. + // + // ## Purpose + // The creator_id field is immutable and set once at document creation. A unique index on + // this field enforces a "one document per creator" constraint. This test extends the + // transfer test to include purchase operations (setting price and purchasing). + // + // ## Why This Test Is Important + // This test ensures that: + // 1. The unique constraint prevents duplicate documents from the same creator + // 2. The creator_id remains immutable during purchases (doesn't change with ownership) + // 3. The unique constraint persists even after purchase operations + // 4. Only document deletion frees up the creator_id for potential reuse + // 5. Price setting and purchasing work correctly with unique creator_id constraints + // + // ## Test Scenario + // This test uses a contract with tradeMode=1 (direct purchase) where the "card" document + // type has a unique index on $creatorId. The test verifies: + // + // 1. Creator creates first document with price → SUCCESS + // 2. Creator tries to create second document → FAIL (creator_id already used) + // 3. Buyer purchases the document → SUCCESS (ownership changes, creator_id stays) + // 4. Creator tries to create new document → FAIL (creator_id still claimed despite purchase) + // 5. New buyer deletes the document → SUCCESS (creator_id is freed) + // 6. Creator creates new document → SUCCESS (creator_id now available again) + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-index.json"; + + // Load the contract with unique creator_id index and tradeMode=1 (direct purchase) + let contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + // Setup two identities: creator and buyer + let (creator, creator_signer, creator_key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.5)); + let (buyer, buyer_signer, buyer_key) = + setup_identity(&mut platform, 450, dash_to_credits!(0.5)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + assert!(!card_document_type.documents_mutable()); + + // Step 1: Creator creates first document with price + let entropy1 = Bytes32::random_with_rng(&mut rng); + + let mut document1 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy1, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document1.set("attack", 5.into()); + document1.set("defense", 8.into()); + + let documents_batch_create_transition1 = + BatchTransition::new_document_creation_transition_from_document( + document1.clone(), + card_document_type, + entropy1.0, + &creator_key, + 2, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition1 = documents_batch_create_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Set price on document1 + document1.set_revision(Some(2)); + + let price = dash_to_credits!(0.1); + + let documents_batch_update_price_transition = + BatchTransition::new_document_update_price_transition_from_document( + document1.clone(), + card_document_type, + price, + &creator_key, + 3, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create update price transition"); + + let documents_batch_update_price_serialized_transition = + documents_batch_update_price_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_update_price_serialized_transition.clone()], + &platform_state, + &BlockInfo::default_with_time(50000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 2: Try to create second document by same creator + // This should FAIL due to unique creator_id constraint + let entropy2 = Bytes32::random_with_rng(&mut rng); + + let mut document2 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy2, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document2.set("attack", 3.into()); + document2.set("defense", 6.into()); + + let documents_batch_create_transition2 = + BatchTransition::new_document_creation_transition_from_document( + document2.clone(), + card_document_type, + entropy2.0, + &creator_key, + 4, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition2 = documents_batch_create_transition2 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition2.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because creator already has a document (unique creator_id constraint) + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 3: Buyer purchases document1 (changes owner to buyer, creator stays same) + document1.set_revision(Some(3)); + + let documents_batch_purchase_transition = + BatchTransition::new_document_purchase_transition_from_document( + document1.clone(), + card_document_type, + buyer.id(), + price, + &buyer_key, + 2, + 0, + None, + &buyer_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for purchase"); + + let documents_batch_purchase_serialized_transition = documents_batch_purchase_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_purchase_serialized_transition.clone()], + &platform_state, + &BlockInfo::default_with_time(100000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify the document was purchased + let buyer_documents_sql_string = + format!("select * from card where $ownerId == '{}'", buyer.id()); + + let query_buyer_identity_documents = DriveDocumentQuery::from_sql_expr( + buyer_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_buyer_results = platform + .drive + .query_documents( + query_buyer_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + assert_eq!(query_buyer_results.documents().len(), 1); + + // Step 4: Try to create a new document by the creator again after purchase + // This should STILL FAIL because creator_id is immutable and still points to creator + let entropy3 = Bytes32::random_with_rng(&mut rng); + + let mut document3 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy3, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document3.set("attack", 7.into()); + document3.set("defense", 4.into()); + + let documents_batch_create_transition3 = + BatchTransition::new_document_creation_transition_from_document( + document3.clone(), + card_document_type, + entropy3.0, + &creator_key, + 5, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition3 = documents_batch_create_transition3 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition3.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should still fail because creator_id is immutable and the unique constraint still applies + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 5: Buyer deletes the document + document1.set_owner_id(buyer.id()); + document1.set_revision(Some(4)); + + let documents_batch_deletion_transition = + BatchTransition::new_document_deletion_transition_from_document( + document1, + card_document_type, + &buyer_key, + 3, + 0, + None, + &buyer_signer, + platform_version, + None, + ) + .expect("expect to create documents batch deletion transition"); + + let documents_batch_deletion_serialized_transition = documents_batch_deletion_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_deletion_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify the document was deleted + let query_buyer_results = platform + .drive + .query_documents( + query_buyer_identity_documents.clone(), + None, + false, + None, + None, + ) + .expect("expected query result"); + + assert_eq!(query_buyer_results.documents().len(), 0); + + // Step 6: Now creator should be able to create a new document + // This should SUCCEED because the previous document with this creator_id was deleted + let entropy4 = Bytes32::random_with_rng(&mut rng); + + let mut document4 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy4, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document4.set("attack", 9.into()); + document4.set("defense", 2.into()); + + let documents_batch_create_transition4 = + BatchTransition::new_document_creation_transition_from_document( + document4.clone(), + card_document_type, + entropy4.0, + &creator_key, + 6, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition4 = documents_batch_create_transition4 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition4.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should succeed now because the previous document was deleted + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify the new document was created + let creator_documents_sql_string = + format!("select * from card where $ownerId == '{}'", creator.id()); + + let query_creator_identity_documents = DriveDocumentQuery::from_sql_expr( + creator_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_creator_results = platform + .drive + .query_documents(query_creator_identity_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_creator_results.documents().len(), 1); + + // Verify via creator_id query + let creator_id_documents_sql_string = + format!("select * from card where $creatorId == '{}'", creator.id()); + + let query_creator_id_documents = DriveDocumentQuery::from_sql_expr( + creator_id_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_creator_id_results = platform + .drive + .query_documents(query_creator_id_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_creator_id_results.documents().len(), 1); + + let issues = platform + .drive + .grove + .visualize_verify_grovedb(None, true, false, &platform_version.drive.grove_version) + .expect("expected to have no issues"); + + assert_eq!( + issues.len(), + 0, + "issues are {}", + issues + .iter() + .map(|(hash, (a, b, c))| format!("{}: {} {} {}", hash, a, b, c)) + .collect::>() + .join(" | ") + ); + } + + #[test] + fn test_document_owner_and_creator_id_unique_index_enforcement_with_purchase() { + // This test verifies that a unique compound index on (owner_id, creator_id) is properly + // enforced throughout the document lifecycle with purchase operations, allowing a creator + // to have multiple documents but preventing duplicate (owner, creator) combinations. + // + // ## Purpose + // A compound unique index on (owner_id, creator_id) creates a more flexible constraint than + // a simple unique creator_id. It allows the same creator to create multiple documents, but + // each owner can hold at most one document from any given creator. This test extends the + // transfer test to include purchase operations. + // + // ## Why This Test Is Important + // This test ensures that: + // 1. The compound constraint prevents duplicate (owner, creator) pairs + // 2. Creators can create multiple documents when owned by different people + // 3. Purchases can fail if they would create a duplicate (owner, creator) combination + // 4. Price setting works correctly with compound unique constraints + // 5. Deletion properly frees up the (owner, creator) constraint + // + // ## Test Scenario + // This test uses a contract with tradeMode=1 where the "card" document type has a unique + // compound index on ($ownerId, $creatorId). The test verifies: + // + // 1. Creator creates document1 with price (owner=creator, creator=creator) → SUCCESS + // 2. Creator tries to create document2 with same owner → FAIL (duplicate (creator, creator)) + // 3. Buyer1 purchases document1 → SUCCESS (now (buyer1, creator) exists) + // 4. Creator creates document3 with price → SUCCESS ((creator, creator) is now available) + // 5. Buyer1 tries to purchase document3 → FAIL ((buyer1, creator) already exists) + // 6. Buyer2 purchases document3 → SUCCESS ((buyer2, creator) is available) + // 7. Buyer1 tries to purchase from another seller → FAIL (simulated via attempting to create) + // 8. Buyer2 deletes document → SUCCESS (frees (buyer2, creator)) + // 9. Buyer1 can now receive from creator → SUCCESS (via purchase or transfer) + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let card_game_path = "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-with-owner-id-index.json"; + + // Load the contract with unique (owner_id, creator_id) compound index and tradeMode=1 + let contract = json_document_to_contract(card_game_path, true, platform_version) + .expect("expected to get data contract"); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + let mut rng = StdRng::seed_from_u64(433); + + let platform_state = platform.state.load(); + + // Setup three identities: creator, buyer1, and buyer2 + let (creator, creator_signer, creator_key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.5)); + let (buyer1, buyer1_signer, buyer1_key) = + setup_identity(&mut platform, 450, dash_to_credits!(0.5)); + let (buyer2, buyer2_signer, buyer2_key) = + setup_identity(&mut platform, 789, dash_to_credits!(0.5)); + + let card_document_type = contract + .document_type_for_name("card") + .expect("expected a card document type"); + + assert!(!card_document_type.documents_mutable()); + + let price = dash_to_credits!(0.1); + + // Step 1: Creator creates document1 with price (owner=creator, creator=creator) + let entropy1 = Bytes32::random_with_rng(&mut rng); + + let mut document1 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy1, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document1.set("attack", 5.into()); + document1.set("defense", 8.into()); + + let documents_batch_create_transition1 = + BatchTransition::new_document_creation_transition_from_document( + document1.clone(), + card_document_type, + entropy1.0, + &creator_key, + 2, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition1 = documents_batch_create_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Set price on document1 + document1.set_revision(Some(2)); + + let documents_batch_update_price_transition1 = + BatchTransition::new_document_update_price_transition_from_document( + document1.clone(), + card_document_type, + price, + &creator_key, + 3, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create update price transition"); + + let documents_batch_update_price_serialized_transition1 = + documents_batch_update_price_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_update_price_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default_with_time(50000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 2: Try to create second document by same creator with same owner + // This should FAIL due to unique (owner_id, creator_id) constraint + let entropy2 = Bytes32::random_with_rng(&mut rng); + + let mut document2 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy2, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document2.set("attack", 3.into()); + document2.set("defense", 6.into()); + + let documents_batch_create_transition2 = + BatchTransition::new_document_creation_transition_from_document( + document2.clone(), + card_document_type, + entropy2.0, + &creator_key, + 4, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition2 = documents_batch_create_transition2 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition2.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because (creator, creator) combination already exists + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 3: Buyer1 purchases document1 (changes owner to buyer1, creator stays same) + // Now we have (owner=buyer1, creator=creator) + document1.set_revision(Some(3)); + + let documents_batch_purchase_transition1 = + BatchTransition::new_document_purchase_transition_from_document( + document1.clone(), + card_document_type, + buyer1.id(), + price, + &buyer1_key, + 2, + 0, + None, + &buyer1_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for purchase"); + + let documents_batch_purchase_serialized_transition1 = documents_batch_purchase_transition1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_purchase_serialized_transition1.clone()], + &platform_state, + &BlockInfo::default_with_time(100000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 4: Now creator can create another document because (owner=creator, creator=creator) is available + // This should SUCCEED + let entropy3 = Bytes32::random_with_rng(&mut rng); + + let mut document3 = card_document_type + .random_document_with_identifier_and_entropy( + &mut rng, + creator.id(), + entropy3, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document3.set("attack", 7.into()); + document3.set("defense", 4.into()); + + let documents_batch_create_transition3 = + BatchTransition::new_document_creation_transition_from_document( + document3.clone(), + card_document_type, + entropy3.0, + &creator_key, + 5, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_create_serialized_transition3 = documents_batch_create_transition3 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition3.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should succeed because (creator, creator) is now available after purchase + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Set price on document3 + document3.set_revision(Some(2)); + + let documents_batch_update_price_transition3 = + BatchTransition::new_document_update_price_transition_from_document( + document3.clone(), + card_document_type, + price, + &creator_key, + 6, + 0, + None, + &creator_signer, + platform_version, + None, + ) + .expect("expect to create update price transition"); + + let documents_batch_update_price_serialized_transition3 = + documents_batch_update_price_transition3 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_update_price_serialized_transition3.clone()], + &platform_state, + &BlockInfo::default_with_time(150000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 5: Try buyer1 to purchase document3 + // This should FAIL because (buyer1, creator) already exists from document1 + document3.set_revision(Some(3)); + + let documents_batch_purchase_transition_buyer1 = + BatchTransition::new_document_purchase_transition_from_document( + document3.clone(), + card_document_type, + buyer1.id(), + price, + &buyer1_key, + 3, + 0, + None, + &buyer1_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for purchase"); + + let documents_batch_purchase_serialized_transition_buyer1 = + documents_batch_purchase_transition_buyer1 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_purchase_serialized_transition_buyer1.clone()], + &platform_state, + &BlockInfo::default_with_time(200000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should fail because (buyer1, creator) combination already exists + assert_eq!(processing_result.valid_count(), 0); + assert_eq!(processing_result.invalid_paid_count(), 1); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 6: Buyer2 purchases document3 + // This should SUCCEED because (buyer2, creator) is available + document3.set_revision(Some(3)); + + let documents_batch_purchase_transition_buyer2 = + BatchTransition::new_document_purchase_transition_from_document( + document3.clone(), + card_document_type, + buyer2.id(), + price, + &buyer2_key, + 2, + 0, + None, + &buyer2_signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition for purchase"); + + let documents_batch_purchase_serialized_transition_buyer2 = + documents_batch_purchase_transition_buyer2 + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_purchase_serialized_transition_buyer2.clone()], + &platform_state, + &BlockInfo::default_with_time(250000000), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Should succeed because (buyer2, creator) is available + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Step 7: Buyer2 deletes document3 (which has owner=buyer2, creator=creator) + document3.set_owner_id(buyer2.id()); + document3.set_revision(Some(4)); + + let documents_batch_deletion_transition = + BatchTransition::new_document_deletion_transition_from_document( + document3, + card_document_type, + &buyer2_key, + 3, + 0, + None, + &buyer2_signer, + platform_version, + None, + ) + .expect("expect to create documents batch deletion transition"); + + let documents_batch_deletion_serialized_transition = documents_batch_deletion_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_deletion_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_eq!(processing_result.valid_count(), 1); + assert_eq!(processing_result.invalid_paid_count(), 0); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Verify final state + let buyer1_documents_sql_string = + format!("select * from card where $ownerId == '{}'", buyer1.id()); + + let query_buyer1_identity_documents = DriveDocumentQuery::from_sql_expr( + buyer1_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_buyer1_results = platform + .drive + .query_documents(query_buyer1_identity_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_buyer1_results.documents().len(), 1); + + let buyer2_documents_sql_string = + format!("select * from card where $ownerId == '{}'", buyer2.id()); + + let query_buyer2_identity_documents = DriveDocumentQuery::from_sql_expr( + buyer2_documents_sql_string.as_str(), + &contract, + Some(&platform.config.drive), + ) + .expect("expected document query"); + + let query_buyer2_results = platform + .drive + .query_documents(query_buyer2_identity_documents, None, false, None, None) + .expect("expected query result"); + + assert_eq!(query_buyer2_results.documents().len(), 0); + + let issues = platform + .drive + .grove + .visualize_verify_grovedb(None, true, false, &platform_version.drive.grove_version) + .expect("expected to have no issues"); + + assert_eq!( + issues.len(), + 0, + "issues are {}", + issues + .iter() + .map(|(hash, (a, b, c))| format!("{}: {} {} {}", hash, a, b, c)) + .collect::>() + .join(" | ") + ); + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs index 90ac2a1018d..6f345d9a70d 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/transfer.rs @@ -1774,7 +1774,7 @@ mod transfer_tests { } #[test] - fn test_document_creator_id_unique_index_enforcement() { + fn test_document_creator_id_unique_index_enforcement_during_transfer() { // This test verifies that a unique index on creator_id is properly enforced throughout // the complete document lifecycle, ensuring that only one document per creator can exist // at any time, regardless of ownership changes. @@ -2284,7 +2284,7 @@ mod transfer_tests { } #[test] - fn test_document_owner_and_creator_id_unique_index_enforcement() { + fn test_document_owner_and_creator_id_unique_index_enforcement_during_transfer() { // This test verifies that a unique compound index on (owner_id, creator_id) is properly // enforced throughout the document lifecycle, allowing a creator to have multiple documents // but preventing duplicate (owner, creator) combinations. diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-index.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-index.json new file mode 100644 index 00000000000..3d9eb91ad14 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-index.json @@ -0,0 +1,143 @@ +{ + "$format_version": "1", + "id": "86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "version": 1, + "documentSchemas": { + "card": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": true, + "transferable": 1, + "tradeMode": 1, + "properties": { + "name": { + "type": "string", + "description": "Name of the card", + "maxLength": 63, + "position": 0 + }, + "description": { + "type": "string", + "description": "Description of the card", + "maxLength": 256, + "position": 1 + }, + "imageUrl": { + "type": "string", + "description": "URL of the image associated with the card", + "maxLength": 2048, + "format": "uri", + "position": 2 + }, + "imageHash": { + "type": "array", + "description": "SHA256 hash of the bytes of the image specified by imageUrl", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 3 + }, + "imageFingerprint": { + "type": "array", + "description": "dHash of the image specified by imageUrl", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "position": 4 + }, + "attack": { + "type": "integer", + "description": "Attack power of the card", + "minimum": 0, + "position": 5 + }, + "defense": { + "type": "integer", + "description": "Defense level of the card", + "minimum": 0, + "position": 6 + } + }, + "indices": [ + { + "name": "owner", + "properties": [ + { + "$ownerId": "asc" + } + ] + }, + { + "name": "creator", + "properties": [ + { + "$creatorId": "asc" + } + ], + "unique": true + }, + { + "name": "attack", + "properties": [ + { + "attack": "asc" + } + ] + }, + { + "name": "defense", + "properties": [ + { + "defense": "asc" + } + ] + }, + { + "name": "transferredAt", + "properties": [ + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "ownerTransferredAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "transferredAtBlockHeight", + "properties": [ + { + "$transferredAtBlockHeight": "asc" + } + ] + }, + { + "name": "transferredAtCoreBlockHeight", + "properties": [ + { + "$transferredAtCoreBlockHeight": "asc" + } + ] + } + ], + "required": [ + "name", + "$transferredAt", + "$transferredAtBlockHeight", + "$transferredAtCoreBlockHeight", + "attack", + "defense" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-with-owner-id-index.json b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-with-owner-id-index.json new file mode 100644 index 00000000000..d1424c6f1d7 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase-unique-creator-id-with-owner-id-index.json @@ -0,0 +1,146 @@ +{ + "$format_version": "1", + "id": "86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e", + "ownerId": "2QjL594djCH2NyDsn45vd6yQjEDHupMKo7CEGVTHtQxU", + "version": 1, + "documentSchemas": { + "card": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": true, + "transferable": 1, + "tradeMode": 1, + "properties": { + "name": { + "type": "string", + "description": "Name of the card", + "maxLength": 63, + "position": 0 + }, + "description": { + "type": "string", + "description": "Description of the card", + "maxLength": 256, + "position": 1 + }, + "imageUrl": { + "type": "string", + "description": "URL of the image associated with the card", + "maxLength": 2048, + "format": "uri", + "position": 2 + }, + "imageHash": { + "type": "array", + "description": "SHA256 hash of the bytes of the image specified by imageUrl", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 3 + }, + "imageFingerprint": { + "type": "array", + "description": "dHash of the image specified by imageUrl", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "position": 4 + }, + "attack": { + "type": "integer", + "description": "Attack power of the card", + "minimum": 0, + "position": 5 + }, + "defense": { + "type": "integer", + "description": "Defense level of the card", + "minimum": 0, + "position": 6 + } + }, + "indices": [ + { + "name": "owner", + "properties": [ + { + "$ownerId": "asc" + } + ] + }, + { + "name": "owner_creator", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$creatorId": "asc" + } + ], + "unique": true + }, + { + "name": "attack", + "properties": [ + { + "attack": "asc" + } + ] + }, + { + "name": "defense", + "properties": [ + { + "defense": "asc" + } + ] + }, + { + "name": "transferredAt", + "properties": [ + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "ownerTransferredAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$transferredAt": "asc" + } + ] + }, + { + "name": "transferredAtBlockHeight", + "properties": [ + { + "$transferredAtBlockHeight": "asc" + } + ] + }, + { + "name": "transferredAtCoreBlockHeight", + "properties": [ + { + "$transferredAtCoreBlockHeight": "asc" + } + ] + } + ], + "required": [ + "name", + "$transferredAt", + "$transferredAtBlockHeight", + "$transferredAtCoreBlockHeight", + "attack", + "defense" + ], + "additionalProperties": false + } + } +} \ No newline at end of file From 26d3454b7cc8631eef0df3aed4eb79bdb12c339a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 1 Oct 2025 15:44:45 +0700 Subject: [PATCH 12/13] fixes --- .../mod.rs | 1 - .../v1/mod.rs | 5 ++--- packages/wasm-dpp/test/integration/document/Document.spec.js | 1 + packages/wasm-dpp/test/unit/document/Document.spec.js | 2 ++ 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs index f747bed59b5..d70296faff5 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/mod.rs @@ -65,7 +65,6 @@ impl Drive { contract, document_type, document_purchase_transition, - owner_id, transaction, platform_version, ), diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs index 4053d41dc5e..885756e5fa5 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs @@ -30,14 +30,13 @@ impl Drive { contract: &DataContract, document_type: DocumentTypeRef, document_purchase_transition: &DocumentPurchaseTransitionAction, - owner_id: Identifier, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { let request = UniquenessOfDataRequestV1 { contract, document_type, - owner_id, + owner_id: document_purchase_transition.document().owner_id(), creator_id: document_purchase_transition.document().creator_id(), document_id: document_purchase_transition.base().id(), created_at: document_purchase_transition.document().created_at(), @@ -70,7 +69,7 @@ impl Drive { changed_transferred_at_block_height: true, changed_updated_at_core_block_height: false, changed_transferred_at_core_block_height: true, - changed_data_values: Cow::Owned(BTreeSet::from(["price".to_string()])), + changed_data_values: Default::default(), // we don't need to set price as changed, because it is changing to None, which isn't a unique index }, }; self.validate_uniqueness_of_data(request.into(), transaction, platform_version) diff --git a/packages/wasm-dpp/test/integration/document/Document.spec.js b/packages/wasm-dpp/test/integration/document/Document.spec.js index e418228e243..cf153f3f0e1 100644 --- a/packages/wasm-dpp/test/integration/document/Document.spec.js +++ b/packages/wasm-dpp/test/integration/document/Document.spec.js @@ -57,6 +57,7 @@ describe('ExtendedDocument', () => { $createdAt: null, // TODO: it should be omitted $createdAtBlockHeight: null, $createdAtCoreBlockHeight: null, + $creatorId: null, $updatedAt: null, $updatedAtBlockHeight: null, $updatedAtCoreBlockHeight: null, diff --git a/packages/wasm-dpp/test/unit/document/Document.spec.js b/packages/wasm-dpp/test/unit/document/Document.spec.js index 382a0b4bc14..7e2a3afbaa2 100644 --- a/packages/wasm-dpp/test/unit/document/Document.spec.js +++ b/packages/wasm-dpp/test/unit/document/Document.spec.js @@ -84,6 +84,7 @@ describe('Document', () => { $createdAt: now, $createdAtBlockHeight: 1, $createdAtCoreBlockHeight: 1, + $creatorId: null, $updatedAt: now, $updatedAtBlockHeight: 1, $updatedAtCoreBlockHeight: 1, @@ -102,6 +103,7 @@ describe('Document', () => { $createdAt: now, $createdAtBlockHeight: 1, $createdAtCoreBlockHeight: 1, + $creatorId: null, $updatedAt: now, $updatedAtBlockHeight: 1, $updatedAtCoreBlockHeight: 1, From 1ecce7c6809d220e99f25bbfb7646c9ac672093b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 1 Oct 2025 15:49:39 +0700 Subject: [PATCH 13/13] linting --- .../v1/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs index 885756e5fa5..640bcf45eee 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/validate_document_purchase_transition_action_uniqueness/v1/mod.rs @@ -1,6 +1,4 @@ use dpp::data_contract::DataContract; -use std::borrow::Cow; -use std::collections::BTreeSet; use crate::drive::Drive; @@ -11,8 +9,6 @@ use crate::error::Error; use dpp::data_contract::document_type::DocumentTypeRef; -use dpp::identifier::Identifier; - use dpp::validation::SimpleConsensusValidationResult; use dpp::document::DocumentV0Getters;