Bläddra i källkod

event_graph: Repair missing pregenerated static blobs

x 1 månad sedan
förälder
incheckning
d9e93af9e6
2 ändrade filer med 113 tillägg och 0 borttagningar
  1. 65 0
      src/event_graph/mod.rs
  2. 48 0
      src/event_graph/tests_rln.rs

+ 65 - 0
src/event_graph/mod.rs

@@ -757,6 +757,8 @@ impl EventGraph {
             self_.bootstrap_genesis_identities().await?;
         }
 
+        self_.audit_static_blobs().await?;
+
         if config.hours_rotation > 0 {
             let task = StoppableTask::new();
             let _ = self_.prune_task.set(task.clone()).await;
@@ -1996,6 +1998,69 @@ impl EventGraph {
         compute_unreferenced_tips(&self.static_dag).await
     }
 
+    /// Audit static-DAG blob coverage and repair deterministic guard blobs.
+    ///
+    /// A static DAG event without its RLN blob cannot be served to late
+    /// joiners because they must re-verify historical static events. The only
+    /// blob we can safely reconstruct is the pregenerated-registration guard:
+    /// it is valid exactly for commitments supplied by this app config. Slash
+    /// blobs and future staked registration proofs are not reconstructible and
+    /// are logged for operator intervention.
+    async fn audit_static_blobs(&self) -> Result<()> {
+        let mut repaired = 0usize;
+        let mut unrecoverable = 0usize;
+        let mut malformed = 0usize;
+
+        for item in self.static_dag.iter() {
+            let (_, val) = item?;
+            let ev: Event = deserialize_async(&val).await?;
+            if ev.header.parents == NULL_PARENTS {
+                continue
+            }
+
+            if matches!(self.static_blob_fetch(&ev.id())?, Some(blob) if !blob.is_empty()) {
+                continue
+            }
+
+            let rln_node: rln::RLNNode = match deserialize_async_partial(ev.content()).await {
+                Ok((node, _)) => node,
+                Err(_) => {
+                    malformed += 1;
+                    continue
+                }
+            };
+
+            match rln_node {
+                rln::RLNNode::Registration(commitment)
+                    if self
+                        .pregenerated_identity_commitment_reprs
+                        .contains(&commitment.to_repr()) =>
+                {
+                    self.static_blob_store(&ev.id(), rln::GENESIS_BLOB_GUARD)?;
+                    repaired += 1;
+                }
+                _ => {
+                    unrecoverable += 1;
+                    warn!(
+                        target: "event_graph::new",
+                        "[EVENTGRAPH] static event {} is missing its RLN blob and cannot be reconstructed",
+                        ev.id(),
+                    );
+                }
+            }
+        }
+
+        if repaired > 0 || unrecoverable > 0 || malformed > 0 {
+            info!(
+                target: "event_graph::new",
+                "[EVENTGRAPH] static blob audit: repaired={} unrecoverable={} malformed={}",
+                repaired, unrecoverable, malformed,
+            );
+        }
+
+        Ok(())
+    }
+
     /// Persist the original RLN blob for a static-DAG event. The
     /// blob is the wire payload from the originating `StaticPut` -
     /// proof + public inputs + attestation - needed to re-verify

+ 48 - 0
src/event_graph/tests_rln.rs

@@ -982,6 +982,54 @@ async fn concurrent_slashes(ex: Arc<Executor<'static>>) {
     shutdown_network(&nodes).await;
 }
 
+#[test]
+fn rln_static_blob_audit_repairs_pregenerated_guard() {
+    smol::block_on(async {
+        let config = EventGraphConfig { hours_rotation: 1, ..test_config() };
+        let eg = make_eg_with_config(config).await;
+        let commitment = genesis_commitment_at(&eg, 0);
+
+        let mut registration_event = None;
+        for item in eg.static_dag.iter() {
+            let (_, bytes) = item.unwrap();
+            let ev: Event = deserialize_async(&bytes).await.unwrap();
+            if ev.header.parents == NULL_PARENTS {
+                continue
+            }
+            let node: RLNNode = deserialize_async(ev.content()).await.unwrap();
+            if matches!(node, RLNNode::Registration(c) if c == commitment) {
+                registration_event = Some(ev);
+                break
+            }
+        }
+        let ev = registration_event.expect("bootstrapped pregenerated registration event");
+
+        eg.static_dag_blobs.remove(ev.id().as_bytes()).unwrap();
+        assert!(eg.static_blob_fetch(&ev.id()).unwrap().is_none());
+
+        eg.audit_static_blobs().await.unwrap();
+
+        assert_eq!(eg.static_blob_fetch(&ev.id()).unwrap().unwrap(), GENESIS_BLOB_GUARD);
+    })
+}
+
+#[test]
+fn rln_static_blob_audit_does_not_fabricate_slash_blob() {
+    smol::block_on(async {
+        let eg = make_eg().await;
+        let commitment = pallas::Base::from(0x515a_5b_u64);
+        let node = RLNNode::Slashing(commitment);
+        let ev = synth_static_event(1, 400_000, &node).await;
+
+        eg.static_insert(&ev).await.unwrap();
+        assert!(eg.static_blob_fetch(&ev.id()).unwrap().is_none());
+
+        eg.audit_static_blobs().await.unwrap();
+
+        assert!(eg.static_blob_fetch(&ev.id()).unwrap().is_none());
+    })
+}
+
 #[test]
 fn rln_multi_node_static_sync_registration() {
     run_multi_node_test(static_sync_registration);