From 8385c8431ead91221066d9e8054971d5f8c19daa Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Tue, 2 Aug 2022 20:58:27 +0300 Subject: [PATCH 01/12] Fix stack_flush_test --- Cargo.lock | 2 + utxo/Cargo.toml | 3 +- utxo/src/undo.rs | 34 ++- utxo/src/utxo_impl/mod.rs | 23 +- utxo/src/utxo_impl/simulation.rs | 119 ++++----- utxo/src/utxo_impl/test.rs | 332 +++++++++++++----------- utxo/src/utxo_impl/test_helper.rs | 32 +-- utxo/src/utxo_impl/utxo_storage/test.rs | 56 ++-- 8 files changed, 327 insertions(+), 274 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e363adb80..11aad6bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4459,7 +4459,9 @@ dependencies = [ "itertools", "logging", "parity-scale-codec", + "rstest", "serialization", + "test-utils", "thiserror", ] diff --git a/utxo/Cargo.toml b/utxo/Cargo.toml index 0c249fa51..fffad7101 100644 --- a/utxo/Cargo.toml +++ b/utxo/Cargo.toml @@ -17,5 +17,6 @@ thiserror = "1.0" [dev-dependencies] crypto = { path = '../crypto' } - itertools = "0.10" +rstest = "0.15" +test-utils = {path = '../test-utils'} diff --git a/utxo/src/undo.rs b/utxo/src/undo.rs index 27df85338..b74e80797 100644 --- a/utxo/src/undo.rs +++ b/utxo/src/undo.rs @@ -75,12 +75,17 @@ impl BlockUndo { pub mod test { use super::*; use crate::test_helper::create_utxo; - use crypto::random::{make_pseudo_rng, Rng}; - - #[test] - fn tx_undo_test() { - let (utxo0, _) = create_utxo(0); - let (utxo1, _) = create_utxo(1); + use crypto::random::Rng; + use rstest::rstest; + use test_utils::random::{make_seedable_rng, Seed}; + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn tx_undo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let (utxo0, _) = create_utxo(&mut rng, 0); + let (utxo1, _) = create_utxo(&mut rng, 1); let mut tx_undo = TxUndo::new(vec![utxo0.clone()]); // check push @@ -102,16 +107,19 @@ pub mod test { } } - #[test] - fn block_undo_test() { + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn block_undo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); let expected_height = BlockHeight::new(5); - let (utxo0, _) = create_utxo(0); - let (utxo1, _) = create_utxo(1); + let (utxo0, _) = create_utxo(&mut rng, 0); + let (utxo1, _) = create_utxo(&mut rng, 1); let tx_undo0 = TxUndo::new(vec![utxo0, utxo1]); - let (utxo2, _) = create_utxo(2); - let (utxo3, _) = create_utxo(3); - let (utxo4, _) = create_utxo(4); + let (utxo2, _) = create_utxo(&mut rng, 2); + let (utxo3, _) = create_utxo(&mut rng, 3); + let (utxo4, _) = create_utxo(&mut rng, 4); let tx_undo1 = TxUndo::new(vec![utxo2, utxo3, utxo4]); let blockundo = BlockUndo::new(vec![tx_undo0.clone(), tx_undo1.clone()], expected_height); diff --git a/utxo/src/utxo_impl/mod.rs b/utxo/src/utxo_impl/mod.rs index eb1854d4b..20334960b 100644 --- a/utxo/src/utxo_impl/mod.rs +++ b/utxo/src/utxo_impl/mod.rs @@ -516,35 +516,44 @@ mod simulation; #[cfg(test)] mod unit_test { use common::primitives::H256; + use rstest::rstest; + use test_utils::random::{make_seedable_rng, Seed}; use crate::test_helper::{insert_single_entry, Presence, DIRTY, FRESH}; use crate::UtxosCache; - #[test] - fn test_uncache() { + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn test_uncache(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); // when the entry is not dirty and not fresh - let (utxo, outp) = insert_single_entry(&mut cache, &Presence::Present, Some(0), None); + let (utxo, outp) = + insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(0), None); let res = cache.uncache(&outp).expect("should return an entry"); assert_eq!(res.utxo(), Some(utxo)); assert!(!cache.has_utxo_in_cache(&outp)); // when the outpoint does not exist. - let (_, outp) = insert_single_entry(&mut cache, &Presence::Absent, None, None); + let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Absent, None, None); assert_eq!(cache.uncache(&outp), None); assert!(!cache.has_utxo_in_cache(&outp)); // when the entry is fresh, entry cannot be removed. - let (_, outp) = insert_single_entry(&mut cache, &Presence::Present, Some(FRESH), None); + let (_, outp) = + insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(FRESH), None); assert_eq!(cache.uncache(&outp), None); // when the entry is dirty, entry cannot be removed. - let (_, outp) = insert_single_entry(&mut cache, &Presence::Present, Some(DIRTY), None); + let (_, outp) = + insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(DIRTY), None); assert_eq!(cache.uncache(&outp), None); // when the entry is both fresh and dirty, entry cannot be removed. - let (_, outp) = insert_single_entry(&mut cache, &Presence::Present, Some(FRESH), None); + let (_, outp) = + insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(FRESH), None); assert_eq!(cache.uncache(&outp), None); } } diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/utxo_impl/simulation.rs index 20e7fe99b..1c86b71e5 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/utxo_impl/simulation.rs @@ -15,86 +15,69 @@ //TODO: need a better way than this. -use crate::utxo_impl::test_helper::{create_utxo, DIRTY, FRESH}; -use crate::{flush_to_base, UtxosCache, UtxosView}; -use crate::{UtxoEntry, UtxoStatus}; -use common::chain::OutPoint; -use common::primitives::{Id, H256}; -use crypto::random::{make_pseudo_rng, Rng}; - -fn random_bool() -> bool { - make_pseudo_rng().gen::() -} - -fn random_u64(max: u64) -> u64 { - make_pseudo_rng().gen_range(0..max) -} +use crate::{ + flush_to_base, + utxo_impl::test_helper::{create_utxo, DIRTY, FRESH}, + UtxoEntry, UtxoStatus, UtxosCache, UtxosView, +}; +use common::{ + chain::OutPoint, + primitives::{Id, H256}, +}; +use crypto::random::Rng; +use rstest::rstest; +use test_utils::random::{make_seedable_rng, Seed}; fn populate_cache<'a>( + rng: &mut impl Rng, parent: &'a UtxosCache, - size: u64, + new_utxo_count: u64, existing_outpoints: &[OutPoint], ) -> (UtxosCache<'a>, Vec) { let mut cache = UtxosCache::new(parent); - // tracker - let mut outps: Vec = vec![]; + let mut outpoints: Vec = vec![]; // let's add utxos based on `size`. - for i in 0..size { - let block_height = if random_bool() { - i - } else { - // setting a random height based on the `size`. - make_pseudo_rng().gen_range(0..size) - }; - - let (utxo, outpoint) = create_utxo(block_height); + for _ in 0..new_utxo_count { + let block_height = rng.gen_range(0..new_utxo_count); + let (utxo, outpoint) = create_utxo(rng, block_height); - let outpoint = if random_bool() && existing_outpoints.len() > 1 { + let outpoint = if rng.gen::() && existing_outpoints.len() > 1 { // setting a random existing 'spent' outpoint - let rng = make_pseudo_rng().gen_range(0..existing_outpoints.len()); - let outpoint = &existing_outpoints[rng]; - - outpoint.clone() + let outpoint_idx = rng.gen_range(0..existing_outpoints.len()); + existing_outpoints[outpoint_idx].clone() } else { // tracking the outpoints - outps.push(outpoint.clone()); + outpoints.push(outpoint.clone()); outpoint }; // randomly set the `possible_overwrite` - let possible_overwrite = random_bool(); + let possible_overwrite = rng.gen::(); let _ = cache.add_utxo(&outpoint, utxo, possible_overwrite); - - // println!("child, insert: {:?}, overwrite: {}", outpoint,possible_overwrite ); } // let's create half of the outpoints provided, to be marked as spent. // there's a possibility when randomly the same outpoint is used, so half seems okay. - let spent_size = outps.len() / 2; + let spent_size = outpoints.len() / 2; for _ in 0..spent_size { // randomly select which outpoint should be marked as "spent" - if random_bool() && existing_outpoints.len() > 1 { + if rng.gen::() && existing_outpoints.len() > 1 { // just call the `spend_utxo`. Does not matter if it removes the outpoint entirely, // or just mark it as `spent`, - let outp_idx = make_pseudo_rng().gen_range(0..existing_outpoints.len()); + let outp_idx = rng.gen_range(0..existing_outpoints.len()); let to_spend = &existing_outpoints[outp_idx]; - assert!(cache.spend_utxo(to_spend).is_ok()); - - //println!("child, spend: {:?}, removed", to_spend); + let _ = cache.spend_utxo(to_spend); } else { // just mark it as "spent" - - let outp_idx = make_pseudo_rng().gen_range(0..outps.len()); - let to_spend = &outps[outp_idx]; - - let key = to_spend; + let outp_idx = rng.gen_range(0..outpoints.len()); + let to_spend = &outpoints[outp_idx]; // randomly select which flags should the spent utxo have. // 0 - NOT FRESH, NOT DIRTY, 1 - FRESH, 2 - DIRTY, 3 - FRESH AND DIRTY - let flags = make_pseudo_rng().gen_range(0..4u8); + let flags = rng.gen_range(0..4u8); let new_entry = match flags { FRESH => UtxoEntry { @@ -118,46 +101,48 @@ fn populate_cache<'a>( is_fresh: false, }, }; - cache.utxos.insert(key.clone(), new_entry); - - //println!("child, spend: {:?}, flags: {}", to_spend,flags ); + cache.utxos.insert(to_spend.clone(), new_entry); }; } - (cache, outps) + (cache, outpoints) } -// TODO: Fix the test (https://github.com/mintlayer/mintlayer-core/issues/219). -#[ignore] -#[test] -fn stack_flush_test() { - let mut outps: Vec = vec![]; +#[rstest] +#[case("8887871176094693639")] +#[trace] +#[case(Seed::from_entropy())] +fn stack_flush_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let mut outpoints: Vec = vec![]; - let block_hash = Id::new(H256::random()); let mut parent = UtxosCache::new_for_test(H256::random().into()); - parent.set_best_block(block_hash); let parent_clone = parent.clone(); - let (cache1, mut cache1_outps) = populate_cache(&parent_clone, random_u64(50), &outps); - outps.append(&mut cache1_outps); + let new_utxo_count1 = rng.gen_range(0..50); + let (cache1, mut cache1_outps) = + populate_cache(&mut rng, &parent_clone, new_utxo_count1, &outpoints); + outpoints.append(&mut cache1_outps); let cache1_clone = cache1.clone(); - let (cache2, mut cache2_outps) = populate_cache(&cache1_clone, random_u64(50), &outps); - outps.append(&mut cache2_outps); + let new_utxo_count2 = rng.gen_range(0..50); + let (cache2, mut cache2_outps) = + populate_cache(&mut rng, &cache1_clone, new_utxo_count2, &outpoints); + outpoints.append(&mut cache2_outps); let cache2_clone = cache2.clone(); - let (mut cache3, mut cache3_outps) = populate_cache(&cache2_clone, random_u64(50), &outps); - outps.append(&mut cache3_outps); + let cache3_utxos_size = rng.gen_range(0..50); + let (mut cache3, mut cache3_outps) = + populate_cache(&mut rng, &cache2_clone, cache3_utxos_size, &outpoints); + outpoints.append(&mut cache3_outps); let new_block_hash = Id::new(H256::random()); cache3.set_best_block(new_block_hash); let cache3_clone = cache3.clone(); assert!(flush_to_base(cache3_clone, &mut parent).is_ok()); - for (key, utxo_entry) in &parent.utxos { - let outpoint = key; + for (outpoint, utxo_entry) in &parent.utxos { let utxo = cache3.utxo(outpoint); - assert_eq!(utxo_entry.utxo(), utxo); } } diff --git a/utxo/src/utxo_impl/test.rs b/utxo/src/utxo_impl/test.rs index d6b6a355e..4a91bb96c 100644 --- a/utxo/src/utxo_impl/test.rs +++ b/utxo/src/utxo_impl/test.rs @@ -25,9 +25,11 @@ use crate::utxo_impl::test_helper::{ }; use crate::utxo_impl::{UtxoSource, UtxoStatus}; use common::chain::{OutPoint, OutPointSourceId, Transaction, TxInput}; -use crypto::random::{make_pseudo_rng, seq}; +use crypto::random::{seq, Rng}; use itertools::Itertools; +use rstest::rstest; use std::collections::BTreeMap; +use test_utils::random::{make_seedable_rng, Seed}; /// Checks `add_utxo` method behaviour. /// # Arguments @@ -37,6 +39,7 @@ use std::collections::BTreeMap; /// `result_flags` - the result ( dirty/not, fresh/not ) after calling the `add_utxo` method. /// `op_result` - the result of calling `add_utxo` method, whether it succeeded or not. fn check_add_utxo( + rng: &mut impl Rng, cache_presence: Presence, cache_flags: Option, possible_overwrite: bool, @@ -44,10 +47,10 @@ fn check_add_utxo( op_result: Result<(), Error>, ) { let mut cache = UtxosCache::new_for_test(H256::random().into()); - let (_, outpoint) = insert_single_entry(&mut cache, &cache_presence, cache_flags, None); + let (_, outpoint) = insert_single_entry(rng, &mut cache, &cache_presence, cache_flags, None); // perform the add_utxo. - let (utxo, _) = create_utxo(0); + let (utxo, _) = create_utxo(rng, 0); let add_result = cache.add_utxo(&outpoint, utxo, possible_overwrite); assert_eq!(add_result, op_result); @@ -67,6 +70,7 @@ fn check_add_utxo( /// `cache_flags` - The flags of a utxo entry in a cache. /// `result_flags` - the result ( dirty/not, fresh/not ) after performing `spend_utxo`. fn check_spend_utxo( + rng: &mut impl Rng, parent_presence: Presence, cache_presence: Presence, cache_flags: Option, @@ -75,8 +79,13 @@ fn check_spend_utxo( ) { // initialize the parent cache. let mut parent = UtxosCache::new_for_test(H256::random().into()); - let (_, parent_outpoint) = - insert_single_entry(&mut parent, &parent_presence, Some(FRESH | DIRTY), None); + let (_, parent_outpoint) = insert_single_entry( + rng, + &mut parent, + &parent_presence, + Some(FRESH | DIRTY), + None, + ); // initialize the child cache let mut child = match parent_presence { @@ -85,6 +94,7 @@ fn check_spend_utxo( }; let (_, child_outpoint) = insert_single_entry( + rng, &mut child, &cache_presence, cache_flags, @@ -111,6 +121,7 @@ fn check_spend_utxo( /// `result` - The result of the parent after performing the `batch_write`. /// `result_flags` - the pair of `result`, indicating whether it is dirty/not, fresh/not or nothing at all. fn check_write_utxo( + rng: &mut impl Rng, parent_presence: Presence, child_presence: Presence, result: Result, @@ -120,7 +131,7 @@ fn check_write_utxo( ) { //initialize the parent cache let mut parent = UtxosCache::new_for_test(H256::random().into()); - let (_, outpoint) = insert_single_entry(&mut parent, &parent_presence, parent_flags, None); + let (_, outpoint) = insert_single_entry(rng, &mut parent, &parent_presence, parent_flags, None); let key = &outpoint; // prepare the map for batch write. @@ -136,7 +147,7 @@ fn check_write_utxo( panic!("Please use `Present` or `Spent` presence when child flags are specified."); } Present => { - let (utxo, _) = create_utxo(0); + let (utxo, _) = create_utxo(rng, 0); let entry = UtxoEntry::new(utxo, is_fresh, is_dirty); single_entry_map.insert(key.clone(), entry); } @@ -177,6 +188,7 @@ fn check_write_utxo( /// Checks the `get_mut_utxo` method behaviour. fn check_get_mut_utxo( + rng: &mut impl Rng, parent_presence: Presence, cache_presence: Presence, result_presence: Presence, @@ -184,14 +196,20 @@ fn check_get_mut_utxo( result_flags: Option, ) { let mut parent = UtxosCache::new_for_test(H256::random().into()); - let (parent_utxo, parent_outpoint) = - insert_single_entry(&mut parent, &parent_presence, Some(FRESH | DIRTY), None); + let (parent_utxo, parent_outpoint) = insert_single_entry( + rng, + &mut parent, + &parent_presence, + Some(FRESH | DIRTY), + None, + ); let mut child = match parent_presence { Absent => UtxosCache::new_for_test(H256::random().into()), _ => UtxosCache::new(&parent), }; let (child_utxo, child_outpoint) = insert_single_entry( + rng, &mut child, &cache_presence, cache_flags, @@ -252,177 +270,192 @@ fn check_get_mut_utxo( } } -#[test] +#[rstest] +#[trace] +#[case(Seed::from_entropy())] #[rustfmt::skip] -fn add_utxo_test() { +fn add_utxo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); /* CACHE PRESENCE CACHE Flags Possible RESULT flags RESULT of `add_utxo` method Overwrite */ - check_add_utxo(Absent, None, false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Absent, None, true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Absent, None, false, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Absent, None, true, Some(DIRTY), Ok(())); - check_add_utxo(Spent, Some(0), false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Spent, Some(0), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(0), false, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(0), true, Some(DIRTY), Ok(())); - check_add_utxo(Spent, Some(FRESH), false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Spent, Some(FRESH), true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(FRESH), false, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(FRESH), true, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Spent, Some(DIRTY), false, Some(DIRTY), Ok(())); - check_add_utxo(Spent, Some(DIRTY), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(DIRTY), false, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(DIRTY), true, Some(DIRTY), Ok(())); - check_add_utxo(Spent, Some(FRESH | DIRTY),false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Spent, Some(FRESH | DIRTY),true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(FRESH | DIRTY),false, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some(FRESH | DIRTY),true, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Present, Some(0), false, None, Err(OverwritingUtxo)); - check_add_utxo(Present, Some(0), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some(0), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some(0), true, Some(DIRTY), Ok(())); - check_add_utxo(Present, Some(FRESH), false, None, Err(OverwritingUtxo)); - check_add_utxo(Present, Some(FRESH), true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some(FRESH), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some(FRESH), true, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(Present, Some(DIRTY), false, None, Err(OverwritingUtxo)); - check_add_utxo(Present, Some(DIRTY), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some(DIRTY), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some(DIRTY), true, Some(DIRTY), Ok(())); - check_add_utxo(Present, Some(FRESH | DIRTY), false, None, Err(OverwritingUtxo)); - check_add_utxo(Present, Some(FRESH | DIRTY), true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some(FRESH | DIRTY), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some(FRESH | DIRTY), true, Some(FRESH | DIRTY), Ok(())); } -#[test] +#[rstest] +#[trace] +#[case(Seed::from_entropy())] #[rustfmt::skip] -fn spend_utxo_test() { +fn spend_utxo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); /* PARENT CACHE PRESENCE PRESENCE CACHE Flags RESULT RESULT Flags */ - check_spend_utxo(Absent, Absent, None, Err(Error::NoUtxoFound), None); - check_spend_utxo(Absent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(Absent, Spent, Some(FRESH), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(Absent, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(Absent, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(Absent, Present, Some(0), Ok(()), Some(DIRTY)); - check_spend_utxo(Absent, Present, Some(FRESH), Ok(()), None); - check_spend_utxo(Absent, Present, Some(DIRTY), Ok(()), Some(DIRTY)); - check_spend_utxo(Absent, Present, Some(FRESH | DIRTY), Ok(()), None); + check_spend_utxo(&mut rng, Absent, Absent, None, Err(Error::NoUtxoFound), None); + check_spend_utxo(&mut rng, Absent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Absent, Spent, Some(FRESH), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Absent, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Absent, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Absent, Present, Some(0), Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Absent, Present, Some(FRESH), Ok(()), None); + check_spend_utxo(&mut rng, Absent, Present, Some(DIRTY), Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Absent, Present, Some(FRESH | DIRTY), Ok(()), None); // this should fail, since there's nothing to remove. - check_spend_utxo(Spent, Absent, None, Err(Error::NoUtxoFound), None); - check_spend_utxo(Spent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Spent, Absent, None, Err(Error::NoUtxoFound), None); + check_spend_utxo(&mut rng, Spent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); // this should fail, as there's nothing to remove. - check_spend_utxo(Spent, Absent, Some(FRESH), Err(Error::NoUtxoFound), None); - check_spend_utxo(Spent, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(Spent, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(Spent, Present, Some(0), Ok(()), Some(DIRTY)); - check_spend_utxo(Spent, Present, Some(FRESH), Ok(()), None); - check_spend_utxo(Spent, Present, Some(DIRTY), Ok(()), Some(DIRTY)); - check_spend_utxo(Spent, Present, Some(FRESH | DIRTY), Ok(()), None); - check_spend_utxo(Present, Absent, None, Ok(()), Some(DIRTY)); - check_spend_utxo(Present, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(Present, Spent, Some(FRESH), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(Present, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(Present, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(Present, Present, Some(0), Ok(()), Some(DIRTY)); - check_spend_utxo(Present, Present, Some(FRESH), Ok(()), None); - check_spend_utxo(Present, Present, Some(DIRTY), Ok(()), Some(DIRTY)); - check_spend_utxo(Present, Present, Some(FRESH | DIRTY), Ok(()), None); + check_spend_utxo(&mut rng, Spent, Absent, Some(FRESH), Err(Error::NoUtxoFound), None); + check_spend_utxo(&mut rng, Spent, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Spent, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Spent, Present, Some(0), Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Spent, Present, Some(FRESH), Ok(()), None); + check_spend_utxo(&mut rng, Spent, Present, Some(DIRTY), Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Spent, Present, Some(FRESH | DIRTY), Ok(()), None); + check_spend_utxo(&mut rng, Present, Absent, None, Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Present, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Present, Spent, Some(FRESH), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Present, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Present, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Present, Present, Some(0), Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Present, Present, Some(FRESH), Ok(()), None); + check_spend_utxo(&mut rng, Present, Present, Some(DIRTY), Ok(()), Some(DIRTY)); + check_spend_utxo(&mut rng, Present, Present, Some(FRESH | DIRTY), Ok(()), None); } -#[test] +#[rstest] +#[trace] +#[case(Seed::from_entropy())] #[rustfmt::skip] -fn batch_write_test() { +fn batch_write_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); /* PARENT CACHE RESULT PRESENCE PRESENCE PRESENCE PARENT Flags CACHE Flags RESULT Flags */ - check_write_utxo(Absent, Absent, Ok(Absent), None, None, None); - check_write_utxo(Absent, Spent , Ok(Spent), None, Some(DIRTY), Some(DIRTY)); - check_write_utxo(Absent, Spent , Ok(Absent), None, Some(FRESH | DIRTY),None ); - check_write_utxo(Absent, Present, Ok(Present), None, Some(DIRTY), Some(DIRTY)); - check_write_utxo(Absent, Present, Ok(Present), None, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_write_utxo(Spent , Absent, Ok(Spent), Some(0), None, Some(0)); - check_write_utxo(Spent , Absent, Ok(Spent), Some(FRESH), None, Some(FRESH)); - check_write_utxo(Spent , Absent, Ok(Spent), Some(DIRTY), None, Some(DIRTY)); - check_write_utxo(Spent , Absent, Ok(Spent), Some(FRESH | DIRTY),None, Some(FRESH | DIRTY)); - check_write_utxo(Spent , Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Spent , Spent , Ok(Spent), Some(0), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(Spent , Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); - check_write_utxo(Spent , Spent , Ok(Absent), Some(FRESH), Some(FRESH | DIRTY),None); - check_write_utxo(Spent , Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Spent , Spent , Ok(Spent), Some(DIRTY), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(Spent , Spent , Ok(Absent), Some(FRESH | DIRTY),Some(DIRTY), None); - check_write_utxo(Spent , Spent , Ok(Absent), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); - check_write_utxo(Spent , Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(0), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(FRESH), Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(DIRTY), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(FRESH | DIRTY),Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(Spent , Present, Ok(Present), Some(FRESH | DIRTY),Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_write_utxo(Present, Absent, Ok(Present), Some(0), None, Some(0)); - check_write_utxo(Present, Absent, Ok(Present), Some(FRESH), None, Some(FRESH)); - check_write_utxo(Present, Absent, Ok(Present), Some(DIRTY), None, Some(DIRTY)); - check_write_utxo(Present, Absent, Ok(Present), Some(FRESH | DIRTY),None , Some(FRESH | DIRTY)); - check_write_utxo(Present, Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Present, Spent , Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY),None); - check_write_utxo(Present, Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); - check_write_utxo(Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY),None); - check_write_utxo(Present, Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Present, Spent , Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY),None); - check_write_utxo(Present, Spent , Ok(Absent), Some(FRESH | DIRTY),Some(DIRTY), None); - check_write_utxo(Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); - check_write_utxo(Present, Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Present, Present, Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY),None); - check_write_utxo(Present, Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY),None); - check_write_utxo(Present, Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(Present, Present, Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY),None); - check_write_utxo(Present, Present, Ok(Present), Some(FRESH | DIRTY),Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); + check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Absent, Spent , Ok(Absent), None, Some(FRESH | DIRTY),None ); + check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(0), None, Some(0)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH), None, Some(FRESH)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(DIRTY), None, Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH | DIRTY),None, Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(FRESH | DIRTY),Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(FRESH | DIRTY),Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY),Some(DIRTY), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(FRESH | DIRTY),Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(FRESH | DIRTY),Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY),Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY),Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(0), None, Some(0)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH), None, Some(FRESH)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(DIRTY), None, Some(DIRTY)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH | DIRTY),None , Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH | DIRTY),Some(DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH | DIRTY),Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); } -#[test] +#[rstest] +#[trace] +#[case(Seed::from_entropy())] #[rustfmt::skip] -fn access_utxo_test() { +fn access_utxo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); /* PARENT CACHE RESULT CACHE PRESENCE PRESENCE PRESENCE Flags RESULT Flags */ - check_get_mut_utxo(Absent, Absent, Absent, None, None); - check_get_mut_utxo(Absent, Spent , Spent , Some(0), Some(0)); - check_get_mut_utxo(Absent, Spent , Spent , Some(FRESH), Some(FRESH)); - check_get_mut_utxo(Absent, Spent , Spent , Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(Absent, Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(Absent, Present, Present, Some(0), Some(0)); - check_get_mut_utxo(Absent, Present, Present, Some(FRESH), Some(FRESH)); - check_get_mut_utxo(Absent, Present, Present, Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(Absent, Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(Spent , Absent, Absent, None, None); - check_get_mut_utxo(Spent , Spent , Spent , Some(0), Some(0)); - check_get_mut_utxo(Spent , Spent , Spent , Some(FRESH), Some(FRESH)); - check_get_mut_utxo(Spent , Spent , Spent , Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(Spent , Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(Spent , Present, Present, Some(0), Some(0)); - check_get_mut_utxo(Spent , Present, Present, Some(FRESH), Some(FRESH)); - check_get_mut_utxo(Spent , Present, Present, Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(Spent , Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(Present, Absent, Present, None, Some(0)); - check_get_mut_utxo(Present, Spent , Spent , Some(0), Some(0)); - check_get_mut_utxo(Present, Spent , Spent , Some(FRESH), Some(FRESH)); - check_get_mut_utxo(Present, Spent , Spent , Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(Present, Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(Present, Present, Present, Some(0), Some(0)); - check_get_mut_utxo(Present, Present, Present, Some(FRESH), Some(FRESH)); - check_get_mut_utxo(Present, Present, Present, Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(Present, Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Absent, Absent, Absent, None, None); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(0), Some(0)); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(FRESH), Some(FRESH)); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(DIRTY), Some(DIRTY)); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(0), Some(0)); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(FRESH), Some(FRESH)); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(DIRTY), Some(DIRTY)); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Spent , Absent, Absent, None, None); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(0), Some(0)); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(FRESH), Some(FRESH)); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(DIRTY), Some(DIRTY)); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(0), Some(0)); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(FRESH), Some(FRESH)); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(DIRTY), Some(DIRTY)); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Present, Absent, Present, None, Some(0)); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(0), Some(0)); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(FRESH), Some(FRESH)); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(DIRTY), Some(DIRTY)); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some(0), Some(0)); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some(FRESH), Some(FRESH)); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some(DIRTY), Some(DIRTY)); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); } -#[test] -fn derive_cache_test() { +#[rstest] +#[trace] +#[case(Seed::from_entropy())] +fn derive_cache_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); - let (utxo, outpoint_1) = create_utxo(10); + let (utxo, outpoint_1) = create_utxo(&mut rng, 10); assert!(cache.add_utxo(&outpoint_1, utxo, false).is_ok()); - let (utxo, outpoint_2) = create_utxo(20); + let (utxo, outpoint_2) = create_utxo(&mut rng, 20); assert!(cache.add_utxo(&outpoint_2, utxo, false).is_ok()); let mut extra_cache = cache.derive_cache(); @@ -431,20 +464,23 @@ fn derive_cache_test() { assert!(extra_cache.has_utxo(&outpoint_1)); assert!(extra_cache.has_utxo(&outpoint_2)); - let (utxo, outpoint) = create_utxo(30); + let (utxo, outpoint) = create_utxo(&mut rng, 30); assert!(extra_cache.add_utxo(&outpoint, utxo, true).is_ok()); assert!(!cache.has_utxo(&outpoint)); } -#[test] -fn blockchain_or_mempool_utxo_test() { +#[rstest] +#[trace] +#[case(Seed::from_entropy())] +fn blockchain_or_mempool_utxo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); - let (utxo, outpoint_1) = create_utxo(10); + let (utxo, outpoint_1) = create_utxo(&mut rng, 10); assert!(cache.add_utxo(&outpoint_1, utxo, false).is_ok()); - let (utxo, outpoint_2) = create_utxo_for_mempool(); + let (utxo, outpoint_2) = create_utxo_for_mempool(&mut rng); assert!(cache.add_utxo(&outpoint_2, utxo, false).is_ok()); let res = cache.utxo(&outpoint_2).expect("should countain utxo"); @@ -452,14 +488,17 @@ fn blockchain_or_mempool_utxo_test() { assert_eq!(res.source, UtxoSource::MemPool); } -#[test] -fn multiple_update_utxos_test() { +#[rstest] +#[trace] +#[case(Seed::from_entropy())] +fn multiple_update_utxos_test(#[case] seed: Seed) { use common::chain::signature::inputsig::InputWitness; + let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); // let's test `add_utxos` - let tx = Transaction::new(0x00, vec![], create_tx_outputs(10), 0x01).unwrap(); + let tx = Transaction::new(0x00, vec![], create_tx_outputs(&mut rng, 10), 0x01).unwrap(); assert!(cache.add_utxos(&tx, UtxoSource::BlockChain(BlockHeight::new(2)), false).is_ok()); // check that the outputs of tx are added in the cache. @@ -472,7 +511,6 @@ fn multiple_update_utxos_test() { }); // let's spend some outputs.; - let mut rng = make_pseudo_rng(); // randomly take half of the outputs to spend. let results = seq::index::sample(&mut rng, tx.outputs().len(), tx.outputs().len() / 2).into_vec(); diff --git a/utxo/src/utxo_impl/test_helper.rs b/utxo/src/utxo_impl/test_helper.rs index b72c8b167..78a24a57a 100644 --- a/utxo/src/utxo_impl/test_helper.rs +++ b/utxo/src/utxo_impl/test_helper.rs @@ -21,7 +21,7 @@ use common::chain::{ }; use common::primitives::{Amount, BlockHeight, Id, H256}; use crypto::key::{KeyKind, PrivateKey}; -use crypto::random::{make_pseudo_rng, seq, Rng}; +use crypto::random::{seq, Rng}; use itertools::Itertools; pub const FRESH: u8 = 1; @@ -37,10 +37,10 @@ pub enum Presence { use crate::UtxoStatus; use Presence::{Absent, Present, Spent}; -pub fn create_tx_outputs(size: u32) -> Vec { +pub fn create_tx_outputs(rng: &mut impl Rng, size: u32) -> Vec { let mut tx_outputs = vec![]; for _ in 0..size { - let random_amt = make_pseudo_rng().gen_range(1..u128::MAX); + let random_amt = rng.gen_range(1..u128::MAX); let (_, pub_key) = PrivateKey::new(KeyKind::RistrettoSchnorr); tx_outputs.push(TxOutput::new( Amount::from_atoms(random_amt), @@ -52,9 +52,8 @@ pub fn create_tx_outputs(size: u32) -> Vec { } /// randomly select half of the provided outpoints to spend, and returns it in a vec of structure of TxInput -pub fn create_tx_inputs(outpoints: &[OutPoint]) -> Vec { - let mut rng = make_pseudo_rng(); - let to_spend = seq::index::sample(&mut rng, outpoints.len(), outpoints.len() / 2).into_vec(); +pub fn create_tx_inputs(rng: &mut impl Rng, outpoints: &[OutPoint]) -> Vec { + let to_spend = seq::index::sample(rng, outpoints.len(), outpoints.len() / 2).into_vec(); to_spend .into_iter() .map(|idx| { @@ -78,24 +77,24 @@ pub fn convert_to_utxo(output: TxOutput, height: u64, output_idx: usize) -> (Out (outpoint, utxo) } -pub fn create_utxo(block_height: u64) -> (Utxo, OutPoint) { - inner_create_utxo(Some(block_height)) +pub fn create_utxo(rng: &mut impl Rng, block_height: u64) -> (Utxo, OutPoint) { + inner_create_utxo(rng, Some(block_height)) } -pub fn create_utxo_for_mempool() -> (Utxo, OutPoint) { - inner_create_utxo(None) +pub fn create_utxo_for_mempool(rng: &mut impl Rng) -> (Utxo, OutPoint) { + inner_create_utxo(rng, None) } /// returns a tuple of utxo and outpoint, for testing. -fn inner_create_utxo(block_height: Option) -> (Utxo, OutPoint) { +fn inner_create_utxo(rng: &mut impl Rng, block_height: Option) -> (Utxo, OutPoint) { // just a random value generated, and also a random `is_block_reward` value. - let rng = make_pseudo_rng().gen_range(0..u128::MAX); + let output_value = rng.gen_range(0..u128::MAX); let (_, pub_key) = PrivateKey::new(KeyKind::RistrettoSchnorr); let output = TxOutput::new( - Amount::from_atoms(rng), + Amount::from_atoms(output_value), OutputPurpose::Transfer(Destination::PublicKey(pub_key)), ); - let is_block_reward = rng % 3 == 0; + let is_block_reward = output_value % 3 == 0; // generate utxo let utxo = match block_height { @@ -127,13 +126,14 @@ fn inner_create_utxo(block_height: Option) -> (Utxo, OutPoint) { /// `cache_flags` - sets the entry of the utxo (fresh/not, dirty/not) /// `outpoint` - optional key to be used, rather than a randomly generated one. pub fn insert_single_entry( + rng: &mut impl Rng, cache: &mut UtxosCache, cache_presence: &Presence, cache_flags: Option, outpoint: Option, ) -> (Utxo, OutPoint) { - let rng_height = make_pseudo_rng().gen_range(0..(u64::MAX - 1)); - let (utxo, outpoint_x) = create_utxo(rng_height); + let rng_height = rng.gen_range(0..(u64::MAX - 1)); + let (utxo, outpoint_x) = create_utxo(rng, rng_height); let outpoint = outpoint.unwrap_or(outpoint_x); let key = &outpoint; diff --git a/utxo/src/utxo_impl/utxo_storage/test.rs b/utxo/src/utxo_impl/utxo_storage/test.rs index fddacc2d1..b3a06dc41 100644 --- a/utxo/src/utxo_impl/utxo_storage/test.rs +++ b/utxo/src/utxo_impl/utxo_storage/test.rs @@ -28,11 +28,14 @@ use common::chain::signature::inputsig::InputWitness; use common::chain::{Destination, OutPointSourceId, Transaction, TxInput, TxOutput}; use common::primitives::{Amount, BlockHeight, Idable}; use common::primitives::{Id, H256}; -use crypto::random::{make_pseudo_rng, seq, Rng}; +use crypto::random::{seq, Rng}; use itertools::Itertools; +use rstest::rstest; use std::collections::{BTreeMap, HashMap}; +use test_utils::random::{make_seedable_rng, Seed}; fn create_transactions( + rng: &mut impl Rng, inputs: Vec, max_num_of_outputs: usize, num_of_txs: usize, @@ -41,14 +44,13 @@ fn create_transactions( let input_size = inputs.len() / num_of_txs; // create the multiple transactions based on the inputs. - // TODO: use proper test random number generation for tests here inputs .chunks(input_size) .into_iter() .map(|inputs| { let outputs = if max_num_of_outputs > 1 { - let rnd = make_pseudo_rng().gen_range(1..max_num_of_outputs); - create_tx_outputs(rnd as u32) + let rnd = rng.gen_range(1..max_num_of_outputs); + create_tx_outputs(rng, rnd as u32) } else { vec![] }; @@ -60,24 +62,25 @@ fn create_transactions( } fn create_block( + rng: &mut impl Rng, prev_block_id: Id, inputs: Vec, max_num_of_outputs: usize, num_of_txs: usize, ) -> Block { - let txs = create_transactions(inputs, max_num_of_outputs, num_of_txs); + let txs = create_transactions(rng, inputs, max_num_of_outputs, num_of_txs); Block::new_with_no_consensus(txs, prev_block_id, BlockTimestamp::from_int_seconds(1)) .expect("should be able to create a block") } /// populate the db with random values, for testing. /// returns a tuple of the best block id and the outpoints (for spending) -fn initialize_db(tx_outputs_size: u32) -> (UtxosDBInMemoryImpl, Vec) { +fn initialize_db(rng: &mut impl Rng, tx_outputs_size: u32) -> (UtxosDBInMemoryImpl, Vec) { let best_block_id: Id = Id::new(H256::random()); let mut db_interface = UtxosDBInMemoryImpl::new(best_block_id, Default::default()); // let's populate the db with outputs. - let tx_outputs = create_tx_outputs(tx_outputs_size); + let tx_outputs = create_tx_outputs(rng, tx_outputs_size); // collect outpoints for spending later let outpoints = tx_outputs @@ -95,10 +98,10 @@ fn initialize_db(tx_outputs_size: u32) -> (UtxosDBInMemoryImpl, Vec) { (db_interface, outpoints) } -fn create_utxo_entries(num_of_utxos: u8) -> BTreeMap { +fn create_utxo_entries(rng: &mut impl Rng, num_of_utxos: u8) -> BTreeMap { let mut map = BTreeMap::new(); for _ in 0..num_of_utxos { - let (utxo, outpoint) = create_utxo(0); + let (utxo, outpoint) = create_utxo(rng, 0); let entry = UtxoEntry::new(utxo.clone(), true, true); map.insert(outpoint, entry); } @@ -106,16 +109,19 @@ fn create_utxo_entries(num_of_utxos: u8) -> BTreeMap { map } -#[test] +#[rstest] +#[trace] +#[case(Seed::from_entropy())] // This tests the utxo and the undo. This does not include testing the state of the block. -fn utxo_and_undo_test() { +fn utxo_and_undo_test(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); let tx_outputs_size = 3; let num_of_txs = 1; // initializing the db with existing utxos. - let (db_interface, outpoints) = initialize_db(tx_outputs_size); + let (db_interface, outpoints) = initialize_db(&mut rng, tx_outputs_size); // create the TxInputs for spending. - let expected_tx_inputs = create_tx_inputs(&outpoints); + let expected_tx_inputs = create_tx_inputs(&mut rng, &outpoints); // create the UtxosDB. let mut db_interface_clone = db_interface.clone(); @@ -146,6 +152,7 @@ fn utxo_and_undo_test() { // create a new block to spend. let block = create_block( + &mut rng, db_interface.best_block_hash(), expected_tx_inputs.clone(), 0, @@ -265,7 +272,7 @@ fn utxo_and_undo_test() { // For error testing: create dummy tx_inputs for spending. { let num_of_txs = 5; - let rnd = make_pseudo_rng().gen_range(num_of_txs..20); + let rnd = rng.gen_range(num_of_txs..20); let tx_inputs: Vec = (0..rnd) .into_iter() @@ -280,7 +287,7 @@ fn utxo_and_undo_test() { let id = db.best_block_hash(); // Create a dummy block. - let block = create_block(id, tx_inputs, 0, num_of_txs as usize); + let block = create_block(&mut rng, id, tx_inputs, 0, num_of_txs as usize); // Create a view. let mut view = db.derive_cache(); @@ -292,10 +299,13 @@ fn utxo_and_undo_test() { } } -#[test] -fn test_utxo() { +#[rstest] +#[trace] +#[case(Seed::from_entropy())] +fn test_utxo(#[case] seed: Seed) { common::concurrency::model(move || { - let utxos = create_utxo_entries(10); + let mut rng = make_seedable_rng(seed); + let utxos = create_utxo_entries(&mut rng, 10); let new_best_block_hash = Id::new(H256::random()); let utxos = ConsumedUtxoCache { @@ -312,8 +322,8 @@ fn test_utxo() { // randomly get a key for checking let keys = utxos.container.keys().collect_vec(); - let rng = make_pseudo_rng().gen_range(0..keys.len()); - let outpoint = keys[rng].clone(); + let key_index = rng.gen_range(0..keys.len()); + let outpoint = keys[key_index].clone(); // test the get_utxo let utxo_opt = utxo_db.utxo(&outpoint); @@ -330,7 +340,7 @@ fn test_utxo() { // try to write a non-dirty utxo { - let (utxo, outpoint) = create_utxo(1); + let (utxo, outpoint) = create_utxo(&mut rng, 1); let mut map = BTreeMap::new(); let entry = UtxoEntry::new(utxo, true, false); map.insert(outpoint.clone(), entry); @@ -348,8 +358,8 @@ fn test_utxo() { // write down a spent utxo. { - let rng = make_pseudo_rng().gen_range(0..keys.len()); - let outpoint_key = keys[rng]; + let key_index = rng.gen_range(0..keys.len()); + let outpoint_key = keys[key_index]; let outpoint = outpoint_key; let utxo = utxos .container From 49111076916bccdb2d2fe2947febc5d1ca74c7ed Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Thu, 4 Aug 2022 16:31:32 +0300 Subject: [PATCH 02/12] Clean up stack_flush_test --- utxo/src/utxo_impl/mod.rs | 3 +- utxo/src/utxo_impl/simulation.rs | 62 ++++++-------------------------- utxo/src/utxo_impl/test.rs | 18 +++++----- 3 files changed, 21 insertions(+), 62 deletions(-) diff --git a/utxo/src/utxo_impl/mod.rs b/utxo/src/utxo_impl/mod.rs index 20334960b..8e40303fe 100644 --- a/utxo/src/utxo_impl/mod.rs +++ b/utxo/src/utxo_impl/mod.rs @@ -448,10 +448,9 @@ impl<'a> UtxosView for UtxosCache<'a> { impl<'a> FlushableUtxoView for UtxosCache<'a> { fn batch_write(&mut self, utxo_entries: ConsumedUtxoCache) -> Result<(), Error> { for (key, entry) in utxo_entries.container { - let parent_entry = self.utxos.get(&key); - // Ignore non-dirty entries (optimization). if entry.is_dirty { + let parent_entry = self.utxos.get(&key); match parent_entry { None => { // The parent cache does not have an entry, while the child cache does. diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/utxo_impl/simulation.rs index 1c86b71e5..7cbae50f8 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/utxo_impl/simulation.rs @@ -16,14 +16,10 @@ //TODO: need a better way than this. use crate::{ - flush_to_base, - utxo_impl::test_helper::{create_utxo, DIRTY, FRESH}, - UtxoEntry, UtxoStatus, UtxosCache, UtxosView, -}; -use common::{ - chain::OutPoint, - primitives::{Id, H256}, + flush_to_base, utxo_impl::test_helper::create_utxo, UtxoEntry, UtxoStatus, UtxosCache, + UtxosView, }; +use common::{chain::OutPoint, primitives::H256}; use crypto::random::Rng; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; @@ -76,30 +72,10 @@ fn populate_cache<'a>( let to_spend = &outpoints[outp_idx]; // randomly select which flags should the spent utxo have. - // 0 - NOT FRESH, NOT DIRTY, 1 - FRESH, 2 - DIRTY, 3 - FRESH AND DIRTY - let flags = rng.gen_range(0..4u8); - - let new_entry = match flags { - FRESH => UtxoEntry { - status: UtxoStatus::Spent, - is_dirty: false, - is_fresh: true, - }, - DIRTY => UtxoEntry { - status: UtxoStatus::Spent, - is_dirty: true, - is_fresh: false, - }, - flag if flag == (FRESH + DIRTY) => UtxoEntry { - status: UtxoStatus::Spent, - is_dirty: true, - is_fresh: true, - }, - _ => UtxoEntry { - status: UtxoStatus::Spent, - is_dirty: false, - is_fresh: false, - }, + let new_entry = UtxoEntry { + status: UtxoStatus::Spent, + is_dirty: rng.gen::(), + is_fresh: rng.gen::(), }; cache.utxos.insert(to_spend.clone(), new_entry); }; @@ -109,40 +85,24 @@ fn populate_cache<'a>( } #[rstest] -#[case("8887871176094693639")] #[trace] #[case(Seed::from_entropy())] fn stack_flush_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); let mut outpoints: Vec = vec![]; - let mut parent = UtxosCache::new_for_test(H256::random().into()); let parent_clone = parent.clone(); - let new_utxo_count1 = rng.gen_range(0..50); - let (cache1, mut cache1_outps) = - populate_cache(&mut rng, &parent_clone, new_utxo_count1, &outpoints); + let (cache1, mut cache1_outps) = populate_cache(&mut rng, &parent_clone, 5000, &outpoints); outpoints.append(&mut cache1_outps); - let cache1_clone = cache1.clone(); - let new_utxo_count2 = rng.gen_range(0..50); - let (cache2, mut cache2_outps) = - populate_cache(&mut rng, &cache1_clone, new_utxo_count2, &outpoints); + let (cache2, mut cache2_outps) = populate_cache(&mut rng, &cache1, 5000, &outpoints); outpoints.append(&mut cache2_outps); - let cache2_clone = cache2.clone(); - let cache3_utxos_size = rng.gen_range(0..50); - let (mut cache3, mut cache3_outps) = - populate_cache(&mut rng, &cache2_clone, cache3_utxos_size, &outpoints); - outpoints.append(&mut cache3_outps); - - let new_block_hash = Id::new(H256::random()); - cache3.set_best_block(new_block_hash); - let cache3_clone = cache3.clone(); - assert!(flush_to_base(cache3_clone, &mut parent).is_ok()); + assert!(flush_to_base(cache2.clone(), &mut parent).is_ok()); for (outpoint, utxo_entry) in &parent.utxos { - let utxo = cache3.utxo(outpoint); + let utxo = cache2.utxo(outpoint); assert_eq!(utxo_entry.utxo(), utxo); } } diff --git a/utxo/src/utxo_impl/test.rs b/utxo/src/utxo_impl/test.rs index 4a91bb96c..695913e56 100644 --- a/utxo/src/utxo_impl/test.rs +++ b/utxo/src/utxo_impl/test.rs @@ -277,8 +277,8 @@ fn check_get_mut_utxo( fn add_utxo_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* - CACHE PRESENCE CACHE Flags Possible RESULT flags RESULT of `add_utxo` method - Overwrite + CACHE CACHE Flags Possible RESULT flags RESULT of `add_utxo` method + PRESENCE Overwrite */ check_add_utxo(&mut rng, Absent, None, false, Some(FRESH | DIRTY), Ok(())); check_add_utxo(&mut rng, Absent, None, true, Some(DIRTY), Ok(())); @@ -315,8 +315,8 @@ fn add_utxo_test(#[case] seed: Seed) { fn spend_utxo_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* - PARENT CACHE - PRESENCE PRESENCE CACHE Flags RESULT RESULT Flags + PARENT CACHE + PRESENCE PRESENCE CACHE Flags RESULT RESULT Flags */ check_spend_utxo(&mut rng, Absent, Absent, None, Err(Error::NoUtxoFound), None); check_spend_utxo(&mut rng, Absent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); @@ -356,8 +356,8 @@ fn spend_utxo_test(#[case] seed: Seed) { fn batch_write_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* - PARENT CACHE RESULT - PRESENCE PRESENCE PRESENCE PARENT Flags CACHE Flags RESULT Flags + PARENT CACHE RESULT + PRESENCE PRESENCE PRESENCE PARENT Flags CACHE Flags RESULT Flags */ check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some(DIRTY), Some(DIRTY)); @@ -413,8 +413,8 @@ fn batch_write_test(#[case] seed: Seed) { fn access_utxo_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* - PARENT CACHE RESULT CACHE - PRESENCE PRESENCE PRESENCE Flags RESULT Flags + PARENT CACHE RESULT CACHE + PRESENCE PRESENCE PRESENCE Flags RESULT Flags */ check_get_mut_utxo(&mut rng, Absent, Absent, Absent, None, None); check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(0), Some(0)); @@ -483,7 +483,7 @@ fn blockchain_or_mempool_utxo_test(#[case] seed: Seed) { let (utxo, outpoint_2) = create_utxo_for_mempool(&mut rng); assert!(cache.add_utxo(&outpoint_2, utxo, false).is_ok()); - let res = cache.utxo(&outpoint_2).expect("should countain utxo"); + let res = cache.utxo(&outpoint_2).expect("should contain utxo"); assert!(res.source_height().is_mempool()); assert_eq!(res.source, UtxoSource::MemPool); } From 73dde74f153746dacf3d568ccafcbc9b44e8aa26 Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Tue, 9 Aug 2022 11:23:49 +0300 Subject: [PATCH 03/12] Fix cache_simulation_test --- utxo/src/utxo_impl/simulation.rs | 217 +++++++++++++++++++++---------- utxo/src/utxo_impl/test.rs | 104 ++++++++------- 2 files changed, 205 insertions(+), 116 deletions(-) diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/utxo_impl/simulation.rs index 7cbae50f8..428627c11 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/utxo_impl/simulation.rs @@ -13,96 +13,173 @@ // See the License for the specific language governing permissions and // limitations under the License. -//TODO: need a better way than this. - -use crate::{ - flush_to_base, utxo_impl::test_helper::create_utxo, UtxoEntry, UtxoStatus, UtxosCache, - UtxosView, -}; +use crate::{utxo_impl::test_helper::create_utxo, FlushableUtxoView, Utxo, UtxosCache, UtxosView}; use common::{chain::OutPoint, primitives::H256}; use crypto::random::Rng; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; -fn populate_cache<'a>( +const NUM_SIMULATION_ITERATIONS: usize = 40_000; + +fn populate_cache( rng: &mut impl Rng, - parent: &'a UtxosCache, - new_utxo_count: u64, - existing_outpoints: &[OutPoint], -) -> (UtxosCache<'a>, Vec) { - let mut cache = UtxosCache::new(parent); - // tracker - let mut outpoints: Vec = vec![]; - - // let's add utxos based on `size`. - for _ in 0..new_utxo_count { - let block_height = rng.gen_range(0..new_utxo_count); - let (utxo, outpoint) = create_utxo(rng, block_height); - - let outpoint = if rng.gen::() && existing_outpoints.len() > 1 { - // setting a random existing 'spent' outpoint - let outpoint_idx = rng.gen_range(0..existing_outpoints.len()); - existing_outpoints[outpoint_idx].clone() + cache: &mut UtxosCache, + iterations_count: u64, + prev_result: &[(OutPoint, Utxo)], +) -> Vec<(OutPoint, Utxo)> { + let mut spent_an_entry = false; + let mut added_an_entry = false; + let mut removed_an_entry = false; + let mut verified_full_cache = false; + let mut missed_an_entry = false; + let mut found_an_entry = false; + // track outpoints and utxos + let mut result: Vec<(OutPoint, Utxo)> = Vec::new(); + + for _ in 0..iterations_count { + // select outpoint and utxo from existing or create new + let flip = rng.gen_range(0..3); + let (outpoint, utxo) = if flip == 0 && prev_result.len() > 1 { + let outpoint_idx = rng.gen_range(0..prev_result.len()); + (prev_result[outpoint_idx].0.clone(), None) + } else if flip == 1 && result.len() > 1 { + let outpoint_idx = rng.gen_range(0..result.len()); + (result[outpoint_idx].0.clone(), None) } else { - // tracking the outpoints - outpoints.push(outpoint.clone()); - outpoint + let block_height = rng.gen_range(0..iterations_count); + let (utxo, outpoint) = create_utxo(rng, block_height); + + result.push((outpoint.clone(), utxo.clone())); + (outpoint, Some(utxo)) }; - // randomly set the `possible_overwrite` - let possible_overwrite = rng.gen::(); - let _ = cache.add_utxo(&outpoint, utxo, possible_overwrite); + // spend utxo or add new random one + if cache.has_utxo(&outpoint) { + assert!(cache.spend_utxo(&outpoint).is_ok()); + spent_an_entry = true; + } else if utxo.is_some() { + let possible_overwrite = rng.gen::(); + assert!(cache.add_utxo(&outpoint, utxo.unwrap(), possible_overwrite).is_ok()); + added_an_entry = true; + } + + // every 10 iterations call uncache + if rng.gen_range(0..10) == 0 { + if rng.gen::() && prev_result.len() > 1 { + let idx = rng.gen_range(0..prev_result.len()); + cache.uncache(&prev_result[idx].0); + } else if result.len() > 1 { + let idx = rng.gen_range(0..result.len()); + cache.uncache(&result[idx].0); + } + removed_an_entry = true; + } + + // every 100 iterations check full cache + if rng.gen_range(0..100) == 0 { + for (outpoint, _) in &result { + let has_utxo = cache.has_utxo(&outpoint); + let utxo = cache.utxo(&outpoint); + assert_eq!(has_utxo, utxo.is_some()); + if utxo.is_some() { + assert!(cache.has_utxo_in_cache(outpoint)); + found_an_entry = true; + } + missed_an_entry = true; + } + verified_full_cache = true; + } } - // let's create half of the outpoints provided, to be marked as spent. - // there's a possibility when randomly the same outpoint is used, so half seems okay. - let spent_size = outpoints.len() / 2; - - for _ in 0..spent_size { - // randomly select which outpoint should be marked as "spent" - if rng.gen::() && existing_outpoints.len() > 1 { - // just call the `spend_utxo`. Does not matter if it removes the outpoint entirely, - // or just mark it as `spent`, - let outp_idx = rng.gen_range(0..existing_outpoints.len()); - let to_spend = &existing_outpoints[outp_idx]; - let _ = cache.spend_utxo(to_spend); - } else { - // just mark it as "spent" - let outp_idx = rng.gen_range(0..outpoints.len()); - let to_spend = &outpoints[outp_idx]; - - // randomly select which flags should the spent utxo have. - let new_entry = UtxoEntry { - status: UtxoStatus::Spent, - is_dirty: rng.gen::(), - is_fresh: rng.gen::(), - }; - cache.utxos.insert(to_spend.clone(), new_entry); - }; + //check coverage + assert!(spent_an_entry); + assert!(added_an_entry); + assert!(removed_an_entry); + assert!(verified_full_cache); + assert!(found_an_entry); + assert!(missed_an_entry); + + result +} + +fn simulation_step<'a>( + rng: &mut impl Rng, + result: &mut Vec<(OutPoint, Utxo)>, + parent: &'a UtxosCache, + steps: usize, +) -> Option> { + if steps == 0 { + return None; } + let mut cache = UtxosCache::new(parent); + + let mut new_cache_res = populate_cache(rng, &mut cache, 2000, &result); + result.append(&mut new_cache_res); + + let new_cache = simulation_step(rng, result, &cache, steps - 1); - (cache, outpoints) + if let Some(new_cache) = new_cache { + cache + .batch_write(new_cache.clone().consume()) + .expect("batch write must succeed"); + } + + Some(cache) } +// should ignore by default? because they take too long #[rstest] #[trace] #[case(Seed::from_entropy())] -fn stack_flush_test(#[case] seed: Seed) { +fn cache_simulation_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); - let mut outpoints: Vec = vec![]; - let mut parent = UtxosCache::new_for_test(H256::random().into()); - - let parent_clone = parent.clone(); - let (cache1, mut cache1_outps) = populate_cache(&mut rng, &parent_clone, 5000, &outpoints); - outpoints.append(&mut cache1_outps); + let mut result: Vec<(OutPoint, Utxo)> = Vec::new(); + let mut base = UtxosCache::new_for_test(H256::random().into()); - let (cache2, mut cache2_outps) = populate_cache(&mut rng, &cache1, 5000, &outpoints); - outpoints.append(&mut cache2_outps); + let new_cache = simulation_step(&mut rng, &mut result, &base, 10); + base.batch_write(new_cache.unwrap().consume()) + .expect("batch write must succeed"); - assert!(flush_to_base(cache2.clone(), &mut parent).is_ok()); - - for (outpoint, utxo_entry) in &parent.utxos { - let utxo = cache2.utxo(outpoint); - assert_eq!(utxo_entry.utxo(), utxo); + for (outpoint, _) in &result { + let has_utxo = base.has_utxo(&outpoint); + let utxo = base.utxo(&outpoint); + assert_eq!(has_utxo, utxo.is_some()); + if utxo.is_some() { + assert!(base.has_utxo_in_cache(outpoint)); + } } } + +//#[ignore] +//#[rstest] +//#[trace] +//#[case(Seed::from_entropy())] +//fn cache_simulation_test(#[case] seed: Seed) { +// let mut rng = make_seedable_rng(seed); +// let mut caches = vec![UtxosCache::new_for_test(H256::random().into())]; +// +// let mut expected_utxos: BTreeMap> = BTreeMap::new(); +// +// let txids = { +// let mut tmp: Vec> = Vec::with_capacity(NUM_SIMULATION_ITERATIONS / 8); +// for _ in 0..NUM_SIMULATION_ITERATIONS / 8 { +// tmp.push(Id::new(H256::random())); +// } +// tmp +// }; +// +// let outpoint_from_idx = +// |idx: usize| OutPoint::new(OutPointSourceId::Transaction(txids[idx]), 0); +// +// for _ in 0..NUM_SIMULATION_ITERATIONS { +// let caches_len = caches.len(); +// let cache = &mut caches[caches_len]; +// +// let txid = outpoint_from_idx(rng.gen_range(0..txids.len())); +// if cache.has_utxo(&txid) { +// } else { +// let block_height = rng.gen_range(0..NUM_SIMULATION_ITERATIONS); +// //let (utxo, outpoint) = create_utxo(&mut rng, block_height); +// } +// } +//} diff --git a/utxo/src/utxo_impl/test.rs b/utxo/src/utxo_impl/test.rs index 695913e56..dc13ece63 100644 --- a/utxo/src/utxo_impl/test.rs +++ b/utxo/src/utxo_impl/test.rs @@ -17,7 +17,9 @@ use common::primitives::{BlockHeight, Id, Idable, H256}; use crate::utxo_impl::test_helper::Presence::{Absent, Present, Spent}; use crate::Error::{self, FreshUtxoAlreadyExists, OverwritingUtxo}; -use crate::{ConsumedUtxoCache, FlushableUtxoView, Utxo, UtxoEntry, UtxosCache, UtxosView}; +use crate::{ + flush_to_base, ConsumedUtxoCache, FlushableUtxoView, Utxo, UtxoEntry, UtxosCache, UtxosView, +}; use crate::test_helper::create_tx_outputs; use crate::utxo_impl::test_helper::{ @@ -359,51 +361,51 @@ fn batch_write_test(#[case] seed: Seed) { PARENT CACHE RESULT PRESENCE PRESENCE PRESENCE PARENT Flags CACHE Flags RESULT Flags */ - check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); - check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Absent, Spent , Ok(Absent), None, Some(FRESH | DIRTY),None ); - check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(0), None, Some(0)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH), None, Some(FRESH)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(DIRTY), None, Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH | DIRTY),None, Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY),Some(DIRTY), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(FRESH | DIRTY),Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY),Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY),Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(0), None, Some(0)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH), None, Some(FRESH)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(DIRTY), None, Some(DIRTY)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH | DIRTY),None , Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH | DIRTY),Some(DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY),None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH | DIRTY),Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY),Some(FRESH | DIRTY),None); + check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); + check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Absent, Spent , Ok(Absent), None, Some(FRESH | DIRTY), None ); + check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(FRESH | DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(0), None, Some(0)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH), None, Some(FRESH)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(DIRTY), None, Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH | DIRTY), None, Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(FRESH | DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(FRESH | DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY), Some(DIRTY), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(FRESH | DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(FRESH | DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(FRESH | DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY), Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY), Some(FRESH | DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(0), None, Some(0)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH), None, Some(FRESH)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(DIRTY), None, Some(DIRTY)); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH | DIRTY), None , Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH | DIRTY), Some(DIRTY), None); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH | DIRTY), Some(DIRTY), Some(FRESH | DIRTY)); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY), Some(FRESH | DIRTY), None); } #[rstest] @@ -537,3 +539,13 @@ fn multiple_update_utxos_test(#[case] seed: Seed) { assert!(cache.utxo(input.outpoint()).is_none()); }); } + +#[test] +fn check_best_block_after_flush() { + let mut cache1 = UtxosCache::new_for_test(H256::random().into()); + let cache2 = UtxosCache::new_for_test(H256::random().into()); + assert_ne!(cache1.best_block_hash(), cache2.best_block_hash()); + let expected_hash = cache2.best_block_hash(); + assert!(flush_to_base(cache2, &mut cache1).is_ok()); + assert_eq!(expected_hash, cache1.best_block_hash()); +} From ba3779db9f775d1569246eafc5fa21653717cbf8 Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Tue, 9 Aug 2022 15:25:59 +0300 Subject: [PATCH 04/12] Cleanup --- utxo/src/utxo_impl/simulation.rs | 157 ++++++++++++++----------------- 1 file changed, 69 insertions(+), 88 deletions(-) diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/utxo_impl/simulation.rs index 2eda6eb5a..b529cd843 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/utxo_impl/simulation.rs @@ -19,12 +19,75 @@ use crypto::random::Rng; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; -const NUM_SIMULATION_ITERATIONS: usize = 40_000; +// This test creates an arbitrary long chain of caches. +// Every new cache is populated with random utxo values which can be created/spend/removed. +// When the last cache in the chain is created and modified the chain starts to fold by flushing +// result to a parent. One by one the chain is folded back to a single cache that is checked for consistency. +#[rstest] +#[trace] +#[case(Seed::from_entropy(), 10, 2000)] +fn cache_simulation_test( + #[case] seed: Seed, + #[case] nested_level: usize, + #[case] iterations_per_cache: usize, +) { + let mut rng = make_seedable_rng(seed); + let mut result: Vec<(OutPoint, Utxo)> = Vec::new(); + let mut base = UtxosCache::new_for_test(H256::random().into()); + + let new_cache = simulation_step( + &mut rng, + &mut result, + &base, + iterations_per_cache, + nested_level, + ); + base.batch_write(new_cache.unwrap().consume()) + .expect("batch write must succeed"); + + for (outpoint, _) in &result { + let has_utxo = base.has_utxo(&outpoint); + let utxo = base.utxo(&outpoint); + assert_eq!(has_utxo, utxo.is_some()); + if utxo.is_some() { + assert!(base.has_utxo_in_cache(outpoint)); + } + } +} +// Each step a new cache is created based on parent. Then it is randomly modified and passed to the +// next step as a parent. After recursion stops the resulting cache is returned and flushed to the base. +fn simulation_step<'a>( + rng: &mut impl Rng, + result: &mut Vec<(OutPoint, Utxo)>, + parent: &'a UtxosCache, + iterations_per_cache: usize, + nested_level: usize, +) -> Option> { + if nested_level == 0 { + return None; + } + + let mut cache = UtxosCache::new(parent); + let mut new_cache_res = populate_cache(rng, &mut cache, iterations_per_cache, &result); + result.append(&mut new_cache_res); + + let new_cache = simulation_step(rng, result, &cache, iterations_per_cache, nested_level - 1); + + if let Some(new_cache) = new_cache { + cache + .batch_write(new_cache.clone().consume()) + .expect("batch write must succeed"); + } + + Some(cache) +} + +// Perform random modification on a cache (add new, spend existing, uncache), tracking the coverage fn populate_cache( rng: &mut impl Rng, cache: &mut UtxosCache, - iterations_count: u64, + iterations_count: usize, prev_result: &[(OutPoint, Utxo)], ) -> Vec<(OutPoint, Utxo)> { let mut spent_an_entry = false; @@ -36,7 +99,7 @@ fn populate_cache( // track outpoints and utxos let mut result: Vec<(OutPoint, Utxo)> = Vec::new(); - for _ in 0..iterations_count { + for i in 0..iterations_count { // select outpoint and utxo from existing or create new let flip = rng.gen_range(0..3); let (outpoint, utxo) = if flip == 0 && prev_result.len() > 1 { @@ -47,7 +110,7 @@ fn populate_cache( (result[outpoint_idx].0.clone(), None) } else { let block_height = rng.gen_range(0..iterations_count); - let (utxo, outpoint) = create_utxo(rng, block_height); + let (utxo, outpoint) = create_utxo(rng, block_height.try_into().unwrap()); result.push((outpoint.clone(), utxo.clone())); (outpoint, Some(utxo)) @@ -64,7 +127,7 @@ fn populate_cache( } // every 10 iterations call uncache - if rng.gen_range(0..10) == 0 { + if i % 10 == 0 { if rng.gen::() && prev_result.len() > 1 { let idx = rng.gen_range(0..prev_result.len()); cache.uncache(&prev_result[idx].0); @@ -76,7 +139,7 @@ fn populate_cache( } // every 100 iterations check full cache - if rng.gen_range(0..100) == 0 { + if i % 100 == 0 { for (outpoint, _) in &result { let has_utxo = cache.has_utxo(&outpoint); let utxo = cache.utxo(&outpoint); @@ -101,85 +164,3 @@ fn populate_cache( result } - -fn simulation_step<'a>( - rng: &mut impl Rng, - result: &mut Vec<(OutPoint, Utxo)>, - parent: &'a UtxosCache, - steps: usize, -) -> Option> { - if steps == 0 { - return None; - } - let mut cache = UtxosCache::new(parent); - - let mut new_cache_res = populate_cache(rng, &mut cache, 2000, &result); - result.append(&mut new_cache_res); - - let new_cache = simulation_step(rng, result, &cache, steps - 1); - - if let Some(new_cache) = new_cache { - cache - .batch_write(new_cache.clone().consume()) - .expect("batch write must succeed"); - } - - Some(cache) -} - -// should ignore by default? because they take too long -#[rstest] -#[trace] -#[case(Seed::from_entropy())] -fn cache_simulation_test(#[case] seed: Seed) { - let mut rng = make_seedable_rng(seed); - let mut result: Vec<(OutPoint, Utxo)> = Vec::new(); - let mut base = UtxosCache::new_for_test(H256::random().into()); - - let new_cache = simulation_step(&mut rng, &mut result, &base, 10); - base.batch_write(new_cache.unwrap().consume()) - .expect("batch write must succeed"); - - for (outpoint, _) in &result { - let has_utxo = base.has_utxo(&outpoint); - let utxo = base.utxo(&outpoint); - assert_eq!(has_utxo, utxo.is_some()); - if utxo.is_some() { - assert!(base.has_utxo_in_cache(outpoint)); - } - } -} - -//#[ignore] -//#[rstest] -//#[trace] -//#[case(Seed::from_entropy())] -//fn cache_simulation_test(#[case] seed: Seed) { -// let mut rng = make_seedable_rng(seed); -// let mut caches = vec![UtxosCache::new_for_test(H256::random().into())]; -// -// let mut expected_utxos: BTreeMap> = BTreeMap::new(); -// -// let txids = { -// let mut tmp: Vec> = Vec::with_capacity(NUM_SIMULATION_ITERATIONS / 8); -// for _ in 0..NUM_SIMULATION_ITERATIONS / 8 { -// tmp.push(Id::new(H256::random())); -// } -// tmp -// }; -// -// let outpoint_from_idx = -// |idx: usize| OutPoint::new(OutPointSourceId::Transaction(txids[idx]), 0); -// -// for _ in 0..NUM_SIMULATION_ITERATIONS { -// let caches_len = caches.len(); -// let cache = &mut caches[caches_len]; -// -// let txid = outpoint_from_idx(rng.gen_range(0..txids.len())); -// if cache.has_utxo(&txid) { -// } else { -// let block_height = rng.gen_range(0..NUM_SIMULATION_ITERATIONS); -// //let (utxo, outpoint) = create_utxo(&mut rng, block_height); -// } -// } -//} From af169c303b116a97b1cab2d98eff382daff6fbce Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Tue, 9 Aug 2022 16:13:11 +0300 Subject: [PATCH 05/12] Fix clippy --- utxo/src/utxo_impl/simulation.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/utxo_impl/simulation.rs index b529cd843..3a4d84646 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/utxo_impl/simulation.rs @@ -46,8 +46,8 @@ fn cache_simulation_test( .expect("batch write must succeed"); for (outpoint, _) in &result { - let has_utxo = base.has_utxo(&outpoint); - let utxo = base.utxo(&outpoint); + let has_utxo = base.has_utxo(outpoint); + let utxo = base.utxo(outpoint); assert_eq!(has_utxo, utxo.is_some()); if utxo.is_some() { assert!(base.has_utxo_in_cache(outpoint)); @@ -69,7 +69,7 @@ fn simulation_step<'a>( } let mut cache = UtxosCache::new(parent); - let mut new_cache_res = populate_cache(rng, &mut cache, iterations_per_cache, &result); + let mut new_cache_res = populate_cache(rng, &mut cache, iterations_per_cache, result); result.append(&mut new_cache_res); let new_cache = simulation_step(rng, result, &cache, iterations_per_cache, nested_level - 1); @@ -141,8 +141,8 @@ fn populate_cache( // every 100 iterations check full cache if i % 100 == 0 { for (outpoint, _) in &result { - let has_utxo = cache.has_utxo(&outpoint); - let utxo = cache.utxo(&outpoint); + let has_utxo = cache.has_utxo(outpoint); + let utxo = cache.utxo(outpoint); assert_eq!(has_utxo, utxo.is_some()); if utxo.is_some() { assert!(cache.has_utxo_in_cache(outpoint)); From 53c582d7f3b688b8ed0adcf3e5ee1406e4206c0b Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Wed, 10 Aug 2022 14:47:09 +0300 Subject: [PATCH 06/12] Try fix coverage --- utxo/src/utxo_impl/simulation.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/utxo_impl/simulation.rs index 3a4d84646..9c64bfadb 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/utxo_impl/simulation.rs @@ -42,8 +42,8 @@ fn cache_simulation_test( iterations_per_cache, nested_level, ); - base.batch_write(new_cache.unwrap().consume()) - .expect("batch write must succeed"); + let consumed_cache = new_cache.unwrap().consume(); + base.batch_write(consumed_cache).expect("batch write must succeed"); for (outpoint, _) in &result { let has_utxo = base.has_utxo(outpoint); @@ -75,9 +75,8 @@ fn simulation_step<'a>( let new_cache = simulation_step(rng, result, &cache, iterations_per_cache, nested_level - 1); if let Some(new_cache) = new_cache { - cache - .batch_write(new_cache.clone().consume()) - .expect("batch write must succeed"); + let consumed_cache = new_cache.consume(); + cache.batch_write(consumed_cache).expect("batch write must succeed"); } Some(cache) From 32b6870e3d5cd624e5444c6b184ce993ee58bd8e Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Wed, 10 Aug 2022 14:07:13 +0300 Subject: [PATCH 07/12] Utxo crate cleanup --- chainstate-storage/src/internal/mod.rs | 3 +- chainstate-storage/src/lib.rs | 2 +- chainstate/src/detail/mod.rs | 2 +- utxo/src/{utxo_impl/mod.rs => cache.rs} | 179 ++++-------------- utxo/src/error.rs | 40 ++++ utxo/src/lib.rs | 44 ++--- .../utxo_storage => storage}/in_memory.rs | 7 +- .../utxo_storage => storage}/mod.rs | 11 +- .../utxo_storage => storage}/rw_impls.rs | 9 +- .../utxo_storage => storage}/test.rs | 33 ++-- .../utxo_storage => storage}/view_impls.rs | 21 +- utxo/src/{utxo_impl/test.rs => tests/mod.rs} | 85 +++++---- utxo/src/{utxo_impl => tests}/simulation.rs | 7 +- utxo/src/{utxo_impl => tests}/test_helper.rs | 37 ++-- utxo/src/undo.rs | 5 +- utxo/src/utxo.rs | 98 ++++++++++ utxo/src/view.rs | 48 +++++ 17 files changed, 338 insertions(+), 293 deletions(-) rename utxo/src/{utxo_impl/mod.rs => cache.rs} (76%) create mode 100644 utxo/src/error.rs rename utxo/src/{utxo_impl/utxo_storage => storage}/in_memory.rs (94%) rename utxo/src/{utxo_impl/utxo_storage => storage}/mod.rs (91%) rename utxo/src/{utxo_impl/utxo_storage => storage}/rw_impls.rs (99%) rename utxo/src/{utxo_impl/utxo_storage => storage}/test.rs (93%) rename utxo/src/{utxo_impl/utxo_storage => storage}/view_impls.rs (89%) rename utxo/src/{utxo_impl/test.rs => tests/mod.rs} (91%) rename utxo/src/{utxo_impl => tests}/simulation.rs (96%) rename utxo/src/{utxo_impl => tests}/test_helper.rs (86%) create mode 100644 utxo/src/utxo.rs create mode 100644 utxo/src/view.rs diff --git a/chainstate-storage/src/internal/mod.rs b/chainstate-storage/src/internal/mod.rs index 5a6a1c82a..89948b0f9 100644 --- a/chainstate-storage/src/internal/mod.rs +++ b/chainstate-storage/src/internal/mod.rs @@ -21,8 +21,7 @@ use common::chain::{Block, GenBlock, OutPoint, OutPointSourceId}; use common::primitives::{BlockHeight, Id, Idable}; use serialization::{Codec, Decode, DecodeAll, Encode}; use storage::traits::{self, MapMut, MapRef, TransactionRo, TransactionRw}; -use utxo::utxo_storage::{UtxosStorageRead, UtxosStorageWrite}; -use utxo::{BlockUndo, Utxo}; +use utxo::{BlockUndo, Utxo, UtxosStorageRead, UtxosStorageWrite}; use crate::{BlockchainStorage, BlockchainStorageRead, BlockchainStorageWrite, Transactional}; diff --git a/chainstate-storage/src/lib.rs b/chainstate-storage/src/lib.rs index c34dc33ce..aa1e9367a 100644 --- a/chainstate-storage/src/lib.rs +++ b/chainstate-storage/src/lib.rs @@ -21,7 +21,7 @@ use common::chain::OutPointSourceId; use common::chain::{Block, GenBlock}; use common::primitives::{BlockHeight, Id}; use storage::traits; -use utxo::utxo_storage::{UtxosStorageRead, UtxosStorageWrite}; +use utxo::{UtxosStorageRead, UtxosStorageWrite}; mod internal; #[cfg(any(test, feature = "mock"))] diff --git a/chainstate/src/detail/mod.rs b/chainstate/src/detail/mod.rs index e3075ad39..ac408c823 100644 --- a/chainstate/src/detail/mod.rs +++ b/chainstate/src/detail/mod.rs @@ -23,7 +23,7 @@ use itertools::Itertools; use logging::log; use std::sync::Arc; use utils::eventhandler::{EventHandler, EventsController}; -use utxo::utxo_storage::UtxosDBMut; +use utxo::UtxosDBMut; mod consensus_validator; mod orphan_blocks; diff --git a/utxo/src/utxo_impl/mod.rs b/utxo/src/cache.rs similarity index 76% rename from utxo/src/utxo_impl/mod.rs rename to utxo/src/cache.rs index acc83a410..ee8b5d3da 100644 --- a/utxo/src/utxo_impl/mod.rs +++ b/utxo/src/cache.rs @@ -13,135 +13,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -//TODO: remove once the functions are used. -#![allow(dead_code)] -use crate::{Error, TxUndo}; -use common::chain::{GenBlock, OutPoint, OutPointSourceId, Transaction, TxOutput}; -use common::primitives::{BlockHeight, Id, Idable}; +use crate::{Error, FlushableUtxoView, TxUndo, Utxo, UtxoSource, UtxosView}; +use common::{ + chain::{GenBlock, OutPoint, OutPointSourceId, Transaction}, + primitives::{BlockHeight, Id, Idable}, +}; use logging::log; use serialization::{Decode, Encode}; -use std::collections::BTreeMap; -use std::fmt::{Debug, Formatter}; - -pub mod utxo_storage; - -//todo: proper placement and derivation of this max -const MAX_OUTPUTS_PER_BLOCK: u32 = 500; - -// Determines whether the utxo is for the blockchain of for mempool -#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] -pub enum UtxoSource { - /// At which height this containing tx was included in the active block chain - BlockChain(BlockHeight), - MemPool, -} - -impl UtxoSource { - fn is_mempool(&self) -> bool { - match self { - UtxoSource::BlockChain(_) => false, - UtxoSource::MemPool => true, - } - } - - fn blockchain_height(&self) -> Result { - match self { - UtxoSource::BlockChain(h) => Ok(*h), - UtxoSource::MemPool => Err(crate::Error::NoBlockchainHeightFound), - } - } -} - -/// The Unspent Transaction Output -#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] -pub struct Utxo { - output: TxOutput, - is_block_reward: bool, - /// identifies whether the utxo is for the blockchain or for mempool. - source: UtxoSource, -} - -impl Utxo { - pub fn new(output: TxOutput, is_block_reward: bool, height: BlockHeight) -> Self { - Self { - output, - is_block_reward, - source: UtxoSource::BlockChain(height), - } - } - - /// a utxo for mempool, that does not need the block height. - pub fn new_for_mempool(output: TxOutput, is_block_reward: bool) -> Self { - Self { - output, - is_block_reward, - source: UtxoSource::MemPool, - } - } - - pub fn is_block_reward(&self) -> bool { - self.is_block_reward - } - - pub fn source_height(&self) -> &UtxoSource { - &self.source - } - - pub fn output(&self) -> &TxOutput { - &self.output - } - - pub fn set_height(&mut self, value: UtxoSource) { - self.source = value - } -} - -pub trait UtxosView { - /// Retrieves utxo. - fn utxo(&self, outpoint: &OutPoint) -> Option; - - /// Checks whether outpoint is unspent. - fn has_utxo(&self, outpoint: &OutPoint) -> bool; - - /// Retrieves the block hash of the best block in this view - fn best_block_hash(&self) -> Id; - - /// Estimated size of the whole view (None if not implemented) - fn estimated_size(&self) -> Option; - - fn derive_cache(&self) -> UtxosCache; -} +use std::{ + collections::BTreeMap, + fmt::{Debug, Formatter}, +}; #[derive(Clone)] pub struct ConsumedUtxoCache { - container: BTreeMap, - best_block: Id, -} - -pub trait FlushableUtxoView { - /// Performs bulk modification - fn batch_write(&mut self, utxos: ConsumedUtxoCache) -> Result<(), Error>; -} - -// flush the cache into the provided base. This will consume the cache and throw it away. -// It uses the batch_write function since it's available in different kinds of views. -pub fn flush_to_base(cache: UtxosCache, base: &mut T) -> Result<(), Error> { - base.batch_write(cache.consume()) + pub(crate) container: BTreeMap, + pub(crate) best_block: Id, } #[derive(Clone)] pub struct UtxosCache<'a> { parent: Option<&'a dyn UtxosView>, current_block_hash: Id, - utxos: BTreeMap, - //TODO: do we need this? + pub(crate) utxos: BTreeMap, + #[allow(dead_code)] memory_usage: usize, } /// Tells the state of the utxo #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] #[allow(clippy::large_enum_variant)] -pub enum UtxoStatus { +pub(crate) enum UtxoStatus { Spent, Entry(Utxo), } @@ -167,9 +69,12 @@ pub(crate) struct UtxoEntry { } impl UtxoEntry { - pub fn new(utxo: Utxo, is_fresh: bool, is_dirty: bool) -> UtxoEntry { + pub fn new(utxo: Option, is_fresh: bool, is_dirty: bool) -> UtxoEntry { UtxoEntry { - status: UtxoStatus::Entry(utxo), + status: match utxo { + Some(utxo) => UtxoStatus::Entry(utxo), + None => UtxoStatus::Spent, + }, is_dirty, is_fresh, } @@ -268,12 +173,8 @@ impl<'a> UtxosCache<'a> { // by default no overwrite allowed. let overwrite = check_for_overwrite && self.has_utxo(&outpoint); - let utxo = Utxo { - output: output.clone(), - // TODO: where do we get the block reward from the transaction? - is_block_reward: false, - source: source.clone(), - }; + // TODO: where do we get the block reward from the transaction? + let utxo = Utxo::new(output.clone(), false, source.clone()); self.add_utxo(&outpoint, utxo, overwrite)?; } @@ -333,7 +234,7 @@ impl<'a> UtxosCache<'a> { }; // create a new entry - let new_entry = UtxoEntry::new(utxo, is_fresh, true); + let new_entry = UtxoEntry::new(Some(utxo), is_fresh, true); // TODO: update the memory usage // self.memory_usage should be added based on this new entry. @@ -379,23 +280,24 @@ impl<'a> UtxosCache<'a> { let utxo: &mut UtxoEntry = self.utxos.entry(outpoint.clone()).or_insert_with(|| { //TODO: update the memory storage here - UtxoEntry::new(utxo, status.is_fresh, status.is_dirty) + UtxoEntry::new(Some(utxo), status.is_fresh, status.is_dirty) }); utxo.utxo_mut() } /// removes the utxo in the cache with the outpoint - pub(crate) fn uncache(&mut self, outpoint: &OutPoint) -> Option { + pub fn uncache(&mut self, outpoint: &OutPoint) -> Result<(), Error> { let key = outpoint; if let Some(entry) = self.utxos.get(key) { // see bitcoin's Uncache. if !entry.is_fresh && !entry.is_dirty { //todo: decrement the memory usage - return self.utxos.remove(key); + self.utxos.remove(key); + return Ok(()); } } - None + Err(Error::NoUtxoFound) } pub fn consume(self) -> ConsumedUtxoCache { @@ -503,24 +405,14 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { } } -#[cfg(test)] -mod test; - -#[cfg(test)] -pub mod test_helper; - -#[cfg(test)] -mod simulation; - #[cfg(test)] mod unit_test { + use super::{Error, UtxosCache}; + use crate::tests::test_helper::{insert_single_entry, Presence, DIRTY, FRESH}; use common::primitives::H256; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; - use crate::test_helper::{insert_single_entry, Presence, DIRTY, FRESH}; - use crate::UtxosCache; - #[rstest] #[trace] #[case(Seed::from_entropy())] @@ -529,30 +421,29 @@ mod unit_test { let mut cache = UtxosCache::new_for_test(H256::random().into()); // when the entry is not dirty and not fresh - let (utxo, outp) = + let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(0), None); - let res = cache.uncache(&outp).expect("should return an entry"); - assert_eq!(res.utxo(), Some(utxo)); + assert!(cache.uncache(&outp).is_ok()); assert!(!cache.has_utxo_in_cache(&outp)); // when the outpoint does not exist. let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Absent, None, None); - assert_eq!(cache.uncache(&outp), None); + assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); assert!(!cache.has_utxo_in_cache(&outp)); // when the entry is fresh, entry cannot be removed. let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(FRESH), None); - assert_eq!(cache.uncache(&outp), None); + assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); // when the entry is dirty, entry cannot be removed. let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(DIRTY), None); - assert_eq!(cache.uncache(&outp), None); + assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); // when the entry is both fresh and dirty, entry cannot be removed. let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(FRESH), None); - assert_eq!(cache.uncache(&outp), None); + assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); } } diff --git a/utxo/src/error.rs b/utxo/src/error.rs new file mode 100644 index 000000000..0a0182fb4 --- /dev/null +++ b/utxo/src/error.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use thiserror::Error; + +#[derive(Error, Debug, Eq, PartialEq)] +pub enum Error { + #[error("Attempted to overwrite an existing utxo")] + OverwritingUtxo, + #[error( + "The utxo was marked FRESH in the child cache, but the utxo exists in the parent cache. This can be considered a fatal error." + )] + FreshUtxoAlreadyExists, + #[error("Attempted to spend a UTXO that's already spent")] + UtxoAlreadySpent, + #[error("Attempted to spend a non-existing UTXO")] + NoUtxoFound, + #[error("Attempted to get the block height of a UTXO source that is based on the mempool")] + NoBlockchainHeightFound, + #[error("Database error: `{0}`")] + DBError(String), +} + +impl From for Error { + fn from(e: chainstate_types::storage_result::Error) -> Self { + Error::DBError(format!("{:?}", e)) + } +} diff --git a/utxo/src/lib.rs b/utxo/src/lib.rs index a0d4b3d6c..035c974a5 100644 --- a/utxo/src/lib.rs +++ b/utxo/src/lib.rs @@ -13,35 +13,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod cache; +mod error; +mod storage; mod undo; -mod utxo_impl; +mod utxo; +mod view; -pub use undo::*; -pub use utxo_impl::*; +pub use crate::{ + cache::{ConsumedUtxoCache, UtxosCache}, + error::Error, + storage::{UtxosDB, UtxosDBMut, UtxosStorageRead, UtxosStorageWrite}, + undo::{BlockUndo, TxUndo}, + utxo::{Utxo, UtxoSource}, + view::{flush_to_base, FlushableUtxoView, UtxosView}, +}; -use thiserror::Error; - -#[allow(dead_code)] -#[derive(Error, Debug, Eq, PartialEq)] -pub enum Error { - #[error("Attempted to overwrite an existing utxo")] - OverwritingUtxo, - #[error( - "The utxo was marked FRESH in the child cache, but the utxo exists in the parent cache. This can be considered a fatal error." - )] - FreshUtxoAlreadyExists, - #[error("Attempted to spend a UTXO that's already spent")] - UtxoAlreadySpent, - #[error("Attempted to spend a non-existing UTXO")] - NoUtxoFound, - #[error("Attempted to get the block height of a UTXO source that is based on the mempool")] - NoBlockchainHeightFound, - #[error("Database error: `{0}`")] - DBError(String), -} - -impl From for Error { - fn from(e: chainstate_types::storage_result::Error) -> Self { - Error::DBError(format!("{:?}", e)) - } -} +#[cfg(test)] +mod tests; diff --git a/utxo/src/utxo_impl/utxo_storage/in_memory.rs b/utxo/src/storage/in_memory.rs similarity index 94% rename from utxo/src/utxo_impl/utxo_storage/in_memory.rs rename to utxo/src/storage/in_memory.rs index 8181b445c..09aa132f2 100644 --- a/utxo/src/utxo_impl/utxo_storage/in_memory.rs +++ b/utxo/src/storage/in_memory.rs @@ -13,8 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, HashMap}; - use super::{UtxosStorageRead, UtxosStorageWrite}; use crate::{BlockUndo, Utxo, UtxosCache, UtxosView}; use chainstate_types::storage_result::Error; @@ -22,6 +20,7 @@ use common::{ chain::{Block, GenBlock, OutPoint}, primitives::Id, }; +use std::collections::BTreeMap; #[derive(Clone)] pub struct UtxosDBInMemoryImpl { @@ -31,7 +30,8 @@ pub struct UtxosDBInMemoryImpl { } impl UtxosDBInMemoryImpl { - pub fn new(best_block: Id, initial_utxos: BTreeMap) -> Self { + #[allow(dead_code)] + pub fn new(best_block: Id, _initial_utxos: BTreeMap) -> Self { Self { store: BTreeMap::new(), undo_store: BTreeMap::new(), @@ -39,6 +39,7 @@ impl UtxosDBInMemoryImpl { } } + #[allow(dead_code)] pub(crate) fn internal_store(&mut self) -> &BTreeMap { &self.store } diff --git a/utxo/src/utxo_impl/utxo_storage/mod.rs b/utxo/src/storage/mod.rs similarity index 91% rename from utxo/src/utxo_impl/utxo_storage/mod.rs rename to utxo/src/storage/mod.rs index 521be3e1e..3e529fb69 100644 --- a/utxo/src/utxo_impl/utxo_storage/mod.rs +++ b/utxo/src/storage/mod.rs @@ -13,19 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(dead_code, unused_variables, unused_imports)] -// todo: remove ^ when all untested codes are tested - -pub mod in_memory; +mod in_memory; mod rw_impls; mod view_impls; -use std::collections::BTreeMap; - use crate::{BlockUndo, FlushableUtxoView, Utxo, UtxosView}; use chainstate_types::storage_result::Error; use common::{ - chain::{Block, ChainConfig, GenBlock, OutPoint, TxOutput}, + chain::{Block, ChainConfig, GenBlock, OutPoint}, primitives::{BlockHeight, Id}, }; @@ -93,7 +88,7 @@ impl<'a, S: UtxosStorageWrite> UtxosDBMut<'a, S> { utxos_cache .add_utxo( &OutPoint::new(genesis_id.into(), index as u32), - Utxo::new(output.clone(), false, BlockHeight::new(0)), + Utxo::new_for_blockchain(output.clone(), false, BlockHeight::new(0)), false, ) .expect("Adding genesis utxo failed"); diff --git a/utxo/src/utxo_impl/utxo_storage/rw_impls.rs b/utxo/src/storage/rw_impls.rs similarity index 99% rename from utxo/src/utxo_impl/utxo_storage/rw_impls.rs rename to utxo/src/storage/rw_impls.rs index 8a59d0d57..a7c9d727b 100644 --- a/utxo/src/utxo_impl/utxo_storage/rw_impls.rs +++ b/utxo/src/storage/rw_impls.rs @@ -13,17 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::{UtxosDB, UtxosDBMut, UtxosStorageRead, UtxosStorageWrite}; +use crate::{BlockUndo, Utxo}; +use chainstate_types::storage_result::Error as StorageError; use common::{ chain::{Block, GenBlock, OutPoint}, primitives::Id, }; -use crate::{BlockUndo, Utxo}; - -use super::{UtxosDB, UtxosDBMut, UtxosStorageRead, UtxosStorageWrite}; - -use chainstate_types::storage_result::Error as StorageError; - impl<'a, S: UtxosStorageRead> UtxosStorageRead for UtxosDBMut<'a, S> { fn get_utxo(&self, outpoint: &OutPoint) -> Result, StorageError> { self.0.get_utxo(outpoint) diff --git a/utxo/src/utxo_impl/utxo_storage/test.rs b/utxo/src/storage/test.rs similarity index 93% rename from utxo/src/utxo_impl/utxo_storage/test.rs rename to utxo/src/storage/test.rs index 0cdbe107c..2e6d32163 100644 --- a/utxo/src/utxo_impl/utxo_storage/test.rs +++ b/utxo/src/storage/test.rs @@ -13,23 +13,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::in_memory::UtxosDBInMemoryImpl; -use super::*; -use crate::test_helper::{convert_to_utxo, create_tx_inputs, create_tx_outputs, create_utxo}; -use crate::utxo_impl::{ - flush_to_base, utxo_storage::UtxosDB, FlushableUtxoView, Utxo, UtxoEntry, UtxosCache, UtxosView, +use super::{in_memory::UtxosDBInMemoryImpl, *}; +use crate::{ + cache::UtxoEntry, + flush_to_base, + tests::test_helper::{convert_to_utxo, create_tx_inputs, create_tx_outputs, create_utxo}, + ConsumedUtxoCache, FlushableUtxoView, UtxosCache, UtxosView, }; -use crate::ConsumedUtxoCache; -use common::chain::block::timestamp::BlockTimestamp; -use common::chain::config::create_mainnet; -use common::chain::signature::inputsig::InputWitness; -use common::chain::{Destination, OutPointSourceId, Transaction, TxInput, TxOutput}; -use common::primitives::{Amount, BlockHeight, Idable}; -use common::primitives::{Id, H256}; -use crypto::random::{seq, Rng}; +use common::{ + chain::{ + block::timestamp::BlockTimestamp, signature::inputsig::InputWitness, OutPointSourceId, + Transaction, TxInput, + }, + primitives::{BlockHeight, Id, Idable, H256}, +}; +use crypto::random::Rng; use itertools::Itertools; use rstest::rstest; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use test_utils::random::{make_seedable_rng, Seed}; fn create_transactions( @@ -100,7 +101,7 @@ fn create_utxo_entries(rng: &mut impl Rng, num_of_utxos: u8) -> BTreeMap(db: &S, outpoint: &OutPoint) -> Option { db.get_utxo(outpoint).unwrap_or_else(|e| { panic!( @@ -49,7 +41,7 @@ mod utxosdb_utxosview_impls { e)) } - pub fn estimated_size(db: &S) -> Option { + pub fn estimated_size(_db: &S) -> Option { None } @@ -105,10 +97,7 @@ impl<'a, S: UtxosStorageWrite> UtxosView for UtxosDBMut<'a, S> { } impl<'a, S: UtxosStorageWrite> FlushableUtxoView for UtxosDBMut<'a, S> { - fn batch_write( - &mut self, - utxos: crate::utxo_impl::ConsumedUtxoCache, - ) -> Result<(), crate::Error> { + fn batch_write(&mut self, utxos: ConsumedUtxoCache) -> Result<(), crate::Error> { // check each entry if it's dirty. Only then will the db be updated. for (key, entry) in utxos.container { let outpoint = &key; diff --git a/utxo/src/utxo_impl/test.rs b/utxo/src/tests/mod.rs similarity index 91% rename from utxo/src/utxo_impl/test.rs rename to utxo/src/tests/mod.rs index 3d770c5b3..332352e26 100644 --- a/utxo/src/utxo_impl/test.rs +++ b/utxo/src/tests/mod.rs @@ -13,20 +13,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::primitives::{BlockHeight, Id, Idable, H256}; +pub mod simulation; +pub mod test_helper; -use crate::utxo_impl::test_helper::Presence::{Absent, Present, Spent}; -use crate::Error::{self, FreshUtxoAlreadyExists, OverwritingUtxo}; use crate::{ - flush_to_base, ConsumedUtxoCache, FlushableUtxoView, Utxo, UtxoEntry, UtxosCache, UtxosView, + cache::UtxoEntry, + flush_to_base, + tests::test_helper::{ + Presence::{self, *}, + DIRTY, FRESH, + }, + ConsumedUtxoCache, + Error::{self, *}, + FlushableUtxoView, Utxo, UtxoSource, UtxosCache, UtxosView, }; - -use crate::test_helper::create_tx_outputs; -use crate::utxo_impl::test_helper::{ - check_flags, create_utxo, create_utxo_for_mempool, insert_single_entry, Presence, DIRTY, FRESH, +use common::{ + chain::{OutPoint, OutPointSourceId, Transaction, TxInput}, + primitives::{BlockHeight, Id, Idable, H256}, }; -use crate::utxo_impl::{UtxoSource, UtxoStatus}; -use common::chain::{OutPoint, OutPointSourceId, Transaction, TxInput}; use crypto::random::{seq, Rng}; use itertools::Itertools; use rstest::rstest; @@ -49,10 +53,11 @@ fn check_add_utxo( op_result: Result<(), Error>, ) { let mut cache = UtxosCache::new_for_test(H256::random().into()); - let (_, outpoint) = insert_single_entry(rng, &mut cache, &cache_presence, cache_flags, None); + let (_, outpoint) = + test_helper::insert_single_entry(rng, &mut cache, &cache_presence, cache_flags, None); // perform the add_utxo. - let (utxo, _) = create_utxo(rng, 0); + let (utxo, _) = test_helper::create_utxo(rng, 0); let add_result = cache.add_utxo(&outpoint, utxo, possible_overwrite); assert_eq!(add_result, op_result); @@ -61,7 +66,7 @@ fn check_add_utxo( let key = &outpoint; let ret_value = cache.utxos.get(key); - check_flags(ret_value, result_flags, false); + test_helper::check_flags(ret_value, result_flags, false); } } @@ -81,7 +86,7 @@ fn check_spend_utxo( ) { // initialize the parent cache. let mut parent = UtxosCache::new_for_test(H256::random().into()); - let (_, parent_outpoint) = insert_single_entry( + let (_, parent_outpoint) = test_helper::insert_single_entry( rng, &mut parent, &parent_presence, @@ -95,7 +100,7 @@ fn check_spend_utxo( _ => UtxosCache::new(&parent), }; - let (_, child_outpoint) = insert_single_entry( + let (_, child_outpoint) = test_helper::insert_single_entry( rng, &mut child, &cache_presence, @@ -111,7 +116,7 @@ fn check_spend_utxo( let key = &child_outpoint; let ret_value = child.utxos.get(key); - check_flags(ret_value, result_flags, true); + test_helper::check_flags(ret_value, result_flags, true); } /// Checks `batch_write` method behaviour. @@ -133,7 +138,8 @@ fn check_write_utxo( ) { //initialize the parent cache let mut parent = UtxosCache::new_for_test(H256::random().into()); - let (_, outpoint) = insert_single_entry(rng, &mut parent, &parent_presence, parent_flags, None); + let (_, outpoint) = + test_helper::insert_single_entry(rng, &mut parent, &parent_presence, parent_flags, None); let key = &outpoint; // prepare the map for batch write. @@ -149,16 +155,12 @@ fn check_write_utxo( panic!("Please use `Present` or `Spent` presence when child flags are specified."); } Present => { - let (utxo, _) = create_utxo(rng, 0); - let entry = UtxoEntry::new(utxo, is_fresh, is_dirty); + let (utxo, _) = test_helper::create_utxo(rng, 0); + let entry = UtxoEntry::new(Some(utxo), is_fresh, is_dirty); single_entry_map.insert(key.clone(), entry); } Spent => { - let entry = UtxoEntry { - status: UtxoStatus::Spent, - is_dirty, - is_fresh, - }; + let entry = UtxoEntry::new(None, is_fresh, is_dirty); single_entry_map.insert(key.clone(), entry); } } @@ -179,7 +181,7 @@ fn check_write_utxo( // no need to check for the flags, it's empty. assert!(entry.is_none()); } - other => check_flags(entry, result_flags, !(other == Present)), + other => test_helper::check_flags(entry, result_flags, !(other == Present)), } } Err(e) => { @@ -198,7 +200,7 @@ fn check_get_mut_utxo( result_flags: Option, ) { let mut parent = UtxosCache::new_for_test(H256::random().into()); - let (parent_utxo, parent_outpoint) = insert_single_entry( + let (parent_utxo, parent_outpoint) = test_helper::insert_single_entry( rng, &mut parent, &parent_presence, @@ -210,7 +212,7 @@ fn check_get_mut_utxo( Absent => UtxosCache::new_for_test(H256::random().into()), _ => UtxosCache::new(&parent), }; - let (child_utxo, child_outpoint) = insert_single_entry( + let (child_utxo, child_outpoint) = test_helper::insert_single_entry( rng, &mut child, &cache_presence, @@ -237,7 +239,7 @@ fn check_get_mut_utxo( } // let's try to update the utxo. - let old_height_num = match utxo.source_height() { + let old_height_num = match utxo.source() { UtxoSource::BlockChain(h) => h, UtxoSource::MemPool => panic!("Unexpected arm"), }; @@ -246,10 +248,10 @@ fn check_get_mut_utxo( let new_height = UtxoSource::BlockChain(new_height_num); utxo.set_height(new_height.clone()); - assert_eq!(new_height, *utxo.source_height()); + assert_eq!(new_height, *utxo.source()); assert_eq!( new_height_num, - utxo.source_height().blockchain_height().expect("Must be a height") + utxo.source().blockchain_height().expect("Must be a height") ); expected_utxo = Some(utxo.clone()); } @@ -267,7 +269,7 @@ fn check_get_mut_utxo( let actual_utxo = actual_utxo_entry.utxo().expect("should have an existing utxo."); assert_eq!(expected_utxo, actual_utxo); } - check_flags(entry, result_flags, !(other == Present)) + test_helper::check_flags(entry, result_flags, !(other == Present)) } } } @@ -454,10 +456,10 @@ fn derive_cache_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); - let (utxo, outpoint_1) = create_utxo(&mut rng, 10); + let (utxo, outpoint_1) = test_helper::create_utxo(&mut rng, 10); assert!(cache.add_utxo(&outpoint_1, utxo, false).is_ok()); - let (utxo, outpoint_2) = create_utxo(&mut rng, 20); + let (utxo, outpoint_2) = test_helper::create_utxo(&mut rng, 20); assert!(cache.add_utxo(&outpoint_2, utxo, false).is_ok()); let mut extra_cache = cache.derive_cache(); @@ -466,7 +468,7 @@ fn derive_cache_test(#[case] seed: Seed) { assert!(extra_cache.has_utxo(&outpoint_1)); assert!(extra_cache.has_utxo(&outpoint_2)); - let (utxo, outpoint) = create_utxo(&mut rng, 30); + let (utxo, outpoint) = test_helper::create_utxo(&mut rng, 30); assert!(extra_cache.add_utxo(&outpoint, utxo, true).is_ok()); assert!(!cache.has_utxo(&outpoint)); @@ -479,15 +481,14 @@ fn blockchain_or_mempool_utxo_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); - let (utxo, outpoint_1) = create_utxo(&mut rng, 10); + let (utxo, outpoint_1) = test_helper::create_utxo(&mut rng, 10); assert!(cache.add_utxo(&outpoint_1, utxo, false).is_ok()); - let (utxo, outpoint_2) = create_utxo_for_mempool(&mut rng); + let (utxo, outpoint_2) = test_helper::create_utxo_for_mempool(&mut rng); assert!(cache.add_utxo(&outpoint_2, utxo, false).is_ok()); let res = cache.utxo(&outpoint_2).expect("should contain utxo"); - assert!(res.source_height().is_mempool()); - assert_eq!(res.source, UtxoSource::MemPool); + assert!(res.source().is_mempool()); } #[rstest] @@ -500,7 +501,13 @@ fn multiple_update_utxos_test(#[case] seed: Seed) { let mut cache = UtxosCache::new_for_test(H256::random().into()); // let's test `add_utxos` - let tx = Transaction::new(0x00, vec![], create_tx_outputs(&mut rng, 10), 0x01).unwrap(); + let tx = Transaction::new( + 0x00, + vec![], + test_helper::create_tx_outputs(&mut rng, 10), + 0x01, + ) + .unwrap(); assert!(cache.add_utxos(&tx, UtxoSource::BlockChain(BlockHeight::new(2)), false).is_ok()); // check that the outputs of tx are added in the cache. diff --git a/utxo/src/utxo_impl/simulation.rs b/utxo/src/tests/simulation.rs similarity index 96% rename from utxo/src/utxo_impl/simulation.rs rename to utxo/src/tests/simulation.rs index 3a4d84646..21b4b30e5 100644 --- a/utxo/src/utxo_impl/simulation.rs +++ b/utxo/src/tests/simulation.rs @@ -13,7 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{utxo_impl::test_helper::create_utxo, FlushableUtxoView, Utxo, UtxosCache, UtxosView}; +use super::test_helper::create_utxo; +use crate::{FlushableUtxoView, Utxo, UtxosCache, UtxosView}; use common::{chain::OutPoint, primitives::H256}; use crypto::random::Rng; use rstest::rstest; @@ -130,10 +131,10 @@ fn populate_cache( if i % 10 == 0 { if rng.gen::() && prev_result.len() > 1 { let idx = rng.gen_range(0..prev_result.len()); - cache.uncache(&prev_result[idx].0); + let _ = cache.uncache(&prev_result[idx].0); } else if result.len() > 1 { let idx = rng.gen_range(0..result.len()); - cache.uncache(&result[idx].0); + let _ = cache.uncache(&result[idx].0); } removed_an_entry = true; } diff --git a/utxo/src/utxo_impl/test_helper.rs b/utxo/src/tests/test_helper.rs similarity index 86% rename from utxo/src/utxo_impl/test_helper.rs rename to utxo/src/tests/test_helper.rs index 476977245..d9e37b39b 100644 --- a/utxo/src/utxo_impl/test_helper.rs +++ b/utxo/src/tests/test_helper.rs @@ -13,16 +13,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Utxo, UtxoEntry, UtxosCache}; -use common::chain::signature::inputsig::InputWitness; -use common::chain::tokens::OutputValue; -use common::chain::{ - Destination, GenBlock, OutPoint, OutPointSourceId, OutputPurpose, Transaction, TxInput, - TxOutput, +use crate::{cache::UtxoEntry, Utxo, UtxosCache}; +use common::{ + chain::{ + signature::inputsig::InputWitness, tokens::OutputValue, Destination, GenBlock, OutPoint, + OutPointSourceId, OutputPurpose, Transaction, TxInput, TxOutput, + }, + primitives::{Amount, BlockHeight, Id, H256}, +}; +use crypto::{ + key::{KeyKind, PrivateKey}, + random::{seq, Rng}, }; -use common::primitives::{Amount, BlockHeight, Id, H256}; -use crypto::key::{KeyKind, PrivateKey}; -use crypto::random::{seq, Rng}; use itertools::Itertools; pub const FRESH: u8 = 1; @@ -35,9 +37,6 @@ pub enum Presence { Spent, } -use crate::UtxoStatus; -use Presence::{Absent, Present, Spent}; - pub fn create_tx_outputs(rng: &mut impl Rng, size: u32) -> Vec { let mut tx_outputs = vec![]; for _ in 0..size { @@ -73,7 +72,7 @@ pub fn convert_to_utxo(output: TxOutput, height: u64, output_idx: usize) -> (Out let utxo_id: Id = Id::new(H256::random()); let id = OutPointSourceId::BlockReward(utxo_id); let outpoint = OutPoint::new(id, output_idx as u32); - let utxo = Utxo::new(output, true, BlockHeight::new(height)); + let utxo = Utxo::new_for_blockchain(output, true, BlockHeight::new(height)); (outpoint, utxo) } @@ -100,7 +99,7 @@ fn inner_create_utxo(rng: &mut impl Rng, block_height: Option) -> (Utxo, Ou // generate utxo let utxo = match block_height { None => Utxo::new_for_mempool(output, is_block_reward), - Some(height) => Utxo::new(output, is_block_reward, BlockHeight::new(height)), + Some(height) => Utxo::new_for_blockchain(output, is_block_reward, BlockHeight::new(height)), }; // create the id based on the `is_block_reward` value. @@ -139,7 +138,7 @@ pub fn insert_single_entry( let key = &outpoint; match cache_presence { - Absent => { + Presence::Absent => { // there shouldn't be an existing entry. Don't bother with the cache flags. } other => { @@ -148,12 +147,8 @@ pub fn insert_single_entry( let is_fresh = (flags & FRESH) == FRESH; let entry = match other { - Present => UtxoEntry::new(utxo.clone(), is_fresh, is_dirty), - Spent => UtxoEntry { - status: UtxoStatus::Spent, - is_dirty, - is_fresh, - }, + Presence::Present => UtxoEntry::new(Some(utxo.clone()), is_fresh, is_dirty), + Presence::Spent => UtxoEntry::new(None, is_fresh, is_dirty), _ => { panic!("something wrong in the code.") } diff --git a/utxo/src/undo.rs b/utxo/src/undo.rs index 31659c0e6..980a2472d 100644 --- a/utxo/src/undo.rs +++ b/utxo/src/undo.rs @@ -13,8 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(unused, dead_code)] - use crate::Utxo; use common::primitives::BlockHeight; use serialization::{Decode, Encode}; @@ -74,8 +72,7 @@ impl BlockUndo { #[cfg(test)] pub mod test { use super::*; - use crate::test_helper::create_utxo; - use crypto::random::Rng; + use crate::tests::test_helper::create_utxo; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; diff --git a/utxo/src/utxo.rs b/utxo/src/utxo.rs new file mode 100644 index 000000000..297fe6737 --- /dev/null +++ b/utxo/src/utxo.rs @@ -0,0 +1,98 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::Error; +use common::{chain::TxOutput, primitives::BlockHeight}; +use serialization::{Decode, Encode}; +use std::fmt::Debug; + +/// Determines whether the utxo is for the blockchain of for mempool +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +pub enum UtxoSource { + /// At which height this containing tx was included in the active block chain + BlockChain(BlockHeight), + MemPool, +} + +impl UtxoSource { + pub fn is_mempool(&self) -> bool { + match self { + UtxoSource::BlockChain(_) => false, + UtxoSource::MemPool => true, + } + } + + pub fn blockchain_height(&self) -> Result { + match self { + UtxoSource::BlockChain(h) => Ok(*h), + UtxoSource::MemPool => Err(crate::Error::NoBlockchainHeightFound), + } + } +} + +/// The Unspent Transaction Output +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +pub struct Utxo { + output: TxOutput, + is_block_reward: bool, + /// identifies whether the utxo is for the blockchain or for mempool. + source: UtxoSource, +} + +impl Utxo { + pub fn new(output: TxOutput, is_block_reward: bool, source: UtxoSource) -> Self { + Self { + output, + is_block_reward, + source, + } + } + + pub fn new_for_blockchain( + output: TxOutput, + is_block_reward: bool, + height: BlockHeight, + ) -> Self { + Self { + output, + is_block_reward, + source: UtxoSource::BlockChain(height), + } + } + + pub fn new_for_mempool(output: TxOutput, is_block_reward: bool) -> Self { + Self { + output, + is_block_reward, + source: UtxoSource::MemPool, + } + } + + pub fn is_block_reward(&self) -> bool { + self.is_block_reward + } + + pub fn source(&self) -> &UtxoSource { + &self.source + } + + pub fn output(&self) -> &TxOutput { + &self.output + } + + pub fn set_height(&mut self, value: UtxoSource) { + self.source = value + } +} diff --git a/utxo/src/view.rs b/utxo/src/view.rs new file mode 100644 index 000000000..08002909a --- /dev/null +++ b/utxo/src/view.rs @@ -0,0 +1,48 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ConsumedUtxoCache, Error, Utxo, UtxosCache}; +use common::{ + chain::{GenBlock, OutPoint}, + primitives::Id, +}; + +pub trait UtxosView { + /// Retrieves utxo. + fn utxo(&self, outpoint: &OutPoint) -> Option; + + /// Checks whether outpoint is unspent. + fn has_utxo(&self, outpoint: &OutPoint) -> bool; + + /// Retrieves the block hash of the best block in this view + fn best_block_hash(&self) -> Id; + + /// Estimated size of the whole view (None if not implemented) + fn estimated_size(&self) -> Option; + + fn derive_cache(&self) -> UtxosCache; +} + +pub trait FlushableUtxoView { + /// Performs bulk modification + fn batch_write(&mut self, utxos: ConsumedUtxoCache) -> Result<(), Error>; +} + +/// Flush the cache into the provided base. This will consume the cache and throw it away. +/// It uses the batch_write function since it's available in different kinds of views. +pub fn flush_to_base(cache: UtxosCache, base: &mut T) -> Result<(), Error> { + let consumed_cache = cache.consume(); + base.batch_write(consumed_cache) +} From 739a4c381ad77a127c1aa7445f363b6435654fbb Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Wed, 10 Aug 2022 18:05:29 +0300 Subject: [PATCH 08/12] Move UtxoEntry to a separate module --- utxo/src/cache.rs | 132 +++++++-------------------------- utxo/src/lib.rs | 1 + utxo/src/storage/test.rs | 6 +- utxo/src/storage/view_impls.rs | 2 +- utxo/src/tests/mod.rs | 4 +- utxo/src/tests/test_helper.rs | 2 +- utxo/src/utxo_entry.rs | 83 +++++++++++++++++++++ 7 files changed, 118 insertions(+), 112 deletions(-) create mode 100644 utxo/src/utxo_entry.rs diff --git a/utxo/src/cache.rs b/utxo/src/cache.rs index ee8b5d3da..e0910dc67 100644 --- a/utxo/src/cache.rs +++ b/utxo/src/cache.rs @@ -13,13 +13,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Error, FlushableUtxoView, TxUndo, Utxo, UtxoSource, UtxosView}; +use crate::{ + utxo_entry::UtxoEntry, + {Error, FlushableUtxoView, TxUndo, Utxo, UtxoSource, UtxosView}, +}; use common::{ chain::{GenBlock, OutPoint, OutPointSourceId, Transaction}, primitives::{BlockHeight, Id, Idable}, }; use logging::log; -use serialization::{Decode, Encode}; use std::{ collections::BTreeMap, fmt::{Debug, Formatter}, @@ -40,80 +42,6 @@ pub struct UtxosCache<'a> { memory_usage: usize, } -/// Tells the state of the utxo -#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] -#[allow(clippy::large_enum_variant)] -pub(crate) enum UtxoStatus { - Spent, - Entry(Utxo), -} - -impl UtxoStatus { - fn into_option(self) -> Option { - match self { - UtxoStatus::Spent => None, - UtxoStatus::Entry(utxo) => Some(utxo), - } - } -} - -/// Just the Utxo with additional information. -#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] -pub(crate) struct UtxoEntry { - status: UtxoStatus, - /// The utxo entry is dirty when this version is different from the parent. - is_dirty: bool, - /// The utxo entry is fresh when the parent does not have this utxo or - /// if it exists in parent but not in current cache. - is_fresh: bool, -} - -impl UtxoEntry { - pub fn new(utxo: Option, is_fresh: bool, is_dirty: bool) -> UtxoEntry { - UtxoEntry { - status: match utxo { - Some(utxo) => UtxoStatus::Entry(utxo), - None => UtxoStatus::Spent, - }, - is_dirty, - is_fresh, - } - } - - pub fn is_dirty(&self) -> bool { - self.is_dirty - } - - pub fn is_fresh(&self) -> bool { - self.is_fresh - } - - pub fn is_spent(&self) -> bool { - self.status == UtxoStatus::Spent - } - - pub fn utxo(&self) -> Option { - match &self.status { - UtxoStatus::Spent => None, - UtxoStatus::Entry(utxo) => Some(utxo.clone()), - } - } - - pub fn take_utxo(self) -> Option { - match self.status { - UtxoStatus::Spent => None, - UtxoStatus::Entry(utxo) => Some(utxo), - } - } - - fn utxo_mut(&mut self) -> Option<&mut Utxo> { - match &mut self.status { - UtxoStatus::Spent => None, - UtxoStatus::Entry(utxo) => Some(utxo), - } - } -} - impl<'a> UtxosCache<'a> { #[cfg(test)] pub(crate) fn new_for_test(best_block: Id) -> Self { @@ -135,14 +63,10 @@ impl<'a> UtxosCache<'a> { // since the utxo does not exist in this view, try to check from parent. self.parent.and_then(|parent| { - parent.utxo(outpoint).map(|utxo| UtxoEntry { - // if the utxo exists in parent: - // dirty is FALSE because this view does not have the utxo, therefore is different from parent - // fresh is FALSE because this view does not have the utxo but the parent has. - status: UtxoStatus::Entry(utxo), - is_dirty: false, - is_fresh: false, - }) + // if the utxo exists in parent: + // dirty is FALSE because this view does not have the utxo, therefore is different from parent + // fresh is FALSE because this view does not have the utxo but the parent has. + parent.utxo(outpoint).map(|utxo| UtxoEntry::new(Some(utxo), false, false)) }) } @@ -225,10 +149,10 @@ impl<'a> UtxosCache<'a> { // is 'spent' when the block adding it is disconnected and then // re-added when it is also added in a newly connected block). // if utxo is spent and is not dirty, then it can be marked as fresh. - !curr_entry.is_dirty || curr_entry.is_fresh + !curr_entry.is_dirty() || curr_entry.is_fresh() } else { // copy from the original entry - curr_entry.is_fresh + curr_entry.is_fresh() } } }; @@ -252,16 +176,12 @@ impl<'a> UtxosCache<'a> { // self.memory_usage must be deducted from this entry's size // check whether this entry is fresh - if entry.is_fresh { + if entry.is_fresh() { // This is only available in this view. Remove immediately. self.utxos.remove(outpoint); } else { // mark this as 'spent' - let new_entry = UtxoEntry { - status: UtxoStatus::Spent, - is_dirty: true, - is_fresh: false, - }; + let new_entry = UtxoEntry::new(None, false, true); self.utxos.insert(outpoint.clone(), new_entry); } @@ -275,12 +195,12 @@ impl<'a> UtxosCache<'a> { /// Returns a mutable reference of the utxo, given the outpoint. pub fn get_mut_utxo(&mut self, outpoint: &OutPoint) -> Option<&mut Utxo> { - let status = self.get_utxo_entry(outpoint)?; - let utxo: Utxo = status.status.into_option()?; + let entry = self.get_utxo_entry(outpoint)?; + let utxo = entry.utxo()?; let utxo: &mut UtxoEntry = self.utxos.entry(outpoint.clone()).or_insert_with(|| { //TODO: update the memory storage here - UtxoEntry::new(Some(utxo), status.is_fresh, status.is_dirty) + UtxoEntry::new(Some(utxo.clone()), entry.is_fresh(), entry.is_dirty()) }); utxo.utxo_mut() @@ -291,7 +211,7 @@ impl<'a> UtxosCache<'a> { let key = outpoint; if let Some(entry) = self.utxos.get(key) { // see bitcoin's Uncache. - if !entry.is_fresh && !entry.is_dirty { + if !entry.is_fresh() && !entry.is_dirty() { //todo: decrement the memory usage self.utxos.remove(key); return Ok(()); @@ -323,7 +243,7 @@ impl<'a> UtxosView for UtxosCache<'a> { fn utxo(&self, outpoint: &OutPoint) -> Option { let key = outpoint; if let Some(res) = self.utxos.get(key) { - return res.utxo(); + return res.utxo().cloned(); } // if utxo is not found in this view, use parent's `get_utxo`. @@ -351,17 +271,17 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { fn batch_write(&mut self, utxo_entries: ConsumedUtxoCache) -> Result<(), Error> { for (key, entry) in utxo_entries.container { // Ignore non-dirty entries (optimization). - if entry.is_dirty { + if entry.is_dirty() { let parent_entry = self.utxos.get(&key); match parent_entry { None => { // The parent cache does not have an entry, while the child cache does. // We can ignore it if it's both spent and FRESH in the child - if !(entry.is_fresh && entry.is_spent()) { + if !(entry.is_fresh() && entry.is_spent()) { // Create the utxo in the parent cache, move the data up // and mark it as dirty. - let mut entry_copy = entry.clone(); - entry_copy.is_dirty = true; + let entry_copy = + UtxoEntry::new(entry.utxo().cloned(), entry.is_fresh(), true); self.utxos.insert(key, entry_copy); // TODO: increase the memory usage @@ -378,15 +298,17 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { return Err(Error::FreshUtxoAlreadyExists); } - if parent_entry.is_fresh && entry.is_spent() { + if parent_entry.is_fresh() && entry.is_spent() { // The grandparent cache does not have an entry, and the utxo // has been spent. We can just delete it from the parent cache. self.utxos.remove(&key); } else { // A normal modification. - let mut entry_copy = entry.clone(); - entry_copy.is_dirty = true; - entry_copy.is_fresh = parent_entry.is_fresh; + let entry_copy = UtxoEntry::new( + entry.utxo().cloned(), + parent_entry.is_fresh(), + true, + ); self.utxos.insert(key, entry_copy); // TODO: update the memory usage diff --git a/utxo/src/lib.rs b/utxo/src/lib.rs index 035c974a5..d75bcd880 100644 --- a/utxo/src/lib.rs +++ b/utxo/src/lib.rs @@ -18,6 +18,7 @@ mod error; mod storage; mod undo; mod utxo; +mod utxo_entry; mod view; pub use crate::{ diff --git a/utxo/src/storage/test.rs b/utxo/src/storage/test.rs index 2e6d32163..3bcb371ea 100644 --- a/utxo/src/storage/test.rs +++ b/utxo/src/storage/test.rs @@ -15,9 +15,9 @@ use super::{in_memory::UtxosDBInMemoryImpl, *}; use crate::{ - cache::UtxoEntry, flush_to_base, tests::test_helper::{convert_to_utxo, create_tx_inputs, create_tx_outputs, create_utxo}, + utxo_entry::UtxoEntry, ConsumedUtxoCache, FlushableUtxoView, UtxosCache, UtxosView, }; use common::{ @@ -329,7 +329,7 @@ fn test_utxo(#[case] seed: Seed) { let outpoint_key = &outpoint; let utxo_entry = utxos.container.get(outpoint_key).expect("an entry should be found"); - assert_eq!(utxo_entry.utxo(), utxo_opt); + assert_eq!(utxo_entry.utxo(), utxo_opt.as_ref()); // check has_utxo assert!(utxo_db.has_utxo(&outpoint)); @@ -368,7 +368,7 @@ fn test_utxo(#[case] seed: Seed) { .expect("utxo should exist"); let mut parent = UtxosCache::new_for_test(utxo_db.best_block_hash()); - parent.add_utxo(outpoint, utxo, false).unwrap(); + parent.add_utxo(outpoint, utxo.clone(), false).unwrap(); let mut child = UtxosCache::new(&parent); child.spend_utxo(outpoint).unwrap(); diff --git a/utxo/src/storage/view_impls.rs b/utxo/src/storage/view_impls.rs index ae8e6cf07..24e1a0468 100644 --- a/utxo/src/storage/view_impls.rs +++ b/utxo/src/storage/view_impls.rs @@ -103,7 +103,7 @@ impl<'a, S: UtxosStorageWrite> FlushableUtxoView for UtxosDBMut<'a, S> { let outpoint = &key; if entry.is_dirty() { if let Some(utxo) = entry.utxo() { - self.0.set_utxo(outpoint, utxo)?; + self.0.set_utxo(outpoint, utxo.clone())?; } else { // entry is spent self.0.del_utxo(outpoint)?; diff --git a/utxo/src/tests/mod.rs b/utxo/src/tests/mod.rs index 332352e26..22b57d82e 100644 --- a/utxo/src/tests/mod.rs +++ b/utxo/src/tests/mod.rs @@ -17,12 +17,12 @@ pub mod simulation; pub mod test_helper; use crate::{ - cache::UtxoEntry, flush_to_base, tests::test_helper::{ Presence::{self, *}, DIRTY, FRESH, }, + utxo_entry::UtxoEntry, ConsumedUtxoCache, Error::{self, *}, FlushableUtxoView, Utxo, UtxoSource, UtxosCache, UtxosView, @@ -267,7 +267,7 @@ fn check_get_mut_utxo( if let Some(expected_utxo) = expected_utxo { let actual_utxo_entry = &entry.expect("should have an existing entry"); let actual_utxo = actual_utxo_entry.utxo().expect("should have an existing utxo."); - assert_eq!(expected_utxo, actual_utxo); + assert_eq!(expected_utxo, *actual_utxo); } test_helper::check_flags(entry, result_flags, !(other == Present)) } diff --git a/utxo/src/tests/test_helper.rs b/utxo/src/tests/test_helper.rs index d9e37b39b..ce3c9f502 100644 --- a/utxo/src/tests/test_helper.rs +++ b/utxo/src/tests/test_helper.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{cache::UtxoEntry, Utxo, UtxosCache}; +use crate::{utxo_entry::UtxoEntry, Utxo, UtxosCache}; use common::{ chain::{ signature::inputsig::InputWitness, tokens::OutputValue, Destination, GenBlock, OutPoint, diff --git a/utxo/src/utxo_entry.rs b/utxo/src/utxo_entry.rs new file mode 100644 index 000000000..f410386a2 --- /dev/null +++ b/utxo/src/utxo_entry.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2022 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::Utxo; +use serialization::{Decode, Encode}; +use std::fmt::Debug; + +/// Tells the state of the utxo +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +#[allow(clippy::large_enum_variant)] +pub enum UtxoStatus { + Spent, + Entry(Utxo), +} + +/// Just the Utxo with additional information. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +pub struct UtxoEntry { + status: UtxoStatus, + /// The utxo entry is dirty when this version is different from the parent. + is_dirty: bool, + /// The utxo entry is fresh when the parent does not have this utxo or + /// if it exists in parent but not in current cache. + is_fresh: bool, +} + +impl UtxoEntry { + pub fn new(utxo: Option, is_fresh: bool, is_dirty: bool) -> UtxoEntry { + UtxoEntry { + status: match utxo { + Some(utxo) => UtxoStatus::Entry(utxo), + None => UtxoStatus::Spent, + }, + is_dirty, + is_fresh, + } + } + + pub fn is_dirty(&self) -> bool { + self.is_dirty + } + + pub fn is_fresh(&self) -> bool { + self.is_fresh + } + + pub fn is_spent(&self) -> bool { + self.status == UtxoStatus::Spent + } + + pub fn utxo(&self) -> Option<&Utxo> { + match &self.status { + UtxoStatus::Spent => None, + UtxoStatus::Entry(utxo) => Some(utxo), + } + } + + pub fn utxo_mut(&mut self) -> Option<&mut Utxo> { + match &mut self.status { + UtxoStatus::Spent => None, + UtxoStatus::Entry(utxo) => Some(utxo), + } + } + + pub fn take_utxo(self) -> Option { + match self.status { + UtxoStatus::Spent => None, + UtxoStatus::Entry(utxo) => Some(utxo), + } + } +} From a4cddcbe1bef956cdaeb7f259f5571bf1fea4fb5 Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Thu, 11 Aug 2022 10:46:36 +0300 Subject: [PATCH 09/12] Replace UtxoEnrty bools with flags --- utxo/src/cache.rs | 28 +++++++++++++++------------- utxo/src/storage/test.rs | 6 +++--- utxo/src/tests/mod.rs | 14 ++++---------- utxo/src/tests/test_helper.rs | 15 ++++++--------- utxo/src/utxo_entry.rs | 21 +++++++++++---------- 5 files changed, 39 insertions(+), 45 deletions(-) diff --git a/utxo/src/cache.rs b/utxo/src/cache.rs index e0910dc67..f65bdebf2 100644 --- a/utxo/src/cache.rs +++ b/utxo/src/cache.rs @@ -14,7 +14,7 @@ // limitations under the License. use crate::{ - utxo_entry::UtxoEntry, + utxo_entry::{UtxoEntry, DIRTY, FRESH}, {Error, FlushableUtxoView, TxUndo, Utxo, UtxoSource, UtxosView}, }; use common::{ @@ -66,7 +66,7 @@ impl<'a> UtxosCache<'a> { // if the utxo exists in parent: // dirty is FALSE because this view does not have the utxo, therefore is different from parent // fresh is FALSE because this view does not have the utxo but the parent has. - parent.utxo(outpoint).map(|utxo| UtxoEntry::new(Some(utxo), false, false)) + parent.utxo(outpoint).map(|utxo| UtxoEntry::new(Some(utxo), 0)) }) } @@ -158,7 +158,8 @@ impl<'a> UtxosCache<'a> { }; // create a new entry - let new_entry = UtxoEntry::new(Some(utxo), is_fresh, true); + let fresh_flag = if is_fresh { FRESH } else { 0 }; + let new_entry = UtxoEntry::new(Some(utxo), fresh_flag | DIRTY); // TODO: update the memory usage // self.memory_usage should be added based on this new entry. @@ -181,7 +182,7 @@ impl<'a> UtxosCache<'a> { self.utxos.remove(outpoint); } else { // mark this as 'spent' - let new_entry = UtxoEntry::new(None, false, true); + let new_entry = UtxoEntry::new(None, DIRTY); self.utxos.insert(outpoint.clone(), new_entry); } @@ -200,7 +201,9 @@ impl<'a> UtxosCache<'a> { let utxo: &mut UtxoEntry = self.utxos.entry(outpoint.clone()).or_insert_with(|| { //TODO: update the memory storage here - UtxoEntry::new(Some(utxo.clone()), entry.is_fresh(), entry.is_dirty()) + let fresh_flag = if entry.is_fresh() { FRESH } else { 0 }; + let dirty_flag = if entry.is_dirty() { DIRTY } else { 0 }; + UtxoEntry::new(Some(utxo.clone()), fresh_flag | dirty_flag) }); utxo.utxo_mut() @@ -280,8 +283,9 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { if !(entry.is_fresh() && entry.is_spent()) { // Create the utxo in the parent cache, move the data up // and mark it as dirty. + let fresh_flag = if entry.is_fresh() { FRESH } else { 0 }; let entry_copy = - UtxoEntry::new(entry.utxo().cloned(), entry.is_fresh(), true); + UtxoEntry::new(entry.utxo().cloned(), fresh_flag | DIRTY); self.utxos.insert(key, entry_copy); // TODO: increase the memory usage @@ -304,11 +308,9 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { self.utxos.remove(&key); } else { // A normal modification. - let entry_copy = UtxoEntry::new( - entry.utxo().cloned(), - parent_entry.is_fresh(), - true, - ); + let fresh_flag = if parent_entry.is_fresh() { FRESH } else { 0 }; + let entry_copy = + UtxoEntry::new(entry.utxo().cloned(), fresh_flag | DIRTY); self.utxos.insert(key, entry_copy); // TODO: update the memory usage @@ -329,8 +331,8 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { #[cfg(test)] mod unit_test { - use super::{Error, UtxosCache}; - use crate::tests::test_helper::{insert_single_entry, Presence, DIRTY, FRESH}; + use super::*; + use crate::tests::test_helper::{insert_single_entry, Presence}; use common::primitives::H256; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; diff --git a/utxo/src/storage/test.rs b/utxo/src/storage/test.rs index 3bcb371ea..783d710ee 100644 --- a/utxo/src/storage/test.rs +++ b/utxo/src/storage/test.rs @@ -17,7 +17,7 @@ use super::{in_memory::UtxosDBInMemoryImpl, *}; use crate::{ flush_to_base, tests::test_helper::{convert_to_utxo, create_tx_inputs, create_tx_outputs, create_utxo}, - utxo_entry::UtxoEntry, + utxo_entry::{UtxoEntry, DIRTY, FRESH}, ConsumedUtxoCache, FlushableUtxoView, UtxosCache, UtxosView, }; use common::{ @@ -101,7 +101,7 @@ fn create_utxo_entries(rng: &mut impl Rng, num_of_utxos: u8) -> BTreeMap { panic!("Please use `Present` or `Spent` presence when child flags are specified."); } Present => { let (utxo, _) = test_helper::create_utxo(rng, 0); - let entry = UtxoEntry::new(Some(utxo), is_fresh, is_dirty); + let entry = UtxoEntry::new(Some(utxo), child_flags); single_entry_map.insert(key.clone(), entry); } Spent => { - let entry = UtxoEntry::new(None, is_fresh, is_dirty); + let entry = UtxoEntry::new(None, child_flags); single_entry_map.insert(key.clone(), entry); } } diff --git a/utxo/src/tests/test_helper.rs b/utxo/src/tests/test_helper.rs index ce3c9f502..a3b9fd94b 100644 --- a/utxo/src/tests/test_helper.rs +++ b/utxo/src/tests/test_helper.rs @@ -13,7 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{utxo_entry::UtxoEntry, Utxo, UtxosCache}; +use crate::{ + utxo_entry::{UtxoEntry, DIRTY, FRESH}, + Utxo, UtxosCache, +}; use common::{ chain::{ signature::inputsig::InputWitness, tokens::OutputValue, Destination, GenBlock, OutPoint, @@ -27,9 +30,6 @@ use crypto::{ }; use itertools::Itertools; -pub const FRESH: u8 = 1; -pub const DIRTY: u8 = 2; - #[derive(Clone, Eq, PartialEq)] pub enum Presence { Absent, @@ -143,12 +143,9 @@ pub fn insert_single_entry( } other => { let flags = cache_flags.expect("please provide flags."); - let is_dirty = (flags & DIRTY) == DIRTY; - let is_fresh = (flags & FRESH) == FRESH; - let entry = match other { - Presence::Present => UtxoEntry::new(Some(utxo.clone()), is_fresh, is_dirty), - Presence::Spent => UtxoEntry::new(None, is_fresh, is_dirty), + Presence::Present => UtxoEntry::new(Some(utxo.clone()), flags), + Presence::Spent => UtxoEntry::new(None, flags), _ => { panic!("something wrong in the code.") } diff --git a/utxo/src/utxo_entry.rs b/utxo/src/utxo_entry.rs index f410386a2..c473d4b13 100644 --- a/utxo/src/utxo_entry.rs +++ b/utxo/src/utxo_entry.rs @@ -17,6 +17,12 @@ use crate::Utxo; use serialization::{Decode, Encode}; use std::fmt::Debug; +/// The utxo entry is dirty when this version is different from the parent. +pub const DIRTY: u8 = 0b01; +/// The utxo entry is fresh when the parent does not have this utxo or +/// if it exists in parent but not in current cache. +pub const FRESH: u8 = 0b10; + /// Tells the state of the utxo #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] #[allow(clippy::large_enum_variant)] @@ -29,31 +35,26 @@ pub enum UtxoStatus { #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] pub struct UtxoEntry { status: UtxoStatus, - /// The utxo entry is dirty when this version is different from the parent. - is_dirty: bool, - /// The utxo entry is fresh when the parent does not have this utxo or - /// if it exists in parent but not in current cache. - is_fresh: bool, + flags: u8, } impl UtxoEntry { - pub fn new(utxo: Option, is_fresh: bool, is_dirty: bool) -> UtxoEntry { + pub fn new(utxo: Option, flags: u8) -> UtxoEntry { UtxoEntry { status: match utxo { Some(utxo) => UtxoStatus::Entry(utxo), None => UtxoStatus::Spent, }, - is_dirty, - is_fresh, + flags, } } pub fn is_dirty(&self) -> bool { - self.is_dirty + self.flags & DIRTY != 0 } pub fn is_fresh(&self) -> bool { - self.is_fresh + self.flags & FRESH != 0 } pub fn is_spent(&self) -> bool { From f5165fb6f48a54901ca447744a46aca168e3d553 Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Fri, 12 Aug 2022 13:39:23 +0300 Subject: [PATCH 10/12] Fix review comments --- chainstate-storage/src/internal/test.rs | 2 +- chainstate-storage/src/internal/utxo_db.rs | 6 ++---- chainstate-storage/src/mock.rs | 3 +-- utxo/src/cache.rs | 16 +++------------- utxo/src/storage/in_memory.rs | 4 +--- utxo/src/storage/mod.rs | 4 +++- 6 files changed, 11 insertions(+), 24 deletions(-) diff --git a/chainstate-storage/src/internal/test.rs b/chainstate-storage/src/internal/test.rs index 42025d9e9..70a77314d 100644 --- a/chainstate-storage/src/internal/test.rs +++ b/chainstate-storage/src/internal/test.rs @@ -266,7 +266,7 @@ fn create_rand_utxo(rng: &mut impl Rng, block_height: u64) -> Utxo { let is_block_reward = random_value % 3 == 0; // generate utxo - Utxo::new(output, is_block_reward, BlockHeight::new(block_height)) + Utxo::new_for_blockchain(output, is_block_reward, BlockHeight::new(block_height)) } /// returns a block undo with random utxos and TxUndos. diff --git a/chainstate-storage/src/internal/utxo_db.rs b/chainstate-storage/src/internal/utxo_db.rs index faa5dcbc3..a841ffe62 100644 --- a/chainstate-storage/src/internal/utxo_db.rs +++ b/chainstate-storage/src/internal/utxo_db.rs @@ -23,9 +23,7 @@ mod test { use crypto::random::Rng; use rstest::rstest; use test_utils::random::{make_seedable_rng, Seed}; - use utxo::utxo_storage::UtxosDBMut; - use utxo::utxo_storage::{UtxosStorageRead, UtxosStorageWrite}; - use utxo::Utxo; + use utxo::{Utxo, UtxosDBMut, UtxosStorageRead, UtxosStorageWrite}; fn create_utxo(block_height: u64, output_value: u128) -> (Utxo, OutPoint) { // just a random value generated, and also a random `is_block_reward` value. @@ -34,7 +32,7 @@ mod test { OutputValue::Coin(Amount::from_atoms(output_value)), OutputPurpose::Transfer(Destination::PublicKey(pub_key)), ); - let utxo = Utxo::new(output, true, BlockHeight::new(block_height)); + let utxo = Utxo::new_for_blockchain(output, true, BlockHeight::new(block_height)); // create the id based on the `is_block_reward` value. let id = OutPointSourceId::BlockReward(Id::new(H256::random())); diff --git a/chainstate-storage/src/mock.rs b/chainstate-storage/src/mock.rs index 2915f3be9..f056492ec 100644 --- a/chainstate-storage/src/mock.rs +++ b/chainstate-storage/src/mock.rs @@ -21,8 +21,7 @@ use common::chain::transaction::{ }; use common::chain::{Block, GenBlock, OutPoint}; use common::primitives::{BlockHeight, Id}; -use utxo::utxo_storage::{UtxosStorageRead, UtxosStorageWrite}; -use utxo::{BlockUndo, Utxo}; +use utxo::{BlockUndo, Utxo, UtxosStorageRead, UtxosStorageWrite}; mockall::mock! { /// A mock object for blockchain storage diff --git a/utxo/src/cache.rs b/utxo/src/cache.rs index f65bdebf2..3f7756805 100644 --- a/utxo/src/cache.rs +++ b/utxo/src/cache.rs @@ -37,19 +37,18 @@ pub struct ConsumedUtxoCache { pub struct UtxosCache<'a> { parent: Option<&'a dyn UtxosView>, current_block_hash: Id, + // pub(crate) visibility is required for tests that are in a different mod pub(crate) utxos: BTreeMap, - #[allow(dead_code)] - memory_usage: usize, + // TODO: calculate memory usage (mintlayer/mintlayer-core#354) } impl<'a> UtxosCache<'a> { #[cfg(test)] - pub(crate) fn new_for_test(best_block: Id) -> Self { + pub fn new_for_test(best_block: Id) -> Self { Self { parent: None, current_block_hash: best_block, utxos: Default::default(), - memory_usage: 0, } } @@ -75,7 +74,6 @@ impl<'a> UtxosCache<'a> { parent: Some(parent), current_block_hash: parent.best_block_hash(), utxos: BTreeMap::new(), - memory_usage: 0, } } @@ -130,9 +128,6 @@ impl<'a> UtxosCache<'a> { !possible_overwrite } Some(curr_entry) => { - // TODO: update the memory usage - // self.memory_usage should be deducted based on this current entry. - if !possible_overwrite { if !curr_entry.is_spent() { // Attempted to overwrite an existing utxo @@ -161,9 +156,6 @@ impl<'a> UtxosCache<'a> { let fresh_flag = if is_fresh { FRESH } else { 0 }; let new_entry = UtxoEntry::new(Some(utxo), fresh_flag | DIRTY); - // TODO: update the memory usage - // self.memory_usage should be added based on this new entry. - self.utxos.insert(outpoint.clone(), new_entry); Ok(()) @@ -173,8 +165,6 @@ impl<'a> UtxosCache<'a> { /// Returns the Utxo if an update was performed. pub fn spend_utxo(&mut self, outpoint: &OutPoint) -> Result { let entry = self.get_utxo_entry(outpoint).ok_or(Error::NoUtxoFound)?; - // TODO: update the memory usage - // self.memory_usage must be deducted from this entry's size // check whether this entry is fresh if entry.is_fresh() { diff --git a/utxo/src/storage/in_memory.rs b/utxo/src/storage/in_memory.rs index 09aa132f2..c3612b326 100644 --- a/utxo/src/storage/in_memory.rs +++ b/utxo/src/storage/in_memory.rs @@ -30,7 +30,6 @@ pub struct UtxosDBInMemoryImpl { } impl UtxosDBInMemoryImpl { - #[allow(dead_code)] pub fn new(best_block: Id, _initial_utxos: BTreeMap) -> Self { Self { store: BTreeMap::new(), @@ -39,8 +38,7 @@ impl UtxosDBInMemoryImpl { } } - #[allow(dead_code)] - pub(crate) fn internal_store(&mut self) -> &BTreeMap { + pub fn internal_store(&mut self) -> &BTreeMap { &self.store } } diff --git a/utxo/src/storage/mod.rs b/utxo/src/storage/mod.rs index 3e529fb69..524b0ce36 100644 --- a/utxo/src/storage/mod.rs +++ b/utxo/src/storage/mod.rs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod in_memory; mod rw_impls; mod view_impls; @@ -102,5 +101,8 @@ impl<'a, S: UtxosStorageWrite> UtxosDBMut<'a, S> { } } +#[cfg(test)] +mod in_memory; + #[cfg(test)] mod test; From 5d4cae864c3caf37d16a127899c67e1b25697332 Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Mon, 15 Aug 2022 16:09:05 +0300 Subject: [PATCH 11/12] Utxo enrty flags with enum --- utxo/src/cache.rs | 87 +++++++--- utxo/src/storage/in_memory.rs | 6 +- utxo/src/storage/test.rs | 6 +- utxo/src/tests/mod.rs | 294 +++++++++++++++++----------------- utxo/src/tests/test_helper.rs | 22 +-- utxo/src/utxo.rs | 16 +- utxo/src/utxo_entry.rs | 59 +++++-- 7 files changed, 284 insertions(+), 206 deletions(-) diff --git a/utxo/src/cache.rs b/utxo/src/cache.rs index abb8375c3..5c06151e5 100644 --- a/utxo/src/cache.rs +++ b/utxo/src/cache.rs @@ -14,7 +14,7 @@ // limitations under the License. use crate::{ - utxo_entry::{UtxoEntry, DIRTY, FRESH}, + utxo_entry::{IsDirty, IsFresh, UtxoEntry}, {Error, FlushableUtxoView, TxUndo, Utxo, UtxoSource, UtxosView}, }; use common::{ @@ -40,6 +40,8 @@ pub struct UtxosCache<'a> { // pub(crate) visibility is required for tests that are in a different mod pub(crate) utxos: BTreeMap, // TODO: calculate memory usage (mintlayer/mintlayer-core#354) + #[allow(dead_code)] + memory_usage: usize, } impl<'a> UtxosCache<'a> { @@ -49,6 +51,7 @@ impl<'a> UtxosCache<'a> { parent: None, current_block_hash: best_block, utxos: Default::default(), + memory_usage: 0, } } @@ -65,7 +68,9 @@ impl<'a> UtxosCache<'a> { // if the utxo exists in parent: // dirty is FALSE because this view does not have the utxo, therefore is different from parent // fresh is FALSE because this view does not have the utxo but the parent has. - parent.utxo(outpoint).map(|utxo| UtxoEntry::new(Some(utxo), 0)) + parent + .utxo(outpoint) + .map(|utxo| UtxoEntry::new(Some(utxo), IsFresh::No, IsDirty::No)) }) } @@ -74,6 +79,7 @@ impl<'a> UtxosCache<'a> { parent: Some(parent), current_block_hash: parent.best_block_hash(), utxos: BTreeMap::new(), + memory_usage: 0, } } @@ -130,7 +136,7 @@ impl<'a> UtxosCache<'a> { let tx_undo: Result, Error> = tx.inputs().iter().map(|tx_in| self.spend_utxo(tx_in.outpoint())).collect(); - self.add_utxos_from_tx(tx, UtxoSource::BlockChain(height), false)?; + self.add_utxos_from_tx(tx, UtxoSource::Blockchain(height), false)?; tx_undo.map(TxUndo::new) } @@ -142,6 +148,9 @@ impl<'a> UtxosCache<'a> { utxo: Utxo, possible_overwrite: bool, // TODO: change this to an enum that explains what happens ) -> Result<(), Error> { + // TODO: update the memory usage + // self.memory_usage should be deducted based on this current entry. + let is_fresh = match self.utxos.get(outpoint) { None => { // An insert can be done. This utxo doesn't exist yet, so it's fresh. @@ -173,8 +182,10 @@ impl<'a> UtxosCache<'a> { }; // create a new entry - let fresh_flag = if is_fresh { FRESH } else { 0 }; - let new_entry = UtxoEntry::new(Some(utxo), fresh_flag | DIRTY); + let new_entry = UtxoEntry::new(Some(utxo), IsFresh::from(is_fresh), IsDirty::Yes); + + // TODO: update the memory usage + // self.memory_usage should be added based on this new entry. self.utxos.insert(outpoint.clone(), new_entry); @@ -185,6 +196,8 @@ impl<'a> UtxosCache<'a> { /// Returns the Utxo if an update was performed. pub fn spend_utxo(&mut self, outpoint: &OutPoint) -> Result { let entry = self.get_utxo_entry(outpoint).ok_or(Error::NoUtxoFound)?; + // TODO: update the memory usage + // self.memory_usage must be deducted from this entry's size // check whether this entry is fresh if entry.is_fresh() { @@ -192,7 +205,7 @@ impl<'a> UtxosCache<'a> { self.utxos.remove(outpoint); } else { // mark this as 'spent' - let new_entry = UtxoEntry::new(None, DIRTY); + let new_entry = UtxoEntry::new(None, IsFresh::No, IsDirty::Yes); self.utxos.insert(outpoint.clone(), new_entry); } @@ -211,9 +224,11 @@ impl<'a> UtxosCache<'a> { let utxo: &mut UtxoEntry = self.utxos.entry(outpoint.clone()).or_insert_with(|| { //TODO: update the memory storage here - let fresh_flag = if entry.is_fresh() { FRESH } else { 0 }; - let dirty_flag = if entry.is_dirty() { DIRTY } else { 0 }; - UtxoEntry::new(Some(utxo.clone()), fresh_flag | dirty_flag) + UtxoEntry::new( + Some(utxo.clone()), + IsFresh::from(entry.is_fresh()), + IsDirty::from(entry.is_dirty()), + ) }); utxo.utxo_mut() @@ -293,9 +308,11 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { if !(entry.is_fresh() && entry.is_spent()) { // Create the utxo in the parent cache, move the data up // and mark it as dirty. - let fresh_flag = if entry.is_fresh() { FRESH } else { 0 }; - let entry_copy = - UtxoEntry::new(entry.utxo().cloned(), fresh_flag | DIRTY); + let entry_copy = UtxoEntry::new( + entry.utxo().cloned(), + IsFresh::from(entry.is_fresh()), + IsDirty::Yes, + ); self.utxos.insert(key, entry_copy); // TODO: increase the memory usage @@ -318,9 +335,11 @@ impl<'a> FlushableUtxoView for UtxosCache<'a> { self.utxos.remove(&key); } else { // A normal modification. - let fresh_flag = if parent_entry.is_fresh() { FRESH } else { 0 }; - let entry_copy = - UtxoEntry::new(entry.utxo().cloned(), fresh_flag | DIRTY); + let entry_copy = UtxoEntry::new( + entry.utxo().cloned(), + IsFresh::from(parent_entry.is_fresh()), + IsDirty::Yes, + ); self.utxos.insert(key, entry_copy); // TODO: update the memory usage @@ -355,29 +374,49 @@ mod unit_test { let mut cache = UtxosCache::new_for_test(H256::random().into()); // when the entry is not dirty and not fresh - let (_, outp) = - insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(0), None); + let (_, outp) = insert_single_entry( + &mut rng, + &mut cache, + Presence::Present, + Some((IsFresh::No, IsDirty::No)), + None, + ); assert!(cache.uncache(&outp).is_ok()); assert!(!cache.has_utxo_in_cache(&outp)); // when the outpoint does not exist. - let (_, outp) = insert_single_entry(&mut rng, &mut cache, &Presence::Absent, None, None); + let (_, outp) = insert_single_entry(&mut rng, &mut cache, Presence::Absent, None, None); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); assert!(!cache.has_utxo_in_cache(&outp)); // when the entry is fresh, entry cannot be removed. - let (_, outp) = - insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(FRESH), None); + let (_, outp) = insert_single_entry( + &mut rng, + &mut cache, + Presence::Present, + Some((IsFresh::Yes, IsDirty::No)), + None, + ); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); // when the entry is dirty, entry cannot be removed. - let (_, outp) = - insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(DIRTY), None); + let (_, outp) = insert_single_entry( + &mut rng, + &mut cache, + Presence::Present, + Some((IsFresh::No, IsDirty::Yes)), + None, + ); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); // when the entry is both fresh and dirty, entry cannot be removed. - let (_, outp) = - insert_single_entry(&mut rng, &mut cache, &Presence::Present, Some(FRESH), None); + let (_, outp) = insert_single_entry( + &mut rng, + &mut cache, + Presence::Present, + Some((IsFresh::Yes, IsDirty::No)), + None, + ); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); } } diff --git a/utxo/src/storage/in_memory.rs b/utxo/src/storage/in_memory.rs index c3612b326..aa762f354 100644 --- a/utxo/src/storage/in_memory.rs +++ b/utxo/src/storage/in_memory.rs @@ -30,15 +30,15 @@ pub struct UtxosDBInMemoryImpl { } impl UtxosDBInMemoryImpl { - pub fn new(best_block: Id, _initial_utxos: BTreeMap) -> Self { + pub fn new(best_block: Id, initial_utxos: BTreeMap) -> Self { Self { - store: BTreeMap::new(), + store: initial_utxos, undo_store: BTreeMap::new(), best_block_id: best_block, } } - pub fn internal_store(&mut self) -> &BTreeMap { + pub(super) fn internal_store(&mut self) -> &BTreeMap { &self.store } } diff --git a/utxo/src/storage/test.rs b/utxo/src/storage/test.rs index 783d710ee..42147648b 100644 --- a/utxo/src/storage/test.rs +++ b/utxo/src/storage/test.rs @@ -17,7 +17,7 @@ use super::{in_memory::UtxosDBInMemoryImpl, *}; use crate::{ flush_to_base, tests::test_helper::{convert_to_utxo, create_tx_inputs, create_tx_outputs, create_utxo}, - utxo_entry::{UtxoEntry, DIRTY, FRESH}, + utxo_entry::{IsDirty, IsFresh, UtxoEntry}, ConsumedUtxoCache, FlushableUtxoView, UtxosCache, UtxosView, }; use common::{ @@ -101,7 +101,7 @@ fn create_utxo_entries(rng: &mut impl Rng, num_of_utxos: u8) -> BTreeMap, + cache_flags: Option<(IsFresh, IsDirty)>, possible_overwrite: bool, - result_flags: Option, + result_flags: Option<(IsFresh, IsDirty)>, op_result: Result<(), Error>, ) { let mut cache = UtxosCache::new_for_test(H256::random().into()); let (_, outpoint) = - test_helper::insert_single_entry(rng, &mut cache, &cache_presence, cache_flags, None); + test_helper::insert_single_entry(rng, &mut cache, cache_presence, cache_flags, None); // perform the add_utxo. let (utxo, _) = test_helper::create_utxo(rng, 0); @@ -77,17 +77,17 @@ fn check_spend_utxo( rng: &mut impl Rng, parent_presence: Presence, cache_presence: Presence, - cache_flags: Option, + cache_flags: Option<(IsFresh, IsDirty)>, spend_result: Result<(), Error>, - result_flags: Option, + result_flags: Option<(IsFresh, IsDirty)>, ) { // initialize the parent cache. let mut parent = UtxosCache::new_for_test(H256::random().into()); let (_, parent_outpoint) = test_helper::insert_single_entry( rng, &mut parent, - &parent_presence, - Some(FRESH | DIRTY), + parent_presence, + Some((IsFresh::Yes, IsDirty::Yes)), None, ); @@ -100,7 +100,7 @@ fn check_spend_utxo( let (_, child_outpoint) = test_helper::insert_single_entry( rng, &mut child, - &cache_presence, + cache_presence, cache_flags, Some(parent_outpoint), ); @@ -129,32 +129,32 @@ fn check_write_utxo( parent_presence: Presence, child_presence: Presence, result: Result, - parent_flags: Option, - child_flags: Option, - result_flags: Option, + parent_flags: Option<(IsFresh, IsDirty)>, + child_flags: Option<(IsFresh, IsDirty)>, + result_flags: Option<(IsFresh, IsDirty)>, ) { //initialize the parent cache let mut parent = UtxosCache::new_for_test(H256::random().into()); let (_, outpoint) = - test_helper::insert_single_entry(rng, &mut parent, &parent_presence, parent_flags, None); + test_helper::insert_single_entry(rng, &mut parent, parent_presence, parent_flags, None); let key = &outpoint; // prepare the map for batch write. let mut single_entry_map = BTreeMap::new(); // inserts utxo in the map - if let Some(child_flags) = child_flags { + if let Some((is_fresh, is_dirty)) = child_flags { match child_presence { Absent => { panic!("Please use `Present` or `Spent` presence when child flags are specified."); } Present => { let (utxo, _) = test_helper::create_utxo(rng, 0); - let entry = UtxoEntry::new(Some(utxo), child_flags); + let entry = UtxoEntry::new(Some(utxo), is_fresh, is_dirty); single_entry_map.insert(key.clone(), entry); } Spent => { - let entry = UtxoEntry::new(None, child_flags); + let entry = UtxoEntry::new(None, is_fresh, is_dirty); single_entry_map.insert(key.clone(), entry); } } @@ -190,15 +190,15 @@ fn check_get_mut_utxo( parent_presence: Presence, cache_presence: Presence, result_presence: Presence, - cache_flags: Option, - result_flags: Option, + cache_flags: Option<(IsFresh, IsDirty)>, + result_flags: Option<(IsFresh, IsDirty)>, ) { let mut parent = UtxosCache::new_for_test(H256::random().into()); let (parent_utxo, parent_outpoint) = test_helper::insert_single_entry( rng, &mut parent, - &parent_presence, - Some(FRESH | DIRTY), + parent_presence, + Some((IsFresh::Yes, IsDirty::Yes)), None, ); @@ -209,7 +209,7 @@ fn check_get_mut_utxo( let (child_utxo, child_outpoint) = test_helper::insert_single_entry( rng, &mut child, - &cache_presence, + cache_presence, cache_flags, Some(parent_outpoint), ); @@ -234,12 +234,12 @@ fn check_get_mut_utxo( // let's try to update the utxo. let old_height_num = match utxo.source() { - UtxoSource::BlockChain(h) => h, - UtxoSource::MemPool => panic!("Unexpected arm"), + UtxoSource::Blockchain(h) => h, + UtxoSource::Mempool => panic!("Unexpected arm"), }; let new_height_num = old_height_num.checked_add(1).expect("should be able to increment"); - let new_height = UtxoSource::BlockChain(new_height_num); + let new_height = UtxoSource::Blockchain(new_height_num); utxo.set_height(new_height.clone()); assert_eq!(new_height, *utxo.source()); @@ -275,35 +275,35 @@ fn check_get_mut_utxo( fn add_utxo_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* - CACHE CACHE Flags Possible RESULT flags RESULT of `add_utxo` method - PRESENCE Overwrite + CACHE CACHE Flags Possible RESULT flags RESULT of `add_utxo` method + PRESENCE Overwrite */ - check_add_utxo(&mut rng, Absent, None, false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(&mut rng, Absent, None, true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Absent, None, false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); + check_add_utxo(&mut rng, Absent, None, true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some(0), false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(&mut rng, Spent, Some(0), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::No)), false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::No)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some(FRESH), false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(&mut rng, Spent, Some(FRESH), true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::No)), false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::No)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some(DIRTY), false, Some(DIRTY), Ok(())); - check_add_utxo(&mut rng, Spent, Some(DIRTY), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::Yes)), false, Some((IsFresh::No, IsDirty::Yes)), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::Yes)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some(FRESH | DIRTY),false, Some(FRESH | DIRTY), Ok(())); - check_add_utxo(&mut rng, Spent, Some(FRESH | DIRTY),true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::Yes)), false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); + check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::Yes)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some(0), false, None, Err(OverwritingUtxo)); - check_add_utxo(&mut rng, Present, Some(0), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::No)), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::No)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some(FRESH), false, None, Err(OverwritingUtxo)); - check_add_utxo(&mut rng, Present, Some(FRESH), true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some((IsFresh::Yes, IsDirty::No)), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some((IsFresh::Yes, IsDirty::No)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some(DIRTY), false, None, Err(OverwritingUtxo)); - check_add_utxo(&mut rng, Present, Some(DIRTY), true, Some(DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::Yes)), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::Yes)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some(FRESH | DIRTY), false, None, Err(OverwritingUtxo)); - check_add_utxo(&mut rng, Present, Some(FRESH | DIRTY), true, Some(FRESH | DIRTY), Ok(())); + check_add_utxo(&mut rng, Present, Some((IsFresh::Yes, IsDirty::Yes)), false, None, Err(OverwritingUtxo)); + check_add_utxo(&mut rng, Present, Some((IsFresh::Yes, IsDirty::Yes)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); } #[rstest] @@ -316,35 +316,35 @@ fn spend_utxo_test(#[case] seed: Seed) { PARENT CACHE PRESENCE PRESENCE CACHE Flags RESULT RESULT Flags */ - check_spend_utxo(&mut rng, Absent, Absent, None, Err(Error::NoUtxoFound), None); - check_spend_utxo(&mut rng, Absent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(&mut rng, Absent, Spent, Some(FRESH), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(&mut rng, Absent, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(&mut rng, Absent, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(&mut rng, Absent, Present, Some(0), Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Absent, Present, Some(FRESH), Ok(()), None); - check_spend_utxo(&mut rng, Absent, Present, Some(DIRTY), Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Absent, Present, Some(FRESH | DIRTY), Ok(()), None); + check_spend_utxo(&mut rng, Absent, Absent, None, Err(Error::NoUtxoFound), None); + check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::No, IsDirty::No)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::Yes, IsDirty::No)), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::No, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::Yes, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::No, IsDirty::No)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::Yes, IsDirty::No)), Ok(()), None); + check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::No, IsDirty::Yes)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::Yes, IsDirty::Yes)), Ok(()), None); // this should fail, since there's nothing to remove. - check_spend_utxo(&mut rng, Spent, Absent, None, Err(Error::NoUtxoFound), None); - check_spend_utxo(&mut rng, Spent, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); + check_spend_utxo(&mut rng, Spent, Absent, None, Err(Error::NoUtxoFound), None); + check_spend_utxo(&mut rng, Spent, Spent, Some((IsFresh::No, IsDirty::No)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); // this should fail, as there's nothing to remove. - check_spend_utxo(&mut rng, Spent, Absent, Some(FRESH), Err(Error::NoUtxoFound), None); - check_spend_utxo(&mut rng, Spent, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(&mut rng, Spent, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(&mut rng, Spent, Present, Some(0), Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Spent, Present, Some(FRESH), Ok(()), None); - check_spend_utxo(&mut rng, Spent, Present, Some(DIRTY), Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Spent, Present, Some(FRESH | DIRTY), Ok(()), None); - check_spend_utxo(&mut rng, Present, Absent, None, Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Present, Spent, Some(0), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(&mut rng, Present, Spent, Some(FRESH), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(&mut rng, Present, Spent, Some(DIRTY), Err(Error::UtxoAlreadySpent), Some(DIRTY)); - check_spend_utxo(&mut rng, Present, Spent, Some(FRESH | DIRTY), Err(Error::UtxoAlreadySpent), None); - check_spend_utxo(&mut rng, Present, Present, Some(0), Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Present, Present, Some(FRESH), Ok(()), None); - check_spend_utxo(&mut rng, Present, Present, Some(DIRTY), Ok(()), Some(DIRTY)); - check_spend_utxo(&mut rng, Present, Present, Some(FRESH | DIRTY), Ok(()), None); + check_spend_utxo(&mut rng, Spent, Absent, Some((IsFresh::Yes, IsDirty::No)), Err(Error::NoUtxoFound), None); + check_spend_utxo(&mut rng, Spent, Spent, Some((IsFresh::No, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Spent, Spent, Some((IsFresh::Yes, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::No, IsDirty::No)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::Yes, IsDirty::No)), Ok(()), None); + check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::No, IsDirty::Yes)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::Yes, IsDirty::Yes)), Ok(()), None); + check_spend_utxo(&mut rng, Present, Absent, None, Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::No, IsDirty::No)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::Yes, IsDirty::No)), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::No, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::Yes, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), None); + check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::No, IsDirty::No)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::Yes, IsDirty::No)), Ok(()), None); + check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::No, IsDirty::Yes)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); + check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Ok(()), None); } #[rstest] @@ -355,53 +355,53 @@ fn batch_write_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* PARENT CACHE RESULT - PRESENCE PRESENCE PRESENCE PARENT Flags CACHE Flags RESULT Flags + PRESENCE PRESENCE PRESENCE PARENT Flags CACHE Flags RESULT Flags */ - check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); - check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Absent, Spent , Ok(Absent), None, Some(FRESH | DIRTY), None ); - check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some(FRESH | DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(0), None, Some(0)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH), None, Some(FRESH)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(DIRTY), None, Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some(FRESH | DIRTY), None, Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(0), Some(FRESH | DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some(DIRTY), Some(FRESH | DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY), Some(DIRTY), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some(FRESH | DIRTY), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(0), Some(FRESH | DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH), Some(FRESH | DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(DIRTY), Some(FRESH | DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some(FRESH | DIRTY), Some(FRESH | DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(0), None, Some(0)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH), None, Some(FRESH)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(DIRTY), None, Some(DIRTY)); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some(FRESH | DIRTY), None , Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH), Some(DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some(FRESH | DIRTY), Some(DIRTY), None); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(0), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(0), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(DIRTY), Some(DIRTY), Some(DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(DIRTY), Some(FRESH | DIRTY), None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some(FRESH | DIRTY), Some(DIRTY), Some(FRESH | DIRTY)); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some(FRESH | DIRTY), Some(FRESH | DIRTY), None); + check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); + check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Absent, Spent , Ok(Absent), None, Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::No, IsDirty::No)), None, Some((IsFresh::No, IsDirty::No))); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::Yes, IsDirty::No)), None, Some((IsFresh::Yes, IsDirty::No))); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), None, Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::Yes, IsDirty::Yes)), None, Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::No, IsDirty::No)), None, Some((IsFresh::No, IsDirty::No))); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), None, Some((IsFresh::Yes, IsDirty::No))); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), None, Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), None , Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); + check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); } #[rstest] @@ -412,35 +412,35 @@ fn access_utxo_test(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); /* PARENT CACHE RESULT CACHE - PRESENCE PRESENCE PRESENCE Flags RESULT Flags + PRESENCE PRESENCE PRESENCE Flags RESULT Flags */ - check_get_mut_utxo(&mut rng, Absent, Absent, Absent, None, None); - check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(0), Some(0)); - check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(FRESH), Some(FRESH)); - check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(0), Some(0)); - check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(FRESH), Some(FRESH)); - check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(&mut rng, Absent, Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(&mut rng, Spent , Absent, Absent, None, None); - check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(0), Some(0)); - check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(FRESH), Some(FRESH)); - check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(0), Some(0)); - check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(FRESH), Some(FRESH)); - check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(&mut rng, Spent , Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(&mut rng, Present, Absent, Present, None, Some(0)); - check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(0), Some(0)); - check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(FRESH), Some(FRESH)); - check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some(FRESH | DIRTY),Some(FRESH | DIRTY)); - check_get_mut_utxo(&mut rng, Present, Present, Present, Some(0), Some(0)); - check_get_mut_utxo(&mut rng, Present, Present, Present, Some(FRESH), Some(FRESH)); - check_get_mut_utxo(&mut rng, Present, Present, Present, Some(DIRTY), Some(DIRTY)); - check_get_mut_utxo(&mut rng, Present, Present, Present, Some(FRESH | DIRTY),Some(FRESH | DIRTY)); + check_get_mut_utxo(&mut rng, Absent, Absent, Absent, None, None); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Spent , Absent, Absent, None, None); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Present, Absent, Present, None, Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); + check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); } #[rstest] @@ -502,7 +502,9 @@ fn multiple_update_utxos_test(#[case] seed: Seed) { 0x01, ) .unwrap(); - assert!(cache.add_utxos(&tx, UtxoSource::BlockChain(BlockHeight::new(2)), false).is_ok()); + assert!(cache + .add_utxos_from_tx(&tx, UtxoSource::Blockchain(BlockHeight::new(2)), false) + .is_ok()); // check that the outputs of tx are added in the cache. tx.outputs().iter().enumerate().for_each(|(i, x)| { diff --git a/utxo/src/tests/test_helper.rs b/utxo/src/tests/test_helper.rs index a3b9fd94b..256cd6fe6 100644 --- a/utxo/src/tests/test_helper.rs +++ b/utxo/src/tests/test_helper.rs @@ -14,7 +14,7 @@ // limitations under the License. use crate::{ - utxo_entry::{UtxoEntry, DIRTY, FRESH}, + utxo_entry::{IsDirty, IsFresh, UtxoEntry}, Utxo, UtxosCache, }; use common::{ @@ -30,7 +30,7 @@ use crypto::{ }; use itertools::Itertools; -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum Presence { Absent, Present, @@ -128,8 +128,8 @@ fn inner_create_utxo(rng: &mut impl Rng, block_height: Option) -> (Utxo, Ou pub fn insert_single_entry( rng: &mut impl Rng, cache: &mut UtxosCache, - cache_presence: &Presence, - cache_flags: Option, + cache_presence: Presence, + cache_flags: Option<(IsFresh, IsDirty)>, outpoint: Option, ) -> (Utxo, OutPoint) { let rng_height = rng.gen_range(0..(u64::MAX - 1)); @@ -142,10 +142,10 @@ pub fn insert_single_entry( // there shouldn't be an existing entry. Don't bother with the cache flags. } other => { - let flags = cache_flags.expect("please provide flags."); + let (is_fresh, is_dirty) = cache_flags.expect("please provide flags."); let entry = match other { - Presence::Present => UtxoEntry::new(Some(utxo.clone()), flags), - Presence::Spent => UtxoEntry::new(None, flags), + Presence::Present => UtxoEntry::new(Some(utxo.clone()), is_fresh, is_dirty), + Presence::Spent => UtxoEntry::new(None, is_fresh, is_dirty), _ => { panic!("something wrong in the code.") } @@ -162,14 +162,14 @@ pub fn insert_single_entry( /// checks the dirty, fresh, and spent flags. pub(crate) fn check_flags( result_entry: Option<&UtxoEntry>, - expected_flags: Option, + expected_flags: Option<(IsFresh, IsDirty)>, is_spent: bool, ) { - if let Some(flags) = expected_flags { + if let Some((is_fresh, is_dirty)) = expected_flags { let result_entry = result_entry.expect("this should have an entry inside"); - assert_eq!(result_entry.is_dirty(), (flags & DIRTY) == DIRTY); - assert_eq!(result_entry.is_fresh(), (flags & FRESH) == FRESH); + assert_eq!(IsDirty::from(result_entry.is_dirty()), is_dirty); + assert_eq!(IsFresh::from(result_entry.is_fresh()), is_fresh); assert_eq!(result_entry.is_spent(), is_spent); } else { assert!(result_entry.is_none()); diff --git a/utxo/src/utxo.rs b/utxo/src/utxo.rs index 297fe6737..5f478463e 100644 --- a/utxo/src/utxo.rs +++ b/utxo/src/utxo.rs @@ -22,22 +22,22 @@ use std::fmt::Debug; #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] pub enum UtxoSource { /// At which height this containing tx was included in the active block chain - BlockChain(BlockHeight), - MemPool, + Blockchain(BlockHeight), + Mempool, } impl UtxoSource { pub fn is_mempool(&self) -> bool { match self { - UtxoSource::BlockChain(_) => false, - UtxoSource::MemPool => true, + UtxoSource::Blockchain(_) => false, + UtxoSource::Mempool => true, } } pub fn blockchain_height(&self) -> Result { match self { - UtxoSource::BlockChain(h) => Ok(*h), - UtxoSource::MemPool => Err(crate::Error::NoBlockchainHeightFound), + UtxoSource::Blockchain(h) => Ok(*h), + UtxoSource::Mempool => Err(crate::Error::NoBlockchainHeightFound), } } } @@ -68,7 +68,7 @@ impl Utxo { Self { output, is_block_reward, - source: UtxoSource::BlockChain(height), + source: UtxoSource::Blockchain(height), } } @@ -76,7 +76,7 @@ impl Utxo { Self { output, is_block_reward, - source: UtxoSource::MemPool, + source: UtxoSource::Mempool, } } diff --git a/utxo/src/utxo_entry.rs b/utxo/src/utxo_entry.rs index c473d4b13..13f27e08f 100644 --- a/utxo/src/utxo_entry.rs +++ b/utxo/src/utxo_entry.rs @@ -17,12 +17,6 @@ use crate::Utxo; use serialization::{Decode, Encode}; use std::fmt::Debug; -/// The utxo entry is dirty when this version is different from the parent. -pub const DIRTY: u8 = 0b01; -/// The utxo entry is fresh when the parent does not have this utxo or -/// if it exists in parent but not in current cache. -pub const FRESH: u8 = 0b10; - /// Tells the state of the utxo #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] #[allow(clippy::large_enum_variant)] @@ -31,30 +25,73 @@ pub enum UtxoStatus { Entry(Utxo), } +/// The utxo entry is fresh when the parent does not have this utxo or +/// if it exists in parent but not in current cache. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +pub enum IsFresh { + Yes, + No, +} + +impl From for IsFresh { + fn from(v: bool) -> Self { + if v { + IsFresh::Yes + } else { + IsFresh::No + } + } +} + +/// The utxo entry is dirty when this version is different from the parent. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +pub enum IsDirty { + Yes, + No, +} + +impl From for IsDirty { + fn from(v: bool) -> Self { + if v { + IsDirty::Yes + } else { + IsDirty::No + } + } +} + /// Just the Utxo with additional information. #[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] pub struct UtxoEntry { status: UtxoStatus, - flags: u8, + is_fresh: IsFresh, + is_dirty: IsDirty, } impl UtxoEntry { - pub fn new(utxo: Option, flags: u8) -> UtxoEntry { + pub fn new(utxo: Option, is_fresh: IsFresh, is_dirty: IsDirty) -> UtxoEntry { UtxoEntry { status: match utxo { Some(utxo) => UtxoStatus::Entry(utxo), None => UtxoStatus::Spent, }, - flags, + is_fresh, + is_dirty, } } pub fn is_dirty(&self) -> bool { - self.flags & DIRTY != 0 + match self.is_dirty { + IsDirty::Yes => true, + IsDirty::No => false, + } } pub fn is_fresh(&self) -> bool { - self.flags & FRESH != 0 + match self.is_fresh { + IsFresh::Yes => true, + IsFresh::No => false, + } } pub fn is_spent(&self) -> bool { From f4ae45fb0e5f3aad6d77e50f4e72d26d96d1333b Mon Sep 17 00:00:00 2001 From: Heorhii Azarov Date: Mon, 15 Aug 2022 20:29:47 +0300 Subject: [PATCH 12/12] Check UtxoEntry validity --- utxo/src/cache.rs | 46 +++++++++++++++++---------- utxo/src/storage/in_memory.rs | 3 +- utxo/src/storage/test.rs | 2 +- utxo/src/tests/mod.rs | 57 +++------------------------------- utxo/src/utxo_entry.rs | 58 ++++++++++++++++++++++++++++++++++- 5 files changed, 94 insertions(+), 72 deletions(-) diff --git a/utxo/src/cache.rs b/utxo/src/cache.rs index cf32839ca..dcdbbb998 100644 --- a/utxo/src/cache.rs +++ b/utxo/src/cache.rs @@ -345,35 +345,41 @@ mod unit_test { #[rstest] #[trace] #[case(Seed::from_entropy())] - fn test_uncache(#[case] seed: Seed) { + fn uncache_absent(#[case] seed: Seed) { let mut rng = make_seedable_rng(seed); let mut cache = UtxosCache::new_for_test(H256::random().into()); - // when the entry is not dirty and not fresh - let (_, outp) = insert_single_entry( - &mut rng, - &mut cache, - Presence::Present, - Some((IsFresh::No, IsDirty::No)), - None, - ); - assert!(cache.uncache(&outp).is_ok()); - assert!(!cache.has_utxo_in_cache(&outp)); - // when the outpoint does not exist. let (_, outp) = insert_single_entry(&mut rng, &mut cache, Presence::Absent, None, None); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); assert!(!cache.has_utxo_in_cache(&outp)); + } - // when the entry is fresh, entry cannot be removed. + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn uncache_not_fresh_not_dirty(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let mut cache = UtxosCache::new_for_test(H256::random().into()); + + // when the entry is not dirty and not fresh let (_, outp) = insert_single_entry( &mut rng, &mut cache, Presence::Present, - Some((IsFresh::Yes, IsDirty::No)), + Some((IsFresh::No, IsDirty::No)), None, ); - assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); + assert!(cache.uncache(&outp).is_ok()); + assert!(!cache.has_utxo_in_cache(&outp)); + } + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn uncache_dirty_not_fresh(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let mut cache = UtxosCache::new_for_test(H256::random().into()); // when the entry is dirty, entry cannot be removed. let (_, outp) = insert_single_entry( @@ -384,13 +390,21 @@ mod unit_test { None, ); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); + } + + #[rstest] + #[trace] + #[case(Seed::from_entropy())] + fn uncache_fresh_and_dirty(#[case] seed: Seed) { + let mut rng = make_seedable_rng(seed); + let mut cache = UtxosCache::new_for_test(H256::random().into()); // when the entry is both fresh and dirty, entry cannot be removed. let (_, outp) = insert_single_entry( &mut rng, &mut cache, Presence::Present, - Some((IsFresh::Yes, IsDirty::No)), + Some((IsFresh::Yes, IsDirty::Yes)), None, ); assert_eq!(Error::NoUtxoFound, cache.uncache(&outp).unwrap_err()); diff --git a/utxo/src/storage/in_memory.rs b/utxo/src/storage/in_memory.rs index aa762f354..6d5b8e874 100644 --- a/utxo/src/storage/in_memory.rs +++ b/utxo/src/storage/in_memory.rs @@ -38,7 +38,8 @@ impl UtxosDBInMemoryImpl { } } - pub(super) fn internal_store(&mut self) -> &BTreeMap { + #[cfg(test)] + pub fn internal_store(&mut self) -> &BTreeMap { &self.store } } diff --git a/utxo/src/storage/test.rs b/utxo/src/storage/test.rs index 42147648b..e180e4cb9 100644 --- a/utxo/src/storage/test.rs +++ b/utxo/src/storage/test.rs @@ -341,7 +341,7 @@ fn test_utxo(#[case] seed: Seed) { { let (utxo, outpoint) = create_utxo(&mut rng, 1); let mut map = BTreeMap::new(); - let entry = UtxoEntry::new(Some(utxo), IsFresh::Yes, IsDirty::No); + let entry = UtxoEntry::new(Some(utxo), IsFresh::No, IsDirty::No); map.insert(outpoint.clone(), entry); let new_hash = Id::new(H256::random()); diff --git a/utxo/src/tests/mod.rs b/utxo/src/tests/mod.rs index 35a343c37..05cfb083d 100644 --- a/utxo/src/tests/mod.rs +++ b/utxo/src/tests/mod.rs @@ -87,7 +87,8 @@ fn check_spend_utxo( rng, &mut parent, parent_presence, - Some((IsFresh::Yes, IsDirty::Yes)), + // parent flags are irrelevant, but this combination can be used for both spent/unspent + Some((IsFresh::No, IsDirty::Yes)), None, ); @@ -198,7 +199,8 @@ fn check_get_mut_utxo( rng, &mut parent, parent_presence, - Some((IsFresh::Yes, IsDirty::Yes)), + // parent flags are irrelevant, but this combination can be used for both spent/unspent + Some((IsFresh::No, IsDirty::Yes)), None, ); @@ -281,24 +283,15 @@ fn add_utxo_test(#[case] seed: Seed) { check_add_utxo(&mut rng, Absent, None, false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); check_add_utxo(&mut rng, Absent, None, true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::No)), false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::No)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::No)), false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::No)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::Yes)), false, Some((IsFresh::No, IsDirty::Yes)), Ok(())); check_add_utxo(&mut rng, Spent, Some((IsFresh::No, IsDirty::Yes)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::Yes)), false, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Spent, Some((IsFresh::Yes, IsDirty::Yes)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::No)), false, None, Err(OverwritingUtxo)); check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::No)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some((IsFresh::Yes, IsDirty::No)), false, None, Err(OverwritingUtxo)); - check_add_utxo(&mut rng, Present, Some((IsFresh::Yes, IsDirty::No)), true, Some((IsFresh::Yes, IsDirty::Yes)), Ok(())); - check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::Yes)), false, None, Err(OverwritingUtxo)); check_add_utxo(&mut rng, Present, Some((IsFresh::No, IsDirty::Yes)), true, Some((IsFresh::No, IsDirty::Yes)), Ok(())); @@ -317,32 +310,20 @@ fn spend_utxo_test(#[case] seed: Seed) { PRESENCE PRESENCE CACHE Flags RESULT RESULT Flags */ check_spend_utxo(&mut rng, Absent, Absent, None, Err(Error::NoUtxoFound), None); - check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::No, IsDirty::No)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::Yes, IsDirty::No)), Err(Error::UtxoAlreadySpent), None); check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::No, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Absent, Spent, Some((IsFresh::Yes, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), None); check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::No, IsDirty::No)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::Yes, IsDirty::No)), Ok(()), None); check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::No, IsDirty::Yes)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); check_spend_utxo(&mut rng, Absent, Present, Some((IsFresh::Yes, IsDirty::Yes)), Ok(()), None); - // this should fail, since there's nothing to remove. check_spend_utxo(&mut rng, Spent, Absent, None, Err(Error::NoUtxoFound), None); - check_spend_utxo(&mut rng, Spent, Spent, Some((IsFresh::No, IsDirty::No)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); - // this should fail, as there's nothing to remove. check_spend_utxo(&mut rng, Spent, Absent, Some((IsFresh::Yes, IsDirty::No)), Err(Error::NoUtxoFound), None); - check_spend_utxo(&mut rng, Spent, Spent, Some((IsFresh::No, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Spent, Spent, Some((IsFresh::Yes, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), None); check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::No, IsDirty::No)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::Yes, IsDirty::No)), Ok(()), None); check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::No, IsDirty::Yes)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); check_spend_utxo(&mut rng, Spent, Present, Some((IsFresh::Yes, IsDirty::Yes)), Ok(()), None); check_spend_utxo(&mut rng, Present, Absent, None, Ok(()), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::No, IsDirty::No)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::Yes, IsDirty::No)), Err(Error::UtxoAlreadySpent), None); check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::No, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Present, Spent, Some((IsFresh::Yes, IsDirty::Yes)), Err(Error::UtxoAlreadySpent), None); check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::No, IsDirty::No)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); - check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::Yes, IsDirty::No)), Ok(()), None); check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::No, IsDirty::Yes)), Ok(()), Some((IsFresh::No, IsDirty::Yes))); check_spend_utxo(&mut rng, Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Ok(()), None); } @@ -359,45 +340,24 @@ fn batch_write_test(#[case] seed: Seed) { */ check_write_utxo(&mut rng, Absent, Absent, Ok(Absent), None, None, None); check_write_utxo(&mut rng, Absent, Spent , Ok(Spent), None, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Absent, Spent , Ok(Absent), None, Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Absent, Present, Ok(Present), None, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::No, IsDirty::No)), None, Some((IsFresh::No, IsDirty::No))); check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::Yes, IsDirty::No)), None, Some((IsFresh::Yes, IsDirty::No))); check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), None, Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Absent, Ok(Spent), Some((IsFresh::Yes, IsDirty::Yes)), None, Some((IsFresh::Yes, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Spent , Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Spent , Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); - check_write_utxo(&mut rng, Spent , Present, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::No, IsDirty::No)), None, Some((IsFresh::No, IsDirty::No))); - check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), None, Some((IsFresh::Yes, IsDirty::No))); check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), None, Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Present, Absent, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), None , Some((IsFresh::Yes, IsDirty::Yes))); check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Present, Spent , Ok(Spent), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Present, Spent , Ok(Absent), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Present, Spent , Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); - check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); - check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_write_utxo(&mut rng, Present, Present, Err(FreshUtxoAlreadyExists), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes)), None); check_write_utxo(&mut rng, Present, Present, Ok(Present), Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); @@ -415,30 +375,21 @@ fn access_utxo_test(#[case] seed: Seed) { PRESENCE PRESENCE PRESENCE Flags RESULT Flags */ check_get_mut_utxo(&mut rng, Absent, Absent, Absent, None, None); - check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_get_mut_utxo(&mut rng, Absent, Spent , Spent , Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); - check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Absent, Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Spent , Absent, Absent, None, None); - check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_get_mut_utxo(&mut rng, Spent , Spent , Spent , Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); - check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Spent , Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Present, Absent, Present, None, Some((IsFresh::No, IsDirty::No))); - check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); - check_get_mut_utxo(&mut rng, Present, Spent , Spent , Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::No, IsDirty::No)), Some((IsFresh::No, IsDirty::No))); - check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::Yes, IsDirty::No)), Some((IsFresh::Yes, IsDirty::No))); check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::No, IsDirty::Yes)), Some((IsFresh::No, IsDirty::Yes))); check_get_mut_utxo(&mut rng, Present, Present, Present, Some((IsFresh::Yes, IsDirty::Yes)), Some((IsFresh::Yes, IsDirty::Yes))); } diff --git a/utxo/src/utxo_entry.rs b/utxo/src/utxo_entry.rs index 13f27e08f..6a5747dbf 100644 --- a/utxo/src/utxo_entry.rs +++ b/utxo/src/utxo_entry.rs @@ -70,14 +70,27 @@ pub struct UtxoEntry { impl UtxoEntry { pub fn new(utxo: Option, is_fresh: IsFresh, is_dirty: IsDirty) -> UtxoEntry { - UtxoEntry { + let entry = UtxoEntry { status: match utxo { Some(utxo) => UtxoStatus::Entry(utxo), None => UtxoStatus::Spent, }, is_fresh, is_dirty, + }; + + // Out of these 2^3 = 8 states, only some combinations are valid: + // - unspent, FRESH, DIRTY (e.g. a new utxo created in the cache) + // - unspent, not FRESH, DIRTY (e.g. a utxo changed in the cache during a reorg) + // - unspent, not FRESH, not DIRTY (e.g. an unspent utxo fetched from the parent cache) + // - spent, FRESH, not DIRTY (e.g. a spent utxo fetched from the parent cache) + // - spent, not FRESH, DIRTY (e.g. a utxo is spent and spentness needs to be flushed to the parent) + match &entry.status { + UtxoStatus::Entry(_) => assert!(!entry.is_fresh() || entry.is_dirty()), + &UtxoStatus::Spent => assert!(entry.is_fresh() ^ entry.is_dirty()), } + + entry } pub fn is_dirty(&self) -> bool { @@ -119,3 +132,46 @@ impl UtxoEntry { } } } + +#[cfg(test)] +mod unit_test { + use super::*; + use crate::UtxoSource; + use common::{ + chain::{tokens::OutputValue, Destination, OutputPurpose, TxOutput}, + primitives::Amount, + }; + use rstest::rstest; + + fn some_utxo() -> Option { + Some(Utxo::new( + TxOutput::new( + OutputValue::Coin(Amount::from_atoms(1)), + OutputPurpose::Transfer(Destination::AnyoneCanSpend), + ), + false, + UtxoSource::Mempool, + )) + } + + #[rustfmt::skip] + #[rstest] + #[case(some_utxo(), IsFresh::Yes, IsDirty::Yes)] + #[case(some_utxo(), IsFresh::No, IsDirty::Yes)] + #[case(some_utxo(), IsFresh::No, IsDirty::No)] + #[case(None, IsFresh::Yes, IsDirty::No)] + #[case(None, IsFresh::No, IsDirty::Yes)] + #[should_panic] + #[case(None, IsFresh::Yes, IsDirty::Yes)] + #[should_panic] + #[case(None, IsFresh::No, IsDirty::No)] + #[should_panic] + #[case(some_utxo(), IsFresh::Yes, IsDirty::No)] + fn create_utxo_entry( + #[case] utxo: Option, + #[case] is_fresh: IsFresh, + #[case] is_dirty: IsDirty, + ) { + let _ = UtxoEntry::new(utxo, is_fresh, is_dirty); + } +}