Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Snapshot take a strategy trait so that users can write their own strategies #82

Open
wants to merge 6 commits into
base: release/1.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/indexed_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ use crate::PrefixBound;
use crate::{Bound, IndexList, Map, Path, Strategy};

/// `IndexedSnapshotMap` works like a `SnapshotMap` but has a secondary index
pub struct IndexedSnapshotMap<'a, K, T, I> {
pub struct IndexedSnapshotMap<'a, K, T, I, S = Strategy> {
pk_namespace: &'a [u8],
primary: SnapshotMap<'a, K, T>,
primary: SnapshotMap<'a, K, T, S>,
/// This is meant to be read directly to get the proper types, like:
/// map.idx.owner.items(...)
pub idx: I,
}

impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> {
impl<'a, K, T, I, S> IndexedSnapshotMap<'a, K, T, I, S> {
/// Examples:
///
/// ```rust
Expand All @@ -48,7 +48,7 @@ impl<'a, K, T, I> IndexedSnapshotMap<'a, K, T, I> {
pk_namespace: &'a str,
checkpoints: &'a str,
changelog: &'a str,
strategy: Strategy,
strategy: S,
indexes: I,
) -> Self {
IndexedSnapshotMap {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub use path::Path;
#[cfg(feature = "iterator")]
pub use prefix::{range_with_prefix, Prefix};
#[cfg(feature = "iterator")]
pub use snapshot::{SnapshotItem, SnapshotMap, Strategy};
pub use snapshot::{IntervalStrategy, SnapshotItem, SnapshotMap, Strategy};

// cw_storage_macro reexports
#[cfg(all(feature = "iterator", feature = "macro"))]
Expand Down
116 changes: 116 additions & 0 deletions src/snapshot/interval_strategy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use cosmwasm_std::{Order, StdResult, Storage};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::{Bound, KeyDeserialize, Map, Prefixer, PrimaryKey};

use super::{ChangeSet, SnapshotStrategy};

/// A SnapshotStrategy that takes a snapshot only if at least the specified interval has passed.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]

clippy complains here

pub struct IntervalStrategy {
/// The interval to archive snapshots at. If the time or number of blocks since the last changelog
/// entry is greater than this interval, a new snapshot will be created.
pub interval: u64,
}

impl IntervalStrategy {
/// Create a new IntervalStrategy with the given interval.
pub const fn new(interval: u64) -> Self {
Self { interval }
}
}

impl<'a, K, T> SnapshotStrategy<'a, K, T> for IntervalStrategy
where
T: Serialize + DeserializeOwned + Clone,
K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize,
{
fn assert_checkpointed(
&self,
_store: &dyn Storage,
_checkpoints: &Map<'a, u64, u32>,
_height: u64,
) -> StdResult<()> {
Ok(())
}

fn should_archive(
&self,
store: &dyn Storage,
_checkpoints: &Map<'a, u64, u32>,
changelog: &Map<'a, (K, u64), ChangeSet<T>>,
key: &K,
height: u64,
) -> StdResult<bool> {
let last_height = height.saturating_sub(self.interval);

// Check if there is a changelog entry since the last interval
let changelog_entry = changelog
.prefix(key.clone())
.range_raw(
store,
Some(Bound::inclusive(last_height)),
None,
Order::Ascending,
)
.next();

Ok(changelog_entry.is_none())
}
}

#[cfg(test)]
mod tests {
use crate::snapshot::Snapshot;

use super::*;
use cosmwasm_std::testing::MockStorage;

type TestSnapshot = Snapshot<'static, &'static str, u64, IntervalStrategy>;
const INTERVAL_5: TestSnapshot = Snapshot::new(
"interval_5__check",
"interval_5__change",
IntervalStrategy::new(5),
);

const DUMMY_KEY: &str = "dummy";

#[test]
fn should_archive() {
let mut store = MockStorage::new();

// Should archive first save since there is no previous changelog entry.
assert_eq!(INTERVAL_5.should_archive(&store, &DUMMY_KEY, 0), Ok(true));

// Store changelog entry
INTERVAL_5
.write_changelog(&mut store, DUMMY_KEY, 0, None)
.unwrap();

// Should not archive again
assert_eq!(INTERVAL_5.should_archive(&store, &DUMMY_KEY, 0), Ok(false));

// Should archive once interval has passed
assert_eq!(INTERVAL_5.should_archive(&store, &DUMMY_KEY, 6), Ok(true));

// Store changelog entry
INTERVAL_5
.write_changelog(&mut store, DUMMY_KEY, 6, None)
.unwrap();

// Should not archive again
assert_eq!(INTERVAL_5.should_archive(&store, &DUMMY_KEY, 6), Ok(false));

// Should not archive before interval
assert_eq!(
INTERVAL_5.should_archive(&store, &DUMMY_KEY, 6 + 5),
Ok(false)
);

// Should archive once interval has passed
assert_eq!(
INTERVAL_5.should_archive(&store, &DUMMY_KEY, 6 + 5 + 1),
Ok(true)
);
}
}
17 changes: 10 additions & 7 deletions src/snapshot/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ use cosmwasm_std::{StdError, StdResult, Storage};
use crate::snapshot::{ChangeSet, Snapshot};
use crate::{Item, Map, Strategy};

use super::SnapshotStrategy;

/// Item that maintains a snapshot of one or more checkpoints.
/// We can query historical data as well as current state.
/// What data is snapshotted depends on the Strategy.
pub struct SnapshotItem<'a, T> {
pub struct SnapshotItem<'a, T, S = Strategy> {
primary: Item<'a, T>,
changelog_namespace: &'a str,
snapshots: Snapshot<'a, (), T>,
snapshots: Snapshot<'a, (), T, S>,
}

impl<'a, T> SnapshotItem<'a, T> {
impl<'a, T, S> SnapshotItem<'a, T, S> {
/// Example:
///
/// ```rust
Expand All @@ -31,7 +33,7 @@ impl<'a, T> SnapshotItem<'a, T> {
storage_key: &'a str,
checkpoints: &'a str,
changelog: &'a str,
strategy: Strategy,
strategy: S,
) -> Self {
SnapshotItem {
primary: Item::new(storage_key),
Expand All @@ -54,9 +56,10 @@ impl<'a, T> SnapshotItem<'a, T> {
}
}

impl<'a, T> SnapshotItem<'a, T>
impl<'a, T, S> SnapshotItem<'a, T, S>
where
T: Serialize + DeserializeOwned + Clone,
S: SnapshotStrategy<'a, (), T>,
{
/// load old value and store changelog
fn write_change(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> {
Expand All @@ -70,14 +73,14 @@ where
}

pub fn save(&self, store: &mut dyn Storage, data: &T, height: u64) -> StdResult<()> {
if self.snapshots.should_checkpoint(store, &())? {
if self.snapshots.should_archive(store, &(), height)? {
self.write_change(store, height)?;
}
self.primary.save(store, data)
}

pub fn remove(&self, store: &mut dyn Storage, height: u64) -> StdResult<()> {
if self.snapshots.should_checkpoint(store, &())? {
if self.snapshots.should_archive(store, &(), height)? {
self.write_change(store, height)?;
}
self.primary.remove(store);
Expand Down
29 changes: 14 additions & 15 deletions src/snapshot/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ use crate::prefix::{namespaced_prefix_range, Prefix};
use crate::snapshot::{ChangeSet, Snapshot};
use crate::{Bound, Prefixer, Strategy};

use super::SnapshotStrategy;

/// Map that maintains a snapshots of one or more checkpoints.
/// We can query historical data as well as current state.
/// What data is snapshotted depends on the Strategy.
pub struct SnapshotMap<'a, K, T> {
pub struct SnapshotMap<'a, K, T, S = Strategy> {
primary: Map<'a, K, T>,
snapshots: Snapshot<'a, K, T>,
snapshots: Snapshot<'a, K, T, S>,
}

impl<'a, K, T> SnapshotMap<'a, K, T> {
impl<'a, K, T, S> SnapshotMap<'a, K, T, S> {
/// Example:
///
/// ```rust
Expand All @@ -34,12 +36,7 @@ impl<'a, K, T> SnapshotMap<'a, K, T> {
/// Strategy::EveryBlock
/// );
/// ```
pub const fn new(
pk: &'a str,
checkpoints: &'a str,
changelog: &'a str,
strategy: Strategy,
) -> Self {
pub const fn new(pk: &'a str, checkpoints: &'a str, changelog: &'a str, strategy: S) -> Self {
SnapshotMap {
primary: Map::new(pk),
snapshots: Snapshot::new(checkpoints, changelog, strategy),
Expand All @@ -51,7 +48,7 @@ impl<'a, K, T> SnapshotMap<'a, K, T> {
}
}

impl<'a, K, T> SnapshotMap<'a, K, T>
impl<'a, K, T, S> SnapshotMap<'a, K, T, S>
where
T: Serialize + DeserializeOwned + Clone,
K: PrimaryKey<'a> + Prefixer<'a>,
Expand All @@ -65,10 +62,11 @@ where
}
}

impl<'a, K, T> SnapshotMap<'a, K, T>
impl<'a, K, T, S> SnapshotMap<'a, K, T, S>
where
T: Serialize + DeserializeOwned + Clone,
K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize,
S: SnapshotStrategy<'a, K, T>,
{
pub fn key(&self, k: K) -> Path<T> {
self.primary.key(k)
Expand All @@ -90,14 +88,14 @@ where
}

pub fn save(&self, store: &mut dyn Storage, k: K, data: &T, height: u64) -> StdResult<()> {
if self.snapshots.should_checkpoint(store, &k)? {
if self.snapshots.should_archive(store, &k, height)? {
self.write_change(store, k.clone(), height)?;
}
self.primary.save(store, k, data)
}

pub fn remove(&self, store: &mut dyn Storage, k: K, height: u64) -> StdResult<()> {
if self.snapshots.should_checkpoint(store, &k)? {
if self.snapshots.should_archive(store, &k, height)? {
self.write_change(store, k.clone(), height)?;
}
self.primary.remove(store, k);
Expand Down Expand Up @@ -162,10 +160,11 @@ where
}

// short-cut for simple keys, rather than .prefix(()).range_raw(...)
impl<'a, K, T> SnapshotMap<'a, K, T>
impl<'a, K, T, S> SnapshotMap<'a, K, T, S>
where
T: Serialize + DeserializeOwned + Clone,
K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize,
S: SnapshotStrategy<'a, K, T>,
{
// I would prefer not to copy code from Prefix, but no other way
// with lifetimes (create Prefix inside function and return ref = no no)
Expand Down Expand Up @@ -197,7 +196,7 @@ where
}

#[cfg(feature = "iterator")]
impl<'a, K, T> SnapshotMap<'a, K, T>
impl<'a, K, T, S> SnapshotMap<'a, K, T, S>
where
T: Serialize + DeserializeOwned,
K: PrimaryKey<'a> + KeyDeserialize,
Expand Down
Loading