Skip to main content

calimero_node_primitives/sync/
handshake.rs

1//! Sync handshake protocol types (CIP §2 - Sync Handshake Protocol).
2//!
3//! Types for initial sync negotiation between peers.
4
5use borsh::{BorshDeserialize, BorshSerialize};
6
7use super::protocol::{SyncProtocol, SyncProtocolKind};
8
9// =============================================================================
10// Constants
11// =============================================================================
12
13/// Wire protocol version for sync handshake.
14///
15/// Increment on breaking changes to ensure nodes can detect incompatibility.
16pub const SYNC_PROTOCOL_VERSION: u32 = 1;
17
18// =============================================================================
19// Capabilities
20// =============================================================================
21
22/// Capabilities advertised during sync negotiation.
23///
24/// Used to determine mutually supported features between peers.
25#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
26pub struct SyncCapabilities {
27    /// Whether compression is supported.
28    pub supports_compression: bool,
29    /// Maximum entities per batch transfer.
30    pub max_batch_size: u64,
31    /// Protocols this node supports (ordered by preference).
32    pub supported_protocols: Vec<SyncProtocolKind>,
33}
34
35impl Default for SyncCapabilities {
36    fn default() -> Self {
37        Self {
38            supports_compression: true,
39            max_batch_size: 1000,
40            supported_protocols: vec![
41                SyncProtocolKind::None,
42                SyncProtocolKind::DeltaSync,
43                SyncProtocolKind::HashComparison,
44                SyncProtocolKind::Snapshot,
45                SyncProtocolKind::LevelWise,
46            ],
47        }
48    }
49}
50
51// =============================================================================
52// Handshake Messages
53// =============================================================================
54
55/// Sync handshake message (Initiator → Responder).
56///
57/// Contains the initiator's state summary for protocol negotiation.
58///
59/// See CIP §2.1 - Handshake Message.
60#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
61pub struct SyncHandshake {
62    /// Protocol version for compatibility checking.
63    pub version: u32,
64    /// Current Merkle root hash.
65    pub root_hash: [u8; 32],
66    /// Number of entities in the tree.
67    pub entity_count: u64,
68    /// Maximum depth of the Merkle tree.
69    pub max_depth: u32,
70    /// Current DAG heads (latest delta IDs).
71    pub dag_heads: Vec<[u8; 32]>,
72    /// Whether this node has any state.
73    pub has_state: bool,
74    /// Supported protocols (ordered by preference).
75    pub supported_protocols: Vec<SyncProtocolKind>,
76}
77
78impl SyncHandshake {
79    /// Create a new handshake message from local state.
80    #[must_use]
81    pub fn new(
82        root_hash: [u8; 32],
83        entity_count: u64,
84        max_depth: u32,
85        dag_heads: Vec<[u8; 32]>,
86    ) -> Self {
87        let has_state = root_hash != [0; 32];
88        Self {
89            version: SYNC_PROTOCOL_VERSION,
90            root_hash,
91            entity_count,
92            max_depth,
93            dag_heads,
94            has_state,
95            supported_protocols: SyncCapabilities::default().supported_protocols,
96        }
97    }
98
99    /// Check if the remote handshake has a compatible protocol version.
100    #[must_use]
101    pub fn is_version_compatible(&self, other: &Self) -> bool {
102        self.version == other.version
103    }
104
105    /// Check if root hashes match (already in sync).
106    #[must_use]
107    pub fn is_in_sync(&self, other: &Self) -> bool {
108        self.root_hash == other.root_hash
109    }
110}
111
112impl Default for SyncHandshake {
113    fn default() -> Self {
114        Self::new([0; 32], 0, 0, vec![])
115    }
116}
117
118/// Sync handshake response (Responder → Initiator).
119///
120/// Contains the selected protocol and responder's state summary.
121///
122/// See CIP §2.2 - Negotiation Flow.
123#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
124pub struct SyncHandshakeResponse {
125    /// Protocol selected for this sync session.
126    pub selected_protocol: SyncProtocol,
127    /// Responder's current root hash.
128    pub root_hash: [u8; 32],
129    /// Responder's entity count.
130    pub entity_count: u64,
131    /// Responder's capabilities.
132    pub capabilities: SyncCapabilities,
133}
134
135impl SyncHandshakeResponse {
136    /// Create a response indicating no sync is needed.
137    #[must_use]
138    pub fn already_synced(root_hash: [u8; 32], entity_count: u64) -> Self {
139        Self {
140            selected_protocol: SyncProtocol::None,
141            root_hash,
142            entity_count,
143            capabilities: SyncCapabilities::default(),
144        }
145    }
146
147    /// Create a response with a selected protocol.
148    #[must_use]
149    pub fn with_protocol(
150        selected_protocol: SyncProtocol,
151        root_hash: [u8; 32],
152        entity_count: u64,
153    ) -> Self {
154        Self {
155            selected_protocol,
156            root_hash,
157            entity_count,
158            capabilities: SyncCapabilities::default(),
159        }
160    }
161}
162
163// =============================================================================
164// Tests
165// =============================================================================
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_sync_capabilities_roundtrip() {
173        let caps = SyncCapabilities::default();
174        let encoded = borsh::to_vec(&caps).expect("serialize");
175        let decoded: SyncCapabilities = borsh::from_slice(&encoded).expect("deserialize");
176        assert_eq!(caps, decoded);
177    }
178
179    #[test]
180    fn test_sync_handshake_roundtrip() {
181        let handshake = SyncHandshake::new([42; 32], 100, 5, vec![[1; 32], [2; 32]]);
182
183        let encoded = borsh::to_vec(&handshake).expect("serialize");
184        let decoded: SyncHandshake = borsh::from_slice(&encoded).expect("deserialize");
185
186        assert_eq!(handshake, decoded);
187        assert_eq!(decoded.version, SYNC_PROTOCOL_VERSION);
188        assert!(decoded.has_state); // non-zero root_hash
189    }
190
191    #[test]
192    fn test_sync_handshake_response_roundtrip() {
193        let response = SyncHandshakeResponse::with_protocol(
194            SyncProtocol::HashComparison {
195                root_hash: [7; 32],
196                divergent_subtrees: vec![],
197            },
198            [8; 32],
199            500,
200        );
201
202        let encoded = borsh::to_vec(&response).expect("serialize");
203        let decoded: SyncHandshakeResponse = borsh::from_slice(&encoded).expect("deserialize");
204
205        assert_eq!(response, decoded);
206    }
207
208    #[test]
209    fn test_sync_handshake_version_compatibility() {
210        let local = SyncHandshake::new([1; 32], 10, 2, vec![]);
211        let compatible = SyncHandshake::new([2; 32], 20, 3, vec![]);
212        let incompatible = SyncHandshake {
213            version: SYNC_PROTOCOL_VERSION + 1,
214            ..SyncHandshake::default()
215        };
216
217        assert!(local.is_version_compatible(&compatible));
218        assert!(!local.is_version_compatible(&incompatible));
219    }
220
221    #[test]
222    fn test_sync_handshake_in_sync_detection() {
223        let local = SyncHandshake::new([42; 32], 100, 5, vec![]);
224        let same_hash = SyncHandshake::new([42; 32], 200, 6, vec![[1; 32]]);
225        let different_hash = SyncHandshake::new([99; 32], 100, 5, vec![]);
226
227        assert!(local.is_in_sync(&same_hash));
228        assert!(!local.is_in_sync(&different_hash));
229    }
230
231    #[test]
232    fn test_sync_handshake_fresh_node() {
233        let fresh = SyncHandshake::new([0; 32], 0, 0, vec![]);
234        assert!(!fresh.has_state);
235
236        let initialized = SyncHandshake::new([1; 32], 1, 1, vec![]);
237        assert!(initialized.has_state);
238    }
239
240    #[test]
241    fn test_sync_handshake_response_already_synced() {
242        let response = SyncHandshakeResponse::already_synced([42; 32], 100);
243        assert_eq!(response.selected_protocol, SyncProtocol::None);
244    }
245}