Repository: farcaster-project/farcaster-node
Pull request #395 (2021-12-20 14:04:43)

Fee estimation
Based on #388

@zkao , I am loosing quite some precision with the amount parsing. The reason I'm using the bitcoin amount type is that sending a float in the emitted Event is kind of hard to get right, since we can't hash floats. Do you want a string instead, that can then be parsed into a float?

codecov-commenter: # [Codecov](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#395](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (1da3c04) into [main](https://codecov.io/gh/farcaster-project/farcaster-node/commit/ae1a682a1c047f18d025d159d18a7e7ef973dd55?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (ae1a682) will **decrease** coverage by `0.2%`.
> The diff coverage is `0.0%`.

[![Impacted file tree graph](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395/graphs/tree.svg?width=650&height=150&src=pr&token=ZZ9RnFhgFC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@           Coverage Diff           @@
##            main    #395     +/-   ##
=======================================
- Coverage   13.6%   13.4%   -0.2%     
=======================================
  Files         27      27             
  Lines       7558    7648     +90     
=======================================
  Hits        1028    1028             
- Misses      6530    6620     +90     
```


| [Impacted Files](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [src/swapd/runtime.rs](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3N3YXBkL3J1bnRpbWUucnM=) | `0.0% <0.0%> (ø)` | |
| [src/syncerd/bitcoin\_syncer.rs](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3N5bmNlcmQvYml0Y29pbl9zeW5jZXIucnM=) | `0.0% <0.0%> (ø)` | |
| [src/syncerd/monero\_syncer.rs](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3N5bmNlcmQvbW9uZXJvX3N5bmNlci5ycw==) | `0.0% <0.0%> (ø)` | |
| [src/syncerd/types.rs](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3N5bmNlcmQvdHlwZXMucnM=) | `25.8% <0.0%> (-1.5%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [ae1a682...1da3c04](https://codecov.io/gh/farcaster-project/farcaster-node/pull/395?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

zkao: @TheCharlatan we need at least 2 decimals (tx sizes on the order of >100vB, thus 0.01 sat/vB * 100 vB = 1 sat) 
zkao: there was a bug in core that forced using sats for fees, though

but also theres some microsats unit or some other small unit, in rust-bitcoin
TheCharlatan: Funny that you mention, because I just saw that in rust-bitcoin. Would require us to upgrade farcaster-core first though :/
TheCharlatan: Actually, I think the precission is higher than you state here, since it is in kB.
h4sh3d: Core uses rust Bitcoin amount which can’t store less than a sat internally.
TheCharlatan: I think core should eventually use sat/kVByte as well.
zkao: that would work too, as we have more than enough precision

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐
On Monday, December 20th, 2021 at 8:34 PM, Reproducibility Matters ***@***.***> wrote:

> I think core should eventually use sat/kVByte as well.
>
> —
> Reply to this email directly, [view it on GitHub](https://github.com/farcaster-project/farcaster-node/pull/395#issuecomment-998213589), or [unsubscribe](https://github.com/notifications/unsubscribe-auth/AIQJTBMODPGZEPWTJ625LODUR6ANZANCNFSM5KNY6ZFA).
> Triage notifications on the go with GitHub Mobile for [iOS](https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675) or [Android](https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub).
> You are receiving this because you were mentioned.Message ID: ***@***.***>
zkao: although i find that unit a bit counter intuitive, because txs are smaller than 1kB

Patch:
diff --git a/src/swapd/runtime.rs b/src/swapd/runtime.rs
index df52ec7a..b5a6f1ce 100644
--- a/src/swapd/runtime.rs
+++ b/src/swapd/runtime.rs
@@ -2413,6 +2413,9 @@ impl Runtime {
                     Event::TransactionRetrieved(event) => {
                         debug!("{}", event)
                     }
+                    Event::FeeEstimation(event) => {
+                        debug!("{}", event)
+                    }
                 }
             }
             Request::Protocol(Msg::CoreArbitratingSetup(core_arb_setup)) if self.state.reveal() => {
diff --git a/src/syncerd/bitcoin_syncer.rs b/src/syncerd/bitcoin_syncer.rs
index b51adbb8..18a0c3cd 100644
--- a/src/syncerd/bitcoin_syncer.rs
+++ b/src/syncerd/bitcoin_syncer.rs
@@ -11,7 +11,9 @@ use crate::syncerd::syncer_state::WatchedTransaction;
 use crate::syncerd::types::{AddressAddendum, Boolean, Task};
 use crate::syncerd::BroadcastTransaction;
 use crate::syncerd::BtcAddressAddendum;
+use crate::syncerd::EstimateFee;
 use crate::syncerd::Event;
+use crate::syncerd::FeeEstimation;
 use crate::syncerd::GetTx;
 use crate::syncerd::TaskTarget;
 use crate::syncerd::TransactionBroadcasted;
@@ -261,7 +263,7 @@ impl ElectrumRpc {
                     {
                         Ok(history) => history,
                         Err(err) => {
-                            trace!(
+                            debug!(
                                 "error getting script history, treating as not found: {}",
                                 err
                             );
@@ -356,7 +358,7 @@ impl ElectrumRpc {
                     drop(state_guard);
                 }
                 Err(err) => {
-                    trace!("error getting transaction, treating as not found: {}", err);
+                    debug!("error getting transaction, treating as not found: {}", err);
                     let mut state_guard = state.lock().await;
                     state_guard
                         .change_transaction(tx_id.to_vec(), None, None, vec![])
@@ -397,7 +399,7 @@ fn query_addr_history(
             }
         }
         if !output_found {
-            trace!("ignoring outgoing transaction in handle address notification, continuing");
+            debug!("ignoring outgoing transaction in handle address notification, continuing");
             continue;
         }
         addr_txs.push(AddressTx {
@@ -439,6 +441,7 @@ async fn run_syncerd_task_receiver(
     state: Arc<Mutex<SyncerState>>,
     transaction_broadcast_tx: TokioSender<(BroadcastTransaction, ServiceId)>,
     transaction_get_tx: TokioSender<(GetTx, ServiceId)>,
+    estimate_fee_tx: TokioSender<(EstimateFee, ServiceId)>,
 ) {
     let task_receiver = Arc::new(Mutex::new(receive_task_channel));
     tokio::spawn(async move {
@@ -456,6 +459,12 @@ async fn run_syncerd_task_receiver(
                                 .await
                                 .expect("failed on transaction_get sender");
                         }
+                        Task::EstimateFee(task) => {
+                            estimate_fee_tx
+                                .send((task, syncerd_task.source))
+                                .await
+                                .expect("failed on estimate fee sender");
+                        }
                         Task::SweepAddress(_) => {
                             error!("sweep address not implemented for bitcoin syncer");
                         }
@@ -734,6 +743,49 @@ fn transaction_broadcasting(
     })
 }
 
+fn estimate_fee(
+    electrum_server: String,
+    mut estimate_fee_rx: TokioReceiver<(EstimateFee, ServiceId)>,
+    tx_event: TokioSender<SyncerdBridgeEvent>,
+) -> tokio::task::JoinHandle<()> {
+    tokio::task::spawn(async move {
+        while let Some((estimate_fee_task, source)) = estimate_fee_rx.recv().await {
+            match Client::new(&electrum_server).and_then(|estimate_fee_client| {
+                estimate_fee_client.estimate_fee(estimate_fee_task.blocks_until_confirmation)
+            }) {
+                Ok(fee) => {
+                    let fee = format!("{:.8} BTC", fee);
+                    let amount = bitcoin::Amount::from_str_with_denomination(&fee).ok();
+                    tx_event
+                        .send(SyncerdBridgeEvent {
+                            event: Event::FeeEstimation(FeeEstimation {
+                                id: estimate_fee_task.id,
+                                btc_per_kbyte: amount,
+                            }),
+                            source,
+                        })
+                        .await
+                        .expect("error sending fee estimation event");
+                    debug!("Successfully sent fee estimation");
+                }
+                Err(e) => {
+                    println!("failed to retrieve fee estimation: {}", e.err());
+                    tx_event
+                        .send(SyncerdBridgeEvent {
+                            event: Event::FeeEstimation(FeeEstimation {
+                                id: estimate_fee_task.id,
+                                btc_per_kbyte: None,
+                            }),
+                            source,
+                        })
+                        .await
+                        .expect("error sending fee estimation event");
+                }
+            }
+        }
+    })
+}
+
 fn transaction_fetcher(
     electrum_server: String,
     mut transaction_get_rx: TokioReceiver<(GetTx, ServiceId)>,
@@ -811,6 +863,10 @@ impl Synclet for BitcoinSyncer {
                         TokioSender<(GetTx, ServiceId)>,
                         TokioReceiver<(GetTx, ServiceId)>,
                     ) = tokio::sync::mpsc::channel(200);
+                    let (estimate_fee_tx, estimate_fee_rx): (
+                        TokioSender<(EstimateFee, ServiceId)>,
+                        TokioReceiver<(EstimateFee, ServiceId)>,
+                    ) = tokio::sync::mpsc::channel(200);
                     let state = Arc::new(Mutex::new(SyncerState::new(
                         event_tx.clone(),
                         Coin::Bitcoin,
@@ -821,6 +877,7 @@ impl Synclet for BitcoinSyncer {
                         Arc::clone(&state),
                         transaction_broadcast_tx,
                         transaction_get_tx,
+                        estimate_fee_tx,
                     )
                     .await;
                     run_syncerd_bridge_event_sender(tx, event_rx, syncer_address).await;
@@ -843,8 +900,14 @@ impl Synclet for BitcoinSyncer {
                         event_tx.clone(),
                     );
 
-                    let transaction_get_handle =
-                        transaction_fetcher(electrum_server, transaction_get_rx, event_tx.clone());
+                    let transaction_get_handle = transaction_fetcher(
+                        electrum_server.clone(),
+                        transaction_get_rx,
+                        event_tx.clone(),
+                    );
+
+                    let estimate_fee_handle =
+                        estimate_fee(electrum_server, estimate_fee_rx, event_tx.clone());
 
                     let res = tokio::try_join!(
                         address_handle,
@@ -852,6 +915,7 @@ impl Synclet for BitcoinSyncer {
                         unseen_transaction_handle,
                         transaction_broadcast_handle,
                         transaction_get_handle,
+                        estimate_fee_handle,
                     );
                     debug!("exiting bitcoin synclet run routine with: {:?}", res);
                 });
diff --git a/src/syncerd/monero_syncer.rs b/src/syncerd/monero_syncer.rs
index b68e43f3..5ea85194 100644
--- a/src/syncerd/monero_syncer.rs
+++ b/src/syncerd/monero_syncer.rs
@@ -166,7 +166,7 @@ impl MoneroRpc {
             .await
         {
             Err(err) => {
-                debug!("wallet doesn't exist, generating a new wallet: {:?}", err);
+                warn!("wallet doesn't exist, generating a new wallet: {:?}", err);
                 wallet
                     .generate_from_keys(GenerateFromKeysArgs {
                         restore_height: Some(address_addendum.from_height),
@@ -248,7 +248,7 @@ async fn sweep_address(
         .open_wallet(wallet_filename.clone(), Some(password.clone()))
         .await
     {
-        debug!(
+        warn!(
             "error opening to be sweeped wallet: {:?}, falling back to generating a new wallet",
             err,
         );
@@ -344,6 +344,9 @@ async fn run_syncerd_task_receiver(
                         Task::GetTx(_) => {
                             error!("get tx not implemented for monero syncer");
                         }
+                        Task::EstimateFee(_) => {
+                            error!("estimate fee not implemented for monero syncer");
+                        }
                         Task::SweepAddress(task) => match task.addendum.clone() {
                             SweepAddressAddendum::Monero(sweep) => {
                                 let addr = sweep.address;
diff --git a/src/syncerd/types.rs b/src/syncerd/types.rs
index c3f9ec9d..ad3d4b4d 100644
--- a/src/syncerd/types.rs
+++ b/src/syncerd/types.rs
@@ -204,6 +204,13 @@ pub struct GetTx {
     pub hash: Vec<u8>,
 }
 
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub struct EstimateFee {
+    pub id: TaskId,
+    pub blocks_until_confirmation: usize,
+}
+
 /// Tasks created by the daemon and handle by syncers to process a blockchain
 /// and generate [`Event`] back to the syncer.
 #[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
@@ -216,6 +223,7 @@ pub enum Task {
     BroadcastTransaction(BroadcastTransaction),
     SweepAddress(SweepAddress),
     GetTx(GetTx),
+    EstimateFee(EstimateFee),
 }
 
 #[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
@@ -277,6 +285,13 @@ pub struct TransactionRetrieved {
     pub tx: Option<bitcoin::Transaction>,
 }
 
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub struct FeeEstimation {
+    pub id: TaskId,
+    pub btc_per_kbyte: Option<bitcoin::Amount>,
+}
+
 /// Events returned by syncers to the daemon to update the blockchain states.
 /// Events are identified with a unique 32-bits integer that match the [`Task`]
 /// id.
@@ -293,4 +308,5 @@ pub enum Event {
     /// Carries the status for the task abortion.
     TaskAborted(TaskAborted),
     TransactionRetrieved(TransactionRetrieved),
+    FeeEstimation(FeeEstimation),
 }


Test patch:
diff --git a/tests/functional-swap.rs b/tests/functional-swap.rs
index f02ae61a..0b841f1a 100644
--- a/tests/functional-swap.rs
+++ b/tests/functional-swap.rs
@@ -24,7 +24,7 @@ use std::str::FromStr;
 
 use ntest::timeout;
 
-const ALLOWED_RETRIES: u32 = 180;
+const ALLOWED_RETRIES: u32 = 120;
 
 #[tokio::test]
 #[timeout(600000)]
@@ -250,18 +250,10 @@ async fn swap_alice_maker() {
     cleanup_processes(vec![farcasterd_maker, farcasterd_taker]);
 }
 
-#[derive(Debug, Clone)]
-struct SwapParams {
-    data_dir_bob: Vec<String>,
-    data_dir_alice: Vec<String>,
-    xmr_dest_wallet_name: String,
-    destination_btc_address: bitcoin::Address,
-}
-
 #[tokio::test]
-#[timeout(800000)]
+#[timeout(600000)]
 #[ignore]
-async fn swap_parallel_execution() {
+async fn swap_parallel_offer_creation_serial_execution() {
     let execution_mutex = Arc::new(Mutex::new(0));
     let bitcoin_rpc = Arc::new(bitcoin_setup());
     let (monero_regtest, monero_wallet) = monero_setup().await;
@@ -269,83 +261,100 @@ async fn swap_parallel_execution() {
     let (farcasterd_maker, data_dir_maker, farcasterd_taker, data_dir_taker) =
         setup_farcaster_clients().await;
 
-    let previous_offers: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
-    let previous_swap_ids: Arc<Mutex<HashSet<SwapId>>> = Arc::new(Mutex::new(HashSet::new()));
-
-    let mut res = Vec::new();
-    for i in 0..5 {
-        let xmr_amount = format!("1.{} XMR", i);
-        res.push(make_and_take_offer_parallel(
-            data_dir_maker.clone(),
-            data_dir_taker.clone(),
-            "Bob".to_string(),
-            Arc::clone(&bitcoin_rpc),
-            Arc::clone(&monero_wallet),
-            bitcoin::Amount::from_str("1 BTC").unwrap(),
-            monero::Amount::from_str_with_denomination(&xmr_amount).unwrap(),
-            Arc::clone(&previous_offers),
-            Arc::clone(&previous_swap_ids),
-        ));
-    }
+    let (xmr_dest_wallet_name, bitcoin_address, swap_id) = make_and_take_offer(
+        data_dir_maker.clone(),
+        data_dir_taker.clone(),
+        "Alice".to_string(),
+        Arc::clone(&bitcoin_rpc),
+        Arc::clone(&monero_wallet),
+        bitcoin::Amount::from_str("1 BTC").unwrap(),
+        monero::Amount::from_str_with_denomination("1 XMR").unwrap(),
+    )
+    .await;
 
-    let mut results = join_all(res).await;
-    let mut swap_info: HashMap<SwapId, SwapParams> = results
-        .drain(..)
-        .map(|(xmr_dest_wallet_name, destination_btc_address, swap_id)| {
-            (
-                swap_id,
-                SwapParams {
-                    data_dir_bob: data_dir_maker.clone(),
-                    data_dir_alice: data_dir_taker.clone(),
-                    xmr_dest_wallet_name,
-                    destination_btc_address,
-                },
-            )
-        })
-        .collect();
+    let alice_future = run_swap(
+        swap_id,
+        data_dir_maker.clone(),
+        data_dir_taker.clone(),
+        Arc::clone(&bitcoin_rpc),
+        bitcoin_address.clone(),
+        monero_regtest.clone(),
+        Arc::clone(&monero_wallet),
+        xmr_dest_wallet_name,
+        Arc::clone(&execution_mutex),
+    );
 
-    let mut res = Vec::new();
-    for i in 0..5 {
-        let xmr_amount = format!("1.{} XMR", i);
-        res.push(make_and_take_offer_parallel(
-            data_dir_maker.clone(),
-            data_dir_taker.clone(),
-            "Alice".to_string(),
-            Arc::clone(&bitcoin_rpc),
-            Arc::clone(&monero_wallet),
-            bitcoin::Amount::from_str("1 BTC").unwrap(),
-            monero::Amount::from_str_with_denomination(&xmr_amount).unwrap(),
-            Arc::clone(&previous_offers),
-            Arc::clone(&previous_swap_ids),
-        ));
-    }
+    let (xmr_dest_wallet_name, bitcoin_address, swap_id) = make_and_take_offer(
+        data_dir_maker.clone(),
+        data_dir_taker.clone(),
+        "Alice".to_string(),
+        Arc::clone(&bitcoin_rpc),
+        Arc::clone(&monero_wallet),
+        bitcoin::Amount::from_str("1 BTC").unwrap(),
+        monero::Amount::from_str_with_denomination("0.999999999999 XMR").unwrap(),
+    )
+    .await;
 
-    let mut results = join_all(res).await;
-    let swap_info_alice: HashMap<SwapId, SwapParams> = results
-        .drain(..)
-        .map(|(xmr_dest_wallet_name, destination_btc_address, swap_id)| {
-            (
-                swap_id,
-                SwapParams {
-                    data_dir_bob: data_dir_taker.clone(),
-                    data_dir_alice: data_dir_maker.clone(),
-                    xmr_dest_wallet_name,
-                    destination_btc_address,
-                },
-            )
-        })
-        .collect();
+    let alice_future_1 = run_swap(
+        swap_id,
+        data_dir_maker.clone(),
+        data_dir_taker.clone(),
+        Arc::clone(&bitcoin_rpc),
+        bitcoin_address.clone(),
+        monero_regtest.clone(),
+        Arc::clone(&monero_wallet),
+        xmr_dest_wallet_name,
+        Arc::clone(&execution_mutex),
+    );
 
-    swap_info.extend(swap_info_alice);
+    let (xmr_dest_wallet_name, bitcoin_address, swap_id) = make_and_take_offer(
+        data_dir_maker.clone(),
+        data_dir_taker.clone(),
+        "Bob".to_string(),
+        Arc::clone(&bitcoin_rpc),
+        Arc::clone(&monero_wallet),
+        bitcoin::Amount::from_str("1 BTC").unwrap(),
+        monero::Amount::from_str_with_denomination("1 XMR").unwrap(),
+    )
+    .await;
 
-    run_swaps_parallel(
-        swap_info,
+    let bob_future = run_swap(
+        swap_id,
+        data_dir_taker.clone(),
+        data_dir_maker.clone(),
         Arc::clone(&bitcoin_rpc),
+        bitcoin_address,
         monero_regtest.clone(),
         Arc::clone(&monero_wallet.clone()),
+        xmr_dest_wallet_name,
         Arc::clone(&execution_mutex),
+    );
+
+    let (xmr_dest_wallet_name, bitcoin_address, swap_id) = make_and_take_offer(
+        data_dir_maker.clone(),
+        data_dir_taker.clone(),
+        "Bob".to_string(),
+        Arc::clone(&bitcoin_rpc),
+        Arc::clone(&monero_wallet),
+        bitcoin::Amount::from_str("1 BTC").unwrap(),
+        monero::Amount::from_str_with_denomination("0.999999999999 XMR").unwrap(),
     )
     .await;
+
+    let bob_future_1 = run_swap(
+        swap_id,
+        data_dir_taker.clone(),
+        data_dir_maker.clone(),
+        Arc::clone(&bitcoin_rpc),
+        bitcoin_address,
+        monero_regtest.clone(),
+        Arc::clone(&monero_wallet.clone()),
+        xmr_dest_wallet_name,
+        Arc::clone(&execution_mutex),
+    );
+
+    join_all(vec![alice_future, alice_future_1, bob_future, bob_future_1]).await;
+
     cleanup_processes(vec![farcasterd_maker, farcasterd_taker]);
 }
 
@@ -414,26 +423,14 @@ async fn run_refund_swap_race_cancel(
         .send_to_address(&address, amount, None, None, None, None, None, None)
         .unwrap();
 
-    println!("waiting for AliceState(RefundSigs");
-    retry_until_finish_state_transition(
-        cli_alice_progress_args.clone(),
-        "AliceState(RefundSigs".to_string(),
-    )
-    .await;
-
     // run until BobState(CoreArb) is received
-    println!("waiting for BobState(CoreArb)");
     retry_until_finish_state_transition(
         cli_bob_progress_args.clone(),
         "BobState(CoreArb)".to_string(),
     )
     .await;
 
-    // run until the funding infos are cleared again
-    println!("waiting for the bitcoin funding info to clear");
-    retry_until_funding_info_cleared(swap_id.clone(), cli_bob_needs_funding_args.clone()).await;
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
+    tokio::time::sleep(time::Duration::from_secs(20)).await;
 
     // generate some bitcoin blocks to finalize the bitcoin arb lock tx
     bitcoin_rpc
@@ -548,8 +545,6 @@ async fn run_refund_swap_kill_alice_after_funding(
     alice_farcasterd: std::process::Child,
 ) {
     let cli_bob_progress_args: Vec<String> = progress_args(data_dir_bob.clone(), swap_id.clone());
-    let cli_alice_progress_args: Vec<String> =
-        progress_args(data_dir_alice.clone(), swap_id.clone());
     let cli_bob_needs_funding_args: Vec<String> =
         needs_funding_args(data_dir_bob, "bitcoin".to_string());
     let cli_alice_needs_funding_args: Vec<String> =
@@ -570,26 +565,14 @@ async fn run_refund_swap_kill_alice_after_funding(
         .send_to_address(&address, amount, None, None, None, None, None, None)
         .unwrap();
 
-    println!("waiting for AliceState(RefundSigs");
-    retry_until_finish_state_transition(
-        cli_alice_progress_args.clone(),
-        "AliceState(RefundSigs".to_string(),
-    )
-    .await;
-
     // run until BobState(CoreArb) is received
-    println!("waiting for BobState(CoreArb)");
     retry_until_finish_state_transition(
         cli_bob_progress_args.clone(),
         "BobState(CoreArb)".to_string(),
     )
     .await;
 
-    // run until the funding infos are cleared again
-    println!("waiting for the bitcoin funding info to clear");
-    retry_until_funding_info_cleared(swap_id.clone(), cli_bob_needs_funding_args.clone()).await;
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
+    tokio::time::sleep(time::Duration::from_secs(20)).await;
 
     // generate some bitcoin blocks to finalize the bitcoin arb lock tx
     bitcoin_rpc
@@ -683,26 +666,14 @@ async fn run_refund_swap_alice_does_not_fund(
         .send_to_address(&address, amount, None, None, None, None, None, None)
         .unwrap();
 
-    println!("waiting for AliceState(RefundSigs");
-    retry_until_finish_state_transition(
-        cli_alice_progress_args.clone(),
-        "AliceState(RefundSigs".to_string(),
-    )
-    .await;
-
     // run until BobState(CoreArb) is received
-    println!("waiting for BobState(CoreArb)");
     retry_until_finish_state_transition(
         cli_bob_progress_args.clone(),
         "BobState(CoreArb)".to_string(),
     )
     .await;
 
-    // run until the funding infos are cleared again
-    println!("waiting for the bitcoin funding info to clear");
-    retry_until_funding_info_cleared(swap_id.clone(), cli_bob_needs_funding_args.clone()).await;
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
+    tokio::time::sleep(time::Duration::from_secs(20)).await;
 
     // generate some bitcoin blocks to finalize the bitcoin arb lock tx
     bitcoin_rpc
@@ -797,26 +768,14 @@ async fn run_punish_swap_kill_bob_before_monero_funding(
         .send_to_address(&address, amount, None, None, None, None, None, None)
         .unwrap();
 
-    println!("waiting for AliceState(RefundSigs");
-    retry_until_finish_state_transition(
-        cli_alice_progress_args.clone(),
-        "AliceState(RefundSigs".to_string(),
-    )
-    .await;
-
     // run until BobState(CoreArb) is received
-    println!("waiting for BobState(CoreArb)");
     retry_until_finish_state_transition(
         cli_bob_progress_args.clone(),
         "BobState(CoreArb)".to_string(),
     )
     .await;
 
-    // run until the funding infos are cleared again
-    println!("waiting for the bitcoin funding info to clear");
-    retry_until_funding_info_cleared(swap_id.clone(), cli_bob_needs_funding_args.clone()).await;
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
+    tokio::time::sleep(time::Duration::from_secs(20)).await;
 
     // generate some bitcoin blocks to finalize the bitcoin arb lock tx
     bitcoin_rpc
@@ -905,54 +864,6 @@ async fn run_punish_swap_kill_bob_before_monero_funding(
     drop(lock);
 }
 
-async fn make_and_take_offer_parallel(
-    data_dir_maker: Vec<String>,
-    data_dir_taker: Vec<String>,
-    role: String,
-    bitcoin_rpc: Arc<bitcoincore_rpc::Client>,
-    monero_wallet: Arc<Mutex<monero_rpc::WalletClient>>,
-    btc_amount: bitcoin::Amount,
-    xmr_amount: monero::Amount,
-    previous_offers: Arc<Mutex<HashSet<String>>>,
-    previous_swap_ids: Arc<Mutex<HashSet<SwapId>>>,
-) -> (String, bitcoin::Address, SwapId) {
-    let maker_info_args = info_args(data_dir_maker.clone());
-    let taker_info_args = info_args(data_dir_maker.clone());
-
-    // test connection to farcasterd and check that swap-cli is in the correct place
-    run("../swap-cli", maker_info_args.clone()).unwrap();
-
-    let (xmr_address, xmr_address_wallet_name) =
-        monero_new_dest_address(Arc::clone(&monero_wallet)).await;
-    let btc_address = bitcoin_rpc.get_new_address(None, None).unwrap();
-    let btc_addr = btc_address.to_string();
-    let xmr_addr = xmr_address.to_string();
-
-    let (_stdout, _stderr) = run("../swap-cli", taker_info_args.clone()).unwrap();
-
-    let cli_make_args = make_offer_args(
-        data_dir_maker.clone(),
-        role,
-        btc_addr.clone(),
-        btc_amount,
-        xmr_addr.clone(),
-        xmr_amount,
-    );
-    let (_stdout, _stderr) = run("../swap-cli", cli_make_args).unwrap();
-
-    // get offer strings
-    let offer =
-        retry_until_offer_parallel(maker_info_args.clone(), Arc::clone(&previous_offers)).await;
-
-    let cli_take_args = take_offer_args(data_dir_taker.clone(), btc_addr, xmr_addr, offer.clone());
-    run("../swap-cli", cli_take_args).unwrap();
-
-    let swap_id =
-        retry_until_swap_id_parallel(taker_info_args.clone(), Arc::clone(&previous_swap_ids)).await;
-
-    (xmr_address_wallet_name, btc_address, swap_id)
-}
-
 async fn make_and_take_offer(
     data_dir_maker: Vec<String>,
     data_dir_taker: Vec<String>,
@@ -1004,217 +915,6 @@ async fn make_and_take_offer(
     (xmr_address_wallet_name, btc_address, swap_id)
 }
 
-#[allow(clippy::too_many_arguments)]
-async fn run_swaps_parallel(
-    swap_info: HashMap<SwapId, SwapParams>,
-    bitcoin_rpc: Arc<bitcoincore_rpc::Client>,
-    monero_regtest: monero_rpc::RegtestDaemonClient,
-    monero_wallet: Arc<Mutex<monero_rpc::WalletClient>>,
-    execution_mutex: Arc<Mutex<u8>>,
-) {
-    bitcoin_rpc
-        .generate_to_address(1, &reusable_btc_address())
-        .unwrap();
-
-    let lock = execution_mutex.lock().await;
-
-    // run until bob has the btc funding address
-    for (swap_id, SwapParams { data_dir_bob, .. }) in swap_info.iter() {
-        let (address, amount) = retry_until_bitcoin_funding_address(
-            swap_id.clone(),
-            needs_funding_args(data_dir_bob.clone(), "bitcoin".to_string()),
-        )
-        .await;
-
-        // fund the bitcoin address
-        bitcoin_rpc
-            .send_to_address(&address, amount, None, None, None, None, None, None)
-            .unwrap();
-    }
-
-    for (swap_id, SwapParams { data_dir_alice, .. }) in swap_info.iter() {
-        println!("waiting for AliceState(RefundSigs");
-        retry_until_finish_state_transition(
-            progress_args(data_dir_alice.clone(), swap_id.clone()),
-            "AliceState(RefundSigs".to_string(),
-        )
-        .await;
-    }
-
-    // run until BobState(CoreArb) is received
-    for (swap_id, SwapParams { data_dir_bob, .. }) in swap_info.iter() {
-        println!("waiting for BobState(CoreArb)");
-        retry_until_finish_state_transition(
-            progress_args(data_dir_bob.clone(), swap_id.clone()),
-            "BobState(CoreArb)".to_string(),
-        )
-        .await;
-    }
-
-    // run until the funding infos are cleared again
-    for (swap_id, SwapParams { data_dir_bob, .. }) in swap_info.iter() {
-        println!("waiting for the funding info to clear");
-        retry_until_funding_info_cleared(
-            swap_id.clone(),
-            needs_funding_args(data_dir_bob.clone(), "bitcoin".to_string()),
-        )
-        .await;
-    }
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
-
-    // generate some bitcoin blocks to finalize the bitcoin arb lock tx
-    bitcoin_rpc
-        .generate_to_address(3, &reusable_btc_address())
-        .unwrap();
-
-    // run until the alice has the monero funding address
-    for (swap_id, SwapParams { data_dir_alice, .. }) in swap_info.iter() {
-        let (monero_address, monero_amount) = retry_until_monero_funding_address(
-            swap_id.clone(),
-            needs_funding_args(data_dir_alice.clone(), "monero".to_string()),
-        )
-        .await;
-        send_monero(Arc::clone(&monero_wallet), monero_address, monero_amount).await;
-    }
-
-    // run until the funding infos are cleared again
-    for (swap_id, SwapParams { data_dir_alice, .. }) in swap_info.iter() {
-        println!("waiting for the funding info to clear");
-        retry_until_funding_info_cleared(
-            swap_id.clone(),
-            needs_funding_args(data_dir_alice.clone(), "monero".to_string()),
-        )
-        .await;
-    }
-
-    // generate some monero blocks to finalize the monero acc lock tx
-    monero_regtest
-        .generate_blocks(6, reusable_xmr_address())
-        .await
-        .unwrap();
-
-    // run until BobState(BuySig) is received
-    for (swap_id, SwapParams { data_dir_bob, .. }) in swap_info.iter() {
-        println!("waiting for BobState(BuySig)");
-        retry_until_finish_state_transition(
-            progress_args(data_dir_bob.clone(), swap_id.clone()),
-            "BobState(BuySig)".to_string(),
-        )
-        .await;
-    }
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
-
-    // generate some bitcoin blocks to make the buy tx final
-    bitcoin_rpc
-        .generate_to_address(5, &reusable_btc_address())
-        .unwrap();
-
-    // run until the AliceState(Finish) is received
-    for (swap_id, SwapParams { data_dir_alice, .. }) in swap_info.iter() {
-        retry_until_finish_state_transition(
-            progress_args(data_dir_alice.clone(), swap_id.clone()),
-            "AliceState(Finish(Success(Swapped)))".to_string(),
-        )
-        .await;
-    }
-
-    // generate some blocks on bitcoin's side
-    bitcoin_rpc
-        .generate_to_address(2, &reusable_btc_address())
-        .unwrap();
-
-    // check that btc was received in the destination address
-    for (
-        _,
-        SwapParams {
-            destination_btc_address,
-            ..
-        },
-    ) in swap_info.iter()
-    {
-        let balance = bitcoin_rpc
-            .get_received_by_address(&destination_btc_address, None)
-            .unwrap();
-        assert!(balance.as_sat() > 90000000);
-    }
-
-    // cache the monero balance before sweeping
-    let mut before_balances: HashMap<SwapId, u64> = HashMap::new();
-    for (
-        swap_id,
-        SwapParams {
-            xmr_dest_wallet_name,
-            ..
-        },
-    ) in swap_info.iter()
-    {
-        let monero_wallet_lock = monero_wallet.lock().await;
-        monero_wallet_lock
-            .open_wallet(xmr_dest_wallet_name.clone(), None)
-            .await
-            .unwrap();
-        before_balances.insert(
-            *swap_id,
-            monero_wallet_lock
-                .get_balance(0, None)
-                .await
-                .unwrap()
-                .balance,
-        );
-        drop(monero_wallet_lock);
-    }
-
-    // Sleep here to work around a race condition between pending
-    // SweepXmrAddress requests and tx Acc Lock confirmations. If Acc Lock
-    // confirmations are produced before the pending request is queued, no
-    // action will take place after this point.
-    tokio::time::sleep(time::Duration::from_secs(20)).await;
-
-    // generate some blocks on monero's side
-    monero_regtest
-        .generate_blocks(10, reusable_xmr_address())
-        .await
-        .unwrap();
-
-    // run until the BobState(Finish) is received
-    for (swap_id, SwapParams { data_dir_bob, .. }) in swap_info.iter() {
-        retry_until_finish_state_transition(
-            progress_args(data_dir_bob.clone(), swap_id.clone()),
-            "BobState(Finish(Success(Swapped)))".to_string(),
-            // monero_regtest.clone(),
-        )
-        .await;
-    }
-
-    monero_regtest
-        .generate_blocks(1, reusable_xmr_address())
-        .await
-        .unwrap();
-
-    for (
-        swap_id,
-        SwapParams {
-            xmr_dest_wallet_name,
-            ..
-        },
-    ) in swap_info.iter()
-    {
-        let monero_wallet_lock = monero_wallet.lock().await;
-        monero_wallet_lock
-            .open_wallet(xmr_dest_wallet_name.clone(), None)
-            .await
-            .unwrap();
-        monero_wallet_lock.refresh(Some(1)).await.unwrap();
-        let after_balance = monero_wallet_lock.get_balance(0, None).await.unwrap();
-        drop(monero_wallet_lock);
-        let delta_balance = after_balance.balance - before_balances[swap_id];
-        assert!(delta_balance > 999660000000);
-    }
-    drop(lock);
-}
-
 #[allow(clippy::too_many_arguments)]
 async fn run_swap(
     swap_id: SwapId,
@@ -1251,26 +951,14 @@ async fn run_swap(
         .send_to_address(&address, amount, None, None, None, None, None, None)
         .unwrap();
 
-    println!("waiting for AliceState(RefundSigs");
-    retry_until_finish_state_transition(
-        cli_alice_progress_args.clone(),
-        "AliceState(RefundSigs".to_string(),
-    )
-    .await;
-
     // run until BobState(CoreArb) is received
-    println!("waiting for BobState(CoreArb)");
     retry_until_finish_state_transition(
         cli_bob_progress_args.clone(),
         "BobState(CoreArb)".to_string(),
     )
     .await;
 
-    // run until the funding infos are cleared again
-    println!("waiting for the bitcoin funding info to clear");
-    retry_until_funding_info_cleared(swap_id.clone(), cli_bob_needs_funding_args.clone()).await;
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
+    tokio::time::sleep(time::Duration::from_secs(20)).await;
 
     // generate some bitcoin blocks to finalize the bitcoin arb lock tx
     bitcoin_rpc
@@ -1282,9 +970,7 @@ async fn run_swap(
         retry_until_monero_funding_address(swap_id, cli_alice_needs_funding_args.clone()).await;
     send_monero(Arc::clone(&monero_wallet), monero_address, monero_amount).await;
 
-    // run until the funding infos are cleared again
-    println!("waiting for the monero funding info to clear");
-    retry_until_funding_info_cleared(swap_id.clone(), cli_alice_needs_funding_args.clone()).await;
+    tokio::time::sleep(time::Duration::from_secs(10)).await;
 
     // generate some monero blocks to finalize the monero acc lock tx
     monero_regtest
@@ -1292,14 +978,7 @@ async fn run_swap(
         .await
         .unwrap();
 
-    // run until BobState(BuySig) is received
-    retry_until_finish_state_transition(
-        cli_bob_progress_args.clone(),
-        "BobState(BuySig)".to_string(),
-    )
-    .await;
-
-    tokio::time::sleep(time::Duration::from_secs(10)).await;
+    tokio::time::sleep(time::Duration::from_secs(20)).await;
 
     // generate some bitcoin blocks to make the buy tx final
     bitcoin_rpc
@@ -1583,28 +1262,6 @@ async fn retry_until_offer(args: Vec<String>) -> Vec<String> {
     panic!("timeout before any offer could be retrieved");
 }
 
-async fn retry_until_offer_parallel(
-    args: Vec<String>,
-    previous_offers: Arc<Mutex<HashSet<String>>>,
-) -> String {
-    for _ in 0..ALLOWED_RETRIES {
-        let mut previous_offers_lock = previous_offers.lock().await;
-        let (stdout, _stderr) = run("../swap-cli", args.clone()).unwrap();
-        let new_offers: HashSet<String> = cli_output_to_node_info(stdout)
-            .offers
-            .drain(..)
-            .map(|offer| offer.to_string())
-            .collect();
-        if let Some(offer) = new_offers.difference(&previous_offers_lock.clone()).next() {
-            previous_offers_lock.insert(offer.clone());
-            drop(previous_offers_lock);
-            return offer.clone();
-        }
-        tokio::time::sleep(time::Duration::from_secs(1)).await;
-    }
-    panic!("timeout before any offer could be retrieved");
-}
-
 async fn retry_until_swap_id(args: Vec<String>, previous_swap_ids: HashSet<SwapId>) -> SwapId {
     for _ in 0..ALLOWED_RETRIES {
         let (stdout, _stderr) = run("../swap-cli", args.clone()).unwrap();
@@ -1618,29 +1275,6 @@ async fn retry_until_swap_id(args: Vec<String>, previous_swap_ids: HashSet<SwapI
     panic!("timeout before any swapid could be retrieved");
 }
 
-async fn retry_until_swap_id_parallel(
-    args: Vec<String>,
-    previous_swap_ids: Arc<Mutex<HashSet<SwapId>>>,
-) -> SwapId {
-    for _ in 0..ALLOWED_RETRIES {
-        let mut previous_swap_ids_lock = previous_swap_ids.lock().await;
-        let (stdout, _stderr) = run("../swap-cli", args.clone()).unwrap();
-        let new_swap_ids: HashSet<SwapId> =
-            cli_output_to_node_info(stdout).swaps.drain(..).collect();
-
-        if let Some(swap_id) = new_swap_ids
-            .difference(&previous_swap_ids_lock.clone())
-            .next()
-        {
-            previous_swap_ids_lock.insert(swap_id.clone());
-            drop(previous_swap_ids_lock);
-            return swap_id.clone();
-        }
-        tokio::time::sleep(time::Duration::from_secs(1)).await;
-    }
-    panic!("timeout before any swapid could be retrieved");
-}
-
 async fn retry_until_bitcoin_funding_address(
     swap_id: SwapId,
     args: Vec<String>,
@@ -1652,7 +1286,6 @@ async fn retry_until_bitcoin_funding_address(
         let funding_infos: Vec<BitcoinFundingInfo> = stdout
             .iter()
             .filter_map(|element| {
-                println!("element: {:?}", element);
                 if let Ok(funding_info) = BitcoinFundingInfo::from_str(element) {
                     if funding_info.swap_id == swap_id {
                         Some(funding_info)
@@ -1673,35 +1306,6 @@ async fn retry_until_bitcoin_funding_address(
     panic!("timeout before any bitcoin funding address could be retrieved");
 }
 
-async fn retry_until_funding_info_cleared(swap_id: SwapId, args: Vec<String>) {
-    for _ in 0..ALLOWED_RETRIES {
-        let (stdout, _stderr) = run("../swap-cli", args.clone()).unwrap();
-
-        // get the btc funding address
-        let funding_infos: Vec<BitcoinFundingInfo> = stdout
-            .iter()
-            .filter_map(|element| {
-                println!("element: {:?}", element);
-                if let Ok(funding_info) = BitcoinFundingInfo::from_str(element) {
-                    if funding_info.swap_id == swap_id {
-                        Some(funding_info)
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                }
-            })
-            .collect();
-
-        if funding_infos.is_empty() {
-            return;
-        }
-        tokio::time::sleep(time::Duration::from_secs(1)).await;
-    }
-    panic!("timeout before any bitcoin funding address could be retrieved");
-}
-
 async fn retry_until_monero_funding_address(
     swap_id: SwapId,
     args: Vec<String>,
diff --git a/tests/functional.rs b/tests/functional.rs
index 8b9fed99..4a0a83a1 100644
--- a/tests/functional.rs
+++ b/tests/functional.rs
@@ -33,8 +33,8 @@ use std::str::FromStr;
 
 use farcaster_core::blockchain::Network;
 use farcaster_node::syncerd::types::{
-    Abort, AddressAddendum, Boolean, BroadcastTransaction, BtcAddressAddendum, Event, GetTx, Task,
-    WatchAddress, WatchHeight, WatchTransaction,
+    Abort, AddressAddendum, Boolean, BroadcastTransaction, BtcAddressAddendum, EstimateFee, Event,
+    GetTx, Task, WatchAddress, WatchHeight, WatchTransaction,
 };
 
 const SOURCE1: ServiceId = ServiceId::Syncer(Coin::Bitcoin, Network::Local);
@@ -129,6 +129,39 @@ fn assert_transaction_received(request: Request, expected_txid: bitcoin::Txid) {
     }
 }
 
+#[test]
+#[timeout(300000)]
+#[ignore]
+fn bitcoin_syncer_estimate_fee_test() {
+    bitcoin_setup();
+    let (tx, rx_event) = create_bitcoin_syncer(true, "estimatefee");
+    let task = SyncerdTask {
+        task: Task::EstimateFee(EstimateFee {
+            id: TaskId(1),
+            blocks_until_confirmation: 1,
+        }),
+        source: SOURCE1.clone(),
+    };
+    tx.send(task).unwrap();
+    let message = rx_event.recv_multipart(0).unwrap();
+    let request = get_request_from_message(message);
+    assert_fee_estimation_received(request)
+}
+
+fn assert_fee_estimation_received(request: Request) {
+    match request {
+        Request::SyncerdBridgeEvent(farcaster_node::rpc::request::SyncerdBridgeEvent {
+            event: Event::FeeEstimation(fee_estimation),
+            ..
+        }) => {
+            assert!(fee_estimation.btc_per_kbyte.is_some());
+        }
+        _ => {
+            panic!("expected syncerd bridge event");
+        }
+    }
+}
+
 /*
 We test for the following scenarios in the block height tests: