Просмотр исходного кода

app: correct load channel secrets from shared db and decrypt incoming msgs

darkfi 2 недель назад
Родитель
Сommit
f415599e9c

+ 2 - 11
bin/app/src/app/mod.rs

@@ -68,18 +68,9 @@ impl App {
 
     /// Does not require miniquad to be init. Created the scene graph tree / schema and all
     /// the objects.
-    pub async fn setup(&self) -> Result<Option<i32>, Error> {
+    pub async fn setup(&self, db: sled::Db) -> Result<Option<i32>, Error> {
         t!("App::setup()");
 
-        let db_path = get_settingsdb_path();
-        let db = match sled::open(&db_path) {
-            Ok(db) => db,
-            Err(err) => {
-                e!("Sled database '{}' failed to open: {err}!", db_path.display());
-                return Err(Error::SledDbErr)
-            }
-        };
-
         let setting_root = SceneNode::new("setting", SceneNodeType::SettingRoot);
         let setting_root = setting_root.setup_null();
         let settings_tree = db.open_tree("settings").unwrap();
@@ -127,7 +118,7 @@ impl App {
         self.sg_root.link(setting_root.clone());
 
         #[cfg(feature = "schema-app")]
-        schema::make(&self, window.clone(), &i18n_fish).await;
+        schema::make(&self, window.clone(), &i18n_fish, db).await;
 
         #[cfg(feature = "schema-test")]
         schema::test::make(&self, window.clone(), &i18n_fish).await;

+ 2 - 2
bin/app/src/app/schema/chat.rs

@@ -557,7 +557,7 @@ pub async fn make(
     //if chat_tree.is_empty() {
     //    populate_tree(&chat_tree);
     //}
-    debug!(target: "app", "Loaded #{channel} history: {} lines", chat_tree.len());
+    debug!(target: "app", "Loaded {channel} history: {} lines", chat_tree.len());
     let chatview_node = node
         .setup(|me| {
             ChatView::new(me, chat_tree, window_scale.clone(), renderer.clone(), sg_root.clone())
@@ -1241,7 +1241,7 @@ pub async fn make(
     });
     layer_node.push_task(editz_text_task);
 
-    layer_node
+    chat_layer_node
 }
 
 // Just for testing

+ 27 - 9
bin/app/src/app/schema/menu/channel.rs

@@ -48,11 +48,11 @@ use crate::{
     gfx::gfxtag,
     mesh::{COLOR_CYAN, COLOR_INACTIVE, COLOR_MINT, COLOR_MINT_OP, MINT_BTN_GRADIENT},
     prop::{PropertyBool, PropertyFloat32, Role},
-    scene::{SceneNodePtr, Slot},
+    scene::{Pimpl, SceneNodePtr, Slot},
     shape,
     ui::{
         emoji_picker::EmojiMeshesPtr, BaseEdit, BaseEditType, Button, Layer, Menu, ShapeVertex,
-        Shortcut, Text, VectorArt, VectorShape,
+        Shortcut, Text, UIObject, VectorArt, VectorShape, Window,
     },
     util::i18n::I18nBabelFish,
 };
@@ -1333,10 +1333,10 @@ pub async fn make(
     let save_channel = app.ex.spawn(async move {
         while let Ok(_) = addchannel_recvr.recv().await {
             let name_prop = nickedit2.get_property("text").unwrap();
-            let name = name_prop.get_str(0).unwrap_or_default();
+            let name = name_prop.get_str(0).unwrap();
 
             let secret_prop = secedit2.get_property("text").unwrap();
-            let secret = secret_prop.get_str(0).unwrap_or_default();
+            let secret = secret_prop.get_str(0).unwrap();
 
             if name.is_empty() {
                 w!("Attempted to add channel with empty name");
@@ -1374,10 +1374,6 @@ pub async fn make(
             channels_tree2.insert(key, val).unwrap();
             let _ = channels_tree2.flush_async().await;
 
-            // Notify darkirc2 to reload channels
-            let darkirc2 = sg_root2.lookup_node("/plugin/darkirc").unwrap();
-            darkirc2.call_method("reload", vec![]).await.unwrap();
-
             let atom = &mut renderer2.make_guard(gfxtag!("add_channel"));
             menu_prop2.push_str(atom, Role::App, &channel_name).unwrap();
 
@@ -1431,12 +1427,13 @@ pub async fn make(
                 continue;
             }
 
+            let content = sg_root.lookup_node("/window/content").unwrap();
             // Create the chat layer and get the node
             let node = chat::make(
                 &sg_root,
                 &renderer,
                 &ex,
-                content2.clone(),
+                content,
                 &channel,
                 &db2,
                 &i18n_fish2,
@@ -1444,6 +1441,11 @@ pub async fn make(
                 is_first_time,
             )
             .await;
+            match node.pimpl() {
+                Pimpl::Layer(layer) => layer.clone().start(ex.clone()).await,
+                _ => panic!("wrong pimpl"),
+            }
+            d!("Added channel layer: {}", node.get_full_path().unwrap());
 
             // Show the chat layer
             node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
@@ -1458,6 +1460,22 @@ pub async fn make(
 
             // Hide channel screen
             channel_vis.set(atom, false);
+
+            // Force redraw so newly added node parent_rect gets set.
+            // There are other ways to do this but this is easiest for now.
+            // We can think later about doing this better.
+            let win = sg_root.lookup_node("/window").unwrap();
+            match win.pimpl() {
+                Pimpl::Window(win) => win.draw(atom).await,
+                _ => panic!("wrong pimpl"),
+            }
+
+            // Trigger rescan for this channel
+            if let Some(darkirc) = sg_root.lookup_node("/plugin/darkirc") {
+                let mut data = vec![];
+                channel.encode(&mut data).unwrap();
+                darkirc.call_method("rescan", data).await.unwrap();
+            }
         }
     });
 

+ 9 - 3
bin/app/src/app/schema/mod.rs

@@ -78,6 +78,10 @@ mod ui_consts {
     pub fn get_settingsdb_path() -> PathBuf {
         get_appdata_path().join("settings")
     }
+
+    pub fn get_main_db_path() -> PathBuf {
+        get_appdata_path().join("db")
+    }
 }
 
 #[cfg(not(target_os = "android"))]
@@ -98,6 +102,10 @@ mod desktop_paths {
     pub fn get_settingsdb_path() -> PathBuf {
         dirs::cache_dir().unwrap().join("darkfi/app/settings")
     }
+
+    pub fn get_main_db_path() -> PathBuf {
+        dirs::data_local_dir().unwrap().join("darkfi/app/db")
+    }
 }
 
 #[cfg(feature = "emulate-android")]
@@ -128,7 +136,7 @@ enum ColorScheme {
     PaperLight,
 }
 
-pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
+pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish, db: sled::Db) {
     let mut cc = Compiler::new();
     cc.add_const_f32("NETSTATUS_ICON_SIZE", NETSTATUS_ICON_SIZE);
     cc.add_const_f32("SETTINGS_ICON_SIZE", SETTINGS_ICON_SIZE);
@@ -590,8 +598,6 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
         let _ = File::create(filename);
     }
 
-    let chatdb_path = get_chatdb_path();
-    let db = sled::open(chatdb_path).expect("cannot open sleddb");
     let channels_tree = db.open_tree("channels").expect("cannot open channels tree");
 
     // Initialize default channels if tree is empty

+ 12 - 5
bin/app/src/main.rs

@@ -64,12 +64,14 @@ use crate::{
 use net::ZeroMQAdapter;
 use {
     // Local imports
+    app::schema::get_main_db_path,
     gfx::Renderer,
     prop::{PropertyBool, PropertyStr, Role},
     scene::Slot,
+    // Global imports
+    sled_overlay::sled,
     std::io::Cursor,
     ui::chatview,
-    // Global imports
     url::Url,
 };
 
@@ -137,6 +139,9 @@ impl God {
         let basename = exe_path.parent().unwrap();
         std::env::set_current_dir(basename).unwrap();
 
+        let db_path = get_main_db_path();
+        let db = sled::open(&db_path).expect("Sled DB failed to open");
+
         let bg_ex = Arc::new(smol::Executor::new());
         let fg_ex = Arc::new(smol::Executor::new());
         let sg_root = SceneNode::root();
@@ -157,8 +162,9 @@ impl God {
         let app2 = app.clone();
         let cv_app_is_setup = Arc::new(CondVar::new());
         let cv = cv_app_is_setup.clone();
+        let db2 = db.clone();
         let app_task = fg_ex.spawn(async move {
-            app2.setup().await.unwrap();
+            app2.setup(db2).await.unwrap();
             cv.notify();
         });
         fg_runtime.push_task(app_task);
@@ -181,7 +187,7 @@ impl God {
             let cv = cv_app_is_setup.clone();
             let renderer = renderer.clone();
             let plug_task = bg_ex.spawn(async move {
-                load_plugins(ex, sg_root, renderer, cv).await;
+                load_plugins(ex, sg_root, renderer, cv, db).await;
             });
             bg_runtime.push_task(plug_task);
         }
@@ -258,6 +264,7 @@ async fn load_plugins(
     sg_root: SceneNodePtr,
     renderer: Renderer,
     cv: Arc<CondVar>,
+    db: sled::Db,
 ) {
     let plugin = SceneNode::new("plugin", SceneNodeType::PluginRoot);
     let plugin = plugin.setup_null();
@@ -273,7 +280,7 @@ async fn load_plugins(
         let darkirc = create_darkirc("darkirc");
         let darkirc = darkirc
             .setup(|me| async {
-                plugin::DarkIrc2::new(me, sg_root.clone(), ex.clone())
+                plugin::DarkIrc2::new(me, sg_root.clone(), ex.clone(), db)
                     .await
                     .expect("DarkIrc pimpl setup")
             })
@@ -774,7 +781,7 @@ pub fn create_darkirc(name: &str) -> SceneNode {
 
     node.add_method("reconnect", vec![], None).unwrap();
 
-    node.add_method("reload", vec![], None).unwrap();
+    node.add_method("rescan", vec![("channel", "Channel", CallArgType::Str)], None).unwrap();
 
     node
 }

+ 111 - 66
bin/app/src/plugin/darkirc2.rs

@@ -19,13 +19,12 @@
 use std::{
     collections::{HashMap, HashSet},
     io::Cursor,
-    sync::{Arc, Mutex as SyncMutex, OnceLock, Weak},
+    sync::{Arc, OnceLock, Weak},
     time::UNIX_EPOCH,
 };
 
 use async_lock::RwLock;
-use async_trait::async_trait;
-use crypto_box::{ChaChaBox, CryptoBox, PublicKey, SecretKey};
+use crypto_box::{ChaChaBox, SecretKey};
 use darkfi::{
     event_graph::{
         self,
@@ -42,13 +41,13 @@ use darkfi::{
 };
 use darkfi_serial::{
     deserialize_async, serialize, serialize_async, AsyncEncodable, Decodable, Encodable,
-    SerialDecodable, SerialEncodable,
 };
 use irc2::{
     crypto::saltbox,
     irc::{server::MAX_NICK_LEN, IrcChannel, IrcContact},
     pad, unpad, Privmsg,
 };
+use parking_lot::Mutex as SyncMutex;
 use sled_overlay::sled;
 
 use crate::{
@@ -88,6 +87,9 @@ mod paths {
     pub fn get_evgrdb_path() -> PathBuf {
         get_external_storage_path().join("evgr2")
     }
+    pub fn get_chatdb_path() -> PathBuf {
+        get_external_storage_path().join("chatdb")
+    }
     pub fn get_use_tor_filename() -> PathBuf {
         get_external_storage_path().join("use_tor.txt")
     }
@@ -111,6 +113,9 @@ mod paths {
     pub fn get_evgrdb_path() -> PathBuf {
         dirs::data_local_dir().unwrap().join("darkfi/app/evgr2")
     }
+    pub fn get_chatdb_path() -> PathBuf {
+        dirs::data_local_dir().unwrap().join("darkfi/app/chatdb")
+    }
     pub fn get_use_tor_filename() -> PathBuf {
         dirs::data_local_dir().unwrap().join("darkfi/app/use_tor.txt")
     }
@@ -163,7 +168,7 @@ pub type DarkIrc2Ptr = Arc<DarkIrc2>;
 
 pub struct DarkIrc2 {
     node: SceneNodeWeak,
-    tasks: OnceLock<Vec<smol::Task<()>>>,
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
     p2p: P2pPtr,
     event_graph: EventGraphPtr,
     seen_msgs: SyncMutex<SeenMessages>,
@@ -172,10 +177,16 @@ pub struct DarkIrc2 {
     pub contacts: RwLock<HashMap<String, IrcContact>>,
     channels_tree: sled::Tree,
     settings: PluginSettings,
+    ex: ExecutorPtr,
 }
 
 impl DarkIrc2 {
-    pub async fn new(node: SceneNodeWeak, sg_root: SceneNodePtr, ex: ExecutorPtr) -> Result<Pimpl> {
+    pub async fn new(
+        node: SceneNodeWeak,
+        sg_root: SceneNodePtr,
+        ex: ExecutorPtr,
+        db: sled::Db,
+    ) -> Result<Pimpl> {
         let node_ref = &node.upgrade().unwrap();
         let nick = PropertyStr::wrap(node_ref, Role::Internal, "nick", 0).unwrap();
 
@@ -184,7 +195,7 @@ impl DarkIrc2 {
 
         i!("Starting DarkIRC backend");
         let evgr_path = get_evgrdb_path();
-        let db = match sled::open(&evgr_path) {
+        let evgr_db = match sled::open(&evgr_path) {
             Ok(db) => db,
             Err(err) => {
                 e!("Sled database '{}' failed to open: {err}!", evgr_path.display());
@@ -192,8 +203,12 @@ impl DarkIrc2 {
             }
         };
 
-        let setting_tree = db.open_tree("settings")?;
+        let setting_tree = evgr_db.open_tree("settings")?;
+
+        // Use the unified db for reading channels (UI stores channels there)
         let channels_tree = db.open_tree("channels")?;
+        i!("Opened channels tree from unified db");
+
         let settings = PluginSettings { setting_root, sled_tree: setting_tree };
 
         let mut p2p_settings: NetSettings = Default::default();
@@ -280,7 +295,7 @@ impl DarkIrc2 {
 
         let self_ = Arc::new(Self {
             node: node.clone(),
-            tasks: OnceLock::new(),
+            tasks: SyncMutex::new(vec![]),
 
             p2p,
             event_graph,
@@ -293,13 +308,10 @@ impl DarkIrc2 {
             channels_tree,
 
             settings,
+            ex: ex.clone(),
         });
 
-        // Load channels from database BEFORE starting P2P
-        if let Err(e) = self_.load_channels_from_db().await {
-            e!("Failed to load channels: {e}");
-        }
-
+        self_.load_channels_from_db().await;
         self_.clone().start(sg_root, ex).await;
         Ok(Pimpl::DarkIrc2(self_))
     }
@@ -418,8 +430,9 @@ impl DarkIrc2 {
                 }
             };
 
-            // TODO: decrypt messages here:
-            // self.try_decrypt(&mut privmsg, &self.nick.get()).await;
+            // Try to decrypt messages (will decrypt encrypted channels/contacts in place)
+            let mut privmsg = privmsg;
+            self.try_decrypt(&mut privmsg, &self.nick.get()).await;
 
             let mut timest = ev.header.timestamp;
             let msg_id = msg_id(&privmsg, timest);
@@ -430,7 +443,7 @@ impl DarkIrc2 {
 
             let is_self = {
                 let mut is_self = false;
-                let mut seen = self.seen_msgs.lock().unwrap();
+                let mut seen = self.seen_msgs.lock();
                 match seen.get_status(&msg_id) {
                     Some(msg) => {
                         is_self = msg.is_self;
@@ -533,9 +546,8 @@ impl DarkIrc2 {
 
         // Send text to channel
         d!("Sending privmsg: {timest} {channel}: <{nick}> {msg}");
-        let msg = Privmsg { version: 0, msg_type: 0, channel, nick, msg };
-        // TODO: messages should be encrypted here with:
-        // self.try_encrypt(&mut msg).await;
+        let mut msg = Privmsg { version: 0, msg_type: 0, channel, nick, msg };
+        self.try_encrypt(&mut msg).await;
         let evgr = self.event_graph.clone();
         let event = event_graph::Event::with_timestamp(timest, serialize_async(&msg).await, &evgr)
             .await
@@ -545,7 +557,7 @@ impl DarkIrc2 {
         // Keep track of our own messages so we don't apply timestamp correction to them
         // which messes up the msg id.
         {
-            let mut seen = self.seen_msgs.lock().unwrap();
+            let mut seen = self.seen_msgs.lock();
             seen.push(msg_id.clone(), true);
         }
 
@@ -562,20 +574,15 @@ impl DarkIrc2 {
     }
 
     /// Load channels from UI database and populate encryption keys
-    pub async fn load_channels_from_db(&self) -> Result<()> {
-        use darkfi_serial::deserialize;
-
+    pub async fn load_channels_from_db(&self) {
         let mut channels = self.channels.write().await;
 
         for item in self.channels_tree.iter() {
-            let (key, val) = item.map_err(|e| {
-                e!("Failed to read channel from database: {e}");
-                Error::SledDbErr
-            })?;
+            let (key, val) = item.unwrap();
             let channel_name = String::from_utf8_lossy(&key).to_string();
 
             // Deserialize UI channel struct
-            let ui_channel = match deserialize::<Channel>(&val) {
+            let ui_channel = match deserialize_async::<Channel>(&val).await {
                 Ok(ch) => ch,
                 Err(e) => {
                     w!("Failed to deserialize channel {channel_name}: {e}");
@@ -589,11 +596,13 @@ impl DarkIrc2 {
                 IrcChannel { topic: String::new(), nicks: HashSet::new(), saltbox: None };
 
             if let Some(secret) = ui_channel.secret {
-                // Convert secret array to CryptoBox
-                let public = PublicKey::from_bytes(secret);
+                // Convert secret array to SecretKey first, then derive PublicKey
                 let secret_key = SecretKey::from_bytes(secret);
-                let saltbox = CryptoBox::new(&public, &secret_key);
+                let public = secret_key.public_key();
+                let saltbox = ChaChaBox::new(&public, &secret_key);
 
+                // Log the secret in base58 for debugging
+                let secret_b58 = bs58::encode(secret).into_string();
                 irc_channel.saltbox = Some(Arc::new(saltbox));
             }
 
@@ -601,33 +610,81 @@ impl DarkIrc2 {
             channels.insert(full_name, irc_channel);
             i!("Loaded channel: #{} (encrypted: {})", channel_name, is_encrypted);
         }
-
-        Ok(())
     }
 
-    /// Reload channels from database (called when UI adds/changes channels)
-    pub async fn reload(&self) -> Result<()> {
-        self.load_channels_from_db().await
+    async fn rescan_channel_history(self: Arc<Self>, channel: String) {
+        i!("Starting background rescan for channel: {channel}");
+
+        // Fetch and order all events from the DAG (like darkirc does)
+        let Ok(dag_events) = self.event_graph.order_events().await else {
+            e!("Failed to fetch events from DAG");
+            return;
+        };
+
+        let mut found_count = 0;
+
+        for event in dag_events.iter() {
+            // Deserialize Privmsg
+            let mut privmsg = match deserialize_async::<Privmsg>(event.content()).await {
+                Ok(pm) => pm,
+                Err(e) => {
+                    t!("Not a Privmsg event, skipping");
+                    continue;
+                }
+            };
+
+            // Try to decrypt (handles encrypted channels)
+            self.try_decrypt(&mut privmsg, &self.nick.get()).await;
+
+            // Check if message belongs to target channel
+            if privmsg.channel != channel {
+                continue;
+            }
+
+            found_count += 1;
+
+            // Calculate message ID
+            let timest = event.header.timestamp;
+            let msg_id = msg_id(&privmsg, timest);
+
+            // Send to ChatView via notify_recv (handles DB storage and duplicates)
+            self.notify_recv(
+                channel.clone(),
+                timest,
+                msg_id,
+                privmsg.nick.clone(),
+                privmsg.msg.clone(),
+            )
+            .await;
+        }
+
+        i!("Rescan complete for {channel}: found {found_count} messages");
     }
 
-    async fn process_reload(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
+    async fn process_rescan(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
         let Ok(method_call) = sub.receive().await else {
-            d!("Reload method closed");
+            d!("Rescan method closed");
             return false
         };
 
-        t!("method called: reload({method_call:?})");
+        t!("method called: rescan({method_call:?})");
 
         let Some(self_) = me.upgrade() else {
-            e!("DarkIrc2 destroyed before reload completed");
+            e!("DarkIrc2 destroyed before rescan completed");
             return false
         };
 
-        if let Err(e) = self_.reload().await {
-            e!("Failed to reload channels: {e}");
-        } else {
-            i!("Successfully reloaded channels");
-        }
+        // Decode channel name from method data
+        let mut cur = std::io::Cursor::new(&method_call.data);
+        let Ok(channel) = String::decode(&mut cur) else {
+            e!("Rescan method called with invalid channel data");
+            return false
+        };
+
+        self_.load_channels_from_db().await;
+
+        let task = self_.ex.clone().spawn(self_.clone().rescan_channel_history(channel));
+        self_.tasks.lock().push(task);
 
         true
     }
@@ -699,10 +756,10 @@ impl DarkIrc2 {
                 async move { while Self::process_reconnect(&me2, &reconnect_method_sub).await {} },
             );
 
-        let reload_method_sub = node.subscribe_method_call("reload").unwrap();
+        let rescan_method_sub = node.subscribe_method_call("rescan").unwrap();
         let me2 = me.clone();
-        let reload_method_task =
-            ex.spawn(async move { while Self::process_reload(&me2, &reload_method_sub).await {} });
+        let rescan_method_task =
+            ex.spawn(async move { while Self::process_rescan(&me2, &rescan_method_sub).await {} });
 
         let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
         async fn save_nick(self_: Arc<DarkIrc2>, _batch: BatchGuardPtr) {
@@ -753,14 +810,14 @@ impl DarkIrc2 {
         let mut tasks = vec![
             send_method_task,
             reconnect_method_task,
-            reload_method_task,
+            rescan_method_task,
             ev_task,
             dag_task,
             start_task,
             stop_task,
         ];
         tasks.append(&mut on_modify.tasks);
-        self.tasks.set(tasks).unwrap();
+        *self.tasks.lock() = tasks;
     }
 
     /// Try encrypting a given `Privmsg` if there is such a channel/contact.
@@ -785,20 +842,9 @@ impl DarkIrc2 {
 
     /// Try decrypting a given potentially encrypted `Privmsg` object.
     pub async fn try_decrypt(&self, privmsg: &mut Privmsg, self_nickname: &str) {
-        let channel_ciphertext = match bs58::decode(&privmsg.channel).into_vec() {
-            Ok(v) => v,
-            Err(_) => return,
-        };
-
-        let nick_ciphertext = match bs58::decode(&privmsg.nick).into_vec() {
-            Ok(v) => v,
-            Err(_) => return,
-        };
-
-        let msg_ciphertext = match bs58::decode(&privmsg.msg).into_vec() {
-            Ok(v) => v,
-            Err(_) => return,
-        };
+        let Ok(channel_ciphertext) = bs58::decode(&privmsg.channel).into_vec() else { return };
+        let Ok(nick_ciphertext) = bs58::decode(&privmsg.nick).into_vec() else { return };
+        let Ok(msg_ciphertext) = bs58::decode(&privmsg.msg).into_vec() else { return };
 
         for (name, channel) in self.channels.read().await.iter() {
             let Some(saltbox) = &channel.saltbox else { continue };
@@ -845,7 +891,6 @@ impl DarkIrc2 {
             privmsg.channel = name.to_string();
             privmsg.nick = nick;
             privmsg.msg = String::from_utf8_lossy(&msg_dec).into();
-            d!("Successfully decrypted message from {name}");
             return
         }
     }