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