drive_abci/execution/validation/state_transition/state_transitions/
mod.rs

1/// Module containing functionality related to batch processing of documents.
2pub mod batch;
3
4/// Module for creating an identity entity.
5pub mod identity_create;
6
7/// Module for managing transfers of credit between identity entities.
8pub mod identity_credit_transfer;
9
10/// Module for managing withdrawals of credit from an identity entity.
11pub mod identity_credit_withdrawal;
12
13/// Module for topping up credit in an identity entity.
14pub mod identity_top_up;
15
16/// Module for updating an existing identity entity.
17pub mod identity_update;
18
19/// Module for creating a data contract entity.
20pub mod data_contract_create;
21
22/// Module for updating an existing data contract entity.
23pub mod data_contract_update;
24
25/// Module for voting from a masternode.
26pub mod masternode_vote;
27
28/// Identity create from addresses
29pub mod identity_create_from_addresses;
30
31/// Module for validation of address funding from asset lock transitions
32pub mod address_funding_from_asset_lock;
33
34/// Module for validation of credit transfer from an identity to addresses
35pub mod identity_credit_transfer_to_addresses;
36
37/// Module for validation of address credit withdrawal transitions
38pub mod address_credit_withdrawal;
39
40/// Module for validation of address funds transfer transitions
41pub mod address_funds_transfer;
42mod identity_top_up_from_addresses;
43
44/// Module for shield transition validation
45pub mod shield;
46/// Module for shield from asset lock transition validation
47pub mod shield_from_asset_lock;
48/// Common validation logic shared by shielded transitions (proof verification)
49pub mod shielded_common;
50/// Module for shielded transfer transition validation
51pub mod shielded_transfer;
52/// Module for shielded withdrawal transition validation
53pub mod shielded_withdrawal;
54/// Module for unshield transition validation
55pub mod unshield;
56
57/// The validation mode we are using
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum ValidationMode {
60    /// The basic checktx before the state transition is put into mempool
61    CheckTx,
62    /// Rechecking a state transition every block
63    RecheckTx,
64    /// The validation during block execution by a proposer or validator
65    Validator,
66    /// A validation mode used to get the action with no validation
67    NoValidation,
68}
69
70impl ValidationMode {
71    /// Can this validation mode alter cache on drive?
72    pub fn can_alter_cache(&self) -> bool {
73        match self {
74            ValidationMode::CheckTx => false,
75            ValidationMode::RecheckTx => false,
76            ValidationMode::Validator => true,
77            ValidationMode::NoValidation => false,
78        }
79    }
80}
81
82#[cfg(test)]
83pub(in crate::execution) mod test_helpers;
84
85#[cfg(test)]
86pub(in crate::execution) mod tests {
87    use crate::rpc::core::MockCoreRPCLike;
88    use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder};
89    use dpp::block::block_info::BlockInfo;
90    use dpp::data_contracts::SystemDataContract;
91    use dpp::fee::Credits;
92    use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
93    use dpp::identity::{Identity, IdentityPublicKey, IdentityV0, KeyID, KeyType, Purpose, SecurityLevel, TimestampMillis};
94    use dpp::prelude::{BlockHeight, Identifier, IdentityNonce};
95    use dpp::state_transition::data_contract_create_transition::methods::DataContractCreateTransitionMethodsV0;
96    use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition;
97    use dpp::system_data_contracts::load_system_data_contract;
98    use dpp::tests::json_document::json_document_to_contract_with_ids;
99    use drive::drive::document::query::QueryDocumentsOutcomeV0Methods;
100    use drive::query::DriveDocumentQuery;
101    use platform_version::version::PlatformVersion;
102    use rand::prelude::StdRng;
103    use rand::{Rng, SeedableRng};
104    use simple_signer::signer::SimpleSigner;
105    use std::borrow::Cow;
106    use std::collections::BTreeMap;
107    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
108    use std::ops::Deref;
109    use std::sync::Arc;
110    use arc_swap::Guard;
111    use assert_matches::assert_matches;
112    use dpp::dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType};
113    use dapi_grpc::platform::v0::{get_contested_resource_vote_state_request, get_contested_resource_vote_state_response, GetContestedResourceVoteStateRequest, GetContestedResourceVoteStateResponse};
114    use dapi_grpc::platform::v0::get_contested_resource_vote_state_request::get_contested_resource_vote_state_request_v0::ResultType;
115    use dapi_grpc::platform::v0::get_contested_resource_vote_state_request::{get_contested_resource_vote_state_request_v0, GetContestedResourceVoteStateRequestV0};
116    use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::{get_contested_resource_vote_state_response_v0, GetContestedResourceVoteStateResponseV0};
117    use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::get_contested_resource_vote_state_response_v0::FinishedVoteInfo;
118    use dpp::balances::credits::TokenAmount;
119    use dpp::dash_to_credits;
120    use dpp::dashcore::{ProTxHash, Txid};
121    use dpp::dashcore::hashes::Hash;
122    use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
123    use dpp::data_contract::accessors::v1::{DataContractV1Getters, DataContractV1Setters};
124    use dpp::data_contract::{DataContract, GroupContractPosition, TokenContractPosition};
125    use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV1Setters};
126    use dpp::data_contract::document_type::random_document::{CreateRandomDocument, DocumentFieldFillSize, DocumentFieldFillType};
127    use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters};
128    use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0;
129    use dpp::fee::fee_result::FeeResult;
130    use dpp::identifier::MasternodeIdentifiers;
131    use dpp::identity::accessors::IdentityGettersV0;
132    use dpp::identity::contract_bounds::ContractBounds;
133    use dpp::identity::hash::IdentityPublicKeyHashMethodsV0;
134    use dpp::platform_value::{Bytes32, Value};
135    use dpp::serialization::PlatformSerializable;
136    use dpp::state_transition::batch_transition::BatchTransition;
137    use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0;
138    use dpp::state_transition::masternode_vote_transition::MasternodeVoteTransition;
139    use dpp::state_transition::masternode_vote_transition::methods::MasternodeVoteTransitionMethodsV0;
140    use dpp::state_transition::StateTransition;
141    use dpp::tokens::calculate_token_id;
142    use dpp::util::hash::hash_double;
143    use dpp::util::strings::convert_to_homograph_safe_chars;
144    use dpp::voting::contender_structs::{Contender, ContenderV0};
145    use dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
146    use dpp::voting::vote_info_storage::contested_document_vote_poll_winner_info::ContestedDocumentVotePollWinnerInfo;
147    use dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll;
148    use dpp::voting::vote_polls::VotePoll;
149    use dpp::voting::votes::resource_vote::ResourceVote;
150    use dpp::voting::votes::resource_vote::v0::ResourceVoteV0;
151    use dpp::voting::votes::Vote;
152    use drive::util::object_size_info::DataContractResolvedInfo;
153    use drive::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed;
154    use drive::query::vote_poll_vote_state_query::ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally;
155    use drive::query::vote_poll_vote_state_query::{ContestedDocumentVotePollDriveQueryResultType, ResolvedContestedDocumentVotePollDriveQuery};
156    use drive::util::test_helpers::setup_contract;
157    use crate::execution::types::block_execution_context::BlockExecutionContext;
158    use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0;
159    use crate::expect_match;
160    use crate::platform_types::platform_state::PlatformState;
161    use crate::platform_types::platform_state::PlatformStateV0Methods;
162    use crate::platform_types::state_transitions_processing_result::{StateTransitionExecutionResult, StateTransitionsProcessingResult};
163    use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult::{SuccessfulExecution, UnpaidConsensusError};
164    use crate::execution::types::block_state_info::BlockStateInfo;
165    use crate::execution::types::block_state_info::v0::BlockStateInfoV0;
166    use crate::platform_types::epoch_info::EpochInfo;
167    use crate::platform_types::epoch_info::v0::EpochInfoV0;
168    use crate::execution::types::block_fees::v0::BlockFeesV0;
169    use crate::execution::types::processed_block_fees_outcome::v0::ProcessedBlockFeesOutcome;
170    use dpp::data_contract::associated_token::token_configuration::TokenConfiguration;
171    use dpp::data_contract::group::Group;
172    use dpp::tokens::gas_fees_paid_by::GasFeesPaidBy;
173    use dpp::tokens::token_amount_on_contract_token::{DocumentActionTokenCost, DocumentActionTokenEffect};
174    use dpp::data_contract::document_type::accessors::DocumentTypeV0MutGetters;
175    use dpp::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure;
176
177    /// We add an identity, but we also add the same amount to system credits
178    pub(in crate::execution) fn setup_identity_with_system_credits(
179        platform: &mut TempPlatform<MockCoreRPCLike>,
180        seed: u64,
181        credits: Credits,
182    ) -> (Identity, SimpleSigner, IdentityPublicKey) {
183        let platform_version = PlatformVersion::latest();
184        platform
185            .drive
186            .add_to_system_credits(credits, None, platform_version)
187            .expect("expected to add to system credits");
188        setup_identity(platform, seed, credits)
189    }
190
191    pub(in crate::execution) fn setup_identity(
192        platform: &mut TempPlatform<MockCoreRPCLike>,
193        seed: u64,
194        credits: Credits,
195    ) -> (Identity, SimpleSigner, IdentityPublicKey) {
196        let platform_version = PlatformVersion::latest();
197        let mut signer = SimpleSigner::default();
198
199        let mut rng = StdRng::seed_from_u64(seed);
200
201        let (master_key, master_private_key) =
202            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
203                0,
204                &mut rng,
205                platform_version,
206            )
207            .expect("expected to get key pair");
208
209        signer.add_identity_public_key(master_key.clone(), master_private_key);
210
211        let (critical_public_key, private_key) =
212            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
213                1,
214                &mut rng,
215                platform_version,
216            )
217            .expect("expected to get key pair");
218
219        signer.add_identity_public_key(critical_public_key.clone(), private_key);
220
221        let identity: Identity = IdentityV0 {
222            id: Identifier::random_with_rng(&mut rng),
223            public_keys: BTreeMap::from([
224                (0, master_key.clone()),
225                (1, critical_public_key.clone()),
226            ]),
227            balance: credits,
228            revision: 0,
229        }
230        .into();
231
232        // We just add this identity to the system first
233
234        platform
235            .drive
236            .add_new_identity(
237                identity.clone(),
238                false,
239                &BlockInfo::default(),
240                true,
241                None,
242                platform_version,
243            )
244            .expect("expected to add a new identity");
245
246        (identity, signer, critical_public_key)
247    }
248
249    pub(in crate::execution) fn setup_identity_without_adding_it(
250        seed: u64,
251        credits: Credits,
252    ) -> (Identity, SimpleSigner, IdentityPublicKey) {
253        let platform_version = PlatformVersion::latest();
254        let mut signer = SimpleSigner::default();
255
256        let mut rng = StdRng::seed_from_u64(seed);
257
258        let (master_key, master_private_key) =
259            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
260                0,
261                &mut rng,
262                platform_version,
263            )
264            .expect("expected to get key pair");
265
266        signer.add_identity_public_key(master_key.clone(), master_private_key);
267
268        let (critical_public_key, private_key) =
269            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
270                1,
271                &mut rng,
272                platform_version,
273            )
274            .expect("expected to get key pair");
275
276        signer.add_identity_public_key(critical_public_key.clone(), private_key);
277
278        let identity: Identity = IdentityV0 {
279            id: Identifier::random_with_rng(&mut rng),
280            public_keys: BTreeMap::from([
281                (0, master_key.clone()),
282                (1, critical_public_key.clone()),
283            ]),
284            balance: credits,
285            revision: 0,
286        }
287        .into();
288
289        (identity, signer, critical_public_key)
290    }
291
292    pub(in crate::execution) fn setup_identity_return_master_key(
293        platform: &mut TempPlatform<MockCoreRPCLike>,
294        seed: u64,
295        credits: Credits,
296    ) -> (Identity, SimpleSigner, IdentityPublicKey, IdentityPublicKey) {
297        let platform_version = PlatformVersion::latest();
298        let mut signer = SimpleSigner::default();
299
300        let mut rng = StdRng::seed_from_u64(seed);
301
302        let (master_key, master_private_key) =
303            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
304                0,
305                &mut rng,
306                platform_version,
307            )
308            .expect("expected to get key pair");
309
310        signer.add_identity_public_key(master_key.clone(), master_private_key);
311
312        let (critical_public_key, private_key) =
313            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
314                1,
315                &mut rng,
316                platform_version,
317            )
318            .expect("expected to get key pair");
319
320        signer.add_identity_public_key(critical_public_key.clone(), private_key);
321
322        let identity: Identity = IdentityV0 {
323            id: Identifier::random_with_rng(&mut rng),
324            public_keys: BTreeMap::from([
325                (0, master_key.clone()),
326                (1, critical_public_key.clone()),
327            ]),
328            balance: credits,
329            revision: 0,
330        }
331        .into();
332
333        // We just add this identity to the system first
334
335        platform
336            .drive
337            .add_new_identity(
338                identity.clone(),
339                false,
340                &BlockInfo::default(),
341                true,
342                None,
343                platform_version,
344            )
345            .expect("expected to add a new identity");
346
347        (identity, signer, critical_public_key, master_key)
348    }
349
350    #[allow(clippy::too_many_arguments)]
351    pub(crate) fn setup_add_key_to_identity(
352        platform: &mut TempPlatform<MockCoreRPCLike>,
353        identity: &mut Identity,
354        signer: &mut SimpleSigner,
355        seed: u64,
356        key_id: KeyID,
357        purpose: Purpose,
358        security_level: SecurityLevel,
359        key_type: KeyType,
360        contract_bounds: Option<ContractBounds>,
361    ) -> IdentityPublicKey {
362        let platform_version = PlatformVersion::latest();
363
364        let mut rng = StdRng::seed_from_u64(seed);
365
366        let (key, private_key) = IdentityPublicKey::random_key_with_known_attributes(
367            key_id,
368            &mut rng,
369            purpose,
370            security_level,
371            key_type,
372            contract_bounds,
373            platform_version,
374        )
375        .expect("expected to get key pair");
376
377        signer.add_identity_public_key(key.clone(), private_key);
378
379        identity.add_public_key(key.clone());
380
381        platform
382            .drive
383            .add_new_unique_keys_to_identity(
384                identity.id().to_buffer(),
385                vec![key.clone()],
386                &BlockInfo::default(),
387                true,
388                None,
389                platform_version,
390            )
391            .expect("expected to add a new key");
392
393        key
394    }
395
396    pub(in crate::execution) fn setup_identity_with_withdrawal_key_and_system_credits(
397        platform: &mut TempPlatform<MockCoreRPCLike>,
398        seed: u64,
399        withdrawal_key_type: KeyType,
400        credits: Credits,
401    ) -> (Identity, SimpleSigner, IdentityPublicKey, IdentityPublicKey) {
402        let platform_version = PlatformVersion::latest();
403        platform
404            .drive
405            .add_to_system_credits(credits, None, platform_version)
406            .expect("expected to add to system credits");
407        let mut signer = SimpleSigner::default();
408
409        let mut rng = StdRng::seed_from_u64(seed);
410
411        let (master_key, master_private_key) =
412            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
413                0,
414                &mut rng,
415                platform_version,
416            )
417            .expect("expected to get key pair");
418
419        signer.add_identity_public_key(master_key.clone(), master_private_key);
420
421        let (critical_public_key, private_key) =
422            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
423                1,
424                &mut rng,
425                platform_version,
426            )
427            .expect("expected to get key pair");
428
429        signer.add_identity_public_key(critical_public_key.clone(), private_key);
430
431        let (withdrawal_public_key, withdrawal_private_key) =
432            IdentityPublicKey::random_key_with_known_attributes(
433                2,
434                &mut rng,
435                Purpose::TRANSFER,
436                SecurityLevel::CRITICAL,
437                withdrawal_key_type,
438                None,
439                platform_version,
440            )
441            .expect("expected to get key pair");
442
443        signer.add_identity_public_key(withdrawal_public_key.clone(), withdrawal_private_key);
444
445        let identity: Identity = IdentityV0 {
446            id: Identifier::random_with_rng(&mut rng),
447            public_keys: BTreeMap::from([
448                (0, master_key.clone()),
449                (1, critical_public_key.clone()),
450                (2, withdrawal_public_key.clone()),
451            ]),
452            balance: credits,
453            revision: 0,
454        }
455        .into();
456
457        // We just add this identity to the system first
458
459        platform
460            .drive
461            .add_new_identity(
462                identity.clone(),
463                false,
464                &BlockInfo::default(),
465                true,
466                None,
467                platform_version,
468            )
469            .expect("expected to add a new identity");
470
471        (identity, signer, critical_public_key, withdrawal_public_key)
472    }
473
474    pub(in crate::execution) fn add_tokens_to_identity(
475        platform: &TempPlatform<MockCoreRPCLike>,
476        token_id: Identifier,
477        identity_id: Identifier,
478        balance_to_add: Credits,
479    ) {
480        let platform_version = PlatformVersion::latest();
481        platform
482            .drive
483            .add_to_identity_token_balance(
484                token_id.to_buffer(),
485                identity_id.to_buffer(),
486                balance_to_add,
487                &BlockInfo::default(),
488                true,
489                None,
490                platform_version,
491                None,
492            )
493            .expect("expected to add token balance to identity");
494        platform
495            .drive
496            .add_to_token_total_supply(
497                token_id.to_buffer(),
498                balance_to_add,
499                true,
500                false,
501                true,
502                &BlockInfo::default(),
503                None,
504                platform_version,
505            )
506            .expect("expected to add to total supply");
507    }
508
509    pub(in crate::execution) fn process_state_transitions(
510        platform: &TempPlatform<MockCoreRPCLike>,
511        state_transitions: &[StateTransition],
512        block_info: BlockInfo,
513        platform_state: &PlatformState,
514    ) -> (Vec<FeeResult>, ProcessedBlockFeesOutcome) {
515        let platform_version = PlatformVersion::latest();
516
517        let raw_state_transitions = state_transitions
518            .iter()
519            .map(|a| a.serialize_to_bytes().expect("expected to serialize"))
520            .collect::<Vec<_>>();
521
522        let transaction = platform.drive.grove.start_transaction();
523
524        let processing_result = platform
525            .platform
526            .process_raw_state_transitions(
527                &raw_state_transitions,
528                platform_state,
529                &block_info,
530                &transaction,
531                platform_version,
532                false,
533                None,
534            )
535            .expect("expected to process state transition");
536
537        let fee_results = processing_result.execution_results().iter().map(|result| {
538            let fee_result = expect_match!(result, StateTransitionExecutionResult::SuccessfulExecution{ fee_result, .. } => fee_result);
539            fee_result.clone()
540        }).collect();
541
542        // while we have the state transitions executed, we now need to process the block fees
543        let block_fees_v0: BlockFeesV0 = processing_result.aggregated_fees().clone().into();
544
545        let block_execution_context = BlockExecutionContext::V0(BlockExecutionContextV0 {
546            block_state_info: BlockStateInfo::V0(BlockStateInfoV0 {
547                height: block_info.height,
548                round: 0,
549                block_time_ms: block_info.time_ms,
550                previous_block_time_ms: platform_state.last_committed_block_time_ms(),
551                proposer_pro_tx_hash: Default::default(),
552                core_chain_locked_height: 0,
553                block_hash: None,
554                app_hash: None,
555            }),
556            epoch_info: EpochInfo::V0(EpochInfoV0::default()),
557            unsigned_withdrawal_transactions: Default::default(),
558            block_address_balance_changes: Default::default(),
559            block_platform_state: platform_state.clone(),
560            proposer_results: None,
561        });
562
563        // Process fees
564        let processed_block_fees = platform
565            .process_block_fees_and_validate_sum_trees(
566                &block_execution_context,
567                block_fees_v0.into(),
568                &transaction,
569                platform_version,
570            )
571            .expect("expected to process block fees");
572
573        platform
574            .drive
575            .grove
576            .commit_transaction(transaction)
577            .unwrap()
578            .expect("expected to commit");
579
580        (fee_results, processed_block_fees)
581    }
582
583    pub(in crate::execution) fn fetch_expected_identity_balance(
584        platform: &TempPlatform<MockCoreRPCLike>,
585        identity_id: Identifier,
586        platform_version: &PlatformVersion,
587        expected_balance: Credits,
588    ) {
589        assert_eq!(
590            expected_balance,
591            platform
592                .drive
593                .fetch_identity_balance(identity_id.to_buffer(), None, platform_version)
594                .expect("expected to be able to fetch balance")
595                .expect("expected a balance")
596        );
597    }
598
599    pub(in crate::execution) fn setup_masternode_owner_identity(
600        platform: &mut TempPlatform<MockCoreRPCLike>,
601        seed: u64,
602        credits: Credits,
603        platform_version: &PlatformVersion,
604    ) -> (Identity, SimpleSigner, IdentityPublicKey, IdentityPublicKey) {
605        let mut signer = SimpleSigner::default();
606
607        platform
608            .drive
609            .add_to_system_credits(credits, None, platform_version)
610            .expect("expected to add to system credits");
611
612        let mut rng = StdRng::seed_from_u64(seed);
613
614        let (transfer_key, transfer_private_key) =
615            IdentityPublicKey::random_masternode_transfer_key_with_rng(
616                0,
617                &mut rng,
618                platform_version,
619            )
620            .expect("expected to get key pair");
621
622        let (owner_key, owner_private_key) =
623            IdentityPublicKey::random_masternode_owner_key_with_rng(1, &mut rng, platform_version)
624                .expect("expected to get key pair");
625
626        let owner_address = owner_key
627            .public_key_hash()
628            .expect("expected a public key hash");
629
630        let payout_address = transfer_key
631            .public_key_hash()
632            .expect("expected a public key hash");
633
634        signer.add_identity_public_key(transfer_key.clone(), transfer_private_key);
635        signer.add_identity_public_key(owner_key.clone(), owner_private_key);
636
637        let pro_tx_hash_bytes: [u8; 32] = rng.gen();
638
639        let identity: Identity = IdentityV0 {
640            id: pro_tx_hash_bytes.into(),
641            public_keys: BTreeMap::from([(0, transfer_key.clone()), (1, owner_key.clone())]),
642            balance: credits,
643            revision: 0,
644        }
645        .into();
646
647        // We just add this identity to the system first
648
649        platform
650            .drive
651            .add_new_identity(
652                identity.clone(),
653                true,
654                &BlockInfo::default(),
655                true,
656                None,
657                platform_version,
658            )
659            .expect("expected to add a new identity");
660
661        let mut platform_state = platform.state.load().clone().deref().clone();
662
663        let pro_tx_hash = ProTxHash::from_byte_array(pro_tx_hash_bytes);
664
665        let random_ip = Ipv4Addr::new(
666            rng.gen_range(0..255),
667            rng.gen_range(0..255),
668            rng.gen_range(0..255),
669            rng.gen_range(0..255),
670        );
671
672        platform_state.full_masternode_list_mut().insert(
673            pro_tx_hash,
674            MasternodeListItem {
675                node_type: MasternodeType::Regular,
676                pro_tx_hash,
677                collateral_hash: Txid::from_byte_array(rng.gen()),
678                collateral_index: 0,
679                collateral_address: rng.gen(),
680                operator_reward: 0.0,
681                state: DMNState {
682                    service: SocketAddr::new(IpAddr::V4(random_ip), 19999),
683                    registered_height: 0,
684                    pose_revived_height: None,
685                    pose_ban_height: None,
686                    revocation_reason: 0,
687                    owner_address,
688                    voting_address: rng.gen(),
689                    payout_address,
690                    pub_key_operator: vec![],
691                    operator_payout_address: None,
692                    platform_node_id: None,
693                    platform_p2p_port: None,
694                    platform_http_port: None,
695                },
696            },
697        );
698
699        platform.state.store(Arc::new(platform_state));
700
701        (identity, signer, owner_key, transfer_key)
702    }
703
704    pub(in crate::execution) fn setup_masternode_voting_identity(
705        platform: &mut TempPlatform<MockCoreRPCLike>,
706        seed: u64,
707        platform_version: &PlatformVersion,
708    ) -> (Identifier, Identity, SimpleSigner, IdentityPublicKey) {
709        let mut signer = SimpleSigner::default();
710
711        let mut rng = StdRng::seed_from_u64(seed);
712
713        let (voting_key, voting_private_key) =
714            IdentityPublicKey::random_voting_key_with_rng(0, &mut rng, platform_version)
715                .expect("expected to get key pair");
716
717        signer.add_identity_public_key(voting_key.clone(), voting_private_key);
718
719        let pro_tx_hash_bytes: [u8; 32] = rng.gen();
720
721        let voting_address = voting_key
722            .public_key_hash()
723            .expect("expected a public key hash");
724
725        let voter_identifier =
726            Identifier::create_voter_identifier(&pro_tx_hash_bytes, &voting_address);
727
728        let identity: Identity = IdentityV0 {
729            id: voter_identifier,
730            public_keys: BTreeMap::from([(0, voting_key.clone())]),
731            balance: 0,
732            revision: 0,
733        }
734        .into();
735
736        // We just add this identity to the system first
737
738        platform
739            .drive
740            .add_new_identity(
741                identity.clone(),
742                true,
743                &BlockInfo::default(),
744                true,
745                None,
746                platform_version,
747            )
748            .expect("expected to add a new identity");
749
750        let mut platform_state = platform.state.load().clone().deref().clone();
751
752        let pro_tx_hash = ProTxHash::from_byte_array(pro_tx_hash_bytes);
753
754        let random_ip = Ipv4Addr::new(
755            rng.gen_range(0..255),
756            rng.gen_range(0..255),
757            rng.gen_range(0..255),
758            rng.gen_range(0..255),
759        );
760
761        platform_state.full_masternode_list_mut().insert(
762            pro_tx_hash,
763            MasternodeListItem {
764                node_type: MasternodeType::Regular,
765                pro_tx_hash,
766                collateral_hash: Txid::from_byte_array(rng.gen()),
767                collateral_index: 0,
768                collateral_address: rng.gen(),
769                operator_reward: 0.0,
770                state: DMNState {
771                    service: SocketAddr::new(IpAddr::V4(random_ip), 19999),
772                    registered_height: 0,
773                    pose_revived_height: None,
774                    pose_ban_height: None,
775                    revocation_reason: 0,
776                    owner_address: rng.gen(),
777                    voting_address,
778                    payout_address: rng.gen(),
779                    pub_key_operator: vec![],
780                    operator_payout_address: None,
781                    platform_node_id: None,
782                    platform_p2p_port: None,
783                    platform_http_port: None,
784                },
785            },
786        );
787
788        platform.state.store(Arc::new(platform_state));
789
790        (pro_tx_hash_bytes.into(), identity, signer, voting_key)
791    }
792
793    pub(in crate::execution) fn take_down_masternode_identities(
794        platform: &mut TempPlatform<MockCoreRPCLike>,
795        masternode_identities: &Vec<Identifier>,
796    ) {
797        let mut platform_state = platform.state.load().clone().deref().clone();
798
799        let list = platform_state.full_masternode_list_mut();
800
801        for masternode_identifiers in masternode_identities {
802            let pro_tx_hash = ProTxHash::from_byte_array(masternode_identifiers.to_buffer());
803
804            list.remove(&pro_tx_hash);
805        }
806
807        platform.state.store(Arc::new(platform_state));
808    }
809
810    #[allow(dead_code)]
811    pub(in crate::execution) enum IdentityTestInfo<'a> {
812        Given {
813            identity: &'a Identity,
814            signer: &'a SimpleSigner,
815            public_key: &'a IdentityPublicKey,
816            identity_nonce: IdentityNonce,
817        },
818        UseSeed(u64),
819        UseRng(&'a mut StdRng),
820    }
821
822    pub(in crate::execution) fn register_contract_from_bytes(
823        platform: &mut TempPlatform<MockCoreRPCLike>,
824        platform_state: &PlatformState,
825        contract_bytes: Vec<u8>,
826        identity_info: IdentityTestInfo,
827        platform_version: &PlatformVersion,
828    ) -> DataContract {
829        // Deserialize the data contract from bytes
830        let mut data_contract =
831            DataContract::versioned_deserialize(&contract_bytes, false, platform_version)
832                .expect("expected to deserialize data contract");
833
834        // Get identity info based on the enum variant
835        let (identity_cow, signer_cow, key_cow, nonce) = match identity_info {
836            IdentityTestInfo::Given {
837                identity,
838                signer,
839                public_key,
840                identity_nonce,
841            } => (
842                Cow::Borrowed(identity),
843                Cow::Borrowed(signer),
844                Cow::Borrowed(public_key),
845                identity_nonce,
846            ),
847            IdentityTestInfo::UseSeed(seed) => {
848                let (identity, signer, key) = setup_identity(platform, seed, dash_to_credits!(1));
849                (Cow::Owned(identity), Cow::Owned(signer), Cow::Owned(key), 1)
850            }
851            IdentityTestInfo::UseRng(rng) => {
852                let seed = rng.gen();
853                let (identity, signer, key) = setup_identity(platform, seed, dash_to_credits!(1));
854                (Cow::Owned(identity), Cow::Owned(signer), Cow::Owned(key), 1)
855            }
856        };
857
858        let contract_id = DataContract::generate_data_contract_id_v0(identity_cow.id(), nonce);
859
860        data_contract.set_id(contract_id);
861
862        // Create and sign the data contract create transition
863        let state_transition = DataContractCreateTransition::new_from_data_contract(
864            data_contract.clone(),
865            nonce,
866            &identity_cow.as_ref().clone().into_partial_identity_info(),
867            key_cow.id(),
868            signer_cow.as_ref(),
869            platform_version,
870            None,
871        )
872        .expect("expected to create and sign data contract create transition");
873
874        // Serialize the state transition
875        let state_transition_bytes = state_transition
876            .serialize_to_bytes()
877            .expect("expected to serialize state transition");
878
879        let transaction = platform.drive.grove.start_transaction();
880
881        let processing_result = platform
882            .platform
883            .process_raw_state_transitions(
884                &[state_transition_bytes],
885                platform_state,
886                &BlockInfo::default(),
887                &transaction,
888                platform_version,
889                false,
890                None,
891            )
892            .expect("expected to process state transition");
893
894        platform
895            .drive
896            .grove
897            .commit_transaction(transaction)
898            .unwrap()
899            .expect("expected to commit transaction");
900
901        let execution_result = processing_result.into_execution_results().remove(0);
902        assert_matches!(execution_result, SuccessfulExecution { .. });
903
904        data_contract
905    }
906
907    pub(in crate::execution) fn create_dpns_name_contest_give_key_info(
908        platform: &mut TempPlatform<MockCoreRPCLike>,
909        platform_state: &PlatformState,
910        seed: u64,
911        name: &str,
912        platform_version: &PlatformVersion,
913    ) -> (
914        (
915            Identity,
916            SimpleSigner,
917            IdentityPublicKey,
918            (Document, Bytes32),
919            (Document, Bytes32),
920        ),
921        (
922            Identity,
923            SimpleSigner,
924            IdentityPublicKey,
925            (Document, Bytes32),
926            (Document, Bytes32),
927        ),
928        Arc<DataContract>,
929    ) {
930        let mut rng = StdRng::seed_from_u64(seed);
931
932        let identity_1_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
933
934        let identity_2_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
935
936        // Flip them if needed so identity 1 id is always smaller than identity 2 id
937        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
938        {
939            (identity_1_info, identity_2_info)
940        } else {
941            (identity_2_info, identity_1_info)
942        };
943
944        let ((preorder_document_1, document_1), (preorder_document_2, document_2), dpns_contract) =
945            create_dpns_name_contest_on_identities(
946                platform,
947                &identity_1_info,
948                &identity_2_info,
949                platform_state,
950                rng,
951                name,
952                None,
953                false,
954                platform_version,
955            );
956
957        let (identity_1, signer_1, identity_key_1) = identity_1_info;
958
959        let (identity_2, signer_2, identity_key_2) = identity_2_info;
960
961        (
962            (
963                identity_1,
964                signer_1,
965                identity_key_1,
966                preorder_document_1,
967                document_1,
968            ),
969            (
970                identity_2,
971                signer_2,
972                identity_key_2,
973                preorder_document_2,
974                document_2,
975            ),
976            dpns_contract,
977        )
978    }
979
980    pub(in crate::execution) fn create_dpns_identity_name_contest(
981        platform: &mut TempPlatform<MockCoreRPCLike>,
982        platform_state: &PlatformState,
983        seed: u64,
984        name: &str,
985        platform_version: &PlatformVersion,
986    ) -> (Identity, Identity, Arc<DataContract>) {
987        let mut rng = StdRng::seed_from_u64(seed);
988
989        let identity_1_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
990
991        let identity_2_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
992
993        // Flip them if needed so identity 1 id is always smaller than identity 2 id
994        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
995        {
996            (identity_1_info, identity_2_info)
997        } else {
998            (identity_2_info, identity_1_info)
999        };
1000
1001        let (_, _, dpns_contract) = create_dpns_name_contest_on_identities(
1002            platform,
1003            &identity_1_info,
1004            &identity_2_info,
1005            platform_state,
1006            rng,
1007            name,
1008            None,
1009            false,
1010            platform_version,
1011        );
1012        (identity_1_info.0, identity_2_info.0, dpns_contract)
1013    }
1014
1015    /// This can be useful if we already created the identities and we reuse the seed
1016    pub(in crate::execution) fn create_dpns_identity_name_contest_skip_creating_identities(
1017        platform: &mut TempPlatform<MockCoreRPCLike>,
1018        platform_state: &PlatformState,
1019        seed: u64,
1020        name: &str,
1021        nonce_offset: Option<IdentityNonce>,
1022        platform_version: &PlatformVersion,
1023    ) -> (Identity, Identity, Arc<DataContract>) {
1024        let mut rng = StdRng::seed_from_u64(seed);
1025
1026        let identity_1_info = setup_identity_without_adding_it(rng.gen(), dash_to_credits!(0.5));
1027
1028        let identity_2_info = setup_identity_without_adding_it(rng.gen(), dash_to_credits!(0.5));
1029
1030        // Flip them if needed so identity 1 id is always smaller than identity 2 id
1031        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
1032        {
1033            (identity_1_info, identity_2_info)
1034        } else {
1035            (identity_2_info, identity_1_info)
1036        };
1037
1038        let (_, _, dpns_contract) = create_dpns_name_contest_on_identities(
1039            platform,
1040            &identity_1_info,
1041            &identity_2_info,
1042            platform_state,
1043            rng,
1044            name,
1045            nonce_offset,
1046            true, //we should also skip preorder
1047            platform_version,
1048        );
1049        (identity_1_info.0, identity_2_info.0, dpns_contract)
1050    }
1051
1052    pub(in crate::execution) fn create_dpns_contract_name_contest(
1053        platform: &mut TempPlatform<MockCoreRPCLike>,
1054        platform_state: &PlatformState,
1055        seed: u64,
1056        name: &str,
1057        platform_version: &PlatformVersion,
1058    ) -> (Identity, Identity, DataContract) {
1059        let mut rng = StdRng::seed_from_u64(seed);
1060
1061        let identity_1_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
1062
1063        let identity_2_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
1064
1065        // Flip them if needed so identity 1 id is always smaller than identity 2 id
1066        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
1067        {
1068            (identity_1_info, identity_2_info)
1069        } else {
1070            (identity_2_info, identity_1_info)
1071        };
1072
1073        let dashpay_contract = setup_contract(
1074            &platform.drive,
1075            "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json",
1076            None,
1077            None,
1078            None::<fn(&mut DataContract)>,
1079            None,
1080            None,
1081        );
1082
1083        let card_game = setup_contract(
1084            &platform.drive,
1085            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase.json",
1086            None,
1087            None,
1088            None::<fn(&mut DataContract)>,
1089            None,
1090            None,
1091        );
1092
1093        let (_, _, dpns_contract) = create_dpns_name_contest_on_identities_for_contract_records(
1094            platform,
1095            &identity_1_info,
1096            &identity_2_info,
1097            &dashpay_contract,
1098            &card_game,
1099            platform_state,
1100            rng,
1101            name,
1102            platform_version,
1103        );
1104        (identity_1_info.0, identity_2_info.0, dpns_contract)
1105    }
1106
1107    #[allow(clippy::too_many_arguments)]
1108    fn create_dpns_name_contest_on_identities(
1109        platform: &mut TempPlatform<MockCoreRPCLike>,
1110        identity_1: &(Identity, SimpleSigner, IdentityPublicKey),
1111        identity_2: &(Identity, SimpleSigner, IdentityPublicKey),
1112        platform_state: &PlatformState,
1113        mut rng: StdRng,
1114        name: &str,
1115        nonce_offset: Option<IdentityNonce>,
1116        skip_preorder: bool,
1117        platform_version: &PlatformVersion,
1118    ) -> (
1119        ((Document, Bytes32), (Document, Bytes32)),
1120        ((Document, Bytes32), (Document, Bytes32)),
1121        Arc<DataContract>,
1122    ) {
1123        let (identity_1, signer_1, key_1) = identity_1;
1124
1125        let (identity_2, signer_2, key_2) = identity_2;
1126
1127        let dpns = platform.drive.cache.system_data_contracts.load_dpns();
1128        let dpns_contract = dpns.clone();
1129
1130        let preorder = dpns_contract
1131            .document_type_for_name("preorder")
1132            .expect("expected a profile document type");
1133
1134        assert!(!preorder.documents_mutable());
1135        assert!(preorder.documents_can_be_deleted());
1136        assert!(!preorder.documents_transferable().is_transferable());
1137
1138        let domain = dpns_contract
1139            .document_type_for_name("domain")
1140            .expect("expected a profile document type");
1141
1142        assert!(!domain.documents_mutable());
1143        // Deletion is disabled with data trigger
1144        assert!(domain.documents_can_be_deleted());
1145        assert!(domain.documents_transferable().is_transferable());
1146
1147        let entropy = Bytes32::random_with_rng(&mut rng);
1148
1149        let mut preorder_document_1 = preorder
1150            .random_document_with_identifier_and_entropy(
1151                &mut rng,
1152                identity_1.id(),
1153                entropy,
1154                DocumentFieldFillType::FillIfNotRequired,
1155                DocumentFieldFillSize::AnyDocumentFillSize,
1156                platform_version,
1157            )
1158            .expect("expected a random document");
1159
1160        let mut preorder_document_2 = preorder
1161            .random_document_with_identifier_and_entropy(
1162                &mut rng,
1163                identity_2.id(),
1164                entropy,
1165                DocumentFieldFillType::FillIfNotRequired,
1166                DocumentFieldFillSize::AnyDocumentFillSize,
1167                platform_version,
1168            )
1169            .expect("expected a random document");
1170
1171        let mut document_1 = domain
1172            .random_document_with_identifier_and_entropy(
1173                &mut rng,
1174                identity_1.id(),
1175                entropy,
1176                DocumentFieldFillType::FillIfNotRequired,
1177                DocumentFieldFillSize::AnyDocumentFillSize,
1178                platform_version,
1179            )
1180            .expect("expected a random document");
1181
1182        let mut document_2 = domain
1183            .random_document_with_identifier_and_entropy(
1184                &mut rng,
1185                identity_2.id(),
1186                entropy,
1187                DocumentFieldFillType::FillIfNotRequired,
1188                DocumentFieldFillSize::AnyDocumentFillSize,
1189                platform_version,
1190            )
1191            .expect("expected a random document");
1192
1193        document_1.set("parentDomainName", "dash".into());
1194        document_1.set("normalizedParentDomainName", "dash".into());
1195        document_1.set("label", name.into());
1196        document_1.set(
1197            "normalizedLabel",
1198            convert_to_homograph_safe_chars(name).into(),
1199        );
1200        document_1.set("records.identity", document_1.owner_id().into());
1201        document_1.set("subdomainRules.allowSubdomains", false.into());
1202
1203        document_2.set("parentDomainName", "dash".into());
1204        document_2.set("normalizedParentDomainName", "dash".into());
1205        document_2.set("label", name.into());
1206        document_2.set(
1207            "normalizedLabel",
1208            convert_to_homograph_safe_chars(name).into(),
1209        );
1210        document_2.set("records.identity", document_2.owner_id().into());
1211        document_2.set("subdomainRules.allowSubdomains", false.into());
1212
1213        let salt_1: [u8; 32] = rng.gen();
1214        let salt_2: [u8; 32] = rng.gen();
1215
1216        let mut salted_domain_buffer_1: Vec<u8> = vec![];
1217        salted_domain_buffer_1.extend(salt_1);
1218        salted_domain_buffer_1.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1219
1220        let salted_domain_hash_1 = hash_double(salted_domain_buffer_1);
1221
1222        let mut salted_domain_buffer_2: Vec<u8> = vec![];
1223        salted_domain_buffer_2.extend(salt_2);
1224        salted_domain_buffer_2.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1225
1226        let salted_domain_hash_2 = hash_double(salted_domain_buffer_2);
1227
1228        preorder_document_1.set("saltedDomainHash", salted_domain_hash_1.into());
1229        preorder_document_2.set("saltedDomainHash", salted_domain_hash_2.into());
1230
1231        document_1.set("preorderSalt", salt_1.into());
1232        document_2.set("preorderSalt", salt_2.into());
1233
1234        let documents_batch_create_preorder_transition_1 =
1235            BatchTransition::new_document_creation_transition_from_document(
1236                preorder_document_1.clone(),
1237                preorder,
1238                entropy.0,
1239                key_1,
1240                2 + nonce_offset.unwrap_or_default(),
1241                0,
1242                None,
1243                signer_1,
1244                platform_version,
1245                None,
1246            )
1247            .expect("expect to create documents batch transition");
1248
1249        let documents_batch_create_serialized_preorder_transition_1 =
1250            documents_batch_create_preorder_transition_1
1251                .serialize_to_bytes()
1252                .expect("expected documents batch serialized state transition");
1253
1254        let documents_batch_create_preorder_transition_2 =
1255            BatchTransition::new_document_creation_transition_from_document(
1256                preorder_document_2.clone(),
1257                preorder,
1258                entropy.0,
1259                key_2,
1260                2 + nonce_offset.unwrap_or_default(),
1261                0,
1262                None,
1263                signer_2,
1264                platform_version,
1265                None,
1266            )
1267            .expect("expect to create documents batch transition");
1268
1269        let documents_batch_create_serialized_preorder_transition_2 =
1270            documents_batch_create_preorder_transition_2
1271                .serialize_to_bytes()
1272                .expect("expected documents batch serialized state transition");
1273
1274        let documents_batch_create_transition_1 =
1275            BatchTransition::new_document_creation_transition_from_document(
1276                document_1.clone(),
1277                domain,
1278                entropy.0,
1279                key_1,
1280                3 + nonce_offset.unwrap_or_default(),
1281                0,
1282                None,
1283                signer_1,
1284                platform_version,
1285                None,
1286            )
1287            .expect("expect to create documents batch transition");
1288
1289        let documents_batch_create_serialized_transition_1 = documents_batch_create_transition_1
1290            .serialize_to_bytes()
1291            .expect("expected documents batch serialized state transition");
1292
1293        let documents_batch_create_transition_2 =
1294            BatchTransition::new_document_creation_transition_from_document(
1295                document_2.clone(),
1296                domain,
1297                entropy.0,
1298                key_2,
1299                3 + nonce_offset.unwrap_or_default(),
1300                0,
1301                None,
1302                signer_2,
1303                platform_version,
1304                None,
1305            )
1306            .expect("expect to create documents batch transition");
1307
1308        let documents_batch_create_serialized_transition_2 = documents_batch_create_transition_2
1309            .serialize_to_bytes()
1310            .expect("expected documents batch serialized state transition");
1311
1312        if !skip_preorder {
1313            let transaction = platform.drive.grove.start_transaction();
1314
1315            let processing_result = platform
1316                .platform
1317                .process_raw_state_transitions(
1318                    &[
1319                        documents_batch_create_serialized_preorder_transition_1.clone(),
1320                        documents_batch_create_serialized_preorder_transition_2.clone(),
1321                    ],
1322                    platform_state,
1323                    &BlockInfo::default_with_time(
1324                        platform_state
1325                            .last_committed_block_time_ms()
1326                            .unwrap_or_default()
1327                            + 3000,
1328                    ),
1329                    &transaction,
1330                    platform_version,
1331                    false,
1332                    None,
1333                )
1334                .expect("expected to process state transition");
1335
1336            platform
1337                .drive
1338                .grove
1339                .commit_transaction(transaction)
1340                .unwrap()
1341                .expect("expected to commit transaction");
1342
1343            let successful_count = processing_result
1344                .execution_results()
1345                .iter()
1346                .filter(|result| {
1347                    assert_matches!(
1348                        result,
1349                        StateTransitionExecutionResult::SuccessfulExecution { .. }
1350                    );
1351                    true
1352                })
1353                .count();
1354
1355            assert_eq!(successful_count, 2);
1356        }
1357
1358        let transaction = platform.drive.grove.start_transaction();
1359
1360        let processing_result = platform
1361            .platform
1362            .process_raw_state_transitions(
1363                &[
1364                    documents_batch_create_serialized_transition_1.clone(),
1365                    documents_batch_create_serialized_transition_2.clone(),
1366                ],
1367                platform_state,
1368                &BlockInfo::default_with_time(
1369                    platform_state
1370                        .last_committed_block_time_ms()
1371                        .unwrap_or_default()
1372                        + 3000,
1373                ),
1374                &transaction,
1375                platform_version,
1376                false,
1377                None,
1378            )
1379            .expect("expected to process state transition");
1380
1381        platform
1382            .drive
1383            .grove
1384            .commit_transaction(transaction)
1385            .unwrap()
1386            .expect("expected to commit transaction");
1387
1388        let successful_count = processing_result
1389            .execution_results()
1390            .iter()
1391            .filter(|result| {
1392                assert_matches!(
1393                    result,
1394                    StateTransitionExecutionResult::SuccessfulExecution { .. }
1395                );
1396                true
1397            })
1398            .count();
1399
1400        assert_eq!(successful_count, 2);
1401        (
1402            ((preorder_document_1, entropy), (document_1, entropy)),
1403            ((preorder_document_2, entropy), (document_2, entropy)),
1404            dpns_contract,
1405        )
1406    }
1407
1408    #[allow(clippy::too_many_arguments)]
1409    fn create_dpns_name_contest_on_identities_for_contract_records(
1410        platform: &mut TempPlatform<MockCoreRPCLike>,
1411        identity_1: &(Identity, SimpleSigner, IdentityPublicKey),
1412        identity_2: &(Identity, SimpleSigner, IdentityPublicKey),
1413        contract_1: &DataContract,
1414        contract_2: &DataContract,
1415        platform_state: &PlatformState,
1416        mut rng: StdRng,
1417        name: &str,
1418        platform_version: &PlatformVersion,
1419    ) -> (
1420        ((Document, Bytes32), (Document, Bytes32)),
1421        ((Document, Bytes32), (Document, Bytes32)),
1422        DataContract,
1423    ) {
1424        let (identity_1, signer_1, key_1) = identity_1;
1425
1426        let (identity_2, signer_2, key_2) = identity_2;
1427
1428        let dpns_contract = setup_contract(
1429            &platform.drive,
1430            "tests/supporting_files/contract/dpns/dpns-contract-contested-unique-index-with-contract-id.json",
1431            None,
1432            None,
1433            None::<fn(&mut DataContract)>,
1434            None,
1435            None,
1436        );
1437
1438        let preorder = dpns_contract
1439            .document_type_for_name("preorder")
1440            .expect("expected a profile document type");
1441
1442        assert!(!preorder.documents_mutable());
1443        assert!(preorder.documents_can_be_deleted());
1444        assert!(!preorder.documents_transferable().is_transferable());
1445
1446        let domain = dpns_contract
1447            .document_type_for_name("domain")
1448            .expect("expected a profile document type");
1449
1450        assert!(!domain.documents_mutable());
1451        // Deletion is disabled with data trigger
1452        assert!(domain.documents_can_be_deleted());
1453        assert!(domain.documents_transferable().is_transferable());
1454
1455        let entropy = Bytes32::random_with_rng(&mut rng);
1456
1457        let mut preorder_document_1 = preorder
1458            .random_document_with_identifier_and_entropy(
1459                &mut rng,
1460                identity_1.id(),
1461                entropy,
1462                DocumentFieldFillType::FillIfNotRequired,
1463                DocumentFieldFillSize::AnyDocumentFillSize,
1464                platform_version,
1465            )
1466            .expect("expected a random document");
1467
1468        let mut preorder_document_2 = preorder
1469            .random_document_with_identifier_and_entropy(
1470                &mut rng,
1471                identity_2.id(),
1472                entropy,
1473                DocumentFieldFillType::FillIfNotRequired,
1474                DocumentFieldFillSize::AnyDocumentFillSize,
1475                platform_version,
1476            )
1477            .expect("expected a random document");
1478
1479        let mut document_1 = domain
1480            .random_document_with_identifier_and_entropy(
1481                &mut rng,
1482                identity_1.id(),
1483                entropy,
1484                DocumentFieldFillType::FillIfNotRequired,
1485                DocumentFieldFillSize::AnyDocumentFillSize,
1486                platform_version,
1487            )
1488            .expect("expected a random document");
1489
1490        let mut document_2 = domain
1491            .random_document_with_identifier_and_entropy(
1492                &mut rng,
1493                identity_2.id(),
1494                entropy,
1495                DocumentFieldFillType::FillIfNotRequired,
1496                DocumentFieldFillSize::AnyDocumentFillSize,
1497                platform_version,
1498            )
1499            .expect("expected a random document");
1500
1501        document_1.set("parentDomainName", "dash".into());
1502        document_1.set("normalizedParentDomainName", "dash".into());
1503        document_1.set("label", name.into());
1504        document_1.set(
1505            "normalizedLabel",
1506            convert_to_homograph_safe_chars(name).into(),
1507        );
1508        document_1.remove("records.identity");
1509        document_1.set("records.contract", contract_1.id().into());
1510        document_1.set("subdomainRules.allowSubdomains", false.into());
1511
1512        document_2.set("parentDomainName", "dash".into());
1513        document_2.set("normalizedParentDomainName", "dash".into());
1514        document_2.set("label", name.into());
1515        document_2.set(
1516            "normalizedLabel",
1517            convert_to_homograph_safe_chars(name).into(),
1518        );
1519        document_2.remove("records.identity");
1520        document_2.set("records.contract", contract_2.id().into());
1521        document_2.set("subdomainRules.allowSubdomains", false.into());
1522
1523        let salt_1: [u8; 32] = rng.gen();
1524        let salt_2: [u8; 32] = rng.gen();
1525
1526        let mut salted_domain_buffer_1: Vec<u8> = vec![];
1527        salted_domain_buffer_1.extend(salt_1);
1528        salted_domain_buffer_1.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1529
1530        let salted_domain_hash_1 = hash_double(salted_domain_buffer_1);
1531
1532        let mut salted_domain_buffer_2: Vec<u8> = vec![];
1533        salted_domain_buffer_2.extend(salt_2);
1534        salted_domain_buffer_2.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1535
1536        let salted_domain_hash_2 = hash_double(salted_domain_buffer_2);
1537
1538        preorder_document_1.set("saltedDomainHash", salted_domain_hash_1.into());
1539        preorder_document_2.set("saltedDomainHash", salted_domain_hash_2.into());
1540
1541        document_1.set("preorderSalt", salt_1.into());
1542        document_2.set("preorderSalt", salt_2.into());
1543
1544        let documents_batch_create_preorder_transition_1 =
1545            BatchTransition::new_document_creation_transition_from_document(
1546                preorder_document_1.clone(),
1547                preorder,
1548                entropy.0,
1549                key_1,
1550                2,
1551                0,
1552                None,
1553                signer_1,
1554                platform_version,
1555                None,
1556            )
1557            .expect("expect to create documents batch transition");
1558
1559        let documents_batch_create_serialized_preorder_transition_1 =
1560            documents_batch_create_preorder_transition_1
1561                .serialize_to_bytes()
1562                .expect("expected documents batch serialized state transition");
1563
1564        let documents_batch_create_preorder_transition_2 =
1565            BatchTransition::new_document_creation_transition_from_document(
1566                preorder_document_2.clone(),
1567                preorder,
1568                entropy.0,
1569                key_2,
1570                2,
1571                0,
1572                None,
1573                signer_2,
1574                platform_version,
1575                None,
1576            )
1577            .expect("expect to create documents batch transition");
1578
1579        let documents_batch_create_serialized_preorder_transition_2 =
1580            documents_batch_create_preorder_transition_2
1581                .serialize_to_bytes()
1582                .expect("expected documents batch serialized state transition");
1583
1584        let documents_batch_create_transition_1 =
1585            BatchTransition::new_document_creation_transition_from_document(
1586                document_1.clone(),
1587                domain,
1588                entropy.0,
1589                key_1,
1590                3,
1591                0,
1592                None,
1593                signer_1,
1594                platform_version,
1595                None,
1596            )
1597            .expect("expect to create documents batch transition");
1598
1599        let documents_batch_create_serialized_transition_1 = documents_batch_create_transition_1
1600            .serialize_to_bytes()
1601            .expect("expected documents batch serialized state transition");
1602
1603        let documents_batch_create_transition_2 =
1604            BatchTransition::new_document_creation_transition_from_document(
1605                document_2.clone(),
1606                domain,
1607                entropy.0,
1608                key_2,
1609                3,
1610                0,
1611                None,
1612                signer_2,
1613                platform_version,
1614                None,
1615            )
1616            .expect("expect to create documents batch transition");
1617
1618        let documents_batch_create_serialized_transition_2 = documents_batch_create_transition_2
1619            .serialize_to_bytes()
1620            .expect("expected documents batch serialized state transition");
1621
1622        let transaction = platform.drive.grove.start_transaction();
1623
1624        let processing_result = platform
1625            .platform
1626            .process_raw_state_transitions(
1627                &[
1628                    documents_batch_create_serialized_preorder_transition_1.clone(),
1629                    documents_batch_create_serialized_preorder_transition_2.clone(),
1630                ],
1631                platform_state,
1632                &BlockInfo::default_with_time(
1633                    platform_state
1634                        .last_committed_block_time_ms()
1635                        .unwrap_or_default()
1636                        + 3000,
1637                ),
1638                &transaction,
1639                platform_version,
1640                false,
1641                None,
1642            )
1643            .expect("expected to process state transition");
1644
1645        platform
1646            .drive
1647            .grove
1648            .commit_transaction(transaction)
1649            .unwrap()
1650            .expect("expected to commit transaction");
1651
1652        assert_eq!(processing_result.valid_count(), 2);
1653
1654        let transaction = platform.drive.grove.start_transaction();
1655
1656        let processing_result = platform
1657            .platform
1658            .process_raw_state_transitions(
1659                &[
1660                    documents_batch_create_serialized_transition_1.clone(),
1661                    documents_batch_create_serialized_transition_2.clone(),
1662                ],
1663                platform_state,
1664                &BlockInfo::default_with_time(
1665                    platform_state
1666                        .last_committed_block_time_ms()
1667                        .unwrap_or_default()
1668                        + 3000,
1669                ),
1670                &transaction,
1671                platform_version,
1672                false,
1673                None,
1674            )
1675            .expect("expected to process state transition");
1676
1677        platform
1678            .drive
1679            .grove
1680            .commit_transaction(transaction)
1681            .unwrap()
1682            .expect("expected to commit transaction");
1683
1684        assert_eq!(processing_result.valid_count(), 2);
1685        (
1686            ((preorder_document_1, entropy), (document_1, entropy)),
1687            ((preorder_document_2, entropy), (document_2, entropy)),
1688            dpns_contract,
1689        )
1690    }
1691
1692    pub(in crate::execution) fn add_contender_to_dpns_name_contest(
1693        platform: &mut TempPlatform<MockCoreRPCLike>,
1694        platform_state: &PlatformState,
1695        seed: u64,
1696        name: &str,
1697        expect_err: Option<&str>,
1698        platform_version: &PlatformVersion,
1699    ) -> Identity {
1700        let mut rng = StdRng::seed_from_u64(seed);
1701
1702        let (identity_1, signer_1, key_1) =
1703            setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
1704
1705        let dpns = platform.drive.cache.system_data_contracts.load_dpns();
1706        let dpns_contract = dpns.clone();
1707
1708        let preorder = dpns_contract
1709            .document_type_for_name("preorder")
1710            .expect("expected a profile document type");
1711
1712        let domain = dpns_contract
1713            .document_type_for_name("domain")
1714            .expect("expected a profile document type");
1715
1716        let entropy = Bytes32::random_with_rng(&mut rng);
1717
1718        let mut preorder_document_1 = preorder
1719            .random_document_with_identifier_and_entropy(
1720                &mut rng,
1721                identity_1.id(),
1722                entropy,
1723                DocumentFieldFillType::FillIfNotRequired,
1724                DocumentFieldFillSize::AnyDocumentFillSize,
1725                platform_version,
1726            )
1727            .expect("expected a random document");
1728
1729        let mut document_1 = domain
1730            .random_document_with_identifier_and_entropy(
1731                &mut rng,
1732                identity_1.id(),
1733                entropy,
1734                DocumentFieldFillType::FillIfNotRequired,
1735                DocumentFieldFillSize::AnyDocumentFillSize,
1736                platform_version,
1737            )
1738            .expect("expected a random document");
1739
1740        document_1.set("parentDomainName", "dash".into());
1741        document_1.set("normalizedParentDomainName", "dash".into());
1742        document_1.set("label", name.into());
1743        document_1.set(
1744            "normalizedLabel",
1745            convert_to_homograph_safe_chars(name).into(),
1746        );
1747        document_1.set("records.identity", document_1.owner_id().into());
1748        document_1.set("subdomainRules.allowSubdomains", false.into());
1749
1750        let salt_1: [u8; 32] = rng.gen();
1751
1752        let mut salted_domain_buffer_1: Vec<u8> = vec![];
1753        salted_domain_buffer_1.extend(salt_1);
1754        salted_domain_buffer_1.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1755
1756        let salted_domain_hash_1 = hash_double(salted_domain_buffer_1);
1757
1758        preorder_document_1.set("saltedDomainHash", salted_domain_hash_1.into());
1759
1760        document_1.set("preorderSalt", salt_1.into());
1761
1762        let documents_batch_create_preorder_transition_1 =
1763            BatchTransition::new_document_creation_transition_from_document(
1764                preorder_document_1,
1765                preorder,
1766                entropy.0,
1767                &key_1,
1768                2,
1769                0,
1770                None,
1771                &signer_1,
1772                platform_version,
1773                None,
1774            )
1775            .expect("expect to create documents batch transition");
1776
1777        let documents_batch_create_serialized_preorder_transition_1 =
1778            documents_batch_create_preorder_transition_1
1779                .serialize_to_bytes()
1780                .expect("expected documents batch serialized state transition");
1781
1782        let documents_batch_create_transition_1 =
1783            BatchTransition::new_document_creation_transition_from_document(
1784                document_1,
1785                domain,
1786                entropy.0,
1787                &key_1,
1788                3,
1789                0,
1790                None,
1791                &signer_1,
1792                platform_version,
1793                None,
1794            )
1795            .expect("expect to create documents batch transition");
1796
1797        let documents_batch_create_serialized_transition_1 = documents_batch_create_transition_1
1798            .serialize_to_bytes()
1799            .expect("expected documents batch serialized state transition");
1800
1801        let transaction = platform.drive.grove.start_transaction();
1802
1803        let processing_result = platform
1804            .platform
1805            .process_raw_state_transitions(
1806                &[documents_batch_create_serialized_preorder_transition_1.clone()],
1807                platform_state,
1808                &BlockInfo::default_with_time(
1809                    platform_state
1810                        .last_committed_block_time_ms()
1811                        .unwrap_or_default()
1812                        + 3000,
1813                ),
1814                &transaction,
1815                platform_version,
1816                false,
1817                None,
1818            )
1819            .expect("expected to process state transition");
1820
1821        platform
1822            .drive
1823            .grove
1824            .commit_transaction(transaction)
1825            .unwrap()
1826            .expect("expected to commit transaction");
1827
1828        assert_eq!(processing_result.valid_count(), 1);
1829
1830        let transaction = platform.drive.grove.start_transaction();
1831
1832        let processing_result = platform
1833            .platform
1834            .process_raw_state_transitions(
1835                &[documents_batch_create_serialized_transition_1.clone()],
1836                platform_state,
1837                &BlockInfo::default_with_time(
1838                    platform_state
1839                        .last_committed_block_time_ms()
1840                        .unwrap_or_default()
1841                        + 3000,
1842                ),
1843                &transaction,
1844                platform_version,
1845                false,
1846                None,
1847            )
1848            .expect("expected to process state transition");
1849
1850        platform
1851            .drive
1852            .grove
1853            .commit_transaction(transaction)
1854            .unwrap()
1855            .expect("expected to commit transaction");
1856
1857        if let Some(expected_err) = expect_err {
1858            let result = processing_result.into_execution_results().remove(0);
1859
1860            let StateTransitionExecutionResult::PaidConsensusError {
1861                error: consensus_error,
1862                ..
1863            } = result
1864            else {
1865                panic!("expected a paid consensus error");
1866            };
1867            assert_eq!(consensus_error.to_string(), expected_err);
1868        } else {
1869            assert_eq!(processing_result.valid_count(), 1);
1870        }
1871        identity_1
1872    }
1873
1874    pub(in crate::execution) fn verify_dpns_name_contest(
1875        platform: &mut TempPlatform<MockCoreRPCLike>,
1876        platform_state: &Guard<Arc<PlatformState>>,
1877        dpns_contract: &DataContract,
1878        identity_1: &Identity,
1879        identity_2: &Identity,
1880        name: &str,
1881        platform_version: &PlatformVersion,
1882    ) {
1883        // Now let's run a query for the vote totals
1884
1885        let domain = dpns_contract
1886            .document_type_for_name("domain")
1887            .expect("expected a profile document type");
1888
1889        let config = bincode::config::standard()
1890            .with_big_endian()
1891            .with_no_limit();
1892
1893        let dash_encoded = bincode::encode_to_vec(Value::Text("dash".to_string()), config)
1894            .expect("expected to encode the word dash");
1895
1896        let quantum_encoded =
1897            bincode::encode_to_vec(Value::Text(convert_to_homograph_safe_chars(name)), config)
1898                .expect("expected to encode the word quantum");
1899
1900        let index_name = "parentNameAndLabel".to_string();
1901
1902        let query_validation_result = platform
1903            .query_contested_resource_vote_state(
1904                GetContestedResourceVoteStateRequest {
1905                    version: Some(get_contested_resource_vote_state_request::Version::V0(
1906                        GetContestedResourceVoteStateRequestV0 {
1907                            contract_id: dpns_contract.id().to_vec(),
1908                            document_type_name: domain.name().clone(),
1909                            index_name: index_name.clone(),
1910                            index_values: vec![dash_encoded.clone(), quantum_encoded.clone()],
1911                            result_type: ResultType::DocumentsAndVoteTally as i32,
1912                            allow_include_locked_and_abstaining_vote_tally: true,
1913                            start_at_identifier_info: None,
1914                            count: None,
1915                            prove: false,
1916                        },
1917                    )),
1918                },
1919                platform_state,
1920                platform_version,
1921            )
1922            .expect("expected to execute query")
1923            .into_data()
1924            .expect("expected query to be valid");
1925
1926        let get_contested_resource_vote_state_response::Version::V0(
1927            GetContestedResourceVoteStateResponseV0 {
1928                metadata: _,
1929                result,
1930            },
1931        ) = query_validation_result.version.expect("expected a version");
1932
1933        let Some(
1934            get_contested_resource_vote_state_response_v0::Result::ContestedResourceContenders(
1935                get_contested_resource_vote_state_response_v0::ContestedResourceContenders {
1936                    contenders,
1937                    ..
1938                },
1939            ),
1940        ) = result
1941        else {
1942            panic!("expected contenders")
1943        };
1944
1945        assert_eq!(contenders.len(), 2);
1946
1947        let first_contender = contenders.first().unwrap();
1948
1949        let second_contender = contenders.last().unwrap();
1950
1951        let first_contender_document = Document::from_bytes(
1952            first_contender
1953                .document
1954                .as_ref()
1955                .expect("expected a document")
1956                .as_slice(),
1957            domain,
1958            platform_version,
1959        )
1960        .expect("expected to get document");
1961
1962        let second_contender_document = Document::from_bytes(
1963            second_contender
1964                .document
1965                .as_ref()
1966                .expect("expected a document")
1967                .as_slice(),
1968            domain,
1969            platform_version,
1970        )
1971        .expect("expected to get document");
1972
1973        assert_ne!(first_contender_document, second_contender_document);
1974
1975        assert_eq!(first_contender.identifier, identity_1.id().to_vec());
1976
1977        assert_eq!(second_contender.identifier, identity_2.id().to_vec());
1978
1979        assert_eq!(first_contender.vote_count, Some(0));
1980
1981        assert_eq!(second_contender.vote_count, Some(0));
1982
1983        let GetContestedResourceVoteStateResponse { version } = platform
1984            .query_contested_resource_vote_state(
1985                GetContestedResourceVoteStateRequest {
1986                    version: Some(get_contested_resource_vote_state_request::Version::V0(
1987                        GetContestedResourceVoteStateRequestV0 {
1988                            contract_id: dpns_contract.id().to_vec(),
1989                            document_type_name: domain.name().clone(),
1990                            index_name: "parentNameAndLabel".to_string(),
1991                            index_values: vec![dash_encoded, quantum_encoded],
1992                            result_type: ResultType::DocumentsAndVoteTally as i32,
1993                            allow_include_locked_and_abstaining_vote_tally: true,
1994                            start_at_identifier_info: None,
1995                            count: None,
1996                            prove: true,
1997                        },
1998                    )),
1999                },
2000                platform_state,
2001                platform_version,
2002            )
2003            .expect("expected to execute query")
2004            .into_data()
2005            .expect("expected query to be valid");
2006
2007        let get_contested_resource_vote_state_response::Version::V0(
2008            GetContestedResourceVoteStateResponseV0 {
2009                metadata: _,
2010                result,
2011            },
2012        ) = version.expect("expected a version");
2013
2014        let Some(get_contested_resource_vote_state_response_v0::Result::Proof(proof)) = result
2015        else {
2016            panic!("expected contenders")
2017        };
2018
2019        let resolved_contested_document_vote_poll_drive_query =
2020            ResolvedContestedDocumentVotePollDriveQuery {
2021                vote_poll: ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed {
2022                    contract: DataContractResolvedInfo::BorrowedDataContract(dpns_contract),
2023                    document_type_name: domain.name().clone(),
2024                    index_name: index_name.clone(),
2025                    index_values: vec![
2026                        Value::Text("dash".to_string()),
2027                        Value::Text(convert_to_homograph_safe_chars(name)),
2028                    ],
2029                },
2030                result_type: DocumentsAndVoteTally,
2031                offset: None,
2032                limit: None,
2033                start_at: None,
2034                allow_include_locked_and_abstaining_vote_tally: true,
2035            };
2036
2037        let (_, result) = resolved_contested_document_vote_poll_drive_query
2038            .verify_vote_poll_vote_state_proof(proof.grovedb_proof.as_ref(), platform_version)
2039            .expect("expected to verify proof");
2040
2041        let contenders = result.contenders;
2042
2043        assert_eq!(contenders.len(), 2);
2044
2045        let first_contender = contenders.first().unwrap();
2046
2047        let second_contender = contenders.last().unwrap();
2048
2049        let first_contender_document = Document::from_bytes(
2050            first_contender
2051                .serialized_document()
2052                .as_ref()
2053                .expect("expected a document")
2054                .as_slice(),
2055            domain,
2056            platform_version,
2057        )
2058        .expect("expected to get document");
2059
2060        let second_contender_document = Document::from_bytes(
2061            second_contender
2062                .serialized_document()
2063                .as_ref()
2064                .expect("expected a document")
2065                .as_slice(),
2066            domain,
2067            platform_version,
2068        )
2069        .expect("expected to get document");
2070
2071        assert_ne!(first_contender_document, second_contender_document);
2072
2073        assert_eq!(first_contender.identity_id(), identity_1.id());
2074
2075        assert_eq!(second_contender.identity_id(), identity_2.id());
2076
2077        assert_eq!(first_contender.vote_tally(), Some(0));
2078
2079        assert_eq!(second_contender.vote_tally(), Some(0));
2080    }
2081
2082    #[allow(clippy::too_many_arguments)]
2083    pub(in crate::execution) fn perform_vote(
2084        platform: &mut TempPlatform<MockCoreRPCLike>,
2085        platform_state: &Guard<Arc<PlatformState>>,
2086        dpns_contract: &DataContract,
2087        resource_vote_choice: ResourceVoteChoice,
2088        name: &str,
2089        signer: &SimpleSigner,
2090        pro_tx_hash: Identifier,
2091        voting_key: &IdentityPublicKey,
2092        nonce: IdentityNonce,
2093        expect_error: Option<&str>,
2094        platform_version: &PlatformVersion,
2095    ) {
2096        // Let's vote for contender 1
2097
2098        let vote = Vote::ResourceVote(ResourceVote::V0(ResourceVoteV0 {
2099            vote_poll: VotePoll::ContestedDocumentResourceVotePoll(
2100                ContestedDocumentResourceVotePoll {
2101                    contract_id: dpns_contract.id(),
2102                    document_type_name: "domain".to_string(),
2103                    index_name: "parentNameAndLabel".to_string(),
2104                    index_values: vec![
2105                        Value::Text("dash".to_string()),
2106                        Value::Text(convert_to_homograph_safe_chars(name)),
2107                    ],
2108                },
2109            ),
2110            resource_vote_choice,
2111        }));
2112
2113        let masternode_vote_transition = MasternodeVoteTransition::try_from_vote_with_signer(
2114            vote,
2115            signer,
2116            pro_tx_hash,
2117            voting_key,
2118            nonce,
2119            platform_version,
2120            None,
2121        )
2122        .expect("expected to make transition vote");
2123
2124        let masternode_vote_serialized_transition = masternode_vote_transition
2125            .serialize_to_bytes()
2126            .expect("expected documents batch serialized state transition");
2127
2128        let transaction = platform.drive.grove.start_transaction();
2129
2130        let processing_result = platform
2131            .platform
2132            .process_raw_state_transitions(
2133                &[masternode_vote_serialized_transition.clone()],
2134                platform_state,
2135                &BlockInfo::default(),
2136                &transaction,
2137                platform_version,
2138                false,
2139                None,
2140            )
2141            .expect("expected to process state transition");
2142
2143        platform
2144            .drive
2145            .grove
2146            .commit_transaction(transaction)
2147            .unwrap()
2148            .expect("expected to commit transaction");
2149
2150        let execution_result = processing_result.into_execution_results().remove(0);
2151        if let Some(error_msg) = expect_error {
2152            assert_matches!(execution_result, UnpaidConsensusError(..));
2153            let UnpaidConsensusError(consensus_error) = execution_result else {
2154                panic!()
2155            };
2156            assert_eq!(consensus_error.to_string(), error_msg)
2157        } else {
2158            assert_matches!(execution_result, SuccessfulExecution { .. });
2159        }
2160    }
2161
2162    #[allow(clippy::too_many_arguments)]
2163    pub(in crate::execution) fn perform_votes(
2164        platform: &mut TempPlatform<MockCoreRPCLike>,
2165        dpns_contract: &DataContract,
2166        resource_vote_choice: ResourceVoteChoice,
2167        name: &str,
2168        count: u64,
2169        start_seed: u64,
2170        nonce_offset: Option<IdentityNonce>,
2171        platform_version: &PlatformVersion,
2172    ) -> Vec<(Identifier, Identity, SimpleSigner, IdentityPublicKey)> {
2173        let mut masternode_infos = vec![];
2174        for i in 0..count {
2175            let (pro_tx_hash_bytes, voting_identity, signer, voting_key) =
2176                setup_masternode_voting_identity(platform, start_seed + i, platform_version);
2177
2178            let platform_state = platform.state.load();
2179
2180            perform_vote(
2181                platform,
2182                &platform_state,
2183                dpns_contract,
2184                resource_vote_choice,
2185                name,
2186                &signer,
2187                pro_tx_hash_bytes,
2188                &voting_key,
2189                1 + nonce_offset.unwrap_or_default(),
2190                None,
2191                platform_version,
2192            );
2193
2194            masternode_infos.push((pro_tx_hash_bytes, voting_identity, signer, voting_key));
2195        }
2196        masternode_infos
2197    }
2198
2199    pub(in crate::execution) fn perform_votes_multi(
2200        platform: &mut TempPlatform<MockCoreRPCLike>,
2201        dpns_contract: &DataContract,
2202        resource_vote_choices: Vec<(ResourceVoteChoice, u64)>,
2203        name: &str,
2204        start_seed: u64,
2205        nonce_offset: Option<IdentityNonce>,
2206        platform_version: &PlatformVersion,
2207    ) -> BTreeMap<ResourceVoteChoice, Vec<(Identifier, Identity, SimpleSigner, IdentityPublicKey)>>
2208    {
2209        let mut count_aggregate = start_seed;
2210        let mut masternodes_by_vote_choice = BTreeMap::new();
2211        for (resource_vote_choice, count) in resource_vote_choices.into_iter() {
2212            let masternode_infos = perform_votes(
2213                platform,
2214                dpns_contract,
2215                resource_vote_choice,
2216                name,
2217                count,
2218                count_aggregate,
2219                nonce_offset,
2220                platform_version,
2221            );
2222            masternodes_by_vote_choice.insert(resource_vote_choice, masternode_infos);
2223            count_aggregate += count;
2224        }
2225        masternodes_by_vote_choice
2226    }
2227
2228    #[allow(clippy::too_many_arguments)]
2229    pub(in crate::execution) fn get_vote_states(
2230        platform: &TempPlatform<MockCoreRPCLike>,
2231        platform_state: &PlatformState,
2232        dpns_contract: &DataContract,
2233        name: &str,
2234        count: Option<u32>,
2235        allow_include_locked_and_abstaining_vote_tally: bool,
2236        start_at_identifier_info: Option<
2237            get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo,
2238        >,
2239        result_type: ResultType,
2240        platform_version: &PlatformVersion,
2241    ) -> (
2242        Vec<Contender>,
2243        Option<u32>,
2244        Option<u32>,
2245        Option<FinishedVoteInfo>,
2246    ) {
2247        // Now let's run a query for the vote totals
2248
2249        let domain = dpns_contract
2250            .document_type_for_name("domain")
2251            .expect("expected a profile document type");
2252
2253        let config = bincode::config::standard()
2254            .with_big_endian()
2255            .with_no_limit();
2256
2257        let dash_encoded = bincode::encode_to_vec(Value::Text("dash".to_string()), config)
2258            .expect("expected to encode the word dash");
2259
2260        let name_encoded =
2261            bincode::encode_to_vec(Value::Text(convert_to_homograph_safe_chars(name)), config)
2262                .expect("expected to encode the word quantum");
2263
2264        let index_name = "parentNameAndLabel".to_string();
2265
2266        let query_validation_result = platform
2267            .query_contested_resource_vote_state(
2268                GetContestedResourceVoteStateRequest {
2269                    version: Some(get_contested_resource_vote_state_request::Version::V0(
2270                        GetContestedResourceVoteStateRequestV0 {
2271                            contract_id: dpns_contract.id().to_vec(),
2272                            document_type_name: domain.name().clone(),
2273                            index_name: index_name.clone(),
2274                            index_values: vec![dash_encoded.clone(), name_encoded.clone()],
2275                            result_type: result_type as i32,
2276                            allow_include_locked_and_abstaining_vote_tally,
2277                            start_at_identifier_info,
2278                            count,
2279                            prove: false,
2280                        },
2281                    )),
2282                },
2283                platform_state,
2284                platform_version,
2285            )
2286            .expect("expected to execute query")
2287            .into_data()
2288            .expect("expected query to be valid");
2289
2290        let get_contested_resource_vote_state_response::Version::V0(
2291            GetContestedResourceVoteStateResponseV0 {
2292                metadata: _,
2293                result,
2294            },
2295        ) = query_validation_result.version.expect("expected a version");
2296
2297        let Some(
2298            get_contested_resource_vote_state_response_v0::Result::ContestedResourceContenders(
2299                get_contested_resource_vote_state_response_v0::ContestedResourceContenders {
2300                    contenders,
2301                    abstain_vote_tally,
2302                    lock_vote_tally,
2303                    finished_vote_info,
2304                },
2305            ),
2306        ) = result
2307        else {
2308            panic!("expected contenders")
2309        };
2310        (
2311            contenders
2312                .into_iter()
2313                .map(|contender| {
2314                    ContenderV0 {
2315                        identity_id: contender.identifier.try_into().expect("expected 32 bytes"),
2316                        document: contender.document.map(|document_bytes| {
2317                            Document::from_bytes(
2318                                document_bytes.as_slice(),
2319                                domain,
2320                                platform_version,
2321                            )
2322                            .expect("expected to deserialize document")
2323                        }),
2324                        vote_tally: contender.vote_count,
2325                    }
2326                    .into()
2327                })
2328                .collect(),
2329            abstain_vote_tally,
2330            lock_vote_tally,
2331            finished_vote_info,
2332        )
2333    }
2334
2335    #[allow(clippy::too_many_arguments)]
2336    pub(in crate::execution) fn get_proved_vote_states(
2337        platform: &TempPlatform<MockCoreRPCLike>,
2338        platform_state: &PlatformState,
2339        dpns_contract: &DataContract,
2340        name: &str,
2341        count: Option<u32>,
2342        allow_include_locked_and_abstaining_vote_tally: bool,
2343        start_at_identifier_info: Option<
2344            get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo,
2345        >,
2346        result_type: ResultType,
2347        platform_version: &PlatformVersion,
2348    ) -> (
2349        Vec<Contender>,
2350        Option<u32>,
2351        Option<u32>,
2352        Option<(ContestedDocumentVotePollWinnerInfo, BlockInfo)>,
2353    ) {
2354        // Now let's run a query for the vote totals
2355
2356        let domain = dpns_contract
2357            .document_type_for_name("domain")
2358            .expect("expected a profile document type");
2359
2360        let config = bincode::config::standard()
2361            .with_big_endian()
2362            .with_no_limit();
2363
2364        let dash_encoded = bincode::encode_to_vec(Value::Text("dash".to_string()), config)
2365            .expect("expected to encode the word dash");
2366
2367        let name_encoded =
2368            bincode::encode_to_vec(Value::Text(convert_to_homograph_safe_chars(name)), config)
2369                .expect("expected to encode the word quantum");
2370
2371        let index_name = "parentNameAndLabel".to_string();
2372
2373        let query_validation_result = platform
2374            .query_contested_resource_vote_state(
2375                GetContestedResourceVoteStateRequest {
2376                    version: Some(get_contested_resource_vote_state_request::Version::V0(
2377                        GetContestedResourceVoteStateRequestV0 {
2378                            contract_id: dpns_contract.id().to_vec(),
2379                            document_type_name: domain.name().clone(),
2380                            index_name: index_name.clone(),
2381                            index_values: vec![dash_encoded.clone(), name_encoded.clone()],
2382                            result_type: result_type as i32,
2383                            allow_include_locked_and_abstaining_vote_tally,
2384                            start_at_identifier_info,
2385                            count,
2386                            prove: true,
2387                        },
2388                    )),
2389                },
2390                platform_state,
2391                platform_version,
2392            )
2393            .expect("expected to execute query")
2394            .into_data()
2395            .expect("expected query to be valid");
2396
2397        let get_contested_resource_vote_state_response::Version::V0(
2398            GetContestedResourceVoteStateResponseV0 {
2399                metadata: _,
2400                result,
2401            },
2402        ) = query_validation_result.version.expect("expected a version");
2403
2404        let Some(get_contested_resource_vote_state_response_v0::Result::Proof(proof)) = result
2405        else {
2406            panic!("expected contenders")
2407        };
2408
2409        let resolved_contested_document_vote_poll_drive_query =
2410            ResolvedContestedDocumentVotePollDriveQuery {
2411                vote_poll: ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed {
2412                    contract: DataContractResolvedInfo::BorrowedDataContract(dpns_contract),
2413                    document_type_name: domain.name().clone(),
2414                    index_name: index_name.clone(),
2415                    index_values: vec![
2416                        Value::Text("dash".to_string()),
2417                        Value::Text(convert_to_homograph_safe_chars(name)),
2418                    ],
2419                },
2420                result_type: ContestedDocumentVotePollDriveQueryResultType::try_from(
2421                    result_type as i32,
2422                )
2423                .expect("expected valid result type"),
2424                offset: None,
2425                limit: count.map(|a| a as u16),
2426                start_at: None,
2427                allow_include_locked_and_abstaining_vote_tally,
2428            };
2429
2430        let (_, result) = resolved_contested_document_vote_poll_drive_query
2431            .verify_vote_poll_vote_state_proof(proof.grovedb_proof.as_ref(), platform_version)
2432            .expect("expected to verify proof");
2433
2434        let abstaining_vote_tally = result.abstaining_vote_tally;
2435        let lock_vote_tally = result.locked_vote_tally;
2436
2437        let contenders = result.contenders;
2438        let finished_vote_info = result.winner;
2439        (
2440            contenders
2441                .into_iter()
2442                .map(|contender| {
2443                    ContenderV0 {
2444                        identity_id: contender.identity_id(),
2445                        document: contender
2446                            .serialized_document()
2447                            .as_ref()
2448                            .map(|document_bytes| {
2449                                Document::from_bytes(
2450                                    document_bytes.as_slice(),
2451                                    domain,
2452                                    platform_version,
2453                                )
2454                                .expect("expected to deserialize document")
2455                            }),
2456                        vote_tally: contender.vote_tally(),
2457                    }
2458                    .into()
2459                })
2460                .collect(),
2461            abstaining_vote_tally,
2462            lock_vote_tally,
2463            finished_vote_info,
2464        )
2465    }
2466
2467    pub(in crate::execution) fn create_token_contract_with_owner_identity(
2468        platform: &mut TempPlatform<MockCoreRPCLike>,
2469        identity_id: Identifier,
2470        token_configuration_modification: Option<impl FnOnce(&mut TokenConfiguration)>,
2471        contract_start_time: Option<TimestampMillis>,
2472        add_groups: Option<BTreeMap<GroupContractPosition, Group>>,
2473        contract_start_block: Option<BlockHeight>,
2474        platform_version: &PlatformVersion,
2475    ) -> (DataContract, Identifier) {
2476        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2477
2478        let basic_token_contract = setup_contract(
2479            &platform.drive,
2480            "tests/supporting_files/contract/basic-token/basic-token.json",
2481            Some(data_contract_id.to_buffer()),
2482            Some(identity_id.to_buffer()),
2483            Some(|data_contract: &mut DataContract| {
2484                data_contract.set_created_at_epoch(Some(0));
2485                data_contract.set_created_at(Some(contract_start_time.unwrap_or_default()));
2486                data_contract
2487                    .set_created_at_block_height(Some(contract_start_block.unwrap_or_default()));
2488                if let Some(token_configuration_modification) = token_configuration_modification {
2489                    let token_configuration = data_contract
2490                        .token_configuration_mut(0)
2491                        .expect("expected token configuration");
2492                    token_configuration_modification(token_configuration);
2493                }
2494                if let Some(add_groups) = add_groups {
2495                    data_contract.set_groups(add_groups);
2496                }
2497            }),
2498            None,
2499            Some(platform_version),
2500        );
2501
2502        let token_id = calculate_token_id(data_contract_id.as_bytes(), 0);
2503
2504        (basic_token_contract, token_id.into())
2505    }
2506
2507    pub(in crate::execution) fn create_card_game_internal_token_contract_with_owner_identity_burn_tokens(
2508        platform: &mut TempPlatform<MockCoreRPCLike>,
2509        identity_id: Identifier,
2510        platform_version: &PlatformVersion,
2511    ) -> (DataContract, Identifier, Identifier) {
2512        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2513
2514        let basic_token_contract = setup_contract(
2515            &platform.drive,
2516            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency-burn-tokens.json",
2517            Some(data_contract_id.to_buffer()),
2518            Some(identity_id.to_buffer()),
2519            Some(|data_contract: &mut DataContract| {
2520                data_contract.set_created_at_epoch(Some(0));
2521                data_contract.set_created_at(Some(0));
2522                data_contract.set_created_at_block_height(Some(0));
2523            }),
2524            None,
2525            Some(platform_version),
2526        );
2527
2528        let token_id = calculate_token_id(data_contract_id.as_bytes(), 0);
2529        let token_id_2 = calculate_token_id(data_contract_id.as_bytes(), 1);
2530
2531        (basic_token_contract, token_id.into(), token_id_2.into())
2532    }
2533
2534    pub(in crate::execution) fn create_card_game_internal_token_contract_with_owner_identity_transfer_tokens(
2535        platform: &mut TempPlatform<MockCoreRPCLike>,
2536        identity_id: Identifier,
2537        platform_version: &PlatformVersion,
2538    ) -> (DataContract, Identifier, Identifier) {
2539        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2540
2541        let basic_token_contract = setup_contract(
2542            &platform.drive,
2543            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency.json",
2544            Some(data_contract_id.to_buffer()),
2545            Some(identity_id.to_buffer()),
2546            Some(|data_contract: &mut DataContract| {
2547                data_contract.set_created_at_epoch(Some(0));
2548                data_contract.set_created_at(Some(0));
2549                data_contract.set_created_at_block_height(Some(0));
2550            }),
2551            None,
2552            Some(platform_version),
2553        );
2554
2555        let token_id = calculate_token_id(data_contract_id.as_bytes(), 0);
2556        let token_id_2 = calculate_token_id(data_contract_id.as_bytes(), 1);
2557
2558        (basic_token_contract, token_id.into(), token_id_2.into())
2559    }
2560
2561    pub(in crate::execution) fn create_card_game_external_token_contract_with_owner_identity(
2562        platform: &mut TempPlatform<MockCoreRPCLike>,
2563        token_contract_id: Identifier,
2564        token_contract_position: TokenContractPosition,
2565        token_cost_amount: TokenAmount,
2566        gas_fees_paid_by: GasFeesPaidBy,
2567        identity_id: Identifier,
2568        platform_version: &PlatformVersion,
2569    ) -> DataContract {
2570        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2571
2572        let basic_token_contract = setup_contract(
2573            &platform.drive,
2574            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-use-external-currency.json",
2575            Some(data_contract_id.to_buffer()),
2576            Some(identity_id.to_buffer()),
2577            Some(|data_contract: &mut DataContract| {
2578                data_contract.set_created_at_epoch(Some(0));
2579                data_contract.set_created_at(Some(0));
2580                data_contract.set_created_at_block_height(Some(0));
2581                let document_type = data_contract.document_types_mut().get_mut("card").expect("expected a document type with name card");
2582                document_type.set_document_creation_token_cost(Some(DocumentActionTokenCost {
2583                    contract_id: Some(token_contract_id),
2584                    token_contract_position,
2585                    token_amount: token_cost_amount,
2586                    effect: DocumentActionTokenEffect::TransferTokenToContractOwner,
2587                    gas_fees_paid_by,
2588                }));
2589                let gas_fees_paid_by_int: u8 = gas_fees_paid_by.into();
2590                let schema = document_type.schema_mut();
2591                let token_cost = schema.get_mut("tokenCost").expect("expected to get token cost").expect("expected token cost to be set");
2592                let creation_token_cost = token_cost.get_mut("create").expect("expected to get creation token cost").expect("expected creation token cost to be set");
2593                creation_token_cost.set_value("contractId", token_contract_id.into()).expect("expected to set token contract id");
2594                creation_token_cost.set_value("tokenPosition", token_contract_position.into()).expect("expected to set token position");
2595                creation_token_cost.set_value("amount", token_cost_amount.into()).expect("expected to set token amount");
2596                creation_token_cost.set_value("gasFeesPaidBy", gas_fees_paid_by_int.into()).expect("expected to set token amount");
2597            }),
2598            None,
2599            Some(platform_version),
2600        );
2601
2602        basic_token_contract
2603    }
2604
2605    pub(in crate::execution) fn process_test_state_transition<S: PlatformSerializable>(
2606        platform: &mut TempPlatform<MockCoreRPCLike>,
2607        state_transition: S,
2608        platform_state: &PlatformState,
2609        platform_version: &PlatformVersion,
2610    ) -> StateTransitionsProcessingResult {
2611        let Ok(serialized_state_transition) = state_transition.serialize_to_bytes() else {
2612            panic!("expected documents batch serialized state transition")
2613        };
2614
2615        let transaction = platform.drive.grove.start_transaction();
2616
2617        let processing_result = platform
2618            .platform
2619            .process_raw_state_transitions(
2620                &[serialized_state_transition],
2621                platform_state,
2622                &BlockInfo::default(),
2623                &transaction,
2624                platform_version,
2625                false,
2626                None,
2627            )
2628            .expect("expected to process state transition");
2629
2630        platform
2631            .drive
2632            .grove
2633            .commit_transaction(transaction)
2634            .unwrap()
2635            .expect("expected to commit transaction");
2636
2637        processing_result
2638    }
2639
2640    mod keyword_search_contract {
2641        use dpp::consensus::basic::BasicError;
2642        use dpp::consensus::ConsensusError;
2643        use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult::PaidConsensusError;
2644        use super::*;
2645        //
2646        // ──────────────────────────────────────────────────────────────────────────
2647        //  Keyword‑Search contract – creation is forbidden
2648        // ──────────────────────────────────────────────────────────────────────────
2649        //
2650
2651        /// Return `(document, entropy)` so we can reuse the exact entropy when
2652        /// we build the transition.  **No rng.clone()** (that caused the ID mismatch).
2653        fn build_random_doc_of_type(
2654            rng: &mut StdRng,
2655            doc_type_name: &str,
2656            identity_id: Identifier,
2657            contract: &DataContract,
2658            platform_version: &PlatformVersion,
2659        ) -> (Document, Bytes32) {
2660            let doc_type = contract
2661                .document_type_for_name(doc_type_name)
2662                .expect("doc type exists");
2663
2664            let entropy = Bytes32::random_with_rng(rng);
2665
2666            let doc = doc_type
2667                .random_document_with_identifier_and_entropy(
2668                    rng,
2669                    identity_id,
2670                    entropy,
2671                    DocumentFieldFillType::FillIfNotRequired,
2672                    DocumentFieldFillSize::AnyDocumentFillSize,
2673                    platform_version,
2674                )
2675                .expect("random doc");
2676
2677            (doc, entropy)
2678        }
2679
2680        #[test]
2681        fn should_err_when_creating_contract_keywords_document() {
2682            let platform_version = PlatformVersion::latest();
2683
2684            let mut platform = TestPlatformBuilder::new()
2685                .build_with_mock_rpc()
2686                .set_genesis_state();
2687
2688            let (identity, signer, key) = setup_identity(&mut platform, 42, dash_to_credits!(1.0));
2689
2690            let contract = load_system_data_contract(
2691                SystemDataContract::KeywordSearch,
2692                PlatformVersion::latest(),
2693            )
2694            .expect("expected to load search contract");
2695
2696            let mut rng = StdRng::seed_from_u64(1);
2697            let (doc, entropy) = build_random_doc_of_type(
2698                &mut rng,
2699                "contractKeywords",
2700                identity.id(),
2701                &contract,
2702                platform_version,
2703            );
2704
2705            let transition = BatchTransition::new_document_creation_transition_from_document(
2706                doc,
2707                contract.document_type_for_name("contractKeywords").unwrap(),
2708                entropy.0, // same entropy → no ID mismatch
2709                &key,
2710                1,
2711                0,
2712                None,
2713                &signer,
2714                platform_version,
2715                None,
2716            )
2717            .expect("batch transition");
2718
2719            let serialized = transition.serialize_to_bytes().unwrap();
2720
2721            let platform_state = platform.state.load();
2722            let processing_result = platform
2723                .platform
2724                .process_raw_state_transitions(
2725                    &[serialized],
2726                    &platform_state,
2727                    &BlockInfo::default(),
2728                    &platform.drive.grove.start_transaction(),
2729                    platform_version,
2730                    false,
2731                    None,
2732                )
2733                .expect("processing failed");
2734
2735            let execution_result = processing_result.into_execution_results().remove(0);
2736            assert_matches!(
2737                execution_result,
2738                StateTransitionExecutionResult::PaidConsensusError{ error, .. }
2739                    if error.to_string().contains("not allowed because of the document type's creation restriction mode")
2740            );
2741        }
2742
2743        #[test]
2744        fn should_err_when_creating_short_description_document() {
2745            let platform_version = PlatformVersion::latest();
2746
2747            let mut platform = TestPlatformBuilder::new()
2748                .build_with_mock_rpc()
2749                .set_genesis_state();
2750
2751            let (identity, signer, key) = setup_identity(&mut platform, 43, dash_to_credits!(1.0));
2752
2753            let contract = load_system_data_contract(
2754                SystemDataContract::KeywordSearch,
2755                PlatformVersion::latest(),
2756            )
2757            .expect("expected to load search contract");
2758
2759            let mut rng = StdRng::seed_from_u64(2);
2760            let (doc, entropy) = build_random_doc_of_type(
2761                &mut rng,
2762                "shortDescription",
2763                identity.id(),
2764                &contract,
2765                platform_version,
2766            );
2767
2768            let transition = BatchTransition::new_document_creation_transition_from_document(
2769                doc,
2770                contract.document_type_for_name("shortDescription").unwrap(),
2771                entropy.0,
2772                &key,
2773                1,
2774                0,
2775                None,
2776                &signer,
2777                platform_version,
2778                None,
2779            )
2780            .expect("batch transition");
2781
2782            let serialized = transition.serialize_to_bytes().unwrap();
2783
2784            let platform_state = platform.state.load();
2785            let processing_result = platform
2786                .platform
2787                .process_raw_state_transitions(
2788                    &[serialized],
2789                    &platform_state,
2790                    &BlockInfo::default(),
2791                    &platform.drive.grove.start_transaction(),
2792                    platform_version,
2793                    false,
2794                    None,
2795                )
2796                .expect("processing failed");
2797
2798            let execution_result = processing_result.into_execution_results().remove(0);
2799            assert_matches!(
2800                execution_result,
2801                StateTransitionExecutionResult::PaidConsensusError{ error, .. }
2802                    if error.to_string().contains("not allowed because of the document type's creation restriction mode")
2803            );
2804        }
2805
2806        #[test]
2807        fn should_err_when_creating_full_description_document() {
2808            let platform_version = PlatformVersion::latest();
2809
2810            let mut platform = TestPlatformBuilder::new()
2811                .build_with_mock_rpc()
2812                .set_genesis_state();
2813
2814            let (identity, signer, key) = setup_identity(&mut platform, 44, dash_to_credits!(1.0));
2815
2816            let contract = load_system_data_contract(
2817                SystemDataContract::KeywordSearch,
2818                PlatformVersion::latest(),
2819            )
2820            .expect("expected to load search contract");
2821
2822            let mut rng = StdRng::seed_from_u64(3);
2823            let (doc, entropy) = build_random_doc_of_type(
2824                &mut rng,
2825                "fullDescription",
2826                identity.id(),
2827                &contract,
2828                platform_version,
2829            );
2830
2831            let transition = BatchTransition::new_document_creation_transition_from_document(
2832                doc,
2833                contract.document_type_for_name("fullDescription").unwrap(),
2834                entropy.0,
2835                &key,
2836                1,
2837                0,
2838                None,
2839                &signer,
2840                platform_version,
2841                None,
2842            )
2843            .expect("batch transition");
2844
2845            let serialized = transition.serialize_to_bytes().unwrap();
2846
2847            let platform_state = platform.state.load();
2848            let processing_result = platform
2849                .platform
2850                .process_raw_state_transitions(
2851                    &[serialized],
2852                    &platform_state,
2853                    &BlockInfo::default(),
2854                    &platform.drive.grove.start_transaction(),
2855                    platform_version,
2856                    false,
2857                    None,
2858                )
2859                .expect("processing failed");
2860
2861            let execution_result = processing_result.into_execution_results().remove(0);
2862            assert_matches!(
2863                execution_result,
2864                StateTransitionExecutionResult::PaidConsensusError{ error, .. }
2865                    if error.to_string().contains("not allowed because of the document type's creation restriction mode")
2866            );
2867        }
2868
2869        //
2870        // ──────────────────────────────────────────────────────────────────────────
2871        //  Keyword‑Search contract – owner can update / delete
2872        // ──────────────────────────────────────────────────────────────────────────
2873        //
2874
2875        fn create_contract_with_keywords_and_description(
2876            platform: &mut TempPlatform<MockCoreRPCLike>,
2877        ) -> (Identity, SimpleSigner, IdentityPublicKey) {
2878            let platform_version = PlatformVersion::latest();
2879
2880            // Owner identity
2881            let (owner_identity, signer, key) =
2882                setup_identity(platform, 777, dash_to_credits!(1.0));
2883
2884            // Load the keyword‑test fixture
2885            let mut contract = json_document_to_contract_with_ids(
2886                "tests/supporting_files/contract/keyword_test/keyword_base_contract.json",
2887                Some(owner_identity.id()),
2888                None,
2889                false,
2890                platform_version,
2891            )
2892            .expect("load contract");
2893
2894            // Inject description + keywords
2895            contract.set_description(Some("A short description".to_string()));
2896            contract.set_keywords(vec!["graph".into(), "indexing".into()]);
2897
2898            // Create transition inside GroveDB tx
2899            let create_transition = DataContractCreateTransition::new_from_data_contract(
2900                contract,
2901                1,
2902                &owner_identity.clone().into_partial_identity_info(),
2903                key.id(),
2904                &signer,
2905                platform_version,
2906                None,
2907            )
2908            .expect("build transition");
2909
2910            let serialized = create_transition.serialize_to_bytes().unwrap();
2911            let platform_state = platform.state.load();
2912            let tx = platform.drive.grove.start_transaction();
2913
2914            let processing_result = platform
2915                .platform
2916                .process_raw_state_transitions(
2917                    &[serialized],
2918                    &platform_state,
2919                    &BlockInfo::default(),
2920                    &tx,
2921                    platform_version,
2922                    false,
2923                    None,
2924                )
2925                .expect("process");
2926
2927            assert_matches!(
2928                processing_result.execution_results().as_slice(),
2929                [StateTransitionExecutionResult::SuccessfulExecution { .. }]
2930            );
2931
2932            platform
2933                .drive
2934                .grove
2935                .commit_transaction(tx)
2936                .unwrap()
2937                .expect("commit");
2938
2939            (owner_identity, signer, key)
2940        }
2941
2942        #[test]
2943        fn owner_can_update_short_description_document() {
2944            let platform_version = PlatformVersion::latest();
2945            let mut platform = TestPlatformBuilder::new()
2946                .build_with_mock_rpc()
2947                .set_genesis_state();
2948
2949            let (_owner, signer, key) =
2950                create_contract_with_keywords_and_description(&mut platform);
2951
2952            // 🔎 fetch shortDescription doc through query
2953            let search_contract =
2954                load_system_data_contract(SystemDataContract::KeywordSearch, platform_version)
2955                    .expect("load search contract");
2956
2957            let doc_type = search_contract
2958                .document_type_for_name("shortDescription")
2959                .unwrap();
2960
2961            let query = DriveDocumentQuery::all_items_query(&search_contract, doc_type, None);
2962            let existing_docs = platform
2963                .drive
2964                .query_documents(
2965                    query,
2966                    None,
2967                    false,
2968                    None,
2969                    Some(platform_version.protocol_version),
2970                )
2971                .expect("query failed");
2972
2973            let mut doc = existing_docs.documents().first().expect("doc").clone();
2974            doc.set_revision(Some(doc.revision().unwrap_or_default() + 1));
2975            doc.set("description", "updated description".into());
2976
2977            let transition = BatchTransition::new_document_replacement_transition_from_document(
2978                doc,
2979                doc_type,
2980                &key,
2981                2,
2982                0,
2983                None,
2984                &signer,
2985                platform_version,
2986                None,
2987            )
2988            .expect("replace");
2989
2990            let serialized = transition.serialize_to_bytes().unwrap();
2991            let platform_state = platform.state.load();
2992
2993            let processing_result = platform
2994                .platform
2995                .process_raw_state_transitions(
2996                    &[serialized],
2997                    &platform_state,
2998                    &BlockInfo::default(),
2999                    &platform.drive.grove.start_transaction(),
3000                    platform_version,
3001                    false,
3002                    None,
3003                )
3004                .expect("process");
3005
3006            assert_matches!(
3007                processing_result.into_execution_results().remove(0),
3008                SuccessfulExecution { .. }
3009            );
3010        }
3011
3012        #[test]
3013        fn owner_can_not_delete_keyword_document() {
3014            let platform_version = PlatformVersion::latest();
3015            let mut platform = TestPlatformBuilder::new()
3016                .build_with_mock_rpc()
3017                .set_genesis_state();
3018
3019            let (_owner, signer, key) =
3020                create_contract_with_keywords_and_description(&mut platform);
3021
3022            let search_contract =
3023                load_system_data_contract(SystemDataContract::KeywordSearch, platform_version)
3024                    .expect("load search contract");
3025            let doc_type = search_contract
3026                .document_type_for_name("contractKeywords")
3027                .unwrap();
3028
3029            let query = DriveDocumentQuery::all_items_query(&search_contract, doc_type, None);
3030            let existing_docs = platform
3031                .drive
3032                .query_documents(
3033                    query,
3034                    None,
3035                    false,
3036                    None,
3037                    Some(platform_version.protocol_version),
3038                )
3039                .expect("query failed");
3040
3041            let doc = existing_docs.documents().first().unwrap().clone();
3042
3043            let transition = BatchTransition::new_document_deletion_transition_from_document(
3044                doc,
3045                doc_type,
3046                &key,
3047                2,
3048                0,
3049                None,
3050                &signer,
3051                platform_version,
3052                None,
3053            )
3054            .expect("delete");
3055
3056            let serialized = transition.serialize_to_bytes().unwrap();
3057            let platform_state = platform.state.load();
3058
3059            let processing_result = platform
3060                .platform
3061                .process_raw_state_transitions(
3062                    &[serialized],
3063                    &platform_state,
3064                    &BlockInfo::default(),
3065                    &platform.drive.grove.start_transaction(),
3066                    platform_version,
3067                    false,
3068                    None,
3069                )
3070                .expect("process");
3071            assert_matches!(
3072                processing_result.execution_results().as_slice(),
3073                [PaidConsensusError {
3074                    error: ConsensusError::BasicError(
3075                        BasicError::InvalidDocumentTransitionActionError { .. }
3076                    ),
3077                    ..
3078                }]
3079            );
3080        }
3081    }
3082}