Просмотр исходного кода

event_graph: Check timestamps for recent RLN roots

x 1 месяц назад
Родитель
Сommit
5fcdbdc8cf
3 измененных файлов с 77 добавлено и 18 удалено
  1. 13 16
      src/event_graph/mod.rs
  2. 9 2
      src/event_graph/rln.rs
  3. 55 0
      src/event_graph/tests_rln.rs

+ 13 - 16
src/event_graph/mod.rs

@@ -2319,11 +2319,9 @@ impl EventGraph {
     /// of an event that produced `root` and ends just before the next
     /// event timestamp (or `u64::MAX` if `root` is currently live).
     ///
-    /// This subsumes the old `recent_roots` window as a special case
-    /// (live verification = signal_timestamp ~= now). The in-memory
-    /// `recent_roots` cache in `IdentityState` remains as a fast
-    /// path for the live-broadcast hot loop; this method is consulted
-    /// when the cache misses.
+    /// The verifier calls this for every non-current root. Current-root
+    /// verification stays on the in-memory fast path, while recent but
+    /// non-current roots still pass through this timestamp-window check.
     pub fn is_root_valid_at(&self, root: &pallas::Base, signal_timestamp: u64) -> Result<bool> {
         let drift = EVENT_TIME_DRIFT;
         let lo = signal_timestamp.saturating_sub(drift);
@@ -2588,19 +2586,18 @@ impl EventGraph {
         // 1) The merkle root must be valid for a signal at this
         //    timestamp. We accept any root that was the live SMT
         //    root at any time within EVENT_TIME_DRIFT of the signal's
-        //    timestamp. This subsumes the old "recent_roots window"
-        //    behaviour as a special case (live verification, signal
-        //    timestamp ~= now) AND supports sync of historical signals
-        //    (signal timestamp = signing time, root corresponds to
-        //    that historical state).
+        //    timestamp. This supports both live propagation races and
+        //    sync of historical signals (signal timestamp = signing
+        //    time, root corresponds to that historical state).
         //
-        //    Hot-path optimization: check the in-memory recent_roots
-        //    cache first. For live broadcasts (the overwhelming
-        //    majority of verifications) the root will be in the
-        //    cache and we skip the sled lookup.
+        //    Hot-path optimization: accept the current in-memory root
+        //    without touching the historical index. Non-current roots,
+        //    even if still present in the recent-roots cache, must pass
+        //    the timestamp-window check below so old pre-slash roots
+        //    cannot stay valid indefinitely.
         {
             let id_state = self.identity_state.read().await;
-            if !id_state.is_known_root(&rcvd.merkle_root) {
+            if !id_state.is_current_root(&rcvd.merkle_root) {
                 drop(id_state);
                 match self.is_root_valid_at(&rcvd.merkle_root, event.header.timestamp) {
                     Ok(true) => {}
@@ -2827,7 +2824,7 @@ impl EventGraph {
                 // slash timestamp.
                 {
                     let id_state = self.identity_state.read().await;
-                    if !id_state.is_known_root(&sl.merkle_root) {
+                    if !id_state.is_current_root(&sl.merkle_root) {
                         drop(id_state);
                         match self.is_root_valid_at(&sl.merkle_root, event_timestamp) {
                             Ok(true) => {}

+ 9 - 2
src/event_graph/rln.rs

@@ -442,9 +442,16 @@ impl IdentityState {
         Ok(commitments)
     }
 
+    /// Check whether `root` matches the current SMT root.
+    pub fn is_current_root(&self, root: &pallas::Base) -> bool {
+        self.smt.root() == *root
+    }
+
     /// Check whether `root` matches the current root or any recent
-    /// historical root. Used during signal proof verification to
-    /// tolerate propagation delays.
+    /// historical root kept in the count-based cache.
+    ///
+    /// This is only an introspection/cache helper. Verifier paths must
+    /// still apply timestamp validity to non-current roots.
     pub fn is_known_root(&self, root: &pallas::Base) -> bool {
         self.recent_roots.contains(root)
     }

+ 55 - 0
src/event_graph/tests_rln.rs

@@ -814,6 +814,61 @@ fn rln_e2e_duplicate_signal_dropped_not_slashed() {
     })
 }
 
+#[test]
+fn rln_verify_signal_rejects_recent_pre_slash_root_after_drift() {
+    smol::block_on(async {
+        use crate::event_graph::event::Header;
+
+        let eg = make_eg().await;
+        let mut id = TestIdentity::new();
+        let commitment = id.commitment();
+        let drift = crate::event_graph::EVENT_TIME_DRIFT;
+
+        let t_reg = 1_000_000_u64;
+        let t_slash = t_reg + 100 * drift;
+        let t_late = t_slash + 2 * drift;
+
+        let reg_node = RLNNode::Registration(commitment);
+        let reg_event = synth_static_event(1, t_reg, &reg_node).await;
+        let pre_slash_root = eg.apply_rln_static_event(&reg_event, &reg_node).await.unwrap();
+        assert!(eg.rln_contains(&commitment).await);
+
+        let content = b"post-slash-old-root-signal".to_vec();
+        let mut parents = NULL_PARENTS;
+        parents[0] = reg_event.id();
+        let signal_event = Event {
+            header: Header {
+                timestamp: t_late,
+                parents,
+                layer: 2,
+                content_hash: blake3::hash(&content),
+            },
+            content,
+        };
+        let mid = id.next_message_id(signal_event.header.timestamp).expect("budget");
+        let blob = id.create_signal(&signal_event, mid, &eg).await.unwrap();
+        assert_eq!(blob.merkle_root, pre_slash_root);
+
+        let slash_node = RLNNode::Slashing(commitment);
+        let slash_event = synth_static_event(2, t_slash, &slash_node).await;
+        let post_slash_root = eg.apply_rln_static_event(&slash_event, &slash_node).await.unwrap();
+        assert_ne!(pre_slash_root, post_slash_root);
+        assert!(!eg.rln_contains(&commitment).await);
+
+        assert!(
+            eg.identity_state.read().await.is_known_root(&pre_slash_root),
+            "pre-slash root should still be in the recent-root cache",
+        );
+        assert!(
+            !eg.is_root_valid_at(&pre_slash_root, t_late).unwrap(),
+            "pre-slash root is outside the post-slash drift window",
+        );
+
+        let bytes = serialize_async(&blob).await;
+        assert!(matches!(eg.rln_verify_signal(&signal_event, &bytes).await, SignalCheck::Rejected));
+    })
+}
+
 #[test]
 fn rln_e2e_slot_reuse_is_slashable() {
     smol::block_on(async {