Repository: farcaster-project/farcaster-node
Pull request #418 (2022-04-16 16:25:19)

Close and delete wallet after sweep


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 3bffe539..b55a1cbd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1772,7 +1772,7 @@ dependencies = [
 [[package]]
 name = "monero-rpc"
 version = "0.1.0"
-source = "git+https://github.com/monero-ecosystem/monero-rpc-rs?branch=master#dcda1161f279f345c95cde783f0d002ab2ae93cf"
+source = "git+https://github.com/monero-ecosystem/monero-rpc-rs?branch=master#29878ff67bba414cb0bbc334a7bbb15e7cd55e36"
 dependencies = [
  "anyhow",
  "chrono",
diff --git a/src/config.rs b/src/config.rs
index 5573e22f..0eb3f086 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -150,6 +150,8 @@ pub struct SyncerServers {
     pub monero_rpc_wallet: String,
     /// Monero lws to use
     pub monero_lws: Option<String>,
+    /// Monero wallet directory
+    pub monero_wallet_dir: Option<String>,
 }
 
 impl Default for SyncersConfig {
@@ -160,12 +162,14 @@ impl Default for SyncersConfig {
                 monero_daemon: FARCASTER_MAINNET_MONERO_DAEMON.into(),
                 monero_rpc_wallet: FARCASTER_MAINNET_MONERO_RPC_WALLET.into(),
                 monero_lws: None,
+                monero_wallet_dir: None,
             }),
             testnet: Some(SyncerServers {
                 electrum_server: FARCASTER_TESTNET_ELECTRUM_SERVER.into(),
                 monero_daemon: FARCASTER_TESTNET_MONERO_DAEMON.into(),
                 monero_rpc_wallet: FARCASTER_TESTNET_MONERO_RPC_WALLET.into(),
                 monero_lws: None,
+                monero_wallet_dir: None,
             }),
             local: None,
         }
diff --git a/src/farcasterd/runtime.rs b/src/farcasterd/runtime.rs
index 7c22f32d..69ec61de 100644
--- a/src/farcasterd/runtime.rs
+++ b/src/farcasterd/runtime.rs
@@ -1166,11 +1166,9 @@ impl Runtime {
                                 monero_rpc::RpcClient::new(host);
                             let wallet = wallet_client.wallet();
                             let options = monero_rpc::TransferOptions::default();
-                            let mut destination = HashMap::new();
-                            destination.insert(address, amount.as_pico());
                             match wallet
                                 .transfer(
-                                    destination.clone(),
+                                    [(address, amount)].iter().cloned().collect(),
                                     monero_rpc::TransferPriority::Default,
                                     options.clone(),
                                 )
@@ -1509,6 +1507,11 @@ fn syncer_servers_args(config: &Config, coin: Coin, net: Network) -> Result<Vec<
                         .monero_lws
                         .map_or(vec![], |v| vec!["--monero-lws".to_string(), v]),
                 );
+                args.extend(
+                    servers
+                        .monero_wallet_dir
+                        .map_or(vec![], |v| vec!["--monero-wallet-dir-path".to_string(), v]),
+                );
                 Ok(args)
             }
         },
diff --git a/src/syncerd/monero_syncer.rs b/src/syncerd/monero_syncer.rs
index eac6dd89..d4774e8f 100644
--- a/src/syncerd/monero_syncer.rs
+++ b/src/syncerd/monero_syncer.rs
@@ -28,7 +28,9 @@ use monero_rpc::{
     PrivateKeyType, TransferType,
 };
 use std::collections::HashMap;
+use std::fs;
 use std::ops::Range;
+use std::path::PathBuf;
 use std::str::FromStr;
 use std::sync::mpsc::{Receiver, TryRecvError};
 use std::sync::Arc;
@@ -256,18 +258,13 @@ async fn sweep_address(
     network: &monero::Network,
     wallet_mutex: Arc<Mutex<monero_rpc::WalletClient>>,
     restore_height: Option<u64>,
+    wallet_dir_path: Option<PathBuf>,
 ) -> Result<Vec<Vec<u8>>, Error> {
     let keypair = monero::KeyPair { view, spend };
     let password = s!(" ");
     let address = monero::Address::from_keypair(*network, &keypair);
     let wallet_filename = format!("sweep:{}", address);
 
-    // add some extra leeway for the wallet restore height
-    // let restore_height = match from_height {
-    //     Some(height) if height > 10 => Some(height - 10),
-    //     _ => None,
-    // };
-
     let wallet = wallet_mutex.lock().await;
     trace!("taking sweep wallet lock");
 
@@ -333,6 +330,42 @@ async fn sweep_address(
             })
             .collect();
 
+        // close the wallet since we are done with it now
+        wallet.close_wallet().await?;
+
+        // delete all the associated wallet data if possible
+        if let Some(path) = wallet_dir_path {
+            if let Some(raw_path) = path.to_str() {
+                let watch_wallet_filename = format!("/watch:{}", address);
+                let sweep_wallet_filename = format!("/sweep:{}", address);
+                if let Err(error) = fs::remove_file(raw_path.to_string() + &sweep_wallet_filename)
+                    .and(fs::remove_file(
+                        raw_path.to_string() + &sweep_wallet_filename + &".keys".to_string(),
+                    ))
+                {
+                    warn!("Failed to clean sweep wallet data after successful sweep. {}. The path used for the wallet directory is probably malformed", error);
+                } else {
+                    info!("Successfully removed sweep wallet data after completed sweep.");
+                }
+
+                if let Err(error) = fs::remove_file(raw_path.to_string() + &watch_wallet_filename)
+                    .and(fs::remove_file(
+                        raw_path.to_string() + &watch_wallet_filename + &".address.txt".to_string(),
+                    ))
+                    .and(fs::remove_file(
+                        raw_path.to_string() + &watch_wallet_filename + &".keys".to_string(),
+                    ))
+                {
+                    debug!("Failed to clean watch-only wallet data after sweep. {}. The path used for the wallet directory is probably malformed", error);
+                } else {
+                    debug!("Successfully removed watch-only wallet data after completed sweep");
+                }
+            } else {
+                warn!("No associated wallet data cleaned up after sweep. The path used for the wallet directory is probably malformed");
+            }
+        } else {
+            info!("{}", format!("Completed operations on Monero wallets with address {} . These wallets can now be safely deleted", address));
+        }
         Ok(tx_ids)
     } else {
         debug!(
@@ -619,6 +652,7 @@ fn sweep_polling(
     state: Arc<Mutex<SyncerState>>,
     wallet: Arc<Mutex<monero_rpc::WalletClient>>,
     network: monero::Network,
+    wallet_dir_path: Option<PathBuf>,
 ) -> tokio::task::JoinHandle<()> {
     tokio::task::spawn(async move {
         loop {
@@ -635,6 +669,7 @@ fn sweep_polling(
                         &network,
                         Arc::clone(&wallet),
                         sweep_address_task.from_height,
+                        wallet_dir_path.clone(),
                     )
                     .await
                     .unwrap_or_else(|err| {
@@ -754,6 +789,10 @@ impl Synclet for MoneroSyncer {
                     monero_lws: opts.monero_lws.clone(),
                 };
                 info!("monero syncer servers: {:?}", syncer_servers);
+                let wallet_dir = opts
+                    .monero_wallet_dir_path
+                    .clone()
+                    .map(|wallet_dir| PathBuf::from(wallet_dir));
 
                 let _handle = std::thread::spawn(move || {
                     use tokio::runtime::Builder;
@@ -796,8 +835,12 @@ impl Synclet for MoneroSyncer {
                         let unseen_transaction_handle =
                             unseen_transaction_polling(Arc::clone(&state), syncer_servers.clone());
 
-                        let sweep_handle =
-                            sweep_polling(Arc::clone(&state), Arc::clone(&wallet_mutex), network);
+                        let sweep_handle = sweep_polling(
+                            Arc::clone(&state),
+                            Arc::clone(&wallet_mutex),
+                            network,
+                            wallet_dir,
+                        );
 
                         let res = tokio::try_join!(
                             address_handle,
diff --git a/src/syncerd/opts.rs b/src/syncerd/opts.rs
index 250bcf8c..419a0441 100644
--- a/src/syncerd/opts.rs
+++ b/src/syncerd/opts.rs
@@ -65,6 +65,10 @@ pub struct Opts {
     /// Monero lws to use for Monero syncers
     #[clap(long)]
     pub monero_lws: Option<String>,
+
+    /// Wallet directory use by the monero-wallet-rpc
+    #[clap(long)]
+    pub monero_wallet_dir_path: Option<String>,
 }
 
 #[derive(Clap, Display, Copy, Clone, Hash, PartialEq, Eq, Debug, StrictEncode, StrictDecode)]


Test patch:
diff --git a/tests/functional-swap.rs b/tests/functional-swap.rs
index f02ae61a..d788befe 100644
--- a/tests/functional-swap.rs
+++ b/tests/functional-swap.rs
@@ -1959,8 +1959,6 @@ async fn send_monero(
     address: monero::Address,
     amount: monero::Amount,
 ) -> Vec<u8> {
-    let mut destination: HashMap<monero::Address, u64> = HashMap::new();
-    destination.insert(address, amount.as_pico());
     let options = monero_rpc::TransferOptions {
         account_index: None,
         subaddr_indices: None,
@@ -1978,7 +1976,7 @@ async fn send_monero(
     wallet_lock.refresh(Some(1)).await.unwrap();
     let transaction = wallet_lock
         .transfer(
-            destination.clone(),
+            [(address, amount)].iter().cloned().collect(),
             monero_rpc::TransferPriority::Default,
             options.clone(),
         )
diff --git a/tests/functional.rs b/tests/functional.rs
index 3b91496c..fbd5e4f5 100644
--- a/tests/functional.rs
+++ b/tests/functional.rs
@@ -16,11 +16,9 @@ use internet2::Decrypt;
 use internet2::PlainTranscoder;
 use internet2::RoutedFrame;
 use internet2::ZMQ_CONTEXT;
-use monero::Address;
 use monero_rpc::GetBlockHeaderSelector;
 use paste::paste;
 use rand::{distributions::Alphanumeric, Rng};
-use std::collections::HashMap;
 use std::sync::mpsc::Receiver;
 use std::sync::mpsc::Sender;
 
@@ -1474,8 +1472,6 @@ async fn monero_syncer_transaction_test() {
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
 
-    let mut destination: HashMap<Address, u64> = HashMap::new();
-    destination.insert(address, 1000);
     let options = monero_rpc::TransferOptions {
         account_index: None,
         subaddr_indices: None,
@@ -1487,7 +1483,10 @@ async fn monero_syncer_transaction_test() {
     };
     let transaction = wallet
         .transfer(
-            destination.clone(),
+            [(address, monero::Amount::from_pico(1000))]
+                .iter()
+                .cloned()
+                .collect(),
             monero_rpc::TransferPriority::Default,
             options.clone(),
         )
@@ -1783,8 +1782,6 @@ async fn send_monero(
     address: monero::Address,
     amount: u64,
 ) -> Vec<u8> {
-    let mut destination: HashMap<Address, u64> = HashMap::new();
-    destination.insert(address, amount);
     let options = monero_rpc::TransferOptions {
         account_index: None,
         subaddr_indices: None,
@@ -1796,7 +1793,10 @@ async fn send_monero(
     };
     let transaction = wallet
         .transfer(
-            destination.clone(),
+            [(address, monero::Amount::from_pico(amount))]
+                .iter()
+                .cloned()
+                .collect(),
             monero_rpc::TransferPriority::Default,
             options.clone(),
         )