mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Dynamically calculate fees using electrum's estimate_fee.
Electrum has an estimate-fee feature which takes as input the block you want a tx to be included. The result is a recommendation of BTC/vbyte. Using this recommendation and the knowledge about the size of our transactions we compute an appropriate fee. The size of the transactions were taken from real transactions as published on bitcoin testnet. Note: in reality these sizes might fluctuate a bit but not for much.
This commit is contained in:
parent
38540b4de5
commit
ee90c228b4
@ -211,6 +211,7 @@ async fn init_bitcoin_wallet(
|
|||||||
&wallet_dir,
|
&wallet_dir,
|
||||||
seed.derive_extended_private_key(env_config.bitcoin_network)?,
|
seed.derive_extended_private_key(env_config.bitcoin_network)?,
|
||||||
env_config,
|
env_config,
|
||||||
|
6, // TODO move this into config
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to initialize Bitcoin wallet")?;
|
.context("Failed to initialize Bitcoin wallet")?;
|
||||||
|
@ -272,6 +272,7 @@ async fn init_bitcoin_wallet(
|
|||||||
&wallet_dir,
|
&wallet_dir,
|
||||||
seed.derive_extended_private_key(env_config.bitcoin_network)?,
|
seed.derive_extended_private_key(env_config.bitcoin_network)?,
|
||||||
env_config,
|
env_config,
|
||||||
|
6, // TODO move this into config
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to initialize Bitcoin wallet")?;
|
.context("Failed to initialize Bitcoin wallet")?;
|
||||||
|
@ -13,8 +13,9 @@ use std::cmp::Ordering;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
// TODO take real tx weight from past transaction
|
// Taken from https://mempool.space/testnet/tx/8da32d6cada4903c7043563c50cacce656a8be9e02f233201996739df5368b1f
|
||||||
pub const ESTIMATED_WEIGHT: usize = 10_000;
|
// The weight might fluctuate slightly in reality.
|
||||||
|
pub const ESTIMATED_WEIGHT: usize = 485;
|
||||||
|
|
||||||
/// Represent a timelock, expressed in relative block height as defined in
|
/// Represent a timelock, expressed in relative block height as defined in
|
||||||
/// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
/// [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki).
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::bitcoin::wallet::Watchable;
|
use crate::bitcoin::wallet::{EstimateFeeRate, Watchable};
|
||||||
use crate::bitcoin::{
|
use crate::bitcoin::{
|
||||||
build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet,
|
build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet,
|
||||||
};
|
};
|
||||||
@ -26,6 +26,7 @@ impl TxLock {
|
|||||||
B: PublicKey,
|
B: PublicKey,
|
||||||
) -> Result<Self>
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
|
C: EstimateFeeRate,
|
||||||
D: BatchDatabase,
|
D: BatchDatabase,
|
||||||
{
|
{
|
||||||
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
|
||||||
@ -147,9 +148,10 @@ impl TxLock {
|
|||||||
witness: Vec::new(),
|
witness: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let i = spending_fee.as_sat();
|
||||||
|
tracing::debug!("Redeem tx fee: {}", i);
|
||||||
let tx_out = TxOut {
|
let tx_out = TxOut {
|
||||||
value: self.inner.clone().extract_tx().output[self.lock_output_vout()].value
|
value: self.inner.clone().extract_tx().output[self.lock_output_vout()].value - i,
|
||||||
- spending_fee.as_sat(),
|
|
||||||
script_pubkey: spend_address.script_pubkey(),
|
script_pubkey: spend_address.script_pubkey(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -181,11 +183,23 @@ impl Watchable for TxLock {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use bdk::FeeRate;
|
||||||
|
|
||||||
|
struct StaticFeeRate {}
|
||||||
|
impl EstimateFeeRate for StaticFeeRate {
|
||||||
|
fn estimate_feerate(&self, _target_block: usize) -> Result<FeeRate> {
|
||||||
|
Ok(FeeRate::default_min_relay_fee())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_relay_fee(&self) -> Result<bitcoin::Amount> {
|
||||||
|
Ok(bitcoin::Amount::from_sat(1_000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_sends_good_psbt_when_reconstructing_then_succeeeds() {
|
async fn given_bob_sends_good_psbt_when_reconstructing_then_succeeeds() {
|
||||||
let (A, B) = alice_and_bob();
|
let (A, B) = alice_and_bob();
|
||||||
let wallet = Wallet::new_funded(50000);
|
let wallet = Wallet::new_funded(50000, StaticFeeRate {});
|
||||||
let agreed_amount = Amount::from_sat(10000);
|
let agreed_amount = Amount::from_sat(10000);
|
||||||
|
|
||||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
||||||
@ -199,7 +213,7 @@ mod tests {
|
|||||||
let (A, B) = alice_and_bob();
|
let (A, B) = alice_and_bob();
|
||||||
let fees = 610;
|
let fees = 610;
|
||||||
let agreed_amount = Amount::from_sat(10000);
|
let agreed_amount = Amount::from_sat(10000);
|
||||||
let wallet = Wallet::new_funded(agreed_amount.as_sat() + fees);
|
let wallet = Wallet::new_funded(agreed_amount.as_sat() + fees, StaticFeeRate {});
|
||||||
|
|
||||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -215,7 +229,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_is_sending_less_than_agreed_when_reconstructing_txlock_then_fails() {
|
async fn given_bob_is_sending_less_than_agreed_when_reconstructing_txlock_then_fails() {
|
||||||
let (A, B) = alice_and_bob();
|
let (A, B) = alice_and_bob();
|
||||||
let wallet = Wallet::new_funded(50000);
|
let wallet = Wallet::new_funded(50000, StaticFeeRate {});
|
||||||
let agreed_amount = Amount::from_sat(10000);
|
let agreed_amount = Amount::from_sat(10000);
|
||||||
|
|
||||||
let bad_amount = Amount::from_sat(5000);
|
let bad_amount = Amount::from_sat(5000);
|
||||||
@ -228,7 +242,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_bob_is_sending_to_a_bad_output_reconstructing_txlock_then_fails() {
|
async fn given_bob_is_sending_to_a_bad_output_reconstructing_txlock_then_fails() {
|
||||||
let (A, B) = alice_and_bob();
|
let (A, B) = alice_and_bob();
|
||||||
let wallet = Wallet::new_funded(50000);
|
let wallet = Wallet::new_funded(50000, StaticFeeRate {});
|
||||||
let agreed_amount = Amount::from_sat(10000);
|
let agreed_amount = Amount::from_sat(10000);
|
||||||
|
|
||||||
let E = eve();
|
let E = eve();
|
||||||
@ -244,7 +258,7 @@ mod tests {
|
|||||||
async fn bob_make_psbt(
|
async fn bob_make_psbt(
|
||||||
A: PublicKey,
|
A: PublicKey,
|
||||||
B: PublicKey,
|
B: PublicKey,
|
||||||
wallet: &Wallet<(), bdk::database::MemoryDatabase, ()>,
|
wallet: &Wallet<(), bdk::database::MemoryDatabase, StaticFeeRate>,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
) -> PartiallySignedTransaction {
|
) -> PartiallySignedTransaction {
|
||||||
TxLock::new(&wallet, amount, A, B).await.unwrap().into()
|
TxLock::new(&wallet, amount, A, B).await.unwrap().into()
|
||||||
|
@ -7,8 +7,9 @@ use bdk::bitcoin::Script;
|
|||||||
use miniscript::{Descriptor, DescriptorTrait};
|
use miniscript::{Descriptor, DescriptorTrait};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// TODO take real tx weight from past transaction
|
// Taken from https://mempool.space/testnet/tx/ed4d60bc1fd172feca444ed3d06cccb90346b9098c2d28d2d034dac66f608f68
|
||||||
pub const ESTIMATED_WEIGHT: usize = 10_000;
|
// The weight might fluctuate slightly in reality.
|
||||||
|
pub const ESTIMATED_WEIGHT: usize = 547;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TxPunish {
|
pub struct TxPunish {
|
||||||
|
@ -15,8 +15,9 @@ use miniscript::{Descriptor, DescriptorTrait};
|
|||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// TODO take real tx weight from past transaction
|
// Taken from https://mempool.space/testnet/tx/80c265f5c3862075aab2bd8c94e261b092bc13a34294c6fb4071717fbf46c801
|
||||||
pub const ESTIMATED_WEIGHT: usize = 10_000;
|
// The weight might fluctuate slightly in reality.
|
||||||
|
pub const ESTIMATED_WEIGHT: usize = 547;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TxRedeem {
|
pub struct TxRedeem {
|
||||||
|
@ -11,8 +11,9 @@ use ecdsa_fun::Signature;
|
|||||||
use miniscript::{Descriptor, DescriptorTrait};
|
use miniscript::{Descriptor, DescriptorTrait};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// TODO take real tx weight from past transaction
|
// Taken from https://mempool.space/testnet/tx/10aef570973bcf524b1a6e8d2eaf3bc1522e776381fc7520fd8987fba96e5424
|
||||||
pub const ESTIMATED_WEIGHT: usize = 10_000;
|
// The weight might fluctuate slightly in reality.
|
||||||
|
pub const ESTIMATED_WEIGHT: usize = 547;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TxRefund {
|
pub struct TxRefund {
|
||||||
|
@ -28,6 +28,7 @@ pub struct Wallet<B = ElectrumBlockchain, D = bdk::sled::Tree, C = Client> {
|
|||||||
wallet: Arc<Mutex<bdk::Wallet<B, D>>>,
|
wallet: Arc<Mutex<bdk::Wallet<B, D>>>,
|
||||||
finality_confirmations: u32,
|
finality_confirmations: u32,
|
||||||
network: Network,
|
network: Network,
|
||||||
|
target_block: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
@ -36,6 +37,7 @@ impl Wallet {
|
|||||||
wallet_dir: &Path,
|
wallet_dir: &Path,
|
||||||
key: impl DerivableKey<Segwitv0> + Clone,
|
key: impl DerivableKey<Segwitv0> + Clone,
|
||||||
env_config: env::Config,
|
env_config: env::Config,
|
||||||
|
target_block: usize,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let client = bdk::electrum_client::Client::new(electrum_rpc_url.as_str())
|
let client = bdk::electrum_client::Client::new(electrum_rpc_url.as_str())
|
||||||
.context("Failed to initialize Electrum RPC client")?;
|
.context("Failed to initialize Electrum RPC client")?;
|
||||||
@ -63,6 +65,7 @@ impl Wallet {
|
|||||||
wallet: Arc::new(Mutex::new(wallet)),
|
wallet: Arc::new(Mutex::new(wallet)),
|
||||||
finality_confirmations: env_config.bitcoin_finality_confirmations,
|
finality_confirmations: env_config.bitcoin_finality_confirmations,
|
||||||
network,
|
network,
|
||||||
|
target_block,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +241,7 @@ impl Subscription {
|
|||||||
|
|
||||||
impl<B, D, C> Wallet<B, D, C>
|
impl<B, D, C> Wallet<B, D, C>
|
||||||
where
|
where
|
||||||
|
C: EstimateFeeRate,
|
||||||
D: BatchDatabase,
|
D: BatchDatabase,
|
||||||
{
|
{
|
||||||
pub async fn balance(&self) -> Result<Amount> {
|
pub async fn balance(&self) -> Result<Amount> {
|
||||||
@ -282,10 +286,12 @@ where
|
|||||||
amount: Amount,
|
amount: Amount,
|
||||||
) -> Result<PartiallySignedTransaction> {
|
) -> Result<PartiallySignedTransaction> {
|
||||||
let wallet = self.wallet.lock().await;
|
let wallet = self.wallet.lock().await;
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
let fee_rate = client.estimate_feerate(self.target_block)?;
|
||||||
|
|
||||||
let mut tx_builder = wallet.build_tx();
|
let mut tx_builder = wallet.build_tx();
|
||||||
tx_builder.add_recipient(address.script_pubkey(), amount.as_sat());
|
tx_builder.add_recipient(address.script_pubkey(), amount.as_sat());
|
||||||
tx_builder.fee_rate(self.select_feerate());
|
tx_builder.fee_rate(fee_rate);
|
||||||
let (psbt, _details) = tx_builder.finish()?;
|
let (psbt, _details) = tx_builder.finish()?;
|
||||||
|
|
||||||
Ok(psbt)
|
Ok(psbt)
|
||||||
@ -298,19 +304,57 @@ where
|
|||||||
/// transaction confirmed.
|
/// transaction confirmed.
|
||||||
pub async fn max_giveable(&self, locking_script_size: usize) -> Result<Amount> {
|
pub async fn max_giveable(&self, locking_script_size: usize) -> Result<Amount> {
|
||||||
let wallet = self.wallet.lock().await;
|
let wallet = self.wallet.lock().await;
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
let fee_rate = client.estimate_feerate(self.target_block)?;
|
||||||
|
|
||||||
let mut tx_builder = wallet.build_tx();
|
let mut tx_builder = wallet.build_tx();
|
||||||
|
|
||||||
let dummy_script = Script::from(vec![0u8; locking_script_size]);
|
let dummy_script = Script::from(vec![0u8; locking_script_size]);
|
||||||
tx_builder.set_single_recipient(dummy_script);
|
tx_builder.set_single_recipient(dummy_script);
|
||||||
tx_builder.drain_wallet();
|
tx_builder.drain_wallet();
|
||||||
tx_builder.fee_rate(self.select_feerate());
|
tx_builder.fee_rate(fee_rate);
|
||||||
let (_, details) = tx_builder.finish().context("Failed to build transaction")?;
|
let (_, details) = tx_builder.finish().context("Failed to build transaction")?;
|
||||||
|
|
||||||
let max_giveable = details.sent - details.fees;
|
let max_giveable = details.sent - details.fees;
|
||||||
|
|
||||||
Ok(Amount::from_sat(max_giveable))
|
Ok(Amount::from_sat(max_giveable))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Estimate total tx fee based for a pre-defined target block based on the
|
||||||
|
/// transaction weight
|
||||||
|
pub async fn estimate_fee(&self, weight: usize) -> Result<bitcoin::Amount> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
let fee_rate = client.estimate_feerate(self.target_block)?;
|
||||||
|
|
||||||
|
let min_relay_fee = client.min_relay_fee()?;
|
||||||
|
tracing::debug!("Min relay fee: {}", min_relay_fee);
|
||||||
|
// Doing some heavy math here :)
|
||||||
|
// `usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide.
|
||||||
|
// This fine because such a big transaction cannot exist and there are also no
|
||||||
|
// negative fees.
|
||||||
|
#[allow(
|
||||||
|
clippy::cast_precision_loss,
|
||||||
|
clippy::cast_possible_truncation,
|
||||||
|
clippy::cast_sign_loss
|
||||||
|
)]
|
||||||
|
let sats_per_vbyte = ((weight as f32) / 4.0 * fee_rate.as_sat_vb()) as u64;
|
||||||
|
tracing::debug!(
|
||||||
|
"Estimated fee for weight: {} for fee_rate: {:?} is in total: {}",
|
||||||
|
weight,
|
||||||
|
fee_rate,
|
||||||
|
sats_per_vbyte
|
||||||
|
);
|
||||||
|
if sats_per_vbyte < min_relay_fee.as_sat() {
|
||||||
|
tracing::warn!(
|
||||||
|
"Estimated fee of {} is smaller than the min relay fee, defaulting to min relay fee {}",
|
||||||
|
sats_per_vbyte,
|
||||||
|
min_relay_fee.as_sat()
|
||||||
|
);
|
||||||
|
Ok(min_relay_fee)
|
||||||
|
} else {
|
||||||
|
Ok(bitcoin::Amount::from_sat(sats_per_vbyte))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B, D, C> Wallet<B, D, C>
|
impl<B, D, C> Wallet<B, D, C>
|
||||||
@ -340,32 +384,20 @@ impl<B, D, C> Wallet<B, D, C> {
|
|||||||
pub fn get_network(&self) -> bitcoin::Network {
|
pub fn get_network(&self) -> bitcoin::Network {
|
||||||
self.network
|
self.network
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects an appropriate [`FeeRate`] to be used for getting transactions
|
|
||||||
/// confirmed within a reasonable amount of time.
|
|
||||||
fn select_feerate(&self) -> FeeRate {
|
|
||||||
// TODO: This should obviously not be a const :)
|
|
||||||
FeeRate::from_sat_per_vb(5.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn estimate_fee(&self, weight: usize) -> bitcoin::Amount {
|
pub trait EstimateFeeRate {
|
||||||
// Doing some heavy math here :)
|
fn estimate_feerate(&self, target_block: usize) -> Result<FeeRate>;
|
||||||
// `usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide
|
fn min_relay_fee(&self) -> Result<bitcoin::Amount>;
|
||||||
// This fine because such a big transaction cannot exist.
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
|
||||||
let calc_fee_bytes = (weight as f32) * self.select_feerate().as_sat_vb() / 4.0;
|
|
||||||
// There are no fractional satoshi, hence we just round to the next one and
|
|
||||||
// truncate. We also do not support negative fees.
|
|
||||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
|
||||||
let calc_fee_bytes_rounded = ((calc_fee_bytes * 10.0).round() as u64) / 10;
|
|
||||||
bitcoin::Amount::from_sat(calc_fee_bytes_rounded)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl Wallet<(), bdk::database::MemoryDatabase, ()> {
|
impl<EFR> Wallet<(), bdk::database::MemoryDatabase, EFR>
|
||||||
|
where
|
||||||
|
EFR: EstimateFeeRate,
|
||||||
|
{
|
||||||
/// Creates a new, funded wallet to be used within tests.
|
/// Creates a new, funded wallet to be used within tests.
|
||||||
pub fn new_funded(amount: u64) -> Self {
|
pub fn new_funded(amount: u64, estimate_fee_rate: EFR) -> Self {
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::{LocalUtxo, TransactionDetails};
|
use bdk::{LocalUtxo, TransactionDetails};
|
||||||
use bitcoin::OutPoint;
|
use bitcoin::OutPoint;
|
||||||
@ -387,10 +419,11 @@ impl Wallet<(), bdk::database::MemoryDatabase, ()> {
|
|||||||
bdk::Wallet::new_offline(&descriptors.0, None, Network::Regtest, database).unwrap();
|
bdk::Wallet::new_offline(&descriptors.0, None, Network::Regtest, database).unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client: Arc::new(Mutex::new(())),
|
client: Arc::new(Mutex::new(estimate_fee_rate)),
|
||||||
wallet: Arc::new(Mutex::new(wallet)),
|
wallet: Arc::new(Mutex::new(wallet)),
|
||||||
finality_confirmations: 1,
|
finality_confirmations: 1,
|
||||||
network: Network::Regtest,
|
network: Network::Regtest,
|
||||||
|
target_block: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -557,6 +590,24 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EstimateFeeRate for Client {
|
||||||
|
fn estimate_feerate(&self, target_block: usize) -> Result<FeeRate> {
|
||||||
|
// https://github.com/romanz/electrs/blob/f9cf5386d1b5de6769ee271df5eef324aa9491bc/src/rpc.rs#L213
|
||||||
|
// Returned estimated fees are per BTC/kb.
|
||||||
|
let fee_per_byte = self.electrum.estimate_fee(target_block)?;
|
||||||
|
// we do not expect fees being that high.
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
Ok(FeeRate::from_btc_per_kvb(fee_per_byte as f32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_relay_fee(&self) -> Result<bitcoin::Amount> {
|
||||||
|
// https://github.com/romanz/electrs/blob/f9cf5386d1b5de6769ee271df5eef324aa9491bc/src/rpc.rs#L219
|
||||||
|
// Returned fee is in BTC/kb
|
||||||
|
let relay_fee = bitcoin::Amount::from_btc(self.electrum.relay_fee()?)?;
|
||||||
|
Ok(relay_fee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum ScriptStatus {
|
pub enum ScriptStatus {
|
||||||
Unseen,
|
Unseen,
|
||||||
|
@ -132,6 +132,12 @@ impl State0 {
|
|||||||
let s_a = monero::Scalar::random(rng);
|
let s_a = monero::Scalar::random(rng);
|
||||||
let (dleq_proof_s_a, (S_a_bitcoin, S_a_monero)) = CROSS_CURVE_PROOF_SYSTEM.prove(&s_a, rng);
|
let (dleq_proof_s_a, (S_a_bitcoin, S_a_monero)) = CROSS_CURVE_PROOF_SYSTEM.prove(&s_a, rng);
|
||||||
|
|
||||||
|
let tx_redeem_fee = bitcoin_wallet
|
||||||
|
.estimate_fee(ESTIMATED_WEIGHT_TX_REDEEM)
|
||||||
|
.await?;
|
||||||
|
let tx_punish_fee = bitcoin_wallet
|
||||||
|
.estimate_fee(ESTIMATED_WEIGHT_TX_PUNISH)
|
||||||
|
.await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
a,
|
a,
|
||||||
s_a,
|
s_a,
|
||||||
@ -147,8 +153,8 @@ impl State0 {
|
|||||||
xmr,
|
xmr,
|
||||||
cancel_timelock: env_config.bitcoin_cancel_timelock,
|
cancel_timelock: env_config.bitcoin_cancel_timelock,
|
||||||
punish_timelock: env_config.bitcoin_punish_timelock,
|
punish_timelock: env_config.bitcoin_punish_timelock,
|
||||||
tx_redeem_fee: bitcoin_wallet.estimate_fee(ESTIMATED_WEIGHT_TX_REDEEM),
|
tx_redeem_fee,
|
||||||
tx_punish_fee: bitcoin_wallet.estimate_fee(ESTIMATED_WEIGHT_TX_PUNISH),
|
tx_punish_fee,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +66,12 @@ async fn next_state(
|
|||||||
Ok(match state {
|
Ok(match state {
|
||||||
BobState::Started { btc_amount } => {
|
BobState::Started { btc_amount } => {
|
||||||
let bitcoin_refund_address = bitcoin_wallet.new_address().await?;
|
let bitcoin_refund_address = bitcoin_wallet.new_address().await?;
|
||||||
let tx_refund_fee = bitcoin_wallet.estimate_fee(bitcoin::ESTIMATED_WEIGHT_TX_REDEEM);
|
let tx_refund_fee = bitcoin_wallet
|
||||||
let tx_cancel_fee = bitcoin_wallet.estimate_fee(bitcoin::ESTIMATED_WEIGHT_TX_CANCEL);
|
.estimate_fee(bitcoin::ESTIMATED_WEIGHT_TX_REDEEM)
|
||||||
|
.await?;
|
||||||
|
let tx_cancel_fee = bitcoin_wallet
|
||||||
|
.estimate_fee(bitcoin::ESTIMATED_WEIGHT_TX_CANCEL)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let state2 = request_price_and_setup(
|
let state2 = request_price_and_setup(
|
||||||
swap_id,
|
swap_id,
|
||||||
|
@ -14,7 +14,10 @@ use std::fmt;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use swap::bitcoin::{CancelTimelock, PunishTimelock};
|
use swap::bitcoin::{
|
||||||
|
CancelTimelock, PunishTimelock, ESTIMATED_WEIGHT_TX_CANCEL, ESTIMATED_WEIGHT_TX_REDEEM,
|
||||||
|
ESTIMATED_WEIGHT_TX_REFUND,
|
||||||
|
};
|
||||||
use swap::database::Database;
|
use swap::database::Database;
|
||||||
use swap::env::{Config, GetConfig};
|
use swap::env::{Config, GetConfig};
|
||||||
use swap::network::swarm;
|
use swap::network::swarm;
|
||||||
@ -35,14 +38,6 @@ use tracing_subscriber::util::SubscriberInitExt;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// Note: this needs to be adopted if bitcoin::wallet.select_feerate() or
|
|
||||||
// bitcoin::ESTIMATED_WEIGHT_TX_REDEEM changes.
|
|
||||||
// TODO: see if there is a better way.
|
|
||||||
const TX_REDEEM_FEE: u64 = 12500;
|
|
||||||
const TX_REFUND_FEE: u64 = 12500;
|
|
||||||
const TX_CANCEL_FEE: u64 = 12500;
|
|
||||||
const TX_PUNISH_FEE: u64 = 12500;
|
|
||||||
|
|
||||||
pub async fn setup_test<T, F, C>(_config: C, testfn: T)
|
pub async fn setup_test<T, F, C>(_config: C, testfn: T)
|
||||||
where
|
where
|
||||||
T: Fn(TestContext) -> F,
|
T: Fn(TestContext) -> F,
|
||||||
@ -293,6 +288,7 @@ async fn init_test_wallets(
|
|||||||
seed.derive_extended_private_key(env_config.bitcoin_network)
|
seed.derive_extended_private_key(env_config.bitcoin_network)
|
||||||
.expect("Could not create extended private key from seed"),
|
.expect("Could not create extended private key from seed"),
|
||||||
env_config,
|
env_config,
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("could not init btc wallet");
|
.expect("could not init btc wallet");
|
||||||
@ -547,7 +543,7 @@ impl TestContext {
|
|||||||
assert_eventual_balance(
|
assert_eventual_balance(
|
||||||
self.alice_bitcoin_wallet.as_ref(),
|
self.alice_bitcoin_wallet.as_ref(),
|
||||||
Ordering::Equal,
|
Ordering::Equal,
|
||||||
self.alice_redeemed_btc_balance(),
|
self.alice_redeemed_btc_balance().await,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -588,7 +584,7 @@ impl TestContext {
|
|||||||
assert_eventual_balance(
|
assert_eventual_balance(
|
||||||
self.alice_bitcoin_wallet.as_ref(),
|
self.alice_bitcoin_wallet.as_ref(),
|
||||||
Ordering::Equal,
|
Ordering::Equal,
|
||||||
self.alice_punished_btc_balance(),
|
self.alice_punished_btc_balance().await,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -639,11 +635,19 @@ impl TestContext {
|
|||||||
|
|
||||||
let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap();
|
let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap();
|
||||||
|
|
||||||
|
let cancel_fee = self
|
||||||
|
.alice_bitcoin_wallet
|
||||||
|
.estimate_fee(ESTIMATED_WEIGHT_TX_CANCEL)
|
||||||
|
.await
|
||||||
|
.expect("To estimate fee correctly");
|
||||||
|
let refund_fee = self
|
||||||
|
.alice_bitcoin_wallet
|
||||||
|
.estimate_fee(ESTIMATED_WEIGHT_TX_REFUND)
|
||||||
|
.await
|
||||||
|
.expect("To estimate fee correctly");
|
||||||
|
|
||||||
let bob_cancelled_and_refunded = btc_balance_after_swap
|
let bob_cancelled_and_refunded = btc_balance_after_swap
|
||||||
== self.bob_starting_balances.btc
|
== self.bob_starting_balances.btc - lock_tx_bitcoin_fee - cancel_fee - refund_fee;
|
||||||
- lock_tx_bitcoin_fee
|
|
||||||
- bitcoin::Amount::from_sat(TX_CANCEL_FEE)
|
|
||||||
- bitcoin::Amount::from_sat(TX_REFUND_FEE);
|
|
||||||
|
|
||||||
assert!(bob_cancelled_and_refunded);
|
assert!(bob_cancelled_and_refunded);
|
||||||
|
|
||||||
@ -678,9 +682,13 @@ impl TestContext {
|
|||||||
self.alice_starting_balances.xmr - self.xmr_amount
|
self.alice_starting_balances.xmr - self.xmr_amount
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount {
|
async fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount {
|
||||||
self.alice_starting_balances.btc + self.btc_amount
|
let fee = self
|
||||||
- bitcoin::Amount::from_sat(TX_REDEEM_FEE)
|
.alice_bitcoin_wallet
|
||||||
|
.estimate_fee(ESTIMATED_WEIGHT_TX_REDEEM)
|
||||||
|
.await
|
||||||
|
.expect("To estimate fee correctly");
|
||||||
|
self.alice_starting_balances.btc + self.btc_amount - fee
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bob_redeemed_xmr_balance(&self) -> monero::Amount {
|
fn bob_redeemed_xmr_balance(&self) -> monero::Amount {
|
||||||
@ -717,10 +725,18 @@ impl TestContext {
|
|||||||
self.alice_starting_balances.xmr - self.xmr_amount
|
self.alice_starting_balances.xmr - self.xmr_amount
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alice_punished_btc_balance(&self) -> bitcoin::Amount {
|
async fn alice_punished_btc_balance(&self) -> bitcoin::Amount {
|
||||||
self.alice_starting_balances.btc + self.btc_amount
|
let cancel_fee = self
|
||||||
- bitcoin::Amount::from_sat(TX_CANCEL_FEE)
|
.alice_bitcoin_wallet
|
||||||
- bitcoin::Amount::from_sat(TX_PUNISH_FEE)
|
.estimate_fee(ESTIMATED_WEIGHT_TX_CANCEL)
|
||||||
|
.await
|
||||||
|
.expect("To estimate fee correctly");
|
||||||
|
let punish_fee = self
|
||||||
|
.alice_bitcoin_wallet
|
||||||
|
.estimate_fee(ESTIMATED_WEIGHT_TX_REFUND)
|
||||||
|
.await
|
||||||
|
.expect("To estimate fee correctly");
|
||||||
|
self.alice_starting_balances.btc + self.btc_amount - cancel_fee - punish_fee
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bob_punished_xmr_balance(&self) -> monero::Amount {
|
fn bob_punished_xmr_balance(&self) -> monero::Amount {
|
||||||
|
Loading…
Reference in New Issue
Block a user