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