| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use sled_overlay::sled;
- use smol::lock::Mutex;
- use std::{collections::HashSet, path::PathBuf};
- use darkfi::{
- event_graph::EventGraphPtr, net::P2pPtr, rpc::jsonrpc::JsonSubscriber, system::StoppableTaskPtr,
- };
- use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
- /// IRC server and client handler implementation
- pub mod irc;
- use irc::server::IrcServer;
- use crate::irc::server::MAX_NICK_LEN;
- /// Cryptography utilities
- pub mod crypto;
- /// Pregenerated DarkIRC RLN identity commitments.
- pub mod genesis_commits;
- /// JSON-RPC methods
- pub mod rpc;
- /// Settings utilities
- pub mod settings;
- /// IRC PRIVMSG
- #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
- pub struct Privmsg {
- pub version: u8,
- pub msg_type: u8,
- pub channel: String,
- pub nick: String,
- pub msg: String,
- }
- pub struct DarkIrc {
- /// P2P network pointer
- p2p: P2pPtr,
- /// Sled DB (also used in event_graph and for RLN)
- sled: sled::Db,
- /// Event Graph instance
- event_graph: EventGraphPtr,
- /// JSON-RPC connection tracker
- rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
- /// dnet JSON-RPC subscriber
- dnet_sub: JsonSubscriber,
- /// deg JSON-RPC subscriber
- deg_sub: JsonSubscriber,
- /// Gource visualization JSON-RPC subscriber
- gource_sub: JsonSubscriber,
- /// Replay logs (DB) path
- replay_datastore: PathBuf,
- }
- impl DarkIrc {
- pub fn new(
- p2p: P2pPtr,
- sled: sled::Db,
- event_graph: EventGraphPtr,
- dnet_sub: JsonSubscriber,
- deg_sub: JsonSubscriber,
- gource_sub: JsonSubscriber,
- replay_datastore: PathBuf,
- ) -> Self {
- Self {
- p2p,
- sled,
- event_graph,
- rpc_connections: Mutex::new(HashSet::new()),
- dnet_sub,
- deg_sub,
- gource_sub,
- replay_datastore,
- }
- }
- }
- pub fn pad(string: &str) -> Vec<u8> {
- let mut bytes = string.as_bytes().to_vec();
- bytes.resize(MAX_NICK_LEN, 0x00);
- bytes
- }
- pub fn unpad(vec: &mut Vec<u8>) {
- if let Some(i) = vec.iter().rposition(|x| *x != 0) {
- let new_len = i + 1;
- vec.truncate(new_len);
- }
- }
- #[cfg(test)]
- mod tests {
- use std::{
- collections::HashMap,
- sync::{
- atomic::{AtomicU16, Ordering},
- Arc,
- },
- };
- use darkfi::{
- event_graph::{
- proto::{ProtocolEventGraph, RangeCursor, SyncDirection},
- Event, EventGraph, EventGraphConfig, EventGraphPtr, Header, NULL_PARENTS,
- },
- net::{
- session::SESSION_DEFAULT,
- settings::{NetworkProfile, Settings},
- P2p, P2pPtr,
- },
- system::sleep,
- };
- use darkfi_serial::{deserialize_async_partial, serialize_async};
- use easy_parallel::Parallel;
- use sled_overlay::sled;
- use smol::{channel, future, Executor};
- use url::Url;
- use super::Privmsg;
- struct HistoryNode {
- p2p: P2pPtr,
- event_graph: EventGraphPtr,
- }
- fn alloc_port_base() -> u16 {
- static NEXT: AtomicU16 = AtomicU16::new(24_400);
- NEXT.fetch_add(2, Ordering::SeqCst)
- }
- fn history_test_config() -> EventGraphConfig {
- EventGraphConfig {
- initial_genesis: 1_704_067_200_000,
- hours_rotation: 1,
- genesis_contents: b"darkirc-mobile-history-test".to_vec(),
- rln_enabled: false,
- pregenerated_identity_commitments: Vec::new(),
- max_dags: Some(5),
- }
- }
- async fn spawn_history_node(
- port_base: u16,
- port_offset: u16,
- peer_offsets: &[u16],
- ex: Arc<Executor<'static>>,
- ) -> HistoryNode {
- let mut profiles = HashMap::new();
- profiles.insert(
- "tcp".to_string(),
- NetworkProfile { outbound_connect_timeout: 2, ..Default::default() },
- );
- let inbound =
- vec![Url::parse(&format!("tcp://127.0.0.1:{}", port_base + port_offset)).unwrap()];
- let peers = peer_offsets
- .iter()
- .map(|offset| Url::parse(&format!("tcp://127.0.0.1:{}", port_base + *offset)).unwrap())
- .collect();
- let settings = Settings {
- localnet: true,
- inbound_addrs: inbound,
- outbound_connections: 0,
- inbound_connections: usize::MAX,
- peers,
- active_profiles: vec!["tcp".to_string()],
- profiles,
- ..Default::default()
- };
- let p2p = P2p::new(settings, ex.clone()).await.unwrap();
- let sled_db = sled::Config::new().temporary(true).open().unwrap();
- let event_graph =
- EventGraph::new(p2p.clone(), sled_db, "/tmp".into(), false, history_test_config(), ex)
- .await
- .unwrap();
- event_graph.synced.store(true, Ordering::Release);
- let event_graph_weak = Arc::downgrade(&event_graph);
- p2p.protocol_registry()
- .register(SESSION_DEFAULT, move |channel, _| {
- let event_graph_weak = event_graph_weak.clone();
- async move {
- let event_graph = event_graph_weak
- .upgrade()
- .expect("EventGraph dropped before protocol factory invoked");
- ProtocolEventGraph::init(event_graph, channel).await.unwrap()
- }
- })
- .await;
- HistoryNode { p2p, event_graph }
- }
- async fn make_history_network(ex: Arc<Executor<'static>>) -> Vec<HistoryNode> {
- let port_base = alloc_port_base();
- let nodes = vec![
- spawn_history_node(port_base, 0, &[1], ex.clone()).await,
- spawn_history_node(port_base, 1, &[0], ex).await,
- ];
- for node in &nodes {
- node.p2p.clone().start().await.unwrap();
- }
- sleep(5).await;
- nodes
- }
- async fn shutdown_history_network(nodes: &[HistoryNode]) {
- for node in nodes {
- node.p2p.stop().await;
- }
- }
- fn run_history_test<F, Fut>(body: F)
- where
- F: FnOnce(Arc<Executor<'static>>) -> Fut,
- Fut: std::future::Future<Output = ()>,
- {
- let ex = Arc::new(Executor::new());
- let ex_ = ex.clone();
- let (signal, shutdown) = channel::unbounded::<()>();
- Parallel::new().each(0..2, |_| future::block_on(ex.run(shutdown.recv()))).finish(|| {
- future::block_on(async {
- body(ex_).await;
- drop(signal);
- })
- });
- }
- fn genesis_for(event_graph: &EventGraphPtr, dag_ts: u64) -> Event {
- let content = event_graph.config.genesis_contents.clone();
- Event {
- header: Header {
- timestamp: dag_ts,
- parents: NULL_PARENTS,
- layer: 0,
- content_hash: blake3::hash(&content),
- },
- content,
- }
- }
- async fn select_current_dag(event_graph: &EventGraphPtr, dag_ts: u64) {
- *event_graph.current_genesis.write().await = genesis_for(event_graph, dag_ts);
- }
- async fn append_chat_event(
- event_graph: &EventGraphPtr,
- dag_ts: u64,
- timestamp: u64,
- nick: &str,
- message: &str,
- ) -> Event {
- select_current_dag(event_graph, dag_ts).await;
- let privmsg = Privmsg {
- version: 0,
- msg_type: 0,
- channel: "#mobile".to_string(),
- nick: nick.to_string(),
- msg: message.to_string(),
- };
- let event = Event::with_timestamp(timestamp, serialize_async(&privmsg).await, event_graph)
- .await
- .unwrap();
- event_graph.insert_signal_with_blob(&event, &[], &dag_ts.to_string()).await.unwrap();
- event
- }
- async fn page_messages(events: &[Event]) -> Vec<String> {
- let mut messages = Vec::with_capacity(events.len());
- for event in events {
- let (privmsg, _) = deserialize_async_partial::<Privmsg>(event.content()).await.unwrap();
- messages.push(privmsg.msg);
- }
- messages
- }
- #[test]
- fn mobile_chat_can_scroll_backwards_across_multiple_dags() {
- run_history_test(|ex| async move {
- const HOUR_MS: u64 = 3_600_000;
- const DAG_COUNT: usize = 5;
- const EVENTS_PER_DAG: usize = 4;
- const PAGE_SIZE: usize = 3;
- let nodes = make_history_network(ex).await;
- let source = nodes[0].event_graph.clone();
- let mobile = nodes[1].event_graph.clone();
- let latest_dag = source.current_genesis.read().await.header.timestamp;
- let mut dag_timestamps = Vec::with_capacity(DAG_COUNT);
- for offset in (0..DAG_COUNT).rev() {
- dag_timestamps.push(latest_dag.saturating_sub((offset as u64) * HOUR_MS));
- }
- let mut seeded = Vec::new();
- let mut expected_scrollback = Vec::new();
- for (dag_index, dag_ts) in dag_timestamps.iter().enumerate() {
- let mut dag_events = Vec::new();
- for event_index in 0..EVENTS_PER_DAG {
- let message = format!("dag {dag_index} history message {event_index}");
- let event = append_chat_event(
- &source,
- *dag_ts,
- dag_ts + 10_000 + event_index as u64,
- "alice",
- &message,
- )
- .await;
- dag_events.push(event);
- }
- expected_scrollback.extend(
- (0..EVENTS_PER_DAG).rev().map(|event_index| {
- format!("dag {dag_index} history message {event_index}")
- }),
- );
- seeded.push(dag_events);
- }
- expected_scrollback =
- expected_scrollback.chunks(EVENTS_PER_DAG).rev().flatten().cloned().collect();
- for dag_events in &seeded {
- for event in dag_events {
- assert!(mobile.fetch_event_from_dags(&event.id()).await.unwrap().is_none());
- }
- }
- mobile.sync_selected_headers(1).await.unwrap();
- let mut collected = Vec::new();
- for dag_ts in dag_timestamps.iter().rev() {
- let mut cursor = RangeCursor::newest();
- loop {
- let page = mobile
- .dag_sync_range(*dag_ts, cursor, SyncDirection::Backward, PAGE_SIZE)
- .await
- .unwrap();
- collected.extend(page_messages(&page.events).await);
- if page.exhausted {
- break
- }
- cursor = page.next_cursor;
- }
- }
- assert_eq!(collected, expected_scrollback);
- for dag_events in &seeded {
- for event in dag_events {
- assert!(mobile.fetch_event_from_dags(&event.id()).await.unwrap().is_some());
- }
- }
- shutdown_history_network(&nodes).await;
- });
- }
- }
|