use crate::config::TreeConfig;
use crate::diff::{
ConflictResolver, DiffResult, IgnoreConflictsResolver, MergeConflict, MergeResult,
};
use crate::digest::ValueDigest;
use crate::node::{Node, ProllyNode};
use crate::proof::Proof;
use crate::storage::NodeStorage;
use std::sync::Arc;
pub trait Tree<const N: usize, S: NodeStorage<N>> {
fn new(storage: S, config: TreeConfig<N>) -> Self;
fn insert(&mut self, key: Vec<u8>, value: Vec<u8>);
fn insert_batch(&mut self, keys: &[Vec<u8>], values: &[Vec<u8>]);
fn update(&mut self, key: Vec<u8>, value: Vec<u8>) -> bool;
fn delete(&mut self, key: &[u8]) -> bool;
fn delete_batch(&mut self, keys: &[Vec<u8>]);
fn find(&self, key: &[u8]) -> Option<ProllyNode<N>>;
fn traverse(&self) -> String;
fn formatted_traverse<F>(&self, formatter: F) -> String
where
F: Fn(&ProllyNode<N>) -> String;
fn get_root_hash(&self) -> Option<ValueDigest<N>>;
fn size(&self) -> usize;
fn depth(&self) -> usize;
fn summary(&self) -> String;
fn stats(&self) -> TreeStats;
fn load_config(storage: &S) -> Result<TreeConfig<N>, &'static str>;
fn save_config(&self) -> Result<(), &'static str>;
fn generate_proof(&self, key: &[u8]) -> Proof<N>;
fn verify(&self, proof: Proof<N>, key: &[u8], expected_value: Option<&[u8]>) -> bool;
fn diff(&self, other: &Self) -> Vec<DiffResult>;
fn print(&mut self);
fn print_proof(&self, key: &[u8]) -> bool;
fn merge(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
) -> Vec<MergeResult>;
fn apply_merge_results(
&self,
destination_root: &ValueDigest<N>,
merge_results: &[MergeResult],
) -> Result<Self, Vec<MergeConflict>>
where
Self: Sized;
fn merge_trees<R: ConflictResolver>(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
resolver: &R,
) -> Result<Self, Vec<MergeConflict>>
where
Self: Sized;
fn merge_trees_ignore_conflicts(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
) -> Result<Self, Vec<MergeConflict>>
where
Self: Sized,
{
self.merge_trees(
source_root,
destination_root,
base_root,
&IgnoreConflictsResolver,
)
}
}
pub struct TreeStats {
pub num_nodes: usize,
pub num_leaves: usize,
pub num_internal_nodes: usize,
pub avg_node_size: f64,
pub total_key_value_pairs: usize,
}
impl TreeStats {
pub fn new() -> Self {
TreeStats {
num_nodes: 0,
num_leaves: 0,
num_internal_nodes: 0,
avg_node_size: 0.0,
total_key_value_pairs: 0,
}
}
}
impl Default for TreeStats {
fn default() -> Self {
TreeStats::new()
}
}
#[derive(Debug, Clone)]
pub struct ProllyTree<const N: usize, S: NodeStorage<N>> {
pub root: ProllyNode<N>,
pub storage: S,
pub config: TreeConfig<N>,
}
impl<const N: usize, S: NodeStorage<N>> Tree<N, S> for ProllyTree<N, S> {
fn new(storage: S, config: TreeConfig<N>) -> Self {
let root = ProllyNode {
keys: Vec::new(),
key_schema: config.key_schema.clone(),
values: Vec::new(),
value_schema: config.value_schema.clone(),
is_leaf: true,
level: 0,
base: config.base,
modulus: config.modulus,
min_chunk_size: config.min_chunk_size,
max_chunk_size: config.max_chunk_size,
pattern: config.pattern,
split: false,
merged: false,
encode_types: Vec::new(),
encode_values: Vec::new(),
};
let root_hash = Some(root.get_hash());
let mut tree = ProllyTree {
root,
storage,
config,
};
tree.config.root_hash = root_hash;
tree
}
fn insert(&mut self, key: Vec<u8>, value: Vec<u8>) {
self.apply_changes(std::iter::once((key, Some(value))));
self.persist_root();
}
fn insert_batch(&mut self, keys: &[Vec<u8>], values: &[Vec<u8>]) {
assert_eq!(
keys.len(),
values.len(),
"insert_batch requires the same number of keys and values"
);
let batch = keys.iter().cloned().zip(values.iter().cloned().map(Some));
self.apply_changes(batch);
}
fn update(&mut self, key: Vec<u8>, value: Vec<u8>) -> bool {
if self.find(&key).is_some() {
self.insert(key, value);
true
} else {
false
}
}
fn delete(&mut self, key: &[u8]) -> bool {
if self.find(key).is_none() {
return false;
}
self.apply_changes(std::iter::once((key.to_vec(), None)));
self.persist_root();
true
}
fn delete_batch(&mut self, keys: &[Vec<u8>]) {
let batch = keys.iter().map(|k| (k.clone(), None));
self.apply_changes(batch);
}
fn find(&self, key: &[u8]) -> Option<ProllyNode<N>> {
self.root.find(key, &self.storage)
}
fn traverse(&self) -> String {
self.root.traverse(&self.storage)
}
fn formatted_traverse<F>(&self, formatter: F) -> String
where
F: Fn(&ProllyNode<N>) -> String,
{
self.root.formatted_traverse(&self.storage, formatter)
}
fn get_root_hash(&self) -> Option<ValueDigest<N>> {
Option::from(self.root.get_hash())
}
fn size(&self) -> usize {
fn count_pairs<const N: usize, S: NodeStorage<N>>(
node: &ProllyNode<N>,
storage: &S,
) -> usize {
if node.is_leaf {
node.keys.len()
} else {
let mut count = 0;
for value in &node.values {
if let Some(child_node) =
storage.get_node_by_hash(&ValueDigest::raw_hash(value))
{
count += count_pairs(&child_node, storage);
}
}
count
}
}
count_pairs(&self.root, &self.storage)
}
fn depth(&self) -> usize {
(self.root.level as usize) + 1
}
fn summary(&self) -> String {
let stats = self.stats();
format!(
"Tree Summary:\n- Number of Key-Value Pairs: {}\n- Number of Nodes: {}\n- Number of Leaves: {}\n- Number of Internal Nodes: {}\n- Average Leaf Node Size: {:.2}",
self.size(),
stats.num_nodes,
stats.num_leaves,
stats.num_internal_nodes,
stats.avg_node_size
)
}
fn stats(&self) -> TreeStats {
fn collect_stats<const N: usize, S: NodeStorage<N>>(
node: &ProllyNode<N>,
storage: &S,
stats: &mut TreeStats,
) {
stats.num_nodes += 1;
if node.is_leaf {
stats.num_leaves += 1;
stats.total_key_value_pairs += node.keys.len();
} else {
stats.num_internal_nodes += 1;
for value in &node.values {
if let Some(child_node) =
storage.get_node_by_hash(&ValueDigest::raw_hash(value))
{
collect_stats(&child_node, storage, stats);
}
}
}
}
let mut stats = TreeStats::new();
collect_stats(&self.root, &self.storage, &mut stats);
if stats.num_leaves > 0 {
stats.avg_node_size = stats.total_key_value_pairs as f64 / stats.num_leaves as f64;
}
stats
}
fn load_config(storage: &S) -> Result<TreeConfig<N>, &'static str> {
if let Some(config_data) = storage.get_config("tree_config") {
let config: TreeConfig<N> =
serde_json::from_slice(&config_data).map_err(|_| "Failed to deserialize config")?;
Ok(config)
} else {
Err("Config not found")
}
}
fn save_config(&self) -> Result<(), &'static str> {
let mut config = self.config.clone();
config.root_hash = Option::from(self.root.get_hash());
let config_data = serde_json::to_vec(&config).map_err(|_| "Failed to serialize config")?;
self.storage.save_config("tree_config", &config_data);
Ok(())
}
fn generate_proof(&self, key: &[u8]) -> Proof<N> {
fn generate_proof_recursive<const N: usize, S: NodeStorage<N>>(
node: &ProllyNode<N>,
key: &[u8],
storage: &S,
path: &mut Vec<ValueDigest<N>>,
) -> Option<ValueDigest<N>> {
path.push(node.get_hash());
if node.is_leaf {
if node.keys.iter().any(|k| k == key) {
Some(node.get_hash())
} else {
None
}
} else {
if node.values.is_empty() {
return None;
}
let i = node.keys.iter().rposition(|k| key >= &k[..]).unwrap_or(0);
let i = i.min(node.values.len() - 1);
let child_hash = node.values[i].clone();
if let Some(child_node) =
storage.get_node_by_hash(&ValueDigest::raw_hash(&child_hash))
{
generate_proof_recursive(&child_node, key, storage, path)
} else {
None
}
}
}
let mut path = Vec::new();
let target_hash = generate_proof_recursive(&self.root, key, &self.storage, &mut path);
Proof { path, target_hash }
}
fn verify(&self, proof: Proof<N>, key: &[u8], expected_value: Option<&[u8]>) -> bool {
let mut current_hash = self.root.get_hash();
for (i, node_hash) in proof.path.iter().enumerate() {
if let Some(node) = self.storage.get_node_by_hash(¤t_hash) {
if node.get_hash() != *node_hash {
return false;
}
if i == proof.path.len() - 1 {
return if node.is_leaf {
node.keys.iter().any(|k| k == key)
&& match expected_value {
None => true,
Some(ev) => node.values.iter().any(|v| ev == &v[..]),
}
} else {
false };
}
let child_index = node.keys.iter().rposition(|k| key >= &k[..]).unwrap_or(0);
current_hash = ValueDigest::raw_hash(&node.values[child_index]);
} else {
return false;
}
}
false }
fn diff(&self, other: &Self) -> Vec<DiffResult> {
let mut diffs = Vec::new();
self.diff_recursive(&self.root, &other.root, &mut diffs);
diffs
}
fn print(&mut self) {
self.root.print_tree(&self.storage);
}
fn print_proof(&self, key: &[u8]) -> bool {
let proof = self.generate_proof(key);
let is_valid = self.verify(proof.clone(), key, None);
#[cfg(feature = "tracing")]
tracing::debug!("root:");
#[cfg(not(feature = "tracing"))]
println!("root:");
self.root.print_tree_with_proof(&self.storage, &proof, key);
#[cfg(feature = "tracing")]
{
tracing::debug!("Proof for key {:?} is valid: {}", key, is_valid);
tracing::debug!("Proof: {:#?}", proof);
}
#[cfg(not(feature = "tracing"))]
{
println!("\nProof for key {key:?} is valid: {is_valid}");
println!("Proof: {proof:#?}");
}
is_valid
}
fn merge(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
) -> Vec<MergeResult> {
let source_tree = self.storage.get_node_by_hash(source_root);
let destination_tree = self.storage.get_node_by_hash(destination_root);
let base_tree = self.storage.get_node_by_hash(base_root);
let (source_tree, destination_tree, base_tree) =
match (source_tree, destination_tree, base_tree) {
(Some(s), Some(d), Some(b)) => (s, d, b),
_ => {
return vec![MergeResult::Conflict(MergeConflict {
key: b"<merge_error>".to_vec(),
base_value: None,
source_value: None,
destination_value: Some(b"Failed to load tree from storage".to_vec()),
})];
}
};
let mut base_to_source_diffs = Vec::new();
let mut base_to_destination_diffs = Vec::new();
self.diff_nodes_recursive(&base_tree, &source_tree, &mut base_to_source_diffs);
self.diff_nodes_recursive(
&base_tree,
&destination_tree,
&mut base_to_destination_diffs,
);
let mut source_changes: std::collections::HashMap<Vec<u8>, DiffResult> =
std::collections::HashMap::new();
let mut destination_changes: std::collections::HashMap<Vec<u8>, DiffResult> =
std::collections::HashMap::new();
for diff in base_to_source_diffs {
let key = match &diff {
DiffResult::Added(k, _) => k.clone(),
DiffResult::Removed(k, _) => k.clone(),
DiffResult::Modified(k, _, _) => k.clone(),
};
source_changes.insert(key, diff);
}
for diff in base_to_destination_diffs {
let key = match &diff {
DiffResult::Added(k, _) => k.clone(),
DiffResult::Removed(k, _) => k.clone(),
DiffResult::Modified(k, _, _) => k.clone(),
};
destination_changes.insert(key, diff);
}
let mut all_changed_keys = std::collections::HashSet::new();
for key in source_changes.keys() {
all_changed_keys.insert(key.clone());
}
for key in destination_changes.keys() {
all_changed_keys.insert(key.clone());
}
let mut merge_results = Vec::new();
for key in all_changed_keys {
let source_change = source_changes.get(&key);
let destination_change = destination_changes.get(&key);
match (source_change, destination_change) {
(Some(source_diff), None) => match source_diff {
DiffResult::Added(_, value) => {
merge_results.push(MergeResult::Added(key, value.clone()));
}
DiffResult::Removed(_, _) => {
merge_results.push(MergeResult::Removed(key));
}
DiffResult::Modified(_, _, new_value) => {
merge_results.push(MergeResult::Modified(key, new_value.clone()));
}
},
(None, Some(_)) => {
}
(Some(source_diff), Some(destination_diff)) => {
let conflict =
self.detect_conflict(&key, source_diff, destination_diff, &base_tree);
if let Some(conflict) = conflict {
merge_results.push(MergeResult::Conflict(conflict));
} else {
match source_diff {
DiffResult::Added(_, value) => {
merge_results.push(MergeResult::Added(key, value.clone()));
}
DiffResult::Removed(_, _) => {
merge_results.push(MergeResult::Removed(key));
}
DiffResult::Modified(_, _, new_value) => {
merge_results.push(MergeResult::Modified(key, new_value.clone()));
}
}
}
}
(None, None) => {}
}
}
merge_results
}
fn apply_merge_results(
&self,
destination_root: &ValueDigest<N>,
merge_results: &[MergeResult],
) -> Result<Self, Vec<MergeConflict>> {
let mut conflicts = Vec::new();
for result in merge_results {
if let MergeResult::Conflict(conflict) = result {
conflicts.push((*conflict).clone());
}
}
if !conflicts.is_empty() {
return Err(conflicts);
}
let destination_tree =
self.storage
.get_node_by_hash(destination_root)
.ok_or_else(|| {
vec![MergeConflict {
key: b"<apply_error>".to_vec(),
base_value: None,
source_value: None,
destination_value: Some(b"Failed to load destination tree".to_vec()),
}]
})?;
let mut new_tree = ProllyTree {
root: Arc::unwrap_or_clone(destination_tree),
storage: self.storage.clone(),
config: self.config.clone(),
};
for result in merge_results {
match result {
MergeResult::Added(key, value) => {
new_tree.insert(key.clone(), value.clone());
}
MergeResult::Modified(key, value) => {
new_tree.insert(key.clone(), value.clone()); }
MergeResult::Removed(key) => {
new_tree.delete(key);
}
MergeResult::Conflict(_) => {
unreachable!("Conflicts should have been filtered out");
}
}
}
Ok(new_tree)
}
fn merge_trees<R: ConflictResolver>(
&self,
source_root: &ValueDigest<N>,
destination_root: &ValueDigest<N>,
base_root: &ValueDigest<N>,
resolver: &R,
) -> Result<Self, Vec<MergeConflict>> {
let merge_results = self.merge(source_root, destination_root, base_root);
let mut resolved_results = Vec::new();
let mut unresolved_conflicts = Vec::new();
for result in merge_results {
match result {
MergeResult::Conflict(conflict) => {
if let Some(resolved_result) = resolver.resolve_conflict(&conflict) {
resolved_results.push(resolved_result);
} else {
unresolved_conflicts.push(conflict);
}
}
other => resolved_results.push(other),
}
}
if !unresolved_conflicts.is_empty() {
return Err(unresolved_conflicts);
}
self.apply_merge_results(destination_root, &resolved_results)
}
}
impl<const N: usize, S: NodeStorage<N>> ProllyTree<N, S> {
fn diff_nodes_recursive(
&self,
old_node: &ProllyNode<N>,
new_node: &ProllyNode<N>,
diffs: &mut Vec<DiffResult>,
) {
if old_node.get_hash() == new_node.get_hash() {
return;
}
match (old_node.is_leaf, new_node.is_leaf) {
(true, true) => {
let o: Vec<(Vec<u8>, Vec<u8>)> = old_node
.keys
.iter()
.cloned()
.zip(old_node.values.iter().cloned())
.collect();
let n: Vec<(Vec<u8>, Vec<u8>)> = new_node
.keys
.iter()
.cloned()
.zip(new_node.values.iter().cloned())
.collect();
self.merge_join_pairs(&o, &n, diffs);
}
(false, false) => self.diff_internal(old_node, new_node, diffs),
_ => {
let mut op = Vec::new();
self.collect_pairs_recursive(old_node, &mut op);
let mut np = Vec::new();
self.collect_pairs_recursive(new_node, &mut np);
self.merge_join_pairs(&op, &np, diffs);
}
}
}
fn diff_internal(
&self,
old_node: &ProllyNode<N>,
new_node: &ProllyNode<N>,
diffs: &mut Vec<DiffResult>,
) {
use std::collections::HashSet;
let new_hashes: HashSet<&Vec<u8>> = new_node.values.iter().collect();
let old_hashes: HashSet<&Vec<u8>> = old_node.values.iter().collect();
let mut old_pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
for child_hash in &old_node.values {
if new_hashes.contains(child_hash) {
continue; }
if let Some(child) = self
.storage
.get_node_by_hash(&ValueDigest::raw_hash(child_hash))
{
self.collect_pairs_recursive(&child, &mut old_pairs);
}
}
let mut new_pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
for child_hash in &new_node.values {
if old_hashes.contains(child_hash) {
continue;
}
if let Some(child) = self
.storage
.get_node_by_hash(&ValueDigest::raw_hash(child_hash))
{
self.collect_pairs_recursive(&child, &mut new_pairs);
}
}
self.merge_join_pairs(&old_pairs, &new_pairs, diffs);
}
fn merge_join_pairs(
&self,
old_pairs: &[(Vec<u8>, Vec<u8>)],
new_pairs: &[(Vec<u8>, Vec<u8>)],
diffs: &mut Vec<DiffResult>,
) {
let mut oi = old_pairs.iter().peekable();
let mut ni = new_pairs.iter().peekable();
while let (Some((ok, ov)), Some((nk, nv))) = (oi.peek(), ni.peek()) {
match ok.cmp(nk) {
std::cmp::Ordering::Less => {
diffs.push(DiffResult::Removed(ok.clone(), ov.clone()));
oi.next();
}
std::cmp::Ordering::Greater => {
diffs.push(DiffResult::Added(nk.clone(), nv.clone()));
ni.next();
}
std::cmp::Ordering::Equal => {
if ov != nv {
diffs.push(DiffResult::Modified(ok.clone(), ov.clone(), nv.clone()));
}
oi.next();
ni.next();
}
}
}
for (ok, ov) in oi {
diffs.push(DiffResult::Removed(ok.clone(), ov.clone()));
}
for (nk, nv) in ni {
diffs.push(DiffResult::Added(nk.clone(), nv.clone()));
}
}
#[cfg(test)]
fn diff_nodes_flatten(
&self,
old_node: &ProllyNode<N>,
new_node: &ProllyNode<N>,
diffs: &mut Vec<DiffResult>,
) {
let mut old_pairs = Vec::new();
self.collect_pairs_recursive(old_node, &mut old_pairs);
let mut new_pairs = Vec::new();
self.collect_pairs_recursive(new_node, &mut new_pairs);
self.merge_join_pairs(&old_pairs, &new_pairs, diffs);
}
fn collect_pairs_recursive(&self, node: &ProllyNode<N>, pairs: &mut Vec<(Vec<u8>, Vec<u8>)>) {
if node.is_leaf {
for (k, v) in node.keys.iter().zip(node.values.iter()) {
pairs.push((k.clone(), v.clone()));
}
} else {
for child in node.children(&self.storage) {
self.collect_pairs_recursive(&child, pairs);
}
}
}
fn find_value_in_node(&self, node: &ProllyNode<N>, key: &[u8]) -> Option<Vec<u8>> {
if node.is_leaf {
for (i, k) in node.keys.iter().enumerate() {
if k.as_slice() == key {
return Some(node.values[i].clone());
}
}
} else {
return node.find(key, &self.storage).and_then(|found_node| {
found_node
.keys
.iter()
.zip(found_node.values.iter())
.find(|(k, _)| k.as_slice() == key)
.map(|(_, v)| v.clone())
});
}
None
}
fn detect_conflict(
&self,
key: &[u8],
source_diff: &DiffResult,
destination_diff: &DiffResult,
base_node: &ProllyNode<N>,
) -> Option<MergeConflict> {
let base_value = self.find_value_in_node(base_node, key);
match (source_diff, destination_diff) {
(DiffResult::Added(_, source_value), DiffResult::Added(_, destination_value)) => {
if source_value != destination_value {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
} else {
None }
}
(DiffResult::Removed(_, _), DiffResult::Removed(_, _)) => None,
(
DiffResult::Modified(_, _, source_value),
DiffResult::Modified(_, _, destination_value),
) => {
if source_value != destination_value {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
} else {
None }
}
(DiffResult::Added(_, source_value), DiffResult::Removed(_, _)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: None,
})
}
(DiffResult::Removed(_, _), DiffResult::Added(_, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: None,
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Added(_, source_value), DiffResult::Modified(_, _, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Modified(_, _, source_value), DiffResult::Added(_, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Removed(_, _), DiffResult::Modified(_, _, destination_value)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: None,
destination_value: Some(destination_value.clone()),
})
}
(DiffResult::Modified(_, _, source_value), DiffResult::Removed(_, _)) => {
Some(MergeConflict {
key: key.to_vec(),
base_value,
source_value: Some(source_value.clone()),
destination_value: None,
})
}
}
}
#[allow(dead_code)]
fn diff_recursive(
&self,
old_node: &ProllyNode<N>,
new_node: &ProllyNode<N>,
diffs: &mut Vec<DiffResult>,
) {
let mut old_iter = old_node.keys.iter().zip(old_node.values.iter()).peekable();
let mut new_iter = new_node.keys.iter().zip(new_node.values.iter()).peekable();
while let (Some((old_key, old_value)), Some((new_key, new_value))) =
(old_iter.peek(), new_iter.peek())
{
match old_key.cmp(new_key) {
std::cmp::Ordering::Less => {
diffs.push(DiffResult::Removed(old_key.to_vec(), old_value.to_vec()));
old_iter.next();
}
std::cmp::Ordering::Greater => {
diffs.push(DiffResult::Added(new_key.to_vec(), new_value.to_vec()));
new_iter.next();
}
std::cmp::Ordering::Equal => {
if old_value != new_value {
diffs.push(DiffResult::Modified(
old_key.to_vec(),
old_value.to_vec(),
new_value.to_vec(),
));
}
old_iter.next();
new_iter.next();
}
}
}
for (old_key, old_value) in old_iter {
diffs.push(DiffResult::Removed(old_key.clone(), old_value.clone()));
}
for (new_key, new_value) in new_iter {
diffs.push(DiffResult::Added(new_key.clone(), new_value.clone()));
}
}
pub fn persist_root(&mut self) {
let root_hash = self.root.get_hash();
if self
.storage
.insert_node(root_hash.clone(), self.root.clone())
.is_ok()
{
self.config.root_hash = Some(root_hash);
let _ = self.save_config();
}
}
pub fn load_from_storage(storage: S, config: TreeConfig<N>) -> Option<Self> {
if let Some(ref root_hash) = config.root_hash {
if let Some(root_node) = storage.get_node_by_hash(root_hash) {
return Some(ProllyTree {
root: Arc::unwrap_or_clone(root_node),
storage,
config,
});
}
}
None
}
pub fn apply_changes<I>(&mut self, changes: I) -> usize
where
I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
{
let map: std::collections::BTreeMap<Vec<u8>, Option<Vec<u8>>> =
changes.into_iter().collect();
let mut missing_deletes = 0usize;
for (k, v) in map.iter() {
if v.is_none() && self.find(k).is_none() {
missing_deletes += 1;
}
}
let new_root = crate::streaming_chunker::apply_mutations(
self.root.clone(),
map,
&self.config,
&mut self.storage,
);
let _ = self
.storage
.insert_node(new_root.get_hash(), new_root.clone());
self.root = new_root;
missing_deletes
}
pub fn collect_keys(&self) -> Vec<Vec<u8>> {
let mut keys = Vec::new();
self.collect_keys_recursive(&self.root, &mut keys);
keys
}
fn collect_keys_recursive(&self, node: &ProllyNode<N>, keys: &mut Vec<Vec<u8>>) {
if node.is_leaf {
for key in &node.keys {
keys.push(key.clone());
}
} else {
for child_node in node.children(&self.storage) {
self.collect_keys_recursive(&child_node, keys);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::InMemoryNodeStorage;
#[test]
fn inmem_node_storage_test() {
let config = TreeConfig {
base: 131,
modulus: 1_000_000_009,
min_chunk_size: 4,
max_chunk_size: 8 * 1024,
pattern: 0b101,
root_hash: None,
key_schema: None,
value_schema: None,
encode_types: vec![],
};
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, config);
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
let traversal = tree.formatted_traverse(|node| {
let keys_as_strings: Vec<String> = node.keys.iter().map(|k| format!("{k:?}")).collect();
format!("[L{}: {}]", node.level, keys_as_strings.join(", "))
});
println!("Traversal: {traversal}");
tree.update(b"key1".to_vec(), b"new_value1".to_vec());
if let Some(node) = tree.find(b"key1") {
println!("Found key1 with value: {node:?}");
} else {
println!("key1 not found");
}
if tree.delete(b"key2") {
println!("key2 deleted");
} else {
println!("key2 not found");
}
println!("Size: {}", tree.size());
println!("Depth: {}", tree.depth());
println!("Summary: {}", tree.summary());
println!("{:?}", tree.root.print_tree(&tree.storage));
}
#[test]
fn file_node_storage_test() {
use crate::storage::FileNodeStorage;
use std::fs;
use std::path::PathBuf;
let config = TreeConfig {
base: 131,
modulus: 1_000_000_009,
min_chunk_size: 4,
max_chunk_size: 8 * 1024,
pattern: 0b101,
root_hash: None,
key_schema: None,
value_schema: None,
encode_types: vec![],
};
let storage_dir = PathBuf::from("/tmp/prolly_tree_storage");
let storage = FileNodeStorage::<32>::new(storage_dir.clone()).unwrap();
let mut tree = ProllyTree::new(storage, config);
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
let traversal = tree.formatted_traverse(|node| {
let keys_as_strings: Vec<String> = node.keys.iter().map(|k| format!("{k:?}")).collect();
format!("[L{}: {}]", node.level, keys_as_strings.join(", "))
});
println!("Traversal: {traversal}");
tree.update(b"key1".to_vec(), b"new_value1".to_vec());
if let Some(node) = tree.find(b"key1") {
println!("Found key1 with value: {node:?}");
} else {
println!("key1 not found");
}
if tree.delete(b"key2") {
println!("key2 deleted");
} else {
println!("key2 not found");
}
println!("Size: {}", tree.size());
println!("Depth: {}", tree.depth());
println!("Summary: {}", tree.summary());
println!("{:?}", tree.root.print_tree(&tree.storage));
fs::remove_dir_all(storage_dir).unwrap();
}
#[test]
fn test_insert_and_find() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
assert!(tree.find(b"key1").is_some());
assert!(tree.find(b"key2").is_some());
assert!(tree.find(b"key3").is_none());
}
#[test]
fn test_persist_and_load() {
let storage = InMemoryNodeStorage::<32>::default();
let config = TreeConfig::default();
let mut tree = ProllyTree::new(storage.clone(), config.clone());
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
tree.persist_root();
let loaded_tree = ProllyTree::load_from_storage(tree.storage, tree.config)
.expect("Should be able to load tree from storage");
assert!(loaded_tree.find(b"key1").is_some());
assert!(loaded_tree.find(b"key2").is_some());
assert!(loaded_tree.find(b"key3").is_none());
}
#[test]
fn test_insert_batch_and_find() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let keys = vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()];
let values = vec![b"value1".to_vec(), b"value2".to_vec(), b"value3".to_vec()];
tree.insert_batch(&keys, &values);
assert!(tree.find(b"key1").is_some());
assert!(tree.find(b"key2").is_some());
assert!(tree.find(b"key3").is_some());
assert!(tree.find(b"key4").is_none());
}
#[test]
#[should_panic(expected = "insert_batch requires the same number of keys and values")]
fn test_insert_batch_rejects_length_mismatch() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let keys = vec![b"key1".to_vec(), b"key2".to_vec()];
let values = vec![b"value1".to_vec()];
tree.insert_batch(&keys, &values);
}
#[test]
fn test_delete() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
tree.insert(b"key1".to_vec(), b"value1".to_vec());
tree.insert(b"key2".to_vec(), b"value2".to_vec());
assert!(tree.delete(b"key1"));
assert!(tree.find(b"key1").is_none());
assert!(tree.find(b"key2").is_some());
}
#[test]
fn test_delete_batch() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let keys = vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()];
let values = vec![b"value1".to_vec(), b"value2".to_vec(), b"value3".to_vec()];
tree.insert_batch(&keys, &values);
assert!(tree.find(b"key1").is_some());
assert!(tree.find(b"key2").is_some());
assert!(tree.find(b"key3").is_some());
tree.delete_batch(&keys);
assert!(tree.find(b"key1").is_none());
assert!(tree.find(b"key2").is_none());
assert!(tree.find(b"key3").is_none());
}
#[test]
fn test_traverse() {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
let key1 = b"key1".to_vec();
let key2 = b"key2".to_vec();
tree.insert(key1.clone(), b"value1".to_vec());
tree.insert(key2.clone(), b"value2".to_vec());
let traversal = tree.traverse();
let expected_key1 = format!("{key1:?}");
let expected_key2 = format!("{key2:?}");
assert!(traversal.contains(&expected_key1.to_string()));
assert!(traversal.contains(&expected_key2.to_string()));
}
#[test]
fn test_stats() {
let storage = InMemoryNodeStorage::<32>::default();
let config = TreeConfig {
base: 131,
modulus: 1_000_000_009,
min_chunk_size: 16,
max_chunk_size: 8 * 1024,
pattern: 0b111,
root_hash: None,
key_schema: None,
value_schema: None,
encode_types: vec![],
};
let mut tree = ProllyTree::new(storage, config);
let max_key = 3000u32;
for i in 0..max_key {
let key = i.to_be_bytes().to_vec();
let value = i.to_be_bytes().to_vec();
tree.insert(key.clone(), value.clone());
}
println!("{:?}", tree.root.print_tree(&tree.storage));
for i in 0..max_key {
let key = i.to_be_bytes().to_vec();
assert!(tree.find(&key).is_some());
}
let non_existing_key = (max_key + 10).to_be_bytes().to_vec();
assert!(tree.find(&non_existing_key).is_none());
assert_eq!(tree.size(), max_key as usize);
assert_eq!(tree.depth(), 3);
println!("Size: {}", tree.size());
println!("Depth: {}", tree.depth());
println!("Summary: {}", tree.summary());
}
#[test]
fn test_generate_proof() {
let config = TreeConfig::default();
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, config);
for i in 0..100 {
let key = vec![i];
let value = vec![i];
tree.insert(key.clone(), value.clone());
}
let key_to_prove = vec![5];
let proof = tree.generate_proof(&key_to_prove);
let verified = tree.verify(proof, &key_to_prove, Some(&key_to_prove));
assert!(verified);
let key_to_prove_wrong = vec![120];
let proof_wrong = tree.generate_proof(&key_to_prove_wrong);
let verified_wrong =
tree.verify(proof_wrong, &key_to_prove_wrong, Some(&key_to_prove_wrong));
assert!(!verified_wrong);
}
#[test]
fn test_diff() {
let config = TreeConfig::default();
let storage1 = InMemoryNodeStorage::<32>::default();
let mut tree1 = ProllyTree::new(storage1, config.clone());
let storage2 = InMemoryNodeStorage::<32>::default();
let mut tree2 = ProllyTree::new(storage2, config);
for i in 0..50 {
tree1.insert(vec![i], vec![i]);
}
for i in 0..50 {
tree2.insert(vec![i], vec![i]);
}
tree2.insert(vec![10], vec![200]);
println!("{:?}", tree1.root.print_tree(&tree1.storage));
println!("{:?}", tree2.root.print_tree(&tree2.storage));
let differences = tree1.diff(&tree2);
for diff in &differences {
match diff {
DiffResult::Added(key, value) => {
println!("Added: key = {key:?}, value = {value:?}");
}
DiffResult::Removed(key, value) => {
println!("Removed: key = {key:?}, value = {value:?}");
}
DiffResult::Modified(key, old_value, new_value) => {
println!(
"Modified: key = {key:?}, old_value = {old_value:?}, new_value = {new_value:?}"
);
}
}
}
}
#[test]
fn test_print_proof_demo() {
let config = TreeConfig::default();
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, config);
for i in 0..20 {
tree.insert(vec![i], vec![i * 10]);
}
println!("=== Prolly Tree with Proof Visualization Demo ===");
let existing_key = vec![10];
println!("\n--- Testing with existing key {:?} ---", existing_key);
let is_valid = tree.print_proof(&existing_key);
assert!(is_valid, "Proof should be valid for existing key");
let non_existing_key = vec![25];
println!(
"\n--- Testing with non-existing key {:?} ---",
non_existing_key
);
let is_valid = tree.print_proof(&non_existing_key);
assert!(!is_valid, "Proof should be invalid for non-existing key");
println!("\n=== Demo completed successfully ===");
}
#[test]
fn test_merge_simple() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 0);
}
#[test]
fn test_merge_with_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
base_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"source_value".to_vec());
source_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"dest_value".to_vec());
dest_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 1);
match &merge_results[0] {
MergeResult::Conflict(conflict) => {
assert_eq!(conflict.key, b"key1".to_vec());
assert_eq!(conflict.base_value, Some(b"value1".to_vec()));
assert_eq!(conflict.source_value, Some(b"source_value".to_vec()));
assert_eq!(conflict.destination_value, Some(b"dest_value".to_vec()));
}
_ => panic!("Expected conflict, got: {:?}", merge_results[0]),
}
}
#[test]
fn test_merge_to_empty_tree() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let base_tree = ProllyTree::new(storage.clone(), config.clone());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"value1".to_vec());
source_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let dest_tree = ProllyTree::new(storage.clone(), config.clone());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 2);
let mut keys_added = std::collections::HashSet::new();
for result in merge_results {
match result {
MergeResult::Added(key, _) => {
keys_added.insert(key);
}
_ => panic!("Expected only additions, got: {:?}", result),
}
}
assert!(keys_added.contains(&b"key1".to_vec()));
assert!(keys_added.contains(&b"key2".to_vec()));
}
#[test]
fn test_merge_add_remove_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"modified_value1".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 2);
let mut has_addition = false;
let mut has_conflict = false;
for result in merge_results {
match result {
MergeResult::Added(key, value) => {
assert_eq!(key, b"key2".to_vec());
assert_eq!(value, b"value2".to_vec());
has_addition = true;
}
MergeResult::Conflict(conflict) => {
assert_eq!(conflict.key, b"key1".to_vec());
assert_eq!(conflict.base_value, Some(b"value1".to_vec()));
assert_eq!(conflict.source_value, None); assert_eq!(
conflict.destination_value,
Some(b"modified_value1".to_vec())
);
has_conflict = true;
}
_ => panic!("Unexpected merge result: {:?}", result),
}
}
assert!(has_addition && has_conflict);
}
#[test]
fn test_merge_complex_scenario() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"modify_conflict".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"modify_conflict".to_vec(), b"source_value".to_vec()); source_tree.insert(b"new_in_source".to_vec(), b"source_addition".to_vec()); let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"modify_conflict".to_vec(), b"dest_value".to_vec()); let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
let mut added_keys = std::collections::HashSet::new();
let mut conflicts = std::collections::HashMap::new();
for result in merge_results {
match result {
MergeResult::Added(key, _) => {
added_keys.insert(key);
}
MergeResult::Conflict(conflict) => {
conflicts.insert(conflict.key.clone(), conflict);
}
_ => panic!("Unexpected merge result: {:?}", result),
}
}
assert!(added_keys.contains(&b"new_in_source".to_vec()));
assert!(conflicts.contains_key(&b"modify_conflict".to_vec()));
let conflict = &conflicts[&b"modify_conflict".to_vec()];
assert_eq!(conflict.source_value, Some(b"source_value".to_vec()));
assert_eq!(conflict.destination_value, Some(b"dest_value".to_vec()));
}
#[test]
fn test_merge_same_changes_no_conflict() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"key1".to_vec(), b"value1".to_vec());
base_tree.insert(b"key2".to_vec(), b"value2".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"key1".to_vec(), b"same_new_value".to_vec()); source_tree.insert(b"key2".to_vec(), b"value2".to_vec()); source_tree.insert(b"key3".to_vec(), b"same_addition".to_vec()); let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"key1".to_vec(), b"same_new_value".to_vec()); dest_tree.insert(b"key2".to_vec(), b"value2".to_vec()); dest_tree.insert(b"key3".to_vec(), b"same_addition".to_vec()); let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let merge_results = merge_tree.merge(&source_root, &dest_root, &base_root);
assert_eq!(merge_results.len(), 2);
let mut has_modification = false;
let mut has_addition = false;
for result in merge_results {
match result {
MergeResult::Modified(key, value) => {
assert_eq!(key, b"key1".to_vec());
assert_eq!(value, b"same_new_value".to_vec());
has_modification = true;
}
MergeResult::Added(key, value) => {
assert_eq!(key, b"key3".to_vec());
assert_eq!(value, b"same_addition".to_vec());
has_addition = true;
}
_ => panic!("Unexpected merge result: {:?}", result),
}
}
assert!(has_modification && has_addition);
}
#[test]
fn test_apply_merge_results_success() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"existing".to_vec(), b"value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let merge_results = vec![
MergeResult::Added(b"new_key".to_vec(), b"new_value".to_vec()),
MergeResult::Modified(b"existing".to_vec(), b"modified_value".to_vec()),
];
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.apply_merge_results(&base_root, &merge_results);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"new_key").is_some());
assert!(merged_tree.find(b"existing").is_some());
if let Some(node) = merged_tree.find(b"new_key") {
let key_idx = node.keys.iter().position(|k| k == b"new_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"new_value".to_vec());
}
if let Some(node) = merged_tree.find(b"existing") {
let key_idx = node.keys.iter().position(|k| k == b"existing").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"modified_value".to_vec());
}
}
#[test]
fn test_apply_merge_results_with_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let base_tree = ProllyTree::new(storage.clone(), config.clone());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let merge_results = vec![
MergeResult::Added(b"new_key".to_vec(), b"new_value".to_vec()),
MergeResult::Conflict(MergeConflict {
key: b"conflict_key".to_vec(),
base_value: Some(b"base".to_vec()),
source_value: Some(b"source".to_vec()),
destination_value: Some(b"dest".to_vec()),
}),
];
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.apply_merge_results(&base_root, &merge_results);
assert!(result.is_err());
let conflicts = result.expect_err("Expected conflicts");
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].key, b"conflict_key".to_vec());
}
#[test]
fn test_merge_trees_success() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"shared".to_vec(), b"original".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"shared".to_vec(), b"original".to_vec());
source_tree.insert(b"from_source".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"shared".to_vec(), b"original".to_vec());
dest_tree.insert(b"from_dest".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.merge_trees_ignore_conflicts(&source_root, &dest_root, &base_root);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"shared").is_some());
assert!(merged_tree.find(b"from_source").is_some());
assert!(merged_tree.find(b"from_dest").is_some()); }
#[test]
fn test_merge_trees_with_conflicts() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"original".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
struct NoResolutionResolver;
impl ConflictResolver for NoResolutionResolver {
fn resolve_conflict(&self, _conflict: &MergeConflict) -> Option<MergeResult> {
None }
}
let resolver = NoResolutionResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_err());
let conflicts = result.expect_err("Expected conflicts");
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].key, b"conflict_key".to_vec());
}
#[test]
fn test_merge_trees_with_ignore_conflicts_resolver() {
use crate::diff::IgnoreConflictsResolver;
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"shared".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"shared".to_vec(), b"source_value".to_vec());
source_tree.insert(b"source_only".to_vec(), b"source_only_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"shared".to_vec(), b"dest_value".to_vec());
dest_tree.insert(b"dest_only".to_vec(), b"dest_only_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let resolver = IgnoreConflictsResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"source_only").is_some());
assert!(merged_tree.find(b"dest_only").is_some());
if let Some(node) = merged_tree.find(b"shared") {
let key_idx = node.keys.iter().position(|k| k == b"shared").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"dest_value".to_vec());
}
}
#[test]
fn test_merge_trees_with_take_source_resolver() {
use crate::diff::TakeSourceResolver;
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let resolver = TakeSourceResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_ok());
let merged_tree = result.unwrap();
if let Some(node) = merged_tree.find(b"conflict_key") {
let key_idx = node.keys.iter().position(|k| k == b"conflict_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"source_value".to_vec());
}
}
#[test]
fn test_merge_trees_with_take_destination_resolver() {
use crate::diff::TakeDestinationResolver;
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let resolver = TakeDestinationResolver;
let result = merge_tree.merge_trees(&source_root, &dest_root, &base_root, &resolver);
assert!(result.is_ok());
let merged_tree = result.unwrap();
if let Some(node) = merged_tree.find(b"conflict_key") {
let key_idx = node.keys.iter().position(|k| k == b"conflict_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"dest_value".to_vec());
}
}
#[test]
fn test_merge_trees_ignore_conflicts_convenience_method() {
let config = TreeConfig::default();
let mut storage = InMemoryNodeStorage::<32>::default();
let mut base_tree = ProllyTree::new(storage.clone(), config.clone());
base_tree.insert(b"conflict_key".to_vec(), b"base_value".to_vec());
let base_root = base_tree.get_root_hash().unwrap();
storage
.insert_node(base_root.clone(), base_tree.root.clone())
.unwrap();
let mut source_tree = ProllyTree::new(storage.clone(), config.clone());
source_tree.insert(b"conflict_key".to_vec(), b"source_value".to_vec());
source_tree.insert(b"new_key".to_vec(), b"new_value".to_vec());
let source_root = source_tree.get_root_hash().unwrap();
storage
.insert_node(source_root.clone(), source_tree.root.clone())
.unwrap();
let mut dest_tree = ProllyTree::new(storage.clone(), config.clone());
dest_tree.insert(b"conflict_key".to_vec(), b"dest_value".to_vec());
let dest_root = dest_tree.get_root_hash().unwrap();
storage
.insert_node(dest_root.clone(), dest_tree.root.clone())
.unwrap();
let merge_tree = ProllyTree::new(storage, config);
let result = merge_tree.merge_trees_ignore_conflicts(&source_root, &dest_root, &base_root);
assert!(result.is_ok());
let merged_tree = result.unwrap();
assert!(merged_tree.find(b"new_key").is_some());
if let Some(node) = merged_tree.find(b"conflict_key") {
let key_idx = node.keys.iter().position(|k| k == b"conflict_key").unwrap();
let value = node.values[key_idx].clone();
assert_eq!(value, b"dest_value".to_vec());
}
}
mod history_independence_tests {
use super::*;
use rand::prelude::StdRng;
use rand::seq::SliceRandom;
use rand::SeedableRng;
fn k8(i: u64) -> Vec<u8> {
i.to_be_bytes().to_vec()
}
fn v16(i: u64) -> Vec<u8> {
let mut v = Vec::with_capacity(16);
v.extend_from_slice(&i.to_be_bytes());
v.extend_from_slice(&(!i).to_be_bytes());
v
}
fn orders(n: u64) -> Vec<(&'static str, Vec<u64>)> {
let asc: Vec<u64> = (0..n).collect();
let desc: Vec<u64> = (0..n).rev().collect();
let alt: Vec<u64> = (0..n).step_by(2).chain((1..n).step_by(2)).collect();
let mut s0: Vec<u64> = (0..n).collect();
s0.shuffle(&mut StdRng::from_seed([0u8; 32]));
let mut s42: Vec<u64> = (0..n).collect();
s42.shuffle(&mut StdRng::from_seed([42u8; 32]));
vec![
("ascending", asc),
("descending", desc),
("alt-odd-even", alt),
("shuffled(0)", s0),
("shuffled(42)", s42),
]
}
fn build_tree(order: &[u64]) -> ProllyTree<32, InMemoryNodeStorage<32>> {
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
for &i in order {
tree.insert(k8(i), v16(i));
}
tree
}
#[test]
fn prollytree_traversal_independent_of_order() {
const N: u64 = 256;
let orders = orders(N);
let baseline_label = orders[0].0;
let baseline_trav = build_tree(&orders[0].1).traverse();
for (label, order) in orders.iter().skip(1) {
let t = build_tree(order).traverse();
assert_eq!(
t, baseline_trav,
"order={} leaf content diverged from order={}",
label, baseline_label
);
}
}
#[test]
fn prollytree_root_hash_independent_of_order() {
const N: u64 = 256;
let orders = orders(N);
let baseline_label = orders[0].0;
let baseline_hash = build_tree(&orders[0].1).get_root_hash().unwrap();
for (label, order) in orders.iter().skip(1) {
let h = build_tree(order).get_root_hash().unwrap();
assert_eq!(
h, baseline_hash,
"order={} root hash diverged from order={}",
label, baseline_label
);
}
}
#[test]
fn prollytree_root_hash_independent_under_updates() {
const N: u64 = 256;
let asc: Vec<u64> = (0..N).collect();
let baseline = build_tree(&asc).get_root_hash().unwrap();
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
for &i in &asc {
tree.insert(k8(i), vec![0u8]);
}
for i in (0..N).rev() {
tree.insert(k8(i), v16(i));
}
let h = tree.get_root_hash().unwrap();
assert_eq!(
h, baseline,
"update-then-final root hash diverged from baseline"
);
}
#[test]
fn prollytree_root_hash_independent_under_deletes() {
const SURVIVE: u64 = 256;
const EXTRA: u64 = 256;
let survivors: Vec<u64> = (0..SURVIVE).collect();
let baseline = build_tree(&survivors).get_root_hash().unwrap();
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
for i in 0..SURVIVE + EXTRA {
tree.insert(k8(i), v16(i));
}
for i in SURVIVE..SURVIVE + EXTRA {
tree.delete(&k8(i));
}
let h = tree.get_root_hash().unwrap();
assert_eq!(
h, baseline,
"delete-then-final root hash diverged from baseline"
);
}
}
mod merge_canonicality_tests {
use super::*;
use crate::diff::IgnoreConflictsResolver;
use rand::prelude::StdRng;
use rand::seq::SliceRandom;
use rand::SeedableRng;
fn k8(i: u64) -> Vec<u8> {
i.to_be_bytes().to_vec()
}
fn v16(i: u64) -> Vec<u8> {
let mut v = Vec::with_capacity(16);
v.extend_from_slice(&i.to_be_bytes());
v.extend_from_slice(&(!i).to_be_bytes());
v
}
fn build_tree_sharing_storage(
storage: &mut InMemoryNodeStorage<32>,
indices: &[u64],
) -> ValueDigest<32> {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for &i in indices {
tree.insert(k8(i), v16(i));
}
let root_hash = tree.get_root_hash().unwrap();
*storage = tree.storage;
root_hash
}
#[test]
fn apply_merge_results_is_canonical() {
let mut storage = InMemoryNodeStorage::<32>::default();
let base_root = build_tree_sharing_storage(&mut storage, &(0..16).collect::<Vec<_>>());
let mut source_keys: Vec<u64> = (0..24).collect();
source_keys.shuffle(&mut StdRng::from_seed([1u8; 32]));
let source_root = build_tree_sharing_storage(&mut storage, &source_keys);
let mut dest_keys: Vec<u64> = (0..16).chain(24..32).collect();
dest_keys.shuffle(&mut StdRng::from_seed([2u8; 32]));
let dest_root = build_tree_sharing_storage(&mut storage, &dest_keys);
let mut expected_keys: Vec<u64> = (0..32).collect();
expected_keys.shuffle(&mut StdRng::from_seed([3u8; 32]));
let expected_root = build_tree_sharing_storage(&mut storage, &expected_keys);
let merge_tool = ProllyTree::new(storage, TreeConfig::default());
let merge_results = merge_tool.merge(&source_root, &dest_root, &base_root);
let merged = merge_tool
.apply_merge_results(&dest_root, &merge_results)
.expect("no conflicts expected");
assert_eq!(
merged.get_root_hash().unwrap(),
expected_root,
"merged tree's root hash does not match a fresh canonical build of the same final state"
);
}
#[test]
fn apply_merge_results_independent_of_result_order() {
let mut storage = InMemoryNodeStorage::<32>::default();
let base_root = build_tree_sharing_storage(&mut storage, &(0..16).collect::<Vec<_>>());
let source_root =
build_tree_sharing_storage(&mut storage, &(0..24).collect::<Vec<_>>());
let dest_root = build_tree_sharing_storage(
&mut storage,
&(0..16).chain(24..32).collect::<Vec<_>>(),
);
let merge_tool = ProllyTree::new(storage, TreeConfig::default());
let mut results = merge_tool.merge(&source_root, &dest_root, &base_root);
let merged_asc = merge_tool
.apply_merge_results(&dest_root, &results)
.expect("no conflicts");
results.shuffle(&mut StdRng::from_seed([4u8; 32]));
let merged_shuf = merge_tool
.apply_merge_results(&dest_root, &results)
.expect("no conflicts");
assert_eq!(
merged_asc.get_root_hash().unwrap(),
merged_shuf.get_root_hash().unwrap(),
"applying the same merge results in different orders produced different root hashes"
);
}
#[test]
fn merge_trees_ignore_conflicts_is_canonical() {
let mut storage = InMemoryNodeStorage::<32>::default();
let base_root = build_tree_sharing_storage(&mut storage, &(0..16).collect::<Vec<_>>());
let mut a_keys: Vec<u64> = (0..24).collect();
a_keys.shuffle(&mut StdRng::from_seed([5u8; 32]));
let source_root = build_tree_sharing_storage(&mut storage, &a_keys);
let mut b_keys: Vec<u64> = (0..16).chain(24..32).collect();
b_keys.shuffle(&mut StdRng::from_seed([6u8; 32]));
let dest_root = build_tree_sharing_storage(&mut storage, &b_keys);
let expected_root =
build_tree_sharing_storage(&mut storage, &(0..32).collect::<Vec<_>>());
let merge_tool = ProllyTree::new(storage, TreeConfig::default());
let merged = merge_tool
.merge_trees(
&source_root,
&dest_root,
&base_root,
&IgnoreConflictsResolver,
)
.expect("no unresolved conflicts");
assert_eq!(
merged.get_root_hash().unwrap(),
expected_root,
"merge_trees output's root hash differs from a fresh canonical build"
);
}
#[test]
fn merge_trees_take_source_is_canonical() {
use crate::diff::TakeSourceResolver;
let mut storage = InMemoryNodeStorage::<32>::default();
let base_root = build_tree_sharing_storage(&mut storage, &(0..16).collect::<Vec<_>>());
let source_root = {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for i in 0u64..24 {
let v = if i == 5 { v16(5_000_000) } else { v16(i) };
tree.insert(k8(i), v);
}
let h = tree.get_root_hash().unwrap();
storage = tree.storage;
h
};
let dest_root = {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for i in (0..16).chain(24..32) {
let v = if i == 5 { v16(9_999_999) } else { v16(i) };
tree.insert(k8(i), v);
}
let h = tree.get_root_hash().unwrap();
storage = tree.storage;
h
};
let expected_root = {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for i in 0u64..32 {
let v = if i == 5 { v16(5_000_000) } else { v16(i) };
tree.insert(k8(i), v);
}
let h = tree.get_root_hash().unwrap();
storage = tree.storage;
h
};
let merge_tool = ProllyTree::new(storage, TreeConfig::default());
let merged = merge_tool
.merge_trees(&source_root, &dest_root, &base_root, &TakeSourceResolver)
.expect("no unresolved conflicts (resolver handles them)");
assert_eq!(
merged.get_root_hash().unwrap(),
expected_root,
"TakeSourceResolver merge output is non-canonical"
);
}
#[test]
fn merge_trees_take_destination_is_canonical() {
use crate::diff::TakeDestinationResolver;
let mut storage = InMemoryNodeStorage::<32>::default();
let base_root = build_tree_sharing_storage(&mut storage, &(0..16).collect::<Vec<_>>());
let source_root = {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for i in 0u64..24 {
let v = if i == 5 { v16(5_000_000) } else { v16(i) };
tree.insert(k8(i), v);
}
let h = tree.get_root_hash().unwrap();
storage = tree.storage;
h
};
let dest_root = {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for i in (0..16).chain(24..32) {
let v = if i == 5 { v16(9_999_999) } else { v16(i) };
tree.insert(k8(i), v);
}
let h = tree.get_root_hash().unwrap();
storage = tree.storage;
h
};
let expected_root = {
let mut tree = ProllyTree::new(storage.clone(), TreeConfig::default());
for i in 0u64..32 {
let v = if i == 5 { v16(9_999_999) } else { v16(i) };
tree.insert(k8(i), v);
}
let h = tree.get_root_hash().unwrap();
storage = tree.storage;
h
};
let merge_tool = ProllyTree::new(storage, TreeConfig::default());
let merged = merge_tool
.merge_trees(
&source_root,
&dest_root,
&base_root,
&TakeDestinationResolver,
)
.expect("no unresolved conflicts (resolver handles them)");
assert_eq!(
merged.get_root_hash().unwrap(),
expected_root,
"TakeDestinationResolver merge output is non-canonical"
);
}
}
}
#[cfg(test)]
mod prefix_free_collision_probe {
use super::*;
use crate::storage::InMemoryNodeStorage;
#[test]
fn distinct_contents_must_not_share_root() {
let cfg = TreeConfig::<32>::default();
let mut a = ProllyTree::new(InMemoryNodeStorage::<32>::default(), cfg.clone());
a.insert(b"k1".to_vec(), b"ab".to_vec());
a.insert(b"k2".to_vec(), b"c".to_vec());
let mut b = ProllyTree::new(InMemoryNodeStorage::<32>::default(), cfg.clone());
b.insert(b"k1".to_vec(), b"a".to_vec());
b.insert(b"k2".to_vec(), b"bc".to_vec());
let ra = a.get_root_hash().unwrap();
let rb = b.get_root_hash().unwrap();
assert_ne!(
ra.as_bytes(),
rb.as_bytes(),
"PREFIX-FREE COLLISION CONFIRMED: distinct (k,v) sets share a root hash"
);
}
}
#[cfg(test)]
mod odiff_differential {
use super::*;
use crate::digest::ValueDigest;
use crate::storage::{InMemoryNodeStorage, NodeStorage, StorageError};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Clone)]
struct CountingStorage<const N: usize> {
inner: InMemoryNodeStorage<N>,
loads: Arc<AtomicUsize>,
}
impl<const N: usize> CountingStorage<N> {
fn new() -> Self {
Self {
inner: InMemoryNodeStorage::<N>::default(),
loads: Arc::new(AtomicUsize::new(0)),
}
}
}
impl<const N: usize> NodeStorage<N> for CountingStorage<N> {
fn get_node_by_hash(&self, hash: &ValueDigest<N>) -> Option<Arc<ProllyNode<N>>> {
self.loads.fetch_add(1, Ordering::SeqCst);
self.inner.get_node_by_hash(hash)
}
fn insert_node(
&mut self,
hash: ValueDigest<N>,
node: ProllyNode<N>,
) -> Result<(), StorageError> {
self.inner.insert_node(hash, node)
}
fn delete_node(&mut self, hash: &ValueDigest<N>) -> Result<(), StorageError> {
self.inner.delete_node(hash)
}
fn save_config(&self, key: &str, config: &[u8]) {
self.inner.save_config(key, config)
}
fn get_config(&self, key: &str) -> Option<Vec<u8>> {
self.inner.get_config(key)
}
}
fn build_two(
config: &TreeConfig<32>,
old_pairs: &[(Vec<u8>, Vec<u8>)],
new_pairs: &[(Vec<u8>, Vec<u8>)],
) -> (CountingStorage<32>, ValueDigest<32>, ValueDigest<32>) {
let mut t_old = ProllyTree::new(CountingStorage::<32>::new(), config.clone());
for (k, v) in old_pairs {
t_old.insert(k.clone(), v.clone());
}
let old_root = t_old.get_root_hash().unwrap();
t_old
.storage
.insert_node(old_root.clone(), t_old.root.clone())
.unwrap();
let mut t_new = ProllyTree::new(t_old.storage.clone(), config.clone());
for (k, v) in new_pairs {
t_new.insert(k.clone(), v.clone());
}
let new_root = t_new.get_root_hash().unwrap();
t_new
.storage
.insert_node(new_root.clone(), t_new.root.clone())
.unwrap();
(t_new.storage, old_root, new_root)
}
fn key(i: u64) -> Vec<u8> {
i.to_be_bytes().to_vec()
}
fn val(i: u64, tag: u8) -> Vec<u8> {
let mut v = vec![tag];
v.extend_from_slice(&i.to_le_bytes());
v.extend(std::iter::repeat_n(tag, (i % 19) as usize));
v
}
fn assert_identical(
old_pairs: &[(Vec<u8>, Vec<u8>)],
new_pairs: &[(Vec<u8>, Vec<u8>)],
label: &str,
) -> (usize, usize) {
let config = TreeConfig::<32>::default();
let (storage, old_root, new_root) = build_two(&config, old_pairs, new_pairs);
let old_node = storage.get_node_by_hash(&old_root).unwrap();
let new_node = storage.get_node_by_hash(&new_root).unwrap();
if !old_node.is_leaf {
assert_eq!(
old_node.children(&storage).len(),
old_node.values.len(),
"[{label}] old root children not all persisted (harness bug)"
);
}
if !new_node.is_leaf {
assert_eq!(
new_node.children(&storage).len(),
new_node.values.len(),
"[{label}] new root children not all persisted (harness bug)"
);
}
let facade = ProllyTree::new(storage.clone(), config.clone());
storage.loads.store(0, Ordering::SeqCst);
let mut d_struct = Vec::new();
facade.diff_nodes_recursive(&old_node, &new_node, &mut d_struct);
let struct_loads = storage.loads.load(Ordering::SeqCst);
storage.loads.store(0, Ordering::SeqCst);
let mut d_flat = Vec::new();
facade.diff_nodes_flatten(&old_node, &new_node, &mut d_flat);
let flat_loads = storage.loads.load(Ordering::SeqCst);
assert_eq!(
d_struct, d_flat,
"[{label}] structural diff diverged from flatten oracle"
);
(struct_loads, flat_loads)
}
fn range_pairs(lo: u64, hi: u64, tag: u8) -> Vec<(Vec<u8>, Vec<u8>)> {
(lo..hi).map(|i| (key(i), val(i, tag))).collect()
}
#[test]
fn identical_trees_zero_diff_zero_descent() {
let base = range_pairs(0, 1000, 0);
let (s, f) = assert_identical(&base, &base, "identical");
assert_eq!(s, 0, "identical trees must load zero nodes structurally");
assert!(f >= s);
}
#[test]
fn localized_modify_skips_shared_subtrees() {
let old = range_pairs(0, 1000, 0);
let mut new = old.clone();
new[500].1 = val(500, 9); let (s, f) = assert_identical(&old, &new, "localized_modify");
assert!(
s < f,
"localized change must load fewer nodes structurally ({s}) than flatten ({f})"
);
let config = TreeConfig::<32>::default();
let (storage, old_root, new_root) = build_two(&config, &old, &new);
let on = storage.get_node_by_hash(&old_root).unwrap();
let nn = storage.get_node_by_hash(&new_root).unwrap();
let facade = ProllyTree::new(storage.clone(), config.clone());
let mut d = Vec::new();
facade.diff_nodes_recursive(&on, &nn, &mut d);
assert_eq!(
d,
vec![DiffResult::Modified(key(500), val(500, 0), val(500, 9))],
"structural diff must report exactly the one changed cell, got {d:?}"
);
}
#[test]
fn bulk_suffix_add_shares_prefix() {
let old = range_pairs(0, 800, 0);
let new = range_pairs(0, 1000, 0); let (s, f) = assert_identical(&old, &new, "bulk_suffix_add");
assert!(
s < f,
"shared-prefix add should skip prefix subtrees ({s} < {f})"
);
}
#[test]
fn bulk_prefix_remove() {
let old = range_pairs(0, 1000, 0);
let new = range_pairs(200, 1000, 0); assert_identical(&old, &new, "bulk_prefix_remove");
}
#[test]
fn scattered_modifications() {
let old = range_pairs(0, 1000, 0);
let mut new = old.clone();
for i in (0..1000).step_by(7) {
new[i].1 = val(i as u64, 5); }
assert_identical(&old, &new, "scattered_modify");
}
#[test]
fn fully_disjoint_keyspaces() {
let old = range_pairs(0, 500, 0);
let new = range_pairs(10_000, 10_500, 0); assert_identical(&old, &new, "disjoint");
}
#[test]
fn interleaved_add_remove_modify() {
let mut old = range_pairs(0, 600, 0);
let mut new: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
for i in 0..600u64 {
if i % 2 == 0 {
continue; }
let tag = if i % 3 == 0 { 7 } else { 0 };
new.push((key(i), val(i, tag)));
}
new.extend(range_pairs(600, 720, 0));
old.sort();
new.sort();
assert_identical(&old, &new, "interleaved");
}
#[test]
fn ground_truth_add_remove_modify() {
let old = range_pairs(0, 200, 0);
let mut new = old.clone();
new.retain(|(k, _)| k != &key(50));
for p in new.iter_mut() {
if p.0 == key(100) {
p.1 = val(100, 9);
}
}
new.push((key(500), val(500, 0)));
new.sort();
let config = TreeConfig::<32>::default();
let (storage, old_root, new_root) = build_two(&config, &old, &new);
let on = storage.get_node_by_hash(&old_root).unwrap();
let nn = storage.get_node_by_hash(&new_root).unwrap();
let facade = ProllyTree::new(storage.clone(), config.clone());
let mut d = Vec::new();
facade.diff_nodes_recursive(&on, &nn, &mut d);
assert_eq!(
d,
vec![
DiffResult::Removed(key(50), val(50, 0)),
DiffResult::Modified(key(100), val(100, 0), val(100, 9)),
DiffResult::Added(key(500), val(500, 0)),
],
"structural diff ground truth mismatch, got {d:?}"
);
}
#[test]
fn report_load_win() {
for n in [1_000u64, 4_000u64] {
let old = range_pairs(0, n, 0);
let mut new = old.clone();
new[(n / 2) as usize].1 = val(n / 2, 9); let (s, f) = assert_identical(&old, &new, "report");
eprintln!(
"ODIFF_LOAD_WIN n={n} one-cell-change: structural_loads={s} flatten_loads={f} ratio={:.1}x",
f as f64 / s.max(1) as f64
);
}
}
#[test]
fn small_multilevel_and_single_leaf() {
assert_identical(&range_pairs(0, 3, 0), &range_pairs(0, 5, 0), "tiny");
assert_identical(
&range_pairs(0, 1, 0),
&range_pairs(0, 1, 1),
"one_key_modify",
);
assert_identical(&[], &range_pairs(0, 4, 0), "empty_to_small");
assert_identical(&range_pairs(0, 4, 0), &[], "small_to_empty");
}
}
#[cfg(test)]
mod chunker_invariants {
use super::*;
use crate::storage::InMemoryNodeStorage;
use proptest::prelude::*;
fn any_single_child_internal(node: &ProllyNode<32>, storage: &InMemoryNodeStorage<32>) -> bool {
if node.is_leaf {
return false;
}
if node.values.len() < 2 {
return true; }
for child in node.children(storage) {
if any_single_child_internal(&child, storage) {
return true;
}
}
false
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(400))]
#[test]
fn streaming_build_has_no_single_child_internal_node(
entries in proptest::collection::vec(
(proptest::collection::vec(any::<u8>(), 1..6), proptest::collection::vec(any::<u8>(), 0..6)),
0..60,
)
) {
let mut by_key: std::collections::BTreeMap<Vec<u8>, Vec<u8>> =
std::collections::BTreeMap::new();
for (k, v) in entries { by_key.insert(k, v); }
let storage = InMemoryNodeStorage::<32>::default();
let mut tree = ProllyTree::new(storage, TreeConfig::default());
for (k, v) in &by_key { tree.insert(k.clone(), v.clone()); }
prop_assert!(
!any_single_child_internal(&tree.root, &tree.storage),
"streaming build produced a single-child internal node (RT-B F1): root is_leaf={} children={}",
tree.root.is_leaf, tree.root.values.len()
);
}
}
}