Ensure that constant weights do not go out of sync with code.

This commit is contained in:
Philipp Hoenisch 2021-05-03 17:35:41 +10:00
parent 7fe9087bc2
commit dc6ab0fa52
No known key found for this signature in database
GPG Key ID: E5F8E74C672BC666
11 changed files with 182 additions and 52 deletions

View File

@ -37,11 +37,6 @@ use serde::{Deserialize, Serialize};
use sha2::Sha256; use sha2::Sha256;
use std::str::FromStr; use std::str::FromStr;
pub use crate::bitcoin::cancel::ESTIMATED_WEIGHT as TX_CANCEL_ESTIMATED_WEIGHT;
pub use crate::bitcoin::punish::ESTIMATED_WEIGHT as TX_PUNISH_ESTIMATED_WEIGHT;
pub use crate::bitcoin::redeem::ESTIMATED_WEIGHT as TX_REDEEM_ESTIMATED_WEIGHT;
pub use crate::bitcoin::refund::ESTIMATED_WEIGHT as TX_REFUND_ESTIMATED_WEIGHT;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct SecretKey { pub struct SecretKey {
inner: Scalar, inner: Scalar,
@ -259,6 +254,12 @@ pub struct NotThreeWitnesses(usize);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::bitcoin::wallet::EstimateFeeRate;
use crate::env::{GetConfig, Regtest};
use crate::protocol::{alice, bob};
use bdk::FeeRate;
use rand::rngs::OsRng;
use uuid::Uuid;
#[test] #[test]
fn lock_confirmations_le_to_cancel_timelock_no_timelock_expired() { fn lock_confirmations_le_to_cancel_timelock_no_timelock_expired() {
@ -304,4 +305,120 @@ mod tests {
assert_eq!(expired_timelock, ExpiredTimelocks::Punish) assert_eq!(expired_timelock, ExpiredTimelocks::Punish)
} }
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]
async fn calculate_transaction_weights() {
let alice_wallet = Wallet::new_funded(Amount::ONE_BTC.as_sat(), StaticFeeRate {});
let bob_wallet = Wallet::new_funded(Amount::ONE_BTC.as_sat(), StaticFeeRate {});
let spending_fee = Amount::from_sat(1_000);
let btc_amount = Amount::from_sat(500_000);
let xmr_amount = crate::monero::Amount::from_piconero(10000);
let tx_redeem_fee = alice_wallet.estimate_fee(TxRedeem::weight()).await.unwrap();
let tx_punish_fee = alice_wallet.estimate_fee(TxPunish::weight()).await.unwrap();
let redeem_address = alice_wallet.new_address().await.unwrap();
let punish_address = alice_wallet.new_address().await.unwrap();
let config = Regtest::get_config();
let alice_state0 = alice::State0::new(
btc_amount,
xmr_amount,
config,
redeem_address,
punish_address,
tx_redeem_fee,
tx_punish_fee,
&mut OsRng,
)
.unwrap();
let bob_state0 = bob::State0::new(
Uuid::new_v4(),
&mut OsRng,
btc_amount,
xmr_amount,
config.bitcoin_cancel_timelock,
config.bitcoin_punish_timelock,
bob_wallet.new_address().await.unwrap(),
config.monero_finality_confirmations,
spending_fee,
spending_fee,
);
let message0 = bob_state0.next_message();
let (_, alice_state1) = alice_state0.receive(message0).unwrap();
let alice_message1 = alice_state1.next_message();
let bob_state1 = bob_state0
.receive(&bob_wallet, alice_message1)
.await
.unwrap();
let bob_message2 = bob_state1.next_message();
let alice_state2 = alice_state1.receive(bob_message2).unwrap();
let alice_message3 = alice_state2.next_message();
let bob_state2 = bob_state1.receive(alice_message3).unwrap();
let bob_message4 = bob_state2.next_message();
let alice_state3 = alice_state2.receive(bob_message4).unwrap();
let (bob_state3, _tx_lock) = bob_state2.lock_btc().await.unwrap();
let bob_state4 = bob_state3.xmr_locked(monero_rpc::wallet::BlockHeight { height: 0 });
let encrypted_signature = bob_state4.tx_redeem_encsig();
let bob_state6 = bob_state4.cancel();
let cancel_transaction = alice_state3.signed_cancel_transaction().unwrap();
let punish_transaction = alice_state3.signed_punish_transaction().unwrap();
let redeem_transaction = alice_state3
.signed_redeem_transaction(encrypted_signature)
.unwrap();
let refund_transaction = bob_state6.signed_refund_transaction().unwrap();
assert_weight(
redeem_transaction.get_weight(),
TxRedeem::weight(),
"TxRedeem",
);
assert_weight(
cancel_transaction.get_weight(),
TxCancel::weight(),
"TxCancel",
);
assert_weight(
punish_transaction.get_weight(),
TxPunish::weight(),
"TxPunish",
);
assert_weight(
refund_transaction.get_weight(),
TxRefund::weight(),
"TxRefund",
);
}
// Weights fluctuate -+2 wu assuming because of the length of the signatures.
fn assert_weight(is_weight: usize, expected_weight: usize, tx_name: &str) {
assert!(
is_weight + 1 == expected_weight
|| is_weight + 2 == expected_weight
|| is_weight == expected_weight,
"{} to have weight {}, but was {}",
tx_name,
expected_weight,
is_weight
)
}
} }

View File

@ -13,10 +13,6 @@ use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Add; use std::ops::Add;
// Taken from https://mempool.space/testnet/tx/8da32d6cada4903c7043563c50cacce656a8be9e02f233201996739df5368b1f
// 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).
/// E.g. The timelock expires 10 blocks after the reference transaction is /// E.g. The timelock expires 10 blocks after the reference transaction is
@ -243,6 +239,10 @@ impl TxCancel {
output: vec![tx_out], output: vec![tx_out],
} }
} }
pub fn weight() -> usize {
596
}
} }
impl Watchable for TxCancel { impl Watchable for TxCancel {

View File

@ -7,10 +7,6 @@ use bdk::bitcoin::Script;
use miniscript::{Descriptor, DescriptorTrait}; use miniscript::{Descriptor, DescriptorTrait};
use std::collections::HashMap; use std::collections::HashMap;
// Taken from https://mempool.space/testnet/tx/ed4d60bc1fd172feca444ed3d06cccb90346b9098c2d28d2d034dac66f608f68
// The weight might fluctuate slightly in reality.
pub const ESTIMATED_WEIGHT: usize = 547;
#[derive(Debug)] #[derive(Debug)]
pub struct TxPunish { pub struct TxPunish {
inner: Transaction, inner: Transaction,
@ -77,6 +73,10 @@ impl TxPunish {
Ok(tx_punish) Ok(tx_punish)
} }
pub fn weight() -> usize {
548
}
} }
impl Watchable for TxPunish { impl Watchable for TxPunish {

View File

@ -15,10 +15,6 @@ use miniscript::{Descriptor, DescriptorTrait};
use sha2::Sha256; use sha2::Sha256;
use std::collections::HashMap; use std::collections::HashMap;
// Taken from https://mempool.space/testnet/tx/80c265f5c3862075aab2bd8c94e261b092bc13a34294c6fb4071717fbf46c801
// 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 {
inner: Transaction, inner: Transaction,
@ -137,6 +133,15 @@ impl TxRedeem {
Ok(sig) Ok(sig)
} }
pub fn weight() -> usize {
548
}
#[cfg(test)]
pub fn inner(&self) -> Transaction {
self.inner.clone()
}
} }
impl Watchable for TxRedeem { impl Watchable for TxRedeem {

View File

@ -11,10 +11,6 @@ use ecdsa_fun::Signature;
use miniscript::{Descriptor, DescriptorTrait}; use miniscript::{Descriptor, DescriptorTrait};
use std::collections::HashMap; use std::collections::HashMap;
// Taken from https://mempool.space/testnet/tx/10aef570973bcf524b1a6e8d2eaf3bc1522e776381fc7520fd8987fba96e5424
// The weight might fluctuate slightly in reality.
pub const ESTIMATED_WEIGHT: usize = 547;
#[derive(Debug)] #[derive(Debug)]
pub struct TxRefund { pub struct TxRefund {
inner: Transaction, inner: Transaction,
@ -141,6 +137,10 @@ impl TxRefund {
Ok(sig) Ok(sig)
} }
pub fn weight() -> usize {
548
}
} }
impl Watchable for TxRefund { impl Watchable for TxRefund {

View File

@ -1,5 +1,4 @@
use crate::asb::Rate; use crate::asb::Rate;
use crate::bitcoin::TX_PUNISH_ESTIMATED_WEIGHT;
use crate::database::Database; use crate::database::Database;
use crate::env::Config; use crate::env::Config;
use crate::monero::BalanceTooLow; use crate::monero::BalanceTooLow;
@ -165,12 +164,24 @@ where
// TODO: This should be cleaned up. // TODO: This should be cleaned up.
let tx_redeem_fee = self.bitcoin_wallet let tx_redeem_fee = self.bitcoin_wallet
.estimate_fee(bitcoin::TxRedeem::weight()) .estimate_fee(bitcoin::TxRedeem::weight())
.await.unwrap(); .await;
let tx_punish_fee = self.bitcoin_wallet let tx_punish_fee = self.bitcoin_wallet
.estimate_fee(TX_PUNISH_ESTIMATED_WEIGHT) .estimate_fee(bitcoin::TxPunish::weight())
.await.unwrap(); .await;
let redeem_address = self.bitcoin_wallet.new_address().await.unwrap(); let redeem_address = self.bitcoin_wallet.new_address().await;
let punish_address = self.bitcoin_wallet.new_address().await.unwrap(); let punish_address = self.bitcoin_wallet.new_address().await;
let (tx_redeem_fee, tx_punish_fee, redeem_address, punish_address) = match (
tx_redeem_fee,
tx_punish_fee,
redeem_address,
punish_address,
) {
(Ok(tx_redeem_fee), Ok(tx_punish_fee), Ok(redeem_address), Ok(punish_address)) => {
(tx_redeem_fee, tx_punish_fee, redeem_address, punish_address)
}
_ => { continue; }
};
let state0 = match State0::new( let state0 = match State0::new(
btc, btc,

View File

@ -113,6 +113,7 @@ pub struct State0 {
} }
impl State0 { impl State0 {
#[allow(clippy::too_many_arguments)]
pub fn new<R>( pub fn new<R>(
btc: bitcoin::Amount, btc: bitcoin::Amount,
xmr: monero::Amount, xmr: monero::Amount,

View File

@ -45,7 +45,7 @@ pub async fn refund(
} }
}; };
state6.refund_btc(bitcoin_wallet.as_ref()).await?; state6.publish_refund_btc(bitcoin_wallet.as_ref()).await?;
let state = BobState::BtcRefunded(state6); let state = BobState::BtcRefunded(state6);
let db_state = state.clone().into(); let db_state = state.clone().into();

View File

@ -646,7 +646,15 @@ impl State6 {
Ok(tx_id) Ok(tx_id)
} }
pub async fn refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> { pub async fn publish_refund_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<()> {
let signed_tx_refund = self.signed_refund_transaction()?;
let (_, subscription) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
subscription.wait_until_final().await?;
Ok(())
}
pub fn signed_refund_transaction(&self) -> Result<Transaction> {
let tx_cancel = bitcoin::TxCancel::new( let tx_cancel = bitcoin::TxCancel::new(
&self.tx_lock, &self.tx_lock,
self.cancel_timelock, self.cancel_timelock,
@ -665,12 +673,7 @@ impl State6 {
let signed_tx_refund = let signed_tx_refund =
tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?;
Ok(signed_tx_refund)
let (_, subscription) = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
subscription.wait_until_final().await?;
Ok(())
} }
pub fn tx_lock_id(&self) -> bitcoin::Txid { pub fn tx_lock_id(&self) -> bitcoin::Txid {

View File

@ -1,4 +1,4 @@
use crate::bitcoin::ExpiredTimelocks; use crate::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund};
use crate::database::Swap; use crate::database::Swap;
use crate::env::Config; use crate::env::Config;
use crate::protocol::bob; use crate::protocol::bob;
@ -66,12 +66,8 @@ 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 let tx_refund_fee = bitcoin_wallet.estimate_fee(TxRefund::weight()).await?;
.estimate_fee(bitcoin::TX_REFUND_ESTIMATED_WEIGHT) let tx_cancel_fee = bitcoin_wallet.estimate_fee(TxCancel::weight()).await?;
.await?;
let tx_cancel_fee = bitcoin_wallet
.estimate_fee(bitcoin::TX_CANCEL_ESTIMATED_WEIGHT)
.await?;
let state2 = request_price_and_setup( let state2 = request_price_and_setup(
swap_id, swap_id,
@ -255,7 +251,7 @@ async fn next_state(
); );
} }
ExpiredTimelocks::Cancel => { ExpiredTimelocks::Cancel => {
state.refund_btc(bitcoin_wallet).await?; state.publish_refund_btc(bitcoin_wallet).await?;
BobState::BtcRefunded(state) BobState::BtcRefunded(state)
} }
ExpiredTimelocks::Punish => BobState::BtcPunished { ExpiredTimelocks::Punish => BobState::BtcPunished {

View File

@ -14,10 +14,7 @@ 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::{ use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund};
CancelTimelock, PunishTimelock, TX_CANCEL_ESTIMATED_WEIGHT, TX_PUNISH_ESTIMATED_WEIGHT,
TX_REDEEM_ESTIMATED_WEIGHT, TX_REFUND_ESTIMATED_WEIGHT,
};
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;
@ -637,12 +634,12 @@ impl TestContext {
let cancel_fee = self let cancel_fee = self
.alice_bitcoin_wallet .alice_bitcoin_wallet
.estimate_fee(TX_CANCEL_ESTIMATED_WEIGHT) .estimate_fee(TxCancel::weight())
.await .await
.expect("To estimate fee correctly"); .expect("To estimate fee correctly");
let refund_fee = self let refund_fee = self
.alice_bitcoin_wallet .alice_bitcoin_wallet
.estimate_fee(TX_REFUND_ESTIMATED_WEIGHT) .estimate_fee(TxRefund::weight())
.await .await
.expect("To estimate fee correctly"); .expect("To estimate fee correctly");
@ -685,7 +682,7 @@ impl TestContext {
async fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount { async fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount {
let fee = self let fee = self
.alice_bitcoin_wallet .alice_bitcoin_wallet
.estimate_fee(TX_REDEEM_ESTIMATED_WEIGHT) .estimate_fee(TxRedeem::weight())
.await .await
.expect("To estimate fee correctly"); .expect("To estimate fee correctly");
self.alice_starting_balances.btc + self.btc_amount - fee self.alice_starting_balances.btc + self.btc_amount - fee
@ -728,12 +725,12 @@ impl TestContext {
async fn alice_punished_btc_balance(&self) -> bitcoin::Amount { async fn alice_punished_btc_balance(&self) -> bitcoin::Amount {
let cancel_fee = self let cancel_fee = self
.alice_bitcoin_wallet .alice_bitcoin_wallet
.estimate_fee(TX_CANCEL_ESTIMATED_WEIGHT) .estimate_fee(TxCancel::weight())
.await .await
.expect("To estimate fee correctly"); .expect("To estimate fee correctly");
let punish_fee = self let punish_fee = self
.alice_bitcoin_wallet .alice_bitcoin_wallet
.estimate_fee(TX_PUNISH_ESTIMATED_WEIGHT) .estimate_fee(TxPunish::weight())
.await .await
.expect("To estimate fee correctly"); .expect("To estimate fee correctly");
self.alice_starting_balances.btc + self.btc_amount - cancel_fee - punish_fee self.alice_starting_balances.btc + self.btc_amount - cancel_fee - punish_fee