mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-25 07:29:32 -05:00
Wrap the Monero wallet client in a Mutex
In order to ensure that we can atomically generate_from_keys and then reload a wallet, we have to wrap the client of the monero wallet RPC inside a mutex. When introducing the Mutex I noticed that several inner RPC calls were leaking to the swap crate monero wallet. As this is a violation of boundaries I introduced the traits `GetAddress`, `WalletBlockHeight` and `Refresh`. Note that the monero wallet could potentially know its own public view key and public spend key. If we refactor the wallet to include this information upon wallet creation we can also generate addresses using `monero::Address::standard`.
This commit is contained in:
parent
aed8358fb7
commit
9f1deb9fdc
@ -31,7 +31,7 @@ use swap::{
|
|||||||
execution_params::GetExecutionParams,
|
execution_params::GetExecutionParams,
|
||||||
fs::default_config_path,
|
fs::default_config_path,
|
||||||
monero,
|
monero,
|
||||||
monero::{Amount, CreateWallet, OpenWallet},
|
monero::{Amount, CreateWallet, GetAddress, OpenWallet},
|
||||||
protocol::alice::EventLoop,
|
protocol::alice::EventLoop,
|
||||||
seed::Seed,
|
seed::Seed,
|
||||||
trace::init_tracing,
|
trace::init_tracing,
|
||||||
@ -177,7 +177,7 @@ async fn init_wallets(
|
|||||||
|
|
||||||
let balance = monero_wallet.get_balance().await?;
|
let balance = monero_wallet.get_balance().await?;
|
||||||
if balance == Amount::ZERO {
|
if balance == Amount::ZERO {
|
||||||
let deposit_address = monero_wallet.inner.get_address(0).await?.address;
|
let deposit_address = monero_wallet.get_main_address().await?;
|
||||||
warn!(
|
warn!(
|
||||||
"The Monero balance is 0, make sure to deposit funds at: {}",
|
"The Monero balance is 0, make sure to deposit funds at: {}",
|
||||||
deposit_address
|
deposit_address
|
||||||
|
@ -30,7 +30,7 @@ use swap::{
|
|||||||
execution_params::GetExecutionParams,
|
execution_params::GetExecutionParams,
|
||||||
fs::default_config_path,
|
fs::default_config_path,
|
||||||
monero,
|
monero,
|
||||||
monero::{CreateWallet, OpenWallet},
|
monero::{CreateWallet, OpenWallet, WalletBlockHeight},
|
||||||
protocol::{
|
protocol::{
|
||||||
bob,
|
bob,
|
||||||
bob::{cancel::CancelError, Builder},
|
bob::{cancel::CancelError, Builder},
|
||||||
@ -316,7 +316,7 @@ async fn init_wallets(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _test_wallet_connection = monero_wallet.inner.block_height().await?;
|
let _test_wallet_connection = monero_wallet.block_height().await?;
|
||||||
info!("The Monero wallet RPC is set up correctly!");
|
info!("The Monero wallet RPC is set up correctly!");
|
||||||
|
|
||||||
Ok((bitcoin_wallet, monero_wallet))
|
Ok((bitcoin_wallet, monero_wallet))
|
||||||
|
@ -8,6 +8,8 @@ use crate::bitcoin;
|
|||||||
use ::bitcoin::hashes::core::fmt::Formatter;
|
use ::bitcoin::hashes::core::fmt::Formatter;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use monero::Address;
|
||||||
|
use monero_rpc::wallet::{BlockHeight, Refreshed};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use rust_decimal::{
|
use rust_decimal::{
|
||||||
prelude::{FromPrimitive, ToPrimitive},
|
prelude::{FromPrimitive, ToPrimitive},
|
||||||
@ -228,6 +230,21 @@ pub trait CreateWallet {
|
|||||||
async fn create_wallet(&self, file_name: &str) -> Result<()>;
|
async fn create_wallet(&self, file_name: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait WalletBlockHeight {
|
||||||
|
async fn block_height(&self) -> Result<BlockHeight>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait GetAddress {
|
||||||
|
async fn get_main_address(&self) -> Result<Address>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Refresh {
|
||||||
|
async fn refresh(&self) -> Result<Refreshed>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
|
||||||
#[error("Overflow, cannot convert {0} to u64")]
|
#[error("Overflow, cannot convert {0} to u64")]
|
||||||
pub struct OverflowError(pub String);
|
pub struct OverflowError(pub String);
|
||||||
|
@ -1,38 +1,43 @@
|
|||||||
use crate::monero::{
|
use crate::monero::{
|
||||||
Amount, CreateWallet, CreateWalletForOutput, InsufficientFunds, OpenWallet, PrivateViewKey,
|
Amount, CreateWallet, CreateWalletForOutput, GetAddress, InsufficientFunds, OpenWallet,
|
||||||
PublicViewKey, Transfer, TransferProof, TxHash, WatchForTransfer,
|
PrivateViewKey, PublicViewKey, Refresh, Transfer, TransferProof, TxHash, WalletBlockHeight,
|
||||||
|
WatchForTransfer,
|
||||||
};
|
};
|
||||||
use ::monero::{Address, Network, PrivateKey, PublicKey};
|
use ::monero::{Address, Network, PrivateKey, PublicKey};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use backoff::{backoff::Constant as ConstantBackoff, future::retry};
|
use backoff::{backoff::Constant as ConstantBackoff, future::retry};
|
||||||
use bitcoin::hashes::core::sync::atomic::AtomicU32;
|
use bitcoin::hashes::core::sync::atomic::AtomicU32;
|
||||||
use monero_rpc::wallet;
|
use monero_rpc::{
|
||||||
|
wallet,
|
||||||
|
wallet::{BlockHeight, Refreshed},
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{atomic::Ordering, Arc},
|
sync::{atomic::Ordering, Arc},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
pub inner: wallet::Client,
|
pub inner: Mutex<wallet::Client>,
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
pub fn new(url: Url, network: Network) -> Self {
|
pub fn new(url: Url, network: Network) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: wallet::Client::new(url),
|
inner: Mutex::new(wallet::Client::new(url)),
|
||||||
network,
|
network,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the balance of the primary account.
|
/// Get the balance of the primary account.
|
||||||
pub async fn get_balance(&self) -> Result<Amount> {
|
pub async fn get_balance(&self) -> Result<Amount> {
|
||||||
let amount = self.inner.get_balance(0).await?;
|
let amount = self.inner.lock().await.get_balance(0).await?;
|
||||||
|
|
||||||
Ok(Amount::from_piconero(amount))
|
Ok(Amount::from_piconero(amount))
|
||||||
}
|
}
|
||||||
@ -51,6 +56,8 @@ impl Transfer for Wallet {
|
|||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.inner
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.transfer(0, amount.as_piconero(), &destination_address.to_string())
|
.transfer(0, amount.as_piconero(), &destination_address.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -82,6 +89,8 @@ impl CreateWalletForOutput for Wallet {
|
|||||||
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.inner
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.generate_from_keys(
|
.generate_from_keys(
|
||||||
&address.to_string(),
|
&address.to_string(),
|
||||||
&private_spend_key.to_string(),
|
&private_spend_key.to_string(),
|
||||||
@ -97,7 +106,7 @@ impl CreateWalletForOutput for Wallet {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OpenWallet for Wallet {
|
impl OpenWallet for Wallet {
|
||||||
async fn open_wallet(&self, file_name: &str) -> Result<()> {
|
async fn open_wallet(&self, file_name: &str) -> Result<()> {
|
||||||
self.inner.open_wallet(file_name).await?;
|
self.inner.lock().await.open_wallet(file_name).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +114,7 @@ impl OpenWallet for Wallet {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl CreateWallet for Wallet {
|
impl CreateWallet for Wallet {
|
||||||
async fn create_wallet(&self, file_name: &str) -> Result<()> {
|
async fn create_wallet(&self, file_name: &str) -> Result<()> {
|
||||||
self.inner.create_wallet(file_name).await?;
|
self.inner.lock().await.create_wallet(file_name).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,7 +138,6 @@ impl WatchForTransfer for Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let address = Address::standard(self.network, public_spend_key, public_view_key.into());
|
let address = Address::standard(self.network, public_spend_key, public_view_key.into());
|
||||||
let wallet = self.inner.clone();
|
|
||||||
|
|
||||||
let confirmations = Arc::new(AtomicU32::new(0u32));
|
let confirmations = Arc::new(AtomicU32::new(0u32));
|
||||||
|
|
||||||
@ -137,7 +145,10 @@ impl WatchForTransfer for Wallet {
|
|||||||
// NOTE: Currently, this is conflicting IO errors with the transaction not being
|
// NOTE: Currently, this is conflicting IO errors with the transaction not being
|
||||||
// in the blockchain yet, or not having enough confirmations on it. All these
|
// in the blockchain yet, or not having enough confirmations on it. All these
|
||||||
// errors warrant a retry, but the strategy should probably differ per case
|
// errors warrant a retry, but the strategy should probably differ per case
|
||||||
let proof = wallet
|
let proof = self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.check_tx_key(
|
.check_tx_key(
|
||||||
&String::from(transfer_proof.tx_hash()),
|
&String::from(transfer_proof.tx_hash()),
|
||||||
&transfer_proof.tx_key().to_string(),
|
&transfer_proof.tx_key().to_string(),
|
||||||
@ -176,3 +187,25 @@ impl WatchForTransfer for Wallet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WalletBlockHeight for Wallet {
|
||||||
|
async fn block_height(&self) -> Result<BlockHeight> {
|
||||||
|
self.inner.lock().await.block_height().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl GetAddress for Wallet {
|
||||||
|
async fn get_main_address(&self) -> Result<Address> {
|
||||||
|
let address = self.inner.lock().await.get_address(0).await?;
|
||||||
|
Ok(Address::from_str(address.address.as_str())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Refresh for Wallet {
|
||||||
|
async fn refresh(&self) -> Result<Refreshed> {
|
||||||
|
self.inner.lock().await.refresh().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
database::{Database, Swap},
|
database::{Database, Swap},
|
||||||
execution_params::ExecutionParams,
|
execution_params::ExecutionParams,
|
||||||
monero,
|
monero,
|
||||||
monero::InsufficientFunds,
|
monero::{InsufficientFunds, WalletBlockHeight},
|
||||||
protocol::bob::{self, event_loop::EventLoopHandle, state::*, QuoteRequest},
|
protocol::bob::{self, event_loop::EventLoopHandle, state::*, QuoteRequest},
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@ -132,8 +132,7 @@ async fn run_until_internal(
|
|||||||
// TODO: This can be optimized further by extracting the block height when
|
// TODO: This can be optimized further by extracting the block height when
|
||||||
// tx-lock was included. However, scanning a few more blocks won't do any harm
|
// tx-lock was included. However, scanning a few more blocks won't do any harm
|
||||||
// and is simpler.
|
// and is simpler.
|
||||||
let monero_wallet_restore_blockheight =
|
let monero_wallet_restore_blockheight = monero_wallet.block_height().await?;
|
||||||
monero_wallet.inner.block_height().await?;
|
|
||||||
|
|
||||||
select! {
|
select! {
|
||||||
transfer_proof = transfer_proof_watcher => {
|
transfer_proof = transfer_proof_watcher => {
|
||||||
|
@ -21,12 +21,17 @@ use swap::{
|
|||||||
execution_params,
|
execution_params,
|
||||||
execution_params::{ExecutionParams, GetExecutionParams},
|
execution_params::{ExecutionParams, GetExecutionParams},
|
||||||
monero,
|
monero,
|
||||||
|
monero::Refresh,
|
||||||
protocol::{alice, alice::AliceState, bob, bob::BobState},
|
protocol::{alice, alice::AliceState, bob, bob::BobState},
|
||||||
seed::Seed,
|
seed::Seed,
|
||||||
};
|
};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use testcontainers::{clients::Cli, Container, Docker, RunArgs};
|
use testcontainers::{clients::Cli, Container, Docker, RunArgs};
|
||||||
use tokio::{sync::mpsc, task::JoinHandle, time::interval};
|
use tokio::{
|
||||||
|
sync::{mpsc, Mutex},
|
||||||
|
task::JoinHandle,
|
||||||
|
time::interval,
|
||||||
|
};
|
||||||
use tracing::dispatcher::DefaultGuard;
|
use tracing::dispatcher::DefaultGuard;
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -168,12 +173,7 @@ impl TestContext {
|
|||||||
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
|
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
|
||||||
|
|
||||||
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
||||||
self.alice_monero_wallet
|
self.alice_monero_wallet.as_ref().refresh().await.unwrap();
|
||||||
.as_ref()
|
|
||||||
.inner
|
|
||||||
.refresh()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let xmr_balance_after_swap = self
|
let xmr_balance_after_swap = self
|
||||||
.alice_monero_wallet
|
.alice_monero_wallet
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -232,12 +232,7 @@ impl TestContext {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
||||||
self.bob_monero_wallet
|
self.bob_monero_wallet.as_ref().refresh().await.unwrap();
|
||||||
.as_ref()
|
|
||||||
.inner
|
|
||||||
.refresh()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
xmr_balance_after_swap,
|
xmr_balance_after_swap,
|
||||||
@ -595,7 +590,7 @@ async fn init_test_wallets(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let xmr_wallet = swap::monero::Wallet {
|
let xmr_wallet = swap::monero::Wallet {
|
||||||
inner: monero.wallet(name).unwrap().client(),
|
inner: Mutex::new(monero.wallet(name).unwrap().client()),
|
||||||
network: monero::Network::default(),
|
network: monero::Network::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user