mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-06 13:34:38 -04:00
WIP - use get_transfer_by_txid to extract the exact block height of tx_lock
This failed on stagenet because somehow the transaction cannot be found with the given tx_hash. This might be because tx are not indexed or other problem, hard to say at this point.
This commit is contained in:
parent
184f179044
commit
45b5af4375
7 changed files with 107 additions and 301 deletions
|
@ -186,7 +186,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get wallet block height, this might be behind monerod height.
|
/// Get wallet block height, this might be behind monerod height.
|
||||||
pub(crate) async fn block_height(&self) -> Result<BlockHeight> {
|
pub async fn block_height(&self) -> Result<BlockHeight> {
|
||||||
let request = Request::new("get_height", "");
|
let request = Request::new("get_height", "");
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
|
@ -233,14 +233,43 @@ impl Client {
|
||||||
Ok(r.result)
|
Ok(r.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_transfer_by_txid(&self, tx_id: &str) -> Result<GetTransferByTxid> {
|
||||||
|
let params = GetTransferByTxidParams {
|
||||||
|
tx_id: tx_id.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = Request::new("get_transfer_by_txid", params);
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.inner
|
||||||
|
.post(self.url.clone())
|
||||||
|
.json(&request)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
debug!("transfer RPC response: {}", response);
|
||||||
|
|
||||||
|
let r: Response<GetTransferByTxid> = serde_json::from_str(&response)?;
|
||||||
|
Ok(r.result)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn generate_from_keys(
|
pub async fn generate_from_keys(
|
||||||
&self,
|
&self,
|
||||||
address: &str,
|
address: &str,
|
||||||
spend_key: &str,
|
spend_key: &str,
|
||||||
view_key: &str,
|
view_key: &str,
|
||||||
|
restore_height: Option<u32>,
|
||||||
) -> Result<GenerateFromKeys> {
|
) -> Result<GenerateFromKeys> {
|
||||||
|
let restore_height = if let Some(restore_height) = restore_height {
|
||||||
|
restore_height
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
let params = GenerateFromKeysParams {
|
let params = GenerateFromKeysParams {
|
||||||
restore_height: 0,
|
restore_height,
|
||||||
filename: view_key.into(),
|
filename: view_key.into(),
|
||||||
address: address.into(),
|
address: address.into(),
|
||||||
spendkey: spend_key.into(),
|
spendkey: spend_key.into(),
|
||||||
|
@ -395,6 +424,17 @@ pub struct CheckTxKey {
|
||||||
pub received: u64,
|
pub received: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone)]
|
||||||
|
struct GetTransferByTxidParams {
|
||||||
|
#[serde(rename = "txid")]
|
||||||
|
tx_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||||
|
pub struct GetTransferByTxid {
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct GenerateFromKeysParams {
|
pub struct GenerateFromKeysParams {
|
||||||
pub restore_height: u32,
|
pub restore_height: u32,
|
||||||
|
|
|
@ -474,7 +474,7 @@ pub async fn run_until(
|
||||||
let view_key = state3.v;
|
let view_key = state3.v;
|
||||||
|
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.create_and_load_wallet_for_output(spend_key, view_key)
|
.create_and_load_wallet_for_output(spend_key, view_key, None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let state = AliceState::XmrRefunded;
|
let state = AliceState::XmrRefunded;
|
||||||
|
|
|
@ -64,6 +64,7 @@ impl CreateWalletForOutput for Wallet {
|
||||||
&self,
|
&self,
|
||||||
private_spend_key: PrivateKey,
|
private_spend_key: PrivateKey,
|
||||||
private_view_key: PrivateViewKey,
|
private_view_key: PrivateViewKey,
|
||||||
|
restore_height: Option<u32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let public_spend_key = PublicKey::from_private_key(&private_spend_key);
|
let public_spend_key = PublicKey::from_private_key(&private_spend_key);
|
||||||
let public_view_key = PublicKey::from_private_key(&private_view_key.into());
|
let public_view_key = PublicKey::from_private_key(&private_view_key.into());
|
||||||
|
@ -76,6 +77,7 @@ impl CreateWalletForOutput for Wallet {
|
||||||
&address.to_string(),
|
&address.to_string(),
|
||||||
&private_spend_key.to_string(),
|
&private_spend_key.to_string(),
|
||||||
&PrivateKey::from(private_view_key).to_string(),
|
&PrivateKey::from(private_view_key).to_string(),
|
||||||
|
restore_height,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -95,16 +97,17 @@ impl WatchForTransfer for Wallet {
|
||||||
transfer_proof: TransferProof,
|
transfer_proof: TransferProof,
|
||||||
expected_amount: Amount,
|
expected_amount: Amount,
|
||||||
expected_confirmations: u32,
|
expected_confirmations: u32,
|
||||||
) -> Result<(), InsufficientFunds> {
|
) -> Result<TransferInfo> {
|
||||||
enum Error {
|
enum Error {
|
||||||
TxNotFound,
|
TxNotFound,
|
||||||
|
TransferNotFound { txid: String },
|
||||||
InsufficientConfirmations,
|
InsufficientConfirmations,
|
||||||
InsufficientFunds { expected: Amount, actual: Amount },
|
InsufficientFunds { expected: Amount, actual: Amount },
|
||||||
}
|
}
|
||||||
|
|
||||||
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 res = (|| async {
|
let result = (|| async {
|
||||||
// NOTE: Currently, this is conflating IO errors with the transaction not being
|
// NOTE: Currently, this is conflating 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
|
||||||
|
@ -129,15 +132,35 @@ impl WatchForTransfer for Wallet {
|
||||||
return Err(backoff::Error::Transient(Error::InsufficientConfirmations));
|
return Err(backoff::Error::Transient(Error::InsufficientConfirmations));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(proof)
|
let tx_hash = transfer_proof.tx_hash();
|
||||||
|
let get_transfer_by_tx_id =
|
||||||
|
self.inner
|
||||||
|
.get_transfer_by_txid(&tx_hash.0)
|
||||||
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
backoff::Error::Permanent(Error::TransferNotFound { txid: tx_hash.0 })
|
||||||
|
})?;
|
||||||
|
let transfer_info = TransferInfo {
|
||||||
|
first_confirmation_block_height: get_transfer_by_tx_id.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((proof, transfer_info))
|
||||||
})
|
})
|
||||||
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Err(Error::InsufficientFunds { expected, actual }) = res {
|
match result {
|
||||||
return Err(InsufficientFunds { expected, actual });
|
Ok((_, transfer_info)) => Ok(transfer_info),
|
||||||
};
|
Err(Error::InsufficientFunds { expected, actual }) => {
|
||||||
|
anyhow::bail!(InsufficientFunds { expected, actual })
|
||||||
Ok(())
|
}
|
||||||
|
Err(Error::TransferNotFound { txid }) => anyhow::bail!(TransferNotFound { txid }),
|
||||||
|
Err(Error::TxNotFound) => {
|
||||||
|
unreachable!("Transient backoff error will never be returned")
|
||||||
|
}
|
||||||
|
Err(Error::InsufficientConfirmations) => {
|
||||||
|
unreachable!("Transient backoff error will never be returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -884,10 +884,11 @@ impl State5 {
|
||||||
|
|
||||||
let s = s_b.scalar + self.s_a.into_ed25519();
|
let s = s_b.scalar + self.s_a.into_ed25519();
|
||||||
|
|
||||||
|
// TODO: Optimized rescan height should be passed for refund as well.
|
||||||
// NOTE: This actually generates and opens a new wallet, closing the currently
|
// NOTE: This actually generates and opens a new wallet, closing the currently
|
||||||
// open one.
|
// open one.
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.create_and_load_wallet_for_output(monero::PrivateKey::from_scalar(s), self.v)
|
.create_and_load_wallet_for_output(monero::PrivateKey::from_scalar(s), self.v, None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
alice,
|
alice,
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
self, poll_until_block_height_is_gte, BroadcastSignedTransaction, BuildTxLockPsbt,
|
self, BroadcastSignedTransaction, BuildTxLockPsbt, TxCancel, WatchForRawTransaction,
|
||||||
SignTxLock, TxCancel, WatchForRawTransaction,
|
|
||||||
},
|
},
|
||||||
monero,
|
monero,
|
||||||
serde::monero_private_key,
|
serde::monero_private_key,
|
||||||
transport::{ReceiveMessage, SendMessage},
|
|
||||||
ExpiredTimelocks,
|
ExpiredTimelocks,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
@ -16,29 +14,15 @@ use ecdsa_fun::{
|
||||||
nonce::Deterministic,
|
nonce::Deterministic,
|
||||||
Signature,
|
Signature,
|
||||||
};
|
};
|
||||||
use futures::{
|
|
||||||
future::{select, Either},
|
|
||||||
pin_mut, FutureExt,
|
|
||||||
};
|
|
||||||
use genawaiter::sync::{Gen, GenBoxed};
|
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use std::{
|
use std::convert::TryFrom;
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
sync::Arc,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use tokio::{sync::Mutex, time::timeout};
|
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
use crate::{
|
use crate::bitcoin::{
|
||||||
bitcoin::{
|
current_epoch, wait_for_cancel_timelock_to_expire, GetBlockHeight, GetRawTransaction, Network,
|
||||||
current_epoch, wait_for_cancel_timelock_to_expire, GetBlockHeight, GetRawTransaction,
|
Timelock, TransactionBlockHeight,
|
||||||
Network, Timelock, TransactionBlockHeight,
|
|
||||||
},
|
|
||||||
monero::{CreateWalletForOutput, WatchForTransfer},
|
|
||||||
};
|
};
|
||||||
use ::bitcoin::{Transaction, Txid};
|
use ::bitcoin::{Transaction, Txid};
|
||||||
pub use message::{Message, Message0, Message1, Message2, Message3};
|
pub use message::{Message, Message0, Message1, Message2, Message3};
|
||||||
|
@ -61,269 +45,6 @@ pub trait ReceiveTransferProof {
|
||||||
async fn receive_transfer_proof(&mut self) -> monero::TransferProof;
|
async fn receive_transfer_proof(&mut self) -> monero::TransferProof;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the on-chain protocol to swap monero and bitcoin as Bob.
|
|
||||||
///
|
|
||||||
/// This is called post handshake, after all the keys, addresses and most of the
|
|
||||||
/// signatures have been exchanged.
|
|
||||||
///
|
|
||||||
/// The argument `bitcoin_tx_lock_timeout` is used to determine how long we will
|
|
||||||
/// wait for Bob, the caller of this function, to lock up the bitcoin.
|
|
||||||
pub fn action_generator<N, M, B>(
|
|
||||||
network: Arc<Mutex<N>>,
|
|
||||||
monero_client: Arc<M>,
|
|
||||||
bitcoin_client: Arc<B>,
|
|
||||||
// TODO: Replace this with a new, slimmer struct?
|
|
||||||
State2 {
|
|
||||||
A,
|
|
||||||
b,
|
|
||||||
s_b,
|
|
||||||
S_a_monero,
|
|
||||||
S_a_bitcoin,
|
|
||||||
v,
|
|
||||||
xmr,
|
|
||||||
cancel_timelock,
|
|
||||||
redeem_address,
|
|
||||||
refund_address,
|
|
||||||
tx_lock,
|
|
||||||
tx_cancel_sig_a,
|
|
||||||
tx_refund_encsig,
|
|
||||||
..
|
|
||||||
}: State2,
|
|
||||||
bitcoin_tx_lock_timeout: u64,
|
|
||||||
) -> GenBoxed<Action, (), ()>
|
|
||||||
where
|
|
||||||
N: ReceiveTransferProof + Send + 'static,
|
|
||||||
M: monero::WatchForTransfer + Send + Sync + 'static,
|
|
||||||
B: bitcoin::GetBlockHeight
|
|
||||||
+ bitcoin::TransactionBlockHeight
|
|
||||||
+ bitcoin::WatchForRawTransaction
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
{
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum SwapFailed {
|
|
||||||
BeforeBtcLock(Reason),
|
|
||||||
AfterBtcLock(Reason),
|
|
||||||
AfterBtcRedeem(Reason),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reason why the swap has failed.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Reason {
|
|
||||||
/// Bob was too slow to lock the bitcoin.
|
|
||||||
InactiveBob,
|
|
||||||
/// The refund timelock has been reached.
|
|
||||||
BtcExpired,
|
|
||||||
/// Alice did not lock up enough monero in the shared output.
|
|
||||||
InsufficientXmr(monero::InsufficientFunds),
|
|
||||||
/// Could not find Bob's signature on the redeem transaction witness
|
|
||||||
/// stack.
|
|
||||||
BtcRedeemSignature,
|
|
||||||
/// Could not recover secret `s_a` from Bob's redeem transaction
|
|
||||||
/// signature.
|
|
||||||
SecretRecovery,
|
|
||||||
}
|
|
||||||
|
|
||||||
Gen::new_boxed(|co| async move {
|
|
||||||
let swap_result: Result<(), SwapFailed> = async {
|
|
||||||
co.yield_(Action::LockBtc(tx_lock.clone())).await;
|
|
||||||
|
|
||||||
timeout(
|
|
||||||
Duration::from_secs(bitcoin_tx_lock_timeout),
|
|
||||||
bitcoin_client.watch_for_raw_transaction(tx_lock.txid()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|tx| tx.txid())
|
|
||||||
.map_err(|_| SwapFailed::BeforeBtcLock(Reason::InactiveBob))?;
|
|
||||||
|
|
||||||
let tx_lock_height = bitcoin_client
|
|
||||||
.transaction_block_height(tx_lock.txid())
|
|
||||||
.await;
|
|
||||||
let poll_until_btc_has_expired = poll_until_block_height_is_gte(
|
|
||||||
bitcoin_client.as_ref(),
|
|
||||||
tx_lock_height + cancel_timelock,
|
|
||||||
)
|
|
||||||
.shared();
|
|
||||||
pin_mut!(poll_until_btc_has_expired);
|
|
||||||
|
|
||||||
let transfer_proof = {
|
|
||||||
let mut guard = network.as_ref().lock().await;
|
|
||||||
let transfer_proof = match select(
|
|
||||||
guard.receive_transfer_proof(),
|
|
||||||
poll_until_btc_has_expired.clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((proof, _)) => proof,
|
|
||||||
Either::Right(_) => return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired)),
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("select returned transfer proof from message");
|
|
||||||
|
|
||||||
transfer_proof
|
|
||||||
};
|
|
||||||
|
|
||||||
let S_b_monero = monero::PublicKey::from_private_key(&monero::PrivateKey::from_scalar(
|
|
||||||
s_b.into_ed25519(),
|
|
||||||
));
|
|
||||||
let S = S_a_monero + S_b_monero;
|
|
||||||
|
|
||||||
match select(
|
|
||||||
monero_client.watch_for_transfer(S, v.public(), transfer_proof, xmr, 0),
|
|
||||||
poll_until_btc_has_expired.clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((Err(e), _)) => {
|
|
||||||
return Err(SwapFailed::AfterBtcLock(Reason::InsufficientXmr(e)))
|
|
||||||
}
|
|
||||||
Either::Right(_) => return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired)),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tx_redeem = bitcoin::TxRedeem::new(&tx_lock, &redeem_address);
|
|
||||||
let tx_redeem_encsig = b.encsign(S_a_bitcoin, tx_redeem.digest());
|
|
||||||
|
|
||||||
co.yield_(Action::SendBtcRedeemEncsig(tx_redeem_encsig.clone()))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let tx_redeem_published = match select(
|
|
||||||
bitcoin_client.watch_for_raw_transaction(tx_redeem.txid()),
|
|
||||||
poll_until_btc_has_expired,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Either::Left((tx, _)) => tx,
|
|
||||||
Either::Right(_) => return Err(SwapFailed::AfterBtcLock(Reason::BtcExpired)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tx_redeem_sig = tx_redeem
|
|
||||||
.extract_signature_by_key(tx_redeem_published, b.public())
|
|
||||||
.map_err(|_| SwapFailed::AfterBtcRedeem(Reason::BtcRedeemSignature))?;
|
|
||||||
let s_a = bitcoin::recover(S_a_bitcoin, tx_redeem_sig, tx_redeem_encsig)
|
|
||||||
.map_err(|_| SwapFailed::AfterBtcRedeem(Reason::SecretRecovery))?;
|
|
||||||
let s_a = monero::private_key_from_secp256k1_scalar(s_a.into());
|
|
||||||
|
|
||||||
let s_b = monero::PrivateKey {
|
|
||||||
scalar: s_b.into_ed25519(),
|
|
||||||
};
|
|
||||||
|
|
||||||
co.yield_(Action::CreateXmrWalletForOutput {
|
|
||||||
spend_key: s_a + s_b,
|
|
||||||
view_key: v,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(ref err) = swap_result {
|
|
||||||
error!("swap failed: {:?}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(SwapFailed::AfterBtcLock(_)) = swap_result {
|
|
||||||
let tx_cancel = bitcoin::TxCancel::new(&tx_lock, cancel_timelock, A, b.public());
|
|
||||||
let tx_cancel_txid = tx_cancel.txid();
|
|
||||||
let signed_tx_cancel = {
|
|
||||||
let sig_a = tx_cancel_sig_a.clone();
|
|
||||||
let sig_b = b.sign(tx_cancel.digest());
|
|
||||||
|
|
||||||
tx_cancel
|
|
||||||
.clone()
|
|
||||||
.add_signatures(&tx_lock, (A, sig_a), (b.public(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_cancel")
|
|
||||||
};
|
|
||||||
|
|
||||||
co.yield_(Action::CancelBtc(signed_tx_cancel)).await;
|
|
||||||
|
|
||||||
let _ = bitcoin_client
|
|
||||||
.watch_for_raw_transaction(tx_cancel_txid)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let tx_refund = bitcoin::TxRefund::new(&tx_cancel, &refund_address);
|
|
||||||
let tx_refund_txid = tx_refund.txid();
|
|
||||||
let signed_tx_refund = {
|
|
||||||
let adaptor = Adaptor::<Sha256, Deterministic<Sha256>>::default();
|
|
||||||
|
|
||||||
let sig_a =
|
|
||||||
adaptor.decrypt_signature(&s_b.into_secp256k1(), tx_refund_encsig.clone());
|
|
||||||
let sig_b = b.sign(tx_refund.digest());
|
|
||||||
|
|
||||||
tx_refund
|
|
||||||
.add_signatures(&tx_cancel, (A, sig_a), (b.public(), sig_b))
|
|
||||||
.expect("sig_{a,b} to be valid signatures for tx_refund")
|
|
||||||
};
|
|
||||||
|
|
||||||
co.yield_(Action::RefundBtc(signed_tx_refund)).await;
|
|
||||||
|
|
||||||
let _ = bitcoin_client
|
|
||||||
.watch_for_raw_transaction(tx_refund_txid)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are no guarantees that send_message and receive_massage do not block
|
|
||||||
// the flow of execution. Therefore they must be paired between Alice/Bob, one
|
|
||||||
// send to one receive in the correct order.
|
|
||||||
pub async fn next_state<
|
|
||||||
R: RngCore + CryptoRng,
|
|
||||||
B: WatchForRawTransaction + SignTxLock + BuildTxLockPsbt + BroadcastSignedTransaction + Network,
|
|
||||||
M: CreateWalletForOutput + WatchForTransfer,
|
|
||||||
T: SendMessage<Message> + ReceiveMessage<alice::Message>,
|
|
||||||
>(
|
|
||||||
bitcoin_wallet: &B,
|
|
||||||
monero_wallet: &M,
|
|
||||||
transport: &mut T,
|
|
||||||
state: State,
|
|
||||||
rng: &mut R,
|
|
||||||
) -> Result<State> {
|
|
||||||
match state {
|
|
||||||
State::State0(state0) => {
|
|
||||||
transport
|
|
||||||
.send_message(state0.next_message(rng).into())
|
|
||||||
.await?;
|
|
||||||
let message0 = transport.receive_message().await?.try_into()?;
|
|
||||||
let state1 = state0.receive(bitcoin_wallet, message0).await?;
|
|
||||||
Ok(state1.into())
|
|
||||||
}
|
|
||||||
State::State1(state1) => {
|
|
||||||
transport.send_message(state1.next_message().into()).await?;
|
|
||||||
|
|
||||||
let message1 = transport.receive_message().await?.try_into()?;
|
|
||||||
let state2 = state1.receive(message1)?;
|
|
||||||
|
|
||||||
let message2 = state2.next_message();
|
|
||||||
transport.send_message(message2.into()).await?;
|
|
||||||
Ok(state2.into())
|
|
||||||
}
|
|
||||||
State::State2(state2) => {
|
|
||||||
let state3 = state2.lock_btc(bitcoin_wallet).await?;
|
|
||||||
tracing::info!("bob has locked btc");
|
|
||||||
|
|
||||||
Ok(state3.into())
|
|
||||||
}
|
|
||||||
State::State3(state3) => {
|
|
||||||
let message2 = transport.receive_message().await?.try_into()?;
|
|
||||||
let state4 = state3.watch_for_lock_xmr(monero_wallet, message2).await?;
|
|
||||||
tracing::info!("bob has seen that alice has locked xmr");
|
|
||||||
Ok(state4.into())
|
|
||||||
}
|
|
||||||
State::State4(state4) => {
|
|
||||||
transport.send_message(state4.next_message().into()).await?;
|
|
||||||
tracing::info!("bob is watching for redeem_btc");
|
|
||||||
let state5 = state4.watch_for_redeem_btc(bitcoin_wallet).await?;
|
|
||||||
tracing::info!("bob has seen that alice has redeemed btc");
|
|
||||||
state5.claim_xmr(monero_wallet).await?;
|
|
||||||
tracing::info!("bob has claimed xmr");
|
|
||||||
Ok(state5.into())
|
|
||||||
}
|
|
||||||
State::State5(state5) => Ok(state5.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
State0(State0),
|
State0(State0),
|
||||||
|
@ -593,7 +314,7 @@ impl State3 {
|
||||||
));
|
));
|
||||||
let S = self.S_a_monero + S_b_monero;
|
let S = self.S_a_monero + S_b_monero;
|
||||||
|
|
||||||
xmr_wallet
|
let monero_transfer_info = xmr_wallet
|
||||||
.watch_for_transfer(
|
.watch_for_transfer(
|
||||||
S,
|
S,
|
||||||
self.v.public(),
|
self.v.public(),
|
||||||
|
@ -603,6 +324,11 @@ impl State3 {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"XMR lock tx 1st confirmation at block height: {}",
|
||||||
|
monero_transfer_info.first_confirmation_block_height
|
||||||
|
);
|
||||||
|
|
||||||
Ok(State4 {
|
Ok(State4 {
|
||||||
A: self.A,
|
A: self.A,
|
||||||
b: self.b,
|
b: self.b,
|
||||||
|
@ -620,6 +346,7 @@ impl State3 {
|
||||||
tx_lock: self.tx_lock,
|
tx_lock: self.tx_lock,
|
||||||
tx_cancel_sig_a: self.tx_cancel_sig_a,
|
tx_cancel_sig_a: self.tx_cancel_sig_a,
|
||||||
tx_refund_encsig: self.tx_refund_encsig,
|
tx_refund_encsig: self.tx_refund_encsig,
|
||||||
|
monero_rescan_block_height: monero_transfer_info.first_confirmation_block_height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,6 +380,7 @@ impl State3 {
|
||||||
tx_lock: self.tx_lock.clone(),
|
tx_lock: self.tx_lock.clone(),
|
||||||
tx_cancel_sig_a: self.tx_cancel_sig_a.clone(),
|
tx_cancel_sig_a: self.tx_cancel_sig_a.clone(),
|
||||||
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
||||||
|
monero_rescan_block_height: 0u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,6 +421,7 @@ pub struct State4 {
|
||||||
pub tx_lock: bitcoin::TxLock,
|
pub tx_lock: bitcoin::TxLock,
|
||||||
pub tx_cancel_sig_a: Signature,
|
pub tx_cancel_sig_a: Signature,
|
||||||
pub tx_refund_encsig: EncryptedSignature,
|
pub tx_refund_encsig: EncryptedSignature,
|
||||||
|
pub monero_rescan_block_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State4 {
|
impl State4 {
|
||||||
|
@ -789,6 +518,7 @@ impl State4 {
|
||||||
tx_lock: self.tx_lock.clone(),
|
tx_lock: self.tx_lock.clone(),
|
||||||
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
tx_refund_encsig: self.tx_refund_encsig.clone(),
|
||||||
tx_cancel_sig: self.tx_cancel_sig_a.clone(),
|
tx_cancel_sig: self.tx_cancel_sig_a.clone(),
|
||||||
|
monero_rescan_block_height: self.monero_rescan_block_height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,6 +616,7 @@ pub struct State5 {
|
||||||
pub tx_lock: bitcoin::TxLock,
|
pub tx_lock: bitcoin::TxLock,
|
||||||
tx_refund_encsig: EncryptedSignature,
|
tx_refund_encsig: EncryptedSignature,
|
||||||
tx_cancel_sig: Signature,
|
tx_cancel_sig: Signature,
|
||||||
|
pub monero_rescan_block_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State5 {
|
impl State5 {
|
||||||
|
@ -902,7 +633,7 @@ impl State5 {
|
||||||
// NOTE: This actually generates and opens a new wallet, closing the currently
|
// NOTE: This actually generates and opens a new wallet, closing the currently
|
||||||
// open one.
|
// open one.
|
||||||
monero_wallet
|
monero_wallet
|
||||||
.create_and_load_wallet_for_output(s, self.v)
|
.create_and_load_wallet_for_output(s, self.v, Some(self.monero_rescan_block_height))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![warn(
|
#![warn(
|
||||||
unused_extern_crates,
|
unused_extern_crates,
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
missing_copy_implementations,
|
|
||||||
rust_2018_idioms,
|
rust_2018_idioms,
|
||||||
clippy::cast_possible_truncation,
|
clippy::cast_possible_truncation,
|
||||||
clippy::cast_sign_loss,
|
clippy::cast_sign_loss,
|
||||||
|
|
|
@ -14,7 +14,7 @@ use rust_decimal::{
|
||||||
};
|
};
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
pub const MIN_CONFIRMATIONS: u32 = 10;
|
pub const MIN_CONFIRMATIONS: u32 = 1;
|
||||||
pub const PICONERO_OFFSET: u64 = 1_000_000_000_000;
|
pub const PICONERO_OFFSET: u64 = 1_000_000_000_000;
|
||||||
|
|
||||||
pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
pub fn random_private_key<R: RngCore + CryptoRng>(rng: &mut R) -> PrivateKey {
|
||||||
|
@ -147,6 +147,11 @@ pub struct TransferProof {
|
||||||
tx_key: PrivateKey,
|
tx_key: PrivateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TransferInfo {
|
||||||
|
pub first_confirmation_block_height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
impl TransferProof {
|
impl TransferProof {
|
||||||
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
|
pub fn new(tx_hash: TxHash, tx_key: PrivateKey) -> Self {
|
||||||
Self { tx_hash, tx_key }
|
Self { tx_hash, tx_key }
|
||||||
|
@ -188,7 +193,7 @@ pub trait WatchForTransfer {
|
||||||
transfer_proof: TransferProof,
|
transfer_proof: TransferProof,
|
||||||
amount: Amount,
|
amount: Amount,
|
||||||
expected_confirmations: u32,
|
expected_confirmations: u32,
|
||||||
) -> Result<(), InsufficientFunds>;
|
) -> Result<TransferInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||||
|
@ -198,12 +203,19 @@ pub struct InsufficientFunds {
|
||||||
pub actual: Amount,
|
pub actual: Amount,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
#[error("Transfer with id {txid:?} not found.")]
|
||||||
|
pub struct TransferNotFound {
|
||||||
|
pub txid: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait CreateWalletForOutput {
|
pub trait CreateWalletForOutput {
|
||||||
async fn create_and_load_wallet_for_output(
|
async fn create_and_load_wallet_for_output(
|
||||||
&self,
|
&self,
|
||||||
private_spend_key: PrivateKey,
|
private_spend_key: PrivateKey,
|
||||||
private_view_key: PrivateViewKey,
|
private_view_key: PrivateViewKey,
|
||||||
|
restore_height: Option<u32>,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue