Repository: farcaster-project/farcaster-node
Pull request #417 (2022-04-16 11:56:49)

Monero Syncer: Require a minimum sweep balance


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b55a1cbd..3bffe539 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#29878ff67bba414cb0bbc334a7bbb15e7cd55e36"
+source = "git+https://github.com/monero-ecosystem/monero-rpc-rs?branch=master#dcda1161f279f345c95cde783f0d002ab2ae93cf"
 dependencies = [
  "anyhow",
  "chrono",
diff --git a/farcasterd.toml b/farcasterd.toml
index 570a0140..ec74c683 100644
--- a/farcasterd.toml
+++ b/farcasterd.toml
@@ -32,9 +32,6 @@ monero_daemon = "http://node.monerooutreach.org:18081"
 # Monero Wallet RPC used by the Monero syncer
 # Point to local running wallet
 monero_rpc_wallet = "http://localhost:18083"
-# Optional: the monero light wallet server to use instead of the monero rpc wallet
-# monero_lws = "http://localhost:38884"
-
 
 # Testnet/stagenet daemons
 [syncers.testnet]
@@ -45,9 +42,6 @@ monero_daemon = "http://stagenet.melo.tools:38081"
 # Monero Wallet RPC used by the Monero syncer on stagenet
 # Point to local running wallet
 monero_rpc_wallet = "http://localhost:38083"
-# Optional: the monero light wallet server to use instead of the monero rpc wallet
-# monero_lws = "http://localhost:38884"
-
 
 # Local development daemons, null by default
 [syncers.local]
@@ -57,5 +51,3 @@ electrum_server = "tcp://localhost:50001"
 monero_daemon = "http://localhost:18081"
 # Monero Wallet RPC used by the Monero syncer on regtest
 monero_rpc_wallet = "http://localhost:18083"
-# Optional: the monero light wallet server to use instead of the monero rpc wallet
-# monero_lws = "http://localhost:38884"
diff --git a/src/config.rs b/src/config.rs
index 0eb3f086..5573e22f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -150,8 +150,6 @@ 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 {
@@ -162,14 +160,12 @@ 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 69ec61de..7c22f32d 100644
--- a/src/farcasterd/runtime.rs
+++ b/src/farcasterd/runtime.rs
@@ -1166,9 +1166,11 @@ 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(
-                                    [(address, amount)].iter().cloned().collect(),
+                                    destination.clone(),
                                     monero_rpc::TransferPriority::Default,
                                     options.clone(),
                                 )
@@ -1507,11 +1509,6 @@ 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/swapd/runtime.rs b/src/swapd/runtime.rs
index b5a6f1ce..26cca798 100644
--- a/src/swapd/runtime.rs
+++ b/src/swapd/runtime.rs
@@ -881,6 +881,7 @@ impl SyncerState {
         spend_key: monero::PrivateKey,
         address: monero::Address,
         from_height: Option<u64>,
+        minimum_balance: monero::Amount,
     ) -> Task {
         let id = self.tasks.new_taskid();
         self.tasks.sweeping_addr = Some(id);
@@ -889,6 +890,7 @@ impl SyncerState {
             view_key,
             spend_key,
             address,
+            minimum_balance,
         });
         let sweep_task = SweepAddress {
             id,
@@ -1393,12 +1395,17 @@ impl Runtime {
                 view_key,
                 spend_key,
                 address,
+                minimum_balance,
                 ..
             }) if source == ServiceId::Wallet => {
                 let from_height = None; // will be set when sending out the request
-                let task = self
-                    .syncer_state
-                    .sweep_xmr(view_key, spend_key, address, from_height);
+                let task = self.syncer_state.sweep_xmr(
+                    view_key,
+                    spend_key,
+                    address,
+                    from_height,
+                    minimum_balance,
+                );
                 let acc_confs_needs =
                     self.temporal_safety.sweep_monero_thr - self.temporal_safety.xmr_finality_thr;
                 let sweep_block = self.syncer_state.height(Coin::Monero) + acc_confs_needs as u64;
diff --git a/src/syncerd/monero_syncer.rs b/src/syncerd/monero_syncer.rs
index d4774e8f..36852cb3 100644
--- a/src/syncerd/monero_syncer.rs
+++ b/src/syncerd/monero_syncer.rs
@@ -28,9 +28,7 @@ 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;
@@ -158,9 +156,7 @@ impl MoneroRpc {
         };
         let address = monero::Address::from_viewpair(network, &keypair);
         let daemon_client = monero_lws::LwsRpcClient::new(monero_lws_url);
-        trace!("checking txs through lws for address {}", address);
         let mut txs = daemon_client.get_address_txs(address, keypair.view).await?;
-        trace!("received txs {:?} from lws for address {}", txs, address);
         let address_txs: Vec<AddressTx> = txs
             .transactions
             .drain(..)
@@ -255,16 +251,22 @@ async fn sweep_address(
     dest_address: monero::Address,
     view: monero::PrivateKey,
     spend: monero::PrivateKey,
+    minimum_balance: monero::Amount,
     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");
 
@@ -295,7 +297,7 @@ async fn sweep_address(
     wallet.refresh(restore_height).await?;
     let balance = wallet.get_balance(account, addrs).await?;
     // only sweep once all the balance is unlocked
-    if balance.unlocked_balance != 0 {
+    if balance.unlocked_balance >= minimum_balance.as_pico() {
         info!(
             "Sweeping address {} with unlocked balance {} into {}",
             address.addr(),
@@ -330,47 +332,11 @@ 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!(
-            "retrying sweep, balance not unlocked yet. Unlocked balance {:?}. Total balance {:?}",
-            balance.unlocked_balance, balance.balance
+            "retrying sweep, balance not unlocked yet. Unlocked balance {:?}. Total balance {:?}. Expected balance {:?}.",
+            balance.unlocked_balance, balance.balance, minimum_balance
         );
         trace!("releasing sweep wallet lock");
         Ok(vec![])
@@ -539,8 +505,8 @@ fn address_polling(
                         )
                         .await
                         {
-                            Ok(()) => {
-                                debug!("success subscribing address to monero lws.");
+                            Ok(res) => {
+                                debug!("success subscribing address to monero lws: {:?}", res);
                                 true
                             }
                             Err(err) => {
@@ -595,7 +561,7 @@ fn address_polling(
                         .await;
                 }
             }
-            tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
+            tokio::time::sleep(std::time::Duration::from_millis(500)).await;
         }
     })
 }
@@ -652,7 +618,6 @@ 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 {
@@ -666,10 +631,10 @@ fn sweep_polling(
                         addendum.address,
                         addendum.view_key,
                         addendum.spend_key,
+                        addendum.minimum_balance,
                         &network,
                         Arc::clone(&wallet),
                         sweep_address_task.from_height,
-                        wallet_dir_path.clone(),
                     )
                     .await
                     .unwrap_or_else(|err| {
@@ -789,10 +754,6 @@ 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;
@@ -835,12 +796,8 @@ 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,
-                            wallet_dir,
-                        );
+                        let sweep_handle =
+                            sweep_polling(Arc::clone(&state), Arc::clone(&wallet_mutex), network);
 
                         let res = tokio::try_join!(
                             address_handle,
diff --git a/src/syncerd/opts.rs b/src/syncerd/opts.rs
index 419a0441..250bcf8c 100644
--- a/src/syncerd/opts.rs
+++ b/src/syncerd/opts.rs
@@ -65,10 +65,6 @@ 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)]
diff --git a/src/syncerd/syncer_state.rs b/src/syncerd/syncer_state.rs
index 3683b352..32604631 100644
--- a/src/syncerd/syncer_state.rs
+++ b/src/syncerd/syncer_state.rs
@@ -997,6 +997,7 @@ async fn syncer_state_sweep_addresses() {
                 "51qzspbPiQ9Z9Wq3hR8HRhPmVcE3URCK8b8A9ypHHzyvhigWTefCapoG1MXVZQQi7B5t4DpJYrHZyaFjHSb5QqLe8YEaBpo"
             )
             .unwrap(),
+            minimum_balance: monero::Amount::from_pico(1),
         }),
     };
     let source1 = ServiceId::Syncer(Coin::Monero, Network::Mainnet);
diff --git a/src/syncerd/types.rs b/src/syncerd/types.rs
index ad3d4b4d..b3283f53 100644
--- a/src/syncerd/types.rs
+++ b/src/syncerd/types.rs
@@ -93,6 +93,7 @@ pub struct SweepXmrAddress {
     pub spend_key: monero::PrivateKey,
     pub view_key: monero::PrivateKey,
     pub address: monero::Address,
+    pub minimum_balance: monero::Amount,
 }
 
 impl StrictEncode for SweepXmrAddress {
@@ -108,9 +109,14 @@ impl StrictEncode for SweepXmrAddress {
             .view_key
             .consensus_encode(&mut e)
             .map_err(|e| strict_encoding::Error::DataIntegrityError(e.to_string()))?;
+        len += self
+            .address
+            .consensus_encode(&mut e)
+            .map_err(|e| strict_encoding::Error::DataIntegrityError(e.to_string()))?;
         Ok(len
             + self
-                .address
+                .minimum_balance
+                .as_pico()
                 .consensus_encode(&mut e)
                 .map_err(|e| strict_encoding::Error::DataIntegrityError(e.to_string()))?)
     }
@@ -125,6 +131,10 @@ impl StrictDecode for SweepXmrAddress {
                 .map_err(|e| strict_encoding::Error::DataIntegrityError(e.to_string()))?,
             address: monero::Address::consensus_decode(&mut d)
                 .map_err(|e| strict_encoding::Error::DataIntegrityError(e.to_string()))?,
+            minimum_balance: monero::Amount::from_pico(
+                u64::consensus_decode(&mut d)
+                    .map_err(|e| strict_encoding::Error::DataIntegrityError(e.to_string()))?,
+            ),
         })
     }
 }
diff --git a/src/walletd/runtime.rs b/src/walletd/runtime.rs
index 57bd4aa6..08126254 100644
--- a/src/walletd/runtime.rs
+++ b/src/walletd/runtime.rs
@@ -1241,6 +1241,7 @@ impl Runtime {
                         view_key: view,
                         spend_key: spend,
                         address,
+                        minimum_balance: pub_offer.offer.accordant_amount,
                     };
                     senders.send_to(
                         ServiceBus::Ctl,
@@ -1324,6 +1325,7 @@ impl Runtime {
                         view_key: view,
                         spend_key: spend,
                         address,
+                        minimum_balance: pub_offer.offer.accordant_amount,
                     };
                     senders.send_to(
                         ServiceBus::Ctl,


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f286e2ae..9b31c6ca 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -122,7 +122,7 @@ jobs:
           key: test-${{ matrix.test }}
 
       - name: Cargo functional test
-        run: cargo test ${{ matrix.test }} --workspace --all-targets --all-features --no-fail-fast --verbose -- --ignored --nocapture --test-threads=1
+        run: cargo test ${{ matrix.test }} --workspace --all-targets --all-features --no-fail-fast --verbose -- --ignored --test-threads=1
         env:
           BITCOIN_COOKIE: /data/regtest/.cookie
           BITCOIN_HOST: bitcoin-core
diff --git a/tests/functional-swap.rs b/tests/functional-swap.rs
index d788befe..f02ae61a 100644
--- a/tests/functional-swap.rs
+++ b/tests/functional-swap.rs
@@ -1959,6 +1959,8 @@ 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,
@@ -1976,7 +1978,7 @@ async fn send_monero(
     wallet_lock.refresh(Some(1)).await.unwrap();
     let transaction = wallet_lock
         .transfer(
-            [(address, amount)].iter().cloned().collect(),
+            destination.clone(),
             monero_rpc::TransferPriority::Default,
             options.clone(),
         )
diff --git a/tests/functional.rs b/tests/functional.rs
index fbd5e4f5..be3d5b23 100644
--- a/tests/functional.rs
+++ b/tests/functional.rs
@@ -16,9 +16,11 @@ 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;
 
@@ -36,9 +38,6 @@ use farcaster_node::syncerd::types::{
     GetTx, Task, WatchAddress, WatchHeight, WatchTransaction,
 };
 
-#[macro_use]
-extern crate log;
-
 const SOURCE1: ServiceId = ServiceId::Syncer(Coin::Bitcoin, Network::Local);
 const SOURCE2: ServiceId = ServiceId::Syncer(Coin::Monero, Network::Local);
 
@@ -80,7 +79,6 @@ make_polling_test!(bitcoin_syncer_broadcast_tx_test);
 #[timeout(600000)]
 #[ignore]
 fn bitcoin_syncer_retrieve_transaction_test() {
-    setup_logging(None);
     let bitcoin_rpc = bitcoin_setup();
     let address = bitcoin_rpc.get_new_address(None, None).unwrap();
 
@@ -112,7 +110,7 @@ fn bitcoin_syncer_retrieve_transaction_test() {
     tx.send(task).unwrap();
     let message = rx_event.recv_multipart(0).unwrap();
     let request = get_request_from_message(message);
-    info!("received request: {:?}", request);
+    println!("received request: {:?}", request);
     assert_transaction_received(request, txid);
 }
 
@@ -177,7 +175,6 @@ We test for the following scenarios in the block height tests:
 - Mine another block and receive two HeightChanged events
 */
 fn bitcoin_syncer_block_height_test(polling: bool) {
-    setup_logging(None);
     let bitcoin_rpc = bitcoin_setup();
     let address = bitcoin_rpc.get_new_address(None, None).unwrap();
 
@@ -197,17 +194,17 @@ fn bitcoin_syncer_block_height_test(polling: bool) {
     tx.send(task).unwrap();
 
     // Receive the request and compare it to the actual block count
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     let blocks = bitcoin_rpc.get_block_count().unwrap();
     assert_received_height_changed(request, blocks);
     // Generate a single height changed event
     bitcoin_rpc.generate_to_address(1, &address).unwrap();
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     let blocks = bitcoin_rpc.get_block_count().unwrap();
     assert_received_height_changed(request, blocks);
@@ -227,15 +224,15 @@ fn bitcoin_syncer_block_height_test(polling: bool) {
 
     // generate another block - this should result in two height changed messages
     bitcoin_rpc.generate_to_address(1, &address).unwrap();
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     let blocks = bitcoin_rpc.get_block_count().unwrap();
     assert_received_height_changed(request, blocks);
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     let blocks = bitcoin_rpc.get_block_count().unwrap();
     assert_received_height_changed(request, blocks);
@@ -264,7 +261,6 @@ the minimum height
 
 */
 fn bitcoin_syncer_address_test(polling: bool) {
-    setup_logging(None);
     let bitcoin_rpc = bitcoin_setup();
 
     // generate some blocks to an address
@@ -323,17 +319,17 @@ fn bitcoin_syncer_address_test(polling: bool) {
     let txid = bitcoin_rpc
         .send_to_address(&address1, amount, None, None, None, None, None, None)
         .unwrap();
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     assert_address_transaction(request, amount.as_sat(), vec![txid.to_vec()]);
 
     // now generate a block for address1, then wait for the response and test it
     let block_hash = bitcoin_rpc.generate_to_address(1, &address1).unwrap();
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     let block = bitcoin_rpc.get_block(&block_hash[0]).unwrap();
     let address_transaction_amount = find_coinbase_transaction_amount(block.txdata.clone());
@@ -351,9 +347,9 @@ fn bitcoin_syncer_address_test(polling: bool) {
     let txid_2 = bitcoin_rpc
         .send_to_address(&address2, amount, None, None, None, None, None, None)
         .unwrap();
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     assert_address_transaction(
         request,
@@ -361,9 +357,9 @@ fn bitcoin_syncer_address_test(polling: bool) {
         vec![txid_1.to_vec(), txid_2.to_vec()],
     );
 
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     assert_address_transaction(
         request,
@@ -382,18 +378,18 @@ fn bitcoin_syncer_address_test(polling: bool) {
         source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_3).unwrap();
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     assert_address_transaction(
         request,
         amount.as_sat(),
         vec![txid_1.to_vec(), txid_2.to_vec()],
     );
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     assert_address_transaction(
         request,
@@ -424,9 +420,9 @@ fn bitcoin_syncer_address_test(polling: bool) {
         .unwrap();
 
     for _ in 0..5 {
-        info!("waiting for repeated address transaction message");
+        println!("waiting for repeated address transaction message");
         let message = rx_event.recv_multipart(0).unwrap();
-        info!("received repeated address transaction message");
+        println!("received repeated address transaction message");
         let request = get_request_from_message(message);
         assert_address_transaction(request, amount.as_sat(), vec![txid.to_vec()]);
     }
@@ -457,9 +453,9 @@ fn bitcoin_syncer_address_test(polling: bool) {
         .send_to_address(&address5, amount, None, None, None, None, None, None)
         .unwrap();
     bitcoin_rpc.generate_to_address(1, &address).unwrap();
-    info!("waiting for address transaction message");
+    println!("waiting for address transaction message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received address transaction message");
+    println!("received address transaction message");
     let request = get_request_from_message(message);
     assert_address_transaction(request, amount.as_sat(), vec![txid.to_vec()]);
 }
@@ -478,7 +474,6 @@ the threshold confs are reached
 - Submit two WatchTransaction tasks in parallel with the same recipient address, receive confirmation events for both
 */
 fn bitcoin_syncer_transaction_test(polling: bool) {
-    setup_logging(None);
     let bitcoin_rpc = bitcoin_setup();
 
     // generate some blocks to an address
@@ -515,9 +510,9 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
         source: SOURCE1.clone(),
     })
     .unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
 
@@ -532,27 +527,27 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
 
     let block_hash = bitcoin_rpc
         .generate_to_address(1, &reusable_address)
         .unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(1), block_hash[0].to_vec());
 
     bitcoin_rpc
         .generate_to_address(1, &reusable_address)
         .unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(2), block_hash[0].to_vec());
 
@@ -574,18 +569,18 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(1), block_hash[0].to_vec());
 
     bitcoin_rpc
         .generate_to_address(1, &reusable_address)
         .unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(2), block_hash[0].to_vec());
 
@@ -620,14 +615,14 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
 
@@ -675,20 +670,20 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, None, vec![0]);
 
-    info!("sending raw transaction");
+    println!("sending raw transaction");
     bitcoin_rpc
         .send_raw_transaction(&signed_tx.transaction().unwrap())
         .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
 }
@@ -708,7 +703,6 @@ We test for the following scenarios in the abort tests:
 #[timeout(300000)]
 #[ignore]
 fn bitcoin_syncer_abort_test() {
-    setup_logging(None);
     let (tx, rx_event) = create_bitcoin_syncer(true, "abort");
     let bitcoin_rpc = bitcoin_setup();
     let blocks = bitcoin_rpc.get_block_count().unwrap();
@@ -723,9 +717,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for confirmation");
+    println!("waiting for confirmation");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, None, vec![0]);
 
@@ -737,9 +731,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(request, None, vec![0]);
 
@@ -751,9 +745,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(
         request,
@@ -771,9 +765,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for confirmation");
+    println!("waiting for confirmation");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, None, vec![0]);
     let task = SyncerdTask {
@@ -786,9 +780,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for confirmation");
+    println!("waiting for confirmation");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, None, vec![0]);
     let task = SyncerdTask {
@@ -799,9 +793,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(request, None, vec![0, 1]);
 
@@ -813,9 +807,9 @@ fn bitcoin_syncer_abort_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(
         request,
@@ -832,7 +826,6 @@ We test the following scenarios in the broadcast tx tests:
 - Submit a BroadcastTransaction task, receive a success event
 */
 fn bitcoin_syncer_broadcast_tx_test(polling: bool) {
-    setup_logging(None);
     let bitcoin_rpc = bitcoin_setup();
     let address = bitcoin_rpc.get_new_address(None, None).unwrap();
 
@@ -863,9 +856,9 @@ fn bitcoin_syncer_broadcast_tx_test(polling: bool) {
     };
     tx.send(task).unwrap();
 
-    info!("waiting for transaction broadcasted message");
+    println!("waiting for transaction broadcasted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("transaction broadcasted");
+    println!("transaction broadcasted");
     let request = get_request_from_message(message);
     assert_transaction_broadcasted(request, true, None);
 
@@ -906,9 +899,9 @@ fn bitcoin_syncer_broadcast_tx_test(polling: bool) {
     };
     tx.send(task).unwrap();
 
-    info!("waiting for transaction broadcasted message");
+    println!("waiting for transaction broadcasted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("transaction broadcasted");
+    println!("transaction broadcasted");
     let request = get_request_from_message(message);
     assert_transaction_broadcasted(request, false, None);
 }
@@ -999,7 +992,6 @@ We test for the following scenarios in the block height tests:
 #[timeout(300000)]
 #[ignore]
 async fn monero_syncer_block_height_test() {
-    setup_logging(None);
     let (regtest, wallet) = setup_monero().await;
     let address = wallet.get_address(0, None).await.unwrap();
     let blocks = regtest.generate_blocks(1, address.address).await.unwrap();
@@ -1022,16 +1014,16 @@ async fn monero_syncer_block_height_test() {
     tx.send(task).unwrap();
 
     // Receive the request and compare it to the actual block count
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     assert_received_height_changed(request, blocks);
     // Generate a single height changed event
     let blocks = regtest.generate_blocks(1, address.address).await.unwrap();
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     // let blocks = regtest.get_block_count().await.unwrap();
     assert_received_height_changed(request, blocks);
@@ -1045,22 +1037,22 @@ async fn monero_syncer_block_height_test() {
         source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     assert_received_height_changed(request, blocks);
 
     // generate another block - this should result in two height changed messages
     let blocks = regtest.generate_blocks(1, address.address).await.unwrap();
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     assert_received_height_changed(request, blocks);
-    info!("waiting for height changed");
+    println!("waiting for height changed");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("height changed");
+    println!("height changed");
     let request = get_request_from_message(message);
     assert_received_height_changed(request, blocks);
 }
@@ -1069,7 +1061,6 @@ async fn monero_syncer_block_height_test() {
 #[timeout(300000)]
 #[ignore]
 async fn monero_syncer_sweep_test() {
-    setup_logging(None);
     let (regtest, wallet) = setup_monero().await;
     let address = wallet.get_address(0, None).await.unwrap();
     let blocks = regtest.generate_blocks(200, address.address).await.unwrap();
@@ -1104,6 +1095,7 @@ async fn monero_syncer_sweep_test() {
                 spend_key,
                 view_key,
                 address: dest_address,
+                minimum_balance: monero::Amount::from_pico(1000000000000),
             }),
         }),
         source: SOURCE2.clone(),
@@ -1112,9 +1104,9 @@ async fn monero_syncer_sweep_test() {
 
     regtest.generate_blocks(20, address.address).await.unwrap();
 
-    info!("waiting for sweep address message");
+    println!("waiting for sweep address message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received sweep success message");
+    println!("received sweep success message");
     let request = get_request_from_message(message);
     assert_sweep_success(request, TaskId(0));
 }
@@ -1142,210 +1134,364 @@ height
 #[tokio::test]
 #[timeout(600000)]
 #[ignore]
-async fn monero_syncer_address_test() {
-    for (socket_name, lws_bool) in [("address", false), ("lws_address", true)] {
-        if lws_bool {
-            setup_logging(Some(log::LevelFilter::Trace))
-        } else {
-            setup_logging(None)
-        };
-        info!(
-            "testing {}",
-            if lws_bool {
-                "monero-lws"
-            } else {
-                "monero-wallet-rpc"
-            }
-        );
-        let (regtest, wallet) = setup_monero().await;
-        let address = wallet.get_address(0, None).await.unwrap();
-        regtest.generate_blocks(200, address.address).await.unwrap();
-
-        // allow some time for things to happen, like the wallet server catching up
-        let duration = std::time::Duration::from_secs(20);
-        std::thread::sleep(duration);
-
-        // create a monero syncer
-        let (tx, rx_event) = create_monero_syncer(socket_name, lws_bool);
-
-        // Generate two addresses and watch them
-        let (address1, view_key1) = new_address(&wallet).await;
-        let tx_id = send_monero(&wallet, address1, 1).await;
-        let blocks = regtest.generate_blocks(10, address.address).await.unwrap();
-
-        let duration = std::time::Duration::from_secs(20);
-        std::thread::sleep(duration);
-
-        let addendum_1 = AddressAddendum::Monero(XmrAddressAddendum {
-            spend_key: address1.public_spend,
-            view_key: view_key1,
-            from_height: if lws_bool { 0 } else { 10 },
-        });
-        let watch_address_task_1 = SyncerdTask {
-            task: Task::WatchAddress(WatchAddress {
-                id: TaskId(1),
-                lifetime: blocks + 1,
-                addendum: addendum_1,
-                include_tx: Boolean::True,
-            }),
-            source: SOURCE1.clone(),
-        };
-        tx.send(watch_address_task_1).unwrap();
+async fn monero_syncer_address_lws_test() {
+    let (regtest, wallet) = setup_monero().await;
+    let address = wallet.get_address(0, None).await.unwrap();
+    let blocks = regtest.generate_blocks(200, address.address).await.unwrap();
 
-        info!(
-            "waiting for address transaction message for address {}",
-            address1
-        );
-        let message = rx_event.recv_multipart(0).unwrap();
-        info!(
-            "received address transaction message for address {}",
-            address1
-        );
-        let request = get_request_from_message(message);
-        assert_address_transaction(request, 1, vec![tx_id]);
-
-        // Generate two transactions for same address and watch them
-        let (address2, view_key2) = new_address(&wallet).await;
-        let tx_id2_1 = send_monero(&wallet, address2, 1).await;
-        let tx_id2_2 = send_monero(&wallet, address2, 1).await;
-        let blocks = if lws_bool {
-            regtest.generate_blocks(1, address.address).await.unwrap()
-        } else {
-            blocks
-        };
-
-        let addendum_2 = AddressAddendum::Monero(XmrAddressAddendum {
-            spend_key: address2.public_spend,
-            view_key: view_key2,
-            from_height: 0,
-        });
-        let watch_address_task_2 = SyncerdTask {
-            task: Task::WatchAddress(WatchAddress {
-                id: TaskId(1),
-                lifetime: blocks + 1,
-                addendum: addendum_2,
-                include_tx: Boolean::True,
-            }),
-            source: SOURCE1.clone(),
-        };
-        tx.send(watch_address_task_2).unwrap();
+    // allow some time for things to happen, like the wallet server catching up
+    let duration = std::time::Duration::from_secs(20);
+    std::thread::sleep(duration);
 
-        info!("waiting for address transaction message");
-        let message = rx_event.recv_multipart(0).unwrap();
-        info!("received address transaction message");
-        let request = get_request_from_message(message);
-        assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+    // create a monero syncer
+    let (tx, rx_event) = create_monero_syncer("lws_address", true);
 
-        info!("waiting for address transaction message");
-        let message = rx_event.recv_multipart(0).unwrap();
-        info!("received address transaction message");
-        let request = get_request_from_message(message);
-        assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
-
-        let addendum_3 = AddressAddendum::Monero(XmrAddressAddendum {
-            spend_key: address2.public_spend,
-            view_key: view_key2,
-            from_height: 0,
-        });
-        let watch_address_task_3 = SyncerdTask {
+    // Generate two addresses and watch them
+    let (address1, view_key1) = new_address(&wallet).await;
+    let tx_id = send_monero(&wallet, address1, 1).await;
+
+    let addendum_1 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address1.public_spend,
+        view_key: view_key1,
+        from_height: 10,
+    });
+    let watch_address_task_1 = SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(1),
+            lifetime: blocks + 1,
+            addendum: addendum_1,
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(watch_address_task_1).unwrap();
+
+    regtest.generate_blocks(1, address.address).await.unwrap();
+
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id]);
+
+    // Generate two transactions for same address and watch them
+    let (address2, view_key2) = new_address(&wallet).await;
+    let tx_id2_1 = send_monero(&wallet, address2, 1).await;
+    let tx_id2_2 = send_monero(&wallet, address2, 1).await;
+    let blocks = regtest.generate_blocks(1, address.address).await.unwrap();
+
+    let addendum_2 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address2.public_spend,
+        view_key: view_key2,
+        from_height: 0,
+    });
+    let watch_address_task_2 = SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(1),
+            lifetime: blocks + 1,
+            addendum: addendum_2,
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(watch_address_task_2).unwrap();
+
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+
+    let addendum_3 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address2.public_spend,
+        view_key: view_key2,
+        from_height: 0,
+    });
+    let watch_address_task_3 = SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(1),
+            lifetime: blocks + 1,
+            addendum: addendum_3,
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(watch_address_task_3).unwrap();
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+
+    let (address4, view_key4) = new_address(&wallet).await;
+
+    let tx_id4 = send_monero(&wallet, address4, 1).await;
+    let blocks = regtest.generate_blocks(1, address.address).await.unwrap();
+
+    let addendum_4 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address4.public_spend,
+        view_key: view_key4,
+        from_height: 0,
+    });
+    for i in 0..5 {
+        tx.send(SyncerdTask {
             task: Task::WatchAddress(WatchAddress {
-                id: TaskId(1),
-                lifetime: blocks + 1,
-                addendum: addendum_3,
+                id: TaskId(i),
+                lifetime: blocks + 5,
+                addendum: addendum_4.clone(),
                 include_tx: Boolean::True,
             }),
-            source: SOURCE1.clone(),
-        };
-        tx.send(watch_address_task_3).unwrap();
-        info!("waiting for address transaction message");
-        let message = rx_event.recv_multipart(0).unwrap();
-        info!("received address transaction message");
-        let request = get_request_from_message(message);
-        assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+            source: SOURCE2.clone(),
+        })
+        .unwrap();
+    }
 
-        info!("waiting for address transaction message");
+    for i in 0..5 {
+        println!("waiting for repeated address transaction message {}", i);
         let message = rx_event.recv_multipart(0).unwrap();
-        info!("received address transaction message");
+        println!("received repeated address transaction message {}", i);
         let request = get_request_from_message(message);
-        assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
-
-        let (address4, view_key4) = new_address(&wallet).await;
-
-        let tx_id4 = send_monero(&wallet, address4, 1).await;
-        let blocks = if lws_bool {
-            regtest.generate_blocks(1, address.address).await.unwrap()
-        } else {
-            blocks
-        };
-
-        let addendum_4 = AddressAddendum::Monero(XmrAddressAddendum {
-            spend_key: address4.public_spend,
-            view_key: view_key4,
-            from_height: 0,
-        });
-        for i in 0..5 {
-            tx.send(SyncerdTask {
-                task: Task::WatchAddress(WatchAddress {
-                    id: TaskId(i),
-                    lifetime: blocks + 5,
-                    addendum: addendum_4.clone(),
-                    include_tx: Boolean::True,
-                }),
-                source: SOURCE2.clone(),
-            })
-            .unwrap();
-        }
+        assert_address_transaction(request, 1, vec![tx_id4.clone()]);
+    }
 
-        for i in 0..5 {
-            info!("waiting for repeated address transaction message {}", i);
-            let message = rx_event.recv_multipart(0).unwrap();
-            info!("received repeated address transaction message {}", i);
-            let request = get_request_from_message(message);
-            assert_address_transaction(request, 1, vec![tx_id4.clone()]);
-        }
+    // generate an address, send Monero to it and ensure that the first transaction sent to it does not show up
+    let (address5, view_key5) = new_address(&wallet).await;
+    send_monero(&wallet, address5, 1).await;
+    // this transaction should not generate an event, because the task's lifetime expired
+    send_monero(&wallet, address4, 1).await;
+    let blocks = regtest.generate_blocks(10, address.address).await.unwrap();
+
+    let addendum_5 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address5.public_spend,
+        view_key: view_key5,
+        from_height: blocks,
+    });
+
+    tx.send(SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(5),
+            lifetime: blocks + 5,
+            addendum: addendum_5.clone(),
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE2.clone(),
+    })
+    .unwrap();
+
+    let tx_id5_2 = send_monero(&wallet, address5, 2).await;
+    regtest.generate_blocks(1, address.address).await.unwrap();
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 2, vec![tx_id5_2.clone()]);
+
+    let tx_id5_2_3 = send_monero(&wallet, address5, 2).await;
+    regtest.generate_blocks(1, address.address).await.unwrap();
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 2, vec![tx_id5_2_3.clone()]);
+}
+
+/*
+We test for the following scenarios in the address transaction tests:
+
+- Submit a WatchAddress task with an address with no history yet, then create a
+transaction for it and check the respective event
+
+- Submit a WatchAddress task with another address in parallel, then create two
+transactions for it and check for both respective events
+
+- Submit a WatchAddress task with the same address again, observe if it receives
+the complete existing transaction history
+
+- Submit a WatchAddress task many times with the same address, ensure we receive
+many times the same event
+
+- Submit a WatchAddress task with a from height to an address with existing
+transactions, ensure we receive only events for transactions after the from
+height
+
+*/
+#[tokio::test]
+#[timeout(300000)]
+#[ignore]
+async fn monero_syncer_address_test() {
+    let (regtest, wallet) = setup_monero().await;
+    let address = wallet.get_address(0, None).await.unwrap();
+    let blocks = regtest.generate_blocks(200, address.address).await.unwrap();
+
+    // allow some time for things to happen, like the wallet server catching up
+    let duration = std::time::Duration::from_secs(20);
+    std::thread::sleep(duration);
+
+    // create a monero syncer
+    let (tx, rx_event) = create_monero_syncer("address", false);
+
+    // Generate two addresses and watch them
+    let (address1, view_key1) = new_address(&wallet).await;
+    let tx_id = send_monero(&wallet, address1, 1).await;
+
+    let addendum_1 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address1.public_spend,
+        view_key: view_key1,
+        from_height: 10,
+    });
+    let watch_address_task_1 = SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(1),
+            lifetime: blocks + 1,
+            addendum: addendum_1,
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(watch_address_task_1).unwrap();
+
+    println!("\nwaiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id]);
+
+    // Generate two addresses and watch them
+    let (address2, view_key2) = new_address(&wallet).await;
+    let tx_id2_1 = send_monero(&wallet, address2, 1).await;
+    let tx_id2_2 = send_monero(&wallet, address2, 1).await;
+
+    let addendum_2 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address2.public_spend,
+        view_key: view_key2,
+        from_height: 0,
+    });
+    let watch_address_task_2 = SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(1),
+            lifetime: blocks + 1,
+            addendum: addendum_2,
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(watch_address_task_2).unwrap();
 
-        // generate an address, send Monero to it and ensure that the first transaction sent to it does not show up
-        let (address5, view_key5) = new_address(&wallet).await;
-        send_monero(&wallet, address5, 1).await;
-        // this transaction should not generate an event, because the task's lifetime expired
-        send_monero(&wallet, address4, 1).await;
-        let blocks = regtest.generate_blocks(10, address.address).await.unwrap();
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
 
-        let addendum_5 = AddressAddendum::Monero(XmrAddressAddendum {
-            spend_key: address5.public_spend,
-            view_key: view_key5,
-            from_height: blocks,
-        });
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
 
+    let addendum_3 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address2.public_spend,
+        view_key: view_key2,
+        from_height: 0,
+    });
+    let watch_address_task_3 = SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(1),
+            lifetime: blocks + 1,
+            addendum: addendum_3,
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(watch_address_task_3).unwrap();
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 1, vec![tx_id2_1.clone(), tx_id2_2.clone()]);
+
+    let (address4, view_key4) = new_address(&wallet).await;
+    let tx_id4 = send_monero(&wallet, address4, 1).await;
+
+    let addendum_4 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address4.public_spend,
+        view_key: view_key4,
+        from_height: 0,
+    });
+    for i in 0..5 {
         tx.send(SyncerdTask {
             task: Task::WatchAddress(WatchAddress {
-                id: TaskId(5),
+                id: TaskId(i),
                 lifetime: blocks + 5,
-                addendum: addendum_5.clone(),
+                addendum: addendum_4.clone(),
                 include_tx: Boolean::True,
             }),
             source: SOURCE2.clone(),
         })
         .unwrap();
+    }
 
-        let tx_id5_2 = send_monero(&wallet, address5, 2).await;
-        if lws_bool {
-            regtest.generate_blocks(1, address.address).await.unwrap();
-        }
-        info!("waiting for address transaction message");
-        let message = rx_event.recv_multipart(0).unwrap();
-        info!("received address transaction message");
-        let request = get_request_from_message(message);
-        assert_address_transaction(request, 2, vec![tx_id5_2.clone()]);
-
-        let tx_id5_2_3 = send_monero(&wallet, address5, 2).await;
-        regtest.generate_blocks(1, address.address).await.unwrap();
-        info!("waiting for address transaction message");
+    for _ in 0..5 {
+        println!("waiting for repeated address transaction message");
         let message = rx_event.recv_multipart(0).unwrap();
-        info!("received address transaction message");
+        println!("received repeated address transaction message");
         let request = get_request_from_message(message);
-        assert_address_transaction(request, 2, vec![tx_id5_2_3.clone()]);
+        assert_address_transaction(request, 1, vec![tx_id4.clone()]);
     }
+
+    let (address5, view_key5) = new_address(&wallet).await;
+    send_monero(&wallet, address5, 1).await;
+    let blocks = regtest.generate_blocks(5, address.address).await.unwrap();
+
+    let addendum_5 = AddressAddendum::Monero(XmrAddressAddendum {
+        spend_key: address5.public_spend,
+        view_key: view_key5,
+        from_height: blocks,
+    });
+
+    tx.send(SyncerdTask {
+        task: Task::WatchAddress(WatchAddress {
+            id: TaskId(5),
+            lifetime: blocks + 5,
+            addendum: addendum_5.clone(),
+            include_tx: Boolean::True,
+        }),
+        source: SOURCE2.clone(),
+    })
+    .unwrap();
+
+    let tx_id5_2 = send_monero(&wallet, address5, 2).await;
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 2, vec![tx_id5_2.clone()]);
+
+    let tx_id5_2_3 = send_monero(&wallet, address5, 2).await;
+    regtest.generate_blocks(1, address.address).await.unwrap();
+    println!("waiting for address transaction message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received address transaction message");
+    let request = get_request_from_message(message);
+    assert_address_transaction(request, 2, vec![tx_id5_2_3.clone()]);
 }
 
 /*
@@ -1365,7 +1511,6 @@ found confirmation event. Then relay and receive further events.
 #[timeout(300000)]
 #[ignore]
 async fn monero_syncer_transaction_test() {
-    setup_logging(None);
     let (regtest, wallet) = setup_monero().await;
     let address = wallet.get_address(0, None).await.unwrap().address;
     let blocks = regtest.generate_blocks(200, address).await.unwrap();
@@ -1390,24 +1535,24 @@ async fn monero_syncer_transaction_test() {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
 
     let block_height = regtest.generate_blocks(1, address).await.unwrap();
     let block_hash = get_block_hash_from_height(&regtest, block_height).await;
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(1), block_hash.clone());
 
     regtest.generate_blocks(1, address).await.unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(2), block_hash);
 
@@ -1424,16 +1569,16 @@ async fn monero_syncer_transaction_test() {
         source: SOURCE1.clone(),
     })
     .unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(1), block_hash.clone());
 
     regtest.generate_blocks(1, address).await.unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(2), block_hash);
 
@@ -1461,17 +1606,19 @@ async fn monero_syncer_transaction_test() {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     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,
@@ -1483,10 +1630,7 @@ async fn monero_syncer_transaction_test() {
     };
     let transaction = wallet
         .transfer(
-            [(address, monero::Amount::from_pico(1000))]
-                .iter()
-                .cloned()
-                .collect(),
+            destination.clone(),
             monero_rpc::TransferPriority::Default,
             options.clone(),
         )
@@ -1503,9 +1647,9 @@ async fn monero_syncer_transaction_test() {
     })
     .unwrap();
 
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, None, vec![0]);
 
@@ -1513,16 +1657,16 @@ async fn monero_syncer_transaction_test() {
         .relay_tx(hex::encode(transaction.tx_metadata.0))
         .await
         .unwrap();
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(0), vec![0]);
     let block_height = regtest.generate_blocks(1, address).await.unwrap();
     let block_hash = get_block_hash_from_height(&regtest, block_height).await;
-    info!("awaiting confirmations");
+    println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("received confirmation");
+    println!("received confirmation");
     let request = get_request_from_message(message);
     assert_transaction_confirmations(request, Some(1), block_hash);
 }
@@ -1542,7 +1686,6 @@ We test for the following scenarios in the abort tests:
 #[timeout(300000)]
 #[ignore]
 async fn monero_syncer_abort_test() {
-    setup_logging(None);
     let (tx, rx_event) = create_monero_syncer("abort", false);
     let (regtest, wallet) = setup_monero().await;
     let address = wallet.get_address(0, None).await.unwrap();
@@ -1570,9 +1713,9 @@ async fn monero_syncer_abort_test() {
         source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(request, None, vec![0]);
 
@@ -1584,9 +1727,9 @@ async fn monero_syncer_abort_test() {
         source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(
         request,
@@ -1628,9 +1771,9 @@ async fn monero_syncer_abort_test() {
         source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(request, None, vec![0, 1]);
 
@@ -1642,9 +1785,9 @@ async fn monero_syncer_abort_test() {
         source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
-    info!("waiting for task aborted message");
+    println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("task aborted");
+    println!("task aborted");
     let request = get_request_from_message(message);
     assert_task_aborted(
         request,
@@ -1660,7 +1803,6 @@ Check that a monero BroadcastTransaction task generates an error
 #[timeout(300000)]
 #[ignore]
 async fn monero_syncer_broadcast_tx_test() {
-    setup_logging(None);
     let (regtest, wallet) = setup_monero().await;
     let address = wallet.get_address(0, None).await.unwrap();
     regtest.generate_blocks(1, address.address).await.unwrap();
@@ -1676,9 +1818,9 @@ async fn monero_syncer_broadcast_tx_test() {
     };
     tx.send(task).unwrap();
 
-    info!("waiting for transaction broadcasted message");
+    println!("waiting for transaction broadcasted message");
     let message = rx_event.recv_multipart(0).unwrap();
-    info!("transaction broadcasted");
+    println!("transaction broadcasted");
     let request = get_request_from_message(message);
     assert_transaction_broadcasted(
         request,
@@ -1782,6 +1924,8 @@ 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,
@@ -1793,10 +1937,7 @@ async fn send_monero(
     };
     let transaction = wallet
         .transfer(
-            [(address, monero::Amount::from_pico(amount))]
-                .iter()
-                .cloned()
-                .collect(),
+            destination.clone(),
             monero_rpc::TransferPriority::Default,
             options.clone(),
         )
@@ -1959,10 +2100,3 @@ fn assert_transaction_broadcasted(request: Request, has_error: bool, error_msg:
         }
     }
 }
-
-fn setup_logging(level: Option<log::LevelFilter>) {
-    let _ = env_logger::builder()
-        .is_test(true)
-        .filter_level(level.unwrap_or(log::LevelFilter::Info))
-        .try_init();
-}