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

event_graph: Index repeated historical RLN roots

x 1 месяц назад
Родитель
Сommit
9b251b02f8
2 измененных файлов с 89 добавлено и 87 удалено
  1. 54 53
      src/event_graph/mod.rs
  2. 35 34
      src/event_graph/tests_rln.rs

+ 54 - 53
src/event_graph/mod.rs

@@ -608,11 +608,13 @@ pub struct EventGraph {
     /// rationale and [`Self::is_root_valid_at`] for how this is
     /// consulted during signal verification.
     pub(crate) rln_historical_roots_ordered: sled::Tree,
-    /// Reverse index: `root:32 -> (layer:u64_be, event_id:32) = 40 bytes`.
+    /// Reverse index: `(root:32, ordered_key:40) -> []`.
     ///
-    /// Lets us answer "is this root historical?" with a single
-    /// `Tree::get(root)`, then chase the returned key into
-    /// `rln_historical_roots_ordered` to get the timestamp interval.
+    /// A root can appear more than once when static events are no-ops
+    /// (duplicate registrations, idempotent slashes), so the value
+    /// index stores every canonical occurrence rather than a single
+    /// root-to-key mapping. [`Self::is_root_valid_at`] scans this
+    /// prefix and accepts if any interval for the root matches.
     pub(crate) rln_historical_roots_by_value: sled::Tree,
     pub(crate) static_dag: sled::Tree,
     /// Side-table mapping `event_id -> original RLN blob` for static
@@ -2064,7 +2066,8 @@ impl EventGraph {
         let key = encode_historical_root_key(ev.header.layer, &ev.id());
         let value = encode_historical_root_value(&new_root, ev.header.timestamp);
         self.rln_historical_roots_ordered.insert(key, value.as_slice())?;
-        self.rln_historical_roots_by_value.insert(new_root.to_repr(), key.as_slice())?;
+        let by_value_key = encode_historical_root_by_value_key(&new_root, &key);
+        self.rln_historical_roots_by_value.insert(by_value_key, &[])?;
 
         Ok(new_root)
     }
@@ -2090,12 +2093,12 @@ impl EventGraph {
     ///   a registration that hadn't fully propagated yet. We tolerate
     ///   up to DRIFT of backward skew.
     ///
-    /// The check uses `rln_historical_roots_by_value` to find the
-    /// canonical position of `root`, then `rln_historical_roots_ordered`
-    /// to bracket the time interval during which `root` was live.
-    /// The interval starts at the timestamp of the event that
-    /// produced `root` and ends just before the next event's
-    /// timestamp (or `u64::MAX` if `root` is currently live).
+    /// The check uses `rln_historical_roots_by_value` to find every
+    /// canonical position where `root` appears, then
+    /// `rln_historical_roots_ordered` to bracket each interval during
+    /// which `root` was live. Each interval starts at the timestamp
+    /// 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
@@ -2103,53 +2106,44 @@ impl EventGraph {
     /// path for the live-broadcast hot loop; this method is consulted
     /// when the cache misses.
     pub fn is_root_valid_at(&self, root: &pallas::Base, signal_timestamp: u64) -> Result<bool> {
-        // Reverse lookup: where in the canonical sequence is `root`?
-        let Some(key_bytes) = self.rln_historical_roots_by_value.get(root.to_repr())? else {
-            return Ok(false)
-        };
+        let drift = EVENT_TIME_DRIFT;
+        let lo = signal_timestamp.saturating_sub(drift);
+        let hi = signal_timestamp.saturating_add(drift);
 
-        // Read the entry that produced this root.
-        let Some(value_bytes) = self.rln_historical_roots_ordered.get(&key_bytes)? else {
-            // Inconsistency between the two tables (shouldn't happen
-            // under normal operation; might happen if a write was
-            // partially applied during a crash). Treat as not-found.
-            return Ok(false)
-        };
-        let (recorded_root, root_timestamp) = decode_historical_root_value(&value_bytes)?;
-        if &recorded_root != root {
-            // Same key collision - should be impossible because the
-            // by_value index is keyed on the root itself, but defend
-            // against future code changes.
-            return Ok(false)
-        }
+        for item in self.rln_historical_roots_by_value.scan_prefix(root.to_repr()) {
+            let (by_value_key, _) = item?;
+            if by_value_key.len() != 72 {
+                continue
+            }
+            let ordered_key = &by_value_key[32..];
 
-        // The interval during which `root` was live ends at the
-        // timestamp of the next event in canonical order, or
-        // u64::MAX if `root` is the current live root.
-        //
-        // We need the strictly-greater key. sled's range-from-exclusive
-        // pattern is range((Excluded(key), Unbounded)).next().
-        let next_timestamp: u64 = {
-            use std::ops::Bound::{Excluded, Unbounded};
-            match self
-                .rln_historical_roots_ordered
-                .range::<&[u8], _>((Excluded(key_bytes.as_ref()), Unbounded))
-                .next()
-            {
-                Some(Ok((_, val))) => decode_historical_root_value(&val)?.1,
-                Some(Err(_)) | None => u64::MAX,
+            let Some(value_bytes) = self.rln_historical_roots_ordered.get(ordered_key)? else {
+                continue
+            };
+            let (recorded_root, root_timestamp) = decode_historical_root_value(&value_bytes)?;
+            if &recorded_root != root {
+                continue
             }
-        };
 
-        // Drift window. Reuse EVENT_TIME_DRIFT for consistency with
-        // event-graph timestamp validation.
-        let drift = EVENT_TIME_DRIFT;
-        let lo = signal_timestamp.saturating_sub(drift);
-        let hi = signal_timestamp.saturating_add(drift);
+            let next_timestamp: u64 = {
+                use std::ops::Bound::{Excluded, Unbounded};
+                match self
+                    .rln_historical_roots_ordered
+                    .range::<&[u8], _>((Excluded(ordered_key), Unbounded))
+                    .next()
+                {
+                    Some(Ok((_, val))) => decode_historical_root_value(&val)?.1,
+                    Some(Err(e)) => return Err(e.into()),
+                    None => u64::MAX,
+                }
+            };
 
-        // `root` was live during [root_timestamp, next_timestamp).
-        // Acceptable iff this interval intersects [lo, hi].
-        Ok(root_timestamp <= hi && next_timestamp > lo)
+            if root_timestamp <= hi && next_timestamp > lo {
+                return Ok(true)
+            }
+        }
+
+        Ok(false)
     }
 }
 
@@ -2167,6 +2161,13 @@ fn encode_historical_root_value(root: &pallas::Base, timestamp: u64) -> [u8; 40]
     buf
 }
 
+fn encode_historical_root_by_value_key(root: &pallas::Base, ordered_key: &[u8; 40]) -> [u8; 72] {
+    let mut buf = [0u8; 72];
+    buf[..32].copy_from_slice(&root.to_repr());
+    buf[32..].copy_from_slice(ordered_key);
+    buf
+}
+
 fn decode_historical_root_value(bytes: &[u8]) -> Result<(pallas::Base, u64)> {
     if bytes.len() != 40 {
         return Err(Error::Custom(format!(

+ 35 - 34
src/event_graph/tests_rln.rs

@@ -850,40 +850,6 @@ fn rln_message_metadata_late_arrival_finds_sibling_after_prune() {
     assert!(!md.is_duplicate(n, &null, &x2, &y2));
 }
 
-#[test]
-fn rln_multi_node_registration_propagates() {
-    run_multi_node_test(registration_propagates);
-}
-async fn registration_propagates(ex: Arc<Executor<'static>>) {
-    let nodes = make_network(ex).await;
-
-    let id = TestIdentity::new();
-    let commitment = id.commitment();
-
-    // Build the registration on node 0, apply it locally, then
-    // broadcast. Production does the same (see nickserv.rs and the
-    // RLN protocol's broadcast paths in proto.rs): callers always
-    // `apply_rln_static_event` before `static_broadcast`, otherwise
-    // the originator's identity_state never sees the new commitment.
-    let blob = id.create_registration(&nodes[0]).expect("build reg");
-    let rln_node = RLNNode::Registration(commitment);
-    let event = Event::new_static(serialize_async(&rln_node).await, &nodes[0]).await;
-    let blob_bytes = serialize_async(&blob).await;
-    nodes[0].static_insert(&event).await.expect("local insert");
-    nodes[0].apply_rln_static_event(&event, &rln_node).await.expect("apply locally");
-    nodes[0].static_broadcast(event, blob_bytes).await.expect("broadcast");
-
-    // Wait for propagation.
-    sleep(5).await;
-
-    // Every node must now have the commitment.
-    for (i, eg) in nodes.iter().enumerate() {
-        assert!(eg.rln_contains(&commitment).await, "node {i} did not receive the registration",);
-    }
-
-    shutdown_network(&nodes).await;
-}
-
 #[test]
 fn rln_multi_node_concurrent_slashes_consistent() {
     run_multi_node_test(concurrent_slashes);
@@ -1583,6 +1549,41 @@ fn rln_slashed_identity_signal_rejection_lifecycle() {
     })
 }
 
+#[test]
+fn rln_repeated_historical_root_keeps_original_interval() {
+    smol::block_on(async {
+        let eg = make_eg().await;
+        let drift = crate::event_graph::EVENT_TIME_DRIFT;
+
+        let commitment_a = pallas::Base::from(0xaaaa_0001_u64);
+        let commitment_b = pallas::Base::from(0xbbbb_0002_u64);
+        let node_a = RLNNode::Registration(commitment_a);
+        let duplicate_a = RLNNode::Registration(commitment_a);
+        let node_b = RLNNode::Registration(commitment_b);
+
+        let t0 = 1_000_000_u64;
+        let t_duplicate = t0 + 2 * drift + 1;
+        let t_next = t_duplicate + 2 * drift + 1;
+
+        let ev_a = synth_static_event(1, t0, &node_a).await;
+        let ev_duplicate = synth_static_event(2, t_duplicate, &duplicate_a).await;
+        let ev_b = synth_static_event(3, t_next, &node_b).await;
+
+        let root_a = eg.apply_rln_static_event(&ev_a, &node_a).await.unwrap();
+        let duplicate_root = eg.apply_rln_static_event(&ev_duplicate, &duplicate_a).await.unwrap();
+        let root_b = eg.apply_rln_static_event(&ev_b, &node_b).await.unwrap();
+
+        assert_eq!(duplicate_root, root_a);
+        assert_ne!(root_b, root_a);
+        assert_eq!(eg.rln_historical_roots_ordered.len(), 3);
+        assert_eq!(eg.rln_historical_roots_by_value.len(), 3);
+
+        assert!(eg.is_root_valid_at(&root_a, t0).unwrap());
+        assert!(eg.is_root_valid_at(&root_a, t_duplicate).unwrap());
+        assert!(!eg.is_root_valid_at(&root_a, t_next + 2 * drift + 1).unwrap());
+    })
+}
+
 #[test]
 fn rln_canonical_order_produces_same_roots_regardless_of_apply_order() {
     // SMT roots are determined by the SET of leaves, not the