diff --git a/README.md b/README.md index d3316e7..2d9876b 100644 --- a/README.md +++ b/README.md @@ -6,53 +6,77 @@ The crate was designed to be the blockchain state database. It provides persiste ### FEATURES: - Key-value updates(in a transaction) applied to storage can be reverted if transaction fails. -- Key-value updates(in a block) applied to storage can be reverted if block failes to commit. -- Prefix-based key-value iteration on commited state. +- Key-value updates(in a block) applied to storage can be reverted if block fails to commit. +- Prefix-based key-value iteration on committed state. - Prefix-based key-value iteration on latest state. +- Versioned key-value pairs available for queries. +- Entire chain-state available as a batch of key-value pairs at any height within version window. - Root hash calculation. **Example:** +```toml +[dependencies] +storage = { git = "ssh://git@github.com/FindoraNetwork/storage.git", branch = "master" } +parking_lot = "0.11.1" +``` ```rust extern crate storage; -use storage::state::ChainState; +use storage::state::{ChainState, State}; use storage::db::FinDB; use storage::store::PrefixedStore; use parking_lot::RwLock; use std::sync::Arc; +use std::thread; +use storage::store::traits::{Stated, Store}; + +// This window is used to determine how many versions of each KV pair are to be kept in the +// auxiliary db. +const VER_WINDOW:u64 = 100; + +fn main() { + println!("Testing Prefixed Store!"); + prefixed_store(); +} fn prefixed_store() { - // create store - let path = thread::current().name().unwrap().to_owned(); - let fdb = FinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); - let mut state = State::new(cs); - let mut store = PrefixedStore::new("my_store", &mut state); - let hash0 = store.state().root_hash(); - - // set kv pairs and commit - store.set(b"k10", b"v10".to_vec()); - store.set(b"k20", b"v20".to_vec()); - let (hash1, _height) = store.state_mut().commit(1).unwrap(); - - // verify - assert_eq!(store.get(b"k10").unwrap(), Some(b"v10".to_vec())); - assert_eq!(store.get(b"k20").unwrap(), Some(b"v20".to_vec())); - assert_ne!(hash0, hash1); - - // add, delete and update - store.set(b"k10", b"v15".to_vec()); - store.delete(b"k20").unwrap(); - store.set(b"k30", b"v30".to_vec()); - - // verify - assert_eq!(store.get(b"k10").unwrap(), Some(b"v15".to_vec())); - assert_eq!(store.get(b"k20").unwrap(), None); - assert_eq!(store.get(b"k30").unwrap(), Some(b"v30".to_vec())); - - // rollback and verify - store.state_mut().discard_session(); - assert_eq!(store.get(b"k10").unwrap(), Some(b"v10".to_vec())); - assert_eq!(store.get(b"k20").unwrap(), Some(b"v20".to_vec())); - assert_eq!(store.get(b"k30").unwrap(), None); + // create store + let path = thread::current().name().unwrap().to_owned(); + let fdb = FinDB::open(path).expect("failed to open db"); + let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string(), VER_WINDOW))); + let mut state = State::new(cs); + let mut store = PrefixedStore::new("my_store", &mut state); + let hash0 = store.state().root_hash(); + + // set kv pairs and commit + store.set(b"k10", b"v10".to_vec()); + store.set(b"k20", b"v20".to_vec()); + let (hash1, _height) = store.state_mut().commit(1).unwrap(); + + // verify + assert_eq!(store.get(b"k10").unwrap(), Some(b"v10".to_vec())); + assert_eq!(store.get(b"k20").unwrap(), Some(b"v20".to_vec())); + assert_ne!(hash0, hash1); + + // add, delete and update + store.set(b"k10", b"v15".to_vec()); + store.delete(b"k20").unwrap(); + store.set(b"k30", b"v30".to_vec()); + + // verify + assert_eq!(store.get(b"k10").unwrap(), Some(b"v15".to_vec())); + assert_eq!(store.get(b"k20").unwrap(), None); + assert_eq!(store.get(b"k30").unwrap(), Some(b"v30".to_vec())); + + // rollback and verify + store.state_mut().discard_session(); + assert_eq!(store.get(b"k10").unwrap(), Some(b"v10".to_vec())); + assert_eq!(store.get(b"k20").unwrap(), Some(b"v20".to_vec())); + assert_eq!(store.get(b"k30").unwrap(), None); + + // get previous version of a key value pair + store.set(b"k10", b"v25".to_vec()); + let _ = store.state_mut().commit(2); + assert_eq!(store.get(b"k10").unwrap(), Some(b"v25".to_vec())); + assert_eq!(store.get_v(b"k10", 1).unwrap(), Some(b"v10".to_vec())); } ``` diff --git a/src/state/chain_state.rs b/src/state/chain_state.rs index 2859075..66552ee 100644 --- a/src/state/chain_state.rs +++ b/src/state/chain_state.rs @@ -4,10 +4,15 @@ /// and RocksDB backend. /// use crate::db::{IterOrder, KVBatch, KValue, MerkleDB}; +use crate::state::cache::KVMap; +use crate::store::Prefix; use merk::tree::{Tree, NULL_HASH}; use ruc::*; +use std::str; const HEIGHT_KEY: &[u8; 6] = b"Height"; +const SPLIT_BGN: &str = "_"; +const TOMBSTONE: [u8; 1] = [206u8]; /// Concrete ChainState struct containing a reference to an instance of MerkleDB, a name and /// current tree height. @@ -16,6 +21,7 @@ where D: MerkleDB, { name: String, + ver_window: u64, db: D, } @@ -30,16 +36,20 @@ where /// MerkleDB trait is assigned. /// /// Returns the implicit struct - pub fn new(db: D, name: String) -> Self { - let mut db_name = "chain-state"; + pub fn new(db: D, name: String, ver_window: u64) -> Self { + let mut db_name = String::from("chain-state"); if !name.is_empty() { - db_name = name.as_str(); + db_name = name; } - ChainState { - name: db_name.to_string(), + let mut cs = ChainState { + name: db_name, + ver_window, db, - } + }; + cs.clean_aux_db(); + + cs } /// Gets a value for the given key from the primary data section in RocksDB @@ -126,6 +136,101 @@ where } } + /// Queries the Aux DB for existence of a key. + /// + /// Returns a bool wrapped in a result as the query involves DB access. + pub fn exists_aux(&self, key: &[u8]) -> Result { + match self.get_aux(key).c(d!())? { + Some(_) => Ok(true), + None => Ok(false), + } + } + + /// Deletes the auxiliary keys stored with a prefix < ( height - ver_window ), + /// if the ver_window == 0 then the function returns without deleting any keys. + /// + /// The main purpose is to save memory on the disk + fn prune_aux_batch(&self, height: u64, batch: &mut KVBatch) -> Result<()> { + if self.ver_window == 0 || height < self.ver_window + 1 { + return Ok(()); + } + //Build range keys for window limits + let window_start_height = Self::height_str(height - self.ver_window); + let pruning_height = Self::height_str(height - self.ver_window - 1); + + let new_window_limit = Prefix::new("VER".as_bytes()).push(window_start_height.as_bytes()); + let old_window_limit = Prefix::new("VER".as_bytes()).push(pruning_height.as_bytes()); + + //Range all auxiliary keys at pruning height + self.iterate_aux( + &old_window_limit.begin(), + &old_window_limit.end(), + IterOrder::Asc, + &mut |(k, v)| -> bool { + let raw_key = Self::get_raw_versioned_key(&k).unwrap_or_default(); + if raw_key.is_empty() { + return false; + } + //If the key doesn't already exist in the window start height, need to add it + //If the value of this key is a TOMBSTONE then we don't need to add it + if !self + .exists_aux(new_window_limit.push(raw_key.as_bytes()).as_ref()) + .unwrap_or(false) + && v.ne(&TOMBSTONE) + { + // Add the key to new window limit height + batch.push(( + new_window_limit + .clone() + .push(raw_key.as_ref()) + .as_ref() + .to_vec(), + Some(v), + )); + } + //Delete the key from the batch + batch.push(( + old_window_limit + .clone() + .push(raw_key.as_ref()) + .as_ref() + .to_vec(), + None, + )); + false + }, + ); + Ok(()) + } + + /// Builds a new batch which is a copy of the original commit with the current height + /// prefixed to each key. + /// + /// This is to keep a versioned history of KV pairs. + fn build_aux_batch(&self, height: u64, batch: &mut KVBatch) -> Result { + let mut aux_batch = KVBatch::new(); + if self.ver_window != 0 { + // Copy keys from batch to aux batch while prefixing them with the current height + aux_batch = batch + .iter() + .map(|(k, v)| { + ( + Self::versioned_key(k, height), + v.clone().map_or(Some(TOMBSTONE.to_vec()), Some), + ) + }) + .collect(); + + // Prune Aux data in the db + self.prune_aux_batch(height, &mut aux_batch)?; + } + + // Store the current height in auxiliary batch + aux_batch.push((HEIGHT_KEY.to_vec(), Some(height.to_string().into_bytes()))); + + Ok(aux_batch) + } + /// Commits a key value batch to the MerkleDB. /// /// The current height is updated in the ChainState as well as in the auxiliary data of the DB. @@ -141,17 +246,11 @@ where height: u64, flush: bool, ) -> Result<(Vec, u64)> { - // Update height value in batch - let height_str = height.to_string(); - batch.sort(); + let aux = self.build_aux_batch(height, &mut batch).c(d!())?; + self.db.put_batch(batch).c(d!())?; - self.db - .commit( - vec![(HEIGHT_KEY.to_vec(), Some(height_str.as_bytes().to_vec()))], - flush, - ) - .c(d!())?; + self.db.commit(aux, flush).c(d!())?; Ok((self.root_hash(), height)) } @@ -177,6 +276,32 @@ where Ok(0u64) } + /// Build key Prefixed with Version height for Auxiliary data + pub fn versioned_key(key: &[u8], height: u64) -> Vec { + Prefix::new("VER".as_bytes()) + .push(Self::height_str(height).as_bytes()) + .push(key) + .as_ref() + .to_vec() + } + + /// Build a height string for versioning history + fn height_str(height: u64) -> String { + format!("{:020}", height) + } + + /// Deconstruct versioned key and return parsed raw key + fn get_raw_versioned_key(key: &[u8]) -> Result { + let key: Vec<_> = str::from_utf8(key) + .c(d!("key parse error"))? + .split(SPLIT_BGN) + .collect(); + if key.len() < 3 { + return Err(eg!("invalid key pattern")); + } + Ok(key[2..].join(SPLIT_BGN)) + } + /// Returns the Name of the ChainState pub fn name(&self) -> &str { self.name.as_str() @@ -186,14 +311,122 @@ where pub fn prune_tree() { unimplemented!() } + + /// Build the chain-state from height 1 to height H + /// + /// Returns a batch with KV pairs valid at height H + pub fn build_state(&self, height: u64) -> KVBatch { + //New map to store KV pairs + let mut map = KVMap::new(); + + let lower = Prefix::new("VER".as_bytes()); + let upper = Prefix::new("VER".as_bytes()).push(Self::height_str(height + 1).as_bytes()); + + self.iterate_aux( + lower.begin().as_ref(), + upper.as_ref(), + IterOrder::Asc, + &mut |(k, v)| -> bool { + let raw_key = Self::get_raw_versioned_key(&k).unwrap_or_default(); + if raw_key.is_empty() { + return false; + } + //If value was deleted in the version history, delete it in the map + if v.eq(TOMBSTONE.to_vec().as_slice()) { + map.remove(raw_key.as_bytes()); + } else { + //update map with current KV + map.insert(raw_key.as_bytes().to_vec(), Some(v)); + } + false + }, + ); + + let kvs: Vec<_> = map + .iter() + .map(|(k, v)| (Self::versioned_key(k, height), v.clone())) + .collect(); + kvs + } + + /// Get the value of a key at a given height + /// + /// Returns the value of the given key at a particular height + /// Returns None if the key was deleted or invalid at height H + pub fn get_ver(&self, key: &[u8], height: u64) -> Result>> { + //Need to set lower bound as the height can get very large + let mut lower_bound = 1; + if height > self.ver_window { + lower_bound = height - self.ver_window; + } + //Iterate in descending order from upper bound until a value is found + let mut result = None; + for h in (lower_bound..height + 1).rev() { + let key = Self::versioned_key(key, h); + if let Some(val) = self.get_aux(&key).c(d!("error reading aux value"))? { + if val.eq(&TOMBSTONE) { + break; + } + result = Some(val); + } + } + Ok(result) + } + + /// When creating a new chain-state instance, any residual aux data outside the current window + /// needs to be cleared as to not waste memory or disrupt the versioning behaviour. + fn clean_aux_db(&mut self) { + //Get current height + let current_height = self.height().unwrap_or(0); + if current_height == 0 { + return; + } + if current_height < self.ver_window + 1 { + return; + } + + //Get batch for state at H = current_height - ver_window + let batch = self.build_state(current_height - self.ver_window); + //Commit this batch at base height H + if self.db.commit(batch, true).is_err() { + println!("error building base chain state"); + return; + } + + //Define upper and lower bounds for iteration + let lower = Prefix::new("VER".as_bytes()); + let upper = Prefix::new("VER".as_bytes()) + .push(Self::height_str(current_height - self.ver_window).as_bytes()); + + //Create an empty batch + let mut batch = KVBatch::new(); + + //Iterate aux data and delete keys within bounds + self.iterate_aux( + lower.begin().as_ref(), + upper.as_ref(), + IterOrder::Asc, + &mut |(k, _v)| -> bool { + //Delete the key from aux db + batch.push((k, None)); + false + }, + ); + + //commit aux batch + let _ = self.db.commit(batch, true); + } } #[cfg(test)] mod tests { - use crate::db::{IterOrder, KValue, MerkleDB, TempFinDB}; - use crate::state::chain_state; + use crate::db::{FinDB, IterOrder, KVBatch, KValue, MerkleDB, TempFinDB}; + use crate::state::{chain_state, ChainState}; + use rand::Rng; use std::thread; + const VER_WINDOW: u64 = 100; + #[test] fn test_new_chain_state() { //Create new database @@ -201,7 +434,7 @@ mod tests { let fdb = TempFinDB::open(path).expect("failed to open db"); //Create new Chain State with new database - let _cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let _cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); } #[test] @@ -221,7 +454,7 @@ mod tests { .unwrap(); //Create new Chain State with new database - let cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); assert_eq!(cs.get(&b"k10".to_vec()).unwrap(), Some(b"v10".to_vec())); assert_eq!(cs.get(&b"k20".to_vec()).unwrap(), Some(b"v20".to_vec())); @@ -259,7 +492,7 @@ mod tests { .unwrap(); //Create new Chain State with new database - let cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); let mut func_iter = |entry: KValue| { println!("Key: {:?}, Value: {:?}", entry.0, entry.1); //Assert Keys are equal @@ -304,7 +537,7 @@ mod tests { fdb.commit(batch, false).unwrap(); //Create new Chain State with new database - let cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); let mut func_iter = |entry: KValue| { println!("Key: {:?}, Value: {:?}", entry.0, entry.1); //Assert Keys are equal @@ -340,7 +573,7 @@ mod tests { .unwrap(); //Create new Chain State with new database - let cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); assert_eq!(cs.exists(&b"k10".to_vec()).unwrap(), true); assert_eq!(cs.exists(&b"k20".to_vec()).unwrap(), true); @@ -366,7 +599,7 @@ mod tests { let batch_clone = batch.clone(); //Create new Chain State with new database - let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); // Commit batch to db, in production the flush would be true let result = cs.commit(batch, 55, false).unwrap(); @@ -405,7 +638,7 @@ mod tests { fdb.commit(vec![(b"height".to_vec(), Some(b"25".to_vec()))], false) .unwrap(); - let cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); let height_aux = cs.get_aux(&b"height".to_vec()).unwrap(); let height = cs.get(&b"height".to_vec()); @@ -428,7 +661,7 @@ mod tests { ]; //Create new Chain State with new database - let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); let (root_hash1, _) = cs.commit(batch, 32, false).unwrap(); let batch2 = vec![ @@ -456,7 +689,7 @@ mod tests { ]; //Create new Chain State with new database - let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string()); + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); assert_eq!(cs.height().unwrap(), 0u64); @@ -473,4 +706,254 @@ mod tests { let (_, _) = cs.commit(batch, 34, false).unwrap(); assert_eq!(cs.height().unwrap(), 34); } + + #[test] + fn test_build_aux_batch() { + let path = thread::current().name().unwrap().to_owned(); + let fdb = TempFinDB::open(path).expect("failed to open db"); + let number_of_batches = 21; + let batch_size = 7; + //Create new Chain State with new database + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), 10); + + //Create Several batches (More than Window size) with different keys and values + for i in 1..number_of_batches { + let mut batch: KVBatch = KVBatch::new(); + for j in 0..batch_size { + let key = format!("key-{}", j); + let val = format!("val-{}", i); + batch.push((Vec::from(key), Some(Vec::from(val)))); + } + + //Commit the new batch + let _ = cs.commit(batch, i as u64, false); + + //After each commit verify the values by using get_aux + for k in 0..batch_size { + let key = + ChainState::::versioned_key(format!("key-{}", k).as_bytes(), i); + let value = format!("val-{}", i); + assert_eq!( + cs.get_aux(key.as_slice()).unwrap().unwrap(), + value.as_bytes() + ) + } + } + } + + #[test] + fn test_prune_aux_batch() { + let path = thread::current().name().unwrap().to_owned(); + let fdb = TempFinDB::open(path).expect("failed to open db"); + let number_of_batches = 21; + let batch_size = 7; + //Create new Chain State with new database + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), 10); + + //Create Several batches (More than Window size) with different keys and values + for i in 1..number_of_batches { + let mut batch: KVBatch = KVBatch::new(); + for j in 0..batch_size { + let key = format!("key-{}", j); + let val = format!("val-{}", i); + batch.push((Vec::from(key), Some(Vec::from(val)))); + } + + //Add a KV to the batch at a random height, 5 in this case + if i == 5 { + batch.push((b"random_key".to_vec(), Some(b"random-value".to_vec()))); + } + + //Commit the new batch + let _ = cs.commit(batch, i as u64, false); + + //After each commit verify the values by using get_aux + for k in 0..batch_size { + let key = + ChainState::::versioned_key(format!("key-{}", k).as_bytes(), i); + let value = format!("val-{}", i); + assert_eq!( + cs.get_aux(key.as_slice()).unwrap().unwrap().as_slice(), + value.as_bytes() + ) + } + } + + //Make sure random key is found within the current window. + //This will be current height - window size = 10 in this case. + assert_eq!( + cs.get_aux(ChainState::::versioned_key(b"random_key", 10).as_slice()) + .unwrap(), + Some(b"random-value".to_vec()) + ); + + //Query aux values that are older than the window size to confirm batches were pruned + for i in 1..10 { + for k in 0..batch_size { + let key = + ChainState::::versioned_key(format!("key-{}", k).as_bytes(), i); + assert_eq!(cs.get_aux(key.as_slice()).unwrap(), None,) + } + } + } + + #[test] + fn test_build_state() { + let path = thread::current().name().unwrap().to_owned(); + let fdb = TempFinDB::open(path).expect("failed to open db"); + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); + + let mut rng = rand::thread_rng(); + let mut keys = Vec::with_capacity(10); + for i in 0..10 { + keys.push(format!("key_{}", i)); + } + + //Apply several batches with select few keys at random + for h in 1..21 { + let mut batch = KVBatch::new(); + + //Build a random batch + for _ in 0..5 { + let rnd_key_idx = rng.gen_range(0..10); + let rnd_val = format!("val-{}", rng.gen_range(0..10)); + batch.push(( + keys[rnd_key_idx].clone().into_bytes(), + Some(rnd_val.into_bytes()), + )); + } + + let _ = cs.commit(batch, h, false); + } + + //Confirm the build_state function produces the same keys and values as the latest state. + let mut cs_batch = KVBatch::new(); + let bound = crate::store::Prefix::new("key".as_bytes()); + cs.iterate( + &bound.begin(), + &bound.end(), + IterOrder::Asc, + &mut |(k, v)| -> bool { + //Delete the key from aux db + cs_batch.push((k, Some(v))); + false + }, + ); + + let built_batch: Vec<_> = cs + .build_state(20) + .iter() + .map(|(k, v)| { + ( + ChainState::::get_raw_versioned_key(k) + .unwrap() + .into_bytes(), + v.clone(), + ) + }) + .collect(); + + assert!(cs_batch.eq(&built_batch)) + } + + #[test] + fn test_clean_aux_db() { + let path = thread::current().name().unwrap().to_owned(); + let fdb = FinDB::open(path.clone()).expect("failed to open db"); + let number_of_batches = 21; + let batch_size = 7; + //Create new Chain State with new database + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), 10); + + //Create Several batches (More than Window size) with different keys and values + for i in 1..number_of_batches { + let mut batch: KVBatch = KVBatch::new(); + for j in 0..batch_size { + let key = format!("key-{}", j); + let val = format!("val-{}", i); + batch.push((Vec::from(key), Some(Vec::from(val)))); + } + + //Commit the new batch + let _ = cs.commit(batch, i as u64, false); + } + + //Open db with new chain-state - window half the size of the previous + //Simulate Node restart + std::mem::drop(cs); + let new_window_size = 5; + let fdb_new = TempFinDB::open(path.clone()).expect("failed to open db"); + let cs_new = chain_state::ChainState::new(fdb_new, "test_db".to_string(), new_window_size); + + //Confirm keys older than new window size have been deleted + for i in 1..(number_of_batches - new_window_size - 1) { + for k in 0..batch_size { + let key = + ChainState::::versioned_key(format!("key-{}", k).as_bytes(), i); + assert_eq!(cs_new.get_aux(key.as_slice()).unwrap(), None) + } + } + + //Confirm keys within new window size still exist + for i in (number_of_batches - new_window_size)..number_of_batches { + for k in 0..batch_size { + let key = + ChainState::::versioned_key(format!("key-{}", k).as_bytes(), i); + assert!(cs_new.exists_aux(key.as_slice()).unwrap()) + } + } + } + + #[test] + fn test_get_ver() { + let path = thread::current().name().unwrap().to_owned(); + let fdb = TempFinDB::open(path.clone()).expect("failed to open db"); + //Create new Chain State with new database + let mut cs = chain_state::ChainState::new(fdb, "test_db".to_string(), VER_WINDOW); + + //Commit a single key at different heights and values + for height in 1..21 { + let mut batch = KVBatch::new(); + if height == 3 { + batch.push((b"test_key".to_vec(), Some(b"test-val1".to_vec()))); + } + if height == 7 { + //Deleted key at height 7 + batch.push((b"test_key".to_vec(), None)); + } + if height == 15 { + batch.push((b"test_key".to_vec(), Some(b"test-val2".to_vec()))); + } + + let _ = cs.commit(batch, height, false); + } + + //Query the key at each version it was updated + assert_eq!( + cs.get_ver(b"test_key", 3).unwrap(), + Some(b"test-val1".to_vec()) + ); + assert_eq!(cs.get_ver(b"test_key", 7).unwrap(), None); + assert_eq!( + cs.get_ver(b"test_key", 15).unwrap(), + Some(b"test-val2".to_vec()) + ); + + //Query the key between update versions + assert_eq!( + cs.get_ver(b"test_key", 5).unwrap(), + Some(b"test-val1".to_vec()) + ); + assert_eq!( + cs.get_ver(b"test_key", 17).unwrap(), + Some(b"test-val2".to_vec()) + ); + assert_eq!(cs.get_ver(b"test_key", 10).unwrap(), None); + + //Query the key at a version it didn't exist + assert_eq!(cs.get_ver(b"test_key", 2).unwrap(), None); + + //Query the key after it's been deleted + assert_eq!(cs.get_ver(b"test_key", 8).unwrap(), None); + } } diff --git a/src/state/mod.rs b/src/state/mod.rs index 45e6345..2cda7df 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -68,6 +68,10 @@ where cs.get(key) } + pub fn get_ver(&self, key: &[u8], height: u64) -> Result>> { + self.chain_state.read().get_ver(key, height) + } + /// Queries whether a key exists in the current state. /// /// First Checks the cache, returns true if found otherwise queries the chainState. @@ -179,13 +183,18 @@ mod tests { use super::*; use crate::db::{KValue, TempFinDB}; use std::thread; + const VER_WINDOW: u64 = 100; #[test] fn test_get() { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs.clone()); //Set some kv pairs @@ -219,7 +228,11 @@ mod tests { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); //Set some kv pairs @@ -243,7 +256,11 @@ mod tests { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); //Set some kv pairs @@ -267,7 +284,11 @@ mod tests { // Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); // Set maximum valid key and value @@ -291,7 +312,11 @@ mod tests { // Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); // Set maximum valid key and value @@ -314,7 +339,11 @@ mod tests { // Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); // Set a big value @@ -330,7 +359,11 @@ mod tests { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); //Set some kv pairs @@ -392,7 +425,11 @@ mod tests { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); //Set some kv pairs @@ -412,7 +449,11 @@ mod tests { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); //Set some kv pairs @@ -446,7 +487,11 @@ mod tests { //Setup let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); //Set some kv pairs @@ -476,7 +521,11 @@ mod tests { fn test_iterate() { let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); let mut count = 0; diff --git a/src/store/mod.rs b/src/store/mod.rs index b04b756..e10963c 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -86,6 +86,8 @@ mod tests { use std::sync::Arc; use std::{thread, time}; + const VER_WINDOW: u64 = 100; + // a example store struct StakeStore<'a, D: MerkleDB> { pfx: Prefix, @@ -205,7 +207,11 @@ mod tests { // create store let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "test_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "test_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs); let mut store = PrefixedStore::new("my_store", &mut state); let hash0 = store.state().root_hash(); @@ -242,7 +248,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut check = State::new(cs); let mut store = StakeStore::new("stake", &mut check); @@ -283,7 +293,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut check = State::new(cs); let mut store = StakeStore::new("stake", &mut check); @@ -313,7 +327,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut check = State::new(cs); let mut store = StakeStore::new("stake", &mut check); @@ -354,7 +372,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut check = State::new(cs); let mut store = StakeStore::new("stake", &mut check); @@ -418,7 +440,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut check = State::new(cs); let mut store = StakeStore::new("stake", &mut check); @@ -459,7 +485,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs.clone()); let mut store = StakeStore::new("stake", &mut state); @@ -621,7 +651,11 @@ mod tests { // create State let path = thread::current().name().unwrap().to_owned(); let fdb = TempFinDB::open(path).expect("failed to open db"); - let cs = Arc::new(RwLock::new(ChainState::new(fdb, "findora_db".to_string()))); + let cs = Arc::new(RwLock::new(ChainState::new( + fdb, + "findora_db".to_string(), + VER_WINDOW, + ))); let mut state = State::new(cs.clone()); let mut store = PrefixedStore::new("testStore", &mut state); diff --git a/src/store/traits.rs b/src/store/traits.rs index 586ab3b..7f44272 100644 --- a/src/store/traits.rs +++ b/src/store/traits.rs @@ -77,6 +77,11 @@ where self.state().get(key) } + /// get value by version. + fn get_v(&self, key: &[u8], height: u64) -> Result>> { + self.state().get_ver(key, height) + } + /// iterate db only fn iter_db(&self, prefix: Prefix, asc: bool, func: &mut dyn FnMut(KValue) -> bool) -> bool { let mut iter_order = IterOrder::Desc; @@ -194,6 +199,11 @@ pub trait StatelessStore { state.get(key) } + /// get value by version. + fn get_v(state: &State, key: &[u8], height: u64) -> Result>> { + state.get_ver(key, height) + } + /// iterate db only fn iter_db( state: &State,