Browse Source

Handle fees properly

bdk-0.11
Lucas Soriano del Pino 3 years ago
parent
commit
9dcc109e9d
No known key found for this signature in database GPG Key ID: EE611E973A1530E7
  1. 109
      cfd_protocol/src/lib.rs

109
cfd_protocol/src/lib.rs

@ -14,15 +14,18 @@ use bdk::descriptor::Descriptor;
use bdk::miniscript::descriptor::Wsh;
use bdk::miniscript::DescriptorTrait;
use bdk::wallet::AddressIndex;
use bdk::FeeRate;
use bitcoin::PrivateKey;
use itertools::Itertools;
use secp256k1_zkp::{self, schnorrsig, EcdsaAdaptorSignature, SecretKey, Signature, SECP256K1};
use std::collections::HashMap;
/// In satoshi per vbyte.
const MIN_RELAY_FEE: u64 = 1;
const SATS_PER_VBYTE: f64 = 1.0;
/// In satoshi.
///
/// FIXME: Use Script::dust_value instead.
const P2PKH_DUST_LIMIT: u64 = 546;
pub trait WalletExt {
@ -37,6 +40,7 @@ where
let mut builder = self.build_tx();
builder
.ordering(bdk::wallet::tx_builder::TxOrdering::Bip69Lexicographic)
.fee_rate(FeeRate::from_sat_per_vb(1.0))
.add_recipient(Script::new(), amount.as_sat());
let (lock_psbt, _) = builder.finish()?;
let address = self.get_address(AddressIndex::New)?.address;
@ -140,7 +144,7 @@ pub fn build_cfd_transactions(
})
}
fn lock_descriptor(maker_pk: PublicKey, taker_pk: PublicKey) -> Descriptor<PublicKey> {
pub fn lock_descriptor(maker_pk: PublicKey, taker_pk: PublicKey) -> Descriptor<PublicKey> {
const MINISCRIPT_TEMPLATE: &str = "c:and_v(v:pk(A),pk_k(B))";
let maker_pk = ToHex::to_hex(&maker_pk.key);
@ -232,6 +236,10 @@ pub fn punish_transaction(
publish_them_pk: PublicKey,
revoked_commit_tx: &Transaction,
) -> Result<Transaction> {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 219.5 + (3.0 * 3.0) / 4.0;
let input = revoked_commit_tx
.input
.clone()
@ -278,7 +286,7 @@ pub fn punish_transaction(
output: vec![output],
};
let fee = tx.get_size() * MIN_RELAY_FEE as usize;
let fee = SIGNED_VBYTES * SATS_PER_VBYTE;
tx.output[0].value = commit_amount - fee as u64;
tx
@ -514,6 +522,10 @@ struct ContractExecutionTransaction {
}
impl ContractExecutionTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 175.25 + (3.0 * 2.0) / 4.0;
fn new(
commit_tx: &CommitTransaction,
payout: &Payout,
@ -534,7 +546,9 @@ impl ContractExecutionTransaction {
output: payout.as_txouts(maker_address, taker_address),
};
let fee = tx.get_size() * MIN_RELAY_FEE as usize;
let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE;
fee += commit_tx.fee() as f64;
let payout = payout.with_updated_fee(Amount::from_sat(fee as u64))?;
tx.output = payout.as_txouts(maker_address, taker_address);
@ -578,6 +592,10 @@ struct RefundTransaction {
}
impl RefundTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 206.5 + (3.0 * 2.0) / 4.0;
fn new(
commit_tx: &CommitTransaction,
relative_locktime_in_blocks: u32,
@ -609,9 +627,10 @@ impl RefundTransaction {
output: vec![maker_output, taker_output],
};
let fee = tx.get_size() as u64 * MIN_RELAY_FEE;
tx.output[0].value -= fee;
tx.output[1].value -= fee;
let mut fee = Self::SIGNED_VBYTES * SATS_PER_VBYTE;
fee += commit_tx.fee() as f64;
tx.output[0].value -= (fee / 2.0) as u64;
tx.output[1].value -= (fee / 2.0) as u64;
let commit_output_descriptor = commit_tx.descriptor();
@ -641,9 +660,14 @@ struct CommitTransaction {
amount: Amount,
sighash: SigHash,
lock_descriptor: Descriptor<PublicKey>,
fee: u64,
}
impl CommitTransaction {
/// Expected size of signed transaction in virtual bytes, plus a
/// buffer to account for different signature lengths.
const SIGNED_VBYTES: f64 = 148.5 + (3.0 * 2.0) / 4.0;
fn new(
lock_tx: &LockTransaction,
(maker_own_pk, maker_rev_pk, maker_publish_pk): (PublicKey, PublicKey, PublicKey),
@ -675,7 +699,8 @@ impl CommitTransaction {
input: vec![lock_input],
output: vec![output],
};
let fee = inner.get_size() * MIN_RELAY_FEE as usize;
let fee = (Self::SIGNED_VBYTES * SATS_PER_VBYTE as f64) as u64;
let commit_tx_amount = lock_tx_amount - fee as u64;
inner.output[0].value = commit_tx_amount;
@ -692,6 +717,7 @@ impl CommitTransaction {
lock_descriptor: lock_tx.descriptor(),
amount: Amount::from_sat(commit_tx_amount),
sighash,
fee,
})
}
@ -726,6 +752,10 @@ impl CommitTransaction {
fn descriptor(&self) -> Descriptor<PublicKey> {
self.descriptor.clone()
}
fn fee(&self) -> u64 {
self.fee
}
}
#[derive(Debug, Clone)]
@ -819,6 +849,16 @@ impl LockTransaction {
}
}
trait TransactionExt {
fn get_virtual_size(&self) -> f64;
}
impl TransactionExt for bitcoin::Transaction {
fn get_virtual_size(&self) -> f64 {
self.get_weight() as f64 / 4.0
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -1040,7 +1080,10 @@ mod tests {
)
.unwrap();
let signed_lock_tx = signed_lock_tx.extract_tx();
// verify commit transaction
let commit_tx = maker_cfd_txs.commit.0;
let maker_sig = maker_cfd_txs.commit.1.decrypt(&taker_publish_sk).unwrap();
let taker_sig = taker_cfd_txs.commit.1.decrypt(&maker_publish_sk).unwrap();
@ -1050,7 +1093,9 @@ mod tests {
(maker_pk, maker_sig),
(taker_pk, taker_sig),
)
.expect("To be signed");
.unwrap();
check_tx_fee(&[&signed_lock_tx], &signed_commit_tx).expect("correct fees for commit tx");
lock_descriptor
.address(Network::Regtest)
@ -1063,7 +1108,7 @@ mod tests {
)
.expect("valid signed commit transaction");
// verify commit transaction
// verify refund transaction
let maker_sig = maker_cfd_txs.refund.1;
let taker_sig = taker_cfd_txs.refund.1;
@ -1073,7 +1118,9 @@ mod tests {
(maker_pk, maker_sig),
(taker_pk, taker_sig),
)
.expect("To be signed");
.unwrap();
check_tx_fee(&[&signed_commit_tx], &signed_refund_tx).expect("correct fees for refund tx");
commit_descriptor
.address(Network::Regtest)
@ -1117,6 +1164,8 @@ mod tests {
(taker_pk, taker_sig),
)?;
check_tx_fee(&[&signed_commit_tx], &signed_cet).expect("correct fees for cet");
commit_descriptor
.address(Network::Regtest)
.expect("can derive address from descriptor")
@ -1143,6 +1192,8 @@ mod tests {
)
.unwrap();
check_tx_fee(&[&signed_commit_tx], &punish_tx).expect("correct fees for punish tx");
commit_descriptor
.address(Network::Regtest)
.expect("can derive address from descriptor")
@ -1219,6 +1270,42 @@ mod tests {
);
}
fn check_tx_fee(input_txs: &[&Transaction], spend_tx: &Transaction) -> Result<()> {
let input_amount = spend_tx
.input
.iter()
.try_fold::<_, _, Result<_>>(0, |acc, input| {
let value = input_txs
.iter()
.find_map(|tx| {
(tx.txid() == input.previous_output.txid)
.then(|| tx.output[input.previous_output.vout as usize].value)
})
.with_context(|| {
format!(
"spend tx input {} not found in input_txs",
input.previous_output
)
})
.context("foo")?;
Ok(acc + value)
})?;
let output_amount = spend_tx
.output
.iter()
.fold(0, |acc, output| acc + output.value);
let fee = input_amount - output_amount;
let min_relay_fee = spend_tx.get_virtual_size();
if (dbg!(fee) as f64) < dbg!(min_relay_fee) {
bail!("min relay fee not met, {} < {}", fee, min_relay_fee)
}
Ok(())
}
fn build_wallet<R>(
rng: &mut R,
utxo_amount: Amount,

Loading…
Cancel
Save