mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-11-29 12:06:35 -05:00
feat: cargo project at root
This commit is contained in:
parent
aa0c0623ca
commit
709a2820c4
313 changed files with 1 additions and 740 deletions
314
swap/src/database/alice.rs
Normal file
314
swap/src/database/alice.rs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
use crate::bitcoin::EncryptedSignature;
|
||||
use crate::monero;
|
||||
use crate::monero::{monero_private_key, TransferProof};
|
||||
use crate::protocol::alice;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use monero_rpc::wallet::BlockHeight;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
// Large enum variant is fine because this is only used for database
|
||||
// and is dropped once written in DB.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum Alice {
|
||||
Started {
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcLockTransactionSeen {
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcLocked {
|
||||
state3: alice::State3,
|
||||
},
|
||||
XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
XmrLocked {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
EncSigLearned {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
encrypted_signature: EncryptedSignature,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcRedeemTransactionPublished {
|
||||
state3: alice::State3,
|
||||
},
|
||||
CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcCancelled {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcPunishable {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
},
|
||||
BtcRefunded {
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
transfer_proof: TransferProof,
|
||||
state3: alice::State3,
|
||||
#[serde(with = "monero_private_key")]
|
||||
spend_key: monero::PrivateKey,
|
||||
},
|
||||
Done(AliceEndState),
|
||||
}
|
||||
|
||||
#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum AliceEndState {
|
||||
SafelyAborted,
|
||||
BtcRedeemed,
|
||||
XmrRefunded,
|
||||
BtcPunished { state3: alice::State3 },
|
||||
}
|
||||
|
||||
impl From<AliceState> for Alice {
|
||||
fn from(alice_state: AliceState) -> Self {
|
||||
match alice_state {
|
||||
AliceState::Started { state3 } => Alice::Started {
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcLockTransactionSeen { state3 } => Alice::BtcLockTransactionSeen {
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcLocked { state3 } => Alice::BtcLocked {
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrLocked {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLocked {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
encrypted_signature,
|
||||
} => Alice::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
encrypted_signature: encrypted_signature.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcRedeemTransactionPublished { state3 } => {
|
||||
Alice::BtcRedeemTransactionPublished {
|
||||
state3: state3.as_ref().clone(),
|
||||
}
|
||||
}
|
||||
AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed),
|
||||
AliceState::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
} => Alice::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded),
|
||||
AliceState::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => Alice::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state3.as_ref().clone(),
|
||||
},
|
||||
AliceState::BtcPunished { state3 } => Alice::Done(AliceEndState::BtcPunished {
|
||||
state3: state3.as_ref().clone(),
|
||||
}),
|
||||
AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Alice> for AliceState {
|
||||
fn from(db_state: Alice) -> Self {
|
||||
match db_state {
|
||||
Alice::Started { state3 } => AliceState::Started {
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::BtcLockTransactionSeen { state3 } => AliceState::BtcLockTransactionSeen {
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::BtcLocked { state3 } => AliceState::BtcLocked {
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::XmrLockTransactionSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::XmrLocked {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::XmrLocked {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::XmrLockTransferProofSent {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: state,
|
||||
encrypted_signature,
|
||||
} => AliceState::EncSigLearned {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state),
|
||||
encrypted_signature: Box::new(encrypted_signature),
|
||||
},
|
||||
Alice::BtcRedeemTransactionPublished { state3 } => {
|
||||
AliceState::BtcRedeemTransactionPublished {
|
||||
state3: Box::new(state3),
|
||||
}
|
||||
}
|
||||
Alice::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::CancelTimelockExpired {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::BtcCancelled {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
|
||||
Alice::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3,
|
||||
} => AliceState::BtcPunishable {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3,
|
||||
} => AliceState::BtcRefunded {
|
||||
monero_wallet_restore_blockheight,
|
||||
transfer_proof,
|
||||
spend_key,
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
Alice::Done(end_state) => match end_state {
|
||||
AliceEndState::SafelyAborted => AliceState::SafelyAborted,
|
||||
AliceEndState::BtcRedeemed => AliceState::BtcRedeemed,
|
||||
AliceEndState::XmrRefunded => AliceState::XmrRefunded,
|
||||
AliceEndState::BtcPunished { state3 } => AliceState::BtcPunished {
|
||||
state3: Box::new(state3),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Alice {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Alice::Started { .. } => write!(f, "Started"),
|
||||
Alice::BtcLockTransactionSeen { .. } => {
|
||||
write!(f, "Bitcoin lock transaction in mempool")
|
||||
}
|
||||
Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
||||
Alice::XmrLockTransactionSent { .. } => f.write_str("Monero lock transaction sent"),
|
||||
Alice::XmrLocked { .. } => f.write_str("Monero locked"),
|
||||
Alice::XmrLockTransferProofSent { .. } => {
|
||||
f.write_str("Monero lock transfer proof sent")
|
||||
}
|
||||
Alice::EncSigLearned { .. } => f.write_str("Encrypted signature learned"),
|
||||
Alice::BtcRedeemTransactionPublished { .. } => {
|
||||
f.write_str("Bitcoin redeem transaction published")
|
||||
}
|
||||
Alice::CancelTimelockExpired { .. } => f.write_str("Cancel timelock is expired"),
|
||||
Alice::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"),
|
||||
Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"),
|
||||
Alice::BtcRefunded { .. } => f.write_str("Monero refundable"),
|
||||
Alice::Done(end_state) => write!(f, "Done: {}", end_state),
|
||||
}
|
||||
}
|
||||
}
|
||||
155
swap/src/database/bob.rs
Normal file
155
swap/src/database/bob.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use crate::monero::TransferProof;
|
||||
use crate::protocol::bob;
|
||||
use crate::protocol::bob::BobState;
|
||||
use monero_rpc::wallet::BlockHeight;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, DisplayFromStr};
|
||||
use std::fmt;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum Bob {
|
||||
Started {
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
btc_amount: bitcoin::Amount,
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
change_address: bitcoin::Address,
|
||||
},
|
||||
ExecutionSetupDone {
|
||||
state2: bob::State2,
|
||||
},
|
||||
BtcLocked {
|
||||
state3: bob::State3,
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
},
|
||||
XmrLockProofReceived {
|
||||
state: bob::State3,
|
||||
lock_transfer_proof: TransferProof,
|
||||
monero_wallet_restore_blockheight: BlockHeight,
|
||||
},
|
||||
XmrLocked {
|
||||
state4: bob::State4,
|
||||
},
|
||||
EncSigSent {
|
||||
state4: bob::State4,
|
||||
},
|
||||
BtcPunished {
|
||||
state: bob::State6,
|
||||
tx_lock_id: bitcoin::Txid,
|
||||
},
|
||||
BtcRedeemed(bob::State5),
|
||||
CancelTimelockExpired(bob::State6),
|
||||
BtcCancelled(bob::State6),
|
||||
Done(BobEndState),
|
||||
}
|
||||
|
||||
#[derive(Clone, strum::Display, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum BobEndState {
|
||||
SafelyAborted,
|
||||
XmrRedeemed { tx_lock_id: bitcoin::Txid },
|
||||
BtcRefunded(Box<bob::State6>),
|
||||
}
|
||||
|
||||
impl From<BobState> for Bob {
|
||||
fn from(bob_state: BobState) -> Self {
|
||||
match bob_state {
|
||||
BobState::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
} => Bob::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
},
|
||||
BobState::SwapSetupCompleted(state2) => Bob::ExecutionSetupDone { state2 },
|
||||
BobState::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => Bob::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
},
|
||||
BobState::XmrLockProofReceived {
|
||||
state,
|
||||
lock_transfer_proof,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => Bob::XmrLockProofReceived {
|
||||
state,
|
||||
lock_transfer_proof,
|
||||
monero_wallet_restore_blockheight,
|
||||
},
|
||||
BobState::XmrLocked(state4) => Bob::XmrLocked { state4 },
|
||||
BobState::EncSigSent(state4) => Bob::EncSigSent { state4 },
|
||||
BobState::BtcRedeemed(state5) => Bob::BtcRedeemed(state5),
|
||||
BobState::CancelTimelockExpired(state6) => Bob::CancelTimelockExpired(state6),
|
||||
BobState::BtcCancelled(state6) => Bob::BtcCancelled(state6),
|
||||
BobState::BtcPunished { state, tx_lock_id } => Bob::BtcPunished { state, tx_lock_id },
|
||||
BobState::BtcRefunded(state6) => Bob::Done(BobEndState::BtcRefunded(Box::new(state6))),
|
||||
BobState::XmrRedeemed { tx_lock_id } => {
|
||||
Bob::Done(BobEndState::XmrRedeemed { tx_lock_id })
|
||||
}
|
||||
BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bob> for BobState {
|
||||
fn from(db_state: Bob) -> Self {
|
||||
match db_state {
|
||||
Bob::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
} => BobState::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
},
|
||||
Bob::ExecutionSetupDone { state2 } => BobState::SwapSetupCompleted(state2),
|
||||
Bob::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => BobState::BtcLocked {
|
||||
state3,
|
||||
monero_wallet_restore_blockheight,
|
||||
},
|
||||
Bob::XmrLockProofReceived {
|
||||
state,
|
||||
lock_transfer_proof,
|
||||
monero_wallet_restore_blockheight,
|
||||
} => BobState::XmrLockProofReceived {
|
||||
state,
|
||||
lock_transfer_proof,
|
||||
monero_wallet_restore_blockheight,
|
||||
},
|
||||
Bob::XmrLocked { state4 } => BobState::XmrLocked(state4),
|
||||
Bob::EncSigSent { state4 } => BobState::EncSigSent(state4),
|
||||
Bob::BtcRedeemed(state5) => BobState::BtcRedeemed(state5),
|
||||
Bob::CancelTimelockExpired(state6) => BobState::CancelTimelockExpired(state6),
|
||||
Bob::BtcCancelled(state6) => BobState::BtcCancelled(state6),
|
||||
Bob::BtcPunished { state, tx_lock_id } => BobState::BtcPunished { state, tx_lock_id },
|
||||
Bob::Done(end_state) => match end_state {
|
||||
BobEndState::SafelyAborted => BobState::SafelyAborted,
|
||||
BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id },
|
||||
BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Bob {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Bob::Started { .. } => write!(f, "Started"),
|
||||
Bob::ExecutionSetupDone { .. } => f.write_str("Execution setup done"),
|
||||
Bob::BtcLocked { .. } => f.write_str("Bitcoin locked"),
|
||||
Bob::XmrLockProofReceived { .. } => {
|
||||
f.write_str("XMR lock transaction transfer proof received")
|
||||
}
|
||||
Bob::XmrLocked { .. } => f.write_str("Monero locked"),
|
||||
Bob::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"),
|
||||
Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"),
|
||||
Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"),
|
||||
Bob::Done(end_state) => write!(f, "Done: {}", end_state),
|
||||
Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"),
|
||||
Bob::BtcPunished { .. } => f.write_str("Bitcoin punished"),
|
||||
}
|
||||
}
|
||||
}
|
||||
511
swap/src/database/sqlite.rs
Normal file
511
swap/src/database/sqlite.rs
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
use crate::database::Swap;
|
||||
use crate::monero::{Address, TransferProof};
|
||||
use crate::protocol::{Database, State};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use sqlx::sqlite::Sqlite;
|
||||
use sqlx::{Pool, SqlitePool};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct SqliteDatabase {
|
||||
pool: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl SqliteDatabase {
|
||||
pub async fn open(path: impl AsRef<Path>) -> Result<Self>
|
||||
where
|
||||
Self: std::marker::Sized,
|
||||
{
|
||||
let path_str = format!("sqlite:{}", path.as_ref().display());
|
||||
let pool = SqlitePool::connect(&path_str).await?;
|
||||
let mut sqlite = Self { pool };
|
||||
sqlite.run_migrations().await?;
|
||||
Ok(sqlite)
|
||||
}
|
||||
|
||||
async fn run_migrations(&mut self) -> anyhow::Result<()> {
|
||||
sqlx::migrate!("./migrations").run(&self.pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for SqliteDatabase {
|
||||
async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
let peer_id = peer_id.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into peers (
|
||||
swap_id,
|
||||
peer_id
|
||||
) values (?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
peer_id
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_peer_id(&self, swap_id: Uuid) -> Result<PeerId> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT peer_id
|
||||
FROM peers
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
let peer_id = PeerId::from_str(&row.peer_id)?;
|
||||
Ok(peer_id)
|
||||
}
|
||||
|
||||
async fn insert_monero_address(&self, swap_id: Uuid, address: Address) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
let address = address.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into monero_addresses (
|
||||
swap_id,
|
||||
address
|
||||
) values (?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
address
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_monero_address(&self, swap_id: Uuid) -> Result<Address> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT address
|
||||
FROM monero_addresses
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
let address = row.address.parse()?;
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let peer_id = peer_id.to_string();
|
||||
let address = address.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into peer_addresses (
|
||||
peer_id,
|
||||
address
|
||||
) values (?, ?);
|
||||
"#,
|
||||
peer_id,
|
||||
address
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_addresses(&self, peer_id: PeerId) -> Result<Vec<Multiaddr>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
|
||||
let peer_id = peer_id.to_string();
|
||||
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT DISTINCT address
|
||||
FROM peer_addresses
|
||||
WHERE peer_id = ?
|
||||
"#,
|
||||
peer_id,
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let addresses = rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let multiaddr = Multiaddr::from_str(&row.address)?;
|
||||
Ok(multiaddr)
|
||||
})
|
||||
.collect::<Result<Vec<Multiaddr>>>();
|
||||
|
||||
addresses
|
||||
}
|
||||
|
||||
async fn get_swap_start_date(&self, swap_id: Uuid) -> Result<String> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT min(entered_at) as start_date
|
||||
FROM swap_states
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
row.start_date
|
||||
.ok_or_else(|| anyhow!("Could not get swap start date"))
|
||||
}
|
||||
|
||||
async fn insert_latest_state(&self, swap_id: Uuid, state: State) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let entered_at = OffsetDateTime::now_utc();
|
||||
|
||||
let swap_id = swap_id.to_string();
|
||||
let swap = serde_json::to_string(&Swap::from(state))?;
|
||||
let entered_at = entered_at.to_string();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into swap_states (
|
||||
swap_id,
|
||||
entered_at,
|
||||
state
|
||||
) values (?, ?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
entered_at,
|
||||
swap
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_state(&self, swap_id: Uuid) -> Result<State> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let swap_id = swap_id.to_string();
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT state
|
||||
FROM swap_states
|
||||
WHERE swap_id = ?
|
||||
ORDER BY id desc
|
||||
LIMIT 1;
|
||||
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let row = row
|
||||
.first()
|
||||
.context(format!("No state in database for swap: {}", swap_id))?;
|
||||
let swap: Swap = serde_json::from_str(&row.state)?;
|
||||
|
||||
Ok(swap.into())
|
||||
}
|
||||
|
||||
async fn all(&self) -> Result<Vec<(Uuid, State)>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT swap_id, state
|
||||
FROM (
|
||||
SELECT max(id), swap_id, state
|
||||
FROM swap_states
|
||||
GROUP BY swap_id
|
||||
)
|
||||
"#
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let result = rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let swap_id = Uuid::from_str(&row.swap_id)?;
|
||||
let state = match serde_json::from_str::<Swap>(&row.state) {
|
||||
Ok(a) => Ok(State::from(a)),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
Ok((swap_id, state))
|
||||
})
|
||||
.collect::<Result<Vec<(Uuid, State)>>>();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn get_states(&self, swap_id: Uuid) -> Result<Vec<State>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
// TODO: We should use query! instead of query here to allow for at-compile-time validation
|
||||
// I didn't manage to generate the mappings for the query! macro because of problems with sqlx-cli
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT state
|
||||
FROM swap_states
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let result = rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let state_str: &str = &row.state;
|
||||
|
||||
let state = match serde_json::from_str::<Swap>(state_str) {
|
||||
Ok(a) => Ok(State::from(a)),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
Ok(state)
|
||||
})
|
||||
.collect::<Result<Vec<State>>>();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn insert_buffered_transfer_proof(
|
||||
&self,
|
||||
swap_id: Uuid,
|
||||
proof: TransferProof,
|
||||
) -> Result<()> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let swap_id = swap_id.to_string();
|
||||
let proof = serde_json::to_string(&proof)?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO buffered_transfer_proofs (
|
||||
swap_id,
|
||||
proof
|
||||
) VALUES (?, ?);
|
||||
"#,
|
||||
swap_id,
|
||||
proof
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_buffered_transfer_proof(&self, swap_id: Uuid) -> Result<Option<TransferProof>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let swap_id = swap_id.to_string();
|
||||
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT proof
|
||||
FROM buffered_transfer_proofs
|
||||
WHERE swap_id = ?
|
||||
"#,
|
||||
swap_id
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
if row.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let proof_str = &row[0].proof;
|
||||
let proof = serde_json::from_str(proof_str)?;
|
||||
|
||||
Ok(Some(proof))
|
||||
}
|
||||
|
||||
async fn raw_all(&self) -> Result<HashMap<Uuid, Vec<serde_json::Value>>> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT swap_id, state
|
||||
FROM swap_states
|
||||
"#
|
||||
)
|
||||
.fetch_all(&mut conn)
|
||||
.await?;
|
||||
|
||||
let mut swaps: HashMap<Uuid, Vec<serde_json::Value>> = HashMap::new();
|
||||
|
||||
for row in &rows {
|
||||
let swap_id = Uuid::from_str(&row.swap_id)?;
|
||||
let state = serde_json::from_str(&row.state)?;
|
||||
|
||||
if let std::collections::hash_map::Entry::Vacant(e) = swaps.entry(swap_id) {
|
||||
e.insert(vec![state]);
|
||||
} else {
|
||||
swaps
|
||||
.get_mut(&swap_id)
|
||||
.ok_or_else(|| anyhow!("Error while retrieving the swap"))?
|
||||
.push(state);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(swaps)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::protocol::alice::AliceState;
|
||||
use crate::protocol::bob::BobState;
|
||||
use std::fs::File;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_and_load_state() {
|
||||
let db = setup_test_db().await.unwrap();
|
||||
|
||||
let state_1 = State::Alice(AliceState::BtcRedeemed);
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
|
||||
db.insert_latest_state(swap_id_1, state_1).await.unwrap();
|
||||
|
||||
let state_1 = State::Alice(AliceState::BtcRedeemed);
|
||||
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let state_1_loaded = db.get_state(swap_id_1).await.unwrap();
|
||||
|
||||
assert_eq!(state_1, state_1_loaded);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_retrieve_all_latest_states() {
|
||||
let db = setup_test_db().await.unwrap();
|
||||
|
||||
let state_1 = State::Alice(AliceState::BtcRedeemed);
|
||||
let state_2 = State::Alice(AliceState::SafelyAborted);
|
||||
let state_3 = State::Bob(BobState::SafelyAborted);
|
||||
let swap_id_1 = Uuid::new_v4();
|
||||
let swap_id_2 = Uuid::new_v4();
|
||||
|
||||
db.insert_latest_state(swap_id_1, state_1.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
db.insert_latest_state(swap_id_1, state_2.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
db.insert_latest_state(swap_id_2, state_3.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let latest_loaded = db.all().await.unwrap();
|
||||
|
||||
assert_eq!(latest_loaded.len(), 2);
|
||||
|
||||
assert!(latest_loaded.contains(&(swap_id_1, state_2)));
|
||||
assert!(latest_loaded.contains(&(swap_id_2, state_3)));
|
||||
|
||||
assert!(!latest_loaded.contains(&(swap_id_1, state_1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_load_monero_address() -> Result<()> {
|
||||
let db = setup_test_db().await?;
|
||||
|
||||
let swap_id = Uuid::new_v4();
|
||||
let monero_address = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a".parse()?;
|
||||
|
||||
db.insert_monero_address(swap_id, monero_address).await?;
|
||||
|
||||
let loaded_monero_address = db.get_monero_address(swap_id).await?;
|
||||
|
||||
assert_eq!(monero_address, loaded_monero_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_and_load_multiaddr() -> Result<()> {
|
||||
let db = setup_test_db().await?;
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
let multiaddr1 = "/ip4/127.0.0.1".parse::<Multiaddr>()?;
|
||||
let multiaddr2 = "/ip4/127.0.0.2".parse::<Multiaddr>()?;
|
||||
|
||||
db.insert_address(peer_id, multiaddr1.clone()).await?;
|
||||
db.insert_address(peer_id, multiaddr2.clone()).await?;
|
||||
|
||||
let loaded_multiaddr = db.get_addresses(peer_id).await?;
|
||||
|
||||
assert!(loaded_multiaddr.contains(&multiaddr1));
|
||||
assert!(loaded_multiaddr.contains(&multiaddr2));
|
||||
assert_eq!(loaded_multiaddr.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_and_load_peer_id() -> Result<()> {
|
||||
let db = setup_test_db().await?;
|
||||
|
||||
let peer_id = PeerId::random();
|
||||
let multiaddr1 = "/ip4/127.0.0.1".parse::<Multiaddr>()?;
|
||||
let multiaddr2 = "/ip4/127.0.0.2".parse::<Multiaddr>()?;
|
||||
|
||||
db.insert_address(peer_id, multiaddr1.clone()).await?;
|
||||
db.insert_address(peer_id, multiaddr2.clone()).await?;
|
||||
|
||||
let loaded_multiaddr = db.get_addresses(peer_id).await?;
|
||||
|
||||
assert!(loaded_multiaddr.contains(&multiaddr1));
|
||||
assert!(loaded_multiaddr.contains(&multiaddr2));
|
||||
assert_eq!(loaded_multiaddr.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn setup_test_db() -> Result<SqliteDatabase> {
|
||||
let temp_db = tempdir().unwrap().into_path().join("tempdb");
|
||||
|
||||
// file has to exist in order to connect with sqlite
|
||||
File::create(temp_db.clone()).unwrap();
|
||||
|
||||
let db = SqliteDatabase::open(temp_db).await?;
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue