243: Some cleanup + improve logging of the `swap_cli` r=thomaseizinger a=thomaseizinger

Please see commit messages for details. I've also included a few minor cleanups that I noticed on the way.



Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
bors[bot] 2021-03-02 02:17:49 +00:00 committed by GitHub
commit 8bc918c511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 278 additions and 362 deletions

24
Cargo.lock generated
View File

@ -302,6 +302,12 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c"
[[package]]
name = "big-bytes"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d73a8ae8ce52d09395e4cafc83b5b81c3deb70a97740e907669c8683c4dd50a"
[[package]]
name = "bincode"
version = "1.3.1"
@ -560,6 +566,18 @@ dependencies = [
"zeroize 1.2.0",
]
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"winapi 0.3.9",
]
[[package]]
name = "clap"
version = "2.33.3"
@ -3525,6 +3543,7 @@ dependencies = [
"backoff",
"base64 0.12.3",
"bdk",
"big-bytes",
"bitcoin",
"bitcoin-harness",
"config",
@ -3943,11 +3962,12 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401"
checksum = "8ab8966ac3ca27126141f7999361cc97dd6fb4b71da04c02044fa9045d98bb96"
dependencies = [
"ansi_term 0.12.1",
"chrono",
"lazy_static",
"matchers",
"regex",

View File

@ -20,6 +20,7 @@ atty = "0.2"
backoff = { version = "0.3", features = ["tokio"] }
base64 = "0.12"
bdk = { version = "0.4" }
big-bytes = "1"
bitcoin = { version = "0.26", features = ["rand", "use-serde"] }
config = { version = "0.10", default-features = false, features = ["toml"] }
conquer-once = "0.3"
@ -57,7 +58,7 @@ toml = "0.5"
tracing = { version = "0.1", features = ["attributes"] }
tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] }
tracing-log = "0.1"
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter"] }
tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono"] }
url = { version = "2.1", features = ["serde"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
void = "1"

View File

@ -34,10 +34,9 @@ use swap::{
bob::{cancel::CancelError, Builder},
},
seed::Seed,
trace::init_tracing,
};
use tracing::{debug, error, info, warn};
use tracing_subscriber::filter::LevelFilter;
use tracing::{debug, error, info, warn, Level};
use tracing_subscriber::FmtSubscriber;
use uuid::Uuid;
#[macro_use]
@ -47,17 +46,41 @@ const MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME: &str = "swap-tool-blockchain-mon
#[tokio::main]
async fn main() -> Result<()> {
init_tracing(LevelFilter::DEBUG).expect("initialize tracing");
let args = Arguments::from_args();
let opt = Arguments::from_args();
let is_terminal = atty::is(atty::Stream::Stderr);
let base_subscriber = |level| {
FmtSubscriber::builder()
.with_writer(std::io::stderr)
.with_ansi(is_terminal)
.with_target(false)
.with_env_filter(format!("swap={}", level))
};
let config = match opt.config {
if args.debug {
let subscriber = base_subscriber(Level::DEBUG)
.with_timer(tracing_subscriber::fmt::time::ChronoLocal::with_format(
"%F %T".to_owned(),
))
.finish();
tracing::subscriber::set_global_default(subscriber)?;
} else {
let subscriber = base_subscriber(Level::INFO)
.without_time()
.with_level(false)
.finish();
tracing::subscriber::set_global_default(subscriber)?;
}
let config = match args.config {
Some(config_path) => read_config(config_path)??,
None => Config::testnet(),
};
info!(
"Database and Seed will be stored in directory: {}",
debug!(
"Database and seed will be stored in {}",
config.data.dir.display()
);
@ -79,7 +102,7 @@ async fn main() -> Result<()> {
.run(monero_network, "stagenet.community.xmr.to")
.await?;
match opt.cmd.unwrap_or_default() {
match args.cmd.unwrap_or_default() {
Command::BuyXmr {
alice_peer_id,
alice_addr,
@ -98,8 +121,8 @@ async fn main() -> Result<()> {
// TODO: Also wait for more funds if balance < dust
if bitcoin_wallet.balance().await? == Amount::ZERO {
debug!(
"Waiting for BTC at address {}",
info!(
"Please deposit BTC to {}",
bitcoin_wallet.new_address().await?
);
@ -110,12 +133,15 @@ async fn main() -> Result<()> {
}
debug!("Received {}", bitcoin_wallet.balance().await?);
} else {
info!(
"Still got {} left in wallet, swapping ...",
bitcoin_wallet.balance().await?
);
}
let send_bitcoin = bitcoin_wallet.max_giveable(TxLock::script_size()).await?;
info!("Swapping {} ...", send_bitcoin);
let bob_factory = Builder::new(
seed,
db,
@ -233,7 +259,7 @@ async fn main() -> Result<()> {
cancel_result = cancel => {
match cancel_result? {
Ok((txid, _)) => {
info!("Cancel transaction successfully published with id {}", txid)
debug!("Cancel transaction successfully published with id {}", txid)
}
Err(CancelError::CancelTimelockNotExpiredYet) => error!(
"The Cancel Transaction cannot be published yet, \
@ -318,13 +344,7 @@ async fn init_wallets(
bitcoin_wallet
.sync_wallet()
.await
.expect("Could not sync btc wallet");
let bitcoin_balance = bitcoin_wallet.balance().await?;
info!(
"Connection to Bitcoin wallet succeeded, balance: {}",
bitcoin_balance
);
.context("failed to sync balance of bitcoin wallet")?;
let monero_wallet = monero::Wallet::new(
monero_wallet_rpc_url.clone(),
@ -346,19 +366,16 @@ async fn init_wallets(
monero_wallet_rpc_url
))?;
info!(
debug!(
"Created Monero wallet for blockchain monitoring with name {}",
MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME
);
} else {
info!(
"Opened Monero wallet for blockchain monitoring with name {}",
MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME
);
}
let _test_wallet_connection = monero_wallet.block_height().await?;
info!("The Monero wallet RPC is set up correctly!");
let _test_wallet_connection = monero_wallet
.block_height()
.await
.context("failed to validate connection to monero-wallet-rpc")?;
Ok((bitcoin_wallet, monero_wallet))
}

View File

@ -19,13 +19,11 @@ pub use ::bitcoin::{util::amount::Amount, Address, Network, Transaction, Txid};
pub use ecdsa_fun::{adaptor::EncryptedSignature, fun::Scalar, Signature};
pub use wallet::Wallet;
use crate::execution_params::ExecutionParams;
use ::bitcoin::{
hashes::{hex::ToHex, Hash},
secp256k1, SigHash,
};
use anyhow::{anyhow, bail, Result};
use async_trait::async_trait;
use ecdsa_fun::{
adaptor::{Adaptor, HashTranscript},
fun::Point,
@ -201,45 +199,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 SignTxLock {
async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result<Transaction>;
}
#[async_trait]
pub trait BroadcastSignedTransaction {
async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result<Txid>;
}
#[async_trait]
pub trait WatchForRawTransaction {
async fn watch_for_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
}
#[async_trait]
pub trait WaitForTransactionFinality {
async fn wait_for_transaction_finality(
&self,
txid: Txid,
execution_params: ExecutionParams,
) -> Result<()>;
}
#[async_trait]
pub trait GetBlockHeight {
async fn get_block_height(&self) -> Result<BlockHeight>;
}
#[async_trait]
pub trait TransactionBlockHeight {
async fn transaction_block_height(&self, txid: Txid) -> Result<BlockHeight>;
}
#[async_trait]
pub trait GetRawTransaction {
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction>;
}
pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Result<SecretKey> {
let adaptor = Adaptor::<HashTranscript<Sha256>, Deterministic<Sha256>>::default();
@ -251,25 +210,22 @@ pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Resu
Ok(s)
}
pub async fn poll_until_block_height_is_gte<B>(client: &B, target: BlockHeight) -> Result<()>
where
B: GetBlockHeight,
{
pub async fn poll_until_block_height_is_gte(
client: &crate::bitcoin::Wallet,
target: BlockHeight,
) -> Result<()> {
while client.get_block_height().await? < target {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Ok(())
}
pub async fn current_epoch<W>(
bitcoin_wallet: &W,
pub async fn current_epoch(
bitcoin_wallet: &crate::bitcoin::Wallet,
cancel_timelock: CancelTimelock,
punish_timelock: PunishTimelock,
lock_tx_id: ::bitcoin::Txid,
) -> Result<ExpiredTimelocks>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
) -> Result<ExpiredTimelocks> {
let current_block_height = bitcoin_wallet.get_block_height().await?;
let lock_tx_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await?;
let cancel_timelock_height = lock_tx_height + cancel_timelock;
@ -285,14 +241,11 @@ where
}
}
pub async fn wait_for_cancel_timelock_to_expire<W>(
bitcoin_wallet: &W,
pub async fn wait_for_cancel_timelock_to_expire(
bitcoin_wallet: &crate::bitcoin::Wallet,
cancel_timelock: CancelTimelock,
lock_tx_id: ::bitcoin::Txid,
) -> Result<()>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
) -> Result<()> {
let tx_lock_height = bitcoin_wallet.transaction_block_height(lock_tx_id).await?;
poll_until_block_height_is_gte(bitcoin_wallet, tx_lock_height + cancel_timelock).await?;

View File

@ -1,14 +1,9 @@
use crate::{
bitcoin::{
timelocks::BlockHeight, Address, Amount, BroadcastSignedTransaction, GetBlockHeight,
GetRawTransaction, SignTxLock, Transaction, TransactionBlockHeight, TxLock,
WaitForTransactionFinality, WatchForRawTransaction,
},
bitcoin::{timelocks::BlockHeight, Address, Amount, Transaction},
execution_params::ExecutionParams,
};
use ::bitcoin::{util::psbt::PartiallySignedTransaction, Txid};
use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait;
use backoff::{backoff::Constant as ConstantBackoff, future::retry};
use bdk::{
blockchain::{noop_progress, Blockchain, ElectrumBlockchain},
@ -152,43 +147,43 @@ impl Wallet {
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)
}
}
/// Broadcast the given transaction to the network and emit a log statement
/// if done so successfully.
pub async fn broadcast(&self, transaction: Transaction, kind: &str) -> Result<Txid> {
let txid = transaction.txid();
#[async_trait]
impl SignTxLock for Wallet {
async fn sign_tx_lock(&self, tx_lock: TxLock) -> Result<Transaction> {
let txid = tx_lock.txid();
tracing::debug!("signing tx lock: {}", txid);
let psbt = PartiallySignedTransaction::from(tx_lock);
self.inner
.lock()
.await
.broadcast(transaction)
.with_context(|| {
format!("failed to broadcast Bitcoin {} transaction {}", kind, txid)
})?;
tracing::info!("Published Bitcoin {} transaction as {}", txid, kind);
Ok(txid)
}
pub async fn sign_and_finalize(&self, psbt: PartiallySignedTransaction) -> Result<Transaction> {
let (signed_psbt, finalized) = self.inner.lock().await.sign(psbt, None)?;
if !finalized {
bail!("Could not finalize TxLock psbt")
bail!("PSBT is not finalized")
}
let tx = signed_psbt.extract_tx();
tracing::debug!("signed tx lock: {}", txid);
Ok(tx)
}
}
#[async_trait]
impl BroadcastSignedTransaction for Wallet {
async fn broadcast_signed_transaction(&self, transaction: Transaction) -> Result<Txid> {
tracing::debug!("attempting to broadcast tx: {}", transaction.txid());
self.inner.lock().await.broadcast(transaction.clone())?;
tracing::info!("Bitcoin tx broadcasted! TXID = {}", transaction.txid());
Ok(transaction.txid())
pub async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
self.get_tx(txid)
.await?
.ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid))
}
}
#[async_trait]
impl WatchForRawTransaction for Wallet {
async fn watch_for_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
pub async fn watch_for_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
tracing::debug!("watching for tx: {}", txid);
let tx = retry(ConstantBackoff::new(Duration::from_secs(1)), || async {
let client = Client::new(self.rpc_url.as_ref())
@ -209,20 +204,8 @@ impl WatchForRawTransaction for Wallet {
Ok(tx)
}
}
#[async_trait]
impl GetRawTransaction for Wallet {
async fn get_raw_transaction(&self, txid: Txid) -> Result<Transaction> {
self.get_tx(txid)
.await?
.ok_or_else(|| anyhow!("Could not get raw tx with id: {}", txid))
}
}
#[async_trait]
impl GetBlockHeight for Wallet {
async fn get_block_height(&self) -> Result<BlockHeight> {
pub async fn get_block_height(&self) -> Result<BlockHeight> {
let url = blocks_tip_height_url(&self.http_url)?;
let height = retry(ConstantBackoff::new(Duration::from_secs(1)), || async {
let height = reqwest::Client::new()
@ -242,11 +225,8 @@ impl GetBlockHeight for Wallet {
Ok(BlockHeight::new(height))
}
}
#[async_trait]
impl TransactionBlockHeight for Wallet {
async fn transaction_block_height(&self, txid: Txid) -> Result<BlockHeight> {
pub async fn transaction_block_height(&self, txid: Txid) -> Result<BlockHeight> {
let url = tx_status_url(txid, &self.http_url)?;
#[derive(Serialize, Deserialize, Debug, Clone)]
struct TransactionStatus {
@ -276,11 +256,8 @@ impl TransactionBlockHeight for Wallet {
Ok(BlockHeight::new(height))
}
}
#[async_trait]
impl WaitForTransactionFinality for Wallet {
async fn wait_for_transaction_finality(
pub async fn wait_for_transaction_finality(
&self,
txid: Txid,
execution_params: ExecutionParams,
@ -310,6 +287,13 @@ impl WaitForTransactionFinality for Wallet {
Ok(())
}
/// 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)
}
}
fn tx_status_url(txid: Txid, base_url: &Url) -> Result<Url> {

View File

@ -14,6 +14,9 @@ pub struct Arguments {
)]
pub config: Option<PathBuf>,
#[structopt(long, help = "Activate debug logging.")]
pub debug: bool,
#[structopt(subcommand)]
pub cmd: Option<Command>,
}

View File

@ -6,7 +6,7 @@ use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use tracing::info;
use tracing::debug;
use url::Url;
pub const DEFAULT_ELECTRUM_HTTP_URL: &str = "https://blockstream.info/testnet/api/";
@ -66,7 +66,7 @@ pub struct ConfigNotInitialized {}
pub fn read_config(config_path: PathBuf) -> Result<Result<Config, ConfigNotInitialized>> {
if config_path.exists() {
info!(
debug!(
"Using config file at default path: {}",
config_path.display()
);

View File

@ -190,7 +190,7 @@ pub trait Transfer {
public_spend_key: PublicKey,
public_view_key: PublicViewKey,
amount: Amount,
) -> Result<(TransferProof, Amount)>;
) -> Result<TransferProof>;
}
#[async_trait]

View File

@ -12,11 +12,7 @@ use monero_rpc::{
wallet,
wallet::{BlockHeight, Refreshed},
};
use std::{
str::FromStr,
sync::{atomic::Ordering, Arc},
time::Duration,
};
use std::{str::FromStr, sync::atomic::Ordering, time::Duration};
use tokio::sync::Mutex;
use tracing::info;
use url::Url;
@ -82,7 +78,7 @@ impl Transfer for Wallet {
public_spend_key: PublicKey,
public_view_key: PublicViewKey,
amount: Amount,
) -> Result<(TransferProof, Amount)> {
) -> Result<TransferProof> {
let destination_address =
Address::standard(self.network, public_spend_key, public_view_key.into());
@ -93,16 +89,17 @@ impl Transfer for Wallet {
.transfer(0, amount.as_piconero(), &destination_address.to_string())
.await?;
let tx_hash = TxHash(res.tx_hash);
tracing::info!("Monero tx broadcasted!, tx hash: {:?}", tx_hash);
let tx_key = PrivateKey::from_str(&res.tx_key)?;
tracing::debug!(
"sent transfer of {} to {} in {}",
amount,
public_spend_key,
res.tx_hash
);
let fee = Amount::from_piconero(res.fee);
let transfer_proof = TransferProof::new(tx_hash, tx_key);
tracing::debug!(" Transfer proof: {:?}", transfer_proof);
Ok((transfer_proof, fee))
Ok(TransferProof::new(
TxHash(res.tx_hash),
PrivateKey::from_str(&res.tx_key)?,
))
}
}
@ -203,7 +200,7 @@ impl WatchForTransfer for Wallet {
let address = Address::standard(self.network, public_spend_key, public_view_key.into());
let confirmations = Arc::new(AtomicU32::new(0u32));
let confirmations = AtomicU32::new(0u32);
let res = retry(ConstantBackoff::new(Duration::from_secs(1)), || async {
// NOTE: Currently, this is conflicting IO errors with the transaction not being

View File

@ -1,8 +1,9 @@
use ::monero::Network;
use anyhow::{Context, Result};
use async_compression::tokio::bufread::BzDecoder;
use big_bytes::BigByte;
use futures::{StreamExt, TryStreamExt};
use reqwest::Url;
use reqwest::{header::CONTENT_LENGTH, Url};
use std::{
io::ErrorKind,
path::{Path, PathBuf},
@ -71,8 +72,19 @@ impl WalletRpc {
.open(monero_wallet_rpc.tar_path())
.await?;
let byte_stream = reqwest::get(DOWNLOAD_URL)
.await?
let response = reqwest::get(DOWNLOAD_URL).await?;
let content_length = response.headers()[CONTENT_LENGTH]
.to_str()
.context("failed to convert content-length to string")?
.parse::<u64>()?;
tracing::info!(
"Downloading monero-wallet-rpc ({})",
content_length.big_byte(2)
);
let byte_stream = response
.bytes_stream()
.map_err(|err| std::io::Error::new(ErrorKind::Other, err));
@ -119,6 +131,8 @@ impl WalletRpc {
.local_addr()?
.port();
tracing::debug!("Starting monero-wallet-rpc on port {}", port);
let mut child = Command::new(self.exec_path())
.stdout(Stdio::piped())
.kill_on_drop(true)
@ -148,6 +162,7 @@ impl WalletRpc {
break;
}
}
Ok(WalletRpcProcess {
_child: child,
port,

View File

@ -10,7 +10,7 @@ use crate::{
};
use anyhow::{Error, Result};
use libp2p::{request_response::ResponseChannel, NetworkBehaviour, PeerId};
use tracing::{debug, info};
use tracing::debug;
#[derive(Debug)]
pub enum OutEvent {
@ -121,7 +121,6 @@ impl Behaviour {
quote_response: QuoteResponse,
) -> Result<()> {
self.quote_response.send(channel, quote_response)?;
info!("Sent quote response");
Ok(())
}

View File

@ -59,7 +59,10 @@ impl From<RequestResponseEvent<QuoteRequest, QuoteResponse>> for OutEvent {
RequestResponseEvent::OutboundFailure { error, .. } => {
OutEvent::Failure(anyhow!("Outbound failure: {:?}", error))
}
RequestResponseEvent::ResponseSent { .. } => OutEvent::ResponseSent,
RequestResponseEvent::ResponseSent { peer, .. } => {
tracing::debug!("successfully sent quote response to {}", peer);
OutEvent::ResponseSent
}
}
}
}
@ -82,7 +85,9 @@ impl Behaviour {
) -> Result<()> {
self.rr
.send_response(channel, msg)
.map_err(|_| anyhow!("Sending quote response failed"))
.map_err(|_| anyhow!("failed to send quote response"))?;
Ok(())
}
}

View File

@ -2,8 +2,7 @@ use crate::{
bitcoin,
bitcoin::{
current_epoch, wait_for_cancel_timelock_to_expire, CancelTimelock, ExpiredTimelocks,
GetBlockHeight, PunishTimelock, TransactionBlockHeight, TxCancel, TxRefund,
WatchForRawTransaction,
PunishTimelock, TxCancel, TxRefund,
},
execution_params::ExecutionParams,
monero,
@ -325,10 +324,10 @@ pub struct State3 {
}
impl State3 {
pub async fn wait_for_cancel_timelock_to_expire<W>(&self, bitcoin_wallet: &W) -> Result<()>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
pub async fn wait_for_cancel_timelock_to_expire(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<()> {
wait_for_cancel_timelock_to_expire(
bitcoin_wallet,
self.cancel_timelock,
@ -337,10 +336,10 @@ impl State3 {
.await
}
pub async fn expired_timelocks<W>(&self, bitcoin_wallet: &W) -> Result<ExpiredTimelocks>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
pub async fn expired_timelocks(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<ExpiredTimelocks> {
current_epoch(
bitcoin_wallet,
self.cancel_timelock,

View File

@ -1,10 +1,8 @@
use crate::{
bitcoin,
bitcoin::{
poll_until_block_height_is_gte, BlockHeight, BroadcastSignedTransaction, CancelTimelock,
EncryptedSignature, GetBlockHeight, GetRawTransaction, PunishTimelock,
TransactionBlockHeight, TxCancel, TxLock, TxRefund, WaitForTransactionFinality,
WatchForRawTransaction,
poll_until_block_height_is_gte, BlockHeight, CancelTimelock, EncryptedSignature,
PunishTimelock, TxCancel, TxLock, TxRefund,
},
execution_params::ExecutionParams,
monero,
@ -27,18 +25,14 @@ use libp2p::PeerId;
use sha2::Sha256;
use std::sync::Arc;
use tokio::time::timeout;
use tracing::info;
// TODO(Franck): Use helper functions from xmr-btc instead of re-writing them
// here
pub async fn wait_for_locked_bitcoin<W>(
pub async fn wait_for_locked_bitcoin(
lock_bitcoin_txid: bitcoin::Txid,
bitcoin_wallet: Arc<W>,
bitcoin_wallet: &bitcoin::Wallet,
execution_params: ExecutionParams,
) -> Result<()>
where
W: WatchForRawTransaction + WaitForTransactionFinality,
{
) -> Result<()> {
// We assume we will see Bob's transaction in the mempool first.
timeout(
execution_params.bob_time_to_act,
@ -69,7 +63,7 @@ where
let public_spend_key = S_a + state3.S_b_monero;
let public_view_key = state3.v.public();
let (transfer_proof, _) = monero_wallet
let transfer_proof = monero_wallet
.transfer(public_spend_key, public_view_key, state3.xmr)
.await?;
@ -130,32 +124,14 @@ pub fn build_bitcoin_redeem_transaction(
Ok(tx)
}
pub async fn publish_bitcoin_redeem_transaction<W>(
redeem_tx: bitcoin::Transaction,
bitcoin_wallet: Arc<W>,
) -> Result<::bitcoin::Txid>
where
W: BroadcastSignedTransaction + WaitForTransactionFinality,
{
info!("Attempting to publish bitcoin redeem txn");
let txid = bitcoin_wallet
.broadcast_signed_transaction(redeem_tx)
.await?;
Ok(txid)
}
pub async fn publish_cancel_transaction<W>(
pub async fn publish_cancel_transaction(
tx_lock: TxLock,
a: bitcoin::SecretKey,
B: bitcoin::PublicKey,
cancel_timelock: CancelTimelock,
tx_cancel_sig_bob: bitcoin::Signature,
bitcoin_wallet: Arc<W>,
) -> Result<bitcoin::TxCancel>
where
W: GetRawTransaction + TransactionBlockHeight + GetBlockHeight + BroadcastSignedTransaction,
{
bitcoin_wallet: Arc<bitcoin::Wallet>,
) -> Result<bitcoin::TxCancel> {
// First wait for cancel timelock to expire
let tx_lock_height = bitcoin_wallet
.transaction_block_height(tx_lock.txid())
@ -183,9 +159,7 @@ where
.expect("sig_{a,b} to be valid signatures for tx_cancel");
// TODO(Franck): Error handling is delicate, why can't we broadcast?
bitcoin_wallet
.broadcast_signed_transaction(tx_cancel)
.await?;
bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
// TODO(Franck): Wait until transaction is mined and returned mined
// block height
@ -194,18 +168,15 @@ where
Ok(tx_cancel)
}
pub async fn wait_for_bitcoin_refund<W>(
pub async fn wait_for_bitcoin_refund(
tx_cancel: &TxCancel,
cancel_tx_height: BlockHeight,
punish_timelock: PunishTimelock,
refund_address: &bitcoin::Address,
bitcoin_wallet: Arc<W>,
) -> Result<(bitcoin::TxRefund, Option<bitcoin::Transaction>)>
where
W: GetBlockHeight + WatchForRawTransaction,
{
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<(bitcoin::TxRefund, Option<bitcoin::Transaction>)> {
let punish_timelock_expired =
poll_until_block_height_is_gte(bitcoin_wallet.as_ref(), cancel_tx_height + punish_timelock);
poll_until_block_height_is_gte(bitcoin_wallet, cancel_tx_height + punish_timelock);
let tx_refund = bitcoin::TxRefund::new(tx_cancel, refund_address);
@ -266,22 +237,3 @@ pub fn build_bitcoin_punish_transaction(
Ok(signed_tx_punish)
}
pub async fn publish_bitcoin_punish_transaction<W>(
punish_tx: bitcoin::Transaction,
bitcoin_wallet: Arc<W>,
execution_params: ExecutionParams,
) -> Result<bitcoin::Txid>
where
W: BroadcastSignedTransaction + WaitForTransactionFinality,
{
let txid = bitcoin_wallet
.broadcast_signed_transaction(punish_tx)
.await?;
bitcoin_wallet
.wait_for_transaction_finality(txid, execution_params)
.await?;
Ok(txid)
}

View File

@ -2,10 +2,7 @@
//! Alice holds XMR and wishes receive BTC.
use crate::{
bitcoin,
bitcoin::{
ExpiredTimelocks, TransactionBlockHeight, WaitForTransactionFinality,
WatchForRawTransaction,
},
bitcoin::ExpiredTimelocks,
database,
database::Database,
execution_params::ExecutionParams,
@ -18,8 +15,7 @@ use crate::{
event_loop::EventLoopHandle,
steps::{
build_bitcoin_punish_transaction, build_bitcoin_redeem_transaction,
extract_monero_private_key, lock_xmr, publish_bitcoin_punish_transaction,
publish_bitcoin_redeem_transaction, publish_cancel_transaction,
extract_monero_private_key, lock_xmr, publish_cancel_transaction,
wait_for_bitcoin_encrypted_signature, wait_for_bitcoin_refund,
wait_for_locked_bitcoin,
},
@ -98,7 +94,7 @@ async fn run_until_internal(
} => {
let _ = wait_for_locked_bitcoin(
state3.tx_lock.txid(),
bitcoin_wallet.clone(),
&bitcoin_wallet,
execution_params,
)
.await?;
@ -222,37 +218,31 @@ async fn run_until_internal(
state3.B,
&state3.redeem_address,
) {
Ok(tx) => {
match publish_bitcoin_redeem_transaction(tx, bitcoin_wallet.clone())
.await
{
Ok(txid) => {
let publishded_redeem_tx = bitcoin_wallet
.wait_for_transaction_finality(txid, execution_params)
.await;
Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await {
Ok(txid) => {
let publishded_redeem_tx = bitcoin_wallet
.wait_for_transaction_finality(txid, execution_params)
.await;
match publishded_redeem_tx {
Ok(_) => AliceState::BtcRedeemed,
Err(e) => {
bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e)
}
}
}
Err(e) => {
error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e);
state3
.wait_for_cancel_timelock_to_expire(
bitcoin_wallet.as_ref(),
)
.await?;
AliceState::CancelTimelockExpired {
state3,
monero_wallet_restore_blockheight,
match publishded_redeem_tx {
Ok(_) => AliceState::BtcRedeemed,
Err(e) => {
bail!("Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed.", e)
}
}
}
}
Err(e) => {
error!("Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried.", e);
state3
.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref())
.await?;
AliceState::CancelTimelockExpired {
state3,
monero_wallet_restore_blockheight,
}
}
},
Err(e) => {
error!("Constructing the redeem transaction failed with {}, attempting to wait for cancellation now.", e);
state3
@ -335,7 +325,7 @@ async fn run_until_internal(
tx_cancel_height,
state3.punish_timelock,
&state3.refund_address,
bitcoin_wallet.clone(),
&bitcoin_wallet,
)
.await?;
@ -430,11 +420,15 @@ async fn run_until_internal(
state3.B,
)?;
let punish_tx_finalised = publish_bitcoin_punish_transaction(
signed_tx_punish,
bitcoin_wallet.clone(),
execution_params,
);
let punish_tx_finalised = async {
let txid = bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?;
bitcoin_wallet
.wait_for_transaction_finality(txid, execution_params)
.await?;
Result::<_, anyhow::Error>::Ok(txid)
};
let refund_tx_seen = bitcoin_wallet.watch_for_raw_transaction(tx_refund.txid());

View File

@ -15,7 +15,7 @@ use crate::{
use anyhow::{bail, Error, Result};
use libp2p::{core::Multiaddr, identity::Keypair, NetworkBehaviour, PeerId};
use std::sync::Arc;
use tracing::{debug, info};
use tracing::debug;
use uuid::Uuid;
pub use self::{
@ -259,8 +259,7 @@ pub struct Behaviour {
impl Behaviour {
/// Sends a quote request to Alice to retrieve the rate.
pub fn send_quote_request(&mut self, alice: PeerId, quote_request: QuoteRequest) {
let _id = self.quote_request.send(alice, quote_request);
info!("Requesting quote from: {}", alice);
let _ = self.quote_request.send(alice, quote_request);
}
pub fn start_execution_setup(
@ -271,10 +270,8 @@ impl Behaviour {
) {
self.execution_setup
.run(alice_peer_id, state0, bitcoin_wallet);
info!("Start execution setup with {}", alice_peer_id);
}
/// Sends Bob's fourth message to Alice.
pub fn send_encrypted_signature(
&mut self,
alice: PeerId,

View File

@ -7,12 +7,12 @@ use crate::{
bob::{Behaviour, OutEvent, QuoteRequest, State0, State2},
},
};
use anyhow::{anyhow, bail, Result};
use anyhow::{anyhow, bail, Context, Result};
use futures::FutureExt;
use libp2p::{core::Multiaddr, PeerId};
use std::{convert::Infallible, sync::Arc};
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::{debug, error, info};
use tracing::{debug, error, trace};
#[derive(Debug)]
pub struct Channels<T> {
@ -72,7 +72,6 @@ impl EventLoopHandle {
/// Dials other party and wait for the connection to be established.
/// Do nothing if we are already connected
pub async fn dial(&mut self) -> Result<()> {
debug!("Attempt to dial Alice");
let _ = self.dial_alice.send(()).await?;
self.conn_established
@ -201,15 +200,11 @@ impl EventLoop {
if option.is_some() {
let peer_id = self.alice_peer_id;
if self.swarm.pt.is_connected(&peer_id) {
debug!("Already connected to Alice: {}", peer_id);
trace!("Already connected to Alice at {}", peer_id);
let _ = self.conn_established.send(peer_id).await;
} else {
info!("dialing alice: {}", peer_id);
if let Err(err) = libp2p::Swarm::dial(&mut self.swarm, &peer_id) {
error!("Could not dial alice: {}", err);
// TODO(Franck): If Dial fails then we should report it.
}
debug!("Dialing alice at {}", peer_id);
libp2p::Swarm::dial(&mut self.swarm, &peer_id).context("failed to dial alice")?;
}
}
},

View File

@ -72,6 +72,8 @@ impl Behaviour {
) {
self.inner
.do_protocol_dialer(alice, move |mut substream| async move {
tracing::debug!("Starting execution setup with {}", alice);
substream
.write_message(
&serde_cbor::to_vec(&state0.next_message())

View File

@ -36,6 +36,8 @@ pub struct Behaviour {
impl Behaviour {
pub fn send(&mut self, alice: PeerId, quote_request: QuoteRequest) -> Result<RequestId> {
debug!("Requesting quote for {}", quote_request.btc_amount);
let id = self.rr.send_request(&alice, quote_request);
Ok(id)
@ -67,13 +69,9 @@ impl From<RequestResponseEvent<QuoteRequest, QuoteResponse>> for OutEvent {
..
} => OutEvent::Failure(anyhow!("Bob should never get a request from Alice")),
RequestResponseEvent::Message {
peer,
message: RequestResponseMessage::Response { response, .. },
..
} => {
debug!("Received quote response from {}", peer);
OutEvent::MsgReceived(response)
}
} => OutEvent::MsgReceived(response),
RequestResponseEvent::InboundFailure { error, .. } => {
OutEvent::Failure(anyhow!("Inbound failure: {:?}", error))
}

View File

@ -1,8 +1,7 @@
use crate::{
bitcoin::{
self, current_epoch, wait_for_cancel_timelock_to_expire, BroadcastSignedTransaction,
CancelTimelock, ExpiredTimelocks, GetBlockHeight, GetRawTransaction, PunishTimelock,
Transaction, TransactionBlockHeight, TxCancel, Txid, WatchForRawTransaction,
self, current_epoch, wait_for_cancel_timelock_to_expire, CancelTimelock, ExpiredTimelocks,
PunishTimelock, Transaction, TxCancel, Txid,
},
execution_params::ExecutionParams,
monero,
@ -14,7 +13,7 @@ use crate::{
CROSS_CURVE_PROOF_SYSTEM,
},
};
use anyhow::{anyhow, bail, Result};
use anyhow::{anyhow, bail, Context, Result};
use ecdsa_fun::{
adaptor::{Adaptor, HashTranscript},
nonce::Deterministic,
@ -269,16 +268,13 @@ impl State2 {
}
}
pub async fn lock_btc<W>(self, bitcoin_wallet: &W) -> Result<State3>
where
W: bitcoin::SignTxLock + bitcoin::BroadcastSignedTransaction,
{
let signed_tx_lock = bitcoin_wallet.sign_tx_lock(self.tx_lock.clone()).await?;
pub async fn lock_btc(self, bitcoin_wallet: &bitcoin::Wallet) -> Result<State3> {
let signed_tx = bitcoin_wallet
.sign_and_finalize(self.tx_lock.clone().into())
.await
.context("failed to sign Bitcoin lock transaction")?;
tracing::info!("{}", self.tx_lock.txid());
let _ = bitcoin_wallet
.broadcast_signed_transaction(signed_tx_lock)
.await?;
let _ = bitcoin_wallet.broadcast(signed_tx, "lock").await?;
Ok(State3 {
A: self.A,
@ -363,10 +359,10 @@ impl State3 {
}))
}
pub async fn wait_for_cancel_timelock_to_expire<W>(&self, bitcoin_wallet: &W) -> Result<()>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
pub async fn wait_for_cancel_timelock_to_expire(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<()> {
wait_for_cancel_timelock_to_expire(
bitcoin_wallet,
self.cancel_timelock,
@ -399,10 +395,10 @@ impl State3 {
self.tx_lock.txid()
}
pub async fn current_epoch<W>(&self, bitcoin_wallet: &W) -> Result<ExpiredTimelocks>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
pub async fn current_epoch(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<ExpiredTimelocks> {
current_epoch(
bitcoin_wallet,
self.cancel_timelock,
@ -443,10 +439,10 @@ impl State4 {
self.b.encsign(self.S_a_bitcoin, tx_redeem.digest())
}
pub async fn check_for_tx_cancel<W>(&self, bitcoin_wallet: &W) -> Result<Transaction>
where
W: GetRawTransaction,
{
pub async fn check_for_tx_cancel(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<Transaction> {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
@ -466,10 +462,7 @@ impl State4 {
Ok(tx)
}
pub async fn submit_tx_cancel<W>(&self, bitcoin_wallet: &W) -> Result<Txid>
where
W: BroadcastSignedTransaction,
{
pub async fn submit_tx_cancel(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<Txid> {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
@ -484,16 +477,12 @@ impl State4 {
tx_cancel",
);
let tx_id = bitcoin_wallet
.broadcast_signed_transaction(tx_cancel)
.await?;
let tx_id = bitcoin_wallet.broadcast(tx_cancel, "cancel").await?;
Ok(tx_id)
}
pub async fn watch_for_redeem_btc<W>(&self, bitcoin_wallet: &W) -> Result<State5>
where
W: WatchForRawTransaction,
{
pub async fn watch_for_redeem_btc(&self, bitcoin_wallet: &bitcoin::Wallet) -> Result<State5> {
let tx_redeem = bitcoin::TxRedeem::new(&self.tx_lock, &self.redeem_address);
let tx_redeem_encsig = self.b.encsign(self.S_a_bitcoin, tx_redeem.digest());
@ -515,10 +504,10 @@ impl State4 {
})
}
pub async fn wait_for_cancel_timelock_to_expire<W>(&self, bitcoin_wallet: &W) -> Result<()>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
pub async fn wait_for_cancel_timelock_to_expire(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<()> {
wait_for_cancel_timelock_to_expire(
bitcoin_wallet,
self.cancel_timelock,
@ -527,10 +516,10 @@ impl State4 {
.await
}
pub async fn expired_timelock<W>(&self, bitcoin_wallet: &W) -> Result<ExpiredTimelocks>
where
W: WatchForRawTransaction + TransactionBlockHeight + GetBlockHeight,
{
pub async fn expired_timelock(
&self,
bitcoin_wallet: &bitcoin::Wallet,
) -> Result<ExpiredTimelocks> {
current_epoch(
bitcoin_wallet,
self.cancel_timelock,
@ -540,14 +529,11 @@ impl State4 {
.await
}
pub async fn refund_btc<W>(
pub async fn refund_btc(
&self,
bitcoin_wallet: &W,
bitcoin_wallet: &bitcoin::Wallet,
execution_params: ExecutionParams,
) -> Result<()>
where
W: bitcoin::BroadcastSignedTransaction + bitcoin::WaitForTransactionFinality,
{
) -> Result<()> {
let tx_cancel =
bitcoin::TxCancel::new(&self.tx_lock, self.cancel_timelock, self.A, self.b.public());
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &self.refund_address);
@ -561,9 +547,7 @@ impl State4 {
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)
.await?;
let txid = bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?;
bitcoin_wallet
.wait_for_transaction_finality(txid, execution_params)

View File

@ -12,7 +12,7 @@ use async_recursion::async_recursion;
use rand::rngs::OsRng;
use std::sync::Arc;
use tokio::select;
use tracing::info;
use tracing::{trace, warn};
use uuid::Uuid;
pub fn is_complete(state: &BobState) -> bool {
@ -30,7 +30,6 @@ pub async fn run(swap: bob::Swap) -> Result<BobState> {
run_until(swap, is_complete).await
}
#[tracing::instrument(name = "swap", skip(swap,is_target_state), fields(id = %swap.swap_id))]
pub async fn run_until(
swap: bob::Swap,
is_target_state: fn(&BobState) -> bool,
@ -61,7 +60,7 @@ async fn run_until_internal(
swap_id: Uuid,
execution_params: ExecutionParams,
) -> Result<BobState> {
info!("Current state: {}", state);
trace!("Current state: {}", state);
if is_target_state(&state) {
Ok(state)
} else {
@ -186,7 +185,7 @@ async fn run_until_internal(
match state4? {
Ok(state4) => BobState::XmrLocked(state4),
Err(InsufficientFunds {..}) => {
info!("The other party has locked insufficient Monero funds! Waiting for refund...");
warn!("The other party has locked insufficient Monero funds! Waiting for refund...");
state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()).await?;
let state4 = state.cancel();
BobState::CancelTimelockExpired(state4)
@ -389,12 +388,14 @@ pub async fn request_quote_and_setup(
.send_quote_request(QuoteRequest { btc_amount })
.await?;
let quote_response = event_loop_handle.recv_quote_response().await?;
let xmr_amount = event_loop_handle.recv_quote_response().await?.xmr_amount;
tracing::info!("Quote for {} is {}", btc_amount, xmr_amount);
let state0 = State0::new(
&mut OsRng,
btc_amount,
quote_response.xmr_amount,
xmr_amount,
execution_params.bitcoin_cancel_timelock,
execution_params.bitcoin_punish_timelock,
bitcoin_refund_address,

View File

@ -45,7 +45,7 @@ impl Seed {
return Self::from_file(&file_path);
}
tracing::info!("No seed file found, creating at: {}", file_path.display());
tracing::debug!("No seed file found, creating at: {}", file_path.display());
let random_seed = Seed::random()?;
random_seed.write_to(file_path.to_path_buf())?;
@ -61,7 +61,7 @@ impl Seed {
let contents = fs::read_to_string(file)?;
let pem = pem::parse(contents)?;
tracing::info!("Read in seed from file: {}", file.display());
tracing::trace!("Read in seed from {}", file.display());
Self::from_pem(pem)
}