Skip to content
Permalink
Browse files

Add cancel_order API #398

  • Loading branch information...
artemii235 committed May 28, 2019
1 parent 4c90ac1 commit 413bc82c8ea77ae3af9a90e8001fdf219b2eac12
Showing with 187 additions and 6 deletions.
  1. +56 −5 mm2src/lp_ordermatch.rs
  2. +129 −0 mm2src/mm2_tests.rs
  3. +2 −1 mm2src/rpc.rs
@@ -188,6 +188,7 @@ struct MakerConnected {
struct OrdermatchContext {
pub my_maker_orders: Mutex<HashMap<Uuid, MakerOrder>>,
pub my_taker_orders: Mutex<HashMap<Uuid, TakerOrder>>,
pub cancelled_orders: Mutex<HashMap<Uuid, MakerOrder>>,
}

impl OrdermatchContext {
@@ -197,6 +198,7 @@ impl OrdermatchContext {
Ok (OrdermatchContext {
my_taker_orders: Mutex::new (HashMap::default()),
my_maker_orders: Mutex::new (HashMap::default()),
cancelled_orders: Mutex::new (HashMap::default()),
})
})))
}
@@ -634,12 +636,16 @@ impl PricePingRequest {
sig_str
};

let my_balance = try_s!(base_coin.my_balance().wait());
let available_amount = order.available_amount();
let max_volume = if available_amount <= my_balance {
available_amount
let max_volume = if available_amount > 0.into() {
let my_balance = try_s!(base_coin.my_balance().wait());
if available_amount <= my_balance && available_amount > 0.into() {
available_amount
} else {
my_balance
}
} else {
my_balance
0.into()
};

Ok(PricePingRequest {
@@ -743,6 +749,24 @@ pub fn broadcast_my_maker_orders(ctx: &MmArc) -> Result<(), String> {
continue;
}
}
// the difference of cancelled orders from maker orders that we broadcast the cancel request only once
// cancelled record can be just dropped then
let cancelled_orders: HashMap<_, _> = try_s!(ordermatch_ctx.cancelled_orders.lock()).drain().collect();
for (_, order) in cancelled_orders {
let ping = match PricePingRequest::new(ctx, &order) {
Ok(p) => p,
Err(e) => {
ctx.log.log("", &[&"broadcast_cancelled_orders", &order.base, &order.rel], &format! ("ping request creation failed {}", e));
continue;
},
};

if let Err(e) = lp_send_price_ping(&ping, ctx) {
ctx.log.log("", &[&"broadcast_cancelled_orders", &order.base, &order.rel], &format! ("ping request send failed {}", e));
continue;
}
}

Ok(())
}

@@ -785,7 +809,7 @@ fn match_order_and_request(maker: &MakerOrder, taker: &TakerRequest) -> OrderMat
}
}

#[derive(Deserialize, Serialize)]
#[derive(Deserialize)]
struct OrderStatusReq {
uuid: Uuid,
}
@@ -815,3 +839,30 @@ pub fn order_status(ctx: MmArc, req: Json) -> HyRes {
"error": format!("Order with uuid {} is not found", req.uuid)
}).to_string())
}

#[derive(Deserialize)]
struct CancelOrderReq {
uuid: Uuid,
}

pub fn cancel_order(ctx: MmArc, req: Json) -> HyRes {
let req: CancelOrderReq = try_h!(json::from_value(req));

let ordermatch_ctx = try_h!(OrdermatchContext::from_ctx(&ctx));
let mut maker_orders = try_h!(ordermatch_ctx.my_maker_orders.lock());
match maker_orders.remove(&req.uuid) {
Some(mut order) => {
let mut cancelled_orders = try_h!(ordermatch_ctx.cancelled_orders.lock());
// TODO cancel means setting the volume to 0 as of now, should refactor
order.max_base_vol = 0.into();
cancelled_orders.insert(req.uuid, order);
rpc_response(200, json!({
"result": "success"
}).to_string())
},
// return error if order is not found
None => rpc_err_response(400, &json!({
"error": format!("Order with uuid {} is not found", req.uuid)
}).to_string())
}
}
@@ -1424,3 +1424,132 @@ fn test_multiple_buy_sell_no_delay() {
let vol = bids[0]["maxvolume"].as_f64().unwrap();
assert_eq!(vol, 0.1);
}

/// https://github.com/artemii235/SuperNET/issues/398
#[test]
fn test_cancel_order() {
let coins = json!([
{"coin":"BEER","asset":"BEER","rpcport":8923,"txversion":4},
{"coin":"PIZZA","asset":"PIZZA","rpcport":11608,"txversion":4},
{"coin":"ETOMIC","asset":"ETOMIC","rpcport":10271,"txversion":4},
{"coin":"ETH","name":"ethereum","etomic":"0x0000000000000000000000000000000000000000","rpcport":80},
{"coin":"JST","name":"jst","etomic":"0xc0eb7AeD740E1796992A08962c15661bDEB58003"}
]);

// start bob and immediately place the order
let mut mm_bob = unwrap! (MarketMakerIt::start (
json! ({
"gui": "nogui",
"netid": 9998,
"dht": "on", // Enable DHT without delay.
"myipaddr": env::var ("BOB_TRADE_IP") .ok(),
"rpcip": env::var ("BOB_TRADE_IP") .ok(),
"canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| unwrap! (s.parse::<i64>())),
"passphrase": "bob passphrase",
"coins": coins,
"i_am_seed": true,
}),
"db4be27033b636c6644c356ded97b0ad08914fcb8a1e2a1efc915b833c2cbd19".into(),
match var ("LOCAL_THREAD_MM") {Ok (ref e) if e == "bob" => Some (local_start()), _ => None}
));
let (_bob_dump_log, _bob_dump_dashboard) = mm_dump (&mm_bob.log_path);
log!({"Bob log path: {}", mm_bob.log_path.display()});
unwrap! (mm_bob.wait_for_log (22., &|log| log.contains (">>>>>>>>> DEX stats ")));
// Enable coins on Bob side. Print the replies in case we need the "address".
log! ({"enable_coins (bob): {:?}", enable_coins_eth_electrum (&mm_bob, vec!["http://195.201.0.6:8545"])});
// issue sell request on Bob side by setting base/rel price
log!("Issue bob sell request");
let rc = unwrap! (mm_bob.rpc (json! ({
"userpass": mm_bob.userpass,
"method": "setprice",
"base": "BEER",
"rel": "PIZZA",
"price": 0.9,
"volume": "0.9",
})));
assert! (rc.0.is_success(), "!setprice: {}", rc.1);
let setprice_json: Json = unwrap!(json::from_str(&rc.1));
log!([setprice_json]);

let mut mm_alice = unwrap! (MarketMakerIt::start (
json! ({
"gui": "nogui",
"netid": 9998,
"dht": "on", // Enable DHT without delay.
"myipaddr": env::var ("ALICE_TRADE_IP") .ok(),
"rpcip": env::var ("ALICE_TRADE_IP") .ok(),
"passphrase": "alice passphrase",
"coins": coins,
"seednodes": [fomat!((mm_bob.ip))],
"alice_contract":"0xe1d4236c5774d35dc47dcc2e5e0ccfc463a3289c",
"bob_contract":"0x105aFE60fDC8B5c021092b09E8a042135A4A976E",
"ethnode":"http://195.201.0.6:8545"
}),
"08bfc72a25d860a6399005abc27d1aea35318c137fbaad686ab8d59f5716b473".into(),
match var ("LOCAL_THREAD_MM") {Ok (ref e) if e == "alice" => Some (local_start()), _ => None}
));

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

unwrap! (mm_alice.wait_for_log (22., &|log| log.contains (">>>>>>>>> DEX stats ")));

// Enable coins on Alice side. Print the replies in case we need the "address".
log! ({"enable_coins (alice): {:?}", enable_coins_eth_electrum (&mm_alice, vec!["http://195.201.0.6:8545"])});

// give Alice 20 seconds to recognize the Bob, MM2 nodes broadcast their pubkey data every 20 seconds
thread::sleep(Duration::from_secs(20));

log!("Get BEER/PIZZA orderbook on Alice side");
let rc = unwrap!(mm_alice.rpc (json! ({
"userpass": mm_alice.userpass,
"method": "orderbook",
"base": "BEER",
"rel": "PIZZA",
})));
assert!(rc.0.is_success(), "!orderbook: {}", rc.1);

let alice_orderbook: Json = unwrap!(json::from_str(&rc.1));
log!("Alice orderbook " [alice_orderbook]);
let asks = alice_orderbook["asks"].as_array().unwrap();
assert_eq!(asks.len(), 1, "Alice BEER/PIZZA orderbook must have exactly 1 ask");

let cancel_rc = unwrap! (mm_bob.rpc (json! ({
"userpass": mm_bob.userpass,
"method": "cancel_order",
"uuid": setprice_json["result"]["uuid"],
})));
assert!(cancel_rc.0.is_success(), "!cancel_order: {}", rc.1);

thread::sleep(Duration::from_secs(11));

// Bob orderbook must show no orders
log!("Get BEER/PIZZA orderbook on Bob side");
let rc = unwrap! (mm_bob.rpc (json! ({
"userpass": mm_bob.userpass,
"method": "orderbook",
"base": "BEER",
"rel": "PIZZA",
})));
assert! (rc.0.is_success(), "!orderbook: {}", rc.1);

let bob_orderbook: Json = unwrap!(json::from_str(&rc.1));
log!("Bob orderbook " [bob_orderbook]);
let asks = bob_orderbook["asks"].as_array().unwrap();
assert_eq!(asks.len(), 0, "Bob BEER/PIZZA asks are not empty");

// Alice orderbook must show no orders
log!("Get BEER/PIZZA orderbook on Alice side");
let rc = unwrap! (mm_alice.rpc (json! ({
"userpass": mm_alice.userpass,
"method": "orderbook",
"base": "BEER",
"rel": "PIZZA",
})));
assert! (rc.0.is_success(), "!orderbook: {}", rc.1);

let alice_orderbook: Json = unwrap!(json::from_str(&rc.1));
log!("Alice orderbook " [alice_orderbook]);
let asks = alice_orderbook["asks"].as_array().unwrap();
assert_eq!(asks.len(), 0, "Alice BEER/PIZZA asks are not empty");
}
@@ -38,7 +38,7 @@ use std::sync::atomic::Ordering;
use tokio_core::net::TcpListener;
use hex;

use crate::mm2::lp_ordermatch::{buy, order_status, sell, set_price};
use crate::mm2::lp_ordermatch::{buy, cancel_order, order_status, sell, set_price};
use crate::mm2::lp_swap::{my_swap_status, stats_swap_status, my_recent_swaps};
use crate::mm2::CJSON;

@@ -199,6 +199,7 @@ pub fn dispatcher (req: Json, _remote_addr: Option<SocketAddr>, ctx: MmArc) -> D
DispatcherRes::Match (match &method[..] { // Sorted alphanumerically (on the first latter) for readability.
"autoprice" => lp_autoprice (ctx, req),
"buy" => buy (ctx, req),
"cancel_order" => cancel_order (ctx, req),
// TODO coin initialization performs blocking IO, i.e request.wait(), have to run it on CPUPOOL to avoid blocking shared CORE.
// at least until we refactor the functions like `utxo_coin_from_iguana_info` to async versions.
"enable" => Box::new(CPUPOOL.spawn_fn(move || { enable (ctx, req) })),

0 comments on commit 413bc82

Please sign in to comment.
You can’t perform that action at this time.