Skip to content

Commit 091ba57

Browse files
binarybarondelta1
andauthored
feat(wallet): Use mempool histogram for fee estimation (#358)
* feat(wallet): Use mempool.space as a secondary fee estimation source * fix: warn if mempool client cannot be instantiated * make clippy happy * nitpick: rename clippy_check to clippy in justfile * rename `estimate_fee_rate_from_mempool` to `estimate_fee_rate_from_histogram` for clarity * dprint fmt * make clippy happy * change teacing level back to debug! * change log levels * refactors * refactor: estimate_fee and min_relay_fee * serde camel case Co-authored-by: Byron Hambly <[email protected]> * refactors * Add comments, use Weight struct where possible * fmt, fix testrs * dont fallback to bitcoin::MAX, fail instead * make mempool space optional * fmt * refactor: use estimate_fee(...) in max_giveable(...) * refactor max_giveable(...) * refactor max_giveeable to return fee as well, remove safety margin for fee * fix compile * fmtr * fix(integration test): Use pre-calculated cancel / punish fees for assert_alice_punished * fix(integration test): Use real fees for asserts * sync wallet before transaction_fee call * split send_to_address into sweep_balance_to_address_dynamic_fee --------- Co-authored-by: Byron Hambly <[email protected]>
1 parent 854b149 commit 091ba57

File tree

23 files changed

+988
-351
lines changed

23 files changed

+988
-351
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- We add a safety margin of 25% to the Bitcoin fee estimation. This ensures pre-signed transactions will get confirmed in time.
10+
- The Bitcoin fee estimation is now more accurate. It uses a combination of `estimatesmartfee` from Bitcoin Core and `mempool.get_fee_histogram` from Electrum to ensure our distance from the mempool tip is appropriate. If our Electrum server doesn't support fee estimation, we use the mempool.space API. The mempool space API can be disabled using the `bitcoin.use_mempool_space_fee_estimation` option in the config file. It defaults to `true`.
1111

1212
## [1.1.2] - 2025-05-24
1313

dprint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
"https://plugins.dprint.dev/exec-0.3.5.json@d687dda57be0fe9a0088ccdaefa5147649ff24127d8b3ea227536c68ee7abeab",
2626
"https://plugins.dprint.dev/prettier-0.26.6.json@0118376786f37496e41bb19dbcfd1e7214e2dc859a55035c5e54d1107b4c9c57"
2727
]
28-
}
28+
}

justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ update_submodules:
5050
cd monero-sys && git submodule update --init --recursive --force
5151

5252
# Run clippy checks
53-
clippy_check:
53+
clippy:
5454
cargo clippy --workspace --all-targets --all-features -- -D warnings
5555

5656
# Check the bindings for the Tauri API
@@ -71,4 +71,4 @@ fmt:
7171

7272
# Sometimes you have to prune the docker network to get the integration tests to work
7373
docker-prune-network:
74-
docker network prune -f
74+
docker network prune -f

swap/proptest-regressions/bitcoin/wallet.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@
55
# It is recommended to check this file in to source control so that
66
# everyone who runs the test benefits from these saved cases.
77
cc 849f8b01f49fc9a913100203698a9151d8de8a37564e1d3b1e3b4169e192f58a # shrinks to funding_amount = 290250686, num_utxos = 3, sats_per_vb = 75.35638, key = ExtendedPrivKey { network: Regtest, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 0b7a29ca6990bbc9b9187c1d1a07e2cf68e32f5ce55d2df01edf8a4ac2ee2a4b }, alice = Point<Normal,Public,NonZero>(0299a8c6a662e2e9e8ee7c6889b75a51c432812b4bf70c1d76eace63abc1bdfb1b), bob = Point<Normal,Public,NonZero>(027165b1f9924030c90d38c511da0f4397766078687997ed34d6ef2743d2a7bbed)
8+
cc ec2c53bbf967d46a4e9a394da96f8089ea77dcca794dec9fcc13c5a7141eb929 # shrinks to funding_amount = 271331, num_utxos = 4, sats_per_vb = 308, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point<Normal,Public,NonZero>(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point<Normal,Public,NonZero>(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)
9+
cc 2062fe113880e48af4d170fca9a2690e57a55f8985f86a66bd53e8bbe5c62dbd # shrinks to funding_amount = 3000, num_utxos = 1, sats_per_vb = 7, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point<Normal,Public,NonZero>(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point<Normal,Public,NonZero>(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)
10+
cc 381f4c0a72579c36107b0fd7a5b9c8aad2f02c0f9908b3a137b71ff05e7d8d33 # shrinks to funding_amount = 3000, num_utxos = 1, sats_per_vb = 7, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point<Normal,Public,NonZero>(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point<Normal,Public,NonZero>(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)

swap/src/asb/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ pub struct Bitcoin {
185185
pub finality_confirmations: Option<u32>,
186186
#[serde(with = "crate::bitcoin::network")]
187187
pub network: bitcoin::Network,
188+
#[serde(default = "default_use_mempool_space_fee_estimation")]
189+
pub use_mempool_space_fee_estimation: bool,
190+
}
191+
192+
fn default_use_mempool_space_fee_estimation() -> bool {
193+
true
188194
}
189195

190196
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
@@ -377,6 +383,7 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
377383
target_block,
378384
finality_confirmations: None,
379385
network: bitcoin_network,
386+
use_mempool_space_fee_estimation: true,
380387
},
381388
monero: Monero {
382389
wallet_rpc_url: monero_wallet_rpc_url,
@@ -421,6 +428,7 @@ mod tests {
421428
target_block: defaults.bitcoin_confirmation_target,
422429
finality_confirmations: None,
423430
network: bitcoin::Network::Testnet,
431+
use_mempool_space_fee_estimation: true,
424432
},
425433
network: Network {
426434
listen: vec![defaults.listen_address_tcp],
@@ -465,6 +473,7 @@ mod tests {
465473
target_block: defaults.bitcoin_confirmation_target,
466474
finality_confirmations: None,
467475
network: bitcoin::Network::Bitcoin,
476+
use_mempool_space_fee_estimation: true,
468477
},
469478
network: Network {
470479
listen: vec![defaults.listen_address_tcp],
@@ -519,6 +528,7 @@ mod tests {
519528
target_block: defaults.bitcoin_confirmation_target,
520529
finality_confirmations: None,
521530
network: bitcoin::Network::Bitcoin,
531+
use_mempool_space_fee_estimation: true,
522532
},
523533
network: Network {
524534
listen,

swap/src/bin/asb.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,19 +310,22 @@ pub async fn main() -> Result<()> {
310310
Command::WithdrawBtc { amount, address } => {
311311
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
312312

313-
let amount = match amount {
314-
Some(amount) => amount,
313+
let withdraw_tx_unsigned = match amount {
314+
Some(amount) => {
315+
bitcoin_wallet
316+
.send_to_address_dynamic_fee(address, amount, None)
317+
.await?
318+
}
315319
None => {
316320
bitcoin_wallet
317-
.max_giveable(address.script_pubkey().len())
321+
.sweep_balance_to_address_dynamic_fee(address)
318322
.await?
319323
}
320324
};
321325

322-
let psbt = bitcoin_wallet
323-
.send_to_address(address, amount, None)
326+
let signed_tx = bitcoin_wallet
327+
.sign_and_finalize(withdraw_tx_unsigned)
324328
.await?;
325-
let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?;
326329

327330
bitcoin_wallet.broadcast(signed_tx, "withdraw").await?;
328331
}
@@ -420,6 +423,7 @@ async fn init_bitcoin_wallet(
420423
})
421424
.finality_confirmations(env_config.bitcoin_finality_confirmations)
422425
.target_block(config.bitcoin.target_block)
426+
.use_mempool_space_fee_estimation(config.bitcoin.use_mempool_space_fee_estimation)
423427
.sync_interval(env_config.bitcoin_sync_interval())
424428
.build()
425429
.await

0 commit comments

Comments
 (0)