//! Merkle tree manager with persistence. //! //! Manages the Merkle tree for assertion hashes with periodic checkpointing //! to the KV store for crash recovery. //! //! # Persistence //! //! The tree is serialized and stored at key `\x00MERKLE_CHECKPOINT`. //! On startup, the manager attempts to load from this checkpoint. //! If not found or corrupt, it rebuilds from the assertion store. //! //! # Thread Safety //! //! All operations are protected by an RwLock, allowing concurrent reads //! but exclusive writes. use crate::error::{Result, SyncError}; use std::sync::Arc; use stemedb_merkle::serialize::{deserialize_tree, serialize_tree}; use stemedb_merkle::{Hash, MerkleTree}; use stemedb_storage::KVStore; use tokio::sync::RwLock; use tracing::{debug, info, instrument, warn}; /// Key for storing the Merkle tree checkpoint. const MERKLE_CHECKPOINT_KEY: &[u8] = b"\x00MERKLE_CHECKPOINT"; /// Manages a Merkle tree with persistence. pub struct MerkleTreeManager { tree: RwLock, store: Arc, } impl MerkleTreeManager { /// Load the Merkle tree from checkpoint, or create a new empty tree. /// /// # Arguments /// /// * `store` - KV store for persistence /// /// # Returns /// /// A manager with the tree loaded from checkpoint if available. #[instrument(skip(store))] pub async fn load_or_create(store: Arc) -> Result { let tree = match store.get(MERKLE_CHECKPOINT_KEY).await? { Some(data) => match deserialize_tree(&data) { Ok(tree) => { info!(leaf_count = tree.len(), "Loaded Merkle tree from checkpoint"); tree } Err(e) => { warn!(error = %e, "Failed to deserialize Merkle checkpoint, starting fresh"); MerkleTree::new() } }, None => { debug!("No Merkle checkpoint found, starting with empty tree"); MerkleTree::new() } }; Ok(Self { tree: RwLock::new(tree), store }) } /// Insert a hash into the Merkle tree. /// /// This operation does NOT automatically checkpoint. Call `checkpoint()` /// periodically to persist the tree. #[instrument(skip(self, hash), fields(hash = %hex::encode(&hash[..8])))] pub async fn insert(&self, hash: Hash) -> Result<()> { let mut tree = self.tree.write().await; tree.insert(hash)?; debug!(leaf_count = tree.len(), "Inserted hash into Merkle tree"); Ok(()) } /// Get the current Merkle root. /// /// Returns `None` if the tree is empty. pub async fn root(&self) -> Result> { let tree = self.tree.read().await; match tree.root() { Ok(root) => Ok(Some(root)), Err(stemedb_merkle::TreeError::EmptyTree) => Ok(None), Err(e) => Err(SyncError::Merkle(e.to_string())), } } /// Get the number of leaves in the tree. pub async fn len(&self) -> usize { self.tree.read().await.len() } /// Check if the tree is empty. pub async fn is_empty(&self) -> bool { self.tree.read().await.is_empty() } /// Get all leaf hashes. /// /// Used for diff operations during anti-entropy sync. pub async fn leaves(&self) -> Vec { self.tree.read().await.leaves().to_vec() } /// Checkpoint the tree to persistent storage. /// /// Should be called periodically (e.g., every 5 minutes) to ensure /// fast recovery after crash. #[instrument(skip(self))] pub async fn checkpoint(&self) -> Result<()> { let tree = self.tree.read().await; let data = serialize_tree(&tree)?; self.store.put(MERKLE_CHECKPOINT_KEY, &data).await?; info!(leaf_count = tree.len(), bytes = data.len(), "Checkpointed Merkle tree"); Ok(()) } /// Rebuild the tree from a list of hashes. /// /// Used during recovery if the checkpoint is corrupt or missing. #[instrument(skip(self, hashes), fields(hash_count = hashes.len()))] pub async fn rebuild_from_hashes(&self, hashes: Vec) -> Result<()> { let mut tree = self.tree.write().await; *tree = MerkleTree::new(); for hash in hashes { tree.insert(hash)?; } info!(leaf_count = tree.len(), "Rebuilt Merkle tree from hashes"); Ok(()) } } #[cfg(test)] mod tests { use super::*; use stemedb_storage::HybridStore; use tempfile::tempdir; async fn create_test_store() -> Arc { let dir = tempdir().expect("create temp dir"); Arc::new(HybridStore::open(dir.path()).expect("open store")) } #[tokio::test] async fn test_empty_tree() { let store = create_test_store().await; let manager = MerkleTreeManager::load_or_create(store).await.expect("create"); assert!(manager.is_empty().await); assert_eq!(manager.len().await, 0); assert!(manager.root().await.expect("root").is_none()); } #[tokio::test] async fn test_insert_and_root() { let store = create_test_store().await; let manager = MerkleTreeManager::load_or_create(store).await.expect("create"); manager.insert([1u8; 32]).await.expect("insert"); manager.insert([2u8; 32]).await.expect("insert"); assert_eq!(manager.len().await, 2); assert!(!manager.is_empty().await); assert!(manager.root().await.expect("root").is_some()); } #[tokio::test] async fn test_checkpoint_and_restore() { let dir = tempdir().expect("create temp dir"); let path = dir.path().to_path_buf(); // Create and populate { let store = Arc::new(HybridStore::open(&path).expect("open store")); let manager = MerkleTreeManager::load_or_create(store).await.expect("create"); manager.insert([1u8; 32]).await.expect("insert"); manager.insert([2u8; 32]).await.expect("insert"); manager.insert([3u8; 32]).await.expect("insert"); manager.checkpoint().await.expect("checkpoint"); } // Reopen and verify { let store = Arc::new(HybridStore::open(&path).expect("open store")); let manager = MerkleTreeManager::load_or_create(store).await.expect("create"); assert_eq!(manager.len().await, 3); let leaves = manager.leaves().await; assert_eq!(leaves.len(), 3); assert_eq!(leaves[0], [1u8; 32]); assert_eq!(leaves[1], [2u8; 32]); assert_eq!(leaves[2], [3u8; 32]); } } #[tokio::test] async fn test_rebuild_from_hashes() { let store = create_test_store().await; let manager = MerkleTreeManager::load_or_create(store).await.expect("create"); let hashes = vec![[1u8; 32], [2u8; 32], [3u8; 32]]; manager.rebuild_from_hashes(hashes).await.expect("rebuild"); assert_eq!(manager.len().await, 3); } }