calimero_node_primitives/sync/state_machine.rs
1//! Shared sync state abstractions for protocol negotiation.
2//!
3//! Provides types and functions shared between production code (`SyncManager`)
4//! and simulation (`SimNode`) for building handshakes and protocol selection.
5//!
6//! # Design
7//!
8//! - `LocalSyncState` trait abstracts access to node state
9//! - `build_handshake()` creates handshakes from any implementor
10//! - Estimation functions provide consistent heuristics for both environments
11//!
12//! # Usage
13//!
14//! ```rust,ignore
15//! // Any type can provide its local state
16//! impl LocalSyncState for MyNode {
17//! fn root_hash(&self) -> [u8; 32] { ... }
18//! fn entity_count(&self) -> u64 { ... }
19//! fn max_depth(&self) -> u32 { ... }
20//! fn dag_heads(&self) -> Vec<[u8; 32]> { ... }
21//! }
22//!
23//! // Build handshake using shared logic
24//! let handshake = build_handshake(&my_node);
25//!
26//! // Use select_protocol() for negotiation
27//! let selection = select_protocol(&local_hs, &remote_hs);
28//! ```
29
30use super::handshake::SyncHandshake;
31
32// =============================================================================
33// Local State Trait
34// =============================================================================
35
36/// Trait for accessing local sync state.
37///
38/// Implement this trait to enable building handshakes and protocol selection.
39/// Both `SyncManager` (production) and `SimNode` (simulation) implement this,
40/// ensuring consistent behavior across both environments.
41pub trait LocalSyncState {
42 /// Current Merkle root hash.
43 ///
44 /// Returns `[0; 32]` for fresh nodes with no state.
45 fn root_hash(&self) -> [u8; 32];
46
47 /// Number of entities in local storage.
48 ///
49 /// Used for divergence calculation in protocol selection.
50 fn entity_count(&self) -> u64;
51
52 /// Maximum depth of the Merkle tree.
53 ///
54 /// Used to select optimal sync protocol:
55 /// - Depth > 3 with low divergence → SubtreePrefetch
56 /// - Depth 1-2 with many children → LevelWise
57 fn max_depth(&self) -> u32;
58
59 /// Current DAG heads (latest delta IDs).
60 ///
61 /// Used for delta sync compatibility checking.
62 fn dag_heads(&self) -> Vec<[u8; 32]>;
63
64 /// Whether this node has any state.
65 ///
66 /// Default implementation: `root_hash != [0; 32]`
67 fn has_state(&self) -> bool {
68 self.root_hash() != [0; 32]
69 }
70}
71
72// =============================================================================
73// Handshake Builder
74// =============================================================================
75
76/// Build a `SyncHandshake` from any type implementing `LocalSyncState`.
77///
78/// This is the canonical way to create handshakes, ensuring consistent
79/// behavior between production and simulation code.
80///
81/// # Example
82///
83/// ```rust,ignore
84/// let handshake = build_handshake(&my_node);
85/// ```
86#[must_use]
87pub fn build_handshake<T: LocalSyncState>(state: &T) -> SyncHandshake {
88 SyncHandshake::new(
89 state.root_hash(),
90 state.entity_count(),
91 state.max_depth(),
92 state.dag_heads(),
93 )
94}
95
96/// Build a `SyncHandshake` from raw state values.
97///
98/// Useful when you have the values but don't have a `LocalSyncState` implementor.
99#[must_use]
100pub fn build_handshake_from_raw(
101 root_hash: [u8; 32],
102 entity_count: u64,
103 max_depth: u32,
104 dag_heads: Vec<[u8; 32]>,
105) -> SyncHandshake {
106 SyncHandshake::new(root_hash, entity_count, max_depth, dag_heads)
107}
108
109// =============================================================================
110// Estimation Helpers
111// =============================================================================
112
113/// Estimate entity count from available data.
114///
115/// This provides a consistent heuristic used when exact entity count is unknown.
116/// The estimation is based on:
117/// - Zero if root hash is zero (fresh node)
118/// - dag_heads.len() if available, minimum 1 if has state
119///
120/// # Arguments
121///
122/// * `root_hash` - The Merkle root hash
123/// * `dag_heads_len` - Number of DAG heads
124#[must_use]
125pub fn estimate_entity_count(root_hash: [u8; 32], dag_heads_len: usize) -> u64 {
126 if root_hash == [0; 32] {
127 0
128 } else if dag_heads_len == 0 {
129 1 // Has state but no heads - assume at least 1 entity
130 } else {
131 dag_heads_len as u64
132 }
133}
134
135/// Estimate max depth from entity count.
136///
137/// Uses log2 approximation for balanced tree estimation:
138/// `max_depth ≈ ceil(log2(entity_count))`
139///
140/// This is bounded to a reasonable maximum to prevent overflow.
141///
142/// # Arguments
143///
144/// * `entity_count` - Number of entities in the tree
145#[must_use]
146pub fn estimate_max_depth(entity_count: u64) -> u32 {
147 if entity_count == 0 {
148 0
149 } else {
150 // log2(n) ≈ 64 - leading_zeros(n)
151 // For a balanced tree, depth is roughly log2(entity_count)
152 let log2_approx = 64u32.saturating_sub(entity_count.leading_zeros());
153 log2_approx.max(1).min(32)
154 }
155}
156
157// =============================================================================
158// Tests
159// =============================================================================
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 // Test implementation of LocalSyncState
166 struct TestNode {
167 root_hash: [u8; 32],
168 entity_count: u64,
169 max_depth: u32,
170 dag_heads: Vec<[u8; 32]>,
171 }
172
173 impl LocalSyncState for TestNode {
174 fn root_hash(&self) -> [u8; 32] {
175 self.root_hash
176 }
177
178 fn entity_count(&self) -> u64 {
179 self.entity_count
180 }
181
182 fn max_depth(&self) -> u32 {
183 self.max_depth
184 }
185
186 fn dag_heads(&self) -> Vec<[u8; 32]> {
187 self.dag_heads.clone()
188 }
189 }
190
191 #[test]
192 fn test_build_handshake_fresh_node() {
193 let node = TestNode {
194 root_hash: [0; 32],
195 entity_count: 0,
196 max_depth: 0,
197 dag_heads: vec![],
198 };
199
200 let hs = build_handshake(&node);
201
202 assert_eq!(hs.root_hash, [0; 32]);
203 assert_eq!(hs.entity_count, 0);
204 assert_eq!(hs.max_depth, 0);
205 assert!(hs.dag_heads.is_empty());
206 assert!(!hs.has_state);
207 }
208
209 #[test]
210 fn test_build_handshake_initialized_node() {
211 let node = TestNode {
212 root_hash: [42; 32],
213 entity_count: 100,
214 max_depth: 5,
215 dag_heads: vec![[1; 32], [2; 32]],
216 };
217
218 let hs = build_handshake(&node);
219
220 assert_eq!(hs.root_hash, [42; 32]);
221 assert_eq!(hs.entity_count, 100);
222 assert_eq!(hs.max_depth, 5);
223 assert_eq!(hs.dag_heads.len(), 2);
224 assert!(hs.has_state);
225 }
226
227 #[test]
228 fn test_estimate_entity_count() {
229 // Fresh node
230 assert_eq!(estimate_entity_count([0; 32], 0), 0);
231
232 // Has state, no heads
233 assert_eq!(estimate_entity_count([1; 32], 0), 1);
234
235 // Has state with heads
236 assert_eq!(estimate_entity_count([1; 32], 5), 5);
237 }
238
239 #[test]
240 fn test_estimate_max_depth() {
241 assert_eq!(estimate_max_depth(0), 0);
242 assert_eq!(estimate_max_depth(1), 1);
243 assert_eq!(estimate_max_depth(2), 2);
244 assert_eq!(estimate_max_depth(16), 5); // log2(16) = 4, +1 = 5
245 assert_eq!(estimate_max_depth(256), 9); // log2(256) = 8, +1 = 9
246 }
247
248 #[test]
249 fn test_has_state_default_implementation() {
250 let fresh = TestNode {
251 root_hash: [0; 32],
252 entity_count: 0,
253 max_depth: 0,
254 dag_heads: vec![],
255 };
256 assert!(!fresh.has_state());
257
258 let initialized = TestNode {
259 root_hash: [1; 32],
260 entity_count: 1,
261 max_depth: 1,
262 dag_heads: vec![],
263 };
264 assert!(initialized.has_state());
265 }
266}