mod.rs 138 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624
  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. cmp::Ordering as CmpOrdering,
  22. collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
  23. path::PathBuf,
  24. str::FromStr,
  25. sync::{
  26. atomic::{AtomicBool, Ordering},
  27. Arc,
  28. },
  29. };
  30. use darkfi_sdk::{crypto::pasta_prelude::PrimeField, pasta::pallas};
  31. use darkfi_serial::{deserialize_async, deserialize_async_partial, serialize_async};
  32. use futures::{stream::FuturesUnordered, StreamExt};
  33. use sled_overlay::{sled, SledTreeOverlay};
  34. use smol::{
  35. lock::{OnceCell, RwLock},
  36. Executor,
  37. };
  38. use tracing::{error, info, warn};
  39. use url::Url;
  40. use crate::{
  41. net::{channel::Channel, P2pPtr},
  42. system::{msleep, Publisher, PublisherPtr, StoppableTask, StoppableTaskPtr, Subscription},
  43. Error, Result,
  44. };
  45. pub mod event;
  46. pub use event::{display_order, Event, Header};
  47. pub mod proto;
  48. use proto::{
  49. cap_layer_tips, count_layer_tips, EventRep, EventReq, HeaderRep, HeaderReq, RangeCursor,
  50. RangeRep, RangeReq, StaticPut, SyncDirection, TipRep, TipReq, MAX_EVENT_REP_EVENTS,
  51. MAX_EVENT_REQ_IDS, MAX_HEADER_REP_HEADERS, MAX_HEADER_REQ_TIPS, MAX_RANGE_PAGE_SIZE,
  52. MAX_TIP_REP_TIPS,
  53. };
  54. pub mod rln;
  55. use rln::{IdentityState, RlnState, ZkKeys};
  56. pub mod util;
  57. use util::{
  58. generate_genesis, millis_until_next_rotation, next_hour_timestamp, next_rotation_timestamp,
  59. replayer_log,
  60. };
  61. pub mod deg;
  62. use deg::DegEvent;
  63. #[cfg(test)]
  64. mod tests;
  65. #[cfg(test)]
  66. mod tests_rln;
  67. #[cfg(test)]
  68. mod test_helpers;
  69. /// Number of parent references each event carries.
  70. pub const N_EVENT_PARENTS: usize = 5;
  71. /// Multiplier for TimeIndex entries scanned to fill one blob-backed range page.
  72. const RANGE_BLOB_SCAN_FACTOR: usize = 4;
  73. /// Allowed timestamp drift in milliseconds.
  74. const EVENT_TIME_DRIFT: u64 = 60_000;
  75. /// The null event ID (32 zero bytes).
  76. pub const NULL_ID: blake3::Hash = blake3::Hash::from_bytes([0x00; blake3::OUT_LEN]);
  77. /// Array of null parents (used by genesis events).
  78. pub const NULL_PARENTS: [blake3::Hash; N_EVENT_PARENTS] = [NULL_ID; N_EVENT_PARENTS];
  79. /// Maximum number of static-DAG events `static_sync` will pull in
  80. /// one invocation. Defends against malicious deep-ancestry chains.
  81. const SYNC_MAX_STATIC_EVENTS: usize = 100_000;
  82. /// Runtime configuration for an Event Graph instance.
  83. #[derive(Clone, Debug)]
  84. pub struct EventGraphConfig {
  85. /// Epoch origin timestamp in millis.
  86. /// All rotation boundaries are computed as offsets from this point.
  87. /// Should be UTC midnight for clean hourly alignment.
  88. pub initial_genesis: u64,
  89. /// How often the DAG rotates, in hours. 0 = no rotation.
  90. pub hours_rotation: u64,
  91. /// Unique payload embedded in genesis events.
  92. /// Different protocols must use different values.
  93. pub genesis_contents: Vec<u8>,
  94. /// App-provided pregenerated RLN identity commitments.
  95. ///
  96. /// EventGraph treats these as the only proof-less registration
  97. /// commitments accepted with [`rln::GENESIS_BLOB_GUARD`]. Apps
  98. /// that do not use pregenerated RLN identities should leave this
  99. /// empty.
  100. pub pregenerated_identity_commitments: Vec<[u8; 32]>,
  101. /// Maximum number of DAGs to keep in the rolling window.
  102. ///
  103. /// * `Some(n)` - keep n rotation periods.
  104. /// When the n+1 period is created, the oldest is permanently
  105. /// deleted from sled. This is the normal mode for end-user nodes.
  106. /// * `None` - never prune. Every DAG ever created is kept in sled
  107. /// and loaded at startup. This is archive mode for nodes that want
  108. /// complete history.
  109. ///
  110. /// With `hours_rotation = 1` and `max_dags = Some(24)`, events
  111. /// older than 24 hours are lost. With `hours_rotation = 6` and
  112. /// `max_dags = Some(24)`, the window is 6 days.
  113. pub max_dags: Option<usize>,
  114. }
  115. impl EventGraphConfig {
  116. /// Validate consensus-critical event graph configuration.
  117. pub fn validate(&self) -> Result<()> {
  118. if self.max_dags == Some(0) {
  119. return Err(Error::Custom("event graph max_dags must be greater than 0".into()))
  120. }
  121. self.rotation_period_millis()?;
  122. Ok(())
  123. }
  124. /// Rotation period in milliseconds, or `None` for non-rotating graphs.
  125. pub(crate) fn rotation_period_millis(&self) -> Result<Option<u64>> {
  126. if self.hours_rotation == 0 {
  127. return Ok(None)
  128. }
  129. let rotation_ms = self.hours_rotation.checked_mul(util::HOUR_MS).ok_or_else(|| {
  130. Error::Custom("event graph rotation period overflows milliseconds".into())
  131. })?;
  132. if self.initial_genesis.checked_add(rotation_ms).is_none() {
  133. return Err(Error::Custom(
  134. "event graph initial genesis plus one rotation overflows".into(),
  135. ))
  136. }
  137. Ok(Some(rotation_ms))
  138. }
  139. }
  140. pub type EventGraphPtr = Arc<EventGraph>;
  141. /// Unreferenced tips grouped by layer.
  142. pub type LayerUTips = BTreeMap<u64, HashSet<blake3::Hash>>;
  143. /// Generate the deterministic genesis event for the static DAG.
  144. fn generate_static_genesis(config: &EventGraphConfig) -> Event {
  145. let header = Header {
  146. timestamp: config.initial_genesis,
  147. parents: NULL_PARENTS,
  148. layer: 0,
  149. content_hash: blake3::hash(&config.genesis_contents),
  150. };
  151. Event { header, content: config.genesis_contents.clone() }
  152. }
  153. fn validate_pregenerated_identity_commitments(
  154. config: &EventGraphConfig,
  155. ) -> Result<(Vec<pallas::Base>, HashSet<[u8; 32]>)> {
  156. let mut commitments = Vec::with_capacity(config.pregenerated_identity_commitments.len());
  157. let mut reprs = HashSet::with_capacity(config.pregenerated_identity_commitments.len());
  158. for (index, repr) in config.pregenerated_identity_commitments.iter().enumerate() {
  159. if !reprs.insert(*repr) {
  160. return Err(Error::Custom(format!(
  161. "duplicate pregenerated identity commitment at index {index}"
  162. )))
  163. }
  164. let commitment: Option<pallas::Base> = pallas::Base::from_repr(*repr).into();
  165. let Some(commitment) = commitment else {
  166. return Err(Error::Custom(format!(
  167. "invalid pregenerated identity commitment at index {index}"
  168. )))
  169. };
  170. commitments.push(commitment);
  171. }
  172. Ok((commitments, reprs))
  173. }
  174. /// Bidirectional timestamp -> event-ID index.
  175. #[derive(Clone, Debug, Default)]
  176. pub struct TimeIndex {
  177. index: BTreeMap<u64, Vec<blake3::Hash>>,
  178. count: usize,
  179. }
  180. impl TimeIndex {
  181. pub fn new() -> Self {
  182. Self::default()
  183. }
  184. pub async fn from_header_dag(tree: &sled::Tree) -> Result<Self> {
  185. let mut idx = Self::new();
  186. for item in tree.iter() {
  187. let (id, hdr) = item?;
  188. let id = blake3::Hash::from_bytes((&id as &[u8]).try_into()?);
  189. let hdr: Header = deserialize_async(&hdr).await?;
  190. idx.insert(hdr.timestamp, id);
  191. }
  192. Ok(idx)
  193. }
  194. pub fn insert(&mut self, ts: u64, id: blake3::Hash) {
  195. let ids = self.index.entry(ts).or_default();
  196. if ids.contains(&id) {
  197. return
  198. }
  199. ids.push(id);
  200. ids.sort_by_key(hash_order_key);
  201. self.count += 1;
  202. }
  203. pub fn newest(&self, n: usize) -> Vec<blake3::Hash> {
  204. self.rev(u64::MAX, n)
  205. }
  206. pub fn oldest(&self, n: usize) -> Vec<blake3::Hash> {
  207. self.fwd(0, n)
  208. }
  209. pub fn before(&self, cursor: u64, n: usize) -> Vec<blake3::Hash> {
  210. self.rev(cursor.saturating_sub(1), n)
  211. }
  212. pub fn after(&self, cursor: u64, n: usize) -> Vec<blake3::Hash> {
  213. self.fwd(cursor.saturating_add(1), n)
  214. }
  215. pub fn before_cursor(&self, cursor: RangeCursor, n: usize) -> Vec<blake3::Hash> {
  216. let mut out = Vec::with_capacity(n);
  217. for (ts, ids) in self.index.range(..=cursor.timestamp).rev() {
  218. for id in ids.iter().rev() {
  219. if *ts == cursor.timestamp && hash_cmp(id, &cursor.event_id) != CmpOrdering::Less {
  220. continue
  221. }
  222. out.push(*id);
  223. if out.len() >= n {
  224. return out
  225. }
  226. }
  227. }
  228. out
  229. }
  230. pub fn after_cursor(&self, cursor: RangeCursor, n: usize) -> Vec<blake3::Hash> {
  231. let mut out = Vec::with_capacity(n);
  232. for (ts, ids) in self.index.range(cursor.timestamp..) {
  233. for id in ids {
  234. if *ts == cursor.timestamp && hash_cmp(id, &cursor.event_id) != CmpOrdering::Greater
  235. {
  236. continue
  237. }
  238. out.push(*id);
  239. if out.len() >= n {
  240. return out
  241. }
  242. }
  243. }
  244. out
  245. }
  246. fn rev(&self, start: u64, n: usize) -> Vec<blake3::Hash> {
  247. let mut out = Vec::with_capacity(n);
  248. for (_, ids) in self.index.range(..=start).rev() {
  249. for id in ids {
  250. out.push(*id);
  251. if out.len() >= n {
  252. return out
  253. }
  254. }
  255. }
  256. out
  257. }
  258. fn fwd(&self, start: u64, n: usize) -> Vec<blake3::Hash> {
  259. let mut out = Vec::with_capacity(n);
  260. for (_, ids) in self.index.range(start..) {
  261. for id in ids {
  262. out.push(*id);
  263. if out.len() >= n {
  264. return out
  265. }
  266. }
  267. }
  268. out
  269. }
  270. pub fn len(&self) -> usize {
  271. self.count
  272. }
  273. pub fn is_empty(&self) -> bool {
  274. self.count == 0
  275. }
  276. }
  277. /// All per-DAG state: trees, tips, and the timestamp index.
  278. pub struct DagSlot {
  279. pub header_tree: sled::Tree,
  280. pub main_tree: sled::Tree,
  281. pub tips: LayerUTips,
  282. pub time_index: TimeIndex,
  283. }
  284. /// Full-scan tip computation.
  285. /// Compute unreferenced tips - events that exist in the DAG but are
  286. /// not referenced as a parent by any other event - grouped by layer.
  287. pub(crate) async fn compute_unreferenced_tips(dag: &sled::Tree) -> Result<LayerUTips> {
  288. let mut candidates: HashMap<blake3::Hash, u64> = HashMap::new();
  289. let mut referenced: HashSet<blake3::Hash> = HashSet::new();
  290. for item in dag.iter() {
  291. let (id_bytes, val_bytes) = item?;
  292. let id = blake3::Hash::from_bytes((&id_bytes as &[u8]).try_into()?);
  293. let ev: Event = deserialize_async(&val_bytes).await?;
  294. candidates.insert(id, ev.header.layer);
  295. for p in ev.header.parents.iter() {
  296. if *p != NULL_ID {
  297. referenced.insert(*p);
  298. }
  299. }
  300. }
  301. // Bucket the unreferenced candidates by their layer
  302. let mut map: LayerUTips = BTreeMap::new();
  303. for (id, layer) in candidates {
  304. if !referenced.contains(&id) {
  305. map.entry(layer).or_default().insert(id);
  306. }
  307. }
  308. Ok(map)
  309. }
  310. /// Pick up to N_EVENT_PARENTS tips from the highest layers.
  311. ///
  312. /// If the highest local tip is already at `u64::MAX`, no valid child
  313. /// layer exists. Return `u64::MAX` rather than wrapping; header
  314. /// validation rejects any attempted child of that saturated layer.
  315. fn select_parents_from_tips(tips: &LayerUTips) -> (u64, [blake3::Hash; N_EVENT_PARENTS]) {
  316. let mut parents = [NULL_ID; N_EVENT_PARENTS];
  317. let mut i = 0;
  318. 'outer: for (_, layer_tips) in tips.iter().rev() {
  319. for t in layer_tips {
  320. parents[i] = *t;
  321. i += 1;
  322. if i >= N_EVENT_PARENTS {
  323. break 'outer
  324. }
  325. }
  326. }
  327. let layer =
  328. tips.last_key_value().and_then(|(layer, _)| layer.checked_add(1)).unwrap_or(u64::MAX);
  329. (layer, parents)
  330. }
  331. /// Storage layer for all rotating DAGs.
  332. pub struct DagStore {
  333. db: sled::Db,
  334. dags: BTreeMap<u64, DagSlot>,
  335. }
  336. impl DagStore {
  337. /// Create or open DAG slots.
  338. ///
  339. /// * **Bounded mode** (`max_dags = Some(n)`): create a rolling
  340. /// window of the most recent `n` DAGs. Old trees already in
  341. /// sled outside this window are left untouched (they're just
  342. /// not loaded into memory).
  343. /// * **Archive mode** (`max_dags = None`): discover *all*
  344. /// existing DAG trees in sled and load them, plus ensure the
  345. /// recent window exists. Nothing is ever dropped.
  346. pub async fn new(sled_db: sled::Db, config: &EventGraphConfig) -> Result<Self> {
  347. config.validate()?;
  348. let mut dags = BTreeMap::new();
  349. if config.hours_rotation == 0 {
  350. let genesis = generate_genesis(config)?;
  351. dags.insert(genesis.header.timestamp, Self::create_slot(&sled_db, &genesis).await?);
  352. return Ok(Self { db: sled_db, dags })
  353. }
  354. // Determine how many recent DAGs to create/ensure exist.
  355. let window = config.max_dags.unwrap_or(24);
  356. // In archive mode, first discover and load any existing DAG
  357. // trees that are already in sled from previous runs.
  358. //
  359. // A DAG is stored across two trees: `<timestamp>` for events
  360. // and `headers_<timestamp>` for headers. We walk every tree
  361. // name in sled and pick out the ones whose name is a valid u64
  362. // timestamp.
  363. if config.max_dags.is_none() {
  364. for name in sled_db.tree_names() {
  365. let name_str = String::from_utf8_lossy(&name);
  366. if let Ok(ts) = name_str.parse::<u64>() {
  367. // Reconstruct the genesis for this timestamp
  368. let hdr = Header {
  369. timestamp: ts,
  370. parents: NULL_PARENTS,
  371. layer: 0,
  372. content_hash: blake3::hash(&config.genesis_contents),
  373. };
  374. let genesis = Event { header: hdr, content: config.genesis_contents.clone() };
  375. let slot = Self::create_slot(&sled_db, &genesis).await?;
  376. dags.insert(ts, slot);
  377. }
  378. }
  379. }
  380. // Ensure the recent window of DAGs exists.
  381. // Creates them if they're not already loaded from the discovery step.
  382. for i in 1..=window {
  383. let ts = next_hour_timestamp((i as i64) - (window as i64))?;
  384. if dags.contains_key(&ts) {
  385. // Already loaded from sled discovery
  386. continue
  387. }
  388. let hdr = Header {
  389. timestamp: ts,
  390. parents: NULL_PARENTS,
  391. layer: 0,
  392. content_hash: blake3::hash(&config.genesis_contents),
  393. };
  394. let genesis = Event { header: hdr, content: config.genesis_contents.clone() };
  395. dags.insert(ts, Self::create_slot(&sled_db, &genesis).await?);
  396. }
  397. Ok(Self { db: sled_db, dags })
  398. }
  399. async fn create_slot(db: &sled::Db, genesis: &Event) -> Result<DagSlot> {
  400. let name = genesis.header.timestamp.to_string();
  401. let ht = db.open_tree(format!("headers_{name}"))?;
  402. let mt = db.open_tree(&name)?;
  403. for (tree, data) in
  404. [(&ht, serialize_async(&genesis.header).await), (&mt, serialize_async(genesis).await)]
  405. {
  406. if tree.is_empty() {
  407. let mut ov = SledTreeOverlay::new(tree);
  408. ov.insert(genesis.id().as_bytes(), &data)?;
  409. if let Some(b) = ov.aggregate() {
  410. tree.apply_batch(b)?;
  411. }
  412. }
  413. }
  414. Ok(DagSlot {
  415. tips: compute_unreferenced_tips(&mt).await?,
  416. time_index: TimeIndex::from_header_dag(&ht).await?,
  417. header_tree: ht,
  418. main_tree: mt,
  419. })
  420. }
  421. /// Add a new DAG on rotation. In bounded mode, drops the oldest DAG
  422. /// when the limit is reached. In archive mode, never drops.
  423. pub async fn add_dag(&mut self, genesis: &Event, max_dags: Option<usize>) -> Result<()> {
  424. if let Some(limit) = max_dags {
  425. if limit == 0 {
  426. return Err(Error::Custom("event graph max_dags must be greater than 0".into()))
  427. }
  428. if self.dags.len() >= limit {
  429. let Some((_, old)) = self.dags.pop_first() else {
  430. return Err(Error::Custom("event graph DAG store is empty".into()))
  431. };
  432. self.db.drop_tree(old.header_tree.name())?;
  433. self.db.drop_tree(old.main_tree.name())?;
  434. }
  435. }
  436. let slot = Self::create_slot(&self.db, genesis).await?;
  437. self.dags.insert(genesis.header.timestamp, slot);
  438. Ok(())
  439. }
  440. pub fn get_slot(&self, ts: &u64) -> Option<&DagSlot> {
  441. self.dags.get(ts)
  442. }
  443. pub fn get_slot_mut(&mut self, ts: &u64) -> Option<&mut DagSlot> {
  444. self.dags.get_mut(ts)
  445. }
  446. pub fn get_header_tree(&self, dag_name: &str) -> Result<sled::Tree> {
  447. Ok(self.db.open_tree(format!("headers_{dag_name}"))?)
  448. }
  449. pub fn dag_timestamps(&self) -> Vec<u64> {
  450. self.dags.keys().cloned().collect()
  451. }
  452. }
  453. enum PeerStatus {
  454. Free,
  455. Busy,
  456. Failed,
  457. }
  458. #[derive(Clone)]
  459. struct PendingLazyEvent {
  460. event: Event,
  461. blob: Vec<u8>,
  462. }
  463. /// Result of one lazy range sync page.
  464. #[derive(Clone, Debug)]
  465. pub struct RangeSyncPage {
  466. /// Verified events returned for immediate application display.
  467. pub events: Vec<Event>,
  468. /// Event IDs durably committed to the local body tree after draining
  469. /// ready pending bodies.
  470. pub committed: Vec<blake3::Hash>,
  471. /// Cursor to pass to the next range request in the same direction.
  472. pub next_cursor: RangeCursor,
  473. /// True when the serving peers reported no more indexed events in this DAG.
  474. pub exhausted: bool,
  475. }
  476. /// Match an `EventRep` against the exact IDs requested for one sync chunk.
  477. ///
  478. /// The response may be partial, but every returned event must be unique and
  479. /// must belong to the outstanding request. Returned events and blobs are
  480. /// reordered to match the request order, and missing IDs are returned for
  481. /// retry with another peer.
  482. pub(crate) fn filter_requested_event_rep(
  483. requested: &[blake3::Hash],
  484. events: Vec<Event>,
  485. blobs: Vec<Vec<u8>>,
  486. ) -> Result<(Vec<Event>, Vec<Vec<u8>>, Vec<blake3::Hash>)> {
  487. if events.len() != blobs.len() {
  488. return Err(Error::DagSyncFailed)
  489. }
  490. let requested_set: HashSet<blake3::Hash> = requested.iter().copied().collect();
  491. let mut by_id = HashMap::with_capacity(events.len());
  492. for (event, blob) in events.into_iter().zip(blobs.into_iter()) {
  493. let event_id = event.id();
  494. if !requested_set.contains(&event_id) || by_id.insert(event_id, (event, blob)).is_some() {
  495. return Err(Error::DagSyncFailed)
  496. }
  497. }
  498. let mut matched_events = Vec::with_capacity(by_id.len());
  499. let mut matched_blobs = Vec::with_capacity(by_id.len());
  500. let mut missing = Vec::new();
  501. for id in requested {
  502. if let Some((event, blob)) = by_id.remove(id) {
  503. matched_events.push(event);
  504. matched_blobs.push(blob);
  505. } else {
  506. missing.push(*id);
  507. }
  508. }
  509. Ok((matched_events, matched_blobs, missing))
  510. }
  511. /// Merge one static-sync `EventRep` into the current batch state.
  512. ///
  513. /// Returns the number of still-pending requested IDs satisfied by this
  514. /// response. Invalid responses are rejected before any state is mutated.
  515. pub(crate) fn merge_static_sync_event_rep(
  516. requested: &[blake3::Hash],
  517. pending: &mut HashSet<blake3::Hash>,
  518. known: &mut HashSet<blake3::Hash>,
  519. want: &mut HashSet<blake3::Hash>,
  520. fetched: &mut Vec<(Event, Vec<u8>)>,
  521. events: Vec<Event>,
  522. blobs: Vec<Vec<u8>>,
  523. ) -> Result<usize> {
  524. let (matched_events, matched_blobs, _) = filter_requested_event_rep(requested, events, blobs)?;
  525. let mut matched = 0;
  526. for (ev, blob) in matched_events.into_iter().zip(matched_blobs) {
  527. let eid = ev.id();
  528. if !pending.remove(&eid) {
  529. continue
  530. }
  531. matched += 1;
  532. if known.insert(eid) {
  533. for p in ev.header.parents.iter() {
  534. if *p != NULL_ID && !known.contains(p) {
  535. want.insert(*p);
  536. }
  537. }
  538. fetched.push((ev, blob));
  539. }
  540. }
  541. Ok(matched)
  542. }
  543. fn hash_order_key(id: &blake3::Hash) -> [u8; blake3::OUT_LEN] {
  544. *id.as_bytes()
  545. }
  546. fn hash_cmp(a: &blake3::Hash, b: &blake3::Hash) -> CmpOrdering {
  547. a.as_bytes().cmp(b.as_bytes())
  548. }
  549. fn range_cursor_for_event(event: &Event) -> RangeCursor {
  550. RangeCursor { timestamp: event.header.timestamp, event_id: event.id() }
  551. }
  552. fn range_cursor_cmp(a: RangeCursor, b: RangeCursor) -> CmpOrdering {
  553. match a.timestamp.cmp(&b.timestamp) {
  554. CmpOrdering::Equal => hash_cmp(&a.event_id, &b.event_id),
  555. ordering => ordering,
  556. }
  557. }
  558. fn range_cursor_before_event(cursor: RangeCursor, event: &Event, dir: SyncDirection) -> bool {
  559. let event_cursor = range_cursor_for_event(event);
  560. match dir {
  561. SyncDirection::Forward => range_cursor_cmp(event_cursor, cursor) == CmpOrdering::Greater,
  562. SyncDirection::Backward => range_cursor_cmp(event_cursor, cursor) == CmpOrdering::Less,
  563. }
  564. }
  565. /// The main Event Graph instance.
  566. ///
  567. /// Manages a rolling window of DAGs (one per rotation period), a
  568. /// static DAG for long-lived state (RLN identities), and the P2P
  569. /// protocol for syncing with peers.
  570. ///
  571. /// # Sync model
  572. ///
  573. /// Headers are synced eagerly (complete DAG skeleton in seconds).
  574. /// Event content is fetched lazily in the direction the application
  575. /// needs.
  576. ///
  577. /// The [`TimeIndex`] in each [`DagSlot`] enables O(log n)
  578. /// bidirectional pagination that crosses DAG boundaries
  579. /// transparently - the caller sees a flat chronological stream.
  580. pub struct EventGraph {
  581. pub(crate) p2p: P2pPtr,
  582. pub(crate) dag_store: RwLock<DagStore>,
  583. /// Side-table mapping `event_id -> original RLN signal blob` for
  584. /// rotating-DAG events. Mirror of [`Self::static_dag_blobs`] but
  585. /// for the rotating DAGs.
  586. ///
  587. /// Populated by `handle_event_put` after successful RLN
  588. /// verification, and by `dag_insert_with_blobs` during sync when
  589. /// the serving peer included the blob in its `EventRep`. Read
  590. /// by `handle_event_req` to forward blobs to syncing peers.
  591. /// Pruned by `dag_prune` when the corresponding rotating DAG
  592. /// rolls out of the retention window.
  593. pub(crate) dag_blobs: sled::Tree,
  594. /// Verified range-sync bodies waiting for older parent bodies before they
  595. /// can be durably committed to the rotating DAG body tree.
  596. lazy_pending: RwLock<HashMap<u64, HashMap<blake3::Hash, PendingLazyEvent>>>,
  597. /// Historical SMT roots, in canonical apply order.
  598. ///
  599. /// Key: `(layer:u64_be, event_id:32) = 40 bytes`. Value:
  600. /// `(root:32, timestamp:u64_be:8) = 40 bytes`.
  601. ///
  602. /// Big-endian layer encoding makes lexicographic byte order
  603. /// match canonical apply order, so `Tree::range` iterates
  604. /// chronologically and `Tree::get_lt` / `get_gt` give cheap
  605. /// neighbor lookups (used to find the timestamp interval during
  606. /// which a given root was the live root).
  607. ///
  608. /// See [`Self::apply_rln_static_event`] for the canonical-order
  609. /// rationale and [`Self::is_root_valid_at`] for how this is
  610. /// consulted during signal verification.
  611. pub(crate) rln_historical_roots_ordered: sled::Tree,
  612. /// Reverse index: `(root:32, ordered_key:40) -> []`.
  613. ///
  614. /// A root can appear more than once when static events are no-ops
  615. /// (duplicate registrations, idempotent slashes), so the value
  616. /// index stores every canonical occurrence rather than a single
  617. /// root-to-key mapping. [`Self::is_root_valid_at`] scans this
  618. /// prefix and accepts if any interval for the root matches.
  619. pub(crate) rln_historical_roots_by_value: sled::Tree,
  620. pub(crate) static_dag: sled::Tree,
  621. /// Side-table mapping `event_id -> original RLN blob` for static
  622. /// events. Used by [`Self::static_sync`] to re-verify the ZK
  623. /// proof of historical events at sync time. Every static-DAG
  624. /// event MUST have a corresponding entry - `static_sync` rejects
  625. /// events whose blob isn't available rather than falling through.
  626. pub(crate) static_dag_blobs: sled::Tree,
  627. datastore: PathBuf,
  628. replay_mode: bool,
  629. pub(crate) broadcasted_ids: RwLock<HashSet<blake3::Hash>>,
  630. pub prune_task: OnceCell<StoppableTaskPtr>,
  631. pub event_pub: PublisherPtr<Event>,
  632. pub static_pub: PublisherPtr<Event>,
  633. pub current_genesis: RwLock<Event>,
  634. pub config: EventGraphConfig,
  635. /// Decoded app-provided pregenerated RLN commitments.
  636. pregenerated_identity_commitments: Vec<pallas::Base>,
  637. /// Canonical byte representations for fast admission checks.
  638. pregenerated_identity_commitment_reprs: HashSet<[u8; 32]>,
  639. pub synced: AtomicBool,
  640. pub deg_enabled: AtomicBool,
  641. deg_publisher: PublisherPtr<DegEvent>,
  642. pub sled_db: sled::Db,
  643. pub zk_keys: Arc<ZkKeys>,
  644. pub identity_state: RwLock<IdentityState>,
  645. pub rln_state: RwLock<RlnState>,
  646. /// App identifier mixed into the RLN external nullifier. Derived
  647. /// from `config.genesis_contents` so two deployments using the
  648. /// same circuit cannot collide on internal_nullifiers.
  649. rln_app_id: rln::RlnAppId,
  650. }
  651. fn sort_event_indices(events: &[Event], indices: &mut [usize]) {
  652. indices.sort_by(|a, b| {
  653. let a_event = &events[*a];
  654. let b_event = &events[*b];
  655. let a_id = a_event.id();
  656. let b_id = b_event.id();
  657. a_event
  658. .header
  659. .layer
  660. .cmp(&b_event.header.layer)
  661. .then_with(|| a_id.as_bytes().cmp(b_id.as_bytes()))
  662. });
  663. }
  664. impl EventGraph {
  665. /// Create a new Event Graph.
  666. pub async fn new(
  667. p2p: P2pPtr,
  668. sled_db: sled::Db,
  669. datastore: PathBuf,
  670. replay_mode: bool,
  671. config: EventGraphConfig,
  672. ex: Arc<Executor<'_>>,
  673. ) -> Result<EventGraphPtr> {
  674. config.validate()?;
  675. let zk_keys = Arc::new(ZkKeys::build_and_load(&sled_db)?);
  676. Self::with_zk_keys(p2p, sled_db, datastore, replay_mode, config, zk_keys, ex).await
  677. }
  678. /// Same as [`Self::new`] but accepts a pre-built [`ZkKeys`].
  679. ///
  680. /// Production always wants `Self::new`, which builds keys once
  681. /// against its own sled DB. Tests use this variant to share a
  682. /// single [`Arc<ZkKeys>`] across many `EventGraph` instances -
  683. /// proving keys are large (hundreds of MB each) and copying
  684. /// them per-test would blow out RAM and `/dev/shm`.
  685. pub async fn with_zk_keys(
  686. p2p: P2pPtr,
  687. sled_db: sled::Db,
  688. datastore: PathBuf,
  689. replay_mode: bool,
  690. config: EventGraphConfig,
  691. zk_keys: Arc<ZkKeys>,
  692. ex: Arc<Executor<'_>>,
  693. ) -> Result<EventGraphPtr> {
  694. config.validate()?;
  695. let identity_state = IdentityState::new(&sled_db)?;
  696. let rln_app_id = rln::RlnAppId::from_genesis(&config.genesis_contents);
  697. let current_genesis = generate_genesis(&config)?;
  698. let (pregenerated_identity_commitments, pregenerated_identity_commitment_reprs) =
  699. validate_pregenerated_identity_commitments(&config)?;
  700. let dag_store = DagStore::new(sled_db.clone(), &config).await?;
  701. let static_dag = Self::static_new(&sled_db, &config).await?;
  702. let static_dag_blobs = sled_db.open_tree("static-dag-blobs")?;
  703. let dag_blobs = sled_db.open_tree("dag-blobs")?;
  704. // Historical-roots side-tables. See the design comment on
  705. // `EventGraph::apply_rln_static_event` for the full rationale.
  706. // In short: every static-DAG mutation produces a new SMT root,
  707. // and we need to recognize *any* historical root for sync-time
  708. // signal verification, not just the most recent N. The
  709. // `ordered` tree gives us canonical replay (and successor
  710. // lookup for the time-window check), the `by_value` tree
  711. // gives us O(log n) "is this root historical?" queries.
  712. let rln_historical_roots_ordered = sled_db.open_tree("rln-historical-roots-ordered")?;
  713. let rln_historical_roots_by_value = sled_db.open_tree("rln-historical-roots-by-value")?;
  714. // Check whether the current genesis event is already in the
  715. // store. If not, we need to prune (create a fresh slot).
  716. let dag_ts = current_genesis.header.timestamp;
  717. let need_prune = dag_store
  718. .get_slot(&dag_ts)
  719. .map(|s| !s.main_tree.contains_key(current_genesis.id().as_bytes()).unwrap_or(false))
  720. .unwrap_or(true);
  721. let self_ = Arc::new(Self {
  722. p2p,
  723. sled_db: sled_db.clone(),
  724. dag_store: RwLock::new(dag_store),
  725. static_dag,
  726. static_dag_blobs,
  727. dag_blobs,
  728. lazy_pending: RwLock::new(HashMap::new()),
  729. rln_historical_roots_ordered,
  730. rln_historical_roots_by_value,
  731. datastore,
  732. replay_mode,
  733. broadcasted_ids: RwLock::new(HashSet::new()),
  734. prune_task: OnceCell::new(),
  735. event_pub: Publisher::new(),
  736. static_pub: Publisher::new(),
  737. current_genesis: RwLock::new(current_genesis.clone()),
  738. config: config.clone(),
  739. pregenerated_identity_commitments,
  740. pregenerated_identity_commitment_reprs,
  741. synced: AtomicBool::new(false),
  742. deg_enabled: AtomicBool::new(false),
  743. deg_publisher: Publisher::new(),
  744. zk_keys,
  745. identity_state: RwLock::new(identity_state),
  746. rln_state: RwLock::new(RlnState::new()),
  747. rln_app_id,
  748. });
  749. if need_prune {
  750. info!(
  751. target: "event_graph::new",
  752. "[EVENTGRAPH] Pruning: current genesis not found",
  753. );
  754. self_.dag_prune(current_genesis).await?;
  755. }
  756. // Reconcile persisted RLN state before bootstrapping. If an
  757. // earlier process crashed after writing identity leaves but before
  758. // inserting the corresponding static event, bootstrapping must see
  759. // the corrected leaf set rather than skip the configured identity.
  760. self_.rebuild_historical_roots_if_needed().await?;
  761. // Init genesis registration events after recovery has made the
  762. // static DAG authoritative for the current identity tree.
  763. if config.hours_rotation > 0 {
  764. self_.bootstrap_genesis_identities().await?;
  765. }
  766. self_.audit_static_blobs().await?;
  767. if config.hours_rotation > 0 {
  768. let task = StoppableTask::new();
  769. let _ = self_.prune_task.set(task.clone()).await;
  770. task.clone().start(
  771. self_.clone().dag_prune_task(),
  772. |res| async move {
  773. if let Err(e) = res {
  774. if !matches!(e, Error::DetachedTaskStopped) {
  775. error!("Prune: {e}");
  776. }
  777. }
  778. },
  779. Error::DetachedTaskStopped,
  780. ex,
  781. );
  782. }
  783. Ok(self_)
  784. }
  785. /// Rebuild the RLN state side-tables from the static DAG.
  786. ///
  787. /// Called once at startup. No-op if the historical-root indexes match
  788. /// the canonical static-DAG event sequence and the persisted identity
  789. /// leaves match the commitment set obtained by replaying that sequence.
  790. /// Otherwise resets the identity SMT and root indexes, then replays every
  791. /// parseable static-DAG event in canonical `(layer, event_id)` order.
  792. ///
  793. /// **Side effect.** The static DAG is authoritative. The in-memory SMT,
  794. /// the persistent `rln-identity-leaves` tree, and both historical-root
  795. /// indexes are derived from it so crashes between the old split write
  796. /// steps cannot leave stale leaves or unusable root indexes behind.
  797. async fn rebuild_historical_roots_if_needed(self: &Arc<Self>) -> Result<()> {
  798. let mut events: Vec<(Event, rln::RLNNode)> = vec![];
  799. for item in self.static_dag.iter() {
  800. let (_, val) = item?;
  801. let ev: Event = deserialize_async(&val).await?;
  802. if ev.header.parents == NULL_PARENTS {
  803. continue
  804. }
  805. let Ok((node, _)) = deserialize_async_partial::<rln::RLNNode>(ev.content()).await
  806. else {
  807. continue
  808. };
  809. events.push((ev, node));
  810. }
  811. events.sort_by(|(a, _), (b, _)| {
  812. a.header
  813. .layer
  814. .cmp(&b.header.layer)
  815. .then_with(|| a.id().as_bytes().cmp(b.id().as_bytes()))
  816. });
  817. let mut expected_commitments = BTreeSet::new();
  818. let mut expected_slashed = BTreeSet::new();
  819. for (_, node) in &events {
  820. match node {
  821. rln::RLNNode::Registration(commitment) => {
  822. let repr = commitment.to_repr();
  823. if !expected_slashed.contains(&repr) {
  824. expected_commitments.insert(repr);
  825. }
  826. }
  827. rln::RLNNode::Slashing(commitment) => {
  828. let repr = commitment.to_repr();
  829. expected_slashed.insert(repr);
  830. expected_commitments.remove(&repr);
  831. }
  832. }
  833. }
  834. let expected_leaves = expected_commitments.len();
  835. let expected_slashed_count = expected_slashed.len();
  836. let (actual_commitments, actual_slashed) = {
  837. let state = self.identity_state.read().await;
  838. (state.commitment_reprs(), state.slashed_commitment_reprs())
  839. };
  840. let (actual_leaves, leaves_consistent) = match actual_commitments {
  841. Ok(commitments) => {
  842. let len = commitments.len();
  843. (len, commitments == expected_commitments)
  844. }
  845. Err(e) => {
  846. warn!(
  847. target: "event_graph::new",
  848. "[EVENTGRAPH] RLN identity leaf audit failed: {e}; rebuilding",
  849. );
  850. (0, false)
  851. }
  852. };
  853. let (actual_slashed_count, slashed_consistent) = match actual_slashed {
  854. Ok(commitments) => {
  855. let len = commitments.len();
  856. (len, commitments == expected_slashed)
  857. }
  858. Err(e) => {
  859. warn!(
  860. target: "event_graph::new",
  861. "[EVENTGRAPH] RLN slashed identity audit failed: {e}; rebuilding",
  862. );
  863. (0, false)
  864. }
  865. };
  866. let static_count = events.len();
  867. let historical_roots_consistent = self.historical_roots_index_consistent(static_count)?;
  868. let recorded_count = self.rln_historical_roots_ordered.len();
  869. let by_value_count = self.rln_historical_roots_by_value.len();
  870. let consistent = historical_roots_consistent && leaves_consistent && slashed_consistent;
  871. info!(
  872. target: "event_graph::new",
  873. concat!(
  874. "[EVENTGRAPH] RLN state audit: static_count={} recorded_count={} ",
  875. "by_value_count={} actual_leaves={} expected_leaves={} actual_slashed={} ",
  876. "expected_slashed={} consistent={}",
  877. ),
  878. static_count, recorded_count, by_value_count, actual_leaves, expected_leaves,
  879. actual_slashed_count, expected_slashed_count, consistent,
  880. );
  881. if consistent {
  882. return Ok(())
  883. }
  884. info!(
  885. target: "event_graph::new",
  886. concat!(
  887. "[EVENTGRAPH] Rebuilding RLN state: {} static events, {} recorded roots, ",
  888. "{} by-value roots, {} leaves (expected {}), {} slashed (expected {})",
  889. ),
  890. static_count, recorded_count, by_value_count, actual_leaves, expected_leaves,
  891. actual_slashed_count, expected_slashed_count,
  892. );
  893. self.rln_historical_roots_ordered.clear()?;
  894. self.rln_historical_roots_by_value.clear()?;
  895. {
  896. let mut state = self.identity_state.write().await;
  897. state.clear_for_rebuild()?;
  898. }
  899. for (ev, rln_node) in events {
  900. let _ = self.apply_rln_static_event(&ev, &rln_node).await?;
  901. }
  902. info!(
  903. target: "event_graph::new",
  904. "[EVENTGRAPH] RLN state rebuild complete",
  905. );
  906. Ok(())
  907. }
  908. fn historical_roots_index_consistent(&self, expected_count: usize) -> Result<bool> {
  909. if self.rln_historical_roots_ordered.len() != expected_count {
  910. return Ok(false)
  911. }
  912. if self.rln_historical_roots_by_value.len() != expected_count {
  913. return Ok(false)
  914. }
  915. for item in self.rln_historical_roots_ordered.iter() {
  916. let (ordered_key_bytes, value_bytes) = item?;
  917. if ordered_key_bytes.len() != 40 {
  918. return Ok(false)
  919. }
  920. let Ok((root, _)) = decode_historical_root_value(&value_bytes) else {
  921. return Ok(false)
  922. };
  923. let mut ordered_key = [0u8; 40];
  924. ordered_key.copy_from_slice(&ordered_key_bytes);
  925. let by_value_key = encode_historical_root_by_value_key(&root, &ordered_key);
  926. if !self.rln_historical_roots_by_value.contains_key(by_value_key)? {
  927. return Ok(false)
  928. }
  929. }
  930. Ok(true)
  931. }
  932. /// After header sync, event content can be fetched lazily via local
  933. /// [`fetch_page`] or peer [`RangeReq`] responses with aligned blobs - the
  934. /// application pulls the events it actually wants to display or process,
  935. /// without downloading the entire content on every sync.
  936. pub async fn dag_sync_headers(&self, dag_ts: u64) -> Result<()> {
  937. self.sync_impl(dag_ts, false).await
  938. }
  939. /// Full sync: headers plus all event content currently in the DAG.
  940. ///
  941. /// Use this when the application wants the complete historical
  942. /// content (e.g. an archive node, or a node rebuilding local state
  943. /// from the full event stream).
  944. pub async fn dag_sync(&self, dag_ts: u64) -> Result<()> {
  945. self.sync_impl(dag_ts, true).await
  946. }
  947. async fn sync_impl(&self, dag_ts: u64, fetch_content: bool) -> Result<()> {
  948. let dag_name = dag_ts.to_string();
  949. let channels = self.p2p.hosts().peers();
  950. // We need at least one peer to ask
  951. if channels.is_empty() {
  952. return Err(Error::DagSyncFailed)
  953. }
  954. let timeout = self.p2p.settings().read().await.outbound_connect_timeout_max();
  955. // Parallel tip collection
  956. let mut futs = FuturesUnordered::new();
  957. for ch in channels.iter() {
  958. futs.push(request_tips(ch, dag_name.clone(), timeout));
  959. }
  960. let mut tips: HashMap<blake3::Hash, (u64, usize)> = HashMap::new();
  961. let mut responded = 0usize;
  962. while let Some(res) = futs.next().await {
  963. if let Ok(peer_tips) = res {
  964. responded += 1;
  965. for (layer, hashes) in &peer_tips {
  966. for h in hashes {
  967. tips.entry(*h).and_modify(|e| e.1 += 1).or_insert((*layer, 1));
  968. }
  969. }
  970. }
  971. }
  972. if tips.is_empty() {
  973. return Err(Error::DagSyncFailed)
  974. }
  975. // 2/3 quorum
  976. let threshold = (responded * 2).div_ceil(3);
  977. let accepted: HashSet<blake3::Hash> = tips
  978. .iter()
  979. .filter(|(h, (_, n))| **h != NULL_ID && *n >= threshold)
  980. .map(|(h, _)| *h)
  981. .collect();
  982. let store = self.dag_store.read().await;
  983. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  984. let missing: HashSet<blake3::Hash> = accepted
  985. .iter()
  986. .filter(|h| !slot.main_tree.contains_key(h.as_bytes()).unwrap_or(true))
  987. .cloned()
  988. .collect();
  989. if missing.is_empty() {
  990. return Ok(())
  991. }
  992. let our_tips = slot.tips.clone();
  993. drop(store);
  994. // Parallel header sync
  995. let mut hfuts = FuturesUnordered::new();
  996. for ch in channels.iter() {
  997. hfuts.push(request_header(ch, dag_name.clone(), our_tips.clone(), timeout));
  998. }
  999. while let Some(res) = hfuts.next().await {
  1000. if let Ok(hdrs) = res {
  1001. self.header_dag_insert(hdrs, &dag_name).await?;
  1002. }
  1003. }
  1004. if fetch_content {
  1005. self.fetch_missing_events(dag_ts, &dag_name, timeout).await?;
  1006. }
  1007. Ok(())
  1008. }
  1009. async fn fetch_missing_events(&self, dag_ts: u64, dag_name: &str, timeout: u64) -> Result<()> {
  1010. let store = self.dag_store.read().await;
  1011. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  1012. let mut sorted = vec![];
  1013. for item in slot.header_tree.iter() {
  1014. let (hb, val) = item?;
  1015. let hdr: Header = deserialize_async(&val).await?;
  1016. if hdr.parents != NULL_PARENTS && !slot.main_tree.contains_key(hb)? {
  1017. sorted.push(hdr);
  1018. }
  1019. }
  1020. sorted.sort_by_key(|h| h.layer);
  1021. drop(store);
  1022. if sorted.is_empty() {
  1023. return Ok(())
  1024. }
  1025. let batch = 20;
  1026. let mut chunks: BTreeMap<usize, Vec<blake3::Hash>> = BTreeMap::new();
  1027. for (i, c) in sorted.chunks(batch).enumerate() {
  1028. chunks.insert(i, c.iter().map(|h| h.id()).collect());
  1029. }
  1030. let mut remaining: BTreeSet<usize> = chunks.keys().cloned().collect();
  1031. let mut peer_st: HashMap<Url, PeerStatus> = HashMap::new();
  1032. let mut count = 0;
  1033. let mut fs = FuturesUnordered::new();
  1034. // Collected by event ID so partial chunk retries cannot disturb
  1035. // the final layer-sorted insertion order. Empty `Vec<u8>` entries
  1036. // mean "this event has no blob from the serving peer".
  1037. let mut received: HashMap<blake3::Hash, (Event, Vec<u8>)> = HashMap::new();
  1038. while count < sorted.len() {
  1039. let mut free = vec![];
  1040. let mut busy = 0;
  1041. self.p2p.hosts().peers().iter().for_each(|ch| match peer_st.get(ch.address()) {
  1042. Some(PeerStatus::Free) | None => {
  1043. free.push(ch.clone());
  1044. }
  1045. Some(PeerStatus::Busy) => {
  1046. busy += 1;
  1047. }
  1048. _ => {}
  1049. });
  1050. if free.is_empty() && busy == 0 {
  1051. return Err(Error::DagSyncFailed)
  1052. }
  1053. if remaining.is_empty() && fs.is_empty() {
  1054. return Err(Error::DagSyncFailed)
  1055. }
  1056. let n = std::cmp::min(free.len(), remaining.len());
  1057. let ids: Vec<usize> = remaining.iter().take(n).copied().collect();
  1058. for (i, cid) in ids.iter().enumerate() {
  1059. fs.push(request_event(free[i].clone(), chunks[cid].clone(), *cid, timeout));
  1060. remaining.remove(cid);
  1061. peer_st.insert(free[i].address().clone(), PeerStatus::Busy);
  1062. }
  1063. if let Some((evts, cid, ch)) = fs.next().await {
  1064. if let Ok((events, blobs)) = evts {
  1065. let Some(requested) = chunks.get(&cid) else {
  1066. peer_st.insert(ch.address().clone(), PeerStatus::Failed);
  1067. continue
  1068. };
  1069. match filter_requested_event_rep(requested, events, blobs) {
  1070. Ok((matched_events, matched_blobs, missing)) => {
  1071. let matched = matched_events.len();
  1072. for (event, blob) in matched_events.into_iter().zip(matched_blobs) {
  1073. let event_id = event.id();
  1074. if received.insert(event_id, (event, blob)).is_none() {
  1075. count += 1;
  1076. }
  1077. }
  1078. if missing.is_empty() {
  1079. peer_st.insert(ch.address().clone(), PeerStatus::Free);
  1080. } else {
  1081. chunks.insert(cid, missing);
  1082. remaining.insert(cid);
  1083. let status = if matched == 0 {
  1084. PeerStatus::Failed
  1085. } else {
  1086. PeerStatus::Free
  1087. };
  1088. peer_st.insert(ch.address().clone(), status);
  1089. }
  1090. }
  1091. Err(_) => {
  1092. remaining.insert(cid);
  1093. peer_st.insert(ch.address().clone(), PeerStatus::Failed);
  1094. }
  1095. }
  1096. } else {
  1097. remaining.insert(cid);
  1098. peer_st.insert(ch.address().clone(), PeerStatus::Failed);
  1099. }
  1100. }
  1101. }
  1102. let mut events = Vec::with_capacity(sorted.len());
  1103. let mut blobs = Vec::with_capacity(sorted.len());
  1104. for hdr in sorted {
  1105. let event_id = hdr.id();
  1106. let Some((event, blob)) = received.remove(&event_id) else {
  1107. return Err(Error::DagSyncFailed)
  1108. };
  1109. events.push(event);
  1110. blobs.push(blob);
  1111. }
  1112. // dag_insert_with_blobs handles RLN re-verification for every
  1113. // non-genesis event and rejects any event whose blob is missing,
  1114. // malformed, or invalid. Strict sync must not report success unless
  1115. // every requested body is now locally committed.
  1116. self.dag_insert_with_blobs(&events, &blobs, dag_name).await?;
  1117. let store = self.dag_store.read().await;
  1118. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  1119. for event in &events {
  1120. if !slot.main_tree.contains_key(event.id().as_bytes())? {
  1121. error!(
  1122. target: "event_graph::sync",
  1123. "[DAG_SYNC] requested event {} was fetched but not committed",
  1124. event.id(),
  1125. );
  1126. return Err(Error::DagSyncFailed)
  1127. }
  1128. }
  1129. Ok(())
  1130. }
  1131. /// Sync the `count` most recent DAGs (full content).
  1132. ///
  1133. /// Iterates oldest-first so that later syncs build on earlier
  1134. /// ones (parent events exist before children reference them).
  1135. pub async fn sync_selected(&self, count: usize) -> Result<()> {
  1136. let ts: Vec<u64> =
  1137. self.dag_store.read().await.dag_timestamps().into_iter().rev().take(count).collect();
  1138. for t in ts.into_iter().rev() {
  1139. self.dag_sync(t).await?;
  1140. }
  1141. self.synced.store(true, Ordering::Release);
  1142. Ok(())
  1143. }
  1144. /// Sync only headers for the `count` most recent DAGs.
  1145. ///
  1146. /// Fast variant - gives a full DAG skeleton without downloading
  1147. /// event bodies. Pair with [`fetch_page`] to pull content on-demand.
  1148. pub async fn sync_selected_headers(&self, count: usize) -> Result<()> {
  1149. let ts: Vec<u64> =
  1150. self.dag_store.read().await.dag_timestamps().into_iter().rev().take(count).collect();
  1151. for t in ts.into_iter().rev() {
  1152. self.dag_sync_headers(t).await?;
  1153. }
  1154. self.synced.store(true, Ordering::Release);
  1155. Ok(())
  1156. }
  1157. /// Lazily sync one body page for a DAG in the requested direction.
  1158. ///
  1159. /// This is the receiver-side API for mobile history loading. It first
  1160. /// syncs headers for `dag_ts`, then requests a blob-backed range page from
  1161. /// peers. Returned events are structurally checked against the synced
  1162. /// header DAG and RLN-verified before they are returned to the caller.
  1163. /// Events whose parent bodies are not loaded yet are held in a verified
  1164. /// pending queue and are committed automatically after later pages bring in
  1165. /// the missing parents.
  1166. pub async fn dag_sync_range(
  1167. &self,
  1168. dag_ts: u64,
  1169. cursor: RangeCursor,
  1170. direction: SyncDirection,
  1171. limit: usize,
  1172. ) -> Result<RangeSyncPage> {
  1173. let limit = limit.min(MAX_RANGE_PAGE_SIZE);
  1174. if limit == 0 {
  1175. return Ok(RangeSyncPage {
  1176. events: vec![],
  1177. committed: self.drain_lazy_pending(dag_ts, &dag_ts.to_string()).await?,
  1178. next_cursor: cursor,
  1179. exhausted: true,
  1180. })
  1181. }
  1182. self.dag_sync_headers(dag_ts).await?;
  1183. let peers = self.p2p.hosts().peers();
  1184. if peers.is_empty() {
  1185. return Err(Error::DagSyncFailed)
  1186. }
  1187. let dag_name = dag_ts.to_string();
  1188. let timeout = self.p2p.settings().read().await.outbound_connect_timeout_max();
  1189. let mut futs = FuturesUnordered::new();
  1190. for peer in peers {
  1191. futs.push(request_range(
  1192. peer,
  1193. dag_name.clone(),
  1194. cursor,
  1195. direction.clone(),
  1196. limit,
  1197. timeout,
  1198. ));
  1199. }
  1200. let mut empty_page = None;
  1201. while let Some((result, peer)) = futs.next().await {
  1202. let Ok((events, blobs, peer_next_cursor, exhausted)) = result else { continue };
  1203. match self
  1204. .accept_range_page(
  1205. dag_ts,
  1206. &dag_name,
  1207. cursor,
  1208. direction.clone(),
  1209. limit,
  1210. events,
  1211. blobs,
  1212. peer_next_cursor,
  1213. exhausted,
  1214. )
  1215. .await
  1216. {
  1217. Ok(page) if !page.events.is_empty() => return Ok(page),
  1218. Ok(page) => empty_page = Some(page),
  1219. Err(e) => {
  1220. warn!(
  1221. target: "event_graph::range",
  1222. "[EVENTGRAPH] rejected RangeRep from {}: {e}",
  1223. peer.address(),
  1224. );
  1225. }
  1226. }
  1227. }
  1228. empty_page.ok_or(Error::DagSyncFailed)
  1229. }
  1230. /// Sync the static DAG from peers.
  1231. ///
  1232. /// The static DAG holds RLN identity events (registrations and
  1233. /// slashes). It is *persistent* across rotation windows - unlike
  1234. /// rotating DAGs, events are never pruned - and has no separate
  1235. /// `header_tree`, so it uses a different sync strategy:
  1236. ///
  1237. /// 1. Ask every peer for their `"static-dag"` tips.
  1238. /// 2. Take the tips that reach a 2/3 quorum.
  1239. /// 3. BFS-fetch the events and their ancestors directly via
  1240. /// `EventReq` until the entire reachable subgraph is local.
  1241. ///
  1242. /// Peers serve static-DAG event requests even when the IDs are
  1243. /// not in their `broadcasted_ids` set (see the relaxation in
  1244. /// `handle_event_req`), because static-DAG state is public
  1245. /// consensus information. Registration-event proof verification,
  1246. /// duplicate detection, and commitment-tree updates are all done
  1247. /// through the normal verified static-event pipeline.
  1248. /// `static_sync` also commits fetched events through
  1249. /// [`Self::commit_verified_static_event`], because catch-up must
  1250. /// preserve the same blob-before-event-before-RLN-state ordering as
  1251. /// live broadcast processing.
  1252. ///
  1253. /// Note: for security, this method ONLY applies events whose
  1254. /// blob/RLN verification passes. We do not trust peers blindly
  1255. /// on historical state - proofs are re-verified locally for
  1256. /// every single event before its effect is merged into the
  1257. /// identity tree. This is the same discipline `handle_static_put`
  1258. /// uses; see [`Self::rln_verify_static_event`].
  1259. pub async fn static_sync(&self) -> Result<()> {
  1260. static DAG_NAME: &str = "static-dag";
  1261. let channels = self.p2p.hosts().peers();
  1262. if channels.is_empty() {
  1263. return Err(Error::DagSyncFailed)
  1264. }
  1265. let timeout = self.p2p.settings().read().await.outbound_connect_timeout_max();
  1266. // Step 1: gather tips from every peer in parallel.
  1267. let mut tip_futs = FuturesUnordered::new();
  1268. for ch in channels.iter() {
  1269. tip_futs.push(request_tips(ch, DAG_NAME.to_string(), timeout));
  1270. }
  1271. let mut tip_counts: HashMap<blake3::Hash, usize> = HashMap::new();
  1272. let mut responded = 0usize;
  1273. while let Some(res) = tip_futs.next().await {
  1274. if let Ok(peer_tips) = res {
  1275. responded += 1;
  1276. for hashes in peer_tips.values() {
  1277. for h in hashes {
  1278. *tip_counts.entry(*h).or_insert(0) += 1;
  1279. }
  1280. }
  1281. }
  1282. }
  1283. // If no peer answered we have nothing to do. An empty
  1284. // network-side static DAG is a valid state (brand new app
  1285. // deployment), so we return Ok rather than error.
  1286. if responded == 0 {
  1287. info!(
  1288. target: "event_graph::static_sync",
  1289. "[STATIC_SYNC] no peer responded to TipReq; nothing to sync"
  1290. );
  1291. return Ok(())
  1292. }
  1293. // Step 2: take tips at 2/3 quorum. This matches the
  1294. // threshold used in `sync_impl`.
  1295. let threshold = (responded * 2).div_ceil(3);
  1296. let total_distinct_tips = tip_counts.len();
  1297. let tip_ids: HashSet<blake3::Hash> = tip_counts
  1298. .into_iter()
  1299. .filter(|(h, n)| *h != NULL_ID && *n >= threshold)
  1300. .map(|(h, _)| h)
  1301. .collect();
  1302. // What's already local?
  1303. let mut known: HashSet<blake3::Hash> = HashSet::new();
  1304. for item in self.static_dag.iter() {
  1305. let (k, _) = item?;
  1306. if let Ok(bytes) = <[u8; 32]>::try_from(&k as &[u8]) {
  1307. known.insert(blake3::Hash::from_bytes(bytes));
  1308. }
  1309. }
  1310. info!(
  1311. target: "event_graph::static_sync",
  1312. "[STATIC_SYNC] peers_responded={} threshold={} distinct_tips_seen={} \
  1313. tip_ids_quorum={} known_local={}",
  1314. responded, threshold, total_distinct_tips, tip_ids.len(), known.len(),
  1315. );
  1316. // Step 3: BFS from the quorum tips, fetching events we
  1317. // don't have. Any event we pull in may reference ancestors
  1318. // we ALSO don't have; enqueue them and keep going until the
  1319. // frontier is empty.
  1320. //
  1321. // Bounded at SYNC_MAX_STATIC_EVENTS (defined at module level)
  1322. // to defend against a malicious peer who serves a fabricated
  1323. // deep-ancestry chain. In practice static DAGs are small (one
  1324. // event per registration / slash), so this bound is
  1325. // comfortably above any real deployment's size.
  1326. let mut want: HashSet<blake3::Hash> = tip_ids.difference(&known).copied().collect();
  1327. // Events fetched during BFS, paired with their blobs (empty
  1328. // Vec if the peer didn't have the blob - see EventRep
  1329. // docstring). Index alignment is preserved through the
  1330. // entire pipeline up to the apply loop.
  1331. let mut fetched: Vec<(Event, Vec<u8>)> = vec![];
  1332. while !want.is_empty() {
  1333. want.retain(|id| !known.contains(id));
  1334. if want.is_empty() {
  1335. break
  1336. }
  1337. if fetched.len() >= SYNC_MAX_STATIC_EVENTS {
  1338. error!(
  1339. target: "event_graph::static_sync",
  1340. "[STATIC_SYNC] reached {} event cap; aborting",
  1341. SYNC_MAX_STATIC_EVENTS,
  1342. );
  1343. return Err(Error::DagSyncFailed)
  1344. }
  1345. let batch: Vec<blake3::Hash> = want.iter().take(MAX_EVENT_REQ_IDS).copied().collect();
  1346. for id in &batch {
  1347. want.remove(id);
  1348. }
  1349. let mut pending: HashSet<blake3::Hash> = batch.iter().copied().collect();
  1350. // Ask every peer for the same batch. We keep consuming
  1351. // responses until the batch is complete or every peer has
  1352. // failed to help. Irrelevant, duplicate, or blob-misaligned
  1353. // replies do not satisfy the request.
  1354. let mut req_futs = FuturesUnordered::new();
  1355. for (i, ch) in channels.iter().enumerate() {
  1356. req_futs.push(request_event(ch.clone(), batch.clone(), i, timeout));
  1357. }
  1358. let mut made_progress = false;
  1359. while !pending.is_empty() {
  1360. let Some((res, _, _)) = req_futs.next().await else { break };
  1361. let Ok((evs, blobs)) = res else { continue };
  1362. if evs.is_empty() {
  1363. continue
  1364. }
  1365. let Ok(matched) = merge_static_sync_event_rep(
  1366. &batch,
  1367. &mut pending,
  1368. &mut known,
  1369. &mut want,
  1370. &mut fetched,
  1371. evs,
  1372. blobs,
  1373. ) else {
  1374. continue
  1375. };
  1376. if matched > 0 {
  1377. made_progress = true;
  1378. }
  1379. }
  1380. if !pending.is_empty() {
  1381. want.extend(pending.iter().copied().filter(|id| !known.contains(id)));
  1382. }
  1383. want.retain(|id| !known.contains(id));
  1384. if !made_progress {
  1385. // Nobody responded usefully. Give up so we don't
  1386. // loop forever on an unreachable ancestor.
  1387. error!(
  1388. target: "event_graph::static_sync",
  1389. "[STATIC_SYNC] no peer served requested events; aborting",
  1390. );
  1391. return Err(Error::DagSyncFailed)
  1392. }
  1393. }
  1394. // Step 4: canonical-order the fetched events so all nodes
  1395. // produce the same intermediate SMT roots. Primary key:
  1396. // layer (matches DAG topology). Secondary key: event_id
  1397. // (32-byte hash, lexicographic byte order is total). Without
  1398. // the tie-breaker, two events at the same layer could be
  1399. // applied in different orders on different nodes, producing
  1400. // different intermediate roots and breaking sync-time signal
  1401. // verification. See the design comment on
  1402. // `apply_rln_static_event` for the full rationale.
  1403. fetched.sort_by(|(a, _), (b, _)| {
  1404. a.header
  1405. .layer
  1406. .cmp(&b.header.layer)
  1407. .then_with(|| a.id().as_bytes().cmp(b.id().as_bytes()))
  1408. });
  1409. // Track the apply-loop outcome for the summary log.
  1410. let mut applied = 0usize;
  1411. let mut already_present = 0usize;
  1412. let mut blob_missing = 0usize;
  1413. let mut rejected = 0usize;
  1414. let mut structural_invalid = 0usize;
  1415. let mut content_unparseable = 0usize;
  1416. let mut parent_missing = 0usize;
  1417. let total_to_consider = fetched.len();
  1418. let mut committed: HashSet<blake3::Hash> = HashSet::with_capacity(total_to_consider);
  1419. for (ev, blob) in fetched {
  1420. let eid = ev.id();
  1421. // Skip if someone else inserted it concurrently.
  1422. if self.static_dag.contains_key(eid.as_bytes())? {
  1423. already_present += 1;
  1424. committed.insert(eid);
  1425. continue
  1426. }
  1427. // Structural validation always runs. Static-DAG events
  1428. // are persistent and may be far older than the 60s drift
  1429. // window allowed by `validate_new`; use the static
  1430. // sibling that omits the freshness check while keeping
  1431. // the structural ones.
  1432. if !ev.validate_new_static() {
  1433. structural_invalid += 1;
  1434. continue
  1435. }
  1436. if !self.static_parents_committed(&ev, &committed)? {
  1437. parent_missing += 1;
  1438. error!(
  1439. target: "event_graph::static_sync",
  1440. "[STATIC_SYNC] static event {} has a parent that was not committed; skipping",
  1441. eid,
  1442. );
  1443. continue
  1444. }
  1445. let rln_node: rln::RLNNode = match deserialize_async_partial(ev.content()).await {
  1446. Ok((v, _)) => v,
  1447. Err(_) => {
  1448. content_unparseable += 1;
  1449. continue
  1450. }
  1451. };
  1452. // RLN verification is mandatory. A non-genesis static
  1453. // event without a blob during sync is treated as
  1454. // misbehavior: either the serving peer is buggy or
  1455. // adversarial, or the originator never persisted the blob
  1456. // (which itself is a protocol violation). Skip with a
  1457. // loud log - we don't strike here because static_sync
  1458. // doesn't have a single peer to attribute the failure
  1459. // to (the quorum collected blobs from multiple peers).
  1460. if blob.is_empty() {
  1461. blob_missing += 1;
  1462. error!(
  1463. target: "event_graph::static_sync",
  1464. concat!(
  1465. "[STATIC_SYNC] no blob available for static event {}; skipping. ",
  1466. "Every static-DAG event must carry an RLN blob.",
  1467. ),
  1468. eid,
  1469. );
  1470. continue
  1471. }
  1472. let outcome = self.rln_verify_static_event(&rln_node, &blob, ev.header.timestamp).await;
  1473. match outcome {
  1474. rln::StaticEventCheck::AcceptedRegistration(_) |
  1475. rln::StaticEventCheck::AcceptedSlash(_) => {
  1476. self.commit_verified_static_event(&ev, &blob, &rln_node).await?;
  1477. committed.insert(eid);
  1478. applied += 1;
  1479. }
  1480. rln::StaticEventCheck::Rejected | rln::StaticEventCheck::Malicious => {
  1481. // A historical event whose blob fails
  1482. // re-verification despite being held by the 2/3
  1483. // quorum is a serious finding - either the blob
  1484. // was tampered with, the quorum was compromised,
  1485. // or our verifying keys diverged. Log loudly and
  1486. // skip.
  1487. rejected += 1;
  1488. error!(
  1489. target: "event_graph::static_sync",
  1490. concat!(
  1491. "[STATIC_SYNC] historical blob FAILED re-verification for event {}: {:?}; ",
  1492. "skipping event despite quorum inclusion",
  1493. ),
  1494. eid,
  1495. outcome,
  1496. );
  1497. }
  1498. }
  1499. }
  1500. info!(
  1501. target: "event_graph::static_sync",
  1502. concat!(
  1503. "[STATIC_SYNC] complete: fetched={} applied={} already_present={} ",
  1504. "blob_missing={} verification_rejected={} structural_invalid={} ",
  1505. "unparseable={} parent_missing={}",
  1506. ),
  1507. total_to_consider, applied, already_present, blob_missing, rejected,
  1508. structural_invalid, content_unparseable, parent_missing,
  1509. );
  1510. if parent_missing > 0 {
  1511. return Err(Error::DagSyncFailed)
  1512. }
  1513. Ok(())
  1514. }
  1515. fn static_parents_committed(
  1516. &self,
  1517. ev: &Event,
  1518. committed: &HashSet<blake3::Hash>,
  1519. ) -> Result<bool> {
  1520. for parent in ev.header.parents.iter().filter(|parent| **parent != NULL_ID) {
  1521. if !committed.contains(parent) && !self.static_dag.contains_key(parent.as_bytes())? {
  1522. return Ok(false)
  1523. }
  1524. }
  1525. Ok(true)
  1526. }
  1527. /// Fetch a page of events, crossing DAG boundaries transparently.
  1528. pub async fn fetch_page(
  1529. &self,
  1530. cursor_ts: u64,
  1531. dir: SyncDirection,
  1532. limit: usize,
  1533. ) -> Result<Vec<Event>> {
  1534. let limit = limit.min(MAX_RANGE_PAGE_SIZE);
  1535. let mut out = vec![];
  1536. let store = self.dag_store.read().await;
  1537. let slots: Vec<_> = match dir {
  1538. SyncDirection::Forward => store.dags.iter().collect(),
  1539. SyncDirection::Backward => store.dags.iter().rev().collect(),
  1540. };
  1541. for (_, slot) in slots {
  1542. if out.len() >= limit {
  1543. break
  1544. }
  1545. let rem = limit - out.len();
  1546. let ids = match dir {
  1547. SyncDirection::Forward => slot.time_index.after(cursor_ts, rem),
  1548. SyncDirection::Backward => slot.time_index.before(cursor_ts, rem),
  1549. };
  1550. for id in ids {
  1551. if let Some(bytes) = slot.main_tree.get(id.as_bytes())? {
  1552. out.push(deserialize_async(&bytes).await?);
  1553. }
  1554. }
  1555. }
  1556. out.truncate(limit);
  1557. Ok(out)
  1558. }
  1559. /// Fetch a DAG-scoped page with aligned RLN blobs for peer range sync.
  1560. ///
  1561. /// Non-genesis events without a stored blob are skipped because a requester
  1562. /// cannot safely insert lazy-loaded bodies without re-verifying their RLN
  1563. /// proofs. The scan is bounded separately from the reply size so sparse
  1564. /// missing blobs cannot turn one range request into an unbounded local walk.
  1565. pub async fn fetch_page_with_blobs(
  1566. &self,
  1567. dag_name: &str,
  1568. cursor: RangeCursor,
  1569. dir: SyncDirection,
  1570. limit: usize,
  1571. ) -> Result<(Vec<Event>, Vec<Vec<u8>>, RangeCursor, bool)> {
  1572. let limit = limit.min(MAX_RANGE_PAGE_SIZE);
  1573. if limit == 0 {
  1574. return Ok((vec![], vec![], cursor, true))
  1575. }
  1576. let dag_ts = u64::from_str(dag_name)?;
  1577. let scan_limit = limit.saturating_mul(RANGE_BLOB_SCAN_FACTOR);
  1578. let mut events = Vec::with_capacity(limit);
  1579. let mut blobs = Vec::with_capacity(limit);
  1580. let mut next_cursor = cursor;
  1581. let store = self.dag_store.read().await;
  1582. let Some(slot) = store.get_slot(&dag_ts) else { return Ok((events, blobs, cursor, true)) };
  1583. let ids = match dir {
  1584. SyncDirection::Forward => slot.time_index.after_cursor(cursor, scan_limit),
  1585. SyncDirection::Backward => slot.time_index.before_cursor(cursor, scan_limit),
  1586. };
  1587. let index_exhausted = ids.len() < scan_limit;
  1588. for id in ids {
  1589. if events.len() >= limit {
  1590. break
  1591. }
  1592. let Some(bytes) = slot.main_tree.get(id.as_bytes())? else { continue };
  1593. let event: Event = deserialize_async(&bytes).await?;
  1594. next_cursor = range_cursor_for_event(&event);
  1595. if event.header.parents == NULL_PARENTS {
  1596. continue
  1597. }
  1598. if event.id() != id || !event.content_matches_header() {
  1599. warn!(
  1600. target: "event_graph::range",
  1601. "[EVENTGRAPH] refusing to serve corrupt range event {id}",
  1602. );
  1603. continue
  1604. }
  1605. let blob = match self.dag_blob_fetch(&id)? {
  1606. Some(blob) if !blob.is_empty() => blob,
  1607. _ => {
  1608. warn!(
  1609. target: "event_graph::range",
  1610. "[EVENTGRAPH] refusing to serve range event {id} without blob",
  1611. );
  1612. continue
  1613. }
  1614. };
  1615. events.push(event);
  1616. blobs.push(blob);
  1617. }
  1618. let exhausted = index_exhausted && events.len() < limit;
  1619. Ok((events, blobs, next_cursor, exhausted))
  1620. }
  1621. async fn accept_range_page(
  1622. &self,
  1623. dag_ts: u64,
  1624. dag_name: &str,
  1625. cursor: RangeCursor,
  1626. direction: SyncDirection,
  1627. limit: usize,
  1628. events: Vec<Event>,
  1629. blobs: Vec<Vec<u8>>,
  1630. peer_next_cursor: RangeCursor,
  1631. exhausted: bool,
  1632. ) -> Result<RangeSyncPage> {
  1633. if events.len() != blobs.len() || events.len() > limit || events.len() > MAX_RANGE_PAGE_SIZE
  1634. {
  1635. return Err(Error::DagSyncFailed)
  1636. }
  1637. let pending_ids: HashSet<blake3::Hash> = self
  1638. .lazy_pending
  1639. .read()
  1640. .await
  1641. .get(&dag_ts)
  1642. .map(|pending| pending.keys().copied().collect())
  1643. .unwrap_or_default();
  1644. let mut seen = HashSet::with_capacity(events.len());
  1645. let mut prev = cursor;
  1646. let mut candidates = Vec::with_capacity(events.len());
  1647. {
  1648. let store = self.dag_store.read().await;
  1649. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  1650. for (event, blob) in events.into_iter().zip(blobs.into_iter()) {
  1651. if event.header.parents == NULL_PARENTS {
  1652. continue
  1653. }
  1654. let event_id = event.id();
  1655. if !seen.insert(event_id) {
  1656. return Err(Error::DagSyncFailed)
  1657. }
  1658. if !range_cursor_before_event(prev, &event, direction.clone()) {
  1659. return Err(Error::DagSyncFailed)
  1660. }
  1661. prev = range_cursor_for_event(&event);
  1662. let already_have = slot.main_tree.contains_key(event_id.as_bytes())?;
  1663. let already_pending = pending_ids.contains(&event_id);
  1664. if !already_have && !slot.header_tree.contains_key(event_id.as_bytes())? {
  1665. return Err(Error::DagSyncFailed)
  1666. }
  1667. if !event.dag_validate(&slot.header_tree, &self.config, dag_ts).await? {
  1668. return Err(Error::DagSyncFailed)
  1669. }
  1670. if !already_have && !already_pending && blob.is_empty() {
  1671. return Err(Error::DagSyncFailed)
  1672. }
  1673. candidates.push((event, blob, already_have, already_pending));
  1674. }
  1675. }
  1676. let mut accepted_events = Vec::with_capacity(candidates.len());
  1677. let mut newly_pending = Vec::new();
  1678. for (event, blob, already_have, already_pending) in candidates {
  1679. if already_have || already_pending {
  1680. accepted_events.push(event);
  1681. continue
  1682. }
  1683. match self.rln_verify_signal(&event, &blob).await {
  1684. rln::SignalCheck::Accepted => {
  1685. newly_pending.push(PendingLazyEvent { event: event.clone(), blob });
  1686. accepted_events.push(event);
  1687. }
  1688. rln::SignalCheck::Rejected | rln::SignalCheck::Slashable(_) => {
  1689. warn!(
  1690. target: "event_graph::range",
  1691. "[EVENTGRAPH] range event {} failed RLN verification",
  1692. event.id(),
  1693. );
  1694. }
  1695. }
  1696. }
  1697. if !newly_pending.is_empty() {
  1698. let mut pending = self.lazy_pending.write().await;
  1699. let pending = pending.entry(dag_ts).or_default();
  1700. for item in newly_pending {
  1701. pending.entry(item.event.id()).or_insert(item);
  1702. }
  1703. }
  1704. let committed = self.drain_lazy_pending(dag_ts, dag_name).await?;
  1705. let next_cursor =
  1706. accepted_events.last().map(range_cursor_for_event).unwrap_or(peer_next_cursor);
  1707. Ok(RangeSyncPage { events: accepted_events, committed, next_cursor, exhausted })
  1708. }
  1709. async fn event_body_exists(&self, dag_ts: u64, event_id: &blake3::Hash) -> Result<bool> {
  1710. let store = self.dag_store.read().await;
  1711. let Some(slot) = store.get_slot(&dag_ts) else { return Ok(false) };
  1712. Ok(slot.main_tree.contains_key(event_id.as_bytes())?)
  1713. }
  1714. async fn drain_lazy_pending(&self, dag_ts: u64, dag_name: &str) -> Result<Vec<blake3::Hash>> {
  1715. let mut committed = Vec::new();
  1716. loop {
  1717. let mut pending_items: Vec<_> = self
  1718. .lazy_pending
  1719. .read()
  1720. .await
  1721. .get(&dag_ts)
  1722. .map(|pending| pending.values().cloned().collect())
  1723. .unwrap_or_default();
  1724. if pending_items.is_empty() {
  1725. break
  1726. }
  1727. pending_items
  1728. .sort_by_key(|item| (item.event.header.layer, hash_order_key(&item.event.id())));
  1729. let mut ready = Vec::new();
  1730. let mut already_committed = Vec::new();
  1731. for item in pending_items {
  1732. let event_id = item.event.id();
  1733. if self.event_body_exists(dag_ts, &event_id).await? {
  1734. already_committed.push(event_id);
  1735. continue
  1736. }
  1737. if self.parents_have_bodies(&item.event, dag_ts, &HashSet::new()).await? {
  1738. ready.push(item);
  1739. }
  1740. }
  1741. if !already_committed.is_empty() {
  1742. let mut pending = self.lazy_pending.write().await;
  1743. if let Some(by_id) = pending.get_mut(&dag_ts) {
  1744. for event_id in already_committed {
  1745. by_id.remove(&event_id);
  1746. }
  1747. if by_id.is_empty() {
  1748. pending.remove(&dag_ts);
  1749. }
  1750. }
  1751. }
  1752. if ready.is_empty() {
  1753. break
  1754. }
  1755. let mut progressed = false;
  1756. for item in ready {
  1757. let event_id = item.event.id();
  1758. let ids = self.insert_verified_signal(&item.event, &item.blob, dag_name).await?;
  1759. if ids.contains(&event_id) || self.event_body_exists(dag_ts, &event_id).await? {
  1760. let mut pending = self.lazy_pending.write().await;
  1761. if let Some(by_id) = pending.get_mut(&dag_ts) {
  1762. by_id.remove(&event_id);
  1763. if by_id.is_empty() {
  1764. pending.remove(&dag_ts);
  1765. }
  1766. }
  1767. if ids.contains(&event_id) {
  1768. committed.push(event_id);
  1769. }
  1770. progressed = true;
  1771. }
  1772. }
  1773. if !progressed {
  1774. break
  1775. }
  1776. }
  1777. Ok(committed)
  1778. }
  1779. async fn dag_prune(&self, genesis: Event) -> Result<()> {
  1780. let mut bcast = self.broadcasted_ids.write().await;
  1781. let mut cur = self.current_genesis.write().await;
  1782. // Before the DAG store evicts the oldest DAG (which would
  1783. // drop its main_tree), enumerate the about-to-be-dropped
  1784. // event IDs so we can remove their blobs from `dag_blobs`.
  1785. // Without this, blob entries would orphan and accumulate
  1786. // forever - the side-table is not bounded by the rotation
  1787. // window on its own.
  1788. if let Some(limit) = self.config.max_dags {
  1789. let store = self.dag_store.read().await;
  1790. if store.dags.len() >= limit {
  1791. if let Some((_, oldest)) = store.dags.iter().next() {
  1792. for item in oldest.main_tree.iter() {
  1793. let (eid, _) = match item {
  1794. Ok(v) => v,
  1795. Err(_) => continue,
  1796. };
  1797. let _ = self.dag_blobs.remove(&eid);
  1798. }
  1799. }
  1800. }
  1801. }
  1802. self.dag_store.write().await.add_dag(&genesis, self.config.max_dags).await?;
  1803. *cur = genesis;
  1804. *bcast = HashSet::new();
  1805. Ok(())
  1806. }
  1807. async fn dag_prune_task(self: Arc<Self>) -> Result<()> {
  1808. loop {
  1809. let next =
  1810. next_rotation_timestamp(self.config.initial_genesis, self.config.hours_rotation)?;
  1811. let hdr = Header {
  1812. timestamp: next,
  1813. parents: NULL_PARENTS,
  1814. layer: 0,
  1815. content_hash: blake3::hash(&self.config.genesis_contents),
  1816. };
  1817. let genesis = Event { header: hdr, content: self.config.genesis_contents.clone() };
  1818. msleep(millis_until_next_rotation(next)?).await;
  1819. self.dag_prune(genesis).await?;
  1820. }
  1821. }
  1822. /// Public insertion path for a rotating-DAG signal event.
  1823. ///
  1824. /// Non-genesis rotating events must carry an RLN signal blob. This method
  1825. /// inserts the header, re-verifies the blob, records RLN metadata, stores
  1826. /// the blob for future sync, and only then commits the event body. External
  1827. /// applications should use this instead of the unchecked post-verification
  1828. /// insertion path.
  1829. pub async fn insert_signal_with_blob(
  1830. &self,
  1831. event: &Event,
  1832. blob: &[u8],
  1833. dag_name: &str,
  1834. ) -> Result<Vec<blake3::Hash>> {
  1835. if event.header.parents != NULL_PARENTS && blob.is_empty() {
  1836. return Err(Error::Custom("rotating-DAG signal event blob must not be empty".into()))
  1837. }
  1838. let dag_ts = u64::from_str(dag_name)?;
  1839. let already_known = if event.header.parents == NULL_PARENTS {
  1840. false
  1841. } else {
  1842. let store = self.dag_store.read().await;
  1843. match store.get_slot(&dag_ts) {
  1844. Some(slot) => slot.main_tree.contains_key(event.id().as_bytes())?,
  1845. None => false,
  1846. }
  1847. };
  1848. self.header_dag_insert(vec![event.header.clone()], dag_name).await?;
  1849. let blobs = if blob.is_empty() { vec![] } else { vec![blob.to_vec()] };
  1850. let ids = self.dag_insert_with_blobs(std::slice::from_ref(event), &blobs, dag_name).await?;
  1851. let accepted = ids.contains(&event.id()) || already_known || {
  1852. let store = self.dag_store.read().await;
  1853. match store.get_slot(&dag_ts) {
  1854. Some(slot) => slot.main_tree.contains_key(event.id().as_bytes())?,
  1855. None => false,
  1856. }
  1857. };
  1858. if event.header.parents != NULL_PARENTS && !accepted {
  1859. return Err(Error::Custom("rotating-DAG signal event was not accepted".into()))
  1860. }
  1861. Ok(ids)
  1862. }
  1863. /// Insert events into a rotating DAG **without RLN verification**.
  1864. ///
  1865. /// This is the crate-internal post-verification entry point for callers
  1866. /// that have already verified the proof separately and recorded RLN
  1867. /// metadata. Public callers must use [`Self::insert_signal_with_blob`] or
  1868. /// [`Self::dag_insert_with_blobs`] so non-genesis events cannot be inserted
  1869. /// without their proof blob.
  1870. pub(crate) async fn dag_insert(
  1871. &self,
  1872. events: &[Event],
  1873. dag_name: &str,
  1874. ) -> Result<Vec<blake3::Hash>> {
  1875. self.dag_insert_inner(events, &[], /* require_blobs */ false, dag_name).await
  1876. }
  1877. /// Commit a rotating-DAG signal event whose RLN proof has already been
  1878. /// verified and recorded by the caller.
  1879. ///
  1880. /// Used by live protocol ingestion after `verify_rln_signal()` accepts the
  1881. /// event. The blob is persisted before the event body so late joiners never
  1882. /// observe a locally committed non-genesis event without its proof blob.
  1883. pub(crate) async fn insert_verified_signal(
  1884. &self,
  1885. event: &Event,
  1886. blob: &[u8],
  1887. dag_name: &str,
  1888. ) -> Result<Vec<blake3::Hash>> {
  1889. if event.header.parents != NULL_PARENTS && blob.is_empty() {
  1890. return Err(Error::Custom("verified signal event blob must not be empty".into()))
  1891. }
  1892. self.header_dag_insert(vec![event.header.clone()], dag_name).await?;
  1893. if event.header.parents != NULL_PARENTS {
  1894. self.dag_blob_store(&event.id(), blob)?;
  1895. }
  1896. self.dag_insert(std::slice::from_ref(event), dag_name).await
  1897. }
  1898. /// Insert events into a rotating DAG, with mandatory RLN
  1899. /// verification.
  1900. ///
  1901. /// `blobs` is index-aligned with `events`. Every non-genesis
  1902. /// event MUST have a non-empty `blobs[i]`; events that don't
  1903. /// (whether `blobs` is empty, shorter, or has an empty entry
  1904. /// at position `i`) are rejected with a loud log. This is the
  1905. /// strict policy required for sync paths - a peer that serves
  1906. /// an event without its blob is buggy or adversarial.
  1907. ///
  1908. /// On `Slashable`, this method does NOT broadcast a slash -
  1909. /// that's the protocol layer's job (see
  1910. /// `proto::handle_event_put::verify_rln_signal`). Sync-time
  1911. /// detection of a slashable conflict simply skips the event.
  1912. /// We don't want a node coming online to flood the network
  1913. /// with stale slash broadcasts.
  1914. pub async fn dag_insert_with_blobs(
  1915. &self,
  1916. events: &[Event],
  1917. blobs: &[Vec<u8>],
  1918. dag_name: &str,
  1919. ) -> Result<Vec<blake3::Hash>> {
  1920. self.dag_insert_inner(events, blobs, /* require_blobs */ true, dag_name).await
  1921. }
  1922. /// Inner implementation shared by both insert paths. The
  1923. /// `require_blobs` flag selects strict (sync) vs. lenient
  1924. /// (post-verified) semantics.
  1925. async fn dag_insert_inner(
  1926. &self,
  1927. events: &[Event],
  1928. blobs: &[Vec<u8>],
  1929. require_blobs: bool,
  1930. dag_name: &str,
  1931. ) -> Result<Vec<blake3::Hash>> {
  1932. if events.is_empty() {
  1933. return Ok(vec![])
  1934. }
  1935. // Pre-flight structural validation and RLN verification. Done
  1936. // BEFORE acquiring the DAG-store write lock so slow proof work does
  1937. // not hold up other inserts. Cheap structural checks run first, so
  1938. // malformed events cannot force proof verification or mutate RLN
  1939. // metadata.
  1940. //
  1941. // Events we already have are skipped without verification. This
  1942. // matters because `rln_verify_signal` records the share on `Accepted`,
  1943. // and re-running it for an already-seen event would trip its
  1944. // duplicate-share check.
  1945. let dag_ts = u64::from_str(dag_name)?;
  1946. let (already_have, structurally_valid): (Vec<bool>, Vec<bool>) = {
  1947. let store = self.dag_store.read().await;
  1948. let slot = store.get_slot(&dag_ts);
  1949. let mut already_have = Vec::with_capacity(events.len());
  1950. let mut structurally_valid = Vec::with_capacity(events.len());
  1951. for ev in events {
  1952. let eid = ev.id();
  1953. let have = match slot {
  1954. Some(s) => s.main_tree.contains_key(eid.as_bytes())?,
  1955. None => false,
  1956. };
  1957. already_have.push(have);
  1958. if have || ev.header.parents == NULL_PARENTS {
  1959. structurally_valid.push(true);
  1960. continue
  1961. }
  1962. let Some(slot) = slot else {
  1963. structurally_valid.push(false);
  1964. continue
  1965. };
  1966. if !slot.header_tree.contains_key(eid.as_bytes())? {
  1967. structurally_valid.push(false);
  1968. continue
  1969. }
  1970. structurally_valid
  1971. .push(ev.dag_validate(&slot.header_tree, &self.config, dag_ts).await?);
  1972. }
  1973. (already_have, structurally_valid)
  1974. };
  1975. let mut candidates: Vec<usize> = (0..events.len()).collect();
  1976. sort_event_indices(events, &mut candidates);
  1977. let mut accepted: Vec<usize> = Vec::with_capacity(events.len());
  1978. let mut accepted_body_ids = HashSet::with_capacity(events.len());
  1979. for i in candidates {
  1980. let ev = &events[i];
  1981. let eid = ev.id();
  1982. if !structurally_valid[i] {
  1983. error!(
  1984. target: "event_graph::dag_insert",
  1985. "[DAG_INSERT] event {} failed structural validation before RLN verification; skipping",
  1986. eid,
  1987. );
  1988. continue
  1989. }
  1990. // Already-known events go through structurally (the downstream
  1991. // `contains_key` check will skip them) but skip the RLN verifier to
  1992. // avoid double-recording the share for the same
  1993. // (epoch, internal_nullifier, x, y) tuple.
  1994. if already_have[i] {
  1995. accepted.push(i);
  1996. accepted_body_ids.insert(eid);
  1997. continue
  1998. }
  1999. // Genesis-shaped events have no blob and no proof - they're
  2000. // consensus inputs, not user signals.
  2001. if ev.header.parents == NULL_PARENTS {
  2002. accepted.push(i);
  2003. continue
  2004. }
  2005. if !self.parents_have_bodies(ev, dag_ts, &accepted_body_ids).await? {
  2006. error!(
  2007. target: "event_graph::dag_insert",
  2008. "[DAG_INSERT] event {} has a missing parent body; skipping before RLN verification",
  2009. eid,
  2010. );
  2011. continue
  2012. }
  2013. let blob = blobs.get(i).cloned().unwrap_or_default();
  2014. if blob.is_empty() {
  2015. if require_blobs {
  2016. error!(
  2017. target: "event_graph::dag_insert",
  2018. concat!(
  2019. "[DAG_INSERT] sync event {} arrived without an RLN blob; rejecting. ",
  2020. "Every non-genesis rotating-DAG event must carry a blob.",
  2021. ),
  2022. eid,
  2023. );
  2024. continue
  2025. }
  2026. // Lenient path: caller pre-verified. Accept the event
  2027. // structurally without running the RLN verifier on it.
  2028. accepted.push(i);
  2029. accepted_body_ids.insert(eid);
  2030. continue
  2031. }
  2032. match self.rln_verify_signal(ev, &blob).await {
  2033. rln::SignalCheck::Accepted => {
  2034. accepted.push(i);
  2035. accepted_body_ids.insert(eid);
  2036. }
  2037. rln::SignalCheck::Rejected => {
  2038. error!(
  2039. target: "event_graph::dag_insert",
  2040. "[DAG_INSERT] sync event {} failed RLN re-verification; skipping",
  2041. eid,
  2042. );
  2043. }
  2044. rln::SignalCheck::Slashable(_) => {
  2045. // The conflicting share is recorded inside
  2046. // `rln_verify_signal` ONLY on `Accepted`. On `Slashable` it
  2047. // returns the conflicting shares without mutating metadata,
  2048. // so we don't double-record. We don't broadcast a slash
  2049. // here - that's the live broadcast handler's job. We just
  2050. // skip the event.
  2051. error!(
  2052. target: "event_graph::dag_insert",
  2053. "[DAG_INSERT] sync event {} is slashable (slot reuse); skipping",
  2054. eid,
  2055. );
  2056. }
  2057. }
  2058. }
  2059. let mut bcast = self.broadcasted_ids.write().await;
  2060. let mut store = self.dag_store.write().await;
  2061. let slot = store.get_slot_mut(&dag_ts).ok_or(Error::DagSyncFailed)?;
  2062. let mut accepted = accepted;
  2063. sort_event_indices(events, &mut accepted);
  2064. let mut ids = Vec::with_capacity(accepted.len());
  2065. let mut committed_indices = Vec::with_capacity(accepted.len());
  2066. let mut overlay = SledTreeOverlay::new(&slot.main_tree);
  2067. let mut staged_body_ids = HashSet::with_capacity(accepted.len());
  2068. 'commit: for &i in &accepted {
  2069. let ev = &events[i];
  2070. let eid = ev.id();
  2071. if ev.header.parents == NULL_PARENTS {
  2072. continue
  2073. }
  2074. if slot.main_tree.contains_key(eid.as_bytes())? {
  2075. staged_body_ids.insert(eid);
  2076. continue
  2077. }
  2078. if !slot.header_tree.contains_key(eid.as_bytes())? {
  2079. continue
  2080. }
  2081. if !ev.dag_validate(&slot.header_tree, &self.config, dag_ts).await? {
  2082. return Err(Error::EventIsInvalid)
  2083. }
  2084. for pid in ev.header.parents.iter().filter(|pid| **pid != NULL_ID) {
  2085. if !staged_body_ids.contains(pid) && !slot.main_tree.contains_key(pid.as_bytes())? {
  2086. error!(
  2087. target: "event_graph::dag_insert",
  2088. "[DAG_INSERT] event {} has parent header {} but no committed parent body; skipping",
  2089. eid, pid,
  2090. );
  2091. continue 'commit
  2092. }
  2093. }
  2094. let se = serialize_async(ev).await;
  2095. overlay.insert(eid.as_bytes(), &se)?;
  2096. staged_body_ids.insert(eid);
  2097. if self.replay_mode {
  2098. replayer_log(&self.datastore, "insert".into(), se)?;
  2099. }
  2100. // Persist the blob alongside the event for future
  2101. // sync-time re-verification by other late-joiners.
  2102. if let Some(blob) = blobs.get(i) {
  2103. if !blob.is_empty() {
  2104. if require_blobs {
  2105. self.dag_blob_store(&eid, blob)?;
  2106. } else {
  2107. let _ = self.dag_blob_store(&eid, blob);
  2108. }
  2109. }
  2110. }
  2111. ids.push(eid);
  2112. committed_indices.push(i);
  2113. }
  2114. if let Some(b) = overlay.aggregate() {
  2115. slot.main_tree.apply_batch(b)?;
  2116. } else {
  2117. return Ok(vec![])
  2118. }
  2119. for &i in &committed_indices {
  2120. let ev = &events[i];
  2121. let eid = ev.id();
  2122. if ev.header.parents == NULL_PARENTS {
  2123. continue
  2124. }
  2125. for pid in ev.header.parents.iter() {
  2126. if *pid != NULL_ID {
  2127. for (layer, tips) in slot.tips.iter_mut() {
  2128. if *layer < ev.header.layer {
  2129. tips.remove(pid);
  2130. }
  2131. }
  2132. bcast.insert(*pid);
  2133. }
  2134. }
  2135. slot.tips.retain(|_, t| !t.is_empty());
  2136. slot.tips.entry(ev.header.layer).or_default().insert(eid);
  2137. self.event_pub.notify(ev.clone()).await;
  2138. }
  2139. Ok(ids)
  2140. }
  2141. async fn parents_have_bodies(
  2142. &self,
  2143. ev: &Event,
  2144. dag_ts: u64,
  2145. accepted_body_ids: &HashSet<blake3::Hash>,
  2146. ) -> Result<bool> {
  2147. let store = self.dag_store.read().await;
  2148. let Some(slot) = store.get_slot(&dag_ts) else { return Ok(false) };
  2149. for pid in ev.header.parents.iter().filter(|pid| **pid != NULL_ID) {
  2150. if !accepted_body_ids.contains(pid) && !slot.main_tree.contains_key(pid.as_bytes())? {
  2151. return Ok(false)
  2152. }
  2153. }
  2154. Ok(true)
  2155. }
  2156. pub async fn header_dag_insert(&self, headers: Vec<Header>, dag_name: &str) -> Result<()> {
  2157. let dag_ts = u64::from_str(dag_name)?;
  2158. // The genesis ID we expect any layer-1 header in this slot
  2159. // to reference. Computed locally from config - two networks
  2160. // with different `genesis_contents` (or any other config
  2161. // mismatch) produce different genesis ids, so a peer whose
  2162. // layer-1 headers reference something else is on a different
  2163. // network. Catching this explicitly here is strictly a
  2164. // defense-in-depth and diagnostics improvement: the existing
  2165. // parent-existence check in `Header::validate` already
  2166. // rejects these (genesis headers are filtered from
  2167. // `header_tree` on insert, so a foreign genesis id never
  2168. // lands in the local tree). The explicit boundary check just
  2169. // turns "HeaderIsInvalid" into a logged, named condition, so
  2170. // an operator debugging a misconfigured deployment sees
  2171. // "peer is on a different network" instead of a generic
  2172. // header rejection.
  2173. //
  2174. // Why layer 1 is sufficient: `select_parents_from_tips` puts
  2175. // an event at layer N+1 where N is the highest layer with
  2176. // tips. For layer = 1, the highest tip layer must be 0, and
  2177. // the only layer-0 entry in any slot is the genesis (the
  2178. // single event placed by `DagStore::create_slot`). So every
  2179. // layer-1 event's non-NULL parents are equal to that slot's
  2180. // genesis id. Higher layers don't need the check because
  2181. // their parent chains transitively pass through layer 1; if
  2182. // the layer-1 events get rejected, layer-2+ events lose
  2183. // their referenced parents and fail the existing parent-
  2184. // existence check.
  2185. let local_genesis_id = Header {
  2186. timestamp: dag_ts,
  2187. parents: NULL_PARENTS,
  2188. layer: 0,
  2189. content_hash: blake3::hash(&self.config.genesis_contents),
  2190. }
  2191. .id();
  2192. let mut store = self.dag_store.write().await;
  2193. let slot = store.get_slot_mut(&dag_ts).ok_or(Error::DagSyncFailed)?;
  2194. let mut overlay = SledTreeOverlay::new(&slot.header_tree);
  2195. let mut staged_headers = Vec::new();
  2196. let mut hdrs = headers;
  2197. hdrs.sort_by_key(|h| h.layer);
  2198. for hdr in &hdrs {
  2199. if hdr.parents == NULL_PARENTS {
  2200. continue
  2201. }
  2202. // Cross-network detection at the layer-1 boundary.
  2203. if hdr.layer == 1 {
  2204. for pid in hdr.parents.iter() {
  2205. if *pid != NULL_ID && *pid != local_genesis_id {
  2206. error!(
  2207. target: "event_graph::header_dag_insert",
  2208. "[HEADER_DAG_INSERT] layer-1 header for dag {dag_ts} \
  2209. references foreign genesis: claimed parent {pid:?}, \
  2210. local genesis is {local_genesis_id:?}. Peer is on a \
  2211. different network.",
  2212. );
  2213. return Err(Error::HeaderIsInvalid)
  2214. }
  2215. }
  2216. }
  2217. let hid = hdr.id();
  2218. if overlay.get(hid.as_bytes())?.is_some() {
  2219. continue
  2220. }
  2221. if !hdr.validate(&slot.header_tree, &self.config, dag_ts, Some(&overlay)).await? {
  2222. return Err(Error::HeaderIsInvalid)
  2223. }
  2224. overlay.insert(hid.as_bytes(), &serialize_async(hdr).await)?;
  2225. staged_headers.push((hdr.timestamp, hid));
  2226. }
  2227. if let Some(b) = overlay.aggregate() {
  2228. slot.header_tree.apply_batch(b)?;
  2229. for (timestamp, hid) in staged_headers {
  2230. slot.time_index.insert(timestamp, hid);
  2231. }
  2232. }
  2233. Ok(())
  2234. }
  2235. pub async fn fetch_event_from_dags(&self, eid: &blake3::Hash) -> Result<Option<Event>> {
  2236. for (_, slot) in self.dag_store.read().await.dags.iter() {
  2237. if let Some(b) = slot.main_tree.get(eid.as_bytes())? {
  2238. return Ok(Some(deserialize_async(&b).await?))
  2239. }
  2240. }
  2241. // Also check the static DAG. Static events (RLN registrations
  2242. // and slashes) are public consensus state, so they're served
  2243. // alongside rotating-DAG events through the same EventReq
  2244. // path. This is what lets a fresh peer's `static_sync` walk
  2245. // ancestry through EventReq after discovering tips.
  2246. if let Some(b) = self.static_dag.get(eid.as_bytes())? {
  2247. return Ok(Some(deserialize_async(&b).await?))
  2248. }
  2249. Ok(None)
  2250. }
  2251. pub(crate) async fn get_next_layer_with_parents(
  2252. &self,
  2253. dag_ts: &u64,
  2254. ) -> Result<(u64, [blake3::Hash; N_EVENT_PARENTS])> {
  2255. let store = self.dag_store.read().await;
  2256. let slot = store
  2257. .get_slot(dag_ts)
  2258. .ok_or_else(|| Error::Custom(format!("event graph DAG slot {dag_ts} not found")))?;
  2259. Ok(select_parents_from_tips(&slot.tips))
  2260. }
  2261. pub(crate) async fn get_next_layer_with_parents_static(
  2262. &self,
  2263. ) -> Result<(u64, [blake3::Hash; N_EVENT_PARENTS])> {
  2264. let tips = compute_unreferenced_tips(&self.static_dag).await?;
  2265. Ok(select_parents_from_tips(&tips))
  2266. }
  2267. pub async fn order_events(&self) -> Result<Vec<Event>> {
  2268. let mut all = vec![];
  2269. for (_, slot) in self.dag_store.read().await.dags.iter() {
  2270. for item in slot.main_tree.iter() {
  2271. let (_, b) = item?;
  2272. let ev: Event = deserialize_async(&b).await?;
  2273. if ev.header.parents != NULL_PARENTS {
  2274. all.push(ev);
  2275. }
  2276. }
  2277. }
  2278. all.sort_unstable_by(display_order);
  2279. Ok(all)
  2280. }
  2281. pub async fn fetch_headers_with_tips(
  2282. &self,
  2283. dag_name: &str,
  2284. tips: &LayerUTips,
  2285. ) -> Result<Vec<Header>> {
  2286. if count_layer_tips(tips) > MAX_HEADER_REQ_TIPS {
  2287. return Err(Error::DagSyncFailed)
  2288. }
  2289. let dag_ts = u64::from_str(dag_name)?;
  2290. let store = self.dag_store.read().await;
  2291. let slot = store.get_slot(&dag_ts).ok_or(Error::DagSyncFailed)?;
  2292. let mut ancestors = HashSet::new();
  2293. for hashes in tips.values() {
  2294. for h in hashes {
  2295. ancestors.insert(*h);
  2296. if let Some(v) = slot.header_tree.get(h.as_bytes())? {
  2297. self.get_ancestors(
  2298. &mut ancestors,
  2299. deserialize_async(&v).await?,
  2300. &slot.header_tree,
  2301. )
  2302. .await?;
  2303. }
  2304. }
  2305. }
  2306. let mut out = Vec::with_capacity(MAX_HEADER_REP_HEADERS);
  2307. let sort_headers = |headers: &mut Vec<Header>| {
  2308. headers.sort_unstable_by(|a, b| {
  2309. a.layer.cmp(&b.layer).then_with(|| a.id().as_bytes().cmp(b.id().as_bytes()))
  2310. });
  2311. };
  2312. for item in slot.header_tree.iter() {
  2313. let (id, v) = item?;
  2314. let h = blake3::Hash::from_bytes((&id as &[u8]).try_into()?);
  2315. if ancestors.contains(&h) {
  2316. continue
  2317. }
  2318. out.push(deserialize_async(&v).await?);
  2319. if out.len() >= MAX_HEADER_REP_HEADERS * 2 {
  2320. sort_headers(&mut out);
  2321. out.truncate(MAX_HEADER_REP_HEADERS);
  2322. }
  2323. }
  2324. sort_headers(&mut out);
  2325. out.truncate(MAX_HEADER_REP_HEADERS);
  2326. Ok(out)
  2327. }
  2328. pub(crate) async fn get_ancestors(
  2329. &self,
  2330. visited: &mut HashSet<blake3::Hash>,
  2331. hdr: Header,
  2332. tree: &sled::Tree,
  2333. ) -> Result<()> {
  2334. let mut stack = VecDeque::new();
  2335. stack.push_back(hdr);
  2336. while let Some(h) = stack.pop_back() {
  2337. for p in h.parents {
  2338. if p != NULL_ID && visited.insert(p) {
  2339. if let Some(v) = tree.get(p.as_bytes())? {
  2340. stack.push_back(deserialize_async(&v).await?);
  2341. }
  2342. }
  2343. }
  2344. }
  2345. Ok(())
  2346. }
  2347. async fn static_new(sled_db: &sled::Db, config: &EventGraphConfig) -> Result<sled::Tree> {
  2348. let tree = sled_db.open_tree("static-dag")?;
  2349. let genesis = generate_static_genesis(config);
  2350. let mut ov = SledTreeOverlay::new(&tree);
  2351. ov.insert(genesis.id().as_bytes(), &serialize_async(&genesis).await)?;
  2352. if let Some(b) = ov.aggregate() {
  2353. tree.apply_batch(b)?;
  2354. }
  2355. Ok(tree)
  2356. }
  2357. pub async fn static_broadcast(&self, ev: Event, blob: Vec<u8>) -> Result<()> {
  2358. self.p2p.broadcast(&StaticPut(ev, blob)).await;
  2359. Ok(())
  2360. }
  2361. fn static_persist_serialized(&self, ev_id: &blake3::Hash, ev_bytes: &[u8]) -> Result<()> {
  2362. let mut ov = SledTreeOverlay::new(&self.static_dag);
  2363. ov.insert(ev_id.as_bytes(), ev_bytes)?;
  2364. if let Some(b) = ov.aggregate() {
  2365. self.static_dag.apply_batch(b)?;
  2366. }
  2367. Ok(())
  2368. }
  2369. #[cfg(test)]
  2370. pub(crate) async fn static_insert(&self, ev: &Event) -> Result<()> {
  2371. let ev_bytes = serialize_async(ev).await;
  2372. self.static_persist_serialized(&ev.id(), &ev_bytes)?;
  2373. self.static_pub.notify(ev.clone()).await;
  2374. Ok(())
  2375. }
  2376. /// Durably commit a verified static RLN event.
  2377. ///
  2378. /// The write order is intentional: blob first, static DAG second, RLN
  2379. /// state last. If a process crashes after the static event becomes
  2380. /// durable but before the identity tree or historical-root indexes are
  2381. /// updated, startup recovery can rebuild those RLN side tables from the
  2382. /// static DAG. Subscribers are notified only after the RLN apply step, so
  2383. /// applications observe the same post-state semantics as the receive path.
  2384. pub async fn commit_verified_static_event(
  2385. &self,
  2386. ev: &Event,
  2387. blob: &[u8],
  2388. rln_node: &rln::RLNNode,
  2389. ) -> Result<pallas::Base> {
  2390. if blob.is_empty() {
  2391. return Err(Error::Custom("static RLN event blob must not be empty".into()))
  2392. }
  2393. let ev_id = ev.id();
  2394. let ev_bytes = serialize_async(ev).await;
  2395. let mut state = self.identity_state.write().await;
  2396. Self::ensure_rln_static_event_transition(&state, rln_node)?;
  2397. self.static_blob_store(&ev_id, blob)?;
  2398. self.static_persist_serialized(&ev_id, &ev_bytes)?;
  2399. let root = self.apply_rln_static_event_locked(ev, rln_node, &mut state)?;
  2400. drop(state);
  2401. self.static_pub.notify(ev.clone()).await;
  2402. Ok(root)
  2403. }
  2404. pub async fn static_fetch(&self, eid: &blake3::Hash) -> Result<Option<Event>> {
  2405. Ok(match self.static_dag.get(eid.as_bytes())? {
  2406. Some(b) => Some(deserialize_async(&b).await?),
  2407. None => None,
  2408. })
  2409. }
  2410. pub async fn static_unreferenced_tips(&self) -> Result<LayerUTips> {
  2411. compute_unreferenced_tips(&self.static_dag).await
  2412. }
  2413. /// Audit static-DAG blob coverage and repair deterministic guard blobs.
  2414. ///
  2415. /// A static DAG event without its RLN blob cannot be served to late
  2416. /// joiners because they must re-verify historical static events. The only
  2417. /// blob we can safely reconstruct is the pregenerated-registration guard:
  2418. /// it is valid exactly for commitments supplied by this app config. Slash
  2419. /// blobs and future staked registration proofs are not reconstructible and
  2420. /// are logged for operator intervention.
  2421. async fn audit_static_blobs(&self) -> Result<()> {
  2422. let mut repaired = 0usize;
  2423. let mut unrecoverable = 0usize;
  2424. let mut malformed = 0usize;
  2425. for item in self.static_dag.iter() {
  2426. let (_, val) = item?;
  2427. let ev: Event = deserialize_async(&val).await?;
  2428. if ev.header.parents == NULL_PARENTS {
  2429. continue
  2430. }
  2431. if matches!(self.static_blob_fetch(&ev.id())?, Some(blob) if !blob.is_empty()) {
  2432. continue
  2433. }
  2434. let rln_node: rln::RLNNode = match deserialize_async_partial(ev.content()).await {
  2435. Ok((node, _)) => node,
  2436. Err(_) => {
  2437. malformed += 1;
  2438. continue
  2439. }
  2440. };
  2441. match rln_node {
  2442. rln::RLNNode::Registration(commitment)
  2443. if self
  2444. .pregenerated_identity_commitment_reprs
  2445. .contains(&commitment.to_repr()) =>
  2446. {
  2447. self.static_blob_store(&ev.id(), rln::GENESIS_BLOB_GUARD)?;
  2448. repaired += 1;
  2449. }
  2450. _ => {
  2451. unrecoverable += 1;
  2452. warn!(
  2453. target: "event_graph::new",
  2454. "[EVENTGRAPH] static event {} is missing its RLN blob and cannot be reconstructed",
  2455. ev.id(),
  2456. );
  2457. }
  2458. }
  2459. }
  2460. if repaired > 0 || unrecoverable > 0 || malformed > 0 {
  2461. info!(
  2462. target: "event_graph::new",
  2463. "[EVENTGRAPH] static blob audit: repaired={} unrecoverable={} malformed={}",
  2464. repaired, unrecoverable, malformed,
  2465. );
  2466. }
  2467. Ok(())
  2468. }
  2469. /// Persist the original RLN blob for a static-DAG event. The
  2470. /// blob is the wire payload from the originating `StaticPut` -
  2471. /// proof + public inputs + attestation - needed to re-verify
  2472. /// the proof at sync time by late-joining peers.
  2473. ///
  2474. /// Writing the same `(eid, blob)` repeatedly is safe.
  2475. pub fn static_blob_store(&self, eid: &blake3::Hash, blob: &[u8]) -> Result<()> {
  2476. self.static_dag_blobs.insert(eid.as_bytes(), blob)?;
  2477. Ok(())
  2478. }
  2479. /// Look up the original RLN blob for a static-DAG event.
  2480. ///
  2481. /// Returns `Ok(None)` only for legitimate "not stored" cases -
  2482. /// a peer that's never seen the event, or an event that pre-dates
  2483. /// the side-table. The verification path in `static_sync` treats
  2484. /// missing blobs on non-genesis events as a sync failure, not as
  2485. /// a fall-through.
  2486. pub fn static_blob_fetch(&self, eid: &blake3::Hash) -> Result<Option<Vec<u8>>> {
  2487. Ok(self.static_dag_blobs.get(eid.as_bytes())?.map(|ivec| ivec.to_vec()))
  2488. }
  2489. /// Persist the original RLN signal blob for a rotating-DAG event.
  2490. ///
  2491. /// Mirror of [`Self::static_blob_store`] but for rotating-DAG
  2492. /// events. Idempotent. Called after successful RLN verification
  2493. /// in `handle_event_put`, and during sync by
  2494. /// `dag_insert_with_blobs` when the peer included a blob in
  2495. /// `EventRep`.
  2496. pub fn dag_blob_store(&self, eid: &blake3::Hash, blob: &[u8]) -> Result<()> {
  2497. self.dag_blobs.insert(eid.as_bytes(), blob)?;
  2498. Ok(())
  2499. }
  2500. /// Look up the original RLN signal blob for a rotating-DAG
  2501. /// event. Returns `Ok(None)` if not stored - see
  2502. /// [`Self::static_blob_fetch`] for the exhaustive list of
  2503. /// reasons a blob may legitimately be missing.
  2504. ///
  2505. /// Note: rotating-DAG blobs are pruned alongside their DAGs
  2506. /// (see `dag_blobs_prune`). Older-than-window events therefore
  2507. /// don't accumulate blobs in this side-table.
  2508. pub fn dag_blob_fetch(&self, eid: &blake3::Hash) -> Result<Option<Vec<u8>>> {
  2509. Ok(self.dag_blobs.get(eid.as_bytes())?.map(|ivec| ivec.to_vec()))
  2510. }
  2511. /// Apply a static-DAG event (registration or slash) to the
  2512. /// identity-state SMT, and record the resulting root in the
  2513. /// historical-roots side-tables.
  2514. ///
  2515. /// **This is the single canonical entry point** for SMT
  2516. /// mutation. All callers - live broadcast (`handle_static_put`),
  2517. /// originator (`nickserv.rs::handle_register`), and sync
  2518. /// (`static_sync` apply loop) - go through here. Bypassing it
  2519. /// will desynchronize the SMT from the historical-roots tables,
  2520. /// which silently breaks signal verification.
  2521. ///
  2522. /// **Canonical order requirement.** Two nodes processing the
  2523. /// same set of static events must produce the same sequence of
  2524. /// intermediate roots. SMTs are commutative under set-of-leaves
  2525. /// (final root is order-independent) but the *intermediate*
  2526. /// roots produced during application are order-dependent. We
  2527. /// pin the order with `(layer, event_id)`: layer is the primary
  2528. /// key (defined by the event's parent links and consensus-agreed),
  2529. /// event_id is the tie-breaker within a layer (32-byte hash,
  2530. /// total-ordered lexicographically).
  2531. ///
  2532. /// In live broadcast and originator paths, events arrive one at
  2533. /// a time; the canonical-order requirement is automatically
  2534. /// satisfied because each event's layer is greater than its
  2535. /// parents'. In sync, the caller must sort by `(layer, event_id)`
  2536. /// before invoking this method (see `static_sync`).
  2537. ///
  2538. /// **Returns** the post-mutation SMT root, or an error if the
  2539. /// SMT mutation itself fails. A duplicate-registration or
  2540. /// slash-of-nonexistent are both treated as soft no-ops at the
  2541. /// SMT layer, but we still record the root (which equals the
  2542. /// pre-call root in that case) - this preserves the invariant
  2543. /// that "every static-DAG event has a corresponding entry in
  2544. /// rln-historical-roots-ordered" without complicating the
  2545. /// caller's logic.
  2546. pub async fn apply_rln_static_event(
  2547. &self,
  2548. ev: &Event,
  2549. node: &rln::RLNNode,
  2550. ) -> Result<pallas::Base> {
  2551. let mut state = self.identity_state.write().await;
  2552. self.apply_rln_static_event_locked(ev, node, &mut state)
  2553. }
  2554. fn ensure_rln_static_event_transition(
  2555. state: &rln::IdentityState,
  2556. node: &rln::RLNNode,
  2557. ) -> Result<()> {
  2558. match node {
  2559. rln::RLNNode::Registration(commitment) => {
  2560. if state.contains(commitment) || state.is_slashed(commitment) {
  2561. return Err(Error::Custom(
  2562. "static RLN registration is duplicate or slashed".into(),
  2563. ))
  2564. }
  2565. }
  2566. rln::RLNNode::Slashing(_) => {}
  2567. }
  2568. Ok(())
  2569. }
  2570. fn apply_rln_static_event_locked(
  2571. &self,
  2572. ev: &Event,
  2573. node: &rln::RLNNode,
  2574. state: &mut rln::IdentityState,
  2575. ) -> Result<pallas::Base> {
  2576. match node {
  2577. rln::RLNNode::Registration(commitment) => {
  2578. // Soft-fail on duplicate during internal replay/rebuild.
  2579. let _ = state.register(*commitment);
  2580. }
  2581. rln::RLNNode::Slashing(commitment) => {
  2582. // Slashes are durable evidence. Replayed slashes keep the
  2583. // tombstone and record another static root entry.
  2584. let _ = state.slash(*commitment);
  2585. }
  2586. }
  2587. let new_root = state.root();
  2588. // Record the root in both side-tables. We do this even if
  2589. // the SMT mutation was a no-op (duplicate register, slash of
  2590. // missing) so the historical-roots table has one entry per
  2591. // static-DAG event. This makes canonical-order replay simple:
  2592. // every event has exactly one entry, no conditional skips.
  2593. let key = encode_historical_root_key(ev.header.layer, &ev.id());
  2594. let value = encode_historical_root_value(&new_root, ev.header.timestamp);
  2595. self.rln_historical_roots_ordered.insert(key, value.as_slice())?;
  2596. let by_value_key = encode_historical_root_by_value_key(&new_root, &key);
  2597. self.rln_historical_roots_by_value.insert(by_value_key, &[])?;
  2598. Ok(new_root)
  2599. }
  2600. /// Check whether `root` is a valid SMT root for a signal whose
  2601. /// `signal_timestamp` is given (in millis-since-epoch).
  2602. ///
  2603. /// A root is valid if it was the live root at any time in the
  2604. /// drift window `[signal_timestamp - DRIFT, signal_timestamp +
  2605. /// DRIFT]`. The drift symmetry handles two distinct concerns:
  2606. ///
  2607. /// * **Forward drift** (signal sees a slightly stale root): the
  2608. /// originator built a proof against root `R_n`, then someone
  2609. /// else registered, producing `R_{n+1}`, before the signal
  2610. /// reached the verifier. The signal's claimed `R_n` is older
  2611. /// than the verifier's current root by an amount up to the
  2612. /// propagation delay. Accept if R_n was current within DRIFT
  2613. /// of the signal's timestamp.
  2614. ///
  2615. /// * **Backward drift** (signal arrives before its root): rare
  2616. /// but possible if the static-DAG broadcast is racing the
  2617. /// rotating-DAG broadcast. The originator's machine knew about
  2618. /// a registration that hadn't fully propagated yet. We tolerate
  2619. /// up to DRIFT of backward skew.
  2620. ///
  2621. /// The check uses `rln_historical_roots_by_value` to find every
  2622. /// canonical position where `root` appears, then
  2623. /// `rln_historical_roots_ordered` to bracket each interval during
  2624. /// which `root` was live. Each interval starts at the timestamp
  2625. /// of an event that produced `root` and ends just before the next
  2626. /// event timestamp (or `u64::MAX` if `root` is currently live).
  2627. ///
  2628. /// The verifier calls this for every non-current root. Current-root
  2629. /// verification stays on the in-memory fast path, while recent but
  2630. /// non-current roots still pass through this timestamp-window check.
  2631. pub fn is_root_valid_at(&self, root: &pallas::Base, signal_timestamp: u64) -> Result<bool> {
  2632. let drift = EVENT_TIME_DRIFT;
  2633. let lo = signal_timestamp.saturating_sub(drift);
  2634. let hi = signal_timestamp.saturating_add(drift);
  2635. for item in self.rln_historical_roots_by_value.scan_prefix(root.to_repr()) {
  2636. let (by_value_key, _) = item?;
  2637. if by_value_key.len() != 72 {
  2638. continue
  2639. }
  2640. let ordered_key = &by_value_key[32..];
  2641. let Some(value_bytes) = self.rln_historical_roots_ordered.get(ordered_key)? else {
  2642. continue
  2643. };
  2644. let (recorded_root, root_timestamp) = decode_historical_root_value(&value_bytes)?;
  2645. if &recorded_root != root {
  2646. continue
  2647. }
  2648. let next_timestamp: u64 = {
  2649. use std::ops::Bound::{Excluded, Unbounded};
  2650. match self
  2651. .rln_historical_roots_ordered
  2652. .range::<&[u8], _>((Excluded(ordered_key), Unbounded))
  2653. .next()
  2654. {
  2655. Some(Ok((_, val))) => decode_historical_root_value(&val)?.1,
  2656. Some(Err(e)) => return Err(e.into()),
  2657. None => u64::MAX,
  2658. }
  2659. };
  2660. if root_timestamp <= hi && next_timestamp > lo {
  2661. return Ok(true)
  2662. }
  2663. }
  2664. Ok(false)
  2665. }
  2666. }
  2667. fn encode_historical_root_key(layer: u64, event_id: &blake3::Hash) -> [u8; 40] {
  2668. let mut buf = [0u8; 40];
  2669. buf[..8].copy_from_slice(&layer.to_be_bytes());
  2670. buf[8..].copy_from_slice(event_id.as_bytes());
  2671. buf
  2672. }
  2673. fn encode_historical_root_value(root: &pallas::Base, timestamp: u64) -> [u8; 40] {
  2674. let mut buf = [0u8; 40];
  2675. buf[..32].copy_from_slice(&root.to_repr());
  2676. buf[32..].copy_from_slice(&timestamp.to_be_bytes());
  2677. buf
  2678. }
  2679. fn encode_historical_root_by_value_key(root: &pallas::Base, ordered_key: &[u8; 40]) -> [u8; 72] {
  2680. let mut buf = [0u8; 72];
  2681. buf[..32].copy_from_slice(&root.to_repr());
  2682. buf[32..].copy_from_slice(ordered_key);
  2683. buf
  2684. }
  2685. fn decode_historical_root_value(bytes: &[u8]) -> Result<(pallas::Base, u64)> {
  2686. if bytes.len() != 40 {
  2687. return Err(Error::Custom(format!(
  2688. "historical-root value must be 40 bytes, got {}",
  2689. bytes.len()
  2690. )))
  2691. }
  2692. let mut root_repr = [0u8; 32];
  2693. root_repr.copy_from_slice(&bytes[..32]);
  2694. let root: pallas::Base = match pallas::Base::from_repr(root_repr).into() {
  2695. Some(r) => r,
  2696. None => return Err(Error::Custom("invalid root encoding".into())),
  2697. };
  2698. let mut ts_bytes = [0u8; 8];
  2699. ts_bytes.copy_from_slice(&bytes[32..]);
  2700. Ok((root, u64::from_be_bytes(ts_bytes)))
  2701. }
  2702. impl EventGraph {
  2703. /// Return a JSON-RPC response representing the current state of
  2704. /// the event graph.
  2705. ///
  2706. /// Shape (matches [`util::recreate_from_replayer_log`] so clients
  2707. /// can reuse their parsers):
  2708. ///
  2709. /// ```json
  2710. /// {
  2711. /// "eventgraph_info": {
  2712. /// "dag": {
  2713. /// "<event-id-hex>": <event>,
  2714. /// ...
  2715. /// }
  2716. /// }
  2717. /// }
  2718. /// ```
  2719. ///
  2720. /// Walks every event currently held in every rotating DAG *and*
  2721. /// every event in the static DAG. Genesis events are included.
  2722. #[cfg(feature = "rpc")]
  2723. pub async fn eventgraph_info(
  2724. &self,
  2725. id: i64,
  2726. _params: crate::rpc::util::JsonValue,
  2727. ) -> crate::rpc::jsonrpc::JsonResult {
  2728. use crate::rpc::{
  2729. jsonrpc::{JsonResponse, JsonResult},
  2730. util::{json_map, JsonValue},
  2731. };
  2732. let mut dag = HashMap::new();
  2733. // Walk every rotating DAG.
  2734. for (_, slot) in self.dag_store.read().await.dags.iter() {
  2735. for item in slot.main_tree.iter() {
  2736. let (eid, val) = match item {
  2737. Ok(v) => v,
  2738. Err(_) => continue,
  2739. };
  2740. let Ok(ev) = deserialize_async::<Event>(&val).await else { continue };
  2741. let key = blake3::Hash::from_bytes(match (&eid as &[u8]).try_into() {
  2742. Ok(b) => b,
  2743. Err(_) => continue,
  2744. });
  2745. dag.insert(key.to_string(), JsonValue::from(ev));
  2746. }
  2747. }
  2748. // And the static DAG.
  2749. for item in self.static_dag.iter() {
  2750. let (eid, val) = match item {
  2751. Ok(v) => v,
  2752. Err(_) => continue,
  2753. };
  2754. let Ok(ev) = deserialize_async::<Event>(&val).await else { continue };
  2755. let key = blake3::Hash::from_bytes(match (&eid as &[u8]).try_into() {
  2756. Ok(b) => b,
  2757. Err(_) => continue,
  2758. });
  2759. dag.insert(key.to_string(), JsonValue::from(ev));
  2760. }
  2761. let values = json_map([("dag", JsonValue::Object(dag))]);
  2762. let result = JsonValue::Object(HashMap::from([("eventgraph_info".into(), values)]));
  2763. JsonResult::Response(JsonResponse::new(result, id))
  2764. }
  2765. pub fn deg_enable(&self) {
  2766. self.deg_enabled.store(true, Ordering::Release);
  2767. }
  2768. pub fn deg_disable(&self) {
  2769. self.deg_enabled.store(false, Ordering::Release);
  2770. }
  2771. pub fn is_deg_enabled(&self) -> bool {
  2772. self.deg_enabled.load(Ordering::Acquire)
  2773. }
  2774. pub fn is_synced(&self) -> bool {
  2775. self.synced.load(Ordering::Acquire)
  2776. }
  2777. pub async fn deg_subscribe(&self) -> Subscription<DegEvent> {
  2778. self.deg_publisher.clone().subscribe().await
  2779. }
  2780. pub async fn deg_notify(&self, ev: DegEvent) {
  2781. self.deg_publisher.notify(ev).await;
  2782. }
  2783. /// Subscribe to rotating-DAG event insertions.
  2784. ///
  2785. /// Each subscriber receives a clone of every [`Event`] that
  2786. /// passes validation and is committed via `dag_insert`. The
  2787. /// publisher fires *after* state mutation, so subscribers can
  2788. /// rely on the event being durably present in the DAG by the
  2789. /// time they observe it.
  2790. ///
  2791. /// Used to build live JSON-RPC subscription endpoints (e.g.
  2792. /// the Gource-feeding endpoint in DarkIRC). Bridge a
  2793. /// [`Subscription`] to a `JsonSubscriber` in the application
  2794. /// layer; this method itself contains no JSON-RPC logic.
  2795. pub async fn event_subscribe(&self) -> Subscription<Event> {
  2796. self.event_pub.clone().subscribe().await
  2797. }
  2798. /// Subscribe to static-DAG event insertions (RLN registrations
  2799. /// and slashes). Mirrors [`Self::event_subscribe`] but for the
  2800. /// static DAG. See that method for semantics.
  2801. pub async fn static_subscribe(&self) -> Subscription<Event> {
  2802. self.static_pub.clone().subscribe().await
  2803. }
  2804. /// The app identifier mixed into RLN external nullifiers.
  2805. /// Exposed so the proto layer (and clients constructing signal
  2806. /// proofs) can use the same value the verifier uses.
  2807. pub fn rln_app_id(&self) -> rln::RlnAppId {
  2808. self.rln_app_id
  2809. }
  2810. /// Build a membership proof for an identity commitment, plus the
  2811. /// current root.
  2812. ///
  2813. /// This is the *only* sanctioned path for clients to produce a
  2814. /// signal proof - they should not be holding their own copy of
  2815. /// the SMT (the previous `event_graph.rln_identity_tree`
  2816. /// pattern). Centralising this keeps client and verifier in
  2817. /// agreement on the root they're proving against.
  2818. pub async fn rln_membership_path(
  2819. &self,
  2820. commitment: &darkfi_sdk::pasta::pallas::Base,
  2821. ) -> (darkfi_sdk::pasta::pallas::Base, darkfi_sdk::crypto::smt::PathFp) {
  2822. let s = self.identity_state.read().await;
  2823. (s.root(), s.prove_membership(commitment))
  2824. }
  2825. /// True if the given identity commitment is registered.
  2826. pub async fn rln_contains(&self, commitment: &darkfi_sdk::pasta::pallas::Base) -> bool {
  2827. self.identity_state.read().await.contains(commitment)
  2828. }
  2829. /// Verify an RLN signal blob against this event graph's state.
  2830. ///
  2831. /// The returned variant tells the caller what to do:
  2832. /// * `Accepted` - proof valid, no conflict, share recorded.
  2833. /// * `Rejected` - drop silently (bad proof, bad bounds, bad
  2834. /// root, exact duplicate).
  2835. /// * `Slashable(shares)` - different `(x, y)` for the same
  2836. /// internal nullifier; the caller (protocol layer) should
  2837. /// build and broadcast a slash from these shares.
  2838. ///
  2839. /// Critical invariant: `Rejected` and `Slashable` outcomes never
  2840. /// mutate `metadata`. `Accepted` is the only mutating outcome.
  2841. /// This is what prevents share-poisoning by an adversary who
  2842. /// observes an honest internal_nullifier and tries to forge a
  2843. /// share against it.
  2844. pub async fn rln_verify_signal(&self, event: &Event, blob: &[u8]) -> rln::SignalCheck {
  2845. use darkfi_sdk::{crypto::poseidon_hash, pasta::pallas};
  2846. use rln::{epoch_of, hash_event, Blob, SignalCheck, MAX_MSG_LIMIT};
  2847. let rcvd: Blob = match deserialize_async_partial(blob).await {
  2848. Ok((v, _)) => v,
  2849. Err(_) => return SignalCheck::Rejected,
  2850. };
  2851. // Defensive bounds. The proof PI binds these too, but
  2852. // checking up front lets us skip an expensive verify() call
  2853. // for trivially malformed blobs.
  2854. if rcvd.user_msg_limit == 0 || rcvd.user_msg_limit > MAX_MSG_LIMIT {
  2855. return SignalCheck::Rejected
  2856. }
  2857. let epoch_n = epoch_of(event.header.timestamp);
  2858. let epoch_field = pallas::Base::from(epoch_n);
  2859. let app_id = self.rln_app_id();
  2860. let ext_null = poseidon_hash([epoch_field, app_id.as_field()]);
  2861. let x = hash_event(event);
  2862. // 1) The merkle root must be valid for a signal at this
  2863. // timestamp. We accept any root that was the live SMT
  2864. // root at any time within EVENT_TIME_DRIFT of the signal's
  2865. // timestamp. This supports both live propagation races and
  2866. // sync of historical signals (signal timestamp = signing
  2867. // time, root corresponds to that historical state).
  2868. //
  2869. // Hot-path optimization: accept the current in-memory root
  2870. // without touching the historical index. Non-current roots,
  2871. // even if still present in the recent-roots cache, must pass
  2872. // the timestamp-window check below so old pre-slash roots
  2873. // cannot stay valid indefinitely.
  2874. {
  2875. let id_state = self.identity_state.read().await;
  2876. if !id_state.is_current_root(&rcvd.merkle_root) {
  2877. drop(id_state);
  2878. match self.is_root_valid_at(&rcvd.merkle_root, event.header.timestamp) {
  2879. Ok(true) => {}
  2880. Ok(false) => {
  2881. // Useful diagnostic: this rejection path
  2882. // catches both "garbage root" (attacker
  2883. // submitted a forged root) and "slashed-user
  2884. // replay" (slashed identity claiming a
  2885. // pre-slash root after the propagation
  2886. // window expired). The two are
  2887. // indistinguishable from the verifier's
  2888. // perspective by design - RLN-V2 privacy
  2889. // guarantees prevent identifying the
  2890. // signer. But observed in aggregate, a
  2891. // burst of these from a single peer is a
  2892. // strong signal of replay-after-slash
  2893. // misbehavior, useful for operators
  2894. // debugging "why are my messages being
  2895. // rejected" or investigating peer abuse.
  2896. let local_root = self.identity_state.read().await.root();
  2897. let historical_count = self.rln_historical_roots_ordered.len();
  2898. warn!(
  2899. target: "event_graph::rln_verify_signal",
  2900. "[RLN] Signal rejected: merkle_root not valid at signal \
  2901. timestamp {}. Possible causes: (1) forged or out-of-sync \
  2902. root, (2) slashed identity replaying against a pre-slash \
  2903. root after the propagation window expired. event_id={}, \
  2904. received_root={:?}, local_current_root={:?}, \
  2905. historical_root_count={}",
  2906. event.header.timestamp,
  2907. event.id(),
  2908. rcvd.merkle_root,
  2909. local_root,
  2910. historical_count,
  2911. );
  2912. return SignalCheck::Rejected
  2913. }
  2914. Err(e) => {
  2915. error!(
  2916. target: "event_graph::rln_verify_signal",
  2917. "[RLN] is_root_valid_at lookup failed for event {}: {e}",
  2918. event.id(),
  2919. );
  2920. return SignalCheck::Rejected
  2921. }
  2922. }
  2923. }
  2924. }
  2925. // 2) Verify the ZK proof. PI order MUST match
  2926. // constrain_instance() in rlnv2-diff-signal.zk:
  2927. // root, external_nullifier, user_message_limit, x, y, internal_nullifier
  2928. let pi = vec![
  2929. rcvd.merkle_root,
  2930. ext_null,
  2931. pallas::Base::from(rcvd.user_msg_limit),
  2932. x,
  2933. rcvd.y,
  2934. rcvd.internal_nullifier,
  2935. ];
  2936. if rcvd.proof.verify(&self.zk_keys.signal_vk, &pi).is_err() {
  2937. return SignalCheck::Rejected
  2938. }
  2939. // 3) Now consult the metadata table. Any share we look at
  2940. // here is guaranteed to be from a valid proof.
  2941. let mut state = self.rln_state.write().await;
  2942. // Prune metadata relative to THIS signal's epoch, not
  2943. // wall-clock. The retention window is conceptually
  2944. // "epochs near the signal we're processing", and the only
  2945. // entries that matter for reuse detection are siblings
  2946. // within `METADATA_RETAIN_EPOCHS` of `epoch_n`.
  2947. //
  2948. // In production, signals carry roughly-current wall-clock
  2949. // timestamps, so `epoch_n ~= current_epoch()` and this is
  2950. // equivalent to the previous `current_epoch()`-based prune.
  2951. // The change matters in three places:
  2952. //
  2953. // * Sync of historical signals: a late-arriving signal
  2954. // keeps its epoch's metadata visible long enough for
  2955. // the verifier to detect reuse. With wall-clock prune,
  2956. // a signal old enough to be outside the retention
  2957. // window would always silently lose its sibling shares
  2958. // before they could be matched.
  2959. //
  2960. // * Tests with deterministic event timestamps: the
  2961. // verifier and the metadata stay consistent regardless
  2962. // of when the test runs.
  2963. //
  2964. // * Minor DoS surface: a peer with a far-future system
  2965. // clock no longer causes mass-wipe of real metadata
  2966. // before consultation.
  2967. state.metadata.prune_old(epoch_n);
  2968. if state.metadata.is_duplicate(epoch_n, &rcvd.internal_nullifier, &x, &rcvd.y) {
  2969. return SignalCheck::Rejected
  2970. }
  2971. if state.metadata.is_reused(epoch_n, &rcvd.internal_nullifier) {
  2972. let mut shares = state.metadata.get_shares(epoch_n, &rcvd.internal_nullifier);
  2973. shares.push((x, rcvd.y));
  2974. return SignalCheck::Slashable(shares)
  2975. }
  2976. state.metadata.add_share(epoch_n, rcvd.internal_nullifier, x, rcvd.y);
  2977. SignalCheck::Accepted
  2978. }
  2979. /// Verify a static-DAG event (RLN registration or slashing)
  2980. /// against this event graph's state.
  2981. ///
  2982. /// This is the testable core of the protocol-layer
  2983. /// `handle_static_put`. It performs all checks that the
  2984. /// protocol layer does - bounds, attestation, proof, root
  2985. /// recency - and returns a [`rln::StaticEventCheck`] outcome
  2986. /// telling the caller what to do next.
  2987. ///
  2988. /// **This method does NOT mutate state.** The caller is
  2989. /// responsible for invoking `IdentityState::register` /
  2990. /// `slash` on `Accepted*` outcomes, and for striking the peer
  2991. /// on `Malicious`. Separating decision from action makes the
  2992. /// behaviour fully testable and lets the protocol layer keep
  2993. /// its mutation under a single locked critical section.
  2994. pub async fn rln_verify_static_event(
  2995. &self,
  2996. rln_node: &rln::RLNNode,
  2997. blob: &[u8],
  2998. event_timestamp: u64,
  2999. ) -> rln::StaticEventCheck {
  3000. use darkfi_sdk::crypto::poseidon_hash;
  3001. use rln::{RLNNode, SlashBlob, StaticEventCheck};
  3002. match rln_node {
  3003. RLNNode::Registration(commitment) => {
  3004. // Current admission policy is pregenerated identities only.
  3005. // The guard blob is valid exclusively for commitments supplied
  3006. // by the app config; pairing it with any other commitment is
  3007. // an unambiguous forgery attempt.
  3008. if blob == rln::GENESIS_BLOB_GUARD {
  3009. let repr = commitment.to_repr();
  3010. if self.pregenerated_identity_commitment_reprs.contains(&repr) {
  3011. let state = self.identity_state.read().await;
  3012. if state.contains(commitment) || state.is_slashed(commitment) {
  3013. return StaticEventCheck::Rejected
  3014. }
  3015. return StaticEventCheck::AcceptedRegistration(*commitment)
  3016. } else {
  3017. return StaticEventCheck::Malicious
  3018. }
  3019. }
  3020. // Non-pregenerated registration is intentionally disabled:
  3021. // an unstaked public tier is a sybil attack surface. Keep
  3022. // the proof scaffold below for the future staked tier, where
  3023. // acceptance must be backed by a DarkFi smart-contract
  3024. // attestation verified by event graph before mutating the
  3025. // identity tree.
  3026. StaticEventCheck::Rejected
  3027. /*
  3028. #[allow(unreachable_code)]
  3029. let reg: RegistrationBlob = match deserialize_async_partial(blob).await {
  3030. Ok((v, _)) => v,
  3031. Err(_) => return StaticEventCheck::Rejected,
  3032. };
  3033. // Bounds. Out-of-range limits are unambiguous misbehavior.
  3034. if reg.user_message_limit == 0 ||
  3035. reg.user_message_limit > MAX_MSG_LIMIT ||
  3036. reg.max_message_limit != MAX_MSG_LIMIT
  3037. {
  3038. return StaticEventCheck::Malicious
  3039. }
  3040. // Attestation must permit the claimed limit.
  3041. // (Dormant SPECIAL proof cap; `Staked` rejected until
  3042. // the DarkFi contract verifier is online.)
  3043. if !reg.attestation.permits(reg.user_message_limit) {
  3044. return StaticEventCheck::Malicious
  3045. }
  3046. // Duplicate registration is a soft Reject (we may
  3047. // have raced a peer), checked here so we don't
  3048. // pay the proof-verification cost for known leaves.
  3049. if self.identity_state.read().await.contains(commitment) {
  3050. return StaticEventCheck::Rejected
  3051. }
  3052. // Proof.
  3053. let pi = vec![
  3054. *commitment,
  3055. pallas::Base::from(reg.user_message_limit),
  3056. pallas::Base::from(reg.max_message_limit),
  3057. ];
  3058. if reg.proof.verify(&self.zk_keys.register_vk, &pi).is_err() {
  3059. return StaticEventCheck::Rejected
  3060. }
  3061. StaticEventCheck::AcceptedRegistration(*commitment)
  3062. */
  3063. }
  3064. RLNNode::Slashing(commitment) => {
  3065. let sl: SlashBlob = match deserialize_async_partial(blob).await {
  3066. Ok((v, _)) => v,
  3067. Err(_) => return StaticEventCheck::Rejected,
  3068. };
  3069. let pi = vec![sl.identity_secret_hash, sl.merkle_root];
  3070. if sl.proof.verify(&self.zk_keys.slash_vk, &pi).is_err() {
  3071. return StaticEventCheck::Rejected
  3072. }
  3073. // Slash event names a specific commitment; verify
  3074. // that the recovered identity_secret_hash actually
  3075. // maps to it. Mismatch is unambiguous misbehavior.
  3076. let rebuilt = poseidon_hash([sl.identity_secret_hash]);
  3077. if *commitment != rebuilt {
  3078. return StaticEventCheck::Malicious
  3079. }
  3080. // The proof's root must be a valid SMT root at the
  3081. // slash event's timestamp. Same logic as signal
  3082. // verification: use the time-window check, which
  3083. // accepts any root that was live within DRIFT of the
  3084. // slash timestamp.
  3085. {
  3086. let id_state = self.identity_state.read().await;
  3087. if !id_state.is_current_root(&sl.merkle_root) {
  3088. drop(id_state);
  3089. match self.is_root_valid_at(&sl.merkle_root, event_timestamp) {
  3090. Ok(true) => {}
  3091. Ok(false) | Err(_) => return StaticEventCheck::Rejected,
  3092. }
  3093. }
  3094. }
  3095. StaticEventCheck::AcceptedSlash(rebuilt)
  3096. }
  3097. }
  3098. }
  3099. /// Insert proof-less pregenerated identity commitments into the
  3100. /// static DAG, called once at startup after the static genesis
  3101. /// event itself is inserted. Idempotent - skips any commitment
  3102. /// already present in the identity tree.
  3103. pub async fn bootstrap_genesis_identities(&self) -> Result<()> {
  3104. // Deterministic for configured pregenerated identities.
  3105. let genesis_event = generate_static_genesis(&self.config);
  3106. let genesis_id = genesis_event.id();
  3107. if !self.static_dag.contains_key(genesis_id.as_bytes())? {
  3108. return Err(Error::Custom("static DAG genesis missing during bootstrap".into()))
  3109. }
  3110. let configured = self.pregenerated_identity_commitments.len();
  3111. let mut inserted = 0usize;
  3112. let mut skipped_active = 0usize;
  3113. let mut skipped_slashed = 0usize;
  3114. let mut skipped_existing_event = 0usize;
  3115. for commitment in self.pregenerated_identity_commitments.iter() {
  3116. {
  3117. let state = self.identity_state.read().await;
  3118. if state.contains(commitment) {
  3119. skipped_active += 1;
  3120. continue
  3121. }
  3122. if state.is_slashed(commitment) {
  3123. skipped_slashed += 1;
  3124. continue
  3125. }
  3126. }
  3127. let rln_node = rln::RLNNode::Registration(*commitment);
  3128. let content = serialize_async(&rln_node).await;
  3129. // Inserted at layer 1
  3130. let mut parents = [NULL_ID; N_EVENT_PARENTS];
  3131. parents[0] = genesis_id;
  3132. let header = Header {
  3133. timestamp: genesis_event.header.timestamp + 1,
  3134. parents,
  3135. layer: 1,
  3136. content_hash: blake3::hash(&content),
  3137. };
  3138. let event = Event { header, content };
  3139. if self.static_dag.contains_key(event.id().as_bytes())? {
  3140. skipped_existing_event += 1;
  3141. continue
  3142. }
  3143. let blob = rln::GENESIS_BLOB_GUARD.to_vec();
  3144. self.commit_verified_static_event(&event, &blob, &rln_node).await?;
  3145. inserted += 1;
  3146. }
  3147. info!(
  3148. target: "event_graph::new",
  3149. concat!(
  3150. "[EVENTGRAPH] Genesis RLN bootstrap: configured={} inserted={} ",
  3151. "skipped_active={} skipped_slashed={} skipped_existing_event={}",
  3152. ),
  3153. configured, inserted, skipped_active, skipped_slashed, skipped_existing_event,
  3154. );
  3155. Ok(())
  3156. }
  3157. }
  3158. async fn request_tips(
  3159. peer: &Channel,
  3160. dag: String,
  3161. timeout: u64,
  3162. ) -> Result<BTreeMap<u64, HashSet<blake3::Hash>>> {
  3163. let sub = peer.subscribe_msg::<TipRep>().await?;
  3164. peer.send(&TipReq(dag)).await?;
  3165. let r = sub
  3166. .receive_with_timeout(timeout)
  3167. .await
  3168. .map_err(|_| Error::EventNotFound("tip timeout".into()))?;
  3169. sub.unsubscribe().await;
  3170. if count_layer_tips(&r.0) > MAX_TIP_REP_TIPS {
  3171. return Err(Error::DagSyncFailed)
  3172. }
  3173. Ok(r.0.clone())
  3174. }
  3175. async fn request_header(
  3176. peer: &Channel,
  3177. name: String,
  3178. tips: LayerUTips,
  3179. timeout: u64,
  3180. ) -> Result<Vec<Header>> {
  3181. let sub = peer.subscribe_msg::<HeaderRep>().await?;
  3182. let tips = cap_layer_tips(&tips, MAX_HEADER_REQ_TIPS);
  3183. peer.send(&HeaderReq(name, tips)).await?;
  3184. let r = sub
  3185. .receive_with_timeout(timeout)
  3186. .await
  3187. .map_err(|_| Error::EventNotFound("hdr timeout".into()))?;
  3188. sub.unsubscribe().await;
  3189. if r.0.len() > MAX_HEADER_REP_HEADERS {
  3190. return Err(Error::DagSyncFailed)
  3191. }
  3192. Ok(r.0.to_vec())
  3193. }
  3194. async fn request_range(
  3195. peer: Arc<Channel>,
  3196. dag_name: String,
  3197. cursor: RangeCursor,
  3198. direction: SyncDirection,
  3199. limit: usize,
  3200. timeout: u64,
  3201. ) -> (Result<(Vec<Event>, Vec<Vec<u8>>, RangeCursor, bool)>, Arc<Channel>) {
  3202. let limit = limit.min(MAX_RANGE_PAGE_SIZE);
  3203. let Ok(limit) = u32::try_from(limit) else { return (Err(Error::DagSyncFailed), peer) };
  3204. let sub = match peer.subscribe_msg::<RangeRep>().await {
  3205. Ok(s) => s,
  3206. Err(e) => return (Err(e), peer),
  3207. };
  3208. if let Err(e) = peer.send(&RangeReq { dag_name, cursor, direction, limit }).await {
  3209. sub.unsubscribe().await;
  3210. return (Err(e), peer)
  3211. }
  3212. match sub.receive_with_timeout(timeout).await {
  3213. Ok(r) => {
  3214. sub.unsubscribe().await;
  3215. if r.0.len() > MAX_RANGE_PAGE_SIZE || r.1.len() > MAX_RANGE_PAGE_SIZE {
  3216. return (Err(Error::DagSyncFailed), peer)
  3217. }
  3218. (Ok((r.0.clone(), r.1.clone(), r.2, r.3)), peer)
  3219. }
  3220. Err(_) => {
  3221. sub.unsubscribe().await;
  3222. (Err(Error::EventNotFound("range timeout".into())), peer)
  3223. }
  3224. }
  3225. }
  3226. async fn request_event(
  3227. peer: Arc<Channel>,
  3228. ids: Vec<blake3::Hash>,
  3229. cid: usize,
  3230. timeout: u64,
  3231. ) -> (Result<(Vec<Event>, Vec<Vec<u8>>)>, usize, Arc<Channel>) {
  3232. if ids.len() > MAX_EVENT_REQ_IDS {
  3233. return (Err(Error::DagSyncFailed), cid, peer)
  3234. }
  3235. let sub = match peer.subscribe_msg::<EventRep>().await {
  3236. Ok(s) => s,
  3237. Err(e) => return (Err(e), cid, peer),
  3238. };
  3239. if let Err(e) = peer.send(&EventReq(ids)).await {
  3240. return (Err(e), cid, peer)
  3241. }
  3242. match sub.receive_with_timeout(timeout).await {
  3243. Ok(r) => {
  3244. sub.unsubscribe().await;
  3245. if r.0.len() > MAX_EVENT_REP_EVENTS || r.1.len() > MAX_EVENT_REP_EVENTS {
  3246. return (Err(Error::DagSyncFailed), cid, peer)
  3247. }
  3248. (Ok((r.0.clone(), r.1.clone())), cid, peer)
  3249. }
  3250. Err(_) => (Err(Error::EventNotFound("ev timeout".into())), cid, peer),
  3251. }
  3252. }