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 3747878cf17..8ea5e256d08 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 @@ -505,6 +505,14 @@ ], "description": "Key requirements. 0 - Unique Non Replaceable, 1 - Multiple, 2 - Multiple with reference to latest key." }, + "documentsCountable": { + "type": "boolean", + "description": "When true, the primary key tree uses a CountTree enabling O(1) total document count queries. Only effective from protocol version 12." + }, + "rangeCountable": { + "type": "boolean", + "description": "When true, the primary key tree uses a ProvableCountTree enabling range countable. Only effective from protocol version 12. Implies documentsCountable." + }, "tokenCost": { "type": "object", "properties": { diff --git a/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs b/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs index af22f81c8fc..562374652dd 100644 --- a/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs @@ -1,5 +1,6 @@ mod v0; mod v1; +mod v2; use crate::data_contract::document_type::index::Index; use crate::data_contract::document_type::index_level::IndexLevel; @@ -21,12 +22,14 @@ use indexmap::IndexMap; use std::collections::{BTreeMap, BTreeSet}; pub use v0::*; pub use v1::*; +pub use v2::*; impl DocumentTypeV0MutGetters for DocumentType { fn schema_mut(&mut self) -> &mut Value { match self { DocumentType::V0(v0) => v0.schema_mut(), DocumentType::V1(v1) => v1.schema_mut(), + DocumentType::V2(v2) => v2.schema_mut(), } } } @@ -36,6 +39,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.name(), DocumentType::V1(v1) => v1.name(), + DocumentType::V2(v2) => v2.name(), } } @@ -43,6 +47,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.schema(), DocumentType::V1(v1) => v1.schema(), + DocumentType::V2(v2) => v2.schema(), } } @@ -50,6 +55,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.schema_owned(), DocumentType::V1(v1) => v1.schema_owned(), + DocumentType::V2(v2) => v2.schema_owned(), } } @@ -57,6 +63,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.indexes(), DocumentType::V1(v1) => v1.indexes(), + DocumentType::V2(v2) => v2.indexes(), } } @@ -64,6 +71,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.find_contested_index(), DocumentType::V1(v1) => v1.find_contested_index(), + DocumentType::V2(v2) => v2.find_contested_index(), } } @@ -71,6 +79,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.index_structure(), DocumentType::V1(v1) => v1.index_structure(), + DocumentType::V2(v2) => v2.index_structure(), } } @@ -78,6 +87,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.flattened_properties(), DocumentType::V1(v1) => v1.flattened_properties(), + DocumentType::V2(v2) => v2.flattened_properties(), } } @@ -85,6 +95,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.properties(), DocumentType::V1(v1) => v1.properties(), + DocumentType::V2(v2) => v2.properties(), } } @@ -92,6 +103,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.identifier_paths(), DocumentType::V1(v1) => v1.identifier_paths(), + DocumentType::V2(v2) => v2.identifier_paths(), } } @@ -99,6 +111,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.binary_paths(), DocumentType::V1(v1) => v1.binary_paths(), + DocumentType::V2(v2) => v2.binary_paths(), } } @@ -106,6 +119,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.required_fields(), DocumentType::V1(v1) => v1.required_fields(), + DocumentType::V2(v2) => v2.required_fields(), } } @@ -113,6 +127,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.transient_fields(), DocumentType::V1(v1) => v1.transient_fields(), + DocumentType::V2(v2) => v2.transient_fields(), } } @@ -120,6 +135,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_keep_history(), DocumentType::V1(v1) => v1.documents_keep_history(), + DocumentType::V2(v2) => v2.documents_keep_history(), } } @@ -127,6 +143,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_mutable(), DocumentType::V1(v1) => v1.documents_mutable(), + DocumentType::V2(v2) => v2.documents_mutable(), } } @@ -134,6 +151,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_can_be_deleted(), DocumentType::V1(v1) => v1.documents_can_be_deleted(), + DocumentType::V2(v2) => v2.documents_can_be_deleted(), } } @@ -141,6 +159,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.documents_transferable(), DocumentType::V1(v1) => v1.documents_transferable(), + DocumentType::V2(v2) => v2.documents_transferable(), } } @@ -148,6 +167,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.trade_mode(), DocumentType::V1(v1) => v1.trade_mode(), + DocumentType::V2(v2) => v2.trade_mode(), } } @@ -155,6 +175,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.creation_restriction_mode(), DocumentType::V1(v1) => v1.creation_restriction_mode(), + DocumentType::V2(v2) => v2.creation_restriction_mode(), } } @@ -162,6 +183,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.data_contract_id(), DocumentType::V1(v1) => v1.data_contract_id(), + DocumentType::V2(v2) => v2.data_contract_id(), } } @@ -169,6 +191,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.requires_identity_encryption_bounded_key(), DocumentType::V1(v1) => v1.requires_identity_encryption_bounded_key(), + DocumentType::V2(v2) => v2.requires_identity_encryption_bounded_key(), } } @@ -176,6 +199,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.requires_identity_decryption_bounded_key(), DocumentType::V1(v1) => v1.requires_identity_decryption_bounded_key(), + DocumentType::V2(v2) => v2.requires_identity_decryption_bounded_key(), } } @@ -183,6 +207,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.security_level_requirement(), DocumentType::V1(v1) => v1.security_level_requirement(), + DocumentType::V2(v2) => v2.security_level_requirement(), } } @@ -191,6 +216,7 @@ impl DocumentTypeV0Getters for DocumentType { match self { DocumentType::V0(v0) => v0.json_schema_validator_ref(), DocumentType::V1(v1) => v1.json_schema_validator_ref(), + DocumentType::V2(v2) => v2.json_schema_validator_ref(), } } } @@ -200,6 +226,7 @@ impl DocumentTypeV0Setters for DocumentType { match self { DocumentType::V0(v0) => v0.set_data_contract_id(data_contract_id), DocumentType::V1(v1) => v1.set_data_contract_id(data_contract_id), + DocumentType::V2(v2) => v2.set_data_contract_id(data_contract_id), } } } @@ -209,6 +236,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_creation_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_creation_token_cost(cost), } } @@ -216,6 +244,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_replacement_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_replacement_token_cost(cost), } } @@ -223,6 +252,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_deletion_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_deletion_token_cost(cost), } } @@ -230,6 +260,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_transfer_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_transfer_token_cost(cost), } } @@ -237,6 +268,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_price_update_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_price_update_token_cost(cost), } } @@ -244,6 +276,7 @@ impl DocumentTypeV1Setters for DocumentType { match self { DocumentType::V0(_) => { /* no-op */ } DocumentType::V1(v1) => v1.set_document_purchase_token_cost(cost), + DocumentType::V2(v2) => v2.set_document_purchase_token_cost(cost), } } } @@ -253,6 +286,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.name(), DocumentTypeRef::V1(v1) => v1.name(), + DocumentTypeRef::V2(v2) => v2.name(), } } @@ -260,6 +294,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.schema(), DocumentTypeRef::V1(v1) => v1.schema(), + DocumentTypeRef::V2(v2) => v2.schema(), } } @@ -267,6 +302,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.clone().schema_owned(), DocumentTypeRef::V1(v1) => v1.clone().schema_owned(), + DocumentTypeRef::V2(v2) => v2.clone().schema_owned(), } } @@ -274,6 +310,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.indexes(), DocumentTypeRef::V1(v1) => v1.indexes(), + DocumentTypeRef::V2(v2) => v2.indexes(), } } @@ -281,6 +318,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.find_contested_index(), DocumentTypeRef::V1(v1) => v1.find_contested_index(), + DocumentTypeRef::V2(v2) => v2.find_contested_index(), } } @@ -288,6 +326,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.index_structure(), DocumentTypeRef::V1(v1) => v1.index_structure(), + DocumentTypeRef::V2(v2) => v2.index_structure(), } } @@ -295,6 +334,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.flattened_properties(), DocumentTypeRef::V1(v1) => v1.flattened_properties(), + DocumentTypeRef::V2(v2) => v2.flattened_properties(), } } @@ -302,6 +342,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.properties(), DocumentTypeRef::V1(v1) => v1.properties(), + DocumentTypeRef::V2(v2) => v2.properties(), } } @@ -309,6 +350,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.identifier_paths(), DocumentTypeRef::V1(v1) => v1.identifier_paths(), + DocumentTypeRef::V2(v2) => v2.identifier_paths(), } } @@ -316,6 +358,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.binary_paths(), DocumentTypeRef::V1(v1) => v1.binary_paths(), + DocumentTypeRef::V2(v2) => v2.binary_paths(), } } @@ -323,6 +366,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.required_fields(), DocumentTypeRef::V1(v1) => v1.required_fields(), + DocumentTypeRef::V2(v2) => v2.required_fields(), } } @@ -330,6 +374,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.transient_fields(), DocumentTypeRef::V1(v1) => v1.transient_fields(), + DocumentTypeRef::V2(v2) => v2.transient_fields(), } } @@ -337,6 +382,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_keep_history(), DocumentTypeRef::V1(v1) => v1.documents_keep_history(), + DocumentTypeRef::V2(v2) => v2.documents_keep_history(), } } @@ -344,6 +390,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_mutable(), DocumentTypeRef::V1(v1) => v1.documents_mutable(), + DocumentTypeRef::V2(v2) => v2.documents_mutable(), } } @@ -351,6 +398,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_can_be_deleted(), DocumentTypeRef::V1(v1) => v1.documents_can_be_deleted(), + DocumentTypeRef::V2(v2) => v2.documents_can_be_deleted(), } } @@ -358,6 +406,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.documents_transferable(), DocumentTypeRef::V1(v1) => v1.documents_transferable(), + DocumentTypeRef::V2(v2) => v2.documents_transferable(), } } @@ -365,6 +414,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.trade_mode(), DocumentTypeRef::V1(v1) => v1.trade_mode(), + DocumentTypeRef::V2(v2) => v2.trade_mode(), } } @@ -372,6 +422,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.creation_restriction_mode(), DocumentTypeRef::V1(v1) => v1.creation_restriction_mode(), + DocumentTypeRef::V2(v2) => v2.creation_restriction_mode(), } } @@ -379,6 +430,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.data_contract_id(), DocumentTypeRef::V1(v1) => v1.data_contract_id(), + DocumentTypeRef::V2(v2) => v2.data_contract_id(), } } @@ -386,6 +438,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.requires_identity_encryption_bounded_key(), DocumentTypeRef::V1(v1) => v1.requires_identity_encryption_bounded_key(), + DocumentTypeRef::V2(v2) => v2.requires_identity_encryption_bounded_key(), } } @@ -393,6 +446,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.requires_identity_decryption_bounded_key(), DocumentTypeRef::V1(v1) => v1.requires_identity_decryption_bounded_key(), + DocumentTypeRef::V2(v2) => v2.requires_identity_decryption_bounded_key(), } } @@ -400,6 +454,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.security_level_requirement(), DocumentTypeRef::V1(v1) => v1.security_level_requirement(), + DocumentTypeRef::V2(v2) => v2.security_level_requirement(), } } @@ -408,6 +463,7 @@ impl DocumentTypeV0Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => v0.json_schema_validator_ref(), DocumentTypeRef::V1(v1) => v1.json_schema_validator_ref(), + DocumentTypeRef::V2(v2) => v2.json_schema_validator_ref(), } } } @@ -416,6 +472,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.name(), DocumentTypeMutRef::V1(v1) => v1.name(), + DocumentTypeMutRef::V2(v2) => v2.name(), } } @@ -423,6 +480,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.schema(), DocumentTypeMutRef::V1(v1) => v1.schema(), + DocumentTypeMutRef::V2(v2) => v2.schema(), } } @@ -430,6 +488,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.clone().schema_owned(), DocumentTypeMutRef::V1(v1) => v1.clone().schema_owned(), + DocumentTypeMutRef::V2(v2) => v2.clone().schema_owned(), } } @@ -437,6 +496,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.indexes(), DocumentTypeMutRef::V1(v1) => v1.indexes(), + DocumentTypeMutRef::V2(v2) => v2.indexes(), } } @@ -444,6 +504,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.find_contested_index(), DocumentTypeMutRef::V1(v1) => v1.find_contested_index(), + DocumentTypeMutRef::V2(v2) => v2.find_contested_index(), } } @@ -451,6 +512,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.index_structure(), DocumentTypeMutRef::V1(v1) => v1.index_structure(), + DocumentTypeMutRef::V2(v2) => v2.index_structure(), } } @@ -458,6 +520,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.flattened_properties(), DocumentTypeMutRef::V1(v1) => v1.flattened_properties(), + DocumentTypeMutRef::V2(v2) => v2.flattened_properties(), } } @@ -465,6 +528,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.properties(), DocumentTypeMutRef::V1(v1) => v1.properties(), + DocumentTypeMutRef::V2(v2) => v2.properties(), } } @@ -472,6 +536,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.identifier_paths(), DocumentTypeMutRef::V1(v1) => v1.identifier_paths(), + DocumentTypeMutRef::V2(v2) => v2.identifier_paths(), } } @@ -479,6 +544,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.binary_paths(), DocumentTypeMutRef::V1(v1) => v1.binary_paths(), + DocumentTypeMutRef::V2(v2) => v2.binary_paths(), } } @@ -486,6 +552,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.required_fields(), DocumentTypeMutRef::V1(v1) => v1.required_fields(), + DocumentTypeMutRef::V2(v2) => v2.required_fields(), } } @@ -493,6 +560,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.transient_fields(), DocumentTypeMutRef::V1(v1) => v1.transient_fields(), + DocumentTypeMutRef::V2(v2) => v2.transient_fields(), } } @@ -500,6 +568,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_keep_history(), DocumentTypeMutRef::V1(v1) => v1.documents_keep_history(), + DocumentTypeMutRef::V2(v2) => v2.documents_keep_history(), } } @@ -507,6 +576,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_mutable(), DocumentTypeMutRef::V1(v1) => v1.documents_mutable(), + DocumentTypeMutRef::V2(v2) => v2.documents_mutable(), } } @@ -514,6 +584,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_can_be_deleted(), DocumentTypeMutRef::V1(v1) => v1.documents_can_be_deleted(), + DocumentTypeMutRef::V2(v2) => v2.documents_can_be_deleted(), } } @@ -521,6 +592,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.documents_transferable(), DocumentTypeMutRef::V1(v1) => v1.documents_transferable(), + DocumentTypeMutRef::V2(v2) => v2.documents_transferable(), } } @@ -528,6 +600,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.trade_mode(), DocumentTypeMutRef::V1(v1) => v1.trade_mode(), + DocumentTypeMutRef::V2(v2) => v2.trade_mode(), } } @@ -535,6 +608,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.creation_restriction_mode(), DocumentTypeMutRef::V1(v1) => v1.creation_restriction_mode(), + DocumentTypeMutRef::V2(v2) => v2.creation_restriction_mode(), } } @@ -542,6 +616,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.data_contract_id(), DocumentTypeMutRef::V1(v1) => v1.data_contract_id(), + DocumentTypeMutRef::V2(v2) => v2.data_contract_id(), } } @@ -549,6 +624,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.requires_identity_encryption_bounded_key(), DocumentTypeMutRef::V1(v1) => v1.requires_identity_encryption_bounded_key(), + DocumentTypeMutRef::V2(v2) => v2.requires_identity_encryption_bounded_key(), } } @@ -556,6 +632,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.requires_identity_decryption_bounded_key(), DocumentTypeMutRef::V1(v1) => v1.requires_identity_decryption_bounded_key(), + DocumentTypeMutRef::V2(v2) => v2.requires_identity_decryption_bounded_key(), } } @@ -563,6 +640,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.security_level_requirement(), DocumentTypeMutRef::V1(v1) => v1.security_level_requirement(), + DocumentTypeMutRef::V2(v2) => v2.security_level_requirement(), } } @@ -571,6 +649,7 @@ impl DocumentTypeV0Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.json_schema_validator_ref(), DocumentTypeMutRef::V1(v1) => v1.json_schema_validator_ref(), + DocumentTypeMutRef::V2(v2) => v2.json_schema_validator_ref(), } } } @@ -580,6 +659,7 @@ impl DocumentTypeV0Setters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(v0) => v0.set_data_contract_id(data_contract_id), DocumentTypeMutRef::V1(v1) => v1.set_data_contract_id(data_contract_id), + DocumentTypeMutRef::V2(v2) => v2.set_data_contract_id(data_contract_id), } } } @@ -589,6 +669,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_creation_token_cost(), + DocumentType::V2(v2) => v2.document_creation_token_cost(), } } @@ -596,6 +677,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_replacement_token_cost(), + DocumentType::V2(v2) => v2.document_replacement_token_cost(), } } @@ -603,6 +685,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_deletion_token_cost(), + DocumentType::V2(v2) => v2.document_deletion_token_cost(), } } @@ -610,6 +693,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_transfer_token_cost(), + DocumentType::V2(v2) => v2.document_transfer_token_cost(), } } @@ -617,6 +701,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_update_price_token_cost(), + DocumentType::V2(v2) => v2.document_update_price_token_cost(), } } @@ -624,6 +709,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => None, DocumentType::V1(v1) => v1.document_purchase_token_cost(), + DocumentType::V2(v2) => v2.document_purchase_token_cost(), } } @@ -631,6 +717,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => vec![], DocumentType::V1(v1) => v1.all_document_token_costs(), + DocumentType::V2(v2) => v2.all_document_token_costs(), } } @@ -640,6 +727,7 @@ impl DocumentTypeV1Getters for DocumentType { match self { DocumentType::V0(_) => BTreeMap::new(), DocumentType::V1(v1) => v1.all_external_token_costs_contract_tokens(), + DocumentType::V2(v2) => v2.all_external_token_costs_contract_tokens(), } } } @@ -649,6 +737,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_creation_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_creation_token_cost(), } } @@ -656,6 +745,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_replacement_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_replacement_token_cost(), } } @@ -663,6 +753,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_deletion_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_deletion_token_cost(), } } @@ -670,6 +761,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_transfer_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_transfer_token_cost(), } } @@ -677,6 +769,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_update_price_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_update_price_token_cost(), } } @@ -684,6 +777,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => None, DocumentTypeRef::V1(v1) => v1.document_purchase_token_cost(), + DocumentTypeRef::V2(v2) => v2.document_purchase_token_cost(), } } @@ -691,6 +785,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => vec![], DocumentTypeRef::V1(v1) => v1.all_document_token_costs(), + DocumentTypeRef::V2(v2) => v2.all_document_token_costs(), } } @@ -700,6 +795,7 @@ impl DocumentTypeV1Getters for DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(_) => BTreeMap::new(), DocumentTypeRef::V1(v1) => v1.all_external_token_costs_contract_tokens(), + DocumentTypeRef::V2(v2) => v2.all_external_token_costs_contract_tokens(), } } } @@ -709,6 +805,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_creation_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_creation_token_cost(), } } @@ -716,6 +813,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_replacement_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_replacement_token_cost(), } } @@ -723,6 +821,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_deletion_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_deletion_token_cost(), } } @@ -730,6 +829,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_transfer_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_transfer_token_cost(), } } @@ -737,6 +837,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_update_price_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_update_price_token_cost(), } } @@ -744,6 +845,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => None, DocumentTypeMutRef::V1(v1) => v1.document_purchase_token_cost(), + DocumentTypeMutRef::V2(v2) => v2.document_purchase_token_cost(), } } @@ -751,6 +853,7 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => vec![], DocumentTypeMutRef::V1(v1) => v1.all_document_token_costs(), + DocumentTypeMutRef::V2(v2) => v2.all_document_token_costs(), } } @@ -760,6 +863,79 @@ impl DocumentTypeV1Getters for DocumentTypeMutRef<'_> { match self { DocumentTypeMutRef::V0(_) => BTreeMap::new(), DocumentTypeMutRef::V1(v1) => v1.all_external_token_costs_contract_tokens(), + DocumentTypeMutRef::V2(v2) => v2.all_external_token_costs_contract_tokens(), + } + } +} + +impl DocumentTypeV2Getters for DocumentType { + fn documents_countable(&self) -> bool { + match self { + DocumentType::V0(_) => false, + DocumentType::V1(_) => false, + DocumentType::V2(v2) => v2.documents_countable(), + } + } + + fn range_countable(&self) -> bool { + match self { + DocumentType::V0(_) => false, + DocumentType::V1(_) => false, + DocumentType::V2(v2) => v2.range_countable(), + } + } +} + +impl DocumentTypeV2Setters for DocumentType { + fn set_documents_countable(&mut self, countable: bool) { + match self { + DocumentType::V0(_) => { /* no-op */ } + DocumentType::V1(_) => { /* no-op */ } + DocumentType::V2(v2) => v2.set_documents_countable(countable), + } + } + + fn set_range_countable(&mut self, range_countable: bool) { + match self { + DocumentType::V0(_) => { /* no-op */ } + DocumentType::V1(_) => { /* no-op */ } + DocumentType::V2(v2) => v2.set_range_countable(range_countable), + } + } +} + +impl DocumentTypeV2Getters for DocumentTypeRef<'_> { + fn documents_countable(&self) -> bool { + match self { + DocumentTypeRef::V0(_) => false, + DocumentTypeRef::V1(_) => false, + DocumentTypeRef::V2(v2) => v2.documents_countable(), + } + } + + fn range_countable(&self) -> bool { + match self { + DocumentTypeRef::V0(_) => false, + DocumentTypeRef::V1(_) => false, + DocumentTypeRef::V2(v2) => v2.range_countable(), + } + } +} + +impl DocumentTypeV2Getters for DocumentTypeMutRef<'_> { + fn documents_countable(&self) -> bool { + match self { + DocumentTypeMutRef::V0(_) => false, + DocumentTypeMutRef::V1(_) => false, + DocumentTypeMutRef::V2(v2) => v2.documents_countable(), + } + } + + fn range_countable(&self) -> bool { + match self { + DocumentTypeMutRef::V0(_) => false, + DocumentTypeMutRef::V1(_) => false, + DocumentTypeMutRef::V2(v2) => v2.range_countable(), } } } diff --git a/packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs b/packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs new file mode 100644 index 00000000000..ac72b5c0bca --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs @@ -0,0 +1,20 @@ +/// Trait providing getters for DocumentTypeV2-specific fields. +pub trait DocumentTypeV2Getters { + /// Returns whether documents of this type are countable. + /// When true, the primary key tree uses a CountTree enabling O(1) total document count queries. + fn documents_countable(&self) -> bool; + + /// Returns whether this document type supports range countable. + /// When true, the primary key tree uses a ProvableCountTree. + /// Implies documents_countable = true. + fn range_countable(&self) -> bool; +} + +/// Trait providing setters for DocumentTypeV2-specific fields. +pub trait DocumentTypeV2Setters { + /// Sets whether documents of this type are countable. + fn set_documents_countable(&mut self, countable: bool); + + /// Sets whether this document type supports range countable. + fn set_range_countable(&mut self, range_countable: bool); +} 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..040fe25d0fc 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 @@ -17,6 +17,7 @@ use std::collections::{BTreeMap, BTreeSet}; mod v0; mod v1; +mod v2; const NOT_ALLOWED_SYSTEM_PROPERTIES: [&str; 1] = ["$id"]; @@ -73,9 +74,22 @@ impl DocumentType { platform_version, ) .map(|document_type| document_type.into()), + 2 => DocumentType::try_from_schema_v2( + data_contract_id, + data_contract_system_version, + contract_config_version, + name, + schema, + schema_defs, + token_configurations, + data_contact_config, + full_validation, + validation_operations, + platform_version, + ), version => Err(ProtocolError::UnknownVersionMismatch { method: "try_from_schema".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs index 39e29550162..8088b4b4091 100644 --- a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs @@ -663,6 +663,12 @@ impl DocumentTypeV1 { .transpose() }; + // Note: documentsCountable / rangeCountable schema keys are intentionally + // ignored here. The v1 parser produces DocumentTypeV1 which has no countable + // fields. When protocol v12+ is active, the v2 parser is used instead, which + // reads these keys and produces DocumentTypeV2. The v1 parser should never + // reject unknown keys — it simply doesn't map them to its output type. + let token_costs = TokenCostsV0 { create: extract_cost("create")?, replace: extract_cost("replace")?, diff --git a/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs new file mode 100644 index 00000000000..a5599fc0d4f --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs @@ -0,0 +1,119 @@ +use crate::data_contract::config::DataContractConfig; +use crate::data_contract::document_type::class_methods::consensus_or_protocol_value_error; +use crate::data_contract::document_type::property_names::{DOCUMENTS_COUNTABLE, RANGE_COUNTABLE}; +use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; +use crate::data_contract::document_type::DocumentType; +use crate::data_contract::{TokenConfiguration, TokenContractPosition}; +use crate::validation::operations::ProtocolValidationOperation; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use platform_value::{Identifier, Value}; +use std::collections::BTreeMap; + +impl DocumentTypeV2 { + /// Parses a document type schema with V2-specific fields (`documentsCountable`, + /// `rangeCountable`). Delegates core parsing to the V1 parser, then wraps the + /// result in a `DocumentTypeV2` with the additional fields set. + /// + /// This parser is only reachable from protocol version 12+ (via CONTRACT_VERSIONS_V4). + #[allow(clippy::too_many_arguments)] + pub(super) fn try_from_schema( + data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, + name: &str, + schema: Value, + schema_defs: Option<&BTreeMap>, + token_configurations: &BTreeMap, + data_contact_config: &DataContractConfig, + full_validation: bool, + validation_operations: &mut impl Extend, + platform_version: &PlatformVersion, + ) -> Result { + // Extract V2-specific fields before the V1 parser consumes the schema map. + // + // Note on pre-v12 contracts: contracts created before v12 used the v1 parser + // which ignores these fields. After v12 upgrade, deserialization uses the v2 + // parser which will read them. This is safe because the contract update path + // runs through the v2 parser with full_validation=true, and the primary key + // tree type is set correctly at contract creation time. Pre-v12 contracts + // can only have these flags if they were explicitly set in the schema — the + // meta-schema allows them as optional boolean properties. + let schema_map_opt = schema.to_map().ok(); + + let documents_countable = schema_map_opt + .as_ref() + .and_then(|schema_map| { + Value::inner_optional_bool_value(schema_map, DOCUMENTS_COUNTABLE) + .map_err(consensus_or_protocol_value_error) + .transpose() + }) + .transpose()? + .unwrap_or(false); + + let range_countable = schema_map_opt + .as_ref() + .and_then(|schema_map| { + Value::inner_optional_bool_value(schema_map, RANGE_COUNTABLE) + .map_err(consensus_or_protocol_value_error) + .transpose() + }) + .transpose()? + .unwrap_or(false); + + // Delegate core parsing to V1 + let v1 = DocumentTypeV1::try_from_schema( + data_contract_id, + data_contract_system_version, + contract_config_version, + name, + schema, + schema_defs, + token_configurations, + data_contact_config, + full_validation, + validation_operations, + platform_version, + )?; + + // Convert to V2 and set the new fields + let mut v2: DocumentTypeV2 = v1.into(); + v2.documents_countable = documents_countable || range_countable; + v2.range_countable = range_countable; + Ok(v2) + } +} + +impl DocumentType { + /// Dispatches to `DocumentTypeV2::try_from_schema` and wraps the result. + #[allow(clippy::too_many_arguments)] + pub(in crate::data_contract::document_type::class_methods) fn try_from_schema_v2( + data_contract_id: Identifier, + data_contract_system_version: u16, + contract_config_version: u16, + name: &str, + schema: Value, + schema_defs: Option<&BTreeMap>, + token_configurations: &BTreeMap, + data_contact_config: &DataContractConfig, + full_validation: bool, + validation_operations: &mut impl Extend, + platform_version: &PlatformVersion, + ) -> Result { + DocumentTypeV2::try_from_schema( + data_contract_id, + data_contract_system_version, + contract_config_version, + name, + schema, + schema_defs, + token_configurations, + data_contact_config, + full_validation, + validation_operations, + platform_version, + ) + .map(DocumentType::V2) + } +} 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..3f7ad290107 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 @@ -1,6 +1,8 @@ use crate::consensus::basic::data_contract::IncompatibleDocumentTypeSchemaError; use crate::consensus::state::data_contract::document_type_update_error::DocumentTypeUpdateError; -use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; +use crate::data_contract::document_type::accessors::{ + DocumentTypeV0Getters, DocumentTypeV2Getters, +}; use crate::data_contract::document_type::schema::validate_schema_compatibility; use crate::data_contract::document_type::DocumentTypeRef; use crate::data_contract::errors::DataContractError; @@ -178,6 +180,36 @@ impl DocumentTypeRef<'_> { ); } + if new_document_type.documents_countable() != self.documents_countable() { + return SimpleConsensusValidationResult::new_with_error( + DocumentTypeUpdateError::new( + self.data_contract_id(), + self.name(), + format!( + "document type can not change whether its documents are countable: changing from {} to {}", + self.documents_countable(), + new_document_type.documents_countable() + ), + ) + .into(), + ); + } + + if new_document_type.range_countable() != self.range_countable() { + return SimpleConsensusValidationResult::new_with_error( + DocumentTypeUpdateError::new( + self.data_contract_id(), + self.name(), + format!( + "document type can not change whether it is range countable: changing from {} to {}", + self.range_countable(), + new_document_type.range_countable() + ), + ) + .into(), + ); + } + SimpleConsensusValidationResult::new() } diff --git a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs index 686aaba0c4b..588c826c383 100644 --- a/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs +++ b/packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs @@ -2,6 +2,7 @@ use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::document_type::methods::DocumentTypeBasicMethods; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; use crate::data_contract::document_type::{ DocumentPropertyType, DocumentType, DocumentTypeRef, Index, DEFAULT_HASH_SIZE, MAX_INDEX_SIZE, }; @@ -594,6 +595,7 @@ pub trait DocumentTypeV0MethodsVersioned: DocumentTypeV0Getters + DocumentTypeBa impl DocumentTypeV0MethodsVersioned for DocumentTypeV0 {} impl DocumentTypeV0MethodsVersioned for DocumentTypeV1 {} +impl DocumentTypeV0MethodsVersioned for DocumentTypeV2 {} impl DocumentTypeV0MethodsVersioned for DocumentType {} impl DocumentTypeV0MethodsVersioned for DocumentTypeRef<'_> {} 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..ff05f513d85 100644 --- a/packages/rs-dpp/src/data_contract/document_type/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/mod.rs @@ -18,6 +18,7 @@ pub mod schema; mod token_costs; pub mod v0; pub mod v1; +pub mod v2; #[cfg(feature = "validation")] pub(crate) mod validator; @@ -26,6 +27,7 @@ use crate::data_contract::document_type::methods::{ }; use crate::data_contract::document_type::v0::DocumentTypeV0; use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; use crate::document::Document; use crate::fee::Credits; use crate::version::PlatformVersion; @@ -74,18 +76,22 @@ 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 DOCUMENTS_COUNTABLE: &str = "documentsCountable"; + pub const RANGE_COUNTABLE: &str = "rangeCountable"; } #[derive(Clone, Copy, Debug, PartialEq)] pub enum DocumentTypeRef<'a> { V0(&'a DocumentTypeV0), V1(&'a DocumentTypeV1), + V2(&'a DocumentTypeV2), } #[derive(Debug)] pub enum DocumentTypeMutRef<'a> { V0(&'a mut DocumentTypeV0), V1(&'a mut DocumentTypeV1), + V2(&'a mut DocumentTypeV2), } #[allow(clippy::large_enum_variant)] @@ -93,6 +99,7 @@ pub enum DocumentTypeMutRef<'a> { pub enum DocumentType { V0(DocumentTypeV0), V1(DocumentTypeV1), + V2(DocumentTypeV2), } impl DocumentType { @@ -100,6 +107,7 @@ impl DocumentType { match self { DocumentType::V0(v0) => DocumentTypeRef::V0(v0), DocumentType::V1(v1) => DocumentTypeRef::V1(v1), + DocumentType::V2(v2) => DocumentTypeRef::V2(v2), } } @@ -107,6 +115,7 @@ impl DocumentType { match self { DocumentType::V0(v0) => DocumentTypeMutRef::V0(v0), DocumentType::V1(v1) => DocumentTypeMutRef::V1(v1), + DocumentType::V2(v2) => DocumentTypeMutRef::V2(v2), } } @@ -122,6 +131,9 @@ impl DocumentType { DocumentType::V1(v1) => { v1.prefunded_voting_balance_for_document(document, platform_version) } + DocumentType::V2(v2) => { + v2.prefunded_voting_balance_for_document(document, platform_version) + } } } } @@ -131,6 +143,7 @@ impl DocumentTypeRef<'_> { match self { DocumentTypeRef::V0(v0) => DocumentType::V0((*v0).to_owned()), DocumentTypeRef::V1(v1) => DocumentType::V1((*v1).to_owned()), + DocumentTypeRef::V2(v2) => DocumentType::V2((*v2).to_owned()), } } } diff --git a/packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs b/packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs new file mode 100644 index 00000000000..5c178ce3be8 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs @@ -0,0 +1,226 @@ +use crate::data_contract::document_type::accessors::{ + DocumentTypeV0Getters, DocumentTypeV0MutGetters, DocumentTypeV0Setters, DocumentTypeV1Getters, + DocumentTypeV2Getters, DocumentTypeV2Setters, +}; +use crate::data_contract::document_type::index::Index; +use crate::data_contract::document_type::index_level::IndexLevel; +use crate::data_contract::document_type::property::DocumentProperty; + +use platform_value::{Identifier, Value}; + +use crate::data_contract::document_type::restricted_creation::CreationRestrictionMode; +use crate::data_contract::document_type::token_costs::accessors::TokenCostGettersV0; +use crate::data_contract::document_type::v2::DocumentTypeV2; +#[cfg(feature = "validation")] +use crate::data_contract::document_type::validator::StatelessJsonSchemaLazyValidator; +use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; +use crate::data_contract::TokenContractPosition; +use crate::document::transfer::Transferable; +use crate::identity::SecurityLevel; +use crate::nft::TradeMode; +use crate::tokens::token_amount_on_contract_token::DocumentActionTokenCost; +use indexmap::IndexMap; +use std::collections::{BTreeMap, BTreeSet}; + +impl DocumentTypeV0MutGetters for DocumentTypeV2 { + fn schema_mut(&mut self) -> &mut Value { + &mut self.schema + } +} + +impl DocumentTypeV0Getters for DocumentTypeV2 { + fn name(&self) -> &String { + &self.name + } + + fn schema(&self) -> &Value { + &self.schema + } + + fn schema_owned(self) -> Value { + self.schema + } + + fn indexes(&self) -> &BTreeMap { + &self.indices + } + + fn find_contested_index(&self) -> Option<&Index> { + self.indices + .iter() + .find(|(_, index)| index.contested_index.is_some()) + .map(|(_, contested_index)| contested_index) + } + + fn index_structure(&self) -> &IndexLevel { + &self.index_structure + } + + fn flattened_properties(&self) -> &IndexMap { + &self.flattened_properties + } + + fn properties(&self) -> &IndexMap { + &self.properties + } + + fn identifier_paths(&self) -> &BTreeSet { + &self.identifier_paths + } + + fn binary_paths(&self) -> &BTreeSet { + &self.binary_paths + } + + fn required_fields(&self) -> &BTreeSet { + &self.required_fields + } + fn transient_fields(&self) -> &BTreeSet { + &self.transient_fields + } + + fn documents_keep_history(&self) -> bool { + self.documents_keep_history + } + + fn documents_mutable(&self) -> bool { + self.documents_mutable + } + + fn documents_can_be_deleted(&self) -> bool { + self.documents_can_be_deleted + } + + fn documents_transferable(&self) -> Transferable { + self.documents_transferable + } + + fn trade_mode(&self) -> TradeMode { + self.trade_mode + } + + fn creation_restriction_mode(&self) -> CreationRestrictionMode { + self.creation_restriction_mode + } + + fn data_contract_id(&self) -> Identifier { + self.data_contract_id + } + + fn requires_identity_encryption_bounded_key(&self) -> Option { + self.requires_identity_encryption_bounded_key + } + + fn requires_identity_decryption_bounded_key(&self) -> Option { + self.requires_identity_decryption_bounded_key + } + + fn security_level_requirement(&self) -> SecurityLevel { + self.security_level_requirement + } + + #[cfg(feature = "validation")] + fn json_schema_validator_ref(&self) -> &StatelessJsonSchemaLazyValidator { + &self.json_schema_validator + } +} + +impl DocumentTypeV0Setters for DocumentTypeV2 { + fn set_data_contract_id(&mut self, data_contract_id: Identifier) { + self.data_contract_id = data_contract_id; + } +} + +impl DocumentTypeV1Getters for DocumentTypeV2 { + fn document_creation_token_cost(&self) -> Option { + self.token_costs.document_creation_token_cost() + } + + fn document_replacement_token_cost(&self) -> Option { + self.token_costs.document_replacement_token_cost() + } + + fn document_deletion_token_cost(&self) -> Option { + self.token_costs.document_deletion_token_cost() + } + + fn document_transfer_token_cost(&self) -> Option { + self.token_costs.document_transfer_token_cost() + } + + fn document_update_price_token_cost(&self) -> Option { + self.token_costs.document_price_update_token_cost() + } + + fn document_purchase_token_cost(&self) -> Option { + self.token_costs.document_purchase_token_cost() + } + + fn all_document_token_costs(&self) -> Vec<&DocumentActionTokenCost> { + let mut result = Vec::new(); + + if let Some(cost) = self.token_costs.document_creation_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_replacement_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_deletion_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_transfer_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_price_update_token_cost_ref() { + result.push(cost); + } + if let Some(cost) = self.token_costs.document_purchase_token_cost_ref() { + result.push(cost); + } + + result + } + + fn all_external_token_costs_contract_tokens( + &self, + ) -> BTreeMap> { + let mut map = BTreeMap::new(); + + for cost in self.all_document_token_costs() { + if let Some(contract_id) = cost.contract_id { + map.entry(contract_id) + .or_insert_with(BTreeSet::new) + .insert(cost.token_contract_position); + } + } + + map + } +} + +impl DocumentTypeV2Getters for DocumentTypeV2 { + fn documents_countable(&self) -> bool { + self.documents_countable || self.range_countable + } + + fn range_countable(&self) -> bool { + self.range_countable + } +} + +impl DocumentTypeV2Setters for DocumentTypeV2 { + fn set_documents_countable(&mut self, countable: bool) { + self.documents_countable = countable; + if !countable { + // Preserve invariant: range_countable implies documents_countable + self.range_countable = false; + } + } + + fn set_range_countable(&mut self, range_countable: bool) { + self.range_countable = range_countable; + if range_countable { + self.documents_countable = true; + } + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/v2/mod.rs b/packages/rs-dpp/src/data_contract/document_type/v2/mod.rs new file mode 100644 index 00000000000..c5daf542be7 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/v2/mod.rs @@ -0,0 +1,306 @@ +use indexmap::IndexMap; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::data_contract::document_type::index::Index; +use crate::data_contract::document_type::index_level::IndexLevel; +use crate::data_contract::document_type::property::DocumentProperty; +use crate::data_contract::storage_requirements::keys_for_document_type::StorageKeyRequirements; + +use crate::data_contract::document_type::methods::{ + DocumentTypeBasicMethods, DocumentTypeV0Methods, +}; +use crate::data_contract::document_type::restricted_creation::CreationRestrictionMode; +use crate::data_contract::document_type::token_costs::accessors::TokenCostSettersV0; +use crate::data_contract::document_type::token_costs::TokenCosts; +use crate::data_contract::document_type::v0::DocumentTypeV0; +use crate::data_contract::document_type::v1::DocumentTypeV1; +#[cfg(feature = "validation")] +use crate::data_contract::document_type::validator::StatelessJsonSchemaLazyValidator; +use crate::document::transfer::Transferable; +use crate::identity::SecurityLevel; +use crate::nft::TradeMode; +use crate::tokens::token_amount_on_contract_token::DocumentActionTokenCost; +use platform_value::{Identifier, Value}; + +mod accessors; +#[cfg(feature = "random-document-types")] +pub mod random_document_type; + +#[derive(Debug, PartialEq, Clone)] +pub struct DocumentTypeV2 { + pub(in crate::data_contract) name: String, + pub(in crate::data_contract) schema: Value, + pub(in crate::data_contract) indices: BTreeMap, + pub(in crate::data_contract) index_structure: IndexLevel, + /// Flattened properties flatten all objects for quick lookups for indexes + /// Document field should not contain sub objects. + pub(in crate::data_contract) flattened_properties: IndexMap, + /// Document field can contain sub objects. + pub(in crate::data_contract) properties: IndexMap, + pub(in crate::data_contract) identifier_paths: BTreeSet, + pub(in crate::data_contract) binary_paths: BTreeSet, + /// The required fields on the document type + pub(in crate::data_contract) required_fields: BTreeSet, + /// The transient fields on the document type + pub(in crate::data_contract) transient_fields: BTreeSet, + /// Should documents keep history? + pub(in crate::data_contract) documents_keep_history: bool, + /// Are documents mutable? + pub(in crate::data_contract) documents_mutable: bool, + /// Can documents of this type be deleted? + pub(in crate::data_contract) documents_can_be_deleted: bool, + /// Can documents be transferred without a trade? + pub(in crate::data_contract) documents_transferable: Transferable, + /// How are these documents traded? + pub(in crate::data_contract) trade_mode: TradeMode, + /// Is document creation restricted? + pub(in crate::data_contract) creation_restriction_mode: CreationRestrictionMode, + /// The data contract id + pub(in crate::data_contract) data_contract_id: Identifier, + /// Encryption key storage requirements + pub(in crate::data_contract) requires_identity_encryption_bounded_key: + Option, + /// Decryption key storage requirements + pub(in crate::data_contract) requires_identity_decryption_bounded_key: + Option, + pub(in crate::data_contract) security_level_requirement: SecurityLevel, + #[cfg(feature = "validation")] + pub(in crate::data_contract) json_schema_validator: StatelessJsonSchemaLazyValidator, + /// The token costs associated with state transitions on this document type + pub(in crate::data_contract) token_costs: TokenCosts, + /// When true, the primary key tree uses a CountTree enabling O(1) total document count queries + pub(in crate::data_contract) documents_countable: bool, + /// When true, the primary key tree uses a ProvableCountTree enabling range countable. + /// Implies documents_countable = true. + pub(in crate::data_contract) range_countable: bool, +} + +impl DocumentTypeBasicMethods for DocumentTypeV2 {} + +impl DocumentTypeV0Methods for DocumentTypeV2 {} + +impl crate::data_contract::document_type::accessors::DocumentTypeV1Setters for DocumentTypeV2 { + fn set_document_creation_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_creation_token_cost(cost) + } + + fn set_document_replacement_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_replacement_token_cost(cost) + } + + fn set_document_deletion_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_deletion_token_cost(cost) + } + + fn set_document_transfer_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_transfer_token_cost(cost) + } + + fn set_document_price_update_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_price_update_token_cost(cost) + } + + fn set_document_purchase_token_cost(&mut self, cost: Option) { + self.token_costs.set_document_purchase_token_cost(cost) + } +} + +impl From for DocumentTypeV2 { + fn from(value: DocumentTypeV0) -> Self { + DocumentTypeV2 { + name: value.name, + schema: value.schema, + indices: value.indices, + index_structure: value.index_structure, + flattened_properties: value.flattened_properties, + properties: value.properties, + identifier_paths: value.identifier_paths, + binary_paths: value.binary_paths, + required_fields: value.required_fields, + transient_fields: value.transient_fields, + documents_keep_history: value.documents_keep_history, + documents_mutable: value.documents_mutable, + documents_can_be_deleted: value.documents_can_be_deleted, + documents_transferable: value.documents_transferable, + trade_mode: value.trade_mode, + creation_restriction_mode: value.creation_restriction_mode, + data_contract_id: value.data_contract_id, + requires_identity_encryption_bounded_key: value + .requires_identity_encryption_bounded_key, + requires_identity_decryption_bounded_key: value + .requires_identity_decryption_bounded_key, + security_level_requirement: value.security_level_requirement, + #[cfg(feature = "validation")] + json_schema_validator: value.json_schema_validator, + token_costs: TokenCosts::V0(Default::default()), + documents_countable: false, + range_countable: false, + } + } +} + +impl From for DocumentTypeV2 { + fn from(value: DocumentTypeV1) -> Self { + DocumentTypeV2 { + name: value.name, + schema: value.schema, + indices: value.indices, + index_structure: value.index_structure, + flattened_properties: value.flattened_properties, + properties: value.properties, + identifier_paths: value.identifier_paths, + binary_paths: value.binary_paths, + required_fields: value.required_fields, + transient_fields: value.transient_fields, + documents_keep_history: value.documents_keep_history, + documents_mutable: value.documents_mutable, + documents_can_be_deleted: value.documents_can_be_deleted, + documents_transferable: value.documents_transferable, + trade_mode: value.trade_mode, + creation_restriction_mode: value.creation_restriction_mode, + data_contract_id: value.data_contract_id, + requires_identity_encryption_bounded_key: value + .requires_identity_encryption_bounded_key, + requires_identity_decryption_bounded_key: value + .requires_identity_decryption_bounded_key, + security_level_requirement: value.security_level_requirement, + #[cfg(feature = "validation")] + json_schema_validator: value.json_schema_validator, + token_costs: value.token_costs, + documents_countable: false, + range_countable: false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::data_contract::document_type::accessors::{ + DocumentTypeV0Getters, DocumentTypeV2Getters, DocumentTypeV2Setters, + }; + use crate::data_contract::document_type::v0::DocumentTypeV0; + use crate::data_contract::document_type::DocumentType; + + fn make_v0() -> DocumentTypeV0 { + DocumentTypeV0 { + name: "test".to_string(), + schema: Value::Null, + indices: BTreeMap::new(), + index_structure: IndexLevel::try_from_indices( + Vec::::new(), + "test", + platform_version::version::PlatformVersion::latest(), + ) + .unwrap(), + flattened_properties: IndexMap::new(), + properties: IndexMap::new(), + identifier_paths: BTreeSet::new(), + binary_paths: BTreeSet::new(), + required_fields: BTreeSet::new(), + transient_fields: BTreeSet::new(), + documents_keep_history: false, + documents_mutable: true, + documents_can_be_deleted: true, + documents_transferable: Transferable::Never, + trade_mode: TradeMode::None, + creation_restriction_mode: CreationRestrictionMode::NoRestrictions, + data_contract_id: Identifier::default(), + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + security_level_requirement: SecurityLevel::HIGH, + #[cfg(feature = "validation")] + json_schema_validator: Default::default(), + } + } + + #[test] + fn from_v0_sets_countable_and_blast_to_false() { + let v2: DocumentTypeV2 = make_v0().into(); + assert!(!v2.documents_countable); + assert!(!v2.range_countable); + } + + #[test] + fn from_v1_sets_countable_and_blast_to_false() { + let v1: DocumentTypeV1 = make_v0().into(); + let v2: DocumentTypeV2 = v1.into(); + assert!(!v2.documents_countable); + assert!(!v2.range_countable); + } + + #[test] + fn documents_countable_getter() { + let mut v2: DocumentTypeV2 = make_v0().into(); + assert!(!v2.documents_countable()); + v2.documents_countable = true; + assert!(v2.documents_countable()); + } + + #[test] + fn range_countable_implies_documents_countable() { + let mut v2: DocumentTypeV2 = make_v0().into(); + v2.range_countable = true; + assert!(v2.documents_countable()); + assert!(v2.range_countable()); + } + + #[test] + fn set_range_countable_also_sets_documents_countable() { + let mut v2: DocumentTypeV2 = make_v0().into(); + v2.set_range_countable(true); + assert!(v2.range_countable); + assert!(v2.documents_countable); + } + + #[test] + fn set_documents_countable_true_does_not_affect_blast() { + let mut v2: DocumentTypeV2 = make_v0().into(); + v2.set_documents_countable(true); + assert!(v2.documents_countable()); + assert!(!v2.range_countable()); + } + + #[test] + fn set_documents_countable_false_clears_range_countable() { + let mut v2: DocumentTypeV2 = make_v0().into(); + v2.set_range_countable(true); + assert!(v2.range_countable()); + assert!(v2.documents_countable()); + + // Setting countable to false must also clear range_countable + v2.set_documents_countable(false); + assert!(!v2.documents_countable()); + assert!(!v2.range_countable()); + } + + #[test] + fn v2_preserves_v0_fields() { + let v0 = make_v0(); + let v2: DocumentTypeV2 = v0.into(); + assert_eq!(v2.name(), "test"); + assert!(v2.documents_mutable()); + assert!(v2.documents_can_be_deleted()); + } + + #[test] + fn document_type_enum_v0_v1_return_false() { + let dt = DocumentType::V0(make_v0()); + assert!(!dt.documents_countable()); + assert!(!dt.range_countable()); + + let dt = DocumentType::V1(make_v0().into()); + assert!(!dt.documents_countable()); + assert!(!dt.range_countable()); + } + + #[test] + fn document_type_enum_v2_dispatch() { + let mut v2: DocumentTypeV2 = make_v0().into(); + v2.documents_countable = true; + v2.range_countable = true; + let dt = DocumentType::V2(v2); + assert!(dt.documents_countable()); + assert!(dt.range_countable()); + } +} diff --git a/packages/rs-dpp/src/data_contract/document_type/v2/random_document_type.rs b/packages/rs-dpp/src/data_contract/document_type/v2/random_document_type.rs new file mode 100644 index 00000000000..849206ab2a6 --- /dev/null +++ b/packages/rs-dpp/src/data_contract/document_type/v2/random_document_type.rs @@ -0,0 +1,40 @@ +use crate::data_contract::document_type::v0::random_document_type::RandomDocumentTypeParameters; +use crate::data_contract::document_type::v1::DocumentTypeV1; +use crate::data_contract::document_type::v2::DocumentTypeV2; +use crate::version::PlatformVersion; +use crate::ProtocolError; +use platform_value::Identifier; +use rand::rngs::StdRng; + +impl DocumentTypeV2 { + pub fn random_document_type( + parameters: RandomDocumentTypeParameters, + data_contract_id: Identifier, + rng: &mut StdRng, + platform_version: &PlatformVersion, + ) -> Result { + Ok(DocumentTypeV1::random_document_type( + parameters, + data_contract_id, + rng, + platform_version, + )? + .into()) + } + + /// This is used to create an invalid random document type, often for testing + pub fn invalid_random_document_type( + parameters: RandomDocumentTypeParameters, + data_contract_id: Identifier, + rng: &mut StdRng, + platform_version: &PlatformVersion, + ) -> Result { + Ok(DocumentTypeV1::invalid_random_document_type( + parameters, + data_contract_id, + rng, + platform_version, + )? + .into()) + } +} diff --git a/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs b/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs index 9657db4afa3..a3b07876b5c 100644 --- a/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/methods/validate_update/v0/mod.rs @@ -497,6 +497,7 @@ mod tests { { DocumentTypeMutRef::V0(dt) => dt.documents_mutable = false, DocumentTypeMutRef::V1(dt) => dt.documents_mutable = false, + DocumentTypeMutRef::V2(dt) => dt.documents_mutable = false, } let result = old_data_contract diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/mod.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/mod.rs index 2dc73f217df..a97f11ad63e 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v0/mod.rs @@ -12,6 +12,7 @@ use dpp::data_contract::config::v0::DataContractConfigGettersV0; use dpp::data_contract::DataContract; use dpp::fee::fee_result::FeeResult; +use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters; use dpp::data_contract::document_type::methods::DocumentTypeBasicMethods; use dpp::serialization::PlatformSerializableWithPlatformVersion; @@ -284,14 +285,34 @@ impl Drive { ]; // primary key tree - let key_info = Key(vec![0]); - self.batch_insert_empty_tree( - type_path, - key_info, - storage_flags.as_ref(), - &mut batch_operations, - &platform_version.drive, - )?; + if document_type.range_countable() { + // Use a ProvableCountTree for range countable support (implies countable) + let path_items: Vec> = type_path.iter().map(|s| s.to_vec()).collect(); + batch_operations.push( + LowLevelDriveOperation::for_known_path_key_empty_provable_count_tree( + path_items, + vec![0], + storage_flags.as_ref(), + ), + ); + } else if document_type.documents_countable() { + // Use a CountTree so total document count is available in O(1) + let path_items: Vec> = type_path.iter().map(|s| s.to_vec()).collect(); + batch_operations.push(LowLevelDriveOperation::for_known_path_key_empty_count_tree( + path_items, + vec![0], + storage_flags.as_ref(), + )); + } else { + let key_info = Key(vec![0]); + self.batch_insert_empty_tree( + type_path, + key_info, + storage_flags.as_ref(), + &mut batch_operations, + &platform_version.drive, + )?; + } let mut index_cache: HashSet<&[u8]> = HashSet::new(); // for each type we should insert the indices that are top level diff --git a/packages/rs-drive/src/drive/contract/update/update_contract/v0/mod.rs b/packages/rs-drive/src/drive/contract/update/update_contract/v0/mod.rs index ec1fd49f720..d3817a3fcce 100644 --- a/packages/rs-drive/src/drive/contract/update/update_contract/v0/mod.rs +++ b/packages/rs-drive/src/drive/contract/update/update_contract/v0/mod.rs @@ -9,7 +9,7 @@ use crate::util::storage_flags::StorageFlags; use dpp::block::block_info::BlockInfo; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::config::v0::DataContractConfigGettersV0; -use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV2Getters}; use dpp::data_contract::DataContract; use dpp::fee::fee_result::FeeResult; @@ -349,13 +349,35 @@ impl Drive { ]; // primary key tree - self.batch_insert_empty_tree( - type_path, - KeyRef(&[0]), - storage_flags.as_ref().map(|flags| flags.as_ref()), - &mut batch_operations, - drive_version, - )?; + if document_type.range_countable() { + // Use a ProvableCountTree for range countable support (implies countable) + let path_items: Vec> = type_path.iter().map(|s| s.to_vec()).collect(); + batch_operations.push( + LowLevelDriveOperation::for_known_path_key_empty_provable_count_tree( + path_items, + vec![0], + storage_flags.as_ref().map(|flags| flags.as_ref()), + ), + ); + } else if document_type.documents_countable() { + // Use a CountTree so total document count is available in O(1) + let path_items: Vec> = type_path.iter().map(|s| s.to_vec()).collect(); + batch_operations.push( + LowLevelDriveOperation::for_known_path_key_empty_count_tree( + path_items, + vec![0], + storage_flags.as_ref().map(|flags| flags.as_ref()), + ), + ); + } else { + self.batch_insert_empty_tree( + type_path, + KeyRef(&[0]), + storage_flags.as_ref().map(|flags| flags.as_ref()), + &mut batch_operations, + drive_version, + )?; + } let mut index_cache: HashSet<&[u8]> = HashSet::new(); // for each type we should insert the indices that are top level diff --git a/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/v0/mod.rs index 55420aceeb7..58b1277bdeb 100644 --- a/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/v0/mod.rs @@ -1,6 +1,7 @@ +use crate::drive::document::primary_key_tree_type::DocumentTypePrimaryKeyTreeType; use grovedb::batch::KeyInfoPath; -use grovedb::{Element, EstimatedLayerInformation, TransactionArg, TreeType}; +use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; use dpp::data_contract::document_type::DocumentTypeRef; @@ -109,8 +110,9 @@ impl Drive { estimated_costs_only_with_layer_info, &platform_version.drive, )?; + let primary_key_tree_type = document_type.primary_key_tree_type(platform_version)?; DirectQueryType::StatelessDirectQuery { - in_tree_type: TreeType::NormalTree, + in_tree_type: primary_key_tree_type, query_target: QueryTargetValue( document_type.estimated_size(platform_version)? as u32 ), diff --git a/packages/rs-drive/src/drive/document/delete/internal/add_estimation_costs_for_remove_document_to_primary_storage/v0/mod.rs b/packages/rs-drive/src/drive/document/delete/internal/add_estimation_costs_for_remove_document_to_primary_storage/v0/mod.rs index ce3ca4d6e5b..f96fafe4fe3 100644 --- a/packages/rs-drive/src/drive/document/delete/internal/add_estimation_costs_for_remove_document_to_primary_storage/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/delete/internal/add_estimation_costs_for_remove_document_to_primary_storage/v0/mod.rs @@ -1,8 +1,9 @@ +use crate::drive::document::primary_key_tree_type::DocumentTypePrimaryKeyTreeType; use grovedb::batch::KeyInfoPath; use grovedb::EstimatedLayerCount::PotentiallyAtMaxElements; +use grovedb::EstimatedLayerInformation; use grovedb::EstimatedLayerSizes::AllItems; -use grovedb::{EstimatedLayerInformation, TreeType}; use dpp::data_contract::document_type::DocumentTypeRef; @@ -64,10 +65,11 @@ impl Drive { None }; let flags_size = StorageFlags::approximate_size(true, approximate_size); + let primary_key_tree_type = document_type.primary_key_tree_type(platform_version)?; estimated_costs_only_with_layer_info.insert( KeyInfoPath::from_known_path(primary_key_path), EstimatedLayerInformation { - tree_type: TreeType::NormalTree, + tree_type: primary_key_tree_type, estimated_layer_count: PotentiallyAtMaxElements, estimated_layer_sizes: AllItems( DEFAULT_HASH_SIZE_U8, diff --git a/packages/rs-drive/src/drive/document/delete/remove_document_from_primary_storage/v0/mod.rs b/packages/rs-drive/src/drive/document/delete/remove_document_from_primary_storage/v0/mod.rs index 75a03dd83cc..7f70fb9054d 100644 --- a/packages/rs-drive/src/drive/document/delete/remove_document_from_primary_storage/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/delete/remove_document_from_primary_storage/v0/mod.rs @@ -1,6 +1,7 @@ +use crate::drive::document::primary_key_tree_type::DocumentTypePrimaryKeyTreeType; use grovedb::batch::KeyInfoPath; -use grovedb::{EstimatedLayerInformation, MaybeTree, TransactionArg, TreeType}; +use grovedb::{EstimatedLayerInformation, MaybeTree, TransactionArg}; use dpp::data_contract::document_type::DocumentTypeRef; @@ -37,9 +38,11 @@ impl Drive { batch_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result<(), Error> { + let primary_key_tree_type = document_type.primary_key_tree_type(platform_version)?; + let apply_type = if estimated_costs_only_with_layer_info.is_some() { StatelessBatchDelete { - in_tree_type: TreeType::NormalTree, + in_tree_type: primary_key_tree_type, estimated_key_size: DEFAULT_HASH_SIZE_U32, estimated_value_size: document_type.estimated_size(platform_version)? as u32, } diff --git a/packages/rs-drive/src/drive/document/estimation_costs/add_estimation_costs_for_add_document_to_primary_storage/v0/mod.rs b/packages/rs-drive/src/drive/document/estimation_costs/add_estimation_costs_for_add_document_to_primary_storage/v0/mod.rs index 5185812f53b..e9ccd48fef0 100644 --- a/packages/rs-drive/src/drive/document/estimation_costs/add_estimation_costs_for_add_document_to_primary_storage/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/estimation_costs/add_estimation_costs_for_add_document_to_primary_storage/v0/mod.rs @@ -1,5 +1,6 @@ use crate::drive::constants::{AVERAGE_NUMBER_OF_UPDATES, AVERAGE_UPDATE_BYTE_COUNT_REQUIRED_SIZE}; use crate::drive::document::paths::contract_documents_keeping_history_primary_key_path_for_document_id; +use crate::drive::document::primary_key_tree_type::DocumentTypePrimaryKeyTreeType; use crate::util::storage_flags::StorageFlags; use crate::drive::Drive; @@ -70,6 +71,7 @@ impl Drive { }; let contract = document_and_contract_info.contract; let document_type = document_and_contract_info.document_type; + let primary_key_tree_type = document_type.primary_key_tree_type(platform_version)?; // at this level we have all the documents for the contract if document_type.documents_keep_history() { // if we keep history this level has trees @@ -84,7 +86,7 @@ impl Drive { estimated_costs_only_with_layer_info.insert( KeyInfoPath::from_known_path(primary_key_path), EstimatedLayerInformation { - tree_type: TreeType::NormalTree, + tree_type: primary_key_tree_type, estimated_layer_count: PotentiallyAtMaxElements, estimated_layer_sizes: AllSubtrees( DEFAULT_HASH_SIZE_U8, @@ -137,7 +139,7 @@ impl Drive { estimated_costs_only_with_layer_info.insert( KeyInfoPath::from_known_path(primary_key_path), EstimatedLayerInformation { - tree_type: TreeType::NormalTree, + tree_type: primary_key_tree_type, estimated_layer_count: PotentiallyAtMaxElements, estimated_layer_sizes: AllItems( DEFAULT_HASH_SIZE_U8, diff --git a/packages/rs-drive/src/drive/document/insert/add_document_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/insert/add_document_for_contract_operations/v0/mod.rs index 2993e01b996..5fd6f8c0c5b 100644 --- a/packages/rs-drive/src/drive/document/insert/add_document_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert/add_document_for_contract_operations/v0/mod.rs @@ -1,4 +1,5 @@ use crate::drive::document::paths::contract_documents_primary_key_path; +use crate::drive::document::primary_key_tree_type::DocumentTypePrimaryKeyTreeType; use crate::drive::Drive; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; @@ -12,7 +13,7 @@ use dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dpp::version::PlatformVersion; use grovedb::batch::KeyInfoPath; -use grovedb::{EstimatedLayerInformation, TransactionArg, TreeType}; +use grovedb::{EstimatedLayerInformation, TransactionArg}; use std::collections::HashMap; impl Drive { @@ -37,12 +38,16 @@ impl Drive { document_and_contract_info.document_type.name().as_str(), ); + let primary_key_tree_type = document_and_contract_info + .document_type + .primary_key_tree_type(platform_version)?; + // Apply means stateful query let query_type = if estimated_costs_only_with_layer_info.is_none() { StatefulDirectQuery } else { StatelessDirectQuery { - in_tree_type: TreeType::NormalTree, + in_tree_type: primary_key_tree_type, query_target: QueryTargetValue( document_and_contract_info .document_type diff --git a/packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs b/packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs index e9df07d11fc..626d3304691 100644 --- a/packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs @@ -1,3 +1,4 @@ +use crate::drive::document::primary_key_tree_type::DocumentTypePrimaryKeyTreeType; use dpp::data_contract::document_type::DocumentPropertyType; use grovedb::batch::key_info::KeyInfo; @@ -67,6 +68,9 @@ impl Drive { let drive_version = &platform_version.drive; let contract = document_and_contract_info.contract; let document_type = document_and_contract_info.document_type; + + let primary_key_tree_type = document_type.primary_key_tree_type(platform_version)?; + let primary_key_path = contract_documents_primary_key_path( contract.id_ref().as_bytes(), document_type.name().as_str(), @@ -122,11 +126,14 @@ impl Drive { inserted_storage_flags, ) }; + + // The per-document history subtree is always NormalTree. + // The parent (primary key tree) may be CountTree/ProvableCountTree. let apply_type = if estimated_costs_only_with_layer_info.is_none() { BatchInsertTreeApplyType::StatefulBatchInsertTree } else { BatchInsertTreeApplyType::StatelessBatchInsertTree { - in_tree_type: TreeType::NormalTree, + in_tree_type: primary_key_tree_type, tree_type: TreeType::NormalTree, flags_len: storage_flags .map(|s| s.serialized_size()) @@ -134,6 +141,7 @@ impl Drive { } }; // we first insert an empty tree if the document is new + // The per-document subtree is always NormalTree (it holds history entries) self.batch_insert_empty_tree_if_not_exists( path_key_info, TreeType::NormalTree, @@ -438,7 +446,7 @@ impl Drive { BatchInsertApplyType::StatefulBatchInsert } else { BatchInsertApplyType::StatelessBatchInsert { - in_tree_type: TreeType::NormalTree, + in_tree_type: primary_key_tree_type, target: QueryTargetValue(document_type.estimated_size(platform_version)? as u32), } }; diff --git a/packages/rs-drive/src/drive/document/mod.rs b/packages/rs-drive/src/drive/document/mod.rs index fb0cd21529a..e157918bd8d 100644 --- a/packages/rs-drive/src/drive/document/mod.rs +++ b/packages/rs-drive/src/drive/document/mod.rs @@ -40,6 +40,10 @@ mod update; #[cfg(any(feature = "server", feature = "verify"))] pub mod paths; +/// Primary key tree type resolution +#[cfg(feature = "server")] +pub mod primary_key_tree_type; + #[cfg(feature = "server")] /// Creates a reference to a document. fn make_document_reference( diff --git a/packages/rs-drive/src/drive/document/primary_key_tree_type.rs b/packages/rs-drive/src/drive/document/primary_key_tree_type.rs new file mode 100644 index 00000000000..92c5643c37f --- /dev/null +++ b/packages/rs-drive/src/drive/document/primary_key_tree_type.rs @@ -0,0 +1,110 @@ +use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters; +use dpp::data_contract::document_type::DocumentTypeRef; +use dpp::version::PlatformVersion; +use grovedb::TreeType; + +use crate::error::drive::DriveError; +use crate::error::Error; + +/// Extension trait for `DocumentTypeRef` that provides the tree type used +/// for primary key storage in Drive. +pub trait DocumentTypePrimaryKeyTreeType { + /// Returns the `TreeType` used for the primary key storage tree. + /// + /// The primary key tree (key `[0]` under the document type path) stores + /// document references keyed by document ID. The tree type depends on the + /// document type's configuration: + /// + /// - `range_countable = true` → `ProvableCountTree` + /// - `documents_countable = true` → `CountTree` + /// - otherwise → `NormalTree` + fn primary_key_tree_type(&self, platform_version: &PlatformVersion) -> Result; +} + +impl DocumentTypePrimaryKeyTreeType for DocumentTypeRef<'_> { + fn primary_key_tree_type(&self, platform_version: &PlatformVersion) -> Result { + match platform_version + .drive + .methods + .document + .primary_key_tree_type + { + 0 => { + if self.range_countable() { + Ok(TreeType::ProvableCountTree) + } else if self.documents_countable() { + Ok(TreeType::CountTree) + } else { + Ok(TreeType::NormalTree) + } + } + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "DocumentTypeRef::primary_key_tree_type".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::document_type::accessors::DocumentTypeV2Setters; + use dpp::data_contract::document_type::DocumentType; + use dpp::tests::json_document::json_document_to_contract_with_ids; + use dpp::version::PlatformVersion; + + fn make_doc_type() -> DocumentType { + let pv = PlatformVersion::latest(); + let contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/family/family-contract.json", + None, + None, + false, + pv, + ) + .expect("contract"); + let dt = contract + .document_type_for_name("person") + .expect("person type"); + dt.to_owned_document_type() + } + + #[test] + fn default_is_normal_tree() { + let dt = make_doc_type(); + let pv = PlatformVersion::latest(); + let result = dt.as_ref().primary_key_tree_type(pv).unwrap(); + assert_eq!(result, TreeType::NormalTree); + } + + #[test] + fn countable_is_count_tree() { + let mut dt = make_doc_type(); + dt.set_documents_countable(true); + let pv = PlatformVersion::latest(); + let result = dt.as_ref().primary_key_tree_type(pv).unwrap(); + assert_eq!(result, TreeType::CountTree); + } + + #[test] + fn blast_is_provable_count_tree() { + let mut dt = make_doc_type(); + dt.set_range_countable(true); + let pv = PlatformVersion::latest(); + let result = dt.as_ref().primary_key_tree_type(pv).unwrap(); + assert_eq!(result, TreeType::ProvableCountTree); + } + + #[test] + fn blast_takes_priority_over_countable() { + let mut dt = make_doc_type(); + dt.set_documents_countable(true); + dt.set_range_countable(true); + let pv = PlatformVersion::latest(); + let result = dt.as_ref().primary_key_tree_type(pv).unwrap(); + assert_eq!(result, TreeType::ProvableCountTree); + } +} diff --git a/packages/rs-drive/src/fees/op.rs b/packages/rs-drive/src/fees/op.rs index 7ed6bd5be52..ae7523804ab 100644 --- a/packages/rs-drive/src/fees/op.rs +++ b/packages/rs-drive/src/fees/op.rs @@ -443,6 +443,23 @@ impl LowLevelDriveOperation { LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree) } + /// Sets `GroveOperation` for inserting an empty provable count tree at the given path and key + pub fn for_known_path_key_empty_provable_count_tree( + path: Vec>, + key: Vec, + storage_flags: Option<&StorageFlags>, + ) -> Self { + let tree = match storage_flags { + Some(storage_flags) => Element::new_provable_count_tree_with_flags( + None, + storage_flags.to_some_element_flags(), + ), + None => Element::empty_provable_count_tree(), + }; + + LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree) + } + /// Sets `GroveOperation` for inserting an empty tree at the given path and key pub fn for_estimated_path_key_empty_tree( path: KeyInfoPath, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs index ea970e7d982..d4148ab1205 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs @@ -2,6 +2,7 @@ use versioned_feature_core::{FeatureVersion, FeatureVersionBounds}; pub mod v1; pub mod v2; pub mod v3; +pub mod v4; #[derive(Clone, Debug, Default)] pub struct DPPContractVersions { diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs new file mode 100644 index 00000000000..759b7687978 --- /dev/null +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs @@ -0,0 +1,66 @@ +use crate::version::dpp_versions::dpp_contract_versions::{ + DPPContractVersions, DataContractMethodVersions, DocumentTypeClassMethodVersions, + DocumentTypeIndexVersions, DocumentTypeMethodVersions, DocumentTypeSchemaVersions, + DocumentTypeVersions, RecursiveSchemaValidatorVersions, TokenVersions, +}; +use versioned_feature_core::FeatureVersionBounds; + +// Introduced in protocol version 12, adds documents_countable feature via try_from_schema v2 +pub const CONTRACT_VERSIONS_V4: DPPContractVersions = DPPContractVersions { + max_serialized_size: 65000, + contract_serialization_version: FeatureVersionBounds { + min_version: 0, + max_version: 1, + default_current_version: 1, + }, + contract_structure_version: 1, + created_data_contract_structure: 0, + config: FeatureVersionBounds { + min_version: 1, + max_version: 1, + default_current_version: 1, + }, + methods: DataContractMethodVersions { + validate_document: 0, + validate_update: 0, + schema: 0, + validate_groups: 0, + equal_ignoring_time_fields: 0, + registration_cost: 1, + }, + document_type_versions: DocumentTypeVersions { + index_versions: DocumentTypeIndexVersions { + index_levels_from_indices: 0, + }, + class_method_versions: DocumentTypeClassMethodVersions { + try_from_schema: 2, // changed: supports documentsCountable + create_document_types_from_document_schemas: 1, + }, + structure_version: 0, + schema: DocumentTypeSchemaVersions { + should_add_creator_id: 1, + enrich_with_base_schema: 0, + find_identifier_and_binary_paths: 0, + validate_max_depth: 0, + max_depth: 256, + recursive_schema_validator_versions: RecursiveSchemaValidatorVersions { + traversal_validator: 0, + }, + validate_schema_compatibility: 0, + }, + methods: DocumentTypeMethodVersions { + create_document_from_data: 0, + create_document_with_prevalidated_properties: 0, + prefunded_voting_balance_for_document: 0, + contested_vote_poll_for_document: 0, + estimated_size: 0, + index_for_types: 0, + max_size: 0, + serialize_value_for_key: 0, + deserialize_value_for_key: 0, + }, + }, + token_versions: TokenVersions { + validate_structure_interval: 0, + }, +}; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs index 8abec60c116..3834e48de42 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs @@ -12,6 +12,7 @@ pub struct DriveDocumentMethodVersions { pub update: DriveDocumentUpdateMethodVersions, pub estimation_costs: DriveDocumentEstimationCostsMethodVersions, pub index_uniqueness: DriveDocumentIndexUniquenessMethodVersions, + pub primary_key_tree_type: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs index 91627b4c1ad..2d90ceb157e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rs @@ -67,4 +67,5 @@ pub const DRIVE_DOCUMENT_METHOD_VERSIONS_V1: DriveDocumentMethodVersions = validate_document_purchase_transition_action_uniqueness: 0, validate_document_update_price_transition_action_uniqueness: 0, }, + primary_key_tree_type: 0, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs index 60e06ac729e..9dcfdc800bc 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rs @@ -69,4 +69,5 @@ pub const DRIVE_DOCUMENT_METHOD_VERSIONS_V2: DriveDocumentMethodVersions = validate_document_purchase_transition_action_uniqueness: 1, // Changed validate_document_update_price_transition_action_uniqueness: 1, // Changed }, + primary_key_tree_type: 0, }; diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index fbe019d3afd..e7149151200 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -1,6 +1,6 @@ use crate::version::consensus_versions::ConsensusVersions; use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1; -use crate::version::dpp_versions::dpp_contract_versions::v3::CONTRACT_VERSIONS_V3; +use crate::version::dpp_versions::dpp_contract_versions::v4::CONTRACT_VERSIONS_V4; use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1; use crate::version::dpp_versions::dpp_document_versions::v3::DOCUMENT_VERSIONS_V3; use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1; @@ -51,7 +51,7 @@ pub const PLATFORM_V12: PlatformVersion = PlatformVersion { state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2, state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V1, state_transitions: STATE_TRANSITION_VERSIONS_V3, - contract_versions: CONTRACT_VERSIONS_V3, + contract_versions: CONTRACT_VERSIONS_V4, // changed: try_from_schema v2 supports documentsCountable document_versions: DOCUMENT_VERSIONS_V3, identity_versions: IDENTITY_VERSIONS_V1, voting_versions: VOTING_VERSION_V2,