فهرست منبع

event_graph: Enforce static DAG parent closure

x 1 ماه پیش
والد
کامیت
e57643646d
2فایلهای تغییر یافته به همراه101 افزوده شده و 11 حذف شده
  1. 52 11
      src/event_graph/mod.rs
  2. 49 0
      src/event_graph/tests_rln.rs

+ 52 - 11
src/event_graph/mod.rs

@@ -1396,12 +1396,17 @@ impl EventGraph {
         let mut rejected = 0usize;
         let mut structural_invalid = 0usize;
         let mut content_unparseable = 0usize;
+        let mut parent_missing = 0usize;
         let total_to_consider = fetched.len();
+        let mut committed: HashSet<blake3::Hash> = HashSet::with_capacity(total_to_consider);
 
         for (ev, blob) in fetched {
+            let eid = ev.id();
+
             // Skip if someone else inserted it concurrently.
-            if self.static_dag.contains_key(ev.id().as_bytes())? {
+            if self.static_dag.contains_key(eid.as_bytes())? {
                 already_present += 1;
+                committed.insert(eid);
                 continue
             }
 
@@ -1414,6 +1419,17 @@ impl EventGraph {
                 structural_invalid += 1;
                 continue
             }
+
+            if !self.static_parents_committed(&ev, &committed)? {
+                parent_missing += 1;
+                error!(
+                    target: "event_graph::static_sync",
+                    "[STATIC_SYNC] static event {} has a parent that was not committed; skipping",
+                    eid,
+                );
+                continue
+            }
+
             let rln_node: rln::RLNNode = match deserialize_async_partial(ev.content()).await {
                 Ok((v, _)) => v,
                 Err(_) => {
@@ -1434,9 +1450,11 @@ impl EventGraph {
                 blob_missing += 1;
                 error!(
                     target: "event_graph::static_sync",
-                    "[STATIC_SYNC] no blob available for static event {}; skipping. \
-                     Every static-DAG event must carry an RLN blob.",
-                    ev.id(),
+                    concat!(
+                        "[STATIC_SYNC] no blob available for static event {}; skipping. ",
+                        "Every static-DAG event must carry an RLN blob.",
+                    ),
+                    eid,
                 );
                 continue
             }
@@ -1446,6 +1464,7 @@ impl EventGraph {
                 rln::StaticEventCheck::AcceptedRegistration(_) |
                 rln::StaticEventCheck::AcceptedSlash(_) => {
                     self.commit_verified_static_event(&ev, &blob, &rln_node).await?;
+                    committed.insert(eid);
                     applied += 1;
                 }
                 rln::StaticEventCheck::Rejected | rln::StaticEventCheck::Malicious => {
@@ -1458,9 +1477,11 @@ impl EventGraph {
                     rejected += 1;
                     error!(
                         target: "event_graph::static_sync",
-                        "[STATIC_SYNC] historical blob FAILED re-verification for event {}: {:?}; \
-                         skipping event despite quorum inclusion",
-                        ev.id(),
+                        concat!(
+                            "[STATIC_SYNC] historical blob FAILED re-verification for event {}: {:?}; ",
+                            "skipping event despite quorum inclusion",
+                        ),
+                        eid,
                         outcome,
                     );
                 }
@@ -1469,16 +1490,36 @@ impl EventGraph {
 
         info!(
             target: "event_graph::static_sync",
-            "[STATIC_SYNC] complete: fetched={} applied={} already_present={} \
-             blob_missing={} verification_rejected={} structural_invalid={} \
-             unparseable={}",
+            concat!(
+                "[STATIC_SYNC] complete: fetched={} applied={} already_present={} ",
+                "blob_missing={} verification_rejected={} structural_invalid={} ",
+                "unparseable={} parent_missing={}",
+            ),
             total_to_consider, applied, already_present, blob_missing, rejected,
-            structural_invalid, content_unparseable,
+            structural_invalid, content_unparseable, parent_missing,
         );
 
+        if parent_missing > 0 {
+            return Err(Error::DagSyncFailed)
+        }
+
         Ok(())
     }
 
+    fn static_parents_committed(
+        &self,
+        ev: &Event,
+        committed: &HashSet<blake3::Hash>,
+    ) -> Result<bool> {
+        for parent in ev.header.parents.iter().filter(|parent| **parent != NULL_ID) {
+            if !committed.contains(parent) && !self.static_dag.contains_key(parent.as_bytes())? {
+                return Ok(false)
+            }
+        }
+
+        Ok(true)
+    }
+
     /// Fetch a page of events, crossing DAG boundaries transparently.
     pub async fn fetch_page(
         &self,

+ 49 - 0
src/event_graph/tests_rln.rs

@@ -1276,6 +1276,55 @@ async fn static_sync_registration(ex: Arc<Executor<'static>>) {
     shutdown_network(&nodes).await;
 }
 
+#[test]
+fn rln_multi_node_static_sync_rejects_child_when_parent_rejected() {
+    run_multi_node_test(static_sync_rejects_child_when_parent_rejected);
+}
+async fn static_sync_rejects_child_when_parent_rejected(ex: Arc<Executor<'static>>) {
+    let nodes = make_network(ex).await;
+
+    let bad_node = RLNNode::Registration(pallas::Base::from(0x51a7_1c_u64));
+    let bad_parent = Event::new_static(serialize_async(&bad_node).await, &nodes[0]).await.unwrap();
+    let bad_blob = b"not-a-valid-static-rln-blob".to_vec();
+    for eg in nodes.iter().take(4) {
+        eg.static_insert(&bad_parent).await.unwrap();
+        eg.static_blob_store(&bad_parent.id(), &bad_blob).unwrap();
+    }
+
+    let commitment = pallas::Base::from_repr(nodes[0].config.pregenerated_identity_commitments[0])
+        .into_option()
+        .unwrap();
+    let child_node = RLNNode::Registration(commitment);
+    let child = Event::new_static(serialize_async(&child_node).await, &nodes[0]).await.unwrap();
+    assert!(child.header.parents.contains(&bad_parent.id()));
+
+    let child_blob = GENESIS_BLOB_GUARD.to_vec();
+    for eg in nodes.iter().take(4) {
+        eg.commit_verified_static_event(&child, &child_blob, &child_node).await.unwrap();
+    }
+
+    assert!(nodes[4].static_fetch(&bad_parent.id()).await.unwrap().is_none());
+    assert!(nodes[4].static_fetch(&child.id()).await.unwrap().is_none());
+    assert!(!nodes[4].rln_contains(&commitment).await);
+
+    let result = nodes[4].static_sync().await;
+    assert!(
+        matches!(result, Err(crate::Error::DagSyncFailed)),
+        "static_sync should fail clearly when a fetched static parent is rejected, got {result:?}",
+    );
+    assert!(
+        nodes[4].static_fetch(&bad_parent.id()).await.unwrap().is_none(),
+        "bad static parent should not be committed",
+    );
+    assert!(
+        nodes[4].static_fetch(&child.id()).await.unwrap().is_none(),
+        "child static event should not be committed without its parent",
+    );
+    assert!(!nodes[4].rln_contains(&commitment).await);
+
+    shutdown_network(&nodes).await;
+}
+
 #[test]
 fn rln_multi_node_static_sync_no_peers_is_ok() {
     // A single node with no peers calling static_sync must return