Skip to content

Commit 296877c

Browse files
authored
Fix market visibility subscription bugs (#365)
1 parent df3bc88 commit 296877c

6 files changed

Lines changed: 987 additions & 27 deletions

backend/.sqlx/query-04bf4b5be629f05d2b61c0bdb92b3b93fac1e86de0267d20a11483cbca7bfc3a.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/.sqlx/query-bbb71f664c418f08aa1b6253faeecc4cb5505e532603de6ee420b01487200372.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/src/db.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,44 @@ impl DB {
11961196
Ok(is_visible)
11971197
}
11981198

1199+
/// Check if a market is visible to any of the given accounts.
1200+
/// Returns true if the market has no visibility restrictions,
1201+
/// or if any of the owned accounts is in the `visible_to` list.
1202+
#[instrument(err, skip(self))]
1203+
pub async fn is_market_visible_to_any(
1204+
&self,
1205+
market_id: i64,
1206+
owned_accounts: &[i64],
1207+
) -> SqlxResult<bool> {
1208+
// First check if the market has any restrictions at all
1209+
let has_restrictions = sqlx::query_scalar!(
1210+
r#"SELECT EXISTS (SELECT 1 FROM market_visible_to WHERE market_id = ?) as "exists!: bool""#,
1211+
market_id
1212+
)
1213+
.fetch_one(&self.pool)
1214+
.await?;
1215+
1216+
if !has_restrictions {
1217+
return Ok(true);
1218+
}
1219+
1220+
// Check if any owned account is in the visible_to list
1221+
for &account_id in owned_accounts {
1222+
let is_listed = sqlx::query_scalar!(
1223+
r#"SELECT EXISTS (SELECT 1 FROM market_visible_to WHERE market_id = ? AND account_id = ?) as "exists!: bool""#,
1224+
market_id,
1225+
account_id
1226+
)
1227+
.fetch_one(&self.pool)
1228+
.await?;
1229+
if is_listed {
1230+
return Ok(true);
1231+
}
1232+
}
1233+
1234+
Ok(false)
1235+
}
1236+
11991237
#[must_use]
12001238
pub fn get_all_live_orders(&self) -> BoxStream<'_, SqlxResult<Order>> {
12011239
sqlx::query_as!(

backend/src/handle_socket.rs

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ async fn handle_socket_fallible(mut socket: WebSocket, app_state: AppState) -> a
121121
match msg {
122122
Ok(mut msg) => {
123123
if !is_admin || !sudo_enabled {
124+
// Filter out messages about markets the user can't see
125+
if should_filter_for_visibility(db, &owned_accounts, &msg).await? {
126+
continue;
127+
}
124128
conditionally_hide_user_ids(db, &owned_accounts, &mut msg).await?;
125129
}
126130
socket.send(msg.encode_to_vec().into()).await?;
@@ -502,6 +506,32 @@ async fn conditionally_hide_user_ids(
502506
Ok(())
503507
}
504508

509+
/// Extract `market_id` from a broadcast message, if it's a market-related message.
510+
fn broadcast_market_id(msg: &ServerMessage) -> Option<i64> {
511+
match &msg.message {
512+
Some(SM::Market(m)) => Some(m.id),
513+
Some(SM::MarketSettled(ms)) => Some(ms.id),
514+
Some(SM::OrderCreated(oc)) => Some(oc.market_id),
515+
Some(SM::OrdersCancelled(oc)) => Some(oc.market_id),
516+
Some(SM::Redeemed(r)) => Some(r.fund_id),
517+
_ => None,
518+
}
519+
}
520+
521+
/// Check if a broadcast message should be filtered out due to market visibility restrictions.
522+
/// Returns true if the message should be SKIPPED (not sent to this client).
523+
async fn should_filter_for_visibility(
524+
db: &DB,
525+
owned_accounts: &[i64],
526+
msg: &ServerMessage,
527+
) -> anyhow::Result<bool> {
528+
let Some(market_id) = broadcast_market_id(msg) else {
529+
return Ok(false);
530+
};
531+
let is_visible = db.is_market_visible_to_any(market_id, owned_accounts).await?;
532+
Ok(!is_visible)
533+
}
534+
505535
struct ActAsInfo {
506536
request_id: String,
507537
account_id: i64,
@@ -624,15 +654,8 @@ async fn handle_client_message(
624654
.await?
625655
{
626656
Ok(market) => {
627-
let visible_to = market.visible_to.clone();
628657
let msg = server_message(request_id, SM::Market(market.into()));
629-
if visible_to.is_empty() {
630-
subscriptions.send_public(msg);
631-
} else {
632-
for account_id in visible_to {
633-
subscriptions.send_private(account_id, msg.encode_to_vec().into());
634-
}
635-
}
658+
subscriptions.send_public(msg);
636659
}
637660
Err(failure) => {
638661
fail!("CreateMarket", failure.message());
@@ -645,16 +668,10 @@ async fn handle_client_message(
645668
Ok(db::MarketSettledWithAffectedAccounts {
646669
market_settled,
647670
affected_accounts,
648-
visible_to,
671+
..
649672
}) => {
650673
let msg = server_message(request_id, SM::MarketSettled(market_settled.into()));
651-
if visible_to.is_empty() {
652-
subscriptions.send_public(msg);
653-
} else {
654-
for account_id in visible_to {
655-
subscriptions.send_private(account_id, msg.encode_to_vec().into());
656-
}
657-
}
674+
subscriptions.send_public(msg);
658675
for account in affected_accounts {
659676
subscriptions.notify_portfolio(account);
660677
}
@@ -882,15 +899,8 @@ async fn handle_client_message(
882899

883900
match db.edit_market(edit_market).await? {
884901
Ok(market) => {
885-
let visible_to = market.visible_to.clone();
886902
let msg = server_message(request_id, SM::Market(market.into()));
887-
if visible_to.is_empty() {
888-
subscriptions.send_public(msg);
889-
} else {
890-
for account_id in visible_to {
891-
subscriptions.send_private(account_id, msg.encode_to_vec().into());
892-
}
893-
}
903+
subscriptions.send_public(msg);
894904
}
895905
Err(err) => {
896906
fail!("EditMarket", err.message());

backend/src/test_utils.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use crate::{
2020
subscriptions::Subscriptions,
2121
websocket_api::{
2222
client_message::Message as CM, server_message::Message as SM, ActAs, Authenticate,
23-
ClientMessage, CreateMarket, CreateOrder, GetFullTradeHistory,
24-
RevokeOwnership, ServerMessage, SetSudo, Side,
23+
ClientMessage, CreateMarket, CreateOrder, EditMarket, GetFullTradeHistory,
24+
RevokeOwnership, ServerMessage, SettleMarket, SetSudo, Side,
2525
},
2626
AppState,
2727
};
@@ -259,6 +259,21 @@ impl TestClient {
259259
min_settlement: f64,
260260
max_settlement: f64,
261261
hide_account_ids: bool,
262+
) -> anyhow::Result<ServerMessage> {
263+
self.create_market_with_visible_to(name, min_settlement, max_settlement, hide_account_ids, vec![]).await
264+
}
265+
266+
/// Send a `CreateMarket` message with visibility restrictions.
267+
///
268+
/// # Errors
269+
/// Returns an error if sending fails.
270+
pub async fn create_market_with_visible_to(
271+
&mut self,
272+
name: &str,
273+
min_settlement: f64,
274+
max_settlement: f64,
275+
hide_account_ids: bool,
276+
visible_to: Vec<i64>,
262277
) -> anyhow::Result<ServerMessage> {
263278
let request_id = self.next_request_id();
264279
let msg = ClientMessage {
@@ -271,7 +286,7 @@ impl TestClient {
271286
redeemable_for: vec![],
272287
redeem_fee: 0.0,
273288
hide_account_ids,
274-
visible_to: vec![],
289+
visible_to,
275290
type_id: 0,
276291
group_id: 0,
277292
})),
@@ -280,6 +295,44 @@ impl TestClient {
280295
self.recv_message().await
281296
}
282297

298+
/// Send a `SettleMarket` message.
299+
///
300+
/// # Errors
301+
/// Returns an error if sending fails.
302+
pub async fn settle_market(
303+
&mut self,
304+
market_id: i64,
305+
settle_price: f64,
306+
) -> anyhow::Result<ServerMessage> {
307+
let request_id = self.next_request_id();
308+
let msg = ClientMessage {
309+
request_id,
310+
message: Some(CM::SettleMarket(SettleMarket {
311+
market_id,
312+
settle_price,
313+
})),
314+
};
315+
self.send_message(msg).await?;
316+
self.recv_message().await
317+
}
318+
319+
/// Send an `EditMarket` message.
320+
///
321+
/// # Errors
322+
/// Returns an error if sending fails.
323+
pub async fn edit_market(
324+
&mut self,
325+
edit: EditMarket,
326+
) -> anyhow::Result<ServerMessage> {
327+
let request_id = self.next_request_id();
328+
let msg = ClientMessage {
329+
request_id,
330+
message: Some(CM::EditMarket(edit)),
331+
};
332+
self.send_message(msg).await?;
333+
self.recv_message().await
334+
}
335+
283336
/// Send a `CreateOrder` message.
284337
///
285338
/// # Errors

0 commit comments

Comments
 (0)