mod.rs 95 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. //! Multi-DAG Event Graph with bidirectional sync, RLN rate limiting,
  19. //! and periodic DAG rotation.
  20. use std::{
  21. collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
  22. path::PathBuf,
  23. str::FromStr,
  24. sync::{
  25. atomic::{AtomicBool, Ordering},
  26. Arc,
  27. },
  28. };
  29. use darkfi_sdk::{crypto::pasta_prelude::PrimeField, pasta::pallas};
  30. use darkfi_serial::{deserialize_async, deserialize_async_partial, serialize_async};
  31. use futures::{stream::FuturesUnordered, StreamExt};
  32. use sled_overlay::{sled, SledTreeOverlay};
  33. use smol::{
  34. lock::{OnceCell, RwLock},
  35. Executor,
  36. };
  37. use tracing::{error, info, warn};
  38. use url::Url;
  39. use crate::{
  40. net::{channel::Channel, P2pPtr},
  41. system::{msleep, Publisher, PublisherPtr, StoppableTask, StoppableTaskPtr, Subscription},
  42. Error, Result,
  43. };
  44. pub mod event;
  45. pub use event::{display_order, Event, Header};
  46. pub mod proto;
  47. use proto::{EventRep, EventReq, HeaderRep, HeaderReq, StaticPut, SyncDirection, TipRep, TipReq};
  48. pub mod rln;
  49. use rln::{IdentityState, RlnState, ZkKeys};
  50. pub mod util;
  51. use util::{
  52. generate_genesis, millis_until_next_rotation, next_hour_timestamp, next_rotation_timestamp,
  53. replayer_log,
  54. };
  55. pub mod deg;
  56. use deg::DegEvent;
  57. #[cfg(test)]
  58. mod tests;
  59. #[cfg(test)]
  60. mod tests_rln;
  61. #[cfg(test)]
  62. mod test_helpers;
  63. /// Number of parent references each event carries.
  64. pub const N_EVENT_PARENTS: usize = 5;
  65. /// Allowed timestamp drift in milliseconds.
  66. const EVENT_TIME_DRIFT: u64 = 60_000;
  67. /// The null event ID (32 zero bytes).
  68. pub const NULL_ID: blake3::Hash = blake3::Hash::from_bytes([0x00; blake3::OUT_LEN]);
  69. /// Array of null parents (used by genesis events).
  70. pub const NULL_PARENTS: [blake3::Hash; N_EVENT_PARENTS] = [NULL_ID; N_EVENT_PARENTS];
  71. /// Maximum number of static-DAG events `static_sync` will pull in
  72. /// one invocation. Defends against malicious deep-ancestry chains.
  73. const SYNC_MAX_STATIC_EVENTS: usize = 100_000;
  74. /// Runtime configuration for an Event Graph instance.
  75. #[derive(Clone, Debug)]
  76. pub struct EventGraphConfig {
  77. /// Epoch origin timestamp in millis.
  78. /// All rotation boundaries are computed as offsets from this point.
  79. /// Should be UTC midnight for clean hourly alignment.
  80. pub initial_genesis: u64,
  81. /// How often the DAG rotates, in hours. 0 = no rotation.
  82. pub hours_rotation: u64,
  83. /// Unique payload embedded in genesis events.
  84. /// Different protocols must use different values.
  85. pub genesis_contents: Vec<u8>,
  86. /// Maximum number of DAGs to keep in the rolling window.
  87. ///
  88. /// * `Some(n)` - keep n rotation periods.
  89. /// When the n+1 period is created, the oldest is permanently
  90. /// deleted from sled. This is the normal mode for end-user nodes.
  91. /// * `None` - never prune. Every DAG ever created is kept in sled
  92. /// and loaded at startup. This is archive mode for nodes that want
  93. /// complete history.
  94. ///
  95. /// With `hours_rotation = 1` and `max_dags = Some(24)`, events
  96. /// older than 24 hours are lost. With `hours_rotation = 6` and
  97. /// `max_dags = Some(24)`, the window is 6 days.
  98. pub max_dags: Option<usize>,
  99. }
  100. pub type EventGraphPtr = Arc<EventGraph>;
  101. /// Unreferenced tips grouped by layer.
  102. pub type LayerUTips = BTreeMap<u64, HashSet<blake3::Hash>>;
  103. /// Bidirectional timestamp -> event-ID index.
  104. #[derive(Clone, Debug, Default)]
  105. pub struct TimeIndex {
  106. index: BTreeMap<u64, Vec<blake3::Hash>>,
  107. count: usize,
  108. }
  109. impl TimeIndex {
  110. pub fn new() -> Self {
  111. Self::default()
  112. }
  113. pub async fn from_header_dag(tree: &sled::Tree) -> Self {
  114. let mut idx = Self::new();
  115. for item in tree.iter() {
  116. let (id, hdr) = item.unwrap();
  117. let id = blake3::Hash::from_bytes((&id as &[u8]).try_into().unwrap());
  118. let hdr: Header = deserialize_async(&hdr).await.unwrap();
  119. idx.insert(hdr.timestamp, id);
  120. }
  121. idx
  122. }
  123. pub fn insert(&mut self, ts: u64, id: blake3::Hash) {
  124. self.index.entry(ts).or_default().push(id);
  125. self.count += 1;
  126. }
  127. pub fn newest(&self, n: usize) -> Vec<blake3::Hash> {
  128. self.rev(u64::MAX, n)
  129. }
  130. pub fn oldest(&self, n: usize) -> Vec<blake3::Hash> {
  131. self.fwd(0, n)
  132. }
  133. pub fn before(&self, cursor: u64, n: usize) -> Vec<blake3::Hash> {
  134. self.rev(cursor.saturating_sub(1), n)
  135. }
  136. pub fn after(&self, cursor: u64, n: usize) -> Vec<blake3::Hash> {
  137. self.fwd(cursor.saturating_add(1), n)
  138. }
  139. fn rev(&self, start: u64, n: usize) -> Vec<blake3::Hash> {
  140. let mut out = Vec::with_capacity(n);
  141. for (_, ids) in self.index.range(..=start).rev() {
  142. for id in ids {
  143. out.push(*id);
  144. if out.len() >= n {
  145. return out
  146. }
  147. }
  148. }
  149. out
  150. }
  151. fn fwd(&self, start: u64, n: usize) -> Vec<blake3::Hash> {
  152. let mut out = Vec::with_capacity(n);
  153. for (_, ids) in self.index.range(start..) {
  154. for id in ids {
  155. out.push(*id);
  156. if out.len() >= n {
  157. return out
  158. }
  159. }
  160. }
  161. out
  162. }
  163. pub fn len(&self) -> usize {
  164. self.count
  165. }
  166. pub fn is_empty(&self) -> bool {
  167. self.count == 0
  168. }
  169. }
  170. /// All per-DAG state: trees, tips, and the timestamp index.
  171. pub struct DagSlot {
  172. pub header_tree: sled::Tree,
  173. pub main_tree: sled::Tree,
  174. pub tips: LayerUTips,
  175. pub time_index: TimeIndex,
  176. }
  177. /// Full-scan tip computation.
  178. /// Compute unreferenced tips - events that exist in the DAG but are
  179. /// not referenced as a parent by any other event - grouped by layer.
  180. pub(crate) async fn compute_unreferenced_tips(dag: &sled::Tree) -> LayerUTips {
  181. let mut candidates: HashMap<blake3::Hash, u64> = HashMap::new();
  182. let mut referenced: HashSet<blake3::Hash> = HashSet::new();
  183. for item in dag.iter() {
  184. let (id_bytes, val_bytes) = item.unwrap();
  185. let id = blake3::Hash::from_bytes((&id_bytes as &[u8]).try_into().unwrap());
  186. let ev: Event = deserialize_async(&val_bytes).await.unwrap();
  187. candidates.insert(id, ev.header.layer);
  188. for p in ev.header.parents.iter() {
  189. if *p != NULL_ID {
  190. referenced.insert(*p);
  191. }
  192. }
  193. }
  194. // Bucket the unreferenced candidates by their layer
  195. let mut map: LayerUTips = BTreeMap::new();
  196. for (id, layer) in candidates {
  197. if !referenced.contains(&id) {
  198. map.entry(layer).or_default().insert(id);
  199. }
  200. }
  201. map
  202. }
  203. /// Pick up to N_EVENT_PARENTS tips from the highest layers.
  204. fn select_parents_from_tips(tips: &LayerUTips) -> (u64, [blake3::Hash; N_EVENT_PARENTS]) {
  205. let mut parents = [NULL_ID; N_EVENT_PARENTS];
  206. let mut i = 0;
  207. 'outer: for (_, layer_tips) in tips.iter().rev() {
  208. for t in layer_tips {
  209. parents[i] = *t;
  210. i += 1;
  211. if i >= N_EVENT_PARENTS {
  212. break 'outer
  213. }
  214. }
  215. }
  216. (tips.last_key_value().unwrap().0 + 1, parents)
  217. }
  218. /// Storage layer for all rotating DAGs.
  219. pub struct DagStore {
  220. db: sled::Db,
  221. dags: BTreeMap<u64, DagSlot>,
  222. }
  223. impl DagStore {
  224. /// Create or open DAG slots.
  225. ///
  226. /// * **Bounded mode** (`max_dags = Some(n)`): create a rolling
  227. /// window of the most recent `n` DAGs. Old trees already in
  228. /// sled outside this window are left untouched (they're just
  229. /// not loaded into memory).
  230. /// * **Archive mode** (`max_dags = None`): discover *all*
  231. /// existing DAG trees in sled and load them, plus ensure the
  232. /// recent window exists. Nothing is ever dropped.
  233. pub async fn new(sled_db: sled::Db, config: &EventGraphConfig) -> Self {
  234. let mut dags = BTreeMap::new();
  235. if config.hours_rotation == 0 {
  236. let genesis = generate_genesis(config);
  237. dags.insert(genesis.header.timestamp, Self::create_slot(&sled_db, &genesis).await);
  238. return Self { db: sled_db, dags }
  239. }
  240. // Determine how many recent DAGs to create/ensure exist.
  241. let window = config.max_dags.unwrap_or(24);
  242. // In archive mode, first discover and load any existing DAG
  243. // trees that are already in sled from previous runs.
  244. //
  245. // A DAG is stored across two trees: `<timestamp>` for events
  246. // and `headers_<timestamp>` for headers. We walk every tree
  247. // name in sled and pick out the ones whose name is a valid u64
  248. // timestamp.
  249. if config.max_dags.is_none() {
  250. for name in sled_db.tree_names() {
  251. let name_str = String::from_utf8_lossy(&name);
  252. if let Ok(ts) = name_str.parse::<u64>() {
  253. // Reconstruct the genesis for this timestamp
  254. let hdr = Header {
  255. timestamp: ts,
  256. parents: NULL_PARENTS,
  257. layer: 0,
  258. content_hash: blake3::hash(&config.genesis_contents),
  259. };
  260. let genesis = Event { header: hdr, content: config.genesis_contents.clone() };
  261. let slot = Self::create_slot(&sled_db, &genesis).await;
  262. dags.insert(ts, slot);
  263. }
  264. }
  265. }
  266. // Ensure the recent window of DAGs exists.
  267. // Creates them if they're not already loaded from the discovery step.
  268. for i in 1..=window {
  269. let ts = next_hour_timestamp((i as i64) - (window as i64));
  270. if dags.contains_key(&ts) {
  271. // Already loaded from sled discovery
  272. continue
  273. }
  274. let hdr = Header {
  275. timestamp: ts,
  276. parents: NULL_PARENTS,
  277. layer: 0,
  278. content_hash: blake3::hash(&config.genesis_contents),
  279. };
  280. let genesis = Event { header: hdr, content: config.genesis_contents.clone() };
  281. dags.insert(ts, Self::create_slot(&sled_db, &genesis).await);
  282. }
  283. Self { db: sled_db, dags }
  284. }
  285. async fn create_slot(db: &sled::Db, genesis: &Event) -> DagSlot {
  286. let name = genesis.header.timestamp.to_string();
  287. let ht = db.open_tree(format!("headers_{name}")).unwrap();
  288. let mt = db.open_tree(&name).unwrap();
  289. for (tree, data) in
  290. [(&ht, serialize_async(&genesis.header).await), (&mt, serialize_async(genesis).await)]
  291. {
  292. if tree.is_empty() {
  293. let mut ov = SledTreeOverlay::new(tree);
  294. ov.insert(genesis.id().as_bytes(), &data).unwrap();
  295. if let Some(b) = ov.aggregate() {
  296. tree.apply_batch(b).unwrap();
  297. }
  298. }
  299. }
  300. DagSlot {
  301. tips: compute_unreferenced_tips(&mt).await,
  302. time_index: TimeIndex::from_header_dag(&ht).await,
  303. header_tree: ht,
  304. main_tree: mt,
  305. }
  306. }
  307. /// Add a new DAG on rotation. In bounded mode, drops the oldest DAG
  308. /// when the limit is reached. In archive mode, never drops.
  309. pub async fn add_dag(&mut self, genesis: &Event, max_dags: Option<usize>) {
  310. if let Some(limit) = max_dags {
  311. if self.dags.len() >= limit {
  312. let (_, old) = self.dags.pop_first().unwrap();
  313. self.db.drop_tree(old.header_tree.name()).unwrap();
  314. self.db.drop_tree(old.main_tree.name()).unwrap();
  315. }
  316. }
  317. let slot = Self::create_slot(&self.db, genesis).await;
  318. self.dags.insert(genesis.header.timestamp, slot);
  319. }
  320. pub fn get_slot(&self, ts: &u64) -> Option<&DagSlot> {
  321. self.dags.get(ts)
  322. }
  323. pub fn get_slot_mut(&mut self, ts: &u64) -> Option<&mut DagSlot> {
  324. self.dags.get_mut(ts)
  325. }
  326. pub fn get_header_tree(&self, dag_name: &str) -> sled::Tree {
  327. self.db.open_tree(format!("headers_{dag_name}")).unwrap()
  328. }
  329. pub fn dag_timestamps(&self) -> Vec<u64> {
  330. self.dags.keys().cloned().collect()
  331. }
  332. }
  333. enum PeerStatus {
  334. Free,
  335. Busy,
  336. Failed,
  337. }
  338. /// The main Event Graph instance.
  339. ///
  340. /// Manages a rolling window of DAGs (one per rotation period), a
  341. /// static DAG for long-lived state (RLN identities), and the P2P
  342. /// protocol for syncing with peers.
  343. ///
  344. /// # Sync model
  345. ///
  346. /// Headers are synced eagerly (complete DAG skeleton in seconds).
  347. /// Event content is fetched lazily in the direction the application
  348. /// needs.
  349. ///
  350. /// The [`TimeIndex`] in each [`DagSlot`] enables O(log n)
  351. /// bidirectional pagination that crosses DAG boundaries
  352. /// transparently - the caller sees a flat chronological stream.
  353. pub struct EventGraph {
  354. pub(crate) p2p: P2pPtr,
  355. pub(crate) dag_store: RwLock<DagStore>,
  356. /// Side-table mapping `event_id -> original RLN signal blob` for
  357. /// rotating-DAG events. Mirror of [`Self::static_dag_blobs`] but
  358. /// for the rotating DAGs.
  359. ///
  360. /// Populated by `handle_event_put` after successful RLN
  361. /// verification, and by `dag_insert_with_blobs` during sync when
  362. /// the serving peer included the blob in its `EventRep`. Read
  363. /// by `handle_event_req` to forward blobs to syncing peers.
  364. /// Pruned by `dag_prune` when the corresponding rotating DAG
  365. /// rolls out of the retention window.
  366. pub(crate) dag_blobs: sled::Tree,
  367. /// Historical SMT roots, in canonical apply order.
  368. ///
  369. /// Key: `(layer:u64_be, event_id:32) = 40 bytes`. Value:
  370. /// `(root:32, timestamp:u64_be:8) = 40 bytes`.
  371. ///
  372. /// Big-endian layer encoding makes lexicographic byte order
  373. /// match canonical apply order, so `Tree::range` iterates
  374. /// chronologically and `Tree::get_lt` / `get_gt` give cheap
  375. /// neighbor lookups (used to find the timestamp interval during
  376. /// which a given root was the live root).
  377. ///
  378. /// See [`Self::apply_rln_static_event`] for the canonical-order
  379. /// rationale and [`Self::is_root_valid_at`] for how this is
  380. /// consulted during signal verification.
  381. pub(crate) rln_historical_roots_ordered: sled::Tree,
  382. /// Reverse index: `root:32 -> (layer:u64_be, event_id:32) = 40 bytes`.
  383. ///
  384. /// Lets us answer "is this root historical?" with a single
  385. /// `Tree::get(root)`, then chase the returned key into
  386. /// `rln_historical_roots_ordered` to get the timestamp interval.
  387. pub(crate) rln_historical_roots_by_value: sled::Tree,
  388. pub(crate) static_dag: sled::Tree,
  389. /// Side-table mapping `event_id -> original RLN blob` for static
  390. /// events. Used by [`Self::static_sync`] to re-verify the ZK
  391. /// proof of historical events at sync time. Every static-DAG
  392. /// event MUST have a corresponding entry - `static_sync` rejects
  393. /// events whose blob isn't available rather than falling through.
  394. pub(crate) static_dag_blobs: sled::Tree,
  395. datastore: PathBuf,
  396. replay_mode: bool,
  397. pub(crate) broadcasted_ids: RwLock<HashSet<blake3::Hash>>,
  398. pub prune_task: OnceCell<StoppableTaskPtr>,
  399. pub event_pub: PublisherPtr<Event>,
  400. pub static_pub: PublisherPtr<Event>,
  401. pub current_genesis: RwLock<Event>,
  402. pub config: EventGraphConfig,
  403. pub synced: AtomicBool,
  404. pub deg_enabled: AtomicBool,
  405. deg_publisher: PublisherPtr<DegEvent>,
  406. pub sled_db: sled::Db,
  407. pub zk_keys: Arc<ZkKeys>,
  408. pub identity_state: RwLock<IdentityState>,
  409. pub rln_state: RwLock<RlnState>,
  410. /// App identifier mixed into the RLN external nullifier. Derived
  411. /// from `config.genesis_contents` so two deployments using the
  412. /// same circuit cannot collide on internal_nullifiers.
  413. rln_app_id: rln::RlnAppId,
  414. }
  415. impl EventGraph {
  416. /// Create a new Event Graph.
  417. pub async fn new(
  418. p2p: P2pPtr,
  419. sled_db: sled::Db,
  420. datastore: PathBuf,
  421. replay_mode: bool,
  422. config: EventGraphConfig,
  423. ex: Arc<Executor<'_>>,
  424. ) -> Result<EventGraphPtr> {
  425. let zk_keys = Arc::new(ZkKeys::build_and_load(&sled_db)?);
  426. Self::with_zk_keys(p2p, sled_db, datastore, replay_mode, config, zk_keys, ex).await
  427. }
  428. /// Same as [`Self::new`] but accepts a pre-built [`ZkKeys`].
  429. ///
  430. /// Production always wants `Self::new`, which builds keys once
  431. /// against its own sled DB. Tests use this variant to share a
  432. /// single [`Arc<ZkKeys>`] across many `EventGraph` instances -
  433. /// proving keys are large (hundreds of MB each) and copying
  434. /// them per-test would blow out RAM and `/dev/shm`.
  435. pub async fn with_zk_keys(
  436. p2p: P2pPtr,
  437. sled_db: sled::Db,
  438. datastore: PathBuf,
  439. replay_mode: bool,
  440. config: EventGraphConfig,
  441. zk_keys: Arc<ZkKeys>,
  442. ex: Arc<Executor<'_>>,
  443. ) -> Result<EventGraphPtr> {
  444. let identity_state = IdentityState::new(&sled_db)?;
  445. let rln_app_id = rln::RlnAppId::from_genesis(&config.genesis_contents);
  446. let current_genesis = generate_genesis(&config);
  447. let dag_store = DagStore::new(sled_db.clone(), &config).await;
  448. let static_dag = Self::static_new(&sled_db, &config).await?;
  449. let static_dag_blobs = sled_db.open_tree("static-dag-blobs")?;
  450. let dag_blobs = sled_db.open_tree("dag-blobs")?;
  451. // Historical-roots side-tables. See the design comment on
  452. // `EventGraph::apply_rln_static_event` for the full rationale.
  453. // In short: every static-DAG mutation produces a new SMT root,
  454. // and we need to recognize *any* historical root for sync-time
  455. // signal verification, not just the most recent N. The
  456. // `ordered` tree gives us canonical replay (and successor
  457. // lookup for the time-window check), the `by_value` tree
  458. // gives us O(log n) "is this root historical?" queries.
  459. let rln_historical_roots_ordered = sled_db.open_tree("rln-historical-roots-ordered")?;
  460. let rln_historical_roots_by_value = sled_db.open_tree("rln-historical-roots-by-value")?;
  461. // Check whether the current genesis event is already in the
  462. // store. If not, we need to prune (create a fresh slot).
  463. let dag_ts = current_genesis.header.timestamp;
  464. let need_prune = dag_store
  465. .get_slot(&dag_ts)
  466. .map(|s| !s.main_tree.contains_key(current_genesis.id().as_bytes()).unwrap_or(false))
  467. .unwrap_or(true);
  468. let self_ = Arc::new(Self {
  469. p2p,
  470. sled_db: sled_db.clone(),
  471. dag_store: RwLock::new(dag_store),
  472. static_dag,
  473. static_dag_blobs,
  474. dag_blobs,
  475. rln_historical_roots_ordered,
  476. rln_historical_roots_by_value,
  477. datastore,
  478. replay_mode,
  479. broadcasted_ids: RwLock::new(HashSet::new()),
  480. prune_task: OnceCell::new(),
  481. event_pub: Publisher::new(),
  482. static_pub: Publisher::new(),
  483. current_genesis: RwLock::new(current_genesis.clone()),
  484. config: config.clone(),
  485. synced: AtomicBool::new(false),
  486. deg_enabled: AtomicBool::new(false),
  487. deg_publisher: Publisher::new(),
  488. zk_keys,
  489. identity_state: RwLock::new(identity_state),
  490. rln_state: RwLock::new(RlnState::new()),
  491. rln_app_id,
  492. });
  493. if need_prune {
  494. info!(
  495. target: "event_graph::new",
  496. "[EVENTGRAPH] Pruning: current genesis not found",
  497. );
  498. self_.dag_prune(current_genesis).await?;
  499. }
  500. // Consistency check: if the static DAG has events but the
  501. // historical-roots tables are empty, rebuild them by
  502. // replaying the static DAG in canonical order. This handles
  503. // the case where the operator manually deleted the
  504. // historical-roots trees, or where this is the first startup
  505. // after upgrading from a version that didn't track them.
  506. //
  507. // Without this, signal verification would fail for any root
  508. // beyond the in-memory `recent_roots` window.
  509. self_.rebuild_historical_roots_if_needed().await?;
  510. if config.hours_rotation > 0 {
  511. let task = StoppableTask::new();
  512. let _ = self_.prune_task.set(task.clone()).await;
  513. task.clone().start(
  514. self_.clone().dag_prune_task(),
  515. |res| async move {
  516. if let Err(e) = res {
  517. if !matches!(e, Error::DetachedTaskStopped) {
  518. error!("Prune: {e}");
  519. }
  520. }
  521. },
  522. Error::DetachedTaskStopped,
  523. ex,
  524. );
  525. }
  526. Ok(self_)
  527. }
  528. /// Rebuild the historical-roots side-tables from the static DAG.
  529. ///
  530. /// Called once at startup. No-op if the historical-roots tables
  531. /// already match the static-DAG event count. Otherwise replays
  532. /// every static-DAG event in canonical `(layer, event_id)` order
  533. /// and re-records the post-mutation root for each one.
  534. ///
  535. /// **Side effect.** Resets the in-memory SMT to empty, then
  536. /// rebuilds it leaf-by-leaf in canonical order, so the SMT and
  537. /// the historical-roots tables come out consistent. The
  538. /// `rln-identity-leaves` tree (which `IdentityState::new`
  539. /// originally read) is implicitly re-derived; we don't read it
  540. /// during rebuild because we want to honor any slashes in the
  541. /// static DAG even if the leaves tree is stale.
  542. async fn rebuild_historical_roots_if_needed(self: &Arc<Self>) -> Result<()> {
  543. // Walk the static DAG once, computing both:
  544. // * static_count: total non-genesis events
  545. // * expected_leaves: registrations - slashes (the number
  546. // of identities that should currently be in the SMT)
  547. // We need the second one to detect a state where leaves and
  548. // historical-roots happen to share counts but the leaves
  549. // don't actually correspond to the static-DAG events. That
  550. // can happen across schema changes or when older code paths
  551. // wrote to leaves without going through `apply_rln_static_event`.
  552. let mut static_count: u64 = 0;
  553. let mut registrations: i64 = 0;
  554. let mut slashes: i64 = 0;
  555. for item in self.static_dag.iter() {
  556. let (_, val) = item?;
  557. let ev: Event = deserialize_async(&val).await?;
  558. if ev.header.parents == NULL_PARENTS {
  559. continue
  560. }
  561. static_count += 1;
  562. // Try to classify this event. We tolerate failed parses
  563. // here because the rebuild path is best-effort: if an
  564. // event's content is unparseable, we just don't count it
  565. // toward expected_leaves. The replay loop below skips
  566. // it for the same reason.
  567. if let Ok((node, _)) = deserialize_async_partial::<rln::RLNNode>(ev.content()).await {
  568. match node {
  569. rln::RLNNode::Registration(_) => registrations += 1,
  570. rln::RLNNode::Slashing(_) => slashes += 1,
  571. }
  572. }
  573. }
  574. let expected_leaves = (registrations - slashes).max(0) as usize;
  575. let recorded_count = self.rln_historical_roots_ordered.len() as u64;
  576. let actual_leaves = self.identity_state.read().await.leaves_count();
  577. let counts_consistent = recorded_count == static_count;
  578. let leaves_consistent = actual_leaves == expected_leaves;
  579. info!(
  580. target: "event_graph::new",
  581. "[EVENTGRAPH] RLN state audit: static_count={} recorded_count={} \
  582. actual_leaves={} expected_leaves={} consistent={}",
  583. static_count, recorded_count, actual_leaves, expected_leaves,
  584. counts_consistent && leaves_consistent,
  585. );
  586. if counts_consistent && leaves_consistent {
  587. // Already consistent across all three sources (static
  588. // DAG, historical-roots table, leaves tree).
  589. return Ok(())
  590. }
  591. info!(
  592. target: "event_graph::new",
  593. "[EVENTGRAPH] Rebuilding historical-roots: {} static events, \
  594. {} recorded roots, {} leaves (expected {})",
  595. static_count, recorded_count, actual_leaves, expected_leaves,
  596. );
  597. // Reset the historical-roots tables to a known-empty state.
  598. self.rln_historical_roots_ordered.clear()?;
  599. self.rln_historical_roots_by_value.clear()?;
  600. // Reset the in-memory SMT and the leaves tree so the replay
  601. // below builds it correctly from the canonical static-DAG
  602. // sequence (including any slashes).
  603. {
  604. let mut state = self.identity_state.write().await;
  605. state.clear_for_rebuild()?;
  606. }
  607. // Collect static-DAG events and sort canonically.
  608. let mut events: Vec<Event> = vec![];
  609. for item in self.static_dag.iter() {
  610. let (_, val) = item?;
  611. let ev: Event = deserialize_async(&val).await?;
  612. if ev.header.parents != NULL_PARENTS {
  613. events.push(ev);
  614. }
  615. }
  616. events.sort_by(|a, b| {
  617. a.header
  618. .layer
  619. .cmp(&b.header.layer)
  620. .then_with(|| a.id().as_bytes().cmp(b.id().as_bytes()))
  621. });
  622. // Replay each event through the canonical apply path.
  623. for ev in events {
  624. let rln_node: rln::RLNNode = match deserialize_async_partial(ev.content()).await {
  625. Ok((v, _)) => v,
  626. Err(_) => continue,
  627. };
  628. let _ = self.apply_rln_static_event(&ev, &rln_node).await?;
  629. }
  630. info!(
  631. target: "event_graph::new",
  632. "[EVENTGRAPH] Historical-roots rebuild complete",
  633. );
  634. Ok(())
  635. }
  636. /// After header sync, event content can be fetched lazily via
  637. /// [`fetch_page`] or peer [`RangeReq`] - the application pulls
  638. /// the events it actually wants to display or process, without
  639. /// downloading the entire content on every sync.
  640. pub async fn dag_sync_headers(&self, dag_ts: u64) -> Result<()> {
  641. self.sync_impl(dag_ts, false).await
  642. }
  643. /// Full sync: headers plus all event content currently in the DAG.
  644. ///
  645. /// Use this when the application wants the complete historical
  646. /// content (e.g. an archive node, or a node rebuilding local state
  647. /// from the full event stream).
  648. pub async fn dag_sync(&self, dag_ts: u64) -> Result<()> {
  649. self.sync_impl(dag_ts, true).await
  650. }
  651. async fn sync_impl(&self, dag_ts: u64, fetch_content: bool) -> Result<()> {
  652. let dag_name = dag_ts.to_string();
  653. let channels = self.p2p.hosts().peers();
  654. // We need at least one peer to ask
  655. if channels.is_empty() {
  656. return Err(Error::DagSyncFailed)
  657. }
  658. let timeout = self.p2p.settings().read().await.outbound_connect_timeout_max();
  659. // Parallel tip collection
  660. let mut futs = FuturesUnordered::new();
  661. for ch in channels.iter() {
  662. futs.push(request_tips(ch, dag_name.clone(), timeout));
  663. }
  664. let mut tips: HashMap<blake3::Hash, (u64, usize)> = HashMap::new();
  665. let mut responded = 0usize;
  666. while let Some(res) = futs.next().await {
  667. if let Ok(peer_tips) = res {
  668. responded += 1;
  669. for (layer, hashes) in &peer_tips {
  670. for h in hashes {
  671. tips.entry(*h).and_modify(|e| e.1 += 1).or_insert((*layer, 1));
  672. }
  673. }
  674. }
  675. }
  676. if tips.is_empty() {
  677. return Err(Error::DagSyncFailed)
  678. }
  679. // 2/3 quorum
  680. let threshold = (responded * 2).div_ceil(3);
  681. let accepted: HashSet<blake3::Hash> = tips
  682. .iter()
  683. .filter(|(h, (_, n))| **h != NULL_ID && *n >= threshold)
  684. .map(|(h, _)| *h)
  685. .collect();
  686. let store = self.dag_store.read().await;
  687. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  688. let missing: HashSet<blake3::Hash> = accepted
  689. .iter()
  690. .filter(|h| !slot.main_tree.contains_key(h.as_bytes()).unwrap_or(true))
  691. .cloned()
  692. .collect();
  693. if missing.is_empty() {
  694. return Ok(())
  695. }
  696. let our_tips = slot.tips.clone();
  697. drop(store);
  698. // Parallel header sync
  699. let mut hfuts = FuturesUnordered::new();
  700. for ch in channels.iter() {
  701. hfuts.push(request_header(ch, dag_name.clone(), our_tips.clone(), timeout));
  702. }
  703. while let Some(res) = hfuts.next().await {
  704. if let Ok(hdrs) = res {
  705. self.header_dag_insert(hdrs, &dag_name).await?;
  706. }
  707. }
  708. if fetch_content {
  709. self.fetch_missing_events(dag_ts, &dag_name, timeout).await?;
  710. }
  711. Ok(())
  712. }
  713. async fn fetch_missing_events(&self, dag_ts: u64, dag_name: &str, timeout: u64) -> Result<()> {
  714. let store = self.dag_store.read().await;
  715. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  716. let mut sorted = vec![];
  717. for item in slot.header_tree.iter() {
  718. let (hb, val) = item.unwrap();
  719. let hdr: Header = deserialize_async(&val).await.unwrap();
  720. if hdr.parents != NULL_PARENTS && !slot.main_tree.contains_key(hb)? {
  721. sorted.push(hdr);
  722. }
  723. }
  724. sorted.sort_by_key(|h| h.layer);
  725. drop(store);
  726. if sorted.is_empty() {
  727. return Ok(())
  728. }
  729. let batch = 20;
  730. let mut chunks: BTreeMap<usize, Vec<blake3::Hash>> = BTreeMap::new();
  731. for (i, c) in sorted.chunks(batch).enumerate() {
  732. chunks.insert(i, c.iter().map(|h| h.id()).collect());
  733. }
  734. let mut remaining: BTreeSet<usize> = chunks.keys().cloned().collect();
  735. let mut peer_st: HashMap<Url, PeerStatus> = HashMap::new();
  736. let mut count = 0;
  737. let mut fs = FuturesUnordered::new();
  738. // Each received chunk is (events, blobs) - blobs aligned
  739. // index-wise with events. Empty `Vec<u8>` entries mean
  740. // "this event has no blob from the serving peer".
  741. let mut received: BTreeMap<usize, (Vec<Event>, Vec<Vec<u8>>)> = BTreeMap::new();
  742. while count < sorted.len() {
  743. let mut free = vec![];
  744. let mut busy = 0;
  745. self.p2p.hosts().peers().iter().for_each(|ch| match peer_st.get(ch.address()) {
  746. Some(PeerStatus::Free) | None => {
  747. free.push(ch.clone());
  748. }
  749. Some(PeerStatus::Busy) => {
  750. busy += 1;
  751. }
  752. _ => {}
  753. });
  754. if free.is_empty() && busy == 0 {
  755. return Err(Error::DagSyncFailed)
  756. }
  757. let n = std::cmp::min(free.len(), remaining.len());
  758. let ids: Vec<usize> = remaining.iter().take(n).copied().collect();
  759. for (i, cid) in ids.iter().enumerate() {
  760. fs.push(request_event(free[i].clone(), chunks[cid].clone(), *cid, timeout));
  761. remaining.remove(cid);
  762. peer_st.insert(free[i].address().clone(), PeerStatus::Busy);
  763. }
  764. if let Some((evts, cid, ch)) = fs.next().await {
  765. if let Ok((e, blobs)) = evts {
  766. count += e.len();
  767. received.insert(cid, (e, blobs));
  768. peer_st.insert(ch.address().clone(), PeerStatus::Free);
  769. } else {
  770. remaining.insert(cid);
  771. peer_st.insert(ch.address().clone(), PeerStatus::Failed);
  772. }
  773. }
  774. }
  775. for (_, (events_chunk, blobs_chunk)) in received {
  776. // dag_insert_with_blobs handles RLN re-verification per
  777. // event when blobs are present, and falls through to the
  778. // trust-the-quorum path when they're not. See
  779. // dag_insert_with_blobs's docstring for the policy.
  780. self.dag_insert_with_blobs(&events_chunk, &blobs_chunk, dag_name).await?;
  781. }
  782. Ok(())
  783. }
  784. /// Sync the `count` most recent DAGs (full content).
  785. ///
  786. /// Iterates oldest-first so that later syncs build on earlier
  787. /// ones (parent events exist before children reference them).
  788. pub async fn sync_selected(&self, count: usize) -> Result<()> {
  789. let ts: Vec<u64> =
  790. self.dag_store.read().await.dag_timestamps().into_iter().rev().take(count).collect();
  791. for t in ts.into_iter().rev() {
  792. self.dag_sync(t).await?;
  793. }
  794. self.synced.store(true, Ordering::Release);
  795. Ok(())
  796. }
  797. /// Sync only headers for the `count` most recent DAGs.
  798. ///
  799. /// Fast variant - gives a full DAG skeleton without downloading
  800. /// event bodies. Pair with [`fetch_page`] to pull content on-demand.
  801. pub async fn sync_selected_headers(&self, count: usize) -> Result<()> {
  802. let ts: Vec<u64> =
  803. self.dag_store.read().await.dag_timestamps().into_iter().rev().take(count).collect();
  804. for t in ts.into_iter().rev() {
  805. self.dag_sync_headers(t).await?;
  806. }
  807. self.synced.store(true, Ordering::Release);
  808. Ok(())
  809. }
  810. /// Sync the static DAG from peers.
  811. ///
  812. /// The static DAG holds RLN identity events (registrations and
  813. /// slashes). It is *persistent* across rotation windows - unlike
  814. /// rotating DAGs, events are never pruned - and has no separate
  815. /// `header_tree`, so it uses a different sync strategy:
  816. ///
  817. /// 1. Ask every peer for their `"static-dag"` tips.
  818. /// 2. Take the tips that reach a 2/3 quorum.
  819. /// 3. BFS-fetch the events and their ancestors directly via
  820. /// `EventReq` until the entire reachable subgraph is local.
  821. ///
  822. /// Peers serve static-DAG event requests even when the IDs are
  823. /// not in their `broadcasted_ids` set (see the relaxation in
  824. /// `handle_event_req`), because static-DAG state is public
  825. /// consensus information. Registration-event proof verification,
  826. /// duplicate detection, and commitment-tree updates are all done
  827. /// through the normal `StaticPut` ingestion path
  828. /// (`handle_static_put`) - but `static_sync` uses direct-insert
  829. /// via [`Self::static_insert`] plus on-the-fly identity-state
  830. /// application, because we're catching up rather than processing
  831. /// broadcasts.
  832. ///
  833. /// Note: for security, this method ONLY applies events whose
  834. /// blob/RLN verification passes. We do not trust peers blindly
  835. /// on historical state - proofs are re-verified locally for
  836. /// every single event before its effect is merged into the
  837. /// identity tree. This is the same discipline `handle_static_put`
  838. /// uses; see [`Self::rln_verify_static_event`].
  839. pub async fn static_sync(&self) -> Result<()> {
  840. static DAG_NAME: &str = "static-dag";
  841. let channels = self.p2p.hosts().peers();
  842. if channels.is_empty() {
  843. return Err(Error::DagSyncFailed)
  844. }
  845. let timeout = self.p2p.settings().read().await.outbound_connect_timeout_max();
  846. // Step 1: gather tips from every peer in parallel.
  847. let mut tip_futs = FuturesUnordered::new();
  848. for ch in channels.iter() {
  849. tip_futs.push(request_tips(ch, DAG_NAME.to_string(), timeout));
  850. }
  851. let mut tip_counts: HashMap<blake3::Hash, usize> = HashMap::new();
  852. let mut responded = 0usize;
  853. while let Some(res) = tip_futs.next().await {
  854. if let Ok(peer_tips) = res {
  855. responded += 1;
  856. for hashes in peer_tips.values() {
  857. for h in hashes {
  858. *tip_counts.entry(*h).or_insert(0) += 1;
  859. }
  860. }
  861. }
  862. }
  863. // If no peer answered we have nothing to do. An empty
  864. // network-side static DAG is a valid state (brand new app
  865. // deployment), so we return Ok rather than error.
  866. if responded == 0 {
  867. info!(
  868. target: "event_graph::static_sync",
  869. "[STATIC_SYNC] no peer responded to TipReq; nothing to sync"
  870. );
  871. return Ok(())
  872. }
  873. // Step 2: take tips at 2/3 quorum. This matches the
  874. // threshold used in `sync_impl`.
  875. let threshold = (responded * 2).div_ceil(3);
  876. let total_distinct_tips = tip_counts.len();
  877. let tip_ids: HashSet<blake3::Hash> = tip_counts
  878. .into_iter()
  879. .filter(|(h, n)| *h != NULL_ID && *n >= threshold)
  880. .map(|(h, _)| h)
  881. .collect();
  882. // What's already local?
  883. let mut known: HashSet<blake3::Hash> = HashSet::new();
  884. for item in self.static_dag.iter() {
  885. let (k, _) = item?;
  886. if let Ok(bytes) = <[u8; 32]>::try_from(&k as &[u8]) {
  887. known.insert(blake3::Hash::from_bytes(bytes));
  888. }
  889. }
  890. info!(
  891. target: "event_graph::static_sync",
  892. "[STATIC_SYNC] peers_responded={} threshold={} distinct_tips_seen={} \
  893. tip_ids_quorum={} known_local={}",
  894. responded, threshold, total_distinct_tips, tip_ids.len(), known.len(),
  895. );
  896. // Step 3: BFS from the quorum tips, fetching events we
  897. // don't have. Any event we pull in may reference ancestors
  898. // we ALSO don't have; enqueue them and keep going until the
  899. // frontier is empty.
  900. //
  901. // Bounded at SYNC_MAX_STATIC_EVENTS (defined at module level)
  902. // to defend against a malicious peer who serves a fabricated
  903. // deep-ancestry chain. In practice static DAGs are small (one
  904. // event per registration / slash), so this bound is
  905. // comfortably above any real deployment's size.
  906. let mut want: HashSet<blake3::Hash> = tip_ids.difference(&known).copied().collect();
  907. // Events fetched during BFS, paired with their blobs (empty
  908. // Vec if the peer didn't have the blob - see EventRep
  909. // docstring). Index alignment is preserved through the
  910. // entire pipeline up to the apply loop.
  911. let mut fetched: Vec<(Event, Vec<u8>)> = vec![];
  912. while !want.is_empty() {
  913. if fetched.len() >= SYNC_MAX_STATIC_EVENTS {
  914. error!(
  915. target: "event_graph::static_sync",
  916. "[STATIC_SYNC] reached {} event cap; aborting",
  917. SYNC_MAX_STATIC_EVENTS,
  918. );
  919. return Err(Error::DagSyncFailed)
  920. }
  921. let batch: Vec<blake3::Hash> = want.iter().copied().collect();
  922. want.clear();
  923. // Race the batch against all peers; first to respond
  924. // with valid events wins. A peer that returns events we
  925. // didn't ask for is striked via its protocol handler,
  926. // not here - this is a best-effort pull.
  927. let mut req_futs = FuturesUnordered::new();
  928. for (i, ch) in channels.iter().enumerate() {
  929. req_futs.push(request_event(ch.clone(), batch.clone(), i, timeout));
  930. }
  931. let mut got_any = false;
  932. while let Some((res, _, _)) = req_futs.next().await {
  933. let Ok((evs, blobs)) = res else { continue };
  934. if evs.is_empty() {
  935. continue
  936. }
  937. got_any = true;
  938. for (i, ev) in evs.into_iter().enumerate() {
  939. let eid = ev.id();
  940. if !batch.contains(&eid) {
  941. // Peer sent something we didn't ask for;
  942. // ignore the rest of this reply.
  943. break
  944. }
  945. if known.insert(eid) {
  946. // New parents to chase next round.
  947. for p in ev.header.parents.iter() {
  948. if *p != NULL_ID && !known.contains(p) {
  949. want.insert(*p);
  950. }
  951. }
  952. // Pair the event with its blob (or empty if
  953. // the peer didn't supply one - that's not an
  954. // error, see EventRep doc and the fall-through
  955. // in the apply loop below).
  956. let blob = blobs.get(i).cloned().unwrap_or_default();
  957. fetched.push((ev, blob));
  958. }
  959. }
  960. break
  961. }
  962. if !got_any {
  963. // Nobody responded usefully. Give up so we don't
  964. // loop forever on an unreachable ancestor.
  965. error!(
  966. target: "event_graph::static_sync",
  967. "[STATIC_SYNC] no peer served requested events; aborting",
  968. );
  969. return Err(Error::DagSyncFailed)
  970. }
  971. }
  972. // Step 4: canonical-order the fetched events so all nodes
  973. // produce the same intermediate SMT roots. Primary key:
  974. // layer (matches DAG topology). Secondary key: event_id
  975. // (32-byte hash, lexicographic byte order is total). Without
  976. // the tie-breaker, two events at the same layer could be
  977. // applied in different orders on different nodes, producing
  978. // different intermediate roots and breaking sync-time signal
  979. // verification. See the design comment on
  980. // `apply_rln_static_event` for the full rationale.
  981. fetched.sort_by(|(a, _), (b, _)| {
  982. a.header
  983. .layer
  984. .cmp(&b.header.layer)
  985. .then_with(|| a.id().as_bytes().cmp(b.id().as_bytes()))
  986. });
  987. // Track the apply-loop outcome for the summary log.
  988. let mut applied = 0usize;
  989. let mut already_present = 0usize;
  990. let mut blob_missing = 0usize;
  991. let mut rejected = 0usize;
  992. let mut structural_invalid = 0usize;
  993. let mut content_unparseable = 0usize;
  994. let total_to_consider = fetched.len();
  995. for (ev, blob) in fetched {
  996. // Skip if someone else inserted it concurrently.
  997. if self.static_dag.contains_key(ev.id().as_bytes())? {
  998. already_present += 1;
  999. continue
  1000. }
  1001. // Structural validation always runs. Static-DAG events
  1002. // are persistent and may be far older than the 60s drift
  1003. // window allowed by `validate_new`; use the static
  1004. // sibling that omits the freshness check while keeping
  1005. // the structural ones.
  1006. if !ev.validate_new_static() {
  1007. structural_invalid += 1;
  1008. continue
  1009. }
  1010. let rln_node: rln::RLNNode = match deserialize_async_partial(ev.content()).await {
  1011. Ok((v, _)) => v,
  1012. Err(_) => {
  1013. content_unparseable += 1;
  1014. continue
  1015. }
  1016. };
  1017. // RLN verification is mandatory. A non-genesis static
  1018. // event without a blob during sync is treated as
  1019. // misbehavior: either the serving peer is buggy or
  1020. // adversarial, or the originator never persisted the blob
  1021. // (which itself is a protocol violation). Skip with a
  1022. // loud log - we don't strike here because static_sync
  1023. // doesn't have a single peer to attribute the failure
  1024. // to (the quorum collected blobs from multiple peers).
  1025. if blob.is_empty() {
  1026. blob_missing += 1;
  1027. error!(
  1028. target: "event_graph::static_sync",
  1029. "[STATIC_SYNC] no blob available for static event {}; skipping. \
  1030. Every static-DAG event must carry an RLN blob.",
  1031. ev.id(),
  1032. );
  1033. continue
  1034. }
  1035. let outcome = self.rln_verify_static_event(&rln_node, &blob, ev.header.timestamp).await;
  1036. match outcome {
  1037. rln::StaticEventCheck::AcceptedRegistration(_) |
  1038. rln::StaticEventCheck::AcceptedSlash(_) => {
  1039. // apply_rln_static_event handles both Registration
  1040. // and Slashing branches and also records the
  1041. // post-mutation root in the historical-roots
  1042. // side-tables.
  1043. let _ = self.apply_rln_static_event(&ev, &rln_node).await;
  1044. self.static_blob_store(&ev.id(), &blob)?;
  1045. self.static_insert(&ev).await?;
  1046. applied += 1;
  1047. }
  1048. rln::StaticEventCheck::Rejected | rln::StaticEventCheck::Malicious => {
  1049. // A historical event whose blob fails
  1050. // re-verification despite being held by the 2/3
  1051. // quorum is a serious finding - either the blob
  1052. // was tampered with, the quorum was compromised,
  1053. // or our verifying keys diverged. Log loudly and
  1054. // skip.
  1055. rejected += 1;
  1056. error!(
  1057. target: "event_graph::static_sync",
  1058. "[STATIC_SYNC] historical blob FAILED re-verification for event {}: {:?}; \
  1059. skipping event despite quorum inclusion",
  1060. ev.id(),
  1061. outcome,
  1062. );
  1063. }
  1064. }
  1065. }
  1066. info!(
  1067. target: "event_graph::static_sync",
  1068. "[STATIC_SYNC] complete: fetched={} applied={} already_present={} \
  1069. blob_missing={} verification_rejected={} structural_invalid={} \
  1070. unparseable={}",
  1071. total_to_consider, applied, already_present, blob_missing, rejected,
  1072. structural_invalid, content_unparseable,
  1073. );
  1074. Ok(())
  1075. }
  1076. /// Fetch a page of events, crossing DAG boundaries transparently.
  1077. pub async fn fetch_page(
  1078. &self,
  1079. cursor_ts: u64,
  1080. dir: SyncDirection,
  1081. limit: usize,
  1082. ) -> Result<Vec<Event>> {
  1083. let mut out = vec![];
  1084. let store = self.dag_store.read().await;
  1085. let slots: Vec<_> = match dir {
  1086. SyncDirection::Forward => store.dags.iter().collect(),
  1087. SyncDirection::Backward => store.dags.iter().rev().collect(),
  1088. };
  1089. for (_, slot) in slots {
  1090. if out.len() >= limit {
  1091. break
  1092. }
  1093. let rem = limit - out.len();
  1094. let ids = match dir {
  1095. SyncDirection::Forward => slot.time_index.after(cursor_ts, rem),
  1096. SyncDirection::Backward => slot.time_index.before(cursor_ts, rem),
  1097. };
  1098. for id in ids {
  1099. if let Some(bytes) = slot.main_tree.get(id.as_bytes())? {
  1100. out.push(deserialize_async(&bytes).await?);
  1101. }
  1102. }
  1103. }
  1104. out.truncate(limit);
  1105. Ok(out)
  1106. }
  1107. async fn dag_prune(&self, genesis: Event) -> Result<()> {
  1108. let mut bcast = self.broadcasted_ids.write().await;
  1109. let mut cur = self.current_genesis.write().await;
  1110. // Before the DAG store evicts the oldest DAG (which would
  1111. // drop its main_tree), enumerate the about-to-be-dropped
  1112. // event IDs so we can remove their blobs from `dag_blobs`.
  1113. // Without this, blob entries would orphan and accumulate
  1114. // forever - the side-table is not bounded by the rotation
  1115. // window on its own.
  1116. if let Some(limit) = self.config.max_dags {
  1117. let store = self.dag_store.read().await;
  1118. if store.dags.len() >= limit {
  1119. if let Some((_, oldest)) = store.dags.iter().next() {
  1120. for item in oldest.main_tree.iter() {
  1121. let (eid, _) = match item {
  1122. Ok(v) => v,
  1123. Err(_) => continue,
  1124. };
  1125. let _ = self.dag_blobs.remove(&eid);
  1126. }
  1127. }
  1128. }
  1129. }
  1130. self.dag_store.write().await.add_dag(&genesis, self.config.max_dags).await;
  1131. *cur = genesis;
  1132. *bcast = HashSet::new();
  1133. Ok(())
  1134. }
  1135. async fn dag_prune_task(self: Arc<Self>) -> Result<()> {
  1136. loop {
  1137. let next =
  1138. next_rotation_timestamp(self.config.initial_genesis, self.config.hours_rotation);
  1139. let hdr = Header {
  1140. timestamp: next,
  1141. parents: NULL_PARENTS,
  1142. layer: 0,
  1143. content_hash: blake3::hash(&self.config.genesis_contents),
  1144. };
  1145. let genesis = Event { header: hdr, content: self.config.genesis_contents.clone() };
  1146. msleep(millis_until_next_rotation(next)).await;
  1147. self.dag_prune(genesis).await?;
  1148. }
  1149. }
  1150. /// Insert events into a rotating DAG **without RLN verification**.
  1151. ///
  1152. /// This is the post-verification entry point for callers that
  1153. /// have already verified the proof separately. Two legitimate
  1154. /// callers in production:
  1155. ///
  1156. /// * `handle_event_put` - already ran `rln_verify_signal` and
  1157. /// recorded the share. Calling `dag_insert_with_blobs` would
  1158. /// trigger the duplicate-share rejection.
  1159. /// * The IRC client's own outbound flow - same shape.
  1160. pub async fn dag_insert(&self, events: &[Event], dag_name: &str) -> Result<Vec<blake3::Hash>> {
  1161. // Implementation just runs the structural-insert path;
  1162. // dag_insert_with_blobs reaches the same shared inner code
  1163. // when called with a `skip_verify=true` shortcut, which is
  1164. // what an empty `blobs` slice now means after the strictness
  1165. // tightening below - but only via this private wrapper.
  1166. self.dag_insert_inner(events, &[], /* require_blobs */ false, dag_name).await
  1167. }
  1168. /// Insert events into a rotating DAG, with mandatory RLN
  1169. /// verification.
  1170. ///
  1171. /// `blobs` is index-aligned with `events`. Every non-genesis
  1172. /// event MUST have a non-empty `blobs[i]`; events that don't
  1173. /// (whether `blobs` is empty, shorter, or has an empty entry
  1174. /// at position `i`) are rejected with a loud log. This is the
  1175. /// strict policy required for sync paths - a peer that serves
  1176. /// an event without its blob is buggy or adversarial.
  1177. ///
  1178. /// On `Slashable`, this method does NOT broadcast a slash -
  1179. /// that's the protocol layer's job (see
  1180. /// `proto::handle_event_put::verify_rln_signal`). Sync-time
  1181. /// detection of a slashable conflict simply skips the event.
  1182. /// We don't want a node coming online to flood the network
  1183. /// with stale slash broadcasts.
  1184. pub async fn dag_insert_with_blobs(
  1185. &self,
  1186. events: &[Event],
  1187. blobs: &[Vec<u8>],
  1188. dag_name: &str,
  1189. ) -> Result<Vec<blake3::Hash>> {
  1190. self.dag_insert_inner(events, blobs, /* require_blobs */ true, dag_name).await
  1191. }
  1192. /// Inner implementation shared by both insert paths. The
  1193. /// `require_blobs` flag selects strict (sync) vs. lenient
  1194. /// (post-verified) semantics.
  1195. async fn dag_insert_inner(
  1196. &self,
  1197. events: &[Event],
  1198. blobs: &[Vec<u8>],
  1199. require_blobs: bool,
  1200. dag_name: &str,
  1201. ) -> Result<Vec<blake3::Hash>> {
  1202. if events.is_empty() {
  1203. return Ok(vec![])
  1204. }
  1205. // Pre-flight RLN verification. Done BEFORE acquiring the
  1206. // DAG-store write lock so a slow proof verification doesn't
  1207. // hold up other inserts.
  1208. //
  1209. // Events we already have are skipped without verification.
  1210. // This matters because `rln_verify_signal` records the share
  1211. // on `Accepted`, and re-running it for an already-seen event
  1212. // would trip its duplicate-share check (returning `Rejected`)
  1213. // - which would be incorrect: the event is legitimate, we
  1214. // just already know about it.
  1215. let dag_ts = u64::from_str(dag_name)?;
  1216. let already_have: Vec<bool> = {
  1217. let store = self.dag_store.read().await;
  1218. let slot = store.get_slot(&dag_ts);
  1219. events
  1220. .iter()
  1221. .map(|ev| match slot {
  1222. Some(s) => s.main_tree.contains_key(ev.id().as_bytes()).unwrap_or(false),
  1223. None => false,
  1224. })
  1225. .collect()
  1226. };
  1227. let mut accepted: Vec<usize> = Vec::with_capacity(events.len());
  1228. for (i, ev) in events.iter().enumerate() {
  1229. // Already-known events go through structurally (the
  1230. // downstream `contains_key` check will skip them) but
  1231. // skip the RLN verifier to avoid double-recording the
  1232. // share for the same (epoch, internal_nullifier, x, y)
  1233. // tuple.
  1234. if already_have[i] {
  1235. accepted.push(i);
  1236. continue
  1237. }
  1238. // Genesis-shaped events have no blob and no proof -
  1239. // they're consensus inputs, not user signals.
  1240. if ev.header.parents == NULL_PARENTS {
  1241. accepted.push(i);
  1242. continue
  1243. }
  1244. let blob = blobs.get(i).cloned().unwrap_or_default();
  1245. if blob.is_empty() {
  1246. if require_blobs {
  1247. error!(
  1248. target: "event_graph::dag_insert",
  1249. "[DAG_INSERT] sync event {} arrived without an RLN blob; rejecting. \
  1250. Every non-genesis rotating-DAG event must carry a blob.",
  1251. ev.id(),
  1252. );
  1253. continue
  1254. }
  1255. // Lenient path: caller pre-verified. Accept the
  1256. // event structurally without running the RLN
  1257. // verifier on it.
  1258. accepted.push(i);
  1259. continue
  1260. }
  1261. match self.rln_verify_signal(ev, &blob).await {
  1262. rln::SignalCheck::Accepted => accepted.push(i),
  1263. rln::SignalCheck::Rejected => {
  1264. error!(
  1265. target: "event_graph::dag_insert",
  1266. "[DAG_INSERT] sync event {} failed RLN re-verification; skipping",
  1267. ev.id(),
  1268. );
  1269. }
  1270. rln::SignalCheck::Slashable(_) => {
  1271. // The conflicting share is recorded inside
  1272. // `rln_verify_signal` ONLY on `Accepted`. On
  1273. // `Slashable` it returns the conflicting shares
  1274. // *without* mutating metadata, so we don't
  1275. // double-record. We don't broadcast a slash
  1276. // here - that's the live broadcast handler's
  1277. // job. We just skip the event.
  1278. error!(
  1279. target: "event_graph::dag_insert",
  1280. "[DAG_INSERT] sync event {} is slashable (slot reuse); skipping",
  1281. ev.id(),
  1282. );
  1283. }
  1284. }
  1285. }
  1286. let mut bcast = self.broadcasted_ids.write().await;
  1287. let mut store = self.dag_store.write().await;
  1288. let slot = store.get_slot_mut(&dag_ts).ok_or(Error::DagSyncFailed)?;
  1289. let mut ids = Vec::with_capacity(accepted.len());
  1290. let mut overlay = SledTreeOverlay::new(&slot.main_tree);
  1291. for &i in &accepted {
  1292. let ev = &events[i];
  1293. let eid = ev.id();
  1294. if ev.header.parents == NULL_PARENTS {
  1295. continue
  1296. }
  1297. if slot.main_tree.contains_key(eid.as_bytes())? {
  1298. continue
  1299. }
  1300. if !slot.header_tree.contains_key(eid.as_bytes())? {
  1301. continue
  1302. }
  1303. if !ev.dag_validate(&slot.header_tree, &self.config).await? {
  1304. return Err(Error::EventIsInvalid)
  1305. }
  1306. let se = serialize_async(ev).await;
  1307. overlay.insert(eid.as_bytes(), &se)?;
  1308. if self.replay_mode {
  1309. replayer_log(&self.datastore, "insert".into(), se)?;
  1310. }
  1311. // Persist the blob alongside the event for future
  1312. // sync-time re-verification by other late-joiners.
  1313. if let Some(blob) = blobs.get(i) {
  1314. if !blob.is_empty() {
  1315. let _ = self.dag_blob_store(&eid, blob);
  1316. }
  1317. }
  1318. ids.push(eid);
  1319. }
  1320. if let Some(b) = overlay.aggregate() {
  1321. slot.main_tree.apply_batch(b).unwrap();
  1322. } else {
  1323. return Ok(vec![])
  1324. }
  1325. for &i in &accepted {
  1326. let ev = &events[i];
  1327. let eid = ev.id();
  1328. if ev.header.parents == NULL_PARENTS {
  1329. continue
  1330. }
  1331. for pid in ev.header.parents.iter() {
  1332. if *pid != NULL_ID {
  1333. for (layer, tips) in slot.tips.iter_mut() {
  1334. if *layer < ev.header.layer {
  1335. tips.remove(pid);
  1336. }
  1337. }
  1338. bcast.insert(*pid);
  1339. }
  1340. }
  1341. slot.tips.retain(|_, t| !t.is_empty());
  1342. slot.tips.entry(ev.header.layer).or_default().insert(eid);
  1343. self.event_pub.notify(ev.clone()).await;
  1344. }
  1345. Ok(ids)
  1346. }
  1347. pub async fn header_dag_insert(&self, headers: Vec<Header>, dag_name: &str) -> Result<()> {
  1348. let dag_ts = u64::from_str(dag_name)?;
  1349. // The genesis ID we expect any layer-1 header in this slot
  1350. // to reference. Computed locally from config - two networks
  1351. // with different `genesis_contents` (or any other config
  1352. // mismatch) produce different genesis ids, so a peer whose
  1353. // layer-1 headers reference something else is on a different
  1354. // network. Catching this explicitly here is strictly a
  1355. // defense-in-depth and diagnostics improvement: the existing
  1356. // parent-existence check in `Header::validate` already
  1357. // rejects these (genesis headers are filtered from
  1358. // `header_tree` on insert, so a foreign genesis id never
  1359. // lands in the local tree). The explicit boundary check just
  1360. // turns "HeaderIsInvalid" into a logged, named condition, so
  1361. // an operator debugging a misconfigured deployment sees
  1362. // "peer is on a different network" instead of a generic
  1363. // header rejection.
  1364. //
  1365. // Why layer 1 is sufficient: `select_parents_from_tips` puts
  1366. // an event at layer N+1 where N is the highest layer with
  1367. // tips. For layer = 1, the highest tip layer must be 0, and
  1368. // the only layer-0 entry in any slot is the genesis (the
  1369. // single event placed by `DagStore::create_slot`). So every
  1370. // layer-1 event's non-NULL parents are equal to that slot's
  1371. // genesis id. Higher layers don't need the check because
  1372. // their parent chains transitively pass through layer 1; if
  1373. // the layer-1 events get rejected, layer-2+ events lose
  1374. // their referenced parents and fail the existing parent-
  1375. // existence check.
  1376. let local_genesis_id = Header {
  1377. timestamp: dag_ts,
  1378. parents: NULL_PARENTS,
  1379. layer: 0,
  1380. content_hash: blake3::hash(&self.config.genesis_contents),
  1381. }
  1382. .id();
  1383. let mut store = self.dag_store.write().await;
  1384. let slot = store.get_slot_mut(&dag_ts).ok_or(Error::DagSyncFailed)?;
  1385. let mut overlay = SledTreeOverlay::new(&slot.header_tree);
  1386. let mut hdrs = headers;
  1387. hdrs.sort_by_key(|h| h.layer);
  1388. for hdr in &hdrs {
  1389. if hdr.parents == NULL_PARENTS {
  1390. continue
  1391. }
  1392. // Cross-network detection at the layer-1 boundary.
  1393. if hdr.layer == 1 {
  1394. for pid in hdr.parents.iter() {
  1395. if *pid != NULL_ID && *pid != local_genesis_id {
  1396. error!(
  1397. target: "event_graph::header_dag_insert",
  1398. "[HEADER_DAG_INSERT] layer-1 header for dag {dag_ts} \
  1399. references foreign genesis: claimed parent {pid:?}, \
  1400. local genesis is {local_genesis_id:?}. Peer is on a \
  1401. different network.",
  1402. );
  1403. return Err(Error::HeaderIsInvalid)
  1404. }
  1405. }
  1406. }
  1407. let hid = hdr.id();
  1408. if !hdr.validate(&slot.header_tree, &self.config, Some(&overlay)).await? {
  1409. return Err(Error::HeaderIsInvalid)
  1410. }
  1411. overlay.insert(hid.as_bytes(), &serialize_async(hdr).await)?;
  1412. slot.time_index.insert(hdr.timestamp, hid);
  1413. }
  1414. if let Some(b) = overlay.aggregate() {
  1415. slot.header_tree.apply_batch(b).unwrap();
  1416. }
  1417. Ok(())
  1418. }
  1419. pub async fn fetch_event_from_dags(&self, eid: &blake3::Hash) -> Result<Option<Event>> {
  1420. for (_, slot) in self.dag_store.read().await.dags.iter() {
  1421. if let Some(b) = slot.main_tree.get(eid.as_bytes())? {
  1422. return Ok(Some(deserialize_async(&b).await?))
  1423. }
  1424. }
  1425. // Also check the static DAG. Static events (RLN registrations
  1426. // and slashes) are public consensus state, so they're served
  1427. // alongside rotating-DAG events through the same EventReq
  1428. // path. This is what lets a fresh peer's `static_sync` walk
  1429. // ancestry through EventReq after discovering tips.
  1430. if let Some(b) = self.static_dag.get(eid.as_bytes())? {
  1431. return Ok(Some(deserialize_async(&b).await?))
  1432. }
  1433. Ok(None)
  1434. }
  1435. pub(crate) async fn get_next_layer_with_parents(
  1436. &self,
  1437. dag_ts: &u64,
  1438. ) -> (u64, [blake3::Hash; N_EVENT_PARENTS]) {
  1439. select_parents_from_tips(&self.dag_store.read().await.get_slot(dag_ts).unwrap().tips)
  1440. }
  1441. pub(crate) async fn get_next_layer_with_parents_static(
  1442. &self,
  1443. ) -> (u64, [blake3::Hash; N_EVENT_PARENTS]) {
  1444. select_parents_from_tips(&compute_unreferenced_tips(&self.static_dag).await)
  1445. }
  1446. pub async fn order_events(&self) -> Vec<Event> {
  1447. let mut all = vec![];
  1448. for (_, slot) in self.dag_store.read().await.dags.iter() {
  1449. for item in slot.main_tree.iter() {
  1450. let (_, b) = item.unwrap();
  1451. let ev: Event = deserialize_async(&b).await.unwrap();
  1452. if ev.header.parents != NULL_PARENTS {
  1453. all.push(ev);
  1454. }
  1455. }
  1456. }
  1457. all.sort_unstable_by(display_order);
  1458. all
  1459. }
  1460. pub async fn fetch_headers_with_tips(
  1461. &self,
  1462. dag_name: &str,
  1463. tips: &LayerUTips,
  1464. ) -> Result<Vec<Header>> {
  1465. let dag_ts = u64::from_str(dag_name)?;
  1466. let store = self.dag_store.read().await;
  1467. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  1468. let mut ancestors = HashSet::new();
  1469. for hashes in tips.values() {
  1470. for h in hashes {
  1471. ancestors.insert(*h);
  1472. if let Some(v) = slot.header_tree.get(h.as_bytes())? {
  1473. self.get_ancestors(
  1474. &mut ancestors,
  1475. deserialize_async(&v).await?,
  1476. &slot.header_tree,
  1477. )
  1478. .await?;
  1479. }
  1480. }
  1481. }
  1482. let mut out = vec![];
  1483. for item in slot.header_tree.iter() {
  1484. let (id, v) = item?;
  1485. let h = blake3::Hash::from_bytes((&id as &[u8]).try_into()?);
  1486. if !ancestors.contains(&h) {
  1487. out.push(deserialize_async(&v).await?);
  1488. }
  1489. }
  1490. out.sort_unstable_by_key(|h: &Header| h.layer);
  1491. Ok(out)
  1492. }
  1493. pub(crate) async fn get_ancestors(
  1494. &self,
  1495. visited: &mut HashSet<blake3::Hash>,
  1496. hdr: Header,
  1497. tree: &sled::Tree,
  1498. ) -> Result<()> {
  1499. let mut stack = VecDeque::new();
  1500. stack.push_back(hdr);
  1501. while let Some(h) = stack.pop_back() {
  1502. for p in h.parents {
  1503. if p != NULL_ID && visited.insert(p) {
  1504. if let Some(v) = tree.get(p.as_bytes())? {
  1505. stack.push_back(deserialize_async(&v).await?);
  1506. }
  1507. }
  1508. }
  1509. }
  1510. Ok(())
  1511. }
  1512. async fn static_new(sled_db: &sled::Db, config: &EventGraphConfig) -> Result<sled::Tree> {
  1513. let tree = sled_db.open_tree("static-dag")?;
  1514. let genesis = generate_genesis(&EventGraphConfig { hours_rotation: 0, ..config.clone() });
  1515. let mut ov = SledTreeOverlay::new(&tree);
  1516. ov.insert(genesis.id().as_bytes(), &serialize_async(&genesis).await).unwrap();
  1517. if let Some(b) = ov.aggregate() {
  1518. tree.apply_batch(b).unwrap();
  1519. }
  1520. Ok(tree)
  1521. }
  1522. pub async fn static_broadcast(&self, ev: Event, blob: Vec<u8>) -> Result<()> {
  1523. self.p2p.broadcast(&StaticPut(ev, blob)).await;
  1524. Ok(())
  1525. }
  1526. pub async fn static_insert(&self, ev: &Event) -> Result<()> {
  1527. let mut ov = SledTreeOverlay::new(&self.static_dag);
  1528. ov.insert(ev.id().as_bytes(), &serialize_async(ev).await).unwrap();
  1529. if let Some(b) = ov.aggregate() {
  1530. self.static_dag.apply_batch(b).unwrap();
  1531. }
  1532. self.static_pub.notify(ev.clone()).await;
  1533. Ok(())
  1534. }
  1535. pub async fn static_fetch(&self, eid: &blake3::Hash) -> Result<Option<Event>> {
  1536. Ok(match self.static_dag.get(eid.as_bytes())? {
  1537. Some(b) => Some(deserialize_async(&b).await?),
  1538. None => None,
  1539. })
  1540. }
  1541. pub async fn static_unreferenced_tips(&self) -> LayerUTips {
  1542. compute_unreferenced_tips(&self.static_dag).await
  1543. }
  1544. /// Persist the original RLN blob for a static-DAG event. The
  1545. /// blob is the wire payload from the originating `StaticPut` -
  1546. /// Persist the original RLN blob for a static-DAG event. The
  1547. /// blob is the wire payload from the originating `StaticPut` -
  1548. /// proof + public inputs + attestation - needed to re-verify
  1549. /// the proof at sync time by late-joining peers.
  1550. ///
  1551. /// Writing the same `(eid, blob)` repeatedly is safe.
  1552. pub fn static_blob_store(&self, eid: &blake3::Hash, blob: &[u8]) -> Result<()> {
  1553. self.static_dag_blobs.insert(eid.as_bytes(), blob)?;
  1554. Ok(())
  1555. }
  1556. /// Look up the original RLN blob for a static-DAG event.
  1557. ///
  1558. /// Returns `Ok(None)` only for legitimate "not stored" cases -
  1559. /// a peer that's never seen the event, or an event that pre-dates
  1560. /// the side-table. The verification path in `static_sync` treats
  1561. /// missing blobs on non-genesis events as a sync failure, not as
  1562. /// a fall-through.
  1563. pub fn static_blob_fetch(&self, eid: &blake3::Hash) -> Result<Option<Vec<u8>>> {
  1564. Ok(self.static_dag_blobs.get(eid.as_bytes())?.map(|ivec| ivec.to_vec()))
  1565. }
  1566. /// Persist the original RLN signal blob for a rotating-DAG event.
  1567. ///
  1568. /// Mirror of [`Self::static_blob_store`] but for rotating-DAG
  1569. /// events. Idempotent. Called after successful RLN verification
  1570. /// in `handle_event_put`, and during sync by
  1571. /// `dag_insert_with_blobs` when the peer included a blob in
  1572. /// `EventRep`.
  1573. pub fn dag_blob_store(&self, eid: &blake3::Hash, blob: &[u8]) -> Result<()> {
  1574. self.dag_blobs.insert(eid.as_bytes(), blob)?;
  1575. Ok(())
  1576. }
  1577. /// Look up the original RLN signal blob for a rotating-DAG
  1578. /// event. Returns `Ok(None)` if not stored - see
  1579. /// [`Self::static_blob_fetch`] for the exhaustive list of
  1580. /// reasons a blob may legitimately be missing.
  1581. ///
  1582. /// Note: rotating-DAG blobs are pruned alongside their DAGs
  1583. /// (see `dag_blobs_prune`). Older-than-window events therefore
  1584. /// don't accumulate blobs in this side-table.
  1585. pub fn dag_blob_fetch(&self, eid: &blake3::Hash) -> Result<Option<Vec<u8>>> {
  1586. Ok(self.dag_blobs.get(eid.as_bytes())?.map(|ivec| ivec.to_vec()))
  1587. }
  1588. /// Apply a static-DAG event (registration or slash) to the
  1589. /// identity-state SMT, and record the resulting root in the
  1590. /// historical-roots side-tables.
  1591. ///
  1592. /// **This is the single canonical entry point** for SMT
  1593. /// mutation. All callers - live broadcast (`handle_static_put`),
  1594. /// originator (`nickserv.rs::handle_register`), and sync
  1595. /// (`static_sync` apply loop) - go through here. Bypassing it
  1596. /// will desynchronize the SMT from the historical-roots tables,
  1597. /// which silently breaks signal verification.
  1598. ///
  1599. /// **Canonical order requirement.** Two nodes processing the
  1600. /// same set of static events must produce the same sequence of
  1601. /// intermediate roots. SMTs are commutative under set-of-leaves
  1602. /// (final root is order-independent) but the *intermediate*
  1603. /// roots produced during application are order-dependent. We
  1604. /// pin the order with `(layer, event_id)`: layer is the primary
  1605. /// key (defined by the event's parent links and consensus-agreed),
  1606. /// event_id is the tie-breaker within a layer (32-byte hash,
  1607. /// total-ordered lexicographically).
  1608. ///
  1609. /// In live broadcast and originator paths, events arrive one at
  1610. /// a time; the canonical-order requirement is automatically
  1611. /// satisfied because each event's layer is greater than its
  1612. /// parents'. In sync, the caller must sort by `(layer, event_id)`
  1613. /// before invoking this method (see `static_sync`).
  1614. ///
  1615. /// **Returns** the post-mutation SMT root, or an error if the
  1616. /// SMT mutation itself fails. A duplicate-registration or
  1617. /// slash-of-nonexistent are both treated as soft no-ops at the
  1618. /// SMT layer, but we still record the root (which equals the
  1619. /// pre-call root in that case) - this preserves the invariant
  1620. /// that "every static-DAG event has a corresponding entry in
  1621. /// rln-historical-roots-ordered" without complicating the
  1622. /// caller's logic.
  1623. pub async fn apply_rln_static_event(
  1624. &self,
  1625. ev: &Event,
  1626. node: &rln::RLNNode,
  1627. ) -> Result<pallas::Base> {
  1628. let mut state = self.identity_state.write().await;
  1629. match node {
  1630. rln::RLNNode::Registration(commitment) => {
  1631. // Soft-fail on duplicate (race with another peer).
  1632. let _ = state.register(*commitment);
  1633. }
  1634. rln::RLNNode::Slashing(commitment) => {
  1635. // Idempotent - slashing a non-present identity is
  1636. // a no-op.
  1637. let _ = state.slash(*commitment);
  1638. }
  1639. }
  1640. let new_root = state.root();
  1641. drop(state);
  1642. // Record the root in both side-tables. We do this even if
  1643. // the SMT mutation was a no-op (duplicate register, slash of
  1644. // missing) so the historical-roots table has one entry per
  1645. // static-DAG event. This makes canonical-order replay simple:
  1646. // every event has exactly one entry, no conditional skips.
  1647. let key = encode_historical_root_key(ev.header.layer, &ev.id());
  1648. let value = encode_historical_root_value(&new_root, ev.header.timestamp);
  1649. self.rln_historical_roots_ordered.insert(key, value.as_slice())?;
  1650. self.rln_historical_roots_by_value.insert(new_root.to_repr(), key.as_slice())?;
  1651. Ok(new_root)
  1652. }
  1653. /// Check whether `root` is a valid SMT root for a signal whose
  1654. /// `signal_timestamp` is given (in millis-since-epoch).
  1655. ///
  1656. /// A root is valid if it was the live root at any time in the
  1657. /// drift window `[signal_timestamp - DRIFT, signal_timestamp +
  1658. /// DRIFT]`. The drift symmetry handles two distinct concerns:
  1659. ///
  1660. /// * **Forward drift** (signal sees a slightly stale root): the
  1661. /// originator built a proof against root `R_n`, then someone
  1662. /// else registered, producing `R_{n+1}`, before the signal
  1663. /// reached the verifier. The signal's claimed `R_n` is older
  1664. /// than the verifier's current root by an amount up to the
  1665. /// propagation delay. Accept if R_n was current within DRIFT
  1666. /// of the signal's timestamp.
  1667. ///
  1668. /// * **Backward drift** (signal arrives before its root): rare
  1669. /// but possible if the static-DAG broadcast is racing the
  1670. /// rotating-DAG broadcast. The originator's machine knew about
  1671. /// a registration that hadn't fully propagated yet. We tolerate
  1672. /// up to DRIFT of backward skew.
  1673. ///
  1674. /// The check uses `rln_historical_roots_by_value` to find the
  1675. /// canonical position of `root`, then `rln_historical_roots_ordered`
  1676. /// to bracket the time interval during which `root` was live.
  1677. /// The interval starts at the timestamp of the event that
  1678. /// produced `root` and ends just before the next event's
  1679. /// timestamp (or `u64::MAX` if `root` is currently live).
  1680. ///
  1681. /// This subsumes the old `recent_roots` window as a special case
  1682. /// (live verification = signal_timestamp ~= now). The in-memory
  1683. /// `recent_roots` cache in `IdentityState` remains as a fast
  1684. /// path for the live-broadcast hot loop; this method is consulted
  1685. /// when the cache misses.
  1686. pub fn is_root_valid_at(&self, root: &pallas::Base, signal_timestamp: u64) -> Result<bool> {
  1687. // Reverse lookup: where in the canonical sequence is `root`?
  1688. let Some(key_bytes) = self.rln_historical_roots_by_value.get(root.to_repr())? else {
  1689. return Ok(false)
  1690. };
  1691. // Read the entry that produced this root.
  1692. let Some(value_bytes) = self.rln_historical_roots_ordered.get(&key_bytes)? else {
  1693. // Inconsistency between the two tables (shouldn't happen
  1694. // under normal operation; might happen if a write was
  1695. // partially applied during a crash). Treat as not-found.
  1696. return Ok(false)
  1697. };
  1698. let (recorded_root, root_timestamp) = decode_historical_root_value(&value_bytes)?;
  1699. if &recorded_root != root {
  1700. // Same key collision - should be impossible because the
  1701. // by_value index is keyed on the root itself, but defend
  1702. // against future code changes.
  1703. return Ok(false)
  1704. }
  1705. // The interval during which `root` was live ends at the
  1706. // timestamp of the next event in canonical order, or
  1707. // u64::MAX if `root` is the current live root.
  1708. //
  1709. // We need the strictly-greater key. sled's range-from-exclusive
  1710. // pattern is range((Excluded(key), Unbounded)).next().
  1711. let next_timestamp: u64 = {
  1712. use std::ops::Bound::{Excluded, Unbounded};
  1713. match self
  1714. .rln_historical_roots_ordered
  1715. .range::<&[u8], _>((Excluded(key_bytes.as_ref()), Unbounded))
  1716. .next()
  1717. {
  1718. Some(Ok((_, val))) => decode_historical_root_value(&val)?.1,
  1719. Some(Err(_)) | None => u64::MAX,
  1720. }
  1721. };
  1722. // Drift window. Reuse EVENT_TIME_DRIFT for consistency with
  1723. // event-graph timestamp validation.
  1724. let drift = EVENT_TIME_DRIFT;
  1725. let lo = signal_timestamp.saturating_sub(drift);
  1726. let hi = signal_timestamp.saturating_add(drift);
  1727. // `root` was live during [root_timestamp, next_timestamp).
  1728. // Acceptable iff this interval intersects [lo, hi].
  1729. Ok(root_timestamp <= hi && next_timestamp > lo)
  1730. }
  1731. }
  1732. fn encode_historical_root_key(layer: u64, event_id: &blake3::Hash) -> [u8; 40] {
  1733. let mut buf = [0u8; 40];
  1734. buf[..8].copy_from_slice(&layer.to_be_bytes());
  1735. buf[8..].copy_from_slice(event_id.as_bytes());
  1736. buf
  1737. }
  1738. fn encode_historical_root_value(root: &pallas::Base, timestamp: u64) -> [u8; 40] {
  1739. let mut buf = [0u8; 40];
  1740. buf[..32].copy_from_slice(&root.to_repr());
  1741. buf[32..].copy_from_slice(&timestamp.to_be_bytes());
  1742. buf
  1743. }
  1744. fn decode_historical_root_value(bytes: &[u8]) -> Result<(pallas::Base, u64)> {
  1745. if bytes.len() != 40 {
  1746. return Err(Error::Custom(format!(
  1747. "historical-root value must be 40 bytes, got {}",
  1748. bytes.len()
  1749. )))
  1750. }
  1751. let mut root_repr = [0u8; 32];
  1752. root_repr.copy_from_slice(&bytes[..32]);
  1753. let root: pallas::Base = match pallas::Base::from_repr(root_repr).into() {
  1754. Some(r) => r,
  1755. None => return Err(Error::Custom("invalid root encoding".into())),
  1756. };
  1757. let mut ts_bytes = [0u8; 8];
  1758. ts_bytes.copy_from_slice(&bytes[32..]);
  1759. Ok((root, u64::from_be_bytes(ts_bytes)))
  1760. }
  1761. impl EventGraph {
  1762. /// Return a JSON-RPC response representing the current state of
  1763. /// the event graph.
  1764. ///
  1765. /// Shape (matches [`util::recreate_from_replayer_log`] so clients
  1766. /// can reuse their parsers):
  1767. ///
  1768. /// ```json
  1769. /// {
  1770. /// "eventgraph_info": {
  1771. /// "dag": {
  1772. /// "<event-id-hex>": <event>,
  1773. /// ...
  1774. /// }
  1775. /// }
  1776. /// }
  1777. /// ```
  1778. ///
  1779. /// Walks every event currently held in every rotating DAG *and*
  1780. /// every event in the static DAG. Genesis events are included.
  1781. #[cfg(feature = "rpc")]
  1782. pub async fn eventgraph_info(
  1783. &self,
  1784. id: u16,
  1785. _params: crate::rpc::util::JsonValue,
  1786. ) -> crate::rpc::jsonrpc::JsonResult {
  1787. use crate::rpc::{
  1788. jsonrpc::{JsonResponse, JsonResult},
  1789. util::{json_map, JsonValue},
  1790. };
  1791. let mut dag = HashMap::new();
  1792. // Walk every rotating DAG.
  1793. for (_, slot) in self.dag_store.read().await.dags.iter() {
  1794. for item in slot.main_tree.iter() {
  1795. let (eid, val) = match item {
  1796. Ok(v) => v,
  1797. Err(_) => continue,
  1798. };
  1799. let Ok(ev) = deserialize_async::<Event>(&val).await else { continue };
  1800. let key = blake3::Hash::from_bytes(match (&eid as &[u8]).try_into() {
  1801. Ok(b) => b,
  1802. Err(_) => continue,
  1803. });
  1804. dag.insert(key.to_string(), JsonValue::from(ev));
  1805. }
  1806. }
  1807. // And the static DAG.
  1808. for item in self.static_dag.iter() {
  1809. let (eid, val) = match item {
  1810. Ok(v) => v,
  1811. Err(_) => continue,
  1812. };
  1813. let Ok(ev) = deserialize_async::<Event>(&val).await else { continue };
  1814. let key = blake3::Hash::from_bytes(match (&eid as &[u8]).try_into() {
  1815. Ok(b) => b,
  1816. Err(_) => continue,
  1817. });
  1818. dag.insert(key.to_string(), JsonValue::from(ev));
  1819. }
  1820. let values = json_map([("dag", JsonValue::Object(dag))]);
  1821. let result = JsonValue::Object(HashMap::from([("eventgraph_info".into(), values)]));
  1822. JsonResult::Response(JsonResponse::new(result, id))
  1823. }
  1824. pub fn deg_enable(&self) {
  1825. self.deg_enabled.store(true, Ordering::Release);
  1826. }
  1827. pub fn deg_disable(&self) {
  1828. self.deg_enabled.store(false, Ordering::Release);
  1829. }
  1830. pub fn is_deg_enabled(&self) -> bool {
  1831. self.deg_enabled.load(Ordering::Acquire)
  1832. }
  1833. pub fn is_synced(&self) -> bool {
  1834. self.synced.load(Ordering::Acquire)
  1835. }
  1836. pub async fn deg_subscribe(&self) -> Subscription<DegEvent> {
  1837. self.deg_publisher.clone().subscribe().await
  1838. }
  1839. pub async fn deg_notify(&self, ev: DegEvent) {
  1840. self.deg_publisher.notify(ev).await;
  1841. }
  1842. /// Subscribe to rotating-DAG event insertions.
  1843. ///
  1844. /// Each subscriber receives a clone of every [`Event`] that
  1845. /// passes validation and is committed via `dag_insert`. The
  1846. /// publisher fires *after* state mutation, so subscribers can
  1847. /// rely on the event being durably present in the DAG by the
  1848. /// time they observe it.
  1849. ///
  1850. /// Used to build live JSON-RPC subscription endpoints (e.g.
  1851. /// the Gource-feeding endpoint in DarkIRC). Bridge a
  1852. /// [`Subscription`] to a `JsonSubscriber` in the application
  1853. /// layer; this method itself contains no JSON-RPC logic.
  1854. pub async fn event_subscribe(&self) -> Subscription<Event> {
  1855. self.event_pub.clone().subscribe().await
  1856. }
  1857. /// Subscribe to static-DAG event insertions (RLN registrations
  1858. /// and slashes). Mirrors [`Self::event_subscribe`] but for the
  1859. /// static DAG. See that method for semantics.
  1860. pub async fn static_subscribe(&self) -> Subscription<Event> {
  1861. self.static_pub.clone().subscribe().await
  1862. }
  1863. /// The app identifier mixed into RLN external nullifiers.
  1864. /// Exposed so the proto layer (and clients constructing signal
  1865. /// proofs) can use the same value the verifier uses.
  1866. pub fn rln_app_id(&self) -> rln::RlnAppId {
  1867. self.rln_app_id
  1868. }
  1869. /// Build a membership proof for an identity commitment, plus the
  1870. /// current root.
  1871. ///
  1872. /// This is the *only* sanctioned path for clients to produce a
  1873. /// signal proof - they should not be holding their own copy of
  1874. /// the SMT (the previous `event_graph.rln_identity_tree`
  1875. /// pattern). Centralising this keeps client and verifier in
  1876. /// agreement on the root they're proving against.
  1877. pub async fn rln_membership_path(
  1878. &self,
  1879. commitment: &darkfi_sdk::pasta::pallas::Base,
  1880. ) -> (darkfi_sdk::pasta::pallas::Base, darkfi_sdk::crypto::smt::PathFp) {
  1881. let s = self.identity_state.read().await;
  1882. (s.root(), s.prove_membership(commitment))
  1883. }
  1884. /// True if the given identity commitment is registered.
  1885. pub async fn rln_contains(&self, commitment: &darkfi_sdk::pasta::pallas::Base) -> bool {
  1886. self.identity_state.read().await.contains(commitment)
  1887. }
  1888. /// Verify an RLN signal blob against this event graph's state.
  1889. ///
  1890. /// The returned variant tells the caller what to do:
  1891. /// * `Accepted` - proof valid, no conflict, share recorded.
  1892. /// * `Rejected` - drop silently (bad proof, bad bounds, bad
  1893. /// root, exact duplicate).
  1894. /// * `Slashable(shares)` - different `(x, y)` for the same
  1895. /// internal nullifier; the caller (protocol layer) should
  1896. /// build and broadcast a slash from these shares.
  1897. ///
  1898. /// Critical invariant: `Rejected` and `Slashable` outcomes never
  1899. /// mutate `metadata`. `Accepted` is the only mutating outcome.
  1900. /// This is what prevents share-poisoning by an adversary who
  1901. /// observes an honest internal_nullifier and tries to forge a
  1902. /// share against it.
  1903. pub async fn rln_verify_signal(&self, event: &Event, blob: &[u8]) -> rln::SignalCheck {
  1904. use darkfi_sdk::{crypto::poseidon_hash, pasta::pallas};
  1905. use rln::{epoch_of, hash_event, Blob, SignalCheck, MAX_MSG_LIMIT};
  1906. let rcvd: Blob = match deserialize_async_partial(blob).await {
  1907. Ok((v, _)) => v,
  1908. Err(_) => return SignalCheck::Rejected,
  1909. };
  1910. // Defensive bounds. The proof PI binds these too, but
  1911. // checking up front lets us skip an expensive verify() call
  1912. // for trivially malformed blobs.
  1913. if rcvd.user_msg_limit == 0 || rcvd.user_msg_limit > MAX_MSG_LIMIT {
  1914. return SignalCheck::Rejected
  1915. }
  1916. let epoch_n = epoch_of(event.header.timestamp);
  1917. let epoch_field = pallas::Base::from(epoch_n);
  1918. let app_id = self.rln_app_id();
  1919. let ext_null = poseidon_hash([epoch_field, app_id.as_field()]);
  1920. let x = hash_event(event);
  1921. // 1) The merkle root must be valid for a signal at this
  1922. // timestamp. We accept any root that was the live SMT
  1923. // root at any time within EVENT_TIME_DRIFT of the signal's
  1924. // timestamp. This subsumes the old "recent_roots window"
  1925. // behaviour as a special case (live verification, signal
  1926. // timestamp ~= now) AND supports sync of historical signals
  1927. // (signal timestamp = signing time, root corresponds to
  1928. // that historical state).
  1929. //
  1930. // Hot-path optimization: check the in-memory recent_roots
  1931. // cache first. For live broadcasts (the overwhelming
  1932. // majority of verifications) the root will be in the
  1933. // cache and we skip the sled lookup.
  1934. {
  1935. let id_state = self.identity_state.read().await;
  1936. if !id_state.is_known_root(&rcvd.merkle_root) {
  1937. drop(id_state);
  1938. match self.is_root_valid_at(&rcvd.merkle_root, event.header.timestamp) {
  1939. Ok(true) => {}
  1940. Ok(false) => {
  1941. // Useful diagnostic: this rejection path
  1942. // catches both "garbage root" (attacker
  1943. // submitted a forged root) and "slashed-user
  1944. // replay" (slashed identity claiming a
  1945. // pre-slash root after the propagation
  1946. // window expired). The two are
  1947. // indistinguishable from the verifier's
  1948. // perspective by design - RLN-V2 privacy
  1949. // guarantees prevent identifying the
  1950. // signer. But observed in aggregate, a
  1951. // burst of these from a single peer is a
  1952. // strong signal of replay-after-slash
  1953. // misbehavior, useful for operators
  1954. // debugging "why are my messages being
  1955. // rejected" or investigating peer abuse.
  1956. let local_root = self.identity_state.read().await.root();
  1957. let historical_count = self.rln_historical_roots_ordered.len();
  1958. warn!(
  1959. target: "event_graph::rln_verify_signal",
  1960. "[RLN] Signal rejected: merkle_root not valid at signal \
  1961. timestamp {}. Possible causes: (1) forged or out-of-sync \
  1962. root, (2) slashed identity replaying against a pre-slash \
  1963. root after the propagation window expired. event_id={}, \
  1964. received_root={:?}, local_current_root={:?}, \
  1965. historical_root_count={}",
  1966. event.header.timestamp,
  1967. event.id(),
  1968. rcvd.merkle_root,
  1969. local_root,
  1970. historical_count,
  1971. );
  1972. return SignalCheck::Rejected
  1973. }
  1974. Err(e) => {
  1975. error!(
  1976. target: "event_graph::rln_verify_signal",
  1977. "[RLN] is_root_valid_at lookup failed for event {}: {e}",
  1978. event.id(),
  1979. );
  1980. return SignalCheck::Rejected
  1981. }
  1982. }
  1983. }
  1984. }
  1985. // 2) Verify the ZK proof. PI order MUST match
  1986. // constrain_instance() in rlnv2-diff-signal.zk:
  1987. // root, external_nullifier, user_message_limit, x, y, internal_nullifier
  1988. let pi = vec![
  1989. rcvd.merkle_root,
  1990. ext_null,
  1991. pallas::Base::from(rcvd.user_msg_limit),
  1992. x,
  1993. rcvd.y,
  1994. rcvd.internal_nullifier,
  1995. ];
  1996. if rcvd.proof.verify(&self.zk_keys.signal_vk, &pi).is_err() {
  1997. return SignalCheck::Rejected
  1998. }
  1999. // 3) Now consult the metadata table. Any share we look at
  2000. // here is guaranteed to be from a valid proof.
  2001. let mut state = self.rln_state.write().await;
  2002. // Prune metadata relative to THIS signal's epoch, not
  2003. // wall-clock. The retention window is conceptually
  2004. // "epochs near the signal we're processing", and the only
  2005. // entries that matter for reuse detection are siblings
  2006. // within `METADATA_RETAIN_EPOCHS` of `epoch_n`.
  2007. //
  2008. // In production, signals carry roughly-current wall-clock
  2009. // timestamps, so `epoch_n ~= current_epoch()` and this is
  2010. // equivalent to the previous `current_epoch()`-based prune.
  2011. // The change matters in three places:
  2012. //
  2013. // * Sync of historical signals: a late-arriving signal
  2014. // keeps its epoch's metadata visible long enough for
  2015. // the verifier to detect reuse. With wall-clock prune,
  2016. // a signal old enough to be outside the retention
  2017. // window would always silently lose its sibling shares
  2018. // before they could be matched.
  2019. //
  2020. // * Tests with deterministic event timestamps: the
  2021. // verifier and the metadata stay consistent regardless
  2022. // of when the test runs.
  2023. //
  2024. // * Minor DoS surface: a peer with a far-future system
  2025. // clock no longer causes mass-wipe of real metadata
  2026. // before consultation.
  2027. state.metadata.prune_old(epoch_n);
  2028. if state.metadata.is_duplicate(epoch_n, &rcvd.internal_nullifier, &x, &rcvd.y) {
  2029. return SignalCheck::Rejected
  2030. }
  2031. if state.metadata.is_reused(epoch_n, &rcvd.internal_nullifier) {
  2032. let mut shares = state.metadata.get_shares(epoch_n, &rcvd.internal_nullifier);
  2033. shares.push((x, rcvd.y));
  2034. return SignalCheck::Slashable(shares)
  2035. }
  2036. state.metadata.add_share(epoch_n, rcvd.internal_nullifier, x, rcvd.y);
  2037. SignalCheck::Accepted
  2038. }
  2039. /// Verify a static-DAG event (RLN registration or slashing)
  2040. /// against this event graph's state.
  2041. ///
  2042. /// This is the testable core of the protocol-layer
  2043. /// `handle_static_put`. It performs all checks that the
  2044. /// protocol layer does - bounds, attestation, proof, root
  2045. /// recency - and returns a [`rln::StaticEventCheck`] outcome
  2046. /// telling the caller what to do next.
  2047. ///
  2048. /// **This method does NOT mutate state.** The caller is
  2049. /// responsible for invoking `IdentityState::register` /
  2050. /// `slash` on `Accepted*` outcomes, and for striking the peer
  2051. /// on `Malicious`. Separating decision from action makes the
  2052. /// behaviour fully testable and lets the protocol layer keep
  2053. /// its mutation under a single locked critical section.
  2054. pub async fn rln_verify_static_event(
  2055. &self,
  2056. rln_node: &rln::RLNNode,
  2057. blob: &[u8],
  2058. event_timestamp: u64,
  2059. ) -> rln::StaticEventCheck {
  2060. use darkfi_sdk::{crypto::poseidon_hash, pasta::pallas};
  2061. use rln::{RLNNode, RegistrationBlob, SlashBlob, StaticEventCheck, MAX_MSG_LIMIT};
  2062. match rln_node {
  2063. RLNNode::Registration(commitment) => {
  2064. let reg: RegistrationBlob = match deserialize_async_partial(blob).await {
  2065. Ok((v, _)) => v,
  2066. Err(_) => return StaticEventCheck::Rejected,
  2067. };
  2068. // Bounds. Out-of-range limits are unambiguous misbehavior.
  2069. if reg.user_message_limit == 0 ||
  2070. reg.user_message_limit > MAX_MSG_LIMIT ||
  2071. reg.max_message_limit != MAX_MSG_LIMIT
  2072. {
  2073. return StaticEventCheck::Malicious
  2074. }
  2075. // Attestation must permit the claimed limit.
  2076. // (Free-tier cap; `Staked` rejected until the stake
  2077. // contract is online.)
  2078. if !reg.attestation.permits(reg.user_message_limit) {
  2079. return StaticEventCheck::Malicious
  2080. }
  2081. // Duplicate registration is a soft Reject (we may
  2082. // have raced a peer), checked here so we don't
  2083. // pay the proof-verification cost for known leaves.
  2084. if self.identity_state.read().await.contains(commitment) {
  2085. return StaticEventCheck::Rejected
  2086. }
  2087. // Proof.
  2088. let pi = vec![
  2089. *commitment,
  2090. pallas::Base::from(reg.user_message_limit),
  2091. pallas::Base::from(reg.max_message_limit),
  2092. ];
  2093. if reg.proof.verify(&self.zk_keys.register_vk, &pi).is_err() {
  2094. return StaticEventCheck::Rejected
  2095. }
  2096. StaticEventCheck::AcceptedRegistration(*commitment)
  2097. }
  2098. RLNNode::Slashing(commitment) => {
  2099. let sl: SlashBlob = match deserialize_async_partial(blob).await {
  2100. Ok((v, _)) => v,
  2101. Err(_) => return StaticEventCheck::Rejected,
  2102. };
  2103. let pi = vec![sl.identity_secret_hash, sl.merkle_root];
  2104. if sl.proof.verify(&self.zk_keys.slash_vk, &pi).is_err() {
  2105. return StaticEventCheck::Rejected
  2106. }
  2107. // Slash event names a specific commitment; verify
  2108. // that the recovered identity_secret_hash actually
  2109. // maps to it. Mismatch is unambiguous misbehavior.
  2110. let rebuilt = poseidon_hash([sl.identity_secret_hash]);
  2111. if *commitment != rebuilt {
  2112. return StaticEventCheck::Malicious
  2113. }
  2114. // The proof's root must be a valid SMT root at the
  2115. // slash event's timestamp. Same logic as signal
  2116. // verification: use the time-window check, which
  2117. // accepts any root that was live within DRIFT of the
  2118. // slash timestamp.
  2119. {
  2120. let id_state = self.identity_state.read().await;
  2121. if !id_state.is_known_root(&sl.merkle_root) {
  2122. drop(id_state);
  2123. match self.is_root_valid_at(&sl.merkle_root, event_timestamp) {
  2124. Ok(true) => {}
  2125. Ok(false) | Err(_) => return StaticEventCheck::Rejected,
  2126. }
  2127. }
  2128. }
  2129. StaticEventCheck::AcceptedSlash(rebuilt)
  2130. }
  2131. }
  2132. }
  2133. }
  2134. async fn request_tips(
  2135. peer: &Channel,
  2136. dag: String,
  2137. timeout: u64,
  2138. ) -> Result<BTreeMap<u64, HashSet<blake3::Hash>>> {
  2139. let sub = peer.subscribe_msg::<TipRep>().await?;
  2140. peer.send(&TipReq(dag)).await?;
  2141. let r = sub
  2142. .receive_with_timeout(timeout)
  2143. .await
  2144. .map_err(|_| Error::EventNotFound("tip timeout".into()))?;
  2145. sub.unsubscribe().await;
  2146. Ok(r.0.clone())
  2147. }
  2148. async fn request_header(
  2149. peer: &Channel,
  2150. name: String,
  2151. tips: LayerUTips,
  2152. timeout: u64,
  2153. ) -> Result<Vec<Header>> {
  2154. let sub = peer.subscribe_msg::<HeaderRep>().await?;
  2155. peer.send(&HeaderReq(name, tips)).await?;
  2156. let r = sub
  2157. .receive_with_timeout(timeout)
  2158. .await
  2159. .map_err(|_| Error::EventNotFound("hdr timeout".into()))?;
  2160. sub.unsubscribe().await;
  2161. Ok(r.0.to_vec())
  2162. }
  2163. async fn request_event(
  2164. peer: Arc<Channel>,
  2165. ids: Vec<blake3::Hash>,
  2166. cid: usize,
  2167. timeout: u64,
  2168. ) -> (Result<(Vec<Event>, Vec<Vec<u8>>)>, usize, Arc<Channel>) {
  2169. let sub = match peer.subscribe_msg::<EventRep>().await {
  2170. Ok(s) => s,
  2171. Err(e) => return (Err(e), cid, peer),
  2172. };
  2173. if let Err(e) = peer.send(&EventReq(ids)).await {
  2174. return (Err(e), cid, peer)
  2175. }
  2176. match sub.receive_with_timeout(timeout).await {
  2177. Ok(r) => {
  2178. sub.unsubscribe().await;
  2179. (Ok((r.0.clone(), r.1.clone())), cid, peer)
  2180. }
  2181. Err(_) => (Err(Error::EventNotFound("ev timeout".into())), cid, peer),
  2182. }
  2183. }