From 2e0d766b6eef67f75f3f13320031ea6c39ea5eec Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 12 Aug 2026 18:51:55 +0700 Subject: [PATCH 1/4] feat(dpp)!: dashpay profile payment addresses for core and platform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DashPay contract v2 (protocol version 14): the profile document gains two optional public payment fields — corePaymentAddress (position 5) and platformPaymentAddress (position 6), both the 21-byte storage form (variant byte 0x00 P2PKH / 0x01 P2SH + 20-byte HASH160). Payments to them are intentionally public and linkable to the profile (per DIP-33, the public tier); no derivation-source publishing. Follows the DPNS-v2 versioning pattern: schema/v2 + system_data_contract_versions v3 (dashpay: 2) at PV14, with the contract reload on the first block after the protocol change. Rebaselines PV14 fees (check_tx, replace, delete), profile fixture strings, and the deterministic root hash. Extracted from the larger payment-addresses branch; payment detection key purposes and stealth derivation follow in a separate PR. Co-Authored-By: Claude Fable 5 --- .../schema/v2/dashpay.schema.json | 271 ++++++++++++++++++ packages/dashpay-contract/src/lib.rs | 8 +- packages/dashpay-contract/src/v2/mod.rs | 11 + .../src/execution/check_tx/v0/mod.rs | 10 +- .../v0/mod.rs | 117 ++++++++ .../batch/tests/document/deletion.rs | 2 +- .../batch/tests/document/replacement.rs | 24 +- packages/rs-drive/src/cache/data_contract.rs | 134 ++++++++- .../src/drive/contract/refresh_cache/mod.rs | 9 +- .../rs-drive/tests/deterministic_root_hash.rs | 6 +- .../system_data_contract_versions/mod.rs | 1 + .../system_data_contract_versions/v3.rs | 17 ++ .../rs-platform-version/src/version/v14.rs | 4 +- 13 files changed, 585 insertions(+), 29 deletions(-) create mode 100644 packages/dashpay-contract/schema/v2/dashpay.schema.json create mode 100644 packages/dashpay-contract/src/v2/mod.rs create mode 100644 packages/rs-platform-version/src/version/system_data_contract_versions/v3.rs 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..3f91b1c09dd --- /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. Clients render it as Base58Check for the network they are on and MUST validate the type byte before paying. 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", + "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..ee8dcfb64dd 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 + 24002786230 ); 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 + 24005572460 ); let check_result = platform @@ -1634,7 +1634,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002489210 + 24002786230 ); platform @@ -1721,7 +1721,7 @@ mod tests { assert_eq!( update_processing_result.aggregated_fees().processing_fee, - 27002504030 + 27002844910 ); let check_result = platform @@ -2093,7 +2093,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002489210 + 24002786230 ); 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..3bbce63b0d3 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,95 @@ 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 payment address fields 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" + ); + } + // 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/tests/document/deletion.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs index 70df01a6e03..9100678840f 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; } 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..c762f9cbacb 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; } @@ -1448,7 +1448,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); let documents_batch_update_transition_1 = BatchTransition::new_document_replacement_transition_from_document( @@ -1529,7 +1529,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Ody platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive @@ -1669,7 +1669,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_400_000_000, 901, 43, 1, false); //next epoch @@ -1751,7 +1751,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Samuel platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_600_000_000, 902, 44, 1, false); //next epoch @@ -1795,7 +1795,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Ody platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive @@ -1931,7 +1931,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_400_000_000, 901, 43, 1, false); //next epoch @@ -2013,7 +2013,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_600_000_000, 902, 44, 1, false); //next epoch @@ -2057,7 +2057,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive @@ -2197,7 +2197,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_400_000_000, 901, 43, 1, false); //next epoch @@ -2279,7 +2279,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Samuel platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); fast_forward_to_block(&platform, 1_600_000_000, 905, 44, 2, true); //next epoch @@ -2323,7 +2323,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 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Ody platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d publicMessage:string 8XG7KBGNvm2 "); let issues = platform .drive 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..77731a84e08 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", + _ => "3fd2fdeb5b8cb0bd12136382096a4c6b60f480ee32d05528a781c50dede3abfc", }; assert_eq!( 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 { From f1ef4cc7f36bbab1cc02fdf1a895fafe400f19b0 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 12 Aug 2026 21:04:25 +0700 Subject: [PATCH 2/4] fix(drive-abci): enforce payment-address type bytes with a data trigger, cover the v13-v14 boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review: the 21-byte schema constraint alone accepted values whose leading byte is not a supported address type. A new data trigger (bindings list v2, PV14) rejects profile creates/replaces whose corePaymentAddress or platformPaymentAddress does not start with 0x00 (P2PKH) or 0x01 (P2SH) — unlike a Base58Check string, the storage form has no checksum, so this check makes accepted values fully decodable. - profile fixtures now carry valid type bytes (guarded to schema versions that have the fields, so protocol-version-11 variants are untouched) - acceptance/rejection matrix test for both fields across leading bytes - boundary test drives the v13→v14 upgrade through the production perform_events_on_first_block_of_protocol_change dispatcher with a stored v1 profile and warmed contract cache, asserting both fields absent before, present after, and the legacy profile still readable - pre-upgrade assertion now also covers platformPaymentAddress - rebaselines for the schema description change (check_tx fees, root hash) Co-Authored-By: Claude Fable 5 --- .../schema/v2/dashpay.schema.json | 4 +- .../src/execution/check_tx/v0/mod.rs | 10 +- .../v0/mod.rs | 221 +++++++++++++++++- .../batch/data_triggers/bindings/list/mod.rs | 7 +- .../data_triggers/bindings/list/v2/mod.rs | 108 +++++++++ .../data_triggers/triggers/dashpay/mod.rs | 29 +++ .../triggers/dashpay/profile_v0/mod.rs | 86 +++++++ .../batch/tests/document/creation.rs | 164 +++++++++++++ .../batch/tests/document/deletion.rs | 20 ++ .../batch/tests/document/replacement.rs | 102 +++++++- .../rs-drive/tests/deterministic_root_hash.rs | 2 +- .../drive_abci_validation_versions/mod.rs | 1 + .../drive_abci_validation_versions/v1.rs | 1 + .../drive_abci_validation_versions/v10.rs | 3 +- .../drive_abci_validation_versions/v2.rs | 1 + .../drive_abci_validation_versions/v3.rs | 1 + .../drive_abci_validation_versions/v4.rs | 1 + .../drive_abci_validation_versions/v5.rs | 1 + .../drive_abci_validation_versions/v6.rs | 1 + .../drive_abci_validation_versions/v7.rs | 1 + .../drive_abci_validation_versions/v8.rs | 1 + .../drive_abci_validation_versions/v9.rs | 1 + 22 files changed, 744 insertions(+), 22 deletions(-) create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/bindings/list/v2/mod.rs create mode 100644 packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/data_triggers/triggers/dashpay/profile_v0/mod.rs diff --git a/packages/dashpay-contract/schema/v2/dashpay.schema.json b/packages/dashpay-contract/schema/v2/dashpay.schema.json index 3f91b1c09dd..db5ce9b4dcd 100644 --- a/packages/dashpay-contract/schema/v2/dashpay.schema.json +++ b/packages/dashpay-contract/schema/v2/dashpay.schema.json @@ -64,7 +64,7 @@ "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. Clients render it as Base58Check for the network they are on and MUST validate the type byte before paying. Payments to it are publicly linkable to this profile.", + "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": { @@ -72,7 +72,7 @@ "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", + "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 } }, 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 ee8dcfb64dd..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, - 24002786230 + 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, - 24005572460 + 24005633260 ); let check_result = platform @@ -1634,7 +1634,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002786230 + 24002816630 ); platform @@ -1721,7 +1721,7 @@ mod tests { assert_eq!( update_processing_result.aggregated_fees().processing_fee, - 27002844910 + 27002879350 ); let check_result = platform @@ -2093,7 +2093,7 @@ mod tests { assert_eq!( processing_result.aggregated_fees().processing_fee, - 24002786230 + 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 3bbce63b0d3..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 @@ -1086,7 +1086,11 @@ mod tests { .collect::>(); assert!( !pre_profile.iter().any(|p| p == "corePaymentAddress"), - "profile must not carry payment address fields before transition_to_version_14" + "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); @@ -1127,6 +1131,221 @@ mod tests { ); } + /// 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..615f7fb5335 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,16 @@ mod creation_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let documents_batch_create_transition = @@ -141,6 +151,130 @@ mod creation_tests { .expect("expected to commit transaction"); } + #[tokio::test] + async fn test_profile_payment_address_type_byte_enforcement() { + 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 + 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()); + + 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 +415,16 @@ mod creation_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let documents_batch_create_transition = @@ -343,6 +487,16 @@ mod creation_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/coy.jpg".into()); let documents_batch_create_transition = @@ -441,6 +595,16 @@ mod creation_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + 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 9100678840f..7d41190b8e4 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 @@ -78,6 +78,16 @@ mod deletion_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -789,6 +799,16 @@ mod deletion_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + 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 c762f9cbacb..fb0127ea8d3 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 @@ -74,6 +74,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -241,6 +251,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("displayName", original_name.into()); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -785,6 +805,16 @@ mod replacement_tests { platform_version, ) .expect("expected a random document"); + + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } // 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 +1270,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1371,6 +1411,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1448,7 +1498,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +1579,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Ody platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +1642,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1669,7 +1729,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +1811,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Samuel platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +1855,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Ody platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +1918,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -1931,7 +2001,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +2083,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +2127,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +2190,16 @@ mod replacement_tests { ) .expect("expected a random document"); + // The generator fills the optional payment address fields (present + // from dashpay v2 / PV14 on) with random bytes; the data trigger + // requires a valid leading type byte + 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()); + } + document.set("avatarUrl", "http://test.com/bob.jpg".into()); let mut altered_document = document.clone(); @@ -2197,7 +2277,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string QBwBNNXXYCngB0er platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +2359,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Samuel platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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 +2403,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)] corePaymentAddress:bytes 3a60e0e081b6cdfb581eee33ac1d025e611d70de0b displayName:string Ody platformPaymentAddress:bytes 148bb6f3729a170bfa82d4baace1d92fa6b5233d1d 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/tests/deterministic_root_hash.rs b/packages/rs-drive/tests/deterministic_root_hash.rs index 77731a84e08..63b362aecf1 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -306,7 +306,7 @@ mod tests { // payment address fields, changing the contract's serialized bytes // and therefore the root hash after insertion. 9..=13 => "14d9e2cdc3f25d1dfd079c1f9dd0d44db5bf73d397b04258449231a2d5bafda7", - _ => "3fd2fdeb5b8cb0bd12136382096a4c6b60f480ee32d05528a781c50dede3abfc", + _ => "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..5aca3480582 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 @@ -153,7 +153,7 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V10: DriveAbciValidationVersions = // PROTOCOL_VERSION_13: v1 drops the reject bindings for // Transfer, Purchase and UpdatePrice on DPNS `domain` // documents, enabling username transfers and sales. - bindings: 1, + bindings: 2, triggers: DriveAbciValidationDataTriggerVersions { // PROTOCOL_VERSION_12 (v3.1 hard fork): triggers // that perform drive reads now have `_v1` versions @@ -161,6 +161,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, From 506be160b87cfb85df53458a65c81bd48ac1bd99 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 12 Aug 2026 21:12:35 +0700 Subject: [PATCH 3/4] test(drive-abci): shared payment-address fixture helper, should-prefixed test name Co-Authored-By: Claude Fable 5 --- .../batch/tests/document/creation.rs | 47 ++--------- .../batch/tests/document/deletion.rs | 20 +---- .../batch/tests/document/replacement.rs | 80 ++----------------- .../state_transitions/batch/tests/mod.rs | 16 ++++ 4 files changed, 32 insertions(+), 131 deletions(-) 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 615f7fb5335..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,15 +91,7 @@ mod creation_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -152,7 +144,7 @@ mod creation_tests { } #[tokio::test] - async fn test_profile_payment_address_type_byte_enforcement() { + async fn should_enforce_profile_payment_address_type_bytes() { use dpp::consensus::state::data_trigger::DataTriggerError; use dpp::consensus::state::state_error::StateError; @@ -207,10 +199,7 @@ mod creation_tests { .expect("expected a random document"); // start from valid values for both fields, then set the case under test - 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()); + set_valid_profile_payment_addresses(&mut document, profile); let mut address = vec![leading_byte]; address.extend([0u8; 20]); @@ -415,15 +404,7 @@ mod creation_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -487,15 +468,7 @@ mod creation_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/coy.jpg".into()); @@ -595,15 +568,7 @@ mod creation_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + 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 7d41190b8e4..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 @@ -78,15 +78,7 @@ mod deletion_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -799,15 +791,7 @@ mod deletion_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); 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 fb0127ea8d3..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 @@ -74,15 +74,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -251,15 +243,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("displayName", original_name.into()); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -806,15 +790,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + 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()); @@ -1270,15 +1246,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -1411,15 +1379,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -1642,15 +1602,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -1918,15 +1870,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); @@ -2190,15 +2134,7 @@ mod replacement_tests { ) .expect("expected a random document"); - // The generator fills the optional payment address fields (present - // from dashpay v2 / PV14 on) with random bytes; the data trigger - // requires a valid leading type byte - 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()); - } + set_valid_profile_payment_addresses(&mut document, profile); document.set("avatarUrl", "http://test.com/bob.jpg".into()); 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; From cbd7e9cc9f4493e063049f86891adb5a32f833f0 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 12 Aug 2026 22:16:56 +0700 Subject: [PATCH 4/4] docs(platform-version): describe bindings v2 at its v10 use site Co-Authored-By: Claude Fable 5 --- .../drive_abci_validation_versions/v10.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 5aca3480582..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,9 +150,12 @@ 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. + // 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