From 18dda42b28e280573f46c3efe52c61c97bf55877 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:53:20 +0100 Subject: [PATCH 01/19] doc: reference validation spec --- docs/specs/reference-validation.md | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 docs/specs/reference-validation.md diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md new file mode 100644 index 00000000000..74575500766 --- /dev/null +++ b/docs/specs/reference-validation.md @@ -0,0 +1,44 @@ +# Reference Validation (`refersTo`) + +## Summary +Introduce an optional `refersTo` keyword on document properties so contracts can request existence checks for referenced identities. When present, state validation rejects documents whose referenced identity does not exist. Contracts and documents without `refersTo` are unchanged. + +## Schema Changes +- Extend document meta-schema (`packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json`) to allow a `refersTo` object alongside existing keywords. +- `refersTo` structure: + - `type`: `"identity"` (reserved for future target types later). + - `mustExist`: `boolean` (optional, defaults to `true`; `false` means no reference validation). +- Validation rules during schema parsing: + - Only allowed on identifier-typed properties (array byteArray=true, minItems=32, maxItems=32, `contentMediaType` identifier). + - Not allowed on non-identifier properties (reject contract). +- Mutability: `refersTo` may only be set at contract creation; contract updates cannot add or change it. +- JSON Schema compatibility rules must allow the keyword but should reject updates that attempt to add/modify it post-creation. + +## Data Model / Parsing +- During `DocumentType::try_from_schema`, detect `refersTo` and store per-property reference metadata (path → `{ targetType: Identity, mustExist: bool }`). +- Expose reference metadata through DocumentType accessors and WASM/JS bindings so clients can introspect. +- Keep existing `identifier_paths`/`binary_paths` behavior (the sets of property paths already tracked for identifier and binary fields); `refersTo` is additive on top. + +## Runtime Validation +- Enforce in state validation (Drive) for document create and replace transitions: + - For each property with `refersTo.mustExist == true`, fetch the referenced identity ID and fail with a consensus state error if missing. + - Support nested properties (use flattened property paths). + - Count identity fetches in execution context fee accounting; use batching/caching within a batch transition to minimize repeated lookups. +- Applied in ABCI paths: CheckTx, PrepareProposal, and ProcessProposal. +- Basic validation (DPP) only checks keyword shape/placement; no state access. + +## Errors +- Add a dedicated consensus state error, e.g., `ReferencedIdentityNotFoundError { path, identityId }`. +- Avoid overloading signature errors; ensure deterministic mapping to codes. + +## Backward Compatibility +- Gated by platform/protocol version (and/or data contract system version). Nodes on older protocol should ignore/allow the keyword; enforcement begins at the gated version. +- Existing contracts and documents remain valid; documents are rejected only when the contract opts in with `mustExist:true`. +- Legacy nodes should ignore `refersTo` until activation; newer nodes enforce `mustExist:true` semantics. + +## Acceptance Criteria +- Contracts containing `refersTo` validate against updated meta-schema and pass compatibility checks when added to existing identifier fields. +- Documents with `refersTo.type=identity` + `mustExist:true` are accepted only if the referenced identities exist; missing ones return the new consensus error. +- Documents without `refersTo`, or with `mustExist:false`, behave exactly as today. +- Enforcement applies to create and replace transitions (including nested fields) with proper fee accounting for identity lookups. +- WASM/JS bindings serialize/deserialize `refersTo` metadata; tests cover parsing and state validation failures. From 802d10e3fb4dda48c55bf1f6cbbbb61e6f3bf00c Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:58:25 +0100 Subject: [PATCH 02/19] chore: minor improvement --- docs/specs/reference-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md index 74575500766..f3a20b6da03 100644 --- a/docs/specs/reference-validation.md +++ b/docs/specs/reference-validation.md @@ -6,7 +6,7 @@ Introduce an optional `refersTo` keyword on document properties so contracts can ## Schema Changes - Extend document meta-schema (`packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json`) to allow a `refersTo` object alongside existing keywords. - `refersTo` structure: - - `type`: `"identity"` (reserved for future target types later). + - `type`: `"identity"` (current target; keep the mechanism extensible for other reference targets such as documents or contracts). - `mustExist`: `boolean` (optional, defaults to `true`; `false` means no reference validation). - Validation rules during schema parsing: - Only allowed on identifier-typed properties (array byteArray=true, minItems=32, maxItems=32, `contentMediaType` identifier). From 06b4e90d4f650dc6e7507025348cd87423077ce5 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:01:29 +0100 Subject: [PATCH 03/19] doc --- docs/specs/reference-validation.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md index f3a20b6da03..e5189419fa4 100644 --- a/docs/specs/reference-validation.md +++ b/docs/specs/reference-validation.md @@ -32,9 +32,8 @@ Introduce an optional `refersTo` keyword on document properties so contracts can - Avoid overloading signature errors; ensure deterministic mapping to codes. ## Backward Compatibility -- Gated by platform/protocol version (and/or data contract system version). Nodes on older protocol should ignore/allow the keyword; enforcement begins at the gated version. -- Existing contracts and documents remain valid; documents are rejected only when the contract opts in with `mustExist:true`. -- Legacy nodes should ignore `refersTo` until activation; newer nodes enforce `mustExist:true` semantics. +- Gated by platform/protocol version (and/or data contract system version). Legacy nodes reject contracts containing `refersTo`; such contracts are accepted only after activation. Post-activation, newer nodes enforce `mustExist:true` semantics. +- Existing pre-activation contracts and documents remain valid; documents are rejected only when the contract opts in with `mustExist:true` and the network is past activation. ## Acceptance Criteria - Contracts containing `refersTo` validate against updated meta-schema and pass compatibility checks when added to existing identifier fields. From 93902c5193054962a7fe4d66a21a2ace6400bfc7 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:15:02 +0100 Subject: [PATCH 04/19] spec update --- docs/specs/reference-validation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md index e5189419fa4..bf3652ea2d0 100644 --- a/docs/specs/reference-validation.md +++ b/docs/specs/reference-validation.md @@ -20,10 +20,11 @@ Introduce an optional `refersTo` keyword on document properties so contracts can - Keep existing `identifier_paths`/`binary_paths` behavior (the sets of property paths already tracked for identifier and binary fields); `refersTo` is additive on top. ## Runtime Validation -- Enforce in state validation (Drive) for document create and replace transitions: +- Enforce during Drive advanced structure validation (`validate_advanced_structure_from_state` in batch transitions) for document create and replace transitions: - For each property with `refersTo.mustExist == true`, fetch the referenced identity ID and fail with a consensus state error if missing. - Support nested properties (use flattened property paths). - - Count identity fetches in execution context fee accounting; use batching/caching within a batch transition to minimize repeated lookups. + - Count identity fetches in execution context fee accounting. +- Implement via versioned advanced structure validators for document create/replace (new v1 modules) while keeping v0 behavior unchanged. - Applied in ABCI paths: CheckTx, PrepareProposal, and ProcessProposal. - Basic validation (DPP) only checks keyword shape/placement; no state access. From 3d5c0b72b40b0e0b07258d5753cfe1c387aec910 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 10:31:00 +0100 Subject: [PATCH 05/19] feat: add refersTo to document schema --- .../document/v0/document-meta.json | 21 +++++++- .../src/rules/rule_set.rs | 48 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json index 78bbeccef3b..b2be21888ae 100644 --- a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json +++ b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json @@ -123,6 +123,25 @@ "minItems": 1, "uniqueItems": true }, + "refersTo": { + "type": "object", + "properties": { + "type": { + "enum": [ + "identity", + "contract" + ] + }, + "mustExist": { + "type": "boolean", + "default": true + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, "type": { "$ref": "https://json-schema.org/draft/2020-12/meta/validation#/properties/type" }, @@ -606,4 +625,4 @@ "properties", "additionalProperties" ] -} \ No newline at end of file +} diff --git a/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs b/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs index 2962395889a..52442c0aa8c 100644 --- a/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs +++ b/packages/rs-json-schema-compatibility-validator/src/rules/rule_set.rs @@ -1160,6 +1160,54 @@ pub static KEYWORD_COMPATIBILITY_RULES: Lazy = Laz ], }, ), + ( + "refersTo", + CompatibilityRules { + allow_addition: false, + allow_removal: false, + allow_replacement_callback: FALSE_CALLBACK.clone(), + subschema_levels_depth: None, + inner: None, + #[cfg(any(test, feature = "examples"))] + examples: vec![ + ( + json!({}), + json!({ "refersTo": { "type": "identity" } }), + Some(JsonSchemaChange::Add(AddOperation { + path: "/refersTo".to_string(), + value: json!({ "type": "identity" }), + })), + ) + .into(), + ( + json!({ "refersTo": { "type": "identity" } }), + json!({}), + Some(JsonSchemaChange::Remove(RemoveOperation { + path: "/refersTo".to_string(), + })), + ) + .into(), + ( + json!({ "refersTo": { "type": "identity" } }), + json!({ "refersTo": { "type": "identity", "mustExist": false } }), + Some(JsonSchemaChange::Add(AddOperation { + path: "/refersTo/mustExist".to_string(), + value: json!(false), + })), + ) + .into(), + ( + json!({ "refersTo": { "type": "identity" } }), + json!({ "refersTo": { "type": "contract" } }), + Some(JsonSchemaChange::Replace(ReplaceOperation { + path: "/refersTo/type".to_string(), + value: json!("contract"), + })), + ) + .into(), + ], + }, + ), ( "byteArray", CompatibilityRules { From dbd9616acdd003a0015c319a077c121202b9f86a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:03:07 +0100 Subject: [PATCH 06/19] chore(dpp): add DocumentProperty.reference --- .../class_methods/try_from_schema/mod.rs | 160 +++++++++++++++++- .../src/data_contract/document_type/mod.rs | 1 + .../document_type/property/mod.rs | 13 ++ .../document_type/v0/random_document_type.rs | 2 + 4 files changed, 174 insertions(+), 2 deletions(-) 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 37de63fe927..878a9ee26d3 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 @@ -2,7 +2,8 @@ use crate::data_contract::config::DataContractConfig; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::v1::DocumentTypeV1; use crate::data_contract::document_type::{ - property_names, DocumentProperty, DocumentPropertyType, DocumentType, + property_names, DocumentProperty, DocumentPropertyReference, DocumentPropertyReferenceTarget, + DocumentPropertyType, DocumentType, }; use crate::data_contract::errors::DataContractError; use crate::data_contract::{TokenConfiguration, TokenContractPosition}; @@ -113,7 +114,10 @@ fn insert_values( let is_required = known_required.contains(&prefixed_property_key); let is_transient = known_transient.contains(&prefixed_property_key); - match DocumentPropertyType::try_from_value_map(&inner_properties, &config.into())? { + let property_type = + DocumentPropertyType::try_from_value_map(&inner_properties, &config.into())?; + + match property_type { DocumentPropertyType::Object(_) => { if let Some(properties_as_value) = inner_properties.get(property_names::PROPERTIES) { @@ -140,12 +144,14 @@ fn insert_values( } } property_type => { + let reference = parse_property_reference(&inner_properties, &property_type)?; document_properties.insert( prefixed_property_key, DocumentProperty { property_type, required: is_required, transient: is_transient, + reference, }, ); } @@ -250,14 +256,164 @@ fn insert_values_nested( property_type => property_type, }; + let reference = parse_property_reference(&inner_properties, &property_type)?; + document_properties.insert( property_key, DocumentProperty { property_type, required: is_required, transient: is_transient, + reference, }, ); Ok(()) } + +fn parse_property_reference( + inner_properties: &BTreeMap, + property_type: &DocumentPropertyType, +) -> Result, DataContractError> { + let Some(refers_to_value) = inner_properties.get(property_names::REFERS_TO) else { + return Ok(None); + }; + + if !matches!(property_type, DocumentPropertyType::Identifier) { + return Err(DataContractError::InvalidContractStructure( + "refersTo is only allowed on identifier properties".to_string(), + )); + } + + let refers_to_map = refers_to_value.to_btree_ref_string_map()?; + + let target = match refers_to_map + .get_str(property_names::TYPE) + .map_err(|e| DataContractError::ValueWrongType(e.to_string()))? + { + "identity" => DocumentPropertyReferenceTarget::Identity, + "contract" => DocumentPropertyReferenceTarget::Contract, + other => { + return Err(DataContractError::InvalidContractStructure(format!( + "invalid refersTo type {other}" + ))) + } + }; + + let must_exist = refers_to_map + .get_optional_bool("mustExist") + .map_err(|e| DataContractError::ValueWrongType(e.to_string()))? + .unwrap_or(true); + + Ok(Some(DocumentPropertyReference { target, must_exist })) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::data_contract::config::DataContractConfig; + use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; + use platform_value::Identifier; + use platform_version::version::PlatformVersion; + use serde_json::json; + use std::collections::BTreeMap; + + #[test] + fn should_parse_refers_to_on_identifier_property() { + let platform_version = PlatformVersion::latest(); + let config = + DataContractConfig::default_for_version(platform_version).expect("config should build"); + + let schema = json!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity" + } + } + }, + "required": [], + "additionalProperties": false + }); + + let value = platform_value::to_value(schema).expect("schema should convert"); + + let document_type = DocumentType::try_from_schema( + Identifier::random(), + 0, + config.version(), + "msg", + value, + None, + &BTreeMap::new(), + &config, + false, + &mut vec![], + platform_version, + ) + .expect("should parse"); + + let reference = document_type + .as_ref() + .flattened_properties() + .get("toUserId") + .and_then(|p| p.reference.clone()) + .expect("reference should be present"); + + assert!(matches!( + reference.target, + DocumentPropertyReferenceTarget::Identity + )); + assert!(reference.must_exist); + } + + #[test] + fn should_reject_refers_to_on_non_identifier_property() { + let platform_version = PlatformVersion::latest(); + let config = + DataContractConfig::default_for_version(platform_version).expect("config should build"); + + let schema = json!({ + "type": "object", + "properties": { + "name": { + "type": "string", + "position": 0, + "refersTo": { "type": "identity" } + } + }, + "required": [], + "additionalProperties": false + }); + + let value = platform_value::to_value(schema).expect("schema should convert"); + + let err = DocumentType::try_from_schema( + Identifier::random(), + 0, + config.version(), + "msg", + value, + None, + &BTreeMap::new(), + &config, + false, + &mut vec![], + platform_version, + ) + .expect_err("should fail"); + + let message = err.to_string(); + assert!( + message.contains("refersTo is only allowed on identifier properties"), + "unexpected error: {message}" + ); + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/mod.rs b/packages/rs-dpp/src/data_contract/document_type/mod.rs index aadb6eedbfc..88140141d51 100644 --- a/packages/rs-dpp/src/data_contract/document_type/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/mod.rs @@ -74,6 +74,7 @@ pub(crate) mod property_names { pub const CONTENT_MEDIA_TYPE: &str = "contentMediaType"; pub const ENCRYPTION_KEY_REQUIREMENTS: &str = "encryptionKeyReqs"; pub const DECRYPTION_KEY_REQUIREMENTS: &str = "decryptionKeyReqs"; + pub const REFERS_TO: &str = "refersTo"; } #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index d295ee7b385..7afbfd47d8f 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -34,6 +34,7 @@ pub struct DocumentProperty { pub property_type: DocumentPropertyType, pub required: bool, pub transient: bool, + pub reference: Option, } #[derive(Debug, PartialEq, Clone, Serialize)] @@ -48,6 +49,18 @@ pub struct ByteArrayPropertySizes { pub max_size: Option, } +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct DocumentPropertyReference { + pub target: DocumentPropertyReferenceTarget, + pub must_exist: bool, +} + +#[derive(Debug, PartialEq, Clone, Serialize)] +pub enum DocumentPropertyReferenceTarget { + Identity, + Contract, +} + // @append_only #[derive(Debug, PartialEq, Clone, Serialize)] pub enum DocumentPropertyType { diff --git a/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs b/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs index 34a844509d1..85664aa72ad 100644 --- a/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs +++ b/packages/rs-dpp/src/data_contract/document_type/v0/random_document_type.rs @@ -197,6 +197,7 @@ impl DocumentTypeV0 { property_type: document_type, required, transient: false, + reference: None, } }; @@ -523,6 +524,7 @@ impl DocumentTypeV0 { property_type: document_type, required, transient: false, + reference: None, } }; From 69bb293f28a355b26b7f9abb8a03a3c994c3f016 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:56:50 +0100 Subject: [PATCH 07/19] chore(dpp): define ReferencedEntityNotFoundError --- .../document_type/property/mod.rs | 16 +++++- packages/rs-dpp/src/errors/consensus/codes.rs | 1 + .../errors/consensus/state/document/mod.rs | 1 + .../referenced_entity_not_found_error.rs | 56 +++++++++++++++++++ .../src/errors/consensus/state/state_error.rs | 4 ++ 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 packages/rs-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 7afbfd47d8f..6d08f29db64 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -3,6 +3,7 @@ use std::convert::TryInto; use std::io::{BufReader, Cursor, Read}; +use bincode::{Decode, Encode}; use crate::data_contract::errors::DataContractError; use crate::consensus::basic::decode::DecodingError; @@ -16,6 +17,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use indexmap::IndexMap; use integer_encoding::{VarInt, VarIntReader}; use itertools::Itertools; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; use platform_value::btreemap_extensions::BTreeValueMapHelper; use platform_value::{Identifier, Value}; use platform_version::version::PlatformVersion; @@ -55,12 +57,24 @@ pub struct DocumentPropertyReference { pub must_exist: bool, } -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive( + Debug, PartialEq, Eq, Clone, Serialize, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[serde(rename_all = "lowercase")] pub enum DocumentPropertyReferenceTarget { Identity, Contract, } +impl std::fmt::Display for DocumentPropertyReferenceTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DocumentPropertyReferenceTarget::Identity => write!(f, "identity"), + DocumentPropertyReferenceTarget::Contract => write!(f, "contract"), + } + } +} + // @append_only #[derive(Debug, PartialEq, Clone, Serialize)] pub enum DocumentPropertyType { diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index b25a99a6909..035ab99980c 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -297,6 +297,7 @@ impl ErrorWithCode for StateError { Self::RequiredTokenPaymentInfoNotSetError(_) => 40115, Self::IdentityHasNotAgreedToPayRequiredTokenAmountError(_) => 40116, Self::IdentityTryingToPayWithWrongTokenError(_) => 40117, + Self::ReferencedEntityNotFoundError { .. } => 40118, // Identity Errors: 40200-40299 Self::IdentityAlreadyExistsError(_) => 40200, diff --git a/packages/rs-dpp/src/errors/consensus/state/document/mod.rs b/packages/rs-dpp/src/errors/consensus/state/document/mod.rs index 0c59e8741c3..7fe5d91a8f3 100644 --- a/packages/rs-dpp/src/errors/consensus/state/document/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/state/document/mod.rs @@ -13,3 +13,4 @@ pub mod document_timestamps_are_equal_error; pub mod document_timestamps_mismatch_error; pub mod duplicate_unique_index_error; pub mod invalid_document_revision_error; +pub mod referenced_entity_not_found_error; diff --git a/packages/rs-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs b/packages/rs-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs new file mode 100644 index 00000000000..7628c155ae9 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs @@ -0,0 +1,56 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::data_contract::document_type::DocumentPropertyReferenceTarget; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use platform_value::Identifier; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("referenced {entity_type} {entity_id} not found for path {path}")] +#[platform_serialize(unversioned)] +pub struct ReferencedEntityNotFoundError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + entity_id: Identifier, + entity_type: DocumentPropertyReferenceTarget, + path: String, +} + +impl ReferencedEntityNotFoundError { + pub fn new( + entity_id: Identifier, + entity_type: DocumentPropertyReferenceTarget, + path: String, + ) -> Self { + Self { + entity_id, + entity_type, + path, + } + } + + pub fn entity_id(&self) -> &Identifier { + &self.entity_id + } + + pub fn entity_type(&self) -> &DocumentPropertyReferenceTarget { + &self.entity_type + } + + pub fn path(&self) -> &str { + &self.path + } +} + +impl From for ConsensusError { + fn from(err: ReferencedEntityNotFoundError) -> Self { + Self::StateError(StateError::ReferencedEntityNotFoundError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index f3d3e9ad731..c739291e05e 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -36,6 +36,7 @@ use crate::consensus::state::document::document_contest_not_joinable_error::Docu use crate::consensus::state::document::document_contest_not_paid_for_error::DocumentContestNotPaidForError; use crate::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use crate::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; +use crate::consensus::state::document::referenced_entity_not_found_error::ReferencedEntityNotFoundError; use crate::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityMemberOfGroupNotFoundError, IdentityNotMemberOfGroupError, ModificationOfGroupActionMainParametersNotPermittedError}; use crate::consensus::state::identity::identity_for_token_configuration_not_found_error::IdentityInTokenConfigurationNotFoundError; use crate::consensus::state::identity::identity_public_key_already_exists_for_unique_contract_bounds_error::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError; @@ -109,6 +110,9 @@ pub enum StateError { #[error(transparent)] DocumentTimestampWindowViolationError(DocumentTimestampWindowViolationError), + #[error(transparent)] + ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), + #[error(transparent)] DuplicateUniqueIndexError(DuplicateUniqueIndexError), From eeb43340b94992d4368e2156bb4c35cd3917bec1 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:03:45 +0100 Subject: [PATCH 08/19] fix(scripts): configure_test_suite_network.sh not working --- scripts/configure_test_suite_network.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/configure_test_suite_network.sh b/scripts/configure_test_suite_network.sh index b84e0286afa..2e3ffe32c4f 100755 --- a/scripts/configure_test_suite_network.sh +++ b/scripts/configure_test_suite_network.sh @@ -53,7 +53,7 @@ FAUCET_PRIVATE_KEY=$(yq .faucet_privkey "$CONFIG") MASTERNODE_NAME=$(grep "$DAPI_SEED" "$INVENTORY" | awk '{print $1;}') MASTERNODE_OWNER_PRO_REG_TX_HASH=$(grep "$DAPI_SEED" "$INVENTORY" | awk -F "=" '{print $6;}') -MASTERNODE_OWNER_MASTER_PRIVATE_KEY=$(yq .hp_masternodes."$MASTERNODE_NAME".owner.private_key "$CONFIG") +MASTERNODE_OWNER_MASTER_PRIVATE_KEY=$(yq ".hp_masternodes[\"$MASTERNODE_NAME\"].owner.private_key" "$CONFIG") if [[ "$NETWORK_STRING" == "devnet"* ]]; then NETWORK=devnet @@ -64,7 +64,7 @@ else CERT_FLAG="" ST_EXECUTION_INTERVAL=15000 fi -INSIGHT_URL="http://insight.${NETWORK_STRING#devnet-}.networks.dash.org:3001/insight-api/sync" +INSIGHT_URL="https://insight.${NETWORK_STRING#devnet-}.networks.dash.org:443/insight-api/sync" SKIP_SYNC_BEFORE_HEIGHT=$(curl -s $INSIGHT_URL | jq '.height - 200') # check variables are not empty From 2eab1cabd31c4d6c405c94259b52cf34bf210b7f Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:56:04 +0100 Subject: [PATCH 09/19] chore: implemented? --- packages/dapi-grpc/Cargo.toml | 2 +- .../document_type/property/mod.rs | 2 +- .../src/errors/consensus/basic/basic_error.rs | 4 + packages/rs-dpp/src/errors/consensus/codes.rs | 1 + .../check_tx_verification/v0/mod.rs | 1 - .../traits/advanced_structure_with_state.rs | 1 + .../state_transition/processor/v0/mod.rs | 1 - .../document_create_transition_action/mod.rs | 12 +- .../state_v2/mod.rs | 65 +++++++ .../document_reference_validation/mod.rs | 61 +++++++ .../document_reference_validation/v0/mod.rs | 164 ++++++++++++++++++ .../document_replace_transition_action/mod.rs | 22 ++- .../state_v1/mod.rs | 44 +++++ .../state_v2/mod.rs | 65 +++++++ .../batch/action_validation/document/mod.rs | 1 + .../batch/advanced_structure/v0/mod.rs | 5 +- .../state_transitions/batch/mod.rs | 2 +- .../identity_create_from_addresses/mod.rs | 2 +- .../drive_abci_validation_versions/mod.rs | 2 + .../drive_abci_validation_versions/v1.rs | 1 + .../drive_abci_validation_versions/v2.rs | 1 + .../drive_abci_validation_versions/v3.rs | 1 + .../drive_abci_validation_versions/v4.rs | 1 + .../drive_abci_validation_versions/v5.rs | 1 + .../drive_abci_validation_versions/v6.rs | 1 + .../drive_abci_validation_versions/v7.rs | 1 + .../drive_abci_validation_versions/v8.rs | 16 ++ .../rs-platform-version/src/version/mod.rs | 5 +- .../src/version/protocol_version.rs | 4 +- .../rs-platform-version/src/version/v12.rs | 68 ++++++++ .../src/errors/consensus/consensus_error.rs | 7 + 31 files changed, 551 insertions(+), 13 deletions(-) create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/state_v2/mod.rs create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/mod.rs create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v1/mod.rs create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v2/mod.rs create mode 100644 packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs create mode 100644 packages/rs-platform-version/src/version/v12.rs diff --git a/packages/dapi-grpc/Cargo.toml b/packages/dapi-grpc/Cargo.toml index da9363202fc..b04dd2bbb0c 100644 --- a/packages/dapi-grpc/Cargo.toml +++ b/packages/dapi-grpc/Cargo.toml @@ -83,6 +83,6 @@ ignored = [ "platform-version", "futures-core", "tonic-prost-build", - "getrandom", # Ignore getrandom as we need it to enable `js` feature + "getrandom", # Ignore getrandom as we need it to enable `js` feature "dash-platform-macros", # used in build.rs ] diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 6d08f29db64..4a85b3f1c2b 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -3,8 +3,8 @@ use std::convert::TryInto; use std::io::{BufReader, Cursor, Read}; -use bincode::{Decode, Encode}; use crate::data_contract::errors::DataContractError; +use bincode::{Decode, Encode}; use crate::consensus::basic::decode::DecodingError; use crate::data_contract::config::v1::DataContractConfigGettersV1; diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index 08f6a5aa874..8fc241782d0 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -1,3 +1,4 @@ +use crate::consensus::state::document::referenced_entity_not_found_error::ReferencedEntityNotFoundError; use crate::errors::ProtocolError; use bincode::{Decode, Encode}; use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; @@ -273,6 +274,9 @@ pub enum BasicError { #[error(transparent)] MaxDocumentsTransitionsExceededError(MaxDocumentsTransitionsExceededError), + #[error(transparent)] + ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), + // Identity #[error(transparent)] DuplicatedIdentityPublicKeyBasicError(DuplicatedIdentityPublicKeyBasicError), diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 035ab99980c..591badfb047 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -153,6 +153,7 @@ impl ErrorWithCode for BasicError { Self::DocumentCreationNotAllowedError(_) => 10416, Self::DocumentFieldMaxSizeExceededError(_) => 10417, Self::ContestedDocumentsTemporarilyNotAllowedError(_) => 10418, + Self::ReferencedEntityNotFoundError(_) => 10419, // Token Errors: 10450-10499 Self::InvalidTokenIdError(_) => 10450, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rs index fe0fc422559..a93a3139106 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rs @@ -241,7 +241,6 @@ pub(super) fn state_transition_to_execution_event_for_check_tx_v0<'a, C: CoreRPC } let action = state_transition_action_result.into_data()?; - // Validating structure let result = state_transition.validate_advanced_structure_from_state( platform.state.last_block_info(), platform.config.network, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/advanced_structure_with_state.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/advanced_structure_with_state.rs index 9287e90c6f5..222e89b4b36 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/advanced_structure_with_state.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/advanced_structure_with_state.rs @@ -25,6 +25,7 @@ pub(crate) trait StateTransitionStructureKnownInStateValidationV0 { /// # Returns /// /// * `Result` - A result with either a SimpleConsensusValidationResult or an Error. + #[allow(clippy::too_many_arguments)] fn validate_advanced_structure_from_state( &self, block_info: &BlockInfo, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs index 8765733d5eb..78f07c3de7b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs @@ -269,7 +269,6 @@ pub(super) fn process_state_transition_v0<'a, C: CoreRPCLike>( } let action = state_transition_action_result.into_data()?; - // Validating structure let result = state_transition.validate_advanced_structure_from_state( block_info, platform.config.network, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/mod.rs index af90241f1da..216dd19bfc3 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/mod.rs @@ -10,12 +10,14 @@ use crate::error::execution::ExecutionError; use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; use crate::execution::validation::state_transition::batch::action_validation::document::document_create_transition_action::state_v0::DocumentCreateTransitionActionStateValidationV0; use crate::execution::validation::state_transition::batch::action_validation::document::document_create_transition_action::state_v1::DocumentCreateTransitionActionStateValidationV1; +use crate::execution::validation::state_transition::batch::action_validation::document::document_create_transition_action::state_v2::DocumentCreateTransitionActionStateValidationV2; use crate::execution::validation::state_transition::batch::action_validation::document::document_create_transition_action::advanced_structure_v0::DocumentCreateTransitionActionStructureValidationV0; use crate::platform_types::platform::PlatformStateRef; mod advanced_structure_v0; mod state_v0; mod state_v1; +mod state_v2; pub trait DocumentCreateTransitionActionValidation { fn validate_structure( @@ -95,9 +97,17 @@ impl DocumentCreateTransitionActionValidation for DocumentCreateTransitionAction transaction, platform_version, ), + 2 => self.validate_state_v2( + platform, + owner_id, + block_info, + execution_context, + transaction, + platform_version, + ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { method: "DocumentCreateTransitionAction::validate_state".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/action_validation/document/document_create_transition_action/state_v2/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/state_v2/mod.rs new file mode 100644 index 00000000000..149c865654b --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/state_v2/mod.rs @@ -0,0 +1,65 @@ +use dpp::block::block_info::BlockInfo; +use dpp::identifier::Identifier; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::version::PlatformVersion; +use drive::query::TransactionArg; +use drive::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::{ + DocumentCreateTransitionAction, DocumentCreateTransitionActionAccessorsV0, +}; + +use crate::error::Error; +use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; +use crate::execution::validation::state_transition::batch::action_validation::document::document_create_transition_action::state_v1::DocumentCreateTransitionActionStateValidationV1; +use crate::execution::validation::state_transition::batch::action_validation::document::document_reference_validation::DocumentReferenceValidation; +use crate::platform_types::platform::PlatformStateRef; + +pub(in crate::execution::validation::state_transition::state_transitions::batch::action_validation) trait DocumentCreateTransitionActionStateValidationV2 +{ + fn validate_state_v2( + &self, + platform: &PlatformStateRef, + owner_id: Identifier, + block_info: &BlockInfo, + execution_context: &mut StateTransitionExecutionContext, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result; +} + +impl DocumentCreateTransitionActionStateValidationV2 for DocumentCreateTransitionAction { + fn validate_state_v2( + &self, + platform: &PlatformStateRef, + owner_id: Identifier, + block_info: &BlockInfo, + execution_context: &mut StateTransitionExecutionContext, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let validation_result = self.validate_state_v1( + platform, + owner_id, + block_info, + execution_context, + transaction, + platform_version, + )?; + if !validation_result.is_valid() { + return Ok(validation_result); + } + + let reference_result = self.base().validate_document_references( + self.data(), + None, + platform, + transaction, + execution_context, + platform_version, + )?; + if !reference_result.is_valid() { + return Ok(reference_result); + } + + Ok(SimpleConsensusValidationResult::new()) + } +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/mod.rs new file mode 100644 index 00000000000..7fb6f40c167 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/mod.rs @@ -0,0 +1,61 @@ +pub mod v0; + +use std::collections::{BTreeMap, BTreeSet}; + +use dpp::platform_value::Value; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::version::PlatformVersion; +use drive::query::TransactionArg; +use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionAction; + +use crate::error::execution::ExecutionError; +use crate::error::Error; +use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; +use crate::execution::validation::state_transition::batch::action_validation::document::document_reference_validation::v0::DocumentReferenceValidationV0; +use crate::platform_types::platform::PlatformStateRef; + +pub(crate) trait DocumentReferenceValidation { + fn validate_document_references( + &self, + document_data: &BTreeMap, + changed_fields: Option<&BTreeSet>, + platform: &PlatformStateRef, + transaction: TransactionArg, + execution_context: &mut StateTransitionExecutionContext, + platform_version: &PlatformVersion, + ) -> Result; +} + +impl DocumentReferenceValidation for DocumentBaseTransitionAction { + fn validate_document_references( + &self, + document_data: &BTreeMap, + changed_fields: Option<&BTreeSet>, + platform: &PlatformStateRef, + transaction: TransactionArg, + execution_context: &mut StateTransitionExecutionContext, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive_abci + .validation_and_processing + .state_transitions + .batch_state_transition + .document_reference_validation + { + 0 => self.validate_document_references_v0( + document_data, + changed_fields, + platform, + transaction, + execution_context, + platform_version, + ), + version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method: "DocumentBaseTransitionAction::validate_document_references".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs new file mode 100644 index 00000000000..5b9e80185f3 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs @@ -0,0 +1,164 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use dpp::consensus::basic::document::InvalidDocumentTypeError; +use dpp::consensus::state::state_error::StateError; +use dpp::consensus::ConsensusError; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::document_type::{DocumentPropertyReferenceTarget, DocumentTypeRef}; +use dpp::errors::consensus::state::document::referenced_entity_not_found_error::ReferencedEntityNotFoundError; +use dpp::identifier::Identifier; +use dpp::platform_value::btreemap_extensions::BTreeValueMapPathHelper; +use dpp::platform_value::Value; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::version::PlatformVersion; +use drive::query::TransactionArg; +use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionAction; +use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; + +use crate::error::Error; +use crate::execution::types::execution_operation::{RetrieveIdentityInfo, ValidationOperation}; +use crate::execution::types::state_transition_execution_context::{ + StateTransitionExecutionContext, StateTransitionExecutionContextMethodsV0, +}; +use crate::platform_types::platform::PlatformStateRef; + +/// Versioned, stateful validation of document references using the v0 rules. +/// +/// This performs existence checks for supported reference targets (currently identity) and +/// can be limited to changed fields for replace transitions. It is intended to be called via +/// the higher-level `DocumentReferenceValidation` dispatcher that selects the version. +pub(crate) trait DocumentReferenceValidationV0 { + fn validate_document_references_v0( + &self, + document_data: &BTreeMap, + changed_fields: Option<&BTreeSet>, + platform: &PlatformStateRef, + transaction: TransactionArg, + execution_context: &mut StateTransitionExecutionContext, + platform_version: &PlatformVersion, + ) -> Result; +} + +impl DocumentReferenceValidationV0 for DocumentBaseTransitionAction { + fn validate_document_references_v0( + &self, + document_data: &BTreeMap, + changed_fields: Option<&BTreeSet>, + platform: &PlatformStateRef, + transaction: TransactionArg, + execution_context: &mut StateTransitionExecutionContext, + platform_version: &PlatformVersion, + ) -> Result { + let contract_fetch_info = self.data_contract_fetch_info(); + let contract = &contract_fetch_info.contract; + let document_type_name = self.document_type_name(); + + let Some(document_type) = contract.document_type_optional_for_name(document_type_name) + else { + return Ok(SimpleConsensusValidationResult::new_with_error( + InvalidDocumentTypeError::new(document_type_name.clone(), contract.id()).into(), + )); + }; + + validate_document_type_references_v0( + document_type, + document_data, + changed_fields, + platform, + transaction, + execution_context, + platform_version, + ) + } +} + +fn validate_document_type_references_v0( + document_type: DocumentTypeRef<'_>, + document_data: &BTreeMap, + changed_fields: Option<&BTreeSet>, + platform: &PlatformStateRef, + transaction: TransactionArg, + execution_context: &mut StateTransitionExecutionContext, + platform_version: &PlatformVersion, +) -> Result { + for (path, property) in document_type.flattened_properties() { + if let Some(changed) = changed_fields { + if !changed.contains(path) { + continue; + } + } + + let Some(reference) = &property.reference else { + continue; + }; + + match reference.target { + DocumentPropertyReferenceTarget::Identity => { + let validation_result = validate_identity_reference_v0( + document_data, + path, + reference.must_exist, + platform, + transaction, + execution_context, + platform_version, + )?; + if !validation_result.is_valid() { + return Ok(validation_result); + } + } + _ => continue, + } + } + + Ok(SimpleConsensusValidationResult::new()) +} + +fn validate_identity_reference_v0( + document_data: &BTreeMap, + path: &str, + must_exist: bool, + platform: &PlatformStateRef, + transaction: TransactionArg, + execution_context: &mut StateTransitionExecutionContext, + platform_version: &PlatformVersion, +) -> Result { + if !must_exist { + return Ok(SimpleConsensusValidationResult::new()); + } + + let Some(identity_bytes) = document_data + .get_optional_identifier_at_path(path) + .map_err(|e| Error::Protocol(e.into()))? + else { + return Ok(SimpleConsensusValidationResult::new()); + }; + + execution_context.add_operation(ValidationOperation::RetrieveIdentity( + RetrieveIdentityInfo::only_revision(), + )); + + let Some(_) = platform.drive.fetch_identity_revision( + identity_bytes, + true, + transaction, + platform_version, + )? + else { + let missing_id = + Identifier::from_bytes(&identity_bytes).map_err(|e| Error::Protocol(e.into()))?; + + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::StateError(StateError::ReferencedEntityNotFoundError( + ReferencedEntityNotFoundError::new( + missing_id, + DocumentPropertyReferenceTarget::Identity, + path.to_string(), + ), + )), + )); + }; + + Ok(SimpleConsensusValidationResult::new()) +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/mod.rs index de32e19b21c..da0672252f6 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/mod.rs @@ -9,11 +9,15 @@ use crate::error::Error; use crate::error::execution::ExecutionError; use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; use crate::execution::validation::state_transition::batch::action_validation::document::document_replace_transition_action::state_v0::DocumentReplaceTransitionActionStateValidationV0; +use crate::execution::validation::state_transition::batch::action_validation::document::document_replace_transition_action::state_v1::DocumentReplaceTransitionActionStateValidationV1; +use crate::execution::validation::state_transition::batch::action_validation::document::document_replace_transition_action::state_v2::DocumentReplaceTransitionActionStateValidationV2; use crate::execution::validation::state_transition::batch::action_validation::document::document_replace_transition_action::advanced_structure_v0::DocumentReplaceTransitionActionStructureValidationV0; use crate::platform_types::platform::PlatformStateRef; mod advanced_structure_v0; mod state_v0; +mod state_v1; +mod state_v2; pub trait DocumentReplaceTransitionActionValidation { fn validate_structure( @@ -77,9 +81,25 @@ impl DocumentReplaceTransitionActionValidation for DocumentReplaceTransitionActi transaction, platform_version, ), + 1 => self.validate_state_v1( + platform, + owner_id, + block_info, + execution_context, + transaction, + platform_version, + ), + 2 => self.validate_state_v2( + platform, + owner_id, + block_info, + execution_context, + transaction, + platform_version, + ), version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { method: "DocumentReplaceTransitionAction::validate_state".to_string(), - known_versions: vec![0], + known_versions: vec![0, 1, 2], received: version, })), } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v1/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v1/mod.rs new file mode 100644 index 00000000000..01802fcd616 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v1/mod.rs @@ -0,0 +1,44 @@ +use dpp::block::block_info::BlockInfo; +use dpp::identifier::Identifier; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::version::PlatformVersion; +use drive::grovedb::TransactionArg; +use drive::state_transition_action::batch::batched_transition::document_transition::document_replace_transition_action::DocumentReplaceTransitionAction; + +use crate::error::Error; +use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; +use crate::execution::validation::state_transition::batch::action_validation::document::document_replace_transition_action::state_v0::DocumentReplaceTransitionActionStateValidationV0; +use crate::platform_types::platform::PlatformStateRef; + +pub(in crate::execution::validation::state_transition::state_transitions::batch::action_validation) trait DocumentReplaceTransitionActionStateValidationV1 { + fn validate_state_v1( + &self, + platform: &PlatformStateRef, + owner_id: Identifier, + block_info: &BlockInfo, + execution_context: &mut StateTransitionExecutionContext, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result; +} + +impl DocumentReplaceTransitionActionStateValidationV1 for DocumentReplaceTransitionAction { + fn validate_state_v1( + &self, + platform: &PlatformStateRef, + owner_id: Identifier, + block_info: &BlockInfo, + execution_context: &mut StateTransitionExecutionContext, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + self.validate_state_v0( + platform, + owner_id, + block_info, + execution_context, + transaction, + platform_version, + ) + } +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v2/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v2/mod.rs new file mode 100644 index 00000000000..e60fbf7d92d --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_replace_transition_action/state_v2/mod.rs @@ -0,0 +1,65 @@ +use dpp::block::block_info::BlockInfo; +use dpp::identifier::Identifier; +use dpp::validation::SimpleConsensusValidationResult; +use dpp::version::PlatformVersion; +use drive::grovedb::TransactionArg; +use drive::state_transition_action::batch::batched_transition::document_transition::document_replace_transition_action::{ + DocumentReplaceTransitionAction, DocumentReplaceTransitionActionAccessorsV0, +}; + +use crate::error::Error; +use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; +use crate::execution::validation::state_transition::batch::action_validation::document::document_reference_validation::DocumentReferenceValidation; +use crate::execution::validation::state_transition::batch::action_validation::document::document_replace_transition_action::state_v1::DocumentReplaceTransitionActionStateValidationV1; +use crate::platform_types::platform::PlatformStateRef; + +pub(in crate::execution::validation::state_transition::state_transitions::batch::action_validation) trait DocumentReplaceTransitionActionStateValidationV2 +{ + fn validate_state_v2( + &self, + platform: &PlatformStateRef, + owner_id: Identifier, + block_info: &BlockInfo, + execution_context: &mut StateTransitionExecutionContext, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result; +} + +impl DocumentReplaceTransitionActionStateValidationV2 for DocumentReplaceTransitionAction { + fn validate_state_v2( + &self, + platform: &PlatformStateRef, + owner_id: Identifier, + block_info: &BlockInfo, + execution_context: &mut StateTransitionExecutionContext, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let validation_result = self.validate_state_v1( + platform, + owner_id, + block_info, + execution_context, + transaction, + platform_version, + )?; + if !validation_result.is_valid() { + return Ok(validation_result); + } + + let reference_result = self.base().validate_document_references( + self.data(), + Some(self.changed_data_fields()), + platform, + transaction, + execution_context, + platform_version, + )?; + if !reference_result.is_valid() { + return Ok(reference_result); + } + + Ok(SimpleConsensusValidationResult::new()) + } +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/mod.rs index ac80aafead6..ee1fb8389f2 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/mod.rs @@ -2,6 +2,7 @@ mod document_base_transaction_action; pub(crate) mod document_create_transition_action; pub(crate) mod document_delete_transition_action; pub(crate) mod document_purchase_transition_action; +pub(crate) mod document_reference_validation; pub(crate) mod document_replace_transition_action; pub(crate) mod document_transfer_transition_action; pub(crate) mod document_update_price_transition_action; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/advanced_structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/advanced_structure/v0/mod.rs index 9f8bad74d68..f42e25562c0 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/advanced_structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/advanced_structure/v0/mod.rs @@ -41,12 +41,13 @@ use crate::execution::validation::state_transition::batch::action_validation::to pub(in crate::execution::validation::state_transition::state_transitions::batch) trait DocumentsBatchStateTransitionStructureValidationV0 { + #[allow(clippy::too_many_arguments)] fn validate_advanced_structure_from_state_v0( &self, block_info: &BlockInfo, - network: Network, action: &BatchTransitionAction, identity: &PartialIdentity, + network: Network, execution_context: &mut StateTransitionExecutionContext, platform_version: &PlatformVersion, ) -> Result, Error>; @@ -56,9 +57,9 @@ impl DocumentsBatchStateTransitionStructureValidationV0 for BatchTransition { fn validate_advanced_structure_from_state_v0( &self, block_info: &BlockInfo, - network: Network, action: &BatchTransitionAction, identity: &PartialIdentity, + network: Network, execution_context: &mut StateTransitionExecutionContext, platform_version: &PlatformVersion, ) -> Result, Error> { diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/mod.rs index 8c0ba510d5e..8c65c0c2c7f 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/mod.rs @@ -173,9 +173,9 @@ impl StateTransitionStructureKnownInStateValidationV0 for BatchTransition { }; self.validate_advanced_structure_from_state_v0( block_info, - network, documents_batch_transition_action, identity, + network, execution_context, platform_version, ) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs index 57e7db27fe7..fe64671e3bf 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs @@ -25,8 +25,8 @@ use dpp::version::PlatformVersion; use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContext; use crate::platform_types::platform_state::PlatformStateV0Methods; -use drive::grovedb::TransactionArg; use drive::state_transition_action::identity::identity_create_from_addresses::IdentityCreateFromAddressesTransitionAction; +use drive::query::TransactionArg; use drive::state_transition_action::StateTransitionAction; use crate::execution::validation::state_transition::identity_create_from_addresses::advanced_structure::v0::IdentityCreateFromAddressesStateTransitionAdvancedStructureValidationV0; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs index 23163606246..e70e99cc3c0 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs @@ -5,6 +5,7 @@ pub mod v4; pub mod v5; pub mod v6; pub mod v7; +pub mod v8; use versioned_feature_core::{FeatureVersion, OptionalFeatureVersion}; @@ -113,6 +114,7 @@ pub struct DriveAbciDocumentsStateTransitionValidationVersions { pub document_transfer_transition_state_validation: FeatureVersion, pub document_purchase_transition_state_validation: FeatureVersion, pub document_update_price_transition_state_validation: FeatureVersion, + pub document_reference_validation: FeatureVersion, pub token_mint_transition_structure_validation: FeatureVersion, pub token_burn_transition_structure_validation: FeatureVersion, pub token_transfer_transition_structure_validation: FeatureVersion, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs index 5fe30a0fb84..6ecca42440c 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs @@ -132,6 +132,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V1: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs index e3fa0aa7649..d79687dbd74 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs @@ -132,6 +132,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V2: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs index 2fd5cc1d3bd..5a89bf5a310 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs @@ -132,6 +132,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V3: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs index 796f8615dca..0768a41c3a9 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs @@ -135,6 +135,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V4: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs index c9d4e774c33..146a48adc2f 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs @@ -136,6 +136,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V5: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs index 3b656d6c1e4..6a0280ef90c 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs @@ -139,6 +139,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V6: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs index 5f2ceb620c7..1aecae0126e 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs @@ -133,6 +133,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V7: DriveAbciValidationVersions = document_transfer_transition_state_validation: 0, document_purchase_transition_state_validation: 0, document_update_price_transition_state_validation: 0, + document_reference_validation: 0, token_mint_transition_structure_validation: 0, token_burn_transition_structure_validation: 0, token_transfer_transition_structure_validation: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs new file mode 100644 index 00000000000..d0ecbdccf3e --- /dev/null +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs @@ -0,0 +1,16 @@ +use crate::version::drive_abci_versions::drive_abci_validation_versions::v7::DRIVE_ABCI_VALIDATION_VERSIONS_V7; +use crate::version::drive_abci_versions::drive_abci_validation_versions::DriveAbciValidationVersions; + +// Introduce document reference validation in document create/replace state validation v2 +pub const DRIVE_ABCI_VALIDATION_VERSIONS_V8: DriveAbciValidationVersions = + DriveAbciValidationVersions { + state_transitions: crate::version::drive_abci_versions::drive_abci_validation_versions::DriveAbciStateTransitionValidationVersions { + batch_state_transition: crate::version::drive_abci_versions::drive_abci_validation_versions::DriveAbciDocumentsStateTransitionValidationVersions { + document_create_transition_state_validation: 2, + document_replace_transition_state_validation: 2, + ..DRIVE_ABCI_VALIDATION_VERSIONS_V7.state_transitions.batch_state_transition + }, + ..DRIVE_ABCI_VALIDATION_VERSIONS_V7.state_transitions + }, + ..DRIVE_ABCI_VALIDATION_VERSIONS_V7 + }; diff --git a/packages/rs-platform-version/src/version/mod.rs b/packages/rs-platform-version/src/version/mod.rs index 7e30d38fbea..38ba95f577b 100644 --- a/packages/rs-platform-version/src/version/mod.rs +++ b/packages/rs-platform-version/src/version/mod.rs @@ -1,6 +1,6 @@ mod protocol_version; -use crate::version::v11::PROTOCOL_VERSION_11; +use crate::version::v12::PROTOCOL_VERSION_12; pub use protocol_version::*; use std::ops::RangeInclusive; @@ -17,6 +17,7 @@ mod system_limits; pub mod v1; pub mod v10; pub mod v11; +pub mod v12; pub mod v2; pub mod v3; pub mod v4; @@ -30,5 +31,5 @@ pub type ProtocolVersion = u32; pub const ALL_VERSIONS: RangeInclusive = 1..=LATEST_VERSION; -pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_11; +pub const LATEST_VERSION: ProtocolVersion = PROTOCOL_VERSION_12; pub const INITIAL_PROTOCOL_VERSION: ProtocolVersion = 1; diff --git a/packages/rs-platform-version/src/version/protocol_version.rs b/packages/rs-platform-version/src/version/protocol_version.rs index 366b3158b27..16c5b194b5a 100644 --- a/packages/rs-platform-version/src/version/protocol_version.rs +++ b/packages/rs-platform-version/src/version/protocol_version.rs @@ -27,6 +27,7 @@ use crate::version::v8::PLATFORM_V8; use crate::version::v9::PLATFORM_V9; use crate::version::v11::PLATFORM_V11; +use crate::version::v12::PLATFORM_V12; use crate::version::ProtocolVersion; pub use versioned_feature_core::*; @@ -54,6 +55,7 @@ pub const PLATFORM_VERSIONS: &[PlatformVersion] = &[ PLATFORM_V9, PLATFORM_V10, PLATFORM_V11, + PLATFORM_V12, ]; #[cfg(feature = "mock-versions")] @@ -62,7 +64,7 @@ pub static PLATFORM_TEST_VERSIONS: OnceLock> = OnceLock::ne #[cfg(feature = "mock-versions")] const DEFAULT_PLATFORM_TEST_VERSIONS: &[PlatformVersion] = &[TEST_PLATFORM_V2, TEST_PLATFORM_V3]; -pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V11; +pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V12; pub const DESIRED_PLATFORM_VERSION: &PlatformVersion = LATEST_PLATFORM_VERSION; diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs new file mode 100644 index 00000000000..8f1456d7875 --- /dev/null +++ b/packages/rs-platform-version/src/version/v12.rs @@ -0,0 +1,68 @@ +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::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; +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; +use crate::version::dpp_versions::dpp_state_transition_conversion_versions::v2::STATE_TRANSITION_CONVERSION_VERSIONS_V2; +use crate::version::dpp_versions::dpp_state_transition_method_versions::v1::STATE_TRANSITION_METHOD_VERSIONS_V1; +use crate::version::dpp_versions::dpp_state_transition_serialization_versions::v2::STATE_TRANSITION_SERIALIZATION_VERSIONS_V2; +use crate::version::dpp_versions::dpp_state_transition_versions::v3::STATE_TRANSITION_VERSIONS_V3; +use crate::version::dpp_versions::dpp_token_versions::v1::TOKEN_VERSIONS_V1; +use crate::version::dpp_versions::dpp_validation_versions::v2::DPP_VALIDATION_VERSIONS_V2; +use crate::version::dpp_versions::dpp_voting_versions::v2::VOTING_VERSION_V2; +use crate::version::dpp_versions::DPPVersion; +use crate::version::drive_abci_versions::drive_abci_checkpoint_parameters::v1::DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1; +use crate::version::drive_abci_versions::drive_abci_method_versions::v7::DRIVE_ABCI_METHOD_VERSIONS_V7; +use crate::version::drive_abci_versions::drive_abci_query_versions::v1::DRIVE_ABCI_QUERY_VERSIONS_V1; +use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIVE_ABCI_STRUCTURE_VERSIONS_V1; +use crate::version::drive_abci_versions::drive_abci_validation_versions::v8::DRIVE_ABCI_VALIDATION_VERSIONS_V8; +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::v6::DRIVE_VERSION_V6; +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; +use crate::version::system_limits::v1::SYSTEM_LIMITS_V1; +use crate::version::ProtocolVersion; + +pub const PROTOCOL_VERSION_12: ProtocolVersion = 12; + +/// This version introduces document reference validation in document state validation. +/// Intruduced in Platform release 3.1.0. +pub const PLATFORM_V12: PlatformVersion = PlatformVersion { + protocol_version: PROTOCOL_VERSION_12, + drive: DRIVE_VERSION_V6, + drive_abci: DriveAbciVersion { + structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, + methods: DRIVE_ABCI_METHOD_VERSIONS_V7, + validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V8, + withdrawal_constants: DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2, + query: DRIVE_ABCI_QUERY_VERSIONS_V1, + checkpoints: DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1, + }, + dpp: DPPVersion { + costs: DPP_COSTS_VERSIONS_V1, + validation: DPP_VALIDATION_VERSIONS_V2, + state_transition_serialization_versions: STATE_TRANSITION_SERIALIZATION_VERSIONS_V2, + state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2, + state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1, + state_transitions: STATE_TRANSITION_VERSIONS_V3, + contract_versions: CONTRACT_VERSIONS_V3, + document_versions: DOCUMENT_VERSIONS_V3, + identity_versions: IDENTITY_VERSIONS_V1, + voting_versions: VOTING_VERSION_V2, + token_versions: TOKEN_VERSIONS_V1, + asset_lock_versions: DPP_ASSET_LOCK_VERSIONS_V1, + methods: DPP_METHOD_VERSIONS_V2, + factory_versions: DPP_FACTORY_VERSIONS_V1, + }, + system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V1, + fee_version: FEE_VERSION2, + system_limits: SYSTEM_LIMITS_V1, + consensus: ConsensusVersions { + tenderdash_consensus_version: 1, + }, +}; diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 095a5b63a86..bccf2dc787e 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -75,6 +75,7 @@ use dpp::consensus::state::document::document_contest_identity_already_contestan use dpp::consensus::state::document::document_contest_not_joinable_error::DocumentContestNotJoinableError; use dpp::consensus::state::document::document_contest_not_paid_for_error::DocumentContestNotPaidForError; use dpp::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; +use dpp::consensus::state::document::referenced_entity_not_found_error::ReferencedEntityNotFoundError; use dpp::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; use dpp::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityMemberOfGroupNotFoundError, IdentityNotMemberOfGroupError, ModificationOfGroupActionMainParametersNotPermittedError}; use dpp::consensus::state::identity::identity_for_token_configuration_not_found_error::IdentityInTokenConfigurationNotFoundError; @@ -443,6 +444,9 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::AddressInvalidNonceError(e) => { generic_consensus_error!(AddressInvalidNonceError, e).into() } + StateError::ReferencedEntityNotFoundError(e) => { + generic_consensus_error!(ReferencedEntityNotFoundError, e).into() + } } } @@ -920,6 +924,9 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::InvalidRemainderOutputCountError(e) => { generic_consensus_error!(InvalidRemainderOutputCountError, e).into() } + BasicError::ReferencedEntityNotFoundError(e) => { + generic_consensus_error!(ReferencedEntityNotFoundError, e).into() + } } } From 038578400214ad2fe6e005cf48fe8b0bb93ad79a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:27:11 +0100 Subject: [PATCH 10/19] test: fix stack size --- .../strategy_tests/test_cases/identity_and_document_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs index 632fe8f6f7f..9638536e634 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs @@ -757,6 +757,7 @@ mod tests { } #[test] + #[stack_size(4 * 1024 * 1024)] fn run_chain_insert_one_new_identity_per_block_and_a_document_with_epoch_change() { let platform_version = PlatformVersion::latest(); let created_contract = json_document_to_created_contract( From 184d1ec1fbb02c0fc8d6932140f73c65884460f1 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:20:06 +0100 Subject: [PATCH 11/19] tests 1 --- docs/specs/reference-validation.md | 8 +- .../batch/tests/document/creation.rs | 197 ++++++++++++-- .../batch/tests/document/replacement.rs | 256 ++++++++++++++++-- .../test_cases/identity_and_document_tests.rs | 1 + ...-validation-contract-must-exist-false.json | 39 +++ .../reference-validation-contract.json | 38 +++ 6 files changed, 488 insertions(+), 51 deletions(-) create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract.json diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md index bf3652ea2d0..6ceb94c4f6c 100644 --- a/docs/specs/reference-validation.md +++ b/docs/specs/reference-validation.md @@ -20,11 +20,11 @@ Introduce an optional `refersTo` keyword on document properties so contracts can - Keep existing `identifier_paths`/`binary_paths` behavior (the sets of property paths already tracked for identifier and binary fields); `refersTo` is additive on top. ## Runtime Validation -- Enforce during Drive advanced structure validation (`validate_advanced_structure_from_state` in batch transitions) for document create and replace transitions: +- Enforce during Drive document state validation (create/replace state validators) for document create and replace transitions: - For each property with `refersTo.mustExist == true`, fetch the referenced identity ID and fail with a consensus state error if missing. - Support nested properties (use flattened property paths). - Count identity fetches in execution context fee accounting. -- Implement via versioned advanced structure validators for document create/replace (new v1 modules) while keeping v0 behavior unchanged. +- Implement via versioned document state validators (new v2 modules) while keeping v0/v1 behavior unchanged. - Applied in ABCI paths: CheckTx, PrepareProposal, and ProcessProposal. - Basic validation (DPP) only checks keyword shape/placement; no state access. @@ -36,6 +36,10 @@ Introduce an optional `refersTo` keyword on document properties so contracts can - Gated by platform/protocol version (and/or data contract system version). Legacy nodes reject contracts containing `refersTo`; such contracts are accepted only after activation. Post-activation, newer nodes enforce `mustExist:true` semantics. - Existing pre-activation contracts and documents remain valid; documents are rejected only when the contract opts in with `mustExist:true` and the network is past activation. +## Implementation Notes +- Reference existence checks use the identity revision lookup (`fetch_identity_revision`) as the minimal-cost existence check. +- Reference validation dispatches through a versioned `DocumentReferenceValidation` trait; v0 rules are implemented for identity references. + ## Acceptance Criteria - Contracts containing `refersTo` validate against updated meta-schema and pass compatibility checks when added to existing identifier fields. - Documents with `refersTo.type=identity` + `mustExist:true` are accepted only if the referenced identities exist; missing ones return the new consensus error. 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 bdc7fbe8516..7b773794d91 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 @@ -50,6 +50,107 @@ mod creation_tests { use crate::config::PlatformConfig; use crate::execution::validation::state_transition::tests::{create_card_game_external_token_contract_with_owner_identity, create_card_game_internal_token_contract_with_owner_identity_transfer_tokens, create_token_contract_with_owner_identity}; + const REFERENCE_VALIDATION_CONTRACT_PATH: &str = + "tests/supporting_files/contract/reference-validation/reference-validation-contract.json"; + const REFERENCE_VALIDATION_MUST_EXIST_FALSE_CONTRACT_PATH: &str = + "tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json"; + + fn run_reference_validation_creation_with_contract( + contract_path: &str, + to_user_id: F, + ) -> StateTransitionExecutionResult + where + F: FnOnce(Identifier) -> Identifier, + { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + 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 contract = setup_contract( + &platform.drive, + contract_path, + None, + None, + None::, + None, + None, + ); + + let message = contract + .document_type_for_name("message") + .expect("expected a message document type"); + + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = message + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::FillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("toUserId", to_user_id(identity.id()).into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document, + message, + 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( + &[documents_batch_create_serialized_transition], + &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"); + + processing_result + .execution_results() + .first() + .expect("expected one execution result") + .clone() + } + #[test] fn test_document_creation() { let platform_version = PlatformVersion::latest(); @@ -112,7 +213,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -198,7 +299,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition], + &[documents_batch_create_serialized_transition], &platform_state, &BlockInfo::default(), &transaction, @@ -295,7 +396,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -356,7 +457,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -463,7 +564,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -701,7 +802,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![ + &[ documents_batch_create_serialized_preorder_transition_1.clone(), documents_batch_create_serialized_preorder_transition_2.clone(), ], @@ -728,7 +829,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![ + &[ documents_batch_create_serialized_transition_1.clone(), documents_batch_create_serialized_transition_2.clone(), ], @@ -1094,7 +1195,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_preorder_transition_1.clone()], + &[documents_batch_create_serialized_preorder_transition_1.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1118,7 +1219,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition_1.clone()], + &[documents_batch_create_serialized_transition_1.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1365,7 +1466,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_preorder_transition_1.clone()], + &[documents_batch_create_serialized_preorder_transition_1.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1389,7 +1490,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition_1.clone()], + &[documents_batch_create_serialized_transition_1.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1774,7 +1875,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![ + &[ documents_batch_create_serialized_preorder_transition_1.clone(), documents_batch_create_serialized_preorder_transition_2.clone(), documents_batch_create_serialized_preorder_transition_3.clone(), @@ -1802,7 +1903,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![ + &[ documents_batch_create_serialized_transition_1.clone(), documents_batch_create_serialized_transition_2.clone(), ], @@ -1829,7 +1930,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition_3.clone()], + &[documents_batch_create_serialized_transition_3.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2211,7 +2312,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition_1.clone()], + &[documents_batch_create_serialized_transition_1.clone()], &platform_state, &BlockInfo::default_with_time( &platform_state @@ -2445,7 +2546,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2506,7 +2607,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2632,7 +2733,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition], + &[documents_batch_create_serialized_transition], &platform_state, &BlockInfo::default(), &transaction, @@ -2769,7 +2870,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2914,7 +3015,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3057,7 +3158,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3188,7 +3289,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3320,7 +3421,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3452,7 +3553,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3573,7 +3674,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3706,7 +3807,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3871,7 +3972,7 @@ mod creation_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3926,4 +4027,46 @@ mod creation_tests { // He was paid 5 assert_eq!(token_balance, Some(5)); } + + #[test] + fn test_document_creation_fails_when_referenced_identity_missing() { + let result = run_reference_validation_creation_with_contract( + REFERENCE_VALIDATION_CONTRACT_PATH, + |_| Identifier::random(), + ); + + assert_matches!( + result, + PaidConsensusError { + error: ConsensusError::StateError(StateError::ReferencedEntityNotFoundError(_)), + .. + } + ); + } + + #[test] + fn test_document_creation_succeeds_when_referenced_identity_exists() { + let result = run_reference_validation_creation_with_contract( + REFERENCE_VALIDATION_CONTRACT_PATH, + |identity_id| identity_id, + ); + + assert_matches!( + result, + StateTransitionExecutionResult::SuccessfulExecution { .. } + ); + } + + #[test] + fn test_document_creation_succeeds_when_must_exist_false() { + let result = run_reference_validation_creation_with_contract( + REFERENCE_VALIDATION_MUST_EXIST_FALSE_CONTRACT_PATH, + |_| Identifier::random(), + ); + + assert_matches!( + result, + StateTransitionExecutionResult::SuccessfulExecution { .. } + ); + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs index cf342b29cc9..9f3b1618cea 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs @@ -3,12 +3,173 @@ use super::*; mod replacement_tests { use super::*; use crate::test::helpers::fast_forward_to_block::fast_forward_to_block; + use dpp::data_contract::DataContract; + use dpp::fee::fee_result::FeeResult; use dpp::identifier::Identifier; use dpp::prelude::IdentityNonce; use dpp::tokens::token_payment_info::v0::TokenPaymentInfoV0; use dpp::tokens::token_payment_info::TokenPaymentInfo; + use drive::util::test_helpers::setup_contract; use std::collections::BTreeMap; + const REFERENCE_VALIDATION_CONTRACT_PATH: &str = + "tests/supporting_files/contract/reference-validation/reference-validation-contract.json"; + + fn run_reference_validation_replace_with_contract( + contract_path: &str, + to_user_id: F, + change_note: bool, + ) -> (StateTransitionExecutionResult, FeeResult) + where + F: FnOnce(Identifier, Identifier) -> Identifier, + { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + 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 (other_identity, ..) = setup_identity(&mut platform, 959, dash_to_credits!(0.1)); + + let contract = setup_contract( + &platform.drive, + contract_path, + None, + None, + None::, + None, + None, + ); + + let message = contract + .document_type_for_name("message") + .expect("expected a message document type"); + + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = message + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::FillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + document.set("toUserId", identity.id().into()); + document.set("note", "before".into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document.clone(), + message, + 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( + &[documents_batch_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"); + + document.increment_revision().unwrap(); + if change_note { + document.set("note", "after".into()); + } + document.set( + "toUserId", + to_user_id(identity.id(), other_identity.id()).into(), + ); + + let documents_batch_replace_transition = + BatchTransition::new_document_replacement_transition_from_document( + document, + message, + &key, + 3, + 0, + None, + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let documents_batch_replace_serialized_transition = documents_batch_replace_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( + &[documents_batch_replace_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"); + + let result = processing_result + .execution_results() + .first() + .expect("expected one execution result") + .clone(); + + (result, processing_result.aggregated_fees().clone()) + } + #[test] fn test_document_replace_on_document_type_that_is_mutable() { let platform_version = PlatformVersion::latest(); @@ -79,7 +240,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -121,7 +282,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition.clone()], + &[documents_batch_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -229,7 +390,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -286,7 +447,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition.clone()], + &[documents_batch_update_serialized_transition.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -573,7 +734,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -615,7 +776,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition.clone()], + &[documents_batch_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -701,7 +862,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -795,7 +956,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_transfer_serialized_transition.clone()], + &[documents_batch_transfer_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -901,7 +1062,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition.clone()], + &[documents_batch_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1003,7 +1164,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1084,7 +1245,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![ + &[ documents_batch_update_serialized_transition_1.clone(), documents_batch_update_serialized_transition_2.clone(), ], @@ -1216,7 +1377,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1301,7 +1462,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition_1.clone()], + &[documents_batch_update_serialized_transition_1.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1345,7 +1506,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition_2.clone()], + &[documents_batch_update_serialized_transition_2.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1470,7 +1631,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1555,7 +1716,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition_1.clone()], + &[documents_batch_update_serialized_transition_1.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1599,7 +1760,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition_2.clone()], + &[documents_batch_update_serialized_transition_2.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1728,7 +1889,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1813,7 +1974,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition_1.clone()], + &[documents_batch_update_serialized_transition_1.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -1857,7 +2018,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition_2.clone()], + &[documents_batch_update_serialized_transition_2.clone()], &platform_state, platform_state.last_block_info(), &transaction, @@ -2000,7 +2161,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_create_serialized_transition.clone()], + &[documents_batch_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2051,7 +2212,7 @@ mod replacement_tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![documents_batch_update_serialized_transition.clone()], + &[documents_batch_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2086,4 +2247,55 @@ mod replacement_tests { // He had 5, but spent 2 assert_eq!(token_balance, Some(3)); } + + #[test] + fn test_document_replace_fails_when_referenced_identity_missing() { + let (result, _) = run_reference_validation_replace_with_contract( + REFERENCE_VALIDATION_CONTRACT_PATH, + |_, _| Identifier::random(), + false, + ); + + assert_matches!( + result, + PaidConsensusError { + error: ConsensusError::StateError(StateError::ReferencedEntityNotFoundError(_)), + .. + } + ); + } + + #[test] + fn test_document_replace_succeeds_when_must_exist_false() { + let (result, _) = run_reference_validation_replace_with_contract( + "tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json", + |_, _| Identifier::random(), + true, + ); + + assert_matches!( + result, + StateTransitionExecutionResult::SuccessfulExecution { .. } + ); + } + + #[test] + fn test_document_replace_validates_only_changed_fields() { + let (_, fee_without_reference) = run_reference_validation_replace_with_contract( + REFERENCE_VALIDATION_CONTRACT_PATH, + |identity_id, _| identity_id, + true, + ); + + let (_, fee_with_reference) = run_reference_validation_replace_with_contract( + REFERENCE_VALIDATION_CONTRACT_PATH, + |_, other_id| other_id, + true, + ); + + assert!( + fee_with_reference.processing_fee > fee_without_reference.processing_fee, + "expected identity reference validation to increase processing fee" + ); + } } diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs index 9638536e634..efa33b21178 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs @@ -668,6 +668,7 @@ mod tests { } #[test] + #[stack_size(4 * 1024 * 1024)] fn run_chain_insert_one_new_identity_per_block_and_one_new_document() { let platform_version = PlatformVersion::latest(); let created_contract = json_document_to_created_contract( diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json b/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json new file mode 100644 index 00000000000..10ae5073200 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json @@ -0,0 +1,39 @@ +{ + "$format_version": "1", + "id": "4Bqs6itzfoDXzmgQibYZQABbqYsXmawVf7SKe3mKDQVd", + "ownerId": "2b994p95akyNFKtkDnDvBRUotDbkH54MHwGbhQLr5gcU", + "version": 1, + "keywords": [], + "documentSchemas": { + "message": { + "type": "object", + "documentsMutable": true, + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity", + "mustExist": false + } + }, + "note": { + "type": "string", + "position": 1, + "maxLength": 64 + } + }, + "required": [ + "toUserId" + ], + "indices": [], + "additionalProperties": false + } + }, + "groups": {}, + "tokens": {} +} diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract.json b/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract.json new file mode 100644 index 00000000000..73ece7761a9 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract.json @@ -0,0 +1,38 @@ +{ + "$format_version": "1", + "id": "4Bqs6itzfoDXzmgQibYZQABbqYsXmawVf7SKe3mKDQVd", + "ownerId": "2b994p95akyNFKtkDnDvBRUotDbkH54MHwGbhQLr5gcU", + "version": 1, + "keywords": [], + "documentSchemas": { + "message": { + "type": "object", + "documentsMutable": true, + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity" + } + }, + "note": { + "type": "string", + "position": 1, + "maxLength": 64 + } + }, + "required": [ + "toUserId" + ], + "indices": [], + "additionalProperties": false + } + }, + "groups": {}, + "tokens": {} +} From f15344613b53cc91f43425481b433b884f0ae9ae Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:46:31 +0100 Subject: [PATCH 12/19] test(dpp): refersTo tests --- .../class_methods/try_from_schema/mod.rs | 57 ++++++ .../methods/validate_update/v0/mod.rs | 180 ++++++++++++++++++ .../document_type/property/mod.rs | 29 +++ .../src/validation/meta_validators/mod.rs | 63 ++++++ .../tests/rules.rs | 47 +++++ 5 files changed, 376 insertions(+) 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 878a9ee26d3..4427bfbdecd 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 @@ -416,4 +416,61 @@ mod tests { "unexpected error: {message}" ); } + + #[test] + fn should_parse_refers_to_with_must_exist_false() { + let platform_version = PlatformVersion::latest(); + let config = + DataContractConfig::default_for_version(platform_version).expect("config should build"); + + let schema = json!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity", + "mustExist": false + } + } + }, + "required": [], + "additionalProperties": false + }); + + let value = platform_value::to_value(schema).expect("schema should convert"); + + let document_type = DocumentType::try_from_schema( + Identifier::random(), + 0, + config.version(), + "msg", + value, + None, + &BTreeMap::new(), + &config, + false, + &mut vec![], + platform_version, + ) + .expect("should parse"); + + let reference = document_type + .as_ref() + .flattened_properties() + .get("toUserId") + .and_then(|p| p.reference.clone()) + .expect("reference should be present"); + + assert!(matches!( + reference.target, + DocumentPropertyReferenceTarget::Identity + )); + assert!(!reference.must_exist); + } } 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 ce0acdf7b44..cd2e984934b 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 @@ -1093,5 +1093,185 @@ mod tests { )] if e.operation() == "replace" && e.property_path() == "/properties/test/type" ); } + + #[test] + fn should_return_invalid_result_when_refers_to_is_added() { + let platform_version = PlatformVersion::latest(); + let data_contract_id = Identifier::random(); + let document_type_name = "test"; + + let schema = platform_value!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0 + } + }, + "signatureSecurityLevelRequirement": 0, + "additionalProperties": false, + }); + + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + + let old_document_type = DocumentType::try_from_schema( + data_contract_id, + 1, + config.version(), + document_type_name, + schema.clone(), + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ) + .expect("failed to create old document type"); + + let schema = platform_value!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity" + } + } + }, + "signatureSecurityLevelRequirement": 0, + "additionalProperties": false, + }); + + let new_document_type = DocumentType::try_from_schema( + data_contract_id, + 1, + config.version(), + document_type_name, + schema, + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ) + .expect("failed to create new document type"); + + let result = old_document_type + .as_ref() + .validate_schema(new_document_type.as_ref(), platform_version) + .expect("failed to validate schema compatibility"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::IncompatibleDocumentTypeSchemaError(e) + )] if e.operation() == "add" + && e.property_path() == "/properties/toUserId/refersTo" + ); + } + + #[test] + fn should_return_invalid_result_when_refers_to_is_modified() { + let platform_version = PlatformVersion::latest(); + let data_contract_id = Identifier::random(); + let document_type_name = "test"; + + let schema = platform_value!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity" + } + } + }, + "signatureSecurityLevelRequirement": 0, + "additionalProperties": false, + }); + + let config = DataContractConfig::default_for_version(platform_version) + .expect("should create a default config"); + + let old_document_type = DocumentType::try_from_schema( + data_contract_id, + 1, + config.version(), + document_type_name, + schema.clone(), + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ) + .expect("failed to create old document type"); + + let schema = platform_value!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity", + "mustExist": false + } + } + }, + "signatureSecurityLevelRequirement": 0, + "additionalProperties": false, + }); + + let new_document_type = DocumentType::try_from_schema( + data_contract_id, + 1, + config.version(), + document_type_name, + schema, + None, + &BTreeMap::new(), + &config, + false, + &mut Vec::new(), + platform_version, + ) + .expect("failed to create new document type"); + + let result = old_document_type + .as_ref() + .validate_schema(new_document_type.as_ref(), platform_version) + .expect("failed to validate schema compatibility"); + + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::IncompatibleDocumentTypeSchemaError(e) + )] if e.operation() == "add" + && e.property_path() == "/properties/toUserId/refersTo/mustExist" + ); + } } } diff --git a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs index 4a85b3f1c2b..f06e335703a 100644 --- a/packages/rs-dpp/src/data_contract/document_type/property/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/property/mod.rs @@ -2389,6 +2389,35 @@ impl DocumentPropertyType { } } +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn should_serialize_reference_metadata() { + let property = DocumentProperty { + property_type: DocumentPropertyType::Identifier, + required: false, + transient: false, + reference: Some(DocumentPropertyReference { + target: DocumentPropertyReferenceTarget::Identity, + must_exist: false, + }), + }; + + let value = serde_json::to_value(&property).expect("serialization should succeed"); + + assert_eq!( + value.get("reference"), + Some(&json!({ + "target": "identity", + "must_exist": false + })) + ); + } +} + #[derive(Debug, Clone)] pub struct DocumentPropertyTypeParsingOptions { pub sized_integer_types: bool, diff --git a/packages/rs-dpp/src/validation/meta_validators/mod.rs b/packages/rs-dpp/src/validation/meta_validators/mod.rs index fd161724f20..290a49f91ac 100644 --- a/packages/rs-dpp/src/validation/meta_validators/mod.rs +++ b/packages/rs-dpp/src/validation/meta_validators/mod.rs @@ -140,3 +140,66 @@ lazy_static! { .expect("Invalid data contract schema"); } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn should_accept_refers_to_in_document_schema() { + let schema = json!({ + "$schema": "https://github.com/dashpay/platform/blob/master/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json", + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity", + "mustExist": false + } + } + }, + "additionalProperties": false + }); + + assert!( + DOCUMENT_META_SCHEMA_V0.validate(&schema).is_ok(), + "expected schema to be valid" + ); + } + + #[test] + fn should_reject_refers_to_with_unknown_type() { + let schema = json!({ + "$schema": "https://github.com/dashpay/platform/blob/master/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json", + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "unknown" + } + } + }, + "additionalProperties": false + }); + + let errors: Vec<_> = DOCUMENT_META_SCHEMA_V0 + .validate(&schema) + .unwrap_err() + .collect(); + + assert!(!errors.is_empty(), "expected schema to be invalid"); + } +} diff --git a/packages/rs-json-schema-compatibility-validator/tests/rules.rs b/packages/rs-json-schema-compatibility-validator/tests/rules.rs index f7e69758ff7..b7436fc9dd5 100644 --- a/packages/rs-json-schema-compatibility-validator/tests/rules.rs +++ b/packages/rs-json-schema-compatibility-validator/tests/rules.rs @@ -1,6 +1,7 @@ use json_schema_compatibility_validator::{ validate_schemas_compatibility, CompatibilityRuleExample, Options, KEYWORD_COMPATIBILITY_RULES, }; +use serde_json::json; #[test] fn test_schema_keyword_rules() { @@ -49,3 +50,49 @@ To: {:?}", } } } + +#[test] +fn test_refers_to_addition_is_incompatible() { + let options = Options::default(); + let original_schema = json!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0 + } + }, + "additionalProperties": false + }); + + let new_schema = json!({ + "type": "object", + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { "type": "identity" } + } + }, + "additionalProperties": false + }); + + let result = validate_schemas_compatibility(&original_schema, &new_schema, &options) + .expect("compatibility validation failed"); + + assert!(!result.is_compatible(), "expected incompatibility"); + assert!( + result.incompatible_changes().iter().any( + |change| change.name() == "add" && change.path() == "/properties/toUserId/refersTo" + ), + "expected add of /properties/toUserId/refersTo to be incompatible" + ); +} From 90a76ef7b482a6111f3379c2c8098028a5f0bb28 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:12:06 +0100 Subject: [PATCH 13/19] test: more tests --- .../batch/tests/document/creation.rs | 67 ++++++++++++++++--- .../batch/tests/document/replacement.rs | 17 +++++ .../reference-validation-contract-nested.json | 67 +++++++++++++++++++ 3 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-nested.json 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 7b773794d91..aba1b2db037 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 @@ -54,13 +54,16 @@ mod creation_tests { "tests/supporting_files/contract/reference-validation/reference-validation-contract.json"; const REFERENCE_VALIDATION_MUST_EXIST_FALSE_CONTRACT_PATH: &str = "tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json"; + const REFERENCE_VALIDATION_NESTED_CONTRACT_PATH: &str = + "tests/supporting_files/contract/reference-validation/reference-validation-contract-nested.json"; - fn run_reference_validation_creation_with_contract( + // Helper to run document creation with custom reference mutations. + fn run_reference_validation_creation_with_mutator( contract_path: &str, - to_user_id: F, + mutator: F, ) -> StateTransitionExecutionResult where - F: FnOnce(Identifier) -> Identifier, + F: FnOnce(&mut Document, Identifier, Identifier), { let platform_version = PlatformVersion::latest(); let mut platform = TestPlatformBuilder::new() @@ -73,6 +76,7 @@ mod creation_tests { let platform_state = platform.state.load(); let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (other_identity, ..) = setup_identity(&mut platform, 959, dash_to_credits!(0.1)); let contract = setup_contract( &platform.drive, @@ -101,7 +105,7 @@ mod creation_tests { ) .expect("expected a random document"); - document.set("toUserId", to_user_id(identity.id()).into()); + mutator(&mut document, identity.id(), other_identity.id()); let documents_batch_create_transition = BatchTransition::new_document_creation_transition_from_document( @@ -4030,9 +4034,11 @@ mod creation_tests { #[test] fn test_document_creation_fails_when_referenced_identity_missing() { - let result = run_reference_validation_creation_with_contract( + let result = run_reference_validation_creation_with_mutator( REFERENCE_VALIDATION_CONTRACT_PATH, - |_| Identifier::random(), + |document, _, _| { + document.set("toUserId", Identifier::random().into()); + }, ); assert_matches!( @@ -4046,9 +4052,11 @@ mod creation_tests { #[test] fn test_document_creation_succeeds_when_referenced_identity_exists() { - let result = run_reference_validation_creation_with_contract( + let result = run_reference_validation_creation_with_mutator( REFERENCE_VALIDATION_CONTRACT_PATH, - |identity_id| identity_id, + |document, identity_id, _| { + document.set("toUserId", identity_id.into()); + }, ); assert_matches!( @@ -4059,9 +4067,28 @@ mod creation_tests { #[test] fn test_document_creation_succeeds_when_must_exist_false() { - let result = run_reference_validation_creation_with_contract( + let result = run_reference_validation_creation_with_mutator( REFERENCE_VALIDATION_MUST_EXIST_FALSE_CONTRACT_PATH, - |_| Identifier::random(), + |document, _, _| { + document.set("toUserId", Identifier::random().into()); + }, + ); + + assert_matches!( + result, + StateTransitionExecutionResult::SuccessfulExecution { .. } + ); + } + + #[test] + fn test_document_creation_succeeds_with_nested_and_multiple_references() { + let result = run_reference_validation_creation_with_mutator( + REFERENCE_VALIDATION_NESTED_CONTRACT_PATH, + |document, owner_id, other_id| { + document.set("toUserId", owner_id.into()); + document.set("otherUserId", other_id.into()); + document.set("meta.nestedUserId", owner_id.into()); + }, ); assert_matches!( @@ -4069,4 +4096,24 @@ mod creation_tests { StateTransitionExecutionResult::SuccessfulExecution { .. } ); } + + #[test] + fn test_document_creation_fails_when_nested_reference_missing() { + let result = run_reference_validation_creation_with_mutator( + REFERENCE_VALIDATION_NESTED_CONTRACT_PATH, + |document, owner_id, other_id| { + document.set("toUserId", owner_id.into()); + document.set("otherUserId", other_id.into()); + document.set("meta.nestedUserId", Identifier::random().into()); + }, + ); + + assert_matches!( + result, + PaidConsensusError { + error: ConsensusError::StateError(StateError::ReferencedEntityNotFoundError(_)), + .. + } + ); + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs index 9f3b1618cea..9ce2709bbfc 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs @@ -2298,4 +2298,21 @@ mod replacement_tests { "expected identity reference validation to increase processing fee" ); } + + #[test] + fn test_document_replace_fails_when_reference_field_changed_to_missing_identity() { + let (result, _) = run_reference_validation_replace_with_contract( + REFERENCE_VALIDATION_CONTRACT_PATH, + |_, _| Identifier::random(), + true, + ); + + assert_matches!( + result, + PaidConsensusError { + error: ConsensusError::StateError(StateError::ReferencedEntityNotFoundError(_)), + .. + } + ); + } } diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-nested.json b/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-nested.json new file mode 100644 index 00000000000..a2e2fea9f65 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-nested.json @@ -0,0 +1,67 @@ +{ + "$format_version": "1", + "id": "4Bqs6itzfoDXzmgQibYZQABbqYsXmawVf7SKe3mKDQVd", + "ownerId": "2b994p95akyNFKtkDnDvBRUotDbkH54MHwGbhQLr5gcU", + "version": 1, + "keywords": [], + "documentSchemas": { + "message": { + "type": "object", + "documentsMutable": true, + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity" + } + }, + "otherUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 1, + "refersTo": { + "type": "identity" + } + }, + "meta": { + "type": "object", + "position": 2, + "properties": { + "nestedUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "contentMediaType": "application/x.dash.dpp.identifier", + "position": 0, + "refersTo": { + "type": "identity" + } + } + }, + "required": [ + "nestedUserId" + ], + "additionalProperties": false + } + }, + "required": [ + "toUserId", + "otherUserId", + "meta" + ], + "indices": [], + "additionalProperties": false + } + }, + "groups": {}, + "tokens": {} +} From c5036a346d8e6481108c1ffe65721ca458d4d819 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:19:07 +0100 Subject: [PATCH 14/19] fix(wasm-dpp): add error variant to fix build --- .../src/errors/consensus/consensus_error.rs | 6 +-- .../errors/consensus/state/document/mod.rs | 2 + .../referenced_entity_not_found_error.rs | 44 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 packages/wasm-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index bccf2dc787e..61315414a5c 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -75,7 +75,6 @@ use dpp::consensus::state::document::document_contest_identity_already_contestan use dpp::consensus::state::document::document_contest_not_joinable_error::DocumentContestNotJoinableError; use dpp::consensus::state::document::document_contest_not_paid_for_error::DocumentContestNotPaidForError; use dpp::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; -use dpp::consensus::state::document::referenced_entity_not_found_error::ReferencedEntityNotFoundError; use dpp::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; use dpp::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityMemberOfGroupNotFoundError, IdentityNotMemberOfGroupError, ModificationOfGroupActionMainParametersNotPermittedError}; use dpp::consensus::state::identity::identity_for_token_configuration_not_found_error::IdentityInTokenConfigurationNotFoundError; @@ -130,6 +129,7 @@ use crate::errors::consensus::state::document::{ DocumentAlreadyPresentErrorWasm, DocumentNotFoundErrorWasm, DocumentOwnerIdMismatchErrorWasm, DocumentTimestampWindowViolationErrorWasm, DocumentTimestampsMismatchErrorWasm, DuplicateUniqueIndexErrorWasm, InvalidDocumentRevisionErrorWasm, + ReferencedEntityNotFoundErrorWasm, }; use crate::errors::consensus::state::identity::{ IdentityAlreadyExistsErrorWasm, IdentityPublicKeyIsDisabledErrorWasm, @@ -445,7 +445,7 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { generic_consensus_error!(AddressInvalidNonceError, e).into() } StateError::ReferencedEntityNotFoundError(e) => { - generic_consensus_error!(ReferencedEntityNotFoundError, e).into() + ReferencedEntityNotFoundErrorWasm::from(e).into() } } } @@ -925,7 +925,7 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { generic_consensus_error!(InvalidRemainderOutputCountError, e).into() } BasicError::ReferencedEntityNotFoundError(e) => { - generic_consensus_error!(ReferencedEntityNotFoundError, e).into() + ReferencedEntityNotFoundErrorWasm::from(e).into() } } } diff --git a/packages/wasm-dpp/src/errors/consensus/state/document/mod.rs b/packages/wasm-dpp/src/errors/consensus/state/document/mod.rs index ac977aac21e..953c755b1e1 100644 --- a/packages/wasm-dpp/src/errors/consensus/state/document/mod.rs +++ b/packages/wasm-dpp/src/errors/consensus/state/document/mod.rs @@ -6,6 +6,7 @@ mod document_timestamps_are_equal_error; mod document_timestamps_mismatch_error; mod duplicate_unique_index_error; mod invalid_document_revision_error; +mod referenced_entity_not_found_error; pub use document_already_present_error::*; pub use document_not_found_error::*; @@ -15,3 +16,4 @@ pub use document_timestamps_are_equal_error::*; pub use document_timestamps_mismatch_error::*; pub use duplicate_unique_index_error::*; pub use invalid_document_revision_error::*; +pub use referenced_entity_not_found_error::*; diff --git a/packages/wasm-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs b/packages/wasm-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs new file mode 100644 index 00000000000..e56bb5d59c7 --- /dev/null +++ b/packages/wasm-dpp/src/errors/consensus/state/document/referenced_entity_not_found_error.rs @@ -0,0 +1,44 @@ +use crate::buffer::Buffer; +use dpp::consensus::codes::ErrorWithCode; +use dpp::consensus::state::document::referenced_entity_not_found_error::ReferencedEntityNotFoundError; +use dpp::consensus::ConsensusError; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(js_name=ReferencedEntityNotFoundError)] +pub struct ReferencedEntityNotFoundErrorWasm { + inner: ReferencedEntityNotFoundError, +} + +impl From<&ReferencedEntityNotFoundError> for ReferencedEntityNotFoundErrorWasm { + fn from(e: &ReferencedEntityNotFoundError) -> Self { + Self { inner: e.clone() } + } +} + +#[wasm_bindgen(js_class=ReferencedEntityNotFoundError)] +impl ReferencedEntityNotFoundErrorWasm { + #[wasm_bindgen(js_name=getEntityId)] + pub fn entity_id(&self) -> Buffer { + Buffer::from_bytes(self.inner.entity_id().as_bytes()) + } + + #[wasm_bindgen(js_name=getEntityType)] + pub fn entity_type(&self) -> String { + self.inner.entity_type().to_string() + } + + #[wasm_bindgen(js_name=getPath)] + pub fn path(&self) -> String { + self.inner.path().to_string() + } + + #[wasm_bindgen(js_name=getCode)] + pub fn get_code(&self) -> u32 { + ConsensusError::from(self.inner.clone()).code() + } + + #[wasm_bindgen(getter)] + pub fn message(&self) -> String { + self.inner.to_string() + } +} From 858ad366efb12ec7267299ac1018e0c55e718047 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:03:45 +0100 Subject: [PATCH 15/19] fix(dpp): order matters in dpp enums --- packages/rs-dpp/src/errors/consensus/basic/basic_error.rs | 6 +++--- packages/rs-dpp/src/errors/consensus/state/state_error.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index 8fc241782d0..f96d135eace 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -274,9 +274,6 @@ pub enum BasicError { #[error(transparent)] MaxDocumentsTransitionsExceededError(MaxDocumentsTransitionsExceededError), - #[error(transparent)] - ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), - // Identity #[error(transparent)] DuplicatedIdentityPublicKeyBasicError(DuplicatedIdentityPublicKeyBasicError), @@ -657,6 +654,9 @@ pub enum BasicError { #[error(transparent)] OutputAddressAlsoInputError(OutputAddressAlsoInputError), + + #[error(transparent)] + ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index c739291e05e..1d70b2c66ce 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -110,9 +110,6 @@ pub enum StateError { #[error(transparent)] DocumentTimestampWindowViolationError(DocumentTimestampWindowViolationError), - #[error(transparent)] - ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), - #[error(transparent)] DuplicateUniqueIndexError(DuplicateUniqueIndexError), @@ -338,6 +335,9 @@ pub enum StateError { #[error(transparent)] AddressInvalidNonceError(AddressInvalidNonceError), + + #[error(transparent)] + ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), } impl From for ConsensusError { From 02125ff421844c7da9c9246e228a6a4216c2e674 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:13:45 +0100 Subject: [PATCH 16/19] fix(platform): err when no required identifier --- .../document_reference_validation/v0/mod.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs index 5b9e80185f3..d93e3530d19 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, BTreeSet}; use dpp::consensus::basic::document::InvalidDocumentTypeError; +use dpp::consensus::basic::invalid_identifier_error::InvalidIdentifierError; use dpp::consensus::state::state_error::StateError; use dpp::consensus::ConsensusError; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -128,11 +129,18 @@ fn validate_identity_reference_v0( return Ok(SimpleConsensusValidationResult::new()); } - let Some(identity_bytes) = document_data - .get_optional_identifier_at_path(path) - .map_err(|e| Error::Protocol(e.into()))? - else { - return Ok(SimpleConsensusValidationResult::new()); + let identity_bytes = match document_data.get_optional_identifier_at_path(path) { + Ok(Some(identity_bytes)) => identity_bytes, + Ok(None) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + InvalidIdentifierError::new(path.to_string(), "not set".to_string()).into(), + )) + } + Err(err) => { + return Ok(SimpleConsensusValidationResult::new_with_error( + InvalidIdentifierError::new(path.to_string(), err.to_string()).into(), + )) + } }; execution_context.add_operation(ValidationOperation::RetrieveIdentity( From 84c5600730c836b0f217885217625996fd098516 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:23:45 +0100 Subject: [PATCH 17/19] chore: schema verification --- .../document/v0/document-meta.json | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json index b2be21888ae..cd8b5de6e07 100644 --- a/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json +++ b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json @@ -222,6 +222,33 @@ "required": [ "maxLength" ] + }, + "refersTo": { + "description": "refersTo is only allowed on identifier properties", + "properties": { + "type": { + "const": "array" + }, + "byteArray": { + "const": true + }, + "contentMediaType": { + "const": "application/x.dash.dpp.identifier" + }, + "minItems": { + "const": 32 + }, + "maxItems": { + "const": 32 + } + }, + "required": [ + "type", + "byteArray", + "contentMediaType", + "minItems", + "maxItems" + ] } }, "allOf": [ From d1cbd32b0a9ba199b5c0fab924f81643580b2c15 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:25:39 +0100 Subject: [PATCH 18/19] chore: test renames --- .../state_transitions/batch/tests/document/replacement.rs | 8 ++++---- packages/rs-platform-version/src/version/v12.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs index 9ce2709bbfc..d4b533a97fc 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs @@ -2249,7 +2249,7 @@ mod replacement_tests { } #[test] - fn test_document_replace_fails_when_referenced_identity_missing() { + fn should_document_replace_fail_when_referenced_identity_missing() { let (result, _) = run_reference_validation_replace_with_contract( REFERENCE_VALIDATION_CONTRACT_PATH, |_, _| Identifier::random(), @@ -2266,7 +2266,7 @@ mod replacement_tests { } #[test] - fn test_document_replace_succeeds_when_must_exist_false() { + fn should_document_replace_succeed_when_must_exist_false() { let (result, _) = run_reference_validation_replace_with_contract( "tests/supporting_files/contract/reference-validation/reference-validation-contract-must-exist-false.json", |_, _| Identifier::random(), @@ -2280,7 +2280,7 @@ mod replacement_tests { } #[test] - fn test_document_replace_validates_only_changed_fields() { + fn should_document_replace_validate_only_changed_fields() { let (_, fee_without_reference) = run_reference_validation_replace_with_contract( REFERENCE_VALIDATION_CONTRACT_PATH, |identity_id, _| identity_id, @@ -2300,7 +2300,7 @@ mod replacement_tests { } #[test] - fn test_document_replace_fails_when_reference_field_changed_to_missing_identity() { + fn should_document_replace_fail_when_reference_field_changed_to_missing_identity() { let (result, _) = run_reference_validation_replace_with_contract( REFERENCE_VALIDATION_CONTRACT_PATH, |_, _| Identifier::random(), diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index 8f1456d7875..b33272ccb6a 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -31,7 +31,7 @@ use crate::version::ProtocolVersion; pub const PROTOCOL_VERSION_12: ProtocolVersion = 12; /// This version introduces document reference validation in document state validation. -/// Intruduced in Platform release 3.1.0. +/// Introduced in Platform release 3.1.0. pub const PLATFORM_V12: PlatformVersion = PlatformVersion { protocol_version: PROTOCOL_VERSION_12, drive: DRIVE_VERSION_V6, From 364305d0b7dc214a7b94ec4f2d9ed92f63ed6636 Mon Sep 17 00:00:00 2001 From: lklimek <842586+lklimek@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:26:06 +0100 Subject: [PATCH 19/19] Update docs/specs/reference-validation.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/specs/reference-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md index 6ceb94c4f6c..2f8ddf8a4fe 100644 --- a/docs/specs/reference-validation.md +++ b/docs/specs/reference-validation.md @@ -29,7 +29,7 @@ Introduce an optional `refersTo` keyword on document properties so contracts can - Basic validation (DPP) only checks keyword shape/placement; no state access. ## Errors -- Add a dedicated consensus state error, e.g., `ReferencedIdentityNotFoundError { path, identityId }`. +- Add a dedicated consensus state error, e.g., `ReferencedEntityNotFoundError { path, identityId }`. - Avoid overloading signature errors; ensure deterministic mapping to codes. ## Backward Compatibility