diff --git a/packages/dashpay-contract/schema/v2/dashpay.schema.json b/packages/dashpay-contract/schema/v2/dashpay.schema.json new file mode 100644 index 00000000000..db5ce9b4dcd --- /dev/null +++ b/packages/dashpay-contract/schema/v2/dashpay.schema.json @@ -0,0 +1,271 @@ +{ + "profile": { + "type": "object", + "indices": [ + { + "name": "ownerId", + "properties": [ + { + "$ownerId": "asc" + } + ], + "unique": true + }, + { + "name": "ownerIdAndUpdatedAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "avatarUrl": { + "type": "string", + "format": "uri", + "minLength": 1, + "maxLength": 2048, + "position": 0 + }, + "avatarHash": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "description": "SHA256 hash of the bytes of the image specified by avatarUrl", + "position": 1 + }, + "avatarFingerprint": { + "type": "array", + "byteArray": true, + "minItems": 8, + "maxItems": 8, + "description": "dHash the image specified by avatarUrl", + "position": 2 + }, + "publicMessage": { + "type": "string", + "minLength": 1, + "maxLength": 140, + "position": 3 + }, + "displayName": { + "type": "string", + "minLength": 1, + "maxLength": 25, + "position": 4 + }, + "corePaymentAddress": { + "type": "array", + "byteArray": true, + "minItems": 21, + "maxItems": 21, + "description": "Core chain address in storage form (type byte 0x00 P2PKH / 0x01 P2SH followed by the 20-byte HASH160, i.e. RIPEMD160 of SHA256, of the public key or redeem script) for public payments. The type byte is consensus-enforced by a data trigger; clients render the address as Base58Check for the network they are on. Payments to it are publicly linkable to this profile.", + "position": 5 + }, + "platformPaymentAddress": { + "type": "array", + "byteArray": true, + "minItems": 21, + "maxItems": 21, + "description": "Platform address in storage form (type byte 0x00 P2PKH / 0x01 P2SH followed by the 20-byte HASH160, i.e. RIPEMD160 of SHA256, of the public key or redeem script) for public payments. The type byte is consensus-enforced by a data trigger.", + "position": 6 + } + }, + "minProperties": 1, + "dependentRequired": { + "avatarUrl": ["avatarHash", "avatarFingerprint"], + "avatarHash": ["avatarUrl", "avatarFingerprint"], + "avatarFingerprint": ["avatarUrl", "avatarHash"] + }, + "required": [ + "$createdAt", + "$updatedAt" + ], + "additionalProperties": false + }, + "contactInfo": { + "type": "object", + "indices": [ + { + "name": "ownerIdAndKeys", + "properties": [ + { + "$ownerId": "asc" + }, + { + "rootEncryptionKeyIndex": "asc" + }, + { + "derivationEncryptionKeyIndex": "asc" + } + ], + "unique": true + }, + { + "name": "ownerIdAndUpdatedAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$updatedAt": "asc" + } + ] + } + ], + "properties": { + "encToUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 0 + }, + "rootEncryptionKeyIndex": { + "type": "integer", + "minimum": 0, + "position": 1 + }, + "derivationEncryptionKeyIndex": { + "type": "integer", + "minimum": 0, + "position": 2 + }, + "privateData": { + "type": "array", + "byteArray": true, + "minItems": 48, + "maxItems": 2048, + "position": 3, + "description": "This is the encrypted values of aliasName + note + displayHidden encoded as an array in cbor" + } + }, + "required": [ + "$createdAt", + "$updatedAt", + "encToUserId", + "privateData", + "rootEncryptionKeyIndex", + "derivationEncryptionKeyIndex" + ], + "additionalProperties": false + }, + "contactRequest": { + "documentsMutable": false, + "canBeDeleted": false, + "requiresIdentityEncryptionBoundedKey": 2, + "requiresIdentityDecryptionBoundedKey": 2, + "type": "object", + "indices": [ + { + "name": "ownerIdUserIdAndAccountRef", + "properties": [ + { + "$ownerId": "asc" + }, + { + "toUserId": "asc" + }, + { + "accountReference": "asc" + } + ], + "unique": true + }, + { + "name": "ownerIdUserId", + "properties": [ + { + "$ownerId": "asc" + }, + { + "toUserId": "asc" + } + ] + }, + { + "name": "userIdCreatedAt", + "properties": [ + { + "toUserId": "asc" + }, + { + "$createdAt": "asc" + } + ] + }, + { + "name": "ownerIdCreatedAt", + "properties": [ + { + "$ownerId": "asc" + }, + { + "$createdAt": "asc" + } + ] + } + ], + "properties": { + "toUserId": { + "type": "array", + "byteArray": true, + "minItems": 32, + "maxItems": 32, + "position": 0, + "contentMediaType": "application/x.dash.dpp.identifier" + }, + "encryptedPublicKey": { + "type": "array", + "byteArray": true, + "minItems": 96, + "maxItems": 96, + "position": 1 + }, + "senderKeyIndex": { + "type": "integer", + "minimum": 0, + "position": 2 + }, + "recipientKeyIndex": { + "type": "integer", + "minimum": 0, + "position": 3 + }, + "accountReference": { + "type": "integer", + "minimum": 0, + "position": 4 + }, + "encryptedAccountLabel": { + "type": "array", + "byteArray": true, + "minItems": 48, + "maxItems": 80, + "position": 5 + }, + "autoAcceptProof": { + "type": "array", + "byteArray": true, + "minItems": 38, + "maxItems": 102, + "position": 6 + } + }, + "required": [ + "$createdAt", + "$createdAtCoreBlockHeight", + "toUserId", + "encryptedPublicKey", + "senderKeyIndex", + "recipientKeyIndex", + "accountReference" + ], + "additionalProperties": false + } +} diff --git a/packages/dashpay-contract/src/lib.rs b/packages/dashpay-contract/src/lib.rs index f17916867c2..3132e0acd50 100644 --- a/packages/dashpay-contract/src/lib.rs +++ b/packages/dashpay-contract/src/lib.rs @@ -1,5 +1,6 @@ mod error; pub mod v1; +pub mod v2; pub use crate::error::Error; use platform_value::{Identifier, IdentifierBytes32}; @@ -18,10 +19,10 @@ 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.dashpay { - 1 => Ok(None), + 1 | 2 => Ok(None), version => Err(Error::UnknownVersionMismatch { method: "dashpay_contract::load_definitions".to_string(), - known_versions: vec![1], + known_versions: vec![1, 2], received: version, }), } @@ -29,9 +30,10 @@ pub fn load_definitions(platform_version: &PlatformVersion) -> Result Result { match platform_version.system_data_contracts.dashpay { 1 => v1::load_documents_schemas(), + 2 => v2::load_documents_schemas(), version => Err(Error::UnknownVersionMismatch { method: "dashpay_contract::load_documents_schemas".to_string(), - known_versions: vec![1], + known_versions: vec![1, 2], received: version, }), } diff --git a/packages/dashpay-contract/src/v2/mod.rs b/packages/dashpay-contract/src/v2/mod.rs new file mode 100644 index 00000000000..131fecb1769 --- /dev/null +++ b/packages/dashpay-contract/src/v2/mod.rs @@ -0,0 +1,11 @@ +use crate::error::Error; +use serde_json::Value; + +// Document-type name and property constants live in `crate::v1::document_types`; +// v2 does not change any names v1 defined, it only adds the optional +// `corePaymentAddress` / `platformPaymentAddress` properties to `profile`. + +pub fn load_documents_schemas() -> Result { + serde_json::from_str(include_str!("../../schema/v2/dashpay.schema.json")) + .map_err(Error::InvalidSchemaJson) +} 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 3a8978f1622..c7c1bf2fb6c 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 @@ -644,7 +644,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002489210 + 24002816630 ); let check_result = platform @@ -1159,7 +1159,7 @@ mod tests { // Plus we have 24_000_000_000 in base costs assert_eq!( processing_result.aggregated_fees().processing_fee, - 24004978420 + 24005633260 ); let check_result = platform @@ -1634,7 +1634,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002489210 + 24002816630 ); platform @@ -1721,7 +1721,7 @@ mod tests { assert_eq!( update_processing_result.aggregated_fees().processing_fee, - 27002504030 + 27002879350 ); let check_result = platform @@ -2093,7 +2093,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002489210 + 24002816630 ); platform 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 0866f15c0f7..e3a5cd227d0 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 @@ -114,6 +114,10 @@ impl Platform { self.transition_to_version_13(block_info, transaction, platform_version)?; } + if previous_protocol_version < 14 && platform_version.protocol_version >= 14 { + self.transition_to_version_14(block_info, transaction, platform_version)?; + } + Ok(()) } @@ -686,6 +690,30 @@ impl Platform { Ok(()) } + + /// When transitioning to version 14 we re-store the DashPay contract whose + /// v2 schema adds the optional public payment address fields to the + /// `profile` document type (DIP-33). + fn transition_to_version_14( + &self, + block_info: &BlockInfo, + transaction: &Transaction, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + let dashpay_contract = + load_system_data_contract(SystemDataContract::Dashpay, platform_version)?; + + self.drive.apply_contract( + &dashpay_contract, + *block_info, + true, + None, + Some(transaction), + platform_version, + )?; + + Ok(()) + } } #[cfg(test)] @@ -1010,6 +1038,314 @@ mod tests { assert!(domain.documents_keep_pricing_history()); } + #[test] + fn test_transition_to_version_14_updates_dashpay_with_payment_address_fields() { + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; + + // A chain born at protocol version 13: DashPay v1 is stored and the + // profile document type has no payment address fields. + let platform = TestPlatformBuilder::new() + .with_initial_protocol_version(13) + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_version = PlatformVersion::get(14).expect("expected platform version 14"); + + let transaction = platform.drive.grove.start_transaction(); + + let block_info = BlockInfo { + time_ms: 1_000_000, + height: 100, + core_height: 100, + epoch: Epoch::new(1).expect("expected epoch"), + }; + + // Before the transition the stored DashPay profile must be v1: no + // corePaymentAddress / platformPaymentAddress properties. + let (_fee_result, pre_upgrade_fetch_info) = platform + .drive + .get_contract_with_fetch_info_and_fee( + *dpp::data_contracts::SystemDataContract::Dashpay + .id() + .as_bytes(), + None, + false, + Some(&transaction), + platform_version, + ) + .expect("expected to fetch DashPay contract"); + let pre_profile = pre_upgrade_fetch_info + .expect("expected the DashPay contract to exist at genesis") + .contract + .document_type_for_name("profile") + .expect("expected the profile document type") + .properties() + .keys() + .cloned() + .collect::>(); + assert!( + !pre_profile.iter().any(|p| p == "corePaymentAddress"), + "profile must not carry corePaymentAddress before transition_to_version_14" + ); + assert!( + !pre_profile.iter().any(|p| p == "platformPaymentAddress"), + "profile must not carry platformPaymentAddress before transition_to_version_14" + ); + + let result = platform.transition_to_version_14(&block_info, &transaction, platform_version); + assert!(result.is_ok(), "transition failed: {:?}", result.err()); + + // After the transition the stored DashPay contract must be v2: the + // profile document type gains the two optional payment address fields. + let (_fee_result, dashpay_fetch_info) = platform + .drive + .get_contract_with_fetch_info_and_fee( + *dpp::data_contracts::SystemDataContract::Dashpay + .id() + .as_bytes(), + None, + false, + Some(&transaction), + platform_version, + ) + .expect("expected to fetch DashPay contract"); + + let profile = dashpay_fetch_info + .expect("expected the DashPay contract to exist") + .contract + .document_type_for_name("profile") + .expect("expected the profile document type") + .properties() + .keys() + .cloned() + .collect::>(); + + assert!( + profile.iter().any(|p| p == "corePaymentAddress"), + "profile must carry corePaymentAddress after transition_to_version_14" + ); + assert!( + profile.iter().any(|p| p == "platformPaymentAddress"), + "profile must carry platformPaymentAddress after transition_to_version_14" + ); + } + + /// The v13→v14 boundary through the production dispatcher + /// (`perform_events_on_first_block_of_protocol_change`), with a v1 profile + /// stored before the upgrade and the contract cache warmed: the dispatcher + /// must select the v14 transition, the refreshed contract must carry both + /// payment address fields, and the legacy profile bytes must stay readable + /// through normal Drive queries against the refreshed contract. + #[tokio::test] + async fn test_protocol_change_v13_to_v14_upgrades_dashpay_and_keeps_v1_profiles_readable() { + use crate::execution::validation::state_transition::tests::setup_identity; + use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; + use assert_matches::assert_matches; + use dpp::dash_to_credits; + use dpp::data_contract::accessors::v0::DataContractV0Getters; + use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; + use dpp::data_contract::document_type::random_document::{ + CreateRandomDocument, DocumentFieldFillSize, DocumentFieldFillType, + }; + use dpp::document::{DocumentV0Getters, DocumentV0Setters}; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::platform_value::Bytes32; + use dpp::serialization::PlatformSerializable; + use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; + use dpp::state_transition::batch_transition::BatchTransition; + use drive::drive::document::query::QueryDocumentsOutcomeV0Methods; + use drive::query::DriveDocumentQuery; + use rand::rngs::StdRng; + use rand::SeedableRng; + + let platform_version_13 = PlatformVersion::get(13).expect("expected platform version 13"); + let platform_version_14 = PlatformVersion::get(14).expect("expected platform version 14"); + + let mut platform = TestPlatformBuilder::new() + .with_initial_protocol_version(13) + .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)); + + // Store a v1 profile through the normal pipeline at protocol version 13 + let dashpay_v1 = platform + .drive + .cache + .system_data_contracts + .load_dashpay(platform_version_13) + .expect("expected the dashpay system contract"); + let profile_v1 = dashpay_v1 + .document_type_for_name("profile") + .expect("expected a profile document type"); + + let mut rng = StdRng::seed_from_u64(438); + let entropy = Bytes32::random_with_rng(&mut rng); + let mut document = profile_v1 + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::FillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version_13, + ) + .expect("expected a random v1 profile document"); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); + let stored_profile_id = document.id(); + + let create_transition = BatchTransition::new_document_creation_transition_from_document( + document, + profile_v1, + entropy.0, + &key, + 2, + 0, + None, + &signer, + platform_version_13, + None, + ) + .await + .expect("expect to create documents batch transition"); + let create_serialized = create_transition + .serialize_to_bytes() + .expect("expected serialized transition"); + + let transaction = platform.drive.grove.start_transaction(); + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![create_serialized], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version_13, + false, + None, + ) + .expect("expected to process state transition"); + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }] + ); + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit"); + + // Warm the drive contract cache with the v1 contract and confirm the + // payment address fields are absent pre-upgrade + let transaction = platform.drive.grove.start_transaction(); + let (_fee, pre_fetch_info) = platform + .drive + .get_contract_with_fetch_info_and_fee( + *dpp::data_contracts::SystemDataContract::Dashpay + .id() + .as_bytes(), + None, + true, + Some(&transaction), + platform_version_13, + ) + .expect("expected to fetch DashPay contract"); + let pre_profile_properties = pre_fetch_info + .expect("expected the DashPay contract pre-upgrade") + .contract + .document_type_for_name("profile") + .expect("expected the profile document type") + .properties() + .keys() + .cloned() + .collect::>(); + for field in ["corePaymentAddress", "platformPaymentAddress"] { + assert!( + !pre_profile_properties.iter().any(|p| p == field), + "profile must not carry {field} before the upgrade" + ); + } + + // Run the v13→v14 boundary through the production dispatcher + let block_info = BlockInfo { + time_ms: 2_000_000, + height: 200, + core_height: 200, + epoch: Epoch::new(1).expect("expected epoch"), + }; + platform + .perform_events_on_first_block_of_protocol_change( + &platform_state, + &block_info, + &transaction, + 13, + platform_version_14, + ) + .expect("expected the protocol change events to succeed"); + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit the upgrade"); + + // The refreshed contract must be v2 with both payment address fields + let (_fee, post_fetch_info) = platform + .drive + .get_contract_with_fetch_info_and_fee( + *dpp::data_contracts::SystemDataContract::Dashpay + .id() + .as_bytes(), + None, + true, + None, + platform_version_14, + ) + .expect("expected to fetch DashPay contract post-upgrade"); + let dashpay_v2_fetch_info = post_fetch_info.expect("expected the DashPay contract"); + let post_profile_properties = dashpay_v2_fetch_info + .contract + .document_type_for_name("profile") + .expect("expected the profile document type") + .properties() + .keys() + .cloned() + .collect::>(); + for field in ["corePaymentAddress", "platformPaymentAddress"] { + assert!( + post_profile_properties.iter().any(|p| p == field), + "profile must carry {field} after the upgrade" + ); + } + + // The pre-upgrade profile must remain readable through normal Drive + // queries against the refreshed v2 contract + let query = DriveDocumentQuery::from_sql_expr( + "select * from profile", + &dashpay_v2_fetch_info.contract, + Some(&platform.config.drive), + ) + .expect("expected a document query"); + let query_results = platform + .drive + .query_documents(query, None, false, None, None) + .expect("expected to query documents"); + let documents = query_results.documents(); + assert_eq!( + documents.len(), + 1, + "the v1 profile must survive the upgrade" + ); + assert_eq!( + documents.first().expect("expected a document").id(), + stored_profile_id, + "the surviving profile must be the pre-upgrade document" + ); + } + // test_transition_to_version_9 removed: requires prior state from versions 4-8 #[test] diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/mod.rs index e3e6a001427..e7e67702fd8 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/mod.rs @@ -4,6 +4,7 @@ use crate::execution::validation::state_transition::batch::data_triggers::bindin mod v0; mod v1; +mod v2; pub fn data_trigger_bindings_list( platform_version: &PlatformVersion, @@ -24,9 +25,13 @@ pub fn data_trigger_bindings_list( .into_iter() .map(|binding| binding.into()) .collect()), + 2 => Ok(v2::data_trigger_bindings_list_v2()? + .into_iter() + .map(|binding| binding.into()) + .collect()), version => Err(ProtocolError::UnknownVersionMismatch { method: "data_trigger_bindings".to_string(), - known_versions: vec![0, 1], + known_versions: vec![0, 1, 2], received: version, }), } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/v2/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/v2/mod.rs new file mode 100644 index 00000000000..4b7963db392 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/v2/mod.rs @@ -0,0 +1,108 @@ +use crate::execution::validation::state_transition::batch::data_triggers::bindings::data_trigger_binding::DataTriggerBindingV0; +use crate::execution::validation::state_transition::batch::data_triggers::triggers::dashpay::{create_contact_request_data_trigger, validate_profile_payment_addresses_data_trigger}; +use crate::execution::validation::state_transition::batch::data_triggers::triggers::dpns::create_domain_data_trigger; +use crate::execution::validation::state_transition::batch::data_triggers::triggers::reject::reject_data_trigger; +use crate::execution::validation::state_transition::batch::data_triggers::triggers::withdrawals::delete_withdrawal_data_trigger; + +use dpp::errors::ProtocolError; +use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal; +use dpp::system_data_contracts::{dashpay_contract, dpns_contract, SystemDataContract}; +use drive::state_transition_action::batch::batched_transition::document_transition::DocumentTransitionActionType; + +/// Retrieves a list of data triggers binding with matching params. +/// +/// This function gets all known data triggers which are then returned +/// as a vector of `DataTrigger` structs. +/// +/// v2 (PROTOCOL_VERSION_14): DashPay `profile` documents gain Create and +/// Replace triggers enforcing the DIP-33 payment-address type byte +/// (`0x00` P2PKH / `0x01` P2SH) that the schema vocabulary cannot express. +/// Everything else is unchanged from v1. +/// +/// # Returns +/// +/// A `Vec` containing all known data triggers. +/// +/// # Errors +/// +/// Returns a `ProtocolError` if there was an error. +#[inline(always)] +pub(super) fn data_trigger_bindings_list_v2() -> Result, ProtocolError> { + let data_triggers = vec![ + DataTriggerBindingV0 { + data_contract_id: dpns_contract::ID, + document_type: "domain".to_string(), + transition_action_type: DocumentTransitionActionType::Create, + data_trigger: create_domain_data_trigger, + }, + // Domain documents can never be modified or deleted, but since + // protocol version 13 they can be transferred and sold + DataTriggerBindingV0 { + data_contract_id: dpns_contract::ID, + document_type: "domain".to_string(), + transition_action_type: DocumentTransitionActionType::Replace, + data_trigger: reject_data_trigger, + }, + DataTriggerBindingV0 { + data_contract_id: dpns_contract::ID, + document_type: "domain".to_string(), + transition_action_type: DocumentTransitionActionType::Delete, + data_trigger: reject_data_trigger, + }, + DataTriggerBindingV0 { + data_contract_id: dashpay_contract::ID, + document_type: "contactRequest".to_string(), + transition_action_type: DocumentTransitionActionType::Create, + data_trigger: create_contact_request_data_trigger, + }, + // DIP-33 payment address fields must carry a supported type byte + DataTriggerBindingV0 { + data_contract_id: dashpay_contract::ID, + document_type: "profile".to_string(), + transition_action_type: DocumentTransitionActionType::Create, + data_trigger: validate_profile_payment_addresses_data_trigger, + }, + DataTriggerBindingV0 { + data_contract_id: dashpay_contract::ID, + document_type: "profile".to_string(), + transition_action_type: DocumentTransitionActionType::Replace, + data_trigger: validate_profile_payment_addresses_data_trigger, + }, + // Only masternodes will be able to update it + DataTriggerBindingV0 { + data_contract_id: SystemDataContract::MasternodeRewards.id(), + document_type: "rewardShare".to_string(), + transition_action_type: DocumentTransitionActionType::Create, + data_trigger: reject_data_trigger, + }, + // Only masternodes will be able to update it + DataTriggerBindingV0 { + data_contract_id: SystemDataContract::MasternodeRewards.id(), + document_type: "rewardShare".to_string(), + transition_action_type: DocumentTransitionActionType::Replace, + data_trigger: reject_data_trigger, + }, + // Only masternodes will be able to update it + DataTriggerBindingV0 { + data_contract_id: SystemDataContract::MasternodeRewards.id(), + document_type: "rewardShare".to_string(), + transition_action_type: DocumentTransitionActionType::Delete, + data_trigger: reject_data_trigger, + }, + // We can't use mutability flag otherwise documents won't have revision + DataTriggerBindingV0 { + data_contract_id: SystemDataContract::Withdrawals.id(), + document_type: withdrawal::NAME.to_string(), + transition_action_type: DocumentTransitionActionType::Replace, + data_trigger: reject_data_trigger, + }, + DataTriggerBindingV0 { + data_contract_id: SystemDataContract::Withdrawals.id(), + document_type: withdrawal::NAME.to_string(), + transition_action_type: DocumentTransitionActionType::Delete, + data_trigger: delete_withdrawal_data_trigger, + }, + ]; + + Ok(data_triggers) +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/mod.rs index c782ac77381..c81debdf64f 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/mod.rs @@ -1,5 +1,6 @@ use crate::error::execution::ExecutionError; use crate::error::Error; +use crate::execution::validation::state_transition::batch::data_triggers::triggers::dashpay::profile_v0::validate_profile_payment_addresses_data_trigger_v0; use crate::execution::validation::state_transition::batch::data_triggers::triggers::dashpay::v0::create_contact_request_data_trigger_v0; use crate::execution::validation::state_transition::batch::data_triggers::triggers::dashpay::v1::create_contact_request_data_trigger_v1; use crate::execution::validation::state_transition::batch::data_triggers::{ @@ -8,6 +9,7 @@ use crate::execution::validation::state_transition::batch::data_triggers::{ use dpp::version::PlatformVersion; use drive::state_transition_action::batch::batched_transition::document_transition::DocumentTransitionAction; +mod profile_v0; mod v0; mod v1; @@ -34,3 +36,30 @@ pub fn create_contact_request_data_trigger( })), } } + +pub fn validate_profile_payment_addresses_data_trigger( + document_transition: &DocumentTransitionAction, + context: &mut DataTriggerExecutionContext<'_>, + platform_version: &PlatformVersion, +) -> Result { + match platform_version + .drive_abci + .validation_and_processing + .state_transitions + .batch_state_transition + .data_triggers + .triggers + .validate_profile_payment_addresses_data_trigger + { + 0 => validate_profile_payment_addresses_data_trigger_v0( + document_transition, + context, + platform_version, + ), + version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method: "validate_profile_payment_addresses_data_trigger".to_string(), + known_versions: vec![0], + received: version, + })), + } +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/profile_v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/profile_v0/mod.rs new file mode 100644 index 00000000000..53d35a343a0 --- /dev/null +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/profile_v0/mod.rs @@ -0,0 +1,86 @@ +use crate::error::execution::ExecutionError; +use crate::error::Error; +use dpp::consensus::state::data_trigger::data_trigger_condition_error::DataTriggerConditionError; +use dpp::data_contract::accessors::v0::DataContractV0Getters; + +use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::ProtocolError; +use drive::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0; +use drive::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::DocumentCreateTransitionActionAccessorsV0; +use drive::state_transition_action::batch::batched_transition::document_transition::document_replace_transition_action::DocumentReplaceTransitionActionAccessorsV0; +use drive::state_transition_action::batch::batched_transition::document_transition::DocumentTransitionAction; + +use crate::execution::types::state_transition_execution_context::StateTransitionExecutionContextMethodsV0; +use crate::execution::validation::state_transition::batch::data_triggers::{ + DataTriggerExecutionContext, DataTriggerExecutionResult, +}; +use dpp::version::PlatformVersion; + +/// DIP-33 profile payment address fields, storage form: type byte `0x00` +/// (P2PKH) or `0x01` (P2SH) followed by a 20-byte HASH160. +const PAYMENT_ADDRESS_FIELDS: [&str; 2] = ["corePaymentAddress", "platformPaymentAddress"]; + +/// Validates the DIP-33 payment address fields of a DashPay profile document +/// on creation and replacement. +/// +/// The schema constrains the fields to exactly 21 bytes; this trigger enforces +/// the remaining invariant the schema vocabulary cannot express: the leading +/// type byte must be `0x00` (P2PKH) or `0x01` (P2SH). Unlike a Base58Check +/// string, the storage form carries no checksum, so this check makes an +/// accepted value fully decodable as the advertised address type. +#[inline(always)] +pub(super) fn validate_profile_payment_addresses_data_trigger_v0( + document_transition: &DocumentTransitionAction, + context: &mut DataTriggerExecutionContext<'_>, + _platform_version: &PlatformVersion, +) -> Result { + let data_contract_fetch_info = document_transition.base().data_contract_fetch_info(); + let data_contract = &data_contract_fetch_info.contract; + let mut result = DataTriggerExecutionResult::default(); + + if context.state_transition_execution_context.in_dry_run() { + return Ok(result); + } + + let data = match document_transition { + DocumentTransitionAction::CreateAction(create_transition) => create_transition.data(), + DocumentTransitionAction::ReplaceAction(replace_transition) => replace_transition.data(), + _ => { + return Err(Error::Execution(ExecutionError::DataTriggerExecutionError( + format!( + "the Document Transition {} isn't 'CREATE' or 'REPLACE'", + document_transition.base().id() + ), + ))); + } + }; + + for field in PAYMENT_ADDRESS_FIELDS { + let Some(address_bytes) = data + .get_optional_binary_bytes(field) + .map_err(ProtocolError::ValueError)? + else { + continue; + }; + + if !matches!(address_bytes.first(), Some(0x00) | Some(0x01)) { + let err = DataTriggerConditionError::new( + data_contract.id(), + document_transition.base().id(), + format!( + "{field} must start with address type byte 0x00 (P2PKH) or 0x01 (P2SH), got {}", + address_bytes + .first() + .map(|byte| format!("0x{byte:02x}")) + .unwrap_or_else(|| "an empty value".to_string()), + ), + ); + + result.add_error(err); + + return Ok(result); + } + } + + Ok(result) +} 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 6c7bb06e665..a0a19039af8 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 @@ -91,6 +91,8 @@ mod creation_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let documents_batch_create_transition = @@ -141,6 +143,127 @@ mod creation_tests { .expect("expected to commit transaction"); } + #[tokio::test] + async fn should_enforce_profile_payment_address_type_bytes() { + use dpp::consensus::state::data_trigger::DataTriggerError; + use dpp::consensus::state::state_error::StateError; + + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut rng = StdRng::seed_from_u64(437); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let dashpay = platform + .drive + .cache + .system_data_contracts + .load_dashpay(platform_version) + .expect("expected the dashpay system contract"); + let dashpay_contract = dashpay.clone(); + + let profile = dashpay_contract + .document_type_for_name("profile") + .expect("expected a profile document type"); + + // (field under test, leading type byte, accepted by the data trigger) + let cases = [ + ("corePaymentAddress", 0x00u8, true), + ("corePaymentAddress", 0x01, true), + ("corePaymentAddress", 0x02, false), + ("corePaymentAddress", 0x3a, false), + ("platformPaymentAddress", 0x00, true), + ("platformPaymentAddress", 0x01, true), + ("platformPaymentAddress", 0x14, false), + ("platformPaymentAddress", 0xff, false), + ]; + + for (field, leading_byte, expect_valid) in cases { + let entropy = Bytes32::random_with_rng(&mut rng); + + let mut document = profile + .random_document_with_identifier_and_entropy( + &mut rng, + identity.id(), + entropy, + DocumentFieldFillType::FillIfNotRequired, + DocumentFieldFillSize::AnyDocumentFillSize, + platform_version, + ) + .expect("expected a random document"); + + // start from valid values for both fields, then set the case under test + set_valid_profile_payment_addresses(&mut document, profile); + + let mut address = vec![leading_byte]; + address.extend([0u8; 20]); + document.set(field, address.into()); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); + + let documents_batch_create_transition = + BatchTransition::new_document_creation_transition_from_document( + document, + profile, + entropy.0, + &key, + 2, + 0, + None, + &signer, + platform_version, + None, + ) + .await + .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"); + + // each case runs in its own discarded transaction, so state and + // identity nonces are untouched between cases + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![documents_batch_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + if expect_valid { + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "{field} with type byte 0x{leading_byte:02x} must be accepted" + ); + } else { + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError { + error: ConsensusError::StateError(StateError::DataTriggerError( + DataTriggerError::DataTriggerConditionError(_) + )), + .. + }], + "{field} with type byte 0x{leading_byte:02x} must be rejected" + ); + } + } + } + #[tokio::test] async fn test_document_creation_should_fail_when_creator_id_is_provided() { let platform_version = PlatformVersion::latest(); @@ -281,6 +404,8 @@ mod creation_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let documents_batch_create_transition = @@ -343,6 +468,8 @@ mod creation_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/coy.jpg".into()); let documents_batch_create_transition = @@ -441,6 +568,8 @@ mod creation_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + let max_field_size = platform_version.system_limits.max_field_value_size; let avatar_size = max_field_size + 1000; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs index 70df01a6e03..f69dfbaf868 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs @@ -10,7 +10,7 @@ mod deletion_tests { async fn test_document_delete_on_document_type_that_is_mutable_and_can_be_deleted() { run_document_delete_on_document_type_that_is_mutable_and_can_be_deleted_at_protocol_version( PlatformVersion::latest().protocol_version, - 1678920, + 1699160, ) .await; } @@ -78,6 +78,8 @@ mod deletion_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -789,6 +791,8 @@ mod deletion_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs index d9feceec18e..ec5a3d79634 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/replacement.rs @@ -13,7 +13,7 @@ mod replacement_tests { async fn test_document_replace_on_document_type_that_is_mutable() { run_document_replace_on_document_type_that_is_mutable_at_protocol_version( PlatformVersion::latest().protocol_version, - 1411320, + 1432760, ) .await; } @@ -74,6 +74,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -241,6 +243,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("displayName", original_name.into()); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -785,6 +789,8 @@ mod replacement_tests { platform_version, ) .expect("expected a random document"); + + set_valid_profile_payment_addresses(&mut document, profile); // Random fillers can produce a non-URI avatarUrl that fails JSON-schema // validation on Create. Pin it to a valid URI like the sibling tests do. document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -1240,6 +1246,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1371,6 +1379,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1448,7 +1458,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] displayName:string QBwBNNXXYCngB0er publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); let documents_batch_update_transition_1 = BatchTransition::new_document_replacement_transition_from_document( @@ -1529,7 +1539,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/drap[...(26)] displayName:string Ody publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/drap[...(26)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string Ody platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive @@ -1592,6 +1602,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1669,7 +1681,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] displayName:string QBwBNNXXYCngB0er publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_400_000_000, 901, 43, 1, false); //next epoch @@ -1751,7 +1763,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-17 04:53:20 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/cat.[...(23)] displayName:string Samuel publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-17 04:53:20 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/cat.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string Samuel platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_600_000_000, 902, 44, 1, false); //next epoch @@ -1795,7 +1807,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-19 12:26:40 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/drap[...(26)] displayName:string Ody publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-19 12:26:40 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/drap[...(26)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string Ody platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive @@ -1858,6 +1870,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1931,7 +1945,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] displayName:string QBwBNNXXYCngB0er publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_400_000_000, 901, 43, 1, false); //next epoch @@ -2013,7 +2027,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-17 04:53:20 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] displayName:string QBwBNNXXYCngB0er publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-17 04:53:20 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_600_000_000, 902, 44, 1, false); //next epoch @@ -2057,7 +2071,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-19 12:26:40 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] displayName:string QBwBNNXXYCngB0er publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-19 12:26:40 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive @@ -2120,6 +2134,8 @@ mod replacement_tests { ) .expect("expected a random document"); + set_valid_profile_payment_addresses(&mut document, profile); + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -2197,7 +2213,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] displayName:string QBwBNNXXYCngB0er publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-14 21:20:00 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/bob.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_400_000_000, 901, 43, 1, false); //next epoch @@ -2279,7 +2295,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-17 04:53:20 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/cat.[...(23)] displayName:string Samuel publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-17 04:53:20 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/cat.[...(23)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string Samuel platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_600_000_000, 905, 44, 2, true); //next epoch @@ -2323,7 +2339,7 @@ mod replacement_tests { .first() .expect("expected a document"); - assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-19 12:26:40 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/drap[...(26)] displayName:string Ody publicMessage:string 8XG7KBGNvm2 "); + assert_eq!(document.to_string(), "v0 : id:GcviwUsEr9Ji4rCrnnsgmVAghNaVPDumsfcagvBbBy45 owner_id:CisQdz2ej7EwWv8JbetSXBNsV4xsf8QsSS8tqp4tEf7V created_at:1970-01-14 21:20:00 updated_at:1970-01-19 12:26:40 avatarFingerprint:bytes d7b0e2b357c10312 avatarHash:bytes32 YonaRoE0hMgat53AYt5LTlQlIkKLReGpB7xNAqJ5HM8= avatarUrl:string http://test.com/drap[...(26)] corePaymentAddress:bytes 000000000000000000000000000000000000000000 displayName:string Ody platformPaymentAddress:bytes 010000000000000000000000000000000000000000 publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/mod.rs index 50a2d030648..41ddaad8029 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/mod.rs @@ -1,6 +1,22 @@ mod document; mod token; +/// Sets valid DIP-33 payment addresses on a random profile document when the +/// schema version carries the fields (dashpay v2, protocol version 14+): the +/// generator fills them with random bytes, and the data trigger requires a +/// valid leading type byte (0x00 P2PKH / 0x01 P2SH). +pub(crate) fn set_valid_profile_payment_addresses( + document: &mut dpp::document::Document, + profile: dpp::data_contract::document_type::DocumentTypeRef, +) { + if profile.properties().contains_key("corePaymentAddress") { + document.set("corePaymentAddress", vec![0u8; 21].into()); + let mut platform_payment_address = vec![1u8]; + platform_payment_address.extend([0u8; 20]); + document.set("platformPaymentAddress", platform_payment_address.into()); + } +} + use crate::platform_types::platform_state::PlatformStateV0Methods; use dpp::data_contract::accessors::v0::DataContractV0Getters; diff --git a/packages/rs-drive/src/cache/data_contract.rs b/packages/rs-drive/src/cache/data_contract.rs index f3dbbf3479d..8b658989a20 100644 --- a/packages/rs-drive/src/cache/data_contract.rs +++ b/packages/rs-drive/src/cache/data_contract.rs @@ -1,5 +1,6 @@ use crate::drive::contract::DataContractFetchInfo; use dpp::data_contract::accessors::v0::DataContractV0Getters; +use moka::ops::compute::Op; use moka::sync::Cache; use std::sync::Arc; @@ -20,14 +21,41 @@ impl DataContractCache { /// Inserts DataContract to block cache /// otherwise to goes to global cache + /// + /// The insert is skipped if the cache already holds the same contract at a + /// **higher** version. Contract versions increase strictly monotonically — + /// the data contract update transition enforces `new == old + 1`, and system + /// contract migrations bump the version — so an insert carrying a lower + /// version is always a delayed writer racing a newer copy in, never fresh + /// information. CONSENSUS-CRITICAL: a read-only query thread fetches from + /// committed state without a transaction and populates the global cache from + /// what it read. If such a thread reads a contract, gets descheduled while + /// block execution rewrites that contract, and performs its insert after the + /// block cache is promoted, an unconditional insert would clobber the newer + /// contract with the stale one — and block execution would then serialize + /// documents against a different contract than a node whose cache was cold, + /// producing a different app hash from the same block. The check-and-insert + /// is atomic per key via moka's compute API, so there is no window between + /// the version comparison and the write. Same-version inserts still + /// overwrite: re-inserting an identical contract with a freshly calculated + /// fee is the normal cache-hit fee path. pub fn insert(&self, fetch_info: Arc, is_block_cache: bool) { let data_contract_id_bytes = fetch_info.contract.id().to_buffer(); - if is_block_cache { - self.block_cache.insert(data_contract_id_bytes, fetch_info); + let cache = if is_block_cache { + &self.block_cache } else { - self.global_cache.insert(data_contract_id_bytes, fetch_info); - } + &self.global_cache + }; + + cache + .entry(data_contract_id_bytes) + .and_compute_with(|existing| match existing { + Some(entry) if entry.value().contract.version() > fetch_info.contract.version() => { + Op::Nop + } + _ => Op::Put(Arc::clone(&fetch_info)), + }); } /// Tries to get a data contract from block cache if present @@ -166,6 +194,104 @@ mod tests { } } + mod insert { + use super::*; + use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters}; + + /// Two copies of the SAME contract (same id) at the given versions. The + /// fixture generates a fresh contract id per call, so both copies must + /// derive from a single fixture. + fn same_contract_at_versions( + first: u32, + second: u32, + ) -> (Arc, Arc) { + let fetch_info = DataContractFetchInfo::dpns_contract_fixture( + PlatformVersion::latest().protocol_version, + ); + let mut first_info = fetch_info.clone(); + first_info.contract.set_version(first); + let mut second_info = fetch_info; + second_info.contract.set_version(second); + (Arc::new(first_info), Arc::new(second_info)) + } + + /// A delayed insert carrying an older contract version must not clobber a newer + /// entry. This is the query-thread race: a read-only query reads a contract from + /// committed state, is descheduled while block execution rewrites the contract, + /// and performs its cache insert only after the migrated contract was promoted + /// to the global cache. + #[test] + fn test_insert_does_not_overwrite_newer_version_with_older() { + let data_contract_cache = DataContractCache::new(10, 10); + + let (stale, newer) = same_contract_at_versions(1, 2); + let contract_id = newer.contract.id().to_buffer(); + data_contract_cache.insert(newer, false); + + // The delayed stale insert + data_contract_cache.insert(stale, false); + + let cached = data_contract_cache + .get(contract_id, false) + .expect("should be present"); + assert_eq!(cached.contract.version(), 2); + } + + /// Same-version inserts must overwrite: re-inserting the same contract with a + /// freshly calculated fee is the normal cache-hit fee path. + #[test] + fn test_insert_overwrites_same_version() { + let data_contract_cache = DataContractCache::new(10, 10); + + let (mut original, mut with_fee) = same_contract_at_versions(1, 1); + let contract_id = original.contract.id().to_buffer(); + Arc::make_mut(&mut original).fee = None; + data_contract_cache.insert(original, false); + + Arc::make_mut(&mut with_fee).fee = + Some(dpp::fee::fee_result::FeeResult::new_from_processing_fee(1)); + data_contract_cache.insert(with_fee, false); + + let cached = data_contract_cache + .get(contract_id, false) + .expect("should be present"); + assert!(cached.fee.is_some(), "same-version insert must overwrite"); + } + + /// The full race, end to end: block execution seeds the migrated (newer) + /// contract into the block cache, the block finalizes and promotes it to the + /// global cache, and only then does the delayed query thread insert the + /// pre-migration contract it read before the rewrite. The promoted contract + /// must survive. + #[test] + fn test_delayed_stale_insert_after_promotion_does_not_stick() { + let data_contract_cache = DataContractCache::new(10, 10); + + // The pre-migration contract, as read from committed state by a query + // thread that will be descheduled before its insert. + let (stale, migrated) = same_contract_at_versions(1, 2); + let contract_id = stale.contract.id().to_buffer(); + + // Block execution writes the migrated contract and seeds the block cache. + data_contract_cache.insert(migrated, true); + + // The block finalizes: block cache promotes to global. + data_contract_cache.merge_and_clear_block_cache(); + + // The query thread wakes up and performs its stale insert. + data_contract_cache.insert(stale, false); + + let cached = data_contract_cache + .get(contract_id, false) + .expect("should be present"); + assert_eq!( + cached.contract.version(), + 2, + "the promoted migrated contract must survive a delayed stale insert" + ); + } + } + mod remove { use super::*; diff --git a/packages/rs-drive/src/drive/contract/refresh_cache/mod.rs b/packages/rs-drive/src/drive/contract/refresh_cache/mod.rs index e023cb155e6..1971fb7003e 100644 --- a/packages/rs-drive/src/drive/contract/refresh_cache/mod.rs +++ b/packages/rs-drive/src/drive/contract/refresh_cache/mod.rs @@ -20,7 +20,14 @@ impl Drive { /// consult the cache first and hand back the very entry being replaced; worse, a /// read-only query thread — which reads committed state with no transaction and does /// populate the global cache — could race a pre-write copy back in between the eviction - /// and the re-seed. + /// and the re-seed. The other half of that race — a query thread that read the + /// pre-migration contract, was descheduled, and performs its cache insert only after the + /// migrated contract was promoted to the global cache — is closed by the monotonic + /// version guard in [`DataContractCache::insert`], which requires every migration rewrite + /// to bump the contract's version (the v13 DPNS rewrite goes 1 → 2, the v14 DashPay + /// rewrite likewise). + /// + /// [`DataContractCache::insert`]: crate::cache::DataContractCache::insert /// * The result is seeded into the **block** cache whenever a transaction is supplied. The /// write is still uncommitted at that point: the block cache is the first cache a /// transactional read consults, it is promoted to the global cache when the block commits diff --git a/packages/rs-drive/tests/deterministic_root_hash.rs b/packages/rs-drive/tests/deterministic_root_hash.rs index dfa15e21558..63b362aecf1 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -302,7 +302,11 @@ 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", - _ => "14d9e2cdc3f25d1dfd079c1f9dd0d44db5bf73d397b04258449231a2d5bafda7", + // The DashPay contract v2 (protocol version 14, DIP-33) adds profile + // payment address fields, changing the contract's serialized bytes + // and therefore the root hash after insertion. + 9..=13 => "14d9e2cdc3f25d1dfd079c1f9dd0d44db5bf73d397b04258449231a2d5bafda7", + _ => "02a78b45ecac17a6c08ce352c22b888fcd044de9f4ff82983c5d8fd97e6e8793", }; assert_eq!( diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs index 4c053895190..ffce6d06ed1 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs @@ -248,6 +248,7 @@ pub struct DriveAbciValidationDataTriggerAndBindingVersions { #[derive(Clone, Debug, Default)] pub struct DriveAbciValidationDataTriggerVersions { pub create_contact_request_data_trigger: FeatureVersion, + pub validate_profile_payment_addresses_data_trigger: FeatureVersion, pub create_domain_data_trigger: FeatureVersion, pub create_identity_data_trigger: FeatureVersion, pub create_feature_flag_data_trigger: FeatureVersion, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs index 89f540a9f3e..c772961c230 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs @@ -113,6 +113,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V1: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs index 757d5770fbf..899f366268d 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs @@ -150,10 +150,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V10: DriveAbciValidationVersions = fetch_documents_for_transitions_knowing_contract_and_document_type: 1, fetch_document_with_id: 1, data_triggers: DriveAbciValidationDataTriggerAndBindingVersions { - // PROTOCOL_VERSION_13: v1 drops the reject bindings for - // Transfer, Purchase and UpdatePrice on DPNS `domain` - // documents, enabling username transfers and sales. - bindings: 1, + // PROTOCOL_VERSION_14: v2 adds DashPay `profile` + // Create/Replace triggers enforcing the DIP-33 + // payment-address type byte (0x00 P2PKH / 0x01 P2SH). + // It keeps v1's PROTOCOL_VERSION_13 change: no reject + // bindings for Transfer, Purchase and UpdatePrice on + // DPNS `domain` documents (username transfers/sales). + bindings: 2, triggers: DriveAbciValidationDataTriggerVersions { // PROTOCOL_VERSION_12 (v3.1 hard fork): triggers // that perform drive reads now have `_v1` versions @@ -161,6 +164,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V10: DriveAbciValidationVersions = // outer execution_context. v0 versions remain // byte-identical to PV11 (don't bill). create_contact_request_data_trigger: 1, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 1, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs index b125c76a94a..f4de9851115 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs @@ -113,6 +113,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V2: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs index 103dcf903ac..f517f8f9670 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs @@ -113,6 +113,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V3: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs index b5ff34370cb..93bfd9d25a7 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs @@ -116,6 +116,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V4: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs index 7704f76b546..afeade81a8a 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs @@ -117,6 +117,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V5: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs index 6119b8ce61b..bb107a9bb96 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs @@ -120,6 +120,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V6: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs index c28d082c450..883c33ede3f 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs @@ -114,6 +114,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V7: DriveAbciValidationVersions = bindings: 0, triggers: DriveAbciValidationDataTriggerVersions { create_contact_request_data_trigger: 0, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 0, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs index c44d1a20c29..69c237d8683 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs @@ -167,6 +167,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V8: DriveAbciValidationVersions = // outer execution_context. v0 versions remain // byte-identical to PV11 (don't bill). create_contact_request_data_trigger: 1, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 1, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v9.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v9.rs index 0a2119580e8..d2404d18557 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v9.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v9.rs @@ -163,6 +163,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V9: DriveAbciValidationVersions = // outer execution_context. v0 versions remain // byte-identical to PV11 (don't bill). create_contact_request_data_trigger: 1, + validate_profile_payment_addresses_data_trigger: 0, create_domain_data_trigger: 1, create_identity_data_trigger: 0, create_feature_flag_data_trigger: 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 4605c85a86b..1f01823d67a 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 @@ -1,5 +1,6 @@ pub mod v1; pub mod v2; +pub mod v3; use crate::version::FeatureVersion; diff --git a/packages/rs-platform-version/src/version/system_data_contract_versions/v3.rs b/packages/rs-platform-version/src/version/system_data_contract_versions/v3.rs new file mode 100644 index 00000000000..94c03f7dd8a --- /dev/null +++ b/packages/rs-platform-version/src/version/system_data_contract_versions/v3.rs @@ -0,0 +1,17 @@ +use crate::version::system_data_contract_versions::SystemDataContractVersions; + +// PROTOCOL_VERSION_14: DashPay contract v2 adds the optional public payment +// address fields to the `profile` document type (`corePaymentAddress`, +// `platformPaymentAddress`) per DIP-33. v2 (dashpay: 1) remains for +// PROTOCOL_VERSION_13 chain replay. +pub const SYSTEM_DATA_CONTRACT_VERSIONS_V3: SystemDataContractVersions = + SystemDataContractVersions { + withdrawals: 1, + dpns: 2, + dashpay: 2, + masternode_reward_shares: 1, + wallet: 1, + token_history: 1, + keyword_search: 1, + document_history: 1, + }; diff --git a/packages/rs-platform-version/src/version/v14.rs b/packages/rs-platform-version/src/version/v14.rs index a5c0aa7433b..984a7e46d64 100644 --- a/packages/rs-platform-version/src/version/v14.rs +++ b/packages/rs-platform-version/src/version/v14.rs @@ -24,7 +24,7 @@ use crate::version::drive_abci_versions::DriveAbciVersion; use crate::version::drive_versions::v9::DRIVE_VERSION_V9; use crate::version::fee::v2::FEE_VERSION2; use crate::version::protocol_version::PlatformVersion; -use crate::version::system_data_contract_versions::v2::SYSTEM_DATA_CONTRACT_VERSIONS_V2; +use crate::version::system_data_contract_versions::v3::SYSTEM_DATA_CONTRACT_VERSIONS_V3; use crate::version::system_limits::v3::SYSTEM_LIMITS_V3; use crate::version::ProtocolVersion; @@ -142,7 +142,7 @@ pub const PLATFORM_V14: PlatformVersion = PlatformVersion { methods: DPP_METHOD_VERSIONS_V2, factory_versions: DPP_FACTORY_VERSIONS_V1, }, - system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V2, + system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V3, // changed: DashPay v2 adds profile payment address fields (DIP-33) fee_version: FEE_VERSION2, system_limits: SYSTEM_LIMITS_V3, consensus: ConsensusVersions {