Merge branch 'change-testing' into research

cleanup of webserver and separation of logic
This commit is contained in:
f321x
2024-07-20 22:40:44 +02:00
10 changed files with 440 additions and 188 deletions

View File

@ -472,6 +472,7 @@ dependencies = [
"sqlx",
"tokio",
"tower",
"validator",
]
[[package]]
@ -583,6 +584,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"
@ -1138,6 +1174,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"
@ -1611,6 +1653,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"
@ -2368,6 +2434,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"
@ -2711,6 +2783,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"

View File

@ -25,6 +25,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

View File

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

View File

@ -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<Arc<CoordinatorDB>>,
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
Json(order): Json<OrderRequest>,
Extension(coordinator): Extension<Arc<Coordinator>>,
Json(offer): Json<OfferRequest>,
) -> Result<Response, AppError> {
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<Arc<CoordinatorDB>>,
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<BondSubmissionRequest>,
) -> Result<Response, AppError> {
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<Arc<CoordinatorDB>>,
Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<OffersRequest>,
) -> Result<Response, AppError> {
let offers: Option<Vec<PublicOffer>> = 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<Arc<CoordinatorDB>>,
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<OfferPsbtRequest>,
) -> Result<Response, AppError> {
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<Arc<CoordinatorDB>>,
Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<OfferTakenRequest>,
) -> Result<Response, AppError> {
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<Arc<CoordinatorDB>>,
Extension(wallet): Extension<Arc<CoordinatorWallet<sled::Tree>>>,
Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<PsbtSubmissionRequest>,
) -> Result<Response, AppError> {
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<Arc<CoordinatorDB>>,
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?
{
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<Coordinator>) -> 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<Coordinator>) -> 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<Coordinator>) -> 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 {

View File

@ -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<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
if offer_duration_ts > unix_timestamp + 604800 {
return Err(ValidationError::new("Offer duration too long"));
}
Ok(())
}

View File

@ -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<Coordinator>,
offer: &OfferRequest,
) -> Result<BondRequirementResponse, AppError> {
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<Coordinator>,
) -> Result<OfferActivatedResponse, BondError> {
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<Coordinator>,
) -> Result<PublicOffers, FetchOffersError> {
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<Coordinator>,
) -> Result<OfferTakenResponse, BondError> {
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<Coordinator>,
) -> Result<OfferTakenResponse, FetchOffersError> {
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<Coordinator>,
) -> Result<bool, FetchEscrowConfirmationError> {
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)
}
}

View File

@ -0,0 +1,13 @@
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
}

View File

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

View File

@ -147,7 +147,7 @@ impl CoordinatorDB {
pub async fn insert_new_maker_request(
&self,
order: &OrderRequest,
order: &OfferRequest,
bond_requirements: &BondRequirementResponse,
) -> Result<()> {
sqlx::query(

View File

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