From 784d18a2ac87f38a604aa992c15a738192fa49c6 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 7 Nov 2026 23:22:19 +0700 Subject: [PATCH] feat(platform)!: dashpay contract v3 declares private contact requests (PV15) DashPay v3 gains `privateContactRequest`, a private document type of 589-byte entries: a contact request encrypted to its recipient and appended by PrivateDocumentCreateFromShieldedPool, with no identity and paid from the shielded pool. Its properties describe the plaintext the recipient decrypts: accountReference, the DIP-15 compact extendedPublicKey (69 bytes), and the optional accountLabel (at most 63 bytes), autoAcceptProof (38 to 102) and shieldedAddress (43). At their largest they encode to 290 bytes (DashPay keeps sized integer types off, so accountReference is an 8-byte i64), and the private document envelope adds 299, so 589 is the smallest entry size that holds every field at DIP-15's bounds. A dpp test pins the size to the properties. Genesis creates the type's store as a contract insert does, and the upgrade to protocol version 15 as a contract update adding a private type does. A drive-abci test appends two entries to it with a real Orchard bundle, and the v14 to v15 upgrade test checks the store. Pins at the latest protocol version move with the larger contract: the check_tx DashPay fixture pays one more document type registration fee (fixtures funded with 0.02 Dash more), two document fees bill a larger sibling node, and the state roots of nullifier_batch_guard and Drive's deterministic root hash change. Co-Authored-By: Claude Opus 5.5 --- .../schema/v3/dashpay.schema.json | 50 ++++++++++ packages/dashpay-contract/src/v3/mod.rs | 8 ++ packages/rs-dpp/src/system_data_contracts.rs | 92 +++++++++++++++++++ .../src/execution/check_tx/v0/mod.rs | 44 +++++---- .../v0/mod.rs | 17 ++++ .../batch/tests/document/deletion.rs | 3 +- .../batch/tests/document/replacement.rs | 3 +- .../token/shielded/nullifier_batch_guard.rs | 20 ++-- .../tests.rs | 60 ++++++++++++ .../rs-drive/tests/deterministic_root_hash.rs | 5 +- .../rs-platform-version/src/version/v15.rs | 11 ++- 11 files changed, 279 insertions(+), 34 deletions(-) diff --git a/packages/dashpay-contract/schema/v3/dashpay.schema.json b/packages/dashpay-contract/schema/v3/dashpay.schema.json index 433cd3756e5..a7c3f794778 100644 --- a/packages/dashpay-contract/schema/v3/dashpay.schema.json +++ b/packages/dashpay-contract/schema/v3/dashpay.schema.json @@ -297,5 +297,55 @@ "accountReference" ], "additionalProperties": false + }, + "privateContactRequest": { + "type": "object", + "description": "A contact request nobody else can see: an entry encrypted to the recipient's DashPay decryption key and appended, without an identity, by a transition paid from the shielded pool. These properties describe the plaintext the recipient decrypts; the chain never reads it.", + "private": { + "entrySize": 589 + }, + "documentsMutable": false, + "canBeDeleted": false, + "properties": { + "accountReference": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295, + "position": 0 + }, + "extendedPublicKey": { + "type": "array", + "byteArray": true, + "minItems": 69, + "maxItems": 69, + "description": "The DIP-15 compact extended public key the recipient pays to: the parent fingerprint (4 bytes), the chain code (32) and the public key (33).", + "position": 1 + }, + "accountLabel": { + "type": "string", + "maxBytes": 63, + "position": 2 + }, + "autoAcceptProof": { + "type": "array", + "byteArray": true, + "minItems": 38, + "maxItems": 102, + "position": 3 + }, + "shieldedAddress": { + "type": "array", + "byteArray": true, + "minItems": 43, + "maxItems": 43, + "description": "Raw Orchard receiving address for this contact: 11-byte diversifier followed by 32-byte diversified transmission key.", + "position": 4 + } + }, + "required": [ + "accountReference", + "extendedPublicKey" + ], + "additionalProperties": false } } diff --git a/packages/dashpay-contract/src/v3/mod.rs b/packages/dashpay-contract/src/v3/mod.rs index 628e3bac60e..3e6cdf5ea1b 100644 --- a/packages/dashpay-contract/src/v3/mod.rs +++ b/packages/dashpay-contract/src/v3/mod.rs @@ -18,6 +18,14 @@ use serde_json::Value; // hold the first byte of each DIP-33 payment address to the address type, the // check the profile data trigger made up to protocol version 14. Their descriptions say // so in place of v2's mention of the data trigger. +// +// And it adds one type: +// +// - `privateContactRequest`, a private type of 589-byte entries: a contact request +// encrypted to the recipient and appended by `PrivateDocumentCreateFromShieldedPool` +// without an identity. Its properties describe the plaintext the recipient decrypts. +// At their largest they take 290 bytes (`accountReference` is an i64, as DashPay +// stores every integer), which with the envelope's 299 bytes fill an entry. pub fn load_documents_schemas() -> Result { serde_json::from_str(include_str!("../../schema/v3/dashpay.schema.json")) diff --git a/packages/rs-dpp/src/system_data_contracts.rs b/packages/rs-dpp/src/system_data_contracts.rs index 3c5a3d7ce94..4c8ac1cf02e 100644 --- a/packages/rs-dpp/src/system_data_contracts.rs +++ b/packages/rs-dpp/src/system_data_contracts.rs @@ -1484,6 +1484,10 @@ mod dashpay_v3_tests { DistinctFrom, DocumentPropertyReferenceTarget, DocumentPropertyType, DocumentTypeRef, EncryptedFor, EncryptedForRecipient, EncryptionScheme, }; + use crate::document::serialization_traits::DocumentPlatformConversionMethodsV0; + use crate::document::DocumentV0; + use platform_value::{Identifier, Value}; + use std::collections::BTreeMap; /// DashPay v3, registered as the chain registers it, under full validation. fn dashpay() -> DataContract { @@ -1503,6 +1507,94 @@ mod dashpay_v3_tests { /// from then on, so every property v1 or v2 declares must be stored exactly as /// it was. DashPay keeps sized integer types off: an integer is an i64 whatever /// its bounds, so the key indexes' new `maximum` changes nothing stored. + /// The bytes of an envelope around a private entry's payload, fixed by the private document + /// envelope (version 1): the commitment (32), the ephemeral key (33), the body's tag (16) and + /// the sender-recovery ciphertext (81) around the body, and the body's own header before the + /// payload (version, auth mode, sender id, sender key id, auth, recipient id, payload length: + /// 1 + 1 + 32 + 4 + 65 + 32 + 2). + const PRIVATE_ENTRY_WRAPPER_BYTES: usize = 162; + const PRIVATE_ENTRY_BODY_HEADER_BYTES: usize = 137; + + /// The type's properties in the document property encoding, as a private entry's payload + /// carries them: each in position order, an optional one led by a presence byte. + fn payload(document_type: DocumentTypeRef, properties: &BTreeMap) -> Vec { + let mut bytes = vec![]; + for (name, property) in document_type.properties() { + match properties.get(name) { + Some(value) => { + if !property.required { + bytes.push(1); + } + bytes.extend( + property + .property_type + .encode_value_ref_with_size(value, property.required) + .expect("the value encodes"), + ); + } + None => bytes.push(0), + } + } + bytes + } + + /// `privateContactRequest` is private, and its entries take the smallest size that holds its + /// largest payload in the envelope: every property at its largest, `accountReference` an + /// 8-byte integer as DashPay stores every integer, is 290 bytes, so 137 + 290 + 162 = 589. + /// The entry size is frozen with the contract, so this pins it to the properties. + #[test] + fn should_size_private_contact_requests_to_hold_their_largest_payload() { + let contract = dashpay(); + let private_contact_request = document_type(&contract, "privateContactRequest"); + let entry_size = private_contact_request + .private_document() + .expect("privateContactRequest is private") + .entry_size() as usize; + assert!(!private_contact_request.documents_mutable()); + assert!(!private_contact_request.documents_can_be_deleted()); + + let largest = BTreeMap::from([ + ("accountReference".to_string(), Value::U32(u32::MAX)), + ("extendedPublicKey".to_string(), Value::Bytes(vec![1; 69])), + ("accountLabel".to_string(), Value::Text("a".repeat(63))), + ("autoAcceptProof".to_string(), Value::Bytes(vec![2; 102])), + ("shieldedAddress".to_string(), Value::Bytes(vec![3; 43])), + ]); + let largest_payload = payload(private_contact_request, &largest); + assert_eq!(largest_payload.len(), 290); + assert_eq!( + PRIVATE_ENTRY_BODY_HEADER_BYTES + largest_payload.len() + PRIVATE_ENTRY_WRAPPER_BYTES, + entry_size + ); + + // The payload is the document's own property encoding: a document of the type + // serializes to its system fields followed by exactly these bytes. + let document = DocumentV0 { + id: Identifier::from([4; 32]), + owner_id: Identifier::from([5; 32]), + properties: largest, + ..Default::default() + }; + let serialized = document + .serialize( + private_contact_request, + &contract, + PlatformVersion::latest(), + ) + .expect("the document serializes"); + assert!(serialized.ends_with(&largest_payload)); + + // The smallest payload: the two required properties and three absent ones. + let smallest = BTreeMap::from([ + ("accountReference".to_string(), Value::U32(0)), + ("extendedPublicKey".to_string(), Value::Bytes(vec![1; 69])), + ]); + assert_eq!( + payload(private_contact_request, &smallest).len(), + 8 + 69 + 3 + ); + } + #[test] fn should_store_every_dashpay_v1_and_v2_property_as_it_was_stored() { let v3 = dashpay(); 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 6a153311fe0..f0aaa21b279 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 @@ -657,7 +657,7 @@ mod tests { 166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44, ]), public_keys: BTreeMap::from([(1, key.clone())]), - balance: 25_000_000_000, // 0.25 Dash + balance: 27_000_000_000, // 0.27 Dash revision: 0, } .into(); @@ -737,8 +737,9 @@ mod tests { // 1.8 times its bytes, which are byte-billed, and checking its compiled size is // charged per compiled byte // and a document type's schema size is billed at least the values the - // repeated-key walk read; the DashPay v3 schema adds its declarations - 24004855740 + // repeated-key walk read; the DashPay v3 schema adds its declarations and the + // privateContactRequest type (one more document type registration fee) + 26005728080 ); let check_result = platform @@ -967,7 +968,7 @@ mod tests { 166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44, ]), public_keys: BTreeMap::from([(1, key.clone())]), - balance: 27_000_000_000, + balance: 29_000_000_000, revision: 0, } .into(); @@ -1329,7 +1330,7 @@ mod tests { 166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44, ]), public_keys: BTreeMap::from([(1, key.clone())]), - balance: 25_000_000_000, // 0.25 Dash + balance: 27_000_000_000, // 0.27 Dash revision: 0, } .into(); @@ -1412,8 +1413,9 @@ mod tests { // 1.8 times its bytes, which are byte-billed, and checking its compiled size is // charged per compiled byte // and a document type's schema size is billed at least the values the - // repeated-key walk read; the DashPay v3 schema adds its declarations - 24009711480 + // repeated-key walk read; the DashPay v3 schema adds its declarations and the + // privateContactRequest type (one more document type registration fee) + 26011456160 ); let check_result = platform @@ -1497,8 +1499,9 @@ mod tests { public_keys: BTreeMap::from([(1, key.clone())]), // we have enough balance only for 1 insertion (this is where this test is different); // from protocol version 15 one insertion stores the contract compiled, about 1.8 - // times its bytes, and DashPay v3 adds its declarations, so it needs more - balance: 24_400_000_000, + // times its bytes, and DashPay v3 adds its declarations and a document type, so it + // needs more + balance: 26_400_000_000, revision: 0, } .into(); @@ -1898,8 +1901,9 @@ mod tests { // 1.8 times its bytes, which are byte-billed, and checking its compiled size is // charged per compiled byte // and a document type's schema size is billed at least the values the - // repeated-key walk read; the DashPay v3 schema adds its declarations - 24004855740 + // repeated-key walk read; the DashPay v3 schema adds its declarations and the + // privateContactRequest type (one more document type registration fee) + 26005728080 ); platform @@ -1997,8 +2001,9 @@ mod tests { // about 1.8 times its bytes, which are byte-billed, and checking its compiled size // is charged per compiled byte // and a document type's schema size is billed at least the values the - // repeated-key walk read; the DashPay v3 schema adds its declarations - 29005647760 + // repeated-key walk read; the DashPay v3 schema adds its declarations and the + // privateContactRequest type (one more document type registration fee) + 31006514040 ); let check_result = platform @@ -2593,8 +2598,9 @@ mod tests { // 1.8 times its bytes, which are byte-billed, and checking its compiled size is // charged per compiled byte // and a document type's schema size is billed at least the values the - // repeated-key walk read; the DashPay v3 schema adds its declarations - 24004855740 + // repeated-key walk read; the DashPay v3 schema adds its declarations and the + // privateContactRequest type (one more document type registration fee) + 26005728080 ); platform @@ -4395,7 +4401,7 @@ mod tests { 166, 203, 222, 4, 64, 31, 215, 199, 149, 151, 190, 246, 251, 44, ]), public_keys: BTreeMap::from([(1, key.clone())]), - balance: 25_000_000_000, // 0.25 Dash + balance: 27_000_000_000, // 0.27 Dash revision: 0, } .into(); @@ -4663,7 +4669,7 @@ mod tests { let identity: Identity = IdentityV0 { id: Identifier::new([7; 32]), public_keys: BTreeMap::from([(1, key.clone())]), - balance: 25_000_000_000, // 0.25 Dash + balance: 27_000_000_000, // 0.27 Dash revision: 0, } .into(); @@ -4791,7 +4797,7 @@ mod tests { BasicError::SerializedObjectParsingError(_) )) ); - assert_eq!(balance, 25_000_000_000, "nothing is charged"); + assert_eq!(balance, 27_000_000_000, "nothing is charged"); } } @@ -4813,7 +4819,7 @@ mod tests { execution_result, StateTransitionExecutionResult::SuccessfulExecution { .. } ); - assert!(balance < 25_000_000_000, "the create is paid"); + assert!(balance < 27_000_000_000, "the create is paid"); } } } 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 d7dfd378053..d3b8ac9458b 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 @@ -2709,6 +2709,23 @@ mod tests { "v3 must read the v2 {document_type_name} back unchanged" ); } + + // v3's new private type gets its store with the upgrade, as a contract update adding a + // private type creates one: 589-byte entries, 512 to a chunk. + assert_eq!( + platform + .drive + .private_document_store_chunk_power_taking_entries( + SystemDataContract::Dashpay.id().as_bytes(), + "privateContactRequest", + 589, + None, + platform_version_15, + ) + .expect("expected to read the store"), + Some(9), + "the upgrade creates the privateContactRequest store" + ); } // test_transition_to_version_9 removed: requires prior state from versions 4-8 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 15648ab7bf9..430f7dfc475 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 @@ -19,7 +19,8 @@ mod deletion_tests { // genesis contracts tree, increasing the bytes billed when reading that tree // (protocol 14 only). The decision proposals and token market contracts add two more // at protocol 15, which reshape the tree so the contract read is billed fewer bytes (-47_280). - 1770160, + // DashPay v3's privateContactRequest type makes its sibling node larger (+2_860). + 1773020, ) .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 a19c1477808..df4a91b4dcb 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 @@ -527,7 +527,8 @@ mod replacement_tests { // genesis contracts tree, increasing the bytes billed when reading that tree // (protocol 14 only). The decision proposals and token market contracts add two more // at protocol 15, which reshape the tree so the contract read is billed fewer bytes (-47_280). - 1498860, + // DashPay v3's privateContactRequest type makes its sibling node larger (+2_860). + 1501720, ) .await; } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/shielded/nullifier_batch_guard.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/shielded/nullifier_batch_guard.rs index 7f8707cfeef..55f724b2fd7 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/shielded/nullifier_batch_guard.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/shielded/nullifier_batch_guard.rs @@ -734,7 +734,7 @@ async fn should_preserve_single_entry_full_processor_fees_and_result() { .value .unwrap() ), - "01b1aa765b58c73b580de019d98fdb10106003b0941b738769a96dc0feba8741" + "c08eb5f5971bb07143a63bc92d6fad5c593e77623ea7a7f178c857eb1ae48cea" ); assert_eq!( pool.platform @@ -2185,39 +2185,39 @@ async fn should_preserve_single_entry_actions_errors_operations_and_settled_stat let (expected_validation, expected_settled) = match case { "shield" => ( "6a823c2db94612652926e6b9c1ae7feec9c2547dfd4cfc4e474318da1c16d59c", - "761e92d28f8cef61e908c2ae3b24938b405868e468f44fd22a68f61da64b682c", + "c9577d238a283729bfce503563e22542356460aef2d4680eddf8c91a6981189b", ), "valid-payment" => ( "9388d5cf63c4a571b9fb009679e2e34d0b681acc88f0f5195e7287c7efbd92f7", - "5a8fa6e02357c351ba35b210eaaadb09cb9786d7b12d06b1483e75411c478d3f", + "22fc25da7ded9ec8ef0c63b691f3c5411c063072458379ff0bc449f4c61f4e33", ), "failed-payment" => ( "d9b5fe191841bea952494f2a45e3833f925f4d367a1937b2ef1235a1aa01f51e", - "1dee6b00d4f6ab7be85238448840e8a124b75260a445a3ec38b0c2283633544b", + "e106a9e7ec95fa9ae56956cb8d2eb55f92b2003c63e52b30b0e2a4015aa4ffea", ), "within-bundle-repeat" => ( "8023e03a697a20f6a78396a05503a4bc583ca8f69e19bd7ee07a6ae0ce7be478", - "5557745a60262645a038803fffb5c3e7f5a85b0822d0d81f515b30cfca8a5316", + "e5168d60f643ae3a98d325fbe4f61b179d85fc281e27784456d05fd2de17bc0b", ), "stored-repeat" => ( "05652ac85c972902e8c09d20a7d8586e17e9434a478b335da136ad85f73def01", - "a8fa01705072cdaddf01155bc21a707da71765cca36e829ed8689bc010721571", + "52f715a3ee906f0e79f474734f1441e61d8a68d4a293b7ea80196bc27c16416a", ), "open-mint-group" => ( "5c68381297c9bf68c5e85ebe6bb9e0f1289e2c07718eecfb5e6773d6009455bb", - "2cc8ac005b08d6bdba5713b81653f43d8db6d62f1f0c64f2e11dc987dd6587be", + "9b8a20a60c362b7cc16224aea3f79ff699c53643b2ecedbd10bec753edac0e5b", ), "closing-mint-group" => ( "2cedc16732c76e0605b5f46085db3d69c12936a5af55a2a82f52ec9e9ca67e38", - "cc45783c058f65155e440957bde48a991854eda553fe868586c4688e7e608963", + "d104393779df056d6444dfa4a69aaf4582913b6f00b61f2f53e062fdef250d47", ), "open-burn-group" => ( "e664a045f020ff84de1ca98f364fbabccfc141ad46aa82c79491a340bb0db5f8", - "a15f8c988bf4aac48480c32842c9e0b1d04b6304fbfd427b289dc7dc9fb54b8a", + "224d1a01832e9faf0f4c9d8af8f8c318855f74f5549976d1b1d1e48896d0ba93", ), "closing-burn-group" => ( "93e1732286f168c4a91d2765931e181c5721bfca1bfd5f93b8baacca03e00b2b", - "d799aab48f5ce1eb60b3076e576b663c982e59ac003b42210565753f68848591", + "5d8c544ca25dc3d9850bae5b5a2eaa17fd8ac36a486bdd48010c6f2e3c929d59", ), _ => unreachable!("enumerated characterization case"), }; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/private_document_create_from_shielded_pool/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/private_document_create_from_shielded_pool/tests.rs index 445c81ccc77..471b5422c39 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/private_document_create_from_shielded_pool/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/private_document_create_from_shielded_pool/tests.rs @@ -16,7 +16,9 @@ use dpp::consensus::state::state_error::StateError; use dpp::consensus::ConsensusError; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::conversion::value::v0::DataContractValueConversionMethodsV0; +use dpp::data_contract::document_type::accessors::DocumentTypeV2Getters; use dpp::data_contract::DataContract; +use dpp::data_contracts::SystemDataContract; use dpp::platform_value::string_encoding::Encoding; use dpp::platform_value::{BinaryData, Identifier}; use dpp::serialization::PlatformSerializable; @@ -31,6 +33,7 @@ use dpp::state_transition::private_document_create_from_shielded_pool_transition use dpp::state_transition::private_document_create_from_shielded_pool_transition::PrivateDocumentCreateFromShieldedPoolTransition; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; +use dpp::system_data_contracts::load_system_data_contract; use drive::drive::Drive; use grovedb_commitment_tree::{ Builder, BundleType, ClientMemoryCommitmentTree, DashMemo, ExtractedNoteCommitment, @@ -324,6 +327,63 @@ fn should_append_the_entries_behind_the_block_header_and_pay_exactly_the_fee() { } } +/// DashPay's `privateContactRequest` (DashPay v3, which a chain born at protocol version 15 +/// registers at genesis) takes entries: its store exists, and a transition appending two +/// 589-byte entries to it executes and leaves them in the store. +#[test] +fn should_append_private_contact_requests_to_dashpay() { + let platform_version = PlatformVersion::latest(); + let platform = setup_platform(); + let dashpay = load_system_data_contract(SystemDataContract::Dashpay, platform_version) + .expect("DashPay v3"); + let private_contact_request = dashpay + .document_type_for_name("privateContactRequest") + .expect("the privateContactRequest type"); + let entry_size = private_contact_request + .private_document() + .expect("privateContactRequest is private") + .entry_size(); + assert_eq!(entry_size, 589); + + let entries = entries(2, entry_size); + let (st, anchor) = proven_transition(dashpay.id(), "privateContactRequest", entries.clone()); + seed_pool(&platform, &anchor); + let platform_state = platform.state.load(); + let (fee_results, _) = process_state_transitions( + &platform, + &[st], + BlockInfo { + height: 3, + time_ms: 1_700_000_000_000, + ..Default::default() + }, + &platform_state, + ); + assert_eq!(fee_results[0].total_base_fee(), fee(&entries)); + + let page = platform + .drive + .fetch_private_document_entries( + dashpay.id().to_buffer(), + private_contact_request, + 0, + None, + platform_version, + ) + .expect("expected to read DashPay's store"); + assert_eq!(page.total_count, 2); + assert_eq!( + page.entries + .iter() + .map(|stored| stored.data.clone()) + .collect::>(), + entries + .iter() + .map(|entry| entry.to_vec()) + .collect::>() + ); +} + #[test] fn should_prove_the_spent_nullifiers_as_the_affected_state() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-drive/tests/deterministic_root_hash.rs b/packages/rs-drive/tests/deterministic_root_hash.rs index 80e3c346ffe..41b041b42f6 100644 --- a/packages/rs-drive/tests/deterministic_root_hash.rs +++ b/packages/rs-drive/tests/deterministic_root_hash.rs @@ -313,8 +313,9 @@ mod tests { 14 => "c25bc863d30a4185311546d51bacfdc030ee57671dbae774775d0efd4474220c", // From protocol version 15 the contract is stored compiled (format version 2), its // document types beside its schemas, and is DashPay v3, which declares the checks of - // its data triggers. - _ => "ba8d5ff45dad759997166e63772ff7c582faf9b28c0a5d4d51b970b90d4ca9bd", + // its data triggers and adds the private `privateContactRequest` type, whose store + // the insert creates. + _ => "66306426bce1beaa7319e7e6d822bcf8a7c93f14425c2f82841a3222a7502912", }; assert_eq!( diff --git a/packages/rs-platform-version/src/version/v15.rs b/packages/rs-platform-version/src/version/v15.rs index 255733a8cc3..1e9f324468b 100644 --- a/packages/rs-platform-version/src/version/v15.rs +++ b/packages/rs-platform-version/src/version/v15.rs @@ -483,7 +483,8 @@ pub const PROTOCOL_VERSION_15: ProtocolVersion = 15; /// is refused unpaid either way. Protocol v14 and earlier keep `basic_structure` 0 and /// `advanced_structure` 0 or 1. /// -/// 24. **DashPay v3 states what its data triggers checked, and its ciphertexts' shape**: +/// 24. **DashPay v3 states what its data triggers checked and its ciphertexts' shape, and adds +/// private contact requests**: /// `SYSTEM_DATA_CONTRACT_VERSIONS_V4` selects DashPay contract v3, loaded at /// genesis and re-stored by `transition_to_version_15` (`apply_contract`, as the /// upgrade to v2 did). A `contactRequest`'s `toUserId` refers to an identity @@ -505,6 +506,14 @@ pub const PROTOCOL_VERSION_15: ProtocolVersion = 15; /// profile create and replace triggers, which made the identity, writer and type byte /// checks and refused with `DataTriggerConditionError` (40500). Protocol v14 keeps /// DashPay v2 and bindings 2, and earlier versions the contract and bindings they had. +/// DashPay v3 also declares `privateContactRequest`, a private type (note 13) of 589-byte +/// entries whose properties describe a contact request only its recipient can read: +/// `accountReference`, the DIP-15 compact `extendedPublicKey` (69 bytes), and the optional +/// `accountLabel` (at most 63 bytes), `autoAcceptProof` (38 to 102) and `shieldedAddress` +/// (43). At their largest they encode to 290 bytes, which with the private document +/// envelope's 299 fill an entry. Genesis creates its store as a contract insert does, and +/// `transition_to_version_15` as a contract update adding a private type does (note 13); +/// only note 25's transition appends to it. /// /// 25. **Private document entries**: a new state transition, /// `PrivateDocumentCreateFromShieldedPool` (type 29), appends entries to the store of a