Эх сурвалжийг харах

darkirc: impl /LIST cmd by recording public channels as seen_channels

darkfi 1 долоо хоног өмнө
parent
commit
895af8b025

+ 0 - 1
Cargo.toml

@@ -79,7 +79,6 @@ oxy-upnp-igd = {version = "0.1", optional = true}
 
 # Encoding
 bs58 = {version = "0.5.1", optional = true}
-crypto-box = {version = "0.2.1", optional = true}
 hex = {version = "0.4.3", optional = true}
 serde = {version = "1.0.228", features = ["derive"], optional = true}
 tinyjson = {version = "2.5.1", optional = true}

+ 11 - 1
bin/darkirc/src/irc/client.rs

@@ -332,7 +332,7 @@ impl Client {
                     }
 
                     // Try to deserialize the `Event`'s content into a `Privmsg`
-                    let mut privmsg = match deserialize_async_partial(r.content()).await {
+                    let mut privmsg: Privmsg = match deserialize_async_partial(r.content()).await {
                         Ok((v, _)) => v,
                         Err(e) => {
                             error!(target: "irc::client", "[IRC CLIENT] Failed deserializing event: {e}");
@@ -340,6 +340,12 @@ impl Client {
                         }
                     };
 
+                    // Record any public (`#`-prefixed) channel observed on the
+                    // wire. Done before decryption so that only truly public
+                    // channels (plaintext channel field) are recorded; encrypted
+                    // channels carry base58 ciphertext in this field.
+                    self.server.record_seen_channel(&privmsg.channel).await?;
+
                     // If successful, potentially decrypt it:
                     self.server.try_decrypt(&mut privmsg, self.nickname.read().await.as_ref()).await;
 
@@ -674,6 +680,10 @@ impl Client {
 
     // Internal helper function that creates an Event from PRIVMSG arguments
     async fn privmsg_to_event(&self, mut privmsg: Privmsg) -> Result<Event> {
+        // Record the outbound channel before encryption so that any public
+        // (`#`-prefixed) channel we send to is reflected in `/LIST`.
+        self.server.record_seen_channel(&privmsg.channel).await?;
+
         // Encrypt the Privmsg if an encryption method is available.
         self.server.try_encrypt(&mut privmsg).await;
 

+ 17 - 14
bin/darkirc/src/irc/command.rs

@@ -58,10 +58,10 @@ use tracing::{error, info, warn};
 use super::{
     client::{Client, ReplyType},
     rpl::*,
-    server::{MAX_MSG_LEN, MAX_NICK_LEN},
+    server::{MAX_MSG_LEN, MAX_NICK_LEN, SEEN_CHANNELS_TREE},
     IrcChannel, SERVER_NAME,
 };
-use crate::crypto::bcrypt::bcrypt_hash_password;
+use crate::{crypto::bcrypt::bcrypt_hash_password, Privmsg};
 
 const MAX_TOPIC_LEN: usize = MAX_MSG_LEN;
 
@@ -368,9 +368,9 @@ impl Client {
 
     /// `LIST [<channels> [<server>]]`
     ///
-    /// List all channels on the server. If the list `<channels>` is given, it
-    /// will return the channel topics. If `<server>` is given, the command will
-    /// be sent to `<server>` for evaluation.
+    /// List all public (`#`-prefixed) channels observed on the p2p network.
+    /// Channels are recorded into the `SEEN_CHANNELS_TREE` as their traffic
+    /// is seen, and persist across restarts.
     pub async fn handle_cmd_list(&self, _args: &str) -> Result<Vec<ReplyType>> {
         if !self.registered.load(SeqCst) {
             self.penalty.fetch_add(1, SeqCst);
@@ -379,15 +379,14 @@ impl Client {
 
         let nick = self.nickname.read().await.to_string();
 
-        let mut list = vec![];
-        for (name, channel) in self.server.channels.read().await.iter() {
-            list.push(format!("{nick} {name} {} :{}", channel.nicks.len(), channel.topic));
-        }
+        let tree = self.server.darkirc.sled.open_tree(SEEN_CHANNELS_TREE)?;
 
-        let mut replies = vec![];
-        replies.push(ReplyType::Server((RPL_LISTSTART, format!("{nick} Channel :Users  Name"))));
-        for chan in list {
-            replies.push(ReplyType::Server((RPL_LIST, chan)));
+        let mut replies =
+            vec![ReplyType::Server((RPL_LISTSTART, format!("{nick} Channel :Users  Name")))];
+        for item in tree.iter() {
+            let (key, _) = item?;
+            let name = String::from_utf8_lossy(&key);
+            replies.push(ReplyType::Server((RPL_LIST, format!("{nick} {name} 0 :"))));
         }
         replies.push(ReplyType::Server((RPL_LISTEND, format!("{nick} :End of /LIST"))));
 
@@ -1017,11 +1016,15 @@ impl Client {
             }
 
             // Try to deserialize it. (Here we skip errors)
-            let mut privmsg = match deserialize_async_partial(event.content()).await {
+            let mut privmsg: Privmsg = match deserialize_async_partial(event.content()).await {
                 Ok((v, _)) => v,
                 Err(_) => continue,
             };
 
+            // Record any public (`#`-prefixed) channel observed on the wire,
+            // before decryption (see note in client::multiplex_connection).
+            self.server.record_seen_channel(&privmsg.channel).await?;
+
             // Potentially decrypt the privmsg
             self.server.try_decrypt(&mut privmsg, self.nickname.read().await.as_ref()).await;
 

+ 67 - 1
bin/darkirc/src/irc/server.rs

@@ -30,7 +30,7 @@ use darkfi::{
     util::path::expand_path,
     Error, Result,
 };
-use darkfi_serial::{deserialize_async, serialize_async};
+use darkfi_serial::{deserialize_async, deserialize_async_partial, serialize_async};
 use futures_rustls::{
     rustls::{
         self,
@@ -67,6 +67,10 @@ pub const MAX_NICK_LEN: usize = 24;
 /// Max message length
 pub const MAX_MSG_LEN: usize = 512;
 
+/// Sled tree storing every public (`#`-prefixed) IRC channel we have
+/// observed on the p2p network. Keys are channel names; values are empty.
+pub const SEEN_CHANNELS_TREE: &str = "darkirc_seen_channels";
+
 /// Result of attempting to reserve the next RLN message slot.
 pub enum RlnMessageReservation {
     /// No active RLN identity is configured.
@@ -356,6 +360,13 @@ impl IrcServer {
         *self.channels.write().await = channels;
         *self.contacts.write().await = contacts;
 
+        // Record configured public channels so `/LIST` can report them even
+        // before any traffic is observed for them on the network.
+        let names: Vec<String> = self.channels.read().await.keys().cloned().collect();
+        for name in &names {
+            self.record_seen_channel(name).await?;
+        }
+
         Ok(())
     }
 
@@ -459,6 +470,61 @@ impl IrcServer {
         Ok(())
     }
 
+    /// Record a public (`#`-prefixed) IRC channel in the `SEEN_CHANNELS_TREE`
+    /// so that `/LIST` can report channels observed on the p2p network.
+    /// Private (encrypted) channels — those with a configured saltbox — are
+    /// skipped. Idempotent. Emits a debug log the first time a given channel
+    /// is seen.
+    pub async fn record_seen_channel(&self, channel: &str) -> Result<()> {
+        if !channel.starts_with('#') {
+            return Ok(())
+        }
+
+        // Skip private channels that have a configured saltbox.
+        if let Some(chan) = self.channels.read().await.get(channel) {
+            if chan.saltbox.is_some() {
+                return Ok(())
+            }
+        }
+
+        let tree = self.darkirc.sled.open_tree(SEEN_CHANNELS_TREE)?;
+        if tree.insert(channel.as_bytes(), &[])?.is_none() {
+            debug!(
+                target: "darkirc::irc::server",
+                "Recorded new public channel: {channel}"
+            );
+        }
+
+        Ok(())
+    }
+
+    /// Walk every stored event in the DAG and record all public (`#`-prefixed)
+    /// channels into the `SEEN_CHANNELS_TREE`. Intended to be called once the
+    /// event graph finishes syncing, so that `/LIST` reflects the full set of
+    /// known public channels even before any new live traffic arrives.
+    /// Only the raw wire channel field is inspected: encrypted (private)
+    /// channels carry base58 ciphertext and are skipped.
+    pub async fn populate_seen_channels(&self) -> Result<usize> {
+        let events = self.darkirc.event_graph.order_events().await?;
+        let tree = self.darkirc.sled.open_tree(SEEN_CHANNELS_TREE)?;
+        let mut batch = sled::Batch::default();
+        let mut count = 0usize;
+        for event in events.iter() {
+            let Ok((privmsg, _)) = deserialize_async_partial::<Privmsg>(event.content()).await
+            else {
+                continue
+            };
+            if privmsg.channel.starts_with('#') {
+                batch.insert(privmsg.channel.as_bytes(), &[]);
+                count += 1;
+            }
+        }
+        if count > 0 {
+            tree.apply_batch(batch)?;
+        }
+        Ok(count)
+    }
+
     /// Try encrypting a given `Privmsg` if there is such a channel/contact.
     pub async fn try_encrypt(&self, privmsg: &mut Privmsg) {
         if let Some((name, channel)) = self.channels.read().await.get_key_value(&privmsg.channel) {

+ 11 - 0
bin/darkirc/src/main.rs

@@ -819,6 +819,17 @@ pub const DARKIRC_GENESIS_COMMITMENTS_REPR: &[[u8; 32]] = &[
                             error!("Failed to drain pending broadcasts: {e}");
                         }
                     }
+
+                    // Populate the seen-channels index from the freshly-synced
+                    // DAG so `/LIST` can report every known public channel.
+                    match irc_server_for_drain.populate_seen_channels().await {
+                        Ok(n) => {
+                            info!("Recorded {n} public channel sightings from DAG history");
+                        }
+                        Err(e) => {
+                            error!("Failed populating seen channels from DAG: {e}");
+                        }
+                    }
                 }
                 last_state = now_state;
             }