236: Some wallet cleanup + watch for deposit r=thomaseizinger a=thomaseizinger



Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
bors[bot] 2021-02-28 23:26:31 +00:00 committed by GitHub
commit 9a32f7d405
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 109 additions and 117 deletions

View File

@ -15,10 +15,11 @@
use anyhow::{Context, Result};
use prettytable::{row, Table};
use reqwest::Url;
use std::{path::Path, sync::Arc};
use std::{path::Path, sync::Arc, time::Duration};
use structopt::StructOpt;
use swap::{
bitcoin,
bitcoin::Amount,
cli::{
command::{Arguments, Cancel, Command, Refund, Resume},
config::{read_config, Config},
@ -35,7 +36,7 @@ use swap::{
seed::Seed,
trace::init_tracing,
};
use tracing::{error, info, warn};
use tracing::{debug, error, info, warn};
use tracing_subscriber::filter::LevelFilter;
use uuid::Uuid;
@ -82,7 +83,6 @@ async fn main() -> Result<()> {
Command::BuyXmr {
alice_peer_id,
alice_addr,
send_bitcoin,
} => {
let (bitcoin_wallet, monero_wallet) = init_wallets(
config,
@ -94,22 +94,30 @@ async fn main() -> Result<()> {
)
.await?;
let swap_id = Uuid::new_v4();
info!(
"Swap buy XMR with {} started with ID {}",
send_bitcoin, swap_id
);
info!(
"BTC deposit address: {}",
// TODO: Also wait for more funds if balance < dust
if bitcoin_wallet.balance().await? == Amount::ZERO {
debug!(
"Waiting for BTC at address {}",
bitcoin_wallet.new_address().await?
);
while bitcoin_wallet.balance().await? == Amount::ZERO {
bitcoin_wallet.sync_wallet().await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
debug!("Received {}", bitcoin_wallet.balance().await?);
}
let send_bitcoin = bitcoin_wallet.max_giveable().await?;
info!("Swapping {} ...", send_bitcoin);
let bob_factory = Builder::new(
seed,
db,
swap_id,
Uuid::new_v4(),
Arc::new(bitcoin_wallet),
Arc::new(monero_wallet),
alice_addr,

View File

@ -22,9 +22,7 @@ pub use wallet::Wallet;
use crate::execution_params::ExecutionParams;
use ::bitcoin::{
hashes::{hex::ToHex, Hash},
secp256k1,
util::psbt::PartiallySignedTransaction,
SigHash,
secp256k1, SigHash,
};
use anyhow::{anyhow, bail, Result};
use async_trait::async_trait;
@ -203,15 +201,6 @@ pub fn build_shared_output_descriptor(A: Point, B: Point) -> Descriptor<bitcoin:
Descriptor::Wsh(Wsh::new(miniscript).expect("a valid descriptor"))
}
#[async_trait]
pub trait BuildTxLockPsbt {
async fn build_tx_lock_psbt(
&self,
output_address: Address,
output_amount: Amount,
) -> Result<PartiallySignedTransaction>;
}
#[async_trait]
pub trait SignTxLock {
async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result<Transaction>;
@ -251,11 +240,6 @@ pub trait GetRawTransaction {
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
}
#[async_trait]
pub trait GetNetwork {
async fn get_network(&self) -> Network;
}
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
let adaptor = Adaptor::<HashTranscript<Sha256>, Deterministic<Sha256>>::default();

View File

@ -58,6 +58,7 @@ pub struct TxCancel {
inner: Transaction,
digest: SigHash,
pub(in crate::bitcoin) output_descriptor: Descriptor<::bitcoin::PublicKey>,
lock_output_descriptor: Descriptor<::bitcoin::PublicKey>,
}
impl TxCancel {
@ -99,6 +100,7 @@ impl TxCancel {
inner: transaction,
digest,
output_descriptor: cancel_output_descriptor,
lock_output_descriptor: tx_lock.output_descriptor.clone(),
}
}
@ -120,7 +122,6 @@ impl TxCancel {
pub fn add_signatures(
self,
tx_lock: &TxLock,
(A, sig_a): (PublicKey, Signature),
(B, sig_b): (PublicKey, Signature),
) -> Result<Transaction> {
@ -144,8 +145,7 @@ impl TxCancel {
};
let mut tx_cancel = self.inner;
tx_lock
.output_descriptor
self.lock_output_descriptor
.satisfy(&mut tx_cancel.input[0], satisfier)?;
Ok(tx_cancel)

View File

@ -1,6 +1,5 @@
use crate::bitcoin::{
build_shared_output_descriptor, Address, Amount, BuildTxLockPsbt, GetNetwork, PublicKey,
Transaction, TX_FEE,
build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, Wallet, TX_FEE,
};
use ::bitcoin::{util::psbt::PartiallySignedTransaction, OutPoint, TxIn, TxOut, Txid};
use anyhow::Result;
@ -14,16 +13,13 @@ pub struct TxLock {
}
impl TxLock {
pub async fn new<W>(wallet: &W, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self>
where
W: BuildTxLockPsbt + GetNetwork,
{
pub async fn new(wallet: &Wallet, amount: Amount, A: PublicKey, B: PublicKey) -> Result<Self> {
let lock_output_descriptor = build_shared_output_descriptor(A.0, B.0);
let address = lock_output_descriptor
.address(wallet.get_network().await)
.expect("can derive address from descriptor");
let psbt = wallet.build_tx_lock_psbt(address, amount).await?;
let psbt = wallet.send_to_address(address, amount).await?;
Ok(Self {
inner: psbt,

View File

@ -2,13 +2,14 @@ use crate::bitcoin::{Address, PublicKey, PunishTimelock, Transaction, TxCancel};
use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType};
use anyhow::Result;
use ecdsa_fun::Signature;
use miniscript::DescriptorTrait;
use miniscript::{Descriptor, DescriptorTrait};
use std::collections::HashMap;
#[derive(Debug)]
pub struct TxPunish {
inner: Transaction,
digest: SigHash,
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
}
impl TxPunish {
@ -29,6 +30,7 @@ impl TxPunish {
Self {
inner: tx_punish,
digest,
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
}
}
@ -38,7 +40,6 @@ impl TxPunish {
pub fn add_signatures(
self,
tx_cancel: &TxCancel,
(A, sig_a): (PublicKey, Signature),
(B, sig_b): (PublicKey, Signature),
) -> Result<Transaction> {
@ -62,8 +63,7 @@ impl TxPunish {
};
let mut tx_punish = self.inner;
tx_cancel
.output_descriptor
self.cancel_output_descriptor
.satisfy(&mut tx_punish.input[0], satisfier)?;
Ok(tx_punish)

View File

@ -5,13 +5,14 @@ use crate::bitcoin::{
use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType, Txid};
use anyhow::{bail, Context, Result};
use ecdsa_fun::Signature;
use miniscript::DescriptorTrait;
use miniscript::{Descriptor, DescriptorTrait};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct TxRedeem {
inner: Transaction,
digest: SigHash,
lock_output_descriptor: Descriptor<::bitcoin::PublicKey>,
}
impl TxRedeem {
@ -30,6 +31,7 @@ impl TxRedeem {
Self {
inner: tx_redeem,
digest,
lock_output_descriptor: tx_lock.output_descriptor.clone(),
}
}
@ -43,7 +45,6 @@ impl TxRedeem {
pub fn add_signatures(
self,
tx_lock: &TxLock,
(A, sig_a): (PublicKey, Signature),
(B, sig_b): (PublicKey, Signature),
) -> Result<Transaction> {
@ -67,8 +68,7 @@ impl TxRedeem {
};
let mut tx_redeem = self.inner;
tx_lock
.output_descriptor
self.lock_output_descriptor
.satisfy(&mut tx_redeem.input[0], satisfier)?;
Ok(tx_redeem)

View File

@ -5,13 +5,14 @@ use crate::bitcoin::{
use ::bitcoin::{util::bip143::SigHashCache, SigHash, SigHashType, Txid};
use anyhow::{bail, Context, Result};
use ecdsa_fun::Signature;
use miniscript::DescriptorTrait;
use miniscript::{Descriptor, DescriptorTrait};
use std::collections::HashMap;
#[derive(Debug)]
pub struct TxRefund {
inner: Transaction,
digest: SigHash,
cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>,
}
impl TxRefund {
@ -28,6 +29,7 @@ impl TxRefund {
Self {
inner: tx_punish,
digest,
cancel_output_descriptor: tx_cancel.output_descriptor.clone(),
}
}
@ -41,7 +43,6 @@ impl TxRefund {
pub fn add_signatures(
self,
tx_cancel: &TxCancel,
(A, sig_a): (PublicKey, Signature),
(B, sig_b): (PublicKey, Signature),
) -> Result<Transaction> {
@ -65,8 +66,7 @@ impl TxRefund {
};
let mut tx_refund = self.inner;
tx_cancel
.output_descriptor
self.cancel_output_descriptor
.satisfy(&mut tx_refund.input[0], satisfier)?;
Ok(tx_refund)

View File

@ -1,8 +1,8 @@
use crate::{
bitcoin::{
timelocks::BlockHeight, Address, Amount, BroadcastSignedTransaction, BuildTxLockPsbt,
GetBlockHeight, GetNetwork, GetRawTransaction, SignTxLock, Transaction,
TransactionBlockHeight, TxLock, WaitForTransactionFinality, WatchForRawTransaction,
timelocks::BlockHeight, Address, Amount, BroadcastSignedTransaction, GetBlockHeight,
GetRawTransaction, SignTxLock, Transaction, TransactionBlockHeight, TxLock,
WaitForTransactionFinality, WatchForRawTransaction,
},
execution_params::ExecutionParams,
};
@ -16,6 +16,7 @@ use bdk::{
miniscript::bitcoin::PrivateKey,
FeeRate,
};
use bitcoin::Script;
use reqwest::{Method, Url};
use serde::{Deserialize, Serialize};
use std::{path::Path, sync::Arc, time::Duration};
@ -38,10 +39,9 @@ enum Error {
}
pub struct Wallet {
pub inner: Arc<Mutex<bdk::Wallet<ElectrumBlockchain, bdk::sled::Tree>>>,
pub network: bitcoin::Network,
pub http_url: Url,
pub rpc_url: Url,
inner: Arc<Mutex<bdk::Wallet<ElectrumBlockchain, bdk::sled::Tree>>>,
http_url: Url,
rpc_url: Url,
}
impl Wallet {
@ -70,7 +70,6 @@ impl Wallet {
Ok(Self {
inner: Arc::new(Mutex::new(bdk_wallet)),
network,
http_url: electrum_http_url,
rpc_url: electrum_rpc_url,
})
@ -82,11 +81,9 @@ impl Wallet {
}
pub async fn new_address(&self) -> Result<Address> {
self.inner
.lock()
.await
.get_new_address()
.map_err(Into::into)
let address = self.inner.lock().await.get_new_address()?;
Ok(address)
}
pub async fn get_tx(&self, txid: Txid) -> Result<Option<Transaction>> {
@ -111,29 +108,60 @@ impl Wallet {
}
pub async fn sync_wallet(&self) -> Result<()> {
tracing::debug!("syncing wallet");
self.inner.lock().await.sync(noop_progress(), None)?;
Ok(())
}
}
#[async_trait]
impl BuildTxLockPsbt for Wallet {
async fn build_tx_lock_psbt(
pub async fn send_to_address(
&self,
output_address: Address,
output_amount: Amount,
address: Address,
amount: Amount,
) -> Result<PartiallySignedTransaction> {
tracing::debug!("building tx lock");
let wallet = self.inner.lock().await;
let mut tx_builder = wallet.build_tx();
tx_builder.add_recipient(output_address.script_pubkey(), output_amount.as_sat());
tx_builder.fee_rate(FeeRate::from_sat_per_vb(5.0)); // todo: get actual fee
tx_builder.add_recipient(address.script_pubkey(), amount.as_sat());
tx_builder.fee_rate(self.select_feerate());
let (psbt, _details) = tx_builder.finish()?;
tracing::debug!("tx lock built");
Ok(psbt)
}
/// Calculates the maximum "giveable" amount of this wallet.
///
/// We define this as the maximum amount we can pay to a single output,
/// already accounting for the fees we need to spend to get the
/// transaction confirmed.
pub async fn max_giveable(&self) -> Result<Amount> {
let wallet = self.inner.lock().await;
let mut tx_builder = wallet.build_tx();
// create a dummy script to make the txbuilder pass
// we don't intend to send this transaction, we just want to know the max amount
// we can spend
let dummy_script = Script::default();
tx_builder.set_single_recipient(dummy_script);
tx_builder.drain_wallet();
tx_builder.fee_rate(self.select_feerate());
let (_, details) = tx_builder.finish()?;
let max_giveable = details.sent - details.fees;
Ok(Amount::from_sat(max_giveable))
}
pub async fn get_network(&self) -> bitcoin::Network {
self.inner.lock().await.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)
}
}
#[async_trait]
@ -288,13 +316,6 @@ impl WaitForTransactionFinality for Wallet {
}
}
#[async_trait]
impl GetNetwork for Wallet {
async fn get_network(&self) -> bitcoin::Network {
self.inner.lock().await.network()
}
}
fn tx_status_url(txid: Txid, base_url: &Url) -> Result<Url> {
let url = base_url.join(&format!("tx/{}/status", txid))?;
Ok(url)

View File

@ -1,5 +1,3 @@
use crate::bitcoin;
use anyhow::Result;
use libp2p::{core::Multiaddr, PeerId};
use std::path::PathBuf;
use uuid::Uuid;
@ -32,9 +30,6 @@ pub enum Command {
default_value = DEFAULT_ALICE_MULTIADDR
)]
alice_addr: Multiaddr,
#[structopt(long = "send-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))]
send_bitcoin: bitcoin::Amount,
},
History,
Resume(Resume),
@ -102,8 +97,3 @@ pub enum Refund {
force: bool,
},
}
fn parse_btc(str: &str) -> Result<bitcoin::Amount> {
let amount = bitcoin::Amount::from_str_in(str, ::bitcoin::Denomination::Bitcoin)?;
Ok(amount)
}

View File

@ -205,7 +205,7 @@ impl From<Alice> for AliceState {
let tx_refund = TxRefund::new(&tx_cancel, &state3.refund_address);
AliceState::BtcPunishable {
monero_wallet_restore_blockheight,
tx_refund,
tx_refund: Box::new(tx_refund),
state3: Box::new(state3),
}
}

View File

@ -53,7 +53,7 @@ pub enum AliceState {
},
BtcPunishable {
monero_wallet_restore_blockheight: BlockHeight,
tx_refund: TxRefund,
tx_refund: Box<TxRefund>,
state3: Box<State3>,
},
XmrRefunded,

View File

@ -124,7 +124,7 @@ pub fn build_bitcoin_redeem_transaction(
let sig_b = adaptor.decrypt_signature(&s_a, encrypted_signature);
let tx = tx_redeem
.add_signatures(&tx_lock, (a.public(), sig_a), (B, sig_b))
.add_signatures((a.public(), sig_a), (B, sig_b))
.context("sig_{a,b} are invalid for tx_redeem")?;
Ok(tx)
@ -179,7 +179,7 @@ where
let tx_cancel = tx_cancel
.clone()
.add_signatures(&tx_lock, (a.public(), sig_a), (B, sig_b))
.add_signatures((a.public(), sig_a), (B, sig_b))
.expect("sig_{a,b} to be valid signatures for tx_cancel");
// TODO(Franck): Error handling is delicate, why can't we broadcast?
@ -224,7 +224,7 @@ where
pub fn extract_monero_private_key(
published_refund_tx: bitcoin::Transaction,
tx_refund: TxRefund,
tx_refund: &TxRefund,
s_a: monero::Scalar,
a: bitcoin::SecretKey,
S_b_bitcoin: bitcoin::PublicKey,
@ -261,7 +261,7 @@ pub fn build_bitcoin_punish_transaction(
let sig_b = tx_punish_sig_bob;
let signed_tx_punish = tx_punish
.add_signatures(&tx_cancel, (a.public(), sig_a), (B, sig_b))
.add_signatures((a.public(), sig_a), (B, sig_b))
.expect("sig_{a,b} to be valid signatures for tx_cancel");
Ok(signed_tx_punish)

View File

@ -343,7 +343,7 @@ async fn run_until_internal(
match published_refund_tx {
None => {
let state = AliceState::BtcPunishable {
tx_refund,
tx_refund: Box::new(tx_refund),
state3,
monero_wallet_restore_blockheight,
};
@ -366,7 +366,7 @@ async fn run_until_internal(
Some(published_refund_tx) => {
let spend_key = extract_monero_private_key(
published_refund_tx,
tx_refund,
&tx_refund,
state3.s_a,
state3.a.clone(),
state3.S_b_bitcoin,
@ -445,7 +445,7 @@ async fn run_until_internal(
Either::Left((published_refund_tx, _)) => {
let spend_key = extract_monero_private_key(
published_refund_tx?,
tx_refund,
&tx_refund,
state3.s_a,
state3.a.clone(),
state3.S_b_bitcoin,

View File

@ -1,9 +1,8 @@
use crate::{
bitcoin::{
self, current_epoch, wait_for_cancel_timelock_to_expire, BroadcastSignedTransaction,
BuildTxLockPsbt, CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetNetwork,
GetRawTransaction, PunishTimelock, Transaction, TransactionBlockHeight, TxCancel, Txid,
WatchForRawTransaction,
CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetRawTransaction, PunishTimelock,
Transaction, TransactionBlockHeight, TxCancel, Txid, WatchForRawTransaction,
},
execution_params::ExecutionParams,
monero,
@ -140,10 +139,7 @@ impl State0 {
}
}
pub async fn receive<W>(self, wallet: &W, msg: Message1) -> Result<State1>
where
W: BuildTxLockPsbt + GetNetwork,
{
pub async fn receive(self, wallet: &bitcoin::Wallet, msg: Message1) -> Result<State1> {
let valid = CROSS_CURVE_PROOF_SYSTEM.verify(
&msg.dleq_proof_s_a,
(
@ -459,7 +455,7 @@ impl State4 {
let tx_cancel = tx_cancel
.clone()
.add_signatures(&self.tx_lock, (self.A, sig_a), (self.b.public(), sig_b))
.add_signatures((self.A, sig_a), (self.b.public(), sig_b))
.expect(
"sig_{a,b} to be valid signatures for
tx_cancel",
@ -482,7 +478,7 @@ impl State4 {
let tx_cancel = tx_cancel
.clone()
.add_signatures(&self.tx_lock, (self.A, sig_a), (self.b.public(), sig_b))
.add_signatures((self.A, sig_a), (self.b.public(), sig_b))
.expect(
"sig_{a,b} to be valid signatures for
tx_cancel",
@ -562,11 +558,8 @@ impl State4 {
let sig_a =
adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone());
let signed_tx_refund = tx_refund.add_signatures(
&tx_cancel.clone(),
(self.A, sig_a),
(self.b.public(), sig_b),
)?;
let signed_tx_refund =
tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?;
let txid = bitcoin_wallet
.broadcast_signed_transaction(signed_tx_refund)

View File

@ -16,7 +16,7 @@ pub fn init_tracing(level: LevelFilter) -> Result<()> {
let subscriber = FmtSubscriber::builder()
.with_env_filter(format!(
"swap={},monero_harness={},bitcoin_harness={},http=warn,warp=warn",
level, level, level,
level, level, level
))
.with_writer(std::io::stderr)
.with_ansi(is_terminal)