Skip to content

Commit b7821ec

Browse files
committed
add complete coverage on order recovery
Signed-off-by: Onur Özkan <[email protected]>
1 parent 3b65b09 commit b7821ec

File tree

5 files changed

+100
-32
lines changed

5 files changed

+100
-32
lines changed

mm2src/mm2_main/src/lp_ordermatch.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,11 @@ async fn process_maker_order_cancelled(
384384
// is received within the `RECENTLY_CANCELLED_TIMEOUT` timeframe.
385385
// We do this even if the order is in the order_set, because it could have been added through
386386
// means other than the order creation message.
387-
orderbook
388-
.recently_cancelled
389-
.insert_expirable(uuid, from_pubkey.clone(), RECENTLY_CANCELLED_TIMEOUT);
387+
orderbook.recently_cancelled.insert_expirable(
388+
uuid,
389+
(from_pubkey.clone(), cancelled_msg.timestamp),
390+
RECENTLY_CANCELLED_TIMEOUT,
391+
);
390392
if let Some(order) = orderbook.order_set.get(&uuid) {
391393
if order.pubkey == from_pubkey {
392394
orderbook.remove_order_trie_update(uuid);
@@ -1108,7 +1110,7 @@ async fn maker_order_updated_p2p_notify(
11081110
pub(crate) async fn maker_order_cancelled_p2p_notify(ctx: &MmArc, order: &MakerOrder) {
11091111
let message = new_protocol::OrdermatchMessage::MakerOrderCancelled(new_protocol::MakerOrderCancelled {
11101112
uuid: order.uuid.into(),
1111-
timestamp: now_sec(),
1113+
timestamp: order.created_at,
11121114
pair_trie_root: H64::default(),
11131115
});
11141116
delete_my_order(ctx, order.uuid, order.p2p_privkey).await;
@@ -2576,7 +2578,7 @@ pub(crate) struct Orderbook {
25762578
/// used to avoid order recreation in case of out-of-order p2p messages,
25772579
/// e.g., when receiving the order cancellation message before the order is created.
25782580
/// Entries are kept for `RECENTLY_CANCELLED_TIMEOUT` seconds.
2579-
recently_cancelled: TimedMap<Uuid, String>,
2581+
recently_cancelled: TimedMap<Uuid, (String, u64)>,
25802582
topics_subscribed_to: HashMap<String, OrderbookRequestingState>,
25812583
/// MemoryDB instance to store Patricia Tries data
25822584
memory_db: MemoryDB<Blake2Hasher64>,
@@ -2627,7 +2629,7 @@ impl Orderbook {
26272629

26282630
fn insert_or_update_order_update_trie(&mut self, order: OrderbookItem) {
26292631
// Ignore the order if it was recently cancelled
2630-
if self.recently_cancelled.get(&order.uuid) == Some(&order.pubkey) {
2632+
if self.recently_cancelled.get(&order.uuid) == Some(&(order.pubkey.clone(), order.created_at)) {
26312633
warn!("Maker order {} was recently cancelled, ignoring", order.uuid);
26322634
return;
26332635
}
@@ -3049,11 +3051,13 @@ impl MakerOrdersContext {
30493051
.remove(&uuid)
30503052
.ok_or_else(|| ERRL!("Cannot find {} order to recover.", uuid))?;
30513053

3052-
self.orders.insert(uuid, order.clone());
3053-
30543054
{
30553055
let mut order = order.lock().await;
3056-
order.updated_at = Some(now_ms());
3056+
3057+
order.matches.clear();
3058+
order.started_swaps.clear();
3059+
order.created_at = now_ms();
3060+
order.updated_at = None;
30573061

30583062
let ctx_arc = MmArc::from_weak(&ctx).ok_or_else(|| ERRL!("This is very unusual."))?;
30593063

@@ -3075,6 +3079,8 @@ impl MakerOrdersContext {
30753079
self.spawn_balance_loop_if_not_spawned(ctx, order.base.clone(), None);
30763080
}
30773081

3082+
self.orders.insert(uuid, order.clone());
3083+
30783084
Ok(order)
30793085
}
30803086

mm2src/mm2_main/src/lp_swap.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,8 @@ async fn recv_swap_msg<T>(
432432

433433
/// Includes the grace time we add to the "normal" timeouts
434434
/// in order to give different and/or heavy communication channels a chance.
435+
///
436+
/// TODO: It doesn't seem right to need such thing.
435437
const BASIC_COMM_TIMEOUT: u64 = 90;
436438

437439
#[cfg(not(any(feature = "custom-swap-locktime", test, feature = "run-docker-tests")))]

mm2src/mm2_main/src/lp_swap/maker_swap.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use crate::lp_network::subscribe_to_topic;
1515
use crate::lp_ordermatch::MakerOrderBuilder;
1616
use crate::lp_swap::swap_events::{SwapStatusEvent, SwapStatusStreamer};
1717
use crate::lp_swap::swap_v2_common::mark_swap_as_finished;
18-
use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF};
18+
use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF,
19+
NEGOTIATION_TIMEOUT_SEC};
1920
use coins::lp_price::fetch_swap_coins_price;
2021
use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend,
2122
MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr,
@@ -594,8 +595,6 @@ impl MakerSwap {
594595

595596
let maker_negotiation_msg = SwapMsg::Negotiation(negotiation_data);
596597

597-
const NEGOTIATION_TIMEOUT_SEC: u64 = 90;
598-
599598
debug!("Sending maker negotiation data: {:?}", maker_negotiation_msg);
600599
let send_abort_handle = broadcast_swap_msg_every(
601600
self.ctx.clone(),

mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,13 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
11601160
type StateMachine = MakerSwapStateMachine<MakerCoin, TakerCoin>;
11611161

11621162
async fn on_changed(self: Box<Self>, state_machine: &mut Self::StateMachine) -> StateResult<Self::StateMachine> {
1163+
#[cfg(feature = "run-docker-tests")]
1164+
if std::env::var("ABORT_SWAP_FOR_TEST").is_ok() {
1165+
debug!("Aborting it intentionally.");
1166+
let reason = AbortReason::FailedToParseTakerFunding("Intentional swap abort.".to_string());
1167+
return Self::change_state(Aborted::new(reason), state_machine).await;
1168+
}
1169+
11631170
let maker_negotiated_msg = MakerNegotiated {
11641171
negotiated: true,
11651172
reason: None,
@@ -2095,6 +2102,8 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
20952102
covered_error!("{error}");
20962103
}
20972104

2105+
info!("{order_uuid} order is recovered");
2106+
20982107
warn!("Swap {} was aborted with reason {}", state_machine.uuid, self.reason);
20992108
}
21002109
}

mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2872,7 +2872,6 @@ fn test_v2_eth_eth_kickstart() {
28722872
}
28732873

28742874
#[test]
2875-
#[ignore]
28762875
fn test_maker_order_recovery_on_tpu() {
28772876
// Initialize swap addresses and configurations
28782877
let swap_addresses = SwapAddresses::init();
@@ -2906,52 +2905,105 @@ fn test_maker_order_recovery_on_tpu() {
29062905
}
29072906
};
29082907

2909-
// start Bob and Alice
2908+
// start Bob, Alice and Onur
29102909
let (_, bob_priv_key) =
29112910
eth_coin_v2_activation_with_random_privkey(&MM_CTX, ETH, &eth_dev_conf(), swap_addresses, false);
29122911
let (_, alice_priv_key) =
29132912
eth_coin_v2_activation_with_random_privkey(&MM_CTX1, ETH1, &eth1_dev_conf(), swap_addresses, false);
2913+
let (_, onur_priv_key) =
2914+
eth_coin_v2_activation_with_random_privkey(&MM_CTX1, ETH1, &eth1_dev_conf(), swap_addresses, false);
2915+
29142916
let coins = json!([eth_dev_conf(), eth1_dev_conf()]);
29152917

29162918
let bob_conf = Mm2TestConf::seednode_trade_v2(&format!("0x{}", hex::encode(bob_priv_key)), &coins);
2917-
let mut mm_bob = MarketMakerIt::start(bob_conf.conf.clone(), bob_conf.rpc_password, None).unwrap();
2918-
let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path);
2919-
log!("Bob log path: {}", mm_bob.log_path.display());
2919+
let mut mm_bob = block_on(MarketMakerIt::start_with_envs(
2920+
bob_conf.conf.clone(),
2921+
bob_conf.rpc_password,
2922+
None,
2923+
&[("ABORT_SWAP_FOR_TEST", "1")],
2924+
))
2925+
.unwrap();
29202926

29212927
let alice_conf =
29222928
Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob
29232929
.ip
29242930
.to_string()]);
29252931
let mut mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), alice_conf.rpc_password, None).unwrap();
2926-
let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path);
2927-
log!("Alice log path: {}", mm_alice.log_path.display());
2932+
2933+
let onur_conf = Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(onur_priv_key)), &coins, &[&mm_bob
2934+
.ip
2935+
.to_string()]);
2936+
let mm_onur = MarketMakerIt::start(onur_conf.conf.clone(), onur_conf.rpc_password, None).unwrap();
29282937

29292938
// Enable ETH and ETH1 for both Bob and Alice
29302939
enable_coins(&mm_bob, &[ETH, ETH1]);
29312940
enable_coins(&mm_alice, &[ETH, ETH1]);
2941+
enable_coins(&mm_onur, &[ETH, ETH1]);
29322942

29332943
let uuids = block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[(ETH, ETH1)], 1.0, 1.0, 77.));
29342944
block_on(mm_bob.wait_for_log(30., |log| {
29352945
log.contains(&format!("Maker swap {} has successfully started", uuids[0]))
29362946
}))
29372947
.unwrap();
29382948

2939-
// Stop alice right after swap start
2940-
block_on(mm_alice.stop()).unwrap();
2949+
let send_my_orders_rpc = |mm: &MarketMakerIt| -> MyOrdersRpcResult {
2950+
let rc = block_on(mm.rpc(&json!({
2951+
"userpass": mm.userpass,
2952+
"method": "my_orders",
2953+
})))
2954+
.unwrap();
2955+
assert!(rc.0.is_success(), "!my_orders: {}", rc.1);
29412956

2942-
// TODO:
2943-
// Wait T amount of seconds to timeout the swap.
2944-
// Verify bob order recovery.
2957+
serde_json::from_str(&rc.1).unwrap()
2958+
};
29452959

2946-
let rc = block_on(mm_bob.rpc(&json!({
2947-
"userpass": mm_bob.userpass,
2948-
"method": "my_orders",
2949-
})))
2950-
.unwrap();
2951-
assert!(rc.0.is_success(), "!my_orders: {}", rc.1);
2960+
let send_orderbook_rpc = |mm: &MarketMakerIt| -> Json {
2961+
let rc = block_on(mm.rpc(&json!({
2962+
"userpass": mm.userpass,
2963+
"method": "orderbook",
2964+
"mmrpc": "2.0",
2965+
"params": {
2966+
"base": "ETH",
2967+
"rel": "ETH1",
2968+
},
2969+
})))
2970+
.unwrap();
2971+
assert!(rc.0.is_success(), "!orderbook: {}", rc.1);
2972+
2973+
serde_json::from_str(&rc.1).unwrap()
2974+
};
2975+
2976+
let my_orders_rpc = send_my_orders_rpc(&mm_bob);
2977+
assert!(
2978+
my_orders_rpc.result.maker_orders.is_empty(),
2979+
"Maker order must be invisible (locked)."
2980+
);
2981+
2982+
let orderbook_result = send_orderbook_rpc(&mm_onur);
2983+
let asks = orderbook_result["result"]["asks"].as_array().unwrap();
2984+
assert!(
2985+
asks.is_empty(),
2986+
"Onur ETH/ETH1 orderbook shouldn't see Bob's locked order."
2987+
);
29522988

2953-
let res: MyOrdersRpcResult = serde_json::from_str(&rc.1).unwrap();
2954-
assert!(!res.result.maker_orders.is_empty(), "Maker order must be recovered.");
2989+
block_on(mm_bob.wait_for_log(30., |log| log.contains("Aborting it intentionally."))).unwrap();
2990+
2991+
// Swap will be aborted. Stop Alice to prevent her from taking the order again.
2992+
block_on(mm_alice.stop()).unwrap();
2993+
2994+
let my_orders_rpc = send_my_orders_rpc(&mm_bob);
2995+
assert!(
2996+
!my_orders_rpc.result.maker_orders.is_empty(),
2997+
"Maker order must be visible again (recovered)."
2998+
);
2999+
3000+
let orderbook_result = send_orderbook_rpc(&mm_onur);
3001+
let asks = orderbook_result["result"]["asks"].as_array().unwrap();
3002+
assert_eq!(
3003+
asks.len(),
3004+
1,
3005+
"Onur ETH/ETH1 orderbook must see Bob's recovered order again."
3006+
);
29553007
}
29563008

29573009
fn log_swap_status_before_stop(mm: &MarketMakerIt, uuid: &str, role: &str) {

0 commit comments

Comments
 (0)