diff --git a/zebrad/proptest-regressions/components/mempool/storage/tests/prop.txt b/zebrad/proptest-regressions/components/mempool/storage/tests/prop.txt index 0c305464411..3caa6ba5080 100644 --- a/zebrad/proptest-regressions/components/mempool/storage/tests/prop.txt +++ b/zebrad/proptest-regressions/components/mempool/storage/tests/prop.txt @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc b258d507c0b2aef6c2793bdb3fc29e6367e62fba9df378ea6797e9bc97fd2780 # shrinks to input = RemoveSameEffects { transactions: alloc::vec::Vec, len=2, mined_ids_to_remove: std::collections::hash::set::HashSet, len=2 } +cc 4616d813ba54e7b7475a1adb880905dfb05a63b59a18dc079893bf963ae92097 # shrinks to rejection_error = SameEffectsChain(Expired), mut rejection_template = Witnessed(WtxId { id: transaction::Hash("0000000000000000000000000000000000000000000000000000000000000000"), auth_digest: AuthDigest("353b92c552d72e06b512c81f909991b291ca0fec5251d6696755091100000000") }) diff --git a/zebrad/src/components/mempool/storage.rs b/zebrad/src/components/mempool/storage.rs index 463490ed41c..bab2c9fb064 100644 --- a/zebrad/src/components/mempool/storage.rs +++ b/zebrad/src/components/mempool/storage.rs @@ -10,13 +10,14 @@ use std::{ collections::{HashMap, HashSet}, mem::size_of, + time::Duration, }; use thiserror::Error; use zebra_chain::transaction::{self, UnminedTx, UnminedTxId, VerifiedUnminedTx}; -use self::verified_set::VerifiedSet; +use self::{eviction_list::EvictionList, verified_set::VerifiedSet}; use super::{config, downloads::TransactionDownloadVerifyError, MempoolError}; #[cfg(any(test, feature = "proptest-impl"))] @@ -25,11 +26,9 @@ use proptest_derive::Arbitrary; #[cfg(test)] pub mod tests; +mod eviction_list; mod verified_set; -/// The maximum number of verified transactions to store in the mempool. -const MEMPOOL_SIZE: usize = 4; - /// The size limit for mempool transaction rejection lists. /// /// > The size of RecentlyEvicted SHOULD never exceed `eviction_memory_entries` entries, @@ -103,7 +102,6 @@ pub enum RejectionError { } /// Hold mempool verified and rejected mempool transactions. -#[derive(Default)] pub struct Storage { /// The set of verified transactions in the mempool. This is a /// cache of size [`MEMPOOL_SIZE`]. @@ -125,8 +123,18 @@ pub struct Storage { /// These rejections apply until a rollback or network upgrade. /// /// Any transaction with the same `transaction::Hash` is invalid. - chain_rejected_same_effects: - HashMap>, + /// + /// An [`EvictionList`] is used for both randomly evicted and expired transactions, + /// even if it is only needed for the evicted ones. This was done just to simplify + /// the existing code; there is no harm in having a timeout for expired transactions + /// too since re-checking expired transactions is cheap. + // If this code is ever refactored and the lists are split in different fields, + // then we can use an `EvictionList` just for the evicted list. + chain_rejected_same_effects: HashMap, + + /// The mempool transaction eviction age limit. + /// Same as [`Config::eviction_memory_time`]. + eviction_memory_time: Duration, /// Max total cost of the verified mempool set, beyond which transactions /// are evicted to make room. @@ -142,9 +150,14 @@ impl Drop for Storage { impl Storage { #[allow(clippy::field_reassign_with_default)] pub(crate) fn new(config: &config::Config) -> Self { - let mut default: Storage = Default::default(); - default.tx_cost_limit = config.tx_cost_limit; - default + Self { + tx_cost_limit: config.tx_cost_limit, + eviction_memory_time: config.eviction_memory_time, + verified: Default::default(), + tip_rejected_exact: Default::default(), + tip_rejected_same_effects: Default::default(), + chain_rejected_same_effects: Default::default(), + } } /// Insert a [`VerifiedUnminedTx`] into the mempool, caching any rejections. @@ -182,15 +195,26 @@ impl Storage { result = Err(rejection_error.into()); } - // Once inserted, we evict transactions over the pool size limit. - while self.verified.transaction_count() > MEMPOOL_SIZE - || self.verified.total_cost() > self.tx_cost_limit - { + // Once inserted, we evict transactions over the pool size limit per [ZIP-401]; + // + // > On receiving a transaction: (...) + // > Calculate its cost. If the total cost of transactions in the mempool including this + // > one would `exceed mempooltxcostlimit`, then the node MUST repeatedly call + // > EvictTransaction (with the new transaction included as a candidate to evict) until the + // > total cost does not exceed `mempooltxcostlimit`. + // + // [ZIP-401]: https://zips.z.cash/zip-0401 + while self.verified.total_cost() > self.tx_cost_limit { + // > EvictTransaction MUST do the following: + // > Select a random transaction to evict, with probability in direct proportion to + // > eviction weight. (...) Remove it from the mempool. let victim_tx = self .verified .evict_one() .expect("mempool is empty, but was expected to be full"); + // > Add the txid and the current time to RecentlyEvicted, dropping the oldest entry in + // > RecentlyEvicted if necessary to keep it to at most `eviction_memory_entries entries`. self.reject( victim_tx.transaction.id, SameEffectsChainRejectionError::RandomlyEvicted.into(), @@ -203,8 +227,6 @@ impl Storage { } } - assert!(self.verified.transaction_count() <= MEMPOOL_SIZE); - result } @@ -277,11 +299,7 @@ impl Storage { if self.tip_rejected_same_effects.len() > MAX_EVICTION_MEMORY_ENTRIES { self.tip_rejected_same_effects.clear(); } - for (_, map) in self.chain_rejected_same_effects.iter_mut() { - if map.len() > MAX_EVICTION_MEMORY_ENTRIES { - map.clear(); - } - } + // `chain_rejected_same_effects` limits its size by itself self.update_rejected_metrics(); } @@ -326,12 +344,12 @@ impl Storage { /// /// Transactions on multiple rejected lists are counted multiple times. #[allow(dead_code)] - pub fn rejected_transaction_count(&self) -> usize { + pub fn rejected_transaction_count(&mut self) -> usize { self.tip_rejected_exact.len() + self.tip_rejected_same_effects.len() + self .chain_rejected_same_effects - .iter() + .iter_mut() .map(|(_, map)| map.len()) .sum::() } @@ -346,12 +364,12 @@ impl Storage { self.tip_rejected_same_effects.insert(txid.mined_id(), e); } RejectionError::SameEffectsChain(e) => { - // TODO: track evicted victims times, removing those older than - // config.eviction_memory_time, as well as FIFO more than - // MAX_EVICTION_MEMORY_ENTRIES + let eviction_memory_time = self.eviction_memory_time; self.chain_rejected_same_effects .entry(e) - .or_default() + .or_insert_with(|| { + EvictionList::new(MAX_EVICTION_MEMORY_ENTRIES, eviction_memory_time) + }) .insert(txid.mined_id()); } } @@ -374,7 +392,7 @@ impl Storage { } for (error, set) in self.chain_rejected_same_effects.iter() { - if set.contains(&txid.mined_id()) { + if set.contains_key(&txid.mined_id()) { return Some(error.clone().into()); } } @@ -486,7 +504,7 @@ impl Storage { /// Update metrics related to the rejected lists. /// /// Must be called every time the rejected lists change. - fn update_rejected_metrics(&self) { + fn update_rejected_metrics(&mut self) { metrics::gauge!( "mempool.rejected.transaction.ids", self.rejected_transaction_count() as _ diff --git a/zebrad/src/components/mempool/storage/eviction_list.rs b/zebrad/src/components/mempool/storage/eviction_list.rs new file mode 100644 index 00000000000..2809608e406 --- /dev/null +++ b/zebrad/src/components/mempool/storage/eviction_list.rs @@ -0,0 +1,144 @@ +//! [`EvictionList`] represents the transaction eviction list with +//! efficient operations. +use std::{ + collections::{HashMap, VecDeque}, + time::{Duration, Instant}, +}; + +use zebra_chain::transaction; + +/// An eviction list that allows Zebra to efficiently add entries, get entries, +/// and remove older entries in the order they were inserted. +pub struct EvictionList { + // Maps each TXID in the list to the most recent instant they were added. + unique_entries: HashMap, + // The same as `unique_entries` but in the order they were inserted. + ordered_entries: VecDeque, + // The maximum size of `unique_entries`. + max_size: usize, + /// The mempool transaction eviction age limit. + /// Same as [`Config::eviction_memory_time`]. + eviction_memory_time: Duration, +} + +impl EvictionList { + /// Create a new [`EvictionList`] with the given maximum size and + /// eviction time. + pub fn new(max_size: usize, eviction_memory_time: Duration) -> Self { + Self { + unique_entries: Default::default(), + ordered_entries: Default::default(), + max_size, + eviction_memory_time, + } + } + + /// Inserts a TXID in the list, keeping track of the time it was inserted. + /// + /// All entries older than [`EvictionList::eviction_memory_time`] will be removed. + /// + /// # Panics + /// + /// If the TXID is already in the list. + /// + pub fn insert(&mut self, key: transaction::Hash) { + // From https://zips.z.cash/zip-0401#specification: + // > Nodes SHOULD remove transactions from RecentlyEvicted that were evicted more than + // > mempoolevictionmemoryminutes minutes ago. This MAY be done periodically, + // > and/or just before RecentlyEvicted is accessed when receiving a transaction. + self.prune_old(); + // > Add the txid and the current time to RecentlyEvicted, dropping the oldest entry + // > in RecentlyEvicted if necessary to keep it to at most eviction_memory_entries entries. + if self.len() >= self.max_size { + self.pop_front(); + } + let value = Instant::now(); + let old_value = self.unique_entries.insert(key, value); + // It should be impossible for an already-evicted transaction to be evicted + // again since transactions are not added to the mempool if they are evicted, + // and the mempool doesn't allow inserting two transactions with the same + // hash (they would conflict). + assert_eq!( + old_value, None, + "an already-evicted transaction should not be evicted again" + ); + self.ordered_entries.push_back(key) + } + + /// Checks if the given TXID is in the list. + pub fn contains_key(&self, txid: &transaction::Hash) -> bool { + if let Some(evicted_at) = self.unique_entries.get(txid) { + // Since the list is pruned only in mutable functions, make sure + // we take expired items into account. + if !self.has_expired(evicted_at) { + return true; + } + } + false + } + + /// Get the size of the list. + // + // Note: if this method being mutable becomes an issue, it's possible + // to compute the number of expired transactions and subtract, + // at the cost of `O(len + expired)` performance each time the method is called. + // + // Currently the performance is `O(expired)` for the first call, then `O(1)` until the next expiry. + pub fn len(&mut self) -> usize { + self.prune_old(); + self.unique_entries.len() + } + + /// Clear the list. + #[allow(dead_code)] + pub fn clear(&mut self) { + self.unique_entries.clear(); + self.ordered_entries.clear(); + } + + /// Prune TXIDs that are older than `eviction_time` ago. + /// + // This method is public because ZIP-401 states about pruning: + // > This MAY be done periodically, + pub fn prune_old(&mut self) { + while let Some(txid) = self.front() { + let evicted_at = self + .unique_entries + .get(txid) + .unwrap_or_else(|| panic!("all entries should exist in both ordered_entries and unique_entries, missing {:?} in unique_entries", txid)); + if self.has_expired(evicted_at) { + self.pop_front(); + } else { + break; + } + } + } + + /// Get the oldest TXID in the list. + fn front(&self) -> Option<&transaction::Hash> { + self.ordered_entries.front() + } + + /// Removes the first element and returns it, or `None` if the `EvictionList` + /// is empty. + fn pop_front(&mut self) -> Option { + if let Some(key) = self.ordered_entries.pop_front() { + let removed = self.unique_entries.remove(&key); + assert!( + removed.is_some(), + "all entries should exist in both ordered_entries and unique_entries, missing {:?} in unique_entries", + key + ); + Some(key) + } else { + None + } + } + + /// Returns if `evicted_at` is considered expired considering the current + /// time and the configured eviction time. + fn has_expired(&self, evicted_at: &Instant) -> bool { + let now = Instant::now(); + (now - *evicted_at) > self.eviction_memory_time + } +} diff --git a/zebrad/src/components/mempool/storage/tests/prop.rs b/zebrad/src/components/mempool/storage/tests/prop.rs index d8ef9c5a0e0..29d9e0d605b 100644 --- a/zebrad/src/components/mempool/storage/tests/prop.rs +++ b/zebrad/src/components/mempool/storage/tests/prop.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, convert::TryFrom, env, fmt::Debug}; +use std::{collections::HashSet, convert::TryFrom, env, fmt::Debug, thread, time::Duration}; use proptest::{collection::vec, prelude::*}; use proptest_derive::Arbitrary; @@ -19,8 +19,8 @@ use zebra_chain::{ use crate::components::mempool::{ config::Config, storage::{ - MempoolError, RejectionError, SameEffectsTipRejectionError, Storage, - MAX_EVICTION_MEMORY_ENTRIES, MEMPOOL_SIZE, + eviction_list::EvictionList, MempoolError, RejectionError, SameEffectsTipRejectionError, + Storage, MAX_EVICTION_MEMORY_ENTRIES, }, SameEffectsChainRejectionError, }; @@ -33,6 +33,13 @@ use MultipleTransactionRemovalTestInput::*; /// so individual tests take less than 10 seconds on most machines. const DEFAULT_MEMPOOL_LIST_PROPTEST_CASES: u32 = 64; +/// Eviction memory time used for tests. Most tests won't care about this +/// so we use a large enough value that will never be reached in the tests. +const EVICTION_MEMORY_TIME: Duration = Duration::from_secs(60 * 60); + +/// Transaction count used in some tests to derive the mempool test size. +const MEMPOOL_TX_COUNT: usize = 4; + proptest! { #![proptest_config( proptest::test_runner::Config::with_cases(env::var("PROPTEST_CASES") @@ -50,6 +57,7 @@ proptest! { let mut storage = Storage::new( &Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -101,11 +109,15 @@ proptest! { /// Test that the reject list length limits are applied when evicting transactions. #[test] fn reject_lists_are_limited_insert_eviction( - transactions in vec(any::(), MEMPOOL_SIZE + 1).prop_map(SummaryDebug), + transactions in vec(any::(), MEMPOOL_TX_COUNT + 1).prop_map(SummaryDebug), mut rejection_template in any::() ) { + // Use as cost limit the costs of all transactions except one + let cost_limit = transactions.iter().take(MEMPOOL_TX_COUNT).map(|tx| tx.cost()).sum(); + let mut storage: Storage = Storage::new(&Config { - tx_cost_limit: 160_000_000, + tx_cost_limit: cost_limit, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -127,19 +139,19 @@ proptest! { // Make sure there were no duplicates prop_assert_eq!(storage.rejected_transaction_count(), MAX_EVICTION_MEMORY_ENTRIES); - for transaction in transactions { + for (i, transaction) in transactions.iter().enumerate() { let tx_id = transaction.transaction.id; - if storage.transaction_count() < MEMPOOL_SIZE { + if i < transactions.len() - 1 { // The initial transactions should be successful prop_assert_eq!( - storage.insert(transaction), + storage.insert(transaction.clone()), Ok(tx_id) ); } else { // The final transaction will cause a random eviction, // which might return an error if this transaction is chosen - let result = storage.insert(transaction); + let result = storage.insert(transaction.clone()); if result.is_ok() { prop_assert_eq!( @@ -155,9 +167,13 @@ proptest! { } } + // Check if at least one transaction was evicted. + // (More than one an be evicted to meet the limit.) + prop_assert!(storage.transaction_count() <= MEMPOOL_TX_COUNT); + // Since we inserted more than MAX_EVICTION_MEMORY_ENTRIES, - // the storage should have cleared the reject list - prop_assert_eq!(storage.rejected_transaction_count(), 0); + // the storage should have removed the older entries and kept its size + prop_assert_eq!(storage.rejected_transaction_count(), MAX_EVICTION_MEMORY_ENTRIES); } /// Test that the reject list length limits are applied when directly rejecting transactions. @@ -168,6 +184,7 @@ proptest! { ) { let mut storage: Storage = Storage::new(&Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -191,11 +208,51 @@ proptest! { } else if index == MAX_EVICTION_MEMORY_ENTRIES { // Since we inserted more than MAX_EVICTION_MEMORY_ENTRIES, // all with the same error, - // the storage should have cleared the reject list - prop_assert_eq!(storage.rejected_transaction_count(), 0); + // the storage should have either cleared the reject list + // or removed the oldest ones, depending on the structure + // used. + match rejection_error { + RejectionError::ExactTip(_) | + RejectionError::SameEffectsTip(_) => { + prop_assert_eq!(storage.rejected_transaction_count(), 0); + }, + RejectionError::SameEffectsChain(_) => { + prop_assert_eq!(storage.rejected_transaction_count(), MAX_EVICTION_MEMORY_ENTRIES); + }, + } } } } + + /// Test that the reject list length time limits are applied + /// when directly rejecting transactions. + #[test] + fn reject_lists_are_time_pruned( + mut rejection_template in any::() + ) { + let mut storage = Storage::new(&Config { + tx_cost_limit: 160_000_000, + eviction_memory_time: Duration::from_millis(10), + ..Default::default() + }); + + // Make unique IDs by converting the index to bytes, and writing it to each ID + let unique_ids: Vec = (0..2_u32).map(move |index| { + let index = index.to_le_bytes(); + rejection_template.mined_id_mut().0[0..4].copy_from_slice(&index); + if let Some(auth_digest) = rejection_template.auth_digest_mut() { + auth_digest.0[0..4].copy_from_slice(&index); + } + + rejection_template + }).collect(); + + storage.reject(unique_ids[0], SameEffectsChainRejectionError::RandomlyEvicted.into()); + thread::sleep(Duration::from_millis(11)); + storage.reject(unique_ids[1], SameEffectsChainRejectionError::RandomlyEvicted.into()); + + prop_assert_eq!(storage.rejected_transaction_count(), 1); + } } proptest! { @@ -208,6 +265,7 @@ proptest! { fn conflicting_transactions_are_rejected(input in any::()) { let mut storage: Storage = Storage::new(&Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -243,6 +301,7 @@ proptest! { { let mut storage: Storage = Storage::new(&Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -299,6 +358,7 @@ proptest! { fn removal_of_multiple_transactions(input in any::()) { let mut storage: Storage = Storage::new(&Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -861,7 +921,7 @@ impl Arbitrary for MultipleTransactionRemovalTestInput { type Parameters = (); fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { - vec(any::(), 1..MEMPOOL_SIZE) + vec(any::(), 1..MEMPOOL_TX_COUNT) .prop_flat_map(|transactions| { let indices_to_remove = vec(any::(), 1..=transactions.len()).prop_map(|removal_markers| { @@ -931,3 +991,142 @@ impl MultipleTransactionRemovalTestInput { } } } + +proptest! { + + // Some tests need to sleep which makes tests slow, so use a single + // case. We also don't need multiple cases since proptest is just used + // to generate random TXIDs. + #![proptest_config( + proptest::test_runner::Config::with_cases(1) + )] + + /// Check if EvictionList limits the number of entries. + #[test] + fn eviction_list_size( + mut rejection_template in any::() + ) { + // Make unique IDs by converting the index to bytes, and writing it to each ID + let txids: Vec = (0..4_u32).map(move |index| { + let index = index.to_le_bytes(); + rejection_template.mined_id_mut().0[0..4].copy_from_slice(&index); + if let Some(auth_digest) = rejection_template.auth_digest_mut() { + auth_digest.0[0..4].copy_from_slice(&index); + } + + rejection_template + }).collect(); + + let mut e = EvictionList::new(2, EVICTION_MEMORY_TIME); + for txid in txids.iter() { + e.insert(txid.mined_id()); + } + prop_assert!(!e.contains_key(&txids[0].mined_id())); + prop_assert!(!e.contains_key(&txids[1].mined_id())); + prop_assert!(e.contains_key(&txids[2].mined_id())); + prop_assert!(e.contains_key(&txids[3].mined_id())); + prop_assert_eq!(e.len(), 2); + } + + /// Check if EvictionList removes old entries. + #[test] + fn eviction_list_time( + mut rejection_template in any::() + ) { + // Make unique IDs by converting the index to bytes, and writing it to each ID + let txids: Vec = (0..2_u32).map(move |index| { + let index = index.to_le_bytes(); + rejection_template.mined_id_mut().0[0..4].copy_from_slice(&index); + if let Some(auth_digest) = rejection_template.auth_digest_mut() { + auth_digest.0[0..4].copy_from_slice(&index); + } + + rejection_template + }).collect(); + + let mut e = EvictionList::new(2, Duration::from_millis(10)); + e.insert(txids[0].mined_id()); + thread::sleep(Duration::from_millis(11)); + + // First txid has expired, but list wasn't pruned yet. + // Make sure len() and contains_key() take that into account. + prop_assert!(!e.contains_key(&txids[0].mined_id())); + prop_assert_eq!(e.len(), 0); + + e.insert(txids[1].mined_id()); + prop_assert!(!e.contains_key(&txids[0].mined_id())); + prop_assert!(e.contains_key(&txids[1].mined_id())); + prop_assert_eq!(e.len(), 1); + } + + /// Check if EvictionList removes old entries and computes length correctly + /// when the list becomes mixed with expired and non-expired entries. + #[test] + fn eviction_list_time_mixed( + mut rejection_template in any::() + ) { + // Eviction time (in ms) used in this test. If the value is too low it may cause + // this test to fail in slower systems. + const EVICTION_TIME: u64 = 500; + // Time to wait (in ms) before adding transactions that should not expire + // after EVICTION_TIME. + // If should be a bit smaller than EVICTION_TIME, but with enough time to + // add 10 transactions to the list and still have time to spare before EVICTION_TIME. + const BEFORE_EVICTION_TIME: u64 = 250; + + // Make unique IDs by converting the index to bytes, and writing it to each ID + let txids: Vec = (0..20_u32).map(move |index| { + let index = index.to_le_bytes(); + rejection_template.mined_id_mut().0[0..4].copy_from_slice(&index); + if let Some(auth_digest) = rejection_template.auth_digest_mut() { + auth_digest.0[0..4].copy_from_slice(&index); + } + + rejection_template + }).collect(); + + let mut e = EvictionList::new(20, Duration::from_millis(EVICTION_TIME)); + for txid in txids.iter().take(10) { + e.insert(txid.mined_id()); + } + thread::sleep(Duration::from_millis(BEFORE_EVICTION_TIME)); + // Add the next 10 before the eviction time to avoid a prune from + // happening. + for txid in txids.iter().skip(10) { + e.insert(txid.mined_id()); + } + thread::sleep(Duration::from_millis(EVICTION_TIME - BEFORE_EVICTION_TIME + 1)); + + // At this point, the first 10 entries should be expired + // and the next 10 should not, and the list hasn't been pruned yet. + // Make sure len() and contains_key() take that into account. + // Note: if one of these fails, you may need to adjust EVICTION_TIME and/or + // BEFORE_EVICTION_TIME, see above. + for txid in txids.iter().take(10) { + prop_assert!(!e.contains_key(&txid.mined_id())); + } + for txid in txids.iter().skip(10) { + prop_assert!(e.contains_key(&txid.mined_id())); + } + prop_assert_eq!(e.len(), 10); + + // Make sure all of them are expired + thread::sleep(Duration::from_millis(EVICTION_TIME + 1)); + + for txid in txids.iter() { + prop_assert!(!e.contains_key(&txid.mined_id())); + } + prop_assert_eq!(e.len(), 0); + } + + /// Check if EvictionList panics if entries are added multiple times. + #[test] + #[should_panic] + fn eviction_list_refresh( + txid in any::() + ) { + let mut e = EvictionList::new(2, EVICTION_MEMORY_TIME); + e.insert(txid.mined_id()); + e.insert(txid.mined_id()); + } +} diff --git a/zebrad/src/components/mempool/storage/tests/vectors.rs b/zebrad/src/components/mempool/storage/tests/vectors.rs index f6dd02642da..4e2ecb8ce39 100644 --- a/zebrad/src/components/mempool/storage/tests/vectors.rs +++ b/zebrad/src/components/mempool/storage/tests/vectors.rs @@ -14,6 +14,10 @@ use crate::components::mempool::{ config, storage::tests::unmined_transactions_in_blocks, storage::*, Mempool, }; +/// Eviction memory time used for tests. Most tests won't care about this +/// so we use a large enough value that will never be reached in the tests. +const EVICTION_MEMORY_TIME: Duration = Duration::from_secs(60 * 60); + #[test] fn mempool_storage_crud_exact_mainnet() { zebra_test::init(); @@ -23,6 +27,7 @@ fn mempool_storage_crud_exact_mainnet() { // Create an empty storage instance let mut storage: Storage = Storage::new(&config::Config { tx_cost_limit: u64::MAX, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -54,6 +59,7 @@ fn mempool_storage_crud_same_effects_mainnet() { // Create an empty storage instance let mut storage: Storage = Storage::new(&config::Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() }); @@ -91,6 +97,7 @@ fn mempool_expired_basic_for_network(network: Network) -> Result<()> { // Create an empty storage let mut storage: Storage = Storage::new(&config::Config { tx_cost_limit: 160_000_000, + eviction_memory_time: EVICTION_MEMORY_TIME, ..Default::default() });