Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ testcontainers = "0.15.0"
tiny-bip39 = "0.8.0"
thiserror = "1.0.40"
time = "0.3.36"
timed-map = { version = "1.4", features = ["rustc-hash", "serde", "wasm"] }
timed-map = { version = "1.5", features = ["rustc-hash", "serde", "wasm"] }
tokio = { version = "1.20", default-features = false }
tokio-rustls = { version = "0.24", default-features = false }
tokio-tungstenite-wasm = { git = "https://github.com/KomodoPlatform/tokio-tungstenite-wasm", rev = "8fc7e2f", defautl-features = false, features = ["rustls-tls-native-roots"]}
Expand Down
55 changes: 49 additions & 6 deletions mm2src/mm2_main/src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,7 @@ pub struct MakerOrder {
pub swap_version: SwapVersion,
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata,
timeout_in_minutes: Option<u16>,
}

pub struct MakerOrderBuilder<'a> {
Expand All @@ -1768,6 +1769,7 @@ pub struct MakerOrderBuilder<'a> {
swap_version: u8,
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata,
timeout_in_minutes: Option<u16>,
}

/// Contains extra and/or optional metadata (e.g., protocol-specific information) that can
Expand Down Expand Up @@ -1930,6 +1932,7 @@ impl<'a> MakerOrderBuilder<'a> {
swap_version: SWAP_VERSION_DEFAULT,
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
}
}

Expand Down Expand Up @@ -1968,6 +1971,8 @@ impl<'a> MakerOrderBuilder<'a> {
self
}

pub fn set_timeout(&mut self, timeout_in_minutes: u16) { self.timeout_in_minutes = Some(timeout_in_minutes); }

/// When a new [MakerOrderBuilder::new] is created, it sets [SWAP_VERSION_DEFAULT].
/// However, if user has not specified in the config to use TPU V2,
/// the MakerOrderBuilder's swap_version is changed to legacy.
Expand Down Expand Up @@ -2033,6 +2038,7 @@ impl<'a> MakerOrderBuilder<'a> {
swap_version: SwapVersion::from(self.swap_version),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: self.order_metadata,
timeout_in_minutes: self.timeout_in_minutes,
})
}

Expand Down Expand Up @@ -2060,6 +2066,7 @@ impl<'a> MakerOrderBuilder<'a> {
swap_version: SwapVersion::from(self.swap_version),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: self.order_metadata,
timeout_in_minutes: self.timeout_in_minutes,
}
}
}
Expand Down Expand Up @@ -2194,6 +2201,7 @@ impl From<TakerOrder> for MakerOrder {
// TODO: Add test coverage for this once we have an integration test for this feature.
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: taker_order.request.order_metadata,
timeout_in_minutes: None,
},
// The "buy" taker order is recreated with reversed pair as Maker order is always considered as "sell"
TakerAction::Buy => {
Expand All @@ -2220,6 +2228,7 @@ impl From<TakerOrder> for MakerOrder {
// TODO: Add test coverage for this once we have an integration test for this feature.
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: taker_order.request.order_metadata,
timeout_in_minutes: None,
Comment thread
onur-ozkan marked this conversation as resolved.
}
},
}
Expand Down Expand Up @@ -2962,7 +2971,7 @@ impl OrdermatchContext {
}

pub struct MakerOrdersContext {
orders: HashMap<Uuid, Arc<AsyncMutex<MakerOrder>>>,
orders: TimedMap<Uuid, Arc<AsyncMutex<MakerOrder>>>,
order_tickers: HashMap<Uuid, String>,
count_by_tickers: HashMap<String, usize>,
/// The `check_balance_update_loop` future abort handles associated stored by corresponding tickers.
Expand All @@ -2976,7 +2985,7 @@ impl MakerOrdersContext {
let balance_loops = ctx.abortable_system.create_subsystem()?;

Ok(MakerOrdersContext {
orders: HashMap::new(),
orders: TimedMap::new_with_map_kind(timed_map::MapKind::FxHashMap),
order_tickers: HashMap::new(),
count_by_tickers: HashMap::new(),
balance_loops,
Expand All @@ -2988,7 +2997,21 @@ impl MakerOrdersContext {

self.order_tickers.insert(order.uuid, order.base.clone());
*self.count_by_tickers.entry(order.base.clone()).or_insert(0) += 1;
self.orders.insert(order.uuid, Arc::new(AsyncMutex::new(order)));

if let Some(t) = order.timeout_in_minutes {
// Use unchecked write to skip automatic cleanup as we need to handle
// expired orders manually.
self.orders.insert_expirable_unchecked(
order.uuid,
Arc::new(AsyncMutex::new(order)),
Duration::from_secs(t as u64 * 60),
);
} else {
// Use unchecked write to skip automatic cleanup as we need to handle
// expired orders manually.
self.orders
.insert_constant_unchecked(order.uuid, Arc::new(AsyncMutex::new(order)));
}
}

fn get_order(&self, uuid: &Uuid) -> Option<&Arc<AsyncMutex<MakerOrder>>> { self.orders.get(uuid) }
Expand Down Expand Up @@ -3562,6 +3585,19 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) {
handle_timed_out_maker_matches(ctx.clone(), &ordermatch_ctx).await;
check_balance_for_maker_orders(ctx.clone(), &ordermatch_ctx).await;

let expired_orders = ordermatch_ctx.maker_orders_ctx.lock().orders.drop_expired_entries();

for (uuid, order_mutex) in expired_orders {
log::info!("Order '{uuid}' is expired, cancelling");

let order = order_mutex.lock().await;
maker_order_cancelled_p2p_notify(&ctx, &order);
delete_my_maker_order(ctx.clone(), order.clone(), MakerOrderCancellationReason::Expired)
.compat()
.await
.ok();
}

{
// remove "timed out" pubkeys states with their orders from orderbook
let mut orderbook = ordermatch_ctx.orderbook.lock();
Expand Down Expand Up @@ -3592,9 +3628,9 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) {
let mut to_cancel = Vec::new();
{
let orderbook = ordermatch_ctx.orderbook.lock();
for (uuid, _) in ordermatch_ctx.maker_orders_ctx.lock().orders.iter() {
if !orderbook.order_set.contains_key(uuid) {
missing_uuids.push(*uuid);
for uuid in ordermatch_ctx.maker_orders_ctx.lock().orders.keys() {
if !orderbook.order_set.contains_key(&uuid) {
missing_uuids.push(uuid);
}
}
}
Expand Down Expand Up @@ -4795,6 +4831,7 @@ pub struct SetPriceReq {
rel_nota: Option<bool>,
#[serde(default = "get_true")]
save_in_history: bool,
timeout_in_minutes: Option<u16>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why minutes not seconds?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using seconds would be unnecessary and can be bad for the network stability (imagine people sending orders with ~5-10 seconds of expiration time).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will wait for @cipig input since he requested this

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imagine people sending orders with ~5-10 seconds of expiration time

On the other hand, if or when we can support this, it won't be bad as it will mirror the CEX experience a lot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And they can cancel it in whatever seconds they like manually or using scripts

Copy link
Copy Markdown
Author

@onur-ozkan onur-ozkan Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't be harmful on CEX platforms because spamming very short-lived orders doesn't affect other nodes. But on a P2P network I think orders should live for at least 30 seconds or so. For example orders that last only 5 seconds just put pressure on the network, especially since it can take up to 5 seconds for peers to fully sync. Instead doing manual validations on the expiration time, I decided to make it minute and I tought it's quite reasonable to say "orders must have at least 1 minute expiration time" to users.

Also, when taking so many users into account, with seconds, this feature alone can put quite a lot of pressure on the network, with minutes, it should be way less compare to seconds.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand and agree. I was just arguing the opposite case, I plan to leave it at minutes unless @cipig objects and wants something like 30 seconds.

Copy link
Copy Markdown
Collaborator

@shamardy shamardy Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cancelling in seconds doesn't have to have an immediate p2p message propagated but if it's matched before it's cancelled on remote nodes, the maker node will not proceed with the order when it's not available locally. And takers choose between the best response from multiple makers anyway so it won't cause problems. But that's for another PR or issue if needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will wait for @cipig input since he requested this

Minutes is perfectly fine. I added it in my bot with a 20 minute timeout (cipig/mmtools@5ee5dbe). It needs anyway 5-15 minutes to update all orders, depending on number of coins. If the bot dies for whatever reason, orders are cancelled like this:

22 14:32:03, mm2_main::lp_ordermatch:3591] INFO Order 'afb833e8-7d6a-42da-9151-4f8f096efb57' is expired, cancelling
22 14:32:05, mm2_main::lp_ordermatch:3591] INFO Order 'f1cc74c7-b0f0-40d7-a4a6-036ea66a39e2' is expired, cancelling
22 14:32:07, mm2_main::lp_ordermatch:3591] INFO Order 'fc996c71-0d19-4e7c-bc35-636afdd88d22' is expired, cancelling
22 14:32:09, mm2_main::lp_ordermatch:3591] INFO Order '28088d79-694b-4b84-965b-1c97b0827729' is expired, cancelling

That's exactly what i wanted, as a safety measure for my own bad programming :-)
Thanks a lot.

}

#[derive(Deserialize)]
Expand Down Expand Up @@ -5059,6 +5096,11 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result<MakerOr
.with_save_in_history(req.save_in_history)
.with_base_orderbook_ticker(ordermatch_ctx.orderbook_ticker(base_coin.ticker()))
.with_rel_orderbook_ticker(ordermatch_ctx.orderbook_ticker(rel_coin.ticker()));

if let Some(t) = req.timeout_in_minutes {
builder.set_timeout(t);
}

if !ctx.use_trading_proto_v2() {
builder.set_legacy_swap_v();
}
Expand Down Expand Up @@ -5349,6 +5391,7 @@ pub enum MakerOrderCancellationReason {
Fulfilled,
InsufficientBalance,
Cancelled,
Expired,
}

#[derive(Display)]
Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,7 @@ mod tests {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: crate::lp_ordermatch::OrderMetadata::default(),
timeout_in_minutes: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ async fn create_single_order(
rel_confs: cfg.rel_confs,
rel_nota: cfg.rel_nota,
save_in_history: true,
timeout_in_minutes: None,
};

let resp = create_maker_order(&ctx, req)
Expand Down
16 changes: 16 additions & 0 deletions mm2src/mm2_main/src/ordermatch_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let request = TakerRequest {
Expand Down Expand Up @@ -86,6 +87,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let request = TakerRequest {
Expand Down Expand Up @@ -130,6 +132,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let request = TakerRequest {
Expand Down Expand Up @@ -174,6 +177,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let request = TakerRequest {
Expand Down Expand Up @@ -218,6 +222,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let request = TakerRequest {
Expand Down Expand Up @@ -262,6 +267,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let request = TakerRequest {
Expand Down Expand Up @@ -308,6 +314,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};
let request = TakerRequest {
base: "KMD".to_owned(),
Expand Down Expand Up @@ -354,6 +361,7 @@ fn test_match_maker_order_and_taker_request() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};
let request = TakerRequest {
base: "REL".to_owned(),
Expand Down Expand Up @@ -431,6 +439,7 @@ fn test_maker_order_available_amount() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};
maker.matches.insert(new_uuid(), MakerMatch {
request: TakerRequest {
Expand Down Expand Up @@ -1103,6 +1112,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver<AdexBehaviourCmd> {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
},
None,
);
Expand All @@ -1128,6 +1138,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver<AdexBehaviourCmd> {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
},
None,
);
Expand All @@ -1153,6 +1164,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver<AdexBehaviourCmd> {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
},
None,
);
Expand Down Expand Up @@ -1353,6 +1365,7 @@ fn test_maker_order_was_updated() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};
let mut update_msg = MakerOrderUpdated::new(maker_order.uuid);
update_msg.with_new_price(BigRational::from_integer(2.into()));
Expand Down Expand Up @@ -3365,6 +3378,7 @@ fn test_maker_order_balance_loops() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

let morty_order = MakerOrder {
Expand All @@ -3387,6 +3401,7 @@ fn test_maker_order_balance_loops() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

assert!(!maker_orders_ctx.balance_loop_exists(rick_ticker));
Expand Down Expand Up @@ -3422,6 +3437,7 @@ fn test_maker_order_balance_loops() {
swap_version: SwapVersion::default(),
#[cfg(feature = "ibc-routing-for-swaps")]
order_metadata: OrderMetadata::default(),
timeout_in_minutes: None,
};

maker_orders_ctx.add_order(ctx.weak(), rick_order_2.clone(), None);
Expand Down
2 changes: 1 addition & 1 deletion mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2229,7 +2229,7 @@ fn test_get_max_maker_vol() {
let actual = block_on(max_maker_vol(&mm, "MYCOIN1")).unwrap::<MaxMakerVolResponse>();
assert_eq!(actual, expected);

let res = block_on(set_price(&mm, "MYCOIN1", "MYCOIN", "1", "0", true));
let res = block_on(set_price(&mm, "MYCOIN1", "MYCOIN", "1", "0", true, None));
assert_eq!(res.result.max_base_vol, expected_volume.to_decimal());
}

Expand Down
4 changes: 2 additions & 2 deletions mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ fn test_iris_ibc_nucleus_orderbook() {
let expected_address = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr";
assert_eq!(response.result.address, expected_address);

let set_price_res = block_on(set_price(&mm, token, platform_coin, "1", "0.1", false));
let set_price_res = block_on(set_price(&mm, token, platform_coin, "1", "0.1", false, None));
log!("{:?}", set_price_res);

let set_price_res = block_on(set_price(&mm, platform_coin, token, "1", "0.1", false));
let set_price_res = block_on(set_price(&mm, platform_coin, token, "1", "0.1", false, None));
log!("{:?}", set_price_res);

let orderbook = block_on(orderbook(&mm, token, platform_coin));
Expand Down
Loading
Loading