From f709f82635d177f1c38d673c9dda8feefffe0506 Mon Sep 17 00:00:00 2001 From: f321x Date: Sat, 20 Jul 2024 22:39:30 +0200 Subject: [PATCH] cleanup and seperate webserver from logic --- taptrade-cli-demo/coordinator/Cargo.lock | 102 ++++++++ taptrade-cli-demo/coordinator/Cargo.toml | 1 + .../coordinator/src/communication/api.rs | 19 +- .../coordinator/src/communication/mod.rs | 205 ++++++---------- .../coordinator/src/communication/utils.rs | 53 ++--- .../coordinator/src/coordinator/mod.rs | 224 +++++++++++++++++- .../coordinator/src/coordinator/utils.rs | 13 + .../coordinator/src/database/db_tests.rs | 2 +- .../coordinator/src/database/mod.rs | 2 +- taptrade-cli-demo/coordinator/src/main.rs | 7 +- 10 files changed, 440 insertions(+), 188 deletions(-) create mode 100644 taptrade-cli-demo/coordinator/src/coordinator/utils.rs diff --git a/taptrade-cli-demo/coordinator/Cargo.lock b/taptrade-cli-demo/coordinator/Cargo.lock index 9fe721e..3214f5b 100644 --- a/taptrade-cli-demo/coordinator/Cargo.lock +++ b/taptrade-cli-demo/coordinator/Cargo.lock @@ -471,6 +471,7 @@ dependencies = [ "sqlx", "tokio", "tower", + "validator", ] [[package]] @@ -582,6 +583,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.68", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.68", +] + [[package]] name = "der" version = "0.7.9" @@ -1137,6 +1173,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1610,6 +1652,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -2367,6 +2433,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2710,6 +2782,36 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "validator" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55591299b7007f551ed1eb79a684af7672c19c3193fb9e0a31936987bb2438ec" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/taptrade-cli-demo/coordinator/Cargo.toml b/taptrade-cli-demo/coordinator/Cargo.toml index 5ae6317..c947789 100644 --- a/taptrade-cli-demo/coordinator/Cargo.toml +++ b/taptrade-cli-demo/coordinator/Cargo.toml @@ -24,6 +24,7 @@ tower = "0.4.13" log = "0.4.22" env_logger = "0.11.3" sha2 = "0.10.8" +validator = { version = "0.18.1", features = ["derive"] } [profile.release] lto = true diff --git a/taptrade-cli-demo/coordinator/src/communication/api.rs b/taptrade-cli-demo/coordinator/src/communication/api.rs index 902350b..ceb8c2c 100644 --- a/taptrade-cli-demo/coordinator/src/communication/api.rs +++ b/taptrade-cli-demo/coordinator/src/communication/api.rs @@ -1,17 +1,20 @@ use super::*; // Receiving this struct as input to the server -#[derive(Deserialize, Serialize, Debug)] -pub struct OrderRequest { - pub robohash_hex: String, // identifier of the trader - pub amount_satoshi: u64, // amount in satoshi to buy or sell - pub is_buy_order: bool, // true if buy, false if sell - pub bond_ratio: u8, // [2, 50]% of trading amount +#[derive(Deserialize, Serialize, Debug, Validate)] +pub struct OfferRequest { + pub robohash_hex: String, // identifier of the trader + #[validate(range(min = 10000, max = 20000000))] + pub amount_satoshi: u64, // amount in satoshi to buy or sell + pub is_buy_order: bool, // true if buy, false if sell + #[validate(range(min = 2, max = 50))] + pub bond_ratio: u8, // [2, 50]% of trading amount + #[validate(custom(function = "validate_timestamp"))] pub offer_duration_ts: u64, // unix timestamp how long the offer should stay available } // Define a struct representing your response data -#[derive(Serialize, PartialEq, Debug)] +#[derive(Serialize, PartialEq, Debug, Validate)] pub struct BondRequirementResponse { pub bond_address: String, pub locking_amount_sat: u64, // min amount of the bond output in sat @@ -30,7 +33,7 @@ pub struct BondSubmissionRequest { // Response after step2 if offer creation was successful and the offer is now online in the orderbook #[derive(Serialize)] -pub struct OrderActivatedResponse { +pub struct OfferActivatedResponse { pub offer_id_hex: String, pub bond_locked_until_timestamp: u64, // unix timestamp. Do not touch bond till then unless offer gets taken. } diff --git a/taptrade-cli-demo/coordinator/src/communication/mod.rs b/taptrade-cli-demo/coordinator/src/communication/mod.rs index 2bcb235..8924bd2 100755 --- a/taptrade-cli-demo/coordinator/src/communication/mod.rs +++ b/taptrade-cli-demo/coordinator/src/communication/mod.rs @@ -5,14 +5,12 @@ use self::api::*; use self::utils::*; use super::*; use crate::wallet::*; -use anyhow::Context; use axum::{ http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Extension, Json, Router, }; -use rand::Rng; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use tokio::net::TcpListener; @@ -22,152 +20,97 @@ use tokio::net::TcpListener; // /// Handler function to process the received data async fn receive_order( - Extension(database): Extension>, - Extension(wallet): Extension>>, - Json(order): Json, + Extension(coordinator): Extension>, + Json(offer): Json, ) -> Result { - debug!("{:#?}", &order); - if order.sanity_check().is_err() { - warn!("Received order failed sanity check"); - return Ok(StatusCode::NOT_ACCEPTABLE.into_response()); + if let Err(_) = offer.validate() { + return Ok(StatusCode::BAD_REQUEST.into_response()); + } else { + let bond_requirements = process_order(coordinator, &offer).await?; + return Ok(Json(bond_requirements).into_response()); } - let bond_requirements = BondRequirementResponse { - bond_address: wallet.get_new_address().await?, - locking_amount_sat: order.amount_satoshi * order.bond_ratio as u64 / 100, - }; - // insert offer into sql database - database - .insert_new_maker_request(&order, &bond_requirements) - .await?; - debug!("Coordinator received new offer: {:?}", order); - Ok(Json(bond_requirements).into_response()) } /// receives the maker bond, verifies it and moves to offer to the active table (orderbook) async fn submit_maker_bond( - Extension(database): Extension>, - Extension(wallet): Extension>>, + Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { debug!("\n\nReceived maker bond: {:?}", payload); - let bond_requirements = if let Ok(requirements) = database - .fetch_bond_requirements(&payload.robohash_hex) - .await - { - requirements - } else { - return Ok(StatusCode::NOT_FOUND.into_response()); - }; - match wallet - .validate_bond_tx_hex(&payload.signed_bond_hex, &bond_requirements) - .await - { - Ok(()) => (), - Err(e) => { - error!("{}", e); + match handle_maker_bond(&payload, coordinator).await { + Ok(offer_activated_response) => Ok(Json(offer_activated_response).into_response()), + Err(BondError::BondNotFound) => { + info!("Bond requirements not found in database"); + return Ok(StatusCode::NOT_FOUND.into_response()); + } + Err(BondError::InvalidBond(e)) => { + warn!("Invalid bond submission: {e}"); return Ok(StatusCode::NOT_ACCEPTABLE.into_response()); } - } - debug!("\nBond validation successful"); - let offer_id_hex: String = generate_random_order_id(16); // 16 bytes random offer id, maybe a different system makes more sense later on? (uuid or increasing counter...) - // create address for taker bond - let new_taker_bond_address = wallet.get_new_address().await.context(format!( - "Error generating taker bond address for offer id: {}", - offer_id_hex - ))?; - // insert bond into sql database and move offer to different table - let bond_locked_until_timestamp = match database - .move_offer_to_active(&payload, &offer_id_hex, new_taker_bond_address) - .await - { - Ok(timestamp) => timestamp, - Err(e) => { - debug!("Error in validate_bond_tx_hex: {}", e); + Err(BondError::CoordinatorError(e)) => { + error!("Coordinator error on bond submission: {e}"); return Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()); } - }; - - // Create the JSON response - Ok(Json(OrderActivatedResponse { - bond_locked_until_timestamp, - offer_id_hex, - }) - .into_response()) + } } /// returns available offers from the active table (orderbook) async fn fetch_available_offers( - Extension(database): Extension>, + Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { - let offers: Option> = database.fetch_suitable_offers(&payload).await?; - if offers.is_none() { - return Ok(StatusCode::NO_CONTENT.into_response()); + debug!("\n\nReceived offer request: {:?}", payload); + + match get_public_offers(&payload, coordinator).await { + Ok(offers) => Ok(Json(offers).into_response()), + Err(FetchOffersError::NoOffersAvailable) => Ok(StatusCode::NO_CONTENT.into_response()), + Err(FetchOffersError::DatabaseError(e)) => { + error!("Database error fetching offers: {e}"); + Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) + } } - Ok(Json(PublicOffers { offers }).into_response()) } /// receives the taker bond for a given offer, verifies it, creates escrow transaction psbt /// and moves the offer to the taken table. Will return the trade contract psbt for the taker to sign. async fn submit_taker_bond( - Extension(database): Extension>, - Extension(wallet): Extension>>, + Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { - let bond_requirements = database - .fetch_taker_bond_requirements(&payload.offer.offer_id_hex) - .await; - match bond_requirements { - Ok(bond_requirements) => { - match wallet - .validate_bond_tx_hex(&payload.trade_data.signed_bond_hex, &bond_requirements) - .await - { - Ok(()) => (), - Err(e) => { - warn!("{}", e); - return Ok(StatusCode::NOT_ACCEPTABLE.into_response()); - } - } + debug!("\n\nReceived taker bond: {:?}", payload); + + match handle_taker_bond(&payload, coordinator).await { + Ok(offer_taken_response) => Ok(Json(offer_taken_response).into_response()), + Err(BondError::BondNotFound) => { + info!("Bond requirements not found in database"); + return Ok(StatusCode::NOT_FOUND.into_response()); + } + Err(BondError::InvalidBond(e)) => { + warn!("Invalid bond submission: {e}"); + return Ok(StatusCode::NOT_ACCEPTABLE.into_response()); + } + Err(BondError::CoordinatorError(e)) => { + error!("Coordinator error on bond submission: {e}"); + return Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()); } - Err(_) => return Ok(StatusCode::NOT_FOUND.into_response()), } - debug!("\nTaker bond validation successful"); - - panic!("Trade contract PSBT not implemented!"); - let trade_contract_psbt_taker = "".to_string(); // implement psbt - let trade_contract_psbt_maker = "".to_string(); // implement psbt - let escrow_tx_txid: String = "".to_string(); // implement txid of psbt - - database - .add_taker_info_and_move_table( - &payload, - &trade_contract_psbt_maker, - &trade_contract_psbt_taker, - escrow_tx_txid, - ) - .await?; - Ok(Json(OfferTakenResponse { - trade_psbt_hex_to_sign: trade_contract_psbt_taker, - }) - .into_response()) } /// gets polled by the maker and returns the escrow psbt in case the offer has been taken async fn request_offer_status_maker( - Extension(database): Extension>, + Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { - let offer = database - .fetch_taken_offer_maker(&payload.offer_id_hex, &payload.robohash_hex) - .await?; - match offer { - Some(offer) => Ok(Json(OfferTakenResponse { - trade_psbt_hex_to_sign: offer, - }) - .into_response()), - None => Ok(StatusCode::NO_CONTENT.into_response()), + debug!("\n\nReceived offer status request: {:?}", payload); + + match get_offer_status_maker(&payload, coordinator).await { + Ok(offer_taken_response) => Ok(Json(offer_taken_response).into_response()), + Err(FetchOffersError::NoOffersAvailable) => Ok(StatusCode::NO_CONTENT.into_response()), + Err(FetchOffersError::DatabaseError(e)) => { + error!("Database error fetching offers: {e}"); + Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) + } } } @@ -176,8 +119,7 @@ async fn request_offer_status_maker( /// coordinator then has to check if their signatures are valid and everything else is according to the agreed upon contract. /// Once the coordinator has received both partitial signed PSBTs he can assemble them together to a transaction and publish it to the bitcoin network. async fn submit_escrow_psbt( - Extension(database): Extension>, - Extension(wallet): Extension>>, + Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { panic!("implement") @@ -192,22 +134,20 @@ async fn submit_escrow_psbt( /// then the traders will know it is secure to begin with the fiat exchange and can continue with the trade (exchange information in the chat and transfer fiat). /// In theory this polling mechanism could also be replaced by the traders scanning the blockchain themself so they could also see once the tx is confirmed. async fn poll_escrow_confirmation( - Extension(database): Extension>, + Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { - if !database - .is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex) - .await? - { - return Ok(StatusCode::NOT_FOUND.into_response()); - } - if database - .fetch_escrow_tx_confirmation_status(&payload.offer_id_hex) - .await? - { - return Ok(StatusCode::OK.into_response()); - } else { - return Ok(StatusCode::ACCEPTED.into_response()); + match fetch_escrow_confirmation_status(&payload, coordinator).await { + Ok(true) => Ok(StatusCode::OK.into_response()), + Ok(false) => Ok(StatusCode::ACCEPTED.into_response()), + Err(FetchEscrowConfirmationError::NotFoundError) => { + info!("Escrow confirmation check transaction not found"); + Ok(StatusCode::NOT_FOUND.into_response()) + } + Err(FetchEscrowConfirmationError::DatabaseError(e)) => { + error!("Database error fetching escrow confirmation: {e}"); + Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) + } } } @@ -315,9 +255,6 @@ async fn test_api() -> &'static str { } pub async fn api_server(coordinator: Arc) -> Result<()> { - let database = Arc::clone(&coordinator.coordinator_db); - let wallet = Arc::clone(&coordinator.coordinator_wallet); - let app = Router::new() .route("/test", get(test_api)) .route("/create-offer", post(receive_order)) @@ -333,8 +270,7 @@ pub async fn api_server(coordinator: Arc) -> Result<()> { ) .route("/request-escrow", post(request_escrow)) .route("/poll-final-payout", post(poll_final_payout)) - .layer(Extension(database)) - .layer(Extension(wallet)); + .layer(Extension(coordinator)); // add other routes here let port: u16 = env::var("PORT") @@ -351,7 +287,8 @@ pub async fn api_server(coordinator: Arc) -> Result<()> { // ANYHOW ERROR HANDLING // -------------- // Make our own error that wraps `anyhow::Error`. -struct AppError(anyhow::Error); +#[derive(Debug)] +pub struct AppError(anyhow::Error); // Tell axum how to convert `AppError` into a response. impl IntoResponse for AppError { diff --git a/taptrade-cli-demo/coordinator/src/communication/utils.rs b/taptrade-cli-demo/coordinator/src/communication/utils.rs index d91d2b9..45f4642 100644 --- a/taptrade-cli-demo/coordinator/src/communication/utils.rs +++ b/taptrade-cli-demo/coordinator/src/communication/utils.rs @@ -1,43 +1,18 @@ -use anyhow::Context; - use super::*; -impl OrderRequest { - pub fn sanity_check(&self) -> Result<()> { - // Get the current time - let now = SystemTime::now(); - // Convert the current time to a UNIX timestamp - let unix_timestamp = now - .duration_since(UNIX_EPOCH) - .context("Time went backwards")? - .as_secs(); - if self.amount_satoshi < 10000 { - return Err(anyhow!("Amount too low")); - } - if self.amount_satoshi > 20000000 { - return Err(anyhow!("Amount too high")); - } - if self.bond_ratio < 2 || self.bond_ratio > 50 { - return Err(anyhow!("Bond ratio out of bounds")); - } - if self.offer_duration_ts < unix_timestamp + 10800 { - return Err(anyhow!("Offer duration too short")); - } - if self.offer_duration_ts > unix_timestamp + 604800 { - return Err(anyhow!("Offer duration too long")); - } - Ok(()) +pub fn validate_timestamp(offer_duration_ts: u64) -> Result<(), ValidationError> { + // Get the current time + let now = SystemTime::now(); + // Convert the current time to a UNIX timestamp + let unix_timestamp = now + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + if offer_duration_ts < unix_timestamp + 10800 { + return Err(ValidationError::new("Offer duration too short")); } -} - -pub fn generate_random_order_id(len: usize) -> String { - // Generate `len` random bytes - let bytes: Vec = rand::thread_rng() - .sample_iter(&rand::distributions::Standard) - .take(len) - .collect(); - - // Convert bytes to hex string - let hex_string = hex::encode(bytes); - hex_string + if offer_duration_ts > unix_timestamp + 604800 { + return Err(ValidationError::new("Offer duration too long")); + } + Ok(()) } diff --git a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs index 8bcadec..ab3078a 100755 --- a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs @@ -1,7 +1,227 @@ pub mod create_taproot; -// pub mod mempool_actor; pub mod mempool_monitoring; pub mod monitoring; -pub mod tx_confirmation_monitoring; // commented out for testing +pub mod tx_confirmation_monitoring; +pub mod utils; +use self::utils::*; use super::*; + +#[derive(Debug)] +pub enum BondError { + InvalidBond(String), + BondNotFound, + CoordinatorError(String), +} + +#[derive(Debug)] +pub enum FetchOffersError { + NoOffersAvailable, + DatabaseError(String), +} + +#[derive(Debug)] +pub enum FetchEscrowConfirmationError { + NotFoundError, + DatabaseError(String), +} + +pub async fn process_order( + coordinator: Arc, + offer: &OfferRequest, +) -> Result { + let wallet = &coordinator.coordinator_wallet; + let database = &coordinator.coordinator_db; + + let bond_address = wallet.get_new_address().await?; + let locking_amount_sat = offer.amount_satoshi * offer.bond_ratio as u64 / 100; + + let bond_requirements = BondRequirementResponse { + bond_address, + locking_amount_sat, + }; + + database + .insert_new_maker_request(offer, &bond_requirements) + .await?; + + debug!("Coordinator received new offer: {:?}", offer); + Ok(bond_requirements) +} + +pub async fn handle_maker_bond( + payload: &BondSubmissionRequest, + coordinator: Arc, +) -> Result { + let wallet = &coordinator.coordinator_wallet; + let database = &coordinator.coordinator_db; + + let bond_requirements = if let Ok(requirements) = database + .fetch_bond_requirements(&payload.robohash_hex) + .await + { + requirements + } else { + return Err(BondError::BondNotFound); + }; + + match wallet + .validate_bond_tx_hex(&payload.signed_bond_hex, &bond_requirements) + .await + { + Ok(()) => (), + Err(e) => { + return Err(BondError::InvalidBond(e.to_string())); + } + } + debug!("\nBond validation successful"); + let offer_id_hex: String = generate_random_order_id(16); // 16 bytes random offer id, maybe a different system makes more sense later on? (uuid or increasing counter...) + // create address for taker bond + let new_taker_bond_address = match wallet.get_new_address().await { + Ok(address) => address, + Err(e) => { + let error = format!( + "Error generating taker bond address for offer id: {}. Error: {e}", + offer_id_hex + ); + return Err(BondError::CoordinatorError(error.to_string())); + } + }; + // insert bond into sql database and move offer to different table + let bond_locked_until_timestamp = match database + .move_offer_to_active(&payload, &offer_id_hex, new_taker_bond_address) + .await + { + Ok(timestamp) => timestamp, + Err(e) => { + debug!( + "Error in validate_bond_tx_hex in move_offer_to_active: {}", + e + ); + return Err(BondError::CoordinatorError(e.to_string())); + } + }; + Ok(OfferActivatedResponse { + bond_locked_until_timestamp, + offer_id_hex, + }) +} + +pub async fn get_public_offers( + request: &OffersRequest, + coordinator: Arc, +) -> Result { + let database = &coordinator.coordinator_db; + + let offers = match database.fetch_suitable_offers(request).await { + Ok(offers) => offers, + Err(e) => { + return Err(FetchOffersError::DatabaseError(e.to_string())); + } + }; + if offers.is_none() { + return Err(FetchOffersError::NoOffersAvailable); + } + Ok(PublicOffers { offers }) +} + +pub async fn handle_taker_bond( + payload: &OfferPsbtRequest, + coordinator: Arc, +) -> Result { + let wallet = &coordinator.coordinator_wallet; + let database = &coordinator.coordinator_db; + + let bond_requirements = database + .fetch_taker_bond_requirements(&payload.offer.offer_id_hex) + .await; + + match bond_requirements { + Ok(bond_requirements) => { + match wallet + .validate_bond_tx_hex(&payload.trade_data.signed_bond_hex, &bond_requirements) + .await + { + Ok(()) => (), + Err(e) => { + return Err(BondError::InvalidBond(e.to_string())); + } + } + } + Err(_) => return Err(BondError::BondNotFound), + } + debug!("\nTaker bond validation successful"); + + panic!("Trade contract PSBT not implemented!"); + let trade_contract_psbt_taker = "".to_string(); // implement psbt + let trade_contract_psbt_maker = "".to_string(); // implement psbt + let escrow_tx_txid: String = "".to_string(); // implement txid of psbt + + if let Err(e) = database + .add_taker_info_and_move_table( + &payload, + &trade_contract_psbt_maker, + &trade_contract_psbt_taker, + escrow_tx_txid, + ) + .await + { + return Err(BondError::CoordinatorError(e.to_string())); + } + + Ok(OfferTakenResponse { + trade_psbt_hex_to_sign: trade_contract_psbt_taker, + }) +} + +pub async fn get_offer_status_maker( + payload: &OfferTakenRequest, + coordinator: Arc, +) -> Result { + let database = &coordinator.coordinator_db; + + let offer = match database + .fetch_taken_offer_maker(&payload.offer_id_hex, &payload.robohash_hex) + .await + { + Ok(offer) => offer, + Err(e) => { + return Err(FetchOffersError::DatabaseError(e.to_string())); + } + }; + match offer { + Some(offer) => Ok(OfferTakenResponse { + trade_psbt_hex_to_sign: offer, + }), + None => Err(FetchOffersError::NoOffersAvailable), + } +} + +pub async fn fetch_escrow_confirmation_status( + payload: &OfferTakenRequest, + coordinator: Arc, +) -> Result { + let database = &coordinator.coordinator_db; + + match database + .is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex) + .await + { + Ok(false) => return Err(FetchEscrowConfirmationError::NotFoundError), + Ok(true) => (), + Err(e) => return Err(FetchEscrowConfirmationError::DatabaseError(e.to_string())), + } + + if match database + .fetch_escrow_tx_confirmation_status(&payload.offer_id_hex) + .await + { + Ok(status) => status, + Err(e) => return Err(FetchEscrowConfirmationError::DatabaseError(e.to_string())), + } { + // rust smh + Ok(true) + } else { + Err(FetchEscrowConfirmationError::NotFoundError) + } +} diff --git a/taptrade-cli-demo/coordinator/src/coordinator/utils.rs b/taptrade-cli-demo/coordinator/src/coordinator/utils.rs new file mode 100644 index 0000000..544a8ed --- /dev/null +++ b/taptrade-cli-demo/coordinator/src/coordinator/utils.rs @@ -0,0 +1,13 @@ +use super::*; + +pub fn generate_random_order_id(len: usize) -> String { + // Generate `len` random bytes + let bytes: Vec = rand::thread_rng() + .sample_iter(&rand::distributions::Standard) + .take(len) + .collect(); + + // Convert bytes to hex string + let hex_string = hex::encode(bytes); + hex_string +} diff --git a/taptrade-cli-demo/coordinator/src/database/db_tests.rs b/taptrade-cli-demo/coordinator/src/database/db_tests.rs index aa88139..e2404ea 100644 --- a/taptrade-cli-demo/coordinator/src/database/db_tests.rs +++ b/taptrade-cli-demo/coordinator/src/database/db_tests.rs @@ -29,7 +29,7 @@ async fn test_insert_new_maker_request() -> Result<()> { let database = create_coordinator().await?; // Create a sample order request and bond requirement response - let order_request = OrderRequest { + let order_request = OfferRequest { robohash_hex: "a3f1f1f0e2f3f4f5".to_string(), is_buy_order: true, amount_satoshi: 1000, diff --git a/taptrade-cli-demo/coordinator/src/database/mod.rs b/taptrade-cli-demo/coordinator/src/database/mod.rs index 6a0da9d..5d738ae 100644 --- a/taptrade-cli-demo/coordinator/src/database/mod.rs +++ b/taptrade-cli-demo/coordinator/src/database/mod.rs @@ -147,7 +147,7 @@ impl CoordinatorDB { pub async fn insert_new_maker_request( &self, - order: &OrderRequest, + order: &OfferRequest, bond_requirements: &BondRequirementResponse, ) -> Result<()> { sqlx::query( diff --git a/taptrade-cli-demo/coordinator/src/main.rs b/taptrade-cli-demo/coordinator/src/main.rs index 3d0e2dd..4d56080 100755 --- a/taptrade-cli-demo/coordinator/src/main.rs +++ b/taptrade-cli-demo/coordinator/src/main.rs @@ -5,16 +5,17 @@ mod wallet; use anyhow::{anyhow, Result}; use bdk::sled; -use communication::{api::*, api_server}; -use coordinator::monitoring::monitor_bonds; -use coordinator::monitoring::*; +use communication::{api::*, api_server, *}; use coordinator::tx_confirmation_monitoring::update_transaction_confirmations; +use coordinator::{monitoring::*, *}; use database::CoordinatorDB; use dotenv::dotenv; use log::{debug, error, info, trace, warn}; +use rand::Rng; use std::time::{SystemTime, UNIX_EPOCH}; use std::{env, sync::Arc}; use tokio::sync::Mutex; +use validator::{Validate, ValidationError}; use wallet::*; pub struct Coordinator {