diff --git a/.github/workflows/tests-rs-package.yml b/.github/workflows/tests-rs-package.yml index 5cefd1a9c5b..b52e2bd8bfd 100644 --- a/.github/workflows/tests-rs-package.yml +++ b/.github/workflows/tests-rs-package.yml @@ -195,7 +195,7 @@ jobs: uses: ./.github/actions/librocksdb - name: Run tests - run: cargo test --package=${{ inputs.package }} --all-features --locked + run: RUST_MIN_STACK=16777216 cargo test --package=${{ inputs.package }} --all-features --locked env: SCCACHE_S3_KEY_PREFIX: ${{ runner.os }}/sccache/${{ runner.arch }}/linux-gnu ROCKSDB_STATIC: "/opt/rocksdb/usr/local/lib/librocksdb.a" diff --git a/Cargo.lock b/Cargo.lock index 060c025670d..35af7043497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1365,6 +1365,7 @@ dependencies = [ "dashpay-contract", "dpns-contract", "feature-flags-contract", + "keyword-search-contract", "masternode-reward-shares-contract", "platform-value", "platform-version", @@ -2886,6 +2887,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keyword-search-contract" +version = "2.0.0-dev.1" +dependencies = [ + "platform-value", + "platform-version", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "lazy_static" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 9923e07d4f2..044a49ee457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ members = [ "packages/rs-json-schema-compatibility-validator", "packages/check-features", "packages/wallet-utils-contract", - "packages/token-history-contract" + "packages/token-history-contract", + "packages/keyword-search-contract" ] exclude = ["packages/wasm-sdk"] # This one is experimental and not ready for use diff --git a/Dockerfile b/Dockerfile index 47e7e5a77fe..c0411248e73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,25 +68,25 @@ FROM node:20-alpine${ALPINE_VERSION} AS deps-base # Install some dependencies # RUN apk add --no-cache \ - alpine-sdk \ - bash \ - binutils \ - ca-certificates \ - clang-static clang-dev \ - cmake \ - curl \ - git \ - libc-dev \ - linux-headers \ - llvm-static llvm-dev \ - openssl-dev \ - snappy-static snappy-dev \ - perl \ - python3 \ - unzip \ - wget \ - xz \ - zeromq-dev + alpine-sdk \ + bash \ + binutils \ + ca-certificates \ + clang-static clang-dev \ + cmake \ + curl \ + git \ + libc-dev \ + linux-headers \ + llvm-static llvm-dev \ + openssl-dev \ + snappy-static snappy-dev \ + perl \ + python3 \ + unzip \ + wget \ + xz \ + zeromq-dev # Configure snappy, dependency of librocksdb-sys RUN <> /platform/packages/dapi-grpc/build.rs && \ cargo build \ - --profile "${CARGO_BUILD_PROFILE}" \ - --package drive-abci \ - ${FEATURES_FLAG} \ - --locked && \ + --profile "${CARGO_BUILD_PROFILE}" \ + --package drive-abci \ + ${FEATURES_FLAG} \ + --locked && \ cp target/${OUT_DIRECTORY}/drive-abci /artifacts/ && \ if [[ -x /usr/bin/sccache ]]; then sccache --show-stats; fi && \ # Remove /platform to reduce layer size @@ -523,11 +525,11 @@ RUN --mount=type=cache,sharing=shared,id=cargo_registry_index,target=${CARGO_HOM source /root/env && \ unset CFLAGS CXXFLAGS && \ cargo chef cook \ - --recipe-path recipe.json \ - --profile "$CARGO_BUILD_PROFILE" \ - --package wasm-dpp \ - --target wasm32-unknown-unknown \ - --locked && \ + --recipe-path recipe.json \ + --profile "$CARGO_BUILD_PROFILE" \ + --package wasm-dpp \ + --target wasm32-unknown-unknown \ + --locked && \ if [[ -x /usr/bin/sccache ]]; then sccache --show-stats; fi @@ -551,6 +553,7 @@ COPY --parents \ packages/withdrawals-contract \ packages/wallet-utils-contract \ packages/token-history-contract \ + packages/keyword-search-contract \ packages/masternode-reward-shares-contract \ packages/feature-flags-contract \ packages/dpns-contract \ @@ -674,6 +677,7 @@ COPY --from=build-dashmate-helper /platform/packages/dapi-grpc packages/dapi-grp COPY --from=build-dashmate-helper /platform/packages/dash-spv packages/dash-spv COPY --from=build-dashmate-helper /platform/packages/wallet-utils-contract packages/wallet-utils-contract COPY --from=build-dashmate-helper /platform/packages/token-history-contract packages/token-history-contract +COPY --from=build-dashmate-helper /platform/packages/keyword-search-contract packages/keyword-search-contract COPY --from=build-dashmate-helper /platform/packages/withdrawals-contract packages/withdrawals-contract COPY --from=build-dashmate-helper /platform/packages/masternode-reward-shares-contract packages/masternode-reward-shares-contract COPY --from=build-dashmate-helper /platform/packages/feature-flags-contract packages/feature-flags-contract @@ -750,6 +754,7 @@ COPY --from=build-dapi /platform/packages/dapi-grpc /platform/packages/dapi-grpc COPY --from=build-dapi /platform/packages/js-grpc-common /platform/packages/js-grpc-common COPY --from=build-dapi /platform/packages/wasm-dpp /platform/packages/wasm-dpp COPY --from=build-dapi /platform/packages/token-history-contract /platform/packages/token-history-contract +COPY --from=build-dapi /platform/packages/keyword-search-contract /platform/packages/keyword-search-contract RUN cp /platform/packages/dapi/.env.example /platform/packages/dapi/.env diff --git a/package.json b/package.json index 091c572b30d..ea3156fbfdb 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,8 @@ "packages/dash-spv", "packages/wasm-dpp", "packages/withdrawals-contract", - "packages/token-history-contract" + "packages/token-history-contract", + "packages/search-contract" ], "resolutions": { "elliptic": "6.5.7", diff --git a/packages/data-contracts/Cargo.toml b/packages/data-contracts/Cargo.toml index 4e4ba137834..6c23afa34be 100644 --- a/packages/data-contracts/Cargo.toml +++ b/packages/data-contracts/Cargo.toml @@ -18,3 +18,4 @@ feature-flags-contract = { path = "../feature-flags-contract" } platform-value = { path = "../rs-platform-value" } wallet-utils-contract = { path = "../wallet-utils-contract" } token-history-contract = { path = "../token-history-contract" } +keyword-search-contract = { path = "../keyword-search-contract" } diff --git a/packages/data-contracts/src/error.rs b/packages/data-contracts/src/error.rs index 7c0c802b71e..9f82617bc18 100644 --- a/packages/data-contracts/src/error.rs +++ b/packages/data-contracts/src/error.rs @@ -136,3 +136,20 @@ impl From for Error { } } } + +impl From for Error { + fn from(e: keyword_search_contract::Error) -> Self { + match e { + keyword_search_contract::Error::UnknownVersionMismatch { + method, + known_versions, + received, + } => Error::UnknownVersionMismatch { + method, + known_versions, + received, + }, + keyword_search_contract::Error::InvalidSchemaJson(e) => Error::InvalidSchemaJson(e), + } + } +} diff --git a/packages/data-contracts/src/lib.rs b/packages/data-contracts/src/lib.rs index a78ffefc373..bd7754cfef3 100644 --- a/packages/data-contracts/src/lib.rs +++ b/packages/data-contracts/src/lib.rs @@ -6,6 +6,7 @@ use crate::error::Error; pub use dashpay_contract; pub use dpns_contract; pub use feature_flags_contract; +pub use keyword_search_contract; pub use masternode_reward_shares_contract; use platform_value::Identifier; use platform_version::version::PlatformVersion; @@ -23,6 +24,7 @@ pub enum SystemDataContract { Dashpay = 4, WalletUtils = 5, TokenHistory = 6, + KeywordSearch = 7, } pub struct DataContractSource { @@ -43,6 +45,7 @@ impl SystemDataContract { SystemDataContract::Dashpay => dashpay_contract::ID_BYTES, SystemDataContract::WalletUtils => wallet_utils_contract::ID_BYTES, SystemDataContract::TokenHistory => token_history_contract::ID_BYTES, + SystemDataContract::KeywordSearch => keyword_search_contract::ID_BYTES, }; Identifier::new(bytes) } @@ -98,10 +101,19 @@ impl SystemDataContract { SystemDataContract::TokenHistory => DataContractSource { id_bytes: token_history_contract::ID_BYTES, owner_id_bytes: token_history_contract::OWNER_ID_BYTES, - version: platform_version.system_data_contracts.wallet as u32, + version: platform_version.system_data_contracts.token_history as u32, definitions: token_history_contract::load_definitions(platform_version)?, document_schemas: token_history_contract::load_documents_schemas(platform_version)?, }, + SystemDataContract::KeywordSearch => DataContractSource { + id_bytes: keyword_search_contract::ID_BYTES, + owner_id_bytes: keyword_search_contract::OWNER_ID_BYTES, + version: platform_version.system_data_contracts.keyword_search as u32, + definitions: keyword_search_contract::load_definitions(platform_version)?, + document_schemas: keyword_search_contract::load_documents_schemas( + platform_version, + )?, + }, }; Ok(data) diff --git a/packages/search-contract/.eslintrc b/packages/keyword-search-contract/.eslintrc similarity index 100% rename from packages/search-contract/.eslintrc rename to packages/keyword-search-contract/.eslintrc diff --git a/packages/search-contract/.mocharc.yml b/packages/keyword-search-contract/.mocharc.yml similarity index 100% rename from packages/search-contract/.mocharc.yml rename to packages/keyword-search-contract/.mocharc.yml diff --git a/packages/search-contract/Cargo.toml b/packages/keyword-search-contract/Cargo.toml similarity index 63% rename from packages/search-contract/Cargo.toml rename to packages/keyword-search-contract/Cargo.toml index ee1b25e2bc7..e99b79a04de 100644 --- a/packages/search-contract/Cargo.toml +++ b/packages/keyword-search-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "search-contract" -description = "Search data contract schema and tools. Search contract is using to find other contracts and tokens" +name = "keyword-search-contract" +description = "Search data contract schema and tools. Keyword Search contract is used to find other contracts and tokens" version = "2.0.0-dev.1" edition = "2021" rust-version.workspace = true diff --git a/packages/search-contract/LICENSE b/packages/keyword-search-contract/LICENSE similarity index 100% rename from packages/search-contract/LICENSE rename to packages/keyword-search-contract/LICENSE diff --git a/packages/search-contract/README.md b/packages/keyword-search-contract/README.md similarity index 100% rename from packages/search-contract/README.md rename to packages/keyword-search-contract/README.md diff --git a/packages/search-contract/lib/systemIds.js b/packages/keyword-search-contract/lib/systemIds.js similarity index 52% rename from packages/search-contract/lib/systemIds.js rename to packages/keyword-search-contract/lib/systemIds.js index 7335d3d1635..f1aa5b80c07 100644 --- a/packages/search-contract/lib/systemIds.js +++ b/packages/keyword-search-contract/lib/systemIds.js @@ -1,4 +1,4 @@ module.exports = { ownerId: '11111111111111111111111111111111', - contractId: '8v8CoKCDdBcQu1Y7GDNJjR7a5vkMmgpXycJURkaUhfU9', + contractId: '7CSFGeF4WNzgDmx94zwvHkYaG3Dx4XEe5LFsFgJswLbm', }; diff --git a/packages/search-contract/package.json b/packages/keyword-search-contract/package.json similarity index 100% rename from packages/search-contract/package.json rename to packages/keyword-search-contract/package.json diff --git a/packages/keyword-search-contract/schema/v1/keyword-search-contract-documents.json b/packages/keyword-search-contract/schema/v1/keyword-search-contract-documents.json new file mode 100644 index 00000000000..fac5cc55d30 --- /dev/null +++ b/packages/keyword-search-contract/schema/v1/keyword-search-contract-documents.json @@ -0,0 +1,126 @@ +{ + "contractKeywords": { + "type": "object", + "documentsMutable": false, + "canBeDeleted": false, + "creationRestrictionMode": 2, + "indices": [ + { + "name": "byKeyword", + "properties": [ + { + "keyword": "asc" + } + ] + }, + { + "name": "byContractId", + "properties": [ + { + "contractId": "asc" + } + ] + } + ], + "properties": { + "keyword": { + "type": "string", + "minLength": 3, + "maxLength": 50, + "position": 0 + }, + "contractId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 1, + "contentMediaType": "application/x.dash.dpp.identifier" + } + }, + "required": [ + "contractId", + "keyword" + ], + "description": "Keywords associated with the contract allowing for better searchability. This document type can be deleted but not manually created nor updated. Creation occurs automatically internally on DataContractCreate and mirrors the contract's keywords field. It can be updated with DataContractUpdate.", + "additionalProperties": false + }, + "shortDescription": { + "type": "object", + "documentsMutable": true, + "canBeDeleted": false, + "creationRestrictionMode": 2, + "indices": [ + { + "name": "byContractId", + "properties": [ + { + "contractId": "asc" + } + ], + "unique": true + } + ], + "properties": { + "contractId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 0, + "contentMediaType": "application/x.dash.dpp.identifier" + }, + "description": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "position": 1 + } + }, + "required": [ + "contractId", + "description" + ], + "description": "Short description of the contract. This document type is mutable but creation is not allowed. Creation occurs automatically internally on DataContractCreate and mirrors the contract's description field initially.", + "additionalProperties": false + }, + "fullDescription": { + "type": "object", + "documentsMutable": true, + "canBeDeleted": false, + "creationRestrictionMode": 2, + "indices": [ + { + "name": "byContractId", + "properties": [ + { + "contractId": "asc" + } + ], + "unique": true + } + ], + "properties": { + "contractId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 0, + "contentMediaType": "application/x.dash.dpp.identifier" + }, + "description": { + "type": "string", + "minLength": 3, + "maxLength": 10000, + "position": 1 + } + }, + "required": [ + "contractId", + "description" + ], + "description": "Full description of the contract. This document type is mutable but creation is not allowed. Creation occurs automatically internally on DataContractCreate and mirrors the contract's description field initially.", + "additionalProperties": false + } +} \ No newline at end of file diff --git a/packages/search-contract/src/error.rs b/packages/keyword-search-contract/src/error.rs similarity index 100% rename from packages/search-contract/src/error.rs rename to packages/keyword-search-contract/src/error.rs diff --git a/packages/search-contract/src/lib.rs b/packages/keyword-search-contract/src/lib.rs similarity index 79% rename from packages/search-contract/src/lib.rs rename to packages/keyword-search-contract/src/lib.rs index f7767ea6c26..e074d139c7f 100644 --- a/packages/search-contract/src/lib.rs +++ b/packages/keyword-search-contract/src/lib.rs @@ -16,20 +16,20 @@ pub const OWNER_ID_BYTES: [u8; 32] = [0; 32]; pub const ID: Identifier = Identifier(IdentifierBytes32(ID_BYTES)); pub const OWNER_ID: Identifier = Identifier(IdentifierBytes32(OWNER_ID_BYTES)); pub fn load_definitions(platform_version: &PlatformVersion) -> Result, Error> { - match platform_version.system_data_contracts.withdrawals { + match platform_version.system_data_contracts.keyword_search { 1 => Ok(None), version => Err(Error::UnknownVersionMismatch { - method: "search_contract::load_definitions".to_string(), + method: "keyword_search_contract::load_definitions".to_string(), known_versions: vec![1], received: version, }), } } pub fn load_documents_schemas(platform_version: &PlatformVersion) -> Result { - match platform_version.system_data_contracts.withdrawals { + match platform_version.system_data_contracts.keyword_search { 1 => v1::load_documents_schemas(), version => Err(Error::UnknownVersionMismatch { - method: "search_contract::load_documents_schemas".to_string(), + method: "keyword_search_contract::load_documents_schemas".to_string(), known_versions: vec![1], received: version, }), diff --git a/packages/keyword-search-contract/src/v1/mod.rs b/packages/keyword-search-contract/src/v1/mod.rs new file mode 100644 index 00000000000..cf69e071d33 --- /dev/null +++ b/packages/keyword-search-contract/src/v1/mod.rs @@ -0,0 +1,35 @@ +use crate::Error; +use serde_json::Value; + +pub mod document_types { + pub mod contract_keywords { + pub const NAME: &str = "contractKeywords"; + + pub mod properties { + pub const KEY_INDEX: &str = "byKeyword"; + } + } + + pub mod short_description { + pub const NAME: &str = "shortDescription"; + + pub mod properties { + pub const KEY_INDEX: &str = "byContractId"; + } + } + + pub mod full_description { + pub const NAME: &str = "fullDescription"; + + pub mod properties { + pub const KEY_INDEX: &str = "byContractId"; + } + } +} + +pub fn load_documents_schemas() -> Result { + serde_json::from_str(include_str!( + "../../schema/v1/keyword-search-contract-documents.json" + )) + .map_err(Error::InvalidSchemaJson) +} diff --git a/packages/search-contract/test/.eslintrc b/packages/keyword-search-contract/test/.eslintrc similarity index 100% rename from packages/search-contract/test/.eslintrc rename to packages/keyword-search-contract/test/.eslintrc diff --git a/packages/search-contract/test/bootstrap.js b/packages/keyword-search-contract/test/bootstrap.js similarity index 100% rename from packages/search-contract/test/bootstrap.js rename to packages/keyword-search-contract/test/bootstrap.js diff --git a/packages/search-contract/test/unit/searchContract.spec.js b/packages/keyword-search-contract/test/unit/searchContract.spec.js similarity index 96% rename from packages/search-contract/test/unit/searchContract.spec.js rename to packages/keyword-search-contract/test/unit/searchContract.spec.js index 2fd94a879b7..c13d4d6d681 100644 --- a/packages/search-contract/test/unit/searchContract.spec.js +++ b/packages/keyword-search-contract/test/unit/searchContract.spec.js @@ -7,7 +7,7 @@ const { const generateRandomIdentifier = require('@dashevo/wasm-dpp/lib/test/utils/generateRandomIdentifierAsync'); const { expect } = require('chai'); -const walletContractDocumentsSchema = require('../../schema/v1/search-contract-documents.json'); +const keywordSearchContractDocumentsSchema = require('../../schema/v1/keyword-search-contract-documents.json'); const expectJsonSchemaError = (validationResult, errorCount = 1) => { const errors = validationResult.getErrors(); @@ -37,11 +37,11 @@ describe('Search Contract', () => { identityId = await generateRandomIdentifier(); - dataContract = dpp.dataContract.create(identityId, BigInt(1), walletContractDocumentsSchema); + dataContract = dpp.dataContract.create(identityId, BigInt(1), keywordSearchContractDocumentsSchema); }); it('should have a valid contract definition', async () => { - expect(() => dpp.dataContract.create(identityId, BigInt(1), walletContractDocumentsSchema)) + expect(() => dpp.dataContract.create(identityId, BigInt(1), keywordSearchContractDocumentsSchema)) .to .not .throw(); 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 5570d74da3c..78bbeccef3b 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 @@ -227,7 +227,9 @@ "properties": { "position": true }, - "required": ["position"] + "required": [ + "position" + ] } } } @@ -420,7 +422,9 @@ "maxLength": 256 } }, - "required": ["resolution"], + "required": [ + "resolution" + ], "additionalProperties": false } }, @@ -580,6 +584,17 @@ "type": "string" } }, + "keywords": { + "type": "array", + "description": "List of up to 20 descriptive keywords for the contract, used in the Keyword Search contract", + "items": { + "type": "string", + "minLength": 3, + "maxLength": 50 + }, + "maxItems": 20, + "uniqueItems": true + }, "additionalProperties": { "type": "boolean", "const": false @@ -591,4 +606,4 @@ "properties", "additionalProperties" ] -} +} \ No newline at end of file diff --git a/packages/rs-dpp/src/data_contract/accessors/mod.rs b/packages/rs-dpp/src/data_contract/accessors/mod.rs index edb686751d0..8557931c81e 100644 --- a/packages/rs-dpp/src/data_contract/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/accessors/mod.rs @@ -18,6 +18,8 @@ use crate::tokens::errors::TokenError; use crate::ProtocolError; use std::collections::BTreeMap; +use super::EMPTY_KEYWORDS; + pub mod v0; pub mod v1; @@ -268,6 +270,34 @@ impl DataContractV1Getters for DataContract { } } + fn keywords(&self) -> &Vec { + match self { + DataContract::V0(_) => &EMPTY_KEYWORDS, + DataContract::V1(v1) => &v1.keywords, + } + } + + fn keywords_mut(&mut self) -> Option<&mut Vec> { + match self { + DataContract::V0(_) => None, + DataContract::V1(v1) => Some(&mut v1.keywords), + } + } + + fn description(&self) -> Option<&String> { + match self { + DataContract::V0(_) => None, + DataContract::V1(v1) => v1.description.as_ref(), + } + } + + fn description_mut(&mut self) -> Option<&mut String> { + match self { + DataContract::V0(_) => None, + DataContract::V1(v1) => v1.description.as_mut(), + } + } + /// Returns the timestamp in milliseconds when the contract was created. fn created_at(&self) -> Option { match self { @@ -399,4 +429,18 @@ impl DataContractV1Setters for DataContract { v1.updated_at_epoch = epoch; } } + + /// Sets the keywords for the contract. + fn set_keywords(&mut self, keywords: Vec) { + if let DataContract::V1(v1) = self { + v1.keywords = keywords; + } + } + + /// Sets the description for the contract. + fn set_description(&mut self, description: Option) { + if let DataContract::V1(v1) = self { + v1.description = description; + } + } } diff --git a/packages/rs-dpp/src/data_contract/accessors/v1/mod.rs b/packages/rs-dpp/src/data_contract/accessors/v1/mod.rs index 8178ab7ec2c..82a32be1b5f 100644 --- a/packages/rs-dpp/src/data_contract/accessors/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/accessors/v1/mod.rs @@ -63,6 +63,18 @@ pub trait DataContractV1Getters: DataContractV0Getters { /// Returns the epoch at which the contract was last updated. fn updated_at_epoch(&self) -> Option; + + /// Returns the keywords for the contract. + fn keywords(&self) -> &Vec; + + /// Returns a mutable reference to the keywords for the contract. + fn keywords_mut(&mut self) -> Option<&mut Vec>; + + /// Returns the description for the contract. + fn description(&self) -> Option<&String>; + + /// Returns a mutable reference to the description for the contract. + fn description_mut(&mut self) -> Option<&mut String>; } pub trait DataContractV1Setters: DataContractV0Setters { @@ -95,4 +107,10 @@ pub trait DataContractV1Setters: DataContractV0Setters { /// Sets the block height at which the contract was last updated. fn set_updated_at_epoch(&mut self, epoch: Option); + + /// Sets the keywords for the contract. + fn set_keywords(&mut self, keywords: Vec); + + /// Sets the description for the contract. + fn set_description(&mut self, description: Option); } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs index 662ab19373f..31bc9009da0 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs @@ -142,6 +142,13 @@ impl TokenConfigurationV0Getters for TokenConfiguration { TokenConfiguration::V0(v0) => v0.all_used_group_positions(), } } + + /// Returns the token description. + fn description(&self) -> &Option { + match self { + TokenConfiguration::V0(v0) => v0.description(), + } + } } /// Implementing TokenConfigurationV0Setters for TokenConfiguration @@ -240,4 +247,11 @@ impl TokenConfigurationV0Setters for TokenConfiguration { TokenConfiguration::V0(v0) => v0.set_main_control_group_can_be_modified(action_takers), } } + + /// Sets the token description. + fn set_description(&mut self, description: Option) { + match self { + TokenConfiguration::V0(v0) => v0.set_description(description), + } + } } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs index 59fa3d34b6c..093d4622ea9 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs @@ -14,13 +14,17 @@ pub trait TokenConfigurationV0Getters { /// Returns a mutable reference to the conventions. fn conventions_mut(&mut self) -> &mut TokenConfigurationConvention; + /// Returns the new tokens destination identity rules. fn conventions_change_rules(&self) -> &ChangeControlRules; /// Returns the base supply. fn base_supply(&self) -> TokenAmount; - /// Returns the base supply. + + /// Returns the KeepsHistory rules. fn keeps_history(&self) -> &TokenKeepsHistoryRules; + + /// Returns if we start as paused. fn start_as_paused(&self) -> bool; /// Returns the maximum supply. @@ -46,8 +50,10 @@ pub trait TokenConfigurationV0Getters { /// Returns the unfreeze rules. fn unfreeze_rules(&self) -> &ChangeControlRules; + /// Returns the destroy frozen funds rules. fn destroy_frozen_funds_rules(&self) -> &ChangeControlRules; + /// Returns the emergency action rules. fn emergency_action_rules(&self) -> &ChangeControlRules; @@ -59,6 +65,9 @@ pub trait TokenConfigurationV0Getters { /// Returns all group positions used in the token configuration fn all_used_group_positions(&self) -> BTreeSet; + + /// Returns the token description. + fn description(&self) -> &Option; } /// Accessor trait for setters of `TokenConfigurationV0` @@ -92,8 +101,10 @@ pub trait TokenConfigurationV0Setters { /// Sets the unfreeze rules. fn set_unfreeze_rules(&mut self, rules: ChangeControlRules); + /// Sets the `destroy frozen funds` rules. fn set_destroy_frozen_funds_rules(&mut self, rules: ChangeControlRules); + /// Sets the emergency action rules. fn set_emergency_action_rules(&mut self, rules: ChangeControlRules); @@ -102,4 +113,7 @@ pub trait TokenConfigurationV0Setters { /// Sets the main control group can be modified. fn set_main_control_group_can_be_modified(&mut self, action_takers: AuthorizedActionTakers); + + /// Sets the token description. + fn set_description(&mut self, description: Option); } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs index 2e7c1353ad4..889df4e4d0a 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs @@ -149,6 +149,11 @@ impl TokenConfigurationV0Getters for TokenConfigurationV0 { group_positions } + + /// Returns the token description. + fn description(&self) -> &Option { + &self.description + } } /// Implementing `TokenConfigurationV0Setters` for `TokenConfigurationV0` @@ -220,4 +225,9 @@ impl TokenConfigurationV0Setters for TokenConfigurationV0 { fn set_main_control_group_can_be_modified(&mut self, action_takers: AuthorizedActionTakers) { self.main_control_group_can_be_modified = action_takers; } + + /// Sets the token description. + fn set_description(&mut self, description: Option) { + self.description = description; + } } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs index 0254aceb4a9..fed112ee003 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/mod.rs @@ -57,6 +57,8 @@ pub struct TokenConfigurationV0 { pub main_control_group: Option, #[serde(default)] pub main_control_group_can_be_modified: AuthorizedActionTakers, + #[serde(default)] + pub description: Option, } // Default function for `keeps_history` @@ -285,6 +287,7 @@ impl TokenConfigurationV0 { .into(), main_control_group: None, main_control_group_can_be_modified: AuthorizedActionTakers::NoOne, + description: None, } } 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 0ce43249224..edfe992a22b 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 @@ -1,10 +1,13 @@ +use std::collections::HashSet; + use crate::block::block_info::BlockInfo; use crate::consensus::state::state_error::StateError; use crate::consensus::state::token::PreProgrammedDistributionTimestampInPastError; use crate::data_contract::accessors::v0::DataContractV0Getters; use crate::consensus::basic::data_contract::{ - IncompatibleDataContractSchemaError, InvalidDataContractVersionError, + DuplicateKeywordsError, IncompatibleDataContractSchemaError, InvalidDataContractVersionError, + InvalidDescriptionLengthError, InvalidKeywordLengthError, TooManyKeywordsError, }; use crate::consensus::state::data_contract::data_contract_update_action_not_allowed_error::DataContractUpdateActionNotAllowedError; use crate::consensus::state::data_contract::data_contract_update_permission_error::DataContractUpdatePermissionError; @@ -263,6 +266,45 @@ impl DataContract { } } + if self.keywords() != new_data_contract.keywords() { + // Validate there are no more than 20 keywords + if new_data_contract.keywords().len() > 20 { + return Ok(SimpleConsensusValidationResult::new_with_error( + TooManyKeywordsError::new(self.id(), self.keywords().len() as u8).into(), + )); + } + + // Validate the keywords are all unique and between 3 and 50 characters + let mut seen_keywords = HashSet::new(); + for keyword in new_data_contract.keywords() { + // First check keyword length + if keyword.len() < 3 || keyword.len() > 50 { + return Ok(SimpleConsensusValidationResult::new_with_error( + InvalidKeywordLengthError::new(self.id(), keyword.to_string()).into(), + )); + } + + // Then check uniqueness + if !seen_keywords.insert(keyword) { + return Ok(SimpleConsensusValidationResult::new_with_error( + DuplicateKeywordsError::new(self.id(), keyword.to_string()).into(), + )); + } + } + } + + if self.description() != new_data_contract.description() { + // Validate the description is between 3 and 100 characters + if let Some(description) = new_data_contract.description() { + if !(description.len() >= 3 && description.len() <= 100) { + return Ok(SimpleConsensusValidationResult::new_with_error( + InvalidDescriptionLengthError::new(self.id(), description.to_string()) + .into(), + )); + } + } + } + Ok(SimpleConsensusValidationResult::new()) } } diff --git a/packages/rs-dpp/src/data_contract/mod.rs b/packages/rs-dpp/src/data_contract/mod.rs index 754bd12febb..8cc81bc0800 100644 --- a/packages/rs-dpp/src/data_contract/mod.rs +++ b/packages/rs-dpp/src/data_contract/mod.rs @@ -73,10 +73,11 @@ type PropertyPath = String; pub const INITIAL_DATA_CONTRACT_VERSION: u32 = 1; -// Define static empty BTreeMaps +// Define static empty BTreeMaps and Vecs static EMPTY_GROUPS: Lazy> = Lazy::new(BTreeMap::new); static EMPTY_TOKENS: Lazy> = Lazy::new(BTreeMap::new); +static EMPTY_KEYWORDS: Lazy> = Lazy::new(Vec::new); /// Understanding Data Contract versioning /// Data contract versioning is both for the code structure and for serialization. diff --git a/packages/rs-dpp/src/data_contract/serialized_version/mod.rs b/packages/rs-dpp/src/data_contract/serialized_version/mod.rs index 5595f288a39..71cdd23526b 100644 --- a/packages/rs-dpp/src/data_contract/serialized_version/mod.rs +++ b/packages/rs-dpp/src/data_contract/serialized_version/mod.rs @@ -21,6 +21,8 @@ use platform_versioning::PlatformVersioned; #[cfg(feature = "data-contract-serde-conversion")] use serde::{Deserialize, Serialize}; +use super::EMPTY_KEYWORDS; + pub(in crate::data_contract) mod v0; pub(in crate::data_contract) mod v1; @@ -97,6 +99,20 @@ impl DataContractInSerializationFormat { } } + pub fn keywords(&self) -> &Vec { + match self { + DataContractInSerializationFormat::V0(_) => &EMPTY_KEYWORDS, + DataContractInSerializationFormat::V1(v1) => &v1.keywords, + } + } + + pub fn description(&self) -> &Option { + match self { + DataContractInSerializationFormat::V0(_) => &None, + DataContractInSerializationFormat::V1(v1) => &v1.description, + } + } + pub fn eq_without_auto_fields(&self, other: &Self) -> bool { match (self, other) { ( diff --git a/packages/rs-dpp/src/data_contract/serialized_version/v1/mod.rs b/packages/rs-dpp/src/data_contract/serialized_version/v1/mod.rs index 12828ce8f53..c9144dfa7c7 100644 --- a/packages/rs-dpp/src/data_contract/serialized_version/v1/mod.rs +++ b/packages/rs-dpp/src/data_contract/serialized_version/v1/mod.rs @@ -59,6 +59,14 @@ pub struct DataContractInSerializationFormatV1 { /// The tokens on the contract. #[serde(default, deserialize_with = "deserialize_u16_token_configuration_map")] pub tokens: BTreeMap, + + /// The contract's keywords for searching + #[serde(default)] + pub keywords: Vec, + + /// The contract's description + #[serde(default)] + pub description: Option, } fn deserialize_u16_group_map<'de, D>( @@ -124,6 +132,8 @@ impl From for DataContractInSerializationFormatV1 { updated_at_epoch: None, groups: Default::default(), tokens: Default::default(), + keywords: Default::default(), + description: None, } } DataContract::V1(v1) => { @@ -142,6 +152,8 @@ impl From for DataContractInSerializationFormatV1 { updated_at_epoch, groups, tokens, + keywords, + description, } = v1; DataContractInSerializationFormatV1 { @@ -162,6 +174,8 @@ impl From for DataContractInSerializationFormatV1 { updated_at_epoch, groups, tokens, + keywords, + description, } } } diff --git a/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs b/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs index 1a6ae46a1b9..72bdb5069c4 100644 --- a/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs @@ -224,6 +224,26 @@ impl DataContractV1Getters for DataContractV1 { fn updated_at_epoch(&self) -> Option { self.updated_at_epoch } + + /// Returns the keywords for the contract. + fn keywords(&self) -> &Vec { + &self.keywords + } + + /// Returns a mutable reference to the keywords for the contract. + fn keywords_mut(&mut self) -> Option<&mut Vec> { + Some(&mut self.keywords) + } + + /// Returns the description of the contract. + fn description(&self) -> Option<&String> { + self.description.as_ref() + } + + /// Returns a mutable reference to the description of the contract. + fn description_mut(&mut self) -> Option<&mut String> { + self.description.as_mut() + } } impl DataContractV1Setters for DataContractV1 { @@ -272,4 +292,14 @@ impl DataContractV1Setters for DataContractV1 { fn set_updated_at_epoch(&mut self, epoch: Option) { self.updated_at_epoch = epoch; } + + /// Sets the keywords for the contract. + fn set_keywords(&mut self, keywords: Vec) { + self.keywords = keywords; + } + + /// Sets the description for the contract. + fn set_description(&mut self, description: Option) { + self.description = description; + } } diff --git a/packages/rs-dpp/src/data_contract/v1/data_contract.rs b/packages/rs-dpp/src/data_contract/v1/data_contract.rs index 104e6334d94..e9c247f2373 100644 --- a/packages/rs-dpp/src/data_contract/v1/data_contract.rs +++ b/packages/rs-dpp/src/data_contract/v1/data_contract.rs @@ -61,6 +61,16 @@ use platform_value::Value; /// - Useful for historical analysis, rollback mechanisms, and ensuring changes are anchored /// to specific blockchain states. /// +/// ## 4. **Keywords** (`keywords: Vec`) +/// - Keywords which contracts can be searched for via the new `search` system contract. +/// - This vector can be left empty, but if populated, it must contain unique keywords. +/// - The maximum number of keywords is limited to 20. +/// +/// ## 5. **Description** (`description: Option`) +/// - A human-readable description of the contract. +/// - This field is optional but if provided, must be between 3 and 100 characters. +/// - The description is automatically added to the new `search` system contract as well. +/// /// These additions ensure that data contracts are not only more flexible and governed but also /// fully auditable in terms of when and how they evolve over time. #[derive(Debug, Clone, PartialEq)] @@ -102,4 +112,10 @@ pub struct DataContractV1 { /// The tokens on the contract. pub tokens: BTreeMap, + + /// The contract's keywords for searching + pub keywords: Vec, + + /// The contract's description + pub description: Option, } diff --git a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs index 7f8976516be..332cf574d2a 100644 --- a/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/serialization/mod.rs @@ -111,6 +111,8 @@ impl DataContractV1 { updated_at_epoch: None, groups: Default::default(), tokens: Default::default(), + keywords: Default::default(), + description: None, }; Ok(data_contract) @@ -137,6 +139,8 @@ impl DataContractV1 { updated_at_epoch, groups, tokens, + keywords, + description, } = data_contract_data; let document_types = DocumentType::create_document_types_from_document_schemas( @@ -165,6 +169,8 @@ impl DataContractV1 { updated_at_epoch, groups, tokens, + keywords, + description, }; Ok(data_contract) 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 9c883845be4..015d27e506a 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -75,6 +75,10 @@ use crate::consensus::basic::{ }; use crate::consensus::ConsensusError; +use super::data_contract::{ + DuplicateKeywordsError, InvalidDescriptionLengthError, InvalidKeywordLengthError, + TooManyKeywordsError, +}; use crate::consensus::basic::group::GroupActionNotAllowedOnTransitionError; use crate::consensus::basic::overflow_error::OverflowError; use crate::consensus::basic::token::{ @@ -515,6 +519,18 @@ pub enum BasicError { TokenPaymentByBurningOnlyAllowedOnInternalTokenError( TokenPaymentByBurningOnlyAllowedOnInternalTokenError, ), + + #[error(transparent)] + TooManyKeywordsError(TooManyKeywordsError), + + #[error(transparent)] + DuplicateKeywordsError(DuplicateKeywordsError), + + #[error(transparent)] + InvalidKeywordLengthError(InvalidKeywordLengthError), + + #[error(transparent)] + InvalidDescriptionLengthError(InvalidDescriptionLengthError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/duplicate_keywords_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/duplicate_keywords_error.rs new file mode 100644 index 00000000000..c84d918cf36 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/duplicate_keywords_error.rs @@ -0,0 +1,45 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::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("Data contract {data_contract_id} has duplicated keyword '{keyword}'.")] +#[platform_serialize(unversioned)] +pub struct DuplicateKeywordsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + data_contract_id: Identifier, + keyword: String, +} + +impl DuplicateKeywordsError { + pub fn new(data_contract_id: Identifier, keyword: String) -> Self { + Self { + data_contract_id, + keyword, + } + } + + pub fn data_contract_id(&self) -> &Identifier { + &self.data_contract_id + } + + pub fn keyword(&self) -> &str { + &self.keyword + } +} + +impl From for ConsensusError { + fn from(err: DuplicateKeywordsError) -> Self { + Self::BasicError(BasicError::DuplicateKeywordsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_description_length_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_description_length_error.rs new file mode 100644 index 00000000000..5554da1ac4e --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_description_length_error.rs @@ -0,0 +1,45 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::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("Data contract {} has description with invalid length: '{}'. Valid length is between 3 and 100 characters.", data_contract_id, description.len())] +#[platform_serialize(unversioned)] +pub struct InvalidDescriptionLengthError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + data_contract_id: Identifier, + description: String, +} + +impl InvalidDescriptionLengthError { + pub fn new(data_contract_id: Identifier, description: String) -> Self { + Self { + data_contract_id, + description, + } + } + + pub fn data_contract_id(&self) -> &Identifier { + &self.data_contract_id + } + + pub fn description(&self) -> &str { + &self.description + } +} + +impl From for ConsensusError { + fn from(err: InvalidDescriptionLengthError) -> Self { + Self::BasicError(BasicError::InvalidDescriptionLengthError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_length_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_length_error.rs new file mode 100644 index 00000000000..40988ef52ce --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_keyword_length_error.rs @@ -0,0 +1,45 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::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("Data contract {data_contract_id} has keyword with invalid length: '{keyword}'. Valid length is between 3 and 50 characters.")] +#[platform_serialize(unversioned)] +pub struct InvalidKeywordLengthError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + data_contract_id: Identifier, + keyword: String, +} + +impl InvalidKeywordLengthError { + pub fn new(data_contract_id: Identifier, keyword: String) -> Self { + Self { + data_contract_id, + keyword, + } + } + + pub fn data_contract_id(&self) -> &Identifier { + &self.data_contract_id + } + + pub fn keyword(&self) -> &str { + &self.keyword + } +} + +impl From for ConsensusError { + fn from(err: InvalidKeywordLengthError) -> Self { + Self::BasicError(BasicError::InvalidKeywordLengthError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs new file mode 100644 index 00000000000..9fd590bc3c4 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/keywords_over_limit.rs @@ -0,0 +1,47 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::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( + "Data contract {data_contract_id} has too many keywords: '{keywords_len}'. The maximum is 20." +)] +#[platform_serialize(unversioned)] +pub struct TooManyKeywordsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + data_contract_id: Identifier, + keywords_len: u8, +} + +impl TooManyKeywordsError { + pub fn new(data_contract_id: Identifier, keywords_len: u8) -> Self { + Self { + data_contract_id, + keywords_len, + } + } + + pub fn data_contract_id(&self) -> &Identifier { + &self.data_contract_id + } + + pub fn keywords_len(&self) -> &u8 { + &self.keywords_len + } +} + +impl From for ConsensusError { + fn from(err: TooManyKeywordsError) -> Self { + Self::BasicError(BasicError::TooManyKeywordsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs index cb59f46d74d..a3d985596b9 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs @@ -9,6 +9,7 @@ mod data_contract_unique_indices_changed_error; mod document_types_are_missing_error; mod duplicate_index_error; mod duplicate_index_name_error; +mod duplicate_keywords_error; mod group_exceeds_max_members_error; mod group_member_has_power_of_zero_error; mod group_member_has_power_over_limit_error; @@ -21,16 +22,19 @@ mod incompatible_re2_pattern_error; mod invalid_compound_index_error; mod invalid_data_contract_id_error; mod invalid_data_contract_version_error; +mod invalid_description_length_error; mod invalid_document_type_name_error; mod invalid_document_type_required_security_level; mod invalid_index_property_type_error; mod invalid_indexed_property_constraint_error; mod invalid_json_schema_ref_error; +mod invalid_keyword_length_error; mod invalid_token_base_supply_error; mod invalid_token_distribution_function_divide_by_zero_error; mod invalid_token_distribution_function_incoherence_error; mod invalid_token_distribution_function_invalid_parameter_error; mod invalid_token_distribution_function_invalid_parameter_tuple_error; +mod keywords_over_limit; mod non_contiguous_contract_group_positions_error; mod non_contiguous_contract_token_positions_error; mod system_property_index_already_present_error; @@ -69,6 +73,7 @@ pub use unique_indices_limit_reached_error::*; pub use contested_unique_index_on_mutable_document_type_error::*; pub use contested_unique_index_with_unique_index_error::*; +pub use duplicate_keywords_error::*; pub use group_exceeds_max_members_error::*; pub use group_member_has_power_of_zero_error::*; pub use group_member_has_power_over_limit_error::*; @@ -76,12 +81,15 @@ pub use group_non_unilateral_member_power_has_less_than_required_power_error::*; pub use group_position_does_not_exist_error::*; pub use group_total_power_has_less_than_required_power_error::*; pub use incompatible_document_type_schema_error::*; +pub use invalid_description_length_error::*; pub use invalid_document_type_name_error::*; +pub use invalid_keyword_length_error::*; pub use invalid_token_base_supply_error::*; pub use invalid_token_distribution_function_divide_by_zero_error::*; pub use invalid_token_distribution_function_incoherence_error::*; pub use invalid_token_distribution_function_invalid_parameter_error::*; pub use invalid_token_distribution_function_invalid_parameter_tuple_error::*; +pub use keywords_over_limit::*; pub use non_contiguous_contract_group_positions_error::*; pub use non_contiguous_contract_token_positions_error::*; pub use token_payment_by_burning_only_allowed_on_internal_token_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 7202f29e76a..430f82cb02b 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -106,6 +106,10 @@ impl ErrorWithCode for BasicError { Self::UnknownGasFeesPaidByError(_) => 10259, Self::UnknownDocumentActionTokenEffectError(_) => 10260, Self::TokenPaymentByBurningOnlyAllowedOnInternalTokenError(_) => 10261, + Self::TooManyKeywordsError(_) => 10262, + Self::DuplicateKeywordsError(_) => 10263, + Self::InvalidKeywordLengthError(_) => 10264, + Self::InvalidDescriptionLengthError(_) => 10265, // Group Errors: 10350-10399 Self::GroupPositionDoesNotExistError(_) => 10350, diff --git a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs index 496136cd309..3e794714efd 100644 --- a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs @@ -630,7 +630,7 @@ mod tests { ) .expect("expected to process state transition"); - assert_eq!(processing_result.aggregated_fees().processing_fee, 2488410); + assert_eq!(processing_result.aggregated_fees().processing_fee, 2489210); let check_result = platform .check_tx( @@ -1141,7 +1141,7 @@ mod tests { // The processing fees should be twice as much as a fee multiplier of 0, // since a fee multiplier of 100 means 100% more of 1 (gives 2) - assert_eq!(processing_result.aggregated_fees().processing_fee, 4976820); + assert_eq!(processing_result.aggregated_fees().processing_fee, 4978420); let check_result = platform .check_tx( @@ -1613,7 +1613,7 @@ mod tests { ) .expect("expected to process state transition"); - assert_eq!(processing_result.aggregated_fees().processing_fee, 2488410); + assert_eq!(processing_result.aggregated_fees().processing_fee, 2489210); platform .drive @@ -1699,7 +1699,7 @@ mod tests { assert_eq!( update_processing_result.aggregated_fees().processing_fee, - 2503110 + 2504030 ); let check_result = platform @@ -2069,7 +2069,7 @@ mod tests { ) .expect("expected to process state transition"); - assert_eq!(processing_result.aggregated_fees().processing_fee, 2488410); + assert_eq!(processing_result.aggregated_fees().processing_fee, 2489210); platform .drive diff --git a/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/fetch_reward_shares_list_for_masternode/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/fetch_reward_shares_list_for_masternode/v0/mod.rs index a7999958dc6..d64499dcd53 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/fetch_reward_shares_list_for_masternode/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/fee_pool_outwards_distribution/fetch_reward_shares_list_for_masternode/v0/mod.rs @@ -66,6 +66,7 @@ impl Platform { block_time_ms: None, }; + // todo: deal with cost of this operation let query_documents_outcome = self.drive.query_documents( drive_query, None, diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs index 89e3bcc85d9..0d1e3180b86 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/test/tokens.rs @@ -1,8 +1,12 @@ use crate::error::Error; use crate::platform_types::platform::Platform; use dpp::block::block_info::BlockInfo; +use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; +use dpp::data_contract::associated_token::token_configuration_convention::accessors::v0::TokenConfigurationConventionV0Getters; use dpp::data_contract::associated_token::token_configuration_convention::v0::TokenConfigurationConventionV0; +use dpp::data_contract::associated_token::token_configuration_localization::v0::TokenConfigurationLocalizationV0; +use dpp::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization; use dpp::data_contract::associated_token::token_distribution_rules::v0::TokenDistributionRulesV0; use dpp::data_contract::associated_token::token_keeps_history_rules::v0::TokenKeepsHistoryRulesV0; use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; @@ -219,7 +223,7 @@ impl Platform { ] .into(); - let token_configuration = TokenConfiguration::V0(TokenConfigurationV0 { + let mut token_configuration = TokenConfiguration::V0(TokenConfigurationV0 { conventions: TokenConfigurationConventionV0 { localizations: Default::default(), decimals: 8, @@ -264,8 +268,21 @@ impl Platform { emergency_action_rules: ChangeControlRulesV0::default().into(), main_control_group: None, main_control_group_can_be_modified: Default::default(), + description: Some("Some token description".to_string()), }); + token_configuration + .conventions_mut() + .localizations_mut() + .insert( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: false, + singular_form: "cat".to_string(), + plural_form: "cats".to_string(), + }), + ); + let tokens = [ (0, token_configuration.clone()), (1, token_configuration.clone()), @@ -288,6 +305,8 @@ impl Platform { updated_at_epoch: None, groups, tokens, + keywords: vec!["cat".into(), "white".into()], + description: Some("Some contract description".to_string()), }); self.drive.apply_contract( diff --git a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v1/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v1/mod.rs index d6a1cf70396..56083655c28 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v1/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/initialization/create_genesis_state/v1/mod.rs @@ -51,6 +51,10 @@ impl Platform { SystemDataContract::TokenHistory, system_data_contracts.load_token_history(), ), + ( + SystemDataContract::KeywordSearch, + system_data_contracts.load_keyword_search(), + ), ]); //todo add Wallet Utils (maybe) diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs index 663f94db0d2..03df8b08887 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs @@ -484,11 +484,22 @@ impl Platform { &platform_version.drive, )?; - let contract = + let token_history_contract = load_system_data_contract(SystemDataContract::TokenHistory, platform_version)?; self.drive.insert_contract( - &contract, + &token_history_contract, + *block_info, + true, + Some(transaction), + platform_version, + )?; + + let search_contract = + load_system_data_contract(SystemDataContract::KeywordSearch, platform_version)?; + + self.drive.insert_contract( + &search_contract, *block_info, true, Some(transaction), diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs index e94ab7595b5..cd7d7ba1a3b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dpns/v0/mod.rs @@ -243,6 +243,7 @@ pub(super) fn create_domain_data_trigger_v0( block_time_ms: None, }; + // todo: deal with cost of this operation let documents = context .platform .drive @@ -333,6 +334,7 @@ pub(super) fn create_domain_data_trigger_v0( block_time_ms: None, }; + // todo: deal with cost of this operation let preorder_documents = context .platform .drive diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/withdrawals/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/withdrawals/v0/mod.rs index f6e6e07bc1c..51be13ab46b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/withdrawals/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/withdrawals/v0/mod.rs @@ -76,6 +76,7 @@ pub(super) fn delete_withdrawal_data_trigger_v0( block_time_ms: None, }; + // todo: deal with cost of this operation let withdrawals = context .platform .drive diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/state/v0/fetch_documents.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/state/v0/fetch_documents.rs index c206b54bd57..88e145b32f4 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/state/v0/fetch_documents.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/state/v0/fetch_documents.rs @@ -163,7 +163,7 @@ pub(crate) fn fetch_documents_for_transitions_knowing_contract_and_document_type block_time_ms: None, }; - //todo: deal with cost of this operation + // todo: deal with cost of this operation let documents_outcome = drive.query_documents( drive_query, None, @@ -207,6 +207,7 @@ pub(crate) fn fetch_document_with_id( block_time_ms: None, }; + // todo: deal with cost of this operation let documents_outcome = drive.query_documents( drive_query, None, 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 5b413b56e07..0ba6788a588 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 @@ -7,6 +7,8 @@ mod creation_tests { use dapi_grpc::platform::v0::get_contested_resource_vote_state_request::GetContestedResourceVoteStateRequestV0; use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::{get_contested_resource_vote_state_response_v0, GetContestedResourceVoteStateResponseV0}; use assert_matches::assert_matches; + use dpp::platform_value::string_encoding::Encoding; + use dpp::prelude::Identifier; use rand::distributions::Standard; use dpp::consensus::basic::document::DocumentFieldMaxSizeExceededError; use dpp::consensus::ConsensusError; @@ -2468,6 +2470,145 @@ mod creation_tests { assert_eq!(consensus_error.to_string(), "Document Creation on 86LHvdC1Tqx5P97LQUSibGFqf2vnKFpB6VkqQ7oso86e:card is not allowed because of the document type's creation restriction mode Owner Only"); } + #[test] + fn test_document_creation_on_search_system_contract_fails_due_to_restriction() { + // Build test platform + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_initial_state_structure(); + + // Create an identity that will attempt to create the document + let (identity, signer, key) = setup_identity(&mut platform, 999, dash_to_credits!(0.1)); + + // Obtain the current platform state and version + let platform_state = platform.state.load(); + let platform_version = platform_state + .current_platform_version() + .expect("expected to get current platform version"); + + // Load the system Search contract + let search_contract = platform + .drive + .cache + .system_data_contracts + .load_keyword_search(); + + platform + .drive + .apply_contract( + &search_contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract successfully"); + + // Get the document type from the search contract. + let doc_type = search_contract + .document_type_for_name("contractKeywords") + .expect("expected to find 'contractKeywords' in Search contract"); + + // Verify that the document type has the restrictive creation mode + assert_eq!( + doc_type.creation_restriction_mode(), + CreationRestrictionMode::NoCreationAllowed, + "Expected creation restriction mode to be NoCreationAllowed (2)." + ); + + // Create a random document + let mut rng = StdRng::seed_from_u64(123456); + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = doc_type + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::DoNotFillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected to create a random document"); + + // Set fields in the document + document.set("keyword", "meme".into()); + document.set("contractId", Identifier::random().into()); + + // Create the transition + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document.clone(), + doc_type, + entropy.0, + &key, + 1, + 0, + None, + &signer, + platform_version, + None, + None, + 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"); + + // Start transaction and submit the transition + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + println!("Processing result: {:?}", processing_result); + + // Since the creationRestrictionMode is 2 (NoCreationAllowed), this should fail + assert_eq!( + processing_result.invalid_paid_count(), + 1, + "Expected exactly 1 invalid paid transition" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Check the returned consensus error + let result = processing_result.into_execution_results().remove(0); + let PaidConsensusError(consensus_error, _) = result else { + panic!("expected a paid consensus error"); + }; + + // Compare message to whatever your code sets for this error + assert_eq!( + consensus_error.to_string(), + format!( + "Document Creation on {}:{} is not allowed because of the document type's creation restriction mode No Creation Allowed", + search_contract.id().to_string(Encoding::Base58), + "contractKeywords" + ), + "Mismatch in error message" + ); + } + #[test] fn test_document_creation_paid_with_a_token_burn() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs index c9089a96aad..cadee083539 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs @@ -155,7 +155,7 @@ mod token_selling_tests { .drive .fetch_identity_balance(buyer.id().to_buffer(), None, platform_version) .expect("expected to fetch credit balance"); - assert_eq!(buyer_credit_balance, Some(699_868_054_220)); // 10.0 - 3.0 spent - fees =~ 7 dash left + assert_eq!(buyer_credit_balance, Some(699_868_051_500)); // 10.0 - 3.0 spent - fees =~ 7 dash left } #[test] diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs index 39876ba7724..121e9cf348c 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/basic_structure/v0/mod.rs @@ -1,8 +1,11 @@ use crate::error::Error; use dpp::consensus::basic::data_contract::{ - InvalidDataContractVersionError, InvalidTokenBaseSupplyError, - NonContiguousContractTokenPositionsError, + DuplicateKeywordsError, InvalidDataContractVersionError, InvalidDescriptionLengthError, + InvalidKeywordLengthError, InvalidTokenBaseSupplyError, + NonContiguousContractTokenPositionsError, TooManyKeywordsError, }; +use dpp::consensus::basic::BasicError; +use dpp::consensus::ConsensusError; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::{TokenContractPosition, INITIAL_DATA_CONTRACT_VERSION}; use dpp::prelude::DataContract; @@ -10,6 +13,7 @@ use dpp::state_transition::data_contract_create_transition::accessors::DataContr use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; use dpp::validation::SimpleConsensusValidationResult; use dpp::version::PlatformVersion; +use std::collections::HashSet; pub(in crate::execution::validation::state_transition::state_transitions::data_contract_create) trait DataContractCreateStateTransitionBasicStructureValidationV0 { @@ -78,6 +82,57 @@ impl DataContractCreateStateTransitionBasicStructureValidationV0 for DataContrac } } + // Validate there are no more than 20 keywords + if self.data_contract().keywords().len() > 20 { + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::BasicError(BasicError::TooManyKeywordsError( + TooManyKeywordsError::new( + self.data_contract().id(), + self.data_contract().keywords().len() as u8, + ), + )), + )); + } + + // Validate the keywords are all unique and between 3 and 50 characters + let mut seen_keywords = HashSet::new(); + for keyword in self.data_contract().keywords() { + // First check keyword length + if keyword.len() < 3 || keyword.len() > 50 { + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::BasicError(BasicError::InvalidKeywordLengthError( + InvalidKeywordLengthError::new( + self.data_contract().id(), + keyword.to_string(), + ), + )), + )); + } + + // Then check uniqueness + if !seen_keywords.insert(keyword) { + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::BasicError(BasicError::DuplicateKeywordsError( + DuplicateKeywordsError::new(self.data_contract().id(), keyword.to_string()), + )), + )); + } + } + + // Validate the description is between 3 and 100 characters + if let Some(description) = self.data_contract().description() { + if !(description.len() >= 3 && description.len() <= 100) { + return Ok(SimpleConsensusValidationResult::new_with_error( + ConsensusError::BasicError(BasicError::InvalidDescriptionLengthError( + InvalidDescriptionLengthError::new( + self.data_contract().id(), + description.to_string(), + ), + )), + )); + } + } + Ok(SimpleConsensusValidationResult::new()) } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs index 574d097abad..073da6f9db0 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs @@ -2470,4 +2470,809 @@ mod tests { assert_eq!(token_balance, None); } } + + mod keywords { + use super::*; + use dpp::{ + data_contract::conversion::value::v0::DataContractValueConversionMethodsV0, + data_contracts::SystemDataContract, document::DocumentV0Getters, + platform_value::string_encoding::Encoding, + system_data_contracts::load_system_data_contract, + }; + use drive::{ + drive::document::query::QueryDocumentsOutcomeV0Methods, query::DriveDocumentQuery, + }; + + #[test] + fn test_data_contract_creation_fails_with_more_than_twenty_keywords() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + // Create a test identity and keys + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // Load the base contract JSON and convert it to `DataContract` + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("expected to load contract"); + + // Convert the contract back to Value so we can mutate its fields + let mut contract_value = data_contract + .to_value(PlatformVersion::latest()) + .expect("to_value failed"); + + // Insert 21 keywords to exceed the max limit + let mut excessive_keywords: Vec = vec![]; + for i in 0..21 { + excessive_keywords.push(Value::Text(format!("keyword{}", i))); + } + contract_value["keywords"] = Value::Array(excessive_keywords); + + // Build a new DataContract from the mutated Value + let data_contract_with_excessive_keywords = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract from Value"); + + // Create the DataContractCreateTransition + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract_with_excessive_keywords, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create data contract transition"); + + // Serialize the transition + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected a serialized data contract transition"); + + let transaction = platform.drive.grove.start_transaction(); + + // Process the state transition + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // We expect a failure due to the JSON schema rejecting >20 keywords + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::TooManyKeywordsError(_)) + )] + ); + } + + #[test] + fn test_data_contract_creation_fails_with_duplicate_keywords() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + // Create a test identity and keys + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // Load the base contract JSON and convert it to `DataContract` + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("expected to load contract"); + + // Convert to Value to mutate fields + let mut contract_value = data_contract + .to_value(PlatformVersion::latest()) + .expect("to_value failed"); + + // Insert some duplicates + let duplicated_keywords = vec!["keyword1", "keyword2", "keyword2"]; + contract_value["keywords"] = Value::Array( + duplicated_keywords + .into_iter() + .map(|str| Value::Text(str.to_string())) + .collect(), + ); + + // Build a new DataContract from the mutated Value + let data_contract_with_duplicates = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract from Value"); + + // Create the DataContractCreateTransition + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract_with_duplicates, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create data contract transition"); + + // Serialize the transition + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected a serialized data contract transition"); + + let transaction = platform.drive.grove.start_transaction(); + + // Process the state transition + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Expect failure + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::DuplicateKeywordsError(_)) + )] + ); + } + + #[test] + fn test_data_contract_creation_fails_with_keyword_too_short() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + // Create identity + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // Load the base contract JSON and convert it to `DataContract` + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("expected to load contract"); + + // Convert to Value for mutation + let mut contract_value = data_contract + .to_value(PlatformVersion::latest()) + .expect("to_value failed"); + + // Insert a keyword with length < 3 + contract_value["keywords"] = Value::Array(vec![Value::Text("hi".to_string())]); + + // Build a new DataContract + let data_contract_invalid = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract"); + + // Create DataContractCreateTransition + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract_invalid, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create transition"); + + // Process + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected to serialize"); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Assert that we get the correct error + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::InvalidKeywordLengthError(_)) + )] + ); + } + + #[test] + fn test_data_contract_creation_fails_with_keyword_too_long() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("expected to load contract"); + + let mut contract_value = data_contract + .to_value(platform_version) + .expect("to_value failed"); + + // Create a 51-char keyword + let too_long_keyword = "x".repeat(51); + contract_value["keywords"] = Value::Array(vec![Value::Text(too_long_keyword)]); + + let data_contract_invalid = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract"); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract_invalid, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected to serialize"); + + let transaction = platform.drive.grove.start_transaction(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::InvalidKeywordLengthError(_)) + )] + ); + } + + #[test] + fn test_data_contract_creation_succeeds_with_valid_keywords() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + // Create a test identity and keys + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // Load the base contract JSON and convert to `DataContract` + let data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("expected to load contract"); + + // Convert to Value so we can adjust fields if needed + let mut contract_value = data_contract + .to_value(PlatformVersion::latest()) + .expect("to_value failed"); + + // Insert a valid set of keywords: all distinct, fewer than 20 + let valid_keywords = vec!["key1", "key2", "key3"]; + contract_value["keywords"] = Value::Array( + valid_keywords + .into_iter() + .map(|str| Value::Text(str.to_string())) + .collect(), + ); + + // Build a new DataContract from the mutated Value + let data_contract_valid = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract from Value"); + + // Create the DataContractCreateTransition + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract_valid, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create data contract transition"); + + // Serialize the transition + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected a serialized data contract transition"); + + let transaction = platform.drive.grove.start_transaction(); + + // Process the state transition + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // This time we expect success + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + // Commit the transaction since it's valid + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + // Get the data contract ID from the transition + // Is there a simpler way to get the ID? + let unique_identifiers = data_contract_create_transition.unique_identifiers(); + let unique_identifier = unique_identifiers + .first() + .expect("expected at least one unique identifier"); + let unique_identifier_str = unique_identifier.as_str(); + let data_contract_id_str = unique_identifier_str + .split('-') + .last() + .expect("expected to extract data contract id from unique identifier"); + let data_contract_id = Identifier::from_string(data_contract_id_str, Encoding::Base58) + .expect("failed to create Identifier from string"); + + // Fetch the contract from the platform + let contract = platform + .drive + .fetch_contract(data_contract_id.into(), None, None, None, platform_version) + .value + .expect("expected to get contract") + .expect("expected to find the contract"); + + // Check the keywords in the contract + let keywords = contract.contract.keywords(); + assert_eq!(keywords.len(), 3); + assert_eq!(keywords[0], "key1"); + assert_eq!(keywords[1], "key2"); + assert_eq!(keywords[2], "key3"); + + // Now check the Search Contract has the keyword documents + let search_contract = load_system_data_contract( + SystemDataContract::KeywordSearch, + PlatformVersion::latest(), + ) + .expect("expected to load search contract"); + let document_type = search_contract + .document_type_for_name("contractKeywords") + .expect("expected to get document type"); + + let drive_query = + DriveDocumentQuery::all_items_query(&search_contract, document_type, None); + + let documents_result = platform + .drive + .query_documents(drive_query, None, false, None, None) + .expect("expected to query documents"); + + let documents = documents_result.documents(); + + assert_eq!(documents.len(), 3); + + let mut valid_keywords_for_verification = vec!["key1", "key2", "key3"]; + for document in documents { + let keyword = document + .get("keyword") + .expect("expected to get keyword") + .as_str() + .expect("expected to get string"); + + assert!(valid_keywords_for_verification.contains(&keyword)); + assert_eq!( + document + .get("contractId") + .expect("expected to get data contract id") + .clone() + .into_identifier() + .expect("expected to get identifier") + .to_string(Encoding::Base58), + data_contract_id_str + ); + valid_keywords_for_verification.retain(|&x| x != keyword); + } + } + } + + mod descriptions { + use dpp::{ + data_contract::conversion::value::v0::DataContractValueConversionMethodsV0, + data_contracts::SystemDataContract, document::DocumentV0Getters, + platform_value::string_encoding::Encoding, + system_data_contracts::load_system_data_contract, + }; + use drive::{ + drive::document::query::QueryDocumentsOutcomeV0Methods, query::DriveDocumentQuery, + }; + + use super::*; + + /// Returns a `DataContract` value that already contains at least one keyword + fn base_contract_value_with_keyword(platform_version: &PlatformVersion) -> Value { + let data_contract = json_document_to_contract_with_ids( + // Re‑use the same fixture you already have; it doesn’t need + // to contain a description field – we mutate it below. + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("expected to load contract"); + + let mut contract_value = data_contract + .to_value(PlatformVersion::latest()) + .expect("to_value failed"); + + // Ensure the `keywords` array is not empty so that Drive will attempt + // to create the description documents. + contract_value["keywords"] = Value::Array(vec![Value::Text("key1".to_string())]); + + contract_value + } + + #[test] + fn test_data_contract_creation_fails_with_description_too_short() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // --- mutate the contract --- + let mut contract_value = base_contract_value_with_keyword(platform_version); + contract_value["description"] = Value::Text("hi".to_string()); // < 3 chars + + let data_contract_invalid = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract from Value"); + + let transition = DataContractCreateTransition::new_from_data_contract( + data_contract_invalid, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expected to create transition"); + + let serialized = transition + .serialize_to_bytes() + .expect("expected to serialize"); + + let tx = platform.drive.grove.start_transaction(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("expected processing"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::InvalidDescriptionLengthError(_)) + )] + ); + } + + #[test] + fn test_data_contract_creation_fails_with_description_too_long() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let mut contract_value = base_contract_value_with_keyword(platform_version); + // 101 chars – valid for the contract (max 10 000) but exceeds the + // 100‑char limit of the autogenerated **shortDescription** document. + let too_long = "x".repeat(101); + contract_value["description"] = Value::Text(too_long); + + let data_contract_invalid = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract"); + + let transition = DataContractCreateTransition::new_from_data_contract( + data_contract_invalid, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expected to create transition"); + + let serialized = transition + .serialize_to_bytes() + .expect("expected to serialize"); + + let tx = platform.drive.grove.start_transaction(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("expected processing"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::InvalidDescriptionLengthError(_)) + )] + ); + } + + #[test] + fn test_data_contract_creation_succeeds_with_valid_description() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let mut contract_value = base_contract_value_with_keyword(platform_version); + contract_value["description"] = + Value::Text("A perfectly valid description.".to_string()); + + let data_contract_valid = + DataContract::from_value(contract_value, true, platform_version) + .expect("failed to create DataContract"); + + let transition = DataContractCreateTransition::new_from_data_contract( + data_contract_valid, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expected to create transition"); + + let serialized = transition + .serialize_to_bytes() + .expect("expected to serialize"); + + let tx = platform.drive.grove.start_transaction(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("expected processing"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + // Commit so we can query the state afterwards + platform + .drive + .grove + .commit_transaction(tx) + .unwrap() + .expect("expected commit"); + + // ---- Verify description persisted in the contract ---- + let unique_identifiers = transition.unique_identifiers(); + let unique_identifier = unique_identifiers + .first() + .expect("expected at least one unique identifier"); + let data_contract_id_str = unique_identifier + .as_str() + .split('-') + .last() + .expect("split contract id"); + let data_contract_id = Identifier::from_string(data_contract_id_str, Encoding::Base58) + .expect("identifier"); + + let contract = platform + .drive + .fetch_contract(data_contract_id.into(), None, None, None, platform_version) + .value + .expect("expected contract") + .expect("contract exists"); + + let desc = contract + .contract + .description() + .expect("description should exist"); + + assert_eq!(desc, "A perfectly valid description."); + + // Now check the Search Contract has the short and full description documents + let search_contract = load_system_data_contract( + SystemDataContract::KeywordSearch, + PlatformVersion::latest(), + ) + .expect("expected to load search contract"); + let short_description_document_type = search_contract + .document_type_for_name("shortDescription") + .expect("expected to get document type"); + let full_description_document_type = search_contract + .document_type_for_name("fullDescription") + .expect("expected to get document type"); + + let drive_query_short_description = DriveDocumentQuery::all_items_query( + &search_contract, + short_description_document_type, + None, + ); + + let short_description_documents_result = platform + .drive + .query_documents(drive_query_short_description, None, false, None, None) + .expect("expected to query documents"); + + let short_description_documents = short_description_documents_result.documents(); + + assert_eq!(short_description_documents.len(), 1); + let short_description_document = short_description_documents + .first() + .expect("expected to get first document"); + let short_description = short_description_document + .get("description") + .expect("expected to get description") + .as_str() + .expect("expected to get string"); + assert_eq!(short_description, "A perfectly valid description."); + assert_eq!( + short_description_document + .get("contractId") + .expect("expected to get data contract id") + .clone() + .into_identifier() + .expect("expected to get identifier") + .to_string(Encoding::Base58), + data_contract_id_str + ); + + let drive_query_full_description = DriveDocumentQuery::all_items_query( + &search_contract, + full_description_document_type, + None, + ); + let full_description_documents_result = platform + .drive + .query_documents(drive_query_full_description, None, false, None, None) + .expect("expected to query documents"); + + let full_description_documents = full_description_documents_result.documents(); + + assert_eq!(full_description_documents.len(), 1); + let full_description_document = full_description_documents + .first() + .expect("expected to get first document"); + let full_description = full_description_document + .get("description") + .expect("expected to get description") + .as_str() + .expect("expected to get string"); + assert_eq!(full_description, "A perfectly valid description."); + assert_eq!( + full_description_document + .get("contractId") + .expect("expected to get data contract id") + .clone() + .into_identifier() + .expect("expected to get identifier") + .to_string(Encoding::Base58), + data_contract_id_str + ); + } + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs index 31f77fc9aec..114a1864901 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs @@ -1298,7 +1298,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![tx_bytes], + &[tx_bytes], &platform_state, &BlockInfo::default(), &transaction, @@ -1488,7 +1488,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![tx_bytes], + &[tx_bytes], &platform_state, &BlockInfo::default(), &transaction, @@ -1608,4 +1608,744 @@ mod tests { .expect("expected to commit transaction"); } } + + mod keyword_updates { + use super::*; + use dpp::{ + data_contract::conversion::value::v0::DataContractValueConversionMethodsV0, + data_contracts::SystemDataContract, + document::DocumentV0Getters, + platform_value::{string_encoding::Encoding, Value}, + state_transition::{ + data_contract_create_transition::{ + methods::DataContractCreateTransitionMethodsV0, DataContractCreateTransition, + }, + StateTransition, + }, + system_data_contracts::load_system_data_contract, + tests::json_document::json_document_to_contract_with_ids, + }; + use drive::{ + drive::document::query::QueryDocumentsOutcomeV0Methods, + query::{DriveDocumentQuery, WhereClause, WhereOperator}, + }; + + // ──────────────────────────────────────────────────────────────────────── + // helpers + // ──────────────────────────────────────────────────────────────────────── + + /// Creates a contract with the supplied keywords and commits it to Drive. + /// Returns `(contract_id, create_transition)`. + fn create_contract_with_keywords( + platform: &mut TempPlatform, + identity: &Identity, + signer: &SimpleSigner, + key: &IdentityPublicKey, + keywords: &[&str], + platform_version: &PlatformVersion, + ) -> (Identifier, StateTransition) { + let base = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("load base contract"); + + let mut val = base.to_value(platform_version).expect("to_value"); + + val["keywords"] = Value::Array( + keywords + .iter() + .map(|k| Value::Text(k.to_string())) + .collect(), + ); + + let contract = + DataContract::from_value(val, true, platform_version).expect("from_value"); + + let create = DataContractCreateTransition::new_from_data_contract( + contract, + 2, + &identity.clone().into_partial_identity_info(), + key.id(), + signer, + platform_version, + None, + ) + .expect("create transition"); + + let tx_bytes = create.serialize_to_bytes().expect("serialize"); + + let tx = platform.drive.grove.start_transaction(); + let platform_state = platform.state.load(); + + let res = platform + .platform + .process_raw_state_transitions( + &[tx_bytes], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("process create"); + + assert_matches!( + res.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(tx) + .unwrap() + .expect("commit create"); + + // pull id from unique_identifiers + let contract_id = Identifier::from_string( + create + .unique_identifiers() + .first() + .unwrap() + .as_str() + .split('-') + .last() + .unwrap(), + Encoding::Base58, + ) + .unwrap(); + + (contract_id, create) + } + + /// Convenience for building and applying an **update** transition that + /// only changes the `keywords` array. + fn apply_keyword_update( + platform: &mut TempPlatform, + contract_id: Identifier, + identity: &Identity, + signer: &SimpleSigner, + key: &IdentityPublicKey, + new_keywords: &[&str], + platform_version: &PlatformVersion, + ) -> Result<(), Vec> { + // fetch existing contract + let fetched = platform + .drive + .fetch_contract(contract_id.into(), None, None, None, platform_version) + .value + .unwrap() + .unwrap(); + + let mut val = fetched.contract.to_value(platform_version).unwrap(); + + val["keywords"] = Value::Array( + new_keywords + .iter() + .map(|k| Value::Text(k.to_string())) + .collect(), + ); + + let mut updated_contract = + DataContract::from_value(val, true, platform_version).unwrap(); + updated_contract.set_version(2); + + let update = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.clone().into_partial_identity_info(), + key.id(), + 2, + 0, + signer, + platform_version, + None, + ) + .expect("build update"); + + let bytes = update.serialize_to_bytes().unwrap(); + + let tx = platform.drive.grove.start_transaction(); + let platform_state = platform.state.load(); + + let outcome = platform + .platform + .process_raw_state_transitions( + &[bytes], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("process update"); + + if matches!( + outcome.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ) { + platform + .drive + .grove + .commit_transaction(tx) + .unwrap() + .expect("commit update"); + Ok(()) + } else { + Err(outcome.execution_results().to_vec()) + } + } + + /// Helper to read all keyword docs for a contract id. + fn keyword_docs_for_contract( + platform: &TempPlatform, + contract_id: Identifier, + platform_version: &PlatformVersion, + ) -> Vec { + let search_contract = + load_system_data_contract(SystemDataContract::KeywordSearch, platform_version) + .unwrap(); + let doc_type = search_contract + .document_type_for_name("contractKeywords") + .unwrap(); + + let mut query = DriveDocumentQuery { + contract: &search_contract, + document_type: doc_type, + internal_clauses: Default::default(), + offset: None, + limit: None, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time_ms: None, + }; + query.internal_clauses.equal_clauses.insert( + "contractId".to_string(), + WhereClause { + field: "contractId".to_string(), + operator: WhereOperator::Equal, + value: contract_id.into(), + }, + ); + + let res = platform + .drive + .query_documents(query, None, false, None, None) + .unwrap(); + + res.documents() + .iter() + .map(|d| d.get("keyword").unwrap().as_str().unwrap().to_owned()) + .collect() + } + + // ──────────────────────────────────────────────────────────────────────── + // negative cases – same validation as create + // ──────────────────────────────────────────────────────────────────────── + + macro_rules! invalid_update_test { + ($name:ident, $keywords:expr, $error:pat_param) => { + #[test] + fn $name() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // create initial contract with one keyword so update is allowed + let (cid, _) = create_contract_with_keywords( + &mut platform, + &identity, + &signer, + &key, + &["orig"], + &platform_version, + ); + + // try invalid update + let err = apply_keyword_update( + &mut platform, + cid, + &identity, + &signer, + &key, + &$keywords, + &platform_version, + ) + .unwrap_err(); + + assert_matches!( + err.as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::BasicError($error), + _ + )] + ); + + // original keyword docs must still be there + let docs = keyword_docs_for_contract(&platform, cid, &platform_version); + assert_eq!(docs, vec!["orig"]); + } + }; + } + + invalid_update_test!( + update_fails_too_many_keywords, + [ + "kw0", "kw1", "kw2", "kw3", "kw4", "kw5", "kw6", "kw7", "kw8", "kw9", "kw10", + "kw11", "kw12", "kw13", "kw14", "kw15", "kw16", "kw17", "kw18", "kw19", "kw20", + ], + BasicError::TooManyKeywordsError(_) + ); + + invalid_update_test!( + update_fails_duplicate_keywords, + ["dup", "dup"], + BasicError::DuplicateKeywordsError(_) + ); + + invalid_update_test!( + update_fails_keyword_too_short, + ["hi"], + BasicError::InvalidKeywordLengthError(_) + ); + + invalid_update_test!( + update_fails_keyword_too_long, + [&"x".repeat(51)], + BasicError::InvalidKeywordLengthError(_) + ); + + // ──────────────────────────────────────────────────────────────────────── + // positive case – old docs removed, new docs inserted + // ──────────────────────────────────────────────────────────────────────── + + #[test] + fn update_keywords_replaces_search_docs() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // initial contract with two keywords + let (cid, _) = create_contract_with_keywords( + &mut platform, + &identity, + &signer, + &key, + &["old1", "old2"], + &platform_version, + ); + + // verify initial docs + let initial_docs = keyword_docs_for_contract(&platform, cid, &platform_version); + assert_eq!(initial_docs.len(), 2); + + // apply update to ["newA", "newB", "newC"] + apply_keyword_update( + &mut platform, + cid, + &identity, + &signer, + &key, + &["newA", "newB", "newC"], + &platform_version, + ) + .expect("update should succeed"); + + // fetch contract – keywords updated? + let fetched = platform + .drive + .fetch_contract(cid.into(), None, None, None, &platform_version) + .value + .unwrap() + .unwrap(); + assert_eq!( + *fetched.contract.keywords(), + ["newA", "newB", "newC"] + .iter() + .map(|&s| s.to_string()) + .collect::>() + ); + + // search‑contract docs updated? + let docs_after = keyword_docs_for_contract(&platform, cid, &platform_version); + assert_eq!(docs_after.len(), 3); + assert!(docs_after.contains(&"newA".to_string())); + assert!(docs_after.contains(&"newB".to_string())); + assert!(docs_after.contains(&"newC".to_string())); + // old docs gone + assert!(!docs_after.contains(&"old1".to_string())); + assert!(!docs_after.contains(&"old2".to_string())); + } + } + + mod description_updates { + use super::*; + use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; + use dpp::{ + data_contract::conversion::value::v0::DataContractValueConversionMethodsV0, + data_contracts::SystemDataContract, + document::DocumentV0Getters, + platform_value::{string_encoding::Encoding, Value}, + state_transition::{ + data_contract_create_transition::{ + methods::DataContractCreateTransitionMethodsV0, DataContractCreateTransition, + }, + StateTransition, + }, + system_data_contracts::load_system_data_contract, + tests::json_document::json_document_to_contract_with_ids, + }; + use drive::{ + drive::document::query::QueryDocumentsOutcomeV0Methods, + query::{DriveDocumentQuery, WhereClause, WhereOperator}, + }; + + // ──────────────────────────────────────────────────────────────────────── + // helpers + // ──────────────────────────────────────────────────────────────────────── + + /// Creates a contract with the supplied description and commits it to Drive. + /// Returns `(contract_id, create_transition)`. + fn create_contract_with_description( + platform: &mut TempPlatform, + identity: &Identity, + signer: &SimpleSigner, + key: &IdentityPublicKey, + description: &str, + platform_version: &PlatformVersion, + ) -> (Identifier, StateTransition) { + let base = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + None, + None, + false, + platform_version, + ) + .expect("load base contract"); + + let mut val = base.to_value(platform_version).expect("to_value"); + + val["description"] = Value::Text(description.to_string()); + + let contract = + DataContract::from_value(val, true, platform_version).expect("from_value"); + + let create = DataContractCreateTransition::new_from_data_contract( + contract, + 2, + &identity.clone().into_partial_identity_info(), + key.id(), + signer, + platform_version, + None, + ) + .expect("create transition"); + + let tx_bytes = create.serialize_to_bytes().expect("serialize"); + + let tx = platform.drive.grove.start_transaction(); + let platform_state = platform.state.load(); + + let res = platform + .platform + .process_raw_state_transitions( + &[tx_bytes], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("process create"); + + assert_matches!( + res.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(tx) + .unwrap() + .expect("commit create"); + + // pull id from unique_identifiers + let contract_id = Identifier::from_string( + create + .unique_identifiers() + .first() + .unwrap() + .as_str() + .split('-') + .last() + .unwrap(), + Encoding::Base58, + ) + .unwrap(); + + (contract_id, create) + } + + /// Convenience for building and applying an **update** transition that + /// only changes the `description` string. + fn apply_description_update( + platform: &mut TempPlatform, + contract_id: Identifier, + identity: &Identity, + signer: &SimpleSigner, + key: &IdentityPublicKey, + new_description: &str, + platform_version: &PlatformVersion, + ) -> Result<(), Vec> { + // fetch existing contract + let fetched = platform + .drive + .fetch_contract(contract_id.into(), None, None, None, platform_version) + .value + .unwrap() + .unwrap(); + + let mut val = fetched.contract.to_value(platform_version).unwrap(); + + val["description"] = Value::Text(new_description.to_string()); + + let mut updated_contract = + DataContract::from_value(val, true, platform_version).unwrap(); + updated_contract.set_version(2); + + let update = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.clone().into_partial_identity_info(), + key.id(), + 2, + 0, + signer, + platform_version, + None, + ) + .expect("build update"); + + let bytes = update.serialize_to_bytes().unwrap(); + + let tx = platform.drive.grove.start_transaction(); + let platform_state = platform.state.load(); + + let outcome = platform + .platform + .process_raw_state_transitions( + &[bytes], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("process update"); + + if matches!( + outcome.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ) { + platform + .drive + .grove + .commit_transaction(tx) + .unwrap() + .expect("commit update"); + Ok(()) + } else { + Err(outcome.execution_results().to_vec()) + } + } + + /// Helper to read all description docs for a contract id. + fn description_docs_for_contract( + platform: &TempPlatform, + contract_id: Identifier, + platform_version: &PlatformVersion, + ) -> String { + let search_contract = + load_system_data_contract(SystemDataContract::KeywordSearch, platform_version) + .unwrap(); + let doc_type = search_contract + .document_type_for_name("shortDescription") + .unwrap(); + + let mut query = DriveDocumentQuery { + contract: &search_contract, + document_type: doc_type, + internal_clauses: Default::default(), + offset: None, + limit: None, + order_by: Default::default(), + start_at: None, + start_at_included: false, + block_time_ms: None, + }; + query.internal_clauses.equal_clauses.insert( + "contractId".to_string(), + WhereClause { + field: "contractId".to_string(), + operator: WhereOperator::Equal, + value: contract_id.into(), + }, + ); + + let mut res = platform + .drive + .query_documents(query, None, false, None, None) + .expect("expected query to succeed") + .documents_owned(); + + if res.is_empty() { + panic!("expected a document description"); + } + + let first_document = res.remove(0); + + first_document + .properties() + .get_string("description") + .expect("expected description to exist") + } + + // ──────────────────────────────────────────────────────────────────────── + // negative cases – same validation as create + // ──────────────────────────────────────────────────────────────────────── + + macro_rules! invalid_update_test { + ($name:ident, $description:expr, $error:pat_param) => { + #[test] + fn $name() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // create initial contract with description so update is allowed + let (cid, _) = create_contract_with_description( + &mut platform, + &identity, + &signer, + &key, + &"orig", + &platform_version, + ); + + // try invalid update + let err = apply_description_update( + &mut platform, + cid, + &identity, + &signer, + &key, + &$description, + &platform_version, + ) + .unwrap_err(); + + assert_matches!( + err.as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::BasicError($error), + _ + )] + ); + + // original description docs must still be there + let docs = description_docs_for_contract(&platform, cid, &platform_version); + assert_eq!(docs, "orig".to_string()); + } + }; + } + + invalid_update_test!( + update_fails_description_too_short, + "hi", + BasicError::InvalidDescriptionLengthError(_) + ); + + invalid_update_test!( + update_fails_description_too_long, + &"x".repeat(101), + BasicError::InvalidDescriptionLengthError(_) + ); + + // ──────────────────────────────────────────────────────────────────────── + // positive case – old docs removed, new docs inserted + // ──────────────────────────────────────────────────────────────────────── + + #[test] + fn update_description_replaces_search_docs() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + // initial contract with description + let (cid, _) = create_contract_with_description( + &mut platform, + &identity, + &signer, + &key, + "old1", + platform_version, + ); + + // verify initial docs + let initial_docs = description_docs_for_contract(&platform, cid, &platform_version); + assert_eq!(initial_docs, "old1".to_string()); + + // apply update to "newA" + apply_description_update( + &mut platform, + cid, + &identity, + &signer, + &key, + "newA", + platform_version, + ) + .expect("update should succeed"); + + // fetch contract – description updated? + let fetched = platform + .drive + .fetch_contract(cid.into(), None, None, None, platform_version) + .value + .unwrap() + .unwrap(); + assert_eq!( + fetched.contract.description(), + Some("newA".to_string()).as_ref() + ); + + // search‑contract docs updated? + let docs_after = description_docs_for_contract(&platform, cid, platform_version); + assert_eq!(docs_after, "newA".to_string()); + // old docs gone + assert!(!docs_after.contains(&"old1".to_string())); + } + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs index 8b1b7f36db1..030f6e22b04 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs @@ -53,11 +53,19 @@ impl ValidationMode { #[cfg(test)] pub(in crate::execution) mod tests { use crate::rpc::core::MockCoreRPCLike; - use crate::test::helpers::setup::TempPlatform; + use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; use dpp::block::block_info::BlockInfo; + use dpp::data_contracts::SystemDataContract; use dpp::fee::Credits; + use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dpp::identity::{Identity, IdentityPublicKey, IdentityV0, KeyID, KeyType, Purpose, SecurityLevel, TimestampMillis}; use dpp::prelude::{Identifier, IdentityNonce}; + use dpp::state_transition::data_contract_create_transition::methods::DataContractCreateTransitionMethodsV0; + use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; + use dpp::system_data_contracts::load_system_data_contract; + use dpp::tests::json_document::json_document_to_contract_with_ids; + use drive::drive::document::query::QueryDocumentsOutcomeV0Methods; + use drive::query::DriveDocumentQuery; use platform_version::version::PlatformVersion; use rand::prelude::StdRng; use rand::{Rng, SeedableRng}; @@ -305,6 +313,7 @@ pub(in crate::execution) mod tests { (identity, signer, master_key) } + #[allow(clippy::too_many_arguments)] pub(crate) fn setup_add_key_to_identity( platform: &mut TempPlatform, identity: &mut Identity, @@ -963,6 +972,7 @@ pub(in crate::execution) mod tests { (identity_1_info.0, identity_2_info.0, dpns_contract) } + #[allow(clippy::too_many_arguments)] fn create_dpns_name_contest_on_identities( platform: &mut TempPlatform, identity_1: &(Identity, SimpleSigner, IdentityPublicKey), @@ -1181,7 +1191,7 @@ pub(in crate::execution) mod 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(), ], @@ -1226,7 +1236,7 @@ pub(in crate::execution) mod 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(), ], @@ -1271,6 +1281,7 @@ pub(in crate::execution) mod tests { ) } + #[allow(clippy::too_many_arguments)] fn create_dpns_name_contest_on_identities_for_contract_records( platform: &mut TempPlatform, identity_1: &(Identity, SimpleSigner, IdentityPublicKey), @@ -1497,7 +1508,7 @@ pub(in crate::execution) mod 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(), ], @@ -1529,7 +1540,7 @@ pub(in crate::execution) mod 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(), ], @@ -1680,7 +1691,7 @@ pub(in crate::execution) mod 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_with_time( platform_state @@ -1709,7 +1720,7 @@ pub(in crate::execution) mod 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 @@ -1953,6 +1964,7 @@ pub(in crate::execution) mod tests { assert_eq!(second_contender.vote_tally(), Some(0)); } + #[allow(clippy::too_many_arguments)] pub(in crate::execution) fn perform_vote( platform: &mut TempPlatform, platform_state: &Guard>, @@ -2003,7 +2015,7 @@ pub(in crate::execution) mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![masternode_vote_serialized_transition.clone()], + &[masternode_vote_serialized_transition.clone()], platform_state, &BlockInfo::default(), &transaction, @@ -2032,6 +2044,7 @@ pub(in crate::execution) mod tests { } } + #[allow(clippy::too_many_arguments)] pub(in crate::execution) fn perform_votes( platform: &mut TempPlatform, dpns_contract: &DataContract, @@ -2097,6 +2110,7 @@ pub(in crate::execution) mod tests { masternodes_by_vote_choice } + #[allow(clippy::too_many_arguments)] pub(in crate::execution) fn get_vote_states( platform: &TempPlatform, platform_state: &PlatformState, @@ -2203,6 +2217,7 @@ pub(in crate::execution) mod tests { ) } + #[allow(clippy::too_many_arguments)] pub(in crate::execution) fn get_proved_vote_states( platform: &TempPlatform, platform_state: &PlatformState, @@ -2485,8 +2500,8 @@ pub(in crate::execution) mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![serialized_state_transition], - &platform_state, + &[serialized_state_transition], + platform_state, &BlockInfo::default(), &transaction, platform_version, @@ -2504,4 +2519,457 @@ pub(in crate::execution) mod tests { processing_result } + + mod keyword_search_contract { + use dpp::consensus::basic::BasicError; + use dpp::consensus::ConsensusError; + use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult::PaidConsensusError; + use super::*; + // + // ────────────────────────────────────────────────────────────────────────── + // Keyword‑Search contract – creation is forbidden + // ────────────────────────────────────────────────────────────────────────── + // + + /// Return `(document, entropy)` so we can reuse the exact entropy when + /// we build the transition. **No rng.clone()** (that caused the ID mismatch). + fn build_random_doc_of_type( + rng: &mut StdRng, + doc_type_name: &str, + identity_id: Identifier, + contract: &DataContract, + platform_version: &PlatformVersion, + ) -> (Document, Bytes32) { + let doc_type = contract + .document_type_for_name(doc_type_name) + .expect("doc type exists"); + + let entropy = Bytes32::random_with_rng(rng); + + let doc = doc_type + .random_document_with_identifier_and_entropy( + rng, + identity_id, + entropy, + DocumentFieldFillType::FillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("random doc"); + + (doc, entropy) + } + + #[test] + fn should_err_when_creating_contract_keywords_document() { + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = setup_identity(&mut platform, 42, dash_to_credits!(1.0)); + + let contract = load_system_data_contract( + SystemDataContract::KeywordSearch, + PlatformVersion::latest(), + ) + .expect("expected to load search contract"); + + let mut rng = StdRng::seed_from_u64(1); + let (doc, entropy) = build_random_doc_of_type( + &mut rng, + "contractKeywords", + identity.id(), + &contract, + platform_version, + ); + + let transition = BatchTransition::new_document_creation_transition_from_document( + doc, + contract.document_type_for_name("contractKeywords").unwrap(), + entropy.0, // same entropy → no ID mismatch + &key, + 1, + 0, + None, + &signer, + platform_version, + None, + None, + None, + ) + .expect("batch transition"); + + let serialized = transition.serialize_to_bytes().unwrap(); + + let platform_state = platform.state.load(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &platform.drive.grove.start_transaction(), + platform_version, + false, + None, + ) + .expect("processing failed"); + + let execution_result = processing_result.into_execution_results().remove(0); + assert_matches!( + execution_result, + StateTransitionExecutionResult::PaidConsensusError(err, _) + if err.to_string().contains("not allowed because of the document type's creation restriction mode") + ); + } + + #[test] + fn should_err_when_creating_short_description_document() { + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = setup_identity(&mut platform, 43, dash_to_credits!(1.0)); + + let contract = load_system_data_contract( + SystemDataContract::KeywordSearch, + PlatformVersion::latest(), + ) + .expect("expected to load search contract"); + + let mut rng = StdRng::seed_from_u64(2); + let (doc, entropy) = build_random_doc_of_type( + &mut rng, + "shortDescription", + identity.id(), + &contract, + platform_version, + ); + + let transition = BatchTransition::new_document_creation_transition_from_document( + doc, + contract.document_type_for_name("shortDescription").unwrap(), + entropy.0, + &key, + 1, + 0, + None, + &signer, + platform_version, + None, + None, + None, + ) + .expect("batch transition"); + + let serialized = transition.serialize_to_bytes().unwrap(); + + let platform_state = platform.state.load(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &platform.drive.grove.start_transaction(), + platform_version, + false, + None, + ) + .expect("processing failed"); + + let execution_result = processing_result.into_execution_results().remove(0); + assert_matches!( + execution_result, + StateTransitionExecutionResult::PaidConsensusError(err, _) + if err.to_string().contains("not allowed because of the document type's creation restriction mode") + ); + } + + #[test] + fn should_err_when_creating_full_description_document() { + let platform_version = PlatformVersion::latest(); + + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (identity, signer, key) = setup_identity(&mut platform, 44, dash_to_credits!(1.0)); + + let contract = load_system_data_contract( + SystemDataContract::KeywordSearch, + PlatformVersion::latest(), + ) + .expect("expected to load search contract"); + + let mut rng = StdRng::seed_from_u64(3); + let (doc, entropy) = build_random_doc_of_type( + &mut rng, + "fullDescription", + identity.id(), + &contract, + platform_version, + ); + + let transition = BatchTransition::new_document_creation_transition_from_document( + doc, + contract.document_type_for_name("fullDescription").unwrap(), + entropy.0, + &key, + 1, + 0, + None, + &signer, + platform_version, + None, + None, + None, + ) + .expect("batch transition"); + + let serialized = transition.serialize_to_bytes().unwrap(); + + let platform_state = platform.state.load(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &platform.drive.grove.start_transaction(), + platform_version, + false, + None, + ) + .expect("processing failed"); + + let execution_result = processing_result.into_execution_results().remove(0); + assert_matches!( + execution_result, + StateTransitionExecutionResult::PaidConsensusError(err, _) + if err.to_string().contains("not allowed because of the document type's creation restriction mode") + ); + } + + // + // ────────────────────────────────────────────────────────────────────────── + // Keyword‑Search contract – owner can update / delete + // ────────────────────────────────────────────────────────────────────────── + // + + fn create_contract_with_keywords_and_description( + platform: &mut TempPlatform, + ) -> (Identity, SimpleSigner, IdentityPublicKey) { + let platform_version = PlatformVersion::latest(); + + // Owner identity + let (owner_identity, signer, key) = + setup_identity(platform, 777, dash_to_credits!(1.0)); + + // Load the keyword‑test fixture + let mut contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/keyword_test/keyword_base_contract.json", + Some(owner_identity.id()), + None, + false, + platform_version, + ) + .expect("load contract"); + + // Inject description + keywords + contract.set_description(Some("A short description".to_string())); + contract.set_keywords(vec!["graph".into(), "indexing".into()]); + + // Create transition inside GroveDB tx + let create_transition = DataContractCreateTransition::new_from_data_contract( + contract, + 1, + &owner_identity.clone().into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("build transition"); + + let serialized = create_transition.serialize_to_bytes().unwrap(); + let platform_state = platform.state.load(); + let tx = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .expect("process"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(tx) + .unwrap() + .expect("commit"); + + (owner_identity, signer, key) + } + + #[test] + fn owner_can_update_short_description_document() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (_owner, signer, key) = + create_contract_with_keywords_and_description(&mut platform); + + // 🔎 fetch shortDescription doc through query + let search_contract = + load_system_data_contract(SystemDataContract::KeywordSearch, platform_version) + .expect("load search contract"); + + let doc_type = search_contract + .document_type_for_name("shortDescription") + .unwrap(); + + let query = DriveDocumentQuery::all_items_query(&search_contract, doc_type, None); + let existing_docs = platform + .drive + .query_documents( + query, + None, + false, + None, + Some(platform_version.protocol_version), + ) + .expect("query failed"); + + let mut doc = existing_docs.documents().first().expect("doc").clone(); + doc.set_revision(Some(doc.revision().unwrap_or_default() + 1)); + doc.set("description", "updated description".into()); + + let transition = BatchTransition::new_document_replacement_transition_from_document( + doc, + doc_type, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + None, + None, + ) + .expect("replace"); + + let serialized = transition.serialize_to_bytes().unwrap(); + let platform_state = platform.state.load(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &platform.drive.grove.start_transaction(), + platform_version, + false, + None, + ) + .expect("process"); + + assert_matches!( + processing_result.into_execution_results().remove(0), + SuccessfulExecution(..) + ); + } + + #[test] + fn owner_can_not_delete_keyword_document() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let (_owner, signer, key) = + create_contract_with_keywords_and_description(&mut platform); + + let search_contract = + load_system_data_contract(SystemDataContract::KeywordSearch, platform_version) + .expect("load search contract"); + let doc_type = search_contract + .document_type_for_name("contractKeywords") + .unwrap(); + + let query = DriveDocumentQuery::all_items_query(&search_contract, doc_type, None); + let existing_docs = platform + .drive + .query_documents( + query, + None, + false, + None, + Some(platform_version.protocol_version), + ) + .expect("query failed"); + + let doc = existing_docs.documents().first().unwrap().clone(); + + let transition = BatchTransition::new_document_deletion_transition_from_document( + doc, + doc_type, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + None, + None, + ) + .expect("delete"); + + let serialized = transition.serialize_to_bytes().unwrap(); + let platform_state = platform.state.load(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &platform.drive.grove.start_transaction(), + platform_version, + false, + None, + ) + .expect("process"); + assert_matches!( + processing_result.execution_results().as_slice(), + [PaidConsensusError( + ConsensusError::BasicError( + BasicError::InvalidDocumentTransitionActionError { .. } + ), + _ + )] + ); + } + } } diff --git a/packages/rs-drive-abci/tests/supporting_files/contract/keyword_test/keyword_base_contract.json b/packages/rs-drive-abci/tests/supporting_files/contract/keyword_test/keyword_base_contract.json new file mode 100644 index 00000000000..11d440ef449 --- /dev/null +++ b/packages/rs-drive-abci/tests/supporting_files/contract/keyword_test/keyword_base_contract.json @@ -0,0 +1,40 @@ +{ + "$format_version": "1", + "id": "4Bqs6itzfoDXzmgQibYZQABbqYsXmawVf7SKe3mKDQVd", + "ownerId": "2b994p95akyNFKtkDnDvBRUotDbkH54MHwGbhQLr5gcU", + "version": 1, + "keywords": [], + "documentSchemas": { + "preorder": { + "type": "object", + "indices": [ + { + "name": "saltedHash", + "properties": [ + { + "saltedDomainHash": "asc" + } + ], + "unique": true + } + ], + "properties": { + "saltedDomainHash": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 0, + "description": "Double sha-256 of the concatenation of a 32 byte random salt and a normalized domain name" + } + }, + "required": [ + "saltedDomainHash" + ], + "additionalProperties": false, + "$comment": "Preorder documents are immutable: modification and deletion are restricted" + } + }, + "groups": {}, + "tokens": {} +} \ No newline at end of file diff --git a/packages/rs-drive/src/cache/system_contracts.rs b/packages/rs-drive/src/cache/system_contracts.rs index 7de44da36bd..870d757c47c 100644 --- a/packages/rs-drive/src/cache/system_contracts.rs +++ b/packages/rs-drive/src/cache/system_contracts.rs @@ -17,6 +17,8 @@ pub struct SystemDataContracts { masternode_reward_shares: ArcSwap, /// Token history contract token_history: ArcSwap, + /// Search contract + keyword_search: ArcSwap, } impl SystemDataContracts { @@ -45,6 +47,10 @@ impl SystemDataContracts { SystemDataContract::TokenHistory, platform_version, )?), + keyword_search: ArcSwap::from_pointee(load_system_data_contract( + SystemDataContract::KeywordSearch, + platform_version, + )?), }) } @@ -72,4 +78,9 @@ impl SystemDataContracts { pub fn load_masternode_reward_shares(&self) -> Guard> { self.masternode_reward_shares.load() } + + /// Returns the search contract + pub fn load_keyword_search(&self) -> Guard> { + self.keyword_search.load() + } } diff --git a/packages/rs-drive/src/drive/contract/insert/add_description/mod.rs b/packages/rs-drive/src/drive/contract/insert/add_description/mod.rs new file mode 100644 index 00000000000..e6c4b2999a2 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/insert/add_description/mod.rs @@ -0,0 +1,134 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; +use dpp::fee::fee_result::FeeResult; +use dpp::version::PlatformVersion; + +use dpp::prelude::Identifier; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +mod v0; + +impl Drive { + /// Creates the documents in the Keyword Search contract for the contract description and + /// returns the fee result + pub fn add_new_contract_description( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + short_only: bool, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .contract + .insert + .add_description + { + 0 => self.add_new_contract_description_v0( + contract_id, + owner_id, + description, + short_only, + block_info, + apply, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_new_contract_description".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and applies the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract description + pub fn add_new_contract_description_add_to_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + short_only: bool, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .contract + .insert + .add_description + { + 0 => self.add_new_contract_description_add_to_operations_v0( + contract_id, + owner_id, + description, + short_only, + block_info, + apply, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_new_contract_description_add_to_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and returns the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract description + pub fn add_new_contract_description_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + short_only: bool, + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .contract + .insert + .add_description + { + 0 => self.add_new_contract_description_operations_v0( + contract_id, + owner_id, + description, + short_only, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_new_contract_description_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs b/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs new file mode 100644 index 00000000000..c0b6b955fca --- /dev/null +++ b/packages/rs-drive/src/drive/contract/insert/add_description/v0/mod.rs @@ -0,0 +1,220 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::object_size_info::DocumentInfo::DocumentOwnedInfo; +use crate::util::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; +use dpp::block::block_info::BlockInfo; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contracts::keyword_search_contract; +use dpp::document::{Document, DocumentV0}; +use dpp::fee::fee_result::FeeResult; +use dpp::identifier::Identifier; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use platform_version::version::PlatformVersion; +use std::collections::{BTreeMap, HashMap}; + +impl Drive { + /// Creates the documents in the Keyword Search contract for the contract description and + /// returns the fee result + pub(super) fn add_new_contract_description_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + short_only: bool, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let mut drive_operations: Vec = vec![]; + self.add_new_contract_description_add_to_operations_v0( + contract_id, + owner_id, + description, + short_only, + block_info, + apply, + transaction, + &mut drive_operations, + platform_version, + )?; + let fees = Drive::calculate_fee( + None, + Some(drive_operations), + &block_info.epoch, + self.config.epochs_per_era, + platform_version, + None, + )?; + Ok(fees) + } + + /// Creates and applies the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract description + pub(super) fn add_new_contract_description_add_to_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + short_only: bool, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + let mut estimated_costs_only_with_layer_info = if apply { + None::> + } else { + Some(HashMap::new()) + }; + + let batch_operations = self.add_new_contract_description_operations_v0( + contract_id, + owner_id, + description, + short_only, + block_info, + &mut estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + + self.apply_batch_low_level_drive_operations( + estimated_costs_only_with_layer_info, + transaction, + batch_operations, + drive_operations, + &platform_version.drive, + ) + } + + /// Creates and returns the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract description + pub(super) fn add_new_contract_description_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + short_only: bool, + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut operations: Vec = vec![]; + + let contract = self.cache.system_data_contracts.load_keyword_search(); + let short_description_document_type = + contract.document_type_for_name("shortDescription")?; + + let short_description_document = self.build_contract_description_document_owned_v0( + contract_id, + owner_id, + description, + false, + block_info, + )?; + + let short_description_ops = self.add_document_for_contract_operations( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentOwnedInfo((short_description_document, None)), + owner_id: Some(owner_id.to_buffer()), + }, + contract: &contract, + document_type: short_description_document_type, + }, + true, + block_info, + &mut None, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + + operations.extend(short_description_ops); + + if !short_only { + let full_description_document_type = + contract.document_type_for_name("fullDescription")?; + let full_description_document = self.build_contract_description_document_owned_v0( + contract_id, + owner_id, + description, + true, + block_info, + )?; + let full_description_ops = self.add_document_for_contract_operations( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentOwnedInfo((full_description_document, None)), + owner_id: Some(owner_id.to_buffer()), + }, + contract: &contract, + document_type: full_description_document_type, + }, + true, + block_info, + &mut None, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + operations.extend(full_description_ops); + } + + Ok(operations) + } + + /// Creates and returns a contract `description` document for the Keyword Search contract + fn build_contract_description_document_owned_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + full_description: bool, + block_info: &BlockInfo, + ) -> Result { + let document_type_name = if full_description { + "fullDescription".to_string() + } else { + "shortDescription".to_string() + }; + + let document_id = Document::generate_document_id_v0( + &keyword_search_contract::ID_BYTES.into(), + &owner_id, + &document_type_name, + contract_id.as_slice(), + ); + + let properties = BTreeMap::from([ + ("contractId".to_string(), contract_id.into()), + ("description".to_string(), description.into()), + ]); + + let document: Document = DocumentV0 { + id: document_id, + owner_id, + properties, + revision: Some(1), + created_at: Some(block_info.time_ms), + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + } + .into(); + + Ok(document) + } +} diff --git a/packages/rs-drive/src/drive/contract/insert/add_new_keywords/mod.rs b/packages/rs-drive/src/drive/contract/insert/add_new_keywords/mod.rs new file mode 100644 index 00000000000..0f89e9043c0 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/insert/add_new_keywords/mod.rs @@ -0,0 +1,110 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; +use dpp::fee::fee_result::FeeResult; +use dpp::version::PlatformVersion; + +use dpp::prelude::Identifier; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +mod v0; + +impl Drive { + /// Creates the documents in the Keyword Search contract for the contract keywords and + /// returns the fee result + pub fn add_new_contract_keywords( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version.drive.methods.contract.insert.add_keywords { + 0 => self.add_new_contract_keywords_v0( + contract_id, + owner_id, + keywords, + block_info, + apply, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_new_keywords".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and applies the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract keywords + pub fn add_new_contract_keywords_add_to_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version.drive.methods.contract.insert.add_keywords { + 0 => self.add_new_contract_keywords_add_to_operations_v0( + contract_id, + owner_id, + keywords, + block_info, + apply, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_new_keywords_add_to_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and returns the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract keywords + pub fn add_new_contract_keywords_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version.drive.methods.contract.insert.add_keywords { + 0 => self.add_new_contract_keywords_operations_v0( + contract_id, + owner_id, + keywords, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "add_new_keywords_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs b/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs new file mode 100644 index 00000000000..b3f49fb0e2d --- /dev/null +++ b/packages/rs-drive/src/drive/contract/insert/add_new_keywords/v0/mod.rs @@ -0,0 +1,181 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::object_size_info::DocumentInfo::DocumentOwnedInfo; +use crate::util::object_size_info::{DocumentAndContractInfo, OwnedDocumentInfo}; +use dpp::block::block_info::BlockInfo; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contracts::keyword_search_contract; +use dpp::document::{Document, DocumentV0}; +use dpp::fee::fee_result::FeeResult; +use dpp::identifier::Identifier; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use platform_version::version::PlatformVersion; +use std::collections::{BTreeMap, HashMap}; + +impl Drive { + /// Creates the documents in the Keyword Search contract for the contract keywords and + /// returns the fee result + pub(super) fn add_new_contract_keywords_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let mut drive_operations: Vec = vec![]; + self.add_new_contract_keywords_add_to_operations_v0( + contract_id, + owner_id, + keywords, + block_info, + apply, + transaction, + &mut drive_operations, + platform_version, + )?; + let fees = Drive::calculate_fee( + None, + Some(drive_operations), + &block_info.epoch, + self.config.epochs_per_era, + platform_version, + None, + )?; + Ok(fees) + } + + /// Creates and applies the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract keywords + pub(super) fn add_new_contract_keywords_add_to_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + let mut estimated_costs_only_with_layer_info = if apply { + None::> + } else { + Some(HashMap::new()) + }; + + let batch_operations = self.add_new_contract_keywords_operations( + contract_id, + owner_id, + keywords, + block_info, + &mut estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + + self.apply_batch_low_level_drive_operations( + estimated_costs_only_with_layer_info, + transaction, + batch_operations, + drive_operations, + &platform_version.drive, + ) + } + + /// Creates and returns the LowLeveLDriveOperations needed to create + /// the documents in the Keyword Search contract for the contract keywords + pub(crate) fn add_new_contract_keywords_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut drive_operations: Vec = vec![]; + + let contract = self.cache.system_data_contracts.load_keyword_search(); + let document_type = contract.document_type_for_name("contractKeywords")?; + + for keyword in keywords.iter() { + let document = self.build_contract_keyword_document_owned_v0( + contract_id, + owner_id, + keyword, // since keywords are unique in the contract, we can use it as entropy + )?; + + let ops = self.add_document_for_contract_operations( + DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentOwnedInfo((document, None)), + owner_id: Some(owner_id.to_buffer()), + }, + contract: &contract, + document_type, + }, + true, + block_info, + &mut Some(&mut drive_operations), + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + + drive_operations.extend(ops); + } + + Ok(drive_operations) + } + + /// Creates and returns a `contractKeyword` document for the Keyword Search contract + pub(super) fn build_contract_keyword_document_owned_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keyword: &String, + ) -> Result { + let mut entropy = Vec::with_capacity(contract_id.len() + keyword.len()); + entropy.extend_from_slice(contract_id.as_slice()); + entropy.extend_from_slice(keyword.as_bytes()); + + let document_id = Document::generate_document_id_v0( + &keyword_search_contract::ID_BYTES.into(), + &owner_id, + "contractKeywords", + entropy.as_slice(), + ); + + let properties = BTreeMap::from([ + ("keyword".to_string(), keyword.into()), + ("contractId".to_string(), contract_id.into()), + ]); + + let document: Document = DocumentV0 { + id: document_id, + owner_id, + properties, + revision: None, + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + } + .into(); + + Ok(document) + } +} diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs index 00aefcf9473..9776452ba6b 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs @@ -307,6 +307,31 @@ impl Drive { )?); } + if !contract.keywords().is_empty() { + batch_operations.extend(self.add_new_contract_keywords_operations( + contract.id(), + contract.owner_id(), + contract.keywords(), + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + + if let Some(description) = contract.description() { + batch_operations.extend(self.add_new_contract_description_operations( + contract.id(), + contract.owner_id(), + description, + false, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + Ok(batch_operations) } } diff --git a/packages/rs-drive/src/drive/contract/insert/mod.rs b/packages/rs-drive/src/drive/contract/insert/mod.rs index a222b57bc4d..5822e243119 100644 --- a/packages/rs-drive/src/drive/contract/insert/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/mod.rs @@ -1,2 +1,6 @@ mod add_contract_to_storage; +#[allow(clippy::too_many_arguments)] +mod add_description; +#[allow(clippy::too_many_arguments)] +mod add_new_keywords; mod insert_contract; diff --git a/packages/rs-drive/src/drive/contract/update/mod.rs b/packages/rs-drive/src/drive/contract/update/mod.rs index c24eb531f5c..d50a7e7d905 100644 --- a/packages/rs-drive/src/drive/contract/update/mod.rs +++ b/packages/rs-drive/src/drive/contract/update/mod.rs @@ -1 +1,4 @@ mod update_contract; +mod update_description; + +mod update_keywords; diff --git a/packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs b/packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs index d350369026f..b3965fe69b6 100644 --- a/packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs +++ b/packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs @@ -225,7 +225,7 @@ impl Drive { for (token_pos, configuration) in contract.tokens() { let token_id = contract.token_id(*token_pos).ok_or(Error::DataContract( DataContractError::CorruptedDataContract(format!( - "data contract has a token at position {}, but can not find it", + "data contract has a token at position {}, but it can not be found", token_pos )), ))?; @@ -251,6 +251,30 @@ impl Drive { )?); } + if !contract.keywords().is_empty() { + batch_operations.extend(self.update_contract_keywords_operations( + contract.id(), + contract.owner_id(), + contract.keywords(), + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + + if let Some(description) = contract.description() { + batch_operations.extend(self.update_contract_description_operations( + contract.id(), + contract.owner_id(), + description, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + Ok(batch_operations) } } diff --git a/packages/rs-drive/src/drive/contract/update/update_description/mod.rs b/packages/rs-drive/src/drive/contract/update/update_description/mod.rs new file mode 100644 index 00000000000..b0f8d2af290 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/update/update_description/mod.rs @@ -0,0 +1,131 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; +use dpp::fee::fee_result::FeeResult; +use dpp::version::PlatformVersion; + +use dpp::prelude::Identifier; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +mod v0; + +impl Drive { + /// Updates the documents in the Keyword Search contract for the contract + /// update description and returns the fee result + #[allow(clippy::too_many_arguments)] + pub fn update_contract_description( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .contract + .update + .update_description + { + 0 => self.update_contract_description_v0( + contract_id, + owner_id, + description, + block_info, + apply, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_contract_description".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and applies the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract description + #[allow(clippy::too_many_arguments)] + pub fn update_contract_description_add_to_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .contract + .update + .update_description + { + 0 => self.update_contract_description_add_to_operations_v0( + contract_id, + owner_id, + description, + block_info, + apply, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_contract_description_add_to_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and returns the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract description + #[allow(clippy::too_many_arguments)] + pub fn update_contract_description_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .contract + .update + .update_description + { + 0 => self.update_contract_description_operations_v0( + contract_id, + owner_id, + description, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_contract_description_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/contract/update/update_description/v0/mod.rs b/packages/rs-drive/src/drive/contract/update/update_description/v0/mod.rs new file mode 100644 index 00000000000..b672b82c5d6 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/update/update_description/v0/mod.rs @@ -0,0 +1,181 @@ +use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::query::{DriveDocumentQuery, WhereClause, WhereOperator}; +use crate::util::object_size_info::{DocumentAndContractInfo, DocumentInfo, OwnedDocumentInfo}; +use dpp::block::block_info::BlockInfo; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::document::DocumentV0Setters; +use dpp::fee::fee_result::FeeResult; +use dpp::identifier::Identifier; +use dpp::platform_value::Value; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use platform_version::version::PlatformVersion; +use std::collections::HashMap; + +impl Drive { + /// Updates the documents in the Keyword Search contract for the contract + /// update description and returns the fee result + #[allow(clippy::too_many_arguments)] + pub(super) fn update_contract_description_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let mut drive_operations: Vec = vec![]; + self.update_contract_description_add_to_operations_v0( + contract_id, + owner_id, + description, + block_info, + apply, + transaction, + &mut drive_operations, + platform_version, + )?; + let fees = Drive::calculate_fee( + None, + Some(drive_operations), + &block_info.epoch, + self.config.epochs_per_era, + platform_version, + None, + )?; + Ok(fees) + } + + /// Creates and applies the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract description + #[allow(clippy::too_many_arguments)] + pub(super) fn update_contract_description_add_to_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + let mut estimated_costs_only_with_layer_info = if apply { + None::> + } else { + Some(HashMap::new()) + }; + + let batch_operations = self.update_contract_description_operations( + contract_id, + owner_id, + description, + block_info, + &mut estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + + self.apply_batch_low_level_drive_operations( + estimated_costs_only_with_layer_info, + transaction, + batch_operations, + drive_operations, + &platform_version.drive, + ) + } + + /// Creates and returns the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract description + #[allow(clippy::too_many_arguments)] + pub(super) fn update_contract_description_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + description: &String, + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut operations: Vec = vec![]; + + let contract = self.cache.system_data_contracts.load_keyword_search(); + let document_type = contract.document_type_for_name("shortDescription")?; + + let mut query = DriveDocumentQuery::all_items_query(&contract, document_type, None); + query.internal_clauses.equal_clauses.insert( + "contractId".to_string(), + WhereClause { + field: "contractId".to_string(), + operator: WhereOperator::Equal, + value: Value::Identifier(contract_id.to_buffer()), + }, + ); + + // todo: deal with cost of this operation + let query_outcome = self.query_documents( + query, + Some(&block_info.epoch), + false, + transaction, + Some(platform_version.protocol_version), + )?; + + let mut existing_documents = query_outcome.documents_owned(); + + if existing_documents.len() > 1 { + return Err(Error::Drive(DriveError::CorruptedContractIndexes( + "There should be only one `shortDescription` document per contract in the Keyword Search contract".to_string(), + ))); + } + + if existing_documents.is_empty() { + // Add the new one + operations.extend(self.add_new_contract_description_operations( + contract_id, + owner_id, + description, + true, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } else { + // Replace the existing one + let mut new_document = existing_documents.remove(0); + new_document.set("description", Value::Text(description.clone())); + new_document.set_updated_at(Some(block_info.time_ms)); + new_document.bump_revision(); + + let info = DocumentAndContractInfo { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentInfo::DocumentOwnedInfo((new_document, None)), + owner_id: Some(owner_id.to_buffer()), + }, + contract: &contract.clone(), + document_type, + }; + + operations.extend(self.update_document_for_contract_operations( + info, + block_info, + &mut None, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + + Ok(operations) + } +} diff --git a/packages/rs-drive/src/drive/contract/update/update_keywords/mod.rs b/packages/rs-drive/src/drive/contract/update/update_keywords/mod.rs new file mode 100644 index 00000000000..73d0b6b07c1 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/update/update_keywords/mod.rs @@ -0,0 +1,131 @@ +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::block::block_info::BlockInfo; +use dpp::fee::fee_result::FeeResult; +use dpp::version::PlatformVersion; + +use dpp::prelude::Identifier; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +mod v0; + +impl Drive { + /// Updates the documents in the Keyword Search contract for the contract + /// update keywords and returns the fee result + #[allow(clippy::too_many_arguments)] + pub fn update_contract_keywords( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .contract + .update + .update_keywords + { + 0 => self.update_contract_keywords_v0( + contract_id, + owner_id, + keywords, + block_info, + apply, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_keywords".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and applies the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract keywords + #[allow(clippy::too_many_arguments)] + pub fn update_contract_keywords_add_to_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .contract + .update + .update_keywords + { + 0 => self.update_contract_keywords_add_to_operations_v0( + contract_id, + owner_id, + keywords, + block_info, + apply, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_keywords_add_to_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Creates and returns the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract keywords + #[allow(clippy::too_many_arguments)] + pub fn update_contract_keywords_operations( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .contract + .update + .update_keywords + { + 0 => self.update_contract_keywords_operations_v0( + contract_id, + owner_id, + keywords, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_keywords_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/contract/update/update_keywords/v0/mod.rs b/packages/rs-drive/src/drive/contract/update/update_keywords/v0/mod.rs new file mode 100644 index 00000000000..00c2bf48a99 --- /dev/null +++ b/packages/rs-drive/src/drive/contract/update/update_keywords/v0/mod.rs @@ -0,0 +1,176 @@ +use crate::drive::document::query::QueryDocumentsOutcomeV0Methods; +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::query::{DriveDocumentQuery, WhereClause, WhereOperator}; +use dpp::block::block_info::BlockInfo; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::document::DocumentV0Getters; +use dpp::fee::fee_result::FeeResult; +use dpp::identifier::Identifier; +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::platform_value::Value; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use platform_version::version::PlatformVersion; +use std::collections::{BTreeMap, HashMap}; + +impl Drive { + /// Updates the documents in the Keyword Search contract for the contract + /// update keywords and returns the fee result + #[allow(clippy::too_many_arguments)] + pub(super) fn update_contract_keywords_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let mut drive_operations: Vec = vec![]; + self.update_contract_keywords_add_to_operations_v0( + contract_id, + owner_id, + keywords, + block_info, + apply, + transaction, + &mut drive_operations, + platform_version, + )?; + let fees = Drive::calculate_fee( + None, + Some(drive_operations), + &block_info.epoch, + self.config.epochs_per_era, + platform_version, + None, + )?; + Ok(fees) + } + + /// Creates and applies the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract keywords + #[allow(clippy::too_many_arguments)] + pub(super) fn update_contract_keywords_add_to_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + apply: bool, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + let mut estimated_costs_only_with_layer_info = if apply { + None::> + } else { + Some(HashMap::new()) + }; + + let batch_operations = self.update_contract_keywords_operations( + contract_id, + owner_id, + keywords, + block_info, + &mut estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?; + + self.apply_batch_low_level_drive_operations( + estimated_costs_only_with_layer_info, + transaction, + batch_operations, + drive_operations, + &platform_version.drive, + ) + } + + /// Creates and returns the LowLeveLDriveOperations needed to update + /// the documents in the Keyword Search contract for the contract keywords + #[allow(clippy::too_many_arguments)] + pub(super) fn update_contract_keywords_operations_v0( + &self, + contract_id: Identifier, + owner_id: Identifier, + keywords: &[String], + block_info: &BlockInfo, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut operations: Vec = vec![]; + + // First get the existing keywords so we know which ones we need to delete and which new ones we need to add + let contract = self.cache.system_data_contracts.load_keyword_search(); + let document_type = contract.document_type_for_name("contractKeywords")?; + + let mut query = DriveDocumentQuery::all_items_query(&contract, document_type, None); + query.internal_clauses.equal_clauses.insert( + "contractId".to_string(), + WhereClause { + field: "contractId".to_string(), + operator: WhereOperator::Equal, + value: Value::Identifier(contract_id.to_buffer()), + }, + ); + + // todo: deal with cost of this operation + let query_outcome = self.query_documents( + query, + Some(&block_info.epoch), + false, + transaction, + Some(platform_version.protocol_version), + )?; + + let mut existing: BTreeMap = BTreeMap::new(); + for doc in query_outcome.documents_owned() { + let kw = doc.properties().get_string("keyword")?; + existing.insert(kw, doc.id()); + } + + // If an existing keyword is not in the new keyword set, we delete it + for (kw, doc_id) in &existing { + if !keywords.contains(kw) { + operations.extend(self.force_delete_document_for_contract_operations( + *doc_id, + &contract, + document_type, + None, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + } + + // Finally, add the new ones + let mut keywords_to_add: Vec = Vec::new(); + for kw in keywords { + if !existing.contains_key(kw) { + keywords_to_add.push(kw.clone()); + } + } + + if !keywords_to_add.is_empty() { + operations.extend(self.add_new_contract_keywords_operations( + contract_id, + owner_id, + &keywords_to_add, + block_info, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + )?); + } + + Ok(operations) + } +} diff --git a/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/mod.rs b/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/mod.rs index 919e1631d0d..74fefe304d9 100644 --- a/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/mod.rs +++ b/packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/mod.rs @@ -65,4 +65,56 @@ impl Drive { })), } } + + /// Prepares the operations for deleting a document and will delete even if the contract does + /// not allow for deletion, this is reserved for system data contracts. + /// + /// # Parameters + /// * `document_id`: The ID of the document to delete. + /// * `contract`: The contract that contains the document. + /// * `document_type`: The type of the document. + /// * `previous_batch_operations`: Previous batch operations to include. + /// * `estimated_costs_only_with_layer_info`: Estimated costs with layer info. + /// * `transaction`: The transaction argument. + /// * `drive_version`: The drive version to select the correct function version to run. + /// + /// # Returns + /// * `Ok(Vec)` if the operation was successful. + /// * `Err(DriveError::UnknownVersionMismatch)` if the drive version does not match known versions. + #[allow(clippy::too_many_arguments)] + pub(crate) fn force_delete_document_for_contract_operations( + &self, + document_id: Identifier, + contract: &DataContract, + document_type: DocumentTypeRef, + previous_batch_operations: Option<&mut Vec>, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .document + .delete + .delete_document_for_contract_operations + { + 0 => self.force_delete_document_for_contract_operations_v0( + document_id, + contract, + document_type, + previous_batch_operations, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "force_delete_document_for_contract_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } } 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 97291e4e8e9..55420aceeb7 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 @@ -49,14 +49,40 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { - let mut batch_operations: Vec = vec![]; - if !document_type.documents_can_be_deleted() { return Err(Error::Drive(DriveError::UpdatingReadOnlyImmutableDocument( "this document type is not mutable and can not be deleted", ))); } + self.force_delete_document_for_contract_operations_v0( + document_id, + contract, + document_type, + previous_batch_operations, + estimated_costs_only_with_layer_info, + transaction, + platform_version, + ) + } + + /// Prepares the operations for deleting a document. + #[inline(always)] + #[allow(clippy::too_many_arguments)] + pub(super) fn force_delete_document_for_contract_operations_v0( + &self, + document_id: Identifier, + contract: &DataContract, + document_type: DocumentTypeRef, + previous_batch_operations: Option<&mut Vec>, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut batch_operations: Vec = vec![]; + if document_type.documents_keep_history() { return Err(Error::Drive( DriveError::InvalidDeletionOfDocumentThatKeepsHistory( diff --git a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs index dab9fd17703..acc70692f62 100644 --- a/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v0/mod.rs @@ -187,6 +187,7 @@ impl Drive { block_time_ms: None, }; + // todo: deal with cost of this operation let query_result = self.query_documents( query, None, diff --git a/packages/rs-drive/src/drive/document/update/internal/update_document_for_contract_operations/v0/mod.rs b/packages/rs-drive/src/drive/document/update/internal/update_document_for_contract_operations/v0/mod.rs index a3c0aec24c5..b903ca123c1 100644 --- a/packages/rs-drive/src/drive/document/update/internal/update_document_for_contract_operations/v0/mod.rs +++ b/packages/rs-drive/src/drive/document/update/internal/update_document_for_contract_operations/v0/mod.rs @@ -57,7 +57,7 @@ impl Drive { // if it requires revision then there are reasons for us to be able to update in drive { return Err(Error::Drive(DriveError::UpdatingReadOnlyImmutableDocument( - "documents for this contract are not mutable", + "this document type is not mutable", ))); } diff --git a/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs index 19fd3a86d8e..b909d49d093 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_action_infos/v0/mod.rs @@ -153,6 +153,8 @@ mod tests { TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), ), ]), + keywords: Vec::new(), + description: None, }); drive @@ -380,6 +382,8 @@ mod tests { TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), ), ]), + keywords: Vec::new(), + description: None, }); drive diff --git a/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs index 81b39a82c36..c8602ac7dd4 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_action_signers/v0/mod.rs @@ -148,6 +148,8 @@ mod tests { TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), ), ]), + keywords: Vec::new(), + description: None, }); drive @@ -316,6 +318,8 @@ mod tests { }), )]), tokens: BTreeMap::new(), + keywords: Vec::new(), + description: None, }); drive @@ -409,6 +413,8 @@ mod tests { }), )]), tokens: BTreeMap::new(), + keywords: Vec::new(), + description: None, }); drive diff --git a/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs index 64dc4bc471f..bab7fbe6036 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_group_info/v0/mod.rs @@ -106,6 +106,8 @@ mod tests { }), )]), tokens: Default::default(), + keywords: Vec::new(), + description: None, }); let contract_id = contract.id(); @@ -175,6 +177,8 @@ mod tests { updated_at_epoch: None, groups: Default::default(), tokens: Default::default(), + keywords: Vec::new(), + description: None, }); let contract_id = contract.id(); diff --git a/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs b/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs index 4ab9965950e..68b20c7b812 100644 --- a/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/prove/prove_group_infos/v0/mod.rs @@ -134,6 +134,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let contract_id = contract.id(); @@ -278,6 +280,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let contract_id = contract.id(); diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs index 4e611e39fb3..13d20203cf1 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/fetch_oldest_withdrawal_documents_by_status/v0/mod.rs @@ -82,6 +82,7 @@ impl Drive { block_time_ms: None, }; + // todo: deal with cost of this operation let outcome = self.query_documents( drive_query, None, diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs index ea92ef5a67b..e99fa9311e1 100644 --- a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs @@ -83,6 +83,7 @@ impl Drive { block_time_ms: None, }; + // todo: deal with cost of this operation let outcome = self.query_documents( drive_query, None, diff --git a/packages/rs-drive/src/drive/tokens/balance/prove_identities_token_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/balance/prove_identities_token_balances/v0/mod.rs index a01a5c55d73..b2a7667a713 100644 --- a/packages/rs-drive/src/drive/tokens/balance/prove_identities_token_balances/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/balance/prove_identities_token_balances/v0/mod.rs @@ -97,6 +97,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); drive @@ -197,6 +199,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); drive @@ -288,6 +292,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); drive diff --git a/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs index 9ee2f26b2a0..354251fe180 100644 --- a/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs @@ -102,6 +102,8 @@ mod tests { TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), ), ]), + keywords: Vec::new(), + description: None, }); let token_id_1 = contract.token_id(0).expect("expected token at position 0"); @@ -262,6 +264,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); @@ -349,6 +353,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); diff --git a/packages/rs-drive/src/drive/tokens/info/prove_identities_token_infos/v0/mod.rs b/packages/rs-drive/src/drive/tokens/info/prove_identities_token_infos/v0/mod.rs index c63c406d378..edc445f0df3 100644 --- a/packages/rs-drive/src/drive/tokens/info/prove_identities_token_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/info/prove_identities_token_infos/v0/mod.rs @@ -97,6 +97,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); drive @@ -197,6 +199,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); drive @@ -286,6 +290,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); drive diff --git a/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs b/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs index 4ed3b4cbf53..18e1a3e920f 100644 --- a/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs @@ -105,6 +105,8 @@ mod tests { TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), ), ]), + keywords: Vec::new(), + description: None, }); let token_id_1 = contract.token_id(0).expect("expected token at position 0"); @@ -252,6 +254,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); diff --git a/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs b/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs index a66e2c2d15c..c7f6d5afdc8 100644 --- a/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs @@ -94,6 +94,8 @@ mod tests { TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), ), ]), + keywords: Vec::new(), + description: None, }); let token_id_1 = contract.token_id(0).expect("expected token at position 0"); diff --git a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs index 5cba606590d..5d1858139bd 100644 --- a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs @@ -88,6 +88,8 @@ mod tests { 0, TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); @@ -213,6 +215,8 @@ mod tests { TokenConfigurationV0::default_most_restrictive().with_base_supply(0), ), )]), + keywords: Vec::new(), + description: None, }); let token_id = contract.token_id(0).expect("expected token at position 0"); diff --git a/packages/rs-drive/tests/deterministic_root_hash.rs b/packages/rs-drive/tests/deterministic_root_hash.rs index 08d4f5d605c..760ed03f3c9 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -302,7 +302,7 @@ mod tests { // We expect a different app hash because data contract is not serialized the same way let expected_app_hash = match platform_version.protocol_version { 0..=8 => "1b80f4a9f00597b3f1ddca904b3cee67576868adcdd802c0a3f91e14209bb402", - _ => "107bcfc100e64a51e60b3e7ca7e7823b11968c2db599c212a8d09869e3ce1dd0", + _ => "76e3af331ff60aea3006671d048ce16871369da4ec562bdc9966837bb49ee92c", }; assert_eq!( diff --git a/packages/rs-drive/tests/query_tests.rs b/packages/rs-drive/tests/query_tests.rs index bb18047a5e6..ac6c5d96250 100644 --- a/packages/rs-drive/tests/query_tests.rs +++ b/packages/rs-drive/tests/query_tests.rs @@ -2509,8 +2509,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 59, 253, 119, 177, 148, 100, 153, 121, 228, 238, 250, 185, 103, 53, 113, 8, 30, 192, - 75, 150, 153, 2, 24, 109, 93, 91, 97, 75, 106, 35, 29, 252, + 53, 9, 163, 92, 116, 134, 17, 186, 21, 68, 156, 162, 47, 181, 214, 162, 253, 4, 246, 8, + 41, 187, 151, 152, 216, 164, 206, 110, 230, 176, 124, 225, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -3823,8 +3823,8 @@ mod tests { assert_eq!( root_hash.as_slice(), vec![ - 5, 92, 86, 251, 178, 238, 8, 246, 80, 139, 148, 81, 135, 108, 57, 197, 114, 102, - 219, 71, 50, 0, 47, 252, 106, 157, 118, 30, 128, 199, 55, 126, + 144, 154, 147, 246, 236, 57, 41, 67, 21, 26, 212, 158, 68, 159, 206, 26, 158, 50, + 252, 62, 143, 176, 149, 50, 19, 226, 239, 65, 112, 243, 225, 64 ], ); } @@ -3974,8 +3974,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 59, 253, 119, 177, 148, 100, 153, 121, 228, 238, 250, 185, 103, 53, 113, 8, 30, 192, - 75, 150, 153, 2, 24, 109, 93, 91, 97, 75, 106, 35, 29, 252, + 53, 9, 163, 92, 116, 134, 17, 186, 21, 68, 156, 162, 47, 181, 214, 162, 253, 4, 246, 8, + 41, 187, 151, 152, 216, 164, 206, 110, 230, 176, 124, 225, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -4426,8 +4426,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 40, 208, 218, 141, 51, 7, 57, 5, 17, 42, 78, 70, 239, 65, 98, 146, 20, 42, 68, 135, - 241, 126, 28, 204, 213, 7, 128, 14, 31, 163, 15, 2, + 75, 38, 164, 96, 117, 46, 13, 23, 183, 41, 83, 163, 112, 55, 172, 37, 186, 36, 223, 39, + 106, 201, 46, 222, 167, 79, 236, 122, 12, 210, 29, 123, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -4549,8 +4549,8 @@ mod tests { // Make sure the state is deterministic let expected_app_hash = vec![ - 59, 253, 119, 177, 148, 100, 153, 121, 228, 238, 250, 185, 103, 53, 113, 8, 30, 192, - 75, 150, 153, 2, 24, 109, 93, 91, 97, 75, 106, 35, 29, 252, + 53, 9, 163, 92, 116, 134, 17, 186, 21, 68, 156, 162, 47, 181, 214, 162, 253, 4, 246, 8, + 41, 187, 151, 152, 216, 164, 206, 110, 230, 176, 124, 225, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -5347,8 +5347,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 89, 134, 179, 83, 10, 119, 219, 251, 215, 151, 38, 111, 63, 245, 250, 229, 201, 136, - 190, 129, 75, 226, 88, 216, 93, 69, 152, 224, 156, 93, 170, 125, + 235, 23, 161, 209, 153, 68, 160, 57, 151, 170, 19, 99, 64, 48, 5, 114, 233, 154, 77, + 65, 104, 102, 128, 181, 159, 124, 54, 108, 229, 88, 185, 134, ]; assert_eq!(root_hash.as_slice(), expected_app_hash,); @@ -5443,8 +5443,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 89, 134, 179, 83, 10, 119, 219, 251, 215, 151, 38, 111, 63, 245, 250, 229, 201, 136, - 190, 129, 75, 226, 88, 216, 93, 69, 152, 224, 156, 93, 170, 125, + 235, 23, 161, 209, 153, 68, 160, 57, 151, 170, 19, 99, 64, 48, 5, 114, 233, 154, 77, + 65, 104, 102, 128, 181, 159, 124, 54, 108, 229, 88, 185, 134, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -5539,8 +5539,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 89, 134, 179, 83, 10, 119, 219, 251, 215, 151, 38, 111, 63, 245, 250, 229, 201, 136, - 190, 129, 75, 226, 88, 216, 93, 69, 152, 224, 156, 93, 170, 125, + 235, 23, 161, 209, 153, 68, 160, 57, 151, 170, 19, 99, 64, 48, 5, 114, 233, 154, 77, + 65, 104, 102, 128, 181, 159, 124, 54, 108, 229, 88, 185, 134, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -5635,8 +5635,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 89, 134, 179, 83, 10, 119, 219, 251, 215, 151, 38, 111, 63, 245, 250, 229, 201, 136, - 190, 129, 75, 226, 88, 216, 93, 69, 152, 224, 156, 93, 170, 125, + 235, 23, 161, 209, 153, 68, 160, 57, 151, 170, 19, 99, 64, 48, 5, 114, 233, 154, 77, + 65, 104, 102, 128, 181, 159, 124, 54, 108, 229, 88, 185, 134, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -5831,8 +5831,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 236, 213, 202, 143, 111, 54, 250, 174, 57, 239, 156, 18, 122, 223, 88, 20, 13, 180, 89, - 144, 31, 20, 138, 189, 2, 148, 160, 95, 231, 108, 216, 163, + 233, 90, 110, 8, 43, 137, 139, 242, 8, 152, 175, 246, 177, 73, 49, 137, 61, 142, 2, 49, + 158, 134, 13, 222, 60, 223, 139, 41, 66, 131, 135, 38, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6038,8 +6038,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 236, 213, 202, 143, 111, 54, 250, 174, 57, 239, 156, 18, 122, 223, 88, 20, 13, 180, 89, - 144, 31, 20, 138, 189, 2, 148, 160, 95, 231, 108, 216, 163, + 233, 90, 110, 8, 43, 137, 139, 242, 8, 152, 175, 246, 177, 73, 49, 137, 61, 142, 2, 49, + 158, 134, 13, 222, 60, 223, 139, 41, 66, 131, 135, 38, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6248,8 +6248,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 236, 213, 202, 143, 111, 54, 250, 174, 57, 239, 156, 18, 122, 223, 88, 20, 13, 180, 89, - 144, 31, 20, 138, 189, 2, 148, 160, 95, 231, 108, 216, 163, + 233, 90, 110, 8, 43, 137, 139, 242, 8, 152, 175, 246, 177, 73, 49, 137, 61, 142, 2, 49, + 158, 134, 13, 222, 60, 223, 139, 41, 66, 131, 135, 38, ]; assert_eq!(root_hash.as_slice(), expected_app_hash,); @@ -6462,8 +6462,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 174, 178, 50, 69, 201, 231, 248, 75, 88, 168, 83, 29, 141, 40, 117, 63, 157, 205, 24, - 56, 113, 108, 224, 27, 225, 24, 134, 153, 157, 130, 80, 200, + 112, 166, 177, 100, 209, 55, 7, 42, 111, 43, 77, 15, 40, 149, 26, 219, 121, 244, 83, 5, + 102, 253, 66, 122, 99, 217, 33, 173, 135, 17, 174, 115, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6542,8 +6542,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 174, 178, 50, 69, 201, 231, 248, 75, 88, 168, 83, 29, 141, 40, 117, 63, 157, 205, 24, - 56, 113, 108, 224, 27, 225, 24, 134, 153, 157, 130, 80, 200, + 112, 166, 177, 100, 209, 55, 7, 42, 111, 43, 77, 15, 40, 149, 26, 219, 121, 244, 83, 5, + 102, 253, 66, 122, 99, 217, 33, 173, 135, 17, 174, 115, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); @@ -6643,8 +6643,8 @@ mod tests { .expect("there is always a root hash"); let expected_app_hash = vec![ - 174, 178, 50, 69, 201, 231, 248, 75, 88, 168, 83, 29, 141, 40, 117, 63, 157, 205, 24, - 56, 113, 108, 224, 27, 225, 24, 134, 153, 157, 130, 80, 200, + 112, 166, 177, 100, 209, 55, 7, 42, 111, 43, 77, 15, 40, 149, 26, 219, 121, 244, 83, 5, + 102, 253, 66, 122, 99, 217, 33, 173, 135, 17, 174, 115, ]; assert_eq!(root_hash.as_slice(), expected_app_hash); diff --git a/packages/rs-drive/tests/query_tests_history.rs b/packages/rs-drive/tests/query_tests_history.rs index 2ffe58ce495..3229c6fbbcd 100644 --- a/packages/rs-drive/tests/query_tests_history.rs +++ b/packages/rs-drive/tests/query_tests_history.rs @@ -1666,8 +1666,8 @@ fn test_query_historical_latest_platform_version() { assert_eq!( root_hash.as_slice(), vec![ - 133, 44, 161, 89, 108, 253, 236, 16, 93, 4, 122, 187, 249, 226, 208, 148, 47, 201, 46, - 129, 167, 114, 63, 221, 133, 12, 1, 136, 135, 208, 226, 213 + 161, 240, 182, 38, 13, 26, 246, 165, 76, 67, 252, 39, 203, 128, 225, 233, 70, 76, 30, + 228, 64, 40, 59, 240, 240, 135, 215, 135, 146, 2, 128, 65 ] ); @@ -3032,8 +3032,8 @@ fn test_query_historical_latest_platform_version() { assert_eq!( root_hash.as_slice(), vec![ - 124, 248, 167, 74, 81, 40, 160, 183, 10, 34, 119, 221, 225, 7, 207, 42, 42, 205, 8, - 149, 201, 22, 165, 217, 125, 191, 152, 223, 66, 102, 9, 180 + 82, 200, 76, 76, 113, 4, 94, 39, 105, 206, 63, 185, 209, 222, 13, 161, 194, 209, 156, + 251, 133, 192, 38, 65, 93, 196, 214, 198, 52, 196, 37, 208 ] ); } diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/mod.rs index a216992f698..a9fc9cbe44f 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/mod.rs @@ -30,11 +30,15 @@ pub struct DriveContractApplyMethodVersions { pub struct DriveContractInsertMethodVersions { pub add_contract_to_storage: FeatureVersion, pub insert_contract: FeatureVersion, + pub add_description: FeatureVersion, + pub add_keywords: FeatureVersion, } #[derive(Clone, Debug, Default)] pub struct DriveContractUpdateMethodVersions { pub update_contract: FeatureVersion, + pub update_description: FeatureVersion, + pub update_keywords: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v1.rs index 67824dafea1..f8f8f3291b4 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v1.rs @@ -18,8 +18,14 @@ pub const DRIVE_CONTRACT_METHOD_VERSIONS_V1: DriveContractMethodVersions = insert: DriveContractInsertMethodVersions { add_contract_to_storage: 0, insert_contract: 0, + add_description: 0, + add_keywords: 0, + }, + update: DriveContractUpdateMethodVersions { + update_contract: 0, + update_description: 0, + update_keywords: 0, }, - update: DriveContractUpdateMethodVersions { update_contract: 0 }, costs: DriveContractCostsMethodVersions { add_estimation_costs_for_contract_insertion: 0, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v2.rs index 5197624ade2..9ba0cd15ef4 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_contract_method_versions/v2.rs @@ -17,9 +17,15 @@ pub const DRIVE_CONTRACT_METHOD_VERSIONS_V2: DriveContractMethodVersions = }, insert: DriveContractInsertMethodVersions { add_contract_to_storage: 0, - insert_contract: 1, // <--- changed to v1 (for token insertion + insert_contract: 1, // <--- changed to v1 (for token insertion) + add_description: 0, + add_keywords: 0, + }, + update: DriveContractUpdateMethodVersions { + update_contract: 1, // <--- changed to v1 (for token insertion) + update_description: 0, + update_keywords: 0, }, - update: DriveContractUpdateMethodVersions { update_contract: 1 }, // <--- changed to v1 (for token insertion) costs: DriveContractCostsMethodVersions { add_estimation_costs_for_contract_insertion: 0, }, diff --git a/packages/rs-platform-version/src/version/system_data_contract_versions/mod.rs b/packages/rs-platform-version/src/version/system_data_contract_versions/mod.rs index cc444b25e9d..1630d740a4b 100644 --- a/packages/rs-platform-version/src/version/system_data_contract_versions/mod.rs +++ b/packages/rs-platform-version/src/version/system_data_contract_versions/mod.rs @@ -10,4 +10,6 @@ pub struct SystemDataContractVersions { pub masternode_reward_shares: FeatureVersion, pub feature_flags: FeatureVersion, pub wallet: FeatureVersion, + pub token_history: FeatureVersion, + pub keyword_search: FeatureVersion, } diff --git a/packages/rs-platform-version/src/version/system_data_contract_versions/v1.rs b/packages/rs-platform-version/src/version/system_data_contract_versions/v1.rs index a55db9a296b..d136fd8e53e 100644 --- a/packages/rs-platform-version/src/version/system_data_contract_versions/v1.rs +++ b/packages/rs-platform-version/src/version/system_data_contract_versions/v1.rs @@ -8,4 +8,6 @@ pub const SYSTEM_DATA_CONTRACT_VERSIONS_V1: SystemDataContractVersions = masternode_reward_shares: 1, feature_flags: 1, wallet: 1, + token_history: 1, + keyword_search: 1, }; diff --git a/packages/rs-platform-version/src/version/v9.rs b/packages/rs-platform-version/src/version/v9.rs index f990d75ed09..aebd1ca30bd 100644 --- a/packages/rs-platform-version/src/version/v9.rs +++ b/packages/rs-platform-version/src/version/v9.rs @@ -33,7 +33,7 @@ pub const PROTOCOL_VERSION_9: ProtocolVersion = 9; //todo: make changes pub const PLATFORM_V9: PlatformVersion = PlatformVersion { protocol_version: PROTOCOL_VERSION_9, - drive: DRIVE_VERSION_V4, // changed (for data contract insert) + drive: DRIVE_VERSION_V4, // changed (for data contract insert and update) drive_abci: DriveAbciVersion { structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, methods: DRIVE_ABCI_METHOD_VERSIONS_V6, // changed because of the genesis state diff --git a/packages/rs-sdk/tests/vectors/document_list_document_query/msg_DocumentQuery_2a42b0afe6c6f58b02d8152142acec7d11a37410433366315d1103f81859344b.json b/packages/rs-sdk/tests/vectors/document_list_document_query/msg_DocumentQuery_2a42b0afe6c6f58b02d8152142acec7d11a37410433366315d1103f81859344b.json new file mode 100644 index 00000000000..9adca368205 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/document_list_document_query/msg_DocumentQuery_2a42b0afe6c6f58b02d8152142acec7d11a37410433366315d1103f81859344b.json differ diff --git a/packages/rs-sdk/tests/vectors/document_list_document_query/msg_DocumentQuery_dc96f69a023cba74603789911c80e0999c3aba33924223d8fdbfbf7d7678f3e1.json b/packages/rs-sdk/tests/vectors/document_list_document_query/msg_DocumentQuery_dc96f69a023cba74603789911c80e0999c3aba33924223d8fdbfbf7d7678f3e1.json deleted file mode 100644 index 337ea0939ed..00000000000 Binary files a/packages/rs-sdk/tests/vectors/document_list_document_query/msg_DocumentQuery_dc96f69a023cba74603789911c80e0999c3aba33924223d8fdbfbf7d7678f3e1.json and /dev/null differ diff --git a/packages/rs-sdk/tests/vectors/document_list_document_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json b/packages/rs-sdk/tests/vectors/document_list_document_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json index 0854fe6f129..d7eb96c1aa9 100644 Binary files a/packages/rs-sdk/tests/vectors/document_list_document_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json and b/packages/rs-sdk/tests/vectors/document_list_document_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json differ diff --git a/packages/rs-sdk/tests/vectors/document_list_document_query/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/document_list_document_query/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/document_list_document_query/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_list_document_query/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/document_list_document_query/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/document_list_document_query/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_DocumentQuery_2fd2f7aebe5686d4e4179323b49e8920dea81c3f44b9549c238dcb82cccc9923.json b/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_DocumentQuery_2fd2f7aebe5686d4e4179323b49e8920dea81c3f44b9549c238dcb82cccc9923.json new file mode 100644 index 00000000000..6ee022e9e57 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_DocumentQuery_2fd2f7aebe5686d4e4179323b49e8920dea81c3f44b9549c238dcb82cccc9923.json differ diff --git a/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_DocumentQuery_89578d545ba2f6d4859c6ba9135914b55a2eeea35e7831ef86e2708b558f915f.json b/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_DocumentQuery_89578d545ba2f6d4859c6ba9135914b55a2eeea35e7831ef86e2708b558f915f.json deleted file mode 100644 index a0e6e681a06..00000000000 Binary files a/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_DocumentQuery_89578d545ba2f6d4859c6ba9135914b55a2eeea35e7831ef86e2708b558f915f.json and /dev/null differ diff --git a/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json b/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json index 0854fe6f129..038ae04f81d 100644 Binary files a/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json and b/packages/rs-sdk/tests/vectors/document_list_drive_query/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json differ diff --git a/packages/rs-sdk/tests/vectors/document_list_drive_query/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/document_list_drive_query/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/document_list_drive_query/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_list_drive_query/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/document_list_drive_query/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/document_list_drive_query/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_2a42b0afe6c6f58b02d8152142acec7d11a37410433366315d1103f81859344b.json b/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_2a42b0afe6c6f58b02d8152142acec7d11a37410433366315d1103f81859344b.json new file mode 100644 index 00000000000..bd892165174 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_2a42b0afe6c6f58b02d8152142acec7d11a37410433366315d1103f81859344b.json differ diff --git a/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_4816e45a0ba02d69c291f23322410a8556ecf1145e99c87071cc8998a97d7ccb.json b/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_4816e45a0ba02d69c291f23322410a8556ecf1145e99c87071cc8998a97d7ccb.json new file mode 100644 index 00000000000..b8c9c8b7c11 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_4816e45a0ba02d69c291f23322410a8556ecf1145e99c87071cc8998a97d7ccb.json differ diff --git a/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_acef348f0a2248716aa25e8ebcd9bb958387b94269284a209a3e5d1636812919.json b/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_acef348f0a2248716aa25e8ebcd9bb958387b94269284a209a3e5d1636812919.json deleted file mode 100644 index 5ce5b1198fd..00000000000 Binary files a/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_acef348f0a2248716aa25e8ebcd9bb958387b94269284a209a3e5d1636812919.json and /dev/null differ diff --git a/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_dc96f69a023cba74603789911c80e0999c3aba33924223d8fdbfbf7d7678f3e1.json b/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_dc96f69a023cba74603789911c80e0999c3aba33924223d8fdbfbf7d7678f3e1.json deleted file mode 100644 index 337ea0939ed..00000000000 Binary files a/packages/rs-sdk/tests/vectors/document_read/msg_DocumentQuery_dc96f69a023cba74603789911c80e0999c3aba33924223d8fdbfbf7d7678f3e1.json and /dev/null differ diff --git a/packages/rs-sdk/tests/vectors/document_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json b/packages/rs-sdk/tests/vectors/document_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json index 0854fe6f129..603afa7eb28 100644 Binary files a/packages/rs-sdk/tests/vectors/document_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json and b/packages/rs-sdk/tests/vectors/document_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json differ diff --git a/packages/rs-sdk/tests/vectors/document_read/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/document_read/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/document_read/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_read/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/document_read/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/document_read/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_read_no_contract/msg_GetDataContractRequest_e4cf74168e03a40bd159451456b501c1ba166a2dd8f6efb31b0289dc011da983.json b/packages/rs-sdk/tests/vectors/document_read_no_contract/msg_GetDataContractRequest_e4cf74168e03a40bd159451456b501c1ba166a2dd8f6efb31b0289dc011da983.json index 139f6b006ce..e6ba55bd212 100644 Binary files a/packages/rs-sdk/tests/vectors/document_read_no_contract/msg_GetDataContractRequest_e4cf74168e03a40bd159451456b501c1ba166a2dd8f6efb31b0289dc011da983.json and b/packages/rs-sdk/tests/vectors/document_read_no_contract/msg_GetDataContractRequest_e4cf74168e03a40bd159451456b501c1ba166a2dd8f6efb31b0289dc011da983.json differ diff --git a/packages/rs-sdk/tests/vectors/document_read_no_contract/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/document_read_no_contract/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/document_read_no_contract/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_read_no_contract/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/document_read_no_contract/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/document_read_no_contract/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_read_no_document/msg_DocumentQuery_614b964c1234c9998fb8e27da8dcc2a50d7b433b50a88dc35fb34a80ecf9019f.json b/packages/rs-sdk/tests/vectors/document_read_no_document/msg_DocumentQuery_614b964c1234c9998fb8e27da8dcc2a50d7b433b50a88dc35fb34a80ecf9019f.json deleted file mode 100644 index 9fda16fc6bb..00000000000 Binary files a/packages/rs-sdk/tests/vectors/document_read_no_document/msg_DocumentQuery_614b964c1234c9998fb8e27da8dcc2a50d7b433b50a88dc35fb34a80ecf9019f.json and /dev/null differ diff --git a/packages/rs-sdk/tests/vectors/document_read_no_document/msg_DocumentQuery_f531f69255dae40cef880290c177ff33d2448d30f2527374349b9f507b44c5c1.json b/packages/rs-sdk/tests/vectors/document_read_no_document/msg_DocumentQuery_f531f69255dae40cef880290c177ff33d2448d30f2527374349b9f507b44c5c1.json new file mode 100644 index 00000000000..aa4d00e5ac0 Binary files /dev/null and b/packages/rs-sdk/tests/vectors/document_read_no_document/msg_DocumentQuery_f531f69255dae40cef880290c177ff33d2448d30f2527374349b9f507b44c5c1.json differ diff --git a/packages/rs-sdk/tests/vectors/document_read_no_document/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json b/packages/rs-sdk/tests/vectors/document_read_no_document/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json index 0854fe6f129..1382d46be43 100644 Binary files a/packages/rs-sdk/tests/vectors/document_read_no_document/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json and b/packages/rs-sdk/tests/vectors/document_read_no_document/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json differ diff --git a/packages/rs-sdk/tests/vectors/document_read_no_document/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/document_read_no_document/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/document_read_no_document/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/document_read_no_document/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/document_read_no_document/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/document_read_no_document/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_data_contract_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json b/packages/rs-sdk/tests/vectors/test_data_contract_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json index 0854fe6f129..6487de69be4 100644 Binary files a/packages/rs-sdk/tests/vectors/test_data_contract_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json and b/packages/rs-sdk/tests/vectors/test_data_contract_read/msg_GetDataContractRequest_e87a2e6acef76975c30eb7272da71733fb6ad13495beb7ca1b6a6c4ceb30e0f7.json differ diff --git a/packages/rs-sdk/tests/vectors/test_data_contract_read/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/test_data_contract_read/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/test_data_contract_read/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_data_contract_read/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/test_data_contract_read/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_data_contract_read/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/msg_GetDataContractRequest_1d1e53ab5e04d9ec5dce4ff9ac048c03122daf7ab2e77108f4bf44af1ad15eae.json b/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/msg_GetDataContractRequest_1d1e53ab5e04d9ec5dce4ff9ac048c03122daf7ab2e77108f4bf44af1ad15eae.json index 2c110c17ed1..bd8f1bdd637 100644 Binary files a/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/msg_GetDataContractRequest_1d1e53ab5e04d9ec5dce4ff9ac048c03122daf7ab2e77108f4bf44af1ad15eae.json and b/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/msg_GetDataContractRequest_1d1e53ab5e04d9ec5dce4ff9ac048c03122daf7ab2e77108f4bf44af1ad15eae.json differ diff --git a/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_data_contract_read_not_found/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/msg_GetDataContractsRequest_f229a0e58a5c4fb050f57c087bf067bd9ccc29eca3092a5664a5a9ba3bb7e967.json b/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/msg_GetDataContractsRequest_f229a0e58a5c4fb050f57c087bf067bd9ccc29eca3092a5664a5a9ba3bb7e967.json index eab8c25c4d9..c1129991c45 100644 Binary files a/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/msg_GetDataContractsRequest_f229a0e58a5c4fb050f57c087bf067bd9ccc29eca3092a5664a5a9ba3bb7e967.json and b/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/msg_GetDataContractsRequest_f229a0e58a5c4fb050f57c087bf067bd9ccc29eca3092a5664a5a9ba3bb7e967.json differ diff --git a/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json b/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json deleted file mode 100644 index e048700d163..00000000000 --- a/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/quorum_pubkey-106-1f0a25d463a2912cd31dd4f91b899a143d6ae990bb9bc89cb34f7c5db8b1b705.json +++ /dev/null @@ -1 +0,0 @@ -acbfb39d5f22cd2f096af600d2f19d618fa9898fecd778393c777d1b1785ee5fbad857cf62b3b69ade96894bafe42c7c \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json b/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json new file mode 100644 index 00000000000..e18b2a7deac --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_data_contracts_1_ok_1_nx/quorum_pubkey-106-3603d12872604475619be7ad682f8339ad60debd43b3f430bd12d51eac268936.json @@ -0,0 +1 @@ +b751df5b612ece651a4d290fa95fab328d5b186b84fc88b103bb45cdf6d3815620b74daa0d72067d74ea86da595c6a59 \ No newline at end of file diff --git a/packages/search-contract/schema/v1/search-contract-documents.json b/packages/search-contract/schema/v1/search-contract-documents.json deleted file mode 100644 index f146ecb3e6c..00000000000 --- a/packages/search-contract/schema/v1/search-contract-documents.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "contract": { - "type": "object", - "documentsMutable": false, - "canBeDeleted": false, - "referenceType": "contract", - "creationRestrictionMode": 2, - "indices": [ - { - "name": "byKeyword", - "properties": [ - { - "keyword": "asc" - } - ] - } - ], - "properties": { - "keyword": { - "type": "string", - "minLength": 3, - "maxLength": 50, - "position": 0 - } - }, - "required": [ - "$contractId", - "keyword" - ], - "additionalProperties": false - }, - "token": { - "type": "object", - "documentsMutable": false, - "canBeDeleted": false, - "referenceType": "token", - "creationRestrictionMode": 2, - "indices": [ - { - "name": "byKeyword", - "properties": [ - { - "keyword": "asc" - } - ] - } - ], - "properties": { - "keyword": { - "type": "string", - "minLength": 3, - "maxLength": 50, - "position": 0 - } - }, - "required": [ - "$tokenId", - "keyword" - ], - "additionalProperties": false - } -} diff --git a/packages/search-contract/src/v1/mod.rs b/packages/search-contract/src/v1/mod.rs deleted file mode 100644 index e5d6a067712..00000000000 --- a/packages/search-contract/src/v1/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::Error; -use serde_json::Value; - -pub mod document_types { - pub mod contract { - pub const NAME: &str = "contract"; - - pub mod properties { - pub const KEY_INDEX: &str = "byKeyword"; - } - } - - pub mod token { - pub const NAME: &str = "token"; - - pub mod properties { - pub const KEY_INDEX: &str = "byKeyword"; - } - } -} - -pub fn load_documents_schemas() -> Result { - serde_json::from_str(include_str!( - "../../schema/v1/search-contract-documents.json" - )) - .map_err(Error::InvalidSchemaJson) -} diff --git a/packages/token-history-contract/src/lib.rs b/packages/token-history-contract/src/lib.rs index 3bf08b2e25b..e176a5a61f3 100644 --- a/packages/token-history-contract/src/lib.rs +++ b/packages/token-history-contract/src/lib.rs @@ -16,20 +16,20 @@ pub const OWNER_ID_BYTES: [u8; 32] = [0; 32]; pub const ID: Identifier = Identifier(IdentifierBytes32(ID_BYTES)); pub const OWNER_ID: Identifier = Identifier(IdentifierBytes32(OWNER_ID_BYTES)); pub fn load_definitions(platform_version: &PlatformVersion) -> Result, Error> { - match platform_version.system_data_contracts.withdrawals { + match platform_version.system_data_contracts.token_history { 1 => Ok(None), version => Err(Error::UnknownVersionMismatch { - method: "wallet_contract::load_definitions".to_string(), + method: "token_history_contract::load_definitions".to_string(), known_versions: vec![1], received: version, }), } } pub fn load_documents_schemas(platform_version: &PlatformVersion) -> Result { - match platform_version.system_data_contracts.withdrawals { + match platform_version.system_data_contracts.token_history { 1 => v1::load_documents_schemas(), version => Err(Error::UnknownVersionMismatch { - method: "wallet_contract::load_documents_schemas".to_string(), + method: "token_history_contract::load_documents_schemas".to_string(), known_versions: vec![1], received: version, }), diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 85adcc177da..65823940f40 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -61,7 +61,7 @@ use dpp::consensus::state::data_trigger::DataTriggerError::{ DataTriggerConditionError, DataTriggerExecutionError, DataTriggerInvalidResultError, }; use wasm_bindgen::{JsError, JsValue}; -use dpp::consensus::basic::data_contract::{ContestedUniqueIndexOnMutableDocumentTypeError, ContestedUniqueIndexWithUniqueIndexError, DataContractTokenConfigurationUpdateError, GroupExceedsMaxMembersError, GroupMemberHasPowerOfZeroError, GroupMemberHasPowerOverLimitError, GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, GroupPositionDoesNotExistError, GroupTotalPowerLessThanRequiredError, InvalidDocumentTypeRequiredSecurityLevelError, InvalidTokenBaseSupplyError, InvalidTokenDistributionFunctionDivideByZeroError, InvalidTokenDistributionFunctionIncoherenceError, InvalidTokenDistributionFunctionInvalidParameterError, InvalidTokenDistributionFunctionInvalidParameterTupleError, NonContiguousContractGroupPositionsError, NonContiguousContractTokenPositionsError, TokenPaymentByBurningOnlyAllowedOnInternalTokenError, UnknownDocumentActionTokenEffectError, UnknownDocumentCreationRestrictionModeError, UnknownGasFeesPaidByError, UnknownSecurityLevelError, UnknownStorageKeyRequirementsError, UnknownTradeModeError, UnknownTransferableTypeError}; +use dpp::consensus::basic::data_contract::{ContestedUniqueIndexOnMutableDocumentTypeError, ContestedUniqueIndexWithUniqueIndexError, DataContractTokenConfigurationUpdateError, DuplicateKeywordsError, GroupExceedsMaxMembersError, GroupMemberHasPowerOfZeroError, GroupMemberHasPowerOverLimitError, GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError, GroupPositionDoesNotExistError, GroupTotalPowerLessThanRequiredError, InvalidDescriptionLengthError, InvalidDocumentTypeRequiredSecurityLevelError, InvalidKeywordLengthError, InvalidTokenBaseSupplyError, InvalidTokenDistributionFunctionDivideByZeroError, InvalidTokenDistributionFunctionIncoherenceError, InvalidTokenDistributionFunctionInvalidParameterError, InvalidTokenDistributionFunctionInvalidParameterTupleError, NonContiguousContractGroupPositionsError, NonContiguousContractTokenPositionsError, TokenPaymentByBurningOnlyAllowedOnInternalTokenError, TooManyKeywordsError, UnknownDocumentActionTokenEffectError, UnknownDocumentCreationRestrictionModeError, UnknownGasFeesPaidByError, UnknownSecurityLevelError, UnknownStorageKeyRequirementsError, UnknownTradeModeError, UnknownTransferableTypeError}; use dpp::consensus::basic::document::{ContestedDocumentsTemporarilyNotAllowedError, DocumentCreationNotAllowedError, DocumentFieldMaxSizeExceededError, MaxDocumentsTransitionsExceededError, MissingPositionsInDocumentTypePropertiesError}; use dpp::consensus::basic::group::GroupActionNotAllowedOnTransitionError; use dpp::consensus::basic::identity::{DataContractBoundsNotPresentError, DisablingKeyIdAlsoBeingAddedInSameTransitionError, InvalidIdentityCreditWithdrawalTransitionAmountError, InvalidIdentityUpdateTransitionDisableKeysError, InvalidIdentityUpdateTransitionEmptyError, TooManyMasterPublicKeyError, WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError}; @@ -770,6 +770,18 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::TokenPaymentByBurningOnlyAllowedOnInternalTokenError(e) => { generic_consensus_error!(TokenPaymentByBurningOnlyAllowedOnInternalTokenError, e).into() } + BasicError::TooManyKeywordsError(e) => { + generic_consensus_error!(TooManyKeywordsError, e).into() + } + BasicError::DuplicateKeywordsError(e) => { + generic_consensus_error!(DuplicateKeywordsError, e).into() + } + BasicError::InvalidKeywordLengthError(e) => { + generic_consensus_error!(InvalidKeywordLengthError, e).into() + } + BasicError::InvalidDescriptionLengthError(e) => { + generic_consensus_error!(InvalidDescriptionLengthError, e).into() + } } }