mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-12-31 00:49:59 +00:00
add escrow psbt combination logic, sql handler and api endpoint
This commit is contained in:
@ -22,4 +22,6 @@ pub enum RequestError {
|
||||
Database(String),
|
||||
NotConfirmed,
|
||||
NotFound,
|
||||
PsbtAlreadySubmitted,
|
||||
PsbtInvalid(String),
|
||||
}
|
||||
|
||||
@ -121,8 +121,27 @@ async fn submit_escrow_psbt(
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<PsbtSubmissionRequest>,
|
||||
) -> Result<Response, AppError> {
|
||||
panic!("implement")
|
||||
|
||||
debug!("\n\nReceived signed escrow psbt: {:?}", payload);
|
||||
match handle_signed_escrow_psbt(&payload, coordinator).await {
|
||||
Ok(()) => Ok(StatusCode::OK.into_response()),
|
||||
Err(RequestError::PsbtAlreadySubmitted) => {
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
Err(RequestError::PsbtInvalid) => Ok(StatusCode::NOT_ACCEPTABLE.into_response()),
|
||||
_ => Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
|
||||
// Err(RequestError::NotFound) => {
|
||||
// info!("Offer for escrow psbt not found");
|
||||
// Ok(StatusCode::NOT_FOUND.into_response())
|
||||
// }
|
||||
// Err(RequestError::NotConfirmed) => {
|
||||
// info!("Offer for escrow psbt not confirmed");
|
||||
// Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
// }
|
||||
// Err(RequestError::Database(e)) => {
|
||||
// error!("Database error fetching escrow psbt: {e}");
|
||||
// Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
// }
|
||||
}
|
||||
// check if psbt is correct, valid and signed
|
||||
// publish psbt if it is correct
|
||||
// return 200 if everything is correct
|
||||
|
||||
@ -4,8 +4,6 @@ pub mod create_taproot;
|
||||
pub mod mempool_monitoring;
|
||||
pub mod tx_confirmation_monitoring;
|
||||
|
||||
use axum::routing::trace;
|
||||
|
||||
use self::coordinator_utils::*;
|
||||
use super::*;
|
||||
|
||||
@ -224,6 +222,55 @@ pub async fn fetch_escrow_confirmation_status(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_signed_escrow_psbt(
|
||||
payload: &PsbtSubmissionRequest,
|
||||
coordinator: Arc<Coordinator>,
|
||||
) -> Result<(), RequestError> {
|
||||
let database = &coordinator.coordinator_db;
|
||||
let wallet = &coordinator.coordinator_wallet;
|
||||
|
||||
match database
|
||||
.is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex)
|
||||
.await
|
||||
{
|
||||
Ok(false) => return Err(RequestError::NotFound),
|
||||
Ok(true) => (),
|
||||
Err(e) => return Err(RequestError::Database(e.to_string())),
|
||||
};
|
||||
|
||||
match wallet
|
||||
.validate_escrow_init_psbt(&payload.signed_psbt_hex)
|
||||
.await
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(e) => return Err(RequestError::PsbtInvalid(e.to_string())),
|
||||
};
|
||||
match database.insert_signed_escrow_psbt(payload).await {
|
||||
Ok(false) => return Err(RequestError::PsbtAlreadySubmitted),
|
||||
Ok(true) => (),
|
||||
Err(e) => return Err(RequestError::Database(e.to_string())),
|
||||
};
|
||||
|
||||
// check if both signed parts are there, if so, combine and broadcast
|
||||
let (maker_psbt, taker_psbt) = match database
|
||||
.fetch_both_signed_escrow_psbts(&payload.offer_id_hex)
|
||||
.await
|
||||
{
|
||||
Ok(Some((maker_psbt, taker_psbt))) => (maker_psbt, taker_psbt),
|
||||
Ok(None) => return Ok(()),
|
||||
Err(e) => return Err(RequestError::Database(e.to_string())),
|
||||
};
|
||||
|
||||
if let Err(e) = wallet
|
||||
.combine_and_broadcast_escrow_psbt(&maker_psbt, &taker_psbt)
|
||||
.await
|
||||
{
|
||||
return Err(RequestError::PsbtInvalid(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_obligation_confirmation(
|
||||
payload: &OfferTakenRequest,
|
||||
coordinator: Arc<Coordinator>,
|
||||
|
||||
@ -3,6 +3,7 @@ mod db_tests;
|
||||
|
||||
use anyhow::Context;
|
||||
use futures_util::StreamExt;
|
||||
use serde::de::IntoDeserializer;
|
||||
|
||||
use super::*;
|
||||
use bdk::bitcoin::address::Address;
|
||||
@ -137,6 +138,8 @@ impl CoordinatorDB {
|
||||
musig_pubkey_compressed_hex_taker TEXT NOT NULL,
|
||||
escrow_psbt_hex TEXT NOT NULL,
|
||||
escrow_psbt_txid TEXT NOT NULL,
|
||||
signed_escrow_psbt_hex_maker TEXT,
|
||||
signed_escrow_psbt_hex_taker TEXT,
|
||||
escrow_psbt_is_confirmed INTEGER,
|
||||
maker_happy INTEGER,
|
||||
taker_happy INTEGER,
|
||||
@ -611,22 +614,8 @@ impl CoordinatorDB {
|
||||
Ok(robohash.is_some())
|
||||
}
|
||||
|
||||
pub async fn fetch_escrow_tx_confirmation_status(&self, offer_id: &str) -> Result<bool> {
|
||||
let status =
|
||||
sqlx::query("SELECT escrow_psbt_is_confirmed FROM taken_offers WHERE offer_id = ?")
|
||||
.bind(offer_id)
|
||||
.fetch_one(&*self.db_pool)
|
||||
.await?;
|
||||
Ok(status.get::<i64, _>("escrow_psbt_is_confirmed") == 1)
|
||||
}
|
||||
|
||||
pub async fn set_trader_happy_field(
|
||||
&self,
|
||||
offer_id: &str,
|
||||
robohash: &str,
|
||||
is_happy: bool,
|
||||
) -> Result<()> {
|
||||
let robohash_bytes = hex::decode(robohash)?;
|
||||
async fn is_maker_in_taken_offers(&self, offer_id: &str, robohash_hex: &str) -> Result<bool> {
|
||||
let robohash_bytes = hex::decode(robohash_hex)?;
|
||||
|
||||
// First, check if the robohash matches the maker or taker
|
||||
let row = sqlx::query(
|
||||
@ -642,6 +631,100 @@ impl CoordinatorDB {
|
||||
if !is_maker && !is_taker {
|
||||
return Err(anyhow::anyhow!("Robohash does not match maker or taker"));
|
||||
}
|
||||
Ok(is_maker)
|
||||
}
|
||||
|
||||
pub async fn insert_signed_escrow_psbt(
|
||||
&self,
|
||||
signed_escow_psbt_data: &PsbtSubmissionRequest,
|
||||
) -> Result<bool> {
|
||||
// first check if the escrow psbt has already been submitted
|
||||
let is_maker = self
|
||||
.is_maker_in_taken_offers(
|
||||
&signed_escow_psbt_data.offer_id_hex,
|
||||
&signed_escow_psbt_data.robohash_hex,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let is_already_there = match is_maker {
|
||||
true => {
|
||||
let status = sqlx::query(
|
||||
"SELECT signed_escrow_psbt_hex_maker FROM taken_offers WHERE offer_id = ?",
|
||||
)
|
||||
.bind(&signed_escow_psbt_data.offer_id_hex)
|
||||
.fetch_one(&*self.db_pool)
|
||||
.await?;
|
||||
status
|
||||
.get::<Option<String>, _>("signed_escrow_psbt_hex_maker")
|
||||
.is_some()
|
||||
}
|
||||
false => {
|
||||
let status = sqlx::query(
|
||||
"SELECT signed_escrow_psbt_hex_taker FROM taken_offers WHERE offer_id = ?",
|
||||
)
|
||||
.bind(&signed_escow_psbt_data.offer_id_hex)
|
||||
.fetch_one(&*self.db_pool)
|
||||
.await?;
|
||||
status
|
||||
.get::<Option<String>, _>("signed_escrow_psbt_hex_taker")
|
||||
.is_some()
|
||||
}
|
||||
};
|
||||
|
||||
if is_already_there {
|
||||
return Ok(false);
|
||||
} else {
|
||||
let query = if is_maker {
|
||||
"UPDATE taken_offers SET signed_escrow_psbt_hex_maker = ? WHERE offer_id = ?"
|
||||
} else {
|
||||
"UPDATE taken_offers SET signed_escrow_psbt_hex_taker = ? WHERE offer_id = ?"
|
||||
};
|
||||
|
||||
sqlx::query(query)
|
||||
.bind(&signed_escow_psbt_data.signed_psbt_hex)
|
||||
.bind(&signed_escow_psbt_data.offer_id_hex)
|
||||
.execute(&*self.db_pool)
|
||||
.await?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_both_signed_escrow_psbts(
|
||||
&self,
|
||||
offer_id_hex: &str,
|
||||
) -> Result<Option<(String, String)>> {
|
||||
let row = sqlx::query(
|
||||
"SELECT signed_escrow_psbt_hex_maker, signed_escrow_psbt_hex_taker FROM taken_offers WHERE offer_id = ?",
|
||||
)
|
||||
.bind(offer_id_hex)
|
||||
.fetch_one(&*self.db_pool)
|
||||
.await?;
|
||||
|
||||
let maker_psbt: Option<String> = row.try_get("signed_escrow_psbt_hex_maker")?;
|
||||
let taker_psbt: Option<String> = row.try_get("signed_escrow_psbt_hex_taker")?;
|
||||
|
||||
Ok(match (maker_psbt, taker_psbt) {
|
||||
(Some(maker), Some(taker)) => Some((maker, taker)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn fetch_escrow_tx_confirmation_status(&self, offer_id: &str) -> Result<bool> {
|
||||
let status =
|
||||
sqlx::query("SELECT escrow_psbt_is_confirmed FROM taken_offers WHERE offer_id = ?")
|
||||
.bind(offer_id)
|
||||
.fetch_one(&*self.db_pool)
|
||||
.await?;
|
||||
Ok(status.get::<i64, _>("escrow_psbt_is_confirmed") == 1)
|
||||
}
|
||||
|
||||
pub async fn set_trader_happy_field(
|
||||
&self,
|
||||
offer_id: &str,
|
||||
robohash: &str,
|
||||
is_happy: bool,
|
||||
) -> Result<()> {
|
||||
let is_maker = self.is_maker_in_taken_offers(offer_id, robohash).await?;
|
||||
|
||||
let query = if is_maker {
|
||||
"UPDATE taken_offers SET maker_happy = ? WHERE offer_id = ?"
|
||||
|
||||
@ -3,7 +3,9 @@ use bdk::{
|
||||
bitcoin::psbt::Input,
|
||||
descriptor::Descriptor,
|
||||
miniscript::{descriptor::TapTree, policy::Concrete, Tap},
|
||||
SignOptions,
|
||||
};
|
||||
use bitcoin::psbt::PartiallySignedTransaction;
|
||||
use musig2::{secp256k1::PublicKey as MuSig2PubKey, KeyAggContext};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -221,4 +223,33 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
escrow_fee_sat_per_participant,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn validate_escrow_init_psbt(&self, escrow_init_psbt: &str) -> Result<()> {
|
||||
warn!("Implement escrow psbt validation. For now, returning Ok");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn combine_and_broadcast_escrow_psbt(
|
||||
&self,
|
||||
signed_maker_psbt_hex: &str,
|
||||
signed_taker_psbt_hex: &str,
|
||||
) -> Result<()> {
|
||||
let mut maker_psbt = PartiallySignedTransaction::from_str(signed_maker_psbt_hex)?;
|
||||
let taker_psbt = PartiallySignedTransaction::from_str(signed_taker_psbt_hex)?;
|
||||
|
||||
maker_psbt.combine(&taker_psbt)?;
|
||||
|
||||
let wallet = self.wallet.lock().await;
|
||||
match wallet.finalize_psbt(&mut maker_psbt, SignOptions::default()) {
|
||||
Ok(true) => {
|
||||
let tx = maker_psbt.extract_tx();
|
||||
let tx_hex = tx.to_string();
|
||||
self.backend.broadcast(&tx)?;
|
||||
info!("Escrow transaction broadcasted: {}", tx.txid());
|
||||
Ok(())
|
||||
}
|
||||
Ok(false) => Err(anyhow!("Failed to finalize escrow psbt")),
|
||||
Err(e) => Err(anyhow!("Error finalizing escrow psbt: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user