diff --git a/docs/specs/reference-validation.md b/docs/specs/reference-validation.md new file mode 100644 index 00000000000..2f8ddf8a4fe --- /dev/null +++ b/docs/specs/reference-validation.md @@ -0,0 +1,48 @@ +# 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"` (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). + - 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 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 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. + +## Errors +- Add a dedicated consensus state error, e.g., `ReferencedEntityNotFoundError { path, identityId }`. +- Avoid overloading signature errors; ensure deterministic mapping to codes. + +## Backward Compatibility +- 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. +- 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. 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/schema/meta_schemas/document/v0/document-meta.json b/packages/rs-dpp/schema/meta_schemas/document/v0/document-meta.json index 78bbeccef3b..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 @@ -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" }, @@ -203,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": [ @@ -606,4 +652,4 @@ "properties", "additionalProperties" ] -} \ No newline at end of file +} 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..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 @@ -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,221 @@ 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}" + ); + } + + #[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/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..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 @@ -4,6 +4,7 @@ use std::convert::TryInto; use std::io::{BufReader, Cursor, Read}; use crate::data_contract::errors::DataContractError; +use bincode::{Decode, Encode}; use crate::consensus::basic::decode::DecodingError; use crate::data_contract::config::v1::DataContractConfigGettersV1; @@ -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; @@ -34,6 +36,7 @@ pub struct DocumentProperty { pub property_type: DocumentPropertyType, pub required: bool, pub transient: bool, + pub reference: Option, } #[derive(Debug, PartialEq, Clone, Serialize)] @@ -48,6 +51,30 @@ 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, 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 { @@ -2362,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/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, } }; 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..f96d135eace 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}; @@ -653,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/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index b25a99a6909..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, @@ -297,6 +298,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..1d70b2c66ce 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; @@ -334,6 +335,9 @@ pub enum StateError { #[error(transparent)] AddressInvalidNonceError(AddressInvalidNonceError), + + #[error(transparent)] + ReferencedEntityNotFoundError(ReferencedEntityNotFoundError), } impl From for ConsensusError { 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-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..d93e3530d19 --- /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,172 @@ +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; +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 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( + 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/batch/tests/document/creation.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs index bdc7fbe8516..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 @@ -50,6 +50,111 @@ 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"; + const REFERENCE_VALIDATION_NESTED_CONTRACT_PATH: &str = + "tests/supporting_files/contract/reference-validation/reference-validation-contract-nested.json"; + + // Helper to run document creation with custom reference mutations. + fn run_reference_validation_creation_with_mutator( + contract_path: &str, + mutator: F, + ) -> StateTransitionExecutionResult + where + F: FnOnce(&mut Document, 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"); + + mutator(&mut document, identity.id(), other_identity.id()); + + 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 +217,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 +303,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 +400,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 +461,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 +568,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 +806,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 +833,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 +1199,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 +1223,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 +1470,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 +1494,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 +1879,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 +1907,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 +1934,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 +2316,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 +2550,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 +2611,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 +2737,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 +2874,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 +3019,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 +3162,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 +3293,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 +3425,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 +3557,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 +3678,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 +3811,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 +3976,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 +4031,89 @@ 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_mutator( + REFERENCE_VALIDATION_CONTRACT_PATH, + |document, _, _| { + document.set("toUserId", Identifier::random().into()); + }, + ); + + 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_mutator( + REFERENCE_VALIDATION_CONTRACT_PATH, + |document, identity_id, _| { + document.set("toUserId", identity_id.into()); + }, + ); + + assert_matches!( + result, + StateTransitionExecutionResult::SuccessfulExecution { .. } + ); + } + + #[test] + fn test_document_creation_succeeds_when_must_exist_false() { + let result = run_reference_validation_creation_with_mutator( + REFERENCE_VALIDATION_MUST_EXIST_FALSE_CONTRACT_PATH, + |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!( + result, + 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 cf342b29cc9..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 @@ -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,72 @@ mod replacement_tests { // He had 5, but spent 2 assert_eq!(token_balance, Some(3)); } + + #[test] + fn should_document_replace_fail_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 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(), + true, + ); + + assert_matches!( + result, + StateTransitionExecutionResult::SuccessfulExecution { .. } + ); + } + + #[test] + 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, + 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" + ); + } + + #[test] + 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(), + true, + ); + + assert_matches!( + result, + PaidConsensusError { + error: ConsensusError::StateError(StateError::ReferencedEntityNotFoundError(_)), + .. + } + ); + } } 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-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..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( @@ -757,6 +758,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( 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-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": {} +} 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": {} +} 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 { 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" + ); +} 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/v12.rs b/packages/rs-platform-version/src/version/v12.rs index 486f4be1fb5..1cf4a763ae8 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -18,7 +18,7 @@ use crate::version::drive_abci_versions::drive_abci_checkpoint_parameters::v1::D 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::v7::DRIVE_ABCI_VALIDATION_VERSIONS_V7; +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; @@ -30,14 +30,14 @@ use crate::version::ProtocolVersion; pub const PROTOCOL_VERSION_12: ProtocolVersion = 12; -/// This version was for 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, drive_abci: DriveAbciVersion { structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, methods: DRIVE_ABCI_METHOD_VERSIONS_V7, - validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V7, + validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V8, // changed to v8 for reference validation withdrawal_constants: DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2, query: DRIVE_ABCI_QUERY_VERSIONS_V1, checkpoints: DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1, diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 095a5b63a86..61315414a5c 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -129,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, @@ -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) => { + ReferencedEntityNotFoundErrorWasm::from(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) => { + 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() + } +} 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