Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion .github/workflows/fmt-and-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

- name: Install toolchain
run: |
rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal --component rustfmt clippy
rustup toolchain install nightly-2023-06-01 --no-self-update --profile=minimal --component rustfmt,clippy
rustup default nightly-2023-06-01

- name: Install build deps
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4997,7 +4997,7 @@ pub async fn delegations_info(ctx: MmArc, req: DelegationsInfo) -> Result<Json,
DelegationsInfoDetails::Qtum => {
let MmCoinEnum::QtumCoin(qtum) = coin else {
return MmError::err(StakingInfoError::InvalidPayload {
reason: format!("{} is not a Qtum coin", req.coin)
reason: format!("{} is not a Qtum coin", req.coin),
});
};

Expand Down Expand Up @@ -5048,7 +5048,7 @@ pub async fn claim_staking_rewards(ctx: MmArc, req: ClaimStakingRewardsRequest)

let MmCoinEnum::Tendermint(tendermint) = coin else {
return MmError::err(DelegationError::InvalidPayload {
reason: format!("{} is not a Cosmos coin", req.coin)
reason: format!("{} is not a Cosmos coin", req.coin),
});
};

Expand Down
17 changes: 17 additions & 0 deletions mm2src/mm2_bin_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,20 @@ async fn finalize_mm2_stop(ctx: MmArc) {
dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await;
let _ = ctx.stop().await;
}

#[cfg_attr(target_arch = "wasm32", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Primitive)]
pub enum StartupResultCode {
/// Operation completed successfully
Ok = 0,
/// Invalid parameters were provided to the function
InvalidParams = 1,
/// The configuration was invalid (missing required fields, etc.)
ConfigError = 2,
/// MM2 is already running
AlreadyRunning = 3,
/// MM2 initialization failed
InitError = 4,
/// Failed to spawn the MM2 process/thread
SpawnError = 5,
}
93 changes: 60 additions & 33 deletions mm2src/mm2_bin_lib/src/mm2_native_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use enum_primitive_derive::Primitive;
use gstuff::any_to_str;
use libc::c_char;
use mm2_core::mm_ctx::MmArc;
use mm2_main::LpMainParams;
use num_traits::FromPrimitive;
use serde_json::{self as json};
use std::ffi::{CStr, CString};
Expand All @@ -15,15 +16,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;

#[derive(Debug, PartialEq, Primitive)]
enum MainErr {
Ok = 0,
AlreadyRuns = 1,
ConfIsNull = 2,
ConfNotUtf8 = 3,
CantThread = 5,
}

/// Starts the MM2 in a detached singleton thread.
#[no_mangle]
#[allow(clippy::missing_safety_doc)]
Expand All @@ -48,40 +40,75 @@ pub unsafe extern "C" fn mm2_main(conf: *const c_char, log_cb: extern "C" fn(lin
}};
}

if LP_MAIN_RUNNING.load(Ordering::Relaxed) {
eret!(MainErr::AlreadyRuns)
}
CTX.store(0, Ordering::Relaxed); // Remove the old context ID during restarts.

if conf.is_null() {
eret!(MainErr::ConfIsNull)
eret!(StartupResultCode::InvalidParams, "Configuration is null")
}
let conf = CStr::from_ptr(conf);
let conf = match conf.to_str() {
let conf_cstr = CStr::from_ptr(conf);
let conf_str = match conf_cstr.to_str() {
Ok(s) => s,
Err(e) => eret!(MainErr::ConfNotUtf8, e),
Err(e) => eret!(
StartupResultCode::InvalidParams,
format!("Configuration is not valid UTF-8: {}", e)
),
};

let conf: json::Value = match json::from_str(conf_str) {
Ok(v) => v,
Err(e) => eret!(
StartupResultCode::ConfigError,
format!("Failed to parse configuration: {}", e)
),
};
let conf = conf.to_owned();

if LP_MAIN_RUNNING.load(Ordering::Relaxed) {
eret!(StartupResultCode::AlreadyRunning, "MM2 is already running");
}

CTX.store(0, Ordering::Relaxed); // Remove the old context ID during restarts.
Comment thread
shamardy marked this conversation as resolved.
Outdated

register_callback(FfiCallback::with_ffi_function(log_cb));

let rc = thread::Builder::new().name("lp_main".into()).spawn(move || {
if let Err(true) = LP_MAIN_RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
log!("lp_main already started!");
return;
}

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.

shouldn't we propagate this as an error back to the caller? do we?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done f61bac7


let ctx_cb = &|ctx| CTX.store(ctx, Ordering::Relaxed);
match catch_unwind(move || mm2_main::run_lp_main(Some(&conf), ctx_cb, KDF_VERSION.into(), KDF_DATETIME.into()))
{
Ok(Ok(_)) => log!("run_lp_main finished"),
Ok(Err(err)) => log!("run_lp_main error: {}", err),
Err(err) => log!("run_lp_main panic: {:?}", any_to_str(&*err)),

match catch_unwind(move || {
let params = LpMainParams::with_conf(conf).log_filter(None);

match block_on(mm2_main::lp_main(
params,
&ctx_cb,
KDF_VERSION.into(),
KDF_DATETIME.into(),
)) {

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.

also errors from lp_main call

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done f61bac7
I propagated them all as init errors for now, we might need new variants for lp_main errors later to propagate back additional better error codes.

Ok(ctx) => {
if let Err(e) = block_on(mm2_main::lp_run(ctx)) {
log!("MM2 runtime error: {}", e);
}
},
Err(e) => log!("MM2 initialization failed: {}", e),
}
}) {
Ok(_) => log!("MM2 thread completed normally"),
Err(err) => log!("MM2 thread panicked: {:?}", any_to_str(&*err)),
};

LP_MAIN_RUNNING.store(false, Ordering::Relaxed)
});

if let Err(e) = rc {
eret!(MainErr::CantThread, e)
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
eret!(
StartupResultCode::SpawnError,
format!("Failed to spawn MM2 thread: {:?}", e)
);
}
MainErr::Ok as i8

StartupResultCode::Ok as i8
}

/// Checks if the MM2 singleton thread is currently running or not.
Expand Down Expand Up @@ -109,7 +136,7 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char
static RUNNING: AtomicBool = AtomicBool::new(false);
if let Err(true) = RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
log!("mm2_test] Running already!");
return -1;
return StartupResultCode::AlreadyRunning as i32;
}

// #402: Stop the MM in order to test the library restart.
Expand All @@ -120,7 +147,7 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char
Ok(ctx) => ctx,
Err(err) => {
log!("mm2_test] Invalid CTX? !from_ffi_handle: {}", err);
return -1;
return StartupResultCode::InvalidParams as i32;
},
};
let conf = json::to_string(&ctx.conf).unwrap();
Expand Down Expand Up @@ -177,10 +204,10 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char
log!("mm2_test] Restarting MM…");
let conf = CString::new(&conf[..]).unwrap();
let rc = unsafe { mm2_main(conf.as_ptr(), log_cb) };
let rc = MainErr::from_i8(rc).unwrap();
if rc != MainErr::Ok {
let rc = StartupResultCode::from_i8(rc).unwrap();
if rc != StartupResultCode::Ok {
log!("!mm2_main: {:?}", rc);
return -1;
return rc as i32;
}

// Wait for the new MM instance to allocate context.
Expand All @@ -192,14 +219,14 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char
}
if now_float() - since > 60.0 {
log!("mm2_test] Won't start");
return -1;
return StartupResultCode::InitError as i32;
}
}

let ctx_id = CTX.load(Ordering::Relaxed);
if ctx_id == prev_ctx_id {
log!("mm2_test] Context ID is the same");
return -1;
return StartupResultCode::InvalidParams as i32;
}
log!("mm2_test] New MM instance {} started", ctx_id);
}
Expand Down
109 changes: 67 additions & 42 deletions mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//! Some specifics of using the [`wasm_bindgen`] library:
//!
//! # Currently only `Result<T, JsValue>` is allowed
//! [tracking issue]: https://github.com/rustwasm/wasm-bindgen/issues/1004
//!
//! # JavaScript enums do not support methods at all
//! [tracking issue]: https://github.com/rustwasm/wasm-bindgen/issues/1715
//!
Expand All @@ -21,18 +18,27 @@ use mm2_rpc::data::legacy::MmVersionResponse;
use mm2_rpc::wasm_rpc::WasmRpcResponse;
use serde::{Deserialize, Serialize};
use serde_json::Value as Json;

/// The errors can be thrown when using the `mm2_main` function incorrectly.
#[wasm_bindgen]
#[derive(Primitive)]
pub enum Mm2MainErr {
AlreadyRuns = 1,
InvalidParams = 2,
NoCoinsInConf = 3,
#[derive(Debug, Clone, Serialize)]
struct StartupError {
code: StartupResultCode,
message: String,
}

impl From<Mm2MainErr> for JsValue {
fn from(e: Mm2MainErr) -> Self { JsValue::from(e as i32) }
#[wasm_bindgen]
impl StartupError {
fn new(code: StartupResultCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
}
}

#[wasm_bindgen(getter)]
pub fn code(&self) -> i8 { self.code as i8 }

#[wasm_bindgen(getter)]
pub fn message(&self) -> String { self.message.clone() }
}

#[derive(Deserialize)]
Expand All @@ -58,7 +64,7 @@ impl From<MainParams> for LpMainParams {
/// # Usage
///
/// ```javascript
/// import init, {mm2_main, LogLevel, Mm2MainErr} from "./path/to/mm2.js";
/// import init, {mm2_main, LogLevel, StartupResultCode} from "./path/to/mm2.js";
///
/// const params = {
/// conf: { "gui":"WASMTEST", mm2:1, "passphrase":"YOUR_PASSPHRASE_HERE", "rpc_password":"test123", "coins":[{"coin":"ETH","protocol":{"type":"ETH"}}] },
Expand All @@ -68,8 +74,8 @@ impl From<MainParams> for LpMainParams {
/// try {
/// mm2_main(params, handle_log);
/// } catch (e) {
/// switch (e) {
/// case Mm2MainErr.AlreadyRuns:
/// switch (e.code) {
/// case StartupResultCode.AlreadyRunning:
/// alert("MarketMaker2 already runs...");
/// break;
/// // handle other errors...
Expand All @@ -80,51 +86,70 @@ impl From<MainParams> for LpMainParams {
/// }
/// ```
#[wasm_bindgen]
pub fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<(), JsValue> {
pub async fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<i8, JsValue> {
let params: MainParams = match deserialize_from_js(params.clone()) {
Ok(p) => p,
Err(e) => {
console_err!("Expected 'MainParams' as the first argument, found {:?}: {}", params, e);
return Err(Mm2MainErr::InvalidParams.into());
let error = StartupError::new(
StartupResultCode::InvalidParams,
format!("Expected 'MainParams' as the first argument, found {:?}: {}", params, e),
);
console_err!("{}", error.message());
return Err(error.into());
},
};
if params.conf["coins"].is_null() {
console_err!("Config must contain 'coins' field: {:?}", params.conf);
return Err(Mm2MainErr::NoCoinsInConf.into());
let error = StartupError::new(
StartupResultCode::ConfigError,
format!("Config must contain 'coins' field: {:?}", params.conf),
);
console_err!("{}", error.message());
return Err(error.into());
}
let params = LpMainParams::from(params);

if LP_MAIN_RUNNING.load(Ordering::Relaxed) {
return Err(Mm2MainErr::AlreadyRuns.into());
let error = StartupError::new(StartupResultCode::AlreadyRunning, "MM2 is already running");
console_err!("{}", error.message());
return Err(error.into());
}
CTX.store(0, Ordering::Relaxed); // Remove the old context ID during restarts.

register_callback(WasmCallback::with_js_function(log_cb));
// Setting up a global panic hook to log panic information to the console
// This doesn't prevent termination of the WebAssembly instance, but ensures error details are visible
// We can't use catch_unwind directly with async/await in WebAssembly, so this is our best option for diagnostics
// If a panic occurs, the MM2 instance will terminate but the browser tab will remain responsive
set_panic_hook();

let fut = async move {
if let Err(true) = LP_MAIN_RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
console_err!("lp_main already started!");
return;
}
let ctx_cb = |ctx| CTX.store(ctx, Ordering::Relaxed);
// TODO figure out how to use catch_unwind here
// use futures::FutureExt;
// match mm2_main::lp_main(params, &ctx_cb).catch_unwind().await {
// Ok(Ok(_)) => console_info!("run_lp_main finished"),
// Ok(Err(err)) => console_err!("run_lp_main error: {}", err),
// Err(err) => console_err!("run_lp_main panic: {:?}", any_to_str(&*err)),
// };
match mm2_main::lp_main(params, &ctx_cb, KDF_VERSION.into(), KDF_DATETIME.into()).await {
Ok(()) => console_info!("run_lp_main finished"),
Err(err) => console_err!("run_lp_main error: {}", err),
};
LP_MAIN_RUNNING.store(false, Ordering::Relaxed)
if let Err(true) = LP_MAIN_RUNNING.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) {
let error = StartupError::new(StartupResultCode::AlreadyRunning, "lp_main already started!");
console_err!("{}", error.message());
return Err(error.into());
}

let ctx_cb = |ctx| CTX.store(ctx, Ordering::Relaxed);
let ctx = match mm2_main::lp_main(params, &ctx_cb, KDF_VERSION.into(), KDF_DATETIME.into()).await {
Ok(ctx) => {
console_info!("lp_main finished");
ctx
},
Err(err) => {
let error = StartupError::new(StartupResultCode::InitError, format!("run_lp_main error: {}", err));
console_err!("{}", error.message());
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
return Err(error.into());
},
Comment thread
borngraced marked this conversation as resolved.
};

// At this moment we still don't have `MmCtx` context to use its `MmCtx::abortable_system` spawner.
executor::spawn_local(fut);
Ok(())
executor::spawn_local(async move {
if let Err(e) = mm2_main::lp_run(ctx).await {
console_err!("MM2 runtime error: {}", e);
}
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
});

Ok(StartupResultCode::Ok as i8)
}

/// Returns the MarketMaker2 instance status.
Expand Down
Loading