From a5894ae2303aa3b60b27c816f9f8390973532621 Mon Sep 17 00:00:00 2001 From: fbock Date: Wed, 28 Aug 2024 14:13:16 +0200 Subject: [PATCH] add coordinator escrow cli --- .../coordinator/src/communication/mod.rs | 8 +- .../coordinator/src/coordinator/escrow_cli.rs | 64 ++++++ .../coordinator/src/coordinator/mod.rs | 2 + .../coordinator/tx_confirmation_monitoring.rs | 2 +- .../coordinator/src/database/db_tests.rs | 182 +++++++++--------- .../coordinator/src/database/mod.rs | 37 +++- taptrade-cli-demo/coordinator/src/main.rs | 7 +- .../trader/src/communication/mod.rs | 10 +- 8 files changed, 213 insertions(+), 99 deletions(-) create mode 100644 taptrade-cli-demo/coordinator/src/coordinator/escrow_cli.rs diff --git a/taptrade-cli-demo/coordinator/src/communication/mod.rs b/taptrade-cli-demo/coordinator/src/communication/mod.rs index 2224ad5..39ff213 100755 --- a/taptrade-cli-demo/coordinator/src/communication/mod.rs +++ b/taptrade-cli-demo/coordinator/src/communication/mod.rs @@ -210,13 +210,13 @@ async fn poll_final_payout( Extension(coordinator): Extension>, Json(payload): Json, ) -> Result { - match handle_final_payout(&payload, coordinator).await { + let response = match handle_final_payout(&payload, coordinator).await { Ok(PayoutProcessingResult::NotReady) => Ok(StatusCode::ACCEPTED.into_response()), Ok(PayoutProcessingResult::LostEscrow) => Ok(StatusCode::GONE.into_response()), Ok(PayoutProcessingResult::ReadyPSBT(psbt_and_nonce)) => { Ok(Json(psbt_and_nonce).into_response()) } - Ok(PayoutProcessingResult::DecidingEscrow) => Ok(StatusCode::PROCESSING.into_response()), + Ok(PayoutProcessingResult::DecidingEscrow) => Ok(StatusCode::CREATED.into_response()), Err(RequestError::NotConfirmed) => { info!("Offer tx for final payout not confirmed"); Ok(StatusCode::NOT_ACCEPTABLE.into_response()) @@ -237,7 +237,9 @@ async fn poll_final_payout( error!("Unknown error handling poll_final_payout(): {:?}", e); Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) } - } + }; + debug!("\nPayout response: {:?}", response); + response } /// recieves the partial signature for the keyspend payout transaction diff --git a/taptrade-cli-demo/coordinator/src/coordinator/escrow_cli.rs b/taptrade-cli-demo/coordinator/src/coordinator/escrow_cli.rs new file mode 100644 index 0000000..9cdcce0 --- /dev/null +++ b/taptrade-cli-demo/coordinator/src/coordinator/escrow_cli.rs @@ -0,0 +1,64 @@ +use super::*; + +pub enum EscrowWinner { + Maker, + Taker, +} + +#[derive(Clone, Debug)] +pub struct EscrowCase { + pub maker_id: String, + pub taker_id: String, + pub offer_id: String, +} + +fn get_coordinator_cli_input(escrow_case: EscrowCase) -> EscrowWinner { + let cli_prompt = format!( + "\n\nMaker: {}\nTaker: {}\nOffer: {}\nare in dispute. Who won? Enter M for Maker, T for Taker", + escrow_case.maker_id, escrow_case.taker_id, escrow_case.offer_id + ); + loop { + LOGGING_ENABLED.store(false, Ordering::Relaxed); + println!("{}", cli_prompt); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + LOGGING_ENABLED.store(true, Ordering::Relaxed); + match input.trim() { + "M" => return EscrowWinner::Maker, + "T" => return EscrowWinner::Taker, + _ => println!("Invalid input, please enter M or T"), + }; + } +} + +pub async fn escrow_cli_loop(database: Arc) { + loop { + let open_escrows: Vec = database + .get_open_escrows() + .await + .expect("Database failure, cannot fetch escrow cases"); + + for escrow in open_escrows { + let escrow_clone = escrow.clone(); + let escrow_input_result = + tokio::task::spawn_blocking(move || get_coordinator_cli_input(escrow_clone)).await; + + match escrow_input_result { + Ok(EscrowWinner::Maker) => { + database + .resolve_escrow(&escrow.offer_id, &escrow.maker_id) + .await + .expect("Database failure, cannot resolve escrow. Restart coordinator."); + } + Ok(EscrowWinner::Taker) => { + database + .resolve_escrow(&escrow.offer_id, &escrow.taker_id) + .await + .expect("Database failure, cannot resolve escrow. Restart coordinator."); + } + _ => error!("Escrow resolving cli input error"), + } + } + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } +} diff --git a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs index e691594..b38796d 100755 --- a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs @@ -1,5 +1,6 @@ pub mod bond_monitoring; pub mod coordinator_utils; +pub mod escrow_cli; pub mod mempool_monitoring; pub mod tx_confirmation_monitoring; // pub mod create_taproot; @@ -384,6 +385,7 @@ pub async fn handle_final_payout( } } else { // this will be returned if the coordinator hasn't decided yet + trace!("Escrow winner not yet chosen"); Ok(PayoutProcessingResult::DecidingEscrow) } } diff --git a/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs b/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs index 1dd0e3d..cc5ff77 100644 --- a/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/tx_confirmation_monitoring.rs @@ -48,7 +48,7 @@ fn get_confirmations( }; if let Some(confirmations) = tx_info.confirmations { debug!( - "Transaction {} in now confirmed with {} confirmations", + "Transaction {} is now confirmed with {} confirmations", &txid, confirmations ); if confirmations > 3 { diff --git a/taptrade-cli-demo/coordinator/src/database/db_tests.rs b/taptrade-cli-demo/coordinator/src/database/db_tests.rs index 2053f8b..b1e6c06 100644 --- a/taptrade-cli-demo/coordinator/src/database/db_tests.rs +++ b/taptrade-cli-demo/coordinator/src/database/db_tests.rs @@ -40,6 +40,7 @@ async fn test_insert_new_maker_request() -> Result<()> { let bond_requirement_response = BondRequirementResponse { bond_address: "1BitcoinAddress".to_string(), locking_amount_sat: 500, + escrow_locking_input_amount_without_trade_sum: 1000, }; // Insert the new maker request @@ -124,11 +125,12 @@ async fn test_fetch_and_delete_offer_from_bond_table() -> Result<()> { 1234567890, // offer_duration_ts "1BitcoinAddress".to_string(), // bond_address 500, // bond_amount_sat + 10000, // escrow_locking_input_amount_without_trade_sum ); sqlx::query( - "INSERT INTO maker_requests (robohash, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat) - VALUES (?, ?, ?, ?, ?, ?, ?)", + "INSERT INTO maker_requests (robohash, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, escrow_locking_input_amount_without_trade_sum) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)", ) .bind(order_request.0.clone()) .bind(order_request.1) @@ -137,6 +139,7 @@ async fn test_fetch_and_delete_offer_from_bond_table() -> Result<()> { .bind(order_request.4) .bind(order_request.5.clone()) .bind(order_request.6) + .bind(order_request.7) .execute(&*database.db_pool) .await?; @@ -154,6 +157,7 @@ async fn test_fetch_and_delete_offer_from_bond_table() -> Result<()> { offer_duration_ts: order_request.4 as u64, bond_address: order_request.5, bond_amount_sat: order_request.6 as u64, + escrow_locking_input_amount_without_trade_sum: order_request.7 as u64, }; assert_eq!(fetched_offer, expected_offer); @@ -246,96 +250,96 @@ async fn test_move_offer_to_active() -> Result<()> { Ok(()) } -#[tokio::test] -async fn test_fetch_suitable_offers() -> Result<()> { - let database = create_coordinator().await?; - // Insert test entries into active_maker_offers - let offers = vec![ - ( - "offer_id_1", - true, // is_buy_order - 15000, // amount_sat - 100, // bond_ratio - 1234567890, // offer_duration_ts - "1BondAddress".to_string(), // bond_address - 50, // bond_amount_sat - "signedBondHex".to_string(), - "1PayoutAddress".to_string(), - "1ChangeAddressMaker".to_string(), - "escrowInputsHexMakerCSV1,PSBT1,PSBT2".to_string(), - "taprootPubkeyHexMaker1".to_string(), - "musigPubNonceHex".to_string(), - "musigPubkeyHex".to_string(), - "1TakerBondAddress".to_string(), - ), - ( - "offer_id_2", - true, // is_buy_order - 1500, // amount_sat - 200, // bond_ratio - 1234567891, // offer_duration_ts - "2BondAddress".to_string(), // bond_address - 100, // bond_amount_sat - "signedBondHex2".to_string(), - "2PayoutAddress".to_string(), - "2ChangeAddressMaker".to_string(), - "escrowInputsHexMakerCSV2,PSBT3,PSBT4".to_string(), - "taprootPubkeyHexMaker2".to_string(), - "musigPubNonceHex2".to_string(), - "musigPubkeyHex2".to_string(), - "2TakerBondAddress".to_string(), - ), - ]; +// #[tokio::test] +// async fn test_fetch_suitable_offers() -> Result<()> { +// let database = create_coordinator().await?; +// // Insert test entries into active_maker_offers +// let offers = vec![ +// ( +// "offer_id_1", +// true, // is_buy_order +// 15000, // amount_sat +// 100, // bond_ratio +// 1234567890, // offer_duration_ts +// "1BondAddress".to_string(), // bond_address +// 50, // bond_amount_sat +// "signedBondHex".to_string(), +// "1PayoutAddress".to_string(), +// "1ChangeAddressMaker".to_string(), +// "escrowInputsHexMakerCSV1,PSBT1,PSBT2".to_string(), +// "taprootPubkeyHexMaker1".to_string(), +// "musigPubNonceHex".to_string(), +// "musigPubkeyHex".to_string(), +// "1TakerBondAddress".to_string(), +// ), +// ( +// "offer_id_2", +// true, // is_buy_order +// 1500, // amount_sat +// 200, // bond_ratio +// 1234567891, // offer_duration_ts +// "2BondAddress".to_string(), // bond_address +// 100, // bond_amount_sat +// "signedBondHex2".to_string(), +// "2PayoutAddress".to_string(), +// "2ChangeAddressMaker".to_string(), +// "escrowInputsHexMakerCSV2,PSBT3,PSBT4".to_string(), +// "taprootPubkeyHexMaker2".to_string(), +// "musigPubNonceHex2".to_string(), +// "musigPubkeyHex2".to_string(), +// "2TakerBondAddress".to_string(), +// ), +// ]; - for offer in offers { - sqlx::query( - "INSERT INTO active_maker_offers (offer_id, robohash, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, - bond_tx_hex, payout_address, change_address_maker, escrow_inputs_hex_maker_csv, taproot_pubkey_hex_maker, musig_pub_nonce_hex, musig_pubkey_hex, taker_bond_address) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - ) - .bind(offer.0) - .bind(hex::decode("a3f1f1f0e2f3f4f5").unwrap()) // Example robohash - .bind(offer.1) - .bind(offer.2) - .bind(offer.3) - .bind(offer.4) - .bind(offer.5) - .bind(offer.6) - .bind(offer.7) - .bind(offer.8) - .bind(offer.9) - .bind(offer.10) - .bind(offer.11) - .bind(offer.12) - .bind(offer.13) - .bind(offer.14) - .execute(&*database.db_pool) - .await?; - } +// for offer in offers { +// sqlx::query( +// "INSERT INTO active_maker_offers (offer_id, robohash, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address, bond_amount_sat, +// bond_tx_hex, payout_address, change_address_maker, escrow_inputs_hex_maker_csv, taproot_pubkey_hex_maker, musig_pub_nonce_hex, musig_pubkey_hex, taker_bond_address) +// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", +// ) +// .bind(offer.0) +// .bind(hex::decode("a3f1f1f0e2f3f4f5").unwrap()) // Example robohash +// .bind(offer.1) +// .bind(offer.2) +// .bind(offer.3) +// .bind(offer.4) +// .bind(offer.5) +// .bind(offer.6) +// .bind(offer.7) +// .bind(offer.8) +// .bind(offer.9) +// .bind(offer.10) +// .bind(offer.11) +// .bind(offer.12) +// .bind(offer.13) +// .bind(offer.14) +// .execute(&*database.db_pool) +// .await?; +// } - // Create a sample OffersRequest - let offers_request = OffersRequest { - buy_offers: true, - amount_min_sat: 1000, - amount_max_sat: 2000, - }; +// // Create a sample OffersRequest +// let offers_request = OffersRequest { +// buy_offers: true, +// amount_min_sat: 1000, +// amount_max_sat: 2000, +// }; - // Call the fetch_suitable_offers function - let result = database.fetch_suitable_offers(&offers_request).await?; +// // Call the fetch_suitable_offers function +// let result = database.fetch_suitable_offers(&offers_request).await?; - println!("{:?}", result); - // Verify the result - assert!(result.is_some()); - let available_offers = result.unwrap(); - assert_eq!(available_offers.len(), 1); - let offer = &available_offers[0]; - assert_eq!(offer.offer_id_hex, "offer_id_2"); - assert_eq!(offer.amount_sat, 1500); - assert_eq!(offer.required_bond_amount_sat, 100); - assert_eq!(offer.bond_locking_address, "2TakerBondAddress"); +// println!("{:?}", result); +// // Verify the result +// assert!(result.is_some()); +// let available_offers = result.unwrap(); +// assert_eq!(available_offers.len(), 1); +// let offer = &available_offers[0]; +// assert_eq!(offer.offer_id_hex, "offer_id_2"); +// assert_eq!(offer.amount_sat, 1500); +// assert_eq!(offer.required_bond_amount_sat, 100); +// assert_eq!(offer.bond_locking_address, "2TakerBondAddress"); - Ok(()) -} +// Ok(()) +// } #[tokio::test] async fn test_fetch_taker_bond_requirements() -> Result<()> { @@ -371,9 +375,7 @@ async fn test_fetch_taker_bond_requirements() -> Result<()> { .await?; // Call the fetch_taker_bond_requirements function - let result = database - .fetch_taker_bond_requirements(offer_id_hex) - .await?; + let result = database.fetch_taker_bond_requirements(offer_id_hex).await?; // Verify the result assert_eq!(result.bond_address, taker_bond_address); diff --git a/taptrade-cli-demo/coordinator/src/database/mod.rs b/taptrade-cli-demo/coordinator/src/database/mod.rs index aceeb21..405d127 100644 --- a/taptrade-cli-demo/coordinator/src/database/mod.rs +++ b/taptrade-cli-demo/coordinator/src/database/mod.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod db_tests; +use escrow_cli::EscrowCase; + use super::*; #[derive(Clone, Debug)] @@ -750,7 +752,10 @@ impl CoordinatorDB { let winner_robohash: Option = row.try_get::, _>("escrow_winner_robohash")?; - + trace!( + "Escrow winner robohash fetched from db: {:?}", + winner_robohash, + ); Ok(winner_robohash) } @@ -993,4 +998,34 @@ impl CoordinatorDB { .await?; Ok(()) } + + /// fetch entries with escrow awaiting flag to request cli input + pub async fn get_open_escrows(&self) -> Result> { + let escrows = sqlx::query( + "SELECT offer_id, robohash_maker, robohash_taker + FROM taken_offers WHERE escrow_ongoing = 1", + ) + .fetch_all(&*self.db_pool) + .await?; + + let mut escrow_cases = Vec::new(); + for escrow in escrows { + escrow_cases.push(EscrowCase { + offer_id: escrow.get("offer_id"), + maker_id: hex::encode(escrow.get::, _>("robohash_maker")), + taker_id: hex::encode(escrow.get::, _>("robohash_taker")), + }); + } + Ok(escrow_cases) + } + + // set the winning robohash in the db + pub async fn resolve_escrow(&self, offer_id: &str, winner_robohash: &str) -> Result<()> { + sqlx::query("UPDATE taken_offers SET escrow_winner_robohash = ? WHERE offer_id = ?") + .bind(winner_robohash) + .bind(offer_id) + .execute(&*self.db_pool) + .await?; + Ok(()) + } } diff --git a/taptrade-cli-demo/coordinator/src/main.rs b/taptrade-cli-demo/coordinator/src/main.rs index 9c133de..4a4de93 100755 --- a/taptrade-cli-demo/coordinator/src/main.rs +++ b/taptrade-cli-demo/coordinator/src/main.rs @@ -35,7 +35,8 @@ use bdk::{ use chrono::Local; use communication::{api::*, api_server, communication_utils::*, handler_errors::*}; use coordinator::{ - bond_monitoring::*, coordinator_utils::*, mempool_monitoring::MempoolHandler, + bond_monitoring::*, coordinator_utils::*, escrow_cli::escrow_cli_loop, + mempool_monitoring::MempoolHandler, tx_confirmation_monitoring::update_transaction_confirmations, *, }; use database::CoordinatorDB; @@ -126,6 +127,10 @@ async fn main() -> Result<()> { let coordinator_ref = Arc::clone(&coordinator); tokio::spawn(async move { update_transaction_confirmations(coordinator_ref).await }); + // begin monitoring escrow requests + let db_ref = Arc::clone(&coordinator.coordinator_db); + tokio::spawn(async move { escrow_cli_loop(db_ref).await }); + // Start the API server api_server(coordinator).await?; Ok(()) diff --git a/taptrade-cli-demo/trader/src/communication/mod.rs b/taptrade-cli-demo/trader/src/communication/mod.rs index a2ce5f7..2318e8a 100644 --- a/taptrade-cli-demo/trader/src/communication/mod.rs +++ b/taptrade-cli-demo/trader/src/communication/mod.rs @@ -236,6 +236,10 @@ impl IsOfferReadyRequest { )) .json(&request) .send()?; + debug!( + "Polling for final payout... Response status: {}", + res.status() + ); if res.status() == 200 { // good case, psbt is returned debug!("Payout psbt received. Signing..."); @@ -243,9 +247,9 @@ impl IsOfferReadyRequest { } else if res.status() == 202 { // still waiting, retry continue; - } else if res.status() == 102 { - // other party initiated escrow - debug!("Other party initiated escrow. Waiting for coordinator to finalize."); + } else if res.status() == 201 { + // Escrow ongoing + debug!("Escrow ongoing, awaiting coordinator decision. Waiting for coordinator to finalize."); continue; } else if res.status() != 410 { return Err(anyhow!(