Browse Source

feat: introduce diff traits and objects

al-gkr-basic-workflow
frisitano 1 year ago
parent
commit
44e60e7228
4 changed files with 224 additions and 7 deletions
  1. +26
    -5
      src/merkle/store/mod.rs
  2. +16
    -0
      src/utils/diff.rs
  3. +180
    -2
      src/utils/kv_map.rs
  4. +2
    -0
      src/utils/mod.rs

+ 26
- 5
src/merkle/store/mod.rs

@ -3,7 +3,10 @@ use super::{
MerklePathSet, MerkleTree, NodeIndex, RecordingMap, RootPath, Rpo256, RpoDigest, SimpleSmt,
TieredSmt, ValuePath, Vec,
};
use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
use crate::utils::{
collections::{ApplyDiff, Diff, KvMapDiff},
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};
use core::borrow::Borrow;
#[cfg(test)]
@ -438,21 +441,21 @@ impl> From<&TieredSmt> for MerkleStore {
impl<T: KvMap<RpoDigest, StoreNode>> From<T> for MerkleStore<T> {
fn from(values: T) -> Self {
let nodes = values.into_iter().chain(empty_hashes().into_iter()).collect();
let nodes = values.into_iter().chain(empty_hashes()).collect();
Self { nodes }
}
}
impl<T: KvMap<RpoDigest, StoreNode>> FromIterator<InnerNodeInfo> for MerkleStore<T> {
fn from_iter<I: IntoIterator<Item = InnerNodeInfo>>(iter: I) -> Self {
let nodes = combine_nodes_with_empty_hashes(iter.into_iter()).collect();
let nodes = combine_nodes_with_empty_hashes(iter).collect();
Self { nodes }
}
}
impl<T: KvMap<RpoDigest, StoreNode>> FromIterator<(RpoDigest, StoreNode)> for MerkleStore<T> {
fn from_iter<I: IntoIterator<Item = (RpoDigest, StoreNode)>>(iter: I) -> Self {
let nodes = iter.into_iter().chain(empty_hashes().into_iter()).collect();
let nodes = iter.into_iter().chain(empty_hashes()).collect();
Self { nodes }
}
}
@ -474,6 +477,24 @@ impl> Extend for MerkleStore {
}
}
// DiffT & ApplyDiffT TRAIT IMPLEMENTATION
// ================================================================================================
impl<T: KvMap<RpoDigest, StoreNode>> Diff<RpoDigest, StoreNode> for MerkleStore<T> {
type DiffType = KvMapDiff<RpoDigest, StoreNode>;
fn diff(&self, other: &Self) -> Self::DiffType {
self.nodes.diff(&other.nodes)
}
}
impl<T: KvMap<RpoDigest, StoreNode>> ApplyDiff<RpoDigest, StoreNode> for MerkleStore<T> {
type DiffType = KvMapDiff<RpoDigest, StoreNode>;
fn apply(&mut self, diff: Self::DiffType) {
self.nodes.apply(diff);
}
}
// SERIALIZATION
// ================================================================================================
@ -553,5 +574,5 @@ fn combine_nodes_with_empty_hashes(
},
)
})
.chain(empty_hashes().into_iter())
.chain(empty_hashes())
}

+ 16
- 0
src/utils/diff.rs

@ -0,0 +1,16 @@
/// A trait for computing the difference between two objects.
pub trait Diff<K: Ord + Clone, V: Clone> {
type DiffType;
/// Returns a `Self::DiffType` object that represents the difference between this object and
/// other.
fn diff(&self, other: &Self) -> Self::DiffType;
}
/// A trait for applying the difference between two objects.
pub trait ApplyDiff<K: Ord + Clone, V: Clone> {
type DiffType;
/// Applies the provided changes described by [DiffType] to the object implementing this trait.
fn apply(&mut self, diff: Self::DiffType);
}

+ 180
- 2
src/utils/kv_map.rs

@ -1,3 +1,4 @@
use super::{collections::ApplyDiff, diff::Diff};
use core::cell::RefCell;
use winter_utils::{
collections::{btree_map::IntoIter, BTreeMap, BTreeSet},
@ -18,6 +19,7 @@ pub trait KvMap:
self.len() == 0
}
fn insert(&mut self, key: K, value: V) -> Option<V>;
fn remove(&mut self, key: &K) -> Option<V>;
fn iter(&self) -> Box<dyn Iterator<Item = (&K, &V)> + '_>;
}
@ -42,6 +44,10 @@ impl KvMap for BTreeMap {
self.insert(key, value)
}
fn remove(&mut self, key: &K) -> Option<V> {
self.remove(key)
}
fn iter(&self) -> Box<dyn Iterator<Item = (&K, &V)> + '_> {
Box::new(self.iter())
}
@ -56,8 +62,9 @@ impl KvMap for BTreeMap {
///
/// The [RecordingMap] is composed of three parts:
/// - `data`: which contains the current set of key-value pairs in the map.
/// - `updates`: which tracks keys for which values have been since the map was instantiated.
/// updates include both insertions and updates of values under existing keys.
/// - `updates`: which tracks keys for which values have been changed since the map was
/// instantiated. updates include both insertions, removals and updates of values under existing
/// keys.
/// - `trace`: which contains the key-value pairs from the original data which have been accesses
/// since the map was instantiated.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
@ -80,6 +87,13 @@ impl RecordingMap {
}
}
// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
pub fn inner(&self) -> &BTreeMap<K, V> {
&self.data
}
// FINALIZER
// --------------------------------------------------------------------------------------------
@ -148,6 +162,19 @@ impl KvMap for RecordingMap {
})
}
/// Removes a key-value pair from the data set.
///
/// If the key exists in the data set, the old value is returned.
fn remove(&mut self, key: &K) -> Option<V> {
self.data.remove(key).map(|old_value| {
let new_update = self.updates.insert(key.clone());
if new_update {
self.trace.borrow_mut().insert(key.clone(), old_value.clone());
}
old_value
})
}
// ITERATION
// --------------------------------------------------------------------------------------------
@ -180,6 +207,74 @@ impl IntoIterator for RecordingMap {
}
}
// KV MAP DIFF
// ================================================================================================
/// [KvMapDiff] stores the difference between two key-value maps.
///
/// The [KvMapDiff] is composed of two parts:
/// - `updates` - a map of key-value pairs that were updated in the second map compared to the
/// first map. This includes new key-value pairs.
/// - `removed` - a set of keys that were removed from the second map compared to the first map.
#[derive(Debug, Clone)]
pub struct KvMapDiff<K, V> {
updated: BTreeMap<K, V>,
removed: BTreeSet<K>,
}
impl<K, V> KvMapDiff<K, V> {
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Creates a new [KvMapDiff] instance.
pub fn new() -> Self {
KvMapDiff {
updated: BTreeMap::new(),
removed: BTreeSet::new(),
}
}
}
impl<K, V> Default for KvMapDiff<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K: Ord + Clone, V: Clone + PartialEq, T: KvMap<K, V>> Diff<K, V> for T {
type DiffType = KvMapDiff<K, V>;
fn diff(&self, other: &T) -> Self::DiffType {
let mut diff = KvMapDiff::default();
for (k, v) in self.iter() {
if let Some(other_value) = other.get(k) {
if v != other_value {
diff.updated.insert(k.clone(), other_value.clone());
}
} else {
diff.removed.insert(k.clone());
}
}
for (k, v) in other.iter() {
if self.get(k).is_none() {
diff.updated.insert(k.clone(), v.clone());
}
}
diff
}
}
impl<K: Ord + Clone, V: Clone, T: KvMap<K, V>> ApplyDiff<K, V> for T {
type DiffType = KvMapDiff<K, V>;
fn apply(&mut self, diff: Self::DiffType) {
for (k, v) in diff.updated {
self.insert(k, v);
}
for k in diff.removed {
self.remove(&k);
}
}
}
// TESTS
// ================================================================================================
@ -321,4 +416,87 @@ mod tests {
let map = RecordingMap::new(ITEMS.to_vec());
assert!(!map.is_empty());
}
#[test]
fn test_remove() {
let mut map = RecordingMap::new(ITEMS.to_vec());
// remove an item that exists
let key = 0;
let value = map.remove(&key).unwrap();
assert_eq!(value, ITEMS[0].1);
assert_eq!(map.len(), ITEMS.len() - 1);
assert_eq!(map.trace_len(), 1);
assert_eq!(map.updates_len(), 1);
// add the item back and then remove it again
let key = 0;
let value = 0;
map.insert(key, value);
let value = map.remove(&key).unwrap();
assert_eq!(value, 0);
assert_eq!(map.len(), ITEMS.len() - 1);
assert_eq!(map.trace_len(), 1);
assert_eq!(map.updates_len(), 1);
// remove an item that does not exist
let key = 100;
let value = map.remove(&key);
assert_eq!(value, None);
assert_eq!(map.len(), ITEMS.len() - 1);
assert_eq!(map.trace_len(), 1);
assert_eq!(map.updates_len(), 1);
// insert a new item and then remove it
let key = 100;
let value = 100;
map.insert(key, value);
let value = map.remove(&key).unwrap();
assert_eq!(value, 100);
assert_eq!(map.len(), ITEMS.len() - 1);
assert_eq!(map.trace_len(), 1);
assert_eq!(map.updates_len(), 2);
// convert the map into a proof
let proof = map.into_proof();
// check that the proof contains the expected values
for (key, value) in ITEMS.iter() {
match key {
0 => assert_eq!(proof.get(key), Some(value)),
_ => assert_eq!(proof.get(key), None),
}
}
}
#[test]
fn test_kv_map_diff() {
let mut initial_state = ITEMS.into_iter().collect::<BTreeMap<_, _>>();
let mut map = RecordingMap::new(initial_state.clone());
// remove an item that exists
let key = 0;
let _value = map.remove(&key).unwrap();
// add a new item
let key = 100;
let value = 100;
map.insert(key, value);
// update an existing item
let key = 1;
let value = 100;
map.insert(key, value);
// compute a diff
let diff = initial_state.diff(map.inner());
assert!(diff.updated.len() == 2);
assert!(diff.updated.iter().all(|(k, v)| [(100, 100), (1, 100)].contains(&(*k, *v))));
assert!(diff.removed.len() == 1);
assert!(diff.removed.first() == Some(&0));
// apply the diff to the initial state and assert the contents are the same as the map
initial_state.apply(diff);
assert!(initial_state.iter().eq(map.iter()));
}
}

+ 2
- 0
src/utils/mod.rs

@ -7,6 +7,7 @@ pub use alloc::format;
#[cfg(feature = "std")]
pub use std::format;
mod diff;
mod kv_map;
// RE-EXPORTS
@ -17,6 +18,7 @@ pub use winter_utils::{
};
pub mod collections {
pub use super::diff::*;
pub use super::kv_map::*;
pub use winter_utils::collections::*;
}

Loading…
Cancel
Save