Răsfoiți Sursa

event_graph: Validate timestamps by DAG slot

x 1 lună în urmă
părinte
comite
31cbb79c89
3 a modificat fișierele cu 173 adăugiri și 34 ștergeri
  1. 29 27
      src/event_graph/event.rs
  2. 2 2
      src/event_graph/mod.rs
  3. 142 5
      src/event_graph/tests.rs

+ 29 - 27
src/event_graph/event.rs

@@ -21,10 +21,7 @@ use std::{cmp::Ordering, collections::HashSet, time::UNIX_EPOCH};
 use darkfi_serial::{async_trait, deserialize_async, Encodable, SerialDecodable, SerialEncodable};
 use sled_overlay::{sled, SledTreeOverlay};
 
-use super::{
-    util::{generate_genesis, next_rotation_timestamp},
-    EventGraph, EventGraphConfig, EVENT_TIME_DRIFT, NULL_ID, N_EVENT_PARENTS,
-};
+use super::{util::HOUR, EventGraph, EventGraphConfig, EVENT_TIME_DRIFT, NULL_ID, N_EVENT_PARENTS};
 use crate::Result;
 
 /// The fixed-size structural metadata of an event.
@@ -84,37 +81,19 @@ impl Header {
     }
 
     /// Full structural validation against a header DAG.
+    ///
+    /// `dag_genesis` is the timestamp/name of the target rotating DAG slot.
     pub async fn validate(
         &self,
         header_dag: &sled::Tree,
         config: &EventGraphConfig,
+        dag_genesis: u64,
         overlay: Option<&SledTreeOverlay>,
     ) -> Result<bool> {
-        // Lower bound: one day before the most recent hourly genesis.
-        // We build a temporary 1-hour config just to compute the
-        // reference timestamp.
-        let hourly_cfg = EventGraphConfig {
-            initial_genesis: config.initial_genesis,
-            hours_rotation: 1,
-            genesis_contents: config.genesis_contents.clone(),
-            pregenerated_identity_commitments: Vec::new(),
-            max_dags: config.max_dags,
-        };
-
-        let oldest_allowed = generate_genesis(&hourly_cfg).header.timestamp - 86_400_000;
-
-        if self.timestamp < oldest_allowed - EVENT_TIME_DRIFT {
+        if !self.timestamp_fits_slot(config, dag_genesis) {
             return Ok(false)
         }
 
-        // Upper bound: next rotation boundary + drift
-        if config.hours_rotation > 0 {
-            let next = next_rotation_timestamp(config.initial_genesis, config.hours_rotation);
-            if self.timestamp > next + EVENT_TIME_DRIFT {
-                return Ok(false)
-            }
-        }
-
         let mut seen = HashSet::new();
         let mut max_parent_layer = None;
         let self_id = self.id();
@@ -145,6 +124,26 @@ impl Header {
 
         Ok(self.layer == expected_layer)
     }
+
+    /// Check whether this header timestamp belongs to the target DAG slot.
+    fn timestamp_fits_slot(&self, config: &EventGraphConfig, dag_genesis: u64) -> bool {
+        if self.timestamp < dag_genesis.saturating_sub(EVENT_TIME_DRIFT) {
+            return false
+        }
+
+        if config.hours_rotation == 0 {
+            let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
+            return self.timestamp <= now.saturating_add(EVENT_TIME_DRIFT)
+        }
+
+        let Some(rotation_ms) = config.hours_rotation.checked_mul(HOUR as u64) else {
+            return false
+        };
+        let Some(next_slot) = dag_genesis.checked_add(rotation_ms) else { return false };
+        let Some(upper_bound) = next_slot.checked_add(EVENT_TIME_DRIFT) else { return false };
+
+        self.timestamp < upper_bound
+    }
 }
 
 /// A complete event: [`Header`] + application-defined content.
@@ -185,10 +184,13 @@ impl Event {
     }
 
     /// Validate for insertion into a DAG.
+    ///
+    /// `dag_genesis` is the timestamp/name of the target rotating DAG slot.
     pub async fn dag_validate(
         &self,
         hdr_dag: &sled::Tree,
         config: &EventGraphConfig,
+        dag_genesis: u64,
     ) -> Result<bool> {
         if self.content.is_empty() {
             return Ok(false)
@@ -198,7 +200,7 @@ impl Event {
             return Ok(false)
         }
 
-        self.header.validate(hdr_dag, config, None).await
+        self.header.validate(hdr_dag, config, dag_genesis, None).await
     }
 
     /// Quick validation (no DAG lookup).

+ 2 - 2
src/event_graph/mod.rs

@@ -1618,7 +1618,7 @@ impl EventGraph {
             if !slot.header_tree.contains_key(eid.as_bytes())? {
                 continue
             }
-            if !ev.dag_validate(&slot.header_tree, &self.config).await? {
+            if !ev.dag_validate(&slot.header_tree, &self.config, dag_ts).await? {
                 return Err(Error::EventIsInvalid)
             }
             let se = serialize_async(ev).await;
@@ -1735,7 +1735,7 @@ impl EventGraph {
 
             let hid = hdr.id();
 
-            if !hdr.validate(&slot.header_tree, &self.config, Some(&overlay)).await? {
+            if !hdr.validate(&slot.header_tree, &self.config, dag_ts, Some(&overlay)).await? {
                 return Err(Error::HeaderIsInvalid)
             }
 

+ 142 - 5
src/event_graph/tests.rs

@@ -35,12 +35,12 @@ use crate::{
         filter_requested_event_rep, merge_static_sync_event_rep,
         proto::{cap_layer_tips, count_layer_tips, EventPut, SyncDirection, MAX_RANGE_PAGE_SIZE},
         test_helpers::{
-            archive_config, bounded_dag_store_config, init_logger, make_eg, make_network,
-            run_multi_node_test, shutdown_network, test_config, TestIdentity,
+            archive_config, bounded_dag_store_config, init_logger, make_eg, make_eg_with_config,
+            make_network, run_multi_node_test, shutdown_network, test_config, TestIdentity,
         },
         util::next_hour_timestamp,
-        DagStore, Event, EventGraphPtr, LayerUTips, TimeIndex, NULL_ID, NULL_PARENTS,
-        N_EVENT_PARENTS,
+        DagStore, Event, EventGraphConfig, EventGraphPtr, LayerUTips, TimeIndex, NULL_ID,
+        NULL_PARENTS, N_EVENT_PARENTS,
     },
     system::{sleep, timeout::timeout},
 };
@@ -433,7 +433,144 @@ fn evgr_header_validate_rejects_layer_overflow_parent() {
             content_hash: blake3::hash(b"overflow-child"),
         };
 
-        assert!(!child.validate(&tree, &test_config(), None).await.unwrap());
+        assert!(!child.validate(&tree, &test_config(), timestamp, None).await.unwrap());
+    })
+}
+
+#[test]
+fn evgr_header_validate_uses_target_slot_bounds() {
+    smol::block_on(async {
+        const HOUR_MS: u64 = 3_600_000;
+
+        let db = sled::Config::new().temporary(true).open().unwrap();
+        let tree = db.open_tree("headers").unwrap();
+        let dag_ts = 1_704_067_200_000;
+        let drift = crate::event_graph::EVENT_TIME_DRIFT;
+        let config = EventGraphConfig { hours_rotation: 6, ..test_config() };
+        let genesis = Header {
+            timestamp: dag_ts,
+            parents: NULL_PARENTS,
+            layer: 0,
+            content_hash: blake3::hash(&config.genesis_contents),
+        };
+        tree.insert(genesis.id().as_bytes(), serialize_async(&genesis).await).unwrap();
+
+        let mut parents = [NULL_ID; N_EVENT_PARENTS];
+        parents[0] = genesis.id();
+        let make_header = |timestamp, content: &[u8]| Header {
+            timestamp,
+            parents,
+            layer: 1,
+            content_hash: blake3::hash(content),
+        };
+
+        let lower_edge = make_header(dag_ts.saturating_sub(drift), b"lower-edge");
+        assert!(lower_edge.validate(&tree, &config, dag_ts, None).await.unwrap());
+
+        let upper_edge = make_header(dag_ts + 6 * HOUR_MS + drift - 1, b"upper-edge");
+        assert!(upper_edge.validate(&tree, &config, dag_ts, None).await.unwrap());
+
+        let too_early = make_header(dag_ts - drift - 1, b"too-early");
+        assert!(!too_early.validate(&tree, &config, dag_ts, None).await.unwrap());
+
+        let too_late = make_header(dag_ts + 6 * HOUR_MS + drift, b"too-late");
+        assert!(!too_late.validate(&tree, &config, dag_ts, None).await.unwrap());
+    })
+}
+
+#[test]
+fn evgr_header_validate_ignores_future_initial_genesis() {
+    smol::block_on(async {
+        const HOUR_MS: u64 = 3_600_000;
+
+        let db = sled::Config::new().temporary(true).open().unwrap();
+        let tree = db.open_tree("headers").unwrap();
+        let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
+        let dag_ts = now.saturating_sub(HOUR_MS);
+        let config =
+            EventGraphConfig { initial_genesis: now + HOUR_MS, hours_rotation: 1, ..test_config() };
+        let genesis = Header {
+            timestamp: dag_ts,
+            parents: NULL_PARENTS,
+            layer: 0,
+            content_hash: blake3::hash(&config.genesis_contents),
+        };
+        tree.insert(genesis.id().as_bytes(), serialize_async(&genesis).await).unwrap();
+
+        let mut parents = [NULL_ID; N_EVENT_PARENTS];
+        parents[0] = genesis.id();
+        let child = Header {
+            timestamp: dag_ts + 1,
+            parents,
+            layer: 1,
+            content_hash: blake3::hash(b"future-initial-genesis"),
+        };
+
+        assert!(child.validate(&tree, &config, dag_ts, None).await.unwrap());
+    })
+}
+
+#[test]
+fn evgr_header_validate_no_rotation_rejects_far_future() {
+    smol::block_on(async {
+        let db = sled::Config::new().temporary(true).open().unwrap();
+        let tree = db.open_tree("headers").unwrap();
+        let config = test_config();
+        let dag_ts = config.initial_genesis;
+        let genesis = Header {
+            timestamp: dag_ts,
+            parents: NULL_PARENTS,
+            layer: 0,
+            content_hash: blake3::hash(&config.genesis_contents),
+        };
+        tree.insert(genesis.id().as_bytes(), serialize_async(&genesis).await).unwrap();
+
+        let mut parents = [NULL_ID; N_EVENT_PARENTS];
+        parents[0] = genesis.id();
+        let old_history = Header {
+            timestamp: dag_ts + 1,
+            parents,
+            layer: 1,
+            content_hash: blake3::hash(b"old-no-rotation-history"),
+        };
+        assert!(old_history.validate(&tree, &config, dag_ts, None).await.unwrap());
+
+        let future = Header {
+            timestamp: UNIX_EPOCH.elapsed().unwrap().as_millis() as u64 +
+                crate::event_graph::EVENT_TIME_DRIFT +
+                1,
+            parents,
+            layer: 1,
+            content_hash: blake3::hash(b"future-no-rotation-header"),
+        };
+        assert!(!future.validate(&tree, &config, dag_ts, None).await.unwrap());
+    })
+}
+
+#[test]
+fn evgr_header_insert_rejects_unloaded_dag_slot() {
+    smol::block_on(async {
+        let config = EventGraphConfig { hours_rotation: 1, max_dags: Some(2), ..test_config() };
+        let eg = make_eg_with_config(config).await;
+        let dag_ts = next_hour_timestamp(-100);
+        let dag_name = dag_ts.to_string();
+        let genesis = Header {
+            timestamp: dag_ts,
+            parents: NULL_PARENTS,
+            layer: 0,
+            content_hash: blake3::hash(&eg.config.genesis_contents),
+        };
+        let mut parents = [NULL_ID; N_EVENT_PARENTS];
+        parents[0] = genesis.id();
+        let header = Header {
+            timestamp: dag_ts + 1,
+            parents,
+            layer: 1,
+            content_hash: blake3::hash(b"unloaded-slot"),
+        };
+
+        let err = eg.header_dag_insert(vec![header], &dag_name).await.unwrap_err();
+        assert!(matches!(err, crate::Error::DagSyncFailed));
     })
 }