diff --git a/lib/core/src/sync/mod.rs b/lib/core/src/sync/mod.rs index 472822176..48a9a0068 100644 --- a/lib/core/src/sync/mod.rs +++ b/lib/core/src/sync/mod.rs @@ -209,4 +209,161 @@ impl SyncService { Ok(()) } } + +#[cfg(test)] +mod tests { + use anyhow::{anyhow, Result}; + use std::sync::Arc; + use tokio::sync::mpsc; + + use crate::{ + prelude::Signer, + test_utils::{ + persist::new_persister, + sync::{ + new_chain_sync_data, new_receive_sync_data, new_send_sync_data, MockSyncerClient, + }, + wallet::MockSigner, + }, + }; + + use super::{ + model::{data::SyncData, sync::Record}, + SyncService, + }; + + #[tokio::test] + async fn test_incoming_sync_create_and_update() -> Result<()> { + let (_temp_dir, persister) = new_persister()?; + let persister = Arc::new(persister); + + let signer: Arc> = Arc::new(Box::new(MockSigner::new())); + + let sync_data = vec![ + SyncData::Receive(new_receive_sync_data(None, None)), + SyncData::Send(new_send_sync_data(None, None, None)), + SyncData::Chain(new_chain_sync_data(None, None, None)), + ]; + let incoming_records = vec![ + Record::new( + "record-1".to_string(), + sync_data[0].clone(), + 1, + signer.clone(), + )?, + Record::new( + "record-2".to_string(), + sync_data[1].clone(), + 2, + signer.clone(), + )?, + Record::new( + "record-3".to_string(), + sync_data[2].clone(), + 3, + signer.clone(), + )?, + ]; + + let (incoming_tx, incoming_rx) = mpsc::channel::(10); + let client = Box::new(MockSyncerClient::new(incoming_rx)); + let sync_service = + SyncService::new("".to_string(), persister.clone(), signer.clone(), client); + + for record in incoming_records { + incoming_tx.send(record).await?; + } + sync_service.pull().await?; + + if let Some(receive_swap) = persister.fetch_receive_swap_by_id(&sync_data[0].id())? { + assert!(receive_swap.description.is_none()); + assert!(receive_swap.payment_hash.is_none()); + } else { + return Err(anyhow!("Receive swap not found")); + } + if let Some(send_swap) = persister.fetch_send_swap_by_id(&sync_data[1].id())? { + assert!(send_swap.preimage.is_none()); + assert!(send_swap.description.is_none()); + assert!(send_swap.payment_hash.is_none()); + } else { + return Err(anyhow!("Send swap not found")); + } + if let Some(chain_swap) = persister.fetch_chain_swap_by_id(&sync_data[2].id())? { + assert!(chain_swap.claim_address.is_none()); + assert!(chain_swap.description.is_none()); + assert!(chain_swap.accept_zero_conf.eq(&true)); + } else { + return Err(anyhow!("Chain swap not found")); + } + + let new_payment_hash = Some("payment_hash".to_string()); + let new_preimage = Some("preimage".to_string()); + let new_description = Some("description".to_string()); + let new_claim_address = Some("claim_address".to_string()); + let new_accept_zero_conf = false; + let sync_data = vec![ + SyncData::Receive(new_receive_sync_data( + new_payment_hash.clone(), + new_description.clone(), + )), + SyncData::Send(new_send_sync_data( + new_preimage.clone(), + new_payment_hash.clone(), + new_description.clone(), + )), + SyncData::Chain(new_chain_sync_data( + new_description.clone(), + new_claim_address.clone(), + Some(new_accept_zero_conf), + )), + ]; + let incoming_records = vec![ + Record::new( + "record-1".to_string(), + sync_data[0].clone(), + 4, + signer.clone(), + )?, + Record::new( + "record-2".to_string(), + sync_data[1].clone(), + 5, + signer.clone(), + )?, + Record::new( + "record-3".to_string(), + sync_data[2].clone(), + 6, + signer.clone(), + )?, + ]; + + for record in incoming_records { + incoming_tx.send(record).await?; + } + sync_service.pull().await?; + + if let Some(receive_swap) = persister.fetch_receive_swap_by_id(&sync_data[0].id())? { + assert_eq!(receive_swap.description, new_description); + assert_eq!(receive_swap.payment_hash, new_payment_hash); + } else { + return Err(anyhow!("Receive swap not found")); + } + if let Some(send_swap) = persister.fetch_send_swap_by_id(&sync_data[1].id())? { + assert_eq!(send_swap.preimage, new_preimage); + assert_eq!(send_swap.description, new_description); + assert_eq!(send_swap.payment_hash, new_payment_hash); + } else { + return Err(anyhow!("Send swap not found")); + } + if let Some(chain_swap) = persister.fetch_chain_swap_by_id(&sync_data[2].id())? { + assert_eq!(chain_swap.claim_address, new_claim_address); + assert_eq!(chain_swap.description, new_description); + assert_eq!(chain_swap.accept_zero_conf, new_accept_zero_conf); + } else { + return Err(anyhow!("Chain swap not found")); + } + + Ok(()) + } } diff --git a/lib/core/src/test_utils/mod.rs b/lib/core/src/test_utils/mod.rs index 9e71df43e..9312f0b2a 100644 --- a/lib/core/src/test_utils/mod.rs +++ b/lib/core/src/test_utils/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod sdk; pub(crate) mod send_swap; pub(crate) mod status_stream; pub(crate) mod swapper; +pub(crate) mod sync; pub(crate) mod wallet; pub(crate) fn generate_random_string(size: usize) -> String { diff --git a/lib/core/src/test_utils/sync.rs b/lib/core/src/test_utils/sync.rs new file mode 100644 index 000000000..a6b81d714 --- /dev/null +++ b/lib/core/src/test_utils/sync.rs @@ -0,0 +1,115 @@ +#![cfg(test)] + +use crate::{ + prelude::Direction, + sync::{ + client::SyncerClient, + model::{ + data::{ChainSyncData, ReceiveSyncData, SendSyncData}, + sync::{ + ListChangesReply, ListChangesRequest, Record, SetRecordReply, SetRecordRequest, + }, + }, + }, +}; +use anyhow::Result; +use async_trait::async_trait; +use tokio::sync::{mpsc::Receiver, Mutex}; + +pub(crate) struct MockSyncerClient { + pub(crate) incoming_rx: Mutex>, +} + +impl MockSyncerClient { + pub(crate) fn new(incoming_rx: Receiver) -> Self { + Self { + incoming_rx: Mutex::new(incoming_rx), + } + } +} + +#[async_trait] +impl SyncerClient for MockSyncerClient { + async fn connect(&self, _connect_url: String) -> Result<()> { + todo!() + } + + async fn push(&self, _req: SetRecordRequest) -> Result { + todo!() + } + + async fn pull(&self, _req: ListChangesRequest) -> Result { + let mut rx = self.incoming_rx.lock().await; + let mut changes = Vec::with_capacity(3); + rx.recv_many(&mut changes, 3).await; + Ok(ListChangesReply { changes }) + } + + async fn disconnect(&self) -> Result<()> { + todo!() + } +} + +pub(crate) fn new_receive_sync_data( + payment_hash: Option, + description: Option, +) -> ReceiveSyncData { + ReceiveSyncData { + swap_id: "receive-swap".to_string(), + invoice: "".to_string(), + create_response_json: "".to_string(), + payer_amount_sat: 0, + receiver_amount_sat: 0, + created_at: 0, + claim_fees_sat: 0, + claim_private_key: "".to_string(), + mrh_address: "".to_string(), + mrh_script_pubkey: "".to_string(), + preimage: "".to_string(), + payment_hash, + description, + } +} + +pub(crate) fn new_send_sync_data( + preimage: Option, + payment_hash: Option, + description: Option, +) -> SendSyncData { + SendSyncData { + swap_id: "send-swap".to_string(), + invoice: "".to_string(), + create_response_json: "".to_string(), + refund_private_key: "".to_string(), + payer_amount_sat: 0, + receiver_amount_sat: 0, + created_at: 0, + preimage, + payment_hash, + description, + } +} + +pub(crate) fn new_chain_sync_data( + description: Option, + claim_address: Option, + accept_zero_conf: Option, +) -> ChainSyncData { + ChainSyncData { + swap_id: "chain-swap".to_string(), + preimage: "".to_string(), + create_response_json: "".to_string(), + direction: Direction::Incoming, + lockup_address: "".to_string(), + claim_fees_sat: 0, + claim_private_key: "".to_string(), + refund_private_key: "".to_string(), + timeout_block_height: 0, + payer_amount_sat: 0, + receiver_amount_sat: 0, + accept_zero_conf: accept_zero_conf.unwrap_or(true), + created_at: 0, + description, + claim_address, + } +} diff --git a/lib/core/src/test_utils/wallet.rs b/lib/core/src/test_utils/wallet.rs index a350cb598..0ee8ec9b5 100644 --- a/lib/core/src/test_utils/wallet.rs +++ b/lib/core/src/test_utils/wallet.rs @@ -10,9 +10,12 @@ use crate::{ }; use anyhow::Result; use async_trait::async_trait; +use boltz_client::{Keypair, Secp256k1}; use lazy_static::lazy_static; use lwk_wollet::{ elements::{Address, Transaction}, + elements_miniscript::ToPublicKey as _, + secp256k1::Message, Tip, WalletTx, }; @@ -82,11 +85,15 @@ impl OnchainWallet for MockWallet { } } -pub(crate) struct MockSigner {} +pub(crate) struct MockSigner { + keypair: Keypair, +} impl MockSigner { pub(crate) fn new() -> Self { - Self {} + let secp = Secp256k1::new(); + let keypair = Keypair::new(&secp, &mut bip39::rand::thread_rng()); + Self { keypair } } } @@ -103,8 +110,16 @@ impl Signer for MockSigner { todo!() } - fn sign_ecdsa_recoverable(&self, _msg: Vec) -> Result, SignerError> { - todo!() + fn sign_ecdsa_recoverable(&self, msg: Vec) -> Result, SignerError> { + let secp = Secp256k1::new(); + let msg: Message = Message::from_digest_slice(msg.as_slice()) + .map_err(|e| SignerError::Generic { err: e.to_string() })?; + // Get message signature and encode to zbase32 + let recoverable_sig = secp.sign_ecdsa_recoverable(&msg, &self.keypair.secret_key()); + let (recovery_id, sig) = recoverable_sig.serialize_compact(); + let mut complete_signature = vec![31 + recovery_id.to_i32() as u8]; + complete_signature.extend_from_slice(&sig); + Ok(complete_signature) } fn slip77_master_blinding_key(&self) -> Result, SignerError> { @@ -116,10 +131,14 @@ impl Signer for MockSigner { } fn ecies_encrypt(&self, msg: &[u8]) -> Result, SignerError> { - todo!() + let rc_pub = self.keypair.public_key().to_public_key().to_bytes(); + Ok(ecies::encrypt(&rc_pub, msg) + .map_err(|err| anyhow::anyhow!("Could not encrypt data: {err}"))?) } fn ecies_decrypt(&self, msg: &[u8]) -> Result, SignerError> { - todo!() + let rc_prv = self.keypair.secret_bytes(); + Ok(ecies::decrypt(&rc_prv, msg) + .map_err(|err| anyhow::anyhow!("Could not decrypt data: {err}"))?) } }