Make bitcoin tx fee configurable

Alice specifies the fee rate in the quote response to Bob. Bob's CLI tool should calculate and show the fees (along with the rate) that he will pay for a swap. Bob can decide whether he wishes to proceed with the swap. Currently the fee rate is hardcoded for Alice and the tests are failing because the balance assertions assume fixed tx fees. Remove fee rate from BuildTxLockPsbt as we manually specify the fees in build_spend_transaction().
This commit is contained in:
rishflab 2021-02-17 10:00:53 +11:00
parent 7a9569ffd4
commit 48b3d6ee6c
9 changed files with 79 additions and 27 deletions

View File

@ -35,14 +35,7 @@ use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::str::FromStr;
// TODO: Configurable tx-fee (note: parties have to agree prior to swapping)
// Current reasoning:
// tx with largest weight (as determined by get_weight() upon broadcast in e2e
// test) = 609 assuming segwit and 60 sat/vB:
// (609 / 4) * 60 (sat/vB) = 9135 sats
// Recommended: Overpay a bit to ensure we don't have to wait too long for test
// runs.
pub const TX_FEE: u64 = 15_000;
pub const FEE_RATE: u64 = 80;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct SecretKey {
@ -317,3 +310,41 @@ pub struct EmptyWitnessStack;
#[derive(Clone, Copy, thiserror::Error, Debug)]
#[error("input has {0} witnesses, expected 3")]
pub struct NotThreeWitnesses(usize);
pub mod bitcoin_amount {
use crate::bitcoin::Amount;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(x: &Amount, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_u64(x.as_sat())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Amount, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
let sats = u64::deserialize(deserializer)?;
let amount = Amount::from_sat(sats);
Ok(amount)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct BitcoinAmount(#[serde(with = "bitcoin_amount")] crate::bitcoin::Amount);
#[test]
fn serde_bitcoin_amount() {
let amount = BitcoinAmount(crate::bitcoin::Amount::from_sat(1000));
let encoded = serde_cbor::to_vec(&amount).unwrap();
let decoded: BitcoinAmount = serde_cbor::from_slice(&encoded).unwrap();
assert_eq!(amount, decoded);
}
}

View File

@ -1,6 +1,6 @@
use crate::bitcoin::{
build_shared_output_descriptor, Address, Amount, BlockHeight, PublicKey, Transaction, TxLock,
TX_FEE,
FEE_RATE,
};
use ::bitcoin::{util::bip143::SigHashCache, OutPoint, SigHash, SigHashType, TxIn, TxOut, Txid};
use anyhow::Result;
@ -77,7 +77,7 @@ impl TxCancel {
};
let tx_out = TxOut {
value: tx_lock.lock_amount().as_sat() - TX_FEE,
value: tx_lock.lock_amount().as_sat(),
script_pubkey: cancel_output_descriptor.script_pubkey(NullCtx),
};
@ -165,8 +165,11 @@ impl TxCancel {
witness: Vec::new(),
};
let vbytes = self.inner.get_weight() / 4;
let tx_fee = vbytes as u64 * FEE_RATE;
let tx_out = TxOut {
value: self.amount().as_sat() - TX_FEE,
value: self.amount().as_sat() - tx_fee,
script_pubkey: spend_address.script_pubkey(),
};

View File

@ -1,6 +1,6 @@
use crate::bitcoin::{
build_shared_output_descriptor, Address, Amount, BuildTxLockPsbt, GetNetwork, PublicKey,
Transaction, TX_FEE,
Transaction, FEE_RATE,
};
use ::bitcoin::{util::psbt::PartiallySignedTransaction, OutPoint, TxIn, TxOut, Txid};
use anyhow::Result;
@ -74,8 +74,11 @@ impl TxLock {
witness: Vec::new(),
};
let vbytes = self.inner.clone().extract_tx().get_weight() / 4;
let tx_fee = vbytes as u64 * FEE_RATE;
let tx_out = TxOut {
value: self.inner.clone().extract_tx().output[self.lock_output_vout()].value - TX_FEE,
value: self.inner.clone().extract_tx().output[self.lock_output_vout()].value - tx_fee,
script_pubkey: spend_address.script_pubkey(),
};

View File

@ -14,7 +14,6 @@ use bdk::{
blockchain::{noop_progress, Blockchain, ElectrumBlockchain},
electrum_client::{self, Client, ElectrumApi},
miniscript::bitcoin::PrivateKey,
FeeRate,
};
use reqwest::{Method, Url};
use serde::{Deserialize, Serialize};
@ -120,14 +119,14 @@ impl BuildTxLockPsbt for Wallet {
) -> Result<PartiallySignedTransaction> {
tracing::debug!("building tx lock");
self.sync_wallet().await?;
let (psbt, _details) = self.inner.lock().await.create_tx(
bdk::TxBuilder::with_recipients(vec![(
output_address.script_pubkey(),
output_amount.as_sat(),
)])
// todo: get actual fee
.fee_rate(FeeRate::from_sat_per_vb(5.0)),
)?;
let (psbt, _details) =
self.inner
.lock()
.await
.create_tx(bdk::TxBuilder::with_recipients(vec![(
output_address.script_pubkey(),
output_amount.as_sat(),
)]))?;
tracing::debug!("tx lock built");
Ok(psbt)
}

View File

@ -1,5 +1,6 @@
use crate::{
bitcoin,
bitcoin::FEE_RATE,
database::Database,
execution_params::ExecutionParams,
monero, network,
@ -202,7 +203,10 @@ impl EventLoop {
let btc_amount = quote_request.btc_amount;
let xmr_amount = btc_amount.as_btc() * RATE as f64;
let xmr_amount = monero::Amount::from_monero(xmr_amount)?;
let quote_response = QuoteResponse { xmr_amount };
let quote_response = QuoteResponse {
xmr_amount,
tx_fee_rate: FEE_RATE,
};
self.swarm
.send_quote_response(channel, quote_response)

View File

@ -29,6 +29,7 @@ pub enum OutEvent {
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct QuoteResponse {
pub xmr_amount: monero::Amount,
pub tx_fee_rate: u64,
}
impl From<RequestResponseEvent<QuoteRequest, QuoteResponse>> for OutEvent {

View File

@ -79,6 +79,7 @@ pub struct State0 {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc: bitcoin::Amount,
xmr: monero::Amount,
btc_fee_rate: u64,
cancel_timelock: CancelTimelock,
punish_timelock: PunishTimelock,
refund_address: bitcoin::Address,
@ -86,10 +87,12 @@ pub struct State0 {
}
impl State0 {
#[allow(clippy::too_many_arguments)]
pub fn new<R: RngCore + CryptoRng>(
rng: &mut R,
btc: bitcoin::Amount,
xmr: monero::Amount,
btc_fee_rate: u64,
cancel_timelock: CancelTimelock,
punish_timelock: PunishTimelock,
refund_address: bitcoin::Address,
@ -107,6 +110,7 @@ impl State0 {
v_b,
btc,
xmr,
btc_fee_rate,
dleq_proof_s_b,
cancel_timelock,
punish_timelock,
@ -559,6 +563,12 @@ impl State4 {
pub fn tx_lock_id(&self) -> bitcoin::Txid {
self.tx_lock.txid()
}
pub fn tx_cancel_id(&self) -> bitcoin::Txid {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
tx_cancel.txid()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]

View File

@ -390,6 +390,7 @@ pub async fn request_quote_and_setup(
&mut OsRng,
btc_amount,
quote_response.xmr_amount,
quote_response.tx_fee_rate,
execution_params.bitcoin_cancel_timelock,
execution_params.bitcoin_punish_timelock,
bitcoin_refund_address,

View File

@ -139,7 +139,7 @@ impl TestContext {
assert_eq!(
btc_balance_after_swap,
self.alice_starting_balances.btc + self.btc_amount
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
- bitcoin::Amount::from_sat(bitcoin::FEE_RATE)
);
let xmr_balance_after_swap = self
@ -193,7 +193,7 @@ impl TestContext {
assert_eq!(
btc_balance_after_swap,
self.alice_starting_balances.btc + self.btc_amount
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
- bitcoin::Amount::from_sat(2 * bitcoin::FEE_RATE)
);
let xmr_balance_after_swap = self
@ -265,12 +265,12 @@ impl TestContext {
let alice_submitted_cancel = btc_balance_after_swap
== self.bob_starting_balances.btc
- lock_tx_bitcoin_fee
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
- bitcoin::Amount::from_sat(bitcoin::FEE_RATE);
let bob_submitted_cancel = btc_balance_after_swap
== self.bob_starting_balances.btc
- lock_tx_bitcoin_fee
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
- bitcoin::Amount::from_sat(2 * bitcoin::FEE_RATE);
// The cancel tx can be submitted by both Alice and Bob.
// Since we cannot be sure who submitted it we have to assert accordingly