add escrow psbt combination logic, sql handler and api endpoint

This commit is contained in:
fbock
2024-08-06 18:22:31 +02:00
parent 6adcabcb54
commit 9b5749d721
5 changed files with 202 additions and 20 deletions

View File

@ -22,4 +22,6 @@ pub enum RequestError {
Database(String),
NotConfirmed,
NotFound,
PsbtAlreadySubmitted,
PsbtInvalid(String),
}

View File

@ -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

View File

@ -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>,

View File

@ -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 = ?"

View File

@ -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)),
}
}
}