Skip to main content

clawft_kernel/
mesh_chain.rs

1//! Chain replication protocol for mesh networking (K6.4).
2//!
3//! Defines request/response types for incremental chain synchronization
4//! between mesh nodes. Chain events are exchanged as serialized JSON
5//! (with optional RVF segment format for efficiency).
6
7use serde::{Deserialize, Serialize};
8
9/// Request to sync chain events from a peer.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ChainSyncRequest {
12    /// Chain identifier (0 = local chain).
13    pub chain_id: u32,
14    /// Request events after this sequence number.
15    pub after_sequence: u64,
16    /// Hash at the after_sequence position (for fork detection).
17    pub after_hash: String,
18    /// Maximum number of events to return.
19    pub max_events: u32,
20}
21
22/// Response with chain events for sync.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ChainSyncResponse {
25    /// Chain identifier.
26    pub chain_id: u32,
27    /// Serialized chain events (JSON).
28    pub events: Vec<serde_json::Value>,
29    /// Whether there are more events beyond this batch.
30    pub has_more: bool,
31    /// Tip sequence number on the responding node.
32    pub tip_sequence: u64,
33    /// Tip hash on the responding node.
34    pub tip_hash: String,
35}
36
37/// Bridge event anchoring a remote chain's head in the local chain.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ChainBridgeEvent {
40    /// Remote node ID.
41    pub remote_node_id: String,
42    /// Remote chain's head sequence.
43    pub remote_head_sequence: u64,
44    /// Remote chain's head hash.
45    pub remote_head_hash: String,
46    /// Timestamp of the bridge anchor.
47    pub anchored_at: chrono::DateTime<chrono::Utc>,
48}
49
50/// Fork detection result.
51#[non_exhaustive]
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub enum ChainForkStatus {
54    /// Chains are in sync (same head).
55    InSync,
56    /// Local is behind -- need to pull events.
57    LocalBehind {
58        /// Local sequence number.
59        local_seq: u64,
60        /// Remote sequence number.
61        remote_seq: u64,
62    },
63    /// Remote is behind -- need to push events.
64    RemoteBehind {
65        /// Local sequence number.
66        local_seq: u64,
67        /// Remote sequence number.
68        remote_seq: u64,
69    },
70    /// Fork detected at the given sequence.
71    Forked {
72        /// Sequence where the fork occurred.
73        fork_point: u64,
74        /// Local hash at the fork point.
75        local_hash: String,
76        /// Remote hash at the fork point.
77        remote_hash: String,
78    },
79}
80
81/// Compare local and remote chain state to detect sync needs.
82pub fn detect_chain_fork(
83    local_seq: u64,
84    local_hash: &str,
85    remote_seq: u64,
86    remote_hash: &str,
87) -> ChainForkStatus {
88    if local_seq == remote_seq && local_hash == remote_hash {
89        ChainForkStatus::InSync
90    } else if local_seq < remote_seq {
91        ChainForkStatus::LocalBehind {
92            local_seq,
93            remote_seq,
94        }
95    } else if local_seq > remote_seq {
96        ChainForkStatus::RemoteBehind {
97            local_seq,
98            remote_seq,
99        }
100    } else {
101        // Same sequence but different hash -- fork!
102        ChainForkStatus::Forked {
103            fork_point: local_seq,
104            local_hash: local_hash.to_string(),
105            remote_hash: remote_hash.to_string(),
106        }
107    }
108}
109
110/// Compact state digest for sync stream initialization.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct SyncStateDigest {
113    /// Chain: highest sequence number.
114    pub chain_seq: u64,
115    /// Chain: head hash.
116    pub chain_hash: String,
117    /// Tree: Merkle root hash.
118    pub tree_root_hash: String,
119    /// Number of governance rules.
120    pub governance_rule_count: u32,
121    /// Node uptime in seconds.
122    pub uptime_secs: u64,
123}
124
125// ── Backpressure: chain checkpoint catch-up ──────────────────────
126
127/// Threshold for switching from event-by-event to checkpoint sync.
128pub const CHECKPOINT_CATCHUP_THRESHOLD: u64 = 1000;
129
130/// Determine sync strategy based on how far behind the peer is.
131pub fn sync_strategy(local_seq: u64, remote_seq: u64) -> ChainSyncStrategy {
132    let behind = local_seq.saturating_sub(remote_seq);
133    if behind == 0 {
134        ChainSyncStrategy::InSync
135    } else if behind <= CHECKPOINT_CATCHUP_THRESHOLD {
136        ChainSyncStrategy::EventByEvent { count: behind }
137    } else {
138        ChainSyncStrategy::CheckpointCatchup {
139            behind,
140            checkpoint_seq: remote_seq,
141        }
142    }
143}
144
145/// Strategy for catching up a behind peer.
146#[non_exhaustive]
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum ChainSyncStrategy {
149    /// Peer is fully in sync.
150    InSync,
151    /// Peer is slightly behind; send events one by one.
152    EventByEvent { count: u64 },
153    /// Peer is far behind; send a checkpoint snapshot then replay.
154    CheckpointCatchup { behind: u64, checkpoint_seq: u64 },
155}
156
157// ── Sync frames with RVF wire segments ──────────────────────────
158
159/// Sync frame header (wraps RVF segment payloads).
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct SyncFrame {
162    /// Which sync stream this frame belongs to.
163    pub stream_type: SyncStreamType,
164    /// Frame sequence number within the stream.
165    pub frame_seq: u64,
166    /// Payload type discriminator.
167    pub payload_type: SyncPayloadType,
168}
169
170/// Discriminator for QUIC sync streams.
171#[non_exhaustive]
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
173#[repr(u8)]
174pub enum SyncStreamType {
175    Control = 0,
176    Chain = 1,
177    Tree = 2,
178    Causal = 3,
179    Hnsw = 4,
180    CrossRef = 5,
181    Impulse = 6,
182    Ipc = 7,
183}
184
185/// Payload type within a sync frame.
186#[non_exhaustive]
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
188#[repr(u8)]
189pub enum SyncPayloadType {
190    StateDigest = 0x00,
191    ChainEvents = 0x01,
192    TreeDiff = 0x02,
193    CausalDelta = 0x03,
194    HnswBatch = 0x04,
195    CrossRefBatch = 0x05,
196    ImpulseFlood = 0x06,
197    Ack = 0x0F,
198}
199
200/// QUIC stream priority per SyncStreamType (lower = higher priority).
201pub fn stream_priority(stream_type: SyncStreamType) -> u8 {
202    match stream_type {
203        SyncStreamType::Control => 0, // highest
204        SyncStreamType::Chain => 1,
205        SyncStreamType::Tree => 2,
206        SyncStreamType::Ipc => 3,
207        SyncStreamType::Causal => 4,
208        SyncStreamType::CrossRef => 4,
209        SyncStreamType::Hnsw => 5,
210        SyncStreamType::Impulse => 6, // lowest
211    }
212}
213
214// ── Tests ────────────────────────────────────────────────────────
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn chain_sync_request_serde_roundtrip() {
222        let req = ChainSyncRequest {
223            chain_id: 1,
224            after_sequence: 42,
225            after_hash: "abc123".into(),
226            max_events: 100,
227        };
228        let json = serde_json::to_string(&req).unwrap();
229        let decoded: ChainSyncRequest = serde_json::from_str(&json).unwrap();
230        assert_eq!(decoded.chain_id, 1);
231        assert_eq!(decoded.after_sequence, 42);
232        assert_eq!(decoded.after_hash, "abc123");
233        assert_eq!(decoded.max_events, 100);
234    }
235
236    #[test]
237    fn chain_sync_response_serde_roundtrip() {
238        let resp = ChainSyncResponse {
239            chain_id: 0,
240            events: vec![serde_json::json!({"type": "write", "key": "a"})],
241            has_more: true,
242            tip_sequence: 99,
243            tip_hash: "tip_hash_val".into(),
244        };
245        let json = serde_json::to_string(&resp).unwrap();
246        let decoded: ChainSyncResponse = serde_json::from_str(&json).unwrap();
247        assert_eq!(decoded.chain_id, 0);
248        assert_eq!(decoded.events.len(), 1);
249        assert!(decoded.has_more);
250        assert_eq!(decoded.tip_sequence, 99);
251        assert_eq!(decoded.tip_hash, "tip_hash_val");
252    }
253
254    #[test]
255    fn chain_bridge_event_serde_roundtrip() {
256        let evt = ChainBridgeEvent {
257            remote_node_id: "node-42".into(),
258            remote_head_sequence: 10,
259            remote_head_hash: "deadbeef".into(),
260            anchored_at: chrono::Utc::now(),
261        };
262        let json = serde_json::to_string(&evt).unwrap();
263        let decoded: ChainBridgeEvent = serde_json::from_str(&json).unwrap();
264        assert_eq!(decoded.remote_node_id, "node-42");
265        assert_eq!(decoded.remote_head_sequence, 10);
266        assert_eq!(decoded.remote_head_hash, "deadbeef");
267    }
268
269    #[test]
270    fn detect_chain_fork_in_sync() {
271        let status = detect_chain_fork(5, "hash_a", 5, "hash_a");
272        assert_eq!(status, ChainForkStatus::InSync);
273    }
274
275    #[test]
276    fn detect_chain_fork_local_behind() {
277        let status = detect_chain_fork(3, "hash_a", 7, "hash_b");
278        assert_eq!(
279            status,
280            ChainForkStatus::LocalBehind {
281                local_seq: 3,
282                remote_seq: 7,
283            }
284        );
285    }
286
287    #[test]
288    fn detect_chain_fork_remote_behind() {
289        let status = detect_chain_fork(10, "hash_a", 5, "hash_b");
290        assert_eq!(
291            status,
292            ChainForkStatus::RemoteBehind {
293                local_seq: 10,
294                remote_seq: 5,
295            }
296        );
297    }
298
299    #[test]
300    fn detect_chain_fork_forked() {
301        let status = detect_chain_fork(8, "hash_local", 8, "hash_remote");
302        assert_eq!(
303            status,
304            ChainForkStatus::Forked {
305                fork_point: 8,
306                local_hash: "hash_local".into(),
307                remote_hash: "hash_remote".into(),
308            }
309        );
310    }
311
312    // ── Backpressure sync strategy tests ────────────────────────────
313
314    #[test]
315    fn sync_strategy_in_sync() {
316        assert_eq!(sync_strategy(100, 100), ChainSyncStrategy::InSync);
317    }
318
319    #[test]
320    fn sync_strategy_event_by_event() {
321        let strategy = sync_strategy(1100, 500);
322        assert_eq!(strategy, ChainSyncStrategy::EventByEvent { count: 600 });
323    }
324
325    #[test]
326    fn sync_strategy_checkpoint_catchup() {
327        let strategy = sync_strategy(5000, 100);
328        assert_eq!(
329            strategy,
330            ChainSyncStrategy::CheckpointCatchup {
331                behind: 4900,
332                checkpoint_seq: 100,
333            }
334        );
335    }
336
337    #[test]
338    fn sync_strategy_boundary_at_threshold() {
339        // Exactly at threshold: event-by-event
340        let strategy = sync_strategy(1000, 0);
341        assert_eq!(strategy, ChainSyncStrategy::EventByEvent { count: 1000 });
342        // One past threshold: checkpoint
343        let strategy = sync_strategy(1001, 0);
344        assert_eq!(
345            strategy,
346            ChainSyncStrategy::CheckpointCatchup {
347                behind: 1001,
348                checkpoint_seq: 0,
349            }
350        );
351    }
352
353    #[test]
354    fn sync_strategy_remote_ahead() {
355        // remote_seq > local_seq: saturating_sub yields 0 = InSync
356        assert_eq!(sync_strategy(50, 100), ChainSyncStrategy::InSync);
357    }
358
359    // ── SyncFrame / SyncStreamType serde tests ───────────────────────
360
361    #[test]
362    fn sync_frame_serde_roundtrip() {
363        let frame = SyncFrame {
364            stream_type: SyncStreamType::Chain,
365            frame_seq: 42,
366            payload_type: SyncPayloadType::ChainEvents,
367        };
368        let json = serde_json::to_string(&frame).unwrap();
369        let restored: SyncFrame = serde_json::from_str(&json).unwrap();
370        assert_eq!(restored.stream_type, SyncStreamType::Chain);
371        assert_eq!(restored.frame_seq, 42);
372        assert_eq!(restored.payload_type, SyncPayloadType::ChainEvents);
373    }
374
375    #[test]
376    fn sync_stream_type_serde_roundtrip() {
377        let types = [
378            SyncStreamType::Control,
379            SyncStreamType::Chain,
380            SyncStreamType::Tree,
381            SyncStreamType::Causal,
382            SyncStreamType::Hnsw,
383            SyncStreamType::CrossRef,
384            SyncStreamType::Impulse,
385            SyncStreamType::Ipc,
386        ];
387        for t in types {
388            let json = serde_json::to_string(&t).unwrap();
389            let restored: SyncStreamType = serde_json::from_str(&json).unwrap();
390            assert_eq!(restored, t);
391        }
392    }
393
394    #[test]
395    fn stream_priority_ordering() {
396        // Control has highest priority (lowest number)
397        assert!(stream_priority(SyncStreamType::Control) < stream_priority(SyncStreamType::Chain));
398        assert!(stream_priority(SyncStreamType::Chain) < stream_priority(SyncStreamType::Tree));
399        assert!(stream_priority(SyncStreamType::Tree) < stream_priority(SyncStreamType::Ipc));
400        assert!(stream_priority(SyncStreamType::Ipc) < stream_priority(SyncStreamType::Hnsw));
401        assert!(stream_priority(SyncStreamType::Hnsw) < stream_priority(SyncStreamType::Impulse));
402        // Causal and CrossRef share priority
403        assert_eq!(
404            stream_priority(SyncStreamType::Causal),
405            stream_priority(SyncStreamType::CrossRef)
406        );
407    }
408
409    #[test]
410    fn sync_state_digest_serde_roundtrip() {
411        let digest = SyncStateDigest {
412            chain_seq: 100,
413            chain_hash: "head_hash".into(),
414            tree_root_hash: "merkle_root".into(),
415            governance_rule_count: 5,
416            uptime_secs: 3600,
417        };
418        let json = serde_json::to_string(&digest).unwrap();
419        let decoded: SyncStateDigest = serde_json::from_str(&json).unwrap();
420        assert_eq!(decoded.chain_seq, 100);
421        assert_eq!(decoded.chain_hash, "head_hash");
422        assert_eq!(decoded.tree_root_hash, "merkle_root");
423        assert_eq!(decoded.governance_rule_count, 5);
424        assert_eq!(decoded.uptime_secs, 3600);
425    }
426}