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
24 changes: 16 additions & 8 deletions mm2src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1812,7 +1812,7 @@ fn broadcast_ordermatch_message(
}

/// The order is ordered by [`OrderbookItem::price`] and [`OrderbookItem::uuid`].
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct OrderedByPriceOrder {
price: MmNumber,
uuid: Uuid,
Expand Down Expand Up @@ -2063,13 +2063,21 @@ impl Orderbook {

let base_rel = (order.base.clone(), order.rel.clone());

self.ordered
.entry(base_rel.clone())
.or_insert_with(BTreeSet::new)
.insert(OrderedByPriceOrder {
price: order.price.clone().into(),
uuid: order.uuid,
});
let ordered = self.ordered.entry(base_rel.clone()).or_insert_with(BTreeSet::new);

// have to clone to drop immutable ordered borrow
let existing = ordered
.iter()
.find(|maybe_existing| maybe_existing.uuid == order.uuid)
.cloned();

if let Some(exists) = existing {
ordered.remove(&exists);
}
ordered.insert(OrderedByPriceOrder {
uuid: order.uuid,
price: order.price.clone().into(),
});

self.pairs_existing_for_base
.entry(order.base.clone())
Expand Down
152 changes: 152 additions & 0 deletions mm2src/mm2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6420,6 +6420,158 @@ fn test_best_orders() {
block_on(mm_alice.stop()).unwrap();
}

#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_best_orders_duplicates_after_update() {
let eve_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap();

let coins = json!([
{"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}},
{"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}},
{"coin":"ETH","name":"ethereum","protocol":{"type":"ETH"},"rpcport":80},
{"coin":"JST","name":"jst","protocol":{"type":"ERC20", "protocol_data":{"platform":"ETH","contract_address":"0x2b294F029Fde858b2c62184e8390591755521d8E"}}}
]);

// start bob as a seednode
let mut mm_bob = MarketMakerIt::start(
json! ({
"gui": "nogui",
"netid": 9998,
"myipaddr": env::var ("BOB_TRADE_IP") .ok(),
"rpcip": env::var ("BOB_TRADE_IP") .ok(),
"canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::<i64>().unwrap()),
"passphrase": "bob",
"coins": coins,
"rpc_password": "pass",
"i_am_seed": true,
}),
"pass".into(),
local_start!("bob"),
)
.unwrap();

// start eve and immediately place the order
let mm_eve = MarketMakerIt::start(
json! ({
"gui": "nogui",
"netid": 9998,
"myipaddr": env::var ("BOB_TRADE_IP") .ok(),
"rpcip": env::var ("BOB_TRADE_IP") .ok(),
"canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::<i64>().unwrap()),
"passphrase": eve_passphrase,
"coins": coins,
"rpc_password": "pass",
"seednodes": [fomat!((mm_bob.ip))],
}),
"pass".into(),
local_start!("bob"),
)
.unwrap();
let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump();
log!({"Bob log path: {}", mm_bob.log_path.display()});

// Enable coins on Eve side. Print the replies in case we need the "address".
let eve_coins = block_on(enable_coins_eth_electrum(&mm_eve, &["http://195.201.0.6:8565"]));
log!({ "enable_coins (eve): {:?}", eve_coins });
// issue sell request on Eve side by setting base/rel price
log!("Issue eve sell request");

let rc = block_on(mm_eve.rpc(json! ({
"userpass": mm_eve.userpass,
"method": "setprice",
"base": "RICK",
"rel": "MORTY",
"price": "1",
"volume": "1",
})))
.unwrap();
assert!(rc.0.is_success(), "!setprice: {}", rc.1);
let eve_order: SetPriceResponse = json::from_str(&rc.1).unwrap();

let mm_alice = MarketMakerIt::start(
json! ({
"gui": "nogui",
"netid": 9998,
"myipaddr": env::var ("ALICE_TRADE_IP") .ok(),
"rpcip": env::var ("ALICE_TRADE_IP") .ok(),
"passphrase": "alice passphrase",
"coins": coins,
"seednodes": [fomat!((mm_bob.ip))],
"rpc_password": "pass",
}),
"pass".into(),
local_start!("alice"),
)
.unwrap();

let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump();
log!({ "Alice log path: {}", mm_alice.log_path.display() });

block_on(mm_bob.wait_for_log(22., |log| {
log.contains("DEBUG Handling IncludedTorelaysMesh message for peer")
}))
.unwrap();

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "best_orders",
"coin": "RICK",
"action": "buy",
"volume": "0.1",
})))
.unwrap();
assert!(rc.0.is_success(), "!best_orders: {}", rc.1);
let response: BestOrdersResponse = json::from_str(&rc.1).unwrap();
let best_morty_orders = response.result.get("MORTY").unwrap();
assert_eq!(1, best_morty_orders.len());
let expected_price: BigDecimal = "1".parse().unwrap();
assert_eq!(expected_price, best_morty_orders[0].price);

for _ in 0..5 {
let rc = block_on(mm_eve.rpc(json!({
"userpass": mm_eve.userpass,
"method": "update_maker_order",
"uuid": eve_order.result.uuid,
"new_price": "1.1",
})))
.unwrap();
assert!(rc.0.is_success(), "!setprice: {}", rc.1);
thread::sleep(Duration::from_secs(1));
}

for _ in 0..5 {
let rc = block_on(mm_eve.rpc(json!({
"userpass": mm_eve.userpass,
"method": "update_maker_order",
"uuid": eve_order.result.uuid,
"new_price": "1.2",
})))
.unwrap();
assert!(rc.0.is_success(), "!setprice: {}", rc.1);
thread::sleep(Duration::from_secs(1));
}

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "best_orders",
"coin": "RICK",
"action": "buy",
"volume": "500",
})))
.unwrap();

assert!(rc.0.is_success(), "!best_orders: {}", rc.1);
let response: BestOrdersResponse = json::from_str(&rc.1).unwrap();
let best_morty_orders = response.result.get("MORTY").unwrap();
assert_eq!(1, best_morty_orders.len());
let expected_price: BigDecimal = "1.2".parse().unwrap();
assert_eq!(expected_price, best_morty_orders[0].price);

block_on(mm_bob.stop()).unwrap();
block_on(mm_alice.stop()).unwrap();
block_on(mm_eve.stop()).unwrap();
}

#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_best_orders_filter_response() {
Expand Down