-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathdispute.rs
More file actions
241 lines (220 loc) · 7.76 KB
/
dispute.rs
File metadata and controls
241 lines (220 loc) · 7.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! This module handles dispute-related functionality for the P2P trading system.
//! It provides mechanisms for users to initiate disputes, notify counterparties,
//! and publish dispute events to the network.
use crate::db::find_dispute_by_order_id;
use crate::nip33::new_event;
use crate::util::{enqueue_order_msg, get_nostr_client, get_order};
use mostro_core::prelude::*;
use nostr::nips::nip59::UnwrappedGift;
use nostr_sdk::prelude::*;
use sqlx::{Pool, Sqlite};
use sqlx_crud::traits::Crud;
use std::borrow::Cow;
use uuid::Uuid;
/// Publishes a dispute event to the Nostr network.
///
/// Creates and publishes a NIP-33 replaceable event containing dispute details,
/// including status, initiator (`buyer` or `seller`), and application metadata.
async fn publish_dispute_event(
dispute: &Dispute,
my_keys: &Keys,
is_buyer_dispute: bool,
) -> Result<(), MostroError> {
// Create initiator string
let initiator = match is_buyer_dispute {
true => "buyer",
false => "seller",
};
// Create tags for the dispute event
let tags = Tags::from_list(vec![
// Status tag - indicates the current state of the dispute
Tag::custom(
TagKind::Custom(Cow::Borrowed("s")),
vec![dispute.status.to_string()],
),
// Who is the dispute creator
Tag::custom(
TagKind::Custom(Cow::Borrowed("initiator")),
vec![initiator.to_string()],
),
// Application identifier tag
Tag::custom(
TagKind::Custom(Cow::Borrowed("y")),
vec!["mostro".to_string()],
),
// Event type tag
Tag::custom(
TagKind::Custom(Cow::Borrowed("z")),
vec!["dispute".to_string()],
),
]);
// Create a new NIP-33 replaceable event
// Empty content string as the information is in the tags
let event = new_event(my_keys, "", dispute.id.to_string(), tags)
.map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;
tracing::info!("Publishing dispute event: {:#?}", event);
// Get nostr client and publish the event
match get_nostr_client() {
Ok(client) => match client.send_event(&event).await {
Ok(_) => {
tracing::info!(
"Successfully published dispute event for dispute ID: {}",
dispute.id
);
Ok(())
}
Err(e) => {
tracing::error!("Failed to send dispute event: {}", e);
Err(MostroInternalErr(ServiceError::NostrError(e.to_string())))
}
},
Err(e) => {
tracing::error!("Failed to get Nostr client: {}", e);
Err(MostroInternalErr(ServiceError::NostrError(e.to_string())))
}
}
}
/// Gets information about the counterparty in a dispute.
///
/// Returns:
/// - Ok(true) if the dispute was initiated by the buyer
/// - Ok(false) if initiated by the seller
/// - Err(CantDoReason::InvalidPubkey) if the sender matches neither party
fn get_counterpart_info(sender: &str, buyer: &str, seller: &str) -> Result<bool, CantDoReason> {
match sender {
s if s == buyer => Ok(true), // buyer is initiator
s if s == seller => Ok(false), // seller is initiator
_ => Err(CantDoReason::InvalidPubkey),
}
}
/// Validates and retrieves an order from the database.
///
/// Checks that:
/// - The order exists
/// - The order status allows disputes (Active or FiatSent)
async fn get_valid_order(pool: &Pool<Sqlite>, msg: &Message) -> Result<Order, MostroError> {
// Try to fetch the order from the database
let order = get_order(msg, pool).await?;
// Check if the order status is Active or FiatSent
if order.check_status(Status::Active).is_err() && order.check_status(Status::FiatSent).is_err()
{
return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
}
Ok(order)
}
async fn notify_dispute_to_users(
dispute: &Dispute,
msg: &Message,
order_id: Uuid,
counterpart_pubkey: PublicKey,
initiator_pubkey: PublicKey,
) -> Result<(), MostroError> {
// Message to counterpart
enqueue_order_msg(
msg.get_inner_message_kind().request_id,
Some(order_id),
Action::DisputeInitiatedByPeer,
Some(Payload::Dispute(dispute.clone().id, None)),
counterpart_pubkey,
None,
)
.await;
// Message to dispute initiator
enqueue_order_msg(
msg.get_inner_message_kind().request_id,
Some(order_id),
Action::DisputeInitiatedByYou,
Some(Payload::Dispute(dispute.clone().id, None)),
initiator_pubkey,
None,
)
.await;
Ok(())
}
/// Main handler for dispute actions.
///
/// This function:
/// 1. Validates the order and dispute status
/// 2. Updates the order status
/// 3. Creates a new dispute record
/// 4. Notifies both parties
/// 5. Publishes the dispute event to the network
pub async fn dispute_action(
msg: Message,
event: &UnwrappedGift,
my_keys: &Keys,
pool: &Pool<Sqlite>,
) -> Result<(), MostroError> {
let order_id = if let Some(order_id) = msg.get_inner_message_kind().id {
order_id
} else {
return Err(MostroInternalErr(ServiceError::InvalidOrderId));
};
// Check dispute for this order id is yet present.
if find_dispute_by_order_id(pool, order_id).await.is_ok() {
return Err(MostroInternalErr(ServiceError::DisputeAlreadyExists));
}
// Get and validate order
let mut order = get_valid_order(pool, &msg).await?;
// Get seller and buyer pubkeys
let (seller, buyer) = match (&order.seller_pubkey, &order.buyer_pubkey) {
(Some(seller), Some(buyer)) => (seller.to_owned(), buyer.to_owned()),
(None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
(_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
};
// Get message sender
let message_sender = event.rumor.pubkey.to_string();
// Get counterpart info
let is_buyer_dispute = match get_counterpart_info(&message_sender, &buyer, &seller) {
Ok(is_buyer_dispute) => is_buyer_dispute,
Err(cause) => return Err(MostroCantDo(cause)),
};
// Create new dispute record
let dispute = Dispute::new(order_id, order.status.clone());
// Setup dispute
if order.setup_dispute(is_buyer_dispute).is_ok() {
order
.clone()
.update(pool)
.await
.map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?;
}
// Save dispute to database
let dispute = dispute
.create(pool)
.await
.map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?;
// Get pubkeys of initiator and counterpart
let (initiator_pubkey, counterpart_pubkey) = if is_buyer_dispute {
(
&order
.get_buyer_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
&order
.get_seller_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
)
} else {
(
&order
.get_seller_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
&order
.get_buyer_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
)
};
notify_dispute_to_users(
&dispute,
&msg,
order_id,
*counterpart_pubkey,
*initiator_pubkey,
)
.await?;
// Publish dispute event to network
publish_dispute_event(&dispute, my_keys, is_buyer_dispute)
.await
.map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;
Ok(())
}