Parcourir la source

app: add toggle to select between tcp and tor (currently disabled for drk)

darkfi il y a 3 semaines
Parent
commit
a60bcedfd7
4 fichiers modifiés avec 318 ajouts et 57 suppressions
  1. 161 9
      bin/app/src/app/schema/mod.rs
  2. 95 41
      bin/app/src/plugin/darkirc.rs
  3. 57 7
      bin/app/src/plugin/drk.rs
  4. 5 0
      bin/app/src/setting.rs

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

@@ -34,7 +34,7 @@ use crate::{
     db::AppDbPtr,
     expr::{self, Compiler},
     gfx::gfxtag,
-    prop::{PropertyAtomicGuard, PropertyFloat32, PropertyStr, Role},
+    prop::{PropertyAtomicGuard, PropertyEnum, PropertyFloat32, PropertyStr, Role},
     scene::{SceneNodePtr, Slot},
     sfx, shape,
     ui::{emoji_picker, Button, Layer, Text, TextScramble, VectorArt, VectorShape, Video},
@@ -72,16 +72,16 @@ mod android_ui_consts {
     pub const SPLASH_FONTSIZE: f32 = 52.;
     pub const SPLASH_MARGIN: f32 = 40.;
 
-    pub const NETSTAT_OVERLAY_HEIGHT: f32 = 760.;
+    pub const NETSTAT_OVERLAY_HEIGHT: f32 = 960.;
     pub const NETSTAT_OVERLAY_SEP_X: f32 = 2.;
-    pub const NETSTAT_OVERLAY_SEP_Y: f32 = 240.;
+    pub const NETSTAT_OVERLAY_SEP_Y: f32 = 440.;
     pub const NETSTAT_OVERLAY_SEP_H: f32 = 2.;
     pub const NETSTAT_OVERLAY_OUTLINE_W: f32 = 4.;
     pub const NETSTAT_OVERLAY_TEXT_X: f32 = 100.;
     pub const NETSTAT_OVERLAY_TEXT_MAX: f32 = 4000.;
     pub const NETSTAT_OVERLAY_P2P_LABEL_Y: f32 = 100.;
-    pub const NETSTAT_OVERLAY_OUTBOUND_LABEL_Y: f32 = 340.;
-    pub const NETSTAT_OVERLAY_CONN_INFO_Y: f32 = 460.;
+    pub const NETSTAT_OVERLAY_OUTBOUND_LABEL_Y: f32 = 540.;
+    pub const NETSTAT_OVERLAY_CONN_INFO_Y: f32 = 660.;
     pub const NETSTAT_OVERLAY_TOGGLE_NEG_X: f32 = 240.;
     pub const NETSTAT_OVERLAY_TOGGLE_R_PAD: f32 = 40.;
     pub const NETSTAT_OVERLAY_TOGGLE_Y: f32 = 40.;
@@ -89,6 +89,9 @@ mod android_ui_consts {
     pub const NETSTAT_OVERLAY_TOGGLE_H: f32 = 160.;
     pub const NETSTAT_OVERLAY_TOGGLE_OUTLINE_W: f32 = 2.;
     pub const NETSTAT_OVERLAY_TOGGLE_LABEL_Y: f32 = 90.;
+    pub const NETSTAT_OVERLAY_TRANSPORT_Y: f32 = 240.;
+    pub const NETSTAT_OVERLAY_TRANSPORT_LABEL_Y: f32 = 300.;
+    pub const NETSTAT_OVERLAY_TRANSPORT_OPT_LABEL_Y: f32 = 290.;
 }
 
 #[cfg(target_os = "android")]
@@ -158,16 +161,16 @@ mod ui_consts {
     pub const SPLASH_FONTSIZE: f32 = 26.;
     pub const SPLASH_MARGIN: f32 = 20.;
 
-    pub const NETSTAT_OVERLAY_HEIGHT: f32 = 380.;
+    pub const NETSTAT_OVERLAY_HEIGHT: f32 = 480.;
     pub const NETSTAT_OVERLAY_SEP_X: f32 = 1.;
-    pub const NETSTAT_OVERLAY_SEP_Y: f32 = 120.;
+    pub const NETSTAT_OVERLAY_SEP_Y: f32 = 220.;
     pub const NETSTAT_OVERLAY_SEP_H: f32 = 1.;
     pub const NETSTAT_OVERLAY_OUTLINE_W: f32 = 2.;
     pub const NETSTAT_OVERLAY_TEXT_X: f32 = 50.;
     pub const NETSTAT_OVERLAY_TEXT_MAX: f32 = 2000.;
     pub const NETSTAT_OVERLAY_P2P_LABEL_Y: f32 = 50.;
-    pub const NETSTAT_OVERLAY_OUTBOUND_LABEL_Y: f32 = 170.;
-    pub const NETSTAT_OVERLAY_CONN_INFO_Y: f32 = 230.;
+    pub const NETSTAT_OVERLAY_OUTBOUND_LABEL_Y: f32 = 270.;
+    pub const NETSTAT_OVERLAY_CONN_INFO_Y: f32 = 330.;
     pub const NETSTAT_OVERLAY_TOGGLE_NEG_X: f32 = 120.;
     pub const NETSTAT_OVERLAY_TOGGLE_R_PAD: f32 = 20.;
     pub const NETSTAT_OVERLAY_TOGGLE_Y: f32 = 20.;
@@ -175,6 +178,9 @@ mod ui_consts {
     pub const NETSTAT_OVERLAY_TOGGLE_H: f32 = 80.;
     pub const NETSTAT_OVERLAY_TOGGLE_OUTLINE_W: f32 = 1.;
     pub const NETSTAT_OVERLAY_TOGGLE_LABEL_Y: f32 = 45.;
+    pub const NETSTAT_OVERLAY_TRANSPORT_Y: f32 = 120.;
+    pub const NETSTAT_OVERLAY_TRANSPORT_LABEL_Y: f32 = 150.;
+    pub const NETSTAT_OVERLAY_TRANSPORT_OPT_LABEL_Y: f32 = 145.;
 
     pub use super::desktop_paths::*;
 }
@@ -261,6 +267,7 @@ pub async fn make(
     cc.add_const_f32("NETSTAT_OVERLAY_BTN_H", NETSTAT_OVERLAY_BTN_H);
     cc.add_const_f32("NETSTAT_OVERLAY_TOGGLE_NEG_X", NETSTAT_OVERLAY_TOGGLE_NEG_X);
     cc.add_const_f32("NETSTAT_OVERLAY_TOGGLE_R_PAD", NETSTAT_OVERLAY_TOGGLE_R_PAD);
+    cc.add_const_f32("NETSTAT_OVERLAY_TOGGLE_W", NETSTAT_OVERLAY_TOGGLE_W);
 
     let atom = &mut PropertyAtomicGuard::none();
 
@@ -836,6 +843,151 @@ pub async fn make(
         node.setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone())).await;
     overlay_node.link(node);
 
+    let node = create_text("transport_label");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, NETSTAT_OVERLAY_TEXT_X).unwrap();
+    prop.set_f32(atom, Role::App, 1, NETSTAT_OVERLAY_TRANSPORT_LABEL_Y).unwrap();
+    prop.set_f32(atom, Role::App, 2, NETSTAT_OVERLAY_TEXT_MAX).unwrap();
+    prop.set_f32(atom, Role::App, 3, NETSTAT_OVERLAY_TEXT_MAX).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", NETSTAT_OVERLAY_BTN_FONTSIZE).unwrap();
+    node.set_property_str(atom, Role::App, "text", "Transport").unwrap();
+    node.set_property_enum(atom, Role::App, "text_align", "left").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.47).unwrap();
+    prop.set_f32(atom, Role::App, 1, 1.).unwrap();
+    prop.set_f32(atom, Role::App, 2, 0.75).unwrap();
+    prop.set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
+    let node = node
+        .setup(|me| {
+            Text::new(
+                me,
+                window_scale.clone(),
+                app.renderer.clone(),
+                i18n_fish.clone(),
+                app.redraw_trigger.clone(),
+            )
+        })
+        .await;
+    overlay_node.link(node);
+
+    // Transport selector: one segment per option, the filled toggle is moved
+    // onto the selected one
+    let transport_opts = ["tcp", "tor"];
+    let net_transport = PropertyEnum::wrap(&setting_node, Role::User, "net.transport", 0).unwrap();
+    let transport_selected = net_transport.get();
+    let mut transport_sel_nodes = vec![];
+    for (idx, opt) in transport_opts.iter().enumerate() {
+        let node = create_vector_art(&format!("transport_sel_{opt}"));
+        let prop = node.get_property("rect").unwrap();
+        let code = cc
+            .compile(&format!(
+                "w - NETSTAT_OVERLAY_TOGGLE_R_PAD - {} * NETSTAT_OVERLAY_TOGGLE_W",
+                transport_opts.len() - idx
+            ))
+            .unwrap();
+        prop.set_expr(atom, Role::App, 0, code).unwrap();
+        prop.set_f32(atom, Role::App, 1, NETSTAT_OVERLAY_TRANSPORT_Y).unwrap();
+        prop.set_f32(atom, Role::App, 2, NETSTAT_OVERLAY_TOGGLE_W).unwrap();
+        prop.set_f32(atom, Role::App, 3, NETSTAT_OVERLAY_TOGGLE_H).unwrap();
+        node.set_property_bool(atom, Role::App, "is_visible", *opt == transport_selected).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+        let mut shape = VectorShape::new();
+        shape.add_filled_box(
+            expr::const_f32(0.),
+            expr::const_f32(0.),
+            expr::load_var("w"),
+            expr::load_var("h"),
+            [0., 0.12, 0.08, 1.],
+        );
+        shape.add_outline(
+            expr::const_f32(0.),
+            expr::const_f32(0.),
+            expr::load_var("w"),
+            expr::load_var("h"),
+            NETSTAT_OVERLAY_TOGGLE_OUTLINE_W,
+            [0.08, 0.68, 0.72, 1.],
+        );
+        let node = node
+            .setup(|me| VectorArt::new(me, shape, app.renderer.clone(), app.redraw_trigger.clone()))
+            .await;
+        overlay_node.link(node.clone());
+        transport_sel_nodes.push(node);
+    }
+
+    for (idx, opt) in transport_opts.iter().enumerate() {
+        let node = create_text(&format!("transport_opt_{opt}"));
+        let prop = node.get_property("rect").unwrap();
+        let code = cc
+            .compile(&format!(
+                "w - NETSTAT_OVERLAY_TOGGLE_R_PAD - {} * NETSTAT_OVERLAY_TOGGLE_W",
+                transport_opts.len() - idx
+            ))
+            .unwrap();
+        prop.set_expr(atom, Role::App, 0, code).unwrap();
+        prop.set_f32(atom, Role::App, 1, NETSTAT_OVERLAY_TRANSPORT_OPT_LABEL_Y).unwrap();
+        prop.set_f32(atom, Role::App, 2, NETSTAT_OVERLAY_TOGGLE_W).unwrap();
+        prop.set_f32(atom, Role::App, 3, NETSTAT_OVERLAY_TEXT_MAX).unwrap();
+        node.set_property_f32(atom, Role::App, "font_size", NETSTAT_OVERLAY_BTN_FONTSIZE).unwrap();
+        node.set_property_str(atom, Role::App, "text", *opt).unwrap();
+        node.set_property_enum(atom, Role::App, "text_align", "center").unwrap();
+        let prop = node.get_property("text_color").unwrap();
+        prop.set_f32(atom, Role::App, 0, 0.08).unwrap();
+        prop.set_f32(atom, Role::App, 1, 0.68).unwrap();
+        prop.set_f32(atom, Role::App, 2, 0.72).unwrap();
+        prop.set_f32(atom, Role::App, 3, 1.).unwrap();
+        node.set_property_u32(atom, Role::App, "z_index", 2).unwrap();
+        let node = node
+            .setup(|me| {
+                Text::new(
+                    me,
+                    window_scale.clone(),
+                    app.renderer.clone(),
+                    i18n_fish.clone(),
+                    app.redraw_trigger.clone(),
+                )
+            })
+            .await;
+        overlay_node.link(node);
+    }
+
+    for (idx, opt) in transport_opts.iter().enumerate() {
+        let node = create_button(&format!("transport_btn_{opt}"));
+        node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
+        let prop = node.get_property("rect").unwrap();
+        let code = cc
+            .compile(&format!(
+                "w - NETSTAT_OVERLAY_TOGGLE_R_PAD - {} * NETSTAT_OVERLAY_TOGGLE_W",
+                transport_opts.len() - idx
+            ))
+            .unwrap();
+        prop.set_expr(atom, Role::App, 0, code).unwrap();
+        prop.set_f32(atom, Role::App, 1, NETSTAT_OVERLAY_TRANSPORT_Y).unwrap();
+        prop.set_f32(atom, Role::App, 2, NETSTAT_OVERLAY_TOGGLE_W).unwrap();
+        prop.set_f32(atom, Role::App, 3, NETSTAT_OVERLAY_TOGGLE_H).unwrap();
+        let (slot, recvr) = Slot::new(&format!("transport_select_{opt}"));
+        node.register("click", slot).unwrap();
+        let redraw = app.redraw_trigger.clone();
+        let sel_nodes = transport_sel_nodes.clone();
+        let net_transport = net_transport.clone();
+        let opt = *opt;
+        let listen_click = ex.spawn(async move {
+            while let Ok(_) = recvr.recv().await {
+                i!("transport_select_{opt}");
+                let atom = &mut redraw.make_guard(gfxtag!("transport_select"));
+                for (j, sel_node) in sel_nodes.iter().enumerate() {
+                    sel_node.set_property_bool(atom, Role::App, "is_visible", j == idx).unwrap();
+                }
+                net_transport.set(atom, opt);
+            }
+        });
+        overlay_node.push_task(listen_click);
+        let node = node
+            .setup(|me| Button::new(me, app.renderer.clone(), app.redraw_trigger.clone()))
+            .await;
+        overlay_node.link(node);
+    }
+
     let node = create_text("outbound_label");
     let prop = node.get_property("rect").unwrap();
     prop.set_f32(atom, Role::App, 0, NETSTAT_OVERLAY_TEXT_X).unwrap();

+ 95 - 41
bin/app/src/plugin/darkirc.rs

@@ -59,7 +59,10 @@ use crate::{
     },
     db::AppDbPtr,
     error::{Error, Result},
-    prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyPtr, PropertyStr, Role},
+    prop::{
+        BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyEnum, PropertyPtr, PropertyStr,
+        Role,
+    },
     scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak, Slot},
     ui::{
         chatview::{MessageId, Timestamp},
@@ -99,9 +102,6 @@ mod paths {
     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")
-    }
 
     pub fn p2p_datastore_path() -> PathBuf {
         get_appdata_path().join("darkirc2_p2p")
@@ -118,9 +118,6 @@ mod paths {
     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")
-    }
 
     pub fn p2p_datastore_path() -> PathBuf {
         dirs::cache_dir().unwrap().join("darkfi/app/darkirc2_p2p")
@@ -172,6 +169,7 @@ pub struct DarkIrc {
     seen_msgs: SyncMutex<SeenMessages>,
     nick: PropertyStr,
     chat_is_enabled: PropertyBool,
+    net_transport: PropertyEnum,
     pub channels: RwLock<HashMap<String, IrcChannel>>,
     pub contacts: RwLock<HashMap<String, IrcContact>>,
     app_db: AppDbPtr,
@@ -192,6 +190,8 @@ impl DarkIrc {
         let setting_node = sg_root.lookup_node("/setting").unwrap();
         let chat_is_enabled =
             PropertyBool::wrap(&setting_node, Role::User, "chat.is_enabled", 0).unwrap();
+        let net_transport =
+            PropertyEnum::wrap(&setting_node, Role::Internal, "net.transport", 0).unwrap();
 
         i!("Starting DarkIRC backend");
 
@@ -213,40 +213,9 @@ impl DarkIrc {
         p2p_settings.magic_bytes = MagicBytes([251, 229, 199, 181]);
         p2p_settings.app_version = semver::Version::parse("0.5.0").unwrap();
         p2p_settings.app_name = "darkirc".to_string();
-        if get_use_tor_filename().exists() {
-            i!("Setup P2P network [tor]");
-            let mut tor_profile = NetworkProfile::tor_default();
-            tor_profile.outbound_connect_timeout = 60;
-            p2p_settings.profiles.insert("tor".to_string(), tor_profile);
-            p2p_settings.outbound_peer_discovery_cooloff_time = 60;
-
-            p2p_settings.seeds.push(
-                url::Url::parse(
-                    "tor://g7fxelebievvpr27w7gt24lflptpw3jeeuvafovgliq5utdst6xyruyd.onion:25552",
-                )
-                .unwrap(),
-            );
-            p2p_settings.seeds.push(
-                url::Url::parse(
-                    "tor://yvklzjnfmwxhyodhrkpomawjcdvcaushsj6torjz2gyd7e25f3gfunyd.onion:25552",
-                )
-                .unwrap(),
-            );
-            p2p_settings.active_profiles = vec!["tor".to_string()];
-        } else {
-            i!("Setup P2P network [clearnet]");
-            let mut profile = NetworkProfile::default();
-            profile.outbound_connect_timeout = 40;
-            profile.channel_handshake_timeout = 30;
-            p2p_settings.profiles.insert("tcp+tls".to_string(), profile);
-
-            p2p_settings.outbound_connections = 5;
-            p2p_settings.inbound_connections = 2;
-
-            p2p_settings.seeds.push(url::Url::parse("tcp+tls://lilith0.dark.fi:9600").unwrap());
-            p2p_settings.seeds.push(url::Url::parse("tcp+tls://lilith1.dark.fi:9600").unwrap());
-            p2p_settings.active_profiles = vec!["tcp+tls".to_string()];
-        }
+        p2p_settings.inbound_connections = 2;
+        let transport = net_transport.get();
+        Self::apply_transport_settings(&mut p2p_settings, &transport);
         p2p_settings.p2p_datastore = p2p_datastore_path().into_os_string().into_string().ok();
         p2p_settings.hostlist = hostlist_path().into_os_string().into_string().ok();
 
@@ -301,6 +270,7 @@ impl DarkIrc {
             seen_msgs: SyncMutex::new(SeenMessages::new()),
             nick,
             chat_is_enabled,
+            net_transport,
 
             channels: RwLock::new(HashMap::new()),
             contacts: RwLock::new(HashMap::new()),
@@ -854,6 +824,78 @@ impl DarkIrc {
         true
     }
 
+    /// Apply the transport-specific P2P configuration (profiles, seeds,
+    /// active profiles) using the defaults from darkirc_config.toml
+    fn apply_transport_settings(settings: &mut NetSettings, transport: &str) {
+        settings.seeds.clear();
+        settings.profiles.clear();
+        settings.active_profiles.clear();
+
+        match transport {
+            "tor" => {
+                i!("Setup P2P network [tor]");
+                let mut tor_profile = NetworkProfile::tor_default();
+                tor_profile.outbound_connect_timeout = 60;
+                settings.profiles.insert("tor".to_string(), tor_profile);
+                settings.outbound_peer_discovery_cooloff_time = 60;
+
+                settings.seeds.push(
+                    url::Url::parse(
+                        "tor://wgxxaifz5gv4iggcflyl67lgmsihffs6bbwobqah4np52t3y3olrnpid.onion:9601",
+                    )
+                    .unwrap(),
+                );
+                settings.seeds.push(
+                    url::Url::parse(
+                        "tor://inx5s3pdzddvgb5ii3oydutmbvw6fvor3oqu65wtxl3pyevtvrdn4had.onion:9601",
+                    )
+                    .unwrap(),
+                );
+                settings.active_profiles.push("tor".to_string());
+            }
+            "tcp" => {
+                i!("Setup P2P network [clearnet]");
+                let mut profile = NetworkProfile::default();
+                profile.outbound_connect_timeout = 40;
+                profile.channel_handshake_timeout = 30;
+                settings.profiles.insert("tcp+tls".to_string(), profile);
+
+                settings.seeds.push(url::Url::parse("tcp+tls://lilith0.dark.fi:9600").unwrap());
+                settings.seeds.push(url::Url::parse("tcp+tls://lilith1.dark.fi:9600").unwrap());
+                settings.active_profiles.push("tcp+tls".to_string());
+            }
+            unhandled => panic!("Unhandled net.transport value: {unhandled}"),
+        }
+    }
+
+    /// `net.transport` was switched, reconfigure the P2P network and restart it
+    async fn handle_transport_change(&self, transport: String) {
+        i!("Transport changed to {transport}, restarting P2P network");
+
+        let was_started = self.chat_is_enabled.get();
+        if was_started {
+            self.p2p.clone().stop().await;
+        }
+
+        let settings_lock = self.p2p.settings();
+        let mut settings = settings_lock.write().await;
+        Self::apply_transport_settings(&mut settings, &transport);
+        drop(settings);
+
+        if was_started {
+            while let Err(err) = self.p2p.clone().start().await {
+                e!("Failed to start P2P network: {err}!");
+                e!("Retrying in {P2P_RETRY_TIME} secs");
+                sleep(P2P_RETRY_TIME).await;
+            }
+
+            let peers_count = self.p2p.peers_count();
+            self.notify_connect(peers_count, self.event_graph.is_synced()).await;
+        }
+
+        i!("P2P transport restart completed");
+    }
+
     /// `chat.is_enabled` was switched on
     async fn handle_start(&self) {
         i!("Manual P2P start triggered");
@@ -938,6 +980,17 @@ impl DarkIrc {
             }
         });
 
+        let net_transport = self.net_transport.clone();
+        let net_transport_sub = net_transport.prop().subscribe_modify();
+        let me2 = me.clone();
+        let transport_task = ex.spawn(async move {
+            while let Ok(_) = net_transport_sub.receive().await {
+                let Some(self_) = me2.upgrade() else { break };
+
+                self_.handle_transport_change(net_transport.get()).await;
+            }
+        });
+
         let rescan_method_sub = node.subscribe_method_call("rescan").unwrap();
         let me2 = me.clone();
         let rescan_method_task =
@@ -1006,6 +1059,7 @@ impl DarkIrc {
         let mut tasks = vec![
             send_method_task,
             setting_task,
+            transport_task,
             rescan_method_task,
             ev_task,
             dag_task,

+ 57 - 7
bin/app/src/plugin/drk.rs

@@ -35,13 +35,17 @@ use url::Url;
 
 use crate::{
     error::{Error, Result},
+    prop::{PropertyEnum, Role},
     scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
     ExecutorPtr,
 };
 
 // TODO: should be configurable at runtime
 //const DARKFID_ENDPOINT: &str = "tcp://127.0.0.1:18345";
-const DARKFID_ENDPOINT: &str = "tcp+tls://node0.testnet.dark.fi:18345";
+/// Testnet endpoint from drk_config.toml
+const DARKFID_ENDPOINT_TCP: &str = "tcp://127.0.0.1:18345";
+/// TODO: replace with the real darkfid tor endpoint
+const DARKFID_ENDPOINT_TOR: &str = "tor://darkfid-tor-placeholder.onion:18345";
 const DARKFID_RETRY_TIME: u64 = 20;
 
 #[cfg(target_os = "android")]
@@ -122,6 +126,7 @@ pub struct DrkPlugin {
     sg_root: SceneNodePtr,
     tasks: OnceLock<Vec<smol::Task<()>>>,
     scan_progress_pub: PublisherPtr<(u32, u32)>,
+    net_transport: PropertyEnum,
 
     drk: Arc<RwLock<Drk>>,
     build_tx_channel: smol::channel::Sender<BuildTxRequest>,
@@ -130,16 +135,19 @@ pub struct DrkPlugin {
 
 impl DrkPlugin {
     pub async fn new(node: SceneNodeWeak, sg_root: SceneNodePtr, ex: ExecutorPtr) -> Result<Pimpl> {
-        let node_ref = node.upgrade().unwrap();
+        let setting_node = sg_root.lookup_node("/setting").unwrap();
+        let net_transport =
+            PropertyEnum::wrap(&setting_node, Role::Internal, "net.transport", 0).unwrap();
 
-        let endpoint = Url::parse(DARKFID_ENDPOINT).unwrap();
+        let endpoint = Url::parse(DARKFID_ENDPOINT_TCP).unwrap();
+        i!("Using {transport} transport for darkfid connection");
 
         let drk = match Drk::new(
             Network::Testnet,
             get_cache_path().to_string_lossy().to_string(),
             get_wallet_path().to_string_lossy().to_string(),
             "changeme".to_string(),
-            Some(endpoint),
+            Some(endpoint.clone()),
             &ex,
             false,
         )
@@ -205,6 +213,7 @@ impl DrkPlugin {
             drk: drk.into_ptr(),
             build_tx_channel: build_tx_tx,
             scan_progress_pub: Publisher::new(),
+            net_transport,
             last_balances: SyncMutex::new(None),
         });
 
@@ -276,6 +285,21 @@ impl DrkPlugin {
         Ok(Pimpl::Drk(self_))
     }
 
+    /// Endpoint for the darkfid daemon connection, derived from the
+    /// `net.transport` setting
+    fn endpoint(&self) -> Url {
+        // Disabled pending drk changes
+        /*
+        let endpoint = match self.net_transport.get().as_str() {
+            "tor" => DARKFID_ENDPOINT_TOR,
+            "tcp" => DARKFID_ENDPOINT_TCP,
+            unhandled => panic!("Unhandled net.transport value: {unhandled}"),
+        };
+        Url::parse(endpoint).unwrap()
+        */
+        Url::parse(DARKFID_ENDPOINT_TCP).unwrap()
+    }
+
     pub async fn get_default_address(&self) -> Result<String> {
         let drk = self.drk.read().await;
         let pubkey = drk.default_address().await.map_err(|e| {
@@ -673,8 +697,6 @@ impl DrkPlugin {
     }
 
     async fn start(self: Arc<Self>, ex: ExecutorPtr, tasks: Vec<smol::Task<()>>) {
-        let endpoint = Url::parse(DARKFID_ENDPOINT).unwrap();
-
         let self2 = self.clone();
         let drk = self.drk.clone();
         let (shell_sender, shell_receiver) = unbounded();
@@ -715,11 +737,11 @@ impl DrkPlugin {
         // Task that handles the RPC subscription with retry logic
         let subscribe_task = ex.spawn(async move {
             loop {
+                let endpoint = self2.endpoint();
                 i!("Attempting to connect to darkfid daemon at {}", endpoint);
                 let subscribe_rpc_task = StoppableTask::new();
                 let shell_sender = shell_sender.clone();
                 let drk = drk.clone();
-                let endpoint = endpoint.clone();
                 let ex = ex_.clone();
                 let progress_pub = self2.scan_progress_pub.clone();
 
@@ -804,6 +826,34 @@ impl DrkPlugin {
             }
         });
 
+        // NOTE: Disabled pending the drk scan_blocks upgrade. The current
+        // drk impl doesn't support restarting the rpc_task, but once we
+        // upgrade it, it will be trivial to fix in our custom loop.
+        //let net_transport = self.net_transport.clone();
+        //let net_transport_sub = net_transport.prop().subscribe_modify();
+        //let drk2 = self.drk.clone();
+        //let rpc_task_lock = self.rpc_task.clone();
+        //let ex_ = ex.clone();
+        //let transport_task = ex.spawn(async move {
+        //    while let Ok(_) = net_transport_sub.receive().await {
+        //        let transport = net_transport.get();
+        //        let endpoint = endpoint_for_transport(&transport);
+        //        i!("Transport changed to {transport}, restarting darkfid connection at {endpoint}");
+        //
+        //        {
+        //            let mut drk = drk2.write().await;
+        //            let _ = drk.stop_rpc_client().await;
+        //            drk.rpc_client = Some(RwLock::new(
+        //                DarkfidRpcClient::new(endpoint.clone(), ex_.clone()).await,
+        //            ));
+        //        }
+        //
+        //        if let Some(rpc_task) = &*rpc_task_lock.read().await {
+        //            rpc_task.stop().await;
+        //        }
+        //    }
+        //});
+
         let mut all_tasks = vec![scan_progress_task, subscribe_task, subscribe_recv_task];
         all_tasks.extend(tasks);
         self.tasks.set(all_tasks).unwrap();

+ 5 - 0
bin/app/src/setting.rs

@@ -57,6 +57,11 @@ pub fn create_setting(name: &str) -> SceneNode {
     prop.set_defaults_bool(vec![true]).unwrap();
     node.add_property(prop).unwrap();
 
+    let mut prop = Property::new("net.transport", PropertyType::Enum, PropertySubType::Null);
+    prop.set_enum_items(vec!["tcp", "tor"]).unwrap();
+    prop.set_defaults_str(vec!["tcp".to_string()]).unwrap();
+    node.add_property(prop).unwrap();
+
     let mut prop = Property::new("win.scale", PropertyType::Float32, PropertySubType::Null);
     prop.set_defaults_f32(vec![1.]).unwrap();
     prop.set_range_f32(0., f32::MAX);