test_helpers.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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. use std::{
  19. collections::HashMap,
  20. sync::{
  21. atomic::{AtomicU16, Ordering},
  22. Arc, OnceLock,
  23. },
  24. };
  25. use darkfi_sdk::{crypto::pasta_prelude::PrimeField, pasta::pallas};
  26. use sled_overlay::sled;
  27. use smol::{channel, future, Executor};
  28. use url::Url;
  29. use crate::{
  30. error::Result,
  31. event_graph::{proto::ProtocolEventGraph, Event, EventGraph, EventGraphConfig, EventGraphPtr},
  32. net::{session::SESSION_DEFAULT, settings::NetworkProfile, P2p, Settings},
  33. };
  34. pub fn test_pregenerated_identity_commitments() -> Vec<[u8; 32]> {
  35. vec![pallas::Base::from(0x4556_4752_u64).to_repr()]
  36. }
  37. pub fn test_config() -> EventGraphConfig {
  38. EventGraphConfig {
  39. initial_genesis: 1_704_067_200_000, // 2024-01-01 UTC
  40. hours_rotation: 0,
  41. genesis_contents: b"darkfi-test-graph".to_vec(),
  42. pregenerated_identity_commitments: test_pregenerated_identity_commitments(),
  43. max_dags: Some(24),
  44. }
  45. }
  46. /// Bounded-mode config for tests that exercise [`DagStore`]
  47. /// directly (without constructing an [`EventGraph`]).
  48. ///
  49. /// Uses `hours_rotation = 1` so `DagStore::new` populates the
  50. /// 24-slot rotation ring (vs the single-slot path under
  51. /// `hours_rotation = 0`). Safe because no `EventGraph` is built,
  52. /// so there's no prune task to leak.
  53. pub fn bounded_dag_store_config() -> EventGraphConfig {
  54. EventGraphConfig { hours_rotation: 1, ..test_config() }
  55. }
  56. /// Archive-mode config: never evicts old DAGs and discovers
  57. /// existing trees from sled on construction. Like
  58. /// [`bounded_dag_store_config`] this is for `DagStore`-direct
  59. /// tests only.
  60. pub fn archive_config() -> EventGraphConfig {
  61. EventGraphConfig { max_dags: None, ..bounded_dag_store_config() }
  62. }
  63. /// Initialise tracing-subscriber once per process. Safe to call
  64. /// multiple times. Tests that want to see log output can call this
  65. /// at the top of their body.
  66. pub fn init_logger() {
  67. static INIT: std::sync::Once = std::sync::Once::new();
  68. INIT.call_once(|| {
  69. let _ = tracing_subscriber::fmt()
  70. .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
  71. .with_test_writer()
  72. .try_init();
  73. });
  74. }
  75. /// Process-wide [`ZkKeys`].
  76. fn shared_zk_keys() -> Arc<crate::event_graph::rln::ZkKeys> {
  77. use crate::event_graph::rln::{
  78. ZkKeys, RLN2_REGISTER_ZKBIN, RLN2_SIGNAL_ZKBIN, RLN2_SLASH_ZKBIN,
  79. };
  80. static SHARED: OnceLock<Arc<ZkKeys>> = OnceLock::new();
  81. SHARED
  82. .get_or_init(|| {
  83. // Hash the three .zk.bin blobs to derive a stable per-version
  84. // cache directory.
  85. let mut hasher = blake3::Hasher::new();
  86. hasher.update(RLN2_REGISTER_ZKBIN);
  87. hasher.update(RLN2_SIGNAL_ZKBIN);
  88. hasher.update(RLN2_SLASH_ZKBIN);
  89. let zkbin_hash = hasher.finalize().to_hex();
  90. let cache_dir =
  91. std::env::temp_dir().join(format!("darkfi-test-zk-cache-{}", &zkbin_hash[..16]));
  92. let db = sled::Config::new().path(&cache_dir).open().unwrap_or_else(|e| {
  93. panic!(
  94. "failed to open shared ZK key sled DB at {}: {e}\n\
  95. (if the cache is corrupted, run `rm -rf {}`)",
  96. cache_dir.display(),
  97. cache_dir.display(),
  98. )
  99. });
  100. let keys = ZkKeys::build_and_load(&db).expect("failed to build shared ZK keys");
  101. Arc::new(keys)
  102. })
  103. .clone()
  104. }
  105. pub async fn make_eg() -> EventGraphPtr {
  106. make_eg_with_config(test_config()).await
  107. }
  108. /// Construct an [`EventGraph`] with a caller-provided test config.
  109. pub async fn make_eg_with_config(config: EventGraphConfig) -> EventGraphPtr {
  110. let sled_db = sled::Config::new().temporary(true).open().unwrap();
  111. make_eg_with_config_and_db(config, sled_db).await
  112. }
  113. /// Construct an [`EventGraph`] with a caller-provided config and sled DB.
  114. pub async fn make_eg_with_config_and_db(
  115. config: EventGraphConfig,
  116. sled_db: sled::Db,
  117. ) -> EventGraphPtr {
  118. let ex = Arc::new(Executor::new());
  119. let p2p = P2p::new(Settings::default(), ex.clone()).await.unwrap();
  120. EventGraph::with_zk_keys(p2p, sled_db, "/tmp".into(), false, config, shared_zk_keys(), ex)
  121. .await
  122. .unwrap()
  123. }
  124. /// Number of nodes a `make_network` call brings up.
  125. pub const N_NODES: usize = 5;
  126. /// Outbound peer count per node.
  127. pub const N_CONNS: usize = 2;
  128. /// Allocate a fresh non-overlapping TCP port range for one
  129. /// `make_network` call. Process-wide counter so parallel tests
  130. /// never collide.
  131. fn alloc_port_base() -> u16 {
  132. static NEXT: AtomicU16 = AtomicU16::new(13_400);
  133. NEXT.fetch_add(N_NODES as u16, Ordering::SeqCst)
  134. }
  135. /// Spawn one `EventGraph` node on a local port, peered with the
  136. /// given `peer_offsets` (relative to `port_base`).
  137. async fn spawn_node(
  138. port_base: u16,
  139. port_offset: usize,
  140. peer_offsets: Vec<usize>,
  141. ex: Arc<Executor<'static>>,
  142. ) -> EventGraphPtr {
  143. let mut profiles = HashMap::new();
  144. profiles.insert(
  145. "tcp".to_string(),
  146. NetworkProfile { outbound_connect_timeout: 2, ..Default::default() },
  147. );
  148. let inbound =
  149. vec![Url::parse(&format!("tcp://127.0.0.1:{}", port_base + port_offset as u16)).unwrap()];
  150. let peers: Vec<_> = peer_offsets
  151. .iter()
  152. .map(|p| Url::parse(&format!("tcp://127.0.0.1:{}", port_base + *p as u16)).unwrap())
  153. .collect();
  154. let settings = Settings {
  155. localnet: true,
  156. inbound_addrs: inbound,
  157. outbound_connections: 0,
  158. inbound_connections: usize::MAX,
  159. peers,
  160. active_profiles: vec!["tcp".to_string()],
  161. profiles,
  162. ..Default::default()
  163. };
  164. let p2p = P2p::new(settings, ex.clone()).await.unwrap();
  165. let sled_db = sled::Config::new().temporary(true).open().unwrap();
  166. let eg = EventGraph::with_zk_keys(
  167. p2p.clone(),
  168. sled_db,
  169. "/tmp".into(),
  170. false,
  171. test_config(),
  172. shared_zk_keys(),
  173. ex.clone(),
  174. )
  175. .await
  176. .unwrap();
  177. // Mark synced so protocol handlers accept events during tests.
  178. eg.synced.store(true, Ordering::Release);
  179. let eg_weak = Arc::downgrade(&eg);
  180. p2p.protocol_registry()
  181. .register(SESSION_DEFAULT, move |channel, _| {
  182. let eg_weak = eg_weak.clone();
  183. async move {
  184. let eg =
  185. eg_weak.upgrade().expect("EventGraph dropped before protocol factory invoked");
  186. ProtocolEventGraph::init(eg, channel).await.unwrap()
  187. }
  188. })
  189. .await;
  190. eg
  191. }
  192. /// Bootstrap an N-node ring, start the P2P stacks, and wait 5
  193. /// seconds for connections to converge.
  194. ///
  195. /// Each call gets a fresh non-overlapping port range, so multiple
  196. /// `make_network` invocations can run in parallel.
  197. pub async fn make_network(ex: Arc<Executor<'static>>) -> Vec<EventGraphPtr> {
  198. use rand::{prelude::SliceRandom, rngs::ThreadRng};
  199. let port_base = alloc_port_base();
  200. let mut rng: ThreadRng = rand::thread_rng();
  201. let idxs: Vec<usize> = (0..N_NODES).collect();
  202. let mut nodes = vec![];
  203. for i in 0..N_NODES {
  204. let mut others = idxs.clone();
  205. others.remove(i);
  206. let conns: Vec<usize> = others.choose_multiple(&mut rng, N_CONNS).copied().collect();
  207. nodes.push(spawn_node(port_base, i, conns, ex.clone()).await);
  208. }
  209. for eg in &nodes {
  210. eg.p2p.clone().start().await.unwrap();
  211. }
  212. crate::system::sleep(5).await;
  213. nodes
  214. }
  215. /// Stop every node's P2P stack. Call at end of multi-node tests.
  216. pub async fn shutdown_network(nodes: &[EventGraphPtr]) {
  217. for eg in nodes {
  218. eg.p2p.clone().stop().await;
  219. }
  220. }
  221. /// Run a multi-node test body on an executor sized for `N_NODES`.
  222. pub fn run_multi_node_test<F, Fut>(body: F)
  223. where
  224. F: FnOnce(Arc<Executor<'static>>) -> Fut,
  225. Fut: std::future::Future<Output = ()>,
  226. {
  227. let ex = Arc::new(Executor::new());
  228. let ex_ = ex.clone();
  229. let (signal, shutdown) = channel::unbounded::<()>();
  230. easy_parallel::Parallel::new()
  231. .each(0..N_NODES, |_| future::block_on(ex.run(shutdown.recv())))
  232. .finish(|| {
  233. future::block_on(async {
  234. body(ex_).await;
  235. drop(signal);
  236. })
  237. });
  238. }
  239. mod test_identity {
  240. use darkfi_sdk::{crypto::poseidon_hash, pasta::pallas};
  241. use halo2_proofs::circuit::Value;
  242. use rand::rngs::OsRng;
  243. use super::*;
  244. use crate::{
  245. event_graph::{
  246. event::Header,
  247. rln::{
  248. epoch_of, hash_event, Blob, RLNNode, RegistrationAttestation, RegistrationBlob,
  249. MAX_MSG_LIMIT, RLN2_REGISTER_ZKBIN, RLN2_SIGNAL_ZKBIN,
  250. },
  251. NULL_PARENTS,
  252. },
  253. zk::{Proof, Witness, ZkCircuit},
  254. zkas::ZkBinary,
  255. };
  256. /// A test RLN identity with deterministic secrets and an
  257. /// auto-incrementing per-epoch `message_id` counter.
  258. pub struct TestIdentity {
  259. pub nullifier: pallas::Base,
  260. pub trapdoor: pallas::Base,
  261. pub user_message_limit: u64,
  262. pub message_id: u64,
  263. pub last_epoch: u64,
  264. }
  265. impl TestIdentity {
  266. /// Default test identity ("Alice").
  267. pub fn new() -> Self {
  268. Self {
  269. nullifier: pallas::Base::from(0xa11ce_u64),
  270. trapdoor: pallas::Base::from(0xb0b_u64),
  271. user_message_limit: RegistrationAttestation::SPECIAL_TIER_LIMIT,
  272. message_id: 0,
  273. last_epoch: 0,
  274. }
  275. }
  276. /// Construct an identity from a seed for cross-identity
  277. /// tests. Different seeds yield distinct identities; the
  278. /// same seed always reproduces the same identity.
  279. pub fn with_seed(seed: u64) -> Self {
  280. // Mixing constants chosen so with_seed(1) does NOT
  281. // collide with new() (which uses 0xa11ce / 0xb0b
  282. // directly).
  283. let n = seed.wrapping_mul(0x9E3779B97F4A7C15_u64).wrapping_add(0x100);
  284. let t = seed.wrapping_mul(0xBF58476D1CE4E5B9_u64).wrapping_add(0x200);
  285. Self {
  286. nullifier: pallas::Base::from(n | 1),
  287. trapdoor: pallas::Base::from(t | 1),
  288. user_message_limit: RegistrationAttestation::SPECIAL_TIER_LIMIT,
  289. message_id: 0,
  290. last_epoch: 0,
  291. }
  292. }
  293. pub fn identity_secret(&self) -> pallas::Base {
  294. poseidon_hash([self.nullifier, self.trapdoor])
  295. }
  296. pub fn identity_secret_hash(&self) -> pallas::Base {
  297. poseidon_hash([self.identity_secret(), pallas::Base::from(self.user_message_limit)])
  298. }
  299. pub fn commitment(&self) -> pallas::Base {
  300. poseidon_hash([self.identity_secret_hash()])
  301. }
  302. /// Advance the per-epoch message-id counter. Returns `None`
  303. /// when the per-epoch budget is exhausted.
  304. pub fn next_message_id(&mut self, now_millis: u64) -> Option<u64> {
  305. let epoch = epoch_of(now_millis);
  306. if epoch != self.last_epoch {
  307. self.last_epoch = epoch;
  308. self.message_id = 0;
  309. }
  310. if self.message_id >= self.user_message_limit {
  311. return None
  312. }
  313. let m = self.message_id;
  314. self.message_id += 1;
  315. Some(m)
  316. }
  317. /// Build a real registration proof and blob.
  318. pub fn create_registration(&self, eg: &EventGraphPtr) -> Result<RegistrationBlob> {
  319. let witnesses = vec![
  320. Witness::Base(Value::known(self.nullifier)),
  321. Witness::Base(Value::known(self.trapdoor)),
  322. Witness::Base(Value::known(pallas::Base::from(self.user_message_limit))),
  323. Witness::Base(Value::known(pallas::Base::from(MAX_MSG_LIMIT))),
  324. ];
  325. let pi = vec![
  326. self.commitment(),
  327. pallas::Base::from(self.user_message_limit),
  328. pallas::Base::from(MAX_MSG_LIMIT),
  329. ];
  330. let zkbin = ZkBinary::decode(RLN2_REGISTER_ZKBIN, false)?;
  331. let circuit = ZkCircuit::new(witnesses, &zkbin);
  332. let pk = eg.zk_keys.load_register_pk()?;
  333. let proof = Proof::create(&pk, &[circuit], &pi, &mut OsRng)?;
  334. Ok(RegistrationBlob {
  335. proof,
  336. user_message_limit: self.user_message_limit,
  337. max_message_limit: MAX_MSG_LIMIT,
  338. attestation: RegistrationAttestation::SPECIAL,
  339. })
  340. }
  341. /// Build a real signal proof and blob.
  342. pub async fn create_signal(
  343. &self,
  344. event: &Event,
  345. message_id: u64,
  346. eg: &EventGraphPtr,
  347. ) -> Result<Blob> {
  348. let commitment = self.commitment();
  349. let (root, path) = eg.rln_membership_path(&commitment).await;
  350. let app_id = eg.rln_app_id().as_field();
  351. let epoch = epoch_of(event.header.timestamp);
  352. let epoch_field = pallas::Base::from(epoch);
  353. let external_nullifier = poseidon_hash([epoch_field, app_id]);
  354. let a_0 = self.identity_secret_hash();
  355. let a_1 = poseidon_hash([a_0, external_nullifier, pallas::Base::from(message_id)]);
  356. let x = hash_event(event);
  357. let y = a_0 + x * a_1;
  358. let internal_nullifier = poseidon_hash([a_1]);
  359. let witnesses = vec![
  360. Witness::Base(Value::known(self.nullifier)),
  361. Witness::Base(Value::known(self.trapdoor)),
  362. Witness::Base(Value::known(pallas::Base::from(message_id))),
  363. Witness::SparseMerklePath(Value::known(path.path)),
  364. Witness::Base(Value::known(x)),
  365. Witness::Base(Value::known(pallas::Base::from(self.user_message_limit))),
  366. Witness::Base(Value::known(app_id)),
  367. Witness::Base(Value::known(epoch_field)),
  368. ];
  369. let pi = vec![
  370. root,
  371. external_nullifier,
  372. pallas::Base::from(self.user_message_limit),
  373. x,
  374. y,
  375. internal_nullifier,
  376. ];
  377. let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN, false)?;
  378. let circuit = ZkCircuit::new(witnesses, &zkbin);
  379. let pk = eg.zk_keys.load_signal_pk()?;
  380. let proof = Proof::create(&pk, &[circuit], &pi, &mut OsRng)?;
  381. Ok(Blob {
  382. proof,
  383. y,
  384. internal_nullifier,
  385. user_msg_limit: self.user_message_limit,
  386. merkle_root: root,
  387. })
  388. }
  389. /// Register this identity directly into `eg` (skipping the
  390. /// gossip layer).
  391. pub async fn register_directly(&self, eg: &EventGraphPtr) -> Result<()> {
  392. let _blob = self.create_registration(eg)?;
  393. let commitment = self.commitment();
  394. let node = RLNNode::Registration(commitment);
  395. let content = darkfi_serial::serialize_async(&node).await;
  396. let mut parents = NULL_PARENTS;
  397. parents[0] = blake3::hash(b"register_directly-parent");
  398. let header = Header {
  399. timestamp: eg.current_genesis.read().await.header.timestamp,
  400. parents,
  401. layer: 1,
  402. content_hash: blake3::hash(&content),
  403. };
  404. let ev = Event { header, content };
  405. eg.apply_rln_static_event(&ev, &node).await?;
  406. Ok(())
  407. }
  408. }
  409. impl Default for TestIdentity {
  410. fn default() -> Self {
  411. Self::new()
  412. }
  413. }
  414. }
  415. pub use test_identity::TestIdentity;