From 60ef3d753ef245ded731f62adfd6014060eab7b0 Mon Sep 17 00:00:00 2001 From: linning Date: Thu, 8 Feb 2024 05:31:40 +0800 Subject: [PATCH] Add test stale_and_in_future_bundle_should_be_rejected and adjust testing infra Also, the test_stale_bundle_should_be_rejected is removed as stale bundle is cover by the bundle slot and proof-of-time check Signed-off-by: linning --- crates/pallet-domains/src/tests.rs | 120 +---------- domains/client/domain-operator/src/tests.rs | 224 ++++++++++++++++---- test/subspace-test-service/src/lib.rs | 103 +++++++-- 3 files changed, 262 insertions(+), 185 deletions(-) diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 50964c2872..a723bf6c2d 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -48,7 +48,6 @@ use sp_std::sync::Arc; use sp_trie::trie_types::TrieDBMutBuilderV1; use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut}; use sp_version::RuntimeVersion; -use std::sync::atomic::{AtomicU64, Ordering}; use subspace_core_primitives::{Randomness, U256 as P256}; use subspace_runtime_primitives::{Moment, StorageFee, SSC}; @@ -116,23 +115,10 @@ parameter_types! { pub const BlockTreePruningDepth: u32 = 16; } -static CONFIRMATION_DEPTH_K: AtomicU64 = AtomicU64::new(10); - pub struct ConfirmationDepthK; - -impl ConfirmationDepthK { - fn set(new: BlockNumber) { - CONFIRMATION_DEPTH_K.store(new, Ordering::SeqCst); - } - - fn get() -> BlockNumber { - CONFIRMATION_DEPTH_K.load(Ordering::SeqCst) - } -} - impl Get for ConfirmationDepthK { fn get() -> BlockNumber { - Self::get() + 10 } } @@ -658,110 +644,6 @@ pub(crate) fn get_block_tree_node_at( BlockTree::::get(domain_id, block_number).and_then(BlockTreeNodes::::get) } -// TODO: Unblock once bundle producer election v2 is finished. -#[test] -#[ignore] -fn test_stale_bundle_should_be_rejected() { - // Small macro in order to be more readable. - // - // We only care about whether the error type is `StaleBundle`. - macro_rules! assert_stale { - ($validate_bundle_result:expr) => { - assert_eq!( - $validate_bundle_result, - Err(pallet_domains::BundleError::StaleBundle) - ) - }; - } - - macro_rules! assert_not_stale { - ($validate_bundle_result:expr) => { - assert_ne!( - $validate_bundle_result, - Err(pallet_domains::BundleError::StaleBundle) - ) - }; - } - - ConfirmationDepthK::set(1); - new_test_ext().execute_with(|| { - // Create a bundle at genesis block -> #1 - let bundle0 = create_dummy_bundle(DOMAIN_ID, 0, System::parent_hash()); - System::initialize(&1, &System::parent_hash(), &Default::default()); - >::on_initialize(1); - assert_not_stale!(pallet_domains::Pallet::::validate_bundle(&bundle0)); - - // Create a bundle at block #1 -> #2 - let block_hash1 = Hash::random(); - let bundle1 = create_dummy_bundle(DOMAIN_ID, 1, block_hash1); - System::initialize(&2, &block_hash1, &Default::default()); - >::on_initialize(2); - assert_stale!(pallet_domains::Pallet::::validate_bundle(&bundle0)); - assert_not_stale!(pallet_domains::Pallet::::validate_bundle(&bundle1)); - }); - - ConfirmationDepthK::set(2); - new_test_ext().execute_with(|| { - // Create a bundle at genesis block -> #1 - let bundle0 = create_dummy_bundle(DOMAIN_ID, 0, System::parent_hash()); - System::initialize(&1, &System::parent_hash(), &Default::default()); - >::on_initialize(1); - assert_not_stale!(pallet_domains::Pallet::::validate_bundle(&bundle0)); - - // Create a bundle at block #1 -> #2 - let block_hash1 = Hash::random(); - let bundle1 = create_dummy_bundle(DOMAIN_ID, 1, block_hash1); - System::initialize(&2, &block_hash1, &Default::default()); - >::on_initialize(2); - assert_stale!(pallet_domains::Pallet::::validate_bundle(&bundle0)); - assert_not_stale!(pallet_domains::Pallet::::validate_bundle(&bundle1)); - - // Create a bundle at block #2 -> #3 - let block_hash2 = Hash::random(); - let bundle2 = create_dummy_bundle(DOMAIN_ID, 2, block_hash2); - System::initialize(&3, &block_hash2, &Default::default()); - >::on_initialize(3); - assert_stale!(pallet_domains::Pallet::::validate_bundle(&bundle0)); - assert_stale!(pallet_domains::Pallet::::validate_bundle(&bundle1)); - assert_not_stale!(pallet_domains::Pallet::::validate_bundle(&bundle2)); - }); - - ConfirmationDepthK::set(10); - let confirmation_depth_k = ConfirmationDepthK::get(); - let (dummy_bundles, block_hashes): (Vec<_>, Vec<_>) = (1..=confirmation_depth_k + 2) - .map(|n| { - let consensus_block_hash = Hash::random(); - ( - create_dummy_bundle(DOMAIN_ID, n, consensus_block_hash), - consensus_block_hash, - ) - }) - .unzip(); - - let run_to_block = |n: BlockNumber, block_hashes: Vec| { - System::initialize(&1, &System::parent_hash(), &Default::default()); - >::on_initialize(1); - System::finalize(); - - for b in 2..=n { - System::set_block_number(b); - System::initialize(&b, &block_hashes[b as usize - 2], &Default::default()); - >::on_initialize(b); - System::finalize(); - } - }; - - new_test_ext().execute_with(|| { - run_to_block(confirmation_depth_k + 2, block_hashes); - for bundle in dummy_bundles.iter().take(2) { - assert_stale!(pallet_domains::Pallet::::validate_bundle(bundle)); - } - for bundle in dummy_bundles.iter().skip(2) { - assert_not_stale!(pallet_domains::Pallet::::validate_bundle(bundle)); - } - }); -} - #[test] fn test_calculate_tx_range() { let cur_tx_range = P256::from(400_u64); diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 6650c75f10..8699bcc0f0 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -3,7 +3,7 @@ use crate::domain_bundle_producer::DomainBundleProducer; use crate::domain_bundle_proposer::DomainBundleProposer; use crate::fraud_proof::{FraudProofGenerator, TraceDiffType}; use crate::tests::TxPoolError::InvalidTransaction as TxPoolInvalidTransaction; -use crate::utils::OperatorSlotInfo; +use crate::OperatorSlotInfo; use codec::{Decode, Encode}; use domain_runtime_primitives::{DomainCoreApi, Hash}; use domain_test_primitives::{OnchainStateApi, TimestampApi}; @@ -329,7 +329,11 @@ async fn test_processing_empty_consensus_block() { let domain_genesis_hash = alice.client.info().best_hash; for _ in 0..10 { // Produce consensus block with no tx thus no bundle - ferdie.produce_block_with_extrinsics(vec![]).await.unwrap(); + let slot = ferdie.produce_slot(); + ferdie + .produce_block_with_slot_at(slot, ferdie.client.info().best_hash, Some(vec![])) + .await + .unwrap(); let consensus_best_hash = ferdie.client.info().best_hash; let consensus_best_number = ferdie.client.info().best_number; @@ -1175,7 +1179,7 @@ async fn test_invalid_state_transition_proof_creation_and_verification( .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1337,7 +1341,7 @@ async fn test_true_invalid_bundles_inherent_extrinsic_proof_creation_and_verific .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1374,7 +1378,7 @@ async fn test_true_invalid_bundles_inherent_extrinsic_proof_creation_and_verific .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1495,7 +1499,7 @@ async fn test_false_invalid_bundles_inherent_extrinsic_proof_creation_and_verifi .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1635,7 +1639,7 @@ async fn test_true_invalid_bundles_illegal_extrinsic_proof_creation_and_verifica .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1672,7 +1676,7 @@ async fn test_true_invalid_bundles_illegal_extrinsic_proof_creation_and_verifica .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1813,7 +1817,7 @@ async fn test_false_invalid_bundles_illegal_extrinsic_proof_creation_and_verific .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -1920,7 +1924,7 @@ async fn test_invalid_block_fees_proof_creation() { .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -2025,7 +2029,7 @@ async fn test_invalid_domain_block_hash_proof_creation() { .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -2130,7 +2134,7 @@ async fn test_invalid_domain_extrinsics_root_proof_creation() { .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) @@ -2213,7 +2217,7 @@ async fn test_bundle_equivocation_fraud_proof() { .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(original_submit_bundle_tx) @@ -2542,7 +2546,7 @@ async fn test_valid_bundle_proof_generation_and_verification() { .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(submit_bundle_tx_with_bad_receipt) .await @@ -2785,7 +2789,7 @@ async fn pallet_domains_unsigned_extrinsics_should_work() { } #[tokio::test(flavor = "multi_thread")] -async fn duplicated_and_stale_bundle_should_be_rejected() { +async fn duplicated_bundle_should_be_rejected() { let directory = TempDir::new().expect("Must be able to create temporary directory"); let mut builder = sc_cli::LoggerBuilder::new(""); @@ -2838,21 +2842,152 @@ async fn duplicated_and_stale_bundle_should_be_rejected() { } e => panic!("Unexpected error: {e}"), } +} - // Wait for `BlockTreePruningDepth + 1` blocks which is 16 + 1 in test - produce_blocks!(ferdie, alice, 17).await.unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn stale_and_in_future_bundle_should_be_rejected() { + let directory = TempDir::new().expect("Must be able to create temporary directory"); + + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + // Start Ferdie + let mut ferdie = MockConsensusNode::run( + tokio_handle.clone(), + Ferdie, + BasePath::new(directory.path().join("ferdie")), + ); + + // Run Alice (a evm domain authority node) + let alice = domain_test_service::DomainNodeBuilder::new( + tokio_handle.clone(), + Alice, + BasePath::new(directory.path().join("alice")), + ) + .build_evm_node(Role::Authority, GENESIS_DOMAIN_ID, &mut ferdie) + .await; - // Bundle is now rejected because its receipt is pruned. + produce_blocks!(ferdie, alice, 1).await.unwrap(); + let bundle_to_tx = |opaque_bundle| { + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_domains::Call::submit_bundle { opaque_bundle }.into(), + ) + .into() + }; + let slot_info = |slot, proof_of_time| OperatorSlotInfo { + slot, + proof_of_time, + }; + let operator_id = 0; + let mut bundle_producer = { + let domain_bundle_proposer = DomainBundleProposer::new( + GENESIS_DOMAIN_ID, + alice.client.clone(), + ferdie.client.clone(), + alice.operator.transaction_pool.clone(), + ); + let (bundle_sender, _bundle_receiver) = + sc_utils::mpsc::tracing_unbounded("domain_bundle_stream", 100); + DomainBundleProducer::new( + GENESIS_DOMAIN_ID, + ferdie.client.clone(), + alice.client.clone(), + domain_bundle_proposer, + Arc::new(bundle_sender), + alice.operator.keystore.clone(), + false, + ) + }; + + let (_, bundle1) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let (_, bundle2) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + ferdie.clear_tx_pool().await.unwrap(); + + // Produce one block that only included `bundle1` + produce_block_with!( + ferdie.produce_block_with_extrinsics(vec![bundle_to_tx(bundle1.unwrap())]), + alice + ) + .await + .unwrap(); + + // `bundle2` will be rejected because its PoT is stale match ferdie - .submit_transaction(submit_bundle_tx) + .submit_transaction(bundle_to_tx(bundle2.unwrap())) .await .unwrap_err() { sc_transaction_pool::error::Error::Pool(TxPoolError::InvalidTransaction(invalid_tx)) => { - assert_eq!(invalid_tx, InvalidTransactionCode::ExecutionReceipt.into()) + assert_eq!(invalid_tx, InvalidTransactionCode::Bundle.into()) } e => panic!("Unexpected error: {e}"), } + + // Bundle with unknow PoT and in future slot will be rejected before entring the tx pool + let (valid_slot, valid_pot) = ferdie.produce_slot(); + let slot_in_future = u64::MAX.into(); + let unknow_pot = PotOutput::from( + <&[u8] as TryInto<[u8; 16]>>::try_into( + &subspace_runtime_primitives::Hash::random().to_fixed_bytes()[..16], + ) + .expect("slice with length of 16 must able convert into [u8; 16]; qed"), + ); + + let valid_bundle = bundle_producer + .produce_bundle(operator_id, slot_info(valid_slot, valid_pot)) + .await + .unwrap() + .unwrap(); + let bundle_with_unknow_pot = bundle_producer + .produce_bundle(operator_id, slot_info(valid_slot, unknow_pot)) + .await + .unwrap() + .unwrap(); + let bundle_with_slot_in_future = bundle_producer + .produce_bundle(operator_id, slot_info(slot_in_future, valid_pot)) + .await + .unwrap() + .unwrap(); + for bundle in [ + bundle_with_unknow_pot.clone(), + bundle_with_slot_in_future.clone(), + ] { + match ferdie + .submit_transaction(bundle_to_tx(bundle)) + .await + .unwrap_err() + { + sc_transaction_pool::error::Error::Pool(TxPoolError::InvalidTransaction( + invalid_tx, + )) => { + assert_eq!(invalid_tx, InvalidTransactionCode::Bundle.into()) + } + e => panic!("Unexpected error: {e}"), + } + } + ferdie + .submit_transaction(bundle_to_tx(valid_bundle)) + .await + .unwrap(); + produce_blocks!(ferdie, alice, 1).await.unwrap(); + + // Even try building consensus block with these invalid bundles, they will fail to pass + // the `pre_dispatch` check, and won't included in the consensus block + let pre_ferdie_best_number = ferdie.client.info().best_number; + let pre_alice_best_number = alice.client.info().best_number; + + ferdie + .produce_block_with_extrinsics(vec![ + bundle_to_tx(bundle_with_unknow_pot), + bundle_to_tx(bundle_with_slot_in_future), + ]) + .await + .unwrap(); + assert_eq!(ferdie.client.info().best_number, pre_ferdie_best_number + 1); + assert_eq!(alice.client.info().best_number, pre_alice_best_number); } #[tokio::test(flavor = "multi_thread")] @@ -2883,6 +3018,7 @@ async fn existing_bundle_can_be_resubmitted_to_new_fork() { produce_blocks!(ferdie, alice, 3).await.unwrap(); + let pre_alice_best_number = alice.client.info().best_number; let mut parent_hash = ferdie.client.info().best_hash; let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; @@ -2899,23 +3035,22 @@ async fn existing_bundle_can_be_resubmitted_to_new_fork() { .await .unwrap(); - // Create fork and build one more blocks on it to make it the new best fork + // Create fork that also contains the bundle and build one more blocks on it to make + // it the new best fork parent_hash = ferdie - .produce_block_with_slot_at(slot, parent_hash, Some(vec![])) + .produce_block_with_slot_at(slot, parent_hash, Some(vec![submit_bundle_tx])) .await .unwrap(); + let slot = ferdie.produce_slot(); ferdie - .produce_block_with_slot_at(slot + 1, parent_hash, Some(vec![])) + .produce_block_with_slot_at(slot, parent_hash, Some(vec![])) .await .unwrap(); - // Bundle can be successfully submitted to the new fork, or it is also possible - // that the `submit_bundle_tx` in the retracted block has been resubmitted to the - // tx pool in the background by the `txpool-notifications` worker. - match ferdie.submit_transaction(submit_bundle_tx).await { - Ok(_) | Err(sc_transaction_pool::error::Error::Pool(TxPoolError::AlreadyImported(_))) => {} - Err(err) => panic!("Unexpected error: {err}"), - } + assert_eq!(alice.client.info().best_number, pre_alice_best_number + 1); + + produce_blocks!(ferdie, alice, 1).await.unwrap(); + assert_eq!(alice.client.info().best_number, pre_alice_best_number + 2); } // TODO: Unlock test when multiple domains are supported in DecEx v2. @@ -3712,7 +3847,7 @@ async fn test_bad_receipt_chain() { .prune_tx_from_pool(&original_submit_bundle_tx) .await .unwrap(); - assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + assert!(ferdie.get_bundle_from_tx_pool(slot).is_none()); ferdie .submit_transaction(bad_submit_bundle_tx) .await @@ -3731,22 +3866,17 @@ async fn test_bad_receipt_chain() { // Produce a bundle with another bad ER that use previous bad ER as parent let parent_bad_receipt_hash = bad_receipt_hash; let slot = ferdie.produce_slot(); - let bundle = { - let random_val: [u8; 16] = Hash::random().to_fixed_bytes()[..16] - .try_into() - .expect("slice with length of 16 must able convert into [u8; 16]; qed"); - bundle_producer - .produce_bundle( - 0, - OperatorSlotInfo { - slot, - proof_of_time: PotOutput::from(random_val), - }, - ) - .await - .expect("produce bundle must success") - .expect("must win the challenge") - }; + let bundle = bundle_producer + .produce_bundle( + 0, + OperatorSlotInfo { + slot: slot.0, + proof_of_time: slot.1, + }, + ) + .await + .expect("produce bundle must success") + .expect("must win the challenge"); let (bad_receipt_hash, bad_submit_bundle_tx) = { let mut opaque_bundle = bundle; let receipt = &mut opaque_bundle.sealed_header.header.receipt; diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index 93ab45d8d0..ee13309d76 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -51,8 +51,10 @@ use sp_application_crypto::UncheckedFrom; use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, Error as ConsensusError}; use sp_consensus_slots::Slot; -use sp_consensus_subspace::digests::{CompatibleDigestItem, PreDigest, PreDigestPotInfo}; -use sp_consensus_subspace::FarmerPublicKey; +use sp_consensus_subspace::digests::{ + extract_pre_digest, CompatibleDigestItem, PreDigest, PreDigestPotInfo, +}; +use sp_consensus_subspace::{FarmerPublicKey, PotExtension}; use sp_core::traits::{CodeExecutor, SpawnEssentialNamed}; use sp_core::H256; use sp_domains::{BundleProducerElectionApi, DomainsApi, OpaqueBundle}; @@ -68,6 +70,7 @@ use sp_runtime::traits::{ use sp_runtime::{DigestItem, OpaqueExtrinsic}; use sp_subspace_mmr::host_functions::{SubspaceMmrExtension, SubspaceMmrHostFunctionsImpl}; use sp_timestamp::Timestamp; +use std::collections::HashMap; use std::error::Error; use std::marker::PhantomData; use std::pin::Pin; @@ -178,19 +181,38 @@ type StorageChanges = sp_api::StorageChanges; struct MockExtensionsFactory { consensus_client: Arc, executor: Arc, + mock_pot_verifier: Arc, _phantom: PhantomData, } impl MockExtensionsFactory { - fn new(consensus_client: Arc, executor: Arc) -> Self { + fn new( + consensus_client: Arc, + executor: Arc, + mock_pot_verifier: Arc, + ) -> Self { Self { consensus_client, executor, + mock_pot_verifier, _phantom: Default::default(), } } } +#[derive(Default)] +struct MockPotVerfier(Mutex>); + +impl MockPotVerfier { + fn is_valid(&self, slot: u64, pot: PotOutput) -> bool { + self.0.lock().get(&slot).map(|p| *p == pot).unwrap_or(false) + } + + fn inject_pot(&self, slot: u64, pot: PotOutput) { + self.0.lock().insert(slot, pot); + } +} + impl ExtensionsFactory for MockExtensionsFactory where @@ -217,9 +239,41 @@ where exts.register(SubspaceMmrExtension::new(Arc::new( SubspaceMmrHostFunctionsImpl::::new(self.consensus_client.clone()), ))); + exts.register(PotExtension::new({ + let client = Arc::clone(&self.consensus_client); + let mock_pot_verifier = Arc::clone(&self.mock_pot_verifier); + Box::new( + move |parent_hash, slot, proof_of_time, _quick_verification| { + let parent_hash = { + let mut converted_parent_hash = Block::Hash::default(); + converted_parent_hash.as_mut().copy_from_slice(&parent_hash); + converted_parent_hash + }; + + let parent_header = match client.header(parent_hash) { + Ok(Some(parent_header)) => parent_header, + _ => return false, + }; + let parent_pre_digest = match extract_pre_digest(&parent_header) { + Ok(parent_pre_digest) => parent_pre_digest, + _ => return false, + }; + + let parent_slot = parent_pre_digest.slot(); + if slot <= *parent_slot { + return false; + } + + mock_pot_verifier.is_valid(slot, proof_of_time) + }, + ) + })); exts } } + +type NewSlot = (Slot, PotOutput); + /// A mock Subspace consensus node instance used for testing. pub struct MockConsensusNode { /// `TaskManager`'s instance. @@ -246,6 +300,8 @@ pub struct MockConsensusNode { pub network_starter: Option, /// The next slot number next_slot: u64, + /// The mock pot verifier + mock_pot_verifier: Arc, /// The slot notification subscribers #[allow(clippy::type_complexity)] new_slot_notification_subscribers: Vec>, @@ -290,11 +346,13 @@ impl MockConsensusNode { .expect("Fail to new full parts"); let client = Arc::new(client); + let mock_pot_verifier = Arc::new(MockPotVerfier::default()); client .execution_extensions() .set_extensions_factory(MockExtensionsFactory::<_, DomainBlock, _>::new( client.clone(), Arc::new(executor.clone()), + Arc::clone(&mock_pot_verifier), )); let select_chain = sc_consensus::LongestChain::new(backend.clone()); @@ -368,6 +426,7 @@ impl MockConsensusNode { rpc_handlers, network_starter: Some(network_starter), next_slot: 1, + mock_pot_verifier, new_slot_notification_subscribers: Vec::new(), acknowledgement_sender_subscribers: Vec::new(), block_import, @@ -425,33 +484,35 @@ impl MockConsensusNode { } /// Produce a slot only, without waiting for the potential slot handlers. - pub fn produce_slot(&mut self) -> Slot { + pub fn produce_slot(&mut self) -> NewSlot { let slot = Slot::from(self.next_slot); + let proof_of_time = PotOutput::from( + <&[u8] as TryInto<[u8; 16]>>::try_into(&Hash::random().to_fixed_bytes()[..16]) + .expect("slice with length of 16 must able convert into [u8; 16]; qed"), + ); + self.mock_pot_verifier.inject_pot(*slot, proof_of_time); self.next_slot += 1; - slot + + (slot, proof_of_time) } /// Notify the executor about the new slot and wait for the bundle produced at this slot. pub async fn notify_new_slot_and_wait_for_bundle( &mut self, - slot: Slot, + new_slot: NewSlot, ) -> Option, Hash, DomainHeader, Balance>> { - let random_val: [u8; 16] = Hash::random().to_fixed_bytes()[..16] - .try_into() - .expect("slice with length of 16 must able convert into [u8; 16]; qed"); - let value = (slot, PotOutput::from(random_val)); self.new_slot_notification_subscribers - .retain(|subscriber| subscriber.unbounded_send(value).is_ok()); + .retain(|subscriber| subscriber.unbounded_send(new_slot).is_ok()); self.confirm_acknowledgement().await; - self.get_bundle_from_tx_pool(slot.into()) + self.get_bundle_from_tx_pool(new_slot) } /// Produce a new slot and wait for a bundle produced at this slot. pub async fn produce_slot_and_wait_for_bundle_submission( &mut self, ) -> ( - Slot, + NewSlot, Option, Hash, DomainHeader, Balance>>, ) { let slot = self.produce_slot(); @@ -535,7 +596,7 @@ impl MockConsensusNode { /// Get the bundle that created at `slot` from the transaction pool pub fn get_bundle_from_tx_pool( &self, - slot: u64, + new_slot: NewSlot, ) -> Option, Hash, DomainHeader, Balance>> { for ready_tx in self.transaction_pool.ready() { let ext = UncheckedExtrinsic::decode(&mut ready_tx.data.encode().as_slice()) @@ -543,7 +604,7 @@ impl MockConsensusNode { if let RuntimeCall::Domains(pallet_domains::Call::submit_bundle { opaque_bundle }) = ext.function { - if opaque_bundle.sealed_header.slot_number() == slot { + if opaque_bundle.sealed_header.slot_number() == *new_slot.0 { return Some(opaque_bundle); } } @@ -701,7 +762,9 @@ impl MockConsensusNode { let inherent_txns = block_builder.create_inherents(inherent_data)?; for tx in inherent_txns.into_iter().chain(extrinsics) { - sc_block_builder::BlockBuilder::push(&mut block_builder, tx)?; + if let Err(err) = sc_block_builder::BlockBuilder::push(&mut block_builder, tx) { + tracing::error!("Invalid transaction while building block: {}", err); + } } let (block, storage_changes, _) = block_builder.build()?.into_inner(); @@ -743,7 +806,7 @@ impl MockConsensusNode { #[sc_tracing::logging::prefix_logs_with(self.log_prefix)] pub async fn produce_block_with_slot_at( &mut self, - slot: Slot, + new_slot: NewSlot, parent_hash: ::Hash, maybe_extrinsics: Option::Extrinsic>>, ) -> Result<::Hash, Box> { @@ -765,7 +828,9 @@ impl MockConsensusNode { .map(|t| self.transaction_pool.hash_of(t)) .collect(); - let (block, storage_changes) = self.build_block(slot, parent_hash, extrinsics).await?; + let (block, storage_changes) = self + .build_block(new_slot.0, parent_hash, extrinsics) + .await?; log_new_block(&block, block_timer.elapsed().as_millis()); @@ -785,7 +850,7 @@ impl MockConsensusNode { /// Produce a new block on top of the current best block, with the extrinsics collected from /// the transaction pool. #[sc_tracing::logging::prefix_logs_with(self.log_prefix)] - pub async fn produce_block_with_slot(&mut self, slot: Slot) -> Result<(), Box> { + pub async fn produce_block_with_slot(&mut self, slot: NewSlot) -> Result<(), Box> { self.produce_block_with_slot_at(slot, self.client.info().best_hash, None) .await?; Ok(())