Skip to content

Commit

Permalink
Mm2.1 wasm light wallet (#881)
Browse files Browse the repository at this point in the history
* Refactor the common::executor
* Use `wasm_bindgen_futures::spawn_local` instead of custom runtime implementation
* Use `js_sys::Date::now` instead of defined in js `date_now`
* Use 'setTimeout' within Timer

* Prepare mm2_lib for wasm32
* Separate mm2_lib into mm2_wasm_lib and mm2_native_lib
* Separate common::lib into common::{native_log, wasm_log}
* Add wasm_build suite

* Mm2 wasm light wallet is ready
* Add the spawn_rpc wasm-only version

* Minor and major changes
* Use 'format_record' within MmLogAppender instead of built-in log4rs::encode::Encode
* Change the 'common::for_tests::require_log_level' argument type
* Document the mm2_main, mm2_rpc

* Pull the mm2_lib.rs changes from the mm2.1-cross
* Also fix the build.rs warning

* Fix web3_transport
* Remove hardcoded uri
* Handle errors instead of panic

* Leave a TODO comment for the next iteration
  • Loading branch information
sergeyboyko0791 authored Apr 6, 2021
1 parent 181a226 commit 5933e5a
Show file tree
Hide file tree
Showing 30 changed files with 1,577 additions and 754 deletions.
26 changes: 21 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ path = "mm2src/docker_tests.rs"
[lib]
name = "mm2"
path = "mm2src/mm2_lib.rs"
crate-type = ["staticlib"]
crate-type = ["cdylib", "staticlib"]
test = false
doctest = false
bench = false
Expand Down
18 changes: 13 additions & 5 deletions js/defined-in-js.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
//! The wasm plug functions.

// pub fn host_ensure_dir_is_writable(ptr: *const c_char, len: i32) -> i32;
export function host_ensure_dir_is_writable(ptr, len) {
}

// fn host_electrum_connect(ptr: *const c_char, len: i32) -> i32;
export function host_electrum_connect(ptr, len) {
return 0;
}

// fn host_electrum_is_connected(ri: i32) -> i32;
export function host_electrum_is_connected(ri) {
return 0;
}

// fn host_electrum_request(ri: i32, ptr: *const c_char, len: i32) -> i32;
export function host_electrum_request(ri, ptr, len) {
return 0;
}

// fn host_electrum_reply(ri: i32, id: i32, rbuf: *mut c_char, rcap: i32) -> i32;
export function host_electrum_reply(ri, id, rbuf, rcap) {
return 0;
}

// pub fn host_env(name: *const c_char, nameˡ: i32, rbuf: *mut c_char, rcap: i32) -> i32;
export function host_env(name, name2, rbuf, rcap) {
return 0;
}

// pub fn date_now() -> f64;
export function date_now() {
return 0;
}

// pub fn host_slurp(path_p: *const c_char, path_l: i32, rbuf: *mut c_char, rcap: i32) -> i32;
export function host_slurp(path_p, path_l, rbuf, rcap) {
return 0;
Expand All @@ -46,20 +49,25 @@ export function host_write(path_p, path_l, ptr, len) {
return 0;
}

// pub fn host_read_dir(path_p: *const c_char, path_l: i32, rbuf: *mut c_char, rcap: i32) -> i32;
export function host_read_dir(path_p, path_l, rbuf, rcap) {
return 0;
}

// fn http_helper_if(helper: *const u8, helper_len: i32, payload: *const u8, payload_len: i32, timeout_ms: i32) -> i32;
export function http_helper_if(helper, helper_len, payload, payload_len, timeout_ms) {
}

// pub fn http_helper_check(helper_request_id: i32, rbuf: *mut u8, rcap: i32) -> i32;
export function http_helper_check(helper_request_id, rbuf, rcap) {
return 0;
}

// pub fn call_back(cb_id: i32, ptr: *const c_char, len: i32);
export function call_back(cb_id, ptr, len) {
}

// fn sleep(ms: u32) -> Promise;
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
32 changes: 2 additions & 30 deletions mm2src/coins/eth/eth_wasm_tests.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
use super::*;
use crate::lp_coininit;
use common::mm_ctx::MmCtxBuilder;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
use web_sys::console;

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
fn pass() {
let ctx = MmCtxBuilder::default().into_mm_arc();
let _coins_context = CoinsContext::from_ctx(&ctx).unwrap();
assert_eq!(1, 1);
}

#[wasm_bindgen]
extern "C" {
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
fn cancelInterval(token: f64);
}

wasm_bindgen_test_configure!(run_in_browser);

pub struct Interval {
closure: Closure<dyn FnMut()>,
}

impl Interval {
fn new() -> Interval {
let closure = Closure::new(common::executor::run);
Interval { closure }
}
}

unsafe impl Send for Interval {}

unsafe impl Sync for Interval {}

lazy_static! {
static ref EXECUTOR_INTERVAL: Interval = Interval::new();
}

#[wasm_bindgen_test]
async fn test_send() {
setInterval(&EXECUTOR_INTERVAL.closure, 200);
let key_pair = KeyPair::from_secret_slice(
&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(),
)
Expand Down Expand Up @@ -86,7 +59,6 @@ async fn test_send() {
async fn test_init_eth_coin() {
use common::privkey::key_pair_from_seed;

setInterval(&EXECUTOR_INTERVAL.closure, 200);
let key_pair =
key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid").unwrap();
let conf = json!({
Expand Down
133 changes: 93 additions & 40 deletions mm2src/coins/eth/web3_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use futures::TryFutureExt;
use futures01::{Future, Poll};
use jsonrpc_core::{Call, Response};
use serde_json::Value as Json;
use std::ops::Deref;
#[cfg(not(target_arch = "wasm32"))] use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use web3::error::{Error, ErrorKind};
Expand All @@ -13,6 +13,7 @@ use web3::{RequestId, Transport};

/// Parse bytes RPC response into `Result`.
/// Implementation copied from Web3 HTTP transport
#[cfg(not(target_arch = "wasm32"))]
fn single_response<T: Deref<Target = [u8]>>(response: T) -> Result<Json, Error> {
let response =
serde_json::from_slice(&*response).map_err(|e| Error::from(ErrorKind::InvalidResponse(format!("{}", e))))?;
Expand Down Expand Up @@ -86,56 +87,21 @@ impl Transport for Web3Transport {
#[cfg(not(target_arch = "wasm32"))]
fn send(&self, _id: RequestId, request: Call) -> Self::Out {
Box::new(
sendʹ(request, self.uris.clone(), self.event_handlers.clone())
send_request(request, self.uris.clone(), self.event_handlers.clone())
.boxed()
.compat(),
)
}

#[cfg(target_arch = "wasm32")]
fn send(&self, _id: RequestId, request: Call) -> Self::Out {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response as JsResponse};

let body = to_string(&request);
self.event_handlers.on_outgoing_request(body.as_bytes());

let mut opts = RequestInit::new();
opts.method("POST");
opts.mode(RequestMode::Cors);
opts.body(Some(&JsValue::from_str(&body)));

let request = Request::new_with_str_and_init("http://195.201.0.6:8565", &opts).unwrap();

request.headers().set("Accept", "application/json").unwrap();

request.headers().set("Content-Type", "application/json").unwrap();

let window = web_sys::window().unwrap();
let request_promise = window.fetch_with_request(&request);

let future = JsFuture::from(request_promise);
let event_handlers = self.event_handlers.clone();
let res = async move {
let resp_value = future.await.unwrap();
assert!(resp_value.is_instance_of::<JsResponse>());
let resp: JsResponse = resp_value.dyn_into().unwrap();
let json_value = JsFuture::from(resp.json().unwrap()).await.unwrap();
let response: Json = json_value.into_serde().unwrap();

let response = serde_json::to_vec(&response).unwrap();
event_handlers.on_incoming_response(&response);

single_response(response)
};
Box::new(SendFuture(Box::pin(res).compat()))
let fut = send_request(request, self.uris.clone(), self.event_handlers.clone());
Box::new(SendFuture(Box::pin(fut).compat()))
}
}

#[cfg(not(target_arch = "wasm32"))]
async fn sendʹ(
async fn send_request(
request: Call,
uris: Vec<http::Uri>,
event_handlers: Vec<RpcTransportEventHandlerShared>,
Expand Down Expand Up @@ -190,3 +156,90 @@ async fn sendʹ(
))
.into())
}

#[cfg(target_arch = "wasm32")]
async fn send_request(
request: Call,
uris: Vec<http::Uri>,
event_handlers: Vec<RpcTransportEventHandlerShared>,
) -> Result<Json, Error> {
let request_payload = to_string(&request);

let mut transport_errors = Vec::new();
for uri in uris {
match send_request_once(&request_payload, &uri, &event_handlers).await {
Ok(response_json) => return Ok(response_json),
Err(Error(ErrorKind::Transport(e), _)) => {
transport_errors.push(e.to_string());
},
Err(e) => return Err(e),
}
}

Err(ErrorKind::Transport(fomat!(
"request " [request] " failed: "
for err in transport_errors {(err)} sep {"; "}
))
.into())
}

#[cfg(target_arch = "wasm32")]
async fn send_request_once(
request_payload: &String,
uri: &http::Uri,
event_handlers: &Vec<RpcTransportEventHandlerShared>,
) -> Result<Json, Error> {
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response as JsResponse};

macro_rules! try_or {
($exp:expr, $errkind:ident) => {
match $exp {
Ok(x) => x,
Err(e) => return Err(Error::from(ErrorKind::$errkind(ERRL!("{:?}", e)))),
}
};
}

let window = web_sys::window().expect("!window");

// account for outgoing traffic
event_handlers.on_outgoing_request(request_payload.as_bytes());

let mut opts = RequestInit::new();
opts.method("POST");
opts.mode(RequestMode::Cors);
opts.body(Some(&JsValue::from_str(&request_payload)));

let request = try_or!(Request::new_with_str_and_init(&uri.to_string(), &opts), Transport);

request.headers().set("Accept", "application/json").unwrap();
request.headers().set("Content-Type", "application/json").unwrap();

let request_promise = window.fetch_with_request(&request);

let future = JsFuture::from(request_promise);
let resp_value = try_or!(future.await, Transport);
let js_response: JsResponse = try_or!(resp_value.dyn_into(), Transport);

let resp_txt_fut = try_or!(js_response.text(), Transport);
let resp_txt = try_or!(JsFuture::from(resp_txt_fut).await, Transport);

let resp_str = resp_txt.as_string().ok_or_else(|| {
Error::from(ErrorKind::Transport(ERRL!(
"Expected a UTF-8 string JSON, found {:?}",
resp_txt
)))
})?;
event_handlers.on_incoming_response(resp_str.as_bytes());

let response: Response = try_or!(serde_json::from_str(&resp_str), InvalidResponse);
match response {
Response::Single(output) => to_result_from_output(output),
Response::Batch(_) => Err(Error::from(ErrorKind::InvalidResponse(
"Expected single, got batch.".to_owned(),
))),
}
}
4 changes: 2 additions & 2 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,8 +1054,8 @@ pub fn my_tx_history(ctx: MmArc, req: Json) -> HyRes {
// Should remove `block_on` when my_tx_history is async.
let coin = match block_on(lp_coinfind(&ctx, &request.coin)) {
Ok(Some(t)) => t,
Ok(None) => return rpc_err_response(500, &fomat!("No such coin: "(request.coin))),
Err(err) => return rpc_err_response(500, &fomat!("!lp_coinfind(" (request.coin) "): " (err))),
Ok(None) => return rpc_err_response(500, &format!("No such coin: {}", request.coin)),
Err(err) => return rpc_err_response(500, &format!("!lp_coinfind({}): {}", request.coin, err)),
};
let file_path = coin.tx_history_path(&ctx);
let content = slurp(&file_path);
Expand Down
Loading

0 comments on commit 5933e5a

Please sign in to comment.