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
22 changes: 22 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,18 @@ pub struct WithdrawRequest {
fee: Option<WithdrawFee>,
}

impl WithdrawRequest {
pub fn new_max(coin: String, to: String) -> WithdrawRequest {
WithdrawRequest {
coin,
to,
amount: 0.into(),
max: true,
fee: None,
}
}
}

/// Please note that no type should have the same structure as another type,
/// because this enum has the `untagged` deserialization.
#[derive(Clone, Debug, PartialEq, Serialize)]
Expand Down Expand Up @@ -1233,6 +1245,16 @@ pub async fn lp_coinfind(ctx: &MmArc, ticker: &str) -> Result<Option<MmCoinEnum>
Ok(coins.get(ticker).cloned())
}

/// Attempts to find a pair of active coins returning None if one is not enabled
pub async fn find_pair(ctx: &MmArc, base: &str, rel: &str) -> Result<Option<(MmCoinEnum, MmCoinEnum)>, String> {
let fut_base = lp_coinfind(ctx, base);
let fut_rel = lp_coinfind(ctx, rel);

futures::future::try_join(fut_base, fut_rel)
.map_ok(|(base, rel)| base.zip(rel))
.await
}

#[derive(Display)]
pub enum CoinFindError {
#[display(fmt = "No such coin: {}", coin)]
Expand Down
95 changes: 94 additions & 1 deletion mm2src/docker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ mod docker_tests {
use coins::utxo::rpc_clients::{UnspentInfo, UtxoRpcClientEnum, UtxoRpcClientOps};
use coins::utxo::utxo_standard::{utxo_standard_coin_from_conf_and_request, UtxoStandardCoin};
use coins::utxo::{coin_daemon_data_dir, dhash160, zcash_params_path, UtxoCoinFields, UtxoCommonOps};
use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SwapOps, TransactionEnum};
use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SwapOps, TransactionEnum, WithdrawRequest};
use common::for_tests::enable_electrum;
use common::mm_number::MmNumber;
use common::{block_on, now_ms};
Expand Down Expand Up @@ -2649,6 +2649,99 @@ mod docker_tests {
assert_eq!(asks.len(), 1, "Bob MYCOIN/MYCOIN1 orderbook must have exactly 1 asks");
}

#[test]
fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_withdrawn() {
let (_ctx, coin, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into());
let coins = json! ([
{"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}},
{"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}},
]);
let mut bob_conf = json! ({
"gui": "nogui",
"netid": 9000,
"dht": "on", // Enable DHT without delay.
"passphrase": format!("0x{}", hex::encode(bob_priv_key)),
"coins": coins,
"rpc_password": "pass",
"i_am_seed": true,
});
let mm_bob = MarketMakerIt::start(bob_conf.clone(), "pass".to_string(), None).unwrap();
let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path);

log!([block_on(enable_native(&mm_bob, "MYCOIN", &[]))]);
log!([block_on(enable_native(&mm_bob, "MYCOIN1", &[]))]);
let rc = block_on(mm_bob.rpc(json! ({
"userpass": mm_bob.userpass,
"method": "setprice",
"base": "MYCOIN",
"rel": "MYCOIN1",
"price": 1,
"max": true,
})))
.unwrap();
assert!(rc.0.is_success(), "!setprice: {}", rc.1);
let res: SetPriceResponse = json::from_str(&rc.1).unwrap();
let uuid = res.result.uuid;

// mm_bob using same DB dir that should kick start the order
bob_conf["dbdir"] = mm_bob.folder.join("DB").to_str().unwrap().into();
bob_conf["log"] = mm_bob.folder.join("mm2_dup.log").to_str().unwrap().into();
block_on(mm_bob.stop()).unwrap();

let withdraw = coin
.withdraw(WithdrawRequest::new_max(
"MYCOIN".to_string(),
"RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw".to_string(),
))
.wait()
.unwrap();
coin.send_raw_tx(&hex::encode(&withdraw.tx_hex.0)).wait().unwrap();
coin.wait_for_confirmations(&withdraw.tx_hex.0, 1, false, (now_ms() / 1000) + 10, 1)
.wait()
.unwrap();

let mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap();
let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path);
log!([block_on(enable_native(&mm_bob_dup, "MYCOIN", &[]))]);
log!([block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[]))]);

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

log!("Get RICK/MORTY orderbook on Bob side");
let rc = block_on(mm_bob_dup.rpc(json! ({
"userpass": mm_bob_dup.userpass,
"method": "orderbook",
"base": "MYCOIN",
"rel": "MYCOIN1",
})))
.unwrap();
assert!(rc.0.is_success(), "!orderbook: {}", rc.1);

let bob_orderbook: Json = json::from_str(&rc.1).unwrap();
log!("Bob orderbook "[bob_orderbook]);
let asks = bob_orderbook["asks"].as_array().unwrap();
assert!(asks.is_empty(), "Bob MYCOIN/MYCOIN1 orderbook must not have asks");

let rc = block_on(mm_bob_dup.rpc(json! ({
"userpass": mm_bob_dup.userpass,
"method": "my_orders",
})))
.unwrap();
assert!(rc.0.is_success(), "!my_orders: {}", rc.1);

let res: MyOrdersRpcResult = json::from_str(&rc.1).unwrap();
assert!(res.result.maker_orders.is_empty(), "Bob maker orders must be empty");

let order_path = mm_bob.folder.join(format!(
"DB/{}/ORDERS/MY/MAKER/{}.json",
hex::encode(rmd160_from_priv(bob_priv_key).take()),
uuid
));

println!("Order path {}", order_path.display());
assert!(!order_path.exists());
}

#[test]
fn test_maker_order_kick_start_should_trigger_subscription_and_match() {
let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into());
Expand Down
62 changes: 47 additions & 15 deletions mm2src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use bigdecimal::BigDecimal;
use blake2::digest::{Update, VariableOutput};
use blake2::VarBlake2b;
use coins::utxo::{compressed_pub_key_from_priv_raw, ChecksumType};
use coins::{lp_coinfind, BalanceTradeFeeUpdatedHandler, FeeApproxStage, MmCoinEnum};
use coins::{find_pair, lp_coinfind, BalanceTradeFeeUpdatedHandler, FeeApproxStage, MmCoinEnum};
use common::executor::{spawn, Timer};
use common::log::error;
use common::mm_ctx::{from_ctx, MmArc, MmWeak};
Expand Down Expand Up @@ -58,10 +58,12 @@ use uuid::Uuid;
use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest};
use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap,
check_other_coin_balance_for_swap, insert_new_swap_to_db, is_pubkey_banned,
lp_atomic_locktime, run_maker_swap, run_taker_swap, AtomicLocktimeVersion, MakerSwap,
RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap};
lp_atomic_locktime, run_maker_swap, run_taker_swap, AtomicLocktimeVersion,
CheckBalanceError, MakerSwap, RunMakerSwapInput, RunTakerSwapInput,
SwapConfirmationsSettings, TakerSwap};

pub use best_orders::best_orders_rpc;
use common::mm_error::MmError;
pub use orderbook_depth::orderbook_depth_rpc;
pub use orderbook_rpc::orderbook_rpc;

Expand Down Expand Up @@ -1616,6 +1618,24 @@ impl MakerOrder {

self.updated_at = Some(now_ms());
}

async fn check_balance(
&self,
ctx: &MmArc,
base: &MmCoinEnum,
rel: &MmCoinEnum,
) -> Result<(), MmError<CheckBalanceError>> {
check_balance_for_maker_swap(
ctx,
base,
rel,
self.available_amount(),
None,
None,
FeeApproxStage::OrderIssue,
)
.await
}
}

impl Into<MakerOrder> for TakerOrder {
Expand Down Expand Up @@ -2531,25 +2551,37 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) {
}

{
let my_maker_orders = ordermatch_ctx.my_maker_orders.lock().await;
let mut my_maker_orders = ordermatch_ctx.my_maker_orders.lock().await;
let mut to_cancel = vec![];

for (uuid, order) in my_maker_orders.iter() {
if !ordermatch_ctx.orderbook.lock().await.order_set.contains_key(uuid) {
if let Ok(Some(_)) = lp_coinfind(&ctx, &order.base).await {
if let Ok(Some(_)) = lp_coinfind(&ctx, &order.rel).await {
let topic = orderbook_topic_from_base_rel(&order.base, &order.rel);
if !ordermatch_ctx.orderbook.lock().await.is_subscribed_to(&topic) {
let request_orderbook = false;
if let Err(e) =
subscribe_to_orderbook_topic(&ctx, &order.base, &order.rel, request_orderbook).await
{
log::error!("Error {} on subscribing to orderbook topic {}", e, topic);
}
if let Ok(Some((base, rel))) = find_pair(&ctx, &order.base, &order.rel).await {
if let Err(e) = order.check_balance(&ctx, &base, &rel).await {
log::info!("Error {} on balance check to kickstart order {}, cancelling", e, uuid);
to_cancel.push(*uuid);
continue;
}

let topic = orderbook_topic_from_base_rel(&order.base, &order.rel);
if !ordermatch_ctx.orderbook.lock().await.is_subscribed_to(&topic) {
let request_orderbook = false;
if let Err(e) =
subscribe_to_orderbook_topic(&ctx, &order.base, &order.rel, request_orderbook).await
{
log::error!("Error {} on subscribing to orderbook topic {}", e, topic);
}
maker_order_created_p2p_notify(ctx.clone(), order).await;
}
maker_order_created_p2p_notify(ctx.clone(), order).await;
}
}
}

for uuid in to_cancel {
if let Some(order) = my_maker_orders.remove(&uuid) {
delete_my_maker_order(&ctx, &order, MakerOrderCancellationReason::InsufficientBalance);
}
}
}

Timer::sleep(0.777).await;
Expand Down
2 changes: 1 addition & 1 deletion mm2src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ use uuid::Uuid;
#[path = "lp_swap/check_balance.rs"] mod check_balance;
#[path = "lp_swap/trade_preimage.rs"] mod trade_preimage;

pub use check_balance::check_other_coin_balance_for_swap;
pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError};
pub use maker_swap::{calc_max_maker_vol, check_balance_for_maker_swap, maker_swap_trade_preimage, run_maker_swap,
stats_maker_swap_dir, MakerSavedSwap, MakerSwap, MakerTradePreimage, RunMakerSwapInput};
use maker_swap::{stats_maker_swap_file_path, MakerSwapEvent};
Expand Down