Skip to main content

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/// Validation shared by the data contract create and update transitions.
20pub mod data_contract_common;
21
22/// Module for creating a data contract entity.
23pub mod data_contract_create;
24
25/// Module for updating an existing data contract entity.
26pub mod data_contract_update;
27
28/// Module for voting from a masternode.
29pub mod masternode_vote;
30
31/// Identity create from addresses
32pub mod identity_create_from_addresses;
33
34/// Module for validation of address funding from asset lock transitions
35pub mod address_funding_from_asset_lock;
36
37/// Module for validation of credit transfer from an identity to addresses
38pub mod identity_credit_transfer_to_addresses;
39
40/// Module for validation of address credit withdrawal transitions
41pub mod address_credit_withdrawal;
42
43/// Module for validation of address funds transfer transitions
44pub mod address_funds_transfer;
45mod identity_top_up_from_addresses;
46
47/// Module for identity-create-from-shielded-pool transition validation
48pub mod identity_create_from_shielded_pool;
49/// Module for shield transition validation
50pub mod shield;
51/// Module for shield from asset lock transition validation
52pub mod shield_from_asset_lock;
53/// Common validation logic shared by shielded transitions (proof verification)
54pub mod shielded_common;
55/// Module for shielded transfer transition validation
56pub mod shielded_transfer;
57/// Module for shielded withdrawal transition validation
58pub mod shielded_withdrawal;
59/// Module for unshield transition validation
60pub mod unshield;
61
62/// The validation mode we are using
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub enum ValidationMode {
65    /// The basic checktx before the state transition is put into mempool
66    CheckTx,
67    /// Rechecking a state transition every block
68    RecheckTx,
69    /// The validation during block execution by a proposer or validator
70    Validator,
71    /// A validation mode used to get the action with no validation
72    NoValidation,
73}
74
75impl ValidationMode {
76    /// Can this validation mode alter cache on drive?
77    pub fn can_alter_cache(&self) -> bool {
78        match self {
79            ValidationMode::CheckTx => false,
80            ValidationMode::RecheckTx => false,
81            ValidationMode::Validator => true,
82            ValidationMode::NoValidation => false,
83        }
84    }
85}
86
87#[cfg(test)]
88pub(crate) mod test_helpers;
89
90#[cfg(test)]
91pub(in crate::execution) mod tests {
92    use crate::rpc::core::MockCoreRPCLike;
93    use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder};
94    use dpp::block::block_info::BlockInfo;
95    use dpp::data_contracts::SystemDataContract;
96    use dpp::fee::Credits;
97    use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
98    use dpp::identity::{Identity, IdentityPublicKey, IdentityV0, KeyID, KeyType, Purpose, SecurityLevel, TimestampMillis};
99    use dpp::prelude::{BlockHeight, Identifier, IdentityNonce};
100    use dpp::state_transition::data_contract_create_transition::methods::DataContractCreateTransitionMethodsV0;
101    use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition;
102    use dpp::system_data_contracts::load_system_data_contract;
103    use dpp::tests::json_document::json_document_to_contract_with_ids;
104    use drive::drive::document::query::QueryDocumentsOutcomeV0Methods;
105    use drive::query::DriveDocumentQuery;
106    use platform_version::version::PlatformVersion;
107    use rand::prelude::StdRng;
108    use rand::{Rng, SeedableRng};
109    use simple_signer::signer::SimpleSigner;
110    use std::borrow::Cow;
111    use std::collections::BTreeMap;
112    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
113    use std::ops::Deref;
114    use std::sync::Arc;
115    use arc_swap::Guard;
116    use assert_matches::assert_matches;
117    use dpp::dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType};
118    use dapi_grpc::platform::v0::{get_contested_resource_vote_state_request, get_contested_resource_vote_state_response, GetContestedResourceVoteStateRequest, GetContestedResourceVoteStateResponse};
119    use dapi_grpc::platform::v0::get_contested_resource_vote_state_request::get_contested_resource_vote_state_request_v0::ResultType;
120    use dapi_grpc::platform::v0::get_contested_resource_vote_state_request::{get_contested_resource_vote_state_request_v0, GetContestedResourceVoteStateRequestV0};
121    use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::{get_contested_resource_vote_state_response_v0, GetContestedResourceVoteStateResponseV0};
122    use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::get_contested_resource_vote_state_response_v0::FinishedVoteInfo;
123    use dpp::balances::credits::TokenAmount;
124    use dpp::dash_to_credits;
125    use dpp::dashcore::{ProTxHash, Txid};
126    use dpp::dashcore::hashes::Hash;
127    use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
128    use dpp::data_contract::accessors::v1::{DataContractV1Getters, DataContractV1Setters};
129    use dpp::data_contract::{DataContract, GroupContractPosition, TokenContractPosition};
130    use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV1Setters};
131    use dpp::data_contract::document_type::random_document::{CreateRandomDocument, DocumentFieldFillSize, DocumentFieldFillType};
132    use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters};
133    use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0;
134    use dpp::fee::fee_result::FeeResult;
135    use dpp::identifier::MasternodeIdentifiers;
136    use dpp::identity::accessors::IdentityGettersV0;
137    use dpp::identity::contract_bounds::ContractBounds;
138    use dpp::identity::hash::IdentityPublicKeyHashMethodsV0;
139    use dpp::platform_value::{Bytes32, Value};
140    use dpp::serialization::PlatformSerializable;
141    use dpp::state_transition::batch_transition::BatchTransition;
142    use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0;
143    use dpp::state_transition::masternode_vote_transition::MasternodeVoteTransition;
144    use dpp::state_transition::masternode_vote_transition::methods::MasternodeVoteTransitionMethodsV0;
145    use dpp::state_transition::StateTransition;
146    use dpp::tokens::calculate_token_id;
147    use dpp::util::hash::hash_double;
148    use dpp::util::strings::convert_to_homograph_safe_chars;
149    use dpp::voting::contender_structs::{Contender, ContenderV0};
150    use dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
151    use dpp::voting::vote_info_storage::contested_document_vote_poll_winner_info::ContestedDocumentVotePollWinnerInfo;
152    use dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll;
153    use dpp::voting::vote_polls::VotePoll;
154    use dpp::voting::votes::resource_vote::ResourceVote;
155    use dpp::voting::votes::resource_vote::v0::ResourceVoteV0;
156    use dpp::voting::votes::Vote;
157    use drive::util::object_size_info::DataContractResolvedInfo;
158    use drive::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed;
159    use drive::query::vote_poll_vote_state_query::ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally;
160    use drive::query::vote_poll_vote_state_query::{ContestedDocumentVotePollDriveQueryResultType, ResolvedContestedDocumentVotePollDriveQuery};
161    use drive::util::test_helpers::setup_contract;
162    use crate::execution::types::block_execution_context::BlockExecutionContext;
163    use crate::execution::types::block_execution_context::v0::BlockExecutionContextV0;
164    use crate::expect_match;
165    use crate::platform_types::platform_state::PlatformState;
166    use crate::platform_types::platform_state::PlatformStateV0Methods;
167    use crate::platform_types::state_transitions_processing_result::{StateTransitionExecutionResult, StateTransitionsProcessingResult};
168    use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult::{SuccessfulExecution, UnpaidConsensusError};
169    use crate::execution::types::block_state_info::BlockStateInfo;
170    use crate::execution::types::block_state_info::v0::BlockStateInfoV0;
171    use crate::platform_types::epoch_info::EpochInfo;
172    use crate::platform_types::epoch_info::v0::EpochInfoV0;
173    use crate::execution::types::block_fees::v0::BlockFeesV0;
174    use crate::execution::types::processed_block_fees_outcome::v0::ProcessedBlockFeesOutcome;
175    use dpp::data_contract::associated_token::token_configuration::TokenConfiguration;
176    use dpp::data_contract::group::Group;
177    use dpp::tokens::gas_fees_paid_by::GasFeesPaidBy;
178    use dpp::tokens::token_amount_on_contract_token::{DocumentActionTokenCost, DocumentActionTokenEffect};
179    use dpp::data_contract::document_type::accessors::DocumentTypeV0MutGetters;
180    use dpp::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure;
181
182    /// We add an identity, but we also add the same amount to system credits
183    pub(in crate::execution) fn setup_identity_with_system_credits(
184        platform: &mut TempPlatform<MockCoreRPCLike>,
185        seed: u64,
186        credits: Credits,
187    ) -> (Identity, SimpleSigner, IdentityPublicKey) {
188        let platform_version = PlatformVersion::latest();
189        platform
190            .drive
191            .add_to_system_credits(credits, None, platform_version)
192            .expect("expected to add to system credits");
193        setup_identity(platform, seed, credits)
194    }
195
196    pub(in crate::execution) fn setup_identity(
197        platform: &mut TempPlatform<MockCoreRPCLike>,
198        seed: u64,
199        credits: Credits,
200    ) -> (Identity, SimpleSigner, IdentityPublicKey) {
201        let platform_version = PlatformVersion::latest();
202        let mut signer = SimpleSigner::default();
203
204        let mut rng = StdRng::seed_from_u64(seed);
205
206        let (master_key, master_private_key) =
207            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
208                0,
209                &mut rng,
210                platform_version,
211            )
212            .expect("expected to get key pair");
213
214        signer.add_identity_public_key(master_key.clone(), master_private_key);
215
216        let (critical_public_key, private_key) =
217            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
218                1,
219                &mut rng,
220                platform_version,
221            )
222            .expect("expected to get key pair");
223
224        signer.add_identity_public_key(critical_public_key.clone(), private_key);
225
226        let identity: Identity = IdentityV0 {
227            id: Identifier::random_with_rng(&mut rng),
228            public_keys: BTreeMap::from([
229                (0, master_key.clone()),
230                (1, critical_public_key.clone()),
231            ]),
232            balance: credits,
233            revision: 0,
234        }
235        .into();
236
237        // We just add this identity to the system first
238
239        platform
240            .drive
241            .add_new_identity(
242                identity.clone(),
243                false,
244                &BlockInfo::default(),
245                true,
246                None,
247                platform_version,
248            )
249            .expect("expected to add a new identity");
250
251        (identity, signer, critical_public_key)
252    }
253
254    pub(in crate::execution) fn setup_identity_without_adding_it(
255        seed: u64,
256        credits: Credits,
257    ) -> (Identity, SimpleSigner, IdentityPublicKey) {
258        let platform_version = PlatformVersion::latest();
259        let mut signer = SimpleSigner::default();
260
261        let mut rng = StdRng::seed_from_u64(seed);
262
263        let (master_key, master_private_key) =
264            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
265                0,
266                &mut rng,
267                platform_version,
268            )
269            .expect("expected to get key pair");
270
271        signer.add_identity_public_key(master_key.clone(), master_private_key);
272
273        let (critical_public_key, private_key) =
274            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
275                1,
276                &mut rng,
277                platform_version,
278            )
279            .expect("expected to get key pair");
280
281        signer.add_identity_public_key(critical_public_key.clone(), private_key);
282
283        let identity: Identity = IdentityV0 {
284            id: Identifier::random_with_rng(&mut rng),
285            public_keys: BTreeMap::from([
286                (0, master_key.clone()),
287                (1, critical_public_key.clone()),
288            ]),
289            balance: credits,
290            revision: 0,
291        }
292        .into();
293
294        (identity, signer, critical_public_key)
295    }
296
297    pub(in crate::execution) fn setup_identity_return_master_key(
298        platform: &mut TempPlatform<MockCoreRPCLike>,
299        seed: u64,
300        credits: Credits,
301    ) -> (Identity, SimpleSigner, IdentityPublicKey, IdentityPublicKey) {
302        let platform_version = PlatformVersion::latest();
303        let mut signer = SimpleSigner::default();
304
305        let mut rng = StdRng::seed_from_u64(seed);
306
307        let (master_key, master_private_key) =
308            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
309                0,
310                &mut rng,
311                platform_version,
312            )
313            .expect("expected to get key pair");
314
315        signer.add_identity_public_key(master_key.clone(), master_private_key);
316
317        let (critical_public_key, private_key) =
318            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
319                1,
320                &mut rng,
321                platform_version,
322            )
323            .expect("expected to get key pair");
324
325        signer.add_identity_public_key(critical_public_key.clone(), private_key);
326
327        let identity: Identity = IdentityV0 {
328            id: Identifier::random_with_rng(&mut rng),
329            public_keys: BTreeMap::from([
330                (0, master_key.clone()),
331                (1, critical_public_key.clone()),
332            ]),
333            balance: credits,
334            revision: 0,
335        }
336        .into();
337
338        // We just add this identity to the system first
339
340        platform
341            .drive
342            .add_new_identity(
343                identity.clone(),
344                false,
345                &BlockInfo::default(),
346                true,
347                None,
348                platform_version,
349            )
350            .expect("expected to add a new identity");
351
352        (identity, signer, critical_public_key, master_key)
353    }
354
355    #[allow(clippy::too_many_arguments)]
356    pub(crate) fn setup_add_key_to_identity(
357        platform: &mut TempPlatform<MockCoreRPCLike>,
358        identity: &mut Identity,
359        signer: &mut SimpleSigner,
360        seed: u64,
361        key_id: KeyID,
362        purpose: Purpose,
363        security_level: SecurityLevel,
364        key_type: KeyType,
365        contract_bounds: Option<ContractBounds>,
366    ) -> IdentityPublicKey {
367        let platform_version = PlatformVersion::latest();
368
369        let mut rng = StdRng::seed_from_u64(seed);
370
371        let (key, private_key) = IdentityPublicKey::random_key_with_known_attributes(
372            key_id,
373            &mut rng,
374            purpose,
375            security_level,
376            key_type,
377            contract_bounds,
378            platform_version,
379        )
380        .expect("expected to get key pair");
381
382        signer.add_identity_public_key(key.clone(), private_key);
383
384        identity.add_public_key(key.clone());
385
386        platform
387            .drive
388            .add_new_unique_keys_to_identity(
389                identity.id().to_buffer(),
390                vec![key.clone()],
391                &BlockInfo::default(),
392                true,
393                None,
394                platform_version,
395            )
396            .expect("expected to add a new key");
397
398        key
399    }
400
401    pub(in crate::execution) fn setup_identity_with_withdrawal_key_and_system_credits(
402        platform: &mut TempPlatform<MockCoreRPCLike>,
403        seed: u64,
404        withdrawal_key_type: KeyType,
405        credits: Credits,
406    ) -> (Identity, SimpleSigner, IdentityPublicKey, IdentityPublicKey) {
407        let platform_version = PlatformVersion::latest();
408        platform
409            .drive
410            .add_to_system_credits(credits, None, platform_version)
411            .expect("expected to add to system credits");
412        let mut signer = SimpleSigner::default();
413
414        let mut rng = StdRng::seed_from_u64(seed);
415
416        let (master_key, master_private_key) =
417            IdentityPublicKey::random_ecdsa_master_authentication_key_with_rng(
418                0,
419                &mut rng,
420                platform_version,
421            )
422            .expect("expected to get key pair");
423
424        signer.add_identity_public_key(master_key.clone(), master_private_key);
425
426        let (critical_public_key, private_key) =
427            IdentityPublicKey::random_ecdsa_critical_level_authentication_key_with_rng(
428                1,
429                &mut rng,
430                platform_version,
431            )
432            .expect("expected to get key pair");
433
434        signer.add_identity_public_key(critical_public_key.clone(), private_key);
435
436        let (withdrawal_public_key, withdrawal_private_key) =
437            IdentityPublicKey::random_key_with_known_attributes(
438                2,
439                &mut rng,
440                Purpose::TRANSFER,
441                SecurityLevel::CRITICAL,
442                withdrawal_key_type,
443                None,
444                platform_version,
445            )
446            .expect("expected to get key pair");
447
448        signer.add_identity_public_key(withdrawal_public_key.clone(), withdrawal_private_key);
449
450        let identity: Identity = IdentityV0 {
451            id: Identifier::random_with_rng(&mut rng),
452            public_keys: BTreeMap::from([
453                (0, master_key.clone()),
454                (1, critical_public_key.clone()),
455                (2, withdrawal_public_key.clone()),
456            ]),
457            balance: credits,
458            revision: 0,
459        }
460        .into();
461
462        // We just add this identity to the system first
463
464        platform
465            .drive
466            .add_new_identity(
467                identity.clone(),
468                false,
469                &BlockInfo::default(),
470                true,
471                None,
472                platform_version,
473            )
474            .expect("expected to add a new identity");
475
476        (identity, signer, critical_public_key, withdrawal_public_key)
477    }
478
479    pub(in crate::execution) fn add_tokens_to_identity(
480        platform: &TempPlatform<MockCoreRPCLike>,
481        token_id: Identifier,
482        identity_id: Identifier,
483        balance_to_add: Credits,
484    ) {
485        let platform_version = PlatformVersion::latest();
486        platform
487            .drive
488            .add_to_identity_token_balance(
489                token_id.to_buffer(),
490                identity_id.to_buffer(),
491                balance_to_add,
492                &BlockInfo::default(),
493                true,
494                None,
495                platform_version,
496                None,
497            )
498            .expect("expected to add token balance to identity");
499        platform
500            .drive
501            .add_to_token_total_supply(
502                token_id.to_buffer(),
503                balance_to_add,
504                true,
505                false,
506                true,
507                &BlockInfo::default(),
508                None,
509                platform_version,
510            )
511            .expect("expected to add to total supply");
512    }
513
514    pub(in crate::execution) fn process_state_transitions(
515        platform: &TempPlatform<MockCoreRPCLike>,
516        state_transitions: &[StateTransition],
517        block_info: BlockInfo,
518        platform_state: &PlatformState,
519    ) -> (Vec<FeeResult>, ProcessedBlockFeesOutcome) {
520        let platform_version = PlatformVersion::latest();
521
522        let raw_state_transitions = state_transitions
523            .iter()
524            .map(|a| a.serialize_to_bytes().expect("expected to serialize"))
525            .collect::<Vec<_>>();
526
527        let transaction = platform.drive.grove.start_transaction();
528
529        let processing_result = platform
530            .platform
531            .process_raw_state_transitions(
532                &raw_state_transitions,
533                platform_state,
534                &block_info,
535                &transaction,
536                platform_version,
537                false,
538                None,
539            )
540            .expect("expected to process state transition");
541
542        let fee_results = processing_result.execution_results().iter().map(|result| {
543            let fee_result = expect_match!(result, StateTransitionExecutionResult::SuccessfulExecution{ fee_result, .. } => fee_result);
544            fee_result.clone()
545        }).collect();
546
547        // while we have the state transitions executed, we now need to process the block fees
548        let block_fees_v0: BlockFeesV0 = processing_result.aggregated_fees().clone().into();
549
550        let block_execution_context = BlockExecutionContext::V0(BlockExecutionContextV0 {
551            block_state_info: BlockStateInfo::V0(BlockStateInfoV0 {
552                height: block_info.height,
553                round: 0,
554                block_time_ms: block_info.time_ms,
555                previous_block_time_ms: platform_state.last_committed_block_time_ms(),
556                proposer_pro_tx_hash: Default::default(),
557                core_chain_locked_height: 0,
558                block_hash: None,
559                app_hash: None,
560            }),
561            epoch_info: EpochInfo::V0(EpochInfoV0::default()),
562            unsigned_withdrawal_transactions: Default::default(),
563            block_address_balance_changes: Default::default(),
564            block_platform_state: platform_state.clone(),
565            proposer_results: None,
566        });
567
568        // Process fees
569        let processed_block_fees = platform
570            .process_block_fees_and_validate_sum_trees(
571                &block_execution_context,
572                block_fees_v0.into(),
573                &transaction,
574                platform_version,
575            )
576            .expect("expected to process block fees");
577
578        platform
579            .drive
580            .grove
581            .commit_transaction(transaction)
582            .unwrap()
583            .expect("expected to commit");
584
585        (fee_results, processed_block_fees)
586    }
587
588    pub(in crate::execution) fn fetch_expected_identity_balance(
589        platform: &TempPlatform<MockCoreRPCLike>,
590        identity_id: Identifier,
591        platform_version: &PlatformVersion,
592        expected_balance: Credits,
593    ) {
594        assert_eq!(
595            expected_balance,
596            platform
597                .drive
598                .fetch_identity_balance(identity_id.to_buffer(), None, platform_version)
599                .expect("expected to be able to fetch balance")
600                .expect("expected a balance")
601        );
602    }
603
604    pub(in crate::execution) fn setup_masternode_owner_identity(
605        platform: &mut TempPlatform<MockCoreRPCLike>,
606        seed: u64,
607        credits: Credits,
608        platform_version: &PlatformVersion,
609    ) -> (Identity, SimpleSigner, IdentityPublicKey, IdentityPublicKey) {
610        let mut signer = SimpleSigner::default();
611
612        platform
613            .drive
614            .add_to_system_credits(credits, None, platform_version)
615            .expect("expected to add to system credits");
616
617        let mut rng = StdRng::seed_from_u64(seed);
618
619        let (transfer_key, transfer_private_key) =
620            IdentityPublicKey::random_masternode_transfer_key_with_rng(
621                0,
622                &mut rng,
623                platform_version,
624            )
625            .expect("expected to get key pair");
626
627        let (owner_key, owner_private_key) =
628            IdentityPublicKey::random_masternode_owner_key_with_rng(1, &mut rng, platform_version)
629                .expect("expected to get key pair");
630
631        let owner_address = owner_key
632            .public_key_hash()
633            .expect("expected a public key hash");
634
635        let payout_address = transfer_key
636            .public_key_hash()
637            .expect("expected a public key hash");
638
639        signer.add_identity_public_key(transfer_key.clone(), transfer_private_key);
640        signer.add_identity_public_key(owner_key.clone(), owner_private_key);
641
642        let pro_tx_hash_bytes: [u8; 32] = rng.gen();
643
644        let identity: Identity = IdentityV0 {
645            id: pro_tx_hash_bytes.into(),
646            public_keys: BTreeMap::from([(0, transfer_key.clone()), (1, owner_key.clone())]),
647            balance: credits,
648            revision: 0,
649        }
650        .into();
651
652        // We just add this identity to the system first
653
654        platform
655            .drive
656            .add_new_identity(
657                identity.clone(),
658                true,
659                &BlockInfo::default(),
660                true,
661                None,
662                platform_version,
663            )
664            .expect("expected to add a new identity");
665
666        let mut platform_state = platform.state.load().clone().deref().clone();
667
668        let pro_tx_hash = ProTxHash::from_byte_array(pro_tx_hash_bytes);
669
670        let random_ip = Ipv4Addr::new(
671            rng.gen_range(0..255),
672            rng.gen_range(0..255),
673            rng.gen_range(0..255),
674            rng.gen_range(0..255),
675        );
676
677        platform_state.full_masternode_list_mut().insert(
678            pro_tx_hash,
679            MasternodeListItem {
680                node_type: MasternodeType::Regular,
681                pro_tx_hash,
682                collateral_hash: Txid::from_byte_array(rng.gen()),
683                collateral_index: 0,
684                collateral_address: rng.gen(),
685                operator_reward: 0.0,
686                state: DMNState {
687                    service: SocketAddr::new(IpAddr::V4(random_ip), 19999),
688                    registered_height: 0,
689                    pose_revived_height: None,
690                    pose_ban_height: None,
691                    revocation_reason: 0,
692                    owner_address,
693                    voting_address: rng.gen(),
694                    payout_address,
695                    pub_key_operator: vec![],
696                    operator_payout_address: None,
697                    platform_node_id: None,
698                    platform_p2p_port: None,
699                    platform_http_port: None,
700                },
701            },
702        );
703
704        platform.state.store(Arc::new(platform_state));
705
706        (identity, signer, owner_key, transfer_key)
707    }
708
709    pub(in crate::execution) fn setup_masternode_voting_identity(
710        platform: &mut TempPlatform<MockCoreRPCLike>,
711        seed: u64,
712        platform_version: &PlatformVersion,
713    ) -> (Identifier, Identity, SimpleSigner, IdentityPublicKey) {
714        let mut signer = SimpleSigner::default();
715
716        let mut rng = StdRng::seed_from_u64(seed);
717
718        let (voting_key, voting_private_key) =
719            IdentityPublicKey::random_voting_key_with_rng(0, &mut rng, platform_version)
720                .expect("expected to get key pair");
721
722        signer.add_identity_public_key(voting_key.clone(), voting_private_key);
723
724        let pro_tx_hash_bytes: [u8; 32] = rng.gen();
725
726        let voting_address = voting_key
727            .public_key_hash()
728            .expect("expected a public key hash");
729
730        let voter_identifier =
731            Identifier::create_voter_identifier(&pro_tx_hash_bytes, &voting_address);
732
733        let identity: Identity = IdentityV0 {
734            id: voter_identifier,
735            public_keys: BTreeMap::from([(0, voting_key.clone())]),
736            balance: 0,
737            revision: 0,
738        }
739        .into();
740
741        // We just add this identity to the system first
742
743        platform
744            .drive
745            .add_new_identity(
746                identity.clone(),
747                true,
748                &BlockInfo::default(),
749                true,
750                None,
751                platform_version,
752            )
753            .expect("expected to add a new identity");
754
755        let mut platform_state = platform.state.load().clone().deref().clone();
756
757        let pro_tx_hash = ProTxHash::from_byte_array(pro_tx_hash_bytes);
758
759        let random_ip = Ipv4Addr::new(
760            rng.gen_range(0..255),
761            rng.gen_range(0..255),
762            rng.gen_range(0..255),
763            rng.gen_range(0..255),
764        );
765
766        platform_state.full_masternode_list_mut().insert(
767            pro_tx_hash,
768            MasternodeListItem {
769                node_type: MasternodeType::Regular,
770                pro_tx_hash,
771                collateral_hash: Txid::from_byte_array(rng.gen()),
772                collateral_index: 0,
773                collateral_address: rng.gen(),
774                operator_reward: 0.0,
775                state: DMNState {
776                    service: SocketAddr::new(IpAddr::V4(random_ip), 19999),
777                    registered_height: 0,
778                    pose_revived_height: None,
779                    pose_ban_height: None,
780                    revocation_reason: 0,
781                    owner_address: rng.gen(),
782                    voting_address,
783                    payout_address: rng.gen(),
784                    pub_key_operator: vec![],
785                    operator_payout_address: None,
786                    platform_node_id: None,
787                    platform_p2p_port: None,
788                    platform_http_port: None,
789                },
790            },
791        );
792
793        platform.state.store(Arc::new(platform_state));
794
795        (pro_tx_hash_bytes.into(), identity, signer, voting_key)
796    }
797
798    pub(in crate::execution) fn take_down_masternode_identities(
799        platform: &mut TempPlatform<MockCoreRPCLike>,
800        masternode_identities: &Vec<Identifier>,
801    ) {
802        let mut platform_state = platform.state.load().clone().deref().clone();
803
804        let list = platform_state.full_masternode_list_mut();
805
806        for masternode_identifiers in masternode_identities {
807            let pro_tx_hash = ProTxHash::from_byte_array(masternode_identifiers.to_buffer());
808
809            list.remove(&pro_tx_hash);
810        }
811
812        platform.state.store(Arc::new(platform_state));
813    }
814
815    #[allow(dead_code)]
816    pub(in crate::execution) enum IdentityTestInfo<'a> {
817        Given {
818            identity: &'a Identity,
819            signer: &'a SimpleSigner,
820            public_key: &'a IdentityPublicKey,
821            identity_nonce: IdentityNonce,
822        },
823        UseSeed(u64),
824        UseRng(&'a mut StdRng),
825    }
826
827    pub(in crate::execution) async fn register_contract_from_bytes(
828        platform: &mut TempPlatform<MockCoreRPCLike>,
829        platform_state: &PlatformState,
830        contract_bytes: Vec<u8>,
831        identity_info: IdentityTestInfo<'_>,
832        platform_version: &PlatformVersion,
833    ) -> DataContract {
834        // Deserialize the data contract from bytes
835        let mut data_contract =
836            DataContract::versioned_deserialize(&contract_bytes, false, platform_version)
837                .expect("expected to deserialize data contract");
838
839        // Get identity info based on the enum variant
840        let (identity_cow, signer_cow, key_cow, nonce) = match identity_info {
841            IdentityTestInfo::Given {
842                identity,
843                signer,
844                public_key,
845                identity_nonce,
846            } => (
847                Cow::Borrowed(identity),
848                Cow::Borrowed(signer),
849                Cow::Borrowed(public_key),
850                identity_nonce,
851            ),
852            IdentityTestInfo::UseSeed(seed) => {
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            IdentityTestInfo::UseRng(rng) => {
857                let seed = rng.gen();
858                let (identity, signer, key) = setup_identity(platform, seed, dash_to_credits!(1));
859                (Cow::Owned(identity), Cow::Owned(signer), Cow::Owned(key), 1)
860            }
861        };
862
863        let contract_id = DataContract::generate_data_contract_id_v0(identity_cow.id(), nonce);
864
865        data_contract.set_id(contract_id);
866
867        // Create and sign the data contract create transition
868        let state_transition = DataContractCreateTransition::new_from_data_contract(
869            data_contract.clone(),
870            nonce,
871            &identity_cow.as_ref().clone().into_partial_identity_info(),
872            key_cow.id(),
873            signer_cow.as_ref(),
874            platform_version,
875            None,
876        )
877        .await
878        .expect("expected to create and sign data contract create transition");
879
880        // Serialize the state transition
881        let state_transition_bytes = state_transition
882            .serialize_to_bytes()
883            .expect("expected to serialize state transition");
884
885        let transaction = platform.drive.grove.start_transaction();
886
887        let processing_result = platform
888            .platform
889            .process_raw_state_transitions(
890                &[state_transition_bytes],
891                platform_state,
892                &BlockInfo::default(),
893                &transaction,
894                platform_version,
895                false,
896                None,
897            )
898            .expect("expected to process state transition");
899
900        platform
901            .drive
902            .grove
903            .commit_transaction(transaction)
904            .unwrap()
905            .expect("expected to commit transaction");
906
907        let execution_result = processing_result.into_execution_results().remove(0);
908        assert_matches!(execution_result, SuccessfulExecution { .. });
909
910        data_contract
911    }
912
913    pub(in crate::execution) async fn create_dpns_name_contest_give_key_info(
914        platform: &mut TempPlatform<MockCoreRPCLike>,
915        platform_state: &PlatformState,
916        seed: u64,
917        name: &str,
918        platform_version: &PlatformVersion,
919    ) -> (
920        (
921            Identity,
922            SimpleSigner,
923            IdentityPublicKey,
924            (Document, Bytes32),
925            (Document, Bytes32),
926        ),
927        (
928            Identity,
929            SimpleSigner,
930            IdentityPublicKey,
931            (Document, Bytes32),
932            (Document, Bytes32),
933        ),
934        Arc<DataContract>,
935    ) {
936        let mut rng = StdRng::seed_from_u64(seed);
937
938        let identity_1_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
939
940        let identity_2_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
941
942        // Flip them if needed so identity 1 id is always smaller than identity 2 id
943        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
944        {
945            (identity_1_info, identity_2_info)
946        } else {
947            (identity_2_info, identity_1_info)
948        };
949
950        let ((preorder_document_1, document_1), (preorder_document_2, document_2), dpns_contract) =
951            create_dpns_name_contest_on_identities(
952                platform,
953                &identity_1_info,
954                &identity_2_info,
955                platform_state,
956                rng,
957                name,
958                None,
959                false,
960                platform_version,
961            )
962            .await;
963
964        let (identity_1, signer_1, identity_key_1) = identity_1_info;
965
966        let (identity_2, signer_2, identity_key_2) = identity_2_info;
967
968        (
969            (
970                identity_1,
971                signer_1,
972                identity_key_1,
973                preorder_document_1,
974                document_1,
975            ),
976            (
977                identity_2,
978                signer_2,
979                identity_key_2,
980                preorder_document_2,
981                document_2,
982            ),
983            dpns_contract,
984        )
985    }
986
987    pub(in crate::execution) async fn create_dpns_identity_name_contest(
988        platform: &mut TempPlatform<MockCoreRPCLike>,
989        platform_state: &PlatformState,
990        seed: u64,
991        name: &str,
992        platform_version: &PlatformVersion,
993    ) -> (Identity, Identity, Arc<DataContract>) {
994        let mut rng = StdRng::seed_from_u64(seed);
995
996        let identity_1_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
997
998        let identity_2_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
999
1000        // Flip them if needed so identity 1 id is always smaller than identity 2 id
1001        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
1002        {
1003            (identity_1_info, identity_2_info)
1004        } else {
1005            (identity_2_info, identity_1_info)
1006        };
1007
1008        let (_, _, dpns_contract) = create_dpns_name_contest_on_identities(
1009            platform,
1010            &identity_1_info,
1011            &identity_2_info,
1012            platform_state,
1013            rng,
1014            name,
1015            None,
1016            false,
1017            platform_version,
1018        )
1019        .await;
1020        (identity_1_info.0, identity_2_info.0, dpns_contract)
1021    }
1022
1023    /// This can be useful if we already created the identities and we reuse the seed
1024    pub(in crate::execution) async fn create_dpns_identity_name_contest_skip_creating_identities(
1025        platform: &mut TempPlatform<MockCoreRPCLike>,
1026        platform_state: &PlatformState,
1027        seed: u64,
1028        name: &str,
1029        nonce_offset: Option<IdentityNonce>,
1030        platform_version: &PlatformVersion,
1031    ) -> (Identity, Identity, Arc<DataContract>) {
1032        let mut rng = StdRng::seed_from_u64(seed);
1033
1034        let identity_1_info = setup_identity_without_adding_it(rng.gen(), dash_to_credits!(0.5));
1035
1036        let identity_2_info = setup_identity_without_adding_it(rng.gen(), dash_to_credits!(0.5));
1037
1038        // Flip them if needed so identity 1 id is always smaller than identity 2 id
1039        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
1040        {
1041            (identity_1_info, identity_2_info)
1042        } else {
1043            (identity_2_info, identity_1_info)
1044        };
1045
1046        let (_, _, dpns_contract) = create_dpns_name_contest_on_identities(
1047            platform,
1048            &identity_1_info,
1049            &identity_2_info,
1050            platform_state,
1051            rng,
1052            name,
1053            nonce_offset,
1054            true, //we should also skip preorder
1055            platform_version,
1056        )
1057        .await;
1058        (identity_1_info.0, identity_2_info.0, dpns_contract)
1059    }
1060
1061    pub(in crate::execution) async fn create_dpns_contract_name_contest(
1062        platform: &mut TempPlatform<MockCoreRPCLike>,
1063        platform_state: &PlatformState,
1064        seed: u64,
1065        name: &str,
1066        platform_version: &PlatformVersion,
1067    ) -> (Identity, Identity, DataContract) {
1068        let mut rng = StdRng::seed_from_u64(seed);
1069
1070        let identity_1_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
1071
1072        let identity_2_info = setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
1073
1074        // Flip them if needed so identity 1 id is always smaller than identity 2 id
1075        let (identity_1_info, identity_2_info) = if identity_1_info.0.id() < identity_2_info.0.id()
1076        {
1077            (identity_1_info, identity_2_info)
1078        } else {
1079            (identity_2_info, identity_1_info)
1080        };
1081
1082        let dashpay_contract = setup_contract(
1083            &platform.drive,
1084            "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json",
1085            None,
1086            None,
1087            None::<fn(&mut DataContract)>,
1088            None,
1089            None,
1090        );
1091
1092        let card_game = setup_contract(
1093            &platform.drive,
1094            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-direct-purchase.json",
1095            None,
1096            None,
1097            None::<fn(&mut DataContract)>,
1098            None,
1099            None,
1100        );
1101
1102        let (_, _, dpns_contract) = create_dpns_name_contest_on_identities_for_contract_records(
1103            platform,
1104            &identity_1_info,
1105            &identity_2_info,
1106            &dashpay_contract,
1107            &card_game,
1108            platform_state,
1109            rng,
1110            name,
1111            platform_version,
1112        )
1113        .await;
1114        (identity_1_info.0, identity_2_info.0, dpns_contract)
1115    }
1116
1117    #[allow(clippy::too_many_arguments)]
1118    async fn create_dpns_name_contest_on_identities(
1119        platform: &mut TempPlatform<MockCoreRPCLike>,
1120        identity_1: &(Identity, SimpleSigner, IdentityPublicKey),
1121        identity_2: &(Identity, SimpleSigner, IdentityPublicKey),
1122        platform_state: &PlatformState,
1123        mut rng: StdRng,
1124        name: &str,
1125        nonce_offset: Option<IdentityNonce>,
1126        skip_preorder: bool,
1127        platform_version: &PlatformVersion,
1128    ) -> (
1129        ((Document, Bytes32), (Document, Bytes32)),
1130        ((Document, Bytes32), (Document, Bytes32)),
1131        Arc<DataContract>,
1132    ) {
1133        let (identity_1, signer_1, key_1) = identity_1;
1134
1135        let (identity_2, signer_2, key_2) = identity_2;
1136
1137        let dpns = platform
1138            .drive
1139            .cache
1140            .system_data_contracts
1141            .load_dpns(platform_version)
1142            .expect("expected the dpns system contract");
1143        let dpns_contract = dpns.clone();
1144
1145        let preorder = dpns_contract
1146            .document_type_for_name("preorder")
1147            .expect("expected a profile document type");
1148
1149        assert!(!preorder.documents_mutable());
1150        assert!(preorder.documents_can_be_deleted());
1151        assert!(!preorder.documents_transferable().is_transferable());
1152
1153        let domain = dpns_contract
1154            .document_type_for_name("domain")
1155            .expect("expected a profile document type");
1156
1157        assert!(!domain.documents_mutable());
1158        // Deletion is disabled with data trigger
1159        assert!(domain.documents_can_be_deleted());
1160        assert!(domain.documents_transferable().is_transferable());
1161
1162        let entropy = Bytes32::random_with_rng(&mut rng);
1163
1164        let mut preorder_document_1 = preorder
1165            .random_document_with_identifier_and_entropy(
1166                &mut rng,
1167                identity_1.id(),
1168                entropy,
1169                DocumentFieldFillType::FillIfNotRequired,
1170                DocumentFieldFillSize::AnyDocumentFillSize,
1171                platform_version,
1172            )
1173            .expect("expected a random document");
1174
1175        let mut preorder_document_2 = preorder
1176            .random_document_with_identifier_and_entropy(
1177                &mut rng,
1178                identity_2.id(),
1179                entropy,
1180                DocumentFieldFillType::FillIfNotRequired,
1181                DocumentFieldFillSize::AnyDocumentFillSize,
1182                platform_version,
1183            )
1184            .expect("expected a random document");
1185
1186        let mut document_1 = domain
1187            .random_document_with_identifier_and_entropy(
1188                &mut rng,
1189                identity_1.id(),
1190                entropy,
1191                DocumentFieldFillType::FillIfNotRequired,
1192                DocumentFieldFillSize::AnyDocumentFillSize,
1193                platform_version,
1194            )
1195            .expect("expected a random document");
1196
1197        let mut document_2 = domain
1198            .random_document_with_identifier_and_entropy(
1199                &mut rng,
1200                identity_2.id(),
1201                entropy,
1202                DocumentFieldFillType::FillIfNotRequired,
1203                DocumentFieldFillSize::AnyDocumentFillSize,
1204                platform_version,
1205            )
1206            .expect("expected a random document");
1207
1208        document_1.set("parentDomainName", "dash".into());
1209        document_1.set("normalizedParentDomainName", "dash".into());
1210        document_1.set("label", name.into());
1211        document_1.set(
1212            "normalizedLabel",
1213            convert_to_homograph_safe_chars(name).into(),
1214        );
1215        document_1.set("records.identity", document_1.owner_id().into());
1216        document_1.set("subdomainRules.allowSubdomains", false.into());
1217
1218        document_2.set("parentDomainName", "dash".into());
1219        document_2.set("normalizedParentDomainName", "dash".into());
1220        document_2.set("label", name.into());
1221        document_2.set(
1222            "normalizedLabel",
1223            convert_to_homograph_safe_chars(name).into(),
1224        );
1225        document_2.set("records.identity", document_2.owner_id().into());
1226        document_2.set("subdomainRules.allowSubdomains", false.into());
1227
1228        let salt_1: [u8; 32] = rng.gen();
1229        let salt_2: [u8; 32] = rng.gen();
1230
1231        let mut salted_domain_buffer_1: Vec<u8> = vec![];
1232        salted_domain_buffer_1.extend(salt_1);
1233        salted_domain_buffer_1.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1234
1235        let salted_domain_hash_1 = hash_double(salted_domain_buffer_1);
1236
1237        let mut salted_domain_buffer_2: Vec<u8> = vec![];
1238        salted_domain_buffer_2.extend(salt_2);
1239        salted_domain_buffer_2.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1240
1241        let salted_domain_hash_2 = hash_double(salted_domain_buffer_2);
1242
1243        preorder_document_1.set("saltedDomainHash", salted_domain_hash_1.into());
1244        preorder_document_2.set("saltedDomainHash", salted_domain_hash_2.into());
1245
1246        document_1.set("preorderSalt", salt_1.into());
1247        document_2.set("preorderSalt", salt_2.into());
1248
1249        let documents_batch_create_preorder_transition_1 =
1250            BatchTransition::new_document_creation_transition_from_document(
1251                preorder_document_1.clone(),
1252                preorder,
1253                entropy.0,
1254                key_1,
1255                2 + nonce_offset.unwrap_or_default(),
1256                0,
1257                None,
1258                signer_1,
1259                platform_version,
1260                None,
1261            )
1262            .await
1263            .expect("expect to create documents batch transition");
1264
1265        let documents_batch_create_serialized_preorder_transition_1 =
1266            documents_batch_create_preorder_transition_1
1267                .serialize_to_bytes()
1268                .expect("expected documents batch serialized state transition");
1269
1270        let documents_batch_create_preorder_transition_2 =
1271            BatchTransition::new_document_creation_transition_from_document(
1272                preorder_document_2.clone(),
1273                preorder,
1274                entropy.0,
1275                key_2,
1276                2 + nonce_offset.unwrap_or_default(),
1277                0,
1278                None,
1279                signer_2,
1280                platform_version,
1281                None,
1282            )
1283            .await
1284            .expect("expect to create documents batch transition");
1285
1286        let documents_batch_create_serialized_preorder_transition_2 =
1287            documents_batch_create_preorder_transition_2
1288                .serialize_to_bytes()
1289                .expect("expected documents batch serialized state transition");
1290
1291        let documents_batch_create_transition_1 =
1292            BatchTransition::new_document_creation_transition_from_document(
1293                document_1.clone(),
1294                domain,
1295                entropy.0,
1296                key_1,
1297                3 + nonce_offset.unwrap_or_default(),
1298                0,
1299                None,
1300                signer_1,
1301                platform_version,
1302                None,
1303            )
1304            .await
1305            .expect("expect to create documents batch transition");
1306
1307        let documents_batch_create_serialized_transition_1 = documents_batch_create_transition_1
1308            .serialize_to_bytes()
1309            .expect("expected documents batch serialized state transition");
1310
1311        let documents_batch_create_transition_2 =
1312            BatchTransition::new_document_creation_transition_from_document(
1313                document_2.clone(),
1314                domain,
1315                entropy.0,
1316                key_2,
1317                3 + nonce_offset.unwrap_or_default(),
1318                0,
1319                None,
1320                signer_2,
1321                platform_version,
1322                None,
1323            )
1324            .await
1325            .expect("expect to create documents batch transition");
1326
1327        let documents_batch_create_serialized_transition_2 = documents_batch_create_transition_2
1328            .serialize_to_bytes()
1329            .expect("expected documents batch serialized state transition");
1330
1331        if !skip_preorder {
1332            let transaction = platform.drive.grove.start_transaction();
1333
1334            let processing_result = platform
1335                .platform
1336                .process_raw_state_transitions(
1337                    &[
1338                        documents_batch_create_serialized_preorder_transition_1.clone(),
1339                        documents_batch_create_serialized_preorder_transition_2.clone(),
1340                    ],
1341                    platform_state,
1342                    &BlockInfo::default_with_time(
1343                        platform_state
1344                            .last_committed_block_time_ms()
1345                            .unwrap_or_default()
1346                            + 3000,
1347                    ),
1348                    &transaction,
1349                    platform_version,
1350                    false,
1351                    None,
1352                )
1353                .expect("expected to process state transition");
1354
1355            platform
1356                .drive
1357                .grove
1358                .commit_transaction(transaction)
1359                .unwrap()
1360                .expect("expected to commit transaction");
1361
1362            let successful_count = processing_result
1363                .execution_results()
1364                .iter()
1365                .filter(|result| {
1366                    assert_matches!(
1367                        result,
1368                        StateTransitionExecutionResult::SuccessfulExecution { .. }
1369                    );
1370                    true
1371                })
1372                .count();
1373
1374            assert_eq!(successful_count, 2);
1375        }
1376
1377        let transaction = platform.drive.grove.start_transaction();
1378
1379        let processing_result = platform
1380            .platform
1381            .process_raw_state_transitions(
1382                &[
1383                    documents_batch_create_serialized_transition_1.clone(),
1384                    documents_batch_create_serialized_transition_2.clone(),
1385                ],
1386                platform_state,
1387                &BlockInfo::default_with_time(
1388                    platform_state
1389                        .last_committed_block_time_ms()
1390                        .unwrap_or_default()
1391                        + 3000,
1392                ),
1393                &transaction,
1394                platform_version,
1395                false,
1396                None,
1397            )
1398            .expect("expected to process state transition");
1399
1400        platform
1401            .drive
1402            .grove
1403            .commit_transaction(transaction)
1404            .unwrap()
1405            .expect("expected to commit transaction");
1406
1407        let successful_count = processing_result
1408            .execution_results()
1409            .iter()
1410            .filter(|result| {
1411                assert_matches!(
1412                    result,
1413                    StateTransitionExecutionResult::SuccessfulExecution { .. }
1414                );
1415                true
1416            })
1417            .count();
1418
1419        assert_eq!(successful_count, 2);
1420        (
1421            ((preorder_document_1, entropy), (document_1, entropy)),
1422            ((preorder_document_2, entropy), (document_2, entropy)),
1423            dpns_contract,
1424        )
1425    }
1426
1427    #[allow(clippy::too_many_arguments)]
1428    async fn create_dpns_name_contest_on_identities_for_contract_records(
1429        platform: &mut TempPlatform<MockCoreRPCLike>,
1430        identity_1: &(Identity, SimpleSigner, IdentityPublicKey),
1431        identity_2: &(Identity, SimpleSigner, IdentityPublicKey),
1432        contract_1: &DataContract,
1433        contract_2: &DataContract,
1434        platform_state: &PlatformState,
1435        mut rng: StdRng,
1436        name: &str,
1437        platform_version: &PlatformVersion,
1438    ) -> (
1439        ((Document, Bytes32), (Document, Bytes32)),
1440        ((Document, Bytes32), (Document, Bytes32)),
1441        DataContract,
1442    ) {
1443        let (identity_1, signer_1, key_1) = identity_1;
1444
1445        let (identity_2, signer_2, key_2) = identity_2;
1446
1447        let dpns_contract = setup_contract(
1448            &platform.drive,
1449            "tests/supporting_files/contract/dpns/dpns-contract-contested-unique-index-with-contract-id.json",
1450            None,
1451            None,
1452            None::<fn(&mut DataContract)>,
1453            None,
1454            None,
1455        );
1456
1457        let preorder = dpns_contract
1458            .document_type_for_name("preorder")
1459            .expect("expected a profile document type");
1460
1461        assert!(!preorder.documents_mutable());
1462        assert!(preorder.documents_can_be_deleted());
1463        assert!(!preorder.documents_transferable().is_transferable());
1464
1465        let domain = dpns_contract
1466            .document_type_for_name("domain")
1467            .expect("expected a profile document type");
1468
1469        assert!(!domain.documents_mutable());
1470        // Deletion is disabled with data trigger
1471        assert!(domain.documents_can_be_deleted());
1472        assert!(domain.documents_transferable().is_transferable());
1473
1474        let entropy = Bytes32::random_with_rng(&mut rng);
1475
1476        let mut preorder_document_1 = preorder
1477            .random_document_with_identifier_and_entropy(
1478                &mut rng,
1479                identity_1.id(),
1480                entropy,
1481                DocumentFieldFillType::FillIfNotRequired,
1482                DocumentFieldFillSize::AnyDocumentFillSize,
1483                platform_version,
1484            )
1485            .expect("expected a random document");
1486
1487        let mut preorder_document_2 = preorder
1488            .random_document_with_identifier_and_entropy(
1489                &mut rng,
1490                identity_2.id(),
1491                entropy,
1492                DocumentFieldFillType::FillIfNotRequired,
1493                DocumentFieldFillSize::AnyDocumentFillSize,
1494                platform_version,
1495            )
1496            .expect("expected a random document");
1497
1498        let mut document_1 = domain
1499            .random_document_with_identifier_and_entropy(
1500                &mut rng,
1501                identity_1.id(),
1502                entropy,
1503                DocumentFieldFillType::FillIfNotRequired,
1504                DocumentFieldFillSize::AnyDocumentFillSize,
1505                platform_version,
1506            )
1507            .expect("expected a random document");
1508
1509        let mut document_2 = domain
1510            .random_document_with_identifier_and_entropy(
1511                &mut rng,
1512                identity_2.id(),
1513                entropy,
1514                DocumentFieldFillType::FillIfNotRequired,
1515                DocumentFieldFillSize::AnyDocumentFillSize,
1516                platform_version,
1517            )
1518            .expect("expected a random document");
1519
1520        document_1.set("parentDomainName", "dash".into());
1521        document_1.set("normalizedParentDomainName", "dash".into());
1522        document_1.set("label", name.into());
1523        document_1.set(
1524            "normalizedLabel",
1525            convert_to_homograph_safe_chars(name).into(),
1526        );
1527        document_1.remove("records.identity");
1528        document_1.set("records.contract", contract_1.id().into());
1529        document_1.set("subdomainRules.allowSubdomains", false.into());
1530
1531        document_2.set("parentDomainName", "dash".into());
1532        document_2.set("normalizedParentDomainName", "dash".into());
1533        document_2.set("label", name.into());
1534        document_2.set(
1535            "normalizedLabel",
1536            convert_to_homograph_safe_chars(name).into(),
1537        );
1538        document_2.remove("records.identity");
1539        document_2.set("records.contract", contract_2.id().into());
1540        document_2.set("subdomainRules.allowSubdomains", false.into());
1541
1542        let salt_1: [u8; 32] = rng.gen();
1543        let salt_2: [u8; 32] = rng.gen();
1544
1545        let mut salted_domain_buffer_1: Vec<u8> = vec![];
1546        salted_domain_buffer_1.extend(salt_1);
1547        salted_domain_buffer_1.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1548
1549        let salted_domain_hash_1 = hash_double(salted_domain_buffer_1);
1550
1551        let mut salted_domain_buffer_2: Vec<u8> = vec![];
1552        salted_domain_buffer_2.extend(salt_2);
1553        salted_domain_buffer_2.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1554
1555        let salted_domain_hash_2 = hash_double(salted_domain_buffer_2);
1556
1557        preorder_document_1.set("saltedDomainHash", salted_domain_hash_1.into());
1558        preorder_document_2.set("saltedDomainHash", salted_domain_hash_2.into());
1559
1560        document_1.set("preorderSalt", salt_1.into());
1561        document_2.set("preorderSalt", salt_2.into());
1562
1563        let documents_batch_create_preorder_transition_1 =
1564            BatchTransition::new_document_creation_transition_from_document(
1565                preorder_document_1.clone(),
1566                preorder,
1567                entropy.0,
1568                key_1,
1569                2,
1570                0,
1571                None,
1572                signer_1,
1573                platform_version,
1574                None,
1575            )
1576            .await
1577            .expect("expect to create documents batch transition");
1578
1579        let documents_batch_create_serialized_preorder_transition_1 =
1580            documents_batch_create_preorder_transition_1
1581                .serialize_to_bytes()
1582                .expect("expected documents batch serialized state transition");
1583
1584        let documents_batch_create_preorder_transition_2 =
1585            BatchTransition::new_document_creation_transition_from_document(
1586                preorder_document_2.clone(),
1587                preorder,
1588                entropy.0,
1589                key_2,
1590                2,
1591                0,
1592                None,
1593                signer_2,
1594                platform_version,
1595                None,
1596            )
1597            .await
1598            .expect("expect to create documents batch transition");
1599
1600        let documents_batch_create_serialized_preorder_transition_2 =
1601            documents_batch_create_preorder_transition_2
1602                .serialize_to_bytes()
1603                .expect("expected documents batch serialized state transition");
1604
1605        let documents_batch_create_transition_1 =
1606            BatchTransition::new_document_creation_transition_from_document(
1607                document_1.clone(),
1608                domain,
1609                entropy.0,
1610                key_1,
1611                3,
1612                0,
1613                None,
1614                signer_1,
1615                platform_version,
1616                None,
1617            )
1618            .await
1619            .expect("expect to create documents batch transition");
1620
1621        let documents_batch_create_serialized_transition_1 = documents_batch_create_transition_1
1622            .serialize_to_bytes()
1623            .expect("expected documents batch serialized state transition");
1624
1625        let documents_batch_create_transition_2 =
1626            BatchTransition::new_document_creation_transition_from_document(
1627                document_2.clone(),
1628                domain,
1629                entropy.0,
1630                key_2,
1631                3,
1632                0,
1633                None,
1634                signer_2,
1635                platform_version,
1636                None,
1637            )
1638            .await
1639            .expect("expect to create documents batch transition");
1640
1641        let documents_batch_create_serialized_transition_2 = documents_batch_create_transition_2
1642            .serialize_to_bytes()
1643            .expect("expected documents batch serialized state transition");
1644
1645        let transaction = platform.drive.grove.start_transaction();
1646
1647        let processing_result = platform
1648            .platform
1649            .process_raw_state_transitions(
1650                &[
1651                    documents_batch_create_serialized_preorder_transition_1.clone(),
1652                    documents_batch_create_serialized_preorder_transition_2.clone(),
1653                ],
1654                platform_state,
1655                &BlockInfo::default_with_time(
1656                    platform_state
1657                        .last_committed_block_time_ms()
1658                        .unwrap_or_default()
1659                        + 3000,
1660                ),
1661                &transaction,
1662                platform_version,
1663                false,
1664                None,
1665            )
1666            .expect("expected to process state transition");
1667
1668        platform
1669            .drive
1670            .grove
1671            .commit_transaction(transaction)
1672            .unwrap()
1673            .expect("expected to commit transaction");
1674
1675        assert_eq!(processing_result.valid_count(), 2);
1676
1677        let transaction = platform.drive.grove.start_transaction();
1678
1679        let processing_result = platform
1680            .platform
1681            .process_raw_state_transitions(
1682                &[
1683                    documents_batch_create_serialized_transition_1.clone(),
1684                    documents_batch_create_serialized_transition_2.clone(),
1685                ],
1686                platform_state,
1687                &BlockInfo::default_with_time(
1688                    platform_state
1689                        .last_committed_block_time_ms()
1690                        .unwrap_or_default()
1691                        + 3000,
1692                ),
1693                &transaction,
1694                platform_version,
1695                false,
1696                None,
1697            )
1698            .expect("expected to process state transition");
1699
1700        platform
1701            .drive
1702            .grove
1703            .commit_transaction(transaction)
1704            .unwrap()
1705            .expect("expected to commit transaction");
1706
1707        assert_eq!(processing_result.valid_count(), 2);
1708        (
1709            ((preorder_document_1, entropy), (document_1, entropy)),
1710            ((preorder_document_2, entropy), (document_2, entropy)),
1711            dpns_contract,
1712        )
1713    }
1714
1715    pub(in crate::execution) async fn add_contender_to_dpns_name_contest(
1716        platform: &mut TempPlatform<MockCoreRPCLike>,
1717        platform_state: &PlatformState,
1718        seed: u64,
1719        name: &str,
1720        expect_err: Option<&str>,
1721        platform_version: &PlatformVersion,
1722    ) -> Identity {
1723        let mut rng = StdRng::seed_from_u64(seed);
1724
1725        let (identity_1, signer_1, key_1) =
1726            setup_identity(platform, rng.gen(), dash_to_credits!(0.5));
1727
1728        let dpns = platform
1729            .drive
1730            .cache
1731            .system_data_contracts
1732            .load_dpns(platform_version)
1733            .expect("expected the dpns system contract");
1734        let dpns_contract = dpns.clone();
1735
1736        let preorder = dpns_contract
1737            .document_type_for_name("preorder")
1738            .expect("expected a profile document type");
1739
1740        let domain = dpns_contract
1741            .document_type_for_name("domain")
1742            .expect("expected a profile document type");
1743
1744        let entropy = Bytes32::random_with_rng(&mut rng);
1745
1746        let mut preorder_document_1 = preorder
1747            .random_document_with_identifier_and_entropy(
1748                &mut rng,
1749                identity_1.id(),
1750                entropy,
1751                DocumentFieldFillType::FillIfNotRequired,
1752                DocumentFieldFillSize::AnyDocumentFillSize,
1753                platform_version,
1754            )
1755            .expect("expected a random document");
1756
1757        let mut document_1 = domain
1758            .random_document_with_identifier_and_entropy(
1759                &mut rng,
1760                identity_1.id(),
1761                entropy,
1762                DocumentFieldFillType::FillIfNotRequired,
1763                DocumentFieldFillSize::AnyDocumentFillSize,
1764                platform_version,
1765            )
1766            .expect("expected a random document");
1767
1768        document_1.set("parentDomainName", "dash".into());
1769        document_1.set("normalizedParentDomainName", "dash".into());
1770        document_1.set("label", name.into());
1771        document_1.set(
1772            "normalizedLabel",
1773            convert_to_homograph_safe_chars(name).into(),
1774        );
1775        document_1.set("records.identity", document_1.owner_id().into());
1776        document_1.set("subdomainRules.allowSubdomains", false.into());
1777
1778        let salt_1: [u8; 32] = rng.gen();
1779
1780        let mut salted_domain_buffer_1: Vec<u8> = vec![];
1781        salted_domain_buffer_1.extend(salt_1);
1782        salted_domain_buffer_1.extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
1783
1784        let salted_domain_hash_1 = hash_double(salted_domain_buffer_1);
1785
1786        preorder_document_1.set("saltedDomainHash", salted_domain_hash_1.into());
1787
1788        document_1.set("preorderSalt", salt_1.into());
1789
1790        let documents_batch_create_preorder_transition_1 =
1791            BatchTransition::new_document_creation_transition_from_document(
1792                preorder_document_1,
1793                preorder,
1794                entropy.0,
1795                &key_1,
1796                2,
1797                0,
1798                None,
1799                &signer_1,
1800                platform_version,
1801                None,
1802            )
1803            .await
1804            .expect("expect to create documents batch transition");
1805
1806        let documents_batch_create_serialized_preorder_transition_1 =
1807            documents_batch_create_preorder_transition_1
1808                .serialize_to_bytes()
1809                .expect("expected documents batch serialized state transition");
1810
1811        let documents_batch_create_transition_1 =
1812            BatchTransition::new_document_creation_transition_from_document(
1813                document_1,
1814                domain,
1815                entropy.0,
1816                &key_1,
1817                3,
1818                0,
1819                None,
1820                &signer_1,
1821                platform_version,
1822                None,
1823            )
1824            .await
1825            .expect("expect to create documents batch transition");
1826
1827        let documents_batch_create_serialized_transition_1 = documents_batch_create_transition_1
1828            .serialize_to_bytes()
1829            .expect("expected documents batch serialized state transition");
1830
1831        let transaction = platform.drive.grove.start_transaction();
1832
1833        let processing_result = platform
1834            .platform
1835            .process_raw_state_transitions(
1836                &[documents_batch_create_serialized_preorder_transition_1.clone()],
1837                platform_state,
1838                &BlockInfo::default_with_time(
1839                    platform_state
1840                        .last_committed_block_time_ms()
1841                        .unwrap_or_default()
1842                        + 3000,
1843                ),
1844                &transaction,
1845                platform_version,
1846                false,
1847                None,
1848            )
1849            .expect("expected to process state transition");
1850
1851        platform
1852            .drive
1853            .grove
1854            .commit_transaction(transaction)
1855            .unwrap()
1856            .expect("expected to commit transaction");
1857
1858        assert_eq!(processing_result.valid_count(), 1);
1859
1860        let transaction = platform.drive.grove.start_transaction();
1861
1862        let processing_result = platform
1863            .platform
1864            .process_raw_state_transitions(
1865                &[documents_batch_create_serialized_transition_1.clone()],
1866                platform_state,
1867                &BlockInfo::default_with_time(
1868                    platform_state
1869                        .last_committed_block_time_ms()
1870                        .unwrap_or_default()
1871                        + 3000,
1872                ),
1873                &transaction,
1874                platform_version,
1875                false,
1876                None,
1877            )
1878            .expect("expected to process state transition");
1879
1880        platform
1881            .drive
1882            .grove
1883            .commit_transaction(transaction)
1884            .unwrap()
1885            .expect("expected to commit transaction");
1886
1887        if let Some(expected_err) = expect_err {
1888            let result = processing_result.into_execution_results().remove(0);
1889
1890            let StateTransitionExecutionResult::PaidConsensusError {
1891                error: consensus_error,
1892                ..
1893            } = result
1894            else {
1895                panic!("expected a paid consensus error");
1896            };
1897            assert_eq!(consensus_error.to_string(), expected_err);
1898        } else {
1899            assert_eq!(processing_result.valid_count(), 1);
1900        }
1901        identity_1
1902    }
1903
1904    pub(in crate::execution) fn verify_dpns_name_contest(
1905        platform: &mut TempPlatform<MockCoreRPCLike>,
1906        platform_state: &Guard<Arc<PlatformState>>,
1907        dpns_contract: &DataContract,
1908        identity_1: &Identity,
1909        identity_2: &Identity,
1910        name: &str,
1911        platform_version: &PlatformVersion,
1912    ) {
1913        // Now let's run a query for the vote totals
1914
1915        let domain = dpns_contract
1916            .document_type_for_name("domain")
1917            .expect("expected a profile document type");
1918
1919        let config = bincode::config::standard()
1920            .with_big_endian()
1921            .with_no_limit();
1922
1923        let dash_encoded = bincode::encode_to_vec(Value::Text("dash".to_string()), config)
1924            .expect("expected to encode the word dash");
1925
1926        let quantum_encoded =
1927            bincode::encode_to_vec(Value::Text(convert_to_homograph_safe_chars(name)), config)
1928                .expect("expected to encode the word quantum");
1929
1930        let index_name = "parentNameAndLabel".to_string();
1931
1932        let query_validation_result = platform
1933            .query_contested_resource_vote_state(
1934                GetContestedResourceVoteStateRequest {
1935                    version: Some(get_contested_resource_vote_state_request::Version::V0(
1936                        GetContestedResourceVoteStateRequestV0 {
1937                            contract_id: dpns_contract.id().to_vec(),
1938                            document_type_name: domain.name().clone(),
1939                            index_name: index_name.clone(),
1940                            index_values: vec![dash_encoded.clone(), quantum_encoded.clone()],
1941                            result_type: ResultType::DocumentsAndVoteTally as i32,
1942                            allow_include_locked_and_abstaining_vote_tally: true,
1943                            start_at_identifier_info: None,
1944                            count: None,
1945                            prove: false,
1946                        },
1947                    )),
1948                },
1949                platform_state,
1950                platform_version,
1951            )
1952            .expect("expected to execute query")
1953            .into_data()
1954            .expect("expected query to be valid");
1955
1956        let get_contested_resource_vote_state_response::Version::V0(
1957            GetContestedResourceVoteStateResponseV0 {
1958                metadata: _,
1959                result,
1960            },
1961        ) = query_validation_result.version.expect("expected a version");
1962
1963        let Some(
1964            get_contested_resource_vote_state_response_v0::Result::ContestedResourceContenders(
1965                get_contested_resource_vote_state_response_v0::ContestedResourceContenders {
1966                    contenders,
1967                    ..
1968                },
1969            ),
1970        ) = result
1971        else {
1972            panic!("expected contenders")
1973        };
1974
1975        assert_eq!(contenders.len(), 2);
1976
1977        let first_contender = contenders.first().unwrap();
1978
1979        let second_contender = contenders.last().unwrap();
1980
1981        let first_contender_document = Document::from_bytes(
1982            first_contender
1983                .document
1984                .as_ref()
1985                .expect("expected a document")
1986                .as_slice(),
1987            domain,
1988            platform_version,
1989        )
1990        .expect("expected to get document");
1991
1992        let second_contender_document = Document::from_bytes(
1993            second_contender
1994                .document
1995                .as_ref()
1996                .expect("expected a document")
1997                .as_slice(),
1998            domain,
1999            platform_version,
2000        )
2001        .expect("expected to get document");
2002
2003        assert_ne!(first_contender_document, second_contender_document);
2004
2005        assert_eq!(first_contender.identifier, identity_1.id().to_vec());
2006
2007        assert_eq!(second_contender.identifier, identity_2.id().to_vec());
2008
2009        assert_eq!(first_contender.vote_count, Some(0));
2010
2011        assert_eq!(second_contender.vote_count, Some(0));
2012
2013        let GetContestedResourceVoteStateResponse { version } = platform
2014            .query_contested_resource_vote_state(
2015                GetContestedResourceVoteStateRequest {
2016                    version: Some(get_contested_resource_vote_state_request::Version::V0(
2017                        GetContestedResourceVoteStateRequestV0 {
2018                            contract_id: dpns_contract.id().to_vec(),
2019                            document_type_name: domain.name().clone(),
2020                            index_name: "parentNameAndLabel".to_string(),
2021                            index_values: vec![dash_encoded, quantum_encoded],
2022                            result_type: ResultType::DocumentsAndVoteTally as i32,
2023                            allow_include_locked_and_abstaining_vote_tally: true,
2024                            start_at_identifier_info: None,
2025                            count: None,
2026                            prove: true,
2027                        },
2028                    )),
2029                },
2030                platform_state,
2031                platform_version,
2032            )
2033            .expect("expected to execute query")
2034            .into_data()
2035            .expect("expected query to be valid");
2036
2037        let get_contested_resource_vote_state_response::Version::V0(
2038            GetContestedResourceVoteStateResponseV0 {
2039                metadata: _,
2040                result,
2041            },
2042        ) = version.expect("expected a version");
2043
2044        let Some(get_contested_resource_vote_state_response_v0::Result::Proof(proof)) = result
2045        else {
2046            panic!("expected contenders")
2047        };
2048
2049        let resolved_contested_document_vote_poll_drive_query =
2050            ResolvedContestedDocumentVotePollDriveQuery {
2051                vote_poll: ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed {
2052                    contract: DataContractResolvedInfo::BorrowedDataContract(dpns_contract),
2053                    document_type_name: domain.name().clone(),
2054                    index_name: index_name.clone(),
2055                    index_values: vec![
2056                        Value::Text("dash".to_string()),
2057                        Value::Text(convert_to_homograph_safe_chars(name)),
2058                    ],
2059                },
2060                result_type: DocumentsAndVoteTally,
2061                offset: None,
2062                limit: None,
2063                start_at: None,
2064                allow_include_locked_and_abstaining_vote_tally: true,
2065            };
2066
2067        let (_, result) = resolved_contested_document_vote_poll_drive_query
2068            .verify_vote_poll_vote_state_proof(proof.grovedb_proof.as_ref(), platform_version)
2069            .expect("expected to verify proof");
2070
2071        let contenders = result.contenders;
2072
2073        assert_eq!(contenders.len(), 2);
2074
2075        let first_contender = contenders.first().unwrap();
2076
2077        let second_contender = contenders.last().unwrap();
2078
2079        let first_contender_document = Document::from_bytes(
2080            first_contender
2081                .serialized_document()
2082                .as_ref()
2083                .expect("expected a document")
2084                .as_slice(),
2085            domain,
2086            platform_version,
2087        )
2088        .expect("expected to get document");
2089
2090        let second_contender_document = Document::from_bytes(
2091            second_contender
2092                .serialized_document()
2093                .as_ref()
2094                .expect("expected a document")
2095                .as_slice(),
2096            domain,
2097            platform_version,
2098        )
2099        .expect("expected to get document");
2100
2101        assert_ne!(first_contender_document, second_contender_document);
2102
2103        assert_eq!(first_contender.identity_id(), identity_1.id());
2104
2105        assert_eq!(second_contender.identity_id(), identity_2.id());
2106
2107        assert_eq!(first_contender.vote_tally(), Some(0));
2108
2109        assert_eq!(second_contender.vote_tally(), Some(0));
2110    }
2111
2112    #[allow(clippy::too_many_arguments)]
2113    pub(in crate::execution) async fn perform_vote(
2114        platform: &mut TempPlatform<MockCoreRPCLike>,
2115        platform_state: &Guard<Arc<PlatformState>>,
2116        dpns_contract: &DataContract,
2117        resource_vote_choice: ResourceVoteChoice,
2118        name: &str,
2119        signer: &SimpleSigner,
2120        pro_tx_hash: Identifier,
2121        voting_key: &IdentityPublicKey,
2122        nonce: IdentityNonce,
2123        expect_error: Option<&str>,
2124        platform_version: &PlatformVersion,
2125    ) {
2126        // Let's vote for contender 1
2127
2128        let vote = Vote::ResourceVote(ResourceVote::V0(ResourceVoteV0 {
2129            vote_poll: VotePoll::ContestedDocumentResourceVotePoll(
2130                ContestedDocumentResourceVotePoll {
2131                    contract_id: dpns_contract.id(),
2132                    document_type_name: "domain".to_string(),
2133                    index_name: "parentNameAndLabel".to_string(),
2134                    index_values: vec![
2135                        Value::Text("dash".to_string()),
2136                        Value::Text(convert_to_homograph_safe_chars(name)),
2137                    ],
2138                },
2139            ),
2140            resource_vote_choice,
2141        }));
2142
2143        let masternode_vote_transition = MasternodeVoteTransition::try_from_vote_with_signer(
2144            vote,
2145            signer,
2146            pro_tx_hash,
2147            voting_key,
2148            nonce,
2149            platform_version,
2150            None,
2151        )
2152        .await
2153        .expect("expected to make transition vote");
2154
2155        let masternode_vote_serialized_transition = masternode_vote_transition
2156            .serialize_to_bytes()
2157            .expect("expected documents batch serialized state transition");
2158
2159        // CheckTx root-invariance guard (devnet paloma h788): `check_tx` asserts under
2160        // cfg(test) that it never mutates committed grovedb state, so every valid vote
2161        // fixture going through this shared helper pins the invariant for masternode votes.
2162        if expect_error.is_none() {
2163            crate::test::helpers::state_mutation_guard::assert_check_tx_valid_at_all_levels(
2164                platform,
2165                &masternode_vote_serialized_transition,
2166                "masternode vote",
2167            );
2168        }
2169
2170        let transaction = platform.drive.grove.start_transaction();
2171
2172        let processing_result = platform
2173            .platform
2174            .process_raw_state_transitions(
2175                &[masternode_vote_serialized_transition.clone()],
2176                platform_state,
2177                &BlockInfo::default(),
2178                &transaction,
2179                platform_version,
2180                false,
2181                None,
2182            )
2183            .expect("expected to process state transition");
2184
2185        platform
2186            .drive
2187            .grove
2188            .commit_transaction(transaction)
2189            .unwrap()
2190            .expect("expected to commit transaction");
2191
2192        let execution_result = processing_result.into_execution_results().remove(0);
2193        if let Some(error_msg) = expect_error {
2194            assert_matches!(execution_result, UnpaidConsensusError(..));
2195            let UnpaidConsensusError(consensus_error) = execution_result else {
2196                panic!()
2197            };
2198            assert_eq!(consensus_error.to_string(), error_msg)
2199        } else {
2200            assert_matches!(execution_result, SuccessfulExecution { .. });
2201        }
2202    }
2203
2204    #[allow(clippy::too_many_arguments)]
2205    pub(in crate::execution) async fn perform_votes(
2206        platform: &mut TempPlatform<MockCoreRPCLike>,
2207        dpns_contract: &DataContract,
2208        resource_vote_choice: ResourceVoteChoice,
2209        name: &str,
2210        count: u64,
2211        start_seed: u64,
2212        nonce_offset: Option<IdentityNonce>,
2213        platform_version: &PlatformVersion,
2214    ) -> Vec<(Identifier, Identity, SimpleSigner, IdentityPublicKey)> {
2215        let mut masternode_infos = vec![];
2216        for i in 0..count {
2217            let (pro_tx_hash_bytes, voting_identity, signer, voting_key) =
2218                setup_masternode_voting_identity(platform, start_seed + i, platform_version);
2219
2220            let platform_state = platform.state.load();
2221
2222            perform_vote(
2223                platform,
2224                &platform_state,
2225                dpns_contract,
2226                resource_vote_choice,
2227                name,
2228                &signer,
2229                pro_tx_hash_bytes,
2230                &voting_key,
2231                1 + nonce_offset.unwrap_or_default(),
2232                None,
2233                platform_version,
2234            )
2235            .await;
2236
2237            masternode_infos.push((pro_tx_hash_bytes, voting_identity, signer, voting_key));
2238        }
2239        masternode_infos
2240    }
2241
2242    pub(in crate::execution) async fn perform_votes_multi(
2243        platform: &mut TempPlatform<MockCoreRPCLike>,
2244        dpns_contract: &DataContract,
2245        resource_vote_choices: Vec<(ResourceVoteChoice, u64)>,
2246        name: &str,
2247        start_seed: u64,
2248        nonce_offset: Option<IdentityNonce>,
2249        platform_version: &PlatformVersion,
2250    ) -> BTreeMap<ResourceVoteChoice, Vec<(Identifier, Identity, SimpleSigner, IdentityPublicKey)>>
2251    {
2252        let mut count_aggregate = start_seed;
2253        let mut masternodes_by_vote_choice = BTreeMap::new();
2254        for (resource_vote_choice, count) in resource_vote_choices.into_iter() {
2255            let masternode_infos = perform_votes(
2256                platform,
2257                dpns_contract,
2258                resource_vote_choice,
2259                name,
2260                count,
2261                count_aggregate,
2262                nonce_offset,
2263                platform_version,
2264            )
2265            .await;
2266            masternodes_by_vote_choice.insert(resource_vote_choice, masternode_infos);
2267            count_aggregate += count;
2268        }
2269        masternodes_by_vote_choice
2270    }
2271
2272    #[allow(clippy::too_many_arguments)]
2273    pub(in crate::execution) fn get_vote_states(
2274        platform: &TempPlatform<MockCoreRPCLike>,
2275        platform_state: &PlatformState,
2276        dpns_contract: &DataContract,
2277        name: &str,
2278        count: Option<u32>,
2279        allow_include_locked_and_abstaining_vote_tally: bool,
2280        start_at_identifier_info: Option<
2281            get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo,
2282        >,
2283        result_type: ResultType,
2284        platform_version: &PlatformVersion,
2285    ) -> (
2286        Vec<Contender>,
2287        Option<u32>,
2288        Option<u32>,
2289        Option<FinishedVoteInfo>,
2290    ) {
2291        // Now let's run a query for the vote totals
2292
2293        let domain = dpns_contract
2294            .document_type_for_name("domain")
2295            .expect("expected a profile document type");
2296
2297        let config = bincode::config::standard()
2298            .with_big_endian()
2299            .with_no_limit();
2300
2301        let dash_encoded = bincode::encode_to_vec(Value::Text("dash".to_string()), config)
2302            .expect("expected to encode the word dash");
2303
2304        let name_encoded =
2305            bincode::encode_to_vec(Value::Text(convert_to_homograph_safe_chars(name)), config)
2306                .expect("expected to encode the word quantum");
2307
2308        let index_name = "parentNameAndLabel".to_string();
2309
2310        let query_validation_result = platform
2311            .query_contested_resource_vote_state(
2312                GetContestedResourceVoteStateRequest {
2313                    version: Some(get_contested_resource_vote_state_request::Version::V0(
2314                        GetContestedResourceVoteStateRequestV0 {
2315                            contract_id: dpns_contract.id().to_vec(),
2316                            document_type_name: domain.name().clone(),
2317                            index_name: index_name.clone(),
2318                            index_values: vec![dash_encoded.clone(), name_encoded.clone()],
2319                            result_type: result_type as i32,
2320                            allow_include_locked_and_abstaining_vote_tally,
2321                            start_at_identifier_info,
2322                            count,
2323                            prove: false,
2324                        },
2325                    )),
2326                },
2327                platform_state,
2328                platform_version,
2329            )
2330            .expect("expected to execute query")
2331            .into_data()
2332            .expect("expected query to be valid");
2333
2334        let get_contested_resource_vote_state_response::Version::V0(
2335            GetContestedResourceVoteStateResponseV0 {
2336                metadata: _,
2337                result,
2338            },
2339        ) = query_validation_result.version.expect("expected a version");
2340
2341        let Some(
2342            get_contested_resource_vote_state_response_v0::Result::ContestedResourceContenders(
2343                get_contested_resource_vote_state_response_v0::ContestedResourceContenders {
2344                    contenders,
2345                    abstain_vote_tally,
2346                    lock_vote_tally,
2347                    finished_vote_info,
2348                },
2349            ),
2350        ) = result
2351        else {
2352            panic!("expected contenders")
2353        };
2354        (
2355            contenders
2356                .into_iter()
2357                .map(|contender| {
2358                    ContenderV0 {
2359                        identity_id: contender.identifier.try_into().expect("expected 32 bytes"),
2360                        document: contender.document.map(|document_bytes| {
2361                            Document::from_bytes(
2362                                document_bytes.as_slice(),
2363                                domain,
2364                                platform_version,
2365                            )
2366                            .expect("expected to deserialize document")
2367                        }),
2368                        vote_tally: contender.vote_count,
2369                    }
2370                    .into()
2371                })
2372                .collect(),
2373            abstain_vote_tally,
2374            lock_vote_tally,
2375            finished_vote_info,
2376        )
2377    }
2378
2379    #[allow(clippy::too_many_arguments)]
2380    pub(in crate::execution) fn get_proved_vote_states(
2381        platform: &TempPlatform<MockCoreRPCLike>,
2382        platform_state: &PlatformState,
2383        dpns_contract: &DataContract,
2384        name: &str,
2385        count: Option<u32>,
2386        allow_include_locked_and_abstaining_vote_tally: bool,
2387        start_at_identifier_info: Option<
2388            get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo,
2389        >,
2390        result_type: ResultType,
2391        platform_version: &PlatformVersion,
2392    ) -> (
2393        Vec<Contender>,
2394        Option<u32>,
2395        Option<u32>,
2396        Option<(ContestedDocumentVotePollWinnerInfo, BlockInfo)>,
2397    ) {
2398        // Now let's run a query for the vote totals
2399
2400        let domain = dpns_contract
2401            .document_type_for_name("domain")
2402            .expect("expected a profile document type");
2403
2404        let config = bincode::config::standard()
2405            .with_big_endian()
2406            .with_no_limit();
2407
2408        let dash_encoded = bincode::encode_to_vec(Value::Text("dash".to_string()), config)
2409            .expect("expected to encode the word dash");
2410
2411        let name_encoded =
2412            bincode::encode_to_vec(Value::Text(convert_to_homograph_safe_chars(name)), config)
2413                .expect("expected to encode the word quantum");
2414
2415        let index_name = "parentNameAndLabel".to_string();
2416
2417        let query_validation_result = platform
2418            .query_contested_resource_vote_state(
2419                GetContestedResourceVoteStateRequest {
2420                    version: Some(get_contested_resource_vote_state_request::Version::V0(
2421                        GetContestedResourceVoteStateRequestV0 {
2422                            contract_id: dpns_contract.id().to_vec(),
2423                            document_type_name: domain.name().clone(),
2424                            index_name: index_name.clone(),
2425                            index_values: vec![dash_encoded.clone(), name_encoded.clone()],
2426                            result_type: result_type as i32,
2427                            allow_include_locked_and_abstaining_vote_tally,
2428                            start_at_identifier_info,
2429                            count,
2430                            prove: true,
2431                        },
2432                    )),
2433                },
2434                platform_state,
2435                platform_version,
2436            )
2437            .expect("expected to execute query")
2438            .into_data()
2439            .expect("expected query to be valid");
2440
2441        let get_contested_resource_vote_state_response::Version::V0(
2442            GetContestedResourceVoteStateResponseV0 {
2443                metadata: _,
2444                result,
2445            },
2446        ) = query_validation_result.version.expect("expected a version");
2447
2448        let Some(get_contested_resource_vote_state_response_v0::Result::Proof(proof)) = result
2449        else {
2450            panic!("expected contenders")
2451        };
2452
2453        let resolved_contested_document_vote_poll_drive_query =
2454            ResolvedContestedDocumentVotePollDriveQuery {
2455                vote_poll: ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed {
2456                    contract: DataContractResolvedInfo::BorrowedDataContract(dpns_contract),
2457                    document_type_name: domain.name().clone(),
2458                    index_name: index_name.clone(),
2459                    index_values: vec![
2460                        Value::Text("dash".to_string()),
2461                        Value::Text(convert_to_homograph_safe_chars(name)),
2462                    ],
2463                },
2464                result_type: ContestedDocumentVotePollDriveQueryResultType::try_from(
2465                    result_type as i32,
2466                )
2467                .expect("expected valid result type"),
2468                offset: None,
2469                limit: count.map(|a| a as u16),
2470                start_at: None,
2471                allow_include_locked_and_abstaining_vote_tally,
2472            };
2473
2474        let (_, result) = resolved_contested_document_vote_poll_drive_query
2475            .verify_vote_poll_vote_state_proof(proof.grovedb_proof.as_ref(), platform_version)
2476            .expect("expected to verify proof");
2477
2478        let abstaining_vote_tally = result.abstaining_vote_tally;
2479        let lock_vote_tally = result.locked_vote_tally;
2480
2481        let contenders = result.contenders;
2482        let finished_vote_info = result.winner;
2483        (
2484            contenders
2485                .into_iter()
2486                .map(|contender| {
2487                    ContenderV0 {
2488                        identity_id: contender.identity_id(),
2489                        document: contender
2490                            .serialized_document()
2491                            .as_ref()
2492                            .map(|document_bytes| {
2493                                Document::from_bytes(
2494                                    document_bytes.as_slice(),
2495                                    domain,
2496                                    platform_version,
2497                                )
2498                                .expect("expected to deserialize document")
2499                            }),
2500                        vote_tally: contender.vote_tally(),
2501                    }
2502                    .into()
2503                })
2504                .collect(),
2505            abstaining_vote_tally,
2506            lock_vote_tally,
2507            finished_vote_info,
2508        )
2509    }
2510
2511    pub(in crate::execution) fn create_token_contract_with_owner_identity(
2512        platform: &mut TempPlatform<MockCoreRPCLike>,
2513        identity_id: Identifier,
2514        token_configuration_modification: Option<impl FnOnce(&mut TokenConfiguration)>,
2515        contract_start_time: Option<TimestampMillis>,
2516        add_groups: Option<BTreeMap<GroupContractPosition, Group>>,
2517        contract_start_block: Option<BlockHeight>,
2518        platform_version: &PlatformVersion,
2519    ) -> (DataContract, Identifier) {
2520        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2521
2522        let basic_token_contract = setup_contract(
2523            &platform.drive,
2524            "tests/supporting_files/contract/basic-token/basic-token.json",
2525            Some(data_contract_id.to_buffer()),
2526            Some(identity_id.to_buffer()),
2527            Some(|data_contract: &mut DataContract| {
2528                data_contract.set_created_at_epoch(Some(0));
2529                data_contract.set_created_at(Some(contract_start_time.unwrap_or_default()));
2530                data_contract
2531                    .set_created_at_block_height(Some(contract_start_block.unwrap_or_default()));
2532                if let Some(token_configuration_modification) = token_configuration_modification {
2533                    let token_configuration = data_contract
2534                        .token_configuration_mut(0)
2535                        .expect("expected token configuration");
2536                    token_configuration_modification(token_configuration);
2537                }
2538                if let Some(add_groups) = add_groups {
2539                    data_contract.set_groups(add_groups);
2540                }
2541            }),
2542            None,
2543            Some(platform_version),
2544        );
2545
2546        let token_id = calculate_token_id(data_contract_id.as_bytes(), 0);
2547
2548        (basic_token_contract, token_id.into())
2549    }
2550
2551    pub(in crate::execution) fn create_card_game_internal_token_contract_with_owner_identity_burn_tokens(
2552        platform: &mut TempPlatform<MockCoreRPCLike>,
2553        identity_id: Identifier,
2554        platform_version: &PlatformVersion,
2555    ) -> (DataContract, Identifier, Identifier) {
2556        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2557
2558        let basic_token_contract = setup_contract(
2559            &platform.drive,
2560            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency-burn-tokens.json",
2561            Some(data_contract_id.to_buffer()),
2562            Some(identity_id.to_buffer()),
2563            Some(|data_contract: &mut DataContract| {
2564                data_contract.set_created_at_epoch(Some(0));
2565                data_contract.set_created_at(Some(0));
2566                data_contract.set_created_at_block_height(Some(0));
2567            }),
2568            None,
2569            Some(platform_version),
2570        );
2571
2572        let token_id = calculate_token_id(data_contract_id.as_bytes(), 0);
2573        let token_id_2 = calculate_token_id(data_contract_id.as_bytes(), 1);
2574
2575        (basic_token_contract, token_id.into(), token_id_2.into())
2576    }
2577
2578    pub(in crate::execution) fn create_card_game_internal_token_contract_with_owner_identity_transfer_tokens(
2579        platform: &mut TempPlatform<MockCoreRPCLike>,
2580        identity_id: Identifier,
2581        platform_version: &PlatformVersion,
2582    ) -> (DataContract, Identifier, Identifier) {
2583        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2584
2585        let basic_token_contract = setup_contract(
2586            &platform.drive,
2587            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-in-game-currency.json",
2588            Some(data_contract_id.to_buffer()),
2589            Some(identity_id.to_buffer()),
2590            Some(|data_contract: &mut DataContract| {
2591                data_contract.set_created_at_epoch(Some(0));
2592                data_contract.set_created_at(Some(0));
2593                data_contract.set_created_at_block_height(Some(0));
2594            }),
2595            None,
2596            Some(platform_version),
2597        );
2598
2599        let token_id = calculate_token_id(data_contract_id.as_bytes(), 0);
2600        let token_id_2 = calculate_token_id(data_contract_id.as_bytes(), 1);
2601
2602        (basic_token_contract, token_id.into(), token_id_2.into())
2603    }
2604
2605    pub(in crate::execution) fn create_card_game_external_token_contract_with_owner_identity(
2606        platform: &mut TempPlatform<MockCoreRPCLike>,
2607        token_contract_id: Identifier,
2608        token_contract_position: TokenContractPosition,
2609        token_cost_amount: TokenAmount,
2610        gas_fees_paid_by: GasFeesPaidBy,
2611        identity_id: Identifier,
2612        platform_version: &PlatformVersion,
2613    ) -> DataContract {
2614        let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1);
2615
2616        let basic_token_contract = setup_contract(
2617            &platform.drive,
2618            "tests/supporting_files/contract/crypto-card-game/crypto-card-game-use-external-currency.json",
2619            Some(data_contract_id.to_buffer()),
2620            Some(identity_id.to_buffer()),
2621            Some(|data_contract: &mut DataContract| {
2622                data_contract.set_created_at_epoch(Some(0));
2623                data_contract.set_created_at(Some(0));
2624                data_contract.set_created_at_block_height(Some(0));
2625                let document_type = data_contract.document_types_mut().get_mut("card").expect("expected a document type with name card");
2626                document_type.set_document_creation_token_cost(Some(DocumentActionTokenCost {
2627                    contract_id: Some(token_contract_id),
2628                    token_contract_position,
2629                    token_amount: token_cost_amount,
2630                    effect: DocumentActionTokenEffect::TransferTokenToContractOwner,
2631                    gas_fees_paid_by,
2632                }));
2633                let gas_fees_paid_by_int: u8 = gas_fees_paid_by.into();
2634                let schema = document_type.schema_mut();
2635                let token_cost = schema.get_mut("tokenCost").expect("expected to get token cost").expect("expected token cost to be set");
2636                let creation_token_cost = token_cost.get_mut("create").expect("expected to get creation token cost").expect("expected creation token cost to be set");
2637                creation_token_cost.set_value("contractId", token_contract_id.into()).expect("expected to set token contract id");
2638                creation_token_cost.set_value("tokenPosition", token_contract_position.into()).expect("expected to set token position");
2639                creation_token_cost.set_value("amount", token_cost_amount.into()).expect("expected to set token amount");
2640                creation_token_cost.set_value("gasFeesPaidBy", gas_fees_paid_by_int.into()).expect("expected to set token amount");
2641            }),
2642            None,
2643            Some(platform_version),
2644        );
2645
2646        basic_token_contract
2647    }
2648
2649    pub(in crate::execution) fn process_test_state_transition<S: PlatformSerializable>(
2650        platform: &mut TempPlatform<MockCoreRPCLike>,
2651        state_transition: S,
2652        platform_state: &PlatformState,
2653        platform_version: &PlatformVersion,
2654    ) -> StateTransitionsProcessingResult {
2655        let Ok(serialized_state_transition) = state_transition.serialize_to_bytes() else {
2656            panic!("expected documents batch serialized state transition")
2657        };
2658
2659        let transaction = platform.drive.grove.start_transaction();
2660
2661        let processing_result = platform
2662            .platform
2663            .process_raw_state_transitions(
2664                &[serialized_state_transition],
2665                platform_state,
2666                &BlockInfo::default(),
2667                &transaction,
2668                platform_version,
2669                false,
2670                None,
2671            )
2672            .expect("expected to process state transition");
2673
2674        platform
2675            .drive
2676            .grove
2677            .commit_transaction(transaction)
2678            .unwrap()
2679            .expect("expected to commit transaction");
2680
2681        processing_result
2682    }
2683
2684    mod keyword_search_contract {
2685        use dpp::consensus::basic::BasicError;
2686        use dpp::consensus::ConsensusError;
2687        use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult::PaidConsensusError;
2688        use super::*;
2689        //
2690        // ──────────────────────────────────────────────────────────────────────────
2691        //  Keyword‑Search contract – creation is forbidden
2692        // ──────────────────────────────────────────────────────────────────────────
2693        //
2694
2695        /// Return `(document, entropy)` so we can reuse the exact entropy when
2696        /// we build the transition.  **No rng.clone()** (that caused the ID mismatch).
2697        fn build_random_doc_of_type(
2698            rng: &mut StdRng,
2699            doc_type_name: &str,
2700            identity_id: Identifier,
2701            contract: &DataContract,
2702            platform_version: &PlatformVersion,
2703        ) -> (Document, Bytes32) {
2704            let doc_type = contract
2705                .document_type_for_name(doc_type_name)
2706                .expect("doc type exists");
2707
2708            let entropy = Bytes32::random_with_rng(rng);
2709
2710            let doc = doc_type
2711                .random_document_with_identifier_and_entropy(
2712                    rng,
2713                    identity_id,
2714                    entropy,
2715                    DocumentFieldFillType::FillIfNotRequired,
2716                    DocumentFieldFillSize::AnyDocumentFillSize,
2717                    platform_version,
2718                )
2719                .expect("random doc");
2720
2721            (doc, entropy)
2722        }
2723
2724        #[tokio::test]
2725        async fn should_err_when_creating_contract_keywords_document() {
2726            let platform_version = PlatformVersion::latest();
2727
2728            let mut platform = TestPlatformBuilder::new()
2729                .build_with_mock_rpc()
2730                .set_genesis_state();
2731
2732            let (identity, signer, key) = setup_identity(&mut platform, 42, dash_to_credits!(1.0));
2733
2734            let contract = load_system_data_contract(
2735                SystemDataContract::KeywordSearch,
2736                PlatformVersion::latest(),
2737            )
2738            .expect("expected to load search contract");
2739
2740            let mut rng = StdRng::seed_from_u64(1);
2741            let (doc, entropy) = build_random_doc_of_type(
2742                &mut rng,
2743                "contractKeywords",
2744                identity.id(),
2745                &contract,
2746                platform_version,
2747            );
2748
2749            let transition = BatchTransition::new_document_creation_transition_from_document(
2750                doc,
2751                contract.document_type_for_name("contractKeywords").unwrap(),
2752                entropy.0, // same entropy → no ID mismatch
2753                &key,
2754                1,
2755                0,
2756                None,
2757                &signer,
2758                platform_version,
2759                None,
2760            )
2761            .await
2762            .expect("batch transition");
2763
2764            let serialized = transition.serialize_to_bytes().unwrap();
2765
2766            let platform_state = platform.state.load();
2767            let processing_result = platform
2768                .platform
2769                .process_raw_state_transitions(
2770                    &[serialized],
2771                    &platform_state,
2772                    &BlockInfo::default(),
2773                    &platform.drive.grove.start_transaction(),
2774                    platform_version,
2775                    false,
2776                    None,
2777                )
2778                .expect("processing failed");
2779
2780            let execution_result = processing_result.into_execution_results().remove(0);
2781            assert_matches!(
2782                execution_result,
2783                StateTransitionExecutionResult::PaidConsensusError{ error, .. }
2784                    if error.to_string().contains("not allowed because of the document type's creation restriction mode")
2785            );
2786        }
2787
2788        #[tokio::test]
2789        async fn should_err_when_creating_short_description_document() {
2790            let platform_version = PlatformVersion::latest();
2791
2792            let mut platform = TestPlatformBuilder::new()
2793                .build_with_mock_rpc()
2794                .set_genesis_state();
2795
2796            let (identity, signer, key) = setup_identity(&mut platform, 43, dash_to_credits!(1.0));
2797
2798            let contract = load_system_data_contract(
2799                SystemDataContract::KeywordSearch,
2800                PlatformVersion::latest(),
2801            )
2802            .expect("expected to load search contract");
2803
2804            let mut rng = StdRng::seed_from_u64(2);
2805            let (doc, entropy) = build_random_doc_of_type(
2806                &mut rng,
2807                "shortDescription",
2808                identity.id(),
2809                &contract,
2810                platform_version,
2811            );
2812
2813            let transition = BatchTransition::new_document_creation_transition_from_document(
2814                doc,
2815                contract.document_type_for_name("shortDescription").unwrap(),
2816                entropy.0,
2817                &key,
2818                1,
2819                0,
2820                None,
2821                &signer,
2822                platform_version,
2823                None,
2824            )
2825            .await
2826            .expect("batch transition");
2827
2828            let serialized = transition.serialize_to_bytes().unwrap();
2829
2830            let platform_state = platform.state.load();
2831            let processing_result = platform
2832                .platform
2833                .process_raw_state_transitions(
2834                    &[serialized],
2835                    &platform_state,
2836                    &BlockInfo::default(),
2837                    &platform.drive.grove.start_transaction(),
2838                    platform_version,
2839                    false,
2840                    None,
2841                )
2842                .expect("processing failed");
2843
2844            let execution_result = processing_result.into_execution_results().remove(0);
2845            assert_matches!(
2846                execution_result,
2847                StateTransitionExecutionResult::PaidConsensusError{ error, .. }
2848                    if error.to_string().contains("not allowed because of the document type's creation restriction mode")
2849            );
2850        }
2851
2852        #[tokio::test]
2853        async fn should_err_when_creating_full_description_document() {
2854            let platform_version = PlatformVersion::latest();
2855
2856            let mut platform = TestPlatformBuilder::new()
2857                .build_with_mock_rpc()
2858                .set_genesis_state();
2859
2860            let (identity, signer, key) = setup_identity(&mut platform, 44, dash_to_credits!(1.0));
2861
2862            let contract = load_system_data_contract(
2863                SystemDataContract::KeywordSearch,
2864                PlatformVersion::latest(),
2865            )
2866            .expect("expected to load search contract");
2867
2868            let mut rng = StdRng::seed_from_u64(3);
2869            let (doc, entropy) = build_random_doc_of_type(
2870                &mut rng,
2871                "fullDescription",
2872                identity.id(),
2873                &contract,
2874                platform_version,
2875            );
2876
2877            let transition = BatchTransition::new_document_creation_transition_from_document(
2878                doc,
2879                contract.document_type_for_name("fullDescription").unwrap(),
2880                entropy.0,
2881                &key,
2882                1,
2883                0,
2884                None,
2885                &signer,
2886                platform_version,
2887                None,
2888            )
2889            .await
2890            .expect("batch transition");
2891
2892            let serialized = transition.serialize_to_bytes().unwrap();
2893
2894            let platform_state = platform.state.load();
2895            let processing_result = platform
2896                .platform
2897                .process_raw_state_transitions(
2898                    &[serialized],
2899                    &platform_state,
2900                    &BlockInfo::default(),
2901                    &platform.drive.grove.start_transaction(),
2902                    platform_version,
2903                    false,
2904                    None,
2905                )
2906                .expect("processing failed");
2907
2908            let execution_result = processing_result.into_execution_results().remove(0);
2909            assert_matches!(
2910                execution_result,
2911                StateTransitionExecutionResult::PaidConsensusError{ error, .. }
2912                    if error.to_string().contains("not allowed because of the document type's creation restriction mode")
2913            );
2914        }
2915
2916        //
2917        // ──────────────────────────────────────────────────────────────────────────
2918        //  Keyword‑Search contract – owner can update / delete
2919        // ──────────────────────────────────────────────────────────────────────────
2920        //
2921
2922        async fn create_contract_with_keywords_and_description(
2923            platform: &mut TempPlatform<MockCoreRPCLike>,
2924        ) -> (Identity, SimpleSigner, IdentityPublicKey) {
2925            let platform_version = PlatformVersion::latest();
2926
2927            // Owner identity
2928            let (owner_identity, signer, key) =
2929                setup_identity(platform, 777, dash_to_credits!(1.0));
2930
2931            // Load the keyword‑test fixture
2932            let mut contract = json_document_to_contract_with_ids(
2933                "tests/supporting_files/contract/keyword_test/keyword_base_contract.json",
2934                Some(owner_identity.id()),
2935                None,
2936                false,
2937                platform_version,
2938            )
2939            .expect("load contract");
2940
2941            // Inject description + keywords
2942            contract.set_description(Some("A short description".to_string()));
2943            contract.set_keywords(vec!["graph".into(), "indexing".into()]);
2944
2945            // Create transition inside GroveDB tx
2946            let create_transition = DataContractCreateTransition::new_from_data_contract(
2947                contract,
2948                1,
2949                &owner_identity.clone().into_partial_identity_info(),
2950                key.id(),
2951                &signer,
2952                platform_version,
2953                None,
2954            )
2955            .await
2956            .expect("build transition");
2957
2958            let serialized = create_transition.serialize_to_bytes().unwrap();
2959            let platform_state = platform.state.load();
2960            let tx = platform.drive.grove.start_transaction();
2961
2962            let processing_result = platform
2963                .platform
2964                .process_raw_state_transitions(
2965                    &[serialized],
2966                    &platform_state,
2967                    &BlockInfo::default(),
2968                    &tx,
2969                    platform_version,
2970                    false,
2971                    None,
2972                )
2973                .expect("process");
2974
2975            assert_matches!(
2976                processing_result.execution_results().as_slice(),
2977                [StateTransitionExecutionResult::SuccessfulExecution { .. }]
2978            );
2979
2980            platform
2981                .drive
2982                .grove
2983                .commit_transaction(tx)
2984                .unwrap()
2985                .expect("commit");
2986
2987            (owner_identity, signer, key)
2988        }
2989
2990        #[tokio::test]
2991        async fn owner_can_update_short_description_document() {
2992            let platform_version = PlatformVersion::latest();
2993            let mut platform = TestPlatformBuilder::new()
2994                .build_with_mock_rpc()
2995                .set_genesis_state();
2996
2997            let (_owner, signer, key) =
2998                create_contract_with_keywords_and_description(&mut platform).await;
2999
3000            // 🔎 fetch shortDescription doc through query
3001            let search_contract =
3002                load_system_data_contract(SystemDataContract::KeywordSearch, platform_version)
3003                    .expect("load search contract");
3004
3005            let doc_type = search_contract
3006                .document_type_for_name("shortDescription")
3007                .unwrap();
3008
3009            let query = DriveDocumentQuery::all_items_query(&search_contract, doc_type, None);
3010            let existing_docs = platform
3011                .drive
3012                .query_documents(
3013                    query,
3014                    None,
3015                    false,
3016                    None,
3017                    Some(platform_version.protocol_version),
3018                )
3019                .expect("query failed");
3020
3021            let mut doc = existing_docs.documents().first().expect("doc").clone();
3022            doc.set_revision(Some(doc.revision().unwrap_or_default() + 1));
3023            doc.set("description", "updated description".into());
3024
3025            let transition = BatchTransition::new_document_replacement_transition_from_document(
3026                doc,
3027                doc_type,
3028                &key,
3029                2,
3030                0,
3031                None,
3032                &signer,
3033                platform_version,
3034                None,
3035            )
3036            .await
3037            .expect("replace");
3038
3039            let serialized = transition.serialize_to_bytes().unwrap();
3040            let platform_state = platform.state.load();
3041
3042            let processing_result = platform
3043                .platform
3044                .process_raw_state_transitions(
3045                    &[serialized],
3046                    &platform_state,
3047                    &BlockInfo::default(),
3048                    &platform.drive.grove.start_transaction(),
3049                    platform_version,
3050                    false,
3051                    None,
3052                )
3053                .expect("process");
3054
3055            assert_matches!(
3056                processing_result.into_execution_results().remove(0),
3057                SuccessfulExecution { .. }
3058            );
3059        }
3060
3061        #[tokio::test]
3062        async fn owner_can_not_delete_keyword_document() {
3063            let platform_version = PlatformVersion::latest();
3064            let mut platform = TestPlatformBuilder::new()
3065                .build_with_mock_rpc()
3066                .set_genesis_state();
3067
3068            let (_owner, signer, key) =
3069                create_contract_with_keywords_and_description(&mut platform).await;
3070
3071            let search_contract =
3072                load_system_data_contract(SystemDataContract::KeywordSearch, platform_version)
3073                    .expect("load search contract");
3074            let doc_type = search_contract
3075                .document_type_for_name("contractKeywords")
3076                .unwrap();
3077
3078            let query = DriveDocumentQuery::all_items_query(&search_contract, doc_type, None);
3079            let existing_docs = platform
3080                .drive
3081                .query_documents(
3082                    query,
3083                    None,
3084                    false,
3085                    None,
3086                    Some(platform_version.protocol_version),
3087                )
3088                .expect("query failed");
3089
3090            let doc = existing_docs.documents().first().unwrap().clone();
3091
3092            let transition = BatchTransition::new_document_deletion_transition_from_document(
3093                doc,
3094                doc_type,
3095                &key,
3096                2,
3097                0,
3098                None,
3099                &signer,
3100                platform_version,
3101                None,
3102            )
3103            .await
3104            .expect("delete");
3105
3106            let serialized = transition.serialize_to_bytes().unwrap();
3107            let platform_state = platform.state.load();
3108
3109            let processing_result = platform
3110                .platform
3111                .process_raw_state_transitions(
3112                    &[serialized],
3113                    &platform_state,
3114                    &BlockInfo::default(),
3115                    &platform.drive.grove.start_transaction(),
3116                    platform_version,
3117                    false,
3118                    None,
3119                )
3120                .expect("process");
3121            assert_matches!(
3122                processing_result.execution_results().as_slice(),
3123                [PaidConsensusError {
3124                    error: ConsensusError::BasicError(
3125                        BasicError::InvalidDocumentTransitionActionError { .. }
3126                    ),
3127                    ..
3128                }]
3129            );
3130        }
3131    }
3132
3133    /// End-to-end regression test for the byteArray on-disk encoding-flip
3134    /// chain-halt vulnerability (Dash Platform v4.0.0-rc.1).
3135    ///
3136    /// This drives the full per-block DAO vote-poll resolver path so that
3137    /// `Platform::check_for_ended_vote_polls` returns `Err` today (pre-fix). On
3138    /// chain that `Err` propagates through `run_dao_platform_events` ->
3139    /// `run_block_proposal` (a per-block event handler with a bare `?`), so it is
3140    /// NOT caught into a per-state-transition result -- it halts the chain on
3141    /// every validator at the block where the contested vote poll ends.
3142    mod byte_array_encoding_flip_chain_halt {
3143        use super::*;
3144        use crate::test::helpers::fast_forward_to_block::fast_forward_to_block;
3145        use dpp::data_contract::document_type::schema::validate_schema_compatibility;
3146
3147        const CUSTOM_CONTESTED_CONTRACT: &str =
3148            "tests/supporting_files/contract/dpns/dpns-contract-contested-unique-index.json";
3149        // Identical to the contract above except `domain.preorderSalt` byteArray
3150        // has its `maxItems` widened from 32 to 64 -- the malicious update.
3151        const CUSTOM_CONTESTED_CONTRACT_WIDENED: &str =
3152            "tests/supporting_files/contract/dpns/dpns-contract-contested-unique-index-byte-array-widened.json";
3153
3154        /// Builds one preorder + one domain (contender) document for `identity`,
3155        /// with `preorderSalt` forced to `[0xFF; 32]` so that, after the
3156        /// byteArray encoding flips to varint-length-prefixed, the first stored
3157        /// byte (0xFF, a varint continuation byte) decodes to an enormous length
3158        /// and overruns the buffer.
3159        #[allow(clippy::too_many_arguments)]
3160        async fn build_preorder_and_domain(
3161            contract: &DataContract,
3162            identity: &Identity,
3163            signer: &SimpleSigner,
3164            key: &IdentityPublicKey,
3165            name: &str,
3166            // A per-contender distinguishing byte placed in the *tail* of the
3167            // salt so the two contenders' `saltedDomainHash` differ (avoiding a
3168            // unique-index collision on the preorder), while byte[0] stays 0xFF.
3169            salt_discriminator: u8,
3170            rng: &mut StdRng,
3171            platform_version: &PlatformVersion,
3172        ) -> (Vec<u8>, Vec<u8>) {
3173            let preorder = contract
3174                .document_type_for_name("preorder")
3175                .expect("expected preorder document type");
3176            let domain = contract
3177                .document_type_for_name("domain")
3178                .expect("expected domain document type");
3179
3180            let entropy = Bytes32::random_with_rng(rng);
3181
3182            let mut preorder_document = preorder
3183                .random_document_with_identifier_and_entropy(
3184                    rng,
3185                    identity.id(),
3186                    entropy,
3187                    DocumentFieldFillType::FillIfNotRequired,
3188                    DocumentFieldFillSize::AnyDocumentFillSize,
3189                    platform_version,
3190                )
3191                .expect("expected a random preorder document");
3192
3193            let mut domain_document = domain
3194                .random_document_with_identifier_and_entropy(
3195                    rng,
3196                    identity.id(),
3197                    entropy,
3198                    DocumentFieldFillType::FillIfNotRequired,
3199                    DocumentFieldFillSize::AnyDocumentFillSize,
3200                    platform_version,
3201                )
3202                .expect("expected a random domain document");
3203
3204            domain_document.set("parentDomainName", "dash".into());
3205            domain_document.set("normalizedParentDomainName", "dash".into());
3206            domain_document.set("label", name.into());
3207            domain_document.set(
3208                "normalizedLabel",
3209                convert_to_homograph_safe_chars(name).into(),
3210            );
3211            domain_document.set("records.identity", domain_document.owner_id().into());
3212            domain_document.set("subdomainRules.allowSubdomains", false.into());
3213
3214            // The crux of the attack: a 32-byte salt whose FIRST byte is 0xFF
3215            // (a varint continuation byte). The last byte distinguishes the two
3216            // contenders so their preorder salted hashes do not collide.
3217            let mut salt: [u8; 32] = [0xFF; 32];
3218            salt[31] = salt_discriminator;
3219
3220            let mut salted_domain_buffer: Vec<u8> = vec![];
3221            salted_domain_buffer.extend(salt);
3222            salted_domain_buffer
3223                .extend((convert_to_homograph_safe_chars(name) + ".dash").as_bytes());
3224            let salted_domain_hash = hash_double(salted_domain_buffer);
3225
3226            preorder_document.set("saltedDomainHash", salted_domain_hash.into());
3227            domain_document.set("preorderSalt", salt.into());
3228
3229            let preorder_transition =
3230                BatchTransition::new_document_creation_transition_from_document(
3231                    preorder_document,
3232                    preorder,
3233                    entropy.0,
3234                    key,
3235                    2,
3236                    0,
3237                    None,
3238                    signer,
3239                    platform_version,
3240                    None,
3241                )
3242                .await
3243                .expect("expect to create preorder batch transition");
3244
3245            let domain_transition =
3246                BatchTransition::new_document_creation_transition_from_document(
3247                    domain_document,
3248                    domain,
3249                    entropy.0,
3250                    key,
3251                    3,
3252                    0,
3253                    None,
3254                    signer,
3255                    platform_version,
3256                    None,
3257                )
3258                .await
3259                .expect("expect to create domain batch transition");
3260
3261            (
3262                preorder_transition
3263                    .serialize_to_bytes()
3264                    .expect("serialize preorder transition"),
3265                domain_transition
3266                    .serialize_to_bytes()
3267                    .expect("serialize domain transition"),
3268            )
3269        }
3270
3271        /// Runs the full contest setup and returns the result of the per-block
3272        /// vote-poll resolver. When `widen` is true the byteArray `maxItems` is
3273        /// flipped (the attack); when false the original contract is left in
3274        /// place (the control).
3275        async fn run_contest_then_resolve(widen: bool) -> Result<(), crate::error::Error> {
3276            let mut platform = TestPlatformBuilder::new()
3277                .with_latest_protocol_version()
3278                .build_with_mock_rpc()
3279                .set_initial_state_structure();
3280
3281            let platform_version = PlatformVersion::latest();
3282
3283            let mut rng = StdRng::seed_from_u64(0xB17E_A77A);
3284
3285            // Two contenders, each prefunded so they can pay the contested-index
3286            // voting balance.
3287            let identity_1_info = setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5));
3288            let identity_2_info = setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5));
3289
3290            let contract_owner = setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5));
3291
3292            // (1) Create the CUSTOM contested contract owned by `contract_owner`.
3293            // It has a contested unique index on `normalizedLabel` and a fixed
3294            // byteArray `preorderSalt` {minItems:32, maxItems:32}.
3295            let contract = setup_contract(
3296                &platform.drive,
3297                CUSTOM_CONTESTED_CONTRACT,
3298                None,
3299                Some(contract_owner.0.id().to_buffer()),
3300                None::<fn(&mut DataContract)>,
3301                None,
3302                Some(platform_version),
3303            );
3304
3305            let name = "quantum";
3306
3307            let platform_state = platform.state.load();
3308
3309            // (2) Create TWO contested documents (contenders) with the same
3310            // contested-index values and a 0xFF-prefixed 32-byte byteArray.
3311            let (preorder_tx_1, domain_tx_1) = build_preorder_and_domain(
3312                &contract,
3313                &identity_1_info.0,
3314                &identity_1_info.1,
3315                &identity_1_info.2,
3316                name,
3317                0x01,
3318                &mut rng,
3319                platform_version,
3320            )
3321            .await;
3322
3323            let (preorder_tx_2, domain_tx_2) = build_preorder_and_domain(
3324                &contract,
3325                &identity_2_info.0,
3326                &identity_2_info.1,
3327                &identity_2_info.2,
3328                name,
3329                0x02,
3330                &mut rng,
3331                platform_version,
3332            )
3333            .await;
3334
3335            // Submit the preorders.
3336            let transaction = platform.drive.grove.start_transaction();
3337            let processing_result = platform
3338                .platform
3339                .process_raw_state_transitions(
3340                    &[preorder_tx_1, preorder_tx_2],
3341                    &platform_state,
3342                    &BlockInfo::default_with_time(
3343                        platform_state
3344                            .last_committed_block_time_ms()
3345                            .unwrap_or_default()
3346                            + 3000,
3347                    ),
3348                    &transaction,
3349                    platform_version,
3350                    false,
3351                    None,
3352                )
3353                .expect("expected to process preorder state transitions");
3354            platform
3355                .drive
3356                .grove
3357                .commit_transaction(transaction)
3358                .unwrap()
3359                .expect("expected to commit transaction");
3360            assert_eq!(
3361                processing_result.valid_count(),
3362                2,
3363                "both preorders should be accepted"
3364            );
3365
3366            // Submit the domains -> this opens the contested vote poll.
3367            let transaction = platform.drive.grove.start_transaction();
3368            let processing_result = platform
3369                .platform
3370                .process_raw_state_transitions(
3371                    &[domain_tx_1, domain_tx_2],
3372                    &platform_state,
3373                    &BlockInfo::default_with_time(
3374                        platform_state
3375                            .last_committed_block_time_ms()
3376                            .unwrap_or_default()
3377                            + 3000,
3378                    ),
3379                    &transaction,
3380                    platform_version,
3381                    false,
3382                    None,
3383                )
3384                .expect("expected to process domain state transitions");
3385            platform
3386                .drive
3387                .grove
3388                .commit_transaction(transaction)
3389                .unwrap()
3390                .expect("expected to commit transaction");
3391            assert_eq!(
3392                processing_result.valid_count(),
3393                2,
3394                "both contenders should be accepted, opening the contest"
3395            );
3396
3397            // (3) Simulate the malicious DataContractUpdate that widens the
3398            // byteArray `maxItems` from 32 to 64 (only in the attack scenario).
3399            if widen {
3400                // The JSON-schema compatibility layer still treats widening
3401                // `maxItems` as a compatible change -- which is exactly why the
3402                // dedicated byte-array-encoding check added to `validate_update`
3403                // (the fix) is required to reject it. We pin that compatibility
3404                // verdict here to document the gap the fix closes.
3405                let original_domain_schema = serde_json::json!({
3406                    "type": "object",
3407                    "properties": {
3408                        "preorderSalt": {
3409                            "type": "array",
3410                            "byteArray": true,
3411                            "minItems": 32,
3412                            "maxItems": 32,
3413                            "position": 4
3414                        }
3415                    },
3416                    "additionalProperties": false
3417                });
3418                let widened_domain_schema = serde_json::json!({
3419                    "type": "object",
3420                    "properties": {
3421                        "preorderSalt": {
3422                            "type": "array",
3423                            "byteArray": true,
3424                            "minItems": 32,
3425                            "maxItems": 64,
3426                            "position": 4
3427                        }
3428                    },
3429                    "additionalProperties": false
3430                });
3431                let compatibility = validate_schema_compatibility(
3432                    &original_domain_schema,
3433                    &widened_domain_schema,
3434                    platform_version,
3435                )
3436                .expect("schema compatibility validation must not error");
3437                assert!(
3438                    compatibility.is_valid(),
3439                    "the maxItems 32 -> 64 widening must be accepted by update \
3440                     validation for this attack to be reachable on chain; \
3441                     reported incompatibilities: {:?}",
3442                    compatibility.errors
3443                );
3444
3445                // Apply the widened contract directly to grovedb to simulate the
3446                // corrupt on-disk state that WOULD result if such an update were
3447                // committed. On a real chain `validate_update` now rejects this
3448                // update at the source (see the rs-dpp validate_byte_array_encoding
3449                // tests), so the resolver is never reached with corrupt bytes;
3450                // this reproduces the consequence the fix prevents.
3451                // The derived `properties` for `preorderSalt` flip to the
3452                // variable-length (varint-prefixed) decode path.
3453                let widened_contract = setup_contract(
3454                    &platform.drive,
3455                    CUSTOM_CONTESTED_CONTRACT_WIDENED,
3456                    None,
3457                    Some(contract_owner.0.id().to_buffer()),
3458                    None::<fn(&mut DataContract)>,
3459                    None,
3460                    Some(platform_version),
3461                );
3462                assert_eq!(
3463                    widened_contract.id(),
3464                    contract.id(),
3465                    "the widened contract must replace the original at the same id"
3466                );
3467            }
3468
3469            // (4) Advance past the vote-poll end date and invoke the resolver.
3470            let time_after_distribution_limit = platform_version
3471                .dpp
3472                .voting_versions
3473                .default_vote_poll_time_duration_test_network_ms
3474                + 10_000;
3475
3476            fast_forward_to_block(&platform, time_after_distribution_limit, 900, 42, 0, false);
3477
3478            let platform_state = platform.state.load();
3479            let transaction = platform.drive.grove.start_transaction();
3480
3481            // This is the exact per-block call. In the attack scenario it returns
3482            // Err because the resolver loads the OLD contender bytes and decodes
3483            // them against the WIDENED `preorderSalt` type, misreading the 0xFF
3484            // first byte as a varint length -> CorruptedSerialization.
3485            platform.check_for_ended_vote_polls(
3486                &platform_state,
3487                &platform_state,
3488                &BlockInfo {
3489                    time_ms: time_after_distribution_limit,
3490                    height: 900,
3491                    core_height: 42,
3492                    epoch: Default::default(),
3493                },
3494                Some(&transaction),
3495                platform_version,
3496            )
3497        }
3498
3499        /// THE CHAIN-HALT CONSEQUENCE. If a byteArray `maxItems` widening were
3500        /// ever committed, the stored contender documents become undecodable and
3501        /// the per-block vote-poll resolver returns `Err`, which propagates out of
3502        /// the bare-`?` per-block event handler and halts every validator.
3503        ///
3504        /// The fix prevents that state at the source: `validate_update` now
3505        /// rejects the widening (see the rs-dpp validate_byte_array_encoding
3506        /// tests). This test deliberately writes the corrupt state directly to
3507        /// grovedb (bypassing validation) to reproduce the consequence the
3508        /// validation fix prevents.
3509        #[tokio::test]
3510        async fn widening_byte_array_max_items_halts_vote_poll_resolver() {
3511            let result = run_contest_then_resolve(true).await;
3512
3513            assert!(
3514                result.is_err(),
3515                "check_for_ended_vote_polls must return Err when stored contender \
3516                 documents are decoded against a widened byteArray encoding -- the \
3517                 chain-halt consequence the `validate_update` fix prevents by \
3518                 rejecting the update at the source. Got Ok instead."
3519            );
3520
3521            // Confirm it is the expected decode-failure variant from the encoding
3522            // flip, not some unrelated error. Matching the variant (rather than the
3523            // Debug string) keeps the test robust to message/format changes.
3524            assert_matches!(
3525                result.unwrap_err(),
3526                crate::error::Error::Protocol(dpp::ProtocolError::DataContractError(
3527                    dpp::data_contract::errors::DataContractError::CorruptedSerialization(_)
3528                        | dpp::data_contract::errors::DataContractError::DecodingContractError(_)
3529                ))
3530            );
3531        }
3532
3533        /// CONTROL (causation proof). The exact same contest, but WITHOUT the
3534        /// byteArray widening, resolves successfully (`Ok`). Together with the
3535        /// test above this proves the widening is what causes the halt -- the
3536        /// only difference between the two runs is the `maxItems` flip.
3537        #[tokio::test]
3538        async fn contest_without_widening_resolves_successfully() {
3539            let result = run_contest_then_resolve(false).await;
3540
3541            assert!(
3542                result.is_ok(),
3543                "control: without the byteArray widening the vote-poll resolver \
3544                 must succeed; got {:?}",
3545                result
3546            );
3547        }
3548    }
3549
3550    /// End-to-end regression test for the contested-index cross-check.
3551    ///
3552    /// The index name in a create transition's `prefunded_voting_balance` is
3553    /// what keys the vote poll, its stored info, its end-date entry and its
3554    /// prefunded specialized balance, while the contested index tree the
3555    /// contender is inserted into always comes from
3556    /// `DocumentType::find_contested_index`. Before the cross-check nothing
3557    /// tied the two together.
3558    ///
3559    /// The contract used here has a non-contested index (`parentName`) whose
3560    /// properties are a strict prefix of the contested index
3561    /// (`parentNameAndLabel`). That is what makes the mismatch survive: the
3562    /// bogus vote poll's contenders path is a tree the contested insert
3563    /// creates anyway, so the poll is registered — one level above the contest
3564    /// it describes.
3565    ///
3566    /// Run against the pre-fix structure validation (v0) this exact test shows
3567    /// the transition executing successfully and the resolver then returning
3568    /// `Err(Drive(CorruptedCodeExecution("expected a locked tally")))`, because
3569    /// the tally query finds no lock/abstain sum trees where the poll points.
3570    /// That `Err` propagates through `run_dao_platform_events` ->
3571    /// `run_block_proposal` (bare `?`), so it is not caught into a
3572    /// per-state-transition result — it halts every validator at the block
3573    /// where the poll ends. The end-date entry is never cleaned up, so every
3574    /// subsequent block proposal fails the same way.
3575    mod contested_index_mismatch_chain_halt {
3576        use super::*;
3577        use crate::config::PlatformConfig;
3578        use crate::test::helpers::fast_forward_to_block::fast_forward_to_block;
3579        use dpp::consensus::state::state_error::StateError;
3580        use dpp::consensus::ConsensusError;
3581        use dpp::dashcore::Network;
3582        use dpp::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
3583        use dpp::state_transition::batch_transition::document_create_transition::DocumentCreateTransitionV0;
3584        use dpp::state_transition::batch_transition::{
3585            BatchTransitionV0, DocumentCreateTransition,
3586        };
3587
3588        /// A contested `domain` document type that also carries a
3589        /// non-contested `parentName` index on `[normalizedParentDomainName]`,
3590        /// a strict prefix of the contested `[normalizedParentDomainName,
3591        /// normalizedLabel]`.
3592        const CONTRACT_WITH_PREFIX_INDEX: &str =
3593            "tests/supporting_files/contract/dpns/dpns-contract-contested-unique-index-and-prefix-index.json";
3594
3595        /// Opens a contest on the prefix-index contract with the voting
3596        /// balance prefunded for `prefunded_index`, then advances past the
3597        /// vote poll end date and runs the per-block resolver.
3598        ///
3599        /// Returns the execution result of the create transition and, when it
3600        /// was executed, the resolver's result.
3601        async fn run_contest_then_resolve(
3602            prefunded_index: &str,
3603        ) -> (
3604            StateTransitionExecutionResult,
3605            Option<Result<(), crate::error::Error>>,
3606        ) {
3607            // Mainnet: on testnet the contested structure validation is skipped
3608            // altogether before epoch 2080.
3609            let platform_config = PlatformConfig {
3610                network: Network::Mainnet,
3611                ..Default::default()
3612            };
3613            let mut platform = TestPlatformBuilder::new()
3614                .with_latest_protocol_version()
3615                .with_config(platform_config)
3616                .build_with_mock_rpc()
3617                .set_initial_state_structure();
3618
3619            let platform_version = PlatformVersion::latest();
3620
3621            let mut rng = StdRng::seed_from_u64(0x0C01_7E57);
3622
3623            let (identity, signer, key) =
3624                setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5));
3625            let contract_owner = setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5));
3626
3627            let contract = setup_contract(
3628                &platform.drive,
3629                CONTRACT_WITH_PREFIX_INDEX,
3630                None,
3631                Some(contract_owner.0.id().to_buffer()),
3632                None::<fn(&mut DataContract)>,
3633                None,
3634                Some(platform_version),
3635            );
3636
3637            let domain = contract
3638                .document_type_for_name("domain")
3639                .expect("expected a domain document type");
3640
3641            let entropy = Bytes32::random_with_rng(&mut rng);
3642
3643            let mut document = domain
3644                .random_document_with_identifier_and_entropy(
3645                    &mut rng,
3646                    identity.id(),
3647                    entropy,
3648                    DocumentFieldFillType::FillIfNotRequired,
3649                    DocumentFieldFillSize::AnyDocumentFillSize,
3650                    platform_version,
3651                )
3652                .expect("expected a random domain document");
3653
3654            document.set("parentDomainName", "dash".into());
3655            document.set("normalizedParentDomainName", "dash".into());
3656            document.set("label", "quantum".into());
3657            document.set("normalizedLabel", "quantum".into());
3658            document.set("records.identity", document.owner_id().into());
3659            document.set("subdomainRules.allowSubdomains", false.into());
3660            document.set("preorderSalt", rng.gen::<[u8; 32]>().into());
3661
3662            let owner_id = document.owner_id();
3663            let create_transition: DocumentCreateTransition = DocumentCreateTransitionV0 {
3664                base: DocumentBaseTransition::from_document(
3665                    &document,
3666                    domain,
3667                    None,
3668                    2,
3669                    platform_version,
3670                    None,
3671                )
3672                .expect("expected a base transition"),
3673                entropy: entropy.0,
3674                data: document.clone().properties_consumed(),
3675                prefunded_voting_balance: Some((
3676                    prefunded_index.to_string(),
3677                    platform_version
3678                        .fee_version
3679                        .vote_resolution_fund_fees
3680                        .contested_document_vote_resolution_fund_required_amount,
3681                )),
3682            }
3683            .into();
3684
3685            let inner_transition: BatchTransition = BatchTransitionV0 {
3686                owner_id,
3687                transitions: vec![create_transition.into()],
3688                user_fee_increase: 0,
3689                signature_public_key_id: 0,
3690                signature: Default::default(),
3691            }
3692            .into();
3693            let mut transition: StateTransition = inner_transition.into();
3694            transition
3695                .sign_external(&key, &signer, Some(|_, _| Ok(SecurityLevel::HIGH)))
3696                .await
3697                .expect("expected to sign");
3698
3699            let serialized_transition = transition
3700                .serialize_to_bytes()
3701                .expect("expected to serialize the transition");
3702
3703            let platform_state = platform.state.load();
3704            let transaction = platform.drive.grove.start_transaction();
3705
3706            let processing_result = platform
3707                .platform
3708                .process_raw_state_transitions(
3709                    &[serialized_transition],
3710                    &platform_state,
3711                    &BlockInfo::default_with_time(3000),
3712                    &transaction,
3713                    platform_version,
3714                    false,
3715                    None,
3716                )
3717                .expect("expected to process the state transition");
3718
3719            platform
3720                .drive
3721                .grove
3722                .commit_transaction(transaction)
3723                .unwrap()
3724                .expect("expected to commit transaction");
3725
3726            let execution_result = processing_result.into_execution_results().remove(0);
3727
3728            if !matches!(
3729                execution_result,
3730                StateTransitionExecutionResult::SuccessfulExecution { .. }
3731            ) {
3732                return (execution_result, None);
3733            }
3734
3735            let after_vote_poll_end = platform_version
3736                .dpp
3737                .voting_versions
3738                .default_vote_poll_time_duration_mainnet_ms
3739                + 10_000;
3740
3741            fast_forward_to_block(&platform, after_vote_poll_end, 900, 42, 0, false);
3742
3743            let platform_state = platform.state.load();
3744            let transaction = platform.drive.grove.start_transaction();
3745
3746            let resolver_result = platform.check_for_ended_vote_polls(
3747                &platform_state,
3748                &platform_state,
3749                &BlockInfo {
3750                    time_ms: after_vote_poll_end,
3751                    height: 900,
3752                    core_height: 42,
3753                    epoch: Default::default(),
3754                },
3755                Some(&transaction),
3756                platform_version,
3757            );
3758
3759            (execution_result, Some(resolver_result))
3760        }
3761
3762        /// THE FIX. A vote poll prefunded for `parentName` describes a
3763        /// different resource than the contest the document would create, so
3764        /// the transition never executes and no vote poll is registered.
3765        #[tokio::test]
3766        async fn prefunding_a_prefix_index_is_rejected() {
3767            let (execution_result, resolver_result) = run_contest_then_resolve("parentName").await;
3768
3769            assert_matches!(
3770                execution_result,
3771                StateTransitionExecutionResult::PaidConsensusError {
3772                    error: ConsensusError::StateError(
3773                        StateError::DocumentContestIndexMismatchError(_)
3774                    ),
3775                    ..
3776                },
3777                "a prefunded voting balance naming an index other than the contested one must be \
3778                 rejected before it can register a vote poll"
3779            );
3780
3781            assert!(
3782                resolver_result.is_none(),
3783                "the transition must not have executed"
3784            );
3785        }
3786
3787        /// CONTROL (causation proof). The exact same contest prefunded for the
3788        /// contested index executes, and its vote poll resolves without error
3789        /// when it ends. The only difference between the two runs is the index
3790        /// name in the prefunded voting balance.
3791        #[tokio::test]
3792        async fn prefunding_the_contested_index_resolves_successfully() {
3793            let (execution_result, resolver_result) =
3794                run_contest_then_resolve("parentNameAndLabel").await;
3795
3796            assert_matches!(
3797                execution_result,
3798                StateTransitionExecutionResult::SuccessfulExecution { .. }
3799            );
3800
3801            let resolver_result =
3802                resolver_result.expect("expected the vote poll resolver to have run");
3803
3804            assert!(
3805                resolver_result.is_ok(),
3806                "control: a contest whose vote poll names its own contested index must resolve; \
3807                 got {:?}",
3808                resolver_result
3809            );
3810        }
3811    }
3812}