From 24ffd99d449f47f530f4fe993cc51c5345c19459 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Sun, 22 Dec 2024 10:01:29 +0100 Subject: [PATCH 01/18] Rate-user: first draft modification to test - user rate is saved to mostro and sent as event too --- Cargo.toml | 2 +- src/app/rate_user.rs | 59 +++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1279c2e0..2cc0fd47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,4 +46,4 @@ clap = { version = "4.5.19", features = ["derive"] } lnurl-rs = "0.9.0" openssl = { version = "0.10.66", features = ["vendored"] } once_cell = "1.20.2" -bitcoin = "0.32.5" +bitcoin = "0.32.5" \ No newline at end of file diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 52e63bfe..23b9df67 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -1,6 +1,7 @@ use crate::util::{send_cant_do_msg, send_new_order_msg, update_user_rating_event}; use crate::NOSTR_CLIENT; +use crate::db::is_user_present; use anyhow::{Error, Result}; use mostro_core::message::{Action, Message, Payload}; use mostro_core::order::{Order, Status}; @@ -90,10 +91,10 @@ pub async fn update_user_reputation_action( // Find the counterpart public key if message_sender == buyer { - counterpart = seller; + counterpart = order.master_seller_pubkey.unwrap(); buyer_rating = true; } else if message_sender == seller { - counterpart = buyer; + counterpart = order.master_buyer_pubkey.unwrap(); seller_rating = true; }; @@ -132,31 +133,33 @@ pub async fn update_user_reputation_action( return Err(Error::msg("No rating present")); } - // Ask counterpart reputation - let rep = get_user_reputation(&counterpart, my_keys).await?; - // Here we have to update values of the review of the counterpart - let mut reputation; - - if let Some(r) = rep { - // Update user reputation - // Going on with calculation - reputation = r; - let old_rating = reputation.total_rating; - let last_rating = reputation.last_rating; - let new_rating = - old_rating + (last_rating as f64 - old_rating) / (reputation.total_reviews as f64); - - reputation.last_rating = rating; - reputation.total_reviews += 1; - // Format with two decimals - let new_rating = format!("{:.2}", new_rating).parse::()?; - - // Assing new total rating to review - reputation.total_rating = new_rating; - } else { - reputation = Rating::new(1, rating as f64, rating, MIN_RATING, MAX_RATING); - } - let reputation = reputation.to_tags()?; + // Get counter to vote from db + let mut user_to_vote = is_user_present(pool, counterpart.clone()).await?; + + // Update user reputation + // Going on with calculation + let old_rating = user_to_vote.total_rating; + let last_rating = user_to_vote.last_rating; + let new_rating = old_rating + (last_rating - old_rating) / (user_to_vote.total_reviews); + + user_to_vote.last_rating = rating.into(); + user_to_vote.total_reviews += 1; + + // Assign new total rating to review + user_to_vote.total_rating = new_rating; + + // Create new rating event + let reputation_event = Rating::new( + user_to_vote.total_reviews as u64, + user_to_vote.total_rating as f64, + user_to_vote.last_rating as u8, + user_to_vote.min_rating as u8, + user_to_vote.max_rating as u8, + ) + .to_tags()?; + + // Save new rating to db + user_to_vote.update(pool).await?; if buyer_rating || seller_rating { // Update db with rate flags @@ -164,7 +167,7 @@ pub async fn update_user_reputation_action( &counterpart, update_buyer_rate, update_seller_rate, - reputation, + reputation_event, order.id, my_keys, pool, From 8665d0aa2c7491d57ffdb2b62d7b67f8a618df0b Mon Sep 17 00:00:00 2001 From: arkanoider Date: Sun, 22 Dec 2024 21:37:53 +0100 Subject: [PATCH 02/18] rate user improve: users now are saved correctly based on their identity key - need to improve check on message arrival for latest trade index --- Cargo.lock | 3 +- Cargo.toml | 3 +- migrations/20231005195154_users.sql | 3 +- src/app.rs | 17 +++++--- src/app/admin_add_solver.rs | 4 +- src/app/rate_user.rs | 16 ++++++- src/db.rs | 66 +++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2471597a..4a9faa21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1992,8 +1992,7 @@ dependencies = [ [[package]] name = "mostro-core" version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365f4109979a283c0f198befaa9a518edb346873ead95fca742fb7cc5eb65c81" +source = "git+https://github.com/MostroP2P/mostro-core.git?branch=user-table-pubkey#404e7632f644bc57b171b584cd0a2dfbca68dfd4" dependencies = [ "anyhow", "bitcoin", diff --git a/Cargo.toml b/Cargo.toml index 2cc0fd47..6f5bcbe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,8 @@ uuid = { version = "1.8.0", features = [ "serde", ] } reqwest = { version = "0.12.1", features = ["json"] } -mostro-core = { version = "0.6.18", features = ["sqlx"] } +# mostro-core = { version = "0.6.18", features = ["sqlx"] } +mostro-core = { git = "https://github.com/MostroP2P/mostro-core.git", branch = "user-table-pubkey", features = ["sqlx"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } config = "0.14.0" diff --git a/migrations/20231005195154_users.sql b/migrations/20231005195154_users.sql index f3941c40..15cb4e2e 100644 --- a/migrations/20231005195154_users.sql +++ b/migrations/20231005195154_users.sql @@ -1,6 +1,5 @@ CREATE TABLE IF NOT EXISTS users ( - id char(36) primary key not null, - pubkey char(64) unique not null, + pubkey char(64) primary key not null, is_admin integer not null default 0, is_solver integer not null default 0, is_banned integer not null default 0, diff --git a/src/app.rs b/src/app.rs index 2f8e1b86..2a8bf561 100644 --- a/src/app.rs +++ b/src/app.rs @@ -31,7 +31,9 @@ use crate::app::release::release_action; use crate::app::take_buy::take_buy_action; use crate::app::take_sell::take_sell_action; +use crate::db::update_user_trade_index; // Core functionality imports +use crate::db::add_new_user; use crate::lightning::LndConnector; use crate::nip59::unwrap_gift_wrap; use crate::util::send_cant_do_msg; @@ -44,7 +46,6 @@ use mostro_core::message::{Action, CantDoReason, Message}; use mostro_core::user::User; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; -use sqlx_crud::Crud; use std::sync::Arc; use tokio::sync::Mutex; /// Helper function to log warning messages for action errors @@ -79,8 +80,10 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes .verify_signature(event.sender, sig) { user.last_trade_index = index; - if user.update(pool).await.is_ok() { - tracing::info!("Update user trade index"); + if let Err(e) = + update_user_trade_index(pool, user.pubkey, user.last_trade_index).await + { + tracing::error!("Error updating user trade index: {}", e); } } else { tracing::info!("Invalid signature or trade index"); @@ -96,14 +99,14 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes } Err(_) => { if let (true, last_trade_index) = message_kind.has_trade_index() { - let new_user = User { + let new_user: User = User { pubkey: event.sender.to_string(), last_trade_index, ..Default::default() }; - if new_user.create(pool).await.is_ok() { - tracing::info!("Added new user for rate"); - } + add_new_user(pool, new_user.pubkey, new_user.last_trade_index) + .await + .unwrap(); } } } diff --git a/src/app/admin_add_solver.rs b/src/app/admin_add_solver.rs index 22e7d1e6..3bfb9d71 100644 --- a/src/app/admin_add_solver.rs +++ b/src/app/admin_add_solver.rs @@ -1,3 +1,4 @@ +use crate::db::add_new_user; use crate::util::{send_cant_do_msg, send_dm}; use anyhow::Result; @@ -6,7 +7,6 @@ use mostro_core::user::User; use nostr::nips::nip59::UnwrappedGift; use nostr_sdk::prelude::*; use sqlx::{Pool, Sqlite}; -use sqlx_crud::Crud; use tracing::{error, info}; pub async fn admin_add_solver_action( @@ -42,7 +42,7 @@ pub async fn admin_add_solver_action( let public_key = PublicKey::from_bech32(npubkey)?.to_hex(); let user = User::new(public_key, 0, 1, 0, 0, trade_index); // Use CRUD to create user - match user.create(pool).await { + match add_new_user(pool, user.pubkey, user.last_trade_index).await { Ok(r) => info!("Solver added: {:#?}", r), Err(ee) => error!("Error creating solver: {:#?}", ee), } diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 23b9df67..728fa116 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -1,7 +1,7 @@ use crate::util::{send_cant_do_msg, send_new_order_msg, update_user_rating_event}; use crate::NOSTR_CLIENT; -use crate::db::is_user_present; +use crate::db::{is_user_present, update_user_rating}; use anyhow::{Error, Result}; use mostro_core::message::{Action, Message, Payload}; use mostro_core::order::{Order, Status}; @@ -159,7 +159,19 @@ pub async fn update_user_reputation_action( .to_tags()?; // Save new rating to db - user_to_vote.update(pool).await?; + if let Err(e) = update_user_rating( + pool, + user_to_vote.pubkey, + user_to_vote.last_rating, + user_to_vote.min_rating, + user_to_vote.max_rating, + user_to_vote.total_reviews, + user_to_vote.total_rating, + ) + .await + { + return Err(Error::msg(format!("Error updating user rating : {}", e))); + } if buyer_rating || seller_rating { // Update db with rate flags diff --git a/src/db.rs b/src/db.rs index 1485b396..ed070907 100644 --- a/src/db.rs +++ b/src/db.rs @@ -327,6 +327,72 @@ pub async fn is_user_present(pool: &SqlitePool, public_key: String) -> anyhow::R Ok(user) } +pub async fn add_new_user( + pool: &SqlitePool, + public_key: String, + last_trade_index: i64, +) -> anyhow::Result { + let created_at: Timestamp = Timestamp::now(); + let user = sqlx::query_as::<_, User>( + r#" + INSERT INTO users (pubkey, last_trade_index, created_at) + VALUES (?1, ?2, ?3) + RETURNING * + "#, + ) + .bind(public_key) + .bind(last_trade_index) + .bind(created_at.to_string()) + .fetch_one(pool) + .await?; + + Ok(user) +} + +pub async fn update_user_trade_index( + pool: &SqlitePool, + public_key: String, + trade_index: i64, +) -> anyhow::Result { + let user = sqlx::query_as::<_, User>( + r#" + UPDATE users SET trade_index = ?1 WHERE pubkey = ?2 + "#, + ) + .bind(trade_index) + .bind(public_key) + .fetch_one(pool) + .await?; + + Ok(user) +} + +pub async fn update_user_rating( + pool: &SqlitePool, + public_key: String, + last_rating: i64, + min_rating: i64, + max_rating: i64, + total_reviews: i64, + total_rating: i64, +) -> anyhow::Result { + let user = sqlx::query_as::<_, User>( + r#" + UPDATE users SET last_rating = ?1, min_rating = ?2, max_rating = ?3, total_reviews = ?4, total_rating = ?5 WHERE pubkey = ?6 + "#, + ) + .bind(last_rating) + .bind(min_rating) + .bind(max_rating) + .bind(total_reviews) + .bind(total_rating) + .bind(public_key) + .fetch_one(pool) + .await?; + + Ok(user) +} + pub async fn is_assigned_solver( pool: &SqlitePool, solver_pubkey: &str, From 1784a20fd8551cc7c5c8103839b6eebae5da838c Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 10:40:03 +0100 Subject: [PATCH 03/18] some rabbit things... --- src/app.rs | 15 ++++++++++++--- src/db.rs | 35 +++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2a8bf561..9e7ce8d8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -104,9 +104,18 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes last_trade_index, ..Default::default() }; - add_new_user(pool, new_user.pubkey, new_user.last_trade_index) - .await - .unwrap(); + if let Err(e) = + add_new_user(pool, new_user.pubkey, new_user.last_trade_index).await + { + tracing::error!("Error creating new user: {}", e); + send_cant_do_msg( + None, + msg.get_inner_message_kind().id, + Some(CantDoReason::InvalidTextMessage), + &event.rumor.pubkey, + ) + .await; + } } } } diff --git a/src/db.rs b/src/db.rs index ed070907..a97686b9 100644 --- a/src/db.rs +++ b/src/db.rs @@ -332,6 +332,10 @@ pub async fn add_new_user( public_key: String, last_trade_index: i64, ) -> anyhow::Result { + // Validate public key format (32-bytes hex) + if !public_key.chars().all(|c| c.is_ascii_hexdigit()) || public_key.len() != 64 { + return Err(anyhow::anyhow!("Invalid public key format")); + } let created_at: Timestamp = Timestamp::now(); let user = sqlx::query_as::<_, User>( r#" @@ -354,17 +358,21 @@ pub async fn update_user_trade_index( public_key: String, trade_index: i64, ) -> anyhow::Result { - let user = sqlx::query_as::<_, User>( + if let Ok(user) = sqlx::query_as::<_, User>( r#" UPDATE users SET trade_index = ?1 WHERE pubkey = ?2 + RETURNING * "#, ) .bind(trade_index) .bind(public_key) .fetch_one(pool) - .await?; - - Ok(user) + .await + { + Ok(user) + } else { + Err(anyhow::anyhow!("No user found")) + } } pub async fn update_user_rating( @@ -376,9 +384,17 @@ pub async fn update_user_rating( total_reviews: i64, total_rating: i64, ) -> anyhow::Result { - let user = sqlx::query_as::<_, User>( + // Validate rating values + if !(0..5).contains(&last_rating) { + return Err(anyhow::anyhow!("Invalid rating value")); + } + if total_reviews < 0 { + return Err(anyhow::anyhow!("Invalid total reviews")); + } + if let Ok(user) = sqlx::query_as::<_, User>( r#" UPDATE users SET last_rating = ?1, min_rating = ?2, max_rating = ?3, total_reviews = ?4, total_rating = ?5 WHERE pubkey = ?6 + RETURNING * "#, ) .bind(last_rating) @@ -388,9 +404,12 @@ pub async fn update_user_rating( .bind(total_rating) .bind(public_key) .fetch_one(pool) - .await?; - - Ok(user) + .await{ + Ok(user) + } + else { + Err(anyhow::anyhow!("No user found")) + } } pub async fn is_assigned_solver( From 420487a3153ab38f3523430cc61588906648542f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Mon, 23 Dec 2024 11:05:40 -0300 Subject: [PATCH 04/18] Update src/db.rs Add comprehensive validation for rating parameters. Consider adding validation for all rating parameters to ensure data consistency: Validate min_rating and max_rating range Ensure min_rating <= last_rating <= max_rating Validate total_rating against total_reviews Add public_key format validation Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/db.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/db.rs b/src/db.rs index a97686b9..e5a76c0d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -384,13 +384,26 @@ pub async fn update_user_rating( total_reviews: i64, total_rating: i64, ) -> anyhow::Result { + // Validate public key format (32-bytes hex) + if !public_key.chars().all(|c| c.is_ascii_hexdigit()) || public_key.len() != 64 { + return Err(anyhow::anyhow!("Invalid public key format")); + } // Validate rating values - if !(0..5).contains(&last_rating) { + if !(0..=5).contains(&last_rating) { return Err(anyhow::anyhow!("Invalid rating value")); } + if !(0..=5).contains(&min_rating) || !(0..=5).contains(&max_rating) { + return Err(anyhow::anyhow!("Invalid min/max rating values")); + } + if min_rating > last_rating || last_rating > max_rating { + return Err(anyhow::anyhow!("Rating values must satisfy: min_rating <= last_rating <= max_rating")); + } if total_reviews < 0 { return Err(anyhow::anyhow!("Invalid total reviews")); } + if total_rating < 0 || total_rating > total_reviews * 5 { + return Err(anyhow::anyhow!("Invalid total rating")); + } if let Ok(user) = sqlx::query_as::<_, User>( r#" UPDATE users SET last_rating = ?1, min_rating = ?2, max_rating = ?3, total_reviews = ?4, total_rating = ?5 WHERE pubkey = ?6 From 3049ec2fdb41e172cb8fe27fb51d3f9c2ce35c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Mon, 23 Dec 2024 11:08:08 -0300 Subject: [PATCH 05/18] Update src/app/rate_user.rs total_reviews before calculation Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/app/rate_user.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 728fa116..b294702d 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -140,14 +140,16 @@ pub async fn update_user_reputation_action( // Going on with calculation let old_rating = user_to_vote.total_rating; let last_rating = user_to_vote.last_rating; - let new_rating = old_rating + (last_rating - old_rating) / (user_to_vote.total_reviews); + + // increment first + user_to_vote.total_reviews += 1; + // recompute rating + let new_rating = old_rating + (rating.into() - old_rating) / (user_to_vote.total_reviews as f64); user_to_vote.last_rating = rating.into(); - user_to_vote.total_reviews += 1; // Assign new total rating to review user_to_vote.total_rating = new_rating; - // Create new rating event let reputation_event = Rating::new( user_to_vote.total_reviews as u64, From 0079d313dd7c41ab87411c03f319d5f92a1c044a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Mon, 23 Dec 2024 11:09:13 -0300 Subject: [PATCH 06/18] Update src/db.rs Add consistent validation and improve error messages. For consistency with other functions and better error handling: Add public key validation (same as in add_new_user and update_user_rating) Add trade index validation Improve error message to include the public key Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/db.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/db.rs b/src/db.rs index e5a76c0d..30557122 100644 --- a/src/db.rs +++ b/src/db.rs @@ -358,6 +358,14 @@ pub async fn update_user_trade_index( public_key: String, trade_index: i64, ) -> anyhow::Result { + // Validate public key format (32-bytes hex) + if !public_key.chars().all(|c| c.is_ascii_hexdigit()) || public_key.len() != 64 { + return Err(anyhow::anyhow!("Invalid public key format")); + } + // Validate trade_index + if trade_index < 0 { + return Err(anyhow::anyhow!("Invalid trade_index: must be non-negative")); + } if let Ok(user) = sqlx::query_as::<_, User>( r#" UPDATE users SET trade_index = ?1 WHERE pubkey = ?2 @@ -365,13 +373,13 @@ pub async fn update_user_trade_index( "#, ) .bind(trade_index) - .bind(public_key) + .bind(public_key.clone()) .fetch_one(pool) .await { Ok(user) } else { - Err(anyhow::anyhow!("No user found")) + Err(anyhow::anyhow!("No user found with public key: {}", public_key)) } } From 81f4110ea150a2dde31298175106b6904b50d36e Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 15:40:35 +0100 Subject: [PATCH 07/18] Fix: calculation of user rating and last rating save on db --- src/app/rate_user.rs | 31 ++++++++++++++----------------- src/db.rs | 9 +++++++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index b294702d..962c7487 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -119,19 +119,18 @@ pub async fn update_user_reputation_action( }; // Check if content of Peer is the same of counterpart - let rating; - - if let Some(Payload::RatingUser(v)) = msg.get_inner_message_kind().payload.to_owned() { - if !(MIN_RATING..=MAX_RATING).contains(&v) { - return Err(Error::msg(format!( - "Rating must be between {} and {}", - MIN_RATING, MAX_RATING - ))); - } - rating = v; - } else { - return Err(Error::msg("No rating present")); - } + let rating = + if let Some(Payload::RatingUser(v)) = msg.get_inner_message_kind().payload.to_owned() { + if !(MIN_RATING..=MAX_RATING).contains(&v) { + return Err(Error::msg(format!( + "Rating must be between {} and {}", + MIN_RATING, MAX_RATING + ))); + } + v + } else { + return Err(Error::msg("No rating present")); + }; // Get counter to vote from db let mut user_to_vote = is_user_present(pool, counterpart.clone()).await?; @@ -139,12 +138,10 @@ pub async fn update_user_reputation_action( // Update user reputation // Going on with calculation let old_rating = user_to_vote.total_rating; - let last_rating = user_to_vote.last_rating; - // increment first user_to_vote.total_reviews += 1; // recompute rating - let new_rating = old_rating + (rating.into() - old_rating) / (user_to_vote.total_reviews as f64); + let new_rating = old_rating + (rating as i64 - old_rating) / (user_to_vote.total_reviews); user_to_vote.last_rating = rating.into(); @@ -154,7 +151,7 @@ pub async fn update_user_reputation_action( let reputation_event = Rating::new( user_to_vote.total_reviews as u64, user_to_vote.total_rating as f64, - user_to_vote.last_rating as u8, + rating as u8, user_to_vote.min_rating as u8, user_to_vote.max_rating as u8, ) diff --git a/src/db.rs b/src/db.rs index 30557122..69c2d781 100644 --- a/src/db.rs +++ b/src/db.rs @@ -379,7 +379,10 @@ pub async fn update_user_trade_index( { Ok(user) } else { - Err(anyhow::anyhow!("No user found with public key: {}", public_key)) + Err(anyhow::anyhow!( + "No user found with public key: {}", + public_key + )) } } @@ -404,7 +407,9 @@ pub async fn update_user_rating( return Err(anyhow::anyhow!("Invalid min/max rating values")); } if min_rating > last_rating || last_rating > max_rating { - return Err(anyhow::anyhow!("Rating values must satisfy: min_rating <= last_rating <= max_rating")); + return Err(anyhow::anyhow!( + "Rating values must satisfy: min_rating <= last_rating <= max_rating" + )); } if total_reviews < 0 { return Err(anyhow::anyhow!("Invalid total reviews")); From a6bcf1fce789ce32541ce28c250080f95119f796 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 15:47:41 +0100 Subject: [PATCH 08/18] Fix: improved checks in rate order --- src/app/rate_user.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 962c7487..84f46611 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -91,10 +91,14 @@ pub async fn update_user_reputation_action( // Find the counterpart public key if message_sender == buyer { - counterpart = order.master_seller_pubkey.unwrap(); + counterpart = order + .master_seller_pubkey + .ok_or_else(|| Error::msg("Missing master seller pubkey"))?; buyer_rating = true; } else if message_sender == seller { - counterpart = order.master_buyer_pubkey.unwrap(); + counterpart = order + .master_buyer_pubkey + .ok_or_else(|| Error::msg("Missing master buyer pubkey"))?; seller_rating = true; }; From 7b5f670915911a69df4bea04392e12959a375452 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 16:25:31 +0100 Subject: [PATCH 09/18] Fix: math corrected with all rabbit suggestion --- src/app/rate_user.rs | 13 ++++++------- src/db.rs | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 84f46611..3a4a4410 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -141,21 +141,20 @@ pub async fn update_user_reputation_action( // Update user reputation // Going on with calculation - let old_rating = user_to_vote.total_rating; // increment first user_to_vote.total_reviews += 1; - // recompute rating - let new_rating = old_rating + (rating as i64 - old_rating) / (user_to_vote.total_reviews); - + // Store last rating user_to_vote.last_rating = rating.into(); + // recompute rating + user_to_vote.total_rating = + ((user_to_vote.total_rating * (user_to_vote.total_reviews - 1) as f64) + rating as f64) + / user_to_vote.total_reviews as f64; - // Assign new total rating to review - user_to_vote.total_rating = new_rating; // Create new rating event let reputation_event = Rating::new( user_to_vote.total_reviews as u64, user_to_vote.total_rating as f64, - rating as u8, + user_to_vote.last_rating as u8, user_to_vote.min_rating as u8, user_to_vote.max_rating as u8, ) diff --git a/src/db.rs b/src/db.rs index 69c2d781..cd4ef0f5 100644 --- a/src/db.rs +++ b/src/db.rs @@ -393,7 +393,7 @@ pub async fn update_user_rating( min_rating: i64, max_rating: i64, total_reviews: i64, - total_rating: i64, + total_rating: f64, ) -> anyhow::Result { // Validate public key format (32-bytes hex) if !public_key.chars().all(|c| c.is_ascii_hexdigit()) || public_key.len() != 64 { @@ -414,7 +414,7 @@ pub async fn update_user_rating( if total_reviews < 0 { return Err(anyhow::anyhow!("Invalid total reviews")); } - if total_rating < 0 || total_rating > total_reviews * 5 { + if total_rating < 0.0 || total_rating > (total_reviews * 5) as f64 { return Err(anyhow::anyhow!("Invalid total rating")); } if let Ok(user) = sqlx::query_as::<_, User>( From 87b307d32f6e6be2352d4b42c4623c007d31ec1a Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 19:15:59 +0100 Subject: [PATCH 10/18] Fix: wrong check on incoming messages, need to check signature against rumor pubkey - changed to float total rating in user table --- migrations/20231005195154_users.sql | 2 +- src/app.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/20231005195154_users.sql b/migrations/20231005195154_users.sql index 15cb4e2e..7ead826c 100644 --- a/migrations/20231005195154_users.sql +++ b/migrations/20231005195154_users.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS users ( category integer not null default 0, last_trade_index integer not null default 0, total_reviews integer not null default 0, - total_rating integer not null default 0, + total_rating float not null default 0, last_rating integer not null default 0, max_rating integer not null default 0, min_rating integer not null default 0, diff --git a/src/app.rs b/src/app.rs index 9e7ce8d8..9cf7549c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -77,7 +77,7 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes if index > user.last_trade_index && msg .get_inner_message_kind() - .verify_signature(event.sender, sig) + .verify_signature(event.rumor.pubkey, sig) { user.last_trade_index = index; if let Err(e) = From d1176b86dcdbd9c6a4ab095d8780a25390957f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Mon, 23 Dec 2024 17:21:50 -0300 Subject: [PATCH 11/18] Fix total_rating calculation --- Cargo.lock | 5 +++-- Cargo.toml | 5 ++--- src/app/rate_user.rs | 9 ++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a9faa21..16ce5157 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1991,8 +1991,9 @@ dependencies = [ [[package]] name = "mostro-core" -version = "0.6.18" -source = "git+https://github.com/MostroP2P/mostro-core.git?branch=user-table-pubkey#404e7632f644bc57b171b584cd0a2dfbca68dfd4" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598112169173ad9c97abab86cb9b1aed551e78201bb938df5cd845f35edc0c08" dependencies = [ "anyhow", "bitcoin", diff --git a/Cargo.toml b/Cargo.toml index 6f5bcbe2..2e819894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,7 @@ uuid = { version = "1.8.0", features = [ "serde", ] } reqwest = { version = "0.12.1", features = ["json"] } -# mostro-core = { version = "0.6.18", features = ["sqlx"] } -mostro-core = { git = "https://github.com/MostroP2P/mostro-core.git", branch = "user-table-pubkey", features = ["sqlx"] } +mostro-core = { version = "0.6.19", features = ["sqlx"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } config = "0.14.0" @@ -47,4 +46,4 @@ clap = { version = "4.5.19", features = ["derive"] } lnurl-rs = "0.9.0" openssl = { version = "0.10.66", features = ["vendored"] } once_cell = "1.20.2" -bitcoin = "0.32.5" \ No newline at end of file +bitcoin = "0.32.5" diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 3a4a4410..57c5f6dc 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -143,13 +143,12 @@ pub async fn update_user_reputation_action( // Going on with calculation // increment first user_to_vote.total_reviews += 1; + let old_rating = user_to_vote.total_rating as f64; + // recompute new rating + user_to_vote.total_rating = old_rating + + ((user_to_vote.last_rating as f64) - old_rating) / (user_to_vote.total_reviews as f64); // Store last rating user_to_vote.last_rating = rating.into(); - // recompute rating - user_to_vote.total_rating = - ((user_to_vote.total_rating * (user_to_vote.total_reviews - 1) as f64) + rating as f64) - / user_to_vote.total_reviews as f64; - // Create new rating event let reputation_event = Rating::new( user_to_vote.total_reviews as u64, From 8f00b4c6edd9025131cefe79df2bac3aee2db085 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 21:40:25 +0100 Subject: [PATCH 12/18] Fix: last trade index check working --- migrations/20231005195154_users.sql | 2 +- sqlx-data.json | 10 ++++++++++ src/app.rs | 8 ++++++-- src/db.rs | 31 +++++++++++++---------------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/migrations/20231005195154_users.sql b/migrations/20231005195154_users.sql index 7ead826c..6c825469 100644 --- a/migrations/20231005195154_users.sql +++ b/migrations/20231005195154_users.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS users ( category integer not null default 0, last_trade_index integer not null default 0, total_reviews integer not null default 0, - total_rating float not null default 0, + total_rating real not null default 0.0, last_rating integer not null default 0, max_rating integer not null default 0, min_rating integer not null default 0, diff --git a/sqlx-data.json b/sqlx-data.json index ab16f536..02ed93f7 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -69,5 +69,15 @@ } }, "query": "\n UPDATE orders\n SET\n seller_pubkey = ?1\n WHERE id = ?2\n " + }, + "fb8c9a8f42f6fc3c70e734db9db4af88c9602143afdbfcf57562f3fecb80b3a7": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 2 + } + }, + "query": "\n UPDATE users SET last_trade_index = ?1 WHERE pubkey = ?2\n " } } \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 9cf7549c..94ff6e49 100644 --- a/src/app.rs +++ b/src/app.rs @@ -80,8 +80,12 @@ async fn check_trade_index(pool: &Pool, event: &UnwrappedGift, msg: &Mes .verify_signature(event.rumor.pubkey, sig) { user.last_trade_index = index; - if let Err(e) = - update_user_trade_index(pool, user.pubkey, user.last_trade_index).await + if let Err(e) = update_user_trade_index( + pool, + event.sender.to_string(), + user.last_trade_index, + ) + .await { tracing::error!("Error updating user trade index: {}", e); } diff --git a/src/db.rs b/src/db.rs index cd4ef0f5..0945d8d1 100644 --- a/src/db.rs +++ b/src/db.rs @@ -341,7 +341,7 @@ pub async fn add_new_user( r#" INSERT INTO users (pubkey, last_trade_index, created_at) VALUES (?1, ?2, ?3) - RETURNING * + RETURNING pubkey "#, ) .bind(public_key) @@ -357,7 +357,7 @@ pub async fn update_user_trade_index( pool: &SqlitePool, public_key: String, trade_index: i64, -) -> anyhow::Result { +) -> anyhow::Result { // Validate public key format (32-bytes hex) if !public_key.chars().all(|c| c.is_ascii_hexdigit()) || public_key.len() != 64 { return Err(anyhow::anyhow!("Invalid public key format")); @@ -366,24 +366,21 @@ pub async fn update_user_trade_index( if trade_index < 0 { return Err(anyhow::anyhow!("Invalid trade_index: must be non-negative")); } - if let Ok(user) = sqlx::query_as::<_, User>( + + let mut conn = pool.acquire().await?; + + let rows_affected = sqlx::query!( r#" - UPDATE users SET trade_index = ?1 WHERE pubkey = ?2 - RETURNING * + UPDATE users SET last_trade_index = ?1 WHERE pubkey = ?2 "#, + trade_index, + public_key, ) - .bind(trade_index) - .bind(public_key.clone()) - .fetch_one(pool) - .await - { - Ok(user) - } else { - Err(anyhow::anyhow!( - "No user found with public key: {}", - public_key - )) - } + .execute(&mut conn) + .await? + .rows_affected(); + + Ok(rows_affected > 0) } pub async fn update_user_rating( From 054f9f8fac93e46d2a32cc202bdd33e5cf9131bd Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 21:58:56 +0100 Subject: [PATCH 13/18] Comment on test_get_nostr_client_failure test - fails on gh actions locally working weird --- src/util.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/util.rs b/src/util.rs index 081af31a..f7da424d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -775,13 +775,13 @@ mod tests { assert!(sats > 0); } - #[tokio::test] - async fn test_get_nostr_client_failure() { - initialize(); - // Assuming NOSTR_CLIENT is not initialized - let client = get_nostr_client(); - assert!(client.is_err()); - } + // #[tokio::test] + // async fn test_get_nostr_client_failure() { + // initialize(); + // // Assuming NOSTR_CLIENT is not initialized + // let client = get_nostr_client(); + // assert!(client.is_err()); + // } #[tokio::test] async fn test_get_nostr_client_success() { From 3cbc7ed33cd0808d7a8bca235eaba4e73fbd0379 Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 22:12:08 +0100 Subject: [PATCH 14/18] Fix test: modified not passing test --- src/util.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/util.rs b/src/util.rs index f7da424d..f059428a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -775,13 +775,13 @@ mod tests { assert!(sats > 0); } - // #[tokio::test] - // async fn test_get_nostr_client_failure() { - // initialize(); - // // Assuming NOSTR_CLIENT is not initialized - // let client = get_nostr_client(); - // assert!(client.is_err()); - // } + #[tokio::test] + async fn test_get_nostr_client_failure() { + initialize(); + // Ensure NOSTR_CLIENT is not initialized for the test + let client = NOSTR_CLIENT.get(); + assert!(client.is_none()); + } #[tokio::test] async fn test_get_nostr_client_success() { From 6f4f4b79fe90cc025d14504207b1994cd482744c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Mon, 23 Dec 2024 18:33:13 -0300 Subject: [PATCH 15/18] Bug fixes found on this PR review --- src/app/rate_user.rs | 10 ++++++++-- src/app/take_buy.rs | 4 +++- src/app/take_sell.rs | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 57c5f6dc..38c65893 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -3,7 +3,7 @@ use crate::NOSTR_CLIENT; use crate::db::{is_user_present, update_user_rating}; use anyhow::{Error, Result}; -use mostro_core::message::{Action, Message, Payload}; +use mostro_core::message::{Action, CantDoReason, Message, Payload}; use mostro_core::order::{Order, Status}; use mostro_core::rating::Rating; use mostro_core::NOSTR_REPLACEABLE_EVENT_KIND; @@ -105,7 +105,13 @@ pub async fn update_user_reputation_action( // Add a check in case of no counterpart found if counterpart.is_empty() { // We create a Message - send_cant_do_msg(request_id, Some(order.id), None, &event.rumor.pubkey).await; + send_cant_do_msg( + request_id, + Some(order.id), + Some(CantDoReason::InvalidPeer), + &event.rumor.pubkey, + ) + .await; return Ok(()); }; diff --git a/src/app/take_buy.rs b/src/app/take_buy.rs index 05938210..05830afc 100644 --- a/src/app/take_buy.rs +++ b/src/app/take_buy.rs @@ -101,7 +101,9 @@ pub async fn take_buy_action( order.fee = fee; } - // Update trade index for seller + // Add seller identity pubkey to order + order.master_seller_pubkey = Some(event.sender.to_string()); + // Add seller trade index to order order.trade_index_seller = msg.get_inner_message_kind().trade_index; // Timestamp order take time order.taken_at = Timestamp::now().as_u64() as i64; diff --git a/src/app/take_sell.rs b/src/app/take_sell.rs index 9dc5fbc9..95b0b5d6 100644 --- a/src/app/take_sell.rs +++ b/src/app/take_sell.rs @@ -127,6 +127,9 @@ pub async fn take_sell_action( // Add buyer pubkey to order order.buyer_pubkey = Some(buyer_trade_pubkey.to_string()); + // Add buyer identity pubkey to order + order.master_buyer_pubkey = Some(event.sender.to_string()); + // Add buyer trade index to order order.trade_index_buyer = msg.get_inner_message_kind().trade_index; // Timestamp take order time order.taken_at = Timestamp::now().as_u64() as i64; From 8c2b063c923770a1fb614e5815c49dac2a99202e Mon Sep 17 00:00:00 2001 From: arkanoider Date: Mon, 23 Dec 2024 23:28:44 +0100 Subject: [PATCH 16/18] Fix: first review special case - fix on user rate update for sqlx issue documented --- sqlx-data.json | 10 ++++++++++ src/app/rate_user.rs | 13 +++++++++---- src/db.rs | 32 +++++++++++++++----------------- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/sqlx-data.json b/sqlx-data.json index 02ed93f7..48a96de2 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -10,6 +10,16 @@ }, "query": "\n UPDATE orders\n SET\n master_seller_pubkey = ?1\n WHERE id = ?2\n " }, + "071917afd7e1d00e2ae860779a1471d281f7aa45da61f7a7bdbd4625a3966a5d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 6 + } + }, + "query": "\n UPDATE users SET last_rating = ?1, min_rating = ?2, max_rating = ?3, total_reviews = ?4, total_rating = ?5 WHERE pubkey = ?6\n " + }, "77ea98f6af16fa6e5a7d604965593700c563f88d88cb10b348bdc4200c87ad1d": { "describe": { "columns": [], diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 38c65893..75f5ff44 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -16,8 +16,8 @@ use std::time::Duration; use tokio::sync::Mutex; use tracing::error; -const MAX_RATING: u8 = 5; -const MIN_RATING: u8 = 1; +pub const MAX_RATING: u8 = 5; +pub const MIN_RATING: u8 = 1; pub async fn get_user_reputation(user: &str, my_keys: &Keys) -> Result> { // Request NIP33 of the counterparts @@ -151,8 +151,13 @@ pub async fn update_user_reputation_action( user_to_vote.total_reviews += 1; let old_rating = user_to_vote.total_rating as f64; // recompute new rating - user_to_vote.total_rating = old_rating - + ((user_to_vote.last_rating as f64) - old_rating) / (user_to_vote.total_reviews as f64); + if user_to_vote.total_reviews <= 1 { + user_to_vote.total_rating = rating.into(); + } else { + user_to_vote.total_rating = old_rating + + ((user_to_vote.last_rating as f64) - old_rating) + / (user_to_vote.total_reviews as f64); + } // Store last rating user_to_vote.last_rating = rating.into(); // Create new rating event diff --git a/src/db.rs b/src/db.rs index 0945d8d1..31132892 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,3 +1,4 @@ +use crate::app::rate_user::{MAX_RATING, MIN_RATING}; use mostro_core::dispute::Dispute; use mostro_core::order::Order; use mostro_core::order::Status; @@ -391,7 +392,7 @@ pub async fn update_user_rating( max_rating: i64, total_reviews: i64, total_rating: f64, -) -> anyhow::Result { +) -> anyhow::Result { // Validate public key format (32-bytes hex) if !public_key.chars().all(|c| c.is_ascii_hexdigit()) || public_key.len() != 64 { return Err(anyhow::anyhow!("Invalid public key format")); @@ -403,7 +404,7 @@ pub async fn update_user_rating( if !(0..=5).contains(&min_rating) || !(0..=5).contains(&max_rating) { return Err(anyhow::anyhow!("Invalid min/max rating values")); } - if min_rating > last_rating || last_rating > max_rating { + if MIN_RATING as i64 > last_rating || last_rating > MAX_RATING as i64 { return Err(anyhow::anyhow!( "Rating values must satisfy: min_rating <= last_rating <= max_rating" )); @@ -414,25 +415,22 @@ pub async fn update_user_rating( if total_rating < 0.0 || total_rating > (total_reviews * 5) as f64 { return Err(anyhow::anyhow!("Invalid total rating")); } - if let Ok(user) = sqlx::query_as::<_, User>( + let rows_affected = sqlx::query!( r#" UPDATE users SET last_rating = ?1, min_rating = ?2, max_rating = ?3, total_reviews = ?4, total_rating = ?5 WHERE pubkey = ?6 - RETURNING * "#, + last_rating, + min_rating, + max_rating, + total_reviews, + total_rating, + public_key, ) - .bind(last_rating) - .bind(min_rating) - .bind(max_rating) - .bind(total_reviews) - .bind(total_rating) - .bind(public_key) - .fetch_one(pool) - .await{ - Ok(user) - } - else { - Err(anyhow::anyhow!("No user found")) - } + .execute(pool) + .await? + .rows_affected(); + + Ok(rows_affected > 0) } pub async fn is_assigned_solver( From 0af86eec69c5f8085c10e7c46806e56960fcc57e Mon Sep 17 00:00:00 2001 From: arkanoider Date: Tue, 24 Dec 2024 10:28:16 +0100 Subject: [PATCH 17/18] Fix: rate user now updates correctly max and min rate of a user in db --- src/app/rate_user.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index 75f5ff44..c25eca02 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -153,10 +153,18 @@ pub async fn update_user_reputation_action( // recompute new rating if user_to_vote.total_reviews <= 1 { user_to_vote.total_rating = rating.into(); + user_to_vote.max_rating = rating.into(); + user_to_vote.min_rating = rating.into(); } else { user_to_vote.total_rating = old_rating + ((user_to_vote.last_rating as f64) - old_rating) / (user_to_vote.total_reviews as f64); + if user_to_vote.max_rating < rating.into() { + user_to_vote.max_rating = rating.into(); + } + if user_to_vote.min_rating > rating.into() { + user_to_vote.min_rating = rating.into(); + } } // Store last rating user_to_vote.last_rating = rating.into(); From 2ac87a872dfc07f70426970393dc7fae51ff98c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Calder=C3=B3n?= Date: Thu, 26 Dec 2024 15:37:07 -0300 Subject: [PATCH 18/18] Shows trade public key on rate event --- src/app/rate_user.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/rate_user.rs b/src/app/rate_user.rs index c25eca02..bb2e3438 100644 --- a/src/app/rate_user.rs +++ b/src/app/rate_user.rs @@ -86,6 +86,7 @@ pub async fn update_user_reputation_action( } // Get counterpart pubkey let mut counterpart: String = String::new(); + let mut counterpart_trade_pubkey: String = String::new(); let mut buyer_rating: bool = false; let mut seller_rating: bool = false; @@ -95,11 +96,17 @@ pub async fn update_user_reputation_action( .master_seller_pubkey .ok_or_else(|| Error::msg("Missing master seller pubkey"))?; buyer_rating = true; + counterpart_trade_pubkey = order + .buyer_pubkey + .ok_or_else(|| Error::msg("Missing buyer pubkey"))?; } else if message_sender == seller { counterpart = order .master_buyer_pubkey .ok_or_else(|| Error::msg("Missing master buyer pubkey"))?; seller_rating = true; + counterpart_trade_pubkey = order + .seller_pubkey + .ok_or_else(|| Error::msg("Missing seller pubkey"))?; }; // Add a check in case of no counterpart found @@ -196,7 +203,7 @@ pub async fn update_user_reputation_action( if buyer_rating || seller_rating { // Update db with rate flags update_user_rating_event( - &counterpart, + &counterpart_trade_pubkey, update_buyer_rate, update_seller_rate, reputation_event,