mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-09-12 20:56:28 +00:00
Merge branch 'research' of github.com:RoboSats/taptrade-core into research
This commit is contained in:
@ -0,0 +1,46 @@
|
||||
use super::*;
|
||||
|
||||
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"));
|
||||
}
|
||||
if offer_duration_ts > unix_timestamp + 604800 {
|
||||
return Err(ValidationError::new("Offer duration too long"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ANYHOW ERROR HANDLING
|
||||
// --------------
|
||||
// Make our own error that wraps `anyhow::Error`.
|
||||
#[derive(Debug)]
|
||||
pub struct AppError(anyhow::Error);
|
||||
|
||||
// Tell axum how to convert `AppError` into a response.
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||
impl<E> From<E> for AppError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
#[derive(Debug)]
|
||||
pub enum BondError {
|
||||
InvalidBond(String),
|
||||
BondNotFound,
|
||||
CoordinatorError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FetchOffersError {
|
||||
NoOffersAvailable,
|
||||
Database(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FetchEscrowConfirmationError {
|
||||
NotFound,
|
||||
Database(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RequestError {
|
||||
Database(String),
|
||||
NotConfirmed,
|
||||
NotFound,
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
pub mod api;
|
||||
mod utils;
|
||||
pub mod communication_utils;
|
||||
pub mod handler_errors;
|
||||
|
||||
use self::api::*;
|
||||
use self::utils::*;
|
||||
use self::communication_utils::*;
|
||||
use super::*;
|
||||
use crate::wallet::*;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
@ -24,10 +23,10 @@ async fn receive_order(
|
||||
Json(offer): Json<OfferRequest>,
|
||||
) -> Result<Response, AppError> {
|
||||
if let Err(_) = offer.validate() {
|
||||
return Ok(StatusCode::BAD_REQUEST.into_response());
|
||||
Ok(StatusCode::BAD_REQUEST.into_response())
|
||||
} else {
|
||||
let bond_requirements = process_order(coordinator, &offer).await?;
|
||||
return Ok(Json(bond_requirements).into_response());
|
||||
Ok(Json(bond_requirements).into_response())
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,15 +41,15 @@ async fn submit_maker_bond(
|
||||
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());
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
Err(BondError::InvalidBond(e)) => {
|
||||
warn!("Invalid bond submission: {e}");
|
||||
return Ok(StatusCode::NOT_ACCEPTABLE.into_response());
|
||||
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());
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,7 +64,7 @@ async fn fetch_available_offers(
|
||||
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)) => {
|
||||
Err(FetchOffersError::Database(e)) => {
|
||||
error!("Database error fetching offers: {e}");
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
@ -84,15 +83,15 @@ async fn submit_taker_bond(
|
||||
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());
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
Err(BondError::InvalidBond(e)) => {
|
||||
warn!("Invalid bond submission: {e}");
|
||||
return Ok(StatusCode::NOT_ACCEPTABLE.into_response());
|
||||
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());
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,7 +106,7 @@ async fn request_offer_status_maker(
|
||||
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)) => {
|
||||
Err(FetchOffersError::Database(e)) => {
|
||||
error!("Database error fetching offers: {e}");
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
@ -140,11 +139,11 @@ async fn poll_escrow_confirmation(
|
||||
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) => {
|
||||
Err(FetchEscrowConfirmationError::NotFound) => {
|
||||
info!("Escrow confirmation check transaction not found");
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
Err(FetchEscrowConfirmationError::DatabaseError(e)) => {
|
||||
Err(FetchEscrowConfirmationError::Database(e)) => {
|
||||
error!("Database error fetching escrow confirmation: {e}");
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
@ -152,22 +151,24 @@ async fn poll_escrow_confirmation(
|
||||
}
|
||||
|
||||
async fn submit_obligation_confirmation(
|
||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<OfferTakenRequest>,
|
||||
) -> Result<Response, AppError> {
|
||||
// sanity check if offer is in table and if the escrow tx is confirmed
|
||||
if !database
|
||||
.is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex)
|
||||
.await? || !database
|
||||
.fetch_escrow_tx_confirmation_status(&payload.offer_id_hex)
|
||||
.await?
|
||||
{
|
||||
return Ok(StatusCode::NOT_FOUND.into_response());
|
||||
match handle_obligation_confirmation(&payload, coordinator).await {
|
||||
Ok(_) => Ok(StatusCode::OK.into_response()),
|
||||
Err(RequestError::NotFound) => {
|
||||
info!("Offer for obligation confirmation not found");
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
Err(RequestError::NotConfirmed) => {
|
||||
info!("Offer for obligation confirmation not confirmed");
|
||||
Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
}
|
||||
Err(RequestError::Database(e)) => {
|
||||
error!("Database error fetching obligation confirmation: {e}");
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
}
|
||||
database
|
||||
.set_trader_happy_field(&payload.offer_id_hex, &payload.robohash_hex, true)
|
||||
.await?;
|
||||
Ok(StatusCode::OK.into_response())
|
||||
}
|
||||
|
||||
// or
|
||||
@ -175,22 +176,24 @@ async fn submit_obligation_confirmation(
|
||||
// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat)
|
||||
// before timeout ends
|
||||
async fn request_escrow(
|
||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<TradeObligationsUnsatisfied>,
|
||||
) -> Result<Response, AppError> {
|
||||
if !database
|
||||
.is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex)
|
||||
.await? || !database
|
||||
.fetch_escrow_tx_confirmation_status(&payload.offer_id_hex)
|
||||
.await?
|
||||
{
|
||||
return Ok(StatusCode::NOT_FOUND.into_response());
|
||||
match initiate_escrow(&payload, coordinator).await {
|
||||
Ok(_) => Ok(StatusCode::OK.into_response()),
|
||||
Err(RequestError::NotConfirmed) => {
|
||||
info!("Offer tx for escrow initiation not confirmed");
|
||||
Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
}
|
||||
Err(RequestError::NotFound) => {
|
||||
info!("Offer for escrow initiation not found");
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
Err(RequestError::Database(e)) => {
|
||||
error!("Database error fetching obligation confirmation: {e}");
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
}
|
||||
database
|
||||
.set_trader_happy_field(&payload.offer_id_hex, &payload.robohash_hex, false)
|
||||
.await?;
|
||||
|
||||
Ok(StatusCode::OK.into_response())
|
||||
}
|
||||
|
||||
/// Is supposed to get polled by the traders once they clicked on "i sent the fiat" or "i received the fiat".
|
||||
@ -198,55 +201,26 @@ async fn request_escrow(
|
||||
/// If one of them is not happy and initiating escrow (e.g. claiming they didn't receive the fiat) then this
|
||||
/// endpoint can return 201 and the escrow mediation logic will get executed (tbd).
|
||||
async fn poll_final_payout(
|
||||
Extension(database): Extension<Arc<CoordinatorDB>>,
|
||||
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
|
||||
Extension(coordinator): Extension<Arc<Coordinator>>,
|
||||
Json(payload): Json<OfferTakenRequest>,
|
||||
) -> Result<Response, AppError> {
|
||||
if !database
|
||||
.is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex)
|
||||
.await? || !database
|
||||
.fetch_escrow_tx_confirmation_status(&payload.offer_id_hex)
|
||||
.await?
|
||||
{
|
||||
return Ok(StatusCode::NOT_FOUND.into_response());
|
||||
}
|
||||
|
||||
let trader_happiness = database
|
||||
.fetch_trader_happiness(&payload.offer_id_hex)
|
||||
.await?;
|
||||
if trader_happiness.maker_happy.is_some_and(|x| x == true)
|
||||
&& trader_happiness.taker_happy.is_some_and(|x| x == true)
|
||||
{
|
||||
panic!("Implement wallet.assemble_keyspend_payout_psbt()");
|
||||
// let payout_keyspend_psbt_hex = wallet
|
||||
// .assemble_keyspend_payout_psbt(&payload.offer_id_hex, &payload.robohash_hex)
|
||||
// .await
|
||||
// .context("Error assembling payout PSBT")?;
|
||||
// return Ok(String::from(payout_keyspend_psbt_hex).into_response());
|
||||
} else if (trader_happiness.maker_happy.is_none() || trader_happiness.taker_happy.is_none())
|
||||
&& !trader_happiness.escrow_ongoing
|
||||
{
|
||||
return Ok(StatusCode::ACCEPTED.into_response());
|
||||
}
|
||||
// if one of them is not happy
|
||||
// open escrow cli on coordinator to decide who will win (chat/dispute is out of scope for this demo)
|
||||
// once decided who will win assemble the correct payout psbt and return it to the according trader
|
||||
// the other trader gets a error code/ end of trade code
|
||||
// escrow winner has to be set true with a cli input of the coordinator. This could be an api
|
||||
// endpoint for the admin UI frontend in the future
|
||||
if let Some(escrow_winner) = database.fetch_escrow_result(&payload.offer_id_hex).await? {
|
||||
if escrow_winner == payload.robohash_hex {
|
||||
panic!("Implement wallet.assemble_script_payout_psbt()");
|
||||
// let script_payout_psbt_hex = wallet
|
||||
// .assemble_script_payout_psbt(&payload.offer_id_hex, &payload.robohash_hex, is_maker_bool)
|
||||
// .await
|
||||
// .context("Error assembling payout PSBT")?;
|
||||
// return Ok(String::from(payout_keyspend_psbt_hex).into_response());
|
||||
} else {
|
||||
return Ok(StatusCode::GONE.into_response()); // this will be returned to the losing trader
|
||||
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)) => Ok(psbt.into_response()),
|
||||
Ok(PayoutProcessingResult::DecidingEscrow) => Ok(StatusCode::PROCESSING.into_response()),
|
||||
Err(RequestError::NotConfirmed) => {
|
||||
info!("Offer tx for final payout not confirmed");
|
||||
Ok(StatusCode::NOT_ACCEPTABLE.into_response())
|
||||
}
|
||||
Err(RequestError::NotFound) => {
|
||||
info!("Offer for final payout not found");
|
||||
Ok(StatusCode::NOT_FOUND.into_response())
|
||||
}
|
||||
Err(RequestError::Database(e)) => {
|
||||
error!("Database error fetching final payout: {e}");
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
} else {
|
||||
return Ok(StatusCode::PROCESSING.into_response()); // this will be returned if the coordinator hasn't decided yet
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,31 +257,3 @@ pub async fn api_server(coordinator: Arc<Coordinator>) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ANYHOW ERROR HANDLING
|
||||
// --------------
|
||||
// Make our own error that wraps `anyhow::Error`.
|
||||
#[derive(Debug)]
|
||||
pub struct AppError(anyhow::Error);
|
||||
|
||||
// Tell axum how to convert `AppError` into a response.
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||
impl<E> From<E> for AppError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
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"));
|
||||
}
|
||||
if offer_duration_ts > unix_timestamp + 604800 {
|
||||
return Err(ValidationError::new("Offer duration too long"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
// Also needs to implement punishment logic in case a fraud is detected.
|
||||
use super::*;
|
||||
use anyhow::Context;
|
||||
use mempool_monitoring::MempoolHandler;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
@ -0,0 +1,46 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PayoutProcessingResult {
|
||||
ReadyPSBT(String),
|
||||
NotReady,
|
||||
LostEscrow,
|
||||
DecidingEscrow,
|
||||
}
|
||||
|
||||
pub fn generate_random_order_id(len: usize) -> String {
|
||||
// Generate `len` random bytes
|
||||
let bytes: Vec<u8> = rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Standard)
|
||||
.take(len)
|
||||
.collect();
|
||||
|
||||
// Convert bytes to hex string
|
||||
hex::encode(bytes)
|
||||
}
|
||||
|
||||
pub async fn check_offer_and_confirmation(
|
||||
offer_id_hex: &str,
|
||||
robohash_hex: &str,
|
||||
database: &CoordinatorDB,
|
||||
) -> Result<(), RequestError> {
|
||||
// sanity check if offer is in table
|
||||
match database
|
||||
.is_valid_robohash_in_table(robohash_hex, offer_id_hex)
|
||||
.await
|
||||
{
|
||||
Ok(false) => return Err(RequestError::NotFound),
|
||||
Ok(true) => (),
|
||||
Err(e) => return Err(RequestError::Database(e.to_string())),
|
||||
};
|
||||
|
||||
// sanity check if the escrow tx is confirmed
|
||||
match database
|
||||
.fetch_escrow_tx_confirmation_status(offer_id_hex)
|
||||
.await
|
||||
{
|
||||
Ok(false) => Err(RequestError::NotConfirmed),
|
||||
Ok(true) => Ok(()),
|
||||
Err(e) => Err(RequestError::Database(e.to_string())),
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ fn run_mempool(mempool: Arc<Mempool>) {
|
||||
let tx = match mempool
|
||||
.json_rpc_client
|
||||
.deref()
|
||||
.get_raw_transaction(&txid, None)
|
||||
.get_raw_transaction(txid, None)
|
||||
{
|
||||
std::result::Result::Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
|
@ -1,31 +1,12 @@
|
||||
pub mod bond_monitoring;
|
||||
pub mod coordinator_utils;
|
||||
pub mod create_taproot;
|
||||
pub mod mempool_monitoring;
|
||||
pub mod monitoring;
|
||||
pub mod tx_confirmation_monitoring;
|
||||
pub mod utils;
|
||||
|
||||
use self::utils::*;
|
||||
use self::coordinator_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<Coordinator>,
|
||||
offer: &OfferRequest,
|
||||
@ -89,7 +70,7 @@ pub async fn handle_maker_bond(
|
||||
};
|
||||
// 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)
|
||||
.move_offer_to_active(payload, &offer_id_hex, new_taker_bond_address)
|
||||
.await
|
||||
{
|
||||
Ok(timestamp) => timestamp,
|
||||
@ -116,7 +97,7 @@ pub async fn get_public_offers(
|
||||
let offers = match database.fetch_suitable_offers(request).await {
|
||||
Ok(offers) => offers,
|
||||
Err(e) => {
|
||||
return Err(FetchOffersError::DatabaseError(e.to_string()));
|
||||
return Err(FetchOffersError::Database(e.to_string()));
|
||||
}
|
||||
};
|
||||
if offers.is_none() {
|
||||
@ -159,7 +140,7 @@ pub async fn handle_taker_bond(
|
||||
|
||||
if let Err(e) = database
|
||||
.add_taker_info_and_move_table(
|
||||
&payload,
|
||||
payload,
|
||||
&trade_contract_psbt_maker,
|
||||
&trade_contract_psbt_taker,
|
||||
escrow_tx_txid,
|
||||
@ -186,7 +167,7 @@ pub async fn get_offer_status_maker(
|
||||
{
|
||||
Ok(offer) => offer,
|
||||
Err(e) => {
|
||||
return Err(FetchOffersError::DatabaseError(e.to_string()));
|
||||
return Err(FetchOffersError::Database(e.to_string()));
|
||||
}
|
||||
};
|
||||
match offer {
|
||||
@ -207,9 +188,9 @@ pub async fn fetch_escrow_confirmation_status(
|
||||
.is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex)
|
||||
.await
|
||||
{
|
||||
Ok(false) => return Err(FetchEscrowConfirmationError::NotFoundError),
|
||||
Ok(false) => return Err(FetchEscrowConfirmationError::NotFound),
|
||||
Ok(true) => (),
|
||||
Err(e) => return Err(FetchEscrowConfirmationError::DatabaseError(e.to_string())),
|
||||
Err(e) => return Err(FetchEscrowConfirmationError::Database(e.to_string())),
|
||||
}
|
||||
|
||||
if match database
|
||||
@ -217,11 +198,101 @@ pub async fn fetch_escrow_confirmation_status(
|
||||
.await
|
||||
{
|
||||
Ok(status) => status,
|
||||
Err(e) => return Err(FetchEscrowConfirmationError::DatabaseError(e.to_string())),
|
||||
Err(e) => return Err(FetchEscrowConfirmationError::Database(e.to_string())),
|
||||
} {
|
||||
// rust smh
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(FetchEscrowConfirmationError::NotFoundError)
|
||||
Err(FetchEscrowConfirmationError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_obligation_confirmation(
|
||||
payload: &OfferTakenRequest,
|
||||
coordinator: Arc<Coordinator>,
|
||||
) -> Result<(), RequestError> {
|
||||
let database = &coordinator.coordinator_db;
|
||||
|
||||
check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?;
|
||||
if let Err(e) = database
|
||||
.set_trader_happy_field(&payload.offer_id_hex, &payload.robohash_hex, true)
|
||||
.await
|
||||
{
|
||||
return Err(RequestError::Database(e.to_string()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn initiate_escrow(
|
||||
payload: &TradeObligationsUnsatisfied,
|
||||
coordinator: Arc<Coordinator>,
|
||||
) -> Result<(), RequestError> {
|
||||
let database = &coordinator.coordinator_db;
|
||||
|
||||
check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?;
|
||||
|
||||
if let Err(e) = database
|
||||
.set_trader_happy_field(&payload.offer_id_hex, &payload.robohash_hex, false)
|
||||
.await
|
||||
{
|
||||
return Err(RequestError::Database(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_final_payout(
|
||||
payload: &OfferTakenRequest,
|
||||
coordinator: Arc<Coordinator>,
|
||||
) -> Result<PayoutProcessingResult, RequestError> {
|
||||
let database = &coordinator.coordinator_db;
|
||||
|
||||
check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?;
|
||||
|
||||
let trader_happiness = match database.fetch_trader_happiness(&payload.offer_id_hex).await {
|
||||
Ok(happiness) => happiness,
|
||||
Err(e) => return Err(RequestError::Database(e.to_string())),
|
||||
};
|
||||
|
||||
if trader_happiness.maker_happy.is_some_and(|x| x)
|
||||
&& trader_happiness.taker_happy.is_some_and(|x| x)
|
||||
{
|
||||
panic!("Implement wallet.assemble_keyspend_payout_psbt()");
|
||||
// let payout_keyspend_psbt_hex = wallet
|
||||
// .assemble_keyspend_payout_psbt(&payload.offer_id_hex, &payload.robohash_hex)
|
||||
// .await
|
||||
// .context("Error assembling payout PSBT")?;
|
||||
// return Ok(PayoutProcessingResult::ReadyPSBT(payout_keyspend_psbt_hex));
|
||||
} else if (trader_happiness.maker_happy.is_none() || trader_happiness.taker_happy.is_none())
|
||||
&& !trader_happiness.escrow_ongoing
|
||||
{
|
||||
return Ok(PayoutProcessingResult::NotReady);
|
||||
}
|
||||
// if one of them is not happy
|
||||
// open escrow cli on coordinator to decide who will win (chat/dispute is out of scope for this demo)
|
||||
// once decided who will win assemble the correct payout psbt and return it to the according trader
|
||||
// the other trader gets a error code/ end of trade code
|
||||
// escrow winner has to be set true with a cli input of the coordinator. This could be an api
|
||||
// endpoint for the admin UI frontend in the future
|
||||
let potential_escrow_winner = match database.fetch_escrow_result(&payload.offer_id_hex).await {
|
||||
Ok(escrow_winner) => escrow_winner,
|
||||
Err(e) => return Err(RequestError::Database(e.to_string())),
|
||||
};
|
||||
|
||||
if let Some(escrow_winner) = potential_escrow_winner {
|
||||
if escrow_winner == payload.robohash_hex {
|
||||
panic!("Implement wallet.assemble_script_payout_psbt()");
|
||||
// let script_payout_psbt_hex = wallet
|
||||
// .assemble_script_payout_psbt(&payload.offer_id_hex, &payload.robohash_hex, is_maker_bool)
|
||||
// .await
|
||||
// .context("Error assembling payout PSBT")?;
|
||||
// return Ok(PayoutProcessingResult::ReadyPSBT(script_payout_psbt_hex));
|
||||
} else {
|
||||
// this will be returned to the losing trader
|
||||
Ok(PayoutProcessingResult::LostEscrow)
|
||||
}
|
||||
} else {
|
||||
// this will be returned if the coordinator hasn't decided yet
|
||||
Ok(PayoutProcessingResult::DecidingEscrow)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
pub fn generate_random_order_id(len: usize) -> String {
|
||||
// Generate `len` random bytes
|
||||
let bytes: Vec<u8> = rand::thread_rng()
|
||||
.sample_iter(&rand::distributions::Standard)
|
||||
.take(len)
|
||||
.collect();
|
||||
|
||||
// Convert bytes to hex string
|
||||
let hex_string = hex::encode(bytes);
|
||||
hex_string
|
||||
}
|
@ -218,7 +218,7 @@ impl CoordinatorDB {
|
||||
pub async fn move_offer_to_active(
|
||||
&self,
|
||||
data: &BondSubmissionRequest,
|
||||
offer_id: &String,
|
||||
offer_id: &str,
|
||||
taker_bond_address: String,
|
||||
) -> Result<u64> {
|
||||
let remaining_offer_information = self
|
||||
@ -291,7 +291,7 @@ impl CoordinatorDB {
|
||||
|
||||
pub async fn fetch_taker_bond_requirements(
|
||||
&self,
|
||||
offer_id_hex: &String,
|
||||
offer_id_hex: &str,
|
||||
) -> Result<BondRequirements> {
|
||||
let taker_bond_requirements = sqlx::query(
|
||||
"SELECT taker_bond_address, bond_amount_sat, amount_sat FROM active_maker_offers WHERE offer_id = ?",
|
||||
@ -345,8 +345,8 @@ impl CoordinatorDB {
|
||||
pub async fn add_taker_info_and_move_table(
|
||||
&self,
|
||||
trade_and_taker_info: &OfferPsbtRequest,
|
||||
trade_contract_psbt_maker: &String,
|
||||
trade_contract_psbt_taker: &String,
|
||||
trade_contract_psbt_maker: &str,
|
||||
trade_contract_psbt_taker: &str,
|
||||
trade_tx_txid: String,
|
||||
) -> Result<()> {
|
||||
let public_offer = self
|
||||
@ -380,8 +380,8 @@ impl CoordinatorDB {
|
||||
.bind(public_offer.musig_pubkey_hex_maker)
|
||||
.bind(trade_and_taker_info.trade_data.musig_pub_nonce_hex.clone())
|
||||
.bind(trade_and_taker_info.trade_data.musig_pubkey_hex.clone())
|
||||
.bind(trade_contract_psbt_maker.clone())
|
||||
.bind(trade_contract_psbt_taker.clone())
|
||||
.bind(trade_contract_psbt_maker)
|
||||
.bind(trade_contract_psbt_taker)
|
||||
.bind(trade_tx_txid)
|
||||
.bind(0)
|
||||
.bind(0)
|
||||
@ -393,8 +393,8 @@ impl CoordinatorDB {
|
||||
|
||||
pub async fn fetch_taken_offer_maker(
|
||||
&self,
|
||||
offer_id_hex: &String,
|
||||
robohash_hex_maker: &String,
|
||||
offer_id_hex: &str,
|
||||
robohash_hex_maker: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let offer = sqlx::query(
|
||||
"SELECT escrow_psbt_hex_maker, robohash_maker FROM taken_offers WHERE offer_id = ?",
|
||||
@ -563,8 +563,8 @@ impl CoordinatorDB {
|
||||
|
||||
pub async fn is_valid_robohash_in_table(
|
||||
&self,
|
||||
robohash_hex: &String,
|
||||
offer_id: &String,
|
||||
robohash_hex: &str,
|
||||
offer_id: &str,
|
||||
) -> Result<bool> {
|
||||
let robohash = hex::decode(robohash_hex)?;
|
||||
let robohash = sqlx::query(
|
||||
@ -578,7 +578,7 @@ impl CoordinatorDB {
|
||||
Ok(robohash.is_some())
|
||||
}
|
||||
|
||||
pub async fn fetch_escrow_tx_confirmation_status(&self, offer_id: &String) -> Result<bool> {
|
||||
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)
|
||||
@ -589,8 +589,8 @@ impl CoordinatorDB {
|
||||
|
||||
pub async fn set_trader_happy_field(
|
||||
&self,
|
||||
offer_id: &String,
|
||||
robohash: &String,
|
||||
offer_id: &str,
|
||||
robohash: &str,
|
||||
is_happy: bool,
|
||||
) -> Result<()> {
|
||||
let robohash_bytes = hex::decode(robohash)?;
|
||||
@ -632,7 +632,7 @@ impl CoordinatorDB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fetch_trader_happiness(&self, offer_id: &String) -> Result<TraderHappiness> {
|
||||
pub async fn fetch_trader_happiness(&self, offer_id: &str) -> Result<TraderHappiness> {
|
||||
let row = sqlx::query(
|
||||
"SELECT maker_happy, taker_happy, escrow_ongoing FROM taken_offers WHERE offer_id = ?",
|
||||
)
|
||||
@ -651,7 +651,7 @@ impl CoordinatorDB {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn fetch_escrow_result(&self, offer_id: &String) -> Result<Option<String>> {
|
||||
pub async fn fetch_escrow_result(&self, offer_id: &str) -> Result<Option<String>> {
|
||||
let row = sqlx::query("SELECT escrow_winner_robohash FROM taken_offers WHERE offer_id = ?")
|
||||
.bind(offer_id)
|
||||
.fetch_one(&*self.db_pool)
|
||||
|
@ -5,15 +5,20 @@ mod wallet;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use bdk::sled;
|
||||
use communication::{api::*, api_server, *};
|
||||
use coordinator::tx_confirmation_monitoring::update_transaction_confirmations;
|
||||
use coordinator::{monitoring::*, *};
|
||||
use communication::{api::*, api_server, communication_utils::*, handler_errors::*};
|
||||
use coordinator::{
|
||||
bond_monitoring::*, coordinator_utils::*,
|
||||
tx_confirmation_monitoring::update_transaction_confirmations, *,
|
||||
};
|
||||
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 std::{
|
||||
env,
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use validator::{Validate, ValidationError};
|
||||
use wallet::*;
|
||||
@ -32,6 +37,7 @@ async fn main() -> Result<()> {
|
||||
.init();
|
||||
dotenv().ok();
|
||||
debug!("Starting coordinator");
|
||||
|
||||
// Initialize the database pool
|
||||
let coordinator = Arc::new(Coordinator {
|
||||
coordinator_db: Arc::new(CoordinatorDB::init().await?),
|
||||
@ -49,8 +55,11 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// begin monitoring escrow transactions confirmations
|
||||
let coordinator_ref = Arc::clone(&coordinator);
|
||||
tokio::spawn(async move { update_transaction_confirmations(coordinator_ref).await });
|
||||
|
||||
// Start the API server
|
||||
api_server(coordinator).await?;
|
||||
Ok(())
|
||||
|
@ -80,7 +80,7 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<sled::Tree>>
|
||||
Ok(CoordinatorWallet {
|
||||
wallet: Arc::new(Mutex::new(wallet)),
|
||||
backend: Arc::new(backend),
|
||||
json_rpc_client: json_rpc_client,
|
||||
json_rpc_client,
|
||||
mempool: Arc::new(mempool),
|
||||
})
|
||||
}
|
||||
@ -128,8 +128,6 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
{
|
||||
let wallet = self.wallet.lock().await;
|
||||
for bond in bonds.as_ref().iter() {
|
||||
let input_sum: u64;
|
||||
|
||||
let tx: Transaction = deserialize(&hex::decode(&bond.bond_tx_hex)?)?;
|
||||
debug!("Validating bond in validate_bonds()");
|
||||
// we need to test this with signed and invalid/unsigned transactions
|
||||
@ -140,7 +138,7 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
}
|
||||
|
||||
// check if the tx has the correct input amounts (have to be >= trading amount)
|
||||
input_sum = match tx.input_sum(blockchain, &*wallet.database()) {
|
||||
let input_sum: u64 = match tx.input_sum(blockchain, &*wallet.database()) {
|
||||
Ok(amount) => {
|
||||
if amount < bond.requirements.min_input_sum_sat {
|
||||
invalid_bonds.insert(
|
||||
|
Reference in New Issue
Block a user