//! gRPC server implementation for the sync service. //! //! This module provides the server-side handlers for sync operations. //! The actual storage and sync logic is injected via traits to allow //! flexible deployment configurations. //! //! # Example //! //! ```ignore //! use stemedb_rpc::server::{SyncServiceHandler, SyncStorage}; //! use tonic::transport::Server; //! //! let storage = MyStorage::new(...); //! let handler = SyncServiceHandler::new(storage); //! //! Server::builder() //! .add_service(SyncServiceServer::new(handler)) //! .serve(addr) //! .await?; //! ``` use crate::proto::sync_service_server::SyncService; use crate::proto::{ AssertionData, FetchRequest, FetchResponse, GetLeavesRequest, GetLeavesResponse, GossipRequest, GossipResponse, PingRequest, PingResponse, RootExchangeRequest, RootExchangeResponse, }; use async_trait::async_trait; use std::sync::Arc; use tonic::{Request, Response, Status}; use tracing::{debug, info, instrument, warn}; /// Backend storage interface for sync operations. /// /// Implement this trait to connect the sync service to your storage layer. #[async_trait] pub trait SyncStorage: Send + Sync + 'static { /// Store an assertion received via gossip. /// /// Returns Ok(true) if stored, Ok(false) if already existed. async fn store_gossip_assertion( &self, hash: [u8; 32], data: Vec, hlc_time: u64, hlc_counter: u32, hlc_node_id: [u8; 16], ) -> Result; /// Get the current Merkle root and assertion count. async fn get_merkle_state(&self) -> Result<(Option<[u8; 32]>, u64), String>; /// Fetch assertions by hash. /// /// Returns (hash, data) pairs for assertions that exist. async fn fetch_assertions( &self, hashes: Vec<[u8; 32]>, ) -> Result)>, String>; /// Get this node's ID and assertion count for ping response. async fn get_node_info(&self) -> Result<([u8; 16], u64), String>; /// Get all Merkle tree leaf hashes. /// /// Returns up to `max_leaves` hashes (0 = no limit, capped at 10000). async fn get_leaves(&self, max_leaves: u64) -> Result<(Vec<[u8; 32]>, bool), String>; } /// gRPC service handler for sync operations. pub struct SyncServiceHandler { storage: Arc, } impl SyncServiceHandler { /// Create a new sync service handler with the given storage backend. pub fn new(storage: Arc) -> Self { Self { storage } } } #[async_trait] impl SyncService for SyncServiceHandler { #[instrument(skip(self, request), fields(hash_len = request.get_ref().assertion_hash.len()))] async fn gossip( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); // Validate hash length if req.assertion_hash.len() != 32 { return Err(Status::invalid_argument(format!( "assertion_hash must be 32 bytes, got {}", req.assertion_hash.len() ))); } // Validate HLC node ID length if req.hlc_node_id.len() != 16 { return Err(Status::invalid_argument(format!( "hlc_node_id must be 16 bytes, got {}", req.hlc_node_id.len() ))); } let mut hash = [0u8; 32]; hash.copy_from_slice(&req.assertion_hash); let mut hlc_node_id = [0u8; 16]; hlc_node_id.copy_from_slice(&req.hlc_node_id); debug!(hash = %hex::encode(&hash[..8]), "Received gossip"); match self .storage .store_gossip_assertion( hash, req.assertion_data, req.hlc_time, req.hlc_counter, hlc_node_id, ) .await { Ok(stored) => { if stored { info!(hash = %hex::encode(&hash[..8]), "Stored gossiped assertion"); } else { debug!(hash = %hex::encode(&hash[..8]), "Duplicate gossip (already stored)"); } Ok(Response::new(GossipResponse { accepted: true, error: String::new() })) } Err(e) => { warn!(error = %e, "Failed to store gossiped assertion"); Ok(Response::new(GossipResponse { accepted: false, error: e })) } } } #[instrument(skip(self, request), fields(assertion_count = request.get_ref().assertion_count))] async fn exchange_roots( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); // Validate root length if provided if !req.merkle_root.is_empty() && req.merkle_root.len() != 32 { return Err(Status::invalid_argument(format!( "merkle_root must be 32 bytes if provided, got {}", req.merkle_root.len() ))); } let (local_root, local_count) = self.storage.get_merkle_state().await.map_err(Status::internal)?; let remote_root: Option<[u8; 32]> = if req.merkle_root.len() == 32 { let mut root = [0u8; 32]; root.copy_from_slice(&req.merkle_root); Some(root) } else { None }; let roots_match = match (&local_root, &remote_root) { (Some(local), Some(remote)) => local == remote, (None, None) => true, _ => false, }; debug!( local_count, remote_count = req.assertion_count, roots_match, "Exchanged Merkle roots" ); Ok(Response::new(RootExchangeResponse { merkle_root: local_root.map(|r| r.to_vec()).unwrap_or_default(), assertion_count: local_count, roots_match, })) } #[instrument(skip(self, request), fields(hash_count = request.get_ref().hashes.len()))] async fn fetch_assertions( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); // Limit request size to prevent abuse const MAX_HASHES: usize = 1000; if req.hashes.len() > MAX_HASHES { return Err(Status::invalid_argument(format!( "Too many hashes requested: {} > {}", req.hashes.len(), MAX_HASHES ))); } // Convert and validate hashes let mut hashes = Vec::with_capacity(req.hashes.len()); for (i, hash_bytes) in req.hashes.iter().enumerate() { if hash_bytes.len() != 32 { return Err(Status::invalid_argument(format!( "hash[{}] must be 32 bytes, got {}", i, hash_bytes.len() ))); } let mut hash = [0u8; 32]; hash.copy_from_slice(hash_bytes); hashes.push(hash); } let results = self.storage.fetch_assertions(hashes).await.map_err(Status::internal)?; debug!(requested = req.hashes.len(), found = results.len(), "Fetched assertions"); let assertions = results .into_iter() .map(|(hash, data)| AssertionData { hash: hash.to_vec(), data }) .collect(); Ok(Response::new(FetchResponse { assertions })) } #[instrument(skip(self, _request))] async fn ping(&self, _request: Request) -> Result, Status> { let (node_id, assertion_count) = self.storage.get_node_info().await.map_err(Status::internal)?; debug!(assertion_count, "Responding to ping"); Ok(Response::new(PingResponse { node_id: node_id.to_vec(), assertion_count })) } #[instrument(skip(self, request), fields(max_leaves = request.get_ref().max_leaves))] async fn get_leaves( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); let (leaves, truncated) = self.storage.get_leaves(req.max_leaves).await.map_err(Status::internal)?; debug!(leaf_count = leaves.len(), truncated, "Returning Merkle leaves"); Ok(Response::new(GetLeavesResponse { leaves: leaves.into_iter().map(|l| l.to_vec()).collect(), truncated, })) } } #[cfg(test)] mod tests { use super::*; /// Mock storage for testing. struct MockStorage { node_id: [u8; 16], assertion_count: u64, } #[async_trait] impl SyncStorage for MockStorage { async fn store_gossip_assertion( &self, _hash: [u8; 32], _data: Vec, _hlc_time: u64, _hlc_counter: u32, _hlc_node_id: [u8; 16], ) -> Result { Ok(true) } async fn get_merkle_state(&self) -> Result<(Option<[u8; 32]>, u64), String> { Ok((Some([1u8; 32]), self.assertion_count)) } async fn fetch_assertions( &self, hashes: Vec<[u8; 32]>, ) -> Result)>, String> { // Return mock data for each hash Ok(hashes.into_iter().map(|h| (h, vec![1, 2, 3])).collect()) } async fn get_node_info(&self) -> Result<([u8; 16], u64), String> { Ok((self.node_id, self.assertion_count)) } async fn get_leaves(&self, max_leaves: u64) -> Result<(Vec<[u8; 32]>, bool), String> { let all_leaves = vec![[1u8; 32], [2u8; 32], [3u8; 32]]; if max_leaves > 0 && (max_leaves as usize) < all_leaves.len() { Ok((all_leaves.into_iter().take(max_leaves as usize).collect(), true)) } else { Ok((all_leaves, false)) } } } #[tokio::test] async fn test_ping() { let storage = Arc::new(MockStorage { node_id: [42u8; 16], assertion_count: 100 }); let handler = SyncServiceHandler::new(storage); let request = Request::new(PingRequest { node_id: vec![1u8; 16] }); let response = handler.ping(request).await.expect("ping should succeed"); assert_eq!(response.get_ref().node_id, vec![42u8; 16]); assert_eq!(response.get_ref().assertion_count, 100); } #[tokio::test] async fn test_gossip_invalid_hash_length() { let storage = Arc::new(MockStorage { node_id: [1u8; 16], assertion_count: 0 }); let handler = SyncServiceHandler::new(storage); let request = Request::new(GossipRequest { assertion_hash: vec![1u8; 16], // Wrong length assertion_data: vec![], hlc_time: 0, hlc_counter: 0, hlc_node_id: vec![1u8; 16], }); let result = handler.gossip(request).await; assert!(result.is_err()); assert_eq!(result.err().map(|e| e.code()), Some(tonic::Code::InvalidArgument)); } #[tokio::test] async fn test_fetch_too_many_hashes() { let storage = Arc::new(MockStorage { node_id: [1u8; 16], assertion_count: 0 }); let handler = SyncServiceHandler::new(storage); let request = Request::new(FetchRequest { hashes: vec![vec![0u8; 32]; 1001], // More than MAX_HASHES }); let result = handler.fetch_assertions(request).await; assert!(result.is_err()); assert_eq!(result.err().map(|e| e.code()), Some(tonic::Code::InvalidArgument)); } }