Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[r2r] Fix KMD withdraw with Trezor, add mm2_stop WASM FFI #1628

Merged
merged 3 commits into from
Jan 23, 2023
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
17 changes: 16 additions & 1 deletion mm2src/coins/utxo_signer/src/with_trezor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl<TxP: TxProvider + Send + Sync> TrezorTxSigner<TxP> {
outputs,
version: self.params.unsigned_tx.version as u32,
lock_time: self.params.unsigned_tx.lock_time,
expiry: self.expiry_if_required(self.params.unsigned_tx.expiry_height),
version_group_id: self.version_group_id(),
branch_id: self.branch_id(),
})
Expand Down Expand Up @@ -149,6 +150,7 @@ impl<TxP: TxProvider + Send + Sync> TrezorTxSigner<TxP> {
outputs: prev_tx_outputs,
version: prev_utxo.version as u32,
lock_time: prev_utxo.lock_time,
expiry: self.expiry_if_required(prev_utxo.expiry_height),
version_group_id: self.version_group_id(),
branch_id: self.branch_id(),
extra_data: self.extra_data(),
Expand All @@ -173,6 +175,16 @@ impl<TxP: TxProvider + Send + Sync> TrezorTxSigner<TxP> {
}
}

/// `expiry` must be set for Decred and Zcash coins *only*.
/// This fixes : https://github.com/KomodoPlatform/atomicDEX-API/issues/1626
fn expiry_if_required(&self, tx_expiry: u32) -> Option<u32> {
if self.is_overwinter_compatible() {
artemii235 marked this conversation as resolved.
Show resolved Hide resolved
Some(tx_expiry)
} else {
None
}
}

/// Temporary use `0000000000000000000000` extra data for Zcash coins *only*.
/// https://github.com/trezor/connect/issues/610#issuecomment-646022404
fn extra_data(&self) -> Vec<u8> {
Expand All @@ -184,5 +196,8 @@ impl<TxP: TxProvider + Send + Sync> TrezorTxSigner<TxP> {
}

/// https://github.com/trezor/trezor-utxo-lib/blob/trezor/src/transaction.js#L405
fn is_overwinter_compatible(&self) -> bool { self.params.unsigned_tx.version > 3 }
fn is_overwinter_compatible(&self) -> bool { self.is_zcash_type() && self.params.unsigned_tx.version > 3 }

/// https://github.com/trezor/trezor-utxo-lib/blob/trezor/src/coins.js#L55
fn is_zcash_type(&self) -> bool { matches!(self.trezor_coin.as_str(), "Komodo" | "Zcash" | "Zcash Testnet") }
}
58 changes: 57 additions & 1 deletion mm2src/mm2_bin_lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use enum_primitive_derive::Primitive;
use mm2_core::mm_ctx::MmArc;
use mm2_main::mm2::lp_dispatcher::{dispatch_lp_event, StopCtxEvent};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
#[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*;

Expand All @@ -24,7 +26,7 @@ pub enum MainStatus {
}

/// Checks if the MM2 singleton thread is currently running or not.
pub fn mm2_status() -> MainStatus {
fn mm2_status() -> MainStatus {
if !LP_MAIN_RUNNING.load(Ordering::Relaxed) {
return MainStatus::NotRunning;
}
Expand All @@ -45,3 +47,57 @@ pub fn mm2_status() -> MainStatus {
MainStatus::NoRpc
}
}

enum PrepareForStopResult {
CanBeStopped(MmArc),
/// Please note that the status is not always an error.
/// [`StopStatus::Ok`] means that the global state was incorrect (`mm2_run` didn't work, although it should have),
/// and there is no need to stop an mm2 instance manually.
ReadyStopStatus(StopStatus),
}

#[derive(Debug, PartialEq, Primitive)]
pub enum StopStatus {
Ok = 0,
NotRunning = 1,
ErrorStopping = 2,
StoppingAlready = 3,
}

/// Checks if we can stop a MarketMaker2 instance.
fn prepare_for_mm2_stop() -> PrepareForStopResult {
// The log callback might be initialized already, so try to use the common logs.
use common::log::warn;

if !LP_MAIN_RUNNING.load(Ordering::Relaxed) {
return PrepareForStopResult::ReadyStopStatus(StopStatus::NotRunning);
}

let ctx = CTX.load(Ordering::Relaxed);
if ctx == 0 {
warn!("mm2_stop] lp_main is running without ctx");
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
return PrepareForStopResult::ReadyStopStatus(StopStatus::Ok);
}

let ctx = match MmArc::from_ffi_handle(ctx) {
Ok(ctx) => ctx,
Err(_) => {
warn!("mm2_stop] lp_main is still running, although ctx has already been dropped");
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
// There is no need to rewrite the `CTX`, because it will be removed on `mm2_main`.
return PrepareForStopResult::ReadyStopStatus(StopStatus::Ok);
},
};

if ctx.is_stopping() {
return PrepareForStopResult::ReadyStopStatus(StopStatus::StoppingAlready);
}

PrepareForStopResult::CanBeStopped(ctx)
}

async fn finalize_mm2_stop(ctx: MmArc) {
dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await;
let _ = ctx.stop();
}
36 changes: 5 additions & 31 deletions mm2src/mm2_bin_lib/src/mm2_native_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use enum_primitive_derive::Primitive;
use gstuff::any_to_str;
use libc::c_char;
use mm2_core::mm_ctx::MmArc;
use mm2_main::mm2::lp_dispatcher::dispatch_lp_event;
use mm2_main::mm2::lp_dispatcher::StopCtxEvent;
use num_traits::FromPrimitive;
use serde_json::{self as json};
use std::ffi::{CStr, CString};
Expand Down Expand Up @@ -223,38 +221,14 @@ enum StopErr {
/// Stop an MM2 instance or reset the static variables.
#[no_mangle]
pub extern "C" fn mm2_stop() -> i8 {
// The log callback might be initialized already, so try to use the common logs.
use common::log::warn;

if !LP_MAIN_RUNNING.load(Ordering::Relaxed) {
return StopErr::NotRunning as i8;
}

let ctx = CTX.load(Ordering::Relaxed);
if ctx == 0 {
warn!("mm2_stop] lp_main is running without ctx");
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
return StopErr::Ok as i8;
}

let ctx = match MmArc::from_ffi_handle(ctx) {
Ok(ctx) => ctx,
Err(_) => {
warn!("mm2_stop] lp_main is still running, although ctx has already been dropped");
LP_MAIN_RUNNING.store(false, Ordering::Relaxed);
// there is no need to rewrite the `CTX`, because it will be removed on `mm2_main`
return StopErr::Ok as i8;
},
let ctx = match prepare_for_mm2_stop() {
PrepareForStopResult::CanBeStopped(ctx) => ctx,
PrepareForStopResult::ReadyStopStatus(res) => return res as i8,
};

if ctx.is_stopping() {
return StopErr::StoppingAlready as i8;
}

let spawner = ctx.spawner();
spawner.spawn(async move {
dispatch_lp_event(ctx, StopCtxEvent.into()).await;
});
let fut = finalize_mm2_stop(ctx);
spawner.spawn(fut);

StopErr::Ok as i8
}
43 changes: 39 additions & 4 deletions mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl From<MainParams> for LpMainParams {
fn from(orig: MainParams) -> Self { LpMainParams::with_conf(orig.conf).log_filter(Some(orig.log_level)) }
}

/// Run the MarketMaker2.
/// Runs a MarketMaker2 instance.
///
/// # Parameters
///
Expand Down Expand Up @@ -126,7 +126,7 @@ pub fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<(), JsValue
Ok(())
}

/// Get the MarketMaker2 status.
/// Returns the MarketMaker2 instance status.
#[wasm_bindgen]
pub fn mm2_main_status() -> MainStatus { mm2_status() }

Expand Down Expand Up @@ -159,7 +159,7 @@ impl From<Mm2RpcErr> for JsValue {
fn from(e: Mm2RpcErr) -> Self { JsValue::from(e as i32) }
}

/// Invoke an RPC request.
/// Invokes an RPC request.
///
/// # Parameters
///
Expand All @@ -176,7 +176,7 @@ impl From<Mm2RpcErr> for JsValue {
/// "userpass": "test123",
/// "method": "version",
/// };
/// const response = mm2_rpc(payload).await;
/// const response = await mm2_rpc(payload);
/// return response.result;
/// } catch (e) {
/// switch (e) {
Expand Down Expand Up @@ -243,3 +243,38 @@ pub fn mm2_version() -> JsValue {
serialize_to_js(&MmVersionResult::new(MM_VERSION.into(), MM_DATETIME.into()))
.expect("expected serialization to succeed")
}

/// Stops the MarketMaker2 instance.
///
/// # Usage
///
/// ```javascript
/// import init, {mm2_stop} from "./path/to/mm2.js";
///
/// async function stop () {
/// try {
/// await mm2_stop();
/// } catch (e) {
/// switch (e) {
/// case Mm2RpcErr.NotRunning:
/// alert("MarketMaker2 not running yet...");
/// break;
/// // handle other errors...
/// default:
/// alert(`Unexpected error: ${e}`);
/// break;
/// }
/// }
/// }
/// ```
#[wasm_bindgen]
pub async fn mm2_stop() -> Result<(), JsValue> {
let ctx = match prepare_for_mm2_stop() {
PrepareForStopResult::CanBeStopped(ctx) => ctx,
PrepareForStopResult::ReadyStopStatus(StopStatus::Ok) => return Ok(()),
PrepareForStopResult::ReadyStopStatus(err) => return Err(JsValue::from(err as i32)),
};

finalize_mm2_stop(ctx).await;
Ok(())
}
7 changes: 3 additions & 4 deletions mm2src/trezor/src/utxo/prev_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ impl PrevTxOutput {
}

/// Missing fields:
/// * extra_data_len - only for Dash, Zcash
/// * expiry - only for Decred and Zcash
/// * optional uint32 version_group_id = 12; // only for Zcash, nVersionGroupId
/// * timestamp - only for Peercoin
pub struct PrevTx {
/// Transaction inputs.
Expand All @@ -67,6 +64,8 @@ pub struct PrevTx {
pub version: u32,
/// Transaction lock_time.
pub lock_time: u32,
/// Expiry height. Only for Decred and Zcash.
pub expiry: Option<u32>,
/// only for Zcash, nVersionGroupId.
pub version_group_id: Option<u32>,
/// only for Zcash, BRANCH_ID.
Expand All @@ -89,7 +88,7 @@ impl PrevTx {
inputs_count: self.inputs.len() as u32,
outputs_count: self.outputs.len() as u32,
extra_data_len,
expiry: None,
expiry: self.expiry,
version_group_id: self.version_group_id,
timestamp: None,
branch_id: self.branch_id,
Expand Down
5 changes: 3 additions & 2 deletions mm2src/trezor/src/utxo/unsigned_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ impl TxOutput {
}

/// Missing fields:
/// * expiry_height - only for Decred and Zcash
/// * overwintered - deprecated in 2.3.2, the field is not needed as it can be derived from `version`.
/// The main reason why it's ignored is that this can be requested asa extra data:
/// https://docs.trezor.io/trezor-firmware/common/communication/bitcoin-signing.html#extra-data
Expand All @@ -160,6 +159,8 @@ pub struct UnsignedUtxoTx {
pub version: u32,
/// Transaction lock_time.
pub lock_time: u32,
/// Expiry height. Only for Decred and Zcash.
pub expiry: Option<u32>,
/// only for Zcash, nVersionGroupId.
pub version_group_id: Option<u32>,
/// only for Zcash, BRANCH_ID.
Expand All @@ -175,7 +176,7 @@ impl UnsignedUtxoTx {
coin_name: Some(self.coin.clone()),
version: Some(self.version),
lock_time: Some(self.lock_time),
expiry: None,
expiry: self.expiry,
overwintered: None,
version_group_id: self.version_group_id,
timestamp: None,
Expand Down
1 change: 1 addition & 0 deletions wasm_build/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<textarea id="wid_conf_input" cols="100" rows="7">{"gui":"WASMTEST","mm2":1,"passphrase":"wasmtest","allow_weak_password":true,"rpc_password":"testpsw","netid":7777,"coins":[{"coin":"ETH","protocol":{"type":"ETH"}},{"coin":"RICK","overwintered":1,"txversion":4,"protocol":{"type":"UTXO"}},{"coin":"MORTY","overwintered":1,"txversion":4,"protocol":{"type":"UTXO"}}]}</textarea>
<br>
<button id="wid_run_mm2_button" disabled="disabled">Start MM2</button>
<button id="wid_stop_mm2_button" disabled="disabled">Stop MM2</button>
<br>
<br>
<label for="wid_rpc_input">RPC payload JSON</label>
Expand Down
21 changes: 19 additions & 2 deletions wasm_build/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import init, {
mm2_main_status,
mm2_rpc,
mm2_version,
mm2_stop,
LogLevel,
Mm2MainErr,
MainStatus,
Mm2RpcErr
} from "./deps/pkg/mm2.js";
} from "./deps/pkg/mm2lib.js";

const LOG_LEVEL = LogLevel.Debug;

Expand Down Expand Up @@ -104,18 +105,25 @@ function handle_log(level, line) {
function spawn_mm2_status_checking() {
setInterval(function () {
const run_button = document.getElementById("wid_run_mm2_button");
const stop_button = document.getElementById("wid_stop_mm2_button");
const rpc_button = document.getElementById("wid_mm2_rpc_button");

const status = mm2_main_status();
switch (status) {
case MainStatus.NotRunning:
rpc_button.disabled = true;
stop_button.disabled = true;
run_button.disabled = false;
break;
case MainStatus.NoContext:
case MainStatus.NoRpc:
rpc_button.disabled = true;
run_button.disabled = false;
stop_button.disabled = false;
run_button.disabled = true;
break;
case MainStatus.RpcIsUp:
rpc_button.disabled = false;
stop_button.disabled = false;
run_button.disabled = true;
break;
default:
Expand Down Expand Up @@ -146,6 +154,15 @@ init_wasm().then(function () {
await run_mm2(params);
});

const stop_mm2_button = document.getElementById("wid_stop_mm2_button");
stop_mm2_button.addEventListener('click', async () => {
try {
await mm2_stop();
} catch (e) {
alert(`Error on 'mm2_stop': ${e}`);
}
});

const rpc_request_button = document.getElementById("wid_mm2_rpc_button");
rpc_request_button.addEventListener('click', async () => {
const request_payload = document.getElementById("wid_rpc_input").value;
Expand Down