domain_block_preprocessor/
lib.rs

1//! This crate provides a preprocessor for the domain block, which is used to construct
2//! domain extrinsics from the consensus block.
3//!
4//! The workflow is as follows:
5//! 1. Extract domain-specific bundles from the consensus block.
6//! 2. Compile the domain bundles into a list of extrinsics.
7//! 3. Shuffle the extrisnics using the seed from the consensus chain.
8//! 4. Filter out the invalid xdm extrinsics.
9//! 5. Push back the potential new domain runtime extrisnic.
10
11#![warn(rust_2018_idioms)]
12
13pub mod inherents;
14pub mod stateless_runtime;
15
16use crate::inherents::is_runtime_upgraded;
17use crate::stateless_runtime::StatelessRuntime;
18use domain_runtime_primitives::opaque::AccountId;
19use domain_runtime_primitives::{CheckExtrinsicsValidityError, opaque};
20use parity_scale_codec::Encode;
21use sc_client_api::{BlockBackend, backend};
22use sc_executor::RuntimeVersionOf;
23use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
24use sp_blockchain::HeaderBackend;
25use sp_core::H256;
26use sp_core::traits::{CodeExecutor, FetchRuntimeCode};
27use sp_domains::core_api::DomainCoreApi;
28use sp_domains::extrinsics::deduplicate_and_shuffle_extrinsics;
29use sp_domains::{
30    DomainId, DomainsApi, ExecutionReceipt, ExtrinsicDigest, HeaderHashingFor, InboxedBundle,
31    InvalidBundleType, OpaqueBundle, OpaqueBundles, ReceiptValidity,
32};
33use sp_messenger::MessengerApi;
34use sp_mmr_primitives::MmrApi;
35use sp_runtime::traits::{Block as BlockT, Hash as HashT, NumberFor};
36use sp_state_machine::LayoutV1;
37use sp_state_machine::backend::AsTrieBackend;
38use sp_subspace_mmr::ConsensusChainMmrLeafProof;
39use sp_weights::Weight;
40use std::collections::VecDeque;
41use std::marker::PhantomData;
42use std::sync::Arc;
43use subspace_core_primitives::{Randomness, U256};
44use subspace_runtime_primitives::{Balance, ExtrinsicFor};
45
46type DomainBlockElements<CBlock> = (Vec<ExtrinsicFor<CBlock>>, Randomness);
47
48/// A wrapper indicating a valid bundle contents, or an invalid bundle reason.
49enum BundleValidity<Extrinsic> {
50    /// A valid bundle contents.
51    Valid(Vec<Extrinsic>),
52    /// A valid bundle contents with signer of each extrinsic.
53    ValidWithSigner(Vec<(Option<opaque::AccountId>, Extrinsic)>),
54    /// An invalid bundle reason.
55    Invalid(InvalidBundleType),
56}
57
58/// Extracts the raw materials for building a new domain block from the primary block.
59fn prepare_domain_block_elements<Block, CBlock, CClient>(
60    consensus_client: &CClient,
61    block_hash: CBlock::Hash,
62) -> sp_blockchain::Result<DomainBlockElements<CBlock>>
63where
64    Block: BlockT,
65    CBlock: BlockT,
66    CClient: HeaderBackend<CBlock> + BlockBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
67    CClient::Api: DomainsApi<CBlock, Block::Header>,
68{
69    let extrinsics = consensus_client.block_body(block_hash)?.ok_or_else(|| {
70        sp_blockchain::Error::Backend(format!("BlockBody of {block_hash:?} unavailable"))
71    })?;
72
73    let shuffling_seed = consensus_client
74        .runtime_api()
75        .extrinsics_shuffling_seed(block_hash)?;
76
77    Ok((extrinsics, shuffling_seed))
78}
79
80pub struct PreprocessResult<Block: BlockT> {
81    pub extrinsics: VecDeque<Block::Extrinsic>,
82    pub bundles: Vec<InboxedBundle<Block::Hash>>,
83}
84
85pub struct DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
86{
87    domain_id: DomainId,
88    client: Arc<Client>,
89    consensus_client: Arc<CClient>,
90    executor: Arc<Exec>,
91    backend: Arc<Backend>,
92    receipt_validator: ReceiptValidator,
93    _phantom_data: PhantomData<(Block, CBlock)>,
94}
95
96impl<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator: Clone> Clone
97    for DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
98{
99    fn clone(&self) -> Self {
100        Self {
101            domain_id: self.domain_id,
102            client: self.client.clone(),
103            consensus_client: self.consensus_client.clone(),
104            executor: self.executor.clone(),
105            backend: self.backend.clone(),
106            receipt_validator: self.receipt_validator.clone(),
107            _phantom_data: self._phantom_data,
108        }
109    }
110}
111
112pub trait ValidateReceipt<Block, CBlock>
113where
114    Block: BlockT,
115    CBlock: BlockT,
116{
117    fn validate_receipt(
118        &self,
119        receipt: &ExecutionReceipt<
120            NumberFor<CBlock>,
121            CBlock::Hash,
122            NumberFor<Block>,
123            Block::Hash,
124            Balance,
125        >,
126    ) -> sp_blockchain::Result<ReceiptValidity>;
127}
128
129impl<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
130    DomainBlockPreprocessor<Block, CBlock, Client, CClient, Exec, Backend, ReceiptValidator>
131where
132    Block: BlockT,
133    Block::Hash: Into<H256>,
134    CBlock: BlockT,
135    CBlock::Hash: From<Block::Hash>,
136    NumberFor<CBlock>: From<NumberFor<Block>>,
137    Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + 'static,
138    Client::Api: DomainCoreApi<Block> + MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>,
139    CClient: HeaderBackend<CBlock>
140        + BlockBackend<CBlock>
141        + ProvideRuntimeApi<CBlock>
142        + Send
143        + Sync
144        + 'static,
145    CClient::Api: DomainsApi<CBlock, Block::Header>
146        + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>
147        + MmrApi<CBlock, H256, NumberFor<CBlock>>,
148    Backend: backend::Backend<Block>,
149    Exec: CodeExecutor + RuntimeVersionOf,
150    ReceiptValidator: ValidateReceipt<Block, CBlock>,
151{
152    pub fn new(
153        domain_id: DomainId,
154        client: Arc<Client>,
155        consensus_client: Arc<CClient>,
156        executor: Arc<Exec>,
157        backend: Arc<Backend>,
158        receipt_validator: ReceiptValidator,
159    ) -> Self {
160        Self {
161            domain_id,
162            client,
163            consensus_client,
164            executor,
165            backend,
166            receipt_validator,
167            _phantom_data: Default::default(),
168        }
169    }
170
171    pub fn preprocess_consensus_block(
172        &self,
173        consensus_block_hash: CBlock::Hash,
174        parent_domain_block: (Block::Hash, NumberFor<Block>),
175    ) -> sp_blockchain::Result<Option<PreprocessResult<Block>>> {
176        let (primary_extrinsics, shuffling_seed) = prepare_domain_block_elements::<Block, CBlock, _>(
177            &*self.consensus_client,
178            consensus_block_hash,
179        )?;
180
181        let bundles = self
182            .consensus_client
183            .runtime_api()
184            .extract_successful_bundles(consensus_block_hash, self.domain_id, primary_extrinsics)?;
185
186        if bundles.is_empty()
187            && !is_runtime_upgraded::<_, _, Block>(
188                &self.consensus_client,
189                consensus_block_hash,
190                self.domain_id,
191            )?
192        {
193            return Ok(None);
194        }
195
196        let tx_range = self
197            .consensus_client
198            .runtime_api()
199            .domain_tx_range(consensus_block_hash, self.domain_id)?;
200
201        let (inboxed_bundles, extrinsics) = self.compile_bundles_to_extrinsics(
202            bundles,
203            tx_range,
204            parent_domain_block,
205            consensus_block_hash,
206        )?;
207
208        let extrinsics =
209            deduplicate_and_shuffle_extrinsics::<Block::Extrinsic>(extrinsics, shuffling_seed);
210
211        Ok(Some(PreprocessResult {
212            extrinsics,
213            bundles: inboxed_bundles,
214        }))
215    }
216
217    /// NOTE: this is needed for compatible with Taurus
218    fn is_batch_api_available(
219        &self,
220        parent_domain_hash: Block::Hash,
221    ) -> sp_blockchain::Result<bool> {
222        let domain_runtime_api = self.client.runtime_api();
223
224        let domain_core_api_version = domain_runtime_api
225            .api_version::<dyn DomainCoreApi<Block>>(parent_domain_hash)?
226            .ok_or(sp_blockchain::Error::Application(Box::from(format!(
227                "DomainCoreApi not found at {parent_domain_hash:?}"
228            ))))?;
229
230        let messenger_api_version = domain_runtime_api
231            .api_version::<dyn MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>>(
232                parent_domain_hash,
233            )?
234            .ok_or(sp_blockchain::Error::Application(Box::from(format!(
235                "MessengerApi not found at {parent_domain_hash:?}"
236            ))))?;
237
238        Ok(domain_core_api_version >= 2 && messenger_api_version >= 3)
239    }
240
241    /// Filter out the invalid bundles first and then convert the remaining valid ones to
242    /// a list of extrinsics.
243    #[allow(clippy::type_complexity)]
244    fn compile_bundles_to_extrinsics(
245        &self,
246        bundles: OpaqueBundles<CBlock, Block::Header, Balance>,
247        tx_range: U256,
248        (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
249        at_consensus_hash: CBlock::Hash,
250    ) -> sp_blockchain::Result<(
251        Vec<InboxedBundle<Block::Hash>>,
252        Vec<(Option<AccountId>, Block::Extrinsic)>,
253    )> {
254        let mut inboxed_bundles = Vec::with_capacity(bundles.len());
255        let mut valid_extrinsics = Vec::new();
256
257        let runtime_api = self.client.runtime_api();
258        for bundle in bundles {
259            // For the honest operator the validity of the extrinsic of the bundle is committed
260            // to (or say verified against) the receipt that is submitted with the bundle, the
261            // consensus runtime should only accept the bundle if the receipt is derived from
262            // the parent domain block. If it is not then either there is a bug in the consensus
263            // runtime (for validating the bundle) or in the domain client (for finding the parent
264            // domain block).
265            //
266            // NOTE: The receipt's `domain_block_number` is verified by the consensus runtime while
267            // the `domain_block_hash` is not (which is take care of by the fraud proof) so we can't
268            // check the parent domain block hash here.
269            if bundle.receipt().domain_block_number != parent_domain_number {
270                return Err(sp_blockchain::Error::RuntimeApiError(
271                    ApiError::Application(
272                        format!(
273                            "Unexpected bundle in consensus block: {at_consensus_hash:?}, something must be wrong"
274                        )
275                        .into(),
276                    ),
277                ));
278            }
279
280            let extrinsic_root = bundle.extrinsics_root();
281            let bundle_validity = if self.is_batch_api_available(parent_domain_hash)? {
282                self.batch_check_bundle_validity(
283                    bundle,
284                    &tx_range,
285                    (parent_domain_hash, parent_domain_number),
286                    at_consensus_hash,
287                )?
288            } else {
289                self.check_bundle_validity(
290                    &bundle,
291                    &tx_range,
292                    (parent_domain_hash, parent_domain_number),
293                    at_consensus_hash,
294                )?
295            };
296            match bundle_validity {
297                BundleValidity::Valid(extrinsics) => {
298                    let extrinsics: Vec<_> = match runtime_api
299                        .extract_signer(parent_domain_hash, extrinsics)
300                    {
301                        Ok(res) => res,
302                        Err(e) => {
303                            tracing::error!(error = ?e, "Error at calling runtime api: extract_signer");
304                            return Err(e.into());
305                        }
306                    };
307                    let bundle_digest: Vec<_> = extrinsics
308                        .iter()
309                        .map(|(signer, tx)| {
310                            (
311                                signer.clone(),
312                                ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
313                                    tx.encode(),
314                                ),
315                            )
316                        })
317                        .collect();
318                    inboxed_bundles.push(InboxedBundle::valid(
319                        HeaderHashingFor::<Block::Header>::hash_of(&bundle_digest),
320                        extrinsic_root,
321                    ));
322                    valid_extrinsics.extend(extrinsics);
323                }
324                BundleValidity::ValidWithSigner(signer_and_extrinsics) => {
325                    let bundle_digest: Vec<_> = signer_and_extrinsics
326                        .iter()
327                        .map(|(signer, tx)| {
328                            (
329                                signer.clone(),
330                                ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
331                                    tx.encode(),
332                                ),
333                            )
334                        })
335                        .collect();
336                    inboxed_bundles.push(InboxedBundle::valid(
337                        HeaderHashingFor::<Block::Header>::hash_of(&bundle_digest),
338                        extrinsic_root,
339                    ));
340                    valid_extrinsics.extend(signer_and_extrinsics);
341                }
342                BundleValidity::Invalid(invalid_bundle_type) => {
343                    inboxed_bundles
344                        .push(InboxedBundle::invalid(invalid_bundle_type, extrinsic_root));
345                }
346            }
347        }
348
349        Ok((inboxed_bundles, valid_extrinsics))
350    }
351
352    fn stateless_runtime_api(
353        &self,
354        parent_domain_hash: Block::Hash,
355    ) -> sp_blockchain::Result<StatelessRuntime<CBlock, Block, Exec>> {
356        let state = self.backend.state_at(parent_domain_hash)?;
357        let trie_backend = state.as_trie_backend();
358        let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend);
359        let runtime_code = state_runtime_code
360            .runtime_code()
361            .map_err(sp_blockchain::Error::RuntimeCode)?
362            .fetch_runtime_code()
363            .ok_or(sp_blockchain::Error::RuntimeCode("missing runtime code"))?
364            .into_owned();
365
366        Ok(StatelessRuntime::<CBlock, Block, _>::new(
367            self.executor.clone(),
368            runtime_code.into(),
369        ))
370    }
371
372    fn check_bundle_validity(
373        &self,
374        bundle: &OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
375        tx_range: &U256,
376        (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
377        at_consensus_hash: CBlock::Hash,
378    ) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
379        let bundle_vrf_hash =
380            U256::from_be_bytes(*bundle.sealed_header.header.proof_of_election.vrf_hash());
381
382        let mut extrinsics = Vec::with_capacity(bundle.extrinsics.len());
383        let mut estimated_bundle_weight = Weight::default();
384        let mut maybe_invalid_bundle_type = None;
385
386        let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
387        let consensus_runtime_api = self.consensus_client.runtime_api();
388
389        // Check the validity of each extrinsic
390        //
391        // NOTE: for each extrinsic the checking order must follow `InvalidBundleType::checking_order`
392        for (index, opaque_extrinsic) in bundle.extrinsics.iter().enumerate() {
393            let decode_result = stateless_runtime_api.decode_extrinsic(opaque_extrinsic.clone())?;
394            let extrinsic = match decode_result {
395                Ok(extrinsic) => extrinsic,
396                Err(err) => {
397                    tracing::error!(
398                        ?opaque_extrinsic,
399                        ?err,
400                        "Undecodable extrinsic in bundle({})",
401                        bundle.hash()
402                    );
403                    maybe_invalid_bundle_type
404                        .replace(InvalidBundleType::UndecodableTx(index as u32));
405                    break;
406                }
407            };
408
409            let is_within_tx_range =
410                stateless_runtime_api.is_within_tx_range(&extrinsic, &bundle_vrf_hash, tx_range)?;
411
412            if !is_within_tx_range {
413                maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index as u32));
414                break;
415            }
416
417            // Check if this extrinsic is an inherent extrinsic.
418            // If so, this is an invalid bundle since these extrinsics should not be included in the
419            // bundle. Extrinsic is always decodable due to the check above.
420            if stateless_runtime_api.is_inherent_extrinsic(&extrinsic)? {
421                maybe_invalid_bundle_type
422                    .replace(InvalidBundleType::InherentExtrinsic(index as u32));
423                break;
424            }
425
426            if let Some(xdm_mmr_proof) =
427                stateless_runtime_api.extract_native_xdm_mmr_proof(&extrinsic)?
428            {
429                let ConsensusChainMmrLeafProof {
430                    opaque_mmr_leaf,
431                    proof,
432                    ..
433                } = xdm_mmr_proof;
434
435                if consensus_runtime_api
436                    .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
437                    .is_err()
438                {
439                    maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index as u32));
440                    break;
441                }
442            }
443
444            let tx_weight = stateless_runtime_api.extrinsic_weight(&extrinsic)?;
445            estimated_bundle_weight = estimated_bundle_weight.saturating_add(tx_weight);
446
447            extrinsics.push(extrinsic);
448        }
449
450        // Using `check_extrinsics_and_do_pre_dispatch` instead of `check_transaction_validity`
451        // to maintain side-effect between tx in the storage buffer.
452        //
453        // Note: call `check_extrinsics_and_do_pre_dispatch` with all the extrinsics instead of
454        // calling it one by one, this is needed to keep consistency with the FP verification.
455        if let Err(CheckExtrinsicsValidityError {
456            extrinsic_index, ..
457        }) = self
458            .client
459            .runtime_api()
460            .check_extrinsics_and_do_pre_dispatch(
461                parent_domain_hash,
462                extrinsics.clone(),
463                parent_domain_number,
464                parent_domain_hash,
465            )?
466        {
467            // It is okay to return error here even if `maybe_invalid_bundle_type` can be `Some`
468            // because the loop above break earlier whenever an invalid tx is found, if there is
469            // illegal tx found here then its `extrinsic_index` must smaller than `maybe_invalid_bundle_type`'s
470            // if any, thus the illegal tx has a higher priority.
471            return Ok(BundleValidity::Invalid(InvalidBundleType::IllegalTx(
472                extrinsic_index,
473            )));
474        }
475
476        if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
477            return Ok(BundleValidity::Invalid(invalid_bundle_type));
478        }
479
480        if estimated_bundle_weight != bundle.estimated_weight() {
481            return Ok(BundleValidity::Invalid(
482                InvalidBundleType::InvalidBundleWeight,
483            ));
484        }
485
486        Ok(BundleValidity::Valid(extrinsics))
487    }
488
489    fn batch_check_bundle_validity(
490        &self,
491        bundle: OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
492        tx_range: &U256,
493        (parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
494        at_consensus_hash: CBlock::Hash,
495    ) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
496        let bundle_vrf_hash =
497            U256::from_be_bytes(*bundle.sealed_header.header.proof_of_election.vrf_hash());
498        let bundle_length = bundle.extrinsics.len();
499        let bundle_estimated_weight = bundle.estimated_weight();
500        let mut maybe_invalid_bundle_type = None;
501
502        // Note: It is okay to use stateless here since all the bundle checks currently do not
503        // require state. But of any checks in the future requires a state read that was part of the
504        // genesis ex: `SelfChainId`, stateless runtime will panic.
505        // So ideal to set the genesis storage as fraud proof already have access to that and would be
506        // no different in terms of verification on both the sides
507        let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
508        let consensus_runtime_api = self.consensus_client.runtime_api();
509
510        // Check the validity of extrinsic inside the bundle, the goal is trying to find the first
511        // invalid tx and the first check it failed to pass, thus even an invalid tx that failed to
512        // pass a given check is found we still continue the following check for other txs that before
513        // it.
514        //
515        // NOTE: the checking order must follow `InvalidBundleType::checking_order`
516
517        let mut extrinsics = stateless_runtime_api.decode_extrinsics_prefix(bundle.extrinsics)?;
518        if extrinsics.len() != bundle_length {
519            // If the length changed meaning there is undecodable tx at index `extrinsics.len()`
520            maybe_invalid_bundle_type
521                .replace(InvalidBundleType::UndecodableTx(extrinsics.len() as u32));
522        }
523
524        let signers = match stateless_runtime_api.extract_signer_if_all_within_tx_range(
525            &extrinsics,
526            &bundle_vrf_hash,
527            tx_range,
528        )? {
529            Err(index) => {
530                maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index));
531                extrinsics.truncate(index as usize);
532
533                // This will never used since there is an invalid tx
534                Vec::default()
535            }
536            Ok(signers) => signers,
537        };
538
539        // Check if this extrinsic is an inherent extrinsic.
540        // If so, this is an invalid bundle since these extrinsics should not be included in the
541        // bundle. Extrinsic is always decodable due to the check above.
542        if let Some(index) = stateless_runtime_api.find_first_inherent_extrinsic(&extrinsics)? {
543            maybe_invalid_bundle_type.replace(InvalidBundleType::InherentExtrinsic(index));
544            extrinsics.truncate(index as usize);
545        }
546
547        let batch_xdm_mmr_proof =
548            stateless_runtime_api.batch_extract_native_xdm_mmr_proof(&extrinsics)?;
549        for (index, xdm_mmr_proof) in batch_xdm_mmr_proof {
550            let ConsensusChainMmrLeafProof {
551                opaque_mmr_leaf,
552                proof,
553                ..
554            } = xdm_mmr_proof;
555
556            if consensus_runtime_api
557                .verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
558                .is_err()
559            {
560                maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index));
561                extrinsics.truncate(index as usize);
562                break;
563            }
564        }
565
566        // Using `check_extrinsics_and_do_pre_dispatch` instead of `check_transaction_validity`
567        // to maintain side-effect between tx in the storage buffer.
568        if let Err(CheckExtrinsicsValidityError {
569            extrinsic_index, ..
570        }) = self
571            .client
572            .runtime_api()
573            .check_extrinsics_and_do_pre_dispatch(
574                parent_domain_hash,
575                extrinsics.clone(),
576                parent_domain_number,
577                parent_domain_hash,
578            )?
579        {
580            maybe_invalid_bundle_type.replace(InvalidBundleType::IllegalTx(extrinsic_index));
581        }
582
583        // If there is any invalid tx then return the error before checking the bundle weight,
584        // which is a check of the whole bundle and should only perform when all tx are valid.
585        if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
586            return Ok(BundleValidity::Invalid(invalid_bundle_type));
587        }
588
589        if bundle_estimated_weight != stateless_runtime_api.extrinsics_weight(&extrinsics)? {
590            return Ok(BundleValidity::Invalid(
591                InvalidBundleType::InvalidBundleWeight,
592            ));
593        }
594
595        let signer_and_extrinsics = signers.into_iter().zip(extrinsics).collect();
596        Ok(BundleValidity::ValidWithSigner(signer_and_extrinsics))
597    }
598}
599
600#[cfg(test)]
601mod tests {
602    use sp_domains::extrinsics::shuffle_extrinsics;
603    use sp_keyring::sr25519::Keyring;
604    use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
605    use subspace_core_primitives::Randomness;
606
607    #[test]
608    fn shuffle_extrinsics_should_work() {
609        let alice = Keyring::Alice.to_account_id();
610        let bob = Keyring::Bob.to_account_id();
611        let charlie = Keyring::Charlie.to_account_id();
612
613        let extrinsics = vec![
614            (Some(alice.clone()), 10),
615            (None, 100),
616            (Some(bob.clone()), 1),
617            (Some(bob), 2),
618            (Some(charlie.clone()), 30),
619            (Some(alice.clone()), 11),
620            (Some(charlie), 31),
621            (None, 101),
622            (None, 102),
623            (Some(alice), 12),
624        ];
625
626        let dummy_seed = Randomness::from(BlakeTwo256::hash_of(&[1u8; 64]).to_fixed_bytes());
627        let shuffled_extrinsics = shuffle_extrinsics(extrinsics, dummy_seed);
628
629        assert_eq!(
630            shuffled_extrinsics,
631            vec![100, 30, 10, 1, 11, 101, 31, 12, 102, 2]
632        );
633    }
634}